diff mbox

[1/2] iptables: utils: Add bash completion

Message ID 1456412814-4580-2-git-send-email-mart.frauenlob@chello.at
State Not Applicable
Delegated to: Pablo Neira
Headers show

Commit Message

Mart Frauenlob Feb. 25, 2016, 3:06 p.m. UTC
This is a fully featured completion for ip[6]tables.
It knows about all the basic parameters and the options of all
targets and matches. Mandatory and exclusive options and inversion
of options are taken care of. Protocol dependency and previously
set values of options are also taken into account.
Completion of option values is done whereever possible.
Enviroment variables allow to modify completion behaviour.
As to feed ip address from file or to turn on input validation.
A readme file is included.

Signed-off-by: Mart Frauenlob <mart.frauenlob@chello.at>
---
 utils/iptables_bash_completion/README.md |  290 ++++
 utils/iptables_bash_completion/iptables  | 2426 ++++++++++++++++++++++++++++++
 2 files changed, 2716 insertions(+), 0 deletions(-)
 create mode 100644 utils/iptables_bash_completion/README.md
 create mode 100644 utils/iptables_bash_completion/iptables

Comments

Pablo Neira Ayuso March 2, 2016, 11:34 a.m. UTC | #1
On Thu, Feb 25, 2016 at 04:06:53PM +0100, Mart Frauenlob wrote:
> This is a fully featured completion for ip[6]tables.
> It knows about all the basic parameters and the options of all
> targets and matches. Mandatory and exclusive options and inversion
> of options are taken care of. Protocol dependency and previously
> set values of options are also taken into account.
> Completion of option values is done whereever possible.
> Enviroment variables allow to modify completion behaviour.
> As to feed ip address from file or to turn on input validation.
> A readme file is included.
> 
> Signed-off-by: Mart Frauenlob <mart.frauenlob@chello.at>
> ---
>  utils/iptables_bash_completion/README.md |  290 ++++
>  utils/iptables_bash_completion/iptables  | 2426 ++++++++++++++++++++++++++++++

This is fair good amount of work, but this is also quite a bit of
new shell code to be maintained in our trees.

Moreover, I was told that, in the specific case of debian, there is a
package (bash-completion) where you place this.

So sorry, I'm not applying this.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jan Engelhardt March 2, 2016, 11:57 a.m. UTC | #2
On Wednesday 2016-03-02 12:34, Pablo Neira Ayuso wrote:
>On Thu, Feb 25, 2016 at 04:06:53PM +0100, Mart Frauenlob wrote:
>> This is a fully featured completion for ip[6]tables.
>>  utils/iptables_bash_completion/README.md |  290 ++++
>>  utils/iptables_bash_completion/iptables  | 2426 ++++++++++++++++++++++++++++++
>
>This is fair good amount of work, but this is also quite a bit of
>new shell code to be maintained in our trees.

That much is true..

>Moreover, I was told that, in the specific case of debian, there is a
>package (bash-completion) where you place this.

In other environments, bash-completion is more of a dumping ground
for scripts that (1.) do not live with their upstream, _and_, (2.)
also do not live with the distro-level source package for some
reason.

12:42 zap:~ # rpm -qf /etc/bash_completion.d/* /usr/share/bash-completion/* | sort -u
btrfsprogs-4.3.1-10.1.x86_64
grub2-2.02~beta2-20.14.2.x86_64
gstreamer-1.6.3-65.3.x86_64
kmod-21-0.x86_64
libreoffice-5.0.4.2-28.1.x86_64
util-linux-2.25.1-20.1.x86_64
util-linux-systemd-2.25.1-20.1.x86_64
youtube-dl-2016.03.01-101.1.noarch
zypper-1.11.34-1.1.x86_64
[...]

Having the completion script in the iptables releases would easily
ensure it always trickles down to distros in the same fashion, without
distro-level maintainers have to pull it down from $some_random_place.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mart Frauenlob March 2, 2016, 12:24 p.m. UTC | #3
On 02.03.2016 12:34, Pablo Neira Ayuso wrote:
> On Thu, Feb 25, 2016 at 04:06:53PM +0100, Mart Frauenlob wrote:
>> This is a fully featured completion for ip[6]tables.
>> It knows about all the basic parameters and the options of all
>> targets and matches. Mandatory and exclusive options and inversion
>> of options are taken care of. Protocol dependency and previously
>> set values of options are also taken into account.
>> Completion of option values is done whereever possible.
>> Enviroment variables allow to modify completion behaviour.
>> As to feed ip address from file or to turn on input validation.
>> A readme file is included.
>>
>> Signed-off-by: Mart Frauenlob <mart.frauenlob@chello.at>
>> ---
>>   utils/iptables_bash_completion/README.md |  290 ++++
>>   utils/iptables_bash_completion/iptables  | 2426 ++++++++++++++++++++++++++++++
>
> This is fair good amount of work, but this is also quite a bit of
> new shell code to be maintained in our trees.
>
> Moreover, I was told that, in the specific case of debian, there is a
> package (bash-completion) where you place this.
>
> So sorry, I'm not applying this.

Hello Pablo,

what a pitty! :-/
Before writing this patch, I've asked the developers of bash completion 
for inclusion.
For them it's fair to big and specific to maintain.
They were worried about iptables becoming deprecated and them having 
dead code in their package.
The only chance for inclusion, and that's not granted too, is to 
additionally write a test suite with tools I don't have any knowledge 
about yet - and to sign up as maintainer there.
So they recommended me to request inclusion in iptables.

I think this is a good piece of work, probably the best featured bash 
completion ever, and I'd wish to get it distributed.
My abilities in that matter are limited.

Of course I'd keep fixing bugs and add new iptables features (though I 
think there are not many to expect in the future, right?).
If someone submits a bug report, I'll take care of it if anyhow 
possible. Just need to be notified by CC or so.

Thanks for considering.

Best regards,
Mart

--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pablo Neira Ayuso March 2, 2016, 12:54 p.m. UTC | #4
On Wed, Mar 02, 2016 at 01:24:01PM +0100, Mart Frauenlob wrote:
> On 02.03.2016 12:34, Pablo Neira Ayuso wrote:
> >On Thu, Feb 25, 2016 at 04:06:53PM +0100, Mart Frauenlob wrote:
> >>  utils/iptables_bash_completion/README.md |  290 ++++
> >>  utils/iptables_bash_completion/iptables  | 2426 ++++++++++++++++++++++++++++++
> >
> >This is fair good amount of work, but this is also quite a bit of
> >new shell code to be maintained in our trees.
> >
> >Moreover, I was told that, in the specific case of debian, there is a
> >package (bash-completion) where you place this.
> >
> >So sorry, I'm not applying this.
> 
> Hello Pablo,
> 
> what a pitty! :-/
> Before writing this patch, I've asked the developers of bash completion for
> inclusion.
> For them it's fair to big and specific to maintain.
> They were worried about iptables becoming deprecated and them having dead
> code in their package.
> The only chance for inclusion, and that's not granted too, is to
> additionally write a test suite with tools I don't have any knowledge about
> yet - and to sign up as maintainer there.
> So they recommended me to request inclusion in iptables.

I see.

> I think this is a good piece of work, probably the best featured bash
> completion ever, and I'd wish to get it distributed.
> My abilities in that matter are limited.
> 
> Of course I'd keep fixing bugs and add new iptables features (though I think
> there are not many to expect in the future, right?).
> If someone submits a bug report, I'll take care of it if anyhow possible.
> Just need to be notified by CC or so.

Then, my suggestion is to simplify this script in order to reduce the
maintainance burden.

One idea is to push into iptables some infrastructure so the script
can inquire iptables on available options. This would be simple C code
to be places on every extension to print the options. Then, add a tool
like iptables-completion that you can use to inquire what is possible
to get as options. Thus, we get a generic script that inquires
iptables, instead of having them all hardcoded into the script.

Would you explore this? I know Giuseppe Longo and Eric Leblond are
looking into this for nft following a similar approach.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mart Frauenlob March 2, 2016, 7:26 p.m. UTC | #5
On 02.03.2016 13:54, Pablo Neira Ayuso wrote:
> On Wed, Mar 02, 2016 at 01:24:01PM +0100, Mart Frauenlob wrote:
>> On 02.03.2016 12:34, Pablo Neira Ayuso wrote:
>>> On Thu, Feb 25, 2016 at 04:06:53PM +0100, Mart Frauenlob wrote:
>>>>   utils/iptables_bash_completion/README.md |  290 ++++
>>>>   utils/iptables_bash_completion/iptables  | 2426 ++++++++++++++++++++++++++++++
>>>
>>> This is fair good amount of work, but this is also quite a bit of
>>> new shell code to be maintained in our trees.
>>>
>>> Moreover, I was told that, in the specific case of debian, there is a
>>> package (bash-completion) where you place this.
>>>
>>> So sorry, I'm not applying this.

[...]

>> I think this is a good piece of work, probably the best featured bash
>> completion ever, and I'd wish to get it distributed.
>> My abilities in that matter are limited.
>>
>> Of course I'd keep fixing bugs and add new iptables features (though I think
>> there are not many to expect in the future, right?).
>> If someone submits a bug report, I'll take care of it if anyhow possible.
>> Just need to be notified by CC or so.
>
> Then, my suggestion is to simplify this script in order to reduce the
> maintainance burden.

Excuse me I'm unsure what maintenance burden you are expecting.
 From my point of view I don't see it.
Bug reports for this for sure have low priority.
Jozsef has my ipset completion in his tree for long time and afaik only 
one false bug report was sent (he just applied my patch to add it to build).
The script is well structured, it should not be hard to fix.
Adding new extensions or options of them is in most cases just an entry 
in the matches/targets array.
If an option requires input, then one entry at the option request 
section is needed:
elif [[ $prev = --new-option ]]; then return 0

And if completion on the value is possible:
elif [[ $prev = --new-option ]]; then complete_new_option_value ...

It's a little bit more work if an extension is protocol depending, but 
that's most likely just an entry in an extglob i.e. 
(tcp|upd|icmp|new_ext) in the get_match/target_opts() functions.
More complex are overlapping option names (hopefully that will never 
happen again in the future).

I can put more documentation into the source, to make it easier to follow.
As said I'll help on fixing bugs.

> One idea is to push into iptables some infrastructure so the script
> can inquire iptables on available options. This would be simple C code
> to be places on every extension to print the options. Then, add a tool
> like iptables-completion that you can use to inquire what is possible
> to get as options. Thus, we get a generic script that inquires
> iptables, instead of having them all hardcoded into the script.
>
> Would you explore this? I know Giuseppe Longo and Eric Leblond are
> looking into this for nft following a similar approach.

Maybe, see below.
But is it worth it? Correct me if I got the wrong impression.
nft will be/is the successor of iptables and most work in iptables will 
go into iptables-translate.
So why not just take the "classical" approach here and add that fancy 
featured completion and put the focus on the new tools?
I'm willing to help there (mainly on the shell side), if wanted.

Now for the new infrastructure:
Retrieving just the options is a fairly small part of my completion 
code. There are many other things I take into account. I try to list 
them all here (hopefully not missing one).
If any of these information is missing, I think it needs to be stored 
hard-coded.

- list of all extensions available - need to know if match or target
- table in which it is valid
- valid for ipv4 or ipv6 or both
- if extension is only valid for certain protocols
- does the core or extension option allow inversion
- which extension options are mandatory
- which core or extension options are mutually exclusive
- which extension options are only valid for certain protocols
- if out of a selection of extension options one is needed (i.e. recent 
match: --rcheck, --update, --remove)
- if an extension option is (in)valid depending on another of its 
options values (i.e. statistic match with --probability and --mode = random)
- core and extension options aliases (i.e. --destination-port, --dport)
- if it is valid to use a match or match option multiple times

Is all this info stored somewhere within iptables?

Also, the reply from he new iptables-completion tool may still need 
parsing, option and option alias de-duping (if not all will be done in C).
A parameter to show long or short or both form of options (I have an 
option in my completion that lets the user choose) would be needed.

So I think for it to be efficient in reducing shell code, I'd need to 
tell the new tool what information I have retrieved from the iptables 
command line. So it can based on that send back completion information. 
i.e. iptables -t filter -p tcp -m[TAB] - is what I have.
The optimal reply would be: all matches that are present on the system, 
are valid in the filter table and are valid for the tcp protocol.
If ! -p tcp is specified, I'd of course expect a different reply.

For non option completion I don't see much of a chance for code 
reduction. Unless for some exceptions like REJECT --reject-type (here 
the protocol is of interest), or -m icmp --icmp-type, or AUDIT --type 
xyz (in that case completion code is so tiny, an external program call 
is not worth it at all) ... where the extension knows very well about 
the expected values. If that kind of information (based on the 
parameters used) can be sent back, that could be useful.

Retrieving builtin chains and retrieving user defined chains would be 
nice to have. Saves the parsing of the output of iptables -S.
Another thing may be protocols/service names. As iptables knows the 
names, it may be possible to return them for completion, so not to parse 
the files from /etc/{services,protocols} with the shell.

If you think this can be done with affordable effort I'm willing to 
help. But mainly on the shell side, as I'm no C programmer. Maybe 
replicating code to other extensions, if someone does a template, is 
possible for me.

Best regards,

Mart



--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mart Frauenlob March 3, 2016, 2:18 p.m. UTC | #6
On 02.03.2016 13:54, Pablo Neira Ayuso wrote:
> On Wed, Mar 02, 2016 at 01:24:01PM +0100, Mart Frauenlob wrote:
>> On 02.03.2016 12:34, Pablo Neira Ayuso wrote:
>>> On Thu, Feb 25, 2016 at 04:06:53PM +0100, Mart Frauenlob wrote:
[...]
>
> One idea is to push into iptables some infrastructure so the script
> can inquire iptables on available options. This would be simple C code
> to be places on every extension to print the options. Then, add a tool
> like iptables-completion that you can use to inquire what is possible
> to get as options. Thus, we get a generic script that inquires
> iptables, instead of having them all hardcoded into the script.

One more thing coming into my mind:
A new tool would not be backwards compatible.
While the shell completion could be used with old versions up to when 
the -S parameter was introduced. When commenting out some unsupported 
extensions in their definition array, it'll be suitable for vast parts.

--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/utils/iptables_bash_completion/README.md b/utils/iptables_bash_completion/README.md
new file mode 100644
index 0000000..d99f684
--- /dev/null
+++ b/utils/iptables_bash_completion/README.md
@@ -0,0 +1,290 @@ 
+iptables-bash_completion
+========================
+
+
+Description
+===========
+
+Programmable completion specification (compspec) for the bash shell
+to support the iptables and ip6tables programs (netfilter.org).
+
+
+Programmable completion allows the user, while working in an interactive shell,
+to retrieve and auto-complete commands, their options, filenames, etc.
+Pressing [TAB] will complete on the current word, if only one match is found.
+If multiple completions are possible, they will be listed by hitting [TAB] again.
+
+
+Features
+========
+
+This completion specification follows the logic of iptables and will only show commands and options, 
+when they are available for the current context (combination of commands and/or options).
+Also some values entered by the user are checked for validity and completion will not continue
+after an invalid input. All together providing some kind of interactive help.
+
+- Show and complete commands and options.
+- Show and complete matches, targets and builtin and/or user-defined chains.
+- Dynamically retrieve, show and complete:
+	set names, services, protocols, active interfaces, cpu numbers, routing realms,
+	user and group names, NFLOG logging groups, tc classes, nfacct names,
+	nfct timeout policy names, osf match genre names.
+- Show and complete hostnames, ip/network/mac addresses (dynamically and from file).
+- Show and complete various arguments for matches and targets (those which are in any way predictable).
+  All options and extensions coming with iptables version 1.6 are supported.
+- Complete on variables and command substitution.
+
+
+Installation
+============
+
+Quote from bash-completion README:
+
+	Install it in one of the directories pointed to by
+	bash-completion's pkgconfig file variables.  There are two
+	alternatives: the recommended one is 'completionsdir' (get it with
+	"pkg-config --variable=completionsdir bash-completion") from which
+	completions are loaded on demand based on invoked commands' names,
+	so be sure to name your completion file accordingly, and to include
+	for example symbolic links in case the file provides completions
+	for more than one command.  The other one which is present for
+	backwards compatibility reasons is 'compatdir' (get it with
+	"pkg-config --variable=compatdir bash-completion") from which files
+	are loaded when bash_completion is loaded.
+
+	For packages using GNU autotools the installation can be handled
+	for example like this in configure.ac:
+
+	 PKG_CHECK_VAR(bashcompdir, [bash-completion], [completionsdir], ,
+	   bashcompdir="${sysconfdir}/bash_completion.d")
+	 AC_SUBST(bashcompdir)
+
+	...accompanied by this in Makefile.am:
+
+	 bashcompdir = @bashcompdir@
+	 dist_bashcomp_DATA = # completion files go here
+
+	For cmake we ship the bash-completion-config.cmake and
+	bash-completion-config-version.cmake files. Example usage:
+
+	 find_package(bash-completion)
+	 if(BASH_COMPLETION_FOUND)
+	   message(STATUS
+		 "Using bash completion dir ${BASH_COMPLETION_COMPLETIONSDIR}")
+	 else()
+	   set (BASH_COMPLETION_COMPLETIONSDIR "/etc/bash_completion.d")
+	   message (STATUS
+		 "Using fallback bash completion dir ${BASH_COMPLETION_COMPLETIONSDIR}")
+	 endif()
+
+	 install(FILES your-completion-file DESTINATION
+	   ${BASH_COMPLETION_COMPLETIONSDIR})
+
+For backwards compatibility it is still possible
+to put it into ~/.bash_completion or /etc/bash_completion.d/.
+
+Tip: To make tab completion more handsome put the following into either /etc/inputrc or ~/.inputrc:
+
+     set show-all-if-ambiguous on
+
+This will allow single tab completion as opposed to requiring a double tab.
+
+     set page-completions off
+
+This turns off the use of the internal pager when returning long completion lists.
+
+
+Usage
+=====
+
+ip[6]tables [TAB] to get you started.
+
+The environment variable **_IPT_OPT_STYLE** controls wether only long style options should be
+shown, or long and short style options. Setting it to 'long' (the default) will show only
+long style options, while any other value will result in both to be offered for completion.
+
+The environment variable **_IPT_OPTS_ON_START** controls
+what options are shown at the beginning.
+Setting it to 'actions' will list only actions (-A,-I, etc.)
+and -t, -m, -g, -j, -h, -v, -w (respectively the long forms if requested).
+Any other value will show all options at start (the default).
+
+Generally typing -[TAB]  starts option completion.
+If the variable **_IPT_OPT_STYLE** is not set to 'long', then using a single dash (-)
+will show the short version of the main options.
+If the current context also provides options of a match or target,
+they will be shown with a double-dash (--) and therefore are easy
+to distinguish from each other.
+
+Typing --[TAB] will show only the long form of the available options.
+
+Type [TAB] to complete on anything available at the current context,
+i.e interface-, host-, port-, set-names, etc,.
+
+---
+
+Available targets (includes builtin and user-defined chains) and matches are selected by table.
+The default table is 'filter'.
+
+Also some targets or matches are not shown if it can be predicted by the protocol,
+that the combination is not valid.
+
+---
+
+To complete named port ranges, enter the : (colon) after the first completed service (port) name,
+hit [TAB] again to start completion on the second named port.
+The list of services will start from the offset+1 of the first part of the range expression.
+No matter if you specified the first part as a numeric value or a name from /etc/services.
+
+	Tip: To reduce the amount of services listed, specify a protocol before.
+
+---
+
+Numeric protocol specifications are recognized for the following protocols
+(their options are loaded for completion afterwards):
+
+	icmp 1
+	tcp 6
+	udp 17
+	dccp 33
+	esp 50
+	ah 51
+	ipcomp 108
+	sctp 132
+	mh 135
+
+
+---
+
+The environment variable **HOSTFILE** controls how hostname completion is performed.
+Taking the description from the bash man-page:
+
+	Contains the name of a file in the same format as /etc/hosts that 
+	should be read when the shell needs to complete a hostname.
+	The list of possible hostname completions may be changed while the shell is running
+	the next time hostname completion is attempted after the value is changed,
+	bash adds the contents of the new file to the existing list.
+	If HOSTFILE is set, but has no value, or does not name a readable file, bash
+	attempts to read /etc/hosts to obtain the list of possible hostname completions.
+	When HOSTFILE is unset, the hostname list is cleared.
+
+
+If the bash-completion package is available hostname completion is extended
+the following way (description from bash-completion source):
+
+	Helper function for completing _known_hosts.
+	This function performs host completion based on ssh config and known_hosts
+	files, as well as hostnames reported by avahi-browse if
+	COMP_KNOWN_HOSTS_WITH_AVAHI is set to a non-empty value.
+	Also hosts from HOSTFILE (compgen -A hostname) are added, unless
+	COMP_KNOWN_HOSTS_WITH_HOSTFILE is set to an empty value.
+
+
+Also if the  *bash-completion* package is not available, **COMP_KNOWN_HOSTS_WITH_HOSTFILE**
+is recognized the same way.
+
+If the environment variable **_IPT_COMP_IP_LOCAL** is set to a non-empty value,
+ip addresses of the local system are added to the list of possible completions.
+
+If the environment variable **_IPT_COMP_NETWORKS** is set to a non-empty value,
+additionally network addresses are taken from /etc/networks
+and get added to the list of possible completions.
+
+Also a list of ip addresses can be supplied using the
+environment variable **_IPT_IPLIST_FILE**. Which should point to a file containing
+an ip address per line. They can be ipv4 and/or ipv6. If iptables is invoked,
+only ipv4 addresses are shown, if ip6tables is invoked only ipv6 addresses are shown.
+
+---
+
+The iprange match only completes on addresses taken from the file specified with **_IPT_IPLIST_FILE**.
+
+---
+
+Mac addresses are retrieved depending on the value of the environment variable **_IPT_MAC_COMPL_MODE**.
+If it is set to 'file', the variable **_IPT_MACLIST_FILE** is queried for a filename,
+which contains a list of mac addresses.
+The file should contain one mac address per line.
+Empty lines and comments (also after the address) are supported.
+Setting it to 'system', mac addresses are fetched from arp cache,
+/etc/ethers and the output of `ip link show`.
+Setting it to 'both' (the default, if unset), both methods are used.
+If _IPT_MAC_COMPL_MODE has any other value, no mac address completion is performed.
+
+---
+
+If a comma separated list is to be completed (i.e. state match),
+no space will be appended to the currently completed word.
+The cursor will be at the end of the current word.
+Hit [TAB] and the comma will be appended.
+Hit [TAB] again to see the possible completions.
+
+---
+
+To retrieve the list of genres for the **osf** match the environment variable
+**_IPT_OSF_PF_OS** has to point to the pf.os file.
+
+---
+
+If the environment variable **_IPT_VALIDATE_INPUT** is defined and set to a non empty value
+validation of users input is enabled.
+Thus completion will stop, if an invalid input value is detected.
+
+---
+
+If the environment variable **_DEBUG_NF_COMPLETION** is defined (any value)
+debugging information is displayed.
+
+---
+
+In case an iptables extension (match or target) is not available on you system
+and you do not want it to show up for completion, you should remove or comment out
+the definition of that extension in either 'arr_matches' or 'arr_targets'.
+
+
+Compatibility
+=============
+
+bash v4+ is required.
+
+Tested with iptables v1.4.21 kernel 3.12 on debian squeeze.
+Tested with iptables v1.6 kernel 3.16 on debian jessie.
+
+Compatibility for future iptables versions cannot be promised, as new options may appear, 
+which of course are currently unpredictable.
+Old iptables version, which do not have the -S (--list-rules) option are not supported.
+
+The bash-completion (v2.0+) package is highly recommended, though not mandatory.
+
+http://bash-completion.alioth.debian.org/
+
+Some things might not be that reliable or feature rich without it.
+Also the colon (if there) is removed from COMP_WORDBREAKS.
+This alteration is globally, which might affect other completions,
+if they do not take care of it themselves.
+
+The iproute program (ip) is needed to display information about the local system.
+
+Other programs used are:
+
+* ipset - to list the sets
+* arp - to retrieve mac addresses
+* tc - to get the list of tc classes
+* nfacct - to show netfilter accounting names
+* nfct - to show netfilters CT target timeout policy names
+
+
+Availability
+============
+
+https://github.com/AllKind/iptables-bash_completion
+
+https://sourceforge.net/projects/ipt-bashcompl/
+
+
+Bugs
+====
+
+Please report bugs!
+Either through sf.net, or email.
+
diff --git a/utils/iptables_bash_completion/iptables b/utils/iptables_bash_completion/iptables
new file mode 100644
index 0000000..897ed6a
--- /dev/null
+++ b/utils/iptables_bash_completion/iptables
@@ -0,0 +1,2426 @@ 
+#!/bin/bash
+
+# -----------------------------------------------------------------
+# Programmable completion specification for iptables (netfilter.org)
+#
+# https://github.com/AllKind/iptables-bash_completion
+# https://sourceforge.net/projects/ipt-bashcompl
+# -----------------------------------------------------------------
+
+# Copyright (C) 2013-2016 Mart Frauenlob aka AllKind (AllKind@fastest.cc)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# -----------------------------------------------------------------
+# Requirements:
+#
+# bash v4 or greater.
+#
+# The bash completion package version 2.0 or greater is recommended.
+# http://bash-completion.alioth.debian.org/
+#
+# If the package is not available, things might not be so reliable.
+# Also the colon (if there) is removed from COMP_WORDBREAKS.
+# This alteration is globally. Which might affect other completions
+# if they don't take care of it themselves.
+#
+# Old iptables versions not having the -S option are not supported.
+# -----------------------------------------------------------------
+
+# -----------------------------------------------------------------
+# Installation (quote from bash-completion README):
+#
+# Install it in one of the directories pointed to by
+# bash-completion's pkgconfig file variables.  There are two
+# alternatives: the recommended one is 'completionsdir' (get it with
+# "pkg-config --variable=completionsdir bash-completion") from which
+# completions are loaded on demand based on invoked commands' names,
+# so be sure to name your completion file accordingly, and to include
+# for example symbolic links in case the file provides completions
+# for more than one command.  The other one which is present for
+# backwards compatibility reasons is 'compatdir' (get it with
+# "pkg-config --variable=compatdir bash-completion") from which files
+# are loaded when bash_completion is loaded.
+#
+# For packages using GNU autotools the installation can be handled
+# for example like this in configure.ac:
+#
+#  PKG_CHECK_VAR(bashcompdir, [bash-completion], [completionsdir], ,
+#    bashcompdir="${sysconfdir}/bash_completion.d")
+#  AC_SUBST(bashcompdir)
+#
+# ...accompanied by this in Makefile.am:
+#
+#  bashcompdir = @bashcompdir@
+#  dist_bashcomp_DATA = # completion files go here
+#
+# For cmake we ship the bash-completion-config.cmake and
+# bash-completion-config-version.cmake files. Example usage:
+#
+#  find_package(bash-completion)
+#  if(BASH_COMPLETION_FOUND)
+#    message(STATUS
+#      "Using bash completion dir ${BASH_COMPLETION_COMPLETIONSDIR}")
+#  else()
+#    set (BASH_COMPLETION_COMPLETIONSDIR "/etc/bash_completion.d")
+#    message (STATUS
+#      "Using fallback bash completion dir ${BASH_COMPLETION_COMPLETIONSDIR}")
+#  endif()
+#
+#  install(FILES your-completion-file DESTINATION
+#    ${BASH_COMPLETION_COMPLETIONSDIR})
+#
+# For backwards compatibility it is still possible to put it into
+# ~/.bash_completion or /etc/bash_completion.d/.
+# -----------------------------------------------------------------
+#
+# Version 1.6
+#
+# -----------------------------------------------------------------
+
+shopt -s extglob
+
+# -----------------------------------------------------------------
+# FUNCTIONS
+# -----------------------------------------------------------------
+
+# -----------------------------------------------------------------
+# iptables functions
+# -----------------------------------------------------------------
+_iptables_select_on_inet_family() {
+if [[ $1 != all ]]; then # select match according to current calling app
+    if [[ $1 = v4 && $str_app = ip6tables ]]; then
+        return 1
+    elif [[ $1 = v6 && $str_app = iptables ]]; then
+        return 1
+    fi
+fi
+return 0
+}
+
+_iptables_is_option() {
+local -i opt_idx
+local str_q="$1"
+[[ $str_q = -?* ]] || return
+# CLUSTERIP also has --new
+if [[ $str_q = --new && $str_target = CLUSTERIP ]] && \
+    ((cword == target_index+2)); then
+    return 1
+fi
+if [[ $str_q = --proto && $str_last_is = match && \
+    ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} = policy ]]
+then # policy match also has --proto
+    return 1
+fi
+for opt_idx in ${!arr_opts[@]}; do set -- ${arr_opts[opt_idx]}
+    while (($#)); do
+        [[ $1 = $str_q ]] && return
+        shift
+    done
+done
+return 1
+}
+
+_iptables_is_target() {
+local -i idx
+[[ $1 = @(""|-*|*/*|\!) ]] && return 1
+for idx in ${!arr_targets[@]}; do
+    [[ ${arr_targets[idx]%% *} = $1 ]] && return
+done
+return 1
+}
+
+_iptables_is_match() {
+local -i idx
+[[ $1 = @(""|-*|*/*|\!) ]] && return 1
+for idx in ${!arr_matches[@]}; do
+    [[ ${arr_matches[idx]%% *} = $1 ]] && return
+done
+return 1
+}
+
+_iptables_get_matches() {
+local -i idx
+local str_x="$1" # table name
+for idx in ${!arr_matches[@]}; do
+    set -- ${arr_matches[idx]}
+    _iptables_select_on_inet_family $2 || continue
+    # valid in current table
+    if [[ $3 = all ]] || [[ ${3//,/ } = *${str_x}* ]]; then
+        if [[ $str_proto ]]; then
+            if [[ $1 = @(connlimit|ecn|osf|tcp|tcpmss) ]]; then
+                [[ $str_proto = tcp ]] || continue
+            elif [[ $1 = multiport ]]; then
+                [[ $str_proto = @(tcp|udp?(lite)|dccp|sctp) ]] || continue
+            elif [[ $1 = @(icmp|udp|tcp|ah|esp|dccp|sctp|mh|ipcomp) ]]; then
+                [[ $str_proto = $1 ]] || continue
+            fi
+        fi
+        if ((proto_negated)); then
+            if [[ $1 = @(connlimit|ecn|ipcomp|osf|tcpmss|multiport|icmp|udp|tcp|ah|esp|dccp|sctp|mh) ]]
+            then continue
+            fi
+        fi
+        printf "%s\n" "$1"
+    fi
+done
+}
+
+_iptables_get_targets() {
+local -i idx
+local str_x="$1" # table
+for idx in ${!arr_targets[@]}; do set -- ${arr_targets[idx]}
+    _iptables_select_on_inet_family $2 || continue
+    if [[ $3 = all ]] || [[ ${3//,/ } = *${str_x}* ]]; then # allowed in table
+        if [[ $1 = @(ECN|SYNPROXY|TCPMSS|TCPOPTSTRIP) ]]; then
+            if [[ $str_proto ]]; then
+                [[ $str_proto = tcp ]] || continue
+            elif ((proto_negated)); then
+                continue
+            fi
+        elif [[ $1 = TPROXY ]]; then
+            if [[ $str_proto ]]; then
+                [[ $str_proto = @(tcp|udp) ]] || continue
+            fi
+        fi
+        printf "%s\n" "$1"
+    fi
+done
+}
+
+_iptables_get_builtin_chains() {
+local str_cmd str_name str_rest
+while read -r str_cmd str_name str_rest; do
+    [[ $str_cmd = -P ]] && printf "%s\n" "$str_name" || return 0
+done < <("${words[0]}" -S -t $1 2>/dev/null)
+}
+
+_iptables_get_user_chains() {
+local str_cmd str_name str_rest
+while read -r str_cmd str_name str_rest; do
+    if [[ $str_cmd = -N ]]; then
+        [[ $prev != @(-t|--table) && $str_chain && \
+            $str_chain = $str_name ]] && continue
+        printf "%s\n" "$str_name"
+    elif [[ $str_cmd = -A ]]; then return 0
+    fi
+done < <("${words[0]}" -S -t $1 2>/dev/null)
+}
+
+_iptables_get_target_opts() {
+local -i idx oidx xidx ridx=0
+local str_x="$1" oIFS="$IFS" str_opt str_flags str_xflag
+local -a arr_topts=() arr_reply=()
+for idx in ${!arr_targets[@]}; do set -- ${arr_targets[idx]}
+    [[ $1 = $str_x ]] || continue
+    _iptables_select_on_inet_family $2 || continue
+    shift 3
+    arr_topts=( "$@" )
+    # check targets option flags
+    for oidx in ${!arr_topts[@]}; do
+        if [[ ${arr_topts[oidx]} != *:* ]]; then
+           arr_reply[ridx++]="${arr_topts[oidx]}"
+           continue
+        fi
+        str_flags="${arr_topts[oidx]#*:}"
+        str_opt="${arr_topts[oidx]%:"$str_flags"}"
+        IFS=:
+        set -- $str_flags
+        IFS="$oIFS"
+        while (($#)); do
+            if [[ $1 = ex ]]; then # mutual exclusive options
+                shift
+                for str_xflag in ${1//,/ }; do
+                    _iptables_is_in_cw $((target_index+1)) "$str_xflag" && continue 3
+                    # check if option was added in a previous cycle (dirty!?)
+                    if [[ $str_opt = $str_xflag  ]]; then
+                        for xidx in ${!arr_reply[@]}; do
+                            if [[ $str_opt = ${arr_reply[xidx]} ]]; then
+                                unset arr_reply[xidx] && continue 4
+                            fi
+                        done
+                    fi
+                done
+            elif [[ $1 = table ]]; then # option is table depending
+                shift
+                for str_xflag in ${1//,/ }; do
+                    [[ $str_table = $str_xflag ]] || continue 3
+                done
+            elif [[ $1 = proto ]]; then # option is protocol depending
+                shift
+                if [[ $str_proto ]]; then
+                    str_xflag="${1//,/|}"
+                    [[ $str_proto = @($str_xflag) ]] || continue 2
+                fi
+                if ((proto_negated)); then
+                    continue 2
+                fi
+            fi
+            shift
+        done
+        arr_reply[ridx++]="$str_opt"
+    done
+    printf "%s\n" "${arr_reply[*]}"
+    return 0
+done
+}
+
+_iptables_get_match_opts() {
+local -i idx cix rix xix xidx got_arg
+local str_match="$1" oIFS="$IFS" str_opt str_tmp str_flags str_xflag
+local arr_reply=()
+for idx in ${!arr_matches[@]}; do set -- ${arr_matches[idx]}
+    [[ $1 = $str_match ]] || continue
+    _iptables_select_on_inet_family $2 || continue
+    shift 3
+    arr_reply=() arr_mopts=( "$@" )
+    # check match option flags
+    for rix in ${!arr_mopts[@]}; do
+        str_flags="${arr_mopts[rix]#*:}"
+        str_opt="${arr_mopts[rix]%:"$str_flags"}"
+        IFS=:
+        set -- $str_flags
+        IFS="$oIFS"
+        while (($#)); do
+            if [[ $1 = ex ]]; then # mutual exclusive options
+                shift
+                for str_xflag in ${1//,/ }; do
+                    _iptables_is_in_cw $((last_match_index+1)) "$str_xflag" && continue 3
+                    # check if option was added in a previous cycle (dirty!?)
+                    if [[ $str_opt = $str_xflag  ]]; then
+                        for xidx in ${!arr_reply[@]}; do
+                            if [[ $str_opt = ${arr_reply[xidx]} ]]; then
+                                unset arr_reply[xidx] && continue 4
+                            fi
+                        done
+                    fi
+                done
+            elif [[ $1 = optis ]]; then
+                # mutual exclusive options, value depending
+                shift
+                for ((x=last_match_index+1; x < ${#words[@]}-1; x++)); do
+                    if [[ ${1%=*} = ${words[x]} && ${1#*=} != ${words[x+1]} ]]
+                    then continue 3
+                    fi
+                done
+            elif [[ $1 = neg ]]; then
+                # negation
+                shift
+                if [[ $prev = \! && $1 = no ]]; then
+                    continue 2
+                fi
+            fi
+            shift
+        done
+        if [[ $str_opt = *,* ]]; then # redundant option names
+            for str_tmp in ${str_opt//,/ }; do # exclude the aliases from listing
+                if _iptables_is_in_cw $((last_match_index+1)) $str_tmp; then
+                    continue 2
+                fi
+            done
+        fi
+        arr_reply[${#arr_reply[@]}]="$str_opt"
+    done
+    for rix in ${!arr_reply[@]}; do str_opt="${arr_reply[rix]}"
+        # find options which can be used globally (multiple use of match), but filter them out
+        # if we already used them in the current active match
+        for xix in ${!arr_global_matches[@]}; do # retrieve list of global options
+            set -- ${arr_global_matches[xix]}
+            [[ $str_match = $1 ]] || continue
+            shift
+            for str_tmp; do
+                [[ $str_tmp = $str_opt ]] || continue
+                got_arg=0
+                for ((cix=$((cword == ${#words[@]}-1 ? cword-1 : cword)); \
+                    cix >= last_match_index; cix--))
+                do
+                    [[ ${words[cix]} = @(""|\!) ]] && continue
+                    if [[ ${words[cix]} != @($str_opt|$str_match) ]]
+                    then
+                        if ((cix == last_option_index)); then
+                            break # another option
+                        elif ((cix == target_index)); then
+                            break # another target
+                        fi
+                    elif [[ ${words[cix]} = $str_opt ]]; then
+                        got_arg=1 # got the option
+                    elif [[ ${words[cix]} = $str_match ]] && \
+                        ((cix == last_match_index)); then # got the match
+                        ((got_arg)) || break # but don't have arg
+                        unset arr_reply[rix]
+                        break
+                    fi
+                done
+            done
+        done
+    done
+    printf "%s\n" "${arr_reply[*]//,/ }"
+    return 0
+done
+}
+
+# -----------------------------------------------------------------
+# retrieve arguments data
+# -----------------------------------------------------------------
+#_iptables_get_networks() {
+#local foo str_net rest
+#[[ -r /etc/networks ]] || return 0
+#while read -r foo str_net rest; do
+#    [[ $foo = @(""|*([[:blank:]])#*) ]] && continue
+#    [[ $str_net = *([[:blank:]])#* ]] && continue
+#    printf "%s\n" "$str_net"
+#done < /etc/networks
+#}
+
+_iptables_get_ifnames() {
+while read -r; do
+    REPLY="${REPLY#*: }"
+    printf "%s\n" ${REPLY%%:*}
+done < <(PATH=${PATH}:/sbin command ip -o link show)
+}
+
+_iptables_get_protocols() {
+local str_name rest
+while read -r str_name rest; do
+    [[ $str_name = *([[:blank:]])#* ]] && continue
+    printf "%s\n" "$str_name"
+done < /etc/protocols
+}
+
+_iptables_get_services() {
+local str_offset="" str_name str_num str_p=all rest
+while (($#)); do
+    if [[ $1 = -p ]]; then # select on protocol
+        str_p="${2:-all}"
+        shift
+    elif [[ $1 = -o && $2 ]]; then
+        # second part of range will have offset = first_part_of_range
+        str_offset="${2}"
+        shift
+    fi
+    shift
+done
+# find service num to set offset
+if [[ $str_offset && $str_offset != +([[:digit:]]) ]]; then
+    while read str_name str_num rest; do
+        if [[ $str_name = *([[:blank:]])#* ]]; then continue
+        elif [[ $str_p != all && ${str_num#*/} != $str_p ]]; then
+            continue
+        fi
+        [[ $str_name = $str_offset ]] && str_offset=${str_num%/*} && break
+    done < /etc/services
+    [[ $str_offset = +([[:digit:]]) ]] || return 0
+fi
+while read -r str_name str_num rest; do
+    if [[ $str_name = *([[:blank:]])#* ]]; then continue
+    elif [[ $str_p != all && ${str_num#*/} != $str_p ]]; then
+        continue
+    elif [[ $str_offset && $str_num && $str_num = +([[:digit:]])/* ]] && \
+        ((${str_num%/*} <= $str_offset)); then
+        continue
+    fi
+    printf "%s\n" "$str_name"
+done < /etc/services
+}
+
+_iptables_compl_port_range() { # complete named port ranges
+local str_prefix="$1" lcur="${1#*:}"
+str_prefix=${str_prefix%"$lcur"}
+compgen -P "$str_prefix" -W \
+    '$(_iptables_get_services -p "$str_proto" -o "${str_prefix%:}")' \
+    -- "$lcur"
+}
+
+# -----------------------------------------------------------------
+# misc functions
+# -----------------------------------------------------------------
+_iptables_dedupe_opt_alias() {
+[[ $_IPT_OPT_STYLE = long ]] && return # no deduping needed
+for i in ${!arr_cmd_opts[@]}; do
+    [[ ${arr_cmd_opts[i]} ]] || continue
+    # if the user supplied the short form of an option previously, and now
+    # requests the long form, remove the corresponding long option,
+    # vice versa for short options
+    for y in ${!arr_opts[@]}; do # cycle through main options
+        set -- ${arr_opts[y]} # $1 = short , $2 = long option
+        str_tmp=""
+        if [[ ${arr_cmd_opts[i]} = $1 ]]; then
+            # we got short version on the cmdline
+            str_tmp=$2
+        elif [[ ${arr_cmd_opts[i]} = $2 ]]; then
+            # we got long version on the cmdline
+            str_tmp=$1
+        fi
+        [[ $str_tmp ]] || continue
+        for x in ${!arr_tmp_opts[@]}; do # compare with compreply
+            [[ ${arr_tmp_opts[x]//[[:blank:]]/} = $str_tmp ]] || continue
+            # options allowed multiple times
+            if [[ ${arr_tmp_opts[x]} = @(-m|--match) ]]; then
+                :
+            elif [[ ${arr_tmp_opts[x]} = --proto && $str_last_is = match && \
+                ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} = policy ]]; then
+                : # always allow --proto if policy is the last match
+            elif [[ ${arr_tmp_opts[x]} = --proto && $str_last_is = match && \
+                ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} = policy && -z $str_proto ]]
+            then
+                : # allow -p after first option of policy match was set, if not set before
+            else # exceptions done
+                if [[ $_DEBUG_NF_COMPLETION ]]; then
+                    printf "removing option alias arr_tmp_opts[$x]: %s\n" "${arr_tmp_opts[x]}"
+                fi
+                unset arr_tmp_opts[x]
+                continue 3
+            fi
+        done
+    done
+done
+}
+
+_iptables_dedupe_opts() {
+local str_opt
+# post process the reply - remove dupclicate options
+for str_opt; do # list an option only once
+    # options allowed multiple times
+    if [[ $str_opt = @(-m|--match) ]]; then
+        printf "%s\n" "$str_opt"
+        continue
+    elif [[ $str_last_is = match ]]; then
+        # per match options allowed multiple times
+        if [[ ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} = policy ]]; then
+            if [[ $str_opt = --@(proto|reqid|spi|mode|tunnel-src|tunnel-dst|next) ]]
+            then
+                printf "%s\n" "$str_opt"
+                continue
+            fi
+        elif [[ ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} = @(connlabel|recent) ]]; then
+            # connlabel and recent match both have --set
+            # allow one instance for each match
+            if ! _iptables_is_in_cw $((last_match_index+1)) "--set"; then
+                printf "%s\n" "$str_opt"
+                continue
+            fi
+        fi
+        for x in ${!arr_global_matches[@]}; do
+            set -- ${arr_global_matches[x]}
+            [[ $1 = ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} ]] || continue
+            shift
+            for y; do
+                if [[ $str_opt = $y ]]; then
+                    printf "%s\n" "$str_opt"
+                    continue 3
+                fi
+            done
+        done
+    elif [[ $str_last_is = target ]]; then
+        if [[ $str_target = CT && ${arr_all_matches[*]} = *helper* ]]; then
+            printf "%s\n" "$str_opt"
+            continue
+        fi
+    fi
+    for i in ${!arr_set_names[@]}; do # could be a weird set name
+        if [[ $str_opt = ${arr_set_names[i]} ]]; then
+            printf "%s\n" "$str_opt"
+            continue
+        fi
+    done
+    for ((i=1; i < ${#words[@]}-1; i++)); do
+        [[ ${words[i]} != -?* ]] && continue
+        if [[ ${words[i]} = $str_opt ]]; then
+            if [[ $_DEBUG_NF_COMPLETION ]]; then
+                printf "removing dupe option: %s\n" "$str_opt"
+            fi
+            continue 2
+        fi
+    done
+    printf "%s\n" "$str_opt"
+done
+}
+
+_iptables_is_in_cw() {
+local -i cix=$1
+shift
+for ((cix=cix; cix < ${#words[@]}-1; cix++)); do
+    [[ $1 = ${words[cix]} ]] && return
+done
+return 1
+}
+
+_iptables_build_comma_list() {
+local lcur="$1" lmatch="" rlist="" prefix=""
+shift
+local arr_list=("${@}")
+local str_pat=$(IFS=\|; printf "%s" "${arr_list[*]}")
+compopt -o nospace
+if [[ $lcur ]]; then
+    if [[ ${lcur} = @($str_pat) ]]; then
+        prefix="${lcur}," rlist="$lcur" lmatch=""
+    elif [[ ${lcur:$((${#lcur}-1))} = , ]]; then
+        prefix="${lcur}" rlist="$lcur" lmatch=""
+    elif [[ ${lcur:$((${#lcur}-1))} != , ]]; then
+        if [[ ${lcur##*,} != @($str_pat) ]]; then
+            prefix="${lcur%${lcur##*,}}"
+            rlist="${lcur%${lcur##*,}}"
+            if [[ $lcur = *,* ]]; then
+                lmatch="${lcur##*,}"
+            else
+                lmatch="${lcur}"
+            fi
+            [[ $prefix && ${prefix:$((${#prefix}-1))} != , ]] && prefix="${prefix},"
+        else
+            prefix="${lcur}," rlist="$lcur" lmatch=""
+        fi
+    fi
+    for x in ${rlist//,/ }; do
+        for i in ${!arr_list[@]}; do
+            if [[ $x = ${arr_list[i]} ]]; then
+                unset arr_list[i]
+            fi
+        done
+    done
+fi
+((${#arr_list[@]} == 1)) && compopt +o nospace
+COMPREPLY=( $( compgen -P "$prefix" -W '${arr_list[@]}' -- "$lmatch" ) )
+}
+
+# -----------------------------------------------------------------
+# main function
+# -----------------------------------------------------------------
+
+_iptables_complete() {
+local cur prev cword words
+local str_target str_jump str_chain str_action str_proto str_help
+local ipt_version str_last_is str_var str_tmp str_regex
+local str_table="filter" str_proto_match=""
+local arr_tmp_opts=() arr_cmd_matches=() arr_cmd_opts=() arr_all_matches=()
+local arr_last_opts=() arr_set_names=() arr_tmp=()
+local -i i=x=y=got_action=got_bashcompl=inline_help=list_only=got_rule_opt=0
+local -i rulenum=line_numbers=set_counters=proto_negated=0
+local -i last_index=last_match_index=last_option_index=target_index=0
+local -r str_app="${COMP_WORDS[0]//*\//}"
+
+# at least bash 4 is required
+((${BASH_VERSINFO[0]} < 4)) && return 0
+
+# iptables version TODO: checks re version
+#ipt_version="$("$str_app" --version)"
+#ipt_version="${ipt_version#$str_app v}"
+
+local arr_global_matches=(
+"connmark --mark"
+"mark --mark"
+"helper --helper"
+"nfacct --nfacct-name"
+"sctp --chunk-types"
+"set --match-set --return-nomatch --update-counters --update-subcounters \
+    --packets-eq --packets-lt --packets-gt --bytes-eq --bytes-lt --bytes-gt"
+)
+
+local arr_proto_defs=(
+"icmp 1"
+"tcp 6"
+"udp 17"
+"dccp 33"
+"esp 50"
+"ah 51"
+"ipcomp 108"
+"sctp 132"
+"mh 135"
+)
+
+# target-name all|v4|v6 all|table1,table2,... option [...]
+local arr_targets=(
+"ACCEPT all all"
+"AUDIT all all --type" # TODO: audit drop in nat table, really valid?
+"CT all raw \
+    --notrack:ex:--helper,--ctevents,--expevents,--zone,--zone-orig,--zone-reply,--timeout \
+    --helper:ex:--notrack \
+    --ctevents:ex:--notrack \
+    --expevents:ex:--notrack \
+    --zone:ex:--notrack \
+    --zone-orig:ex:--notrack \
+    --zone-reply:ex:--notrack \
+    --timeout:ex:--notrack" # TODO: get zone-ids?
+"DROP all filter,mangle,security,raw"
+"DNPT v6 mangle --src-pfx --dst-pfx"
+"CHECKSUM all mangle --checksum-fill"
+"CLASSIFY all all --set-class"
+"CLUSTERIP v4 all --new:need --hashmode:need --clustermac:need \
+    --total-nodes:need --local-node:need --hash-init"
+"CONNMARK all all \
+    --save-mark:ex:--restore-mark,--set-xmark,--and-mark,--or-mark,--xor-mark,--set-mark:table:mangle \
+    --restore-mark:ex:--save-mark,--set-xmark,--and-mark,--or-mark,--xor-mark,--set-mark:table:mangle \
+    --mask:ex:--nfmask,--ctmask \
+    --nfmask:ex:--mask,--ctmask \
+    --ctmask:ex:--mask,--nfmask \
+    --set-xmark:ex:--save-mark,--restore-mark,--and-mark,--or-mark,--xor-mark,--set-mark,--mask,--ctmask,--nfmask \
+    --and-mark:ex:--save-mark,--restore-mark,--set-xmark,--or-mark,--xor-mark,--set-mark,--mask,--ctmask,--nfmask \
+    --or-mark:ex:--save-mark,--restore-mark,--and-mark,--set-xmark,--xor-mark,--set-mark,--mask,--ctmask,--nfmask \
+    --xor-mark:ex:--save-mark,--restore-mark,--and-mark,--or-mark,--set-xmark,--set-mark,--mask,--ctmask,--nfmask \
+    --set-mark:ex:--save-mark,--restore-mark,--and-mark,--or-mark,--set-xmark,--xor-mark,--mask,--ctmask,--nfmask"
+"CONNSECMARK all mangle,security --save:ex:--restore --restore:ex:--save"
+"ECN all mangle --ecn-tcp-remove"
+"DNAT all nat --to-destination:need --random:ex:--presistent:proto:tcp,udp,dccp,sctp --persistent:ex:--random"
+"DSCP all mangle --set-dscp:ex:--set-dscp-class --set-dscp-class:ex:set-dscp"
+"HL v6 mangle --hl-set:ex:--hl-dec,--hl-inc \
+    --hl-dec:ex:--hl-set,--hl-inc \
+    --hl-inc:ex:--hl-set,--hl-dec"
+"HMARK all all \
+    --hmark-tuple:ex:--hmark-src-prefix,--hmark-dst-prefix,--hmark-sport-mask,--hmark-dport-mask,--hmark-spi-mask,--hmark-proto-mask:need1 \
+    --hmark-mod:need --hmark-offset \
+    --hmark-src-prefix:ex:--hmark-tuple:need1 \
+    --hmark-dst-prefix:ex:--hmark-tuple:need1 \
+    --hmark-sport-mask:ex:--hmark-tuple,--hmark-spi-mask:need1 \
+    --hmark-dport-mask:ex:--hmark-tuple,--hmark-spi-mask:need1 \
+    --hmark-spi-mask:ex:--hmark-tuple,--hmark-sport-mask,--hmark-dport-mask:need1 \
+    --hmark-proto-mask:ex:--hmark-tuple:need1 \
+    --hmark-rnd:need" # TODO: check used options form -tuple completion
+"IDLETIMER all all --timeout --label:need"
+"LED all all --led-trigger-id:need --led-delay --led-always-blink"
+"LOG all all --log-level --log-prefix --log-tcp-sequence --log-tcp-options --log-ip-options --log-uid"
+"MASQUERADE all nat --to-ports:proto:tcp,udp,dccp,sctp --random:proto:tcp,udp,dccp,sctp"
+"MIRROR v4 mangle" # TODO: tables? don't have it
+"NETMAP all nat --to"
+"NFLOG all all --nflog-group --nflog-prefix --nflog-range --nflog-threshold"
+"NFQUEUE all all --queue-num:ex:--queue-balance,--cpu-fanout \
+    --queue-balance:ex:--queue-num
+    --cpu-fanout:ex:--queue-num
+    --queue-bypass"
+"NOTRACK all raw"
+"RATEEST all all --rateest-name:need --rateest-interval:need --rateest-ewmalog:need"
+"REDIRECT v4 nat --to-ports:proto:tcp,udp,dccp,sctp --random:proto:tcp,udp,dccp,sctp"
+"REJECT all filter --reject-with"
+"SECMARK all mangle,security --selctx"
+"SET all all --add-set:ex:--del-set,--map-set,--map-mark,--map-prio,--map-queue:need1 \
+    --del-set:ex:--map-set,--add-set,--exist,--timeout,--map-mark,--map-prio,--map-queue:need1 \
+    --map-set:ex:--add-set,--del-set,--exist,--timeout:need1 \
+    --map-mark:ex:--add-set,--del-set,--exist,--timeout \
+    --map-prio:ex:--add-set,--del-set,--exist,--timeout \
+    --map-queue:ex:--add-set,--del-set,--exist,--timeout \
+    --timeout:ex:--del-set,--map-set,--map-mark,--map-prio,--map-queue \
+    --exist:ex:--del-set,--map-set,--map-mark,--map-prio,--map-queue"
+"SNAT all nat --to-source:need --random:ex:--persistent,--random-fully:proto:tcp,udp,dccp,sctp \
+    --random-fully:ex:--persistent,--random:proto:tcp,udp,dccp,sctp --persistent:ex:--random,--random-fully"
+"SNPT v6 mangle --src-pfx --dst-pfx"
+"SYNPROXY all mangle --sack-perm --timestamp --wscale --mss"
+"TEE all all --gateway" # TODO: really valid in all tables? cmdline does not forbid it
+"TCPMSS all mangle --set-mss:ex:--clamp-mss-to-pmtu --clamp-mss-to-pmtu:ex:--set-mss"
+"TCPOPTSTRIP all mangle --strip-options"
+"TOS all mangle --set-tos:ex:--and-tos,--or-tos,--xor-tos \
+    --and-tos:ex:--set-tos,--or-tos, --xor-tos \
+    --or-tos:ex:--set-tos,--and-tos,--xor-tos \
+       --xor-tos:ex:--set-tos,--and-tos,--or-tos"
+"TPROXY all mangle --on-port:proto:tcp,udp:need --on-ip:proto:tcp,udp --tproxy-mark"
+"TRACE all raw"
+"TTL v4 mangle --ttl-set:ex:--ttl-dec,--ttl-inc \
+   --ttl-dec:ex:--ttl-set,--ttl-inc \
+   --ttl-inc:ex:--ttl-set,--ttl-dec"
+"ULOG v4 all --ulog-nlgroup --ulog-prefix --ulog-cprange --ulog-qthreshold"
+)
+
+# match-name all|v4|v6 all|table1,table2,... option[,alias] [...]
+local arr_matches=(
+"tcp all all --source-port,--sport --destination-port,--dport --tcp-flags --syn --tcp-option"
+"udp all all --source-port,--sport --destination-port,--dport"
+"icmp v4 all --icmp-type"
+"icmp6 v6 all --icmpv6-type"
+"addrtype all all --src-type:need1 --dst-type:need1 --limit-iface-in:neg:no --limit-iface-out:neg:no"
+"ah v4 all --ahspi"
+"ah v6 all --ahspi --ahlen --ahres:neg:no"
+"bpf all all --bytecode"
+"cgroup all all --cgroup:ex:--path --path:ex:--cgroup"
+"cluster all all --cluster-total-nodes:neg:no:need --cluster-local-node:need1 \
+    --cluster-local-nodemask:need1 --cluster-hash-seed:neg:no:need"
+"comment all all --comment:neg:no"
+"connbytes all all --connbytes:need --connbytes-dir:neg:no:need --connbytes-mode:neg:no:need"
+"connlabel all all --label:need --set:neg:no"
+"connlimit all all --connlimit-upto:ex:--connlimit-above:neg:no:need1 \
+    --connlimit-above:ex:--connlimit-upto:neg:no:need1 \
+    --connlimit-mask:neg:no --connlimit-saddr:neg:no --connlimit-daddr:neg:no" # TODO: valid in all tables?
+"connmark all all --mark"
+"conntrack all all --ctstate --ctproto --ctorigsrc --ctorigdst --ctreplsrc \
+    --ctrepldst --ctorigsrcport --ctorigdstport --ctreplsrcport \
+    --ctrepldstport --ctstatus --ctexpire --ctdir:neg:no"
+"cpu all all --cpu"
+"dccp all all --source-port,--sport --destination-port,--dport --dccp-types --dccp-option"
+"devgroup all all --src-group --dst-group" # TODO: retrieve names of device groups
+"dscp all all --dscp:ex:--dscp-class --dscp-class:ex:--dscp"
+"dst v6 all --dst-len --dst-opts:neg:no" # TODO: valid values, --dst-opts value only twice?
+"ecn all all --ecn-tcp-cwr --ecn-tcp-ece --ecn-ip-ect"
+"esp all all --espspi"
+"eui64 v6 all"
+"frag v6 all --fragid --fraglen --fragres:neg:no --fragfirst:neg:no \
+    --fragmore:neg:no --fraglast:neg:no" # TODO: any option mandatory?
+"hashlimit all all --hashlimit-upto:ex:--hashlimit-above:neg:no:need1 \
+    --hashlimit-above:ex:--hashlimit-upto:neg:no:need1 \
+    --hashlimit-burst:neg:no --hashlimit-mode:neg:no \
+    --hashlimit-srcmask:neg:no --hashlimit-dstmask:neg:no \
+    --hashlimit-name:neg:no:need --hashlimit-htable-size:neg:no \
+    --hashlimit-htable-max:neg:no --hashlimit-htable-expire:neg:no \
+    --hashlimit-htable-gcinterval:neg:no"
+"hbh v6 all --hbh-len --hbh-opts:neg:no"
+"helper all all --helper"
+"hl v6 all --hl-eq:ex:--hl-gt,--hl-lt \
+    --hl-gt:ex:--hl-eq,--hl-lt:neg:no \
+    --hl-lt:ex:--hl-eq,--hl-gt:neg:no"
+"ipcomp all all --ipcompspi --compres:neg:no"
+"iprange all all --src-range --dst-range"
+"ipv6header v6 all --header:need --soft:neg:no"
+"ipvs all all --ipvs --vproto --vaddr --vport --vdir:neg:no --vmethod --vportctl"
+"length all all --length"
+"mac all all --mac-source"
+"mh v6 all --mh-type"
+"owner all all --uid-owner --gid-owner --socket-exists"
+"limit all all --limit:neg:no --limit-burst:neg:no"
+"mark all all --mark"
+"multiport all all --source-ports,--sports:ex:--ports \
+    --destination-ports,--dports:ex:--ports \
+    --ports:ex:--source-ports,--sports,--destination-ports,--dports"
+"nfacct all all --nfacct-name:neg:no"
+"osf all all --genre:need --ttl:neg:no --log:neg:no"
+"physdev all all --physdev-in --physdev-out --physdev-is-in --physdev-is-out --physdev-is-bridged"
+"pkttype all all --pkt-type"
+"policy all all --dir:neg:no:need --pol:neg:no:need --strict:neg:no --reqid --spi --proto --mode \
+    --tunnel-src:optis:--mode=tunnel --tunnel-dst:optis:--mode=tunnel --next:neg:no" # TODO: reqid value?
+"quota all all --quota"
+"rateest all all --rateest-delta:neg:no \
+    --rateest-lt:ex:--rateest-gt,--rateest-eq \
+    --rateest-gt:ex:--rateest-lt,--rateest-eq \
+    --rateest-eq:ex:--rateest-lt,--rateest-gt \
+    --rateest:ex:--rateest1,--rateest2,--rateest-bps1,--rateest-bps2,--rateest-pps1,--rateest-pps2:neg:no:need1 \
+    --rateest1:ex:--rateest,--rateest-bps,--rateest-pps:neg:no:need1 \
+    --rateest2:ex:--rateest,--rateest-bps,--rateest-pps:neg:no:need1 \
+    --rateest-bps:ex:--rateest1,--rateest2,--rateest-bps1,--rateest-bps2,--rateest-pps1,--rateest-pps2:neg:no \
+    --rateest-pps:ex:--rateest1,--rateest2,--rateest-bps1,--rateest-bps2,--rateest-pps1,--rateest-pps2:neg:no \
+    --rateest-bps1:ex:--rateest,--rateest-bps,--rateest-pps:neg:no \
+    --rateest-bps2:ex:--rateest,--rateest-bps,--rateest-pps:neg:no \
+    --rateest-pps1:ex:--rateest,--rateest-bps,--rateest-pps:neg:no \
+    --rateest-pps2:ex:--rateest,--rateest-bps,--rateest-pps:neg:no"
+"realm v4 all --realm"
+"recent all all --name:neg:no --set:ex:--rcheck,--update,--remove:need1 \
+    --rcheck:ex:--set,--update,--remove:need1 \
+    --update:ex:--set,--rcheck,--remove:need1 \
+    --remove:ex:--set,--rcheck,--update:need1 \
+    --rttl:ex:--set,--remove:neg:no \
+    --rsource:neg:no --rdest:neg:no --mask:neg:no --seconds:neg:no \
+    --reap:neg:no --hitcount:neg:no"
+"rpfilter all mangle,raw --loose:neg:no --validmark:neg:no \
+    --accept-local:neg:no --invert:neg:no"
+"rt v6 all --rt-type --rt-segsleft --rt-len --rt-0-res:neg:no \
+    --rt-0-addrs:neg:no --rt-0-not-strict:neg:no"
+"sctp all all --source-port,--sport --destination-port,--dport --chunk-types"
+"set all all --match-set:need --return-nomatch:neg:no --update-counters --update-subcounters
+    --packets-eq:ex:--packets-lt,--packets-gt \
+    --packets-lt:neg:no:ex:--packets-eq,--packets-gt \
+    --packets-gt:neg:no:ex:--packets-eq,--packets-lt
+    --bytes-eq:ex:--bytes-lt,--bytes-gt \
+    --bytes-lt:neg:no:ex:--bytes-eq,--bytes-gt \
+    --bytes-gt:neg:no:ex:--bytes-eq,--bytes-lt"
+"socket all all --transparent:neg:no --nowildcard:neg:no --restore-skmark:neg:no"
+"state all all --state"
+"statistic all all --mode:neg:no:need --probability:optis:--mode=random \
+    --every:optis:--mode=nth --packet:optis:--mode=nth:neg:no"
+"string all all --algo:neg:no:need --from:neg:no --to:neg:no \
+    --string:ex:--hex-string --hex-string:ex:--string --icase:neg:no"
+"tcpmss all all --mss"
+"time all all --datestart:neg:no --timestart:neg:no --timestop:neg:no \
+    --monthdays --weekdays --kerneltz:neg:no --contiguous:neg:no" # TODO: --date[start|stop] value verification
+"tos all all --tos"
+"ttl v4 all --ttl-eq:ex:--ttl-gt,--ttl-lt \
+    --ttl-gt:ex:--ttl-eq,--ttl-lt:neg:no \
+    --ttl-lt:ex:--ttl-eq,--ttl-gt:neg:no"
+"u32 all all --u32"
+"unclean v4 all" # don't have it
+)
+
+COMPREPLY=()
+
+# expecting _get_comp_words_by_ref() to exist from bash_completion
+if declare -f _get_comp_words_by_ref &>/dev/null; then
+    got_bashcompl=1
+    _get_comp_words_by_ref -n : cur prev cword words || return
+else # not so neat, but a workaround
+    COMP_WORDBREAKS="${COMP_WORDBREAKS//:/}"
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    prev="${COMP_WORDS[COMP_CWORD-1]}"
+    cword=$COMP_CWORD
+    for i in ${!COMP_WORDS[@]}; do words[i]="${COMP_WORDS[i]}"; done
+fi
+
+# show debug information if env var is set
+#_DEBUG_NF_COMPLETION=Y
+if [[ $_DEBUG_NF_COMPLETION ]]; then
+    printf "\nCOMP_WORDBREAKS: <%s>\n" "$COMP_WORDBREAKS"
+    printf "COMP_LINE: <%s>\n" "$COMP_LINE"
+    printf "COMP_TYPE: <%s>\n" "$COMP_TYPE"
+    printf "COMP_POINT: <%s>\n" "$COMP_POINT"
+    printf "COMP_KEY: <%s>\n" "$COMP_KEY"
+    printf "COMP_CWORD: <%s>\n" "$COMP_CWORD"
+    printf "cword: <%s>\n" "$cword"
+    printf "words:\n" "<%s>\n" "${words[@]}"
+    printf "cur: <%s> prev: <%s>\n" "$cur" "$prev"
+fi
+
+# set default values if variables are unset
+: ${_IPT_OPTS_ON_START:=default_no} # default: show all options at start
+: ${_IPT_OPT_STYLE:=long} # default: show long options only
+
+local arr_opts=(
+"-d --destination"
+"-f --fragment"
+"-g --goto"
+"-h --help"
+"-i --in-interface"
+"-j --jump"
+"-m --match"
+"-n --numeric"
+"-o --out-interface"
+"-p --protocol"
+"-s --source"
+"-t --table"
+"-v --verbose"
+"-w --wait"
+"-x --exact"
+"-A --append"
+"-C --check"
+"-D --delete"
+"-E --rename-chain"
+"-F --flush"
+"-I --insert"
+"-L --list"
+"-N --new"
+"-P --policy"
+"-R --replace"
+"-S --list-rules"
+"-V --version"
+"-X --delete-chain"
+"-Z --zero"
+--line-numbers
+--set-counters
+--modprobe=
+)
+
+# gather information about the command line
+for ((i=1; i < ${#words[@]}-1; i++)); do
+    if [[ ${words[i]} = @(-j|--jump|-g|--goto) ]]; then
+        if ((cword > last_index && cword > i)); then
+            str_last_is=target
+            last_option_index=$i
+            arr_last_opts+=(${words[i]})
+        fi
+        str_jump=${words[i]} str_target="${words[i+1]}"
+        target_index=$((i+1)) last_index=$i
+        arr_cmd_opts+=(${words[i]})
+    elif [[ ${words[i]} = @(-t|--table) ]]; then
+        if ((cword > last_index && cword > i)); then
+            str_last_is=option
+            last_option_index=$i
+            arr_last_opts+=(${words[i]})
+        fi
+        str_table="${words[i+1]}"
+        arr_cmd_opts+=(${words[i]})
+        last_index=$i
+    elif [[ ${words[i]} = @(-A|--append|-C|--check|-E|--rename-chain|-F|--flush|-P|--policy|-X|--delete-chain) ]]
+    then
+        let got_action+=1
+        if ((cword > last_index && cword > i)); then
+            str_last_is=action
+            arr_last_opts+=(${words[i]})
+            last_option_index=$i
+         fi
+        last_index=$i
+        [[ ${words[i+1]} != -* ]] && str_chain="${words[i+1]}"
+        str_action=${words[i]}
+        arr_cmd_opts+=(${words[i]})
+    elif [[ ${words[i]} = @(-D|--delete|-I|--insert|-R|--replace|-L|--list|-S|--list-rules|-Z|--zero) ]]
+    then
+        let got_action+=1
+        if ((cword > last_index && cword > i)); then
+            str_last_is=action
+            arr_last_opts+=(${words[i]})
+            last_option_index=$i
+        fi
+        last_index=$i
+        [[ ${words[i+1]} != -* ]] && str_chain="${words[i+1]}"
+        str_action=${words[i]}
+        rulenum=$([[ ${words[${i}+2]} = +([[:digit:]]) ]] && printf "%d\n" ${words[${i}+2]})
+        arr_cmd_opts+=(${words[i]})
+    elif [[ ${words[i]} = @(-N|--new) ]]; then
+        if [[ $str_target = CLUSTERIP ]] && ((i == target_index+1)); then
+            # CLUSTERIP also has --new
+            continue
+        fi
+        let got_action+=1
+        if ((cword > last_index && cword > i)); then
+            str_last_is=action
+            arr_last_opts+=(${words[i]})
+            last_option_index=$i
+         fi
+        last_index=$i
+        [[ ${words[i+1]} != -* ]] && str_chain="${words[i+1]}"
+        str_action=${words[i]}
+        arr_cmd_opts+=(${words[i]})
+    elif [[ ${words[i]} = @(-m|--match) ]]; then
+        if ((cword > last_index && cword > i)); then
+            str_last_is=match
+            last_match_index=$((i+1))
+            arr_cmd_matches+=("${words[i+1]}")
+        fi
+        last_index=$i
+        arr_cmd_opts+=(${words[i]})
+        got_rule_opt=1
+        arr_all_matches+=("${words[i+1]}")
+        if [[ ${words[i+1]} = @(icmp|tcp|udp|ah|mh|ipcomp|esp|dccp|sctp) ]]
+        then
+            str_proto_match="${words[i+1]}"
+        fi
+    elif [[ ${words[i]} = @(-p|--proto|--protocol) ]]; then
+        if [[ ${words[i]} = --proto ]]; then
+            if ((${#arr_cmd_matches[@]})) && \
+                [[ ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} = policy ]]
+            then
+                continue # policy match also has --proto !!DAMN!!
+            fi
+        fi
+        if [[ ${words[i-1]} != \! ]]; then
+            str_proto="${words[i+1]}"
+            if [[ $str_proto = +([[:digit:]]) ]]; then
+                # translate known numeric proto specs into names
+                for x in ${!arr_proto_defs[@]}; do
+                    set -- ${arr_proto_defs[x]}
+                    if ((str_proto == $2)); then
+                        str_proto=$1
+                        # if proto is a known match, add it to list of matches
+                        if ((cword > last_index && cword > i)); then
+                            str_last_is=match
+                            last_match_index=$((i+1))
+                            arr_cmd_matches+=("$str_proto")
+                        else
+                            str_last_is=option
+                            arr_last_opts+=(${words[i]})
+                            last_option_index=$i
+                        fi
+                        break
+                    fi
+                done
+            else
+                # if proto is a known match, add it to list of matches
+                if ((cword > last_index && cword > i)); then
+                    if _iptables_is_match "${words[i+1]}"; then
+                        str_last_is=match
+                        arr_cmd_matches+=("$str_proto")
+                        last_match_index=$((i+1))
+                    else
+                        str_last_is=option
+                        arr_last_opts+=(${words[i]})
+                        last_option_index=$i
+                    fi
+                fi
+            fi
+        else
+            proto_negated=1
+            if ((cword > last_index && cword > i)); then
+                str_last_is=option
+                arr_last_opts+=(${words[i]})
+                last_option_index=$i
+            fi
+        fi
+        last_index=$i
+        got_rule_opt=1
+        arr_cmd_opts+=(${words[i]})
+    elif [[ ${words[i]} = @(-d|--destination|-f|--fragment|-i|--in-interface|-o|--out-interface|-s|--source|--modprobe=*) ]]
+    then
+        if ((cword > last_index && cword > i)); then
+            str_last_is=option
+            arr_last_opts+=(${words[i]})
+            last_option_index=$i
+        fi
+        last_index=$i
+        arr_cmd_opts+=(${words[i]})
+        got_rule_opt=1
+    elif [[ ${words[i]} = @(-w|--wait|-v|--verbose|--ipv4|-4|--ipv6|-6) ]]
+    then
+        if ((cword > last_index && cword > i)); then
+            str_last_is=option
+            arr_last_opts+=(${words[i]})
+            last_option_index=$i
+        fi
+        last_index=$i
+        arr_cmd_opts+=(${words[i]})
+    elif [[ ${words[i]} = --line-numbers ]]; then
+        list_only=1
+        if ((cword > last_index && cword > i)); then
+            str_last_is=option
+            arr_last_opts+=(${words[i]})
+            last_option_index=$i
+        fi
+        last_index=$i
+        rulenum=$([[ ${words[${i}+2]} = +([[:digit:]]) ]] && printf "%d\n" ${words[${i}+2]})
+        arr_cmd_opts+=(${words[i]})
+    elif [[ ${words[i]} = --set-counters ]]; then
+        set_counters=1
+        if ((cword > last_index && cword > i)); then
+            str_last_is=option
+            arr_last_opts+=(${words[i]})
+            last_option_index=$i
+        fi
+        last_index=$i
+        arr_cmd_opts+=(${words[i]})
+    elif [[ ${words[i]} = @(--exact|-x|-n|--numeric) ]]; then
+        list_only=1
+        if ((cword > last_index && cword > i)); then
+            str_last_is=option
+            arr_last_opts+=(${words[i]})
+            last_option_index=$i
+        fi
+        last_index=$i
+        arr_cmd_opts+=(${words[i]})
+    elif [[ ${words[i]} = @(--match-set|--add-set|--del-set|--map-set) ]]; then
+        if [[ ${words[i+1]} = -?* && ${words[i+1]} != $cur ]]; then
+            # quit if we have a set name overlapping an option name
+            for x in ${arr_opts[*]}; do
+                [[ $x = ${words[i+1]} ]] && return 0
+            done
+            # register weird set names
+            arr_set_names+=("${words[i+1]}")
+        fi
+    fi
+done
+if ((got_action > 1)); then # allow only one main command
+    return 0
+fi
+
+# init with empty string so we don't run into bad subscript error
+((${#arr_cmd_matches[@]})) || arr_cmd_matches[0]=""
+((${#arr_cmd_opts[@]})) || arr_cmd_opts[0]=""
+((${#arr_last_opts[@]})) || arr_last_opts[0]=""
+
+# debug info about the gathered information
+if [[ $_DEBUG_NF_COMPLETION ]]; then
+    printf "arr_cmd_matches[*]: %s\n" "${arr_cmd_matches[*]}"
+    printf "arr_cmd_opts[*]: %s\n" "${arr_cmd_opts[*]}"
+    printf "arr_last_opts[*]: %s\n" "${arr_last_opts[*]}"
+#    printf "str_chain: %s\n" "$str_chain"
+    printf "str_jump: %s\n" "$str_jump"
+    printf "str_target: %s\n" "$str_target"
+    printf "str_proto: %s\n" "$str_proto"
+    printf "target_index: %s\n" "$target_index"
+    printf "last_match_index: %s\n" "$last_match_index"
+    printf "last_option_index: %s\n" "$last_option_index"
+    printf "last_index: %s\n" "$last_index"
+    printf "proto_negated: %s\n" "$proto_negated"
+fi
+
+# time to match on current word request
+if [[ $cur = -* ]]; then # any option is requested
+    if ((cword > 1)); then
+        if [[ $str_target = \! ]] && ((cword == target_index+1)); then # can't be valid target name
+            return 0
+        fi
+        if [[ $str_target = CLUSTERIP ]]; then # CLUSTERIP rules must start with --new
+            if [[ ${words[target_index+2]} && ${words[target_index+1]} != --new ]]
+            then return 0
+            fi
+        fi
+        # depend on previous option - terminating choices first
+        # option is requested but not valid in the cases following
+        if [[ $prev = @(-h|--help|-V|--version) ]]
+        then return 0
+        elif [[ $prev = @(-A|--append|-C|--check|-D|--delete|-E|--rename-chain|-I|--insert|-N|-P|--policy|-R|--replace) ]]
+        then return 0
+        elif [[ $prev = @(-j|--jump|-g|--goto|-i|--in-interface|-o|--out-interface|-m|--match) ]]
+        then return 0
+        elif [[ $prev = @(-p|--proto|--protocol|-s|--source|-d|--destination|-t|--table|--set-counters) ]]
+        then return 0
+        elif [[ $prev = --@(dport|destination-port|sport|source-port) ]]
+        then return 0
+        elif [[ $prev = --@(icmp-type|icmpv6-type|tcp-option|tcp-flags) ]]
+        then return 0
+        elif [[ $prev = --@(add-set|del-set|match-set|map-set) ]]
+        then # set match and SET target
+            # if the set names start with a dash
+            COMPREPLY=( $( compgen -W '$(PATH=${PATH}:/sbin command ipset -n list)' -- $cur ) )
+            ((got_bashcompl)) && __ltrim_colon_completions "$cur"
+            return 0
+        elif [[ $prev = --new ]] # CLUSTERIP also has --new
+        then
+            if ! ( [[ $str_target = CLUSTERIP ]] && ((cword == target_index+2)) ); then
+                return 0
+            fi
+        # various matches with just one option suitable here
+        elif [[ $prev = --@(comment|chunk-types|cpu|ecn-ip-ect|helper) ]]
+        then return 0
+        elif [[ $prev = --@(length|mac-source|mark|mss|pkt-type|quota|realm|tos) ]]
+        then return 0
+        elif [[ $prev = --@(mh-type|nfacct-name|ahspi|espspi) ]]
+        then return 0
+        elif [[ $prev = --@(bytecode|ipcompspi|cgroup|path) ]]
+        then return 0
+        elif [[ $prev = --@(gateway|selctx|set-class|set-mss|label|type) ]]
+        then return 0
+        elif [[ $prev = --@(state|header|strip-options|u32) ]]
+        then return 0
+        elif [[ $prev = --@(src-type|dst-type) ]]
+        then return 0 # addrtype match
+        elif [[ $prev = --@(cluster-total-nodes|cluster-local-node|cluster-local-nodemask|cluster-hash-seed) ]]
+        then return 0 # cluster match
+        elif [[ $prev = --@(connbytes|connbytes-dir|connbytes-mode) ]]
+        then return 0 # connbytes match
+        elif [[ $prev = --@(connlimit-upto|connlimit-above|connlimit-mask) ]]
+        then return 0 # connlimit match
+        elif [[ $prev = --ct@(state|proto|origsrc|origdst|replsrc|repldst|origsrcport|origdstport|replsrcport|repldstport|status|expire|dir) ]]
+        then return 0 # conntrack match
+        elif [[ $prev = --@(dccp-option|dccp-types) ]]
+        then return 0 # dccp match
+        elif [[ $prev = --@(src-group|dst-group) ]]
+        then return 0 # devgroup match
+        elif [[ $prev = --@(dst-len|dst-opts) ]]
+        then return 0 # dst match
+        elif [[ $prev = --@(fragid|fraglen) ]]
+        then return 0 # frag match
+        elif [[ $prev = --hashlimit-@(upto|above|burst|mode|srcmask|dstmask|name|htable-size|htable-max|htable-expire|htable-gcinterval) ]]
+        then return 0 # hashlimit match
+        elif [[ $prev = --@(hbh-len|hbh-opts) ]]
+        then return 0 # hbh match
+        elif [[ $prev = --@(src-range|dst-range) ]]
+        then return 0 # iprange match
+        elif [[ $prev = --@(vproto|vaddr|vport|vdir|vmethod|vportctl) ]]
+        then return 0 # ipvs match
+        elif [[ $prev = --@(limit|limit-burst) ]]
+        then return 0 # limit match
+        elif [[ $prev = --@(dports|destination-ports|sports|source-ports|ports) ]]
+        then return 0 # multiport match
+        elif [[ $prev = --@(genre|ttl|log|) ]]
+        then return 0 # osf match
+        elif [[ $prev = --@(uid-owner|gid-owner) ]]
+        then return 0 # owner match
+        elif [[ $prev = --@(dir|pol|reqid|spi|tunnel-src|tunnel-dst) ]]
+        then return 0 # policy match
+        elif [[ $prev = --@(physdev-in|physdev-out) ]]
+        then return 0 # physdev match
+        elif [[ $prev = --rateest?(1|2|-bps|-pps|-bps1|-bps2|-pps1|-pps2) ]]
+        then return 0 # rateest match
+        elif [[ $prev = --@(name|mask|seconds|hitcount) ]]
+        then return 0 # recent match
+        elif [[ $prev = --@(rt-type|rt-segsleft|rt-len|rt-0-addrs) ]]
+        then return 0 # rt match
+        elif [[ $prev = --@(packets-eq|packets-lt|packets-gt|bytes-eq|bytes-lt|bytes-gt) ]]
+        then return 0 # set match
+        elif [[ $prev = --@(transparent|nowildcard|restore-skmark) ]]
+        then return 0 # socket match
+        elif [[ $prev = --@(mode|probability|every|packet) ]]
+        then return 0 # statistic match
+        elif [[ $prev = --@(algo|from|to|string|hex-string) ]]
+        then return 0 # string match - NETMAP target also has --to
+        elif [[ $prev = --@(datestart|datestop|timestart|timestop|monthdays|weekdays) ]]
+        then return 0 # time match
+        elif [[ $prev = --@(hashmode|clustermac|total-nodes|local-node|hash-init) ]]
+        then return 0 # CLUSTERIP target
+        elif [[ $prev = --@(set-xmark|and-mark|or-mark|xor-mark|set-mark|nfmask|ctmask) ]]
+        then return 0 # CONNMARK target
+        elif [[ $prev = --@(ctevents|expevents|zone|zone-orig|zone-reply|timeout) ]]
+        then return 0 # CT target
+        elif [[ $prev = --@(src-pfx|dst-pfx) ]]
+        then return 0 # DNPT/SNPT target
+        elif [[ $prev = --@(set-dscp|dscp|dscp-class|set-dscp-class) ]]
+        then return 0 # DSCP target / dscp match
+        elif [[ $prev = --@(hl-set|hl-dec|hl-inc|hl-eq|hl-gt|hl-lt) ]]
+        then return 0 # HL target / hl match
+        elif [[ $prev = --hmark-@(tuple|mod|offset|src-prefix|dst-prefix|sport-mask|dport-mask|spi-mask|proto-mask|rnd) ]]
+        then return 0 # HMARK target
+        elif [[ $prev = --@(led-trigger-id|led-delay) ]]
+        then return 0 # LED target
+        elif [[ $prev = --@(log-level|log-prefix) ]]
+        then return 0 # LOG target
+        elif [[ $prev = --@(nflog-group|nflog-prefix|nflog-range|nflog-threshold) ]]
+        then return 0 # NFLOG target
+        elif [[ $prev = --@(queue-num|queue-balance) ]]
+        then return 0 # NFQUEUE target
+        elif [[ $prev = --@(rateest-name|rateest-interval|rateest-ewmalog) ]]
+        then return 0 # RATEEST target
+        elif [[ $prev = --@(reject-with|to-destination|to-source|to-ports) ]]
+        then return 0 # MASQUERADE / REJECT / DNAT / SNAT target
+        elif [[ $prev = --@(mss|wscale) ]]
+        then return 0 # SYNPROXY target
+        elif [[ $prev = --@(set-tos|and-tos|or-tos|xor-tos) ]]
+        then return 0 # TOS target
+        elif [[ $prev = --@(on-port|on-ip|tproxy-mark) ]]
+        then return 0 # TPROXY target
+        elif [[ $prev = --@(ttl-set|ttl-dec|ttl-inc|ttl-eq|ttl-gt|ttl-lt) ]]
+        then return 0 # TTL target / ttl match
+        elif [[ $prev = --@(ulog-nlgroup|ulog-prefix|ulog-cprange|ulog-qthreshold) ]]
+        then return 0 # ULOG target
+        fi
+        i=$((cword - 2))
+        if ((i >= 1)); then # depend on option before previous option
+            if [[ ${words[i]} = @(-E|--rename-chain|-P|--policy|-R|--replace|--set-counters|--chunk-types) ]]
+            then return 0
+            elif [[ ${words[i]} = @(--chunk-types|--tcp-flags) ]]
+            then return 0
+            elif [[ ${words[i]} = --@(add-set|del-set|match-set|map-set) ]]
+            then return 0 # expecting set flag
+            fi
+            # if _IPT_VALIDATE_INPUT is defined and not empty, validate users input
+            # stop completion if invalid input is detected
+            if [[ $_IPT_VALIDATE_INPUT ]]; then
+                # the following options expect an integer
+                if [[ ${words[i]} = --@(hl-set|hl-dec|hl-inc|hl-eq|hl-gt|hl-lt) ]]
+                then [[ $prev = +([[:digit:]]) ]] || return 0
+                elif [[ ${words[i]} = --@(ttl-set|ttl-dec|ttl-inc|ttl-eq|ttl-gt|ttl-lt|ttl) ]]
+                then [[ $prev = +([[:digit:]]) ]] || return 0
+                elif [[ ${words[i]} = --@(nflog-group|nflog-range|nflog-threshold|ulog-cprange|ulog-qthreshold) ]]
+                then [[ $prev = +([[:digit:]]) ]] || return 0
+                elif [[ ${words[i]} = --@(total-nodes|local-node|cluster-total-nodes|cluster-local-node) ]]
+                then [[ $prev = +([[:digit:]]) ]] || return 0
+                elif [[ ${words[i]} = --@(dccp-option|led-delay|from|to|every|packet) ]]
+                then [[ $prev = +([[:digit:]]) ]] || return 0
+                elif [[ ${words[i]} = --@(rt-type|rt-len|log|fraglen|ahlen|spi) ]]
+                then [[ $prev = +([[:digit:]]) ]] || return 0
+                elif [[ ${words[i]} = --@(limit-burst|connlimit-upto|connlimit-above) ]]
+                then [[ $prev = +([[:digit:]]) ]] || return 0
+                elif [[ ${words[i]} = --@(seconds|hitcount|hbh-len|dst-len|hmark-offset|cgroup) ]]
+                then [[ $prev = +([[:digit:]]) ]] || return 0
+                elif [[ ${words[i]} = --@(packets-eq|packets-lt|packets-gt|bytes-eq|bytes-lt|bytes-gt) ]]
+                then
+                     # set match - digit|xdigit
+                    [[ $prev = @(+([[:digit:]])|0[xX]+([[:xdigit:]])) ]] || return 0
+                elif [[ ${words[i]} = --hashlimit-@(burst|buckets|entries|htable-expire|htable-gcinterval) ]]
+                then
+                    [[ $prev = +([[:digit:]]) ]] || return 0 # hashlimit match - digit value expected
+                    return 0
+                elif [[ ${words[i]} = --@(hashlimit-srcmask|hashlimit-dstmask) ]]
+                then
+                    [[ $prev = +([[:digit:]]) ]] || return 0
+                    ((prev > 32)) && return 0
+                elif [[ ${words[i]} = @(--zone|--zone-orig|--zone-reply) ]]
+                then # CT target | 16bit digit|xdigit
+                    [[ $prev = @(+([[:digit:]])|0[xX]+([[:xdigit:]])) ]] || return 0
+                    ((prev >= 0 && prev <= 0xFFFF)) || return 0
+                elif [[ ${words[i]} = --timeout ]]
+                then
+                    if [[ $str_target = @(IDLETIMER|SET) ]]; then
+                        [[ $prev = +([[:digit:]]) ]] || return 0 # IDELTIMER,SET targets - digit value expected
+                        # CT target also has --timeout
+                    fi
+                elif [[ ${words[i]} = --hmark-mod ]]; then # HMARK target - int >0
+                    [[ $prev = +([[:digit:]]) ]] || return 0
+                    ((prev > 0)) || return 0
+                elif [[ ${words[i]} = @(--hmark-sport-mask|--hmark-dport-mask) ]]; then # HMARK target - 16 bit xdigit
+                    [[ $prev = 0[xX]+([[:xdigit:]]) ]] || return 0
+                    ((prev >= 0 && prev <= 0xFFFF)) || return 0
+                elif [[ ${words[i]} = --mask ]]; then # CONNMARK target and recent match have --mask
+                    if [[ $str_target = CONNMARK ]]; then
+                        [[ $prev = @(0[xX]+([[:xdigit:]])|+([[:digit:]])) ]] || return 0
+                        ((prev <= 4294967295)) || return 0
+                    elif [[ ${arr_matches[${#arr_matches[@]}-1]} = recent ]]; then
+                        [[ $prev = +([[:digit:]]) ]] || return 0
+                        ((prev > 32)) && return 0
+                    fi
+                elif [[ ${words[i]} = --hmark-proto-mask ]]; then # 8bit digit or xdigit value | HMARK target
+                    [[ $prev = @(0[xX]+([[:xdigit:]])|+([[:digit:]])) ]] || return 0
+                    ((prev <= 0xFF)) || return 0
+                elif [[ ${words[i]} = --@(cluster-local-nodemask|ct-mask|nf-mask|and-mark|or-mark|xor-mark|hmark-spi-mask|hmark-rnd|reqid|wscale) ]]
+                then
+                    # 32bit digit or xdigit value expected
+                    [[ $prev = @(0[xX]+([[:xdigit:]])|+([[:digit:]])) ]] || return 0
+                    ((prev <= 4294967295)) || return 0
+                elif [[ ${words[i]} = --@(mark|set-mark|set-xmark|tproxy-mark) ]]; then
+                    # [CONN]MARK/TPROXY target mark[/mask], [conn]mark match
+                    [[ $prev = @(+([[:digit:]])|0[xX]+([[:xdigit:]]))?(/@(+([[:digit:]])|0[xX]+([[:xdigit:]]))) ]] || return 0
+                    for str_tmp in ${prev//\// }; do
+                        ((str_tmp <= 4294967295)) || return 0
+                    done
+                elif [[ ${words[i]} = --@(fragid|mss|rt-segsleft|ctexpire) ]]; then
+                    # numeric range definition with `:' expected -> COMP_WORDBREAKS `:' problem
+                    # SYNPROXY target also has --mss, will keep it without a more detailed check for now
+                    [[ $prev = +([[:digit:]])?(:+([[:digit:]])) ]] || return 0
+                elif [[ ${words[i]} = --ipcompspi ]]; then
+                    # xdigit?(:xdigit)
+                    # numeric range definition with `:' expected -> COMP_WORDBREAKS `:' problem
+                    [[ $prev = 0[xX]+([[:xdigit:]])?(:0[xX]+([[:xdigit:]])) ]] || return 0
+                elif [[ ${words[i]} = @(--ahspi|--espspi) ]]; then # 32 bit range values -> COMP_WORDBREAKS `:' problem
+                    [[ $prev = +([[:digit:]])?(:+([[:digit:]])) ]] || return 0
+                    for str_tmp in ${prev/:/ }; do
+                        ((str_tmp <= 4294967295)) || return 0
+                    done
+                elif [[ ${words[i]} = --connlimit-mask ]]; then # connlimit match
+                    [[ $prev = +([[:digit:]]) ]] || return 0
+                    if [[ $str_app = iptables ]]; then
+                        ((prev > 32)) && return 0
+                    elif [[ $str_app = ip6tables ]]; then
+                        ((prev > 128)) && return 0
+                    fi
+                elif [[ ${words[i]} = --label ]]; then # IDLETIMER target (connlabel match also has --label)
+                    if [[ $str_target = IDELETIMER && $str_last_is = target ]]; then
+                        ((${#prev} > 27)) && return 0
+                    fi
+                elif [[ ${words[i]} = --log-prefix ]]; then # LOG target
+                    ((${#prev} > 29)) && return 0
+                elif [[ ${words[i]} = --nflog-prefix ]]; then # NFLOG target
+                    ((${#prev} > 64)) && return 0
+                elif [[ ${words[i]} = --ulog-prefix ]]; then # ULOG target
+                    ((${#prev} > 32)) && return 0
+                elif [[ ${words[i]} = --@(limit|hashlimit-upto|hashlimit-above) ]]; then # [hash]limit match
+                    [[ $prev = +([[:digit:]])?(/@(second|minute|hour|day)) ]] || return 0
+                elif [[ ${words[i]} = --probability ]]; then # statistic match
+                    [[ $prev = @(0?(.[0-9])|1?(.0)) ]] || return 0
+                elif [[ ${words[i]} = --queue-num ]]; then # NFQUEUE target
+                    [[ $prev = +([[:digit:]]) ]] || return 0
+                    ((prev > 65535)) && return 0
+                elif [[ ${words[i]} = --ulog-nlgroup ]]; then # ULOG target
+                    [[ $prev = +([[:digit:]]) ]] || return 0
+                    ((prev < 1 || prev > 32)) && return 0
+                elif [[ ${words[i]} = --@(dscp|set-dscp) ]]; then # dscp match and DSCP target - digit or 0xxdigit
+                    [[ $prev = @(+([[:digit:]])|0[xX]+([[:xdigit:]])) ]] || return 0
+                    ((prev > 63)) && return 0
+                elif [[ ${words[i]} = @(--hbh-opts|--dst-opts) ]]; then # hbh and dst match -> COMP_WORDBREAKS `:' problem
+                    [[ $prev = +([[:digit:]]):+([[:digit:]])*(,+([[:digit:]]):+([[:digit:]])) ]] || return 0
+                elif [[ ${words[i]} = --to-ports ]]; then # MASQUERADE/REDIRECT target
+                    [[ $prev = +([[:digit:]])-+([[:digit:]]) ]] || return 0
+                elif [[ ${words[i]} = @(--src-pfx|--dst-pfx) ]]; then # DNPT/SNPT target -> COMP_WORDBREAKS `:' problem
+                    [[ $prev = */+([[:digit:]]) ]] || return 0
+                elif [[ ${words[i]} = @(--timestart|--timestop) ]]; then # time match -> COMP_WORDBREAKS `:' problem
+                    [[ $prev = +([[:digit:]])+([[:digit:]]):+([[:digit:]])+([[:digit:]])?(:+([[:digit:]])+([[:digit:]])) ]] || return 0
+                elif [[ ${words[i]} = --monthdays ]]; then # time match
+                    [[ $prev = +([[:digit:]])*(,+([[:digit:]])) ]] || return 0
+                    for str_tmp in ${prev//,/ }; do
+                        ((str_tmp < 1 || str_tmp > 31)) && return 0
+                    done
+                elif [[ ${words[i]} = --weekdays ]]; then # time match
+                    str_tmp="Mo|Mon|Tu|Tue|We|Wed|Th|Thu|Fr|Fri|Sa|Sat|Su|Sun"
+                    if [[ $prev = @(@($str_tmp)|+([[:digit:]]))*(,@(@($str_tmp)|+([[:digit:]]))) ]]; then
+                        for str_var in ${prev//,/ }; do
+                            if [[ $str_var = +([[:digit:]]) ]]; then
+                                ((str_var < 1 || str_var > 7)) && return 0
+                            elif [[ $str_var = @($str_tmp)*(,@($str_tmp)) ]]; then :
+                            else return 0
+                            fi
+                        done
+                    else return 0
+                    fi
+                fi
+            fi
+        fi
+    fi
+
+    # done with terminating conditions, set options
+    if [[ $prev = \! ]]; then
+        local arr_rule_action_opts=(
+        "-d --destination"
+        "-f --fragment"
+        "-i --in-interface"
+        "-o --out-interface"
+        "-p --protocol"
+        "-s --source"
+        )
+    else
+        local arr_rule_action_opts=(
+        "-d --destination"
+        "-f --fragment"
+        "-g --goto"
+        "-i --in-interface"
+        "-j --jump"
+        "-m --match"
+        "-o --out-interface"
+        "-p --protocol"
+        "-s --source"
+        "-t --table"
+        "-v --verbose"
+        --modprobe=
+        )
+    fi
+
+    if ((got_action)); then # we know the main action
+        if ((rulenum)) && \
+            [[ $str_action != @(-L|--list|-S|--list-rules|-I|--insert|-R|--replace) ]]
+        then
+            # we have a rulenumber given, under certain commands,
+            # this reduces amount of valid options
+            if [[ $prev != \! ]]; then
+                arr_tmp_opts=( "-t --table" "-v --verbose" )
+            fi
+        # depend on action
+        elif [[ $str_action = @(-A|--append|-I|--insert) ]]; then
+            if [[ $prev != \! ]]; then
+                arr_rule_action_opts+=("--set-counters")
+            fi
+            arr_tmp_opts=("${arr_rule_action_opts[@]}")
+        elif [[ $str_action = @(-C|--check|-D|--delete|-R|--replace) ]]; then
+            arr_tmp_opts=("${arr_rule_action_opts[@]}")
+        elif [[ $str_action = @(-E|--rename-chain|-F|--flush|-N|--new|-X|--delete-chain|-P|--policy|-S|--list-rules|-Z|--zero) ]]
+        then
+            arr_tmp_opts=( "-t --table" "-v --verbose" )
+        elif [[ $str_action = @(-L|--list) ]]; then
+            if [[ $prev != \! ]]; then
+                arr_tmp_opts=(
+                    "-n --numeric"
+                    "-t --table"
+                    "-v --verbose"
+                    "-x --exact"
+                    "--line-numbers"
+                    )
+            fi
+        fi
+    else # we don't know the main action
+        if ((list_only)); then
+            # rule listing options
+            if [[ $prev != \! ]]; then
+                arr_tmp_opts=(
+                "-t --table"
+                "-v --verbose"
+                "-L --list"
+                "-n --numeric"
+                "-x --exact"
+                )
+            fi
+        elif ((set_counters)); then
+            # set-counters reduces available options
+            if [[ $prev != \! ]]; then
+                if [[ $_IPT_OPTS_ON_START = actions ]]; then
+                    arr_tmp_opts=(
+                    "-j --jump"
+                    "-g --goto"
+                    "-m --match"
+                    "-t --table"
+                    "-v --verbose"
+                    "-w --wait"
+                    "-A --append"
+                    "-I --insert"
+                    --modprobe=
+                    )
+                else
+                    arr_tmp_opts=(
+                    "-d --destination"
+                    "-f --fragment"
+                    "-g --goto"
+                    "-i --in-interface"
+                    "-j --jump"
+                    "-m --match"
+                    "-o --out-interface"
+                    "-p --protocol"
+                    "-s --source"
+                    "-t --table"
+                    "-v --verbose"
+                    "-A --append"
+                    "-I --insert"
+                    --modprobe=
+                    )
+                fi
+            fi
+        elif ((got_rule_opt)); then
+            # we already got an option on the command line
+            if [[ $prev = \! ]]; then
+                arr_tmp_opts=("${arr_rule_action_opts[@]}")
+            else
+                arr_tmp_opts=(
+                "-d --destination"
+                "-f --fragment"
+                "-g --goto"
+                "-i --in-interface"
+                "-j --jump"
+                "-m --match"
+                "-o --out-interface"
+                "-p --protocol"
+                "-s --source"
+                "-t --table"
+                "-v --verbose"
+                "-w --wait"
+                "-A --append"
+                "-C --check"
+                "-D --delete"
+                "-I --insert"
+                "-R --replace"
+                --set-counters
+                --modprobe=
+                )
+            fi
+        else
+            # we got almost no information when we come here. Let's check for the target
+            if [[ $str_target ]]; then
+                if [[ $prev = \! ]]; then
+                    arr_tmp_opts=("${arr_rule_action_opts[@]}")
+                else
+                    arr_tmp_opts=(
+                        "-m --match"
+                        "-t --table"
+                        "-v --verbose"
+                        "-w --wait"
+                        "-A --append"
+                        "-C --check"
+                        "-D --delete"
+                        "-I --insert"
+                        "-R --replace"
+                        "-d --destination"
+                        "-f --fragment"
+                        "-i --in-interface"
+                        "-o --out-interface"
+                        "-p --protocol"
+                        "-s --source"
+                        "-v --verbose"
+                        "-w --wait"
+                        --modprobe=
+                        )
+                fi
+            else
+                # as we know nothing we asume we just started the game
+                # depend on startup variable
+                if [[ $_IPT_OPTS_ON_START = actions ]]; then
+                    if [[ $prev = \! ]]; then
+                        arr_tmp_opts=(
+                            "-h --help"
+                            "-V --version"
+                            )
+                    else
+                        arr_tmp_opts=(
+                            "-h --help"
+                            "-g --goto"
+                            "-j --jump"
+                            "-m --match"
+                            "-t --table"
+                            "-v --verbose"
+                            "-w --wait"
+                            "-A --append"
+                            "-C --check"
+                            "-D --delete"
+                            "-E --rename-chain"
+                            "-F --flush"
+                            "-I --insert"
+                            "-L --list"
+                            "-N --new"
+                            "-P --policy"
+                            "-R --replace"
+                            "-S --list-rules"
+                            "-V --version"
+                            "-X --delete-chain"
+                            "-Z --zero"
+                            )
+                    fi
+                else
+                    if [[ $prev = \! ]]; then
+                        arr_tmp_opts=("${arr_rule_action_opts[@]}")
+                    else
+                        arr_tmp_opts=("${arr_opts[@]}")
+                    fi
+                fi
+            fi
+        fi
+    fi
+
+    # evaluate if help string should be shown
+    if ((! got_action && cword < 7)); then
+        if [[ -z ${arr_last_opts[0]} ]]; then
+            if [[ ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} && -z $str_target ]]
+            then inline_help=1
+            elif [[ $str_target && -z ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} ]]
+            then
+                for x in ${!arr_targets[@]}; do
+                    [[ $str_target = ${arr_targets[x]%% *} ]] && inline_help=1 && break
+                done
+            fi
+        fi
+    fi
+
+    # By default show only long options
+    if [[ $_IPT_OPT_STYLE = long ]]; then
+        arr_tmp_opts=( "${arr_tmp_opts[@]/#-* }" )
+        ((inline_help)) && str_help=--help
+    else
+        # show either short or long options (single or double dash)
+        if [[ $cur = -?(+([[:word:]])) ]]; then # any short-option is requested
+            arr_tmp_opts=( "${arr_tmp_opts[@]/% --*/}" )
+            ((inline_help)) && str_help=-h
+        elif [[ $cur = --?(+([[:word:]]))* ]]; then # any long-option is requested
+            arr_tmp_opts=( "${arr_tmp_opts[@]/#-* }" )
+            ((inline_help)) && str_help=--help
+        fi
+    fi
+
+
+    if [[ $str_jump ]]; then # remove mutual exclusive jump/goto
+        case "$str_jump" in
+            -j|--jump) str_tmp="-g|--goto" ;;
+            -g|--goto) str_tmp="-j||--jump" ;;
+        esac
+        for x in ${!arr_tmp_opts[@]}; do
+            if [[ ${arr_tmp_opts[x]} = @($str_tmp) ]]; then
+                unset arr_tmp_opts[x]
+                break
+            fi
+        done
+    fi
+
+    if ((${#words[@]} > 3)); then
+        # we got one option, so remove options that are mutual exclusive to all others
+        for i in ${!arr_tmp_opts[@]}; do
+            if [[ ${arr_tmp_opts[i]} = @(-V|--version) ]]; then
+                unset arr_tmp_opts[i]
+                break
+            fi
+        done
+    fi
+
+    # from here we start to set a compreply
+
+    if ((cword == 1)); then
+    # at very first option request
+        COMPREPLY=( $( compgen -W '${arr_tmp_opts[@]}' -- $cur ) )
+    elif ((cword == 2)) && [[ $prev = \! ]]; then
+    # nothing there yet but a negation
+    # if this is not here, we produce bad subscript error later
+        COMPREPLY=( $( compgen -W '${arr_tmp_opts[@]}' -- $cur ) )
+    # we're on the word after the target
+    elif ((cword == target_index+1)) && _iptables_is_target "$prev"
+    then
+        # targets with no option
+        if [[ $prev = @(ACCEPT|DROP|MIRROR|NOTRACK|TRACE) ]]; then
+            _iptables_dedupe_opt_alias
+            COMPREPLY=( $( compgen -W \
+                '$(_iptables_dedupe_opts ${arr_tmp_opts[@]} $str_help)' -- $cur ) )
+        # targets which don't demand an option
+        elif [[ $prev = @(CT|LOG|NFLOG|NFQUEUE|ULOG|MASQUERADE|REDIRECT|REJECT) ]]
+        then
+            _iptables_dedupe_opt_alias
+            COMPREPLY=( $( compgen -W \
+                '$(_iptables_dedupe_opts $(_iptables_get_target_opts "$prev") \
+                ${arr_tmp_opts[@]} $str_help)' -- $cur ) )
+        else
+            COMPREPLY=( $( compgen -W '$(_iptables_dedupe_opts \
+                $(_iptables_get_target_opts "$prev") $str_help)' -- $cur ) )
+        fi
+    # two words after the target and previous is a negation
+    elif ((cword == target_index+2)) && [[ $prev = \! ]]
+    then
+        if [[ $str_target != @(CT|LOG|NFLOG|NFQUEUE|ULOG|MASQUERADE|REDIRECT|REJECT) ]]
+        then
+            arr_tmp_opts=() # these targets do demand an option
+        fi
+        _iptables_dedupe_opt_alias
+        COMPREPLY=( $( compgen -W \
+            '$(_iptables_dedupe_opts ${arr_tmp_opts[@]})' -- $cur ) )
+    # we're at position after the match name
+    elif ((cword == last_match_index+1)) && _iptables_is_match "$prev"
+    then
+        # modules with no options
+        if [[ $prev = @(unclean|eui64) ]]; then
+            _iptables_dedupe_opt_alias
+            COMPREPLY=( $( compgen -W \
+                '$(_iptables_dedupe_opts ${arr_tmp_opts[@]} $str_help)' -- $cur ) )
+        # matches which don't demand an option
+        elif [[ $prev = @(ah|frag|rpfilter) ]]; then
+            _iptables_dedupe_opt_alias
+            COMPREPLY=( $( compgen -W \
+                '$(_iptables_dedupe_opts $(_iptables_get_match_opts "$prev") \
+                ${arr_tmp_opts[@]} $str_help)' -- $cur ) )
+        # proto = match name
+        elif [[ ${words[cword-2]} = @(-p|--proto|--protocol) ]]; then
+            _iptables_dedupe_opt_alias
+            COMPREPLY=( $( compgen -W \
+                '$(_iptables_dedupe_opts $(_iptables_get_match_opts "$prev") \
+                ${arr_tmp_opts[@]} $str_help)' -- $cur ) )
+        else
+            COMPREPLY=( $( compgen -W '$(_iptables_dedupe_opts \
+                $(_iptables_get_match_opts "$prev") $str_help)' -- $cur ) )
+        fi
+    # handle negation of proto-match
+    elif ((cword == last_match_index+2)) && [[ $prev = \! && \
+        ${words[last_match_index-1]} = @(-p|--proto|--protocol) ]]
+    then
+        _iptables_dedupe_opt_alias
+        COMPREPLY=( $( compgen -W '$(_iptables_dedupe_opts \
+            $(_iptables_get_match_opts "${words[cword-2]}") ${arr_tmp_opts[@]})' -- $cur ) )
+    # handle negated match
+    elif ((cword == last_match_index+2)) && [[ $prev = \! ]] && \
+        _iptables_is_match "${words[cword-2]}"
+    then
+        _iptables_dedupe_opt_alias
+        COMPREPLY=( $( compgen -W '$(_iptables_dedupe_opts \
+            $(_iptables_get_match_opts "${words[cword-2]}"))' -- $cur ) )
+    # previous word is the last option
+    elif [[ $str_last_is = option ]] && ((cword == last_option_index+1))
+    then
+        _iptables_dedupe_opt_alias
+        COMPREPLY=( $( compgen -W \
+            '$(_iptables_dedupe_opts ${arr_tmp_opts[@]})' -- $cur ) )
+    # previous word is an option
+    elif _iptables_is_option "$prev"; then
+        _iptables_dedupe_opt_alias
+        COMPREPLY=( $( compgen -W \
+            '$(_iptables_dedupe_opts ${arr_tmp_opts[@]})' -- $cur ) )
+    else
+        # these are not immediately following an option, match or target
+        # last was an action, nothing else to consider
+        if [[ $str_last_is = action ]]; then
+            _iptables_dedupe_opt_alias
+            COMPREPLY=( $( compgen -W \
+                '$(_iptables_dedupe_opts ${arr_tmp_opts[@]})' -- $cur ) )
+        elif [[ $str_last_is = target ]]; then # last was a target
+            _iptables_dedupe_opt_alias
+            if [[ $prev = \! ]]; then
+                COMPREPLY=( $( compgen -W \
+                    '$(_iptables_dedupe_opts ${arr_tmp_opts[@]})' -- $cur ) )
+            # targets with no option
+            elif [[ $str_target = @(ACCEPT|DROP|MIRROR|NOTRACK|TRACE) ]]; then
+                COMPREPLY=( $( compgen -W \
+                    '$(_iptables_dedupe_opts ${arr_tmp_opts[@]})' -- $cur ) )
+            else
+                # check for mandatory target options
+                str_var="$IFS"
+                for x in ${!arr_targets[@]}; do
+                    set -- ${arr_targets[x]}
+                    [[ $1 = $str_target ]] || continue
+                    _iptables_select_on_inet_family $2 || continue
+                    shift 3
+                    y=0
+                    # check targets option flags
+                    for str_tmp; do
+                        [[ $str_tmp = *:* ]] || continue
+                        local str_flags="${str_tmp#*:}"
+                        local str_opt="${str_tmp%:"$str_flags"}"
+                        IFS=:
+                        set -- $str_flags
+                        IFS="$str_var"
+                        while (($#)); do
+                            if [[ $1 = need ]]; then # mandatory options
+                                for ((i=target_index+1; i < ${#words[@]}-1; i++)); do
+                                    if [[ ${words[i]} = $str_opt ]]; then
+                                        continue 3
+                                    fi
+                                done
+                                arr_tmp_opts=()
+                                break 3
+                            # mandatory options - one of them is needed
+                            elif [[ $1 = need1 ]]; then
+                                let y+=1
+                                for ((i=target_index+1; i < ${#words[@]}-1; i++)); do
+                                    if [[ ${words[i]} = $str_opt ]]; then
+                                        break 4
+                                    fi
+                                done
+                            fi
+                            shift
+                        done
+                    done
+                    ((y)) && arr_tmp_opts=()
+                    break
+                done
+                COMPREPLY=( $( compgen -W '$(_iptables_dedupe_opts \
+                    $(_iptables_get_target_opts "$str_target") ${arr_tmp_opts[@]})' \
+                    -- $cur ) )
+            fi
+        elif [[ $str_last_is = match ]]; then # last was a match
+            _iptables_dedupe_opt_alias
+            # check for mandatory match options
+            str_var="$IFS"
+            for x in ${!arr_matches[@]}; do
+                set -- ${arr_matches[x]}
+                [[ $1 = ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} ]] || continue
+                _iptables_select_on_inet_family $2 || continue
+                shift 3
+                y=0
+                # check match option flags
+                for str_tmp; do
+                    [[ $str_tmp = *:* ]] || continue
+                    local str_flags="${str_tmp#*:}"
+                    local str_opt="${str_tmp%:"$str_flags"}"
+                    IFS=:
+                    set -- $str_flags
+                    IFS="$str_var"
+                    while (($#)); do
+                        if [[ $1 = need ]]; then # mandatory options
+                            for ((i=last_match_index+1; i < ${#words[@]}-1; i++)); do
+                                if [[ ${words[i]} = $str_opt ]]; then
+                                    continue 3
+                                fi
+                            done
+                            arr_tmp_opts=()
+                            break 3
+                        elif [[ $1 = need1 ]]; then # mandatory options
+                            let y+=1
+                            for ((i=last_match_index+1; i < ${#words[@]}-1; i++)); do
+                                if [[ ${words[i]} = $str_opt ]]; then
+                                    break 4
+                                fi
+                            done
+                        fi
+                        shift
+                    done
+                done
+                ((y)) && arr_tmp_opts=()
+                break
+            done
+            COMPREPLY=( $( compgen -W '$(_iptables_dedupe_opts \
+                $(_iptables_get_match_opts "${arr_cmd_matches[${#arr_cmd_matches[@]}-1]}") \
+                ${arr_tmp_opts[@]})' -- $cur ) )
+        else
+            # by default display main options
+            _iptables_dedupe_opt_alias
+            COMPREPLY=( $( compgen -W \
+                '$(_iptables_dedupe_opts ${arr_tmp_opts[@]})' -- $cur ) )
+        fi
+    fi
+# not an option or negation and not a variable request
+elif [[ $cur != @(\!|\$*) ]]; then
+    # no option given, present hyphen to start option completion
+    if ((${#words[@]} == 2)) && [[ -z $cur ]]; then
+        compopt -o nospace
+        if [[ $_IPT_OPT_STYLE = long ]]; then
+            COMPREPLY=( -- )
+        else
+            COMPREPLY=( - )
+        fi
+        return 0
+    elif ((${#words[@]} == 3)) && [[ -z $cur && $prev = \! ]]; then
+        compopt -o nospace
+        if [[ $_IPT_OPT_STYLE = long ]]; then
+            COMPREPLY=( -- )
+        else
+            COMPREPLY=( - )
+        fi
+        return 0
+    fi
+    # depend on previous option
+    if [[ $prev = @(-h|--help|-V|--version) ]]; then
+        :
+    elif [[ $prev = @(-A|--append|-C|--check|-D|--delete|-F|--flush|-I|--insert|-L|--list|-S|--list-rules|-X|--delete-chain|-Z|--zero) ]]
+    then
+        # chain name expected next
+        COMPREPLY=( $( compgen -W \
+            '$(_iptables_get_builtin_chains $str_table) $(_iptables_get_user_chains $str_table)' \
+            -- $cur ) )
+    elif [[ $prev = @(-E|--rename-chain|-R|--replace) ]]; then # retrieve existing chains
+        COMPREPLY=( $( compgen -W '$(_iptables_get_user_chains $str_table)' -- $cur ) )
+    elif [[ $prev = @(-P|--policy) ]]; then
+        COMPREPLY=( $( compgen -W '$(_iptables_get_builtin_chains $str_table)' -- $cur ) )
+    elif [[ $prev = @(-g|--goto) ]]; then
+        COMPREPLY=( $( compgen -W '$(_iptables_get_user_chains $str_table)' -- $cur ) )
+    elif [[ $prev = @(-j|--jump) ]]; then
+        COMPREPLY=( $( compgen -W \
+            '$(_iptables_get_targets $str_table) $(_iptables_get_user_chains $str_table)' \
+            -- $cur ) )
+    elif [[ $prev = @(-t|--table) ]]; then
+        if [[ $str_chain && $str_action != @(-N|--new) && -r /proc/net/ip_tables_names ]]
+        then
+            while read -r; do
+                for str_tmp in $(_iptables_get_builtin_chains $REPLY) $(_iptables_get_user_chains $REPLY)
+                do
+                    if [[ $str_tmp = $str_chain ]]; then
+                        COMPREPLY+=($REPLY)
+                        break
+                    fi
+                done
+            done < /proc/net/ip_tables_names
+            COMPREPLY=( $( compgen -W '${COMPREPLY[@]}' -- $cur ) )
+        else
+            COMPREPLY=( $( compgen -W 'filter mangle nat raw security' -- $cur ) )
+        fi
+    elif [[ $prev = @(-m|--match) ]]; then arr_tmp=()
+        # do not show matches already used
+        # except those explicitly defined for multiple usage
+        for str_tmp in $(_iptables_get_matches $str_table); do
+            # matches already used on the command-line
+            for ((i=0; i < ${#arr_all_matches[@]}-1; i++)); do
+                [[ $str_tmp = ${arr_all_matches[i]} ]] || continue
+                if [[ $str_tmp = $str_proto && \
+                    $COMP_LINE != *@(-m|--match)+([[:blank:]])$str_tmp* ]]
+                then # proto used matches, allow if it hasnt been used
+                    arr_tmp[${#arr_tmp[@]}]="$str_tmp"
+                else # those are always shown
+                    for x in ${!arr_global_matches[@]}; do
+                        if [[ $str_tmp = ${arr_global_matches[x]%% *} ]]; then
+                            arr_tmp[${#arr_tmp[@]}]="$str_tmp"
+                            break
+                        fi
+                    done
+                fi
+                continue 2
+            done
+            arr_tmp[${#arr_tmp[@]}]="$str_tmp"
+        done
+        COMPREPLY=( $( compgen -W '${arr_tmp[@]}' -- $cur ) )
+    elif [[ $prev = -@(i|-in-interface|o|-out-interface|-physdev-in|-physdev-out) ]]; then
+        COMPREPLY=( $( compgen -W '$(_iptables_get_ifnames)' -- $cur ) )
+    elif [[ $prev = @(--src-range|--dst-range) ]]; then
+        if [[ $_IPT_IPLIST_FILE && -r $_IPT_IPLIST_FILE ]]; then
+            # if a file with ip addresses is in env var, load em
+            if [[ $str_app = iptables ]]; then
+            str_regex='^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/([3][0-2]|[1-2]?[0-9]))?$'
+            elif [[ $str_app = ip6tables ]]; then
+            str_regex='^([0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}|([0-9a-fA-F]{1,4}:){1}(:[0-9a-fA-F]{1,4}){1,6}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}){1})(/([1][0-2][0-9]|[1-9]?[0-9]))?$'
+            fi
+            str_tmp=$(while read -r ip rest; do
+                [[ $ip = *([[:blank:]])\#* ]] && continue
+                ip="${ip//\#*/}"
+                [[ $ip =~ $str_regex ]] && printf "%s\n" "${ip%/*}"
+                done < "${_IPT_IPLIST_FILE}")
+        fi
+        if [[ $cur = *-* ]]; then
+            if [[ $str_tmp ]]; then
+                COMPREPLY+=( $( compgen -P "${cur%-*}-" -W "$str_tmp" -- "${cur#*-}" ) )
+                ((got_bashcompl)) && __ltrim_colon_completions "$str_prefix$cur"
+            fi
+        else
+            if [[ $str_tmp ]]; then
+                compopt -o nospace
+                COMPREPLY+=( $( compgen -W "$str_tmp" -S '-' -- "$cur" ) )
+                ((got_bashcompl)) && __ltrim_colon_completions "$cur"
+            fi
+        fi
+    elif [[ $prev = @(-s|--source|-d|--destination|--ctorigsrc|--ctorigdst|--ctreplsrc|--ctrepldst|--vaddr|--tunnel-src|--tunnel-dst|--gateway|--on-ip) ]]
+    then
+        if ((got_bashcompl)); then
+           _known_hosts_real -- "$cur"
+        else
+            if [[ ${COMP_KNOWN_HOSTS_WITH_HOSTFILE-1} ]]; then
+                COMPREPLY=( $( compgen -A hostname "$cur" ) )
+            fi
+        fi
+        if [[ $_IPT_IPLIST_FILE && -r $_IPT_IPLIST_FILE ]]; then
+            # if a file with ip addresses is in env var, load em
+            if [[ $str_app = iptables ]]; then
+            str_regex='^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/([3][0-2]|[1-2]?[0-9]))?$'
+            elif [[ $str_app = ip6tables ]]; then
+            str_regex='^([0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}|([0-9a-fA-F]{1,4}:){1}(:[0-9a-fA-F]{1,4}){1,6}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}){1})(/([1][0-2][0-9]|[1-9]?[0-9]))?$'
+            fi
+            str_tmp=$(while read -r ip rest; do
+                [[ $ip = *([[:blank:]])\#* ]] && continue
+                ip="${ip//\#*/}"
+                [[ $ip =~ $str_regex ]] && printf "%s\n" "$ip"
+                done < "${_IPT_IPLIST_FILE}")
+            COMPREPLY+=( $( compgen -W "$str_tmp" -- "$cur" ) )
+        fi
+        if [[ $prev != --gateway ]]; then
+            # add networks
+            if [[ ${_IPT_COMP_NETWORKS-1} ]]; then
+                if [[ -r /etc/networks ]]; then
+                    COMPREPLY+=( $( compgen -W '$(while read -r foo str_net rest
+                    do
+                        [[ $foo = @(""|*([[:blank:]])#*) ]] && continue
+                        [[ $str_net = *([[:blank:]])#* ]] && continue
+                        printf "%s\n" "$str_net"
+                    done < /etc/networks)' -- "$cur" ) )
+                fi
+            fi
+
+            # add ip addresses of interfaces
+            if [[ ${_IPT_COMP_IP_LOCAL-1} ]]; then
+                if [[ $str_app = iptables ]]; then
+                    str_tmp=-4
+                elif [[ $str_app = ip6tables ]]; then
+                    str_tmp=-6
+                fi
+                COMPREPLY+=( $( \
+                    compgen -W '$(while read one two three str_var rest; do
+                     printf "%s\n" "$str_var"
+                    done < <(PATH="$PATH:/sbin" command ip $str_tmp -o addr)
+                    )' -- "$cur" ) )
+            fi
+        fi
+        ((got_bashcompl)) && __ltrim_colon_completions "$cur"
+        if ((${#COMPREPLY[@]} == 1)) && [[ ${COMPREPLY[0]} != */* ]]
+        then
+            compopt -o nospace
+        fi
+    elif [[ $prev = @(-p|--proto|--protocol|--ctproto|--vproto) ]]; then
+        if [[ $prev = --proto ]]; then
+            if [[ ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} = policy ]]
+            then
+                # policy match also has --proto
+                COMPREPLY=( $( compgen -W 'ah esp ipcomp' -- $cur ) )
+            fi
+        fi
+        if [[ $prev = @(-p|--proto|--protocol) && $str_proto_match ]]; then
+            COMPREPLY=( $( compgen -W "$str_proto_match" -- $cur ) )
+        fi
+        if ! ((${#COMPREPLY[@]})); then
+            COMPREPLY=( $( compgen -W '$(_iptables_get_protocols)' -- $cur ) )
+        fi
+    elif [[ $prev = --@(on-port|vport|vportctl) ]]; then
+        COMPREPLY=( $( compgen -W '$(_iptables_get_services -p "$str_proto")' -- $cur ) )
+    elif [[ $prev = --@(dport|destination-port|sport|source-port|ctorigsrcport|ctorigdstport|ctreplsrcport|ctrepldstport) ]]
+    then
+        if [[ $cur = *:* ]]; then
+            COMPREPLY=( $(_iptables_compl_port_range "$cur") )
+            ((got_bashcompl)) && __ltrim_colon_completions "$cur"
+        else
+            compopt -o nospace
+            COMPREPLY=( $( compgen -W '$(_iptables_get_services -p "$str_proto")' -- $cur ) )
+        fi
+    elif [[ $prev = --@(dports|destination-ports|sports|source-ports|ports) ]]
+    then
+        if [[ $cur = *,* ]]; then
+            str_tmp="$cur" str_var="${cur##*,}"
+            str_tmp="${str_tmp%"$cur"}"
+            if [[ $cur = *:* ]]; then
+                 _iptables_build_comma_list "$cur" \
+                     $( compgen -P "$str_tmp" -W '$(_iptables_compl_port_range "$str_var")' \
+                     -- "$str_var" )
+                ((got_bashcompl)) && __ltrim_colon_completions "$cur"
+            else
+                _iptables_build_comma_list "$cur" \
+                    $( compgen -P "$str_tmp" -W '$(_iptables_get_services -p "$str_proto")' \
+                    -- $str_var )
+            fi
+        else
+            if [[ $cur = *:* ]]; then
+                COMPREPLY=( $(_iptables_compl_port_range "$cur") )
+                ((got_bashcompl)) && __ltrim_colon_completions "$cur"
+            else
+                COMPREPLY=( $( compgen -W '$(_iptables_get_services -p "$str_proto")' -- $cur ) )
+            fi
+        fi
+        # as long we dont reach the max dont append a space
+        (( $(IFS=,; set -- $cur;printf "%d\n" $#) > 15 )) || compopt -o nospace
+    elif [[ $prev = --icmp-type ]]; then
+        COMPREPLY=( $( compgen -W 'any echo-reply pong destination-unreachable \
+            network-unreachable host-unreachable protocol-unreachable \
+            port-unreachable fragmentation-needed source-route-failed \
+            network-unknown host-unknown network-prohibited host-prohibited \
+            TOS-network-unreachable TOS-host-unreachable communication-prohibited \
+            host-precedence-violation precedence-cutoff source-quench redirect \
+            network-redirect host-redirect TOS-network-redirect TOS-host-redirect \
+            echo-request ping router-advertisement router-solicitation \
+            time-exceeded ttl-exceeded ttl-zero-during-transit ttl-zero-during-reassembly \
+            parameter-problem ip-header-bad required-option-missing timestamp-request \
+            timestamp-reply address-mask-request address-mask-reply' -- $cur ) )
+    elif [[ $prev = --icmpv6-type ]]; then
+        COMPREPLY=( $( compgen -W 'destination-unreachable no-route communication-prohibited \
+            beyond-scope address-unreachable port-unreachable failed-policy reject-route \
+            packet-too-big time-exceeded ttl-exceeded ttl-zero-during-transit \
+            ttl-zero-during-reassembly parameter-problem bad-header unknown-header-type \
+            unknown-option echo-request ping echo-reply pong router-solicitation \
+            router-advertisement neighbour-solicitation neighbor-solicitation \
+            neighbour-advertisement neighbor-advertisement redirect' -- $cur ) )
+    elif [[ $prev = --reject-with ]]; then # REJECT target
+        if [[ $str_app = iptables ]]; then
+            COMPREPLY=( $( compgen -W 'icmp-net-unreachable icmp-host-unreachable\
+               icmp-port-unreachable icmp-proto-unreachable icmp-net-prohibited\
+               icmp-host-prohibited icmp-admin-prohibited tcp-reset' -- $cur ) )
+        elif [[ $str_app = ip6tables ]]; then
+            COMPREPLY=( $( compgen -W 'icmp6-no-route no-route icmp6-adm-prohibited\
+               adm-prohibited icmp6-addr-unreachable addr-unreach\
+               icmp6-port-unreachable port-unreach tcp-reset\
+               icmp6-policy-fail policy-fail icmp6-reject-route reject-route' -- $cur ) )
+        fi
+        if [[ $str_proto && $str_proto != tcp ]]; then
+            COMPREPLY=( ${COMPREPLY[*]%tcp-reset} )
+        fi
+    elif [[ $prev = --timeout ]]; then # CT target
+        if [[ $str_target = CT ]]; then x=0
+            while read -r; do
+                if [[ $REPLY = .*"= {" ]]; then
+                    ((!x)) && REPLY=${REPLY#.} && printf "%s\n" "${REPLY%=*}" && x=1
+                elif [[ $REPLY = \}\; ]]; then
+                    x=0
+                fi
+            done < <(PATH=${PATH}:/sbin command nfct timeout list 2>/dev/null)
+        fi
+    elif [[ $prev = --type ]]; then # AUDIT target
+        COMPREPLY=( $( compgen -W 'accept drop reject' -- $cur ) )
+    elif [[ $prev = --set-class ]]; then # CLASSIFY target TODO: query by interface?
+        COMPREPLY=( $( compgen -W '$(while read -r; do
+            [[ $REPLY = lo ]] && continue
+            for str_dev in $REPLY; do
+                while read c t str_class rest; do
+                    [[ $str_class = +([[:digit:]]):+([[:digit:]]) ]] && printf "%s\n" "$str_class"
+                done < <(PATH=${PATH}:/sbin command tc class show dev $str_dev)
+            done
+            done < <(_iptables_get_ifnames))' -- $cur ) )
+        ((got_bashcompl)) && __ltrim_colon_completions "$cur"
+    elif [[ $prev = --hashmode ]]; then # CLUSTERIP target
+        COMPREPLY=( $( compgen -W \
+            'sourceip sourceip-sourceport sourceip-sourceport-destport' \
+            -- $cur ) )
+    elif [[ $prev = --expevents ]]; then # CT target
+        COMPREPLY=( $( compgen -W 'new' -- $cur ) )
+    elif [[ $prev = --ctevents ]]; then # CT target
+        _iptables_build_comma_list "$cur" new related destroy reply assured \
+            protoinfo helper mark natseqinfo secmark
+    elif [[ $prev = --helper ]]; then # CT target / helper match
+            compopt -o nospace
+            COMPREPLY=( $( compgen -W \
+                'amanda broadcast dccp ftp gre h323 irc netbios_ns netlink pptp \
+                    sane sip snmp ssdp tftp udplite' -- $cur ) )
+    elif [[ $prev = --hmark-tuple ]]; then # HMARK target
+        _iptables_build_comma_list "$cur" src dst sport dport spi ct
+    elif [[ $prev = --log-level ]]; then # LOG target
+        COMPREPLY=( $( compgen -W 'alert crit debug emerg err info notice warning' -- $cur ) )
+    elif [[ $prev = --nflog-group ]]; then # NFLOG target
+        str_tmp=$(set +f;cd /proc/sys/net/netfilter/nf_log/ && printf "%s\n" *)
+        COMPREPLY=( $( compgen -W '$str_tmp' -- $cur ) )
+    elif [[ $prev = --strip-options ]]; then # TCPOPTSTRIP target
+        _iptables_build_comma_list "$cur" wscale mss sack-permitted sack timestamp md5
+    elif [[ $prev = --@(add-set|del-set|match-set|map-set) ]]; then # set match and SET target
+        COMPREPLY=( $( compgen -W '$(PATH=${PATH}:/sbin command ipset -n list)' -- "$cur" ) )
+        ((got_bashcompl)) && __ltrim_colon_completions "$cur"
+    elif [[ $prev = --algo ]]; then # string match
+        COMPREPLY=( $( compgen -W 'bm kmp' -- $cur ) )
+    elif [[ $prev = --chunk-types ]]; then # sctp match
+        COMPREPLY=( $( compgen -W 'all any only' -- $cur ) )
+    elif [[ $prev = --tcp-flags ]]; then # tcp match tcp flags
+        _iptables_build_comma_list "$cur" SYN ACK FIN RST URG PSH ALL NONE
+    elif [[ $prev = --ctstate ]]; then # conntrack match
+        _iptables_build_comma_list "$cur" NEW ESTABLISHED RELATED INVALID UNTRACKED SNAT DNAT
+    elif [[ $prev = --ctstatus ]]; then # conntrack match
+        _iptables_build_comma_list "$cur" NONE EXPECTED SEEN_REPLY ASSURED CONFIRMED
+    elif [[ $prev = --ctdir ]]; then # conntrack match
+        COMPREPLY=( $( compgen -W 'ORIGINAL REPLY' -- $cur ) )
+    elif [[ $prev = --cpu ]]; then # cpu match
+        str_tmp=$(while read -r str_name c num; do
+            [[ $str_name = processor ]] && printf "%s\n" "$num"
+            done</proc/cpuinfo)
+        COMPREPLY=( $( compgen -W '$str_tmp' -- $cur ) )
+    elif [[ $prev = --connbytes-dir ]]; then # connbytes match
+        COMPREPLY=( $( compgen -W 'original reply both' -- $cur ) )
+    elif [[ $prev = --connbytes-mode ]]; then # connbytes match
+        COMPREPLY=( $( compgen -W 'packets bytes avgpkt' -- $cur ) )
+    elif [[ $prev = --dccp-types ]]; then # dccp match
+        _iptables_build_comma_list "$cur" REQUEST RESPONSE DATA ACK \
+            DATAACK CLOSEREQ CLOSE RESET SYNC SYNCACK INVALID
+    elif [[ $prev = @(--log|--ttl) ]]; then # osf match
+        COMPREPLY=( $( compgen -W '0 1 2' -- $cur ) )
+    elif [[ $prev = --genre ]]; then # osf match
+        if [[ $_IPT_OSF_PF_OS && -r $_IPT_OSF_PF_OS ]]; then
+            COMPREPLY=( $( compgen -W '$(while read -r a b c; do
+                [[ $a = *([[:blank:]])#* ]] && continue
+                [[ $a = *:*:* && $b = ?(\@)[[:word:]]+([[:word:]-]):* ]] && \
+                    b="${b#\@}" && printf "%s\n" ${b%%:*}
+                   done < "$_IPT_OSF_PF_OS")' -- $cur ) )
+        fi
+    elif [[ $prev = --ecn-ip-ect ]]; then # ecn match
+        COMPREPLY=( $( compgen -W '0 1 2 3' -- $cur ) )
+    elif [[ $prev = --header ]]; then # header match TODO: add protocols list?
+        _iptables_build_comma_list "$cur" hop dst route frag auth esp none prot
+    elif [[ $prev = --mac-source ]]; then
+        str_regex='^([[:xdigit:]]{2})(:[[:xdigit:]]{2}){5}$'
+        x=0 y=0
+        if [[ ${_IPT_MAC_COMPL_MODE:=both} = both ]]; then
+            x=1 y=1
+        elif [[ $_IPT_MAC_COMPL_MODE = file ]]; then
+            x=1
+        elif [[ $_IPT_MAC_COMPL_MODE = system ]]; then
+            y=1
+        fi
+        if ((x)); then
+            if [[ $_IPT_MACLIST_FILE && -r $_IPT_MACLIST_FILE ]]
+            then
+                # if a file with mac addresses is in env var, load em
+                str_tmp=$(while read -r mac rest; do
+                    [[ $mac = *([[:blank:]])\#* ]] && continue
+                    mac="${mac//\#*/}"
+                    [[ $mac =~ $str_regex ]] && printf "%s\n" "$mac"
+                    done < "${_IPT_MACLIST_FILE}")
+                COMPREPLY=( $( compgen -W "$str_tmp" -- "$cur" ) )
+            fi
+        fi
+        if ((y)); then
+            # read arp cache, addresses of local interfaces and /etc/ethers
+            str_tmp=$(while read a b addr rest; do
+                [[ $addr =~ $str_regex ]] && printf "%s\n" "$addr"
+                done < <(PATH=$PATH:/sbin command arp -n 2>/dev/null))
+            str_tmp+=" $(while read -r; do
+                [[ $REPLY = *link/loopback* ]] && continue
+                REPLY=${REPLY#*link/*+([[:blank:]])}
+                REPLY=${REPLY%+([[:blank:]])brd*}
+                [[ $REPLY =~ $str_regex ]] && printf "%s\n" "$REPLY"
+                done < <(PATH=$PATH:/sbin command ip -o link show 2>/dev/null))"
+            if [[ -r /etc/ethers ]]; then
+                str_tmp+=" $(while read -r addr rest; do
+                    [[ $addr =~ $str_regex ]] && printf "%s\n" "$addr"
+                    done < /etc/ethers)"
+            fi
+            COMPREPLY+=( $( compgen -W "$str_tmp" -- "$cur" ) )
+        fi
+        ((got_bashcompl)) && __ltrim_colon_completions "$cur"
+    elif [[ $prev = --mh-type ]]; then # mh match
+        COMPREPLY=( $( compgen -W 'binding-refresh-request home-test-init \
+            careof-test-init home-test careof-test binding-update \
+            binding-acknowledgement binding-error' -- $cur ) )
+    # overlap in option names, policy and statistic match both have --mode
+    elif [[ $prev = --mode ]]; then
+            if [[ ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} = policy ]]; then
+                COMPREPLY=( $( compgen -W 'tunnel transport' -- $cur ) )
+            elif [[ ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} = statistic ]]; then
+                COMPREPLY=( $( compgen -W 'random nth' -- $cur ) )
+            fi
+    elif [[ $prev = --dir ]]; then # policy match
+        COMPREPLY=( $( compgen -W 'in out' -- $cur ) )
+    elif [[ $prev = --pol ]]; then # policy match
+        COMPREPLY=( $( compgen -W 'none ipsec' -- $cur ) )
+    elif [[ $prev = --nfacct-name ]]; then # nfacct match
+        str_tmp=$( while read -r; do
+            REPLY="${REPLY%;}"
+            printf "%s\n" ${REPLY##*=}
+        done < <(PATH=${PATH}:/sbin command nfacct list) )
+        COMPREPLY=( $( compgen -W '$str_tmp' -- $cur ) )
+    elif [[ $prev = --uid-owner ]]; then # owner match
+        COMPREPLY=( $( compgen -u -- $cur ) )
+    elif [[ $prev = --gid-owner ]]; then # owner match
+        COMPREPLY=( $( compgen -g -- $cur ) )
+    elif [[ $prev = --pkt-type ]]; then # pkttype match
+        COMPREPLY=( $( compgen -W 'unicast broadcast multicast' -- $cur ) )
+    elif [[ $prev = --realm ]]; then # realm match
+        COMPREPLY=( $( compgen -W '$(while read -r; do
+            [[ $REPLY = *([[:space:]])#* ]] && continue
+            if [[ $REPLY = *([[:space:]])+([[:digit:]])+([[:space:]])+([[:graph:]])* ]]
+            then
+                str_tmp="${REPLY#*+([[:digit:]])+([[:space:]])}"
+                printf "%s\n" "${str_tmp%#*}"
+            fi
+            done </etc/iproute2/rt_realms)' -- $cur ) )
+    elif [[ $prev = --state ]]; then # state match
+        _iptables_build_comma_list "$cur" NEW ESTABLISHED RELATED INVALID UNTRACKED
+    elif [[ $prev = @(--src-type|--dst-type) ]]; then # addrtype match
+        COMPREPLY=( $( compgen -W 'UNSPEC UNICAST LOCAL BROADCAST \
+            ANYCAST MULTICAST BLACKHOLE UNREACHABLE PROHIBIT THROW NAT XRESOLVE' -- $cur ) )
+    elif [[ $prev = @(--dscp-class|--set-dscp-class) ]]; then # dscp match and DSCP target
+        COMPREPLY=( $( compgen -W \
+            'BE EF AF11 AF12 AF13 AF21 AF22 AF23 AF31 AF32 AF33 AF41 AF42 AF42 CS1 CS2 CS3 Cs4' \
+            -- $cur ) )
+    elif [[ $prev = @(--rateest-interval|--rateest-ewmalog) ]]
+    then # RATEEST target
+        if [[ $cur = +([[:digit:]])?(.*([[:digit:]]))*([[:alpha:]]) ]]
+        then # glob not perfect, requires more work
+            COMPREPLY=( $( compgen -W \
+                '${cur//[[:alpha:]]/}s ${cur//[[:alpha:]]/}ms ${cur//[[:alpha:]]/}us' \
+                -- $cur ) )
+        fi
+    elif [[ $prev = --rateest-@(bps|pps|bps1|bps2|pps1|pps2) ]]
+    then # rateest match
+        if [[ $cur = +([[:digit:]])?(.*([[:digit:]]))*([[:alpha:]]) ]]
+        then # glob not perfect, requires more work
+            str_tmp="${cur%%[[:alpha:]]}"
+            COMPREPLY=( $( compgen -P "$str_tmp" -W \
+                'bit kbit mbit gbit tbit Kibit Mibit Gibit Tibit Bps \
+                KBps MBps GBps TBps KiBps MiBps GiBps TiBps' \
+                -- ${cur##+([[:digit:]])?(.*([[:digit:]]))} ) )
+        fi
+    elif [[ $prev = @(--limit|--hashlimit-above|--hashlimit-upto) ]]
+    then # limit / hashlimit match
+        if [[ $cur = +([[:digit:]])?(/*([[:alpha:]])) ]]; then
+            str_tmp="${cur//+(\/|+([[:alpha:]]))/}"
+            COMPREPLY=( $( compgen -W \
+                '${str_tmp}/second ${str_tmp}/minute ${str_tmp}/hour ${str_tmp}/day' \
+                -- $cur ) )
+        fi
+    elif [[ $prev = --hashlimit-mode ]]; then # hashlimit match
+        _iptables_build_comma_list "$cur" srcip srcport dstip dstport
+    elif [[ $prev = @(--tos|--set-tos) ]]; then # tos match and TOS target
+        if [[ $str_app = iptables ]]; then
+            COMPREPLY=( $( compgen -W \
+                'Minimize-Delay Maximize-Throughput Maximize-Reliability \
+                Minimize-Cost Normal-Service' -- $cur ) )
+        fi
+    elif [[ $prev = --vdir ]]; then # ipvs match
+        COMPREPLY=( $( compgen -W 'ORIGINAL REPLY' -- $cur ) )
+    elif [[ $prev = --vmethod ]]; then # ipvs match
+        COMPREPLY=( $( compgen -W 'GATE IPIP MASQ' -- $cur ) )
+    elif [[ $prev = --weekdays ]]; then # time match
+        _iptables_build_comma_list "$cur" Mon Tue Wed Thu Fri Sat Sun
+    else
+        i=$((cword - 2))
+        if ((i >= 1)); then # depend on option before previous option
+            if [[ ${words[i]} = @(-P|--policy) ]]; then # chain policy
+                COMPREPLY=( $( compgen -W 'ACCEPT DROP' -- $cur ) )
+            elif [[ ${words[i]} = @(--add-set|--del-set|--match-set|--map-set) ]]; then
+                # set match and SET target
+                # build flag list
+                prefix="" arr_tmp=(src dst)
+                str_tmp=$(IFS=\|; printf "%s" "${arr_tmp[*]}")
+                if [[ $cur ]]; then
+                    if [[ ${cur} = @($str_tmp) ]]; then
+                        prefix="${cur}," cur=""
+                    elif [[ ${cur:$((${#cur}-1))} = , ]]; then
+                        prefix="${cur}" cur=""
+                    elif [[ ${cur:$((${#cur}-1))} != , ]]; then
+                        if [[ ${cur##*,} != @($str_tmp) ]]; then
+                            prefix="${cur%${cur##*,}}"
+                            [[ $cur = *,* ]] && cur="${cur##*,}"
+                            [[ $prefix && ${prefix:$((${#prefix}-1))} != , ]] && prefix="${prefix},"
+                        else
+                            prefix="${cur}," cur=""
+                        fi
+                    fi
+                fi
+                (($(IFS=,; set -- ${prefix}; printf "%d\n" $#) < 5)) && compopt -o nospace
+                COMPREPLY=( $( compgen -P "${prefix}" -W '${arr_tmp[@]}' -- "$cur" ) )
+            elif [[ ${words[i]} = --tcp-flags ]]; then # tcp match tcp flags
+                _iptables_build_comma_list "$cur" SYN ACK FIN RST URG PSH ALL NONE
+            else
+                if [[ $str_last_is = match && ${arr_cmd_matches[${#arr_cmd_matches[@]}-1]} = sctp ]]
+                then # sctp match allows multiple option arguments
+                    for ((i=cword-1; i > last_match_index; i--)); do
+                        if [[ ${words[i]} = -?* && ${words[i]} != --chunk-types ]]
+                        then break
+                        elif [[ ${words[i]} = --chunk-types ]]; then
+                            if [[ $cur = *,* ]]; then
+                                _iptables_build_comma_list "$cur" DATA INIT INIT_ACK SACK \
+                                    HEARTBEAT HEARTBEAT_ACK ABORT SHUTDOWN SHUTDOWN_ACK \
+                                    ERROR COOKIE_ECHO COOKIE_ACK ECN_ECNE ECN_CWR \
+                                    SHUTDOWN_COMPLETE ASCONF ASCONF_ACK FORWARD_TSN
+                                break
+                            elif [[ $cur = *:* ]]; then
+                                str_tmp="${cur%%:*}" str_prefix="${cur%%:*}:" cur="${cur##*:}"
+                                if [[ $str_tmp = DATA ]]; then
+                                    COMPREPLY=( $( compgen -P "$str_prefix" \
+                                        -W 'I U B E i u b e' -- "$cur" ) )
+                                elif [[ $str_tmp = @(ABORT|SHUTDOWN_COMPLETE) ]]; then
+                                    COMPREPLY=( $( compgen -P "$str_prefix" -W 'T t' -- "$cur" ) )
+                                fi
+                                ((got_bashcompl)) && __ltrim_colon_completions "$str_prefix$cur"
+                                break
+                            elif [[ $cur = @(DATA|ABORT|SHUTDOWN_COMPLETE) ]]; then
+                                compopt -o nospace
+                                COMPREPLY=( "${cur}:" "${cur}," )
+                                break
+                            elif [[ $cur = @(INIT|INIT_ACK|SACK|HEARTBEAT|HEARTBEAT_ACK|SHUTDOWN|SHUTDOWN_ACK|ERROR|COOKIE_ECHO|COOKIE_ACK|ECN_ECNE|ECN_CWR|ASCONF|ASCONF_ACK|FORWARD_TSN) ]]
+                            then
+                                compopt -o nospace
+                                COMPREPLY=( "${cur}," )
+                                break
+                            else
+                                compopt -o nospace
+                                COMPREPLY=( $( compgen -W 'DATA INIT INIT_ACK SACK \
+                                    HEARTBEAT HEARTBEAT_ACK ABORT SHUTDOWN SHUTDOWN_ACK \
+                                    ERROR COOKIE_ECHO COOKIE_ACK ECN_ECNE ECN_CWR \
+                                    SHUTDOWN_COMPLETE ASCONF ASCONF_ACK FORWARD_TSN' \
+                                    -- $cur ) )
+                                break
+                            fi
+                        fi
+                    done
+                fi
+            fi
+        fi
+    fi
+elif [[ $cur = \$\(* ]]; then # command substitution
+    cur=${cur#??}
+    COMPREPLY=( $(compgen -c -P '$(' $cur) )
+elif [[ $cur = \$\{* ]]; then # variables with a leading `${'
+    cur=${1#??}
+    COMPREPLY=( $(compgen -v -P '${' -S '}' $cur) )
+elif [[ $cur = \$* ]]; then # variables with a leading `$'
+    cur=${cur#?}
+    COMPREPLY=( $(compgen -v -P '$' $cur ) )
+fi
+
+if [[ ${COMPREPLY[0]} = --modprobe= ]]; then
+    compopt -o nospace
+fi
+
+if [[ $_DEBUG_NF_COMPLETION ]]; then
+    printf "COMPREPLY:\n"
+    printf "<%s>\n" "${COMPREPLY[@]}"
+fi
+}
+complete -F _iptables_complete iptables ip6tables
+