"""
Support for iptables

Configuration Options
---------------------

The following options can be set in the minion config, grains, pillar, or
master config. The configuration is read using :py:func:`config.get
<salt.modules.config.get>`.

- ``iptables.save_filters``: List of REGEX strings to FILTER OUT matching lines

  This is useful for filtering out chains, rules, etc that you do not wish to
  persist, such as ephemeral Docker rules.

  The default is to not filter out anything.

  .. code-block:: yaml

      iptables.save_filters:
        - "-j CATTLE_PREROUTING"
        - "-j DOCKER"
        - "-A POSTROUTING"
        - "-A CATTLE_POSTROUTING"
        - "-A FORWARD"
"""

import logging
import os
import re
import string
import sys
import uuid

import salt.utils.args
import salt.utils.files
import salt.utils.path
from salt.exceptions import SaltException
from salt.state import STATE_INTERNAL_KEYWORDS as _STATE_INTERNAL_KEYWORDS

log = logging.getLogger(__name__)


def __virtual__():
    """
    Only load the module if iptables is installed
    """
    if not salt.utils.path.which("iptables"):
        return (
            False,
            "The iptables execution module cannot be loaded: iptables not installed.",
        )

    return True


def _iptables_cmd(family="ipv4"):
    """
    Return correct command based on the family, e.g. ipv4 or ipv6
    """
    if family == "ipv6":
        return salt.utils.path.which("ip6tables")
    else:
        return salt.utils.path.which("iptables")


def _has_option(option, family="ipv4"):
    """
    Return truth of whether iptables has `option`.  For example:

    .. code-block:: python

        _has_option('--wait')
        _has_option('--check', family='ipv6')
    """
    cmd = "{} --help".format(_iptables_cmd(family))
    if option in __salt__["cmd.run_stdout"](cmd, output_loglevel="quiet"):
        return True
    return False


def _conf(family="ipv4"):
    """
    Some distros have a specific location for config files
    """
    if __grains__["os_family"] == "RedHat":
        if family == "ipv6":
            return "/etc/sysconfig/ip6tables"
        else:
            return "/etc/sysconfig/iptables"
    elif __grains__["os_family"] == "Arch":
        if family == "ipv6":
            return "/etc/iptables/ip6tables.rules"
        else:
            return "/etc/iptables/iptables.rules"
    elif __grains__["os_family"] == "Debian":
        # This assumes the iptables-persistent package is installed
        if family == "ipv6":
            return "/etc/iptables/rules.v6"
        else:
            return "/etc/iptables/rules.v4"
    elif __grains__["os"] == "Gentoo":
        if family == "ipv6":
            return "/var/lib/ip6tables/rules-save"
        else:
            return "/var/lib/iptables/rules-save"
    elif __grains__["os_family"] == "Suse":
        # SuSE does not seem to use separate files for IPv4 and IPv6
        return "/etc/sysconfig/scripts/SuSEfirewall2-custom"
    elif __grains__["os_family"] == "Void":
        if family == "ipv4":
            return "/etc/iptables/iptables.rules"
        else:
            return "/etc/iptables/ip6tables.rules"
    elif __grains__["os"] == "Alpine":
        if family == "ipv6":
            return "/etc/iptables/rules6-save"
        else:
            return "/etc/iptables/rules-save"
    elif __grains__["os_family"] == "NILinuxRT":
        if family == "ipv6":
            return "/etc/natinst/share/ip6tables.conf"
        else:
            return "/etc/natinst/share/iptables.conf"
    else:
        raise SaltException(
            "Saving iptables to file is not"
            + " supported on {}.".format(__grains__["os"])
            + " Please file an issue with SaltStack"
        )


def _conf_save_filters():
    """
    Return array of strings from `save_filters` in config.

    This array will be pulled from minion config, minion grains,
    minion pillar, or master config.  The default value returned is [].

    .. code-block:: python

        _conf_save_filters()
    """
    config = __salt__["config.option"]("iptables.save_filters", [])
    return config


def _regex_iptables_save(cmd_output, filters=None):
    """
    Return string with `save_filter` regex entries removed.  For example:

    If `filters` is not provided, it will be pulled from minion config,
    minion grains, minion pillar, or master config. Default return value
    if no filters found is the original cmd_output string.

    .. code-block:: python

        _regex_iptables_save(cmd_output, ['-A DOCKER*'])
    """
    # grab RE compiled filters from context for performance
    if "iptables.save_filters" not in __context__:
        __context__["iptables.save_filters"] = []
        for pattern in filters or _conf_save_filters():
            try:
                __context__["iptables.save_filters"].append(re.compile(pattern))
            except re.error as e:
                log.warning("Skipping regex rule: '%s': %s", pattern, e)
                continue

    if __context__["iptables.save_filters"]:
        # line by line get rid of any regex matches
        _filtered_cmd_output = [
            line
            for line in cmd_output.splitlines(True)
            if not any(reg.search(line) for reg in __context__["iptables.save_filters"])
        ]
        return "".join(_filtered_cmd_output)

    return cmd_output


def version(family="ipv4"):
    """
    Return version from iptables --version

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.version

        IPv6:
        salt '*' iptables.version family=ipv6
    """
    cmd = "{} --version".format(_iptables_cmd(family))
    out = __salt__["cmd.run_stdout"](cmd).split()
    return out[1]


def build_rule(
    table="filter",
    chain=None,
    command=None,
    position="",
    full=None,
    family="ipv4",
    **kwargs
):
    """
    Build a well-formatted iptables rule based on kwargs. A `table` and `chain`
    are not required, unless `full` is True.

    If `full` is `True`, then `table`, `chain` and `command` are required.
    `command` may be specified as either a short option ('I') or a long option
    (`--insert`). This will return the iptables command, exactly as it would
    be used from the command line.

    If a position is required (as with `-I` or `-D`), it may be specified as
    `position`. This will only be useful if `full` is True.

    If `state` is passed, it will be ignored, use `connstate`.
    If `connstate` is passed in, it will automatically be changed to `state`.

    To pass in jump options that doesn't take arguments, pass in an empty
    string.

    .. note::

        Whereas iptables will accept ``-p``, ``--proto[c[o[l]]]`` as synonyms
        of ``--protocol``, if ``--proto`` appears in an iptables command after
        the appearance of ``-m policy``, it is interpreted as the ``--proto``
        option of the policy extension (see the iptables-extensions(8) man
        page).

    CLI Examples:

    .. code-block:: bash

        salt '*' iptables.build_rule match=state \\
            connstate=RELATED,ESTABLISHED jump=ACCEPT

        salt '*' iptables.build_rule filter INPUT command=I position=3 \\
            full=True match=state connstate=RELATED,ESTABLISHED jump=ACCEPT

        salt '*' iptables.build_rule filter INPUT command=A \\
            full=True match=state connstate=RELATED,ESTABLISHED \\
            source='127.0.0.1' jump=ACCEPT

        .. Invert Rules
        salt '*' iptables.build_rule filter INPUT command=A \\
            full=True match=state connstate=RELATED,ESTABLISHED \\
            source='!127.0.0.1' jump=ACCEPT

        salt '*' iptables.build_rule filter INPUT command=A \\
            full=True match=state connstate=RELATED,ESTABLISHED \\
            destination='not 127.0.0.1' jump=ACCEPT

        IPv6:
        salt '*' iptables.build_rule match=state \\
            connstate=RELATED,ESTABLISHED jump=ACCEPT \\
            family=ipv6
        salt '*' iptables.build_rule filter INPUT command=I position=3 \\
            full=True match=state connstate=RELATED,ESTABLISHED jump=ACCEPT \\
            family=ipv6
    """
    if "target" in kwargs:
        kwargs["jump"] = kwargs.pop("target")

    # Ignore name and state for this function
    kwargs.pop("name", None)
    kwargs.pop("state", None)

    for ignore in list(_STATE_INTERNAL_KEYWORDS) + ["chain", "save", "table"]:
        if ignore in kwargs:
            del kwargs[ignore]

    rule = []
    protocol = False
    bang_not_pat = re.compile(r"(!|not)\s?")

    def maybe_add_negation(arg):
        """
        Will check if the defined argument is intended to be negated,
        (i.e. prefixed with '!' or 'not'), and add a '! ' to the rule.

        The prefix will be removed from the value in the kwargs dict.
        """
        value = str(kwargs[arg])
        if value.startswith("!") or value.startswith("not"):
            kwargs[arg] = re.sub(bang_not_pat, "", value)
            return "! "
        return ""

    if "if" in kwargs:
        rule.append("{}-i {}".format(maybe_add_negation("if"), kwargs["if"]))
        del kwargs["if"]

    if "of" in kwargs:
        rule.append("{}-o {}".format(maybe_add_negation("of"), kwargs["of"]))
        del kwargs["of"]

    if "proto" in kwargs and kwargs.get("match") != "policy":
        kwargs["protocol"] = kwargs["proto"]
        del kwargs["proto"]
        # Handle the case 'proto' in kwargs and kwargs.get('match') == 'policy' below
    if "protocol" in kwargs:
        if not protocol:
            rule.append(
                "{}-p {}".format(maybe_add_negation("protocol"), kwargs["protocol"])
            )
            protocol = True
        del kwargs["protocol"]

    if "match" in kwargs:
        match_value = kwargs["match"]
        if not isinstance(match_value, list):
            match_value = match_value.split(",")
        for match in match_value:
            rule.append("-m {}".format(match))
            if "name_" in kwargs and match.strip() in ("pknock", "quota2", "recent"):
                rule.append("--name {}".format(kwargs["name_"]))
                del kwargs["name_"]
        if "proto" in kwargs and kwargs.get("match") == "policy":
            rule.append(
                "{}--proto {}".format(maybe_add_negation("proto"), kwargs["proto"])
            )
            del kwargs["proto"]
        del kwargs["match"]

    if "match-set" in kwargs:
        if isinstance(kwargs["match-set"], str):
            kwargs["match-set"] = [kwargs["match-set"]]
        for match_set in kwargs["match-set"]:
            negative_match_set = ""
            if match_set.startswith("!") or match_set.startswith("not"):
                negative_match_set = "! "
                match_set = re.sub(bang_not_pat, "", match_set)
            rule.append("-m set {}--match-set {}".format(negative_match_set, match_set))
        del kwargs["match-set"]

    if "connstate" in kwargs:
        if "-m state" not in rule:
            rule.append("-m state")

        rule.append(
            "{}--state {}".format(maybe_add_negation("connstate"), kwargs["connstate"])
        )

        del kwargs["connstate"]

    if "dport" in kwargs:
        rule.append("{}--dport {}".format(maybe_add_negation("dport"), kwargs["dport"]))
        del kwargs["dport"]

    if "sport" in kwargs:
        rule.append("{}--sport {}".format(maybe_add_negation("sport"), kwargs["sport"]))
        del kwargs["sport"]

    for multiport_arg in ("dports", "sports"):
        if multiport_arg in kwargs:
            if "-m multiport" not in rule:
                rule.append("-m multiport")
                if not protocol:
                    return "Error: protocol must be specified"

            mp_value = kwargs[multiport_arg]
            if isinstance(mp_value, list):
                if any(
                    i
                    for i in mp_value
                    if str(i).startswith("!") or str(i).startswith("not")
                ):
                    mp_value = [
                        re.sub(bang_not_pat, "", str(item)) for item in mp_value
                    ]
                    rule.append("!")
                dports = ",".join(str(i) for i in mp_value)
            else:
                if str(mp_value).startswith("!") or str(mp_value).startswith("not"):
                    dports = re.sub(bang_not_pat, "", mp_value)
                    rule.append("!")
                else:
                    dports = mp_value

            rule.append("--{} {}".format(multiport_arg, dports))
            del kwargs[multiport_arg]

    if "comment" in kwargs:
        if "-m comment" not in rule:
            rule.append("-m comment")

        rule.append('--comment "{}"'.format(kwargs["comment"]))
        del kwargs["comment"]

    # --set in ipset is deprecated, works but returns error.
    # rewrite to --match-set if not empty, otherwise treat as recent option
    if "set" in kwargs and kwargs["set"]:
        rule.append("{}--match-set {}".format(maybe_add_negation("set"), kwargs["set"]))
        del kwargs["set"]

    # Jumps should appear last, except for any arguments that are passed to
    # jumps, which of course need to follow.
    after_jump = []
    # All jump arguments as extracted from man iptables-extensions, man iptables,
    # man xtables-addons and http://www.iptables.info/en/iptables-targets-and-jumps.html
    after_jump_arguments = (
        "j",  # j and jump needs to be first
        "jump",
        # IPTABLES
        "add-set",
        "and-mark",
        "and-tos",
        "checksum-fill",
        "clamp-mss-to-pmtu",
        "clustermac",
        "ctevents",
        "ctmask",
        "del-set",
        "ecn-tcp-remove",
        "exist",
        "expevents",
        "gateway",
        "hash-init",
        "hashmode",
        "helper",
        "label",
        "local-node",
        "log-ip-options",
        "log-level",
        "log-prefix",
        "log-tcp-options",
        "log-tcp-sequence",
        "log-uid",
        "mask",
        "new",
        "nfmask",
        "nflog-group",
        "nflog-prefix",
        "nflog-range",
        "nflog-threshold",
        "nodst",
        "notrack",
        "on-ip",
        "on-port",
        "or-mark",
        "or-tos",
        "persistent",
        "queue-balance",
        "queue-bypass",
        "queue-num",
        "random",
        "rateest-ewmalog",
        "rateest-interval",
        "rateest-name",
        "reject-with",
        "restore",
        "restore-mark",
        #'save',  # no arg, problematic name: How do we avoid collision with this?
        "save-mark",
        "selctx",
        "set-class",
        "set-dscp",
        "set-dscp-class",
        "set-mark",
        "set-mss",
        "set-tos",
        "set-xmark",
        "strip-options",
        "timeout",
        "to",
        "to-destination",
        "to-ports",
        "to-source",
        "total-nodes",
        "tproxy-mark",
        "ttl-dec",
        "ttl-inc",
        "ttl-set",
        "type",
        "ulog-cprange",
        "ulog-nlgroup",
        "ulog-prefix",
        "ulog-qthreshold",
        "xor-mark",
        "xor-tos",
        "zone",
        # IPTABLES-EXTENSIONS
        "dst-pfx",
        "hl-dec",
        "hl-inc",
        "hl-set",
        "hmark-dport-mask",
        "hmark-dst-prefix",
        "hmark-mod",
        "hmark-offset",
        "hmark-proto-mask",
        "hmark-rnd",
        "hmark-spi-mask",
        "hmark-sport-mask",
        "hmark-src-prefix",
        "hmark-tuple",
        "led-always-blink",
        "led-delay",
        "led-trigger-id",
        "queue-cpu-fanout",
        "src-pfx",
        # WEB
        "to-port",
        # XTABLES
        "addr",
        "and-mask",
        "delude",
        "honeypot",
        "or-mask",
        "prefix",
        "reset",
        "reuse",
        "set-mac",
        "shift",
        "static",
        "tarpit",
        "tname",
        "ttl",
    )
    for after_jump_argument in after_jump_arguments:
        if after_jump_argument in kwargs:
            value = kwargs[after_jump_argument]
            if value in (None, ""):  # options without arguments
                after_jump.append("--{}".format(after_jump_argument))
            elif any(ws_char in str(value) for ws_char in string.whitespace):
                after_jump.append('--{} "{}"'.format(after_jump_argument, value))
            else:
                after_jump.append("--{} {}".format(after_jump_argument, value))
            del kwargs[after_jump_argument]

    for key in kwargs:
        negation = maybe_add_negation(key)
        # don't use .items() since maybe_add_negation removes the prefix from
        # the value in the kwargs, thus we need to fetch it after that has run
        value = kwargs[key]
        flag = "-" if len(key) == 1 else "--"
        value = "" if value in (None, "") else " {}".format(value)
        rule.append("{}{}{}{}".format(negation, flag, key, value))

    rule += after_jump

    if full:
        if not table:
            return "Error: Table needs to be specified"
        if not chain:
            return "Error: Chain needs to be specified"
        if not command:
            return "Error: Command needs to be specified"

        if command in "ACDIRLSFZNXPE":
            flag = "-"
        else:
            flag = "--"

        wait = "--wait" if _has_option("--wait", family) else ""

        return "{} {} -t {} {}{} {} {} {}".format(
            _iptables_cmd(family),
            wait,
            table,
            flag,
            command,
            chain,
            position,
            " ".join(rule),
        )

    return " ".join(rule)


def get_saved_rules(conf_file=None, family="ipv4"):
    """
    Return a data structure of the rules in the conf file

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.get_saved_rules

        IPv6:
        salt '*' iptables.get_saved_rules family=ipv6
    """
    return _parse_conf(conf_file=conf_file, family=family)


def get_rules(family="ipv4"):
    """
    Return a data structure of the current, in-memory rules

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.get_rules

        IPv6:
        salt '*' iptables.get_rules family=ipv6

    """
    return _parse_conf(in_mem=True, family=family)


def get_saved_policy(table="filter", chain=None, conf_file=None, family="ipv4"):
    """
    Return the current policy for the specified table/chain

    CLI Examples:

    .. code-block:: bash

        salt '*' iptables.get_saved_policy filter INPUT
        salt '*' iptables.get_saved_policy filter INPUT \\
            conf_file=/etc/iptables.saved

        IPv6:
        salt '*' iptables.get_saved_policy filter INPUT family=ipv6
        salt '*' iptables.get_saved_policy filter INPUT \\
            conf_file=/etc/iptables.saved family=ipv6

    """
    if not chain:
        return "Error: Chain needs to be specified"

    rules = _parse_conf(conf_file, family=family)
    try:
        return rules[table][chain]["policy"]
    except KeyError:
        return None


def get_policy(table="filter", chain=None, family="ipv4"):
    """
    Return the current policy for the specified table/chain

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.get_policy filter INPUT

        IPv6:
        salt '*' iptables.get_policy filter INPUT family=ipv6
    """
    if not chain:
        return "Error: Chain needs to be specified"

    rules = _parse_conf(in_mem=True, family=family)
    try:
        return rules[table][chain]["policy"]
    except KeyError:
        return None


def set_policy(table="filter", chain=None, policy=None, family="ipv4"):
    """
    Set the current policy for the specified table/chain

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.set_policy filter INPUT ACCEPT

        IPv6:
        salt '*' iptables.set_policy filter INPUT ACCEPT family=ipv6
    """
    if not chain:
        return "Error: Chain needs to be specified"
    if not policy:
        return "Error: Policy needs to be specified"

    wait = "--wait" if _has_option("--wait", family) else ""
    cmd = "{} {} -t {} -P {} {}".format(
        _iptables_cmd(family), wait, table, chain, policy
    )
    out = __salt__["cmd.run_stderr"](cmd)
    return out


def save(filename=None, family="ipv4"):
    """
    Save the current in-memory rules to disk

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.save /etc/sysconfig/iptables

        IPv6:
        salt '*' iptables.save /etc/sysconfig/iptables family=ipv6
    """
    if _conf() and not filename:
        filename = _conf(family)

    log.debug("Saving rules to %s", filename)

    parent_dir = os.path.dirname(filename)
    if not os.path.isdir(parent_dir):
        os.makedirs(parent_dir)
    cmd = "{}-save".format(_iptables_cmd(family))
    ipt = __salt__["cmd.run_stdout"](cmd)

    # regex out the output if configured with filters
    if _conf_save_filters():
        ipt = _regex_iptables_save(ipt)

    out = __salt__["file.write"](filename, ipt)
    return out


def check(table="filter", chain=None, rule=None, family="ipv4"):
    """
    Check for the existence of a rule in the table and chain

    This function accepts a rule in a standard iptables command format,
        starting with the chain. Trying to force users to adapt to a new
        method of creating rules would be irritating at best, and we
        already have a parser that can handle it.

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.check filter INPUT \\
            rule='-m state --state RELATED,ESTABLISHED -j ACCEPT'

        IPv6:
        salt '*' iptables.check filter INPUT \\
            rule='-m state --state RELATED,ESTABLISHED -j ACCEPT' \\
            family=ipv6
    """
    if not chain:
        return "Error: Chain needs to be specified"
    if not rule:
        return "Error: Rule needs to be specified"
    ipt_cmd = _iptables_cmd(family)

    if _has_option("--check", family):
        cmd = "{} -t {} -C {} {}".format(ipt_cmd, table, chain, rule)
        out = __salt__["cmd.run_stderr"](cmd, output_loglevel="quiet")
    else:
        _chain_name = hex(uuid.getnode())

        # Create temporary table
        __salt__["cmd.run"]("{} -t {} -N {}".format(ipt_cmd, table, _chain_name))
        __salt__["cmd.run"](
            "{} -t {} -A {} {}".format(ipt_cmd, table, _chain_name, rule)
        )

        out = __salt__["cmd.run_stdout"]("{}-save".format(ipt_cmd))

        # Clean up temporary table
        __salt__["cmd.run"]("{} -t {} -F {}".format(ipt_cmd, table, _chain_name))
        __salt__["cmd.run"]("{} -t {} -X {}".format(ipt_cmd, table, _chain_name))

        for i in out.splitlines():
            if i.startswith("-A {}".format(_chain_name)):
                if i.replace(_chain_name, chain) in out.splitlines():
                    return True

        return False

    if not out:
        return True
    return out


def check_chain(table="filter", chain=None, family="ipv4"):
    """
    .. versionadded:: 2014.1.0

    Check for the existence of a chain in the table

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.check_chain filter INPUT

        IPv6:
        salt '*' iptables.check_chain filter INPUT family=ipv6
    """

    if not chain:
        return "Error: Chain needs to be specified"

    cmd = "{}-save -t {}".format(_iptables_cmd(family), table)
    out = __salt__["cmd.run_stdout"](cmd).find(":{} ".format(chain))

    if out != -1:
        out = True
    else:
        out = False

    return out


def new_chain(table="filter", chain=None, family="ipv4"):
    """
    .. versionadded:: 2014.1.0

    Create new custom chain to the specified table.

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.new_chain filter CUSTOM_CHAIN

        IPv6:
        salt '*' iptables.new_chain filter CUSTOM_CHAIN family=ipv6
    """

    if not chain:
        return "Error: Chain needs to be specified"

    wait = "--wait" if _has_option("--wait", family) else ""
    cmd = "{} {} -t {} -N {}".format(_iptables_cmd(family), wait, table, chain)
    out = __salt__["cmd.run_stderr"](cmd)

    if not out:
        out = True
    return out


def delete_chain(table="filter", chain=None, family="ipv4"):
    """
    .. versionadded:: 2014.1.0

    Delete custom chain to the specified table.

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.delete_chain filter CUSTOM_CHAIN

        IPv6:
        salt '*' iptables.delete_chain filter CUSTOM_CHAIN family=ipv6
    """

    if not chain:
        return "Error: Chain needs to be specified"

    wait = "--wait" if _has_option("--wait", family) else ""
    cmd = "{} {} -t {} -X {}".format(_iptables_cmd(family), wait, table, chain)
    out = __salt__["cmd.run_stderr"](cmd)

    if not out:
        out = True
    return out


def append(table="filter", chain=None, rule=None, family="ipv4"):
    """
    Append a rule to the specified table/chain.

    This function accepts a rule in a standard iptables command format,
        starting with the chain. Trying to force users to adapt to a new
        method of creating rules would be irritating at best, and we
        already have a parser that can handle it.

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.append filter INPUT \\
            rule='-m state --state RELATED,ESTABLISHED -j ACCEPT'

        IPv6:
        salt '*' iptables.append filter INPUT \\
            rule='-m state --state RELATED,ESTABLISHED -j ACCEPT' \\
            family=ipv6
    """
    if not chain:
        return "Error: Chain needs to be specified"
    if not rule:
        return "Error: Rule needs to be specified"

    wait = "--wait" if _has_option("--wait", family) else ""
    returnCheck = check(table, chain, rule, family)
    if isinstance(returnCheck, bool) and returnCheck:
        return False
    cmd = "{} {} -t {} -A {} {}".format(_iptables_cmd(family), wait, table, chain, rule)
    out = __salt__["cmd.run_stderr"](cmd)
    return not out


def insert(table="filter", chain=None, position=None, rule=None, family="ipv4"):
    """
    Insert a rule into the specified table/chain, at the specified position.

    This function accepts a rule in a standard iptables command format,
        starting with the chain. Trying to force users to adapt to a new
        method of creating rules would be irritating at best, and we
        already have a parser that can handle it.

    If the position specified is a negative number, then the insert will be
        performed counting from the end of the list. For instance, a position
        of -1 will insert the rule as the second to last rule. To insert a rule
        in the last position, use the append function instead.

    CLI Examples:

    .. code-block:: bash

        salt '*' iptables.insert filter INPUT position=3 \\
            rule='-m state --state RELATED,ESTABLISHED -j ACCEPT'

        IPv6:
        salt '*' iptables.insert filter INPUT position=3 \\
            rule='-m state --state RELATED,ESTABLISHED -j ACCEPT' \\
            family=ipv6
    """
    if not chain:
        return "Error: Chain needs to be specified"
    if not position:
        return "Error: Position needs to be specified or use append (-A)"
    if not rule:
        return "Error: Rule needs to be specified"

    if position < 0:
        rules = get_rules(family=family)
        size = len(rules[table][chain]["rules"])
        position = (size + position) + 1
        if position == 0:
            position = 1

    wait = "--wait" if _has_option("--wait", family) else ""
    returnCheck = check(table, chain, rule, family)
    if isinstance(returnCheck, bool) and returnCheck:
        return False
    cmd = "{} {} -t {} -I {} {} {}".format(
        _iptables_cmd(family), wait, table, chain, position, rule
    )
    out = __salt__["cmd.run_stderr"](cmd)
    return out


def delete(table, chain=None, position=None, rule=None, family="ipv4"):
    """
    Delete a rule from the specified table/chain, specifying either the rule
        in its entirety, or the rule's position in the chain.

    This function accepts a rule in a standard iptables command format,
        starting with the chain. Trying to force users to adapt to a new
        method of creating rules would be irritating at best, and we
        already have a parser that can handle it.

    CLI Examples:

    .. code-block:: bash

        salt '*' iptables.delete filter INPUT position=3
        salt '*' iptables.delete filter INPUT \\
            rule='-m state --state RELATED,ESTABLISHED -j ACCEPT'

        IPv6:
        salt '*' iptables.delete filter INPUT position=3 family=ipv6
        salt '*' iptables.delete filter INPUT \\
            rule='-m state --state RELATED,ESTABLISHED -j ACCEPT' \\
            family=ipv6
    """

    if position and rule:
        return "Error: Only specify a position or a rule, not both"

    if position:
        rule = position

    wait = "--wait" if _has_option("--wait", family) else ""
    cmd = "{} {} -t {} -D {} {}".format(_iptables_cmd(family), wait, table, chain, rule)
    out = __salt__["cmd.run_stderr"](cmd)
    return out


def flush(table="filter", chain="", family="ipv4"):
    """
    Flush the chain in the specified table, flush all chains in the specified
    table if not specified chain.

    CLI Example:

    .. code-block:: bash

        salt '*' iptables.flush filter INPUT

        IPv6:
        salt '*' iptables.flush filter INPUT family=ipv6
    """

    wait = "--wait" if _has_option("--wait", family) else ""
    cmd = "{} {} -t {} -F {}".format(_iptables_cmd(family), wait, table, chain)
    out = __salt__["cmd.run_stderr"](cmd)
    return out


def _parse_conf(conf_file=None, in_mem=False, family="ipv4"):
    """
    If a file is not passed in, and the correct one for this OS is not
    detected, return False
    """
    if _conf() and not conf_file and not in_mem:
        conf_file = _conf(family)

    rules = ""
    if conf_file:
        with salt.utils.files.fopen(conf_file, "r") as ifile:
            rules = ifile.read()
    elif in_mem:
        cmd = "{}-save".format(_iptables_cmd(family))
        rules = __salt__["cmd.run_stdout"](cmd)
    else:
        raise SaltException("A file was not found to parse")

    ret = {}
    table = ""
    parser = _parser()
    for line in rules.splitlines():
        line = salt.utils.stringutils.to_unicode(line)
        if line.startswith("*"):
            table = line.replace("*", "")
            ret[table] = {}
        elif line.startswith(":"):
            comps = line.split()
            chain = comps[0].replace(":", "")
            ret[table][chain] = {}
            ret[table][chain]["policy"] = comps[1]
            counters = comps[2].replace("[", "").replace("]", "")
            (pcount, bcount) = counters.split(":")
            ret[table][chain]["packet count"] = pcount
            ret[table][chain]["byte count"] = bcount
            ret[table][chain]["rules"] = []
            ret[table][chain]["rules_comment"] = {}
        elif line.startswith("-A"):
            args = salt.utils.args.shlex_split(line)
            index = 0
            while index + 1 < len(args):
                swap = args[index] == "!" and args[index + 1].startswith("-")
                if swap:
                    args[index], args[index + 1] = args[index + 1], args[index]
                if args[index].startswith("-"):
                    index += 1
                    if args[index].startswith("-") or (args[index] == "!" and not swap):
                        args.insert(index, "")
                    else:
                        while (
                            index + 1 < len(args)
                            and args[index + 1] != "!"
                            and not args[index + 1].startswith("-")
                        ):
                            args[index] += " {}".format(args.pop(index + 1))
                index += 1
            if args[-1].startswith("-"):
                args.append("")
            parsed_args = []
            opts, _ = parser.parse_known_args(args)
            parsed_args = vars(opts)
            ret_args = {}
            chain = parsed_args["append"]
            for arg in parsed_args:
                if parsed_args[arg] and arg != "append":
                    ret_args[arg] = parsed_args[arg]
            if parsed_args["comment"] is not None:
                comment = parsed_args["comment"][0].strip('"')
                ret[table][chain[0]]["rules_comment"][comment] = ret_args
            ret[table][chain[0]]["rules"].append(ret_args)
    return ret


def _parser():
    """
    This function attempts to list all the options documented in the
    iptables(8) and iptables-extensions(8) man pages.  They will not all be
    used by all parts of the module; use them intelligently and appropriately.
    """
    add_arg = None
    if sys.version.startswith("2.6"):
        import optparse

        parser = optparse.OptionParser()
        add_arg = parser.add_option
    else:
        import argparse  # pylint: disable=minimum-python-version

        parser = argparse.ArgumentParser()
        add_arg = parser.add_argument

    # COMMANDS
    add_arg("-A", "--append", dest="append", action="append")
    add_arg("-D", "--delete", dest="delete", action="append")
    add_arg("-I", "--insert", dest="insert", action="append")
    add_arg("-R", "--replace", dest="replace", action="append")
    add_arg("-L", "--list", dest="list", action="append")
    add_arg("-F", "--flush", dest="flush", action="append")
    add_arg("-Z", "--zero", dest="zero", action="append")
    add_arg("-N", "--new-chain", dest="new-chain", action="append")
    add_arg("-X", "--delete-chain", dest="delete-chain", action="append")
    add_arg("-P", "--policy", dest="policy", action="append")
    add_arg("-E", "--rename-chain", dest="rename-chain", action="append")

    # PARAMETERS
    add_arg("-p", "--protocol", dest="protocol", action="append")
    add_arg("-s", "--source", dest="source", action="append")
    add_arg("-d", "--destination", dest="destination", action="append")
    add_arg("-j", "--jump", dest="jump", action="append")
    add_arg("-g", "--goto", dest="goto", action="append")
    add_arg("-i", "--in-interface", dest="in-interface", action="append")
    add_arg("-o", "--out-interface", dest="out-interface", action="append")
    add_arg("-f", "--fragment", dest="fragment", action="append")
    add_arg("-c", "--set-counters", dest="set-counters", action="append")

    # MATCH EXTENSIONS
    add_arg("-m", "--match", dest="match", action="append")
    ## addrtype
    add_arg("--src-type", dest="src-type", action="append")
    add_arg("--dst-type", dest="dst-type", action="append")
    add_arg("--limit-iface-in", dest="limit-iface-in", action="append")
    add_arg("--limit-iface-out", dest="limit-iface-out", action="append")
    ## ah
    add_arg("--ahspi", dest="ahspi", action="append")
    add_arg("--ahlen", dest="ahlen", action="append")
    add_arg("--ahres", dest="ahres", action="append")
    ## bpf
    add_arg("--bytecode", dest="bytecode", action="append")
    ## cgroup
    add_arg("--cgroup", dest="cgroup", action="append")
    ## cluster
    add_arg("--cluster-total-nodes", dest="cluster-total-nodes", action="append")
    add_arg("--cluster-local-node", dest="cluster-local-node", action="append")
    add_arg("--cluster-local-nodemask", dest="cluster-local-nodemask", action="append")
    add_arg("--cluster-hash-seed", dest="cluster-hash-seed", action="append")
    add_arg("--h-length", dest="h-length", action="append")
    add_arg("--mangle-mac-s", dest="mangle-mac-s", action="append")
    add_arg("--mangle-mac-d", dest="mangle-mac-d", action="append")
    ## comment
    add_arg("--comment", dest="comment", action="append")
    ## connbytes
    add_arg("--connbytes", dest="connbytes", action="append")
    add_arg("--connbytes-dir", dest="connbytes-dir", action="append")
    add_arg("--connbytes-mode", dest="connbytes-mode", action="append")
    ## connlabel
    add_arg("--label", dest="label", action="append")
    ## connlimit
    add_arg("--connlimit-upto", dest="connlimit-upto", action="append")
    add_arg("--connlimit-above", dest="connlimit-above", action="append")
    add_arg("--connlimit-mask", dest="connlimit-mask", action="append")
    add_arg("--connlimit-saddr", dest="connlimit-saddr", action="append")
    add_arg("--connlimit-daddr", dest="connlimit-daddr", action="append")
    ## connmark
    add_arg("--mark", dest="mark", action="append")
    ## conntrack
    add_arg("--ctstate", dest="ctstate", action="append")
    add_arg("--ctproto", dest="ctproto", action="append")
    add_arg("--ctorigsrc", dest="ctorigsrc", action="append")
    add_arg("--ctorigdst", dest="ctorigdst", action="append")
    add_arg("--ctreplsrc", dest="ctreplsrc", action="append")
    add_arg("--ctrepldst", dest="ctrepldst", action="append")
    add_arg("--ctorigsrcport", dest="ctorigsrcport", action="append")
    add_arg("--ctorigdstport", dest="ctorigdstport", action="append")
    add_arg("--ctreplsrcport", dest="ctreplsrcport", action="append")
    add_arg("--ctrepldstport", dest="ctrepldstport", action="append")
    add_arg("--ctstatus", dest="ctstatus", action="append")
    add_arg("--ctexpire", dest="ctexpire", action="append")
    add_arg("--ctdir", dest="ctdir", action="append")
    ## cpu
    add_arg("--cpu", dest="cpu", action="append")
    ## dccp
    add_arg("--sport", "--source-port", dest="source_port", action="append")
    add_arg("--dport", "--destination-port", dest="destination_port", action="append")
    add_arg("--dccp-types", dest="dccp-types", action="append")
    add_arg("--dccp-option", dest="dccp-option", action="append")
    ## devgroup
    add_arg("--src-group", dest="src-group", action="append")
    add_arg("--dst-group", dest="dst-group", action="append")
    ## dscp
    add_arg("--dscp", dest="dscp", action="append")
    add_arg("--dscp-class", dest="dscp-class", action="append")
    ## dst
    add_arg("--dst-len", dest="dst-len", action="append")
    add_arg("--dst-opts", dest="dst-opts", action="append")
    ## ecn
    add_arg("--ecn-tcp-cwr", dest="ecn-tcp-cwr", action="append")
    add_arg("--ecn-tcp-ece", dest="ecn-tcp-ece", action="append")
    add_arg("--ecn-ip-ect", dest="ecn-ip-ect", action="append")
    ## esp
    add_arg("--espspi", dest="espspi", action="append")
    ## frag
    add_arg("--fragid", dest="fragid", action="append")
    add_arg("--fraglen", dest="fraglen", action="append")
    add_arg("--fragres", dest="fragres", action="append")
    add_arg("--fragfirst", dest="fragfirst", action="append")
    add_arg("--fragmore", dest="fragmore", action="append")
    add_arg("--fraglast", dest="fraglast", action="append")
    ## hashlimit
    add_arg("--hashlimit-upto", dest="hashlimit-upto", action="append")
    add_arg("--hashlimit-above", dest="hashlimit-above", action="append")
    add_arg("--hashlimit-burst", dest="hashlimit-burst", action="append")
    add_arg("--hashlimit-mode", dest="hashlimit-mode", action="append")
    add_arg("--hashlimit-srcmask", dest="hashlimit-srcmask", action="append")
    add_arg("--hashlimit-dstmask", dest="hashlimit-dstmask", action="append")
    add_arg("--hashlimit-name", dest="hashlimit-name", action="append")
    add_arg("--hashlimit-htable-size", dest="hashlimit-htable-size", action="append")
    add_arg("--hashlimit-htable-max", dest="hashlimit-htable-max", action="append")
    add_arg(
        "--hashlimit-htable-expire", dest="hashlimit-htable-expire", action="append"
    )
    add_arg(
        "--hashlimit-htable-gcinterval",
        dest="hashlimit-htable-gcinterval",
        action="append",
    )
    ## hbh
    add_arg("--hbh-len", dest="hbh-len", action="append")
    add_arg("--hbh-opts", dest="hbh-opts", action="append")
    ## helper
    add_arg("--helper", dest="helper", action="append")
    ## hl
    add_arg("--hl-eq", dest="hl-eq", action="append")
    add_arg("--hl-lt", dest="hl-lt", action="append")
    add_arg("--hl-gt", dest="hl-gt", action="append")
    ## icmp
    add_arg("--icmp-type", dest="icmp-type", action="append")
    ## icmp6
    add_arg("--icmpv6-type", dest="icmpv6-type", action="append")
    ## iprange
    add_arg("--src-range", dest="src-range", action="append")
    add_arg("--dst-range", dest="dst-range", action="append")
    ## ipv6header
    add_arg("--soft", dest="soft", action="append")
    add_arg("--header", dest="header", action="append")
    ## ipvs
    add_arg("--ipvs", dest="ipvs", action="append")
    add_arg("--vproto", dest="vproto", action="append")
    add_arg("--vaddr", dest="vaddr", action="append")
    add_arg("--vport", dest="vport", action="append")
    add_arg("--vdir", dest="vdir", action="append")
    add_arg("--vmethod", dest="vmethod", action="append")
    add_arg("--vportctl", dest="vportctl", action="append")
    ## length
    add_arg("--length", dest="length", action="append")
    ## limit
    add_arg("--limit", dest="limit", action="append")
    add_arg("--limit-burst", dest="limit-burst", action="append")
    ## mac
    add_arg("--mac-source", dest="mac-source", action="append")
    ## mh
    add_arg("--mh-type", dest="mh-type", action="append")
    ## multiport
    add_arg("--sports", "--source-ports", dest="source-ports", action="append")
    add_arg(
        "--dports", "--destination-ports", dest="destination-ports", action="append"
    )
    add_arg("--ports", dest="ports", action="append")
    ## nfacct
    add_arg("--nfacct-name", dest="nfacct-name", action="append")
    ## osf
    add_arg("--genre", dest="genre", action="append")
    add_arg("--ttl", dest="ttl", action="append")
    add_arg("--log", dest="log", action="append")
    ## owner
    add_arg("--uid-owner", dest="uid-owner", action="append")
    add_arg("--gid-owner", dest="gid-owner", action="append")
    add_arg("--socket-exists", dest="socket-exists", action="append")
    ## physdev
    add_arg("--physdev-in", dest="physdev-in", action="append")
    add_arg("--physdev-out", dest="physdev-out", action="append")
    add_arg("--physdev-is-in", dest="physdev-is-in", action="append")
    add_arg("--physdev-is-out", dest="physdev-is-out", action="append")
    add_arg("--physdev-is-bridged", dest="physdev-is-bridged", action="append")
    ## pkttype
    add_arg("--pkt-type", dest="pkt-type", action="append")
    ## policy
    add_arg("--dir", dest="dir", action="append")
    add_arg("--pol", dest="pol", action="append")
    add_arg("--strict", dest="strict", action="append")
    add_arg("--reqid", dest="reqid", action="append")
    add_arg("--spi", dest="spi", action="append")
    add_arg("--proto", dest="proto", action="append")
    add_arg("--mode", dest="mode", action="append")
    add_arg("--tunnel-src", dest="tunnel-src", action="append")
    add_arg("--tunnel-dst", dest="tunnel-dst", action="append")
    add_arg("--next", dest="next", action="append")
    ## quota
    add_arg("--quota", dest="quota", action="append")
    ## rateest
    add_arg("--rateest", dest="rateest", action="append")
    add_arg("--rateest1", dest="rateest1", action="append")
    add_arg("--rateest2", dest="rateest2", action="append")
    add_arg("--rateest-delta", dest="rateest-delta", action="append")
    add_arg("--rateest-bps", dest="rateest-bps", action="append")
    add_arg("--rateest-bps1", dest="rateest-bps1", action="append")
    add_arg("--rateest-bps2", dest="rateest-bps2", action="append")
    add_arg("--rateest-pps", dest="rateest-pps", action="append")
    add_arg("--rateest-pps1", dest="rateest-pps1", action="append")
    add_arg("--rateest-pps2", dest="rateest-pps2", action="append")
    add_arg("--rateest-lt", dest="rateest-lt", action="append")
    add_arg("--rateest-gt", dest="rateest-gt", action="append")
    add_arg("--rateest-eq", dest="rateest-eq", action="append")
    add_arg("--rateest-name", dest="rateest-name", action="append")
    add_arg("--rateest-interval", dest="rateest-interval", action="append")
    add_arg("--rateest-ewma", dest="rateest-ewma", action="append")
    ## realm
    add_arg("--realm", dest="realm", action="append")
    ## recent
    add_arg("--name", dest="name", action="append")
    add_arg("--set", dest="set", action="append")
    add_arg("--rsource", dest="rsource", action="append")
    add_arg("--rdest", dest="rdest", action="append")
    add_arg("--mask", dest="mask", action="append")
    add_arg("--rcheck", dest="rcheck", action="append")
    add_arg("--update", dest="update", action="append")
    add_arg("--remove", dest="remove", action="append")
    add_arg("--seconds", dest="seconds", action="append")
    add_arg("--reap", dest="reap", action="append")
    add_arg("--hitcount", dest="hitcount", action="append")
    add_arg("--rttl", dest="rttl", action="append")
    ## rpfilter
    add_arg("--loose", dest="loose", action="append")
    add_arg("--validmark", dest="validmark", action="append")
    add_arg("--accept-local", dest="accept-local", action="append")
    add_arg("--invert", dest="invert", action="append")
    ## rt
    add_arg("--rt-type", dest="rt-type", action="append")
    add_arg("--rt-segsleft", dest="rt-segsleft", action="append")
    add_arg("--rt-len", dest="rt-len", action="append")
    add_arg("--rt-0-res", dest="rt-0-res", action="append")
    add_arg("--rt-0-addrs", dest="rt-0-addrs", action="append")
    add_arg("--rt-0-not-strict", dest="rt-0-not-strict", action="append")
    ## sctp
    add_arg("--chunk-types", dest="chunk-types", action="append")
    ## set
    add_arg("--match-set", dest="match-set", action="append")
    add_arg("--return-nomatch", dest="return-nomatch", action="append")
    add_arg("--update-counters", dest="update-counters", action="append")
    add_arg("--update-subcounters", dest="update-subcounters", action="append")
    add_arg("--packets-eq", dest="packets-eq", action="append")
    add_arg("--packets-lt", dest="packets-lt", action="append")
    add_arg("--packets-gt", dest="packets-gt", action="append")
    add_arg("--bytes-eq", dest="bytes-eq", action="append")
    add_arg("--bytes-lt", dest="bytes-lt", action="append")
    add_arg("--bytes-gt", dest="bytes-gt", action="append")
    ## socket
    add_arg("--transparent", dest="transparent", action="append")
    add_arg("--nowildcard", dest="nowildcard", action="append")
    ## state
    add_arg("--state", dest="state", action="append")
    ## statistic
    add_arg("--probability", dest="probability", action="append")
    add_arg("--every", dest="every", action="append")
    add_arg("--packet", dest="packet", action="append")
    ## string
    add_arg("--algo", dest="algo", action="append")
    add_arg("--from", dest="from", action="append")
    add_arg("--to", dest="to", action="append")
    add_arg("--string", dest="string", action="append")
    add_arg("--hex-string", dest="hex-string", action="append")
    ## tcp
    add_arg("--tcp-flags", dest="tcp-flags", action="append")
    add_arg("--syn", dest="syn", action="append")
    add_arg("--tcp-option", dest="tcp-option", action="append")
    ## tcpmss
    add_arg("--mss", dest="mss", action="append")
    ## time
    add_arg("--datestart", dest="datestart", action="append")
    add_arg("--datestop", dest="datestop", action="append")
    add_arg("--timestart", dest="timestart", action="append")
    add_arg("--timestop", dest="timestop", action="append")
    add_arg("--monthdays", dest="monthdays", action="append")
    add_arg("--weekdays", dest="weekdays", action="append")
    add_arg("--contiguous", dest="contiguous", action="append")
    add_arg("--kerneltz", dest="kerneltz", action="append")
    add_arg("--utc", dest="utc", action="append")
    add_arg("--localtz", dest="localtz", action="append")
    ## tos
    add_arg("--tos", dest="tos", action="append")
    ## ttl
    add_arg("--ttl-eq", dest="ttl-eq", action="append")
    add_arg("--ttl-gt", dest="ttl-gt", action="append")
    add_arg("--ttl-lt", dest="ttl-lt", action="append")
    ## u32
    add_arg("--u32", dest="u32", action="append")

    # Xtables-addons matches
    ## condition
    add_arg("--condition", dest="condition", action="append")
    ## dhcpmac
    add_arg("--mac", dest="mac", action="append")
    ## fuzzy
    add_arg("--lower-limit", dest="lower-limit", action="append")
    add_arg("--upper-limit", dest="upper-limit", action="append")
    ## geoip
    add_arg("--src-cc", "--source-country", dest="source-country", action="append")
    add_arg(
        "--dst-cc", "--destination-country", dest="destination-country", action="append"
    )
    ## gradm
    add_arg("--enabled", dest="enabled", action="append")
    add_arg("--disabled", dest="disabled", action="append")
    ## iface
    add_arg("--iface", dest="iface", action="append")
    add_arg("--dev-in", dest="dev-in", action="append")
    add_arg("--dev-out", dest="dev-out", action="append")
    add_arg("--up", dest="up", action="append")
    add_arg("--down", dest="down", action="append")
    add_arg("--broadcast", dest="broadcast", action="append")
    add_arg("--loopback", dest="loopback", action="append")
    add_arg("--pointtopoint", dest="pointtopoint", action="append")
    add_arg("--running", dest="running", action="append")
    add_arg("--noarp", dest="noarp", action="append")
    add_arg("--arp", dest="arp", action="append")
    add_arg("--promisc", dest="promisc", action="append")
    add_arg("--multicast", dest="multicast", action="append")
    add_arg("--dynamic", dest="dynamic", action="append")
    add_arg("--lower-up", dest="lower-up", action="append")
    add_arg("--dormant", dest="dormant", action="append")
    ## ipp2p
    add_arg("--edk", dest="edk", action="append")
    add_arg("--kazaa", dest="kazaa", action="append")
    add_arg("--gnu", dest="gnu", action="append")
    add_arg("--dc", dest="dc", action="append")
    add_arg("--bit", dest="bit", action="append")
    add_arg("--apple", dest="apple", action="append")
    add_arg("--soul", dest="soul", action="append")
    add_arg("--winmx", dest="winmx", action="append")
    add_arg("--ares", dest="ares", action="append")
    add_arg("--debug", dest="debug", action="append")
    ## ipv4options
    add_arg("--flags", dest="flags", action="append")
    add_arg("--any", dest="any", action="append")
    ## length2
    add_arg("--layer3", dest="layer3", action="append")
    add_arg("--layer4", dest="layer4", action="append")
    add_arg("--layer5", dest="layer5", action="append")
    ## lscan
    add_arg("--stealth", dest="stealth", action="append")
    add_arg("--synscan", dest="synscan", action="append")
    add_arg("--cnscan", dest="cnscan", action="append")
    add_arg("--grscan", dest="grscan", action="append")
    ## psd
    add_arg("--psd-weight-threshold", dest="psd-weight-threshold", action="append")
    add_arg("--psd-delay-threshold", dest="psd-delay-threshold", action="append")
    add_arg("--psd-lo-ports-weight", dest="psd-lo-ports-weight", action="append")
    add_arg("--psd-hi-ports-weight", dest="psd-hi-ports-weight", action="append")
    ## quota2
    add_arg("--grow", dest="grow", action="append")
    add_arg("--no-change", dest="no-change", action="append")
    add_arg("--packets", dest="packets", action="append")
    ## pknock
    add_arg("--knockports", dest="knockports", action="append")
    add_arg("--time", dest="time", action="append")
    add_arg("--autoclose", dest="autoclose", action="append")
    add_arg("--checkip", dest="checkip", action="append")

    # TARGET EXTENSIONS
    ## AUDIT
    add_arg("--type", dest="type", action="append")
    ## CHECKSUM
    add_arg("--checksum-fill", dest="checksum-fill", action="append")
    ## CLASSIFY
    add_arg("--set-class", dest="set-class", action="append")
    ## CLUSTERIP
    add_arg("--new", dest="new", action="append")
    add_arg("--hashmode", dest="hashmode", action="append")
    add_arg("--clustermac", dest="clustermac", action="append")
    add_arg("--total-nodes", dest="total-nodes", action="append")
    add_arg("--local-node", dest="local-node", action="append")
    add_arg("--hash-init", dest="hash-init", action="append")
    ## CONNMARK
    add_arg("--set-xmark", dest="set-xmark", action="append")
    add_arg("--save-mark", dest="save-mark", action="append")
    add_arg("--restore-mark", dest="restore-mark", action="append")
    add_arg("--and-mark", dest="and-mark", action="append")
    add_arg("--or-mark", dest="or-mark", action="append")
    add_arg("--xor-mark", dest="xor-mark", action="append")
    add_arg("--set-mark", dest="set-mark", action="append")
    add_arg("--nfmask", dest="nfmask", action="append")
    add_arg("--ctmask", dest="ctmask", action="append")
    ## CONNSECMARK
    add_arg("--save", dest="save", action="append")
    add_arg("--restore", dest="restore", action="append")
    ## CT
    add_arg("--notrack", dest="notrack", action="append")
    add_arg("--ctevents", dest="ctevents", action="append")
    add_arg("--expevents", dest="expevents", action="append")
    add_arg("--zone", dest="zone", action="append")
    add_arg("--timeout", dest="timeout", action="append")
    ## DNAT
    add_arg("--to-destination", dest="to-destination", action="append")
    add_arg("--random", dest="random", action="append")
    add_arg("--persistent", dest="persistent", action="append")
    ## DNPT
    add_arg("--src-pfx", dest="src-pfx", action="append")
    add_arg("--dst-pfx", dest="dst-pfx", action="append")
    ## DSCP
    add_arg("--set-dscp", dest="set-dscp", action="append")
    add_arg("--set-dscp-class", dest="set-dscp-class", action="append")
    ## ECN
    add_arg("--ecn-tcp-remove", dest="ecn-tcp-remove", action="append")
    ## HL
    add_arg("--hl-set", dest="hl-set", action="append")
    add_arg("--hl-dec", dest="hl-dec", action="append")
    add_arg("--hl-inc", dest="hl-inc", action="append")
    ## HMARK
    add_arg("--hmark-tuple", dest="hmark-tuple", action="append")
    add_arg("--hmark-mod", dest="hmark-mod", action="append")
    add_arg("--hmark-offset", dest="hmark-offset", action="append")
    add_arg("--hmark-src-prefix", dest="hmark-src-prefix", action="append")
    add_arg("--hmark-dst-prefix", dest="hmark-dst-prefix", action="append")
    add_arg("--hmark-sport-mask", dest="hmark-sport-mask", action="append")
    add_arg("--hmark-dport-mask", dest="hmark-dport-mask", action="append")
    add_arg("--hmark-spi-mask", dest="hmark-spi-mask", action="append")
    add_arg("--hmark-proto-mask", dest="hmark-proto-mask", action="append")
    add_arg("--hmark-rnd", dest="hmark-rnd", action="append")
    ## LED
    add_arg("--led-trigger-id", dest="led-trigger-id", action="append")
    add_arg("--led-delay", dest="led-delay", action="append")
    add_arg("--led-always-blink", dest="led-always-blink", action="append")
    ## LOG
    add_arg("--log-level", dest="log-level", action="append")
    add_arg("--log-prefix", dest="log-prefix", action="append")
    add_arg("--log-tcp-sequence", dest="log-tcp-sequence", action="append")
    add_arg("--log-tcp-options", dest="log-tcp-options", action="append")
    add_arg("--log-ip-options", dest="log-ip-options", action="append")
    add_arg("--log-uid", dest="log-uid", action="append")
    ## MASQUERADE
    add_arg("--to-ports", dest="to-ports", action="append")
    ## NFLOG
    add_arg("--nflog-group", dest="nflog-group", action="append")
    add_arg("--nflog-prefix", dest="nflog-prefix", action="append")
    add_arg("--nflog-range", dest="nflog-range", action="append")
    add_arg("--nflog-threshold", dest="nflog-threshold", action="append")
    ## NFQUEUE
    add_arg("--queue-num", dest="queue-num", action="append")
    add_arg("--queue-balance", dest="queue-balance", action="append")
    add_arg("--queue-bypass", dest="queue-bypass", action="append")
    add_arg("--queue-cpu-fanout", dest="queue-cpu-fanout", action="append")
    ## RATEEST
    add_arg("--rateest-ewmalog", dest="rateest-ewmalog", action="append")
    ## REJECT
    add_arg("--reject-with", dest="reject-with", action="append")
    ## SAME
    add_arg("--nodst", dest="nodst", action="append")
    ## SECMARK
    add_arg("--selctx", dest="selctx", action="append")
    ## SET
    add_arg("--add-set", dest="add-set", action="append")
    add_arg("--del-set", dest="del-set", action="append")
    add_arg("--exist", dest="exist", action="append")
    ## SNAT
    add_arg("--to-source", dest="to-source", action="append")
    ## TCPMSS
    add_arg("--set-mss", dest="set-mss", action="append")
    add_arg("--clamp-mss-to-pmtu", dest="clamp-mss-to-pmtu", action="append")
    ## TCPOPTSTRIP
    add_arg("--strip-options", dest="strip-options", action="append")
    ## TEE
    add_arg("--gateway", dest="gateway", action="append")
    ## TOS
    add_arg("--set-tos", dest="set-tos", action="append")
    add_arg("--and-tos", dest="and-tos", action="append")
    add_arg("--or-tos", dest="or-tos", action="append")
    add_arg("--xor-tos", dest="xor-tos", action="append")
    ## TPROXY
    add_arg("--on-port", dest="on-port", action="append")
    add_arg("--on-ip", dest="on-ip", action="append")
    add_arg("--tproxy-mark", dest="tproxy-mark", action="append")
    ## TTL
    add_arg("--ttl-set", dest="ttl-set", action="append")
    add_arg("--ttl-dec", dest="ttl-dec", action="append")
    add_arg("--ttl-inc", dest="ttl-inc", action="append")
    ## ULOG
    add_arg("--ulog-nlgroup", dest="ulog-nlgroup", action="append")
    add_arg("--ulog-prefix", dest="ulog-prefix", action="append")
    add_arg("--ulog-cprange", dest="ulog-cprange", action="append")
    add_arg("--ulog-qthreshold", dest="ulog-qthreshold", action="append")

    # Xtables-addons targets
    ## ACCOUNT
    add_arg("--addr", dest="addr", action="append")
    add_arg("--tname", dest="tname", action="append")
    ## CHAOS
    add_arg("--delude", dest="delude", action="append")
    add_arg("--tarpit", dest="tarpit", action="append")
    ## DHCPMAC
    add_arg("--set-mac", dest="set-mac", action="append")
    ## DNETMAP
    add_arg("--prefix", dest="prefix", action="append")
    add_arg("--reuse", dest="reuse", action="append")
    add_arg("--static", dest="static", action="append")
    ## IPMARK
    add_arg("--and-mask", dest="and-mask", action="append")
    add_arg("--or-mask", dest="or-mask", action="append")
    add_arg("--shift", dest="shift", action="append")
    ## TARPIT
    add_arg("--honeypot", dest="honeypot", action="append")
    add_arg("--reset", dest="reset", action="append")

    return parser
