Project import generated by Copybara.

GitOrigin-RevId: 4dee35161ed8f4fafc5edd380a5ac47b7939216a
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6c156da
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+*.a
+*.la
+*.lo
+*.so
+*.o
+.deps/
+.dirstamp
+.libs/
+Makefile
+Makefile.in
+
+# pre-generated for Android: /include/xtables-version.h
+
+/aclocal.m4
+/autom4te.cache/
+/build-aux/
+/config.*
+/configure
+/libtool
+/stamp-h1
+/iptables/iptables-apply.8
+
+/iptables/xtables-multi
+/iptables/xtables-compat-multi
+
+# vim/nano swap file
+*.swp
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..cec2851
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,70 @@
+package {
+    default_applicable_licenses: ["external_iptables_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+    name: "external_iptables_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Artistic",
+        "SPDX-license-identifier-Artistic-2.0",
+        "SPDX-license-identifier-GPL",
+        "SPDX-license-identifier-GPL-2.0",
+        "SPDX-license-identifier-LGPL",
+        "SPDX-license-identifier-MIT",
+    ],
+    license_text: [
+        "COPYING",
+    ],
+}
+
+cc_library_headers {
+    name: "iptables_headers",
+    export_include_dirs: ["include"],
+}
+
+cc_library_headers {
+    name: "iptables_config_header",
+    export_include_dirs: ["."],
+}
+
+cc_library_headers {
+    name: "iptables_iptables_headers",
+    export_include_dirs: ["iptables"],
+}
+
+cc_defaults {
+    name: "iptables_defaults",
+
+    cflags: [
+        "-D_LARGEFILE_SOURCE=1",
+        "-D_LARGE_FILES",
+        "-D_FILE_OFFSET_BITS=64",
+        "-D_REENTRANT",
+
+        "-DENABLE_IPV4",
+        "-DENABLE_IPV6",
+
+        "-Wall",
+        "-Werror",
+        "-Wno-pointer-arith",
+        "-Wno-sign-compare",
+        "-Wno-unused-parameter",
+    ],
+
+    header_libs: ["iptables_headers"],
+}
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..4a2bd29
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,3 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-subdir-makefiles)
diff --git a/COMMIT_NOTES b/COMMIT_NOTES
new file mode 100644
index 0000000..592808c
--- /dev/null
+++ b/COMMIT_NOTES
@@ -0,0 +1,19 @@
+A quick list of rules for committing stuff into netfilter git:
+
+- Always add an appropriate description, in git format
+  (i.e. first line is a summary)
+
+- Please try to include references to bugs when the description does not
+  include total discussion coverage or when the bug report is external to
+  netfilter-devel, e.g.
+  "Closes: netfilter bugzilla #123", or
+  "Reference: http://bugs.{debian,gentoo}.org/..."
+
+- If you touch any parts of libxtables (xtables.c, include/xtables.h.in),
+  make sure the so-version is updated _appropriately_ (i.e. read the
+  libtool manual about Versioning:: first, if need be) in configure.ac.
+  Adding fields to a struct always entails a vcurrent bump.
+
+  - Check, whether a bump (vcurrent,vage) has already been made since the
+    last release (no more than one per release), e.g.:
+            git log v1.4.4.. configure.ac
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 2 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, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/INCOMPATIBILITIES b/INCOMPATIBILITIES
new file mode 100644
index 0000000..ddb2408
--- /dev/null
+++ b/INCOMPATIBILITIES
@@ -0,0 +1,14 @@
+INCOMPATIBILITIES:
+
+- The REJECT target has an '--reject-with admin-prohib' option which used
+  with kernels that do not support it, will result in a plain DROP instead
+  of REJECT.  Use with caution.
+  Kernels that do support it:
+  	2.4 - since 2.4.22-pre9
+	2.6 - all
+
+- There are some issues related to upgrading from 1.2.x to 1.3.x on a system
+  with dynamic ruleset changes during runtime. (Please see 
+  https://bugzilla.netfilter.org/bugzilla/show_bug.cgi?id=334).
+  After upgrading from 1.2 to 1.3, it suggest go do an iptables-save, then
+  iptables-restore to ensure your dynamic rule changes continue to work.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..d62b428
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,101 @@
+Installation instructions for iptables
+======================================
+
+iptables uses the well-known configure(autotools) infrastructure.
+
+	$ ./configure
+	$ make
+	# make install
+
+
+Prerequisites
+=============
+
+	* no kernel-source required
+
+	* but obviously a compiler, glibc-devel and linux-kernel-headers
+	  (/usr/include/linux)
+
+
+Configuring and compiling
+=========================
+
+./configure [options]
+
+--prefix=
+
+	The prefix to put all installed files under. It defaults to
+	/usr/local, so the binaries will go into /usr/local/bin, sbin,
+	manpages into /usr/local/share/man, etc.
+
+--with-xtlibdir=
+
+	The path to where Xtables extensions should be installed to. It
+	defaults to ${libdir}/xtables.
+
+--enable-devel (or --disable-devel)
+
+	This option causes development files to be installed to
+	${includedir}, which is needed for building additional packages,
+	such as Xtables-addons or other 3rd-party extensions.
+
+	It is enabled by default.
+
+--enable-static
+
+	Produce additional binaries, iptables-static/ip6tables-static,
+	which have all shipped extensions compiled in.
+
+--disable-shared
+
+	Produce binaries that have dynamic loading of extensions disabled.
+	This implies --enable-static.
+	(See some details below.)
+
+--enable-libipq
+
+	This option causes libipq to be installed into ${libdir} and
+	${includedir}.
+
+--with-ksource=
+
+	Xtables does not depend on kernel headers anymore, but you can
+	optionally specify a search path to include anyway. This is
+	probably only useful for development.
+
+If you want to enable debugging, use
+
+	./configure CFLAGS="-ggdb3 -O0"
+
+(-O0 is used to turn off instruction reordering, which makes debugging
+much easier.)
+
+To show debug traces you can add -DDEBUG to CFLAGS option
+
+
+Other notes
+===========
+
+The make process will automatically build multipurpose binaries.
+These have the core (iptables), -save, -restore and -xml code
+compiled into one binary, but extensions remain as modules.
+
+
+Static and shared
+=================
+
+Basically there are three configuration modes defined:
+
+ --disable-static --enable-shared (this is the default)
+
+	Build a binary that relies upon dynamic loading of extensions.
+
+ --enable-static --enable-shared
+
+	Build a binary that has the shipped extensions built-in, but
+	is still capable of loading additional extensions.
+
+ --enable-static --disable-shared
+
+	Shipped extensions are built-in, and dynamic loading is
+	deactivated.
diff --git a/MODULE_LICENSE_GPL b/MODULE_LICENSE_GPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_GPL
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..799bf8b
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,33 @@
+# -*- Makefile -*-
+
+ACLOCAL_AMFLAGS  = -I m4
+AUTOMAKE_OPTIONS = foreign subdir-objects
+
+SUBDIRS          = libiptc libxtables
+if ENABLE_DEVEL
+SUBDIRS         += include
+endif
+if ENABLE_LIBIPQ
+SUBDIRS         += libipq
+endif
+SUBDIRS         += utils
+# Depends on libxtables:
+SUBDIRS         += extensions
+# Depends on extensions/libext.a:
+SUBDIRS         += iptables
+
+if ENABLE_NFTABLES
+confdir		= $(sysconfdir)
+dist_conf_DATA	= etc/ethertypes
+endif
+
+.PHONY: tarball
+tarball:
+	rm -Rf /tmp/${PACKAGE_TARNAME}-${PACKAGE_VERSION};
+	pushd ${top_srcdir} && git archive --prefix=${PACKAGE_TARNAME}-${PACKAGE_VERSION}/ HEAD | tar -C /tmp -x && popd;
+	pushd /tmp/${PACKAGE_TARNAME}-${PACKAGE_VERSION} && ./autogen.sh && popd;
+	tar -C /tmp -cjf ${PACKAGE_TARNAME}-${PACKAGE_VERSION}.tar.bz2 --owner=root --group=root ${PACKAGE_TARNAME}-${PACKAGE_VERSION}/;
+	rm -Rf /tmp/${PACKAGE_TARNAME}-${PACKAGE_VERSION};
+
+config.status: extensions/GNUmakefile.in \
+	include/xtables-version.h.in
diff --git a/NOTICE b/NOTICE
new file mode 120000
index 0000000..d24842f
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1 @@
+COPYING
\ No newline at end of file
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..139b26e
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+eliribble@google.com
+allenlintwo@google.com
+jlevasseur@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..2db0cd5
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+    "presubmit": [
+        { "name": "libnetdbpf_test" },
+        { "name": "netd_integration_test" },
+        { "name": "netd_unit_test" },
+        { "name": "netdutils_test" },
+        { "name": "resolv_integration_test" },
+        { "name": "resolv_unit_test" }
+    ]
+}
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..a0c4395
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+
+autoreconf -fi;
+rm -Rf autom4te*.cache;
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..f4a827d
--- /dev/null
+++ b/config.h
@@ -0,0 +1,86 @@
+/* config.h.  Generated from config.h.in by configure.  */
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `pcap' library (-lpcap). */
+/* #undef HAVE_LIBPCAP */
+
+/* Define to 1 if you have the <linux/bpf.h> header file. */
+#define HAVE_LINUX_BPF_H 1
+
+/* Define to 1 if you have the <linux/dccp.h> header file. */
+#define HAVE_LINUX_DCCP_H 1
+
+/* Define to 1 if you have the <linux/ip_vs.h> header file. */
+#define HAVE_LINUX_IP_VS_H 1
+
+/* Define to 1 if you have the <linux/magic.h> header file. */
+#define HAVE_LINUX_MAGIC_H 1
+
+/* Define to 1 if you have the <linux/proc_fs.h> header file. */
+/* #undef HAVE_LINUX_PROC_FS_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to the sub-directory where libtool stores uninstalled libraries. */
+#define LT_OBJDIR ".libs/"
+
+/* Name of package */
+#define PACKAGE "iptables"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "iptables"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "iptables 1.8.7"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "iptables"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.8.7"
+
+/* The size of `struct ip6_hdr', as computed by sizeof. */
+#define SIZEOF_STRUCT_IP6_HDR 40
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#define VERSION "1.8.7"
+
+/* Location of the iptables lock file */
+#define XT_LOCK_NAME "/system/etc/xtables.lock"
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..6864378
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,274 @@
+
+AC_INIT([iptables], [1.8.7])
+
+# See libtool.info "Libtool's versioning system"
+libxtables_vcurrent=16
+libxtables_vage=4
+
+AC_CONFIG_AUX_DIR([build-aux])
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_MACRO_DIR([m4])
+AC_PROG_INSTALL
+AM_INIT_AUTOMAKE([-Wall])
+AC_PROG_CC
+AM_PROG_CC_C_O
+AC_DISABLE_STATIC
+m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
+AM_PROG_LIBTOOL
+
+AC_ARG_WITH([kernel],
+	AS_HELP_STRING([--with-kernel=PATH],
+	[Path to kernel source/build directory]),
+	[kbuilddir="$withval"; ksourcedir="$withval";])
+AC_ARG_WITH([kbuild],
+	AS_HELP_STRING([--with-kbuild=PATH],
+	[Path to kernel build directory [[/lib/modules/CURRENT/build]]]),
+	[kbuilddir="$withval"])
+AC_ARG_WITH([ksource],
+	AS_HELP_STRING([--with-ksource=PATH],
+	[Path to kernel source directory [[/lib/modules/CURRENT/source]]]),
+	[ksourcedir="$withval"])
+AC_ARG_WITH([xtlibdir],
+	AS_HELP_STRING([--with-xtlibdir=PATH],
+	[Path where to install Xtables extensions [[LIBEXECDIR/xtables]]]),
+	[xtlibdir="$withval"],
+	[xtlibdir="${libdir}/xtables"])
+AC_ARG_ENABLE([ipv4],
+	AS_HELP_STRING([--disable-ipv4], [Do not build iptables]),
+	[enable_ipv4="$enableval"], [enable_ipv4="yes"])
+AC_ARG_ENABLE([ipv6],
+	AS_HELP_STRING([--disable-ipv6], [Do not build ip6tables]),
+	[enable_ipv6="$enableval"], [enable_ipv6="yes"])
+AC_ARG_ENABLE([largefile],
+	AS_HELP_STRING([--disable-largefile], [Do not build largefile support]),
+	[enable_largefile="$enableval"],
+	[enable_largefile="yes"])
+AS_IF([test "$enable_largefile" = "yes"], [largefile_cppflags='-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64'])
+
+AC_ARG_ENABLE([devel],
+	AS_HELP_STRING([--enable-devel],
+	[Install Xtables development headers]),
+	[enable_devel="$enableval"], [enable_devel="yes"])
+AC_ARG_ENABLE([libipq],
+	AS_HELP_STRING([--enable-libipq], [Build and install libipq]),
+	[enable_libipq="$enableval"], [enable_libipq="no"])
+AC_ARG_ENABLE([bpf-compiler],
+	AS_HELP_STRING([--enable-bpf-compiler], [Build bpf compiler]),
+	[enable_bpfc="$enableval"], [enable_bpfc="no"])
+AC_ARG_ENABLE([nfsynproxy],
+	AS_HELP_STRING([--enable-nfsynproxy], [Build SYNPROXY configuration tool]),
+	[enable_nfsynproxy="$enableval"], [enable_nfsynproxy="no"])
+AC_ARG_WITH([pkgconfigdir], AS_HELP_STRING([--with-pkgconfigdir=PATH],
+	[Path to the pkgconfig directory [[LIBDIR/pkgconfig]]]),
+	[pkgconfigdir="$withval"], [pkgconfigdir='${libdir}/pkgconfig'])
+AC_ARG_ENABLE([nftables],
+	AS_HELP_STRING([--disable-nftables], [Do not build nftables compat]),
+	[enable_nftables="$enableval"], [enable_nftables="yes"])
+AC_ARG_ENABLE([connlabel],
+	AS_HELP_STRING([--disable-connlabel],
+	[Do not build libnetfilter_conntrack]),
+	[enable_connlabel="$enableval"], [enable_connlabel="yes"])
+AC_ARG_WITH([xt-lock-name], AS_HELP_STRING([--with-xt-lock-name=PATH],
+	[Path to the xtables lock [[/run/xtables.lock]]]),
+	[xt_lock_name="$withval"],
+	[xt_lock_name="/run/xtables.lock"])
+
+AC_MSG_CHECKING([whether $LD knows -Wl,--no-undefined])
+saved_LDFLAGS="$LDFLAGS";
+LDFLAGS="-Wl,--no-undefined";
+AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void) {}])],
+	[noundef_LDFLAGS="$LDFLAGS"; AC_MSG_RESULT([yes])],
+	[AC_MSG_RESULT([no])]
+)
+LDFLAGS="$saved_LDFLAGS";
+
+blacklist_modules=""
+blacklist_x_modules=""
+blacklist_b_modules=""
+blacklist_a_modules=""
+blacklist_4_modules=""
+blacklist_6_modules=""
+
+AC_CHECK_HEADERS([linux/dccp.h linux/ip_vs.h linux/magic.h linux/proc_fs.h linux/bpf.h])
+if test "$ac_cv_header_linux_dccp_h" != "yes"; then
+	blacklist_modules="$blacklist_modules dccp";
+fi;
+if test "$ac_cv_header_linux_ip_vs_h" != "yes"; then
+	blacklist_modules="$blacklist_modules ipvs";
+fi;
+
+AC_CHECK_SIZEOF([struct ip6_hdr], [], [#include <netinet/ip6.h>])
+
+AM_CONDITIONAL([ENABLE_STATIC], [test "$enable_static" = "yes"])
+AM_CONDITIONAL([ENABLE_SHARED], [test "$enable_shared" = "yes"])
+AM_CONDITIONAL([ENABLE_IPV4], [test "$enable_ipv4" = "yes"])
+AM_CONDITIONAL([ENABLE_IPV6], [test "$enable_ipv6" = "yes"])
+AM_CONDITIONAL([ENABLE_LARGEFILE], [test "$enable_largefile" = "yes"])
+AM_CONDITIONAL([ENABLE_DEVEL], [test "$enable_devel" = "yes"])
+AM_CONDITIONAL([ENABLE_LIBIPQ], [test "$enable_libipq" = "yes"])
+AM_CONDITIONAL([ENABLE_BPFC], [test "$enable_bpfc" = "yes"])
+AM_CONDITIONAL([ENABLE_SYNCONF], [test "$enable_nfsynproxy" = "yes"])
+AM_CONDITIONAL([ENABLE_NFTABLES], [test "$enable_nftables" = "yes"])
+AM_CONDITIONAL([ENABLE_CONNLABEL], [test "$enable_connlabel" = "yes"])
+
+if test "x$enable_bpfc" = "xyes" || test "x$enable_nfsynproxy" = "xyes"; then
+	AC_CHECK_LIB(pcap, pcap_compile,, AC_MSG_ERROR(missing libpcap library required by bpf compiler or nfsynproxy tool))
+fi
+
+PKG_CHECK_MODULES([libnfnetlink], [libnfnetlink >= 1.0],
+	[nfnetlink=1], [nfnetlink=0])
+AM_CONDITIONAL([HAVE_LIBNFNETLINK], [test "$nfnetlink" = 1])
+
+if test "x$enable_nftables" = "xyes"; then
+	PKG_CHECK_MODULES([libmnl], [libmnl >= 1.0], [mnl=1], [mnl=0])
+
+	if test "$mnl" = 0;
+	then
+		echo "*** Error: No suitable libmnl found. ***"
+		echo "    Please install the 'libmnl' package"
+		echo "    Or consider --disable-nftables to skip"
+		echo "    iptables-compat over nftables support."
+		exit 1
+	fi
+
+	PKG_CHECK_MODULES([libnftnl], [libnftnl >= 1.1.6], [nftables=1], [nftables=0])
+
+	if test "$nftables" = 0;
+	then
+		echo "*** Error: no suitable libnftnl found. ***"
+		echo "    Please install the 'libnftnl' package"
+		echo "    Or consider --disable-nftables to skip"
+		echo "    iptables-compat over nftables support."
+		exit 1
+	fi
+fi
+
+AM_CONDITIONAL([HAVE_LIBMNL], [test "$mnl" = 1])
+AM_CONDITIONAL([HAVE_LIBNFTNL], [test "$nftables" = 1])
+
+if test "$nftables" != 1; then
+	blacklist_b_modules="$blacklist_b_modules limit mark nflog mangle"
+	blacklist_a_modules="$blacklist_a_modules mangle"
+fi
+
+if test "x$enable_connlabel" = "xyes"; then
+	PKG_CHECK_MODULES([libnetfilter_conntrack],
+		[libnetfilter_conntrack >= 1.0.6],
+		[nfconntrack=1], [nfconntrack=0])
+
+	if test "$nfconntrack" -ne 1; then
+		blacklist_modules="$blacklist_modules connlabel";
+		echo "WARNING: libnetfilter_conntrack not found, connlabel match will not be built";
+		enable_connlabel="no";
+	fi;
+else
+	blacklist_modules="$blacklist_modules connlabel";
+fi;
+
+AM_CONDITIONAL([HAVE_LIBNETFILTER_CONNTRACK], [test "$nfconntrack" = 1])
+
+AC_SUBST([blacklist_modules])
+AC_SUBST([blacklist_x_modules])
+AC_SUBST([blacklist_b_modules])
+AC_SUBST([blacklist_a_modules])
+AC_SUBST([blacklist_4_modules])
+AC_SUBST([blacklist_6_modules])
+
+regular_CFLAGS="-Wall -Waggregate-return -Wmissing-declarations \
+	-Wmissing-prototypes -Wredundant-decls -Wshadow -Wstrict-prototypes \
+	-Wlogical-op \
+	-Winline -pipe";
+regular_CPPFLAGS="${largefile_cppflags} -D_REENTRANT \
+	-DXTABLES_LIBDIR=\\\"\${xtlibdir}\\\" -DXTABLES_INTERNAL";
+kinclude_CPPFLAGS="";
+if [[ -n "$kbuilddir" ]]; then
+	kinclude_CPPFLAGS="$kinclude_CPPFLAGS -I$kbuilddir/include/uapi -I$kbuilddir/include";
+fi;
+if [[ -n "$ksourcedir" ]]; then
+	kinclude_CPPFLAGS="$kinclude_CPPFLAGS -I$ksourcedir/include/uapi -I$ksourcedir/include";
+fi;
+pkgdatadir='${datadir}/xtables';
+
+define([EXPAND_VARIABLE],
+[$2=[$]$1
+if test $prefix = 'NONE'; then
+	prefix="/usr/local"
+fi
+while true; do
+  case "[$]$2" in
+    *\[$]* ) eval "$2=[$]$2" ;;
+    *) break ;;
+  esac
+done
+eval "$2=[$]$2"
+])dnl EXPAND_VARIABLE
+
+AC_SUBST([regular_CFLAGS])
+AC_SUBST([regular_CPPFLAGS])
+AC_SUBST([noundef_LDFLAGS])
+AC_SUBST([kinclude_CPPFLAGS])
+AC_SUBST([kbuilddir])
+AC_SUBST([ksourcedir])
+AC_SUBST([xtlibdir])
+AC_SUBST([pkgconfigdir])
+AC_SUBST([pkgdatadir])
+AC_SUBST([libxtables_vcurrent])
+AC_SUBST([libxtables_vage])
+libxtables_vmajor=$(($libxtables_vcurrent - $libxtables_vage));
+AC_SUBST([libxtables_vmajor])
+
+AC_DEFINE_UNQUOTED([XT_LOCK_NAME], "${xt_lock_name}",
+	[Location of the iptables lock file])
+AC_SUBST([XT_LOCK_NAME], "${xt_lock_name}")
+
+AC_CONFIG_FILES([Makefile extensions/GNUmakefile include/Makefile
+	iptables/Makefile iptables/xtables.pc
+	iptables/iptables.8 iptables/iptables-extensions.8.tmpl
+	iptables/iptables-save.8 iptables/iptables-restore.8
+	iptables/iptables-apply.8 iptables/iptables-xml.1
+	libipq/Makefile libipq/libipq.pc
+	libiptc/Makefile libiptc/libiptc.pc
+	libiptc/libip4tc.pc libiptc/libip6tc.pc
+	libxtables/Makefile utils/Makefile
+	include/xtables-version.h
+	iptables/xtables-monitor.8
+	utils/nfnl_osf.8
+	utils/nfbpf_compile.8])
+AC_OUTPUT
+
+
+EXPAND_VARIABLE(xtlibdir, e_xtlibdir)
+EXPAND_VARIABLE(pkgconfigdir, e_pkgconfigdir)
+
+echo "
+Iptables Configuration:
+  IPv4 support:				${enable_ipv4}
+  IPv6 support:				${enable_ipv6}
+  Devel support:			${enable_devel}
+  IPQ support:				${enable_libipq}
+  Large file support:			${enable_largefile}
+  BPF utils support:			${enable_bpfc}
+  nfsynproxy util support:		${enable_nfsynproxy}
+  nftables support:			${enable_nftables}
+  connlabel support:			${enable_connlabel}
+
+Build parameters:
+  Put plugins into executable (static):	${enable_static}
+  Support plugins via dlopen (shared):	${enable_shared}
+  Installation prefix (--prefix):	${prefix}
+  Xtables extension directory:		${e_xtlibdir}
+  Pkg-config directory:			${e_pkgconfigdir}
+  Xtables lock file:			${xt_lock_name}"
+
+if [[ -n "$ksourcedir" ]]; then
+	echo "  Kernel source directory:		${ksourcedir}"
+fi;
+if [[ -n "$kbuilddir" ]]; then
+	echo "  Kernel build directory:		${kbuilddir}"
+fi;
+
+echo "  Host:					${host}
+  GCC binary:				${CC}"
+
+test x"$blacklist_modules" = "x" || echo "
+Iptables modules that will not be built: $blacklist_modules"
diff --git a/etc/ethertypes b/etc/ethertypes
new file mode 100644
index 0000000..813177b
--- /dev/null
+++ b/etc/ethertypes
@@ -0,0 +1,39 @@
+#
+# Ethernet frame types
+#		This file describes some of the various Ethernet
+#		protocol types that are used on Ethernet networks.
+#
+# This list could be found on:
+#         http://www.iana.org/assignments/ethernet-numbers
+#         http://www.iana.org/assignments/ieee-802-numbers
+#
+# <name>    <hexnumber> <alias1>...<alias35> #Comment
+#
+IPv4	 	0800  	ip ip4 		# Internet IP (IPv4)
+X25		0805
+ARP		0806	ether-arp	#
+FR_ARP		0808    		# Frame Relay ARP        [RFC1701]
+BPQ		08FF			# G8BPQ AX.25 Ethernet Packet
+DEC		6000			# DEC Assigned proto
+DNA_DL		6001			# DEC DNA Dump/Load
+DNA_RC		6002			# DEC DNA Remote Console
+DNA_RT		6003			# DEC DNA Routing
+LAT		6004			# DEC LAT
+DIAG		6005			# DEC Diagnostics
+CUST		6006			# DEC Customer use
+SCA		6007			# DEC Systems Comms Arch
+TEB		6558			# Trans Ether Bridging   [RFC1701]
+RAW_FR  	6559			# Raw Frame Relay        [RFC1701]
+RARP		8035			# Reverse ARP            [RFC903]
+AARP		80F3			# Appletalk AARP
+ATALK		809B			# Appletalk
+802_1Q		8100	8021q 1q 802.1q	dot1q # 802.1Q Virtual LAN tagged frame
+IPX		8137			# Novell IPX
+NetBEUI		8191			# NetBEUI
+IPv6		86DD	ip6 		# IP version 6
+PPP		880B			# PPP
+ATMMPOA		884C			# MultiProtocol over ATM
+PPP_DISC	8863			# PPPoE discovery messages
+PPP_SES		8864			# PPPoE session messages
+ATMFATE		8884			# Frame-based ATM Transport over Ethernet
+LOOP		9000	loopback 	# loop proto
diff --git a/etc/xtables.conf b/etc/xtables.conf
new file mode 100644
index 0000000..3c54ced
--- /dev/null
+++ b/etc/xtables.conf
@@ -0,0 +1,74 @@
+family ipv4 {
+	table raw {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -300
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -300
+	}
+
+	table mangle {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -150
+		chain INPUT hook NF_INET_LOCAL_IN prio -150
+		chain FORWARD hook NF_INET_FORWARD prio -150
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -150
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio -150
+	}
+
+	table filter {
+		chain INPUT hook NF_INET_LOCAL_IN prio 0
+		chain FORWARD hook NF_INET_FORWARD prio 0
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 0
+	}
+
+	table nat {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -100
+		chain INPUT hook NF_INET_LOCAL_IN prio 100
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -100
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio 100
+	}
+
+	table security {
+		chain INPUT hook NF_INET_LOCAL_IN prio 50
+		chain FORWARD hook NF_INET_FORWARD prio 50
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 50
+	}
+}
+
+family ipv6 {
+	table raw {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -300
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -300
+	}
+
+	table mangle {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -150
+		chain INPUT hook NF_INET_LOCAL_IN prio -150
+		chain FORWARD hook NF_INET_FORWARD prio -150
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -150
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio -150
+	}
+
+	table filter {
+		chain INPUT hook NF_INET_LOCAL_IN prio 0
+		chain FORWARD hook NF_INET_FORWARD prio 0
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 0
+	}
+
+	table nat {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -100
+		chain INPUT hook NF_INET_LOCAL_IN prio 100
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -100
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio 100
+	}
+
+	table security {
+		chain INPUT hook NF_INET_LOCAL_IN prio 50
+		chain FORWARD hook NF_INET_FORWARD prio 50
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 50
+	}
+}
+
+family arp {
+	table filter {
+		chain INPUT hook NF_ARP_IN prio 0
+		chain OUTPUT hook NF_ARP_OUT prio 0
+	}
+}
diff --git a/extensions/.gitignore b/extensions/.gitignore
new file mode 100644
index 0000000..b1260f0
--- /dev/null
+++ b/extensions/.gitignore
@@ -0,0 +1,9 @@
+.*.d
+.*.dd
+*.oo
+
+/GNUmakefile
+/initext.c
+/initext?.c
+/matches.man
+/targets.man
diff --git a/extensions/Android.bp b/extensions/Android.bp
new file mode 100644
index 0000000..8a6b6e9
--- /dev/null
+++ b/extensions/Android.bp
@@ -0,0 +1,139 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_iptables_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL
+    //   SPDX-license-identifier-GPL-2.0
+    default_applicable_licenses: ["external_iptables_license"],
+}
+
+cc_defaults {
+    name: "libext_defaults",
+    defaults: ["iptables_defaults"],
+
+    header_libs: ["iptables_config_header"],
+
+    cflags: [
+        "-DNO_SHARED_LIBS=1",
+        "-DXTABLES_INTERNAL",
+
+        "-Wno-format",
+        "-Wno-missing-field-initializers",
+        // libxt_recent.c:202:11: error: address of array 'info->name' will always evaluate to 'true' [-Werror,-Wpointer-bool-conversion]
+        "-Wno-pointer-bool-conversion",
+        "-Wno-tautological-pointer-compare",
+    ],
+}
+
+// All of the extension source files have the same function name (_init). Since we don't support
+// per-file cflags that upstream uses, instead:
+//
+//  1. Rewrite the source files with filter_init to have per-file function names. (libext*_srcs)
+//  2. Create a new source file that defines a function (init_extensions*) with gen_init that calls
+//     all of the renamed _init functions (libext*_init)
+//
+// This all happens three times -- once each for libext, libext4, libext6
+
+genrule {
+    name: "libext_init",
+    cmd: "$(location gen_init) '' $(locations libxt_*.c) > $(out)",
+    srcs: [
+        "gen_init",
+        "libxt_*.c",
+    ],
+    out: ["initext.c"],
+    exclude_srcs: [
+        // Exclude some modules that are problematic to compile (types/headers)
+        "libxt_TCPOPTSTRIP.c",
+        "libxt_connlabel.c",
+        "libxt_cgroup.c",
+
+        "libxt_dccp.c",
+        "libxt_ipvs.c",
+    ],
+}
+
+gensrcs {
+    name: "libext_srcs",
+    tool_files: ["filter_init"],
+    cmd: "$(location filter_init) $(in) > $(out)",
+    output_extension: "c",
+    srcs: ["libxt_*.c"],
+    exclude_srcs: [
+        // Exclude some modules that are problematic to compile (types/headers)
+        "libxt_TCPOPTSTRIP.c",
+        "libxt_connlabel.c",
+        "libxt_cgroup.c",
+
+        "libxt_dccp.c",
+        "libxt_ipvs.c",
+    ],
+}
+
+cc_library_static {
+    name: "libext",
+    defaults: ["libext_defaults"],
+    srcs: [
+        ":libext_init",
+        ":libext_srcs",
+    ],
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+genrule {
+    name: "libext4_init",
+    cmd: "$(location gen_init) '4' $(locations libipt_*.c) > $(out)",
+    srcs: [
+        "gen_init",
+        "libipt_*.c",
+    ],
+    out: ["initext.c"],
+}
+
+gensrcs {
+    name: "libext4_srcs",
+    tool_files: ["filter_init"],
+    cmd: "$(location filter_init) $(in) > $(out)",
+    output_extension: "c",
+    srcs: ["libipt_*.c"],
+}
+
+cc_library_static {
+    name: "libext4",
+    defaults: ["libext_defaults"],
+    srcs: [
+        ":libext4_init",
+        ":libext4_srcs",
+    ],
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+genrule {
+    name: "libext6_init",
+    cmd: "$(location gen_init) '6' $(locations libip6t_*.c) > $(out)",
+    srcs: [
+        "gen_init",
+        "libip6t_*.c",
+    ],
+    out: ["initext.c"],
+}
+
+gensrcs {
+    name: "libext6_srcs",
+    tool_files: ["filter_init"],
+    cmd: "$(location filter_init) $(in) > $(out)",
+    output_extension: "c",
+    srcs: ["libip6t_*.c"],
+}
+
+cc_library_static {
+    name: "libext6",
+    defaults: ["libext_defaults"],
+    srcs: [
+        ":libext6_init",
+        ":libext6_srcs",
+    ],
+}
diff --git a/extensions/Android.mk b/extensions/Android.mk
new file mode 100644
index 0000000..74f1cab
--- /dev/null
+++ b/extensions/Android.mk
@@ -0,0 +1,45 @@
+LOCAL_PATH:= $(call my-dir)
+#----------------------------------------------------------------
+## extension
+
+MY_srcdir:=$(LOCAL_PATH)
+# Exclude some modules that are problematic to compile (types/header).
+MY_excluded_modules:=TCPOPTSTRIP connlabel cgroup
+
+MY_pfx_build_mod := $(patsubst ${MY_srcdir}/libxt_%.c,%,$(sort $(wildcard ${MY_srcdir}/libxt_*.c)))
+MY_pf4_build_mod := $(patsubst ${MY_srcdir}/libipt_%.c,%,$(sort $(wildcard ${MY_srcdir}/libipt_*.c)))
+MY_pf6_build_mod := $(patsubst ${MY_srcdir}/libip6t_%.c,%,$(sort $(wildcard ${MY_srcdir}/libip6t_*.c)))
+MY_pfx_build_mod := $(filter-out ${MY_excluded_modules} dccp ipvs,${MY_pfx_build_mod})
+MY_pf4_build_mod := $(filter-out ${MY_excluded_modules} dccp ipvs,${MY_pf4_build_mod})
+MY_pf6_build_mod := $(filter-out ${MY_excluded_modules} dccp ipvs,${MY_pf6_build_mod})
+MY_pfx_objs      := $(patsubst %,libxt_%.o,${MY_pfx_build_mod})
+MY_pf4_objs      := $(patsubst %,libipt_%.o,${MY_pf4_build_mod})
+MY_pf6_objs      := $(patsubst %,libip6t_%.o,${MY_pf6_build_mod})
+# libxt_recent.c:202:11: error: address of array 'info->name' will always evaluate to 'true' [-Werror,-Wpointer-bool-conversion]
+MY_warnings      := \
+    -Wall -Werror \
+    -Wno-format \
+    -Wno-missing-field-initializers \
+    -Wno-pointer-arith \
+    -Wno-pointer-bool-conversion \
+    -Wno-sign-compare \
+    -Wno-tautological-pointer-compare \
+    -Wno-unused-parameter \
+
+libext_suffix :=
+libext_prefix := xt
+libext_build_mod := $(MY_pfx_build_mod)
+include $(LOCAL_PATH)/libext.mk
+
+libext_suffix := 4
+libext_prefix := ipt
+libext_build_mod := $(MY_pf4_build_mod)
+include $(LOCAL_PATH)/libext.mk
+
+libext_suffix := 6
+libext_prefix := ip6t
+libext_build_mod := $(MY_pf6_build_mod)
+include $(LOCAL_PATH)/libext.mk
+
+
+#----------------------------------------------------------------
diff --git a/extensions/GNUmakefile.in b/extensions/GNUmakefile.in
new file mode 100644
index 0000000..956ccb3
--- /dev/null
+++ b/extensions/GNUmakefile.in
@@ -0,0 +1,307 @@
+# -*- Makefile -*-
+
+top_builddir = @top_builddir@
+builddir     = @builddir@
+top_srcdir   = @top_srcdir@
+srcdir       = @srcdir@
+ksourcedir   = @ksourcedir@
+prefix       = @prefix@
+exec_prefix  = @exec_prefix@
+libdir       = @libdir@
+libexecdir   = @libexecdir@
+xtlibdir     = @xtlibdir@
+
+AR                 = @AR@
+CC                 = @CC@
+CCLD               = ${CC}
+CFLAGS             = @CFLAGS@
+CPPFLAGS           = @CPPFLAGS@
+LDFLAGS            = @LDFLAGS@
+regular_CFLAGS     = @regular_CFLAGS@
+regular_CPPFLAGS   = @regular_CPPFLAGS@
+kinclude_CPPFLAGS  = @kinclude_CPPFLAGS@
+
+AM_CFLAGS       = ${regular_CFLAGS}
+AM_CPPFLAGS     = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_builddir} -I${top_srcdir}/include -I${top_srcdir} ${kinclude_CPPFLAGS} ${CPPFLAGS} @libnetfilter_conntrack_CFLAGS@ @libnftnl_CFLAGS@
+AM_DEPFLAGS     = -Wp,-MMD,$(@D)/.$(@F).d,-MT,$@
+AM_LDFLAGS      = @noundef_LDFLAGS@
+
+ifeq (${V},)
+AM_LIBTOOL_SILENT = --silent
+AM_VERBOSE_CC     = @echo "  CC      " $@;
+AM_VERBOSE_CCLD   = @echo "  CCLD    " $@;
+AM_VERBOSE_CXX    = @echo "  CXX     " $@;
+AM_VERBOSE_CXXLD  = @echo "  CXXLD   " $@;
+AM_VERBOSE_AR     = @echo "  AR      " $@;
+AM_VERBOSE_GEN    = @echo "  GEN     " $@;
+endif
+
+#
+#	Wildcard module list
+#
+pfx_build_mod := $(patsubst ${srcdir}/libxt_%.c,%,$(sort $(wildcard ${srcdir}/libxt_*.c)))
+@ENABLE_NFTABLES_TRUE@ pfb_build_mod := $(patsubst ${srcdir}/libebt_%.c,%,$(sort $(wildcard ${srcdir}/libebt_*.c)))
+@ENABLE_NFTABLES_TRUE@ pfa_build_mod := $(patsubst ${srcdir}/libarpt_%.c,%,$(sort $(wildcard ${srcdir}/libarpt_*.c)))
+pfx_symlinks  := NOTRACK state
+@ENABLE_IPV4_TRUE@ pf4_build_mod := $(patsubst ${srcdir}/libipt_%.c,%,$(sort $(wildcard ${srcdir}/libipt_*.c)))
+@ENABLE_IPV6_TRUE@ pf6_build_mod := $(patsubst ${srcdir}/libip6t_%.c,%,$(sort $(wildcard ${srcdir}/libip6t_*.c)))
+pfx_build_mod := $(filter-out @blacklist_modules@ @blacklist_x_modules@,${pfx_build_mod})
+pfb_build_mod := $(filter-out @blacklist_modules@ @blacklist_b_modules@,${pfb_build_mod})
+pfa_build_mod := $(filter-out @blacklist_modules@ @blacklist_a_modules@,${pfa_build_mod})
+pf4_build_mod := $(filter-out @blacklist_modules@ @blacklist_4_modules@,${pf4_build_mod})
+pf6_build_mod := $(filter-out @blacklist_modules@ @blacklist_6_modules@,${pf6_build_mod})
+pfx_objs      := $(patsubst %,libxt_%.o,${pfx_build_mod})
+pfb_objs      := $(patsubst %,libebt_%.o,${pfb_build_mod})
+pfa_objs      := $(patsubst %,libarpt_%.o,${pfa_build_mod})
+pf4_objs      := $(patsubst %,libipt_%.o,${pf4_build_mod})
+pf6_objs      := $(patsubst %,libip6t_%.o,${pf6_build_mod})
+pfx_solibs    := $(patsubst %,libxt_%.so,${pfx_build_mod})
+pfb_solibs    := $(patsubst %,libebt_%.so,${pfb_build_mod})
+pfa_solibs    := $(patsubst %,libarpt_%.so,${pfa_build_mod})
+pf4_solibs    := $(patsubst %,libipt_%.so,${pf4_build_mod})
+pf6_solibs    := $(patsubst %,libip6t_%.so,${pf6_build_mod})
+pfx_symlink_files := $(patsubst %,libxt_%.so,${pfx_symlinks})
+
+
+#
+# Building blocks
+#
+targets := libext.a libext4.a libext6.a libext_ebt.a libext_arpt.a matches.man targets.man
+targets_install :=
+@ENABLE_STATIC_TRUE@ libext_objs := ${pfx_objs}
+@ENABLE_STATIC_TRUE@ libext_ebt_objs := ${pfb_objs}
+@ENABLE_STATIC_TRUE@ libext_arpt_objs := ${pfa_objs}
+@ENABLE_STATIC_TRUE@ libext4_objs := ${pf4_objs}
+@ENABLE_STATIC_TRUE@ libext6_objs := ${pf6_objs}
+@ENABLE_STATIC_FALSE@ targets += ${pfx_solibs} ${pfb_solibs} ${pf4_solibs} ${pf6_solibs} ${pfa_solibs} ${pfx_symlink_files}
+@ENABLE_STATIC_FALSE@ targets_install += ${pfx_solibs} ${pfb_solibs} ${pf4_solibs} ${pf6_solibs} ${pfa_solibs}
+@ENABLE_STATIC_FALSE@ symlinks_install := ${pfx_symlink_files}
+
+.SECONDARY:
+
+.PHONY: all install uninstall clean distclean FORCE
+
+all: ${targets}
+
+install: ${targets_install} ${symlinks_install}
+	@mkdir -p "${DESTDIR}${xtlibdir}";
+	if test -n "${targets_install}"; then \
+		install -pm0755 ${targets_install} "${DESTDIR}${xtlibdir}/"; \
+	fi;
+	if test -n "${symlinks_install}"; then \
+		cp -P ${symlinks_install} "${DESTDIR}${xtlibdir}/"; \
+	fi;
+
+uninstall:
+	dir=${DESTDIR}${xtlibdir}; { \
+		test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; \
+	} || { \
+		test -z "${targets_install}" || ( \
+			cd "$$dir" && rm -f ${targets_install} \
+		); \
+		test -z "${symlinks_install}" || ( \
+			cd "$$dir" && rm -f ${symlinks_install} \
+		); \
+		rmdir -p --ignore-fail-on-non-empty "$$dir"; \
+	}
+
+clean:
+	rm -f *.o *.oo *.so *.a {matches,targets}.man initext.c initext4.c initext6.c initextb.c initexta.c;
+	rm -f .*.d .*.dd;
+
+distclean: clean
+
+init%.o: init%.c
+	${AM_VERBOSE_CC} ${CC} ${AM_CPPFLAGS} ${AM_DEPFLAGS} ${AM_CFLAGS} -D_INIT=$*_init ${CFLAGS} -o $@ -c $<;
+
+-include .*.d
+
+
+#
+#	Shared libraries
+#
+lib%.so: lib%.oo
+	${AM_VERBOSE_CCLD} ${CCLD} ${AM_LDFLAGS} ${LDFLAGS} -shared -o $@ $< -L../libxtables/.libs -lxtables ${$*_LIBADD};
+
+lib%.oo: ${srcdir}/lib%.c
+	${AM_VERBOSE_CC} ${CC} ${AM_CPPFLAGS} ${AM_DEPFLAGS} ${AM_CFLAGS} -D_INIT=lib$*_init -DPIC -fPIC ${CFLAGS} -o $@ -c $<;
+
+libxt_NOTRACK.so: libxt_CT.so
+	ln -fs $< $@
+libxt_state.so: libxt_conntrack.so
+	ln -fs $< $@
+
+# Need the LIBADDs in iptables/Makefile.am too for libxtables_la_LIBADD
+xt_RATEEST_LIBADD   = -lm
+xt_statistic_LIBADD = -lm
+xt_connlabel_LIBADD = @libnetfilter_conntrack_LIBS@
+
+#
+#	Static bits
+#
+#	If static building is disabled, libext*.a will still be generated,
+#	but will be empty. This is good since we can do with less case
+#	handling code in the Makefiles.
+#
+lib%.o: ${srcdir}/lib%.c
+	${AM_VERBOSE_CC} ${CC} ${AM_CPPFLAGS} ${AM_DEPFLAGS} ${AM_CFLAGS} -DNO_SHARED_LIBS=1 -D_INIT=lib$*_init ${CFLAGS} -o $@ -c $<;
+
+libext.a: initext.o ${libext_objs}
+	${AM_VERBOSE_AR} ${AR} crs $@ $^;
+
+libext_ebt.a: initextb.o ${libext_ebt_objs}
+	${AM_VERBOSE_AR} ${AR} crs $@ $^;
+
+libext_arpt.a: initexta.o ${libext_arpt_objs}
+	${AM_VERBOSE_AR} ${AR} crs $@ $^;
+
+libext4.a: initext4.o ${libext4_objs}
+	${AM_VERBOSE_AR} ${AR} crs $@ $^;
+
+libext6.a: initext6.o ${libext6_objs}
+	${AM_VERBOSE_AR} ${AR} crs $@ $^;
+
+initext_func  := $(addprefix xt_,${pfx_build_mod})
+initextb_func := $(addprefix ebt_,${pfb_build_mod})
+initexta_func := $(addprefix arpt_,${pfa_build_mod})
+initext4_func := $(addprefix ipt_,${pf4_build_mod})
+initext6_func := $(addprefix ip6t_,${pf6_build_mod})
+
+.initext.dd: FORCE
+	@echo "${initext_func}" >$@.tmp; \
+	cmp -s $@ $@.tmp || mv $@.tmp $@; \
+	rm -f $@.tmp;
+
+.initextb.dd: FORCE
+	@echo "${initextb_func}" >$@.tmp; \
+	cmp -s $@ $@.tmp || mv $@.tmp $@; \
+	rm -f $@.tmp;
+
+.initexta.dd: FORCE
+	@echo "${initexta_func}" >$@.tmp; \
+	cmp -s $@ $@.tmp || mv $@.tmp $@; \
+	rm -f $@.tmp;
+
+.initext4.dd: FORCE
+	@echo "${initext4_func}" >$@.tmp; \
+	cmp -s $@ $@.tmp || mv $@.tmp $@; \
+	rm -f $@.tmp;
+
+.initext6.dd: FORCE
+	@echo "${initext6_func}" >$@.tmp; \
+	cmp -s $@ $@.tmp || mv $@.tmp $@; \
+	rm -f $@.tmp;
+
+initext.c: .initext.dd
+	${AM_VERBOSE_GEN}
+	@( \
+	echo "" >$@; \
+	for i in ${initext_func}; do \
+		echo "extern void lib$${i}_init(void);" >>$@; \
+	done; \
+	echo "void init_extensions(void);" >>$@; \
+	echo "void init_extensions(void)" >>$@; \
+	echo "{" >>$@; \
+	for i in ${initext_func}; do \
+		echo  " ""lib$${i}_init();" >>$@; \
+	done; \
+	echo "}" >>$@; \
+	);
+
+initextb.c: .initextb.dd
+	${AM_VERBOSE_GEN}
+	@( \
+	echo "" >$@; \
+	for i in ${initextb_func}; do \
+		echo "extern void lib$${i}_init(void);" >>$@; \
+	done; \
+	echo "void init_extensionsb(void);" >>$@; \
+	echo "void init_extensionsb(void)" >>$@; \
+	echo "{" >>$@; \
+	for i in ${initextb_func}; do \
+		echo  " ""lib$${i}_init();" >>$@; \
+	done; \
+	echo "}" >>$@; \
+	);
+
+initexta.c: .initexta.dd
+	${AM_VERBOSE_GEN}
+	@( \
+	echo "" >$@; \
+	for i in ${initexta_func}; do \
+		echo "extern void lib$${i}_init(void);" >>$@; \
+	done; \
+	echo "void init_extensionsa(void);" >>$@; \
+	echo "void init_extensionsa(void)" >>$@; \
+	echo "{" >>$@; \
+	for i in ${initexta_func}; do \
+		echo  " ""lib$${i}_init();" >>$@; \
+	done; \
+	echo "}" >>$@; \
+	);
+
+initext4.c: .initext4.dd
+	${AM_VERBOSE_GEN}
+	@( \
+	echo "" >$@; \
+	for i in ${initext4_func}; do \
+		echo "extern void lib$${i}_init(void);" >>$@; \
+	done; \
+	echo "void init_extensions4(void);" >>$@; \
+	echo "void init_extensions4(void)" >>$@; \
+	echo "{" >>$@; \
+	for i in ${initext4_func}; do \
+		echo  " ""lib$${i}_init();" >>$@; \
+	done; \
+	echo "}" >>$@; \
+	);
+
+initext6.c: .initext6.dd
+	${AM_VERBOSE_GEN}
+	@( \
+	echo "" >$@; \
+	for i in ${initext6_func}; do \
+		echo "extern void lib$${i}_init(void);" >>$@; \
+	done; \
+	echo "void init_extensions6(void);" >>$@; \
+	echo "void init_extensions6(void)" >>$@; \
+	echo "{" >>$@; \
+	for i in ${initext6_func}; do \
+		echo " ""lib$${i}_init();" >>$@; \
+	done; \
+	echo "}" >>$@; \
+	);
+
+#
+#	Manual pages
+#
+ex_matches = $(shell echo ${1} | LC_ALL=POSIX grep -Eo '\b[[:lower:][:digit:]_]+\b')
+ex_targets = $(shell echo ${1} | LC_ALL=POSIX grep -Eo '\b[[:upper:][:digit:]_]+\b')
+man_run    = \
+	${AM_VERBOSE_GEN} \
+	for ext in $(sort ${1}); do \
+		f="${srcdir}/libxt_$$ext.man"; \
+		if [ -f "$$f" ]; then \
+			echo -e "\t+ $$f" >&2; \
+			echo ".SS $$ext"; \
+			cat "$$f" || exit $$?; \
+		fi; \
+		f="${srcdir}/libip6t_$$ext.man"; \
+		if [ -f "$$f" ]; then \
+			echo -e "\t+ $$f" >&2; \
+			echo ".SS $$ext (IPv6-specific)"; \
+			cat "$$f" || exit $$?; \
+		fi; \
+		f="${srcdir}/libipt_$$ext.man"; \
+		if [ -f "$$f" ]; then \
+			echo -e "\t+ $$f" >&2; \
+			echo ".SS $$ext (IPv4-specific)"; \
+			cat "$$f" || exit $$?; \
+		fi; \
+	done >$@;
+
+matches.man: .initext.dd .initextb.dd .initexta.dd .initext4.dd .initext6.dd $(wildcard ${srcdir}/lib*.man)
+	$(call man_run,$(call ex_matches,${pfx_build_mod} ${pfb_build_mod} ${pfa_build_mod} ${pf4_build_mod} ${pf6_build_mod} ${pfx_symlinks}))
+
+targets.man: .initext.dd .initextb.dd .initexta.dd .initext4.dd .initext6.dd $(wildcard ${srcdir}/lib*.man)
+	$(call man_run,$(call ex_targets,${pfx_build_mod} ${pfb_build_mod} ${pfa_build_mod} ${pf4_build_mod} ${pf6_build_mod} ${pfx_symlinks}))
diff --git a/extensions/dscp_helper.c b/extensions/dscp_helper.c
new file mode 100644
index 0000000..75b1fec
--- /dev/null
+++ b/extensions/dscp_helper.c
@@ -0,0 +1,79 @@
+/*
+ * DiffServ classname <-> DiffServ codepoint mapping functions.
+ *
+ * The latest list of the mappings can be found at:
+ * <http://www.iana.org/assignments/dscp-registry>
+ *
+ * This code is released under the GNU GPL v2, 1991
+ *
+ * Author: Iain Barnes
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+
+
+static const struct ds_class
+{
+	const char *name;
+	unsigned int dscp;
+} ds_classes[] =
+{
+	{ "CS0", 0x00 },
+	{ "CS1", 0x08 },
+	{ "CS2", 0x10 },
+	{ "CS3", 0x18 },
+	{ "CS4", 0x20 },
+	{ "CS5", 0x28 },
+	{ "CS6", 0x30 },
+	{ "CS7", 0x38 },
+	{ "BE", 0x00 },
+	{ "AF11", 0x0a },
+	{ "AF12", 0x0c },
+	{ "AF13", 0x0e },
+	{ "AF21", 0x12 },
+	{ "AF22", 0x14 },
+	{ "AF23", 0x16 },
+	{ "AF31", 0x1a },
+	{ "AF32", 0x1c },
+	{ "AF33", 0x1e },
+	{ "AF41", 0x22 },
+	{ "AF42", 0x24 },
+	{ "AF43", 0x26 },
+	{ "EF", 0x2e }
+};
+
+
+
+static unsigned int
+class_to_dscp(const char *name)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(ds_classes); i++) {
+		if (!strncasecmp(name, ds_classes[i].name,
+					strlen(ds_classes[i].name)))
+			return ds_classes[i].dscp;
+	}
+
+	xtables_error(PARAMETER_PROBLEM,
+			"Invalid DSCP value `%s'\n", name);
+}
+
+
+#if 0
+static const char *
+dscp_to_name(unsigned int dscp)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ds_classes); ++i) 
+		if (dscp == ds_classes[i].dscp)
+			return ds_classes[i].name;
+
+	xtables_error(PARAMETER_PROBLEM,
+			"Invalid DSCP value `%d'\n", dscp);
+}
+#endif
+
diff --git a/extensions/filter_init b/extensions/filter_init
new file mode 100755
index 0000000..2fcdd6d
--- /dev/null
+++ b/extensions/filter_init
@@ -0,0 +1,7 @@
+#!/bin/sh
+# This is for working around Android.mk's incapability to handle $* in CFLAGS,
+# even with SECONDEXPNASION.
+# LOCAL_CFLAGS:=-D_INIT=$*_init
+f=${1##*/}
+f=${f%%.*}
+sed "s/\([ ]*\)\(_init\)\(([ ]*void\)/\1${f}_init\3/" $1
diff --git a/extensions/gen_init b/extensions/gen_init
new file mode 100755
index 0000000..c1844f3
--- /dev/null
+++ b/extensions/gen_init
@@ -0,0 +1,36 @@
+#!/bin/bash -e
+#
+# Generate init_extensions* functions to call all the _init functions from
+# filter_init
+#
+# Usage: gen_init <suffix> filename...
+#
+# Example output:
+#
+#   void libxt_tcp_init(void);
+#   void libxt_udp_init(void);
+#   void init_extensions(void);
+#   void init_extensions(void) {
+#     libxt_tcp_init();
+#     libxt_udp_init();
+#   }
+
+EXT=$1
+shift
+
+for i in "$@"; do
+  f=${i##*/}
+  f=${f%%.*}
+  echo "void ${f}_init(void);"
+done
+
+echo "void init_extensions${EXT}(void);"
+echo "void init_extensions${EXT}(void) {"
+
+for i in "$@"; do
+  f=${i##*/}
+  f=${f%%.*}
+  echo "  ${f}_init();"
+done
+
+echo "}"
diff --git a/extensions/generic.txlate b/extensions/generic.txlate
new file mode 100644
index 0000000..0e256c3
--- /dev/null
+++ b/extensions/generic.txlate
@@ -0,0 +1,36 @@
+iptables-translate -I OUTPUT -p udp -d 8.8.8.8 -j ACCEPT
+nft insert rule ip filter OUTPUT ip protocol udp ip daddr 8.8.8.8 counter accept
+
+iptables-translate -F -t nat
+nft flush table ip nat
+
+iptables-translate -I INPUT -i iifname -s 10.0.0.0/8
+nft insert rule ip filter INPUT iifname "iifname" ip saddr 10.0.0.0/8 counter
+
+iptables-translate -A INPUT -i iif+ ! -d 10.0.0.0/8
+nft add rule ip filter INPUT iifname "iif*" ip daddr != 10.0.0.0/8 counter
+
+ebtables-translate -I INPUT -i iname --logical-in ilogname -s 0:0:0:0:0:0
+nft insert rule bridge filter INPUT iifname "iname" meta ibrname "ilogname" ether saddr 00:00:00:00:00:00 counter
+
+ebtables-translate -A FORWARD ! -i iname --logical-in ilogname -o out+ --logical-out lout+ -d 1:2:3:4:de:af
+nft add rule bridge filter FORWARD iifname != "iname" meta ibrname "ilogname" oifname "out*" meta obrname "lout*" ether daddr 01:02:03:04:de:af counter
+
+ebtables-translate -I INPUT -p ip -d 1:2:3:4:5:6/ff:ff:ff:ff:00:00
+nft insert rule bridge filter INPUT ether type 0x800 ether daddr 01:02:03:04:00:00 and ff:ff:ff:ff:00:00 == 01:02:03:04:00:00 counter
+
+# asterisk is not special in iptables and it is even a valid interface name
+iptables-translate -A FORWARD -i '*' -o 'eth*foo'
+nft add rule ip filter FORWARD iifname "\*" oifname "eth\*foo" counter
+
+# escape all asterisks but translate only the first plus character
+iptables-translate -A FORWARD -i 'eth*foo*+' -o 'eth++'
+nft add rule ip filter FORWARD iifname "eth\*foo\**" oifname "eth+*" counter
+
+# skip for always matching interface names
+iptables-translate -A FORWARD -i '+'
+nft add rule ip filter FORWARD counter
+
+# match against invalid interface name to simulate never matching rule
+iptables-translate -A FORWARD ! -i '+'
+nft add rule ip filter FORWARD iifname "INVAL/D" counter
diff --git a/extensions/iptables.t b/extensions/iptables.t
new file mode 100644
index 0000000..b4b6d67
--- /dev/null
+++ b/extensions/iptables.t
@@ -0,0 +1,6 @@
+:FORWARD
+-i alongifacename0;=;OK
+-i thisinterfaceistoolong0;;FAIL
+-i eth+ -o alongifacename+;=;OK
+! -i eth0;=;OK
+! -o eth+;=;OK
diff --git a/extensions/libarpt_CLASSIFY.t b/extensions/libarpt_CLASSIFY.t
new file mode 100644
index 0000000..0cf0f2c
--- /dev/null
+++ b/extensions/libarpt_CLASSIFY.t
@@ -0,0 +1,4 @@
+:OUTPUT
+-o lo --destination-mac 11:22:33:44:55:66;-o lo --dst-mac 11:22:33:44:55:66;OK
+--dst-mac Broadcast ;--dst-mac ff:ff:ff:ff:ff:ff;OK
+! -o eth+ -d 1.2.3.4/24 -j CLASSIFY --set-class 0:0;-j CLASSIFY ! -o eth+ -d 1.2.3.0/24 --set-class 0:0;OK
diff --git a/extensions/libarpt_MARK.t b/extensions/libarpt_MARK.t
new file mode 100644
index 0000000..3b13d44
--- /dev/null
+++ b/extensions/libarpt_MARK.t
@@ -0,0 +1,4 @@
+:INPUT,OUTPUT
+-j MARK -d 0.0.0.0/8 --set-mark 1;=;OK
+-s ! 0.0.0.0 -j MARK --and-mark 0x17;-j MARK ! -s 0.0.0.0 --and-mark 17;OK
+-j MARK -s 0.0.0.0 --or-mark 17;=;OK
diff --git a/extensions/libarpt_mangle.c b/extensions/libarpt_mangle.c
new file mode 100644
index 0000000..a2378a8
--- /dev/null
+++ b/extensions/libarpt_mangle.c
@@ -0,0 +1,193 @@
+/*
+ * Arturo Borrero Gonzalez <arturo@debian.org> adapted
+ * this code to libxtables for arptables-compat in 2015
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <getopt.h>
+#include <netinet/ether.h>
+#include <xtables.h>
+#include <linux/netfilter_arp/arpt_mangle.h>
+#include "iptables/nft.h"
+#include "iptables/nft-arp.h"
+
+static void arpmangle_print_help(void)
+{
+	printf(
+	"mangle target options:\n"
+	"--mangle-ip-s IP address\n"
+	"--mangle-ip-d IP address\n"
+	"--mangle-mac-s MAC address\n"
+	"--mangle-mac-d MAC address\n"
+	"--mangle-target target (DROP, CONTINUE or ACCEPT -- default is ACCEPT)\n");
+}
+
+#define MANGLE_IPS    '1'
+#define MANGLE_IPT    '2'
+#define MANGLE_DEVS   '3'
+#define MANGLE_DEVT   '4'
+#define MANGLE_TARGET '5'
+
+static const struct option arpmangle_opts[] = {
+	{ .name = "mangle-ip-s",	.has_arg = true, .val = MANGLE_IPS },
+	{ .name = "mangle-ip-d",	.has_arg = true, .val = MANGLE_IPT },
+	{ .name = "mangle-mac-s",	.has_arg = true, .val = MANGLE_DEVS },
+	{ .name = "mangle-mac-d",	.has_arg = true, .val = MANGLE_DEVT },
+	{ .name = "mangle-target",	.has_arg = true, .val = MANGLE_TARGET },
+	XT_GETOPT_TABLEEND,
+};
+
+static void arpmangle_init(struct xt_entry_target *target)
+{
+	struct arpt_mangle *mangle = (struct arpt_mangle *)target->data;
+
+	mangle->target = NF_ACCEPT;
+}
+
+static int
+arpmangle_parse(int c, char **argv, int invert, unsigned int *flags,
+		const void *entry, struct xt_entry_target **target)
+{
+	struct arpt_mangle *mangle = (struct arpt_mangle *)(*target)->data;
+	struct in_addr *ipaddr, mask;
+	struct ether_addr *macaddr;
+	const struct arpt_entry *e = (const struct arpt_entry *)entry;
+	unsigned int nr;
+	int ret = 1;
+
+	memset(&mask, 0, sizeof(mask));
+
+	switch (c) {
+	case MANGLE_IPS:
+		xtables_ipparse_any(optarg, &ipaddr, &mask, &nr);
+		mangle->u_s.src_ip.s_addr = ipaddr->s_addr;
+		free(ipaddr);
+		mangle->flags |= ARPT_MANGLE_SIP;
+		break;
+	case MANGLE_IPT:
+		xtables_ipparse_any(optarg, &ipaddr, &mask, &nr);
+		mangle->u_t.tgt_ip.s_addr = ipaddr->s_addr;
+		free(ipaddr);
+		mangle->flags |= ARPT_MANGLE_TIP;
+		break;
+	case MANGLE_DEVS:
+		if (e->arp.arhln_mask == 0)
+			xtables_error(PARAMETER_PROBLEM,
+				      "no --h-length defined");
+		if (e->arp.invflags & ARPT_INV_ARPHLN)
+			xtables_error(PARAMETER_PROBLEM,
+				      "! --h-length not allowed for "
+				      "--mangle-mac-s");
+		if (e->arp.arhln != 6)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only --h-length 6 supported");
+		macaddr = ether_aton(optarg);
+		if (macaddr == NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				      "invalid source MAC");
+		memcpy(mangle->src_devaddr, macaddr, e->arp.arhln);
+		mangle->flags |= ARPT_MANGLE_SDEV;
+		break;
+	case MANGLE_DEVT:
+		if (e->arp.arhln_mask == 0)
+			xtables_error(PARAMETER_PROBLEM,
+				      "no --h-length defined");
+		if (e->arp.invflags & ARPT_INV_ARPHLN)
+			xtables_error(PARAMETER_PROBLEM,
+				      "! hln not allowed for --mangle-mac-d");
+		if (e->arp.arhln != 6)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only --h-length 6 supported");
+		macaddr = ether_aton(optarg);
+		if (macaddr == NULL)
+			xtables_error(PARAMETER_PROBLEM, "invalid target MAC");
+		memcpy(mangle->tgt_devaddr, macaddr, e->arp.arhln);
+		mangle->flags |= ARPT_MANGLE_TDEV;
+		break;
+	case MANGLE_TARGET:
+		if (!strcmp(optarg, "DROP"))
+			mangle->target = NF_DROP;
+		else if (!strcmp(optarg, "ACCEPT"))
+			mangle->target = NF_ACCEPT;
+		else if (!strcmp(optarg, "CONTINUE"))
+			mangle->target = XT_CONTINUE;
+		else
+			xtables_error(PARAMETER_PROBLEM,
+				      "bad target for --mangle-target");
+		break;
+	default:
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static void arpmangle_final_check(unsigned int flags)
+{
+}
+
+static const char *ipaddr_to(const struct in_addr *addrp, int numeric)
+{
+	if (numeric)
+		return xtables_ipaddr_to_numeric(addrp);
+	else
+		return xtables_ipaddr_to_anyname(addrp);
+}
+
+static void
+arpmangle_print(const void *ip, const struct xt_entry_target *target,
+		int numeric)
+{
+	struct arpt_mangle *m = (struct arpt_mangle *)(target->data);
+
+	if (m->flags & ARPT_MANGLE_SIP) {
+		printf(" --mangle-ip-s %s",
+		       ipaddr_to(&(m->u_s.src_ip), numeric));
+	}
+	if (m->flags & ARPT_MANGLE_SDEV) {
+		printf(" --mangle-mac-s ");
+		xtables_print_mac((unsigned char *)m->src_devaddr);
+	}
+	if (m->flags & ARPT_MANGLE_TIP) {
+		printf(" --mangle-ip-d %s",
+		       ipaddr_to(&(m->u_t.tgt_ip), numeric));
+	}
+	if (m->flags & ARPT_MANGLE_TDEV) {
+		printf(" --mangle-mac-d ");
+		xtables_print_mac((unsigned char *)m->tgt_devaddr);
+	}
+	if (m->target != NF_ACCEPT) {
+		printf(" --mangle-target %s",
+		       m->target == NF_DROP ? "DROP" : "CONTINUE");
+	}
+}
+
+static void arpmangle_save(const void *ip, const struct xt_entry_target *target)
+{
+	arpmangle_print(ip, target, 0);
+}
+
+static struct xtables_target arpmangle_target = {
+	.name		= "mangle",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_ARP,
+	.size		= XT_ALIGN(sizeof(struct arpt_mangle)),
+	.userspacesize	= XT_ALIGN(sizeof(struct arpt_mangle)),
+	.help		= arpmangle_print_help,
+	.init		= arpmangle_init,
+	.parse		= arpmangle_parse,
+	.final_check	= arpmangle_final_check,
+	.print		= arpmangle_print,
+	.save		= arpmangle_save,
+	.extra_opts	= arpmangle_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&arpmangle_target);
+}
diff --git a/extensions/libarpt_mangle.t b/extensions/libarpt_mangle.t
new file mode 100644
index 0000000..da96694
--- /dev/null
+++ b/extensions/libarpt_mangle.t
@@ -0,0 +1,5 @@
+:OUTPUT
+-j mangle -s 1.2.3.4 --mangle-ip-s 1.2.3.5;=;OK
+-j mangle -d 1.2.3.4 --mangle-ip-d 1.2.3.5;=;OK
+-j mangle -d 1.2.3.4 --mangle-mac-d 00:01:02:03:04:05;=;OK
+-d 1.2.3.4 --h-length 5 -j mangle --mangle-mac-s 00:01:02:03:04:05;=;FAIL
diff --git a/extensions/libarpt_standard.t b/extensions/libarpt_standard.t
new file mode 100644
index 0000000..e84a00b
--- /dev/null
+++ b/extensions/libarpt_standard.t
@@ -0,0 +1,14 @@
+:INPUT
+-s 192.168.0.1;=;OK
+-s 0.0.0.0/8;=;OK
+-s ! 0.0.0.0;! -s 0.0.0.0;OK
+-d 192.168.0.1;=;OK
+! -d 0.0.0.0;=;OK
+-d 0.0.0.0/24;=;OK
+-j DROP -i lo;=;OK
+-j ACCEPT ! -i lo;=;OK
+-i ppp+;=;OK
+! -i ppp+;=;OK
+-i lo --destination-mac 11:22:33:44:55:66;-i lo --dst-mac 11:22:33:44:55:66;OK
+--source-mac Unicast;--src-mac 00:00:00:00:00:00/01:00:00:00:00:00;OK
+! --src-mac Multicast;! --src-mac 01:00:00:00:00:00/01:00:00:00:00:00;OK
diff --git a/extensions/libebt_802_3.c b/extensions/libebt_802_3.c
new file mode 100644
index 0000000..f05d02e
--- /dev/null
+++ b/extensions/libebt_802_3.c
@@ -0,0 +1,133 @@
+/* 802_3
+ *
+ * Author:
+ * Chris Vitale <csv@bluetail.com>
+ *
+ * May 2003
+ *
+ * Adapted by Arturo Borrero Gonzalez <arturo@debian.org>
+ * to use libxtables for ebtables-compat
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_802_3.h>
+
+#define _802_3_SAP	'1'
+#define _802_3_TYPE	'2'
+
+static const struct option br802_3_opts[] = {
+	{ .name = "802_3-sap",	.has_arg = true, .val = _802_3_SAP },
+	{ .name = "802_3-type",	.has_arg = true, .val = _802_3_TYPE },
+	XT_GETOPT_TABLEEND,
+};
+
+static void br802_3_print_help(void)
+{
+	printf(
+"802_3 options:\n"
+"--802_3-sap [!] protocol       : 802.3 DSAP/SSAP- 1 byte value (hex)\n"
+"  DSAP and SSAP are always the same.  One SAP applies to both fields\n"
+"--802_3-type [!] protocol      : 802.3 SNAP Type- 2 byte value (hex)\n"
+"  Type implies SAP value 0xaa\n");
+}
+
+static void br802_3_init(struct xt_entry_match *match)
+{
+	struct ebt_802_3_info *info = (struct ebt_802_3_info *)match->data;
+
+	info->invflags = 0;
+	info->bitmask = 0;
+}
+
+static int
+br802_3_parse(int c, char **argv, int invert, unsigned int *flags,
+	      const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_802_3_info *info = (struct ebt_802_3_info *) (*match)->data;
+	unsigned int i;
+	char *end;
+
+	switch (c) {
+	case _802_3_SAP:
+		if (invert)
+			info->invflags |= EBT_802_3_SAP;
+		i = strtoul(optarg, &end, 16);
+		if (i > 255 || *end != '\0')
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with specified "
+					"sap hex value, %x",i);
+		info->sap = i; /* one byte, so no byte order worries */
+		info->bitmask |= EBT_802_3_SAP;
+		break;
+	case _802_3_TYPE:
+		if (invert)
+			info->invflags |= EBT_802_3_TYPE;
+		i = strtoul(optarg, &end, 16);
+		if (i > 65535 || *end != '\0') {
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with the specified "
+					"type hex value, %x",i);
+		}
+		info->type = htons(i);
+		info->bitmask |= EBT_802_3_TYPE;
+		break;
+	default:
+		return 0;
+	}
+
+	*flags |= info->bitmask;
+	return 1;
+}
+
+static void
+br802_3_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void br802_3_print(const void *ip, const struct xt_entry_match *match,
+			  int numeric)
+{
+	struct ebt_802_3_info *info = (struct ebt_802_3_info *)match->data;
+
+	if (info->bitmask & EBT_802_3_SAP) {
+		printf("--802_3-sap ");
+		if (info->invflags & EBT_802_3_SAP)
+			printf("! ");
+		printf("0x%.2x ", info->sap);
+	}
+	if (info->bitmask & EBT_802_3_TYPE) {
+		printf("--802_3-type ");
+		if (info->invflags & EBT_802_3_TYPE)
+			printf("! ");
+		printf("0x%.4x ", ntohs(info->type));
+	}
+}
+
+static struct xtables_match br802_3_match =
+{
+	.name		= "802_3",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_802_3_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_802_3_info)),
+	.init		= br802_3_init,
+	.help		= br802_3_print_help,
+	.parse		= br802_3_parse,
+	.final_check	= br802_3_final_check,
+	.print		= br802_3_print,
+	.extra_opts	= br802_3_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&br802_3_match);
+}
diff --git a/extensions/libebt_802_3.t b/extensions/libebt_802_3.t
new file mode 100644
index 0000000..ddfb2f0
--- /dev/null
+++ b/extensions/libebt_802_3.t
@@ -0,0 +1,3 @@
+:INPUT,FORWARD,OUTPUT
+--802_3-sap ! 0x0a -j CONTINUE;=;OK
+--802_3-type 0x000a -j RETURN;=;OK
diff --git a/extensions/libebt_among.c b/extensions/libebt_among.c
new file mode 100644
index 0000000..2b9a1b6
--- /dev/null
+++ b/extensions/libebt_among.c
@@ -0,0 +1,243 @@
+/* ebt_among
+ *
+ * Authors:
+ * Grzegorz Borowiak <grzes@gnu.univ.gda.pl>
+ *
+ * August, 2003
+ */
+
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <xtables.h>
+#include <arpa/inet.h>
+#include <netinet/ether.h>
+#include <netinet/in.h>
+#include <linux/if_ether.h>
+#include <linux/netfilter_bridge/ebt_among.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define AMONG_DST '1'
+#define AMONG_SRC '2'
+#define AMONG_DST_F '3'
+#define AMONG_SRC_F '4'
+
+static const struct option bramong_opts[] = {
+	{"among-dst", required_argument, 0, AMONG_DST},
+	{"among-src", required_argument, 0, AMONG_SRC},
+	{"among-dst-file", required_argument, 0, AMONG_DST_F},
+	{"among-src-file", required_argument, 0, AMONG_SRC_F},
+	{0}
+};
+
+static void bramong_print_help(void)
+{
+	printf(
+"`among' options:\n"
+"--among-dst      [!] list      : matches if ether dst is in list\n"
+"--among-src      [!] list      : matches if ether src is in list\n"
+"--among-dst-file [!] file      : obtain dst list from file\n"
+"--among-src-file [!] file      : obtain src list from file\n"
+"list has form:\n"
+" xx:xx:xx:xx:xx:xx[=ip.ip.ip.ip],yy:yy:yy:yy:yy:yy[=ip.ip.ip.ip]"
+",...,zz:zz:zz:zz:zz:zz[=ip.ip.ip.ip][,]\n"
+"Things in brackets are optional.\n"
+"If you want to allow two (or more) IP addresses to one MAC address, you\n"
+"can specify two (or more) pairs with the same MAC, e.g.\n"
+" 00:00:00:fa:eb:fe=153.19.120.250,00:00:00:fa:eb:fe=192.168.0.1\n"
+	);
+}
+
+static void
+parse_nft_among_pair(char *buf, struct nft_among_pair *pair, bool have_ip)
+{
+	char *sep = index(buf, '=');
+	struct ether_addr *ether;
+
+	if (sep) {
+		*sep = '\0';
+
+		if (!inet_aton(sep + 1, &pair->in))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid IP address '%s'\n", sep + 1);
+	}
+	ether = ether_aton(buf);
+	if (!ether)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Invalid MAC address '%s'\n", buf);
+	memcpy(&pair->ether, ether, sizeof(*ether));
+}
+
+static void
+parse_nft_among_pairs(struct nft_among_pair *pairs, char *buf,
+		      size_t cnt, bool have_ip)
+{
+	size_t tmpcnt = 0;
+
+	buf = strtok(buf, ",");
+	while (buf) {
+		struct nft_among_pair pair = {};
+
+		parse_nft_among_pair(buf, &pair, have_ip);
+		nft_among_insert_pair(pairs, &tmpcnt, &pair);
+		buf = strtok(NULL, ",");
+	}
+}
+
+static size_t count_nft_among_pairs(char *buf)
+{
+	size_t cnt = 0;
+	char *p = buf;
+
+	if (!*buf)
+		return 0;
+
+	do {
+		cnt++;
+		p = index(++p, ',');
+	} while (p);
+
+	return cnt;
+}
+
+static bool nft_among_pairs_have_ip(char *buf)
+{
+	return !!index(buf, '=');
+}
+
+static int bramong_parse(int c, char **argv, int invert,
+		 unsigned int *flags, const void *entry,
+		 struct xt_entry_match **match)
+{
+	struct nft_among_data *data = (struct nft_among_data *)(*match)->data;
+	struct xt_entry_match *new_match;
+	bool have_ip, dst = false;
+	size_t new_size, cnt;
+	struct stat stats;
+	int fd = -1, poff;
+	long flen = 0;
+
+	switch (c) {
+	case AMONG_DST_F:
+		dst = true;
+		/* fall through */
+	case AMONG_SRC_F:
+		if ((fd = open(optarg, O_RDONLY)) == -1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Couldn't open file '%s'", optarg);
+		if (fstat(fd, &stats) < 0)
+			xtables_error(PARAMETER_PROBLEM,
+				      "fstat(%s) failed: '%s'",
+				      optarg, strerror(errno));
+		flen = stats.st_size;
+		/* use mmap because the file will probably be big */
+		optarg = mmap(0, flen, PROT_READ | PROT_WRITE,
+			      MAP_PRIVATE, fd, 0);
+		if (optarg == MAP_FAILED)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Couldn't map file to memory");
+		if (optarg[flen-1] != '\n')
+			xtables_error(PARAMETER_PROBLEM,
+				      "File should end with a newline");
+		if (strchr(optarg, '\n') != optarg+flen-1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "File should only contain one line");
+		optarg[flen-1] = '\0';
+		/* fall through */
+	case AMONG_DST:
+		if (c == AMONG_DST)
+			dst = true;
+		/* fall through */
+	case AMONG_SRC:
+		break;
+	default:
+		return 0;
+	}
+
+	cnt = count_nft_among_pairs(optarg);
+	if (cnt == 0)
+		return 0;
+
+	new_size = data->src.cnt + data->dst.cnt + cnt;
+	new_size *= sizeof(struct nft_among_pair);
+	new_size += XT_ALIGN(sizeof(struct xt_entry_match)) +
+			sizeof(struct nft_among_data);
+	new_match = xtables_calloc(1, new_size);
+	memcpy(new_match, *match, (*match)->u.match_size);
+	new_match->u.match_size = new_size;
+
+	data = (struct nft_among_data *)new_match->data;
+	have_ip = nft_among_pairs_have_ip(optarg);
+	poff = nft_among_prepare_data(data, dst, cnt, invert, have_ip);
+	parse_nft_among_pairs(data->pairs + poff, optarg, cnt, have_ip);
+
+	free(*match);
+	*match = new_match;
+
+	if (c == AMONG_DST_F || c == AMONG_SRC_F) {
+		munmap(argv, flen);
+		close(fd);
+	}
+	return 1;
+}
+
+static void __bramong_print(struct nft_among_pair *pairs,
+			    int cnt, bool inv, bool have_ip)
+{
+	const char *isep = inv ? "! " : "";
+	int i;
+
+	for (i = 0; i < cnt; i++) {
+		printf("%s", isep);
+		isep = ",";
+
+		printf("%s", ether_ntoa(&pairs[i].ether));
+		if (pairs[i].in.s_addr != INADDR_ANY)
+			printf("=%s", inet_ntoa(pairs[i].in));
+	}
+	printf(" ");
+}
+
+static void bramong_print(const void *ip, const struct xt_entry_match *match,
+			  int numeric)
+{
+	struct nft_among_data *data = (struct nft_among_data *)match->data;
+
+	if (data->src.cnt) {
+		printf("--among-src ");
+		__bramong_print(data->pairs,
+				data->src.cnt, data->src.inv, data->src.ip);
+	}
+	if (data->dst.cnt) {
+		printf("--among-dst ");
+		__bramong_print(data->pairs + data->src.cnt,
+				data->dst.cnt, data->dst.inv, data->dst.ip);
+	}
+}
+
+static struct xtables_match bramong_match = {
+	.name		= "among",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct nft_among_data)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nft_among_data)),
+	.help		= bramong_print_help,
+	.parse		= bramong_parse,
+	.print		= bramong_print,
+	.extra_opts	= bramong_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&bramong_match);
+}
diff --git a/extensions/libebt_among.t b/extensions/libebt_among.t
new file mode 100644
index 0000000..a02206f
--- /dev/null
+++ b/extensions/libebt_among.t
@@ -0,0 +1,16 @@
+:INPUT,FORWARD,OUTPUT
+--among-dst de:ad:0:be:ee:ff,c0:ff:ee:0:ba:be;--among-dst c0:ff:ee:0:ba:be,de:ad:0:be:ee:ff;OK
+--among-dst ! c0:ff:ee:0:ba:be,de:ad:0:be:ee:ff;=;OK
+--among-src be:ef:0:c0:ff:ee,c0:ff:ee:0:ba:be,de:ad:0:be:ee:ff;=;OK
+--among-src de:ad:0:be:ee:ff=10.0.0.1,c0:ff:ee:0:ba:be=192.168.1.1;--among-src c0:ff:ee:0:ba:be=192.168.1.1,de:ad:0:be:ee:ff=10.0.0.1;OK
+--among-src ! c0:ff:ee:0:ba:be=192.168.1.1,de:ad:0:be:ee:ff=10.0.0.1;=;OK
+--among-src de:ad:0:be:ee:ff --among-dst c0:ff:ee:0:ba:be;=;OK
+--among-src de:ad:0:be:ee:ff=10.0.0.1 --among-dst c0:ff:ee:0:ba:be=192.168.1.1;=;OK
+--among-src ! de:ad:0:be:ee:ff --among-dst c0:ff:ee:0:ba:be;=;OK
+--among-src de:ad:0:be:ee:ff=10.0.0.1 --among-dst ! c0:ff:ee:0:ba:be=192.168.1.1;=;OK
+--among-src ! de:ad:0:be:ee:ff --among-dst c0:ff:ee:0:ba:be=192.168.1.1;=;OK
+--among-src de:ad:0:be:ee:ff=10.0.0.1 --among-dst ! c0:ff:ee:0:ba:be=192.168.1.1;=;OK
+--among-src;=;FAIL
+--among-src 00:11=10.0.0.1;=;FAIL
+--among-src de:ad:0:be:ee:ff=10.256.0.1;=;FAIL
+--among-src c0:ff:ee:0:ba:be=192.168.1.1,de:ad:0:be:ee:ff;=;OK
diff --git a/extensions/libebt_arp.c b/extensions/libebt_arp.c
new file mode 100644
index 0000000..d5035b9
--- /dev/null
+++ b/extensions/libebt_arp.c
@@ -0,0 +1,363 @@
+/* ebt_arp
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ * Tim Gardner <timg@tpi.com>
+ *
+ * April, 2002
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <netinet/ether.h>
+
+#include <xtables.h>
+#include <net/if_arp.h>
+#include <linux/netfilter_bridge/ebt_arp.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define ARP_OPCODE '1'
+#define ARP_HTYPE  '2'
+#define ARP_PTYPE  '3'
+#define ARP_IP_S   '4'
+#define ARP_IP_D   '5'
+#define ARP_MAC_S  '6'
+#define ARP_MAC_D  '7'
+#define ARP_GRAT   '8'
+
+static const struct option brarp_opts[] = {
+	{ "arp-opcode"    , required_argument, 0, ARP_OPCODE },
+	{ "arp-op"        , required_argument, 0, ARP_OPCODE },
+	{ "arp-htype"     , required_argument, 0, ARP_HTYPE  },
+	{ "arp-ptype"     , required_argument, 0, ARP_PTYPE  },
+	{ "arp-ip-src"    , required_argument, 0, ARP_IP_S   },
+	{ "arp-ip-dst"    , required_argument, 0, ARP_IP_D   },
+	{ "arp-mac-src"   , required_argument, 0, ARP_MAC_S  },
+	{ "arp-mac-dst"   , required_argument, 0, ARP_MAC_D  },
+	{ "arp-gratuitous",       no_argument, 0, ARP_GRAT   },
+	XT_GETOPT_TABLEEND,
+};
+
+/* a few names */
+static char *opcodes[] =
+{
+	"Request",
+	"Reply",
+	"Request_Reverse",
+	"Reply_Reverse",
+	"DRARP_Request",
+	"DRARP_Reply",
+	"DRARP_Error",
+	"InARP_Request",
+	"ARP_NAK",
+};
+
+static void brarp_print_help(void)
+{
+	int i;
+
+	printf(
+"arp options:\n"
+"--arp-opcode  [!] opcode        : ARP opcode (integer or string)\n"
+"--arp-htype   [!] type          : ARP hardware type (integer or string)\n"
+"--arp-ptype   [!] type          : ARP protocol type (hexadecimal or string)\n"
+"--arp-ip-src  [!] address[/mask]: ARP IP source specification\n"
+"--arp-ip-dst  [!] address[/mask]: ARP IP target specification\n"
+"--arp-mac-src [!] address[/mask]: ARP MAC source specification\n"
+"--arp-mac-dst [!] address[/mask]: ARP MAC target specification\n"
+"[!] --arp-gratuitous            : ARP gratuitous packet\n"
+" opcode strings: \n");
+	for (i = 0; i < ARRAY_SIZE(opcodes); i++)
+		printf(" %d = %s\n", i + 1, opcodes[i]);
+	printf(
+" hardware type string: 1 = Ethernet\n"
+" protocol type string: see "XT_PATH_ETHERTYPES"\n");
+}
+
+#define OPT_OPCODE 0x01
+#define OPT_HTYPE  0x02
+#define OPT_PTYPE  0x04
+#define OPT_IP_S   0x08
+#define OPT_IP_D   0x10
+#define OPT_MAC_S  0x20
+#define OPT_MAC_D  0x40
+#define OPT_GRAT   0x80
+
+static int undot_ip(char *ip, unsigned char *ip2)
+{
+	char *p, *q, *end;
+	long int onebyte;
+	int i;
+	char buf[20];
+
+	strncpy(buf, ip, sizeof(buf) - 1);
+
+	p = buf;
+	for (i = 0; i < 3; i++) {
+		if ((q = strchr(p, '.')) == NULL)
+			return -1;
+		*q = '\0';
+		onebyte = strtol(p, &end, 10);
+		if (*end != '\0' || onebyte > 255 || onebyte < 0)
+			return -1;
+		ip2[i] = (unsigned char)onebyte;
+		p = q + 1;
+	}
+
+	onebyte = strtol(p, &end, 10);
+	if (*end != '\0' || onebyte > 255 || onebyte < 0)
+		return -1;
+	ip2[3] = (unsigned char)onebyte;
+
+	return 0;
+}
+
+static int ip_mask(char *mask, unsigned char *mask2)
+{
+	char *end;
+	long int bits;
+	uint32_t mask22;
+
+	if (undot_ip(mask, mask2)) {
+		/* not the /a.b.c.e format, maybe the /x format */
+		bits = strtol(mask, &end, 10);
+		if (*end != '\0' || bits > 32 || bits < 0)
+			return -1;
+		if (bits != 0) {
+			mask22 = htonl(0xFFFFFFFF << (32 - bits));
+			memcpy(mask2, &mask22, 4);
+		} else {
+			mask22 = 0xFFFFFFFF;
+			memcpy(mask2, &mask22, 4);
+		}
+	}
+	return 0;
+}
+
+static void ebt_parse_ip_address(char *address, uint32_t *addr, uint32_t *msk)
+{
+	char *p;
+
+	/* first the mask */
+	if ((p = strrchr(address, '/')) != NULL) {
+		*p = '\0';
+		if (ip_mask(p + 1, (unsigned char *)msk)) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with the IP mask '%s'", p + 1);
+			return;
+		}
+	} else
+		*msk = 0xFFFFFFFF;
+
+	if (undot_ip(address, (unsigned char *)addr)) {
+		xtables_error(PARAMETER_PROBLEM,
+			      "Problem with the IP address '%s'", address);
+		return;
+	}
+	*addr = *addr & *msk;
+}
+
+static int
+brarp_parse(int c, char **argv, int invert, unsigned int *flags,
+	    const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_arp_info *arpinfo = (struct ebt_arp_info *)(*match)->data;
+	long int i;
+	char *end;
+	uint32_t *addr;
+	uint32_t *mask;
+	unsigned char *maddr;
+	unsigned char *mmask;
+
+	switch (c) {
+	case ARP_OPCODE:
+		EBT_CHECK_OPTION(flags, OPT_OPCODE);
+		if (invert)
+			arpinfo->invflags |= EBT_ARP_OPCODE;
+		i = strtol(optarg, &end, 10);
+		if (i < 0 || i >= (0x1 << 16) || *end !='\0') {
+			for (i = 0; i < ARRAY_SIZE(opcodes); i++)
+				if (!strcasecmp(opcodes[i], optarg))
+					break;
+			if (i == ARRAY_SIZE(opcodes))
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified ARP opcode");
+			i++;
+		}
+		arpinfo->opcode = htons(i);
+		arpinfo->bitmask |= EBT_ARP_OPCODE;
+		break;
+
+	case ARP_HTYPE:
+		EBT_CHECK_OPTION(flags, OPT_HTYPE);
+		if (invert)
+			arpinfo->invflags |= EBT_ARP_HTYPE;
+		i = strtol(optarg, &end, 10);
+		if (i < 0 || i >= (0x1 << 16) || *end !='\0') {
+			if (!strcasecmp("Ethernet", argv[optind - 1]))
+				i = 1;
+			else
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified ARP hardware type");
+		}
+		arpinfo->htype = htons(i);
+		arpinfo->bitmask |= EBT_ARP_HTYPE;
+		break;
+	case ARP_PTYPE: {
+		uint16_t proto;
+
+		EBT_CHECK_OPTION(flags, OPT_PTYPE);
+		if (invert)
+			arpinfo->invflags |= EBT_ARP_PTYPE;
+
+		i = strtol(optarg, &end, 16);
+		if (i < 0 || i >= (0x1 << 16) || *end !='\0') {
+			struct xt_ethertypeent *ent;
+
+			ent = xtables_getethertypebyname(argv[optind - 1]);
+			if (!ent)
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified ARP "
+								 "protocol type");
+			proto = ent->e_ethertype;
+
+		} else
+			proto = i;
+		arpinfo->ptype = htons(proto);
+		arpinfo->bitmask |= EBT_ARP_PTYPE;
+		break;
+	}
+
+	case ARP_IP_S:
+	case ARP_IP_D:
+		if (c == ARP_IP_S) {
+			EBT_CHECK_OPTION(flags, OPT_IP_S);
+			addr = &arpinfo->saddr;
+			mask = &arpinfo->smsk;
+			arpinfo->bitmask |= EBT_ARP_SRC_IP;
+		} else {
+			EBT_CHECK_OPTION(flags, OPT_IP_D);
+			addr = &arpinfo->daddr;
+			mask = &arpinfo->dmsk;
+			arpinfo->bitmask |= EBT_ARP_DST_IP;
+		}
+		if (invert) {
+			if (c == ARP_IP_S)
+				arpinfo->invflags |= EBT_ARP_SRC_IP;
+			else
+				arpinfo->invflags |= EBT_ARP_DST_IP;
+		}
+		ebt_parse_ip_address(optarg, addr, mask);
+		break;
+	case ARP_MAC_S:
+	case ARP_MAC_D:
+		if (c == ARP_MAC_S) {
+			EBT_CHECK_OPTION(flags, OPT_MAC_S);
+			maddr = arpinfo->smaddr;
+			mmask = arpinfo->smmsk;
+			arpinfo->bitmask |= EBT_ARP_SRC_MAC;
+		} else {
+			EBT_CHECK_OPTION(flags, OPT_MAC_D);
+			maddr = arpinfo->dmaddr;
+			mmask = arpinfo->dmmsk;
+			arpinfo->bitmask |= EBT_ARP_DST_MAC;
+		}
+		if (invert) {
+			if (c == ARP_MAC_S)
+				arpinfo->invflags |= EBT_ARP_SRC_MAC;
+			else
+				arpinfo->invflags |= EBT_ARP_DST_MAC;
+		}
+		if (xtables_parse_mac_and_mask(optarg, maddr, mmask))
+			xtables_error(PARAMETER_PROBLEM, "Problem with ARP MAC address argument");
+		break;
+	case ARP_GRAT:
+		EBT_CHECK_OPTION(flags, OPT_GRAT);
+		arpinfo->bitmask |= EBT_ARP_GRAT;
+		if (invert)
+			arpinfo->invflags |= EBT_ARP_GRAT;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void brarp_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct ebt_arp_info *arpinfo = (struct ebt_arp_info *)match->data;
+
+	if (arpinfo->bitmask & EBT_ARP_OPCODE) {
+		int opcode = ntohs(arpinfo->opcode);
+		printf("--arp-op ");
+		if (arpinfo->invflags & EBT_ARP_OPCODE)
+			printf("! ");
+		if (opcode > 0 && opcode <= ARRAY_SIZE(opcodes))
+			printf("%s ", opcodes[opcode - 1]);
+		else
+			printf("%d ", opcode);
+	}
+	if (arpinfo->bitmask & EBT_ARP_HTYPE) {
+		printf("--arp-htype ");
+		if (arpinfo->invflags & EBT_ARP_HTYPE)
+			printf("! ");
+		printf("%d ", ntohs(arpinfo->htype));
+	}
+	if (arpinfo->bitmask & EBT_ARP_PTYPE) {
+		printf("--arp-ptype ");
+		if (arpinfo->invflags & EBT_ARP_PTYPE)
+			printf("! ");
+		printf("0x%x ", ntohs(arpinfo->ptype));
+	}
+	if (arpinfo->bitmask & EBT_ARP_SRC_IP) {
+		printf("--arp-ip-src ");
+		if (arpinfo->invflags & EBT_ARP_SRC_IP)
+			printf("! ");
+		printf("%s%s ", xtables_ipaddr_to_numeric((const struct in_addr*) &arpinfo->saddr),
+		       xtables_ipmask_to_numeric((const struct in_addr*)&arpinfo->smsk));
+	}
+	if (arpinfo->bitmask & EBT_ARP_DST_IP) {
+		printf("--arp-ip-dst ");
+		if (arpinfo->invflags & EBT_ARP_DST_IP)
+			printf("! ");
+		printf("%s%s ", xtables_ipaddr_to_numeric((const struct in_addr*) &arpinfo->daddr),
+		       xtables_ipmask_to_numeric((const struct in_addr*)&arpinfo->dmsk));
+	}
+	if (arpinfo->bitmask & EBT_ARP_SRC_MAC) {
+		printf("--arp-mac-src ");
+		if (arpinfo->invflags & EBT_ARP_SRC_MAC)
+			printf("! ");
+		xtables_print_mac_and_mask(arpinfo->smaddr, arpinfo->smmsk);
+		printf(" ");
+	}
+	if (arpinfo->bitmask & EBT_ARP_DST_MAC) {
+		printf("--arp-mac-dst ");
+		if (arpinfo->invflags & EBT_ARP_DST_MAC)
+			printf("! ");
+		xtables_print_mac_and_mask(arpinfo->dmaddr, arpinfo->dmmsk);
+		printf(" ");
+	}
+	if (arpinfo->bitmask & EBT_ARP_GRAT) {
+		if (arpinfo->invflags & EBT_ARP_GRAT)
+			printf("! ");
+		printf("--arp-gratuitous ");
+	}
+}
+
+static struct xtables_match brarp_match = {
+	.name		= "arp",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_arp_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_arp_info)),
+	.help		= brarp_print_help,
+	.parse		= brarp_parse,
+	.print		= brarp_print,
+	.extra_opts	= brarp_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brarp_match);
+}
diff --git a/extensions/libebt_arp.t b/extensions/libebt_arp.t
new file mode 100644
index 0000000..14ff0f0
--- /dev/null
+++ b/extensions/libebt_arp.t
@@ -0,0 +1,12 @@
+:INPUT,FORWARD,OUTPUT
+-p ARP --arp-op Request;=;OK
+-p ARP --arp-htype ! 1;=;OK
+-p ARP --arp-ptype 0x2;=;OK
+-p ARP --arp-ip-src 1.2.3.4;=;OK
+-p ARP ! --arp-ip-dst 1.2.3.4;-p ARP --arp-ip-dst ! 1.2.3.4 -j CONTINUE;OK
+-p ARP --arp-ip-src ! 0.0.0.0;=;OK
+-p ARP --arp-ip-dst ! 0.0.0.0/8;=;OK
+-p ARP --arp-mac-src 00:de:ad:be:ef:00;=;OK
+-p ARP --arp-mac-dst de:ad:be:ef:00:00/ff:ff:ff:ff:00:00;=;OK
+-p ARP --arp-gratuitous;=;OK
+--arp-htype 1;=;FAIL
diff --git a/extensions/libebt_arpreply.c b/extensions/libebt_arpreply.c
new file mode 100644
index 0000000..80ba215
--- /dev/null
+++ b/extensions/libebt_arpreply.c
@@ -0,0 +1,101 @@
+/* ebt_arpreply
+ *
+ * Authors:
+ * Grzegorz Borowiak <grzes@gnu.univ.gda.pl>
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ *  August, 2003
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <netinet/ether.h>
+#include <linux/netfilter_bridge/ebt_arpreply.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define OPT_REPLY_MAC     0x01
+#define OPT_REPLY_TARGET  0x02
+
+#define REPLY_MAC '1'
+#define REPLY_TARGET '2'
+static const struct option brarpreply_opts[] = {
+	{ "arpreply-mac" ,    required_argument, 0, REPLY_MAC    },
+	{ "arpreply-target" , required_argument, 0, REPLY_TARGET },
+	XT_GETOPT_TABLEEND,
+};
+
+static void brarpreply_print_help(void)
+{
+	printf(
+	"arpreply target options:\n"
+	" --arpreply-mac address           : source MAC of generated reply\n"
+	" --arpreply-target target         : ACCEPT, DROP, RETURN or CONTINUE\n"
+	"                                    (standard target is DROP)\n");
+}
+
+static void brarpreply_init(struct xt_entry_target *target)
+{
+	struct ebt_arpreply_info *replyinfo = (void *)target->data;
+
+	replyinfo->target = EBT_DROP;
+}
+
+static int
+brarpreply_parse(int c, char **argv, int invert, unsigned int *flags,
+	    const void *entry, struct xt_entry_target **tg)
+
+{
+	struct ebt_arpreply_info *replyinfo = (void *)(*tg)->data;
+	struct ether_addr *addr;
+
+	switch (c) {
+	case REPLY_MAC:
+		EBT_CHECK_OPTION(flags, OPT_REPLY_MAC);
+		if (!(addr = ether_aton(optarg)))
+			xtables_error(PARAMETER_PROBLEM, "Problem with specified --arpreply-mac mac");
+		memcpy(replyinfo->mac, addr, ETH_ALEN);
+		break;
+	case REPLY_TARGET:
+		EBT_CHECK_OPTION(flags, OPT_REPLY_TARGET);
+		if (ebt_fill_target(optarg, (unsigned int *)&replyinfo->target))
+			xtables_error(PARAMETER_PROBLEM, "Illegal --arpreply-target target");
+		break;
+
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void brarpreply_print(const void *ip, const struct xt_entry_target *t, int numeric)
+{
+	struct ebt_arpreply_info *replyinfo = (void *)t->data;
+
+	printf("--arpreply-mac ");
+	xtables_print_mac(replyinfo->mac);
+	if (replyinfo->target == EBT_DROP)
+		return;
+	printf(" --arpreply-target %s", ebt_target_name(replyinfo->target));
+}
+
+static struct xtables_target arpreply_target = {
+	.name		= "arpreply",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.init		= brarpreply_init,
+	.size		= XT_ALIGN(sizeof(struct ebt_arpreply_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_arpreply_info)),
+	.help		= brarpreply_print_help,
+	.parse		= brarpreply_parse,
+	.print		= brarpreply_print,
+	.extra_opts	= brarpreply_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&arpreply_target);
+}
diff --git a/extensions/libebt_arpreply.t b/extensions/libebt_arpreply.t
new file mode 100644
index 0000000..6734501
--- /dev/null
+++ b/extensions/libebt_arpreply.t
@@ -0,0 +1,4 @@
+:PREROUTING
+*nat
+-p ARP -i foo -j arpreply --arpreply-mac de:ad:00:be:ee:ff --arpreply-target ACCEPT;=;OK
+-p ARP -i foo -j arpreply --arpreply-mac de:ad:00:be:ee:ff;=;OK
diff --git a/extensions/libebt_dnat.c b/extensions/libebt_dnat.c
new file mode 100644
index 0000000..9f5f721
--- /dev/null
+++ b/extensions/libebt_dnat.c
@@ -0,0 +1,129 @@
+/* ebt_nat
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * June, 2002
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <netinet/ether.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_nat.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define NAT_D '1'
+#define NAT_D_TARGET '2'
+static const struct option brdnat_opts[] =
+{
+	{ "to-destination", required_argument, 0, NAT_D },
+	{ "to-dst"        , required_argument, 0, NAT_D },
+	{ "dnat-target"   , required_argument, 0, NAT_D_TARGET },
+	{ 0 }
+};
+
+static void brdnat_print_help(void)
+{
+	printf(
+	"dnat options:\n"
+	" --to-dst address       : MAC address to map destination to\n"
+	" --dnat-target target   : ACCEPT, DROP, RETURN or CONTINUE\n");
+}
+
+static void brdnat_init(struct xt_entry_target *target)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)target->data;
+
+	natinfo->target = EBT_ACCEPT;
+}
+
+#define OPT_DNAT        0x01
+#define OPT_DNAT_TARGET 0x02
+static int brdnat_parse(int c, char **argv, int invert, unsigned int *flags,
+			 const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)(*target)->data;
+	struct ether_addr *addr;
+
+	switch (c) {
+	case NAT_D:
+		EBT_CHECK_OPTION(flags, OPT_DNAT);
+		if (!(addr = ether_aton(optarg)))
+			xtables_error(PARAMETER_PROBLEM, "Problem with specified --to-destination mac");
+		memcpy(natinfo->mac, addr, ETH_ALEN);
+		break;
+	case NAT_D_TARGET:
+		EBT_CHECK_OPTION(flags, OPT_DNAT_TARGET);
+		if (ebt_fill_target(optarg, (unsigned int *)&natinfo->target))
+			xtables_error(PARAMETER_PROBLEM, "Illegal --dnat-target target");
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void brdnat_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void brdnat_print(const void *ip, const struct xt_entry_target *target, int numeric)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)target->data;
+
+	printf("--to-dst ");
+	xtables_print_mac(natinfo->mac);
+	printf(" --dnat-target %s", ebt_target_name(natinfo->target));
+}
+
+static const char* brdnat_verdict(int verdict)
+{
+	switch (verdict) {
+	case EBT_ACCEPT: return "accept";
+	case EBT_DROP: return "drop";
+	case EBT_CONTINUE: return "continue";
+	case EBT_RETURN: return "return";
+	}
+
+	return "";
+}
+
+static int brdnat_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_nat_info *natinfo = (const void*)params->target->data;
+
+	xt_xlate_add(xl, "ether daddr set %s %s ",
+		     ether_ntoa((struct ether_addr *)natinfo->mac),
+		     brdnat_verdict(natinfo->target));
+
+	return 1;
+}
+
+static struct xtables_target brdnat_target =
+{
+	.name		= "dnat",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size           = XT_ALIGN(sizeof(struct ebt_nat_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_nat_info)),
+	.help		= brdnat_print_help,
+	.init		= brdnat_init,
+	.parse		= brdnat_parse,
+	.final_check	= brdnat_final_check,
+	.print		= brdnat_print,
+	.xlate		= brdnat_xlate,
+	.extra_opts	= brdnat_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brdnat_target);
+}
diff --git a/extensions/libebt_dnat.t b/extensions/libebt_dnat.t
new file mode 100644
index 0000000..9428d23
--- /dev/null
+++ b/extensions/libebt_dnat.t
@@ -0,0 +1,5 @@
+:PREROUTING
+*nat
+-i someport -j dnat --to-dst de:ad:0:be:ee:ff;-i someport -j dnat --to-dst de:ad:00:be:ee:ff --dnat-target ACCEPT;OK
+-j dnat --to-dst de:ad:00:be:ee:ff --dnat-target ACCEPT;=;OK
+-j dnat --to-dst de:ad:00:be:ee:ff --dnat-target CONTINUE;=;OK
diff --git a/extensions/libebt_dnat.txlate b/extensions/libebt_dnat.txlate
new file mode 100644
index 0000000..2652dd5
--- /dev/null
+++ b/extensions/libebt_dnat.txlate
@@ -0,0 +1,8 @@
+ebtables-translate -t nat -A PREROUTING -i someport --to-dst de:ad:00:be:ee:ff
+nft add rule bridge nat PREROUTING iifname "someport" ether daddr set de:ad:0:be:ee:ff accept counter
+
+ebtables-translate -t nat -A PREROUTING -i someport --to-dst de:ad:00:be:ee:ff --dnat-target ACCEPT
+nft add rule bridge nat PREROUTING iifname "someport" ether daddr set de:ad:0:be:ee:ff accept counter
+
+ebtables-translate -t nat -A PREROUTING -i someport --to-dst de:ad:00:be:ee:ff --dnat-target CONTINUE
+nft add rule bridge nat PREROUTING iifname "someport" ether daddr set de:ad:0:be:ee:ff continue counter
diff --git a/extensions/libebt_ip.c b/extensions/libebt_ip.c
new file mode 100644
index 0000000..acb9bfc
--- /dev/null
+++ b/extensions/libebt_ip.c
@@ -0,0 +1,732 @@
+/* ebt_ip
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * Changes:
+ *    added ip-sport and ip-dport; parsing of port arguments is
+ *    based on code from iptables-1.2.7a
+ *    Innominate Security Technologies AG <mhopf@innominate.com>
+ *    September, 2002
+ *
+ * Adapted by Arturo Borrero Gonzalez <arturo@debian.org>
+ * to use libxtables for ebtables-compat in 2015.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <inttypes.h>
+#include <xtables.h>
+
+#include "libxt_icmp.h"
+
+#define EBT_IP_SOURCE 0x01
+#define EBT_IP_DEST 0x02
+#define EBT_IP_TOS 0x04
+#define EBT_IP_PROTO 0x08
+#define EBT_IP_SPORT 0x10
+#define EBT_IP_DPORT 0x20
+#define EBT_IP_ICMP 0x40
+#define EBT_IP_IGMP 0x80
+#define EBT_IP_MASK (EBT_IP_SOURCE | EBT_IP_DEST | EBT_IP_TOS | EBT_IP_PROTO |\
+		     EBT_IP_SPORT | EBT_IP_DPORT | EBT_IP_ICMP | EBT_IP_IGMP)
+
+struct ebt_ip_info {
+	__be32 saddr;
+	__be32 daddr;
+	__be32 smsk;
+	__be32 dmsk;
+	__u8  tos;
+	__u8  protocol;
+	__u8  bitmask;
+	__u8  invflags;
+	union {
+		__u16 sport[2];
+		__u8 icmp_type[2];
+		__u8 igmp_type[2];
+	};
+	union {
+		__u16 dport[2];
+		__u8 icmp_code[2];
+	};
+};
+
+#define IP_SOURCE	'1'
+#define IP_DEST		'2'
+#define IP_EBT_TOS	'3' /* include/bits/in.h seems to already define IP_TOS */
+#define IP_PROTO	'4'
+#define IP_SPORT	'5'
+#define IP_DPORT	'6'
+#define IP_EBT_ICMP	'7'
+#define IP_EBT_IGMP	'8'
+
+static const struct option brip_opts[] = {
+	{ .name = "ip-source",		.has_arg = true, .val = IP_SOURCE },
+	{ .name = "ip-src",		.has_arg = true, .val = IP_SOURCE },
+	{ .name = "ip-destination",	.has_arg = true, .val = IP_DEST },
+	{ .name = "ip-dst",		.has_arg = true, .val = IP_DEST },
+	{ .name = "ip-tos",		.has_arg = true, .val = IP_EBT_TOS },
+	{ .name = "ip-protocol",	.has_arg = true, .val = IP_PROTO },
+	{ .name = "ip-proto",		.has_arg = true, .val = IP_PROTO },
+	{ .name = "ip-source-port",	.has_arg = true, .val = IP_SPORT },
+	{ .name = "ip-sport",		.has_arg = true, .val = IP_SPORT },
+	{ .name = "ip-destination-port",.has_arg = true, .val = IP_DPORT },
+	{ .name = "ip-dport",		.has_arg = true, .val = IP_DPORT },
+	{ .name = "ip-icmp-type",       .has_arg = true, .val = IP_EBT_ICMP },
+	{ .name = "ip-igmp-type",       .has_arg = true, .val = IP_EBT_IGMP },
+	XT_GETOPT_TABLEEND,
+};
+
+static const struct xt_icmp_names icmp_codes[] = {
+	{ "echo-reply", 0, 0, 0xFF },
+	/* Alias */ { "pong", 0, 0, 0xFF },
+
+	{ "destination-unreachable", 3, 0, 0xFF },
+	{   "network-unreachable", 3, 0, 0 },
+	{   "host-unreachable", 3, 1, 1 },
+	{   "protocol-unreachable", 3, 2, 2 },
+	{   "port-unreachable", 3, 3, 3 },
+	{   "fragmentation-needed", 3, 4, 4 },
+	{   "source-route-failed", 3, 5, 5 },
+	{   "network-unknown", 3, 6, 6 },
+	{   "host-unknown", 3, 7, 7 },
+	{   "network-prohibited", 3, 9, 9 },
+	{   "host-prohibited", 3, 10, 10 },
+	{   "TOS-network-unreachable", 3, 11, 11 },
+	{   "TOS-host-unreachable", 3, 12, 12 },
+	{   "communication-prohibited", 3, 13, 13 },
+	{   "host-precedence-violation", 3, 14, 14 },
+	{   "precedence-cutoff", 3, 15, 15 },
+
+	{ "source-quench", 4, 0, 0xFF },
+
+	{ "redirect", 5, 0, 0xFF },
+	{   "network-redirect", 5, 0, 0 },
+	{   "host-redirect", 5, 1, 1 },
+	{   "TOS-network-redirect", 5, 2, 2 },
+	{   "TOS-host-redirect", 5, 3, 3 },
+
+	{ "echo-request", 8, 0, 0xFF },
+	/* Alias */ { "ping", 8, 0, 0xFF },
+
+	{ "router-advertisement", 9, 0, 0xFF },
+
+	{ "router-solicitation", 10, 0, 0xFF },
+
+	{ "time-exceeded", 11, 0, 0xFF },
+	/* Alias */ { "ttl-exceeded", 11, 0, 0xFF },
+	{   "ttl-zero-during-transit", 11, 0, 0 },
+	{   "ttl-zero-during-reassembly", 11, 1, 1 },
+
+	{ "parameter-problem", 12, 0, 0xFF },
+	{   "ip-header-bad", 12, 0, 0 },
+	{   "required-option-missing", 12, 1, 1 },
+
+	{ "timestamp-request", 13, 0, 0xFF },
+
+	{ "timestamp-reply", 14, 0, 0xFF },
+
+	{ "address-mask-request", 17, 0, 0xFF },
+
+	{ "address-mask-reply", 18, 0, 0xFF }
+};
+
+static const struct xt_icmp_names igmp_types[] = {
+	{ "membership-query", 0x11 },
+	{ "membership-report-v1", 0x12 },
+	{ "membership-report-v2", 0x16 },
+	{ "leave-group", 0x17 },
+	{ "membership-report-v3", 0x22 },
+};
+
+static void brip_print_help(void)
+{
+	printf(
+"ip options:\n"
+"--ip-src    [!] address[/mask]: ip source specification\n"
+"--ip-dst    [!] address[/mask]: ip destination specification\n"
+"--ip-tos    [!] tos           : ip tos specification\n"
+"--ip-proto  [!] protocol      : ip protocol specification\n"
+"--ip-sport  [!] port[:port]   : tcp/udp source port or port range\n"
+"--ip-dport  [!] port[:port]   : tcp/udp destination port or port range\n"
+"--ip-icmp-type [!] type[[:type]/code[:code]] : icmp type/code or type/code range\n"
+"--ip-igmp-type [!] type[:type]               : igmp type or type range\n");
+
+	printf("\nValid ICMP Types:\n");
+	xt_print_icmp_types(icmp_codes, ARRAY_SIZE(icmp_codes));
+	printf("\nValid IGMP Types:\n");
+	xt_print_icmp_types(igmp_types, ARRAY_SIZE(igmp_types));
+}
+
+static void brip_init(struct xt_entry_match *match)
+{
+	struct ebt_ip_info *info = (struct ebt_ip_info *)match->data;
+
+	info->invflags = 0;
+	info->bitmask = 0;
+}
+
+static void
+parse_port_range(const char *protocol, const char *portstring, uint16_t *ports)
+{
+	char *buffer;
+	char *cp;
+
+	buffer = strdup(portstring);
+	if ((cp = strchr(buffer, ':')) == NULL)
+		ports[0] = ports[1] = xtables_parse_port(buffer, NULL);
+	else {
+		*cp = '\0';
+		cp++;
+
+		ports[0] = buffer[0] ? xtables_parse_port(buffer, NULL) : 0;
+		ports[1] = cp[0] ? xtables_parse_port(cp, NULL) : 0xFFFF;
+
+		if (ports[0] > ports[1])
+			xtables_error(PARAMETER_PROBLEM,
+				      "invalid portrange (min > max)");
+	}
+	free(buffer);
+}
+
+/* original code from ebtables: useful_functions.c */
+static int undot_ip(char *ip, unsigned char *ip2)
+{
+	char *p, *q, *end;
+	long int onebyte;
+	int i;
+	char buf[20];
+
+	strncpy(buf, ip, sizeof(buf) - 1);
+
+	p = buf;
+	for (i = 0; i < 3; i++) {
+		if ((q = strchr(p, '.')) == NULL)
+			return -1;
+		*q = '\0';
+		onebyte = strtol(p, &end, 10);
+		if (*end != '\0' || onebyte > 255 || onebyte < 0)
+			return -1;
+		ip2[i] = (unsigned char)onebyte;
+		p = q + 1;
+	}
+
+	onebyte = strtol(p, &end, 10);
+	if (*end != '\0' || onebyte > 255 || onebyte < 0)
+		return -1;
+	ip2[3] = (unsigned char)onebyte;
+
+	return 0;
+}
+
+static int ip_mask(char *mask, unsigned char *mask2)
+{
+	char *end;
+	long int bits;
+	uint32_t mask22;
+
+	if (undot_ip(mask, mask2)) {
+		/* not the /a.b.c.e format, maybe the /x format */
+		bits = strtol(mask, &end, 10);
+		if (*end != '\0' || bits > 32 || bits < 0)
+			return -1;
+		if (bits != 0) {
+			mask22 = htonl(0xFFFFFFFF << (32 - bits));
+			memcpy(mask2, &mask22, 4);
+		} else {
+			mask22 = 0xFFFFFFFF;
+			memcpy(mask2, &mask22, 4);
+		}
+	}
+	return 0;
+}
+
+static void ebt_parse_ip_address(char *address, uint32_t *addr, uint32_t *msk)
+{
+	char *p;
+
+	/* first the mask */
+	if ((p = strrchr(address, '/')) != NULL) {
+		*p = '\0';
+		if (ip_mask(p + 1, (unsigned char *)msk)) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with the IP mask '%s'", p + 1);
+			return;
+		}
+	} else
+		*msk = 0xFFFFFFFF;
+
+	if (undot_ip(address, (unsigned char *)addr)) {
+		xtables_error(PARAMETER_PROBLEM,
+			      "Problem with the IP address '%s'", address);
+		return;
+	}
+	*addr = *addr & *msk;
+}
+
+static char *parse_range(const char *str, unsigned int res[])
+{
+	char *next;
+
+	if (!xtables_strtoui(str, &next, &res[0], 0, 255))
+		return NULL;
+
+	res[1] = res[0];
+	if (*next == ':') {
+		str = next + 1;
+		if (!xtables_strtoui(str, &next, &res[1], 0, 255))
+			return NULL;
+	}
+
+	return next;
+}
+
+static int ebt_parse_icmp(const struct xt_icmp_names *codes, size_t n_codes,
+			  const char *icmptype, uint8_t type[], uint8_t code[])
+{
+	unsigned int match = n_codes;
+	unsigned int i, number[2];
+
+	for (i = 0; i < n_codes; i++) {
+		if (strncasecmp(codes[i].name, icmptype, strlen(icmptype)))
+			continue;
+		if (match != n_codes)
+			xtables_error(PARAMETER_PROBLEM, "Ambiguous ICMP type `%s':"
+					" `%s' or `%s'?",
+					icmptype, codes[match].name,
+					codes[i].name);
+		match = i;
+	}
+
+	if (match < n_codes) {
+		type[0] = type[1] = codes[match].type;
+		if (code) {
+			code[0] = codes[match].code_min;
+			code[1] = codes[match].code_max;
+		}
+	} else {
+		char *next = parse_range(icmptype, number);
+		if (!next) {
+			xtables_error(PARAMETER_PROBLEM, "Unknown ICMP type `%s'",
+							icmptype);
+			return -1;
+		}
+
+		type[0] = (uint8_t) number[0];
+		type[1] = (uint8_t) number[1];
+		switch (*next) {
+		case 0:
+			if (code) {
+				code[0] = 0;
+				code[1] = 255;
+			}
+			return 0;
+		case '/':
+			if (code) {
+				next = parse_range(next+1, number);
+				code[0] = (uint8_t) number[0];
+				code[1] = (uint8_t) number[1];
+				if (next == NULL)
+					return -1;
+				if (next && *next == 0)
+					return 0;
+			}
+		/* fallthrough */
+		default:
+			xtables_error(PARAMETER_PROBLEM, "unknown character %c", *next);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static void print_icmp_code(uint8_t *code)
+{
+	if (!code)
+		return;
+
+	if (code[0] == code[1])
+		printf("/%"PRIu8 " ", code[0]);
+	else
+		printf("/%"PRIu8":%"PRIu8 " ", code[0], code[1]);
+}
+
+static void ebt_print_icmp_type(const struct xt_icmp_names *codes,
+				size_t n_codes, uint8_t *type, uint8_t *code)
+{
+	unsigned int i;
+
+	if (type[0] != type[1]) {
+		printf("%"PRIu8 ":%" PRIu8, type[0], type[1]);
+		print_icmp_code(code);
+		return;
+	}
+
+	for (i = 0; i < n_codes; i++) {
+		if (codes[i].type != type[0])
+			continue;
+
+		if (!code || (codes[i].code_min == code[0] &&
+			      codes[i].code_max == code[1])) {
+			printf("%s ", codes[i].name);
+			return;
+		}
+	}
+	printf("%"PRIu8, type[0]);
+	print_icmp_code(code);
+}
+
+static int
+brip_parse(int c, char **argv, int invert, unsigned int *flags,
+	   const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_ip_info *info = (struct ebt_ip_info *)(*match)->data;
+
+	switch (c) {
+	case IP_SOURCE:
+		if (invert)
+			info->invflags |= EBT_IP_SOURCE;
+		ebt_parse_ip_address(optarg, &info->saddr, &info->smsk);
+		info->bitmask |= EBT_IP_SOURCE;
+		break;
+	case IP_DEST:
+		if (invert)
+			info->invflags |= EBT_IP_DEST;
+		ebt_parse_ip_address(optarg, &info->daddr, &info->dmsk);
+		info->bitmask |= EBT_IP_DEST;
+		break;
+	case IP_SPORT:
+		if (invert)
+			info->invflags |= EBT_IP_SPORT;
+		parse_port_range(NULL, optarg, info->sport);
+		info->bitmask |= EBT_IP_SPORT;
+		break;
+	case IP_DPORT:
+		if (invert)
+			info->invflags |= EBT_IP_DPORT;
+		parse_port_range(NULL, optarg, info->dport);
+		info->bitmask |= EBT_IP_DPORT;
+		break;
+	case IP_EBT_ICMP:
+		if (invert)
+			info->invflags |= EBT_IP_ICMP;
+		ebt_parse_icmp(icmp_codes, ARRAY_SIZE(icmp_codes), optarg,
+			      info->icmp_type, info->icmp_code);
+		info->bitmask |= EBT_IP_ICMP;
+		break;
+	case IP_EBT_IGMP:
+		if (invert)
+			info->invflags |= EBT_IP_IGMP;
+		ebt_parse_icmp(igmp_types, ARRAY_SIZE(igmp_types), optarg,
+			       info->igmp_type, NULL);
+		info->bitmask |= EBT_IP_IGMP;
+		break;
+	case IP_EBT_TOS: {
+		uintmax_t tosvalue;
+
+		if (invert)
+			info->invflags |= EBT_IP_TOS;
+		if (!xtables_strtoul(optarg, NULL, &tosvalue, 0, 255))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with specified IP tos");
+		info->tos = tosvalue;
+		info->bitmask |= EBT_IP_TOS;
+	}
+		break;
+	case IP_PROTO:
+		if (invert)
+			info->invflags |= EBT_IP_PROTO;
+		info->protocol = xtables_parse_protocol(optarg);
+		info->bitmask |= EBT_IP_PROTO;
+		break;
+	default:
+		return 0;
+	}
+
+	*flags |= info->bitmask;
+	return 1;
+}
+
+static void brip_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void print_port_range(uint16_t *ports)
+{
+	if (ports[0] == ports[1])
+		printf("%d ", ports[0]);
+	else
+		printf("%d:%d ", ports[0], ports[1]);
+}
+
+static void brip_print(const void *ip, const struct xt_entry_match *match,
+		       int numeric)
+{
+	struct ebt_ip_info *info = (struct ebt_ip_info *)match->data;
+	struct in_addr *addrp, *maskp;
+
+	if (info->bitmask & EBT_IP_SOURCE) {
+		printf("--ip-src ");
+		if (info->invflags & EBT_IP_SOURCE)
+			printf("! ");
+		addrp = (struct in_addr *)&info->saddr;
+		maskp = (struct in_addr *)&info->smsk;
+		printf("%s%s ", xtables_ipaddr_to_numeric(addrp),
+		       xtables_ipmask_to_numeric(maskp));
+	}
+	if (info->bitmask & EBT_IP_DEST) {
+		printf("--ip-dst ");
+		if (info->invflags & EBT_IP_DEST)
+			printf("! ");
+		addrp = (struct in_addr *)&info->daddr;
+		maskp = (struct in_addr *)&info->dmsk;
+		printf("%s%s ", xtables_ipaddr_to_numeric(addrp),
+		       xtables_ipmask_to_numeric(maskp));
+	}
+	if (info->bitmask & EBT_IP_TOS) {
+		printf("--ip-tos ");
+		if (info->invflags & EBT_IP_TOS)
+			printf("! ");
+		printf("0x%02X ", info->tos);
+	}
+	if (info->bitmask & EBT_IP_PROTO) {
+		struct protoent *pe;
+
+		printf("--ip-proto ");
+		if (info->invflags & EBT_IP_PROTO)
+			printf("! ");
+		pe = getprotobynumber(info->protocol);
+		if (pe == NULL) {
+			printf("%d ", info->protocol);
+		} else {
+			printf("%s ", pe->p_name);
+		}
+	}
+	if (info->bitmask & EBT_IP_SPORT) {
+		printf("--ip-sport ");
+		if (info->invflags & EBT_IP_SPORT)
+			printf("! ");
+		print_port_range(info->sport);
+	}
+	if (info->bitmask & EBT_IP_DPORT) {
+		printf("--ip-dport ");
+		if (info->invflags & EBT_IP_DPORT)
+			printf("! ");
+		print_port_range(info->dport);
+	}
+	if (info->bitmask & EBT_IP_ICMP) {
+		printf("--ip-icmp-type ");
+		if (info->invflags & EBT_IP_ICMP)
+			printf("! ");
+		ebt_print_icmp_type(icmp_codes, ARRAY_SIZE(icmp_codes),
+				    info->icmp_type, info->icmp_code);
+	}
+	if (info->bitmask & EBT_IP_IGMP) {
+		printf("--ip-igmp-type ");
+		if (info->invflags & EBT_IP_IGMP)
+			printf("! ");
+		ebt_print_icmp_type(igmp_types, ARRAY_SIZE(igmp_types),
+				    info->igmp_type, NULL);
+	}
+}
+
+static const char *brip_xlate_proto_to_name(uint8_t proto)
+{
+	switch (proto) {
+	case IPPROTO_TCP:
+		return "tcp";
+	case IPPROTO_UDP:
+		return "udp";
+	case IPPROTO_UDPLITE:
+		return "udplite";
+	case IPPROTO_SCTP:
+		return "sctp";
+	case IPPROTO_DCCP:
+		return "dccp";
+	default:
+		return NULL;
+	}
+}
+
+static void brip_xlate_icmp(struct xt_xlate *xl,
+			    const struct ebt_ip_info *info, int bit)
+{
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	xt_xlate_add(xl, "icmp type ");
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+	if (info->icmp_type[0] == info->icmp_type[1])
+		xt_xlate_add(xl, "%d ", info->icmp_type[0]);
+	else
+		xt_xlate_add(xl, "%d-%d ", info->icmp_type[0],
+					   info->icmp_type[1]);
+	if (info->icmp_code[0] == 0 &&
+	    info->icmp_code[1] == 0xff)
+		return;
+
+	xt_xlate_add(xl, "icmp code ");
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+	if (info->icmp_code[0] == info->icmp_code[1])
+		xt_xlate_add(xl, "%d ", info->icmp_code[0]);
+	else
+		xt_xlate_add(xl, "%d-%d ", info->icmp_code[0],
+					   info->icmp_code[1]);
+}
+
+static void brip_xlate_igmp(struct xt_xlate *xl,
+			    const struct ebt_ip_info *info, int bit)
+{
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	xt_xlate_add(xl, "@th,0,8 ");
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+	if (info->icmp_type[0] == info->icmp_type[1])
+		xt_xlate_add(xl, "%d ", info->icmp_type[0]);
+	else
+		xt_xlate_add(xl, "%d-%d ", info->icmp_type[0],
+					   info->icmp_type[1]);
+}
+
+static void brip_xlate_th(struct xt_xlate *xl,
+			  const struct ebt_ip_info *info, int bit,
+			  const char *pname)
+{
+	const uint16_t *ports;
+
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	switch (bit) {
+	case EBT_IP_SPORT:
+		if (pname)
+			xt_xlate_add(xl, "%s sport ", pname);
+		else
+			xt_xlate_add(xl, "@th,0,16 ");
+
+		ports = info->sport;
+		break;
+	case EBT_IP_DPORT:
+		if (pname)
+			xt_xlate_add(xl, "%s dport ", pname);
+		else
+			xt_xlate_add(xl, "@th,16,16 ");
+
+		ports = info->dport;
+		break;
+	default:
+		return;
+	}
+
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+
+	if (ports[0] == ports[1])
+		xt_xlate_add(xl, "%d ", ports[0]);
+	else
+		xt_xlate_add(xl, "%d-%d ", ports[0], ports[1]);
+}
+
+static void brip_xlate_nh(struct xt_xlate *xl,
+			  const struct ebt_ip_info *info, int bit)
+{
+	struct in_addr *addrp, *maskp;
+
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	switch (bit) {
+	case EBT_IP_SOURCE:
+		xt_xlate_add(xl, "ip saddr ");
+		addrp = (struct in_addr *)&info->saddr;
+		maskp = (struct in_addr *)&info->smsk;
+		break;
+	case EBT_IP_DEST:
+		xt_xlate_add(xl, "ip daddr ");
+		addrp = (struct in_addr *)&info->daddr;
+		maskp = (struct in_addr *)&info->dmsk;
+		break;
+	default:
+		return;
+	}
+
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+
+	xt_xlate_add(xl, "%s%s ", xtables_ipaddr_to_numeric(addrp),
+				  xtables_ipmask_to_numeric(maskp));
+}
+
+static int brip_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct ebt_ip_info *info = (const void *)params->match->data;
+	const char *pname = NULL;
+
+	brip_xlate_nh(xl, info, EBT_IP_SOURCE);
+	brip_xlate_nh(xl, info, EBT_IP_DEST);
+
+	if (info->bitmask & EBT_IP_TOS) {
+		xt_xlate_add(xl, "ip dscp ");
+		if (info->invflags & EBT_IP_TOS)
+			xt_xlate_add(xl, "!= ");
+		xt_xlate_add(xl, "0x%02x ", info->tos & 0x3f); /* remove ECN bits */
+	}
+	if (info->bitmask & EBT_IP_PROTO) {
+		struct protoent *pe;
+
+		if (info->bitmask & (EBT_IP_SPORT|EBT_IP_DPORT|EBT_IP_ICMP) &&
+		    (info->invflags & EBT_IP_PROTO) == 0) {
+			/* port number or icmp given and not inverted, no need to print this */
+			pname = brip_xlate_proto_to_name(info->protocol);
+		} else {
+			xt_xlate_add(xl, "ip protocol ");
+			if (info->invflags & EBT_IP_PROTO)
+				xt_xlate_add(xl, "!= ");
+			pe = getprotobynumber(info->protocol);
+			if (pe == NULL)
+				xt_xlate_add(xl, "%d ", info->protocol);
+			else
+				xt_xlate_add(xl, "%s ", pe->p_name);
+		}
+	}
+
+	brip_xlate_th(xl, info, EBT_IP_SPORT, pname);
+	brip_xlate_th(xl, info, EBT_IP_DPORT, pname);
+
+	brip_xlate_icmp(xl, info, EBT_IP_ICMP);
+	brip_xlate_igmp(xl, info, EBT_IP_IGMP);
+
+	return 1;
+}
+
+static struct xtables_match brip_match = {
+	.name		= "ip",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_ip_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_ip_info)),
+	.init		= brip_init,
+	.help		= brip_print_help,
+	.parse		= brip_parse,
+	.final_check	= brip_final_check,
+	.print		= brip_print,
+	.xlate		= brip_xlate,
+	.extra_opts	= brip_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brip_match);
+}
diff --git a/extensions/libebt_ip.t b/extensions/libebt_ip.t
new file mode 100644
index 0000000..8be5dfb
--- /dev/null
+++ b/extensions/libebt_ip.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+-p ip --ip-src ! 192.168.0.0/24 -j ACCEPT;-p IPv4 --ip-src ! 192.168.0.0/24 -j ACCEPT;OK
+-p IPv4 --ip-dst 10.0.0.1;=;OK
+-p IPv4 --ip-tos 0xFF;=;OK
+-p IPv4 --ip-tos ! 0xFF;=;OK
+-p IPv4 --ip-proto tcp --ip-dport 22;=;OK
+-p IPv4 --ip-proto udp --ip-sport 1024:65535;=;OK
+-p IPv4 --ip-proto 253;=;OK
+-p IPv4 --ip-proto icmp --ip-icmp-type echo-request;=;OK
+-p IPv4 --ip-proto icmp --ip-icmp-type 1/1;=;OK
+-p ip --ip-protocol icmp --ip-icmp-type ! 1:10;-p IPv4 --ip-proto icmp --ip-icmp-type ! 1:10/0:255 -j CONTINUE;OK
+--ip-proto icmp --ip-icmp-type 1/1;=;FAIL
+! -p ip --ip-proto icmp --ip-icmp-type 1/1;=;FAIL
diff --git a/extensions/libebt_ip.txlate b/extensions/libebt_ip.txlate
new file mode 100644
index 0000000..b5882c3
--- /dev/null
+++ b/extensions/libebt_ip.txlate
@@ -0,0 +1,26 @@
+ebtables-translate -A FORWARD -p ip --ip-src ! 192.168.0.0/24 -j ACCEPT
+nft add rule bridge filter FORWARD ip saddr != 192.168.0.0/24 counter accept
+
+ebtables-translate -I FORWARD -p ip --ip-dst 10.0.0.1
+nft insert rule bridge filter FORWARD ip daddr 10.0.0.1 counter
+
+ebtables-translate -I OUTPUT 3 -p ip -o eth0 --ip-tos 0xff
+nft insert rule bridge filter OUTPUT oifname "eth0" ip dscp 0x3f counter
+
+ebtables-translate -A FORWARD -p ip --ip-proto tcp --ip-dport 22
+nft add rule bridge filter FORWARD tcp dport 22 counter
+
+ebtables-translate -A FORWARD -p ip --ip-proto udp --ip-sport 1024:65535
+nft add rule bridge filter FORWARD udp sport 1024-65535 counter
+
+ebtables-translate -A FORWARD -p ip --ip-proto 253
+nft add rule bridge filter FORWARD ip protocol 253 counter
+
+ebtables-translate -A FORWARD -p ip --ip-protocol icmp --ip-icmp-type "echo-request"
+nft add rule bridge filter FORWARD icmp type 8 counter
+
+ebtables-translate -A FORWARD -p ip --ip-proto icmp --ip-icmp-type 1/1
+nft add rule bridge filter FORWARD icmp type 1 icmp code 1 counter
+
+ebtables-translate -A FORWARD -p ip --ip-protocol icmp --ip-icmp-type ! 1:10
+nft add rule bridge filter FORWARD icmp type != 1-10 counter
diff --git a/extensions/libebt_ip6.c b/extensions/libebt_ip6.c
new file mode 100644
index 0000000..b8a5a5d
--- /dev/null
+++ b/extensions/libebt_ip6.c
@@ -0,0 +1,632 @@
+/* ebt_ip6
+ *
+ * Authors:
+ * Kuo-Lang Tseng <kuo-lang.tseng@intel.com>
+ * Manohar Castelino <manohar.castelino@intel.com>
+ *
+ * Summary:
+ * This is just a modification of the IPv4 code written by
+ * Bart De Schuymer <bdschuym@pandora.be>
+ * with the changes required to support IPv6
+ *
+ */
+
+#include <errno.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_ip6.h>
+
+#include "libxt_icmp.h"
+
+#define IP_SOURCE '1'
+#define IP_DEST   '2'
+#define IP_TCLASS '3'
+#define IP_PROTO  '4'
+#define IP_SPORT  '5'
+#define IP_DPORT  '6'
+#define IP_ICMP6  '7'
+
+static const struct option brip6_opts[] = {
+	{ .name = "ip6-source",		.has_arg = true, .val = IP_SOURCE },
+	{ .name = "ip6-src",		.has_arg = true, .val = IP_SOURCE },
+	{ .name = "ip6-destination",	.has_arg = true, .val = IP_DEST },
+	{ .name = "ip6-dst",		.has_arg = true, .val = IP_DEST },
+	{ .name = "ip6-tclass",		.has_arg = true, .val = IP_TCLASS },
+	{ .name = "ip6-protocol",	.has_arg = true, .val = IP_PROTO },
+	{ .name = "ip6-proto",		.has_arg = true, .val = IP_PROTO },
+	{ .name = "ip6-source-port",	.has_arg = true, .val = IP_SPORT },
+	{ .name = "ip6-sport",		.has_arg = true, .val = IP_SPORT },
+	{ .name = "ip6-destination-port",.has_arg = true,.val = IP_DPORT },
+	{ .name = "ip6-dport",		.has_arg = true, .val = IP_DPORT },
+	{ .name = "ip6-icmp-type",	.has_arg = true, .val = IP_ICMP6 },
+	XT_GETOPT_TABLEEND,
+};
+
+static const struct xt_icmp_names icmpv6_codes[] = {
+	{ "destination-unreachable", 1, 0, 0xFF },
+	{ "no-route", 1, 0, 0 },
+	{ "communication-prohibited", 1, 1, 1 },
+	{ "address-unreachable", 1, 3, 3 },
+	{ "port-unreachable", 1, 4, 4 },
+
+	{ "packet-too-big", 2, 0, 0xFF },
+
+	{ "time-exceeded", 3, 0, 0xFF },
+	/* Alias */ { "ttl-exceeded", 3, 0, 0xFF },
+	{ "ttl-zero-during-transit", 3, 0, 0 },
+	{ "ttl-zero-during-reassembly", 3, 1, 1 },
+
+	{ "parameter-problem", 4, 0, 0xFF },
+	{ "bad-header", 4, 0, 0 },
+	{ "unknown-header-type", 4, 1, 1 },
+	{ "unknown-option", 4, 2, 2 },
+
+	{ "echo-request", 128, 0, 0xFF },
+	/* Alias */ { "ping", 128, 0, 0xFF },
+
+	{ "echo-reply", 129, 0, 0xFF },
+	/* Alias */ { "pong", 129, 0, 0xFF },
+
+	{ "router-solicitation", 133, 0, 0xFF },
+
+	{ "router-advertisement", 134, 0, 0xFF },
+
+	{ "neighbour-solicitation", 135, 0, 0xFF },
+	/* Alias */ { "neighbor-solicitation", 135, 0, 0xFF },
+
+	{ "neighbour-advertisement", 136, 0, 0xFF },
+	/* Alias */ { "neighbor-advertisement", 136, 0, 0xFF },
+
+	{ "redirect", 137, 0, 0xFF },
+};
+
+static void
+parse_port_range(const char *protocol, const char *portstring, uint16_t *ports)
+{
+	char *buffer;
+	char *cp;
+
+	buffer = strdup(portstring);
+	if ((cp = strchr(buffer, ':')) == NULL)
+		ports[0] = ports[1] = xtables_parse_port(buffer, NULL);
+	else {
+		*cp = '\0';
+		cp++;
+
+		ports[0] = buffer[0] ? xtables_parse_port(buffer, NULL) : 0;
+		ports[1] = cp[0] ? xtables_parse_port(cp, NULL) : 0xFFFF;
+
+		if (ports[0] > ports[1])
+			xtables_error(PARAMETER_PROBLEM,
+				      "invalid portrange (min > max)");
+	}
+	free(buffer);
+}
+
+static char *parse_range(const char *str, unsigned int res[])
+{
+	char *next;
+
+	if (!xtables_strtoui(str, &next, &res[0], 0, 255))
+		return NULL;
+
+	res[1] = res[0];
+	if (*next == ':') {
+		str = next + 1;
+		if (!xtables_strtoui(str, &next, &res[1], 0, 255))
+			return NULL;
+	}
+
+	return next;
+}
+
+static int
+parse_icmpv6(const char *icmpv6type, uint8_t type[], uint8_t code[])
+{
+	static const unsigned int limit = ARRAY_SIZE(icmpv6_codes);
+	unsigned int match = limit;
+	unsigned int i, number[2];
+
+	for (i = 0; i < limit; i++) {
+		if (strncasecmp(icmpv6_codes[i].name, icmpv6type, strlen(icmpv6type)))
+			continue;
+		if (match != limit)
+			xtables_error(PARAMETER_PROBLEM, "Ambiguous ICMPv6 type `%s':"
+					" `%s' or `%s'?",
+					icmpv6type, icmpv6_codes[match].name,
+					icmpv6_codes[i].name);
+		match = i;
+	}
+
+	if (match < limit) {
+		type[0] = type[1] = icmpv6_codes[match].type;
+		code[0] = icmpv6_codes[match].code_min;
+		code[1] = icmpv6_codes[match].code_max;
+	} else {
+		char *next = parse_range(icmpv6type, number);
+		if (!next) {
+			xtables_error(PARAMETER_PROBLEM, "Unknown ICMPv6 type `%s'",
+							icmpv6type);
+			return -1;
+		}
+		type[0] = (uint8_t) number[0];
+		type[1] = (uint8_t) number[1];
+		switch (*next) {
+		case 0:
+			code[0] = 0;
+			code[1] = 255;
+			return 0;
+		case '/':
+			next = parse_range(next+1, number);
+			code[0] = (uint8_t) number[0];
+			code[1] = (uint8_t) number[1];
+			if (next == NULL)
+				return -1;
+			if (next && *next == 0)
+				return 0;
+		/* fallthrough */
+		default:
+			xtables_error(PARAMETER_PROBLEM, "unknown character %c", *next);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static void print_port_range(uint16_t *ports)
+{
+	if (ports[0] == ports[1])
+		printf("%d ", ports[0]);
+	else
+		printf("%d:%d ", ports[0], ports[1]);
+}
+
+static void print_icmp_code(uint8_t *code)
+{
+	if (code[0] == code[1])
+		printf("/%"PRIu8 " ", code[0]);
+	else
+		printf("/%"PRIu8":%"PRIu8 " ", code[0], code[1]);
+}
+
+static void print_icmp_type(uint8_t *type, uint8_t *code)
+{
+	unsigned int i;
+
+	if (type[0] != type[1]) {
+		printf("%"PRIu8 ":%" PRIu8, type[0], type[1]);
+		print_icmp_code(code);
+		return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(icmpv6_codes); i++) {
+		if (icmpv6_codes[i].type != type[0])
+			continue;
+
+		if (icmpv6_codes[i].code_min == code[0] &&
+		    icmpv6_codes[i].code_max == code[1]) {
+			printf("%s ", icmpv6_codes[i].name);
+			return;
+		}
+	}
+	printf("%"PRIu8, type[0]);
+	print_icmp_code(code);
+}
+
+static void brip6_print_help(void)
+{
+	printf(
+"ip6 options:\n"
+"--ip6-src    [!] address[/mask]: ipv6 source specification\n"
+"--ip6-dst    [!] address[/mask]: ipv6 destination specification\n"
+"--ip6-tclass [!] tclass        : ipv6 traffic class specification\n"
+"--ip6-proto  [!] protocol      : ipv6 protocol specification\n"
+"--ip6-sport  [!] port[:port]   : tcp/udp source port or port range\n"
+"--ip6-dport  [!] port[:port]   : tcp/udp destination port or port range\n"
+"--ip6-icmp-type [!] type[[:type]/code[:code]] : ipv6-icmp type/code or type/code range\n");
+	printf("Valid ICMPv6 Types:");
+	xt_print_icmp_types(icmpv6_codes, ARRAY_SIZE(icmpv6_codes));
+}
+
+static void brip6_init(struct xt_entry_match *match)
+{
+	struct ebt_ip6_info *ipinfo = (struct ebt_ip6_info *)match->data;
+
+	ipinfo->invflags = 0;
+	ipinfo->bitmask = 0;
+	memset(ipinfo->saddr.s6_addr, 0, sizeof(ipinfo->saddr.s6_addr));
+	memset(ipinfo->smsk.s6_addr, 0, sizeof(ipinfo->smsk.s6_addr));
+	memset(ipinfo->daddr.s6_addr, 0, sizeof(ipinfo->daddr.s6_addr));
+	memset(ipinfo->dmsk.s6_addr, 0, sizeof(ipinfo->dmsk.s6_addr));
+}
+
+static struct in6_addr *numeric_to_addr(const char *num)
+{
+	static struct in6_addr ap;
+	int err;
+
+	if ((err=inet_pton(AF_INET6, num, &ap)) == 1)
+		return &ap;
+	return (struct in6_addr *)NULL;
+}
+
+static struct in6_addr *parse_ip6_mask(char *mask)
+{
+	static struct in6_addr maskaddr;
+	struct in6_addr *addrp;
+	unsigned int bits;
+
+	if (mask == NULL) {
+		/* no mask at all defaults to 128 bits */
+		memset(&maskaddr, 0xff, sizeof maskaddr);
+		return &maskaddr;
+	}
+	if ((addrp = numeric_to_addr(mask)) != NULL)
+		return addrp;
+	if (!xtables_strtoui(mask, NULL, &bits, 0, 128))
+		xtables_error(PARAMETER_PROBLEM, "Invalid IPv6 Mask '%s' specified", mask);
+	if (bits != 0) {
+		char *p = (char *)&maskaddr;
+		memset(p, 0xff, bits / 8);
+		memset(p + (bits / 8) + 1, 0, (128 - bits) / 8);
+		p[bits / 8] = 0xff << (8 - (bits & 7));
+		return &maskaddr;
+	}
+
+	memset(&maskaddr, 0, sizeof maskaddr);
+	return &maskaddr;
+}
+
+/* Set the ipv6 mask and address. Callers should check ebt_errormsg[0].
+ * The string pointed to by address can be altered. */
+static void ebt_parse_ip6_address(char *address, struct in6_addr *addr, struct in6_addr *msk)
+{
+	struct in6_addr *tmp_addr;
+	char buf[256];
+	char *p;
+	int i;
+	int err;
+
+	strncpy(buf, address, sizeof(buf) - 1);
+	/* first the mask */
+	buf[sizeof(buf) - 1] = '\0';
+	if ((p = strrchr(buf, '/')) != NULL) {
+		*p = '\0';
+		tmp_addr = parse_ip6_mask(p + 1);
+	} else
+		tmp_addr = parse_ip6_mask(NULL);
+
+	*msk = *tmp_addr;
+
+	/* if a null mask is given, the name is ignored, like in "any/0" */
+	if (!memcmp(msk, &in6addr_any, sizeof(in6addr_any)))
+		strcpy(buf, "::");
+
+	if ((err=inet_pton(AF_INET6, buf, addr)) < 1) {
+		xtables_error(PARAMETER_PROBLEM, "Invalid IPv6 Address '%s' specified", buf);
+		return;
+	}
+
+	for (i = 0; i < 4; i++)
+		addr->s6_addr32[i] &= msk->s6_addr32[i];
+}
+
+#define OPT_SOURCE 0x01
+#define OPT_DEST   0x02
+#define OPT_TCLASS 0x04
+#define OPT_PROTO  0x08
+#define OPT_SPORT  0x10
+#define OPT_DPORT  0x20
+static int
+brip6_parse(int c, char **argv, int invert, unsigned int *flags,
+	   const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_ip6_info *info = (struct ebt_ip6_info *)(*match)->data;
+	unsigned int i;
+	char *end;
+
+	switch (c) {
+	case IP_SOURCE:
+		if (invert)
+			info->invflags |= EBT_IP6_SOURCE;
+		ebt_parse_ip6_address(optarg, &info->saddr, &info->smsk);
+		info->bitmask |= EBT_IP6_SOURCE;
+		break;
+	case IP_DEST:
+		if (invert)
+			info->invflags |= EBT_IP6_DEST;
+		ebt_parse_ip6_address(optarg, &info->daddr, &info->dmsk);
+		info->bitmask |= EBT_IP6_DEST;
+		break;
+	case IP_SPORT:
+		if (invert)
+			info->invflags |= EBT_IP6_SPORT;
+		parse_port_range(NULL, optarg, info->sport);
+		info->bitmask |= EBT_IP6_SPORT;
+		break;
+	case IP_DPORT:
+		if (invert)
+			info->invflags |= EBT_IP6_DPORT;
+		parse_port_range(NULL, optarg, info->dport);
+		info->bitmask |= EBT_IP6_DPORT;
+		break;
+	case IP_ICMP6:
+		if (invert)
+			info->invflags |= EBT_IP6_ICMP6;
+		if (parse_icmpv6(optarg, info->icmpv6_type, info->icmpv6_code))
+			return 0;
+		info->bitmask |= EBT_IP6_ICMP6;
+		break;
+	case IP_TCLASS:
+		if (invert)
+			info->invflags |= EBT_IP6_TCLASS;
+		if (!xtables_strtoui(optarg, &end, &i, 0, 255))
+			xtables_error(PARAMETER_PROBLEM, "Problem with specified IPv6 traffic class '%s'", optarg);
+		info->tclass = i;
+		info->bitmask |= EBT_IP6_TCLASS;
+		break;
+	case IP_PROTO:
+		if (invert)
+			info->invflags |= EBT_IP6_PROTO;
+		info->protocol = xtables_parse_protocol(optarg);
+		info->bitmask |= EBT_IP6_PROTO;
+		break;
+	default:
+		return 0;
+	}
+
+	*flags |= info->bitmask;
+	return 1;
+}
+
+static void brip6_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void brip6_print(const void *ip, const struct xt_entry_match *match,
+		       int numeric)
+{
+	struct ebt_ip6_info *ipinfo = (struct ebt_ip6_info *)match->data;
+
+	if (ipinfo->bitmask & EBT_IP6_SOURCE) {
+		printf("--ip6-src ");
+		if (ipinfo->invflags & EBT_IP6_SOURCE)
+			printf("! ");
+		printf("%s", xtables_ip6addr_to_numeric(&ipinfo->saddr));
+		printf("%s ", xtables_ip6mask_to_numeric(&ipinfo->smsk));
+	}
+	if (ipinfo->bitmask & EBT_IP6_DEST) {
+		printf("--ip6-dst ");
+		if (ipinfo->invflags & EBT_IP6_DEST)
+			printf("! ");
+		printf("%s", xtables_ip6addr_to_numeric(&ipinfo->daddr));
+		printf("%s ", xtables_ip6mask_to_numeric(&ipinfo->dmsk));
+	}
+	if (ipinfo->bitmask & EBT_IP6_TCLASS) {
+		printf("--ip6-tclass ");
+		if (ipinfo->invflags & EBT_IP6_TCLASS)
+			printf("! ");
+		printf("0x%02X ", ipinfo->tclass);
+	}
+	if (ipinfo->bitmask & EBT_IP6_PROTO) {
+		struct protoent *pe;
+
+		printf("--ip6-proto ");
+		if (ipinfo->invflags & EBT_IP6_PROTO)
+			printf("! ");
+		pe = getprotobynumber(ipinfo->protocol);
+		if (pe == NULL) {
+			printf("%d ", ipinfo->protocol);
+		} else {
+			printf("%s ", pe->p_name);
+		}
+	}
+	if (ipinfo->bitmask & EBT_IP6_SPORT) {
+		printf("--ip6-sport ");
+		if (ipinfo->invflags & EBT_IP6_SPORT)
+			printf("! ");
+		print_port_range(ipinfo->sport);
+	}
+	if (ipinfo->bitmask & EBT_IP6_DPORT) {
+		printf("--ip6-dport ");
+		if (ipinfo->invflags & EBT_IP6_DPORT)
+			printf("! ");
+		print_port_range(ipinfo->dport);
+	}
+	if (ipinfo->bitmask & EBT_IP6_ICMP6) {
+		printf("--ip6-icmp-type ");
+		if (ipinfo->invflags & EBT_IP6_ICMP6)
+			printf("! ");
+		print_icmp_type(ipinfo->icmpv6_type, ipinfo->icmpv6_code);
+	}
+}
+
+static void brip_xlate_th(struct xt_xlate *xl,
+			  const struct ebt_ip6_info *info, int bit,
+			  const char *pname)
+{
+	const uint16_t *ports;
+
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	switch (bit) {
+	case EBT_IP6_SPORT:
+		if (pname)
+			xt_xlate_add(xl, "%s sport ", pname);
+		else
+			xt_xlate_add(xl, "@th,0,16 ");
+
+		ports = info->sport;
+		break;
+	case EBT_IP6_DPORT:
+		if (pname)
+			xt_xlate_add(xl, "%s dport ", pname);
+		else
+			xt_xlate_add(xl, "@th,16,16 ");
+
+		ports = info->dport;
+		break;
+	default:
+		return;
+	}
+
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+
+	if (ports[0] == ports[1])
+		xt_xlate_add(xl, "%d ", ports[0]);
+	else
+		xt_xlate_add(xl, "%d-%d ", ports[0], ports[1]);
+}
+
+static void brip_xlate_nh(struct xt_xlate *xl,
+			  const struct ebt_ip6_info *info, int bit)
+{
+	struct in6_addr *addrp, *maskp;
+
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	switch (bit) {
+	case EBT_IP6_SOURCE:
+		xt_xlate_add(xl, "ip6 saddr ");
+		addrp = (struct in6_addr *)&info->saddr;
+		maskp = (struct in6_addr *)&info->smsk;
+		break;
+	case EBT_IP6_DEST:
+		xt_xlate_add(xl, "ip6 daddr ");
+		addrp = (struct in6_addr *)&info->daddr;
+		maskp = (struct in6_addr *)&info->dmsk;
+		break;
+	default:
+		return;
+	}
+
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+
+	xt_xlate_add(xl, "%s%s ", xtables_ip6addr_to_numeric(addrp),
+				  xtables_ip6mask_to_numeric(maskp));
+}
+
+static const char *brip6_xlate_proto_to_name(uint8_t proto)
+{
+	switch (proto) {
+	case IPPROTO_TCP:
+		return "tcp";
+	case IPPROTO_UDP:
+		return "udp";
+	case IPPROTO_UDPLITE:
+		return "udplite";
+	case IPPROTO_SCTP:
+		return "sctp";
+	case IPPROTO_DCCP:
+		return "dccp";
+	default:
+		return NULL;
+	}
+}
+
+static int brip6_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct ebt_ip6_info *info = (const void *)params->match->data;
+	const char *pname = NULL;
+
+	if ((info->bitmask & (EBT_IP6_SOURCE|EBT_IP6_DEST|EBT_IP6_ICMP6|EBT_IP6_TCLASS)) == 0)
+		xt_xlate_add(xl, "ether type ip6 ");
+
+	brip_xlate_nh(xl, info, EBT_IP6_SOURCE);
+	brip_xlate_nh(xl, info, EBT_IP6_DEST);
+
+	if (info->bitmask & EBT_IP6_TCLASS) {
+		xt_xlate_add(xl, "ip6 dscp ");
+		if (info->invflags & EBT_IP6_TCLASS)
+			xt_xlate_add(xl, "!= ");
+		xt_xlate_add(xl, "0x%02x ", info->tclass & 0x3f); /* remove ECN bits */
+	}
+
+	if (info->bitmask & EBT_IP6_PROTO) {
+		struct protoent *pe;
+
+		if (info->bitmask & (EBT_IP6_SPORT|EBT_IP6_DPORT|EBT_IP6_ICMP6) &&
+		    (info->invflags & EBT_IP6_PROTO) == 0) {
+			/* port number given and not inverted, no need to
+			 * add explicit 'meta l4proto'.
+			 */
+			pname = brip6_xlate_proto_to_name(info->protocol);
+		} else {
+			xt_xlate_add(xl, "meta l4proto ");
+			if (info->invflags & EBT_IP6_PROTO)
+				xt_xlate_add(xl, "!= ");
+			pe = getprotobynumber(info->protocol);
+			if (pe == NULL)
+				xt_xlate_add(xl, "%d ", info->protocol);
+			else
+				xt_xlate_add(xl, "%s ", pe->p_name);
+		}
+	}
+
+	brip_xlate_th(xl, info, EBT_IP6_SPORT, pname);
+	brip_xlate_th(xl, info, EBT_IP6_DPORT, pname);
+
+	if (info->bitmask & EBT_IP6_ICMP6) {
+		xt_xlate_add(xl, "icmpv6 type ");
+		if (info->invflags & EBT_IP6_ICMP6)
+			xt_xlate_add(xl, "!= ");
+
+		if (info->icmpv6_type[0] == info->icmpv6_type[1])
+			xt_xlate_add(xl, "%d ", info->icmpv6_type[0]);
+		else
+			xt_xlate_add(xl, "%d-%d ", info->icmpv6_type[0],
+						   info->icmpv6_type[1]);
+
+		if (info->icmpv6_code[0] == 0 &&
+		    info->icmpv6_code[1] == 0xff)
+			return 1;
+
+		xt_xlate_add(xl, "icmpv6 code ");
+		if (info->invflags & EBT_IP6_ICMP6)
+			xt_xlate_add(xl, "!= ");
+
+		if (info->icmpv6_code[0] == info->icmpv6_code[1])
+			xt_xlate_add(xl, "%d ", info->icmpv6_code[0]);
+		else
+			xt_xlate_add(xl, "%d-%d ", info->icmpv6_code[0],
+						   info->icmpv6_code[1]);
+	}
+
+	return 1;
+}
+
+static struct xtables_match brip6_match = {
+	.name		= "ip6",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_ip6_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_ip6_info)),
+	.init		= brip6_init,
+	.help		= brip6_print_help,
+	.parse		= brip6_parse,
+	.final_check	= brip6_final_check,
+	.print		= brip6_print,
+	.xlate		= brip6_xlate,
+	.extra_opts	= brip6_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brip6_match);
+}
diff --git a/extensions/libebt_ip6.t b/extensions/libebt_ip6.t
new file mode 100644
index 0000000..fa1038a
--- /dev/null
+++ b/extensions/libebt_ip6.t
@@ -0,0 +1,15 @@
+:INPUT,FORWARD,OUTPUT
+-p ip6 --ip6-src ! dead::beef/64 -j ACCEPT;-p IPv6 --ip6-src ! dead::/64 -j ACCEPT;OK
+-p IPv6 --ip6-dst dead:beef::/64 -j ACCEPT;=;OK
+-p IPv6 --ip6-dst f00:ba::;=;OK
+-p IPv6 --ip6-tclass 0xFF;=;OK
+-p IPv6 --ip6-proto tcp --ip6-dport 22;=;OK
+-p IPv6 --ip6-proto tcp --ip6-dport ! 22;=;OK
+-p IPv6 --ip6-proto udp --ip6-sport 1024:65535;=;OK
+-p IPv6 --ip6-proto 253;=;OK
+-p IPv6 --ip6-proto ipv6-icmp --ip6-icmp-type echo-request -j CONTINUE;=;OK
+-p IPv6 --ip6-proto ipv6-icmp --ip6-icmp-type echo-request;=;OK
+-p ip6 --ip6-protocol icmpv6 --ip6-icmp-type 1/1;-p IPv6 --ip6-proto ipv6-icmp --ip6-icmp-type communication-prohibited -j CONTINUE;OK
+-p IPv6 --ip6-proto ipv6-icmp --ip6-icmp-type ! 1:10/0:255;=;OK
+--ip6-proto ipv6-icmp ! --ip6-icmp-type 1:10/0:255;=;FAIL
+! -p IPv6 --ip6-proto ipv6-icmp ! --ip6-icmp-type 1:10/0:255;=;FAIL
diff --git a/extensions/libebt_ip6.txlate b/extensions/libebt_ip6.txlate
new file mode 100644
index 0000000..0271734
--- /dev/null
+++ b/extensions/libebt_ip6.txlate
@@ -0,0 +1,29 @@
+ebtables-translate -A FORWARD -p ip6 --ip6-src ! dead::beef/64 -j ACCEPT
+nft add rule bridge filter FORWARD ip6 saddr != dead::/64 counter accept
+
+ebtables-translate -A FORWARD -p ip6 ! --ip6-dst dead:beef::/64 -j ACCEPT
+nft add rule bridge filter FORWARD ip6 daddr != dead:beef::/64 counter accept
+
+ebtables-translate -I FORWARD -p ip6 --ip6-dst f00:ba::
+nft insert rule bridge filter FORWARD ip6 daddr f00:ba:: counter
+
+ebtables-translate -I OUTPUT -o eth0 -p ip6 --ip6-tclass 0xff
+nft insert rule bridge filter OUTPUT oifname "eth0" ip6 dscp 0x3f counter
+
+ebtables-translate -A FORWARD -p ip6 --ip6-proto tcp --ip6-dport 22
+nft add rule bridge filter FORWARD ether type ip6 tcp dport 22 counter
+
+ebtables-translate -A FORWARD -p ip6 --ip6-proto udp --ip6-sport 1024:65535
+nft add rule bridge filter FORWARD ether type ip6 udp sport 1024-65535 counter
+
+ebtables-translate -A FORWARD -p ip6 --ip6-proto 253
+nft add rule bridge filter FORWARD ether type ip6 meta l4proto 253 counter
+
+ebtables-translate -A FORWARD -p ip6  --ip6-protocol icmpv6 --ip6-icmp-type "echo-request"
+nft add rule bridge filter FORWARD icmpv6 type 128 counter
+
+ebtables-translate -A FORWARD -p ip6 --ip6-protocol icmpv6  --ip6-icmp-type 1/1
+nft add rule bridge filter FORWARD icmpv6 type 1 icmpv6 code 1 counter
+
+ebtables-translate -A FORWARD -p ip6 --ip6-protocol icmpv6 --ip6-icmp-type ! 1:10
+nft add rule bridge filter FORWARD icmpv6 type != 1-10 counter
diff --git a/extensions/libebt_limit.txlate b/extensions/libebt_limit.txlate
new file mode 100644
index 0000000..b6af15d
--- /dev/null
+++ b/extensions/libebt_limit.txlate
@@ -0,0 +1,8 @@
+ebtables-translate -A INPUT --limit 3/m --limit-burst 3
+nft add rule bridge filter INPUT limit rate 3/minute burst 3 packets counter
+
+ebtables-translate -A INPUT --limit 10/s --limit-burst 5
+nft add rule bridge filter INPUT limit rate 10/second burst 5 packets counter
+
+ebtables-translate -A INPUT --limit 10/s --limit-burst 0
+nft add rule bridge filter INPUT limit rate 10/second counter
diff --git a/extensions/libebt_log.c b/extensions/libebt_log.c
new file mode 100644
index 0000000..8858cf0
--- /dev/null
+++ b/extensions/libebt_log.c
@@ -0,0 +1,217 @@
+/*
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Giuseppe Longo <giuseppelng@gmail.com> adapted the original code to the
+ * xtables-compat environment in 2015.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_log.h>
+
+#define LOG_DEFAULT_LEVEL LOG_INFO
+
+#define LOG_PREFIX '1'
+#define LOG_LEVEL  '2'
+#define LOG_ARP    '3'
+#define LOG_IP     '4'
+#define LOG_LOG    '5'
+#define LOG_IP6    '6'
+
+struct code {
+	char *c_name;
+	int c_val;
+};
+
+static struct code eight_priority[] = {
+	{ "emerg", LOG_EMERG },
+	{ "alert", LOG_ALERT },
+	{ "crit", LOG_CRIT },
+	{ "error", LOG_ERR },
+	{ "warning", LOG_WARNING },
+	{ "notice", LOG_NOTICE },
+	{ "info", LOG_INFO },
+	{ "debug", LOG_DEBUG }
+};
+
+static int name_to_loglevel(const char *arg)
+{
+	int i;
+
+	for (i = 0; i < 8; i++)
+		if (!strcmp(arg, eight_priority[i].c_name))
+			return eight_priority[i].c_val;
+
+	/* return bad loglevel */
+	return 9;
+}
+
+static const struct option brlog_opts[] = {
+	{ .name = "log-prefix",		.has_arg = true,  .val = LOG_PREFIX },
+	{ .name = "log-level",		.has_arg = true,  .val = LOG_LEVEL  },
+	{ .name = "log-arp",		.has_arg = false, .val = LOG_ARP    },
+	{ .name = "log-ip",		.has_arg = false, .val = LOG_IP     },
+	{ .name = "log",		.has_arg = false, .val = LOG_LOG    },
+	{ .name = "log-ip6",		.has_arg = false, .val = LOG_IP6    },
+	XT_GETOPT_TABLEEND,
+};
+
+static void brlog_help(void)
+{
+	int i;
+
+	printf(
+"log options:\n"
+"--log               : use this if you're not specifying anything\n"
+"--log-level level   : level = [1-8] or a string\n"
+"--log-prefix prefix : max. %d chars.\n"
+"--log-ip            : put ip info. in the log for ip packets\n"
+"--log-arp           : put (r)arp info. in the log for (r)arp packets\n"
+"--log-ip6           : put ip6 info. in the log for ip6 packets\n"
+	, EBT_LOG_PREFIX_SIZE - 1);
+	for (i = 0; i < 8; i++)
+		printf("%d = %s\n", eight_priority[i].c_val,
+				    eight_priority[i].c_name);
+}
+
+static void brlog_init(struct xt_entry_target *t)
+{
+	struct ebt_log_info *loginfo = (struct ebt_log_info *)t->data;
+
+	loginfo->bitmask = 0;
+	loginfo->prefix[0] = '\0';
+	loginfo->loglevel = LOG_NOTICE;
+}
+
+static unsigned int log_chk_inv(int inv, unsigned int bit, const char *suffix)
+{
+	if (inv)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Unexpected `!' after --log%s", suffix);
+	return bit;
+}
+
+static int brlog_parse(int c, char **argv, int invert, unsigned int *flags,
+		       const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_log_info *loginfo = (struct ebt_log_info *)(*target)->data;
+	long int i;
+	char *end;
+
+	switch (c) {
+	case LOG_PREFIX:
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Unexpected `!` after --log-prefix");
+		if (strlen(optarg) > sizeof(loginfo->prefix) - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Prefix too long");
+		if (strchr(optarg, '\"'))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Use of \\\" is not allowed"
+				      " in the prefix");
+		strcpy((char *)loginfo->prefix, (char *)optarg);
+		break;
+	case LOG_LEVEL:
+		i = strtol(optarg, &end, 16);
+		if (*end != '\0' || i < 0 || i > 7)
+			loginfo->loglevel = name_to_loglevel(optarg);
+		else
+			loginfo->loglevel = i;
+
+		if (loginfo->loglevel == 9)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with the log-level");
+		break;
+	case LOG_IP:
+		loginfo->bitmask |= log_chk_inv(invert, EBT_LOG_IP, "-ip");
+		break;
+	case LOG_ARP:
+		loginfo->bitmask |= log_chk_inv(invert, EBT_LOG_ARP, "-arp");
+		break;
+	case LOG_LOG:
+		loginfo->bitmask |= log_chk_inv(invert, 0, "");
+		break;
+	case LOG_IP6:
+		loginfo->bitmask |= log_chk_inv(invert, EBT_LOG_IP6, "-ip6");
+		break;
+	default:
+		return 0;
+	}
+
+	*flags |= loginfo->bitmask;
+	return 1;
+}
+
+static void brlog_final_check(unsigned int flags)
+{
+}
+
+static void brlog_print(const void *ip, const struct xt_entry_target *target,
+			int numeric)
+{
+	struct ebt_log_info *loginfo = (struct ebt_log_info *)target->data;
+
+	printf("--log-level %s --log-prefix \"%s\"",
+		eight_priority[loginfo->loglevel].c_name,
+		loginfo->prefix);
+
+	if (loginfo->bitmask & EBT_LOG_IP)
+		printf(" --log-ip");
+	if (loginfo->bitmask & EBT_LOG_ARP)
+		printf(" --log-arp");
+	if (loginfo->bitmask & EBT_LOG_IP6)
+		printf(" --log-ip6");
+	printf(" ");
+}
+
+static int brlog_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_log_info *loginfo = (const void *)params->target->data;
+
+	xt_xlate_add(xl, "log");
+	if (loginfo->prefix[0]) {
+		if (params->escape_quotes)
+			xt_xlate_add(xl, " prefix \\\"%s\\\"", loginfo->prefix);
+		else
+			xt_xlate_add(xl, " prefix \"%s\"", loginfo->prefix);
+	}
+
+	if (loginfo->loglevel != LOG_DEFAULT_LEVEL)
+		xt_xlate_add(xl, " level %s", eight_priority[loginfo->loglevel].c_name);
+
+	xt_xlate_add(xl, " flags ether ");
+
+	return 1;
+}
+
+static struct xtables_target brlog_target = {
+	.name		= "log",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_log_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_log_info)),
+	.init		= brlog_init,
+	.help		= brlog_help,
+	.parse		= brlog_parse,
+	.final_check	= brlog_final_check,
+	.print		= brlog_print,
+	.xlate		= brlog_xlate,
+	.extra_opts	= brlog_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brlog_target);
+}
diff --git a/extensions/libebt_log.t b/extensions/libebt_log.t
new file mode 100644
index 0000000..a0df616
--- /dev/null
+++ b/extensions/libebt_log.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+--log;=;OK
+--log-level crit;=;OK
+--log-level 1;--log-level alert --log-prefix "";OK
+--log-level emerg --log-ip --log-arp --log-ip6;--log-level emerg --log-prefix "" --log-ip --log-arp --log-ip6 -j CONTINUE;OK
+--log-level crit --log-ip --log-arp --log-ip6 --log-prefix foo;--log-level crit --log-prefix "foo" --log-ip --log-arp --log-ip6 -j CONTINUE;OK
diff --git a/extensions/libebt_log.txlate b/extensions/libebt_log.txlate
new file mode 100644
index 0000000..7ef8d5e
--- /dev/null
+++ b/extensions/libebt_log.txlate
@@ -0,0 +1,15 @@
+ebtables-translate -A INPUT --log
+nft add rule bridge filter INPUT log level notice flags ether counter
+
+ebtables-translate -A INPUT --log-level 1
+nft add rule bridge filter INPUT log level alert flags ether counter
+
+ebtables-translate -A INPUT --log-level crit
+nft add rule bridge filter INPUT log level crit flags ether counter
+
+ebtables-translate -A INPUT --log-level emerg --log-ip --log-arp --log-ip6
+nft add rule bridge filter INPUT log level emerg flags ether counter
+
+ebtables-translate -A INPUT --log-level crit --log-ip --log-arp --log-ip6 --log-prefix foo
+nft add rule bridge filter INPUT log prefix "foo" level crit flags ether counter
+
diff --git a/extensions/libebt_mark.c b/extensions/libebt_mark.c
new file mode 100644
index 0000000..423c5c9
--- /dev/null
+++ b/extensions/libebt_mark.c
@@ -0,0 +1,228 @@
+/* ebt_mark
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * July, 2002, September 2006
+ *
+ * Adapted by Arturo Borrero Gonzalez <arturo@debian.org>
+ * to use libxtables for ebtables-compat in 2015.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_mark_t.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define MARK_TARGET  '1'
+#define MARK_SETMARK '2'
+#define MARK_ORMARK  '3'
+#define MARK_ANDMARK '4'
+#define MARK_XORMARK '5'
+static const struct option brmark_opts[] = {
+	{ .name = "mark-target",.has_arg = true,	.val = MARK_TARGET },
+	/* an oldtime messup, we should have always used the scheme
+	 * <extension-name>-<option> */
+	{ .name = "set-mark",	.has_arg = true,	.val = MARK_SETMARK },
+	{ .name = "mark-set",	.has_arg = true,	.val = MARK_SETMARK },
+	{ .name = "mark-or",	.has_arg = true,	.val = MARK_ORMARK },
+	{ .name = "mark-and",	.has_arg = true,	.val = MARK_ANDMARK },
+	{ .name = "mark-xor",	.has_arg = true,	.val = MARK_XORMARK },
+	XT_GETOPT_TABLEEND,
+};
+
+static void brmark_print_help(void)
+{
+	printf(
+	"mark target options:\n"
+	" --mark-set value     : Set nfmark value\n"
+	" --mark-or  value     : Or nfmark with value (nfmark |= value)\n"
+	" --mark-and value     : And nfmark with value (nfmark &= value)\n"
+	" --mark-xor value     : Xor nfmark with value (nfmark ^= value)\n"
+	" --mark-target target : ACCEPT, DROP, RETURN or CONTINUE\n");
+}
+
+static void brmark_init(struct xt_entry_target *target)
+{
+	struct ebt_mark_t_info *info = (struct ebt_mark_t_info *)target->data;
+
+	info->target = EBT_ACCEPT;
+	info->mark = 0;
+}
+
+#define OPT_MARK_TARGET   0x01
+#define OPT_MARK_SETMARK  0x02
+#define OPT_MARK_ORMARK   0x04
+#define OPT_MARK_ANDMARK  0x08
+#define OPT_MARK_XORMARK  0x10
+
+static int
+brmark_parse(int c, char **argv, int invert, unsigned int *flags,
+	     const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_mark_t_info *info = (struct ebt_mark_t_info *)
+				       (*target)->data;
+	char *end;
+	uint32_t mask;
+
+	switch (c) {
+	case MARK_TARGET:
+		{ unsigned int tmp;
+		EBT_CHECK_OPTION(flags, OPT_MARK_TARGET);
+		if (ebt_fill_target(optarg, &tmp))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Illegal --mark-target target");
+		/* the 4 lsb are left to designate the target */
+		info->target = (info->target & ~EBT_VERDICT_BITS) |
+			       (tmp & EBT_VERDICT_BITS);
+		}
+		return 1;
+	case MARK_SETMARK:
+		EBT_CHECK_OPTION(flags, OPT_MARK_SETMARK);
+		mask = (OPT_MARK_ORMARK|OPT_MARK_ANDMARK|OPT_MARK_XORMARK);
+		if (*flags & mask)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--mark-set cannot be used together with"
+				      " specific --mark option");
+		info->target = (info->target & EBT_VERDICT_BITS) |
+			       MARK_SET_VALUE;
+		break;
+	case MARK_ORMARK:
+		EBT_CHECK_OPTION(flags, OPT_MARK_ORMARK);
+		mask = (OPT_MARK_SETMARK|OPT_MARK_ANDMARK|OPT_MARK_XORMARK);
+		if (*flags & mask)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--mark-or cannot be used together with"
+				      " specific --mark option");
+		info->target = (info->target & EBT_VERDICT_BITS) |
+			       MARK_OR_VALUE;
+		break;
+	case MARK_ANDMARK:
+		EBT_CHECK_OPTION(flags, OPT_MARK_ANDMARK);
+		mask = (OPT_MARK_SETMARK|OPT_MARK_ORMARK|OPT_MARK_XORMARK);
+		if (*flags & mask)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--mark-and cannot be used together with"
+				      " specific --mark option");
+		info->target = (info->target & EBT_VERDICT_BITS) |
+			       MARK_AND_VALUE;
+		break;
+	case MARK_XORMARK:
+		EBT_CHECK_OPTION(flags, OPT_MARK_XORMARK);
+		mask = (OPT_MARK_SETMARK|OPT_MARK_ANDMARK|OPT_MARK_ORMARK);
+		if (*flags & mask)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--mark-xor cannot be used together with"
+				      " specific --mark option");
+		info->target = (info->target & EBT_VERDICT_BITS) |
+			       MARK_XOR_VALUE;
+		break;
+	default:
+		return 0;
+	}
+	/* mutual code */
+	info->mark = strtoul(optarg, &end, 0);
+	if (*end != '\0' || end == optarg)
+		xtables_error(PARAMETER_PROBLEM, "Bad MARK value '%s'",
+			      optarg);
+
+	return 1;
+}
+
+static void brmark_print(const void *ip, const struct xt_entry_target *target,
+			 int numeric)
+{
+	struct ebt_mark_t_info *info = (struct ebt_mark_t_info *)target->data;
+	int tmp;
+
+	tmp = info->target & ~EBT_VERDICT_BITS;
+	if (tmp == MARK_SET_VALUE)
+		printf("--mark-set");
+	else if (tmp == MARK_OR_VALUE)
+		printf("--mark-or");
+	else if (tmp == MARK_XOR_VALUE)
+		printf("--mark-xor");
+	else if (tmp == MARK_AND_VALUE)
+		printf("--mark-and");
+	else
+		xtables_error(PARAMETER_PROBLEM, "Unknown mark action");
+
+	printf(" 0x%lx", info->mark);
+	tmp = info->target | ~EBT_VERDICT_BITS;
+	printf(" --mark-target %s", ebt_target_name(tmp));
+}
+
+static void brmark_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify some option");
+}
+
+static const char* brmark_verdict(int verdict)
+{
+	switch (verdict) {
+	case EBT_ACCEPT: return "accept";
+	case EBT_DROP: return "drop";
+	case EBT_CONTINUE: return "continue";
+	case EBT_RETURN: return "return";
+	}
+
+	return "";
+}
+
+static int brmark_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_mark_t_info *info = (const void*)params->target->data;
+	int tmp;
+
+	tmp = info->target & ~EBT_VERDICT_BITS;
+
+	xt_xlate_add(xl, "meta mark set ");
+
+	switch (tmp) {
+	case MARK_SET_VALUE:
+		break;
+	case MARK_OR_VALUE:
+		xt_xlate_add(xl, "meta mark or ");
+		break;
+	case MARK_XOR_VALUE:
+		xt_xlate_add(xl, "meta mark xor ");
+		break;
+	case MARK_AND_VALUE:
+		xt_xlate_add(xl, "meta mark and ");
+		break;
+	default:
+		return 0;
+	}
+
+	tmp = info->target & EBT_VERDICT_BITS;
+	xt_xlate_add(xl, "0x%lx %s ", info->mark, brmark_verdict(tmp));
+	return 1;
+}
+
+static struct xtables_target brmark_target = {
+	.name		= "mark",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_mark_t_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_mark_t_info)),
+	.help		= brmark_print_help,
+	.init		= brmark_init,
+	.parse		= brmark_parse,
+	.final_check	= brmark_final_check,
+	.print		= brmark_print,
+	.xlate		= brmark_xlate,
+	.extra_opts	= brmark_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brmark_target);
+}
diff --git a/extensions/libebt_mark.t b/extensions/libebt_mark.t
new file mode 100644
index 0000000..2d8f9d7
--- /dev/null
+++ b/extensions/libebt_mark.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-j mark --mark-set 1;-j mark --mark-set 0x1 --mark-target ACCEPT;OK
+-j mark --mark-or 0xa --mark-target CONTINUE;=;OK
+-j mark --mark-and 0x1 --mark-target RETURN;=;OK
+-j mark --mark-xor 0x1 --mark-target CONTINUE;=;OK
diff --git a/extensions/libebt_mark.xlate b/extensions/libebt_mark.xlate
new file mode 100644
index 0000000..e0982a1
--- /dev/null
+++ b/extensions/libebt_mark.xlate
@@ -0,0 +1,11 @@
+ebtables-translate -A INPUT --mark-set 42
+nft add rule bridge filter INPUT mark set 0x2a counter
+
+ebtables-translate -A INPUT --mark-or 42 --mark-target RETURN
+nft add rule bridge filter INPUT mark set mark or 0x2a counter return
+
+ebtables-translate -A INPUT --mark-and 42 --mark-target ACCEPT
+nft add rule bridge filter INPUT mark set mark and 0x2a counter accept
+
+ebtables-translate -A INPUT --mark-xor 42 --mark-target DROP
+nft add rule bridge filter INPUT mark set mark xor 0x2a counter drop
diff --git a/extensions/libebt_mark_m.c b/extensions/libebt_mark_m.c
new file mode 100644
index 0000000..2462d0a
--- /dev/null
+++ b/extensions/libebt_mark_m.c
@@ -0,0 +1,143 @@
+/* ebt_mark_m
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * July, 2002
+ *
+ * Adapted by Arturo Borrero Gonzalez <arturo@debian.org>
+ * to use libxtables for ebtables-compat in 2015.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_mark_m.h>
+
+#define MARK '1'
+
+static const struct option brmark_m_opts[] = {
+	{ .name = "mark",	.has_arg = true, .val = MARK },
+	XT_GETOPT_TABLEEND,
+};
+
+static void brmark_m_print_help(void)
+{
+	printf(
+"mark option:\n"
+"--mark    [!] [value][/mask]: Match nfmask value (see man page)\n");
+}
+
+static void brmark_m_init(struct xt_entry_match *match)
+{
+	struct ebt_mark_m_info *info = (struct ebt_mark_m_info *)match->data;
+
+	info->mark = 0;
+	info->mask = 0;
+	info->invert = 0;
+	info->bitmask = 0;
+}
+
+#define OPT_MARK 0x01
+static int
+brmark_m_parse(int c, char **argv, int invert, unsigned int *flags,
+	       const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_mark_m_info *info = (struct ebt_mark_m_info *)
+				       (*match)->data;
+	char *end;
+
+	switch (c) {
+	case MARK:
+		if (invert)
+			info->invert = 1;
+		info->mark = strtoul(optarg, &end, 0);
+		info->bitmask = EBT_MARK_AND;
+		if (*end == '/') {
+			if (end == optarg)
+				info->bitmask = EBT_MARK_OR;
+			info->mask = strtoul(end+1, &end, 0);
+		} else {
+			info->mask = 0xffffffff;
+		}
+		if (*end != '\0' || end == optarg)
+			xtables_error(PARAMETER_PROBLEM, "Bad mark value '%s'",
+				      optarg);
+		break;
+	default:
+		return 0;
+	}
+
+	*flags |= info->bitmask;
+	return 1;
+}
+
+static void brmark_m_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void brmark_m_print(const void *ip, const struct xt_entry_match *match,
+			   int numeric)
+{
+	struct ebt_mark_m_info *info = (struct ebt_mark_m_info *)match->data;
+
+	printf("--mark ");
+	if (info->invert)
+		printf("! ");
+	if (info->bitmask == EBT_MARK_OR)
+		printf("/0x%lx ", info->mask);
+	else if (info->mask != 0xffffffff)
+		printf("0x%lx/0x%lx ", info->mark, info->mask);
+	else
+		printf("0x%lx ", info->mark);
+}
+
+static int brmark_m_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct ebt_mark_m_info *info = (const void*)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (info->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "meta mark ");
+
+	if (info->bitmask == EBT_MARK_OR) {
+		xt_xlate_add(xl, "and 0x%x %s0 ", (uint32_t)info->mask,
+			     info->invert ? "" : "!= ");
+	} else if (info->mask != 0xffffffffU) {
+		xt_xlate_add(xl, "and 0x%x %s0x%x ", (uint32_t)info->mask,
+			   op == XT_OP_EQ ? "" : "!= ", (uint32_t)info->mark);
+	} else {
+		xt_xlate_add(xl, "%s0x%x ",
+			   op == XT_OP_EQ ? "" : "!= ", (uint32_t)info->mark);
+	}
+
+	return 1;
+}
+static struct xtables_match brmark_m_match = {
+	.name		= "mark_m",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_mark_m_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_mark_m_info)),
+	.init		= brmark_m_init,
+	.help		= brmark_m_print_help,
+	.parse		= brmark_m_parse,
+	.final_check	= brmark_m_final_check,
+	.print		= brmark_m_print,
+	.xlate		= brmark_m_xlate,
+	.extra_opts	= brmark_m_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brmark_m_match);
+}
diff --git a/extensions/libebt_mark_m.t b/extensions/libebt_mark_m.t
new file mode 100644
index 0000000..0003542
--- /dev/null
+++ b/extensions/libebt_mark_m.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+--mark 42;--mark 0x2a;OK
+--mark ! 42;--mark ! 0x2a;OK
+--mark 42/0xff;--mark 0x2a/0xff;OK
+--mark ! 0x1/0xff;=;OK
+--mark /0x2;=;OK
diff --git a/extensions/libebt_mark_m.txlate b/extensions/libebt_mark_m.txlate
new file mode 100644
index 0000000..7b44425
--- /dev/null
+++ b/extensions/libebt_mark_m.txlate
@@ -0,0 +1,14 @@
+ebtables-translate -A INPUT --mark 42
+nft add rule bridge filter INPUT meta mark 0x2a counter
+
+ebtables-translate -A INPUT ! --mark 42
+nft add rule bridge filter INPUT meta mark != 0x2a counter
+
+ebtables-translate -A INPUT --mark ! 42
+nft add rule bridge filter INPUT meta mark != 0x2a counter
+
+ebtables-translate -A INPUT --mark ! 0x1/0xff
+nft add rule bridge filter INPUT meta mark and 0xff != 0x1 counter
+
+ebtables-translate -A INPUT --mark /0x02
+nft add rule bridge filter INPUT meta mark and 0x2 != 0 counter
diff --git a/extensions/libebt_nflog.c b/extensions/libebt_nflog.c
new file mode 100644
index 0000000..9801f35
--- /dev/null
+++ b/extensions/libebt_nflog.c
@@ -0,0 +1,168 @@
+/* ebt_nflog
+ *
+ * Authors:
+ * Peter Warasin <peter@endian.com>
+ *
+ *  February, 2008
+ *
+ * Based on:
+ *  ebt_ulog.c, (C) 2004, Bart De Schuymer <bdschuym@pandora.be>
+ *  libxt_NFLOG.c
+ *
+ * Adapted to libxtables for ebtables-compat in 2015 by
+ * Arturo Borrero Gonzalez <arturo@debian.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+#include <linux/netfilter_bridge/ebt_nflog.h>
+
+enum {
+	NFLOG_GROUP	= 0x1,
+	NFLOG_PREFIX	= 0x2,
+	NFLOG_RANGE	= 0x4,
+	NFLOG_THRESHOLD	= 0x8,
+	NFLOG_NFLOG	= 0x16,
+};
+
+static const struct option brnflog_opts[] = {
+	{ .name = "nflog-group",     .has_arg = true,  .val = NFLOG_GROUP},
+	{ .name = "nflog-prefix",    .has_arg = true,  .val = NFLOG_PREFIX},
+	{ .name = "nflog-range",     .has_arg = true,  .val = NFLOG_RANGE},
+	{ .name = "nflog-threshold", .has_arg = true,  .val = NFLOG_THRESHOLD},
+	{ .name = "nflog",           .has_arg = false, .val = NFLOG_NFLOG},
+	XT_GETOPT_TABLEEND,
+};
+
+static void brnflog_help(void)
+{
+	printf("nflog options:\n"
+	       "--nflog               : use the default nflog parameters\n"
+	       "--nflog-prefix prefix : Prefix string for log message\n"
+	       "--nflog-group group   : NETLINK group used for logging\n"
+	       "--nflog-range range   : Number of byte to copy\n"
+	       "--nflog-threshold     : Message threshold of"
+	       "in-kernel queue\n");
+}
+
+static void brnflog_init(struct xt_entry_target *t)
+{
+	struct ebt_nflog_info *info = (struct ebt_nflog_info *)t->data;
+
+	info->prefix[0]	= '\0';
+	info->group	= EBT_NFLOG_DEFAULT_GROUP;
+	info->threshold = EBT_NFLOG_DEFAULT_THRESHOLD;
+}
+
+static int brnflog_parse(int c, char **argv, int invert, unsigned int *flags,
+			 const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_nflog_info *info = (struct ebt_nflog_info *)(*target)->data;
+	unsigned int i;
+
+	if (invert)
+		xtables_error(PARAMETER_PROBLEM,
+			      "The use of '!' makes no sense for the"
+			      " nflog watcher");
+
+	switch (c) {
+	case NFLOG_PREFIX:
+		EBT_CHECK_OPTION(flags, NFLOG_PREFIX);
+		if (strlen(optarg) > EBT_NFLOG_PREFIX_SIZE - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Prefix too long for nflog-prefix");
+		strncpy(info->prefix, optarg, EBT_NFLOG_PREFIX_SIZE);
+		break;
+	case NFLOG_GROUP:
+		EBT_CHECK_OPTION(flags, NFLOG_GROUP);
+		if (!xtables_strtoui(optarg, NULL, &i, 1, UINT32_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				      "--nflog-group must be a number!");
+		info->group = i;
+		break;
+	case NFLOG_RANGE:
+		EBT_CHECK_OPTION(flags, NFLOG_RANGE);
+		if (!xtables_strtoui(optarg, NULL, &i, 1, UINT32_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				      "--nflog-range must be a number!");
+		info->len = i;
+		break;
+	case NFLOG_THRESHOLD:
+		EBT_CHECK_OPTION(flags, NFLOG_THRESHOLD);
+		if (!xtables_strtoui(optarg, NULL, &i, 1, UINT32_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				      "--nflog-threshold must be a number!");
+		info->threshold = i;
+		break;
+	case NFLOG_NFLOG:
+		EBT_CHECK_OPTION(flags, NFLOG_NFLOG);
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void
+brnflog_print(const void *ip, const struct xt_entry_target *target,
+	      int numeric)
+{
+	struct ebt_nflog_info *info = (struct ebt_nflog_info *)target->data;
+
+	if (info->prefix[0] != '\0')
+		printf("--nflog-prefix \"%s\" ", info->prefix);
+	if (info->group)
+		printf("--nflog-group %d ", info->group);
+	if (info->len)
+		printf("--nflog-range %d ", info->len);
+	if (info->threshold != EBT_NFLOG_DEFAULT_THRESHOLD)
+		printf("--nflog-threshold %d ", info->threshold);
+}
+
+static int brnflog_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_nflog_info *info = (void *)params->target->data;
+
+	xt_xlate_add(xl, "log ");
+	if (info->prefix[0] != '\0') {
+		if (params->escape_quotes)
+			xt_xlate_add(xl, "prefix \\\"%s\\\" ", info->prefix);
+		else
+			xt_xlate_add(xl, "prefix \"%s\" ", info->prefix);
+	}
+
+	xt_xlate_add(xl, "group %u ", info->group);
+
+	if (info->len)
+		xt_xlate_add(xl, "snaplen %u ", info->len);
+	if (info->threshold != EBT_NFLOG_DEFAULT_THRESHOLD)
+		xt_xlate_add(xl, "queue-threshold %u ", info->threshold);
+
+	return 1;
+}
+
+static struct xtables_target brnflog_watcher = {
+	.name		= "nflog",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_nflog_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_nflog_info)),
+	.init		= brnflog_init,
+	.help		= brnflog_help,
+	.parse		= brnflog_parse,
+	.print		= brnflog_print,
+	.xlate		= brnflog_xlate,
+	.extra_opts	= brnflog_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brnflog_watcher);
+}
diff --git a/extensions/libebt_nflog.t b/extensions/libebt_nflog.t
new file mode 100644
index 0000000..f867df3
--- /dev/null
+++ b/extensions/libebt_nflog.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+--nflog;=;OK
+--nflog-group 42;=;OK
+--nflog-range 42;--nflog-group 1 --nflog-range 42 -j CONTINUE;OK
+--nflog-threshold 100 --nflog-prefix foo;--nflog-prefix "foo" --nflog-group 1 --nflog-threshold 100 -j CONTINUE;OK
diff --git a/extensions/libebt_nflog.txlate b/extensions/libebt_nflog.txlate
new file mode 100644
index 0000000..bc3f536
--- /dev/null
+++ b/extensions/libebt_nflog.txlate
@@ -0,0 +1,11 @@
+ebtables-translate -A INPUT --nflog
+nft add rule bridge filter INPUT log group 1 counter
+
+ebtables-translate -A INPUT --nflog-group 42
+nft add rule bridge filter INPUT log group 42 counter
+
+ebtables-translate -A INPUT --nflog-range 42
+nft add rule bridge filter INPUT log group 1 snaplen 42 counter
+
+ebtables-translate -A INPUT --nflog-threshold 100 --nflog-prefix foo
+nft add rule bridge filter INPUT log prefix "foo" group 1 queue-threshold 100 counter
diff --git a/extensions/libebt_pkttype.c b/extensions/libebt_pkttype.c
new file mode 100644
index 0000000..4e2d19d
--- /dev/null
+++ b/extensions/libebt_pkttype.c
@@ -0,0 +1,119 @@
+/* ebt_pkttype
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * April, 2003
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <xtables.h>
+#include <linux/if_packet.h>
+#include <linux/netfilter_bridge/ebt_pkttype.h>
+
+static const char *classes[] = {
+	"host",
+	"broadcast",
+	"multicast",
+	"otherhost",
+	"outgoing",
+	"loopback",
+	"fastroute",
+};
+
+static const struct option brpkttype_opts[] =
+{
+	{ "pkttype-type"        , required_argument, 0, '1' },
+	{ 0 }
+};
+
+static void brpkttype_print_help(void)
+{
+	printf(
+"pkttype options:\n"
+"--pkttype-type    [!] type: class the packet belongs to\n"
+"Possible values: broadcast, multicast, host, otherhost, or any other byte value (which would be pretty useless).\n");
+}
+
+
+static int brpkttype_parse(int c, char **argv, int invert, unsigned int *flags,
+			   const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_pkttype_info *ptinfo = (struct ebt_pkttype_info *)(*match)->data;
+	char *end;
+	long int i;
+
+	switch (c) {
+	case '1':
+		if (invert)
+			ptinfo->invert = 1;
+		i = strtol(optarg, &end, 16);
+		if (*end != '\0') {
+			for (i = 0; i < ARRAY_SIZE(classes); i++) {
+				if (!strcasecmp(optarg, classes[i]))
+					break;
+			}
+			if (i >= ARRAY_SIZE(classes))
+				xtables_error(PARAMETER_PROBLEM, "Could not parse class '%s'", optarg);
+		}
+		if (i < 0 || i > 255)
+			xtables_error(PARAMETER_PROBLEM, "Problem with specified pkttype class");
+		ptinfo->pkt_type = (uint8_t)i;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+
+static void brpkttype_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	struct ebt_pkttype_info *pt = (struct ebt_pkttype_info *)match->data;
+
+	printf("--pkttype-type %s", pt->invert ? "! " : "");
+
+	if (pt->pkt_type < ARRAY_SIZE(classes))
+		printf("%s ", classes[pt->pkt_type]);
+	else
+		printf("%d ", pt->pkt_type);
+}
+
+static int brpkttype_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct ebt_pkttype_info *info = (const void*)params->match->data;
+
+	xt_xlate_add(xl, "meta pkttype %s", info->invert ? "!= " : "");
+
+	if (info->pkt_type < 3)
+		xt_xlate_add(xl, "%s ", classes[info->pkt_type]);
+	else if (info->pkt_type == 3)
+		xt_xlate_add(xl, "other ");
+	else
+		xt_xlate_add(xl, "%d ", info->pkt_type);
+
+	return 1;
+}
+
+static struct xtables_match brpkttype_match = {
+	.name		= "pkttype",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_pkttype_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_pkttype_info)),
+	.help		= brpkttype_print_help,
+	.parse		= brpkttype_parse,
+	.print		= brpkttype_print,
+	.xlate		= brpkttype_xlate,
+	.extra_opts	= brpkttype_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brpkttype_match);
+}
diff --git a/extensions/libebt_pkttype.t b/extensions/libebt_pkttype.t
new file mode 100644
index 0000000..e3b95de
--- /dev/null
+++ b/extensions/libebt_pkttype.t
@@ -0,0 +1,14 @@
+:INPUT,FORWARD,OUTPUT
+! --pkttype-type host;--pkttype-type ! host -j CONTINUE;OK
+--pkttype-type host;=;OK
+--pkttype-type ! host;=;OK
+--pkttype-type broadcast;=;OK
+--pkttype-type ! broadcast;=;OK
+--pkttype-type multicast;=;OK
+--pkttype-type ! multicast;=;OK
+--pkttype-type otherhost;=;OK
+--pkttype-type ! otherhost;=;OK
+--pkttype-type outgoing;=;OK
+--pkttype-type ! outgoing;=;OK
+--pkttype-type loopback;=;OK
+--pkttype-type ! loopback;=;OK
diff --git a/extensions/libebt_pkttype.txlate b/extensions/libebt_pkttype.txlate
new file mode 100644
index 0000000..94d016d
--- /dev/null
+++ b/extensions/libebt_pkttype.txlate
@@ -0,0 +1,20 @@
+ebtables-translate -A INPUT --pkttype-type host
+nft add rule bridge filter INPUT meta pkttype host counter
+
+ebtables-translate -A INPUT ! --pkttype-type broadcast
+nft add rule bridge filter INPUT meta pkttype != broadcast counter
+
+ebtables-translate -A INPUT --pkttype-type ! multicast
+nft add rule bridge filter INPUT meta pkttype != multicast counter
+
+ebtables-translate -A INPUT --pkttype-type otherhost
+nft add rule bridge filter INPUT meta pkttype other counter
+
+ebtables-translate -A INPUT --pkttype-type outgoing
+nft add rule bridge filter INPUT meta pkttype 4 counter
+
+ebtables-translate -A INPUT --pkttype-type loopback
+nft add rule bridge filter INPUT meta pkttype 5 counter
+
+ebtables-translate -A INPUT --pkttype-type fastroute
+nft add rule bridge filter INPUT meta pkttype 6 counter
diff --git a/extensions/libebt_redirect.c b/extensions/libebt_redirect.c
new file mode 100644
index 0000000..6e65399
--- /dev/null
+++ b/extensions/libebt_redirect.c
@@ -0,0 +1,109 @@
+/* ebt_redirect
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * April, 2002
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_redirect.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define REDIRECT_TARGET '1'
+static const struct option brredir_opts[] =
+{
+	{ "redirect-target", required_argument, 0, REDIRECT_TARGET },
+	{ 0 }
+};
+
+static void brredir_print_help(void)
+{
+	printf(
+	"redirect option:\n"
+	" --redirect-target target   : ACCEPT, DROP, RETURN or CONTINUE\n");
+}
+
+static void brredir_init(struct xt_entry_target *target)
+{
+	struct ebt_redirect_info *redirectinfo =
+	   (struct ebt_redirect_info *)target->data;
+
+	redirectinfo->target = EBT_ACCEPT;
+}
+
+#define OPT_REDIRECT_TARGET  0x01
+static int brredir_parse(int c, char **argv, int invert, unsigned int *flags,
+			 const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_redirect_info *redirectinfo =
+	   (struct ebt_redirect_info *)(*target)->data;
+
+	switch (c) {
+	case REDIRECT_TARGET:
+		EBT_CHECK_OPTION(flags, OPT_REDIRECT_TARGET);
+		if (ebt_fill_target(optarg, (unsigned int *)&redirectinfo->target))
+			xtables_error(PARAMETER_PROBLEM, "Illegal --redirect-target target");
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void brredir_print(const void *ip, const struct xt_entry_target *target, int numeric)
+{
+	struct ebt_redirect_info *redirectinfo =
+	   (struct ebt_redirect_info *)target->data;
+
+	if (redirectinfo->target == EBT_ACCEPT)
+		return;
+	printf("--redirect-target %s", ebt_target_name(redirectinfo->target));
+}
+
+static const char* brredir_verdict(int verdict)
+{
+	switch (verdict) {
+	case EBT_ACCEPT: return "accept";
+	case EBT_DROP: return "drop";
+	case EBT_CONTINUE: return "continue";
+	case EBT_RETURN: return "return";
+	}
+
+	return "";
+}
+
+static int brredir_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_redirect_info *red = (const void*)params->target->data;
+
+	xt_xlate_add(xl, "meta set pkttype host");
+	if (red->target != EBT_ACCEPT)
+		xt_xlate_add(xl, " %s ", brredir_verdict(red->target));
+	return 0;
+}
+
+static struct xtables_target brredirect_target = {
+	.name		= "redirect",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_redirect_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_redirect_info)),
+	.help		= brredir_print_help,
+	.init		= brredir_init,
+	.parse		= brredir_parse,
+	.print		= brredir_print,
+	.xlate		= brredir_xlate,
+	.extra_opts	= brredir_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brredirect_target);
+}
diff --git a/extensions/libebt_redirect.t b/extensions/libebt_redirect.t
new file mode 100644
index 0000000..23858af
--- /dev/null
+++ b/extensions/libebt_redirect.t
@@ -0,0 +1,4 @@
+:PREROUTING
+*nat
+-j redirect;=;OK
+-j redirect --redirect-target RETURN;=;OK
diff --git a/extensions/libebt_snat.c b/extensions/libebt_snat.c
new file mode 100644
index 0000000..c1124bf
--- /dev/null
+++ b/extensions/libebt_snat.c
@@ -0,0 +1,146 @@
+/* ebt_nat
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * June, 2002
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <netinet/ether.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_nat.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define NAT_S '1'
+#define NAT_S_TARGET '2'
+#define NAT_S_ARP '3'
+static const struct option brsnat_opts[] =
+{
+	{ "to-source"     , required_argument, 0, NAT_S },
+	{ "to-src"        , required_argument, 0, NAT_S },
+	{ "snat-target"   , required_argument, 0, NAT_S_TARGET },
+	{ "snat-arp"      ,       no_argument, 0, NAT_S_ARP },
+	{ 0 }
+};
+
+static void brsnat_print_help(void)
+{
+	printf(
+	"snat options:\n"
+	" --to-src address       : MAC address to map source to\n"
+	" --snat-target target   : ACCEPT, DROP, RETURN or CONTINUE\n"
+	" --snat-arp             : also change src address in arp msg\n");
+}
+
+static void brsnat_init(struct xt_entry_target *target)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)target->data;
+
+	natinfo->target = EBT_ACCEPT;
+}
+
+#define OPT_SNAT         0x01
+#define OPT_SNAT_TARGET  0x02
+#define OPT_SNAT_ARP     0x04
+static int brsnat_parse(int c, char **argv, int invert, unsigned int *flags,
+			 const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)(*target)->data;
+	struct ether_addr *addr;
+
+	switch (c) {
+	case NAT_S:
+		EBT_CHECK_OPTION(flags, OPT_SNAT);
+		if (!(addr = ether_aton(optarg)))
+			xtables_error(PARAMETER_PROBLEM, "Problem with specified --to-source mac");
+		memcpy(natinfo->mac, addr, ETH_ALEN);
+		break;
+	case NAT_S_TARGET:
+		{ unsigned int tmp;
+		EBT_CHECK_OPTION(flags, OPT_SNAT_TARGET);
+		if (ebt_fill_target(optarg, &tmp))
+			xtables_error(PARAMETER_PROBLEM, "Illegal --snat-target target");
+		natinfo->target = (natinfo->target & ~EBT_VERDICT_BITS) | (tmp & EBT_VERDICT_BITS);
+		}
+		break;
+	case NAT_S_ARP:
+		EBT_CHECK_OPTION(flags, OPT_SNAT_ARP);
+		natinfo->target ^= NAT_ARP_BIT;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void brsnat_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void brsnat_print(const void *ip, const struct xt_entry_target *target, int numeric)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)target->data;
+
+	printf("--to-src ");
+	xtables_print_mac(natinfo->mac);
+	if (!(natinfo->target&NAT_ARP_BIT))
+		printf(" --snat-arp");
+	printf(" --snat-target %s", ebt_target_name((natinfo->target|~EBT_VERDICT_BITS)));
+}
+
+static const char* brsnat_verdict(int verdict)
+{
+	switch (verdict) {
+	case EBT_ACCEPT: return "accept";
+	case EBT_DROP: return "drop";
+	case EBT_CONTINUE: return "continue";
+	case EBT_RETURN: return "return";
+	}
+
+	return "";
+}
+
+static int brsnat_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_nat_info *natinfo = (const void*)params->target->data;
+
+	xt_xlate_add(xl, "ether saddr set %s ",
+		     ether_ntoa((struct ether_addr *)natinfo->mac));
+
+	/* NAT_ARP_BIT set -> no arp mangling, not set -> arp mangling (yes, its inverted) */
+	if (!(natinfo->target&NAT_ARP_BIT))
+		return 0;
+
+	xt_xlate_add(xl, "%s ", brsnat_verdict(natinfo->target | ~EBT_VERDICT_BITS));
+	return 1;
+}
+
+static struct xtables_target brsnat_target =
+{
+	.name		= "snat",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size           = XT_ALIGN(sizeof(struct ebt_nat_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_nat_info)),
+	.help		= brsnat_print_help,
+	.init		= brsnat_init,
+	.parse		= brsnat_parse,
+	.final_check	= brsnat_final_check,
+	.print		= brsnat_print,
+	.xlate		= brsnat_xlate,
+	.extra_opts	= brsnat_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brsnat_target);
+}
diff --git a/extensions/libebt_snat.t b/extensions/libebt_snat.t
new file mode 100644
index 0000000..639b13f
--- /dev/null
+++ b/extensions/libebt_snat.t
@@ -0,0 +1,4 @@
+:POSTROUTING
+*nat
+-o someport -j snat --to-source a:b:c:d:e:f;-o someport -j snat --to-src 0a:0b:0c:0d:0e:0f --snat-target ACCEPT;OK
+-o someport+ -j snat --to-src de:ad:00:be:ee:ff --snat-target CONTINUE;=;OK
diff --git a/extensions/libebt_snat.txlate b/extensions/libebt_snat.txlate
new file mode 100644
index 0000000..0d84602
--- /dev/null
+++ b/extensions/libebt_snat.txlate
@@ -0,0 +1,5 @@
+ebtables-translate -t nat -A POSTROUTING -s 0:0:0:0:0:0 -o someport+ --to-source de:ad:00:be:ee:ff
+nft add rule bridge nat POSTROUTING oifname "someport*" ether saddr 00:00:00:00:00:00 ether saddr set de:ad:0:be:ee:ff accept counter
+
+ebtables-translate -t nat -A POSTROUTING -o someport --to-src de:ad:00:be:ee:ff --snat-target CONTINUE
+nft add rule bridge nat POSTROUTING oifname "someport" ether saddr set de:ad:0:be:ee:ff continue counter
diff --git a/extensions/libebt_standard.t b/extensions/libebt_standard.t
new file mode 100644
index 0000000..c6c3172
--- /dev/null
+++ b/extensions/libebt_standard.t
@@ -0,0 +1,28 @@
+:INPUT,FORWARD,OUTPUT
+-d de:ad:be:ef:00:00;=;OK
+-s 0:0:0:0:0:0;-s 00:00:00:00:00:00;OK
+-d 00:00:00:00:00:00;=;OK
+-s de:ad:be:ef:0:00 -j RETURN;-s de:ad:be:ef:00:00 -j RETURN;OK
+-d de:ad:be:ef:00:00 -j CONTINUE;=;OK
+-d de:ad:be:ef:0:00/ff:ff:ff:ff:0:0 -j DROP;-d de:ad:be:ef:00:00/ff:ff:ff:ff:00:00 -j DROP;OK
+-p ARP -j ACCEPT;=;OK
+-p ! ARP -j ACCEPT;=;OK
+-p 0 -j ACCEPT;=;FAIL
+-p ! 0 -j ACCEPT;=;FAIL
+:INPUT
+-i foobar;=;OK
+-o foobar;=;FAIL
+:FORWARD
+-i foobar;=;OK
+-o foobar;=;OK
+:OUTPUT
+-i foobar;=;FAIL
+-o foobar;=;OK
+:PREROUTING
+*nat
+-i foobar;=;OK
+-o foobar;=;FAIL
+:POSTROUTING
+*nat
+-i foobar;=;FAIL
+-o foobar;=;OK
diff --git a/extensions/libebt_stp.c b/extensions/libebt_stp.c
new file mode 100644
index 0000000..81ba572
--- /dev/null
+++ b/extensions/libebt_stp.c
@@ -0,0 +1,318 @@
+/* ebt_stp
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * July, 2003
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <netinet/ether.h>
+#include <linux/netfilter_bridge/ebt_stp.h>
+#include <xtables.h>
+
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define STP_TYPE	'a'
+#define STP_FLAGS	'b'
+#define STP_ROOTPRIO	'c'
+#define STP_ROOTADDR	'd'
+#define STP_ROOTCOST	'e'
+#define STP_SENDERPRIO	'f'
+#define STP_SENDERADDR	'g'
+#define STP_PORT	'h'
+#define STP_MSGAGE	'i'
+#define STP_MAXAGE	'j'
+#define STP_HELLOTIME	'k'
+#define STP_FWDD	'l'
+#define STP_NUMOPS 12
+
+static const struct option brstp_opts[] =
+{
+	{ "stp-type"         , required_argument, 0, STP_TYPE},
+	{ "stp-flags"        , required_argument, 0, STP_FLAGS},
+	{ "stp-root-prio"    , required_argument, 0, STP_ROOTPRIO},
+	{ "stp-root-addr"    , required_argument, 0, STP_ROOTADDR},
+	{ "stp-root-cost"    , required_argument, 0, STP_ROOTCOST},
+	{ "stp-sender-prio"  , required_argument, 0, STP_SENDERPRIO},
+	{ "stp-sender-addr"  , required_argument, 0, STP_SENDERADDR},
+	{ "stp-port"         , required_argument, 0, STP_PORT},
+	{ "stp-msg-age"      , required_argument, 0, STP_MSGAGE},
+	{ "stp-max-age"      , required_argument, 0, STP_MAXAGE},
+	{ "stp-hello-time"   , required_argument, 0, STP_HELLOTIME},
+	{ "stp-forward-delay", required_argument, 0, STP_FWDD},
+	{ 0 }
+};
+
+#define BPDU_TYPE_CONFIG 0
+#define BPDU_TYPE_TCN 0x80
+#define BPDU_TYPE_CONFIG_STRING "config"
+#define BPDU_TYPE_TCN_STRING "tcn"
+
+#define FLAG_TC 0x01
+#define FLAG_TC_ACK 0x80
+#define FLAG_TC_STRING "topology-change"
+#define FLAG_TC_ACK_STRING "topology-change-ack"
+
+static void brstp_print_help(void)
+{
+	printf(
+"stp options:\n"
+"--stp-type type                  : BPDU type\n"
+"--stp-flags flag                 : control flag\n"
+"--stp-root-prio prio[:prio]      : root priority (16-bit) range\n"
+"--stp-root-addr address[/mask]   : MAC address of root\n"
+"--stp-root-cost cost[:cost]      : root cost (32-bit) range\n"
+"--stp-sender-prio prio[:prio]    : sender priority (16-bit) range\n"
+"--stp-sender-addr address[/mask] : MAC address of sender\n"
+"--stp-port port[:port]           : port id (16-bit) range\n"
+"--stp-msg-age age[:age]          : message age timer (16-bit) range\n"
+"--stp-max-age age[:age]          : maximum age timer (16-bit) range\n"
+"--stp-hello-time time[:time]     : hello time timer (16-bit) range\n"
+"--stp-forward-delay delay[:delay]: forward delay timer (16-bit) range\n"
+" Recognized BPDU type strings:\n"
+"   \"config\": configuration BPDU (=0)\n"
+"   \"tcn\"   : topology change notification BPDU (=0x80)\n"
+" Recognized control flag strings:\n"
+"   \"topology-change\"    : topology change flag (0x01)\n"
+"   \"topology-change-ack\": topology change acknowledgement flag (0x80)");
+}
+
+static int parse_range(const char *portstring, void *lower, void *upper,
+   int bits, uint32_t min, uint32_t max)
+{
+	char *buffer;
+	char *cp, *end;
+	uint32_t low_nr, upp_nr;
+	int ret = 0;
+
+	buffer = strdup(portstring);
+	if ((cp = strchr(buffer, ':')) == NULL) {
+		low_nr = strtoul(buffer, &end, 10);
+		if (*end || low_nr < min || low_nr > max) {
+			ret = -1;
+			goto out;
+		}
+		if (bits == 2) {
+			*(uint16_t *)lower =  low_nr;
+			*(uint16_t *)upper =  low_nr;
+		} else {
+			*(uint32_t *)lower =  low_nr;
+			*(uint32_t *)upper =  low_nr;
+		}
+	} else {
+		*cp = '\0';
+		cp++;
+		if (!*buffer)
+			low_nr = min;
+		else {
+			low_nr = strtoul(buffer, &end, 10);
+			if (*end || low_nr < min) {
+				ret = -1;
+				goto out;
+			}
+		}
+		if (!*cp)
+			upp_nr = max;
+		else {
+			upp_nr = strtoul(cp, &end, 10);
+			if (*end || upp_nr > max) {
+				ret = -1;
+				goto out;
+			}
+		}
+		if (upp_nr < low_nr) {
+			ret = -1;
+			goto out;
+		}
+		if (bits == 2) {
+			*(uint16_t *)lower = low_nr;
+			*(uint16_t *)upper = upp_nr;
+		} else {
+			*(uint32_t *)lower = low_nr;
+			*(uint32_t *)upper = upp_nr;
+		}
+	}
+out:
+	free(buffer);
+	return ret;
+}
+
+static void print_range(unsigned int l, unsigned int u)
+{
+	if (l == u)
+		printf("%u ", l);
+	else
+		printf("%u:%u ", l, u);
+}
+
+static int
+brstp_parse(int c, char **argv, int invert, unsigned int *flags,
+	    const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_stp_info *stpinfo = (struct ebt_stp_info *)(*match)->data;
+	unsigned int flag;
+	long int i;
+	char *end = NULL;
+
+	if (c < 'a' || c > ('a' + STP_NUMOPS - 1))
+		return 0;
+	flag = 1 << (c - 'a');
+	EBT_CHECK_OPTION(flags, flag);
+	if (invert)
+		stpinfo->invflags |= flag;
+	stpinfo->bitmask |= flag;
+	switch (flag) {
+	case EBT_STP_TYPE:
+		i = strtol(optarg, &end, 0);
+		if (i < 0 || i > 255 || *end != '\0') {
+			if (!strcasecmp(optarg, BPDU_TYPE_CONFIG_STRING))
+				stpinfo->type = BPDU_TYPE_CONFIG;
+			else if (!strcasecmp(optarg, BPDU_TYPE_TCN_STRING))
+				stpinfo->type = BPDU_TYPE_TCN;
+			else
+				xtables_error(PARAMETER_PROBLEM, "Bad --stp-type argument");
+		} else
+			stpinfo->type = i;
+		break;
+	case EBT_STP_FLAGS:
+		i = strtol(optarg, &end, 0);
+		if (i < 0 || i > 255 || *end != '\0') {
+			if (!strcasecmp(optarg, FLAG_TC_STRING))
+				stpinfo->config.flags = FLAG_TC;
+			else if (!strcasecmp(optarg, FLAG_TC_ACK_STRING))
+				stpinfo->config.flags = FLAG_TC_ACK;
+			else
+				xtables_error(PARAMETER_PROBLEM, "Bad --stp-flags argument");
+		} else
+			stpinfo->config.flags = i;
+		break;
+	case EBT_STP_ROOTPRIO:
+		if (parse_range(argv[optind-1], &(stpinfo->config.root_priol),
+		    &(stpinfo->config.root_priou), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-root-prio range");
+		break;
+	case EBT_STP_ROOTCOST:
+		if (parse_range(argv[optind-1], &(stpinfo->config.root_costl),
+		    &(stpinfo->config.root_costu), 4, 0, 0xffffffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-root-cost range");
+		break;
+	case EBT_STP_SENDERPRIO:
+		if (parse_range(argv[optind-1], &(stpinfo->config.sender_priol),
+		    &(stpinfo->config.sender_priou), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-sender-prio range");
+		break;
+	case EBT_STP_PORT:
+		if (parse_range(argv[optind-1], &(stpinfo->config.portl),
+		    &(stpinfo->config.portu), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-port-range");
+		break;
+	case EBT_STP_MSGAGE:
+		if (parse_range(argv[optind-1], &(stpinfo->config.msg_agel),
+		    &(stpinfo->config.msg_ageu), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-msg-age range");
+		break;
+	case EBT_STP_MAXAGE:
+		if (parse_range(argv[optind-1], &(stpinfo->config.max_agel),
+		    &(stpinfo->config.max_ageu), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-max-age range");
+		break;
+	case EBT_STP_HELLOTIME:
+		if (parse_range(argv[optind-1], &(stpinfo->config.hello_timel),
+		    &(stpinfo->config.hello_timeu), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-hello-time range");
+		break;
+	case EBT_STP_FWDD:
+		if (parse_range(argv[optind-1], &(stpinfo->config.forward_delayl),
+		    &(stpinfo->config.forward_delayu), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-forward-delay range");
+		break;
+	case EBT_STP_ROOTADDR:
+		if (xtables_parse_mac_and_mask(argv[optind-1],
+					       stpinfo->config.root_addr,
+					       stpinfo->config.root_addrmsk))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-root-addr address");
+		break;
+	case EBT_STP_SENDERADDR:
+		if (xtables_parse_mac_and_mask(argv[optind-1],
+					       stpinfo->config.sender_addr,
+					       stpinfo->config.sender_addrmsk))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-sender-addr address");
+		break;
+	default:
+		xtables_error(PARAMETER_PROBLEM, "Unknown stp option");
+	}
+	return 1;
+}
+
+static void brstp_print(const void *ip, const struct xt_entry_match *match,
+			 int numeric)
+{
+	const struct ebt_stp_info *stpinfo = (struct ebt_stp_info *)match->data;
+	const struct ebt_stp_config_info *c = &(stpinfo->config);
+	int i;
+
+	for (i = 0; i < STP_NUMOPS; i++) {
+		if (!(stpinfo->bitmask & (1 << i)))
+			continue;
+		printf("--%s %s", brstp_opts[i].name,
+		       (stpinfo->invflags & (1 << i)) ? "! " : "");
+		if (EBT_STP_TYPE == (1 << i)) {
+			if (stpinfo->type == BPDU_TYPE_CONFIG)
+				printf("%s", BPDU_TYPE_CONFIG_STRING);
+			else if (stpinfo->type == BPDU_TYPE_TCN)
+				printf("%s", BPDU_TYPE_TCN_STRING);
+			else
+				printf("%d", stpinfo->type);
+		} else if (EBT_STP_FLAGS == (1 << i)) {
+			if (c->flags == FLAG_TC)
+				printf("%s", FLAG_TC_STRING);
+			else if (c->flags == FLAG_TC_ACK)
+				printf("%s", FLAG_TC_ACK_STRING);
+			else
+				printf("%d", c->flags);
+		} else if (EBT_STP_ROOTPRIO == (1 << i))
+			print_range(c->root_priol, c->root_priou);
+		else if (EBT_STP_ROOTADDR == (1 << i))
+			xtables_print_mac_and_mask((unsigned char *)c->root_addr,
+			   (unsigned char*)c->root_addrmsk);
+		else if (EBT_STP_ROOTCOST == (1 << i))
+			print_range(c->root_costl, c->root_costu);
+		else if (EBT_STP_SENDERPRIO == (1 << i))
+			print_range(c->sender_priol, c->sender_priou);
+		else if (EBT_STP_SENDERADDR == (1 << i))
+			xtables_print_mac_and_mask((unsigned char *)c->sender_addr,
+			   (unsigned char *)c->sender_addrmsk);
+		else if (EBT_STP_PORT == (1 << i))
+			print_range(c->portl, c->portu);
+		else if (EBT_STP_MSGAGE == (1 << i))
+			print_range(c->msg_agel, c->msg_ageu);
+		else if (EBT_STP_MAXAGE == (1 << i))
+			print_range(c->max_agel, c->max_ageu);
+		else if (EBT_STP_HELLOTIME == (1 << i))
+			print_range(c->hello_timel, c->hello_timeu);
+		else if (EBT_STP_FWDD == (1 << i))
+			print_range(c->forward_delayl, c->forward_delayu);
+		printf(" ");
+	}
+}
+
+static struct xtables_match brstp_match = {
+	.name		= "stp",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= sizeof(struct ebt_stp_info),
+	.help		= brstp_print_help,
+	.parse		= brstp_parse,
+	.print		= brstp_print,
+	.extra_opts	= brstp_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brstp_match);
+}
diff --git a/extensions/libebt_stp.t b/extensions/libebt_stp.t
new file mode 100644
index 0000000..0c6b77b
--- /dev/null
+++ b/extensions/libebt_stp.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+--stp-type 1;=;OK
+--stp-flags 0x1;--stp-flags topology-change -j CONTINUE;OK
+--stp-root-prio 1  -j ACCEPT;=;OK
+--stp-root-addr 0d:ea:d0:0b:ee:f0;=;OK
+--stp-root-cost 1;=;OK
+--stp-sender-prio 1;=;OK
+--stp-sender-addr de:ad:be:ef:00:00;=;OK
+--stp-port 1;=;OK
+--stp-msg-age 1;=;OK
+--stp-max-age 1;=;OK
+--stp-hello-time 1;=;OK
+--stp-forward-delay 1;=;OK
diff --git a/extensions/libebt_vlan.c b/extensions/libebt_vlan.c
new file mode 100644
index 0000000..fa69792
--- /dev/null
+++ b/extensions/libebt_vlan.c
@@ -0,0 +1,156 @@
+/* ebt_vlan
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ * Nick Fedchik <nick@fedchik.org.ua>
+ * June, 2002
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <xtables.h>
+#include <netinet/if_ether.h>
+#include <linux/netfilter_bridge/ebt_vlan.h>
+#include <linux/if_ether.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define NAME_VLAN_ID    "id"
+#define NAME_VLAN_PRIO  "prio"
+#define NAME_VLAN_ENCAP "encap"
+
+#define VLAN_ID    '1'
+#define VLAN_PRIO  '2'
+#define VLAN_ENCAP '3'
+
+static const struct option brvlan_opts[] = {
+	{"vlan-id"   , required_argument, NULL, VLAN_ID},
+	{"vlan-prio" , required_argument, NULL, VLAN_PRIO},
+	{"vlan-encap", required_argument, NULL, VLAN_ENCAP},
+	XT_GETOPT_TABLEEND,
+};
+
+/*
+ * option inverse flags definition
+ */
+#define OPT_VLAN_ID     0x01
+#define OPT_VLAN_PRIO   0x02
+#define OPT_VLAN_ENCAP  0x04
+#define OPT_VLAN_FLAGS	(OPT_VLAN_ID | OPT_VLAN_PRIO | OPT_VLAN_ENCAP)
+
+static void brvlan_print_help(void)
+{
+	printf(
+"vlan options:\n"
+"--vlan-id [!] id       : vlan-tagged frame identifier, 0,1-4096 (integer)\n"
+"--vlan-prio [!] prio   : Priority-tagged frame's user priority, 0-7 (integer)\n"
+"--vlan-encap [!] encap : Encapsulated frame protocol (hexadecimal or name)\n");
+}
+
+static int
+brvlan_parse(int c, char **argv, int invert, unsigned int *flags,
+	       const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_vlan_info *vlaninfo = (struct ebt_vlan_info *) (*match)->data;
+	struct xt_ethertypeent *ethent;
+	char *end;
+	struct ebt_vlan_info local;
+
+	switch (c) {
+	case VLAN_ID:
+		EBT_CHECK_OPTION(flags, OPT_VLAN_ID);
+		if (invert)
+			vlaninfo->invflags |= EBT_VLAN_ID;
+		local.id = strtoul(optarg, &end, 10);
+		if (local.id > 4094 || *end != '\0')
+			xtables_error(PARAMETER_PROBLEM, "Invalid --vlan-id range ('%s')", optarg);
+		vlaninfo->id = local.id;
+		vlaninfo->bitmask |= EBT_VLAN_ID;
+		break;
+	case VLAN_PRIO:
+		EBT_CHECK_OPTION(flags, OPT_VLAN_PRIO);
+		if (invert)
+			vlaninfo->invflags |= EBT_VLAN_PRIO;
+		local.prio = strtoul(optarg, &end, 10);
+		if (local.prio >= 8 || *end != '\0')
+			xtables_error(PARAMETER_PROBLEM, "Invalid --vlan-prio range ('%s')", optarg);
+		vlaninfo->prio = local.prio;
+		vlaninfo->bitmask |= EBT_VLAN_PRIO;
+		break;
+	case VLAN_ENCAP:
+		EBT_CHECK_OPTION(flags, OPT_VLAN_ENCAP);
+		if (invert)
+			vlaninfo->invflags |= EBT_VLAN_ENCAP;
+		local.encap = strtoul(optarg, &end, 16);
+		if (*end != '\0') {
+			ethent = xtables_getethertypebyname(optarg);
+			if (ethent == NULL)
+				xtables_error(PARAMETER_PROBLEM, "Unknown --vlan-encap value ('%s')", optarg);
+			local.encap = ethent->e_ethertype;
+		}
+		if (local.encap < ETH_ZLEN)
+			xtables_error(PARAMETER_PROBLEM, "Invalid --vlan-encap range ('%s')", optarg);
+		vlaninfo->encap = htons(local.encap);
+		vlaninfo->bitmask |= EBT_VLAN_ENCAP;
+		break;
+	default:
+		return 0;
+
+	}
+	return 1;
+}
+
+static void brvlan_print(const void *ip, const struct xt_entry_match *match,
+			 int numeric)
+{
+	struct ebt_vlan_info *vlaninfo = (struct ebt_vlan_info *) match->data;
+
+	if (vlaninfo->bitmask & EBT_VLAN_ID) {
+		printf("--vlan-id %s%d ", (vlaninfo->invflags & EBT_VLAN_ID) ? "! " : "", vlaninfo->id);
+	}
+	if (vlaninfo->bitmask & EBT_VLAN_PRIO) {
+		printf("--vlan-prio %s%d ", (vlaninfo->invflags & EBT_VLAN_PRIO) ? "! " : "", vlaninfo->prio);
+	}
+	if (vlaninfo->bitmask & EBT_VLAN_ENCAP) {
+		printf("--vlan-encap %s", (vlaninfo->invflags & EBT_VLAN_ENCAP) ? "! " : "");
+		printf("%4.4X ", ntohs(vlaninfo->encap));
+	}
+}
+
+static int brvlan_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_mt_params *params)
+{
+	const struct ebt_vlan_info *vlaninfo = (const void*)params->match->data;
+
+	if (vlaninfo->bitmask & EBT_VLAN_ID)
+		xt_xlate_add(xl, "vlan id %s%d ", (vlaninfo->invflags & EBT_VLAN_ID) ? "!= " : "", vlaninfo->id);
+
+	if (vlaninfo->bitmask & EBT_VLAN_PRIO)
+		xt_xlate_add(xl, "vlan pcp %s%d ", (vlaninfo->invflags & EBT_VLAN_PRIO) ? "!= " : "", vlaninfo->prio);
+
+	if (vlaninfo->bitmask & EBT_VLAN_ENCAP)
+		xt_xlate_add(xl, "vlan type %s0x%4.4x ", (vlaninfo->invflags & EBT_VLAN_ENCAP) ? "!= " : "", ntohs(vlaninfo->encap));
+
+	return 1;
+}
+
+static struct xtables_match brvlan_match = {
+	.name		= "vlan",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_vlan_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_vlan_info)),
+	.help		= brvlan_print_help,
+	.parse		= brvlan_parse,
+	.print		= brvlan_print,
+	.xlate		= brvlan_xlate,
+	.extra_opts	= brvlan_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brvlan_match);
+}
diff --git a/extensions/libebt_vlan.t b/extensions/libebt_vlan.t
new file mode 100644
index 0000000..81c7958
--- /dev/null
+++ b/extensions/libebt_vlan.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+-p 802_1Q --vlan-id 42;=;OK
+-p 802_1Q --vlan-id ! 42;=;OK
+-p 802_1Q --vlan-prio 1;=;OK
+-p 802_1Q --vlan-prio ! 1;=;OK
+-p 802_1Q --vlan-encap ip;-p 802_1Q --vlan-encap 0800 -j CONTINUE;OK
+-p 802_1Q --vlan-encap 0800 ;=;OK
+-p 802_1Q --vlan-encap ! 0800 ;=;OK
+-p 802_1Q --vlan-encap IPv6 ! --vlan-id 1;-p 802_1Q --vlan-id ! 1 --vlan-encap 86DD -j CONTINUE;OK
+-p 802_1Q --vlan-id ! 1 --vlan-encap 86DD;=;OK
+--vlan-encap ip;=;FAIL
+--vlan-id 2;=;FAIL
+--vlan-prio 1;=;FAIL
diff --git a/extensions/libebt_vlan.txlate b/extensions/libebt_vlan.txlate
new file mode 100644
index 0000000..2ab62d5
--- /dev/null
+++ b/extensions/libebt_vlan.txlate
@@ -0,0 +1,11 @@
+ebtables-translate -A INPUT -p 802_1Q --vlan-id 42
+nft add rule bridge filter INPUT vlan id 42 counter
+
+ebtables-translate -A INPUT -p 802_1Q --vlan-prio ! 1
+nft add rule bridge filter INPUT vlan pcp != 1 counter
+
+ebtables-translate -A INPUT -p 802_1Q --vlan-encap ip
+nft add rule bridge filter INPUT vlan type 0x0800 counter
+
+ebtables-translate -A INPUT -p 802_1Q --vlan-encap ipv6 ! --vlan-id 1
+nft add rule bridge filter INPUT vlan id != 1 vlan type 0x86dd counter
diff --git a/extensions/libext.mk b/extensions/libext.mk
new file mode 100644
index 0000000..49bad61
--- /dev/null
+++ b/extensions/libext.mk
@@ -0,0 +1,62 @@
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS:=
+LOCAL_MODULE:=libext$(libext_suffix)
+
+# LOCAL_MODULE_CLASS must be defined before calling $(local-generated-sources-dir)
+#
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+MY_gen := $(call local-intermediates-dir)
+
+# LOCAL_PATH needed because of dirty #include "blabla.c"
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include/ \
+	$(LOCAL_PATH)/.. \
+	$(MY_gen) \
+	$(LOCAL_PATH)
+
+LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
+# The $* does not work as expected. It ends up empty. Even with SECONDEXPANSION.
+# LOCAL_CFLAGS+=-D_INIT=lib$*_init
+LOCAL_CFLAGS+=-DXTABLES_INTERNAL
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
+LOCAL_CFLAGS+=-D__ANDROID__
+LOCAL_CFLAGS += $(MY_warnings)
+
+MY_GEN_INITEXT:= $(MY_gen)/initext.c
+$(MY_GEN_INITEXT): MY_initext_func := $(addprefix $(libext_prefix)_,$(libext_build_mod))
+$(MY_GEN_INITEXT): MY_suffix := $(libext_suffix)
+$(MY_GEN_INITEXT):
+	@mkdir -p $(dir $@)
+	@( \
+	echo "" >$@; \
+	for i in $(MY_initext_func); do \
+		echo "extern void lib$${i}_init(void);" >>$@; \
+	done; \
+	echo "void init_extensions$(MY_suffix)(void);" >>$@; \
+	echo "void init_extensions$(MY_suffix)(void)" >>$@; \
+	echo "{" >>$@; \
+	for i in $(MY_initext_func); do \
+		echo " ""lib$${i}_init();" >>$@; \
+	done; \
+	echo "}" >>$@; \
+	);
+
+MY_lib_sources:= \
+	$(patsubst %,$(LOCAL_PATH)/lib$(libext_prefix)_%.c,$(libext_build_mod))
+
+MY_gen_lib_sources:= $(patsubst $(LOCAL_PATH)/%,${MY_gen}/%,${MY_lib_sources})
+
+${MY_gen_lib_sources}: PRIVATE_PATH := $(LOCAL_PATH)
+${MY_gen_lib_sources}: PRIVATE_CUSTOM_TOOL = $(PRIVATE_PATH)/filter_init $(PRIVATE_PATH)/$(notdir $@) > $@
+${MY_gen_lib_sources}: PRIVATE_MODULE := $(LOCAL_MODULE)
+${MY_gen_lib_sources}: PRIVATE_C_INCLUDES := $(LOCAL_C_INCLUDES)
+${MY_gen_lib_sources}: ${MY_gen}/% : $(LOCAL_PATH)/% $(LOCAL_PATH)/filter_init
+	$(transform-generated-source)
+
+$(MY_GEN_INITEXT): $(MY_gen_lib_sources)
+
+LOCAL_GENERATED_SOURCES:= $(MY_GEN_INITEXT) $(MY_gen_lib_sources)
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/extensions/libip6t_DNAT.c b/extensions/libip6t_DNAT.c
new file mode 100644
index 0000000..89c5ceb
--- /dev/null
+++ b/extensions/libip6t_DNAT.c
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Rusty Russell's IPv4 DNAT target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <iptables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_DEST = 0,
+	O_RANDOM,
+	O_PERSISTENT,
+	O_X_TO_DEST,
+	F_TO_DEST   = 1 << O_TO_DEST,
+	F_RANDOM   = 1 << O_RANDOM,
+	F_X_TO_DEST = 1 << O_X_TO_DEST,
+};
+
+static void DNAT_help(void)
+{
+	printf(
+"DNAT target options:\n"
+" --to-destination [<ipaddr>[-<ipaddr>]][:port[-port]]\n"
+"				Address to map destination to.\n"
+"[--random] [--persistent]\n");
+}
+
+static void DNAT_help_v2(void)
+{
+	printf(
+"DNAT target options:\n"
+" --to-destination [<ipaddr>[-<ipaddr>]][:port[-port[/port]]]\n"
+"				Address to map destination to.\n"
+"[--random] [--persistent]\n");
+}
+
+static const struct xt_option_entry DNAT_opts[] = {
+	{.name = "to-destination", .id = O_TO_DEST, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_MULTI},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+/* Ranges expected in network order. */
+static void
+parse_to(const char *orig_arg, int portok, struct nf_nat_range2 *range, int rev)
+{
+	char *arg, *start, *end = NULL, *colon = NULL, *dash, *error;
+	const struct in6_addr *ip;
+
+	arg = strdup(orig_arg);
+	if (arg == NULL)
+		xtables_error(RESOURCE_PROBLEM, "strdup");
+
+	start = strchr(arg, '[');
+	if (start == NULL) {
+		start = arg;
+		/* Lets assume one colon is port information. Otherwise its an IPv6 address */
+		colon = strchr(arg, ':');
+		if (colon && strchr(colon+1, ':'))
+			colon = NULL;
+	}
+	else {
+		start++;
+		end = strchr(start, ']');
+		if (end == NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid address format");
+
+		*end = '\0';
+		colon = strchr(end + 1, ':');
+	}
+
+	if (colon) {
+		int port;
+
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+
+		range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+		port = atoi(colon+1);
+		if (port <= 0 || port > 65535)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Port `%s' not valid\n", colon+1);
+
+		error = strchr(colon+1, ':');
+		if (error)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid port:port syntax - use dash\n");
+
+		dash = strchr(colon, '-');
+		if (!dash) {
+			range->min_proto.tcp.port
+				= range->max_proto.tcp.port
+				= htons(port);
+		} else {
+			int maxport;
+
+			maxport = atoi(dash + 1);
+			if (maxport <= 0 || maxport > 65535)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port `%s' not valid\n", dash+1);
+			if (maxport < port)
+				/* People are stupid. */
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port range `%s' funky\n", colon+1);
+			range->min_proto.tcp.port = htons(port);
+			range->max_proto.tcp.port = htons(maxport);
+
+			if (rev >= 2) {
+				char *slash = strchr(dash, '/');
+				if (slash) {
+					int baseport;
+
+					baseport = atoi(slash + 1);
+					if (baseport <= 0 || baseport > 65535)
+						xtables_error(PARAMETER_PROBLEM,
+								 "Port `%s' not valid\n", slash+1);
+					range->flags |= NF_NAT_RANGE_PROTO_OFFSET;
+					range->base_proto.tcp.port = htons(baseport);
+				}
+			}
+		}
+		/* Starts with colon or [] colon? No IP info...*/
+		if (colon == arg || colon == arg+2) {
+			free(arg);
+			return;
+		}
+		*colon = '\0';
+	}
+
+	range->flags |= NF_NAT_RANGE_MAP_IPS;
+	dash = strchr(start, '-');
+	if (colon && dash && dash > colon)
+		dash = NULL;
+
+	if (dash)
+		*dash = '\0';
+
+	ip = xtables_numeric_to_ip6addr(start);
+	if (!ip)
+		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+			      start);
+	range->min_addr.in6 = *ip;
+	if (dash) {
+		ip = xtables_numeric_to_ip6addr(dash + 1);
+		if (!ip)
+			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+				      dash+1);
+		range->max_addr.in6 = *ip;
+	} else
+		range->max_addr = range->min_addr;
+
+	free(arg);
+	return;
+}
+
+static void _DNAT_parse(struct xt_option_call *cb,
+		struct nf_nat_range2 *range, int rev)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	int portok;
+
+	if (entry->ipv6.proto == IPPROTO_TCP ||
+	    entry->ipv6.proto == IPPROTO_UDP ||
+	    entry->ipv6.proto == IPPROTO_SCTP ||
+	    entry->ipv6.proto == IPPROTO_DCCP ||
+	    entry->ipv6.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_DEST:
+		if (cb->xflags & F_X_TO_DEST) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "DNAT: Multiple --to-destination not supported");
+		}
+		parse_to(cb->arg, portok, range, rev);
+		cb->xflags |= F_X_TO_DEST;
+		break;
+	case O_PERSISTENT:
+		range->flags |= NF_NAT_RANGE_PERSISTENT;
+		break;
+	}
+}
+
+static void DNAT_parse(struct xt_option_call *cb)
+{
+	struct nf_nat_range *range_v1 = (void *)cb->data;
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, range_v1, sizeof(*range_v1));
+	_DNAT_parse(cb, &range, 1);
+	memcpy(range_v1, &range, sizeof(*range_v1));
+}
+
+static void DNAT_parse_v2(struct xt_option_call *cb)
+{
+	_DNAT_parse(cb, (struct nf_nat_range2 *)cb->data, 2);
+}
+
+static void _DNAT_fcheck(struct xt_fcheck_call *cb, unsigned int *flags)
+{
+	static const unsigned int f = F_TO_DEST | F_RANDOM;
+
+	if ((cb->xflags & f) == f)
+		*flags |= NF_NAT_RANGE_PROTO_RANDOM;
+}
+
+static void DNAT_fcheck(struct xt_fcheck_call *cb)
+{
+	_DNAT_fcheck(cb, &((struct nf_nat_range *)cb->data)->flags);
+}
+
+static void DNAT_fcheck_v2(struct xt_fcheck_call *cb)
+{
+	_DNAT_fcheck(cb, &((struct nf_nat_range2 *)cb->data)->flags);
+}
+
+static void print_range(const struct nf_nat_range2 *range, int rev)
+{
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
+			printf("[");
+		printf("%s", xtables_ip6addr_to_numeric(&range->min_addr.in6));
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr)))
+			printf("-%s", xtables_ip6addr_to_numeric(&range->max_addr.in6));
+		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
+			printf("]");
+	}
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(":");
+		printf("%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			printf("-%hu", ntohs(range->max_proto.tcp.port));
+		if (rev >= 2 && (range->flags & NF_NAT_RANGE_PROTO_OFFSET))
+			printf("/%hu", ntohs(range->base_proto.tcp.port));
+	}
+}
+
+static void _DNAT_print(const struct nf_nat_range2 *range, int rev)
+{
+	printf(" to:");
+	print_range(range, rev);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" random");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" persistent");
+}
+
+static void DNAT_print(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+	const struct nf_nat_range *range_v1 = (const void *)target->data;
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, range_v1, sizeof(*range_v1));
+	_DNAT_print(&range, 1);
+}
+
+static void DNAT_print_v2(const void *ip, const struct xt_entry_target *target,
+                          int numeric)
+{
+	_DNAT_print((const struct nf_nat_range2 *)target->data, 2);
+}
+
+static void _DNAT_save(const struct nf_nat_range2 *range, int rev)
+{
+	printf(" --to-destination ");
+	print_range(range, rev);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" --random");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" --persistent");
+}
+
+static void DNAT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_range *range_v1 = (const void *)target->data;
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, range_v1, sizeof(*range_v1));
+	_DNAT_save(&range, 1);
+}
+
+static void DNAT_save_v2(const void *ip, const struct xt_entry_target *target)
+{
+	_DNAT_save((const struct nf_nat_range2 *)target->data, 2);
+}
+
+static void print_range_xlate(const struct nf_nat_range2 *range,
+			      struct xt_xlate *xl, int rev)
+{
+	bool proto_specified = range->flags & NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		xt_xlate_add(xl, "%s%s%s",
+			     proto_specified ? "[" : "",
+			     xtables_ip6addr_to_numeric(&range->min_addr.in6),
+			     proto_specified ? "]" : "");
+
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr))) {
+			xt_xlate_add(xl, "-%s%s%s",
+				     proto_specified ? "[" : "",
+				     xtables_ip6addr_to_numeric(&range->max_addr.in6),
+				     proto_specified ? "]" : "");
+		}
+	}
+	if (proto_specified) {
+		xt_xlate_add(xl, ":%hu", ntohs(range->min_proto.tcp.port));
+
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			xt_xlate_add(xl, "-%hu",
+				   ntohs(range->max_proto.tcp.port));
+	}
+}
+
+static int _DNAT_xlate(struct xt_xlate *xl,
+		      const struct nf_nat_range2 *range, int rev)
+{
+	bool sep_need = false;
+	const char *sep = " ";
+
+	xt_xlate_add(xl, "dnat to ");
+	print_range_xlate(range, xl, rev);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM) {
+		xt_xlate_add(xl, " random");
+		sep_need = true;
+	}
+	if (range->flags & NF_NAT_RANGE_PERSISTENT) {
+		if (sep_need)
+			sep = ",";
+		xt_xlate_add(xl, "%spersistent", sep);
+	}
+
+	return 1;
+}
+
+static int DNAT_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_range *range_v1 = (const void *)params->target->data;
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, range_v1, sizeof(*range_v1));
+	_DNAT_xlate(xl, &range, 1);
+
+	return 1;
+}
+
+static int DNAT_xlate_v2(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	_DNAT_xlate(xl, (const struct nf_nat_range2 *)params->target->data, 2);
+
+	return 1;
+}
+
+static struct xtables_target dnat_tg_reg[] = {
+	{
+		.name		= "DNAT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV6,
+		.revision	= 1,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+		.help		= DNAT_help,
+		.print		= DNAT_print,
+		.save		= DNAT_save,
+		.x6_parse	= DNAT_parse,
+		.x6_fcheck	= DNAT_fcheck,
+		.x6_options	= DNAT_opts,
+		.xlate		= DNAT_xlate,
+	},
+	{
+		.name		= "DNAT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV6,
+		.revision	= 2,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_range2)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range2)),
+		.help		= DNAT_help_v2,
+		.print		= DNAT_print_v2,
+		.save		= DNAT_save_v2,
+		.x6_parse	= DNAT_parse_v2,
+		.x6_fcheck	= DNAT_fcheck_v2,
+		.x6_options	= DNAT_opts,
+		.xlate		= DNAT_xlate_v2,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(dnat_tg_reg, ARRAY_SIZE(dnat_tg_reg));
+}
diff --git a/extensions/libip6t_DNAT.t b/extensions/libip6t_DNAT.t
new file mode 100644
index 0000000..ec7d61f
--- /dev/null
+++ b/extensions/libip6t_DNAT.t
@@ -0,0 +1,16 @@
+:PREROUTING
+*nat
+-j DNAT --to-destination dead::beef;=;OK
+-j DNAT --to-destination dead::beef-dead::fee7;=;OK
+-j DNAT --to-destination [dead::beef]:1025-65535;;FAIL
+-j DNAT --to-destination [dead::beef] --to-destination [dead::fee7];;FAIL
+-p tcp -j DNAT --to-destination [dead::beef]:1025-65535;=;OK
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1025-65535;=;OK
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1025-65536;;FAIL
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1025-65535 --to-destination [dead::beef-dead::fee8]:1025-65535;;FAIL
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1000-2000/1000;=;OK
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1000-2000/3000;=;OK
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1000-2000/65535;=;OK
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1000-2000/0;;FAIL
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1000-2000/65536;;FAIL
+-j DNAT;;FAIL
diff --git a/extensions/libip6t_DNAT.txlate b/extensions/libip6t_DNAT.txlate
new file mode 100644
index 0000000..03c4caf
--- /dev/null
+++ b/extensions/libip6t_DNAT.txlate
@@ -0,0 +1,11 @@
+ip6tables-translate -t nat -A prerouting -i eth1 -p tcp --dport 8080 -j DNAT --to-destination [fec0::1234]:80
+nft add rule ip6 nat prerouting iifname "eth1" tcp dport 8080 counter dnat to [fec0::1234]:80
+
+ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:1-20
+nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:1-20
+
+ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:80 --persistent
+nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:80 persistent
+
+ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:80 --random --persistent
+nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:80 random,persistent
diff --git a/extensions/libip6t_DNPT.c b/extensions/libip6t_DNPT.c
new file mode 100644
index 0000000..d045e44
--- /dev/null
+++ b/extensions/libip6t_DNPT.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2012-2013 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_NPT.h>
+
+enum {
+	O_SRC_PFX	= 1 << 0,
+	O_DST_PFX	= 1 << 1,
+};
+
+static const struct xt_option_entry DNPT_options[] = {
+	{ .name = "src-pfx", .id = O_SRC_PFX, .type = XTTYPE_HOSTMASK,
+	  .flags = XTOPT_MAND },
+	{ .name = "dst-pfx", .id = O_DST_PFX, .type = XTTYPE_HOSTMASK,
+	  .flags = XTOPT_MAND },
+	{ }
+};
+
+static void DNPT_help(void)
+{
+	printf("DNPT target options:"
+	       "\n"
+	       " --src-pfx prefix/length\n"
+	       " --dst-pfx prefix/length\n"
+	       "\n");
+}
+
+static void DNPT_parse(struct xt_option_call *cb)
+{
+	struct ip6t_npt_tginfo *npt = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRC_PFX:
+		npt->src_pfx = cb->val.haddr;
+		npt->src_pfx_len = cb->val.hlen;
+		break;
+	case O_DST_PFX:
+		npt->dst_pfx = cb->val.haddr;
+		npt->dst_pfx_len = cb->val.hlen;
+		break;
+	}
+}
+
+static void DNPT_print(const void *ip, const struct xt_entry_target *target,
+		       int numeric)
+{
+	const struct ip6t_npt_tginfo *npt = (const void *)target->data;
+
+	printf(" DNPT src-pfx %s/%u", xtables_ip6addr_to_numeric(&npt->src_pfx.in6),
+				 npt->src_pfx_len);
+	printf(" dst-pfx %s/%u", xtables_ip6addr_to_numeric(&npt->dst_pfx.in6),
+				 npt->dst_pfx_len);
+}
+
+static void DNPT_save(const void *ip, const struct xt_entry_target *target)
+{
+	static const struct in6_addr zero_addr;
+	const struct ip6t_npt_tginfo *info = (const void *)target->data;
+
+	if (memcmp(&info->src_pfx.in6, &zero_addr, sizeof(zero_addr)) != 0 ||
+	    info->src_pfx_len != 0)
+		printf(" --src-pfx %s/%u",
+		       xtables_ip6addr_to_numeric(&info->src_pfx.in6),
+		       info->src_pfx_len);
+	if (memcmp(&info->dst_pfx.in6, &zero_addr, sizeof(zero_addr)) != 0 ||
+	    info->dst_pfx_len != 0)
+		printf(" --dst-pfx %s/%u",
+		       xtables_ip6addr_to_numeric(&info->dst_pfx.in6),
+		       info->dst_pfx_len);
+}
+
+static struct xtables_target snpt_tg_reg = {
+	.name		= "DNPT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct ip6t_npt_tginfo)),
+	.userspacesize	= offsetof(struct ip6t_npt_tginfo, adjustment),
+	.help		= DNPT_help,
+	.x6_parse	= DNPT_parse,
+	.print		= DNPT_print,
+	.save		= DNPT_save,
+	.x6_options	= DNPT_options,
+};
+
+void _init(void)
+{
+	xtables_register_target(&snpt_tg_reg);
+}
diff --git a/extensions/libip6t_DNPT.man b/extensions/libip6t_DNPT.man
new file mode 100644
index 0000000..9b060f5
--- /dev/null
+++ b/extensions/libip6t_DNPT.man
@@ -0,0 +1,30 @@
+Provides stateless destination IPv6-to-IPv6 Network Prefix Translation (as
+described by RFC 6296).
+.PP
+You have to use this target in the
+.B mangle
+table, not in the
+.B nat
+table. It takes the following options:
+.TP
+\fB\-\-src\-pfx\fP [\fIprefix/\fP\fIlength]
+Set source prefix that you want to translate and length
+.TP
+\fB\-\-dst\-pfx\fP [\fIprefix/\fP\fIlength]
+Set destination prefix that you want to use in the translation and length
+.PP
+You have to use the SNPT target to undo the translation. Example:
+.IP
+ip6tables \-t mangle \-I POSTROUTING \-s fd00::/64 \! \-o vboxnet0
+\-j SNPT \-\-src-pfx fd00::/64 \-\-dst-pfx 2001:e20:2000:40f::/64
+.IP
+ip6tables \-t mangle \-I PREROUTING \-i wlan0 \-d 2001:e20:2000:40f::/64
+\-j DNPT \-\-src-pfx 2001:e20:2000:40f::/64 \-\-dst-pfx fd00::/64
+.PP
+You may need to enable IPv6 neighbor proxy:
+.IP
+sysctl \-w net.ipv6.conf.all.proxy_ndp=1
+.PP
+You also have to use the
+.B NOTRACK
+target to disable connection tracking for translated flows.
diff --git a/extensions/libip6t_DNPT.t b/extensions/libip6t_DNPT.t
new file mode 100644
index 0000000..0406dc9
--- /dev/null
+++ b/extensions/libip6t_DNPT.t
@@ -0,0 +1,7 @@
+:PREROUTING
+*mangle
+-j DNPT --src-pfx dead::/64 --dst-pfx 1c3::/64;=;OK
+-j DNPT --src-pfx dead::beef --dst-pfx 1c3::/64;;FAIL
+-j DNPT --src-pfx dead::/64;;FAIL
+-j DNPT --dst-pfx dead::/64;;FAIL
+-j DNPT;;FAIL
diff --git a/extensions/libip6t_HL.c b/extensions/libip6t_HL.c
new file mode 100644
index 0000000..52ca5d3
--- /dev/null
+++ b/extensions/libip6t_HL.c
@@ -0,0 +1,127 @@
+/*
+ * IPv6 Hop Limit Target module
+ * Maciej Soltysiak <solt@dns.toxicfilms.tv>
+ * Based on HW's ttl target
+ * This program is distributed under the terms of GNU GPL
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_HL.h>
+
+enum {
+	O_HL_SET = 0,
+	O_HL_INC,
+	O_HL_DEC,
+	F_HL_SET = 1 << O_HL_SET,
+	F_HL_INC = 1 << O_HL_INC,
+	F_HL_DEC = 1 << O_HL_DEC,
+	F_ANY    = F_HL_SET | F_HL_INC | F_HL_DEC,
+};
+
+#define s struct ip6t_HL_info
+static const struct xt_option_entry HL_opts[] = {
+	{.name = "hl-set", .type = XTTYPE_UINT8, .id = O_HL_SET,
+	 .excl = F_ANY, .flags = XTOPT_PUT, XTOPT_POINTER(s, hop_limit)},
+	{.name = "hl-dec", .type = XTTYPE_UINT8, .id = O_HL_DEC,
+	 .excl = F_ANY, .flags = XTOPT_PUT, XTOPT_POINTER(s, hop_limit),
+	 .min = 1},
+	{.name = "hl-inc", .type = XTTYPE_UINT8, .id = O_HL_INC,
+	 .excl = F_ANY, .flags = XTOPT_PUT, XTOPT_POINTER(s, hop_limit),
+	 .min = 1},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void HL_help(void)
+{
+	printf(
+"HL target options\n"
+"  --hl-set value		Set HL to <value 0-255>\n"
+"  --hl-dec value		Decrement HL by <value 1-255>\n"
+"  --hl-inc value		Increment HL by <value 1-255>\n");
+}
+
+static void HL_parse(struct xt_option_call *cb)
+{
+	struct ip6t_HL_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_HL_SET:
+		info->mode = IP6T_HL_SET;
+		break;
+	case O_HL_INC:
+		info->mode = IP6T_HL_INC;
+		break;
+	case O_HL_DEC:
+		info->mode = IP6T_HL_DEC;
+		break;
+	}
+}
+
+static void HL_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & F_ANY))
+		xtables_error(PARAMETER_PROBLEM,
+				"HL: You must specify an action");
+}
+
+static void HL_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ip6t_HL_info *info = 
+		(struct ip6t_HL_info *) target->data;
+
+	switch (info->mode) {
+		case IP6T_HL_SET:
+			printf(" --hl-set");
+			break;
+		case IP6T_HL_DEC:
+			printf(" --hl-dec");
+			break;
+
+		case IP6T_HL_INC:
+			printf(" --hl-inc");
+			break;
+	}
+	printf(" %u", info->hop_limit);
+}
+
+static void HL_print(const void *ip, const struct xt_entry_target *target,
+                     int numeric)
+{
+	const struct ip6t_HL_info *info =
+		(struct ip6t_HL_info *) target->data;
+
+	printf(" HL ");
+	switch (info->mode) {
+		case IP6T_HL_SET:
+			printf("set to");
+			break;
+		case IP6T_HL_DEC:
+			printf("decrement by");
+			break;
+		case IP6T_HL_INC:
+			printf("increment by");
+			break;
+	}
+	printf(" %u", info->hop_limit);
+}
+
+static struct xtables_target hl_tg6_reg = {
+	.name 		= "HL",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct ip6t_HL_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ip6t_HL_info)),
+	.help		= HL_help,
+	.print		= HL_print,
+	.save		= HL_save,
+	.x6_parse	= HL_parse,
+	.x6_fcheck	= HL_check,
+	.x6_options	= HL_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&hl_tg6_reg);
+}
diff --git a/extensions/libip6t_HL.man b/extensions/libip6t_HL.man
new file mode 100644
index 0000000..0f3afb1
--- /dev/null
+++ b/extensions/libip6t_HL.man
@@ -0,0 +1,17 @@
+This is used to modify the Hop Limit field in IPv6 header. The Hop Limit field
+is similar to what is known as TTL value in IPv4.  Setting or incrementing the
+Hop Limit field can potentially be very dangerous, so it should be avoided at
+any cost. This target is only valid in
+.B mangle
+table.
+.PP
+.B Don't ever set or increment the value on packets that leave your local network!
+.TP
+\fB\-\-hl\-set\fP \fIvalue\fP
+Set the Hop Limit to `value'.
+.TP
+\fB\-\-hl\-dec\fP \fIvalue\fP
+Decrement the Hop Limit `value' times.
+.TP
+\fB\-\-hl\-inc\fP \fIvalue\fP
+Increment the Hop Limit `value' times.
diff --git a/extensions/libip6t_HL.t b/extensions/libip6t_HL.t
new file mode 100644
index 0000000..4e529f8
--- /dev/null
+++ b/extensions/libip6t_HL.t
@@ -0,0 +1,10 @@
+:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j HL --hl-set 42;=;OK
+-j HL --hl-inc 1;=;OK
+-j HL --hl-dec 1;=;OK
+-j HL --hl-set 256;;FAIL
+-j HL --hl-inc 0;;FAIL
+-j HL --hl-dec 0;;FAIL
+-j HL --hl-dec 1 --hl-inc 1;;FAIL
+-j HL --hl-set --hl-inc 1;;FAIL
diff --git a/extensions/libip6t_LOG.c b/extensions/libip6t_LOG.c
new file mode 100644
index 0000000..40adc69
--- /dev/null
+++ b/extensions/libip6t_LOG.c
@@ -0,0 +1,250 @@
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_LOG.h>
+
+#ifndef IP6T_LOG_UID	/* Old kernel */
+#define IP6T_LOG_UID	0x08
+#undef  IP6T_LOG_MASK
+#define IP6T_LOG_MASK	0x0f
+#endif
+
+#define LOG_DEFAULT_LEVEL LOG_WARNING
+
+enum {
+	O_LOG_LEVEL = 0,
+	O_LOG_PREFIX,
+	O_LOG_TCPSEQ,
+	O_LOG_TCPOPTS,
+	O_LOG_IPOPTS,
+	O_LOG_UID,
+	O_LOG_MAC,
+};
+
+static void LOG_help(void)
+{
+	printf(
+"LOG target options:\n"
+" --log-level level		Level of logging (numeric or see syslog.conf)\n"
+" --log-prefix prefix		Prefix log messages with this prefix.\n"
+" --log-tcp-sequence		Log TCP sequence numbers.\n"
+" --log-tcp-options		Log TCP options.\n"
+" --log-ip-options		Log IP options.\n"
+" --log-uid			Log UID owning the local socket.\n"
+" --log-macdecode		Decode MAC addresses and protocol.\n");
+}
+
+#define s struct ip6t_log_info
+static const struct xt_option_entry LOG_opts[] = {
+	{.name = "log-level", .id = O_LOG_LEVEL, .type = XTTYPE_SYSLOGLEVEL,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, level)},
+	{.name = "log-prefix", .id = O_LOG_PREFIX, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, prefix), .min = 1},
+	{.name = "log-tcp-sequence", .id = O_LOG_TCPSEQ, .type = XTTYPE_NONE},
+	{.name = "log-tcp-options", .id = O_LOG_TCPOPTS, .type = XTTYPE_NONE},
+	{.name = "log-ip-options", .id = O_LOG_IPOPTS, .type = XTTYPE_NONE},
+	{.name = "log-uid", .id = O_LOG_UID, .type = XTTYPE_NONE},
+	{.name = "log-macdecode", .id = O_LOG_MAC, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void LOG_init(struct xt_entry_target *t)
+{
+	struct ip6t_log_info *loginfo = (struct ip6t_log_info *)t->data;
+
+	loginfo->level = LOG_DEFAULT_LEVEL;
+
+}
+
+struct ip6t_log_names {
+	const char *name;
+	unsigned int level;
+};
+
+struct ip6t_log_xlate {
+	const char *name;
+	unsigned int level;
+};
+
+static const struct ip6t_log_names ip6t_log_names[]
+= { { .name = "alert",   .level = LOG_ALERT },
+    { .name = "crit",    .level = LOG_CRIT },
+    { .name = "debug",   .level = LOG_DEBUG },
+    { .name = "emerg",   .level = LOG_EMERG },
+    { .name = "error",   .level = LOG_ERR },		/* DEPRECATED */
+    { .name = "info",    .level = LOG_INFO },
+    { .name = "notice",  .level = LOG_NOTICE },
+    { .name = "panic",   .level = LOG_EMERG },		/* DEPRECATED */
+    { .name = "warning", .level = LOG_WARNING }
+};
+
+static void LOG_parse(struct xt_option_call *cb)
+{
+	struct ip6t_log_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_LOG_PREFIX:
+		if (strchr(cb->arg, '\n') != NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Newlines not allowed in --log-prefix");
+		break;
+	case O_LOG_TCPSEQ:
+		info->logflags |= IP6T_LOG_TCPSEQ;
+		break;
+	case O_LOG_TCPOPTS:
+		info->logflags |= IP6T_LOG_TCPOPT;
+		break;
+	case O_LOG_IPOPTS:
+		info->logflags |= IP6T_LOG_IPOPT;
+		break;
+	case O_LOG_UID:
+		info->logflags |= IP6T_LOG_UID;
+		break;
+	case O_LOG_MAC:
+		info->logflags |= IP6T_LOG_MACDECODE;
+		break;
+	}
+}
+
+static void LOG_print(const void *ip, const struct xt_entry_target *target,
+                      int numeric)
+{
+	const struct ip6t_log_info *loginfo
+		= (const struct ip6t_log_info *)target->data;
+	unsigned int i = 0;
+
+	printf(" LOG");
+	if (numeric)
+		printf(" flags %u level %u",
+		       loginfo->logflags, loginfo->level);
+	else {
+		for (i = 0; i < ARRAY_SIZE(ip6t_log_names); ++i)
+			if (loginfo->level == ip6t_log_names[i].level) {
+				printf(" level %s", ip6t_log_names[i].name);
+				break;
+			}
+		if (i == ARRAY_SIZE(ip6t_log_names))
+			printf(" UNKNOWN level %u", loginfo->level);
+		if (loginfo->logflags & IP6T_LOG_TCPSEQ)
+			printf(" tcp-sequence");
+		if (loginfo->logflags & IP6T_LOG_TCPOPT)
+			printf(" tcp-options");
+		if (loginfo->logflags & IP6T_LOG_IPOPT)
+			printf(" ip-options");
+		if (loginfo->logflags & IP6T_LOG_UID)
+			printf(" uid");
+		if (loginfo->logflags & IP6T_LOG_MACDECODE)
+			printf(" macdecode");
+		if (loginfo->logflags & ~(IP6T_LOG_MASK))
+			printf(" unknown-flags");
+	}
+
+	if (strcmp(loginfo->prefix, "") != 0)
+		printf(" prefix \"%s\"", loginfo->prefix);
+}
+
+static void LOG_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ip6t_log_info *loginfo
+		= (const struct ip6t_log_info *)target->data;
+
+	if (strcmp(loginfo->prefix, "") != 0) {
+		printf(" --log-prefix");
+		xtables_save_string(loginfo->prefix);
+	}
+
+	if (loginfo->level != LOG_DEFAULT_LEVEL)
+		printf(" --log-level %d", loginfo->level);
+
+	if (loginfo->logflags & IP6T_LOG_TCPSEQ)
+		printf(" --log-tcp-sequence");
+	if (loginfo->logflags & IP6T_LOG_TCPOPT)
+		printf(" --log-tcp-options");
+	if (loginfo->logflags & IP6T_LOG_IPOPT)
+		printf(" --log-ip-options");
+	if (loginfo->logflags & IP6T_LOG_UID)
+		printf(" --log-uid");
+	if (loginfo->logflags & IP6T_LOG_MACDECODE)
+		printf(" --log-macdecode");
+}
+
+static const struct ip6t_log_xlate ip6t_log_xlate_names[] = {
+	{"alert",       LOG_ALERT },
+	{"crit",        LOG_CRIT },
+	{"debug",       LOG_DEBUG },
+	{"emerg",       LOG_EMERG },
+	{"err",         LOG_ERR },
+	{"info",        LOG_INFO },
+	{"notice",      LOG_NOTICE },
+	{"warn",        LOG_WARNING }
+};
+
+static int LOG_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params)
+{
+	const struct ip6t_log_info *loginfo =
+		(const struct ip6t_log_info *)params->target->data;
+	unsigned int i = 0;
+
+	xt_xlate_add(xl, "log");
+	if (strcmp(loginfo->prefix, "") != 0) {
+		if (params->escape_quotes)
+			xt_xlate_add(xl, " prefix \\\"%s\\\"", loginfo->prefix);
+		else
+			xt_xlate_add(xl, " prefix \"%s\"", loginfo->prefix);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(ip6t_log_xlate_names); ++i)
+		if (loginfo->level == ip6t_log_xlate_names[i].level &&
+		    loginfo->level != LOG_DEFAULT_LEVEL) {
+			xt_xlate_add(xl, " level %s",
+				   ip6t_log_xlate_names[i].name);
+			break;
+		}
+
+	if ((loginfo->logflags & IP6T_LOG_MASK) == IP6T_LOG_MASK) {
+		xt_xlate_add(xl, " flags all");
+	} else {
+		if (loginfo->logflags & (IP6T_LOG_TCPSEQ | IP6T_LOG_TCPOPT)) {
+			const char *delim = " ";
+
+			xt_xlate_add(xl, " flags tcp");
+			if (loginfo->logflags & IP6T_LOG_TCPSEQ) {
+				xt_xlate_add(xl, " sequence");
+				delim = ",";
+			}
+			if (loginfo->logflags & IP6T_LOG_TCPOPT)
+				xt_xlate_add(xl, "%soptions", delim);
+		}
+		if (loginfo->logflags & IP6T_LOG_IPOPT)
+			xt_xlate_add(xl, " flags ip options");
+		if (loginfo->logflags & IP6T_LOG_UID)
+			xt_xlate_add(xl, " flags skuid");
+		if (loginfo->logflags & IP6T_LOG_MACDECODE)
+			xt_xlate_add(xl, " flags ether");
+	}
+
+	return 1;
+}
+static struct xtables_target log_tg6_reg = {
+	.name          = "LOG",
+	.version       = XTABLES_VERSION,
+	.family        = NFPROTO_IPV6,
+	.size          = XT_ALIGN(sizeof(struct ip6t_log_info)),
+	.userspacesize = XT_ALIGN(sizeof(struct ip6t_log_info)),
+	.help          = LOG_help,
+	.init          = LOG_init,
+	.print         = LOG_print,
+	.save          = LOG_save,
+	.x6_parse      = LOG_parse,
+	.x6_options    = LOG_opts,
+	.xlate	       = LOG_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&log_tg6_reg);
+}
diff --git a/extensions/libip6t_LOG.t b/extensions/libip6t_LOG.t
new file mode 100644
index 0000000..fbf5118
--- /dev/null
+++ b/extensions/libip6t_LOG.t
@@ -0,0 +1,12 @@
+:INPUT,FORWARD,OUTPUT
+-j LOG;-j LOG;OK
+-j LOG --log-prefix "test: ";=;OK
+-j LOG --log-prefix "test: " --log-level 1;=;OK
+# iptables displays the log-level output using the number; not the string
+-j LOG --log-prefix "test: " --log-level alert;-j LOG --log-prefix "test: " --log-level 1;OK
+-j LOG --log-prefix "test: " --log-tcp-sequence;=;OK
+-j LOG --log-prefix "test: " --log-tcp-options;=;OK
+-j LOG --log-prefix "test: " --log-ip-options;=;OK
+-j LOG --log-prefix "test: " --log-uid;=;OK
+-j LOG --log-prefix "test: " --log-level bad;;FAIL
+-j LOG --log-prefix;;FAIL
diff --git a/extensions/libip6t_LOG.txlate b/extensions/libip6t_LOG.txlate
new file mode 100644
index 0000000..2820a82
--- /dev/null
+++ b/extensions/libip6t_LOG.txlate
@@ -0,0 +1,8 @@
+iptables-translate -I INPUT -j LOG
+nft insert rule ip filter INPUT counter log
+
+ip6tables-translate -A FORWARD -p tcp -j LOG --log-level debug
+nft add rule ip6 filter FORWARD meta l4proto tcp counter log level debug
+
+ip6tables-translate -A FORWARD -p tcp -j LOG --log-prefix "Checking log"
+nft add rule ip6 filter FORWARD meta l4proto tcp counter log prefix \"Checking log\"
diff --git a/extensions/libip6t_MASQUERADE.c b/extensions/libip6t_MASQUERADE.c
new file mode 100644
index 0000000..f92760f
--- /dev/null
+++ b/extensions/libip6t_MASQUERADE.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Rusty Russell's IPv4 MASQUERADE target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_PORTS = 0,
+	O_RANDOM,
+	O_RANDOM_FULLY,
+};
+
+static void MASQUERADE_help(void)
+{
+	printf(
+"MASQUERADE target options:\n"
+" --to-ports <port>[-<port>]\n"
+"				Port (range) to map to.\n"
+" --random\n"
+"				Randomize source port.\n"
+" --random-fully\n"
+"				Fully randomize source port.\n");
+}
+
+static const struct xt_option_entry MASQUERADE_opts[] = {
+	{.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+/* Parses ports */
+static void
+parse_ports(const char *arg, struct nf_nat_range *r)
+{
+	char *end;
+	unsigned int port, maxport;
+
+	r->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX))
+		xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--to-ports", arg);
+
+	switch (*end) {
+	case '\0':
+		r->min_proto.tcp.port
+			= r->max_proto.tcp.port
+			= htons(port);
+		return;
+	case '-':
+		if (!xtables_strtoui(end + 1, NULL, &maxport, 0, UINT16_MAX))
+			break;
+
+		if (maxport < port)
+			break;
+
+		r->min_proto.tcp.port = htons(port);
+		r->max_proto.tcp.port = htons(maxport);
+		return;
+	default:
+		break;
+	}
+	xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--to-ports", arg);
+}
+
+static void MASQUERADE_parse(struct xt_option_call *cb)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	struct nf_nat_range *r = cb->data;
+	int portok;
+
+	if (entry->ipv6.proto == IPPROTO_TCP ||
+	    entry->ipv6.proto == IPPROTO_UDP ||
+	    entry->ipv6.proto == IPPROTO_SCTP ||
+	    entry->ipv6.proto == IPPROTO_DCCP ||
+	    entry->ipv6.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_PORTS:
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+		parse_ports(cb->arg, r);
+		break;
+	case O_RANDOM:
+		r->flags |=  NF_NAT_RANGE_PROTO_RANDOM;
+		break;
+	case O_RANDOM_FULLY:
+		r->flags |=  NF_NAT_RANGE_PROTO_RANDOM_FULLY;
+		break;
+	}
+}
+
+static void
+MASQUERADE_print(const void *ip, const struct xt_entry_target *target,
+                 int numeric)
+{
+	const struct nf_nat_range *r = (const void *)target->data;
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" masq ports: ");
+		printf("%hu", ntohs(r->min_proto.tcp.port));
+		if (r->max_proto.tcp.port != r->min_proto.tcp.port)
+			printf("-%hu", ntohs(r->max_proto.tcp.port));
+	}
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" random");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" random-fully");
+}
+
+static void
+MASQUERADE_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_range *r = (const void *)target->data;
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" --to-ports %hu", ntohs(r->min_proto.tcp.port));
+		if (r->max_proto.tcp.port != r->min_proto.tcp.port)
+			printf("-%hu", ntohs(r->max_proto.tcp.port));
+	}
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" --random");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" --random-fully");
+}
+
+static int MASQUERADE_xlate(struct xt_xlate *xl,
+			    const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_range *r = (const void *)params->target->data;
+
+	xt_xlate_add(xl, "masquerade");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, " to :%hu", ntohs(r->min_proto.tcp.port));
+		if (r->max_proto.tcp.port != r->min_proto.tcp.port)
+			xt_xlate_add(xl, "-%hu", ntohs(r->max_proto.tcp.port));
+	}
+
+	xt_xlate_add(xl, " ");
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		xt_xlate_add(xl, "random ");
+
+	xt_xlate_add(xl, " ");
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		xt_xlate_add(xl, "random-fully ");
+
+	return 1;
+}
+
+static struct xtables_target masquerade_tg_reg = {
+	.name		= "MASQUERADE",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.help		= MASQUERADE_help,
+	.x6_parse	= MASQUERADE_parse,
+	.print		= MASQUERADE_print,
+	.save		= MASQUERADE_save,
+	.x6_options	= MASQUERADE_opts,
+	.xlate		= MASQUERADE_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&masquerade_tg_reg);
+}
diff --git a/extensions/libip6t_MASQUERADE.t b/extensions/libip6t_MASQUERADE.t
new file mode 100644
index 0000000..e25d2a0
--- /dev/null
+++ b/extensions/libip6t_MASQUERADE.t
@@ -0,0 +1,9 @@
+:POSTROUTING
+*nat
+-j MASQUERADE;=;OK
+-j MASQUERADE --random;=;OK
+-j MASQUERADE --random-fully;=;OK
+-p tcp -j MASQUERADE --to-ports 1024;=;OK
+-p udp -j MASQUERADE --to-ports 1024-65535;=;OK
+-p udp -j MASQUERADE --to-ports 1024-65536;;FAIL
+-p udp -j MASQUERADE --to-ports -1;;FAIL
diff --git a/extensions/libip6t_MASQUERADE.txlate b/extensions/libip6t_MASQUERADE.txlate
new file mode 100644
index 0000000..6c289c2
--- /dev/null
+++ b/extensions/libip6t_MASQUERADE.txlate
@@ -0,0 +1,8 @@
+ip6tables-translate -t nat -A POSTROUTING -j MASQUERADE
+nft add rule ip6 nat POSTROUTING counter masquerade
+
+ip6tables-translate -t nat -A POSTROUTING -p tcp -j MASQUERADE --to-ports 10
+nft add rule ip6 nat POSTROUTING meta l4proto tcp counter masquerade to :10
+
+ip6tables-translate -t nat -A POSTROUTING -p tcp -j MASQUERADE --to-ports 10-20 --random
+nft add rule ip6 nat POSTROUTING meta l4proto tcp counter masquerade to :10-20 random
diff --git a/extensions/libip6t_NETMAP.c b/extensions/libip6t_NETMAP.c
new file mode 100644
index 0000000..579ed04
--- /dev/null
+++ b/extensions/libip6t_NETMAP.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Svenning Soerensen's IPv4 NETMAP target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <libiptc/libip6tc.h>
+#include <linux/netfilter/nf_nat.h>
+
+#define MODULENAME "NETMAP"
+
+enum {
+	O_TO = 0,
+};
+
+static const struct xt_option_entry NETMAP_opts[] = {
+	{.name = "to", .id = O_TO, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_MAND},
+	XTOPT_TABLEEND,
+};
+
+static void NETMAP_help(void)
+{
+	printf(MODULENAME" target options:\n"
+	       "  --%s address[/mask]\n"
+	       "				Network address to map to.\n\n",
+	       NETMAP_opts[0].name);
+}
+
+static void NETMAP_parse(struct xt_option_call *cb)
+{
+	struct nf_nat_range *range = cb->data;
+	unsigned int i;
+
+	xtables_option_parse(cb);
+	range->flags |= NF_NAT_RANGE_MAP_IPS;
+	for (i = 0; i < 4; i++) {
+		range->min_addr.ip6[i] = cb->val.haddr.ip6[i] &
+					 cb->val.hmask.ip6[i];
+		range->max_addr.ip6[i] = range->min_addr.ip6[i] |
+					 ~cb->val.hmask.ip6[i];
+	}
+}
+
+static void __NETMAP_print(const void *ip, const struct xt_entry_target *target,
+                           int numeric)
+{
+	const struct nf_nat_range *r = (const void *)target->data;
+	struct in6_addr a;
+	unsigned int i;
+	int bits;
+
+	a = r->min_addr.in6;
+	printf("%s", xtables_ip6addr_to_numeric(&a));
+	for (i = 0; i < 4; i++)
+		a.s6_addr32[i] = ~(r->min_addr.ip6[i] ^ r->max_addr.ip6[i]);
+	bits = xtables_ip6mask_to_cidr(&a);
+	if (bits < 0)
+		printf("/%s", xtables_ip6addr_to_numeric(&a));
+	else
+		printf("/%d", bits);
+}
+
+static void NETMAP_print(const void *ip, const struct xt_entry_target *target,
+                           int numeric)
+{
+	printf(" to:");
+	__NETMAP_print(ip, target, numeric);
+}
+
+static void NETMAP_save(const void *ip, const struct xt_entry_target *target)
+{
+	printf(" --%s ", NETMAP_opts[0].name);
+	__NETMAP_print(ip, target, 0);
+}
+
+static struct xtables_target netmap_tg_reg = {
+	.name		= MODULENAME,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.help		= NETMAP_help,
+	.x6_parse	= NETMAP_parse,
+	.print		= NETMAP_print,
+	.save		= NETMAP_save,
+	.x6_options	= NETMAP_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&netmap_tg_reg);
+}
diff --git a/extensions/libip6t_NETMAP.t b/extensions/libip6t_NETMAP.t
new file mode 100644
index 0000000..043562d
--- /dev/null
+++ b/extensions/libip6t_NETMAP.t
@@ -0,0 +1,4 @@
+:PREROUTING,INPUT,OUTPUT,POSTROUTING
+*nat
+-j NETMAP --to dead::/64;=;OK
+-j NETMAP --to dead::beef;=;OK
diff --git a/extensions/libip6t_REDIRECT.c b/extensions/libip6t_REDIRECT.c
new file mode 100644
index 0000000..8e04d2c
--- /dev/null
+++ b/extensions/libip6t_REDIRECT.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Rusty Russell's IPv4 REDIRECT target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_PORTS = 0,
+	O_RANDOM,
+	F_TO_PORTS = 1 << O_TO_PORTS,
+	F_RANDOM   = 1 << O_RANDOM,
+};
+
+static void REDIRECT_help(void)
+{
+	printf(
+"REDIRECT target options:\n"
+" --to-ports <port>[-<port>]\n"
+"				Port (range) to map to.\n"
+" [--random]\n");
+}
+
+static const struct xt_option_entry REDIRECT_opts[] = {
+	{.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+/* Parses ports */
+static void
+parse_ports(const char *arg, struct nf_nat_range *range)
+{
+	char *end = "";
+	unsigned int port, maxport;
+
+	range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX) &&
+	    (port = xtables_service_to_port(arg, NULL)) == (unsigned)-1)
+		xtables_param_act(XTF_BAD_VALUE, "REDIRECT", "--to-ports", arg);
+
+	switch (*end) {
+	case '\0':
+		range->min_proto.tcp.port
+			= range->max_proto.tcp.port
+			= htons(port);
+		return;
+	case '-':
+		if (!xtables_strtoui(end + 1, NULL, &maxport, 0, UINT16_MAX) &&
+		    (maxport = xtables_service_to_port(end + 1, NULL)) == (unsigned)-1)
+			break;
+
+		if (maxport < port)
+			break;
+
+		range->min_proto.tcp.port = htons(port);
+		range->max_proto.tcp.port = htons(maxport);
+		return;
+	default:
+		break;
+	}
+	xtables_param_act(XTF_BAD_VALUE, "REDIRECT", "--to-ports", arg);
+}
+
+static void REDIRECT_parse(struct xt_option_call *cb)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	struct nf_nat_range *range = (void *)(*cb->target)->data;
+	int portok;
+
+	if (entry->ipv6.proto == IPPROTO_TCP
+	    || entry->ipv6.proto == IPPROTO_UDP
+	    || entry->ipv6.proto == IPPROTO_SCTP
+	    || entry->ipv6.proto == IPPROTO_DCCP
+	    || entry->ipv6.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_PORTS:
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+		parse_ports(cb->arg, range);
+		if (cb->xflags & F_RANDOM)
+			range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
+		break;
+	case O_RANDOM:
+		if (cb->xflags & F_TO_PORTS)
+			range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
+		break;
+	}
+}
+
+static void REDIRECT_print(const void *ip, const struct xt_entry_target *target,
+                           int numeric)
+{
+	const struct nf_nat_range *range = (const void *)target->data;
+
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" redir ports ");
+		printf("%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			printf("-%hu", ntohs(range->max_proto.tcp.port));
+		if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+			printf(" random");
+	}
+}
+
+static void REDIRECT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_range *range = (const void *)target->data;
+
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" --to-ports ");
+		printf("%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			printf("-%hu", ntohs(range->max_proto.tcp.port));
+		if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+			printf(" --random");
+	}
+}
+
+static int REDIRECT_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_range *range = (const void *)params->target->data;
+
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, "redirect to :%hu",
+			   ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			xt_xlate_add(xl, "-%hu ",
+				   ntohs(range->max_proto.tcp.port));
+		if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+			xt_xlate_add(xl, " random ");
+	}
+
+	return 1;
+}
+
+static struct xtables_target redirect_tg_reg = {
+	.name		= "REDIRECT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.help		= REDIRECT_help,
+	.x6_parse	= REDIRECT_parse,
+	.print		= REDIRECT_print,
+	.save		= REDIRECT_save,
+	.x6_options	= REDIRECT_opts,
+	.xlate		= REDIRECT_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&redirect_tg_reg);
+}
diff --git a/extensions/libip6t_REDIRECT.t b/extensions/libip6t_REDIRECT.t
new file mode 100644
index 0000000..a0fb0ed
--- /dev/null
+++ b/extensions/libip6t_REDIRECT.t
@@ -0,0 +1,6 @@
+:PREROUTING,OUTPUT
+*nat
+-p tcp -j REDIRECT --to-ports 42;=;OK
+-p udp -j REDIRECT --to-ports 42-1234;=;OK
+-p tcp -j REDIRECT --to-ports 42-1234 --random;=;OK
+-j REDIRECT --to-ports 42;;FAIL
diff --git a/extensions/libip6t_REDIRECT.txlate b/extensions/libip6t_REDIRECT.txlate
new file mode 100644
index 0000000..209f67a
--- /dev/null
+++ b/extensions/libip6t_REDIRECT.txlate
@@ -0,0 +1,5 @@
+ip6tables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080
+nft add rule ip6 nat prerouting tcp dport 80 counter redirect to :8080
+
+ip6tables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080 --random
+nft add rule ip6 nat prerouting tcp dport 80 counter redirect to :8080 random
diff --git a/extensions/libip6t_REJECT.c b/extensions/libip6t_REJECT.c
new file mode 100644
index 0000000..e3929d1
--- /dev/null
+++ b/extensions/libip6t_REJECT.c
@@ -0,0 +1,185 @@
+/* Shared library add-on to ip6tables to add customized REJECT support.
+ *
+ * (C) 2000 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ * 
+ * ported to IPv6 by Harald Welte <laforge@gnumonks.org>
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_REJECT.h>
+
+struct reject_names {
+	const char *name;
+	const char *alias;
+	const char *desc;
+	const char *xlate;
+};
+
+enum {
+	O_REJECT_WITH = 0,
+};
+
+static const struct reject_names reject_table[] = {
+	[IP6T_ICMP6_NO_ROUTE] = {
+		"icmp6-no-route", "no-route",
+		"ICMPv6 no route",
+		"no-route",
+	},
+	[IP6T_ICMP6_ADM_PROHIBITED] = {
+		"icmp6-adm-prohibited", "adm-prohibited",
+		"ICMPv6 administratively prohibited",
+		"admin-prohibited",
+	},
+#if 0
+	[IP6T_ICMP6_NOT_NEIGHBOR] = {
+		"icmp6-not-neighbor", "not-neighbor",
+		"ICMPv6 not a neighbor",
+	},
+#endif
+	[IP6T_ICMP6_ADDR_UNREACH] = {
+		"icmp6-addr-unreachable", "addr-unreach",
+		"ICMPv6 address unreachable",
+		"addr-unreachable",
+	},
+	[IP6T_ICMP6_PORT_UNREACH] = {
+		"icmp6-port-unreachable", "port-unreach",
+		"ICMPv6 port unreachable",
+		"port-unreachable",
+	},
+#if 0
+	[IP6T_ICMP6_ECHOREPLY] = {},
+#endif
+	[IP6T_TCP_RESET] = {
+		"tcp-reset", "tcp-reset",
+		"TCP RST packet",
+		"tcp reset",
+	},
+	[IP6T_ICMP6_POLICY_FAIL] = {
+		"icmp6-policy-fail", "policy-fail",
+		"ICMPv6 policy fail",
+		"policy-fail",
+	},
+	[IP6T_ICMP6_REJECT_ROUTE] = {
+		"icmp6-reject-route", "reject-route",
+		"ICMPv6 reject route",
+		"reject-route",
+	},
+};
+
+static void
+print_reject_types(void)
+{
+	unsigned int i;
+
+	printf("Valid reject types:\n");
+
+	for (i = 0; i < ARRAY_SIZE(reject_table); ++i) {
+		if (!reject_table[i].name)
+			continue;
+		printf("    %-25s\t%s\n", reject_table[i].name, reject_table[i].desc);
+		printf("    %-25s\talias\n", reject_table[i].alias);
+	}
+	printf("\n");
+}
+
+static void REJECT_help(void)
+{
+	printf(
+"REJECT target options:\n"
+"--reject-with type              drop input packet and send back\n"
+"                                a reply packet according to type:\n");
+
+	print_reject_types();
+}
+
+static const struct xt_option_entry REJECT_opts[] = {
+	{.name = "reject-with", .id = O_REJECT_WITH, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+
+static void REJECT_init(struct xt_entry_target *t)
+{
+	struct ip6t_reject_info *reject = (struct ip6t_reject_info *)t->data;
+
+	/* default */
+	reject->with = IP6T_ICMP6_PORT_UNREACH;
+
+}
+
+static void REJECT_parse(struct xt_option_call *cb)
+{
+	struct ip6t_reject_info *reject = cb->data;
+	unsigned int i;
+
+	xtables_option_parse(cb);
+	for (i = 0; i < ARRAY_SIZE(reject_table); ++i) {
+		if (!reject_table[i].name)
+			continue;
+		if (strncasecmp(reject_table[i].name,
+		      cb->arg, strlen(cb->arg)) == 0 ||
+		    strncasecmp(reject_table[i].alias,
+		      cb->arg, strlen(cb->arg)) == 0) {
+			reject->with = i;
+			return;
+		}
+	}
+	xtables_error(PARAMETER_PROBLEM,
+		"unknown reject type \"%s\"", cb->arg);
+}
+
+static void REJECT_print(const void *ip, const struct xt_entry_target *target,
+                         int numeric)
+{
+	const struct ip6t_reject_info *reject
+		= (const struct ip6t_reject_info *)target->data;
+
+	printf(" reject-with %s", reject_table[reject->with].name);
+}
+
+static void REJECT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ip6t_reject_info *reject
+		= (const struct ip6t_reject_info *)target->data;
+
+	printf(" --reject-with %s", reject_table[reject->with].name);
+}
+
+static int REJECT_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	const struct ip6t_reject_info *reject =
+		(const struct ip6t_reject_info *)params->target->data;
+
+	if (reject->with == IP6T_ICMP6_PORT_UNREACH)
+		xt_xlate_add(xl, "reject");
+	else if (reject->with == IP6T_TCP_RESET)
+		xt_xlate_add(xl, "reject with %s",
+			     reject_table[reject->with].xlate);
+	else
+		xt_xlate_add(xl, "reject with icmpv6 type %s",
+			     reject_table[reject->with].xlate);
+
+	return 1;
+}
+
+static struct xtables_target reject_tg6_reg = {
+	.name = "REJECT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size 		= XT_ALIGN(sizeof(struct ip6t_reject_info)),
+	.userspacesize 	= XT_ALIGN(sizeof(struct ip6t_reject_info)),
+	.help		= REJECT_help,
+	.init		= REJECT_init,
+	.print		= REJECT_print,
+	.save		= REJECT_save,
+	.x6_parse	= REJECT_parse,
+	.x6_options	= REJECT_opts,
+	.xlate		= REJECT_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&reject_tg6_reg);
+}
diff --git a/extensions/libip6t_REJECT.man b/extensions/libip6t_REJECT.man
new file mode 100644
index 0000000..3c42768
--- /dev/null
+++ b/extensions/libip6t_REJECT.man
@@ -0,0 +1,52 @@
+This is used to send back an error packet in response to the matched
+packet: otherwise it is equivalent to 
+.B DROP
+so it is a terminating TARGET, ending rule traversal.
+This target is only valid in the
+.BR INPUT ,
+.B FORWARD
+and
+.B OUTPUT
+chains, and user-defined chains which are only called from those
+chains.  The following option controls the nature of the error packet
+returned:
+.TP
+\fB\-\-reject\-with\fP \fItype\fP
+The type given can be
+\fBicmp6\-no\-route\fP,
+\fBno\-route\fP,
+\fBicmp6\-adm\-prohibited\fP,
+\fBadm\-prohibited\fP,
+\fBicmp6\-addr\-unreachable\fP,
+\fBaddr\-unreach\fP, or
+\fBicmp6\-port\-unreachable\fP,
+which return the appropriate ICMPv6 error message (\fBicmp6\-port\-unreachable\fP is
+the default). Finally, the option
+\fBtcp\-reset\fP
+can be used on rules which only match the TCP protocol: this causes a
+TCP RST packet to be sent back.  This is mainly useful for blocking 
+.I ident
+(113/tcp) probes which frequently occur when sending mail to broken mail
+hosts (which won't accept your mail otherwise).
+\fBtcp\-reset\fP
+can only be used with kernel versions 2.6.14 or later.
+.PP
+\fIWarning:\fP You should not indiscriminately apply the REJECT target to
+packets whose connection state is classified as INVALID; instead, you should
+only DROP these.
+.PP
+Consider a source host transmitting a packet P, with P experiencing so much
+delay along its path that the source host issues a retransmission, P_2, with
+P_2 being successful in reaching its destination and advancing the connection
+state normally. It is conceivable that the late-arriving P may be considered
+not to be associated with any connection tracking entry. Generating a reject
+response for a packet so classed would then terminate the healthy connection.
+.PP
+So, instead of:
+.PP
+-A INPUT ... -j REJECT
+.PP
+do consider using:
+.PP
+-A INPUT ... -m conntrack --ctstate INVALID -j DROP
+-A INPUT ... -j REJECT
diff --git a/extensions/libip6t_REJECT.t b/extensions/libip6t_REJECT.t
new file mode 100644
index 0000000..d2b337d
--- /dev/null
+++ b/extensions/libip6t_REJECT.t
@@ -0,0 +1,11 @@
+:INPUT,FORWARD,OUTPUT
+-j REJECT;=;OK
+# manpage for IPv6 variant of REJECT does not show up for some reason?
+-j REJECT --reject-with icmp6-no-route;=;OK
+-j REJECT --reject-with icmp6-adm-prohibited;=;OK
+-j REJECT --reject-with icmp6-addr-unreachable;=;OK
+-j REJECT --reject-with icmp6-port-unreachable;=;OK
+-j REJECT --reject-with icmp6-policy-fail;=;OK
+-j REJECT --reject-with icmp6-reject-route;=;OK
+-p tcp -j REJECT --reject-with tcp-reset;=;OK
+-j REJECT --reject-with tcp-reset;;FAIL
diff --git a/extensions/libip6t_REJECT.txlate b/extensions/libip6t_REJECT.txlate
new file mode 100644
index 0000000..cfa35eb
--- /dev/null
+++ b/extensions/libip6t_REJECT.txlate
@@ -0,0 +1,8 @@
+ip6tables-translate -A FORWARD -p TCP --dport 22 -j REJECT
+nft add rule ip6 filter FORWARD tcp dport 22 counter reject
+
+ip6tables-translate -A FORWARD -p TCP --dport 22 -j REJECT --reject-with icmp6-reject-route
+nft add rule ip6 filter FORWARD tcp dport 22 counter reject with icmpv6 type reject-route
+
+ip6tables-translate -A FORWARD -p TCP --dport 22 -j REJECT --reject-with tcp-reset
+nft add rule ip6 filter FORWARD tcp dport 22 counter reject with tcp reset
diff --git a/extensions/libip6t_SNAT.c b/extensions/libip6t_SNAT.c
new file mode 100644
index 0000000..7d74b3d
--- /dev/null
+++ b/extensions/libip6t_SNAT.c
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Rusty Russell's IPv4 SNAT target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <iptables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_SRC = 0,
+	O_RANDOM,
+	O_RANDOM_FULLY,
+	O_PERSISTENT,
+	O_X_TO_SRC,
+	F_TO_SRC       = 1 << O_TO_SRC,
+	F_RANDOM       = 1 << O_RANDOM,
+	F_RANDOM_FULLY = 1 << O_RANDOM_FULLY,
+	F_X_TO_SRC     = 1 << O_X_TO_SRC,
+};
+
+static void SNAT_help(void)
+{
+	printf(
+"SNAT target options:\n"
+" --to-source [<ipaddr>[-<ipaddr>]][:port[-port]]\n"
+"				Address to map source to.\n"
+"[--random] [--random-fully] [--persistent]\n");
+}
+
+static const struct xt_option_entry SNAT_opts[] = {
+	{.name = "to-source", .id = O_TO_SRC, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_MULTI},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE},
+	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+/* Ranges expected in network order. */
+static void
+parse_to(const char *orig_arg, int portok, struct nf_nat_range *range)
+{
+	char *arg, *start, *end = NULL, *colon = NULL, *dash, *error;
+	const struct in6_addr *ip;
+
+	arg = strdup(orig_arg);
+	if (arg == NULL)
+		xtables_error(RESOURCE_PROBLEM, "strdup");
+
+	start = strchr(arg, '[');
+	if (start == NULL) {
+		start = arg;
+		/* Lets assume one colon is port information. Otherwise its an IPv6 address */
+		colon = strchr(arg, ':');
+		if (colon && strchr(colon+1, ':'))
+			colon = NULL;
+	}
+	else {
+		start++;
+		end = strchr(start, ']');
+		if (end == NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid address format");
+
+		*end = '\0';
+		colon = strchr(end + 1, ':');
+	}
+
+	if (colon) {
+		int port;
+
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+
+		range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+		port = atoi(colon+1);
+		if (port <= 0 || port > 65535)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Port `%s' not valid\n", colon+1);
+
+		error = strchr(colon+1, ':');
+		if (error)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid port:port syntax - use dash\n");
+
+		dash = strchr(colon, '-');
+		if (!dash) {
+			range->min_proto.tcp.port
+				= range->max_proto.tcp.port
+				= htons(port);
+		} else {
+			int maxport;
+
+			maxport = atoi(dash + 1);
+			if (maxport <= 0 || maxport > 65535)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port `%s' not valid\n", dash+1);
+			if (maxport < port)
+				/* People are stupid. */
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port range `%s' funky\n", colon+1);
+			range->min_proto.tcp.port = htons(port);
+			range->max_proto.tcp.port = htons(maxport);
+		}
+		/* Starts with colon or [] colon? No IP info...*/
+		if (colon == arg || colon == arg+2) {
+			free(arg);
+			return;
+		}
+		*colon = '\0';
+	}
+
+	range->flags |= NF_NAT_RANGE_MAP_IPS;
+	dash = strchr(start, '-');
+	if (colon && dash && dash > colon)
+		dash = NULL;
+
+	if (dash)
+		*dash = '\0';
+
+	ip = xtables_numeric_to_ip6addr(start);
+	if (!ip)
+		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+			      start);
+	range->min_addr.in6 = *ip;
+	if (dash) {
+		ip = xtables_numeric_to_ip6addr(dash + 1);
+		if (!ip)
+			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+				      dash+1);
+		range->max_addr.in6 = *ip;
+	} else
+		range->max_addr = range->min_addr;
+
+	free(arg);
+	return;
+}
+
+static void SNAT_parse(struct xt_option_call *cb)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	struct nf_nat_range *range = cb->data;
+	int portok;
+
+	if (entry->ipv6.proto == IPPROTO_TCP ||
+	    entry->ipv6.proto == IPPROTO_UDP ||
+	    entry->ipv6.proto == IPPROTO_SCTP ||
+	    entry->ipv6.proto == IPPROTO_DCCP ||
+	    entry->ipv6.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_SRC:
+		if (cb->xflags & F_X_TO_SRC) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "SNAT: Multiple --to-source not supported");
+		}
+		parse_to(cb->arg, portok, range);
+		cb->xflags |= F_X_TO_SRC;
+		break;
+	case O_PERSISTENT:
+		range->flags |= NF_NAT_RANGE_PERSISTENT;
+		break;
+	}
+}
+
+static void SNAT_fcheck(struct xt_fcheck_call *cb)
+{
+	static const unsigned int f = F_TO_SRC | F_RANDOM;
+	static const unsigned int r = F_TO_SRC | F_RANDOM_FULLY;
+	struct nf_nat_range *range = cb->data;
+
+	if ((cb->xflags & f) == f)
+		range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
+	if ((cb->xflags & r) == r)
+		range->flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY;
+}
+
+static void print_range(const struct nf_nat_range *range)
+{
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
+			printf("[");
+		printf("%s", xtables_ip6addr_to_numeric(&range->min_addr.in6));
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr)))
+			printf("-%s", xtables_ip6addr_to_numeric(&range->max_addr.in6));
+		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
+			printf("]");
+	}
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(":");
+		printf("%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			printf("-%hu", ntohs(range->max_proto.tcp.port));
+	}
+}
+
+static void SNAT_print(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+	const struct nf_nat_range *range = (const void *)target->data;
+
+	printf(" to:");
+	print_range(range);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" random");
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" random-fully");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" persistent");
+}
+
+static void SNAT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_range *range = (const void *)target->data;
+
+	printf(" --to-source ");
+	print_range(range);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" --random");
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" --random-fully");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" --persistent");
+}
+
+static void print_range_xlate(const struct nf_nat_range *range,
+			      struct xt_xlate *xl)
+{
+	bool proto_specified = range->flags & NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		xt_xlate_add(xl, "%s%s%s",
+			     proto_specified ? "[" : "",
+			     xtables_ip6addr_to_numeric(&range->min_addr.in6),
+			     proto_specified ? "]" : "");
+
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr))) {
+			xt_xlate_add(xl, "-%s%s%s",
+				     proto_specified ? "[" : "",
+				     xtables_ip6addr_to_numeric(&range->max_addr.in6),
+				     proto_specified ? "]" : "");
+		}
+	}
+	if (proto_specified) {
+		xt_xlate_add(xl, ":%hu", ntohs(range->min_proto.tcp.port));
+
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			xt_xlate_add(xl, "-%hu",
+				   ntohs(range->max_proto.tcp.port));
+	}
+}
+
+static int SNAT_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_range *range = (const void *)params->target->data;
+	bool sep_need = false;
+	const char *sep = " ";
+
+	xt_xlate_add(xl, "snat to ");
+	print_range_xlate(range, xl);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM) {
+		xt_xlate_add(xl, " random");
+		sep_need = true;
+	}
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) {
+		if (sep_need)
+			sep = ",";
+		xt_xlate_add(xl, "%sfully-random", sep);
+		sep_need = true;
+	}
+	if (range->flags & NF_NAT_RANGE_PERSISTENT) {
+		if (sep_need)
+			sep = ",";
+		xt_xlate_add(xl, "%spersistent", sep);
+	}
+
+	return 1;
+}
+
+static struct xtables_target snat_tg_reg = {
+	.name		= "SNAT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.revision	= 1,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.help		= SNAT_help,
+	.x6_parse	= SNAT_parse,
+	.x6_fcheck	= SNAT_fcheck,
+	.print		= SNAT_print,
+	.save		= SNAT_save,
+	.x6_options	= SNAT_opts,
+	.xlate		= SNAT_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&snat_tg_reg);
+}
diff --git a/extensions/libip6t_SNAT.t b/extensions/libip6t_SNAT.t
new file mode 100644
index 0000000..d188a6b
--- /dev/null
+++ b/extensions/libip6t_SNAT.t
@@ -0,0 +1,11 @@
+:POSTROUTING
+*nat
+-j SNAT --to-source dead::beef;=;OK
+-j SNAT --to-source dead::beef-dead::fee7;=;OK
+-j SNAT --to-source [dead::beef]:1025-65535;;FAIL
+-j SNAT --to-source [dead::beef] --to-source [dead::fee7];;FAIL
+-p tcp -j SNAT --to-source [dead::beef]:1025-65535;=;OK
+-p tcp -j SNAT --to-source [dead::beef-dead::fee7]:1025-65535;=;OK
+-p tcp -j SNAT --to-source [dead::beef-dead::fee7]:1025-65536;;FAIL
+-p tcp -j SNAT --to-source [dead::beef-dead::fee7]:1025-65535 --to-source [dead::beef-dead::fee8]:1025-65535;;FAIL
+-j SNAT;;FAIL
diff --git a/extensions/libip6t_SNAT.txlate b/extensions/libip6t_SNAT.txlate
new file mode 100644
index 0000000..44f2fce
--- /dev/null
+++ b/extensions/libip6t_SNAT.txlate
@@ -0,0 +1,11 @@
+ip6tables-translate -t nat -A postrouting -o eth0 -p tcp -j SNAT --to [fec0::1234]:80
+nft add rule ip6 nat postrouting oifname "eth0" meta l4proto tcp counter snat to [fec0::1234]:80
+
+ip6tables-translate -t nat -A postrouting -o eth0 -p tcp -j SNAT --to [fec0::1234]:1-20
+nft add rule ip6 nat postrouting oifname "eth0" meta l4proto tcp counter snat to [fec0::1234]:1-20
+
+ip6tables-translate -t nat -A postrouting -o eth0 -p tcp -j SNAT --to [fec0::1234]:123 --random
+nft add rule ip6 nat postrouting oifname "eth0" meta l4proto tcp counter snat to [fec0::1234]:123 random
+
+ip6tables-translate -t nat -A postrouting -o eth0 -p tcp -j SNAT --to [fec0::1234]:123 --random-fully --persistent
+nft add rule ip6 nat postrouting oifname "eth0" meta l4proto tcp counter snat to [fec0::1234]:123 fully-random,persistent
diff --git a/extensions/libip6t_SNPT.c b/extensions/libip6t_SNPT.c
new file mode 100644
index 0000000..65f787d
--- /dev/null
+++ b/extensions/libip6t_SNPT.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2012-2013 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_NPT.h>
+
+enum {
+	O_SRC_PFX	= 1 << 0,
+	O_DST_PFX	= 1 << 1,
+};
+
+static const struct xt_option_entry SNPT_options[] = {
+	{ .name = "src-pfx", .id = O_SRC_PFX, .type = XTTYPE_HOSTMASK,
+	  .flags = XTOPT_MAND },
+	{ .name = "dst-pfx", .id = O_DST_PFX, .type = XTTYPE_HOSTMASK,
+	  .flags = XTOPT_MAND },
+	{ }
+};
+
+static void SNPT_help(void)
+{
+	printf("SNPT target options:"
+	       "\n"
+	       " --src-pfx prefix/length\n"
+	       " --dst-pfx prefix/length\n"
+	       "\n");
+}
+
+static void SNPT_parse(struct xt_option_call *cb)
+{
+	struct ip6t_npt_tginfo *npt = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRC_PFX:
+		npt->src_pfx = cb->val.haddr;
+		npt->src_pfx_len = cb->val.hlen;
+		break;
+	case O_DST_PFX:
+		npt->dst_pfx = cb->val.haddr;
+		npt->dst_pfx_len = cb->val.hlen;
+		break;
+	}
+}
+
+static void SNPT_print(const void *ip, const struct xt_entry_target *target,
+		       int numeric)
+{
+	const struct ip6t_npt_tginfo *npt = (const void *)target->data;
+
+	printf(" SNPT src-pfx %s/%u", xtables_ip6addr_to_numeric(&npt->src_pfx.in6),
+				 npt->src_pfx_len);
+	printf(" dst-pfx %s/%u", xtables_ip6addr_to_numeric(&npt->dst_pfx.in6),
+				 npt->dst_pfx_len);
+}
+
+static void SNPT_save(const void *ip, const struct xt_entry_target *target)
+{
+	static const struct in6_addr zero_addr;
+	const struct ip6t_npt_tginfo *info = (const void *)target->data;
+
+	if (memcmp(&info->src_pfx.in6, &zero_addr, sizeof(zero_addr)) != 0 ||
+	    info->src_pfx_len != 0)
+		printf(" --src-pfx %s/%u",
+		       xtables_ip6addr_to_numeric(&info->src_pfx.in6),
+		       info->src_pfx_len);
+	if (memcmp(&info->dst_pfx.in6, &zero_addr, sizeof(zero_addr)) != 0 ||
+	    info->dst_pfx_len != 0)
+		printf(" --dst-pfx %s/%u",
+		       xtables_ip6addr_to_numeric(&info->dst_pfx.in6),
+		       info->dst_pfx_len);
+}
+
+static struct xtables_target snpt_tg_reg = {
+	.name		= "SNPT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct ip6t_npt_tginfo)),
+	.userspacesize	= offsetof(struct ip6t_npt_tginfo, adjustment),
+	.help		= SNPT_help,
+	.x6_parse	= SNPT_parse,
+	.print		= SNPT_print,
+	.save		= SNPT_save,
+	.x6_options	= SNPT_options,
+};
+
+void _init(void)
+{
+	xtables_register_target(&snpt_tg_reg);
+}
diff --git a/extensions/libip6t_SNPT.man b/extensions/libip6t_SNPT.man
new file mode 100644
index 0000000..97e0071
--- /dev/null
+++ b/extensions/libip6t_SNPT.man
@@ -0,0 +1,30 @@
+Provides stateless source IPv6-to-IPv6 Network Prefix Translation (as described
+by RFC 6296).
+.PP
+You have to use this target in the
+.B mangle
+table, not in the
+.B nat
+table. It takes the following options:
+.TP
+\fB\-\-src\-pfx\fP [\fIprefix/\fP\fIlength]
+Set source prefix that you want to translate and length
+.TP
+\fB\-\-dst\-pfx\fP [\fIprefix/\fP\fIlength]
+Set destination prefix that you want to use in the translation and length
+.PP
+You have to use the DNPT target to undo the translation. Example:
+.IP
+ip6tables \-t mangle \-I POSTROUTING \-s fd00::/64 \! \-o vboxnet0
+\-j SNPT \-\-src-pfx fd00::/64 \-\-dst-pfx 2001:e20:2000:40f::/64
+.IP
+ip6tables \-t mangle \-I PREROUTING \-i wlan0 \-d 2001:e20:2000:40f::/64
+\-j DNPT \-\-src-pfx 2001:e20:2000:40f::/64 \-\-dst-pfx fd00::/64
+.PP
+You may need to enable IPv6 neighbor proxy:
+.IP
+sysctl \-w net.ipv6.conf.all.proxy_ndp=1
+.PP
+You also have to use the
+.B NOTRACK
+target to disable connection tracking for translated flows.
diff --git a/extensions/libip6t_SNPT.t b/extensions/libip6t_SNPT.t
new file mode 100644
index 0000000..7ed6d0c
--- /dev/null
+++ b/extensions/libip6t_SNPT.t
@@ -0,0 +1,7 @@
+:INPUT,POSTROUTING
+*mangle
+-j SNPT --src-pfx dead::/64 --dst-pfx 1c3::/64;=;OK
+-j SNPT --src-pfx dead::beef --dst-pfx 1c3::/64;;FAIL
+-j SNPT --src-pfx dead::/64;;FAIL
+-j SNPT --dst-pfx dead::/64;;FAIL
+-j SNPT;;FAIL
diff --git a/extensions/libip6t_ah.c b/extensions/libip6t_ah.c
new file mode 100644
index 0000000..f35982f
--- /dev/null
+++ b/extensions/libip6t_ah.c
@@ -0,0 +1,185 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_ah.h>
+
+enum {
+	O_AHSPI = 0,
+	O_AHLEN,
+	O_AHRES,
+};
+
+static void ah_help(void)
+{
+	printf(
+"ah match options:\n"
+"[!] --ahspi spi[:spi]          match spi (range)\n"
+"[!] --ahlen length             total length of this header\n"
+" --ahres                       check the reserved field too\n");
+}
+
+#define s struct ip6t_ah
+static const struct xt_option_entry ah_opts[] = {
+	{.name = "ahspi", .id = O_AHSPI, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, spis)},
+	{.name = "ahlen", .id = O_AHLEN, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdrlen)},
+	{.name = "ahres", .id = O_AHRES, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void ah_init(struct xt_entry_match *m)
+{
+	struct ip6t_ah *ahinfo = (void *)m->data;
+
+	/* Defaults for when no --ahspi is used at all */
+	ahinfo->spis[1] = ~0U;
+}
+
+static void ah_parse(struct xt_option_call *cb)
+{
+	struct ip6t_ah *ahinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_AHSPI:
+		if (cb->nvals == 1)
+			ahinfo->spis[1] = ahinfo->spis[0];
+		if (cb->invert)
+			ahinfo->invflags |= IP6T_AH_INV_SPI;
+		break;
+	case O_AHLEN:
+		if (cb->invert)
+			ahinfo->invflags |= IP6T_AH_INV_LEN;
+		break;
+	case O_AHRES:
+		ahinfo->hdrres = 1;
+		break;
+	}
+}
+
+static void
+print_spis(const char *name, uint32_t min, uint32_t max,
+	    int invert)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFFFFFF || invert) {
+		if (min == max)
+			printf("%s:%s%u", name, inv, min);
+		else
+			printf("%ss:%s%u:%u", name, inv, min, max);
+	}
+}
+
+static void
+print_len(const char *name, uint32_t len, int invert)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (len != 0 || invert)
+		printf("%s:%s%u", name, inv, len);
+}
+
+static void ah_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	const struct ip6t_ah *ah = (struct ip6t_ah *)match->data;
+
+	printf(" ah ");
+	print_spis("spi", ah->spis[0], ah->spis[1],
+		    ah->invflags & IP6T_AH_INV_SPI);
+	print_len("length", ah->hdrlen, 
+		    ah->invflags & IP6T_AH_INV_LEN);
+
+	if (ah->hdrres)
+		printf(" reserved");
+
+	if (ah->invflags & ~IP6T_AH_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       ah->invflags & ~IP6T_AH_INV_MASK);
+}
+
+static void ah_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_ah *ahinfo = (struct ip6t_ah *)match->data;
+
+	if (!(ahinfo->spis[0] == 0
+	    && ahinfo->spis[1] == 0xFFFFFFFF)) {
+		printf("%s --ahspi ",
+			(ahinfo->invflags & IP6T_AH_INV_SPI) ? " !" : "");
+		if (ahinfo->spis[0]
+		    != ahinfo->spis[1])
+			printf("%u:%u",
+			       ahinfo->spis[0],
+			       ahinfo->spis[1]);
+		else
+			printf("%u",
+			       ahinfo->spis[0]);
+	}
+
+	if (ahinfo->hdrlen != 0 || (ahinfo->invflags & IP6T_AH_INV_LEN) ) {
+		printf("%s --ahlen %u",
+			(ahinfo->invflags & IP6T_AH_INV_LEN) ? " !" : "",
+			ahinfo->hdrlen);
+	}
+
+	if (ahinfo->hdrres != 0 )
+		printf(" --ahres");
+}
+
+static int ah_xlate(struct xt_xlate *xl,
+		    const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_ah *ahinfo = (struct ip6t_ah *)params->match->data;
+	char *space = "";
+
+	if (!(ahinfo->spis[0] == 0 && ahinfo->spis[1] == 0xFFFFFFFF)) {
+		xt_xlate_add(xl, "ah spi%s ",
+			(ahinfo->invflags & IP6T_AH_INV_SPI) ? " !=" : "");
+		if (ahinfo->spis[0] != ahinfo->spis[1])
+			xt_xlate_add(xl, "%u-%u", ahinfo->spis[0],
+				     ahinfo->spis[1]);
+		else
+			xt_xlate_add(xl, "%u", ahinfo->spis[0]);
+		space = " ";
+	}
+
+	if (ahinfo->hdrlen != 0 || (ahinfo->invflags & IP6T_AH_INV_LEN)) {
+		xt_xlate_add(xl, "%sah hdrlength%s %u", space,
+			     (ahinfo->invflags & IP6T_AH_INV_LEN) ? " !=" : "",
+			     ahinfo->hdrlen);
+		space = " ";
+	}
+
+	if (ahinfo->hdrres != 0) {
+		xt_xlate_add(xl, "%sah reserved %u", space, ahinfo->hdrres);
+		space = " ";
+	}
+
+	if (!space[0]) /* plain '-m ah' */
+		xt_xlate_add(xl, "meta l4proto ah");
+
+	return 1;
+}
+
+static struct xtables_match ah_mt6_reg = {
+	.name          = "ah",
+	.version       = XTABLES_VERSION,
+	.family        = NFPROTO_IPV6,
+	.size          = XT_ALIGN(sizeof(struct ip6t_ah)),
+	.userspacesize = XT_ALIGN(sizeof(struct ip6t_ah)),
+	.help          = ah_help,
+	.init          = ah_init,
+	.print         = ah_print,
+	.save          = ah_save,
+	.x6_parse      = ah_parse,
+	.x6_options    = ah_opts,
+	.xlate	       = ah_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&ah_mt6_reg);
+}
diff --git a/extensions/libip6t_ah.man b/extensions/libip6t_ah.man
new file mode 100644
index 0000000..9c24dcf
--- /dev/null
+++ b/extensions/libip6t_ah.man
@@ -0,0 +1,10 @@
+This module matches the parameters in Authentication header of IPsec packets.
+.TP
+[\fB!\fP] \fB\-\-ahspi\fP \fIspi\fP[\fB:\fP\fIspi\fP]
+Matches SPI.
+.TP
+[\fB!\fP] \fB\-\-ahlen\fP \fIlength\fP
+Total length of this header in octets.
+.TP
+\fB\-\-ahres\fP
+Matches if the reserved field is filled with zero.
diff --git a/extensions/libip6t_ah.t b/extensions/libip6t_ah.t
new file mode 100644
index 0000000..c1898d4
--- /dev/null
+++ b/extensions/libip6t_ah.t
@@ -0,0 +1,15 @@
+:INPUT,FORWARD,OUTPUT
+-m ah --ahspi 0;=;OK
+-m ah --ahspi 4294967295;=;OK
+-m ah --ahspi 0:4294967295;-m ah;OK
+-m ah ! --ahspi 0;=;OK
+# ERROR: should fail: iptables -A FORWARD -t mangle -j CLASSIFY --set-class 1:-1
+# -m ah --ahres;=;OK
+# ERROR: line 7 (cannot find: ip6tables -I INPUT -m ah --ahlen 32
+# -m ah --ahlen 32;=;OK
+-m ah --ahspi -1;;FAIL
+-m ah --ahspi 4294967296;;FAIL
+-m ah --ahspi invalid;;FAIL
+-m ah --ahspi 0:invalid;;FAIL
+-m ah --ahspi;;FAIL
+-m ah;=;OK
diff --git a/extensions/libip6t_ah.txlate b/extensions/libip6t_ah.txlate
new file mode 100644
index 0000000..c6b09a2
--- /dev/null
+++ b/extensions/libip6t_ah.txlate
@@ -0,0 +1,17 @@
+ip6tables-translate -A INPUT -m ah --ahspi 500 -j DROP
+nft add rule ip6 filter INPUT ah spi 500 counter drop
+
+ip6tables-translate -A INPUT -m ah --ahspi 500:550 -j DROP
+nft add rule ip6 filter INPUT ah spi 500-550 counter drop
+
+ip6tables-translate -A INPUT -m ah ! --ahlen 120
+nft add rule ip6 filter INPUT ah hdrlength != 120 counter
+
+ip6tables-translate -A INPUT -m ah --ahres
+nft add rule ip6 filter INPUT ah reserved 1 counter
+
+ip6tables-translate -A INPUT -m ah --ahspi 500 ! --ahlen 120 -j DROP
+nft add rule ip6 filter INPUT ah spi 500 ah hdrlength != 120 counter drop
+
+ip6tables-translate -A INPUT -m ah --ahspi 500 --ahlen 120 --ahres -j ACCEPT
+nft add rule ip6 filter INPUT ah spi 500 ah hdrlength 120 ah reserved 1 counter accept
diff --git a/extensions/libip6t_dst.c b/extensions/libip6t_dst.c
new file mode 100644
index 0000000..fe7e340
--- /dev/null
+++ b/extensions/libip6t_dst.c
@@ -0,0 +1,196 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_opts.h>
+
+enum {
+	O_DSTLEN = 0,
+	O_DSTOPTS,
+};
+
+static void dst_help(void)
+{
+	printf(
+"dst match options:\n"
+"[!] --dst-len length            total length of this header\n"
+"  --dst-opts TYPE[:LEN][,TYPE[:LEN]...]\n"
+"                                Options and its length (list, max: %d)\n",
+IP6T_OPTS_OPTSNR);
+}
+
+static const struct xt_option_entry dst_opts[] = {
+	{.name = "dst-len", .id = O_DSTLEN, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct ip6t_opts, hdrlen)},
+	{.name = "dst-opts", .id = O_DSTOPTS, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+
+static uint32_t
+parse_opts_num(const char *idstr, const char *typestr)
+{
+	unsigned long int id;
+	char* ep;
+
+	id = strtoul(idstr, &ep, 0);
+
+	if ( idstr == ep ) {
+		xtables_error(PARAMETER_PROBLEM,
+		           "dst: no valid digits in %s `%s'", typestr, idstr);
+	}
+	if ( id == ULONG_MAX  && errno == ERANGE ) {
+		xtables_error(PARAMETER_PROBLEM,
+			   "%s `%s' specified too big: would overflow",
+			   typestr, idstr);
+	}
+	if ( *idstr != '\0'  && *ep != '\0' ) {
+		xtables_error(PARAMETER_PROBLEM,
+		           "dst: error parsing %s `%s'", typestr, idstr);
+	}
+	return id;
+}
+
+static int
+parse_options(const char *optsstr, uint16_t *opts)
+{
+        char *buffer, *cp, *next, *range;
+        unsigned int i;
+	
+	buffer = strdup(optsstr);
+        if (!buffer)
+		xtables_error(OTHER_PROBLEM, "strdup failed");
+			
+        for (cp = buffer, i = 0; cp && i < IP6T_OPTS_OPTSNR; cp = next, i++)
+        {
+                next = strchr(cp, ',');
+
+                if (next)
+			*next++='\0';
+
+                range = strchr(cp, ':');
+
+                if (range) {
+                        if (i == IP6T_OPTS_OPTSNR-1)
+				xtables_error(PARAMETER_PROBLEM,
+                                           "too many ports specified");
+                        *range++ = '\0';
+                }
+
+		opts[i] = (parse_opts_num(cp, "opt") & 0xFF) << 8;
+                if (range) {
+			if (opts[i] == 0)
+				xtables_error(PARAMETER_PROBLEM,
+					"PAD0 hasn't got length");
+			opts[i] |= parse_opts_num(range, "length") & 0xFF;
+                } else
+                        opts[i] |= (0x00FF);
+
+#ifdef DEBUG
+		printf("opts str: %s %s\n", cp, range);
+		printf("opts opt: %04X\n", opts[i]);
+#endif
+	}
+
+        if (cp)
+		xtables_error(PARAMETER_PROBLEM, "too many addresses specified");
+
+	free(buffer);
+
+#ifdef DEBUG
+	printf("addr nr: %d\n", i);
+#endif
+
+	return i;
+}
+
+static void dst_parse(struct xt_option_call *cb)
+{
+	struct ip6t_opts *optinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_DSTLEN:
+		if (cb->invert)
+			optinfo->invflags |= IP6T_OPTS_INV_LEN;
+		optinfo->flags |= IP6T_OPTS_LEN;
+		break;
+	case O_DSTOPTS:
+		optinfo->optsnr = parse_options(cb->arg, optinfo->opts);
+		optinfo->flags |= IP6T_OPTS_OPTS;
+		break;
+	}
+}
+
+static void
+print_options(unsigned int optsnr, uint16_t *optsp)
+{
+	unsigned int i;
+
+	printf(" ");
+	for(i = 0; i < optsnr; i++) {
+		printf("%d", (optsp[i] & 0xFF00) >> 8);
+
+		if ((optsp[i] & 0x00FF) != 0x00FF)
+			printf(":%d", (optsp[i] & 0x00FF));
+
+		printf("%c", (i != optsnr - 1) ? ',' : ' ');
+	}
+}
+
+static void dst_print(const void *ip, const struct xt_entry_match *match,
+                      int numeric)
+{
+	const struct ip6t_opts *optinfo = (struct ip6t_opts *)match->data;
+
+	printf(" dst");
+	if (optinfo->flags & IP6T_OPTS_LEN)
+		printf(" length:%s%u",
+			optinfo->invflags & IP6T_OPTS_INV_LEN ? "!" : "",
+			optinfo->hdrlen);
+
+	if (optinfo->flags & IP6T_OPTS_OPTS)
+		printf(" opts");
+
+	print_options(optinfo->optsnr, (uint16_t *)optinfo->opts);
+
+	if (optinfo->invflags & ~IP6T_OPTS_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       optinfo->invflags & ~IP6T_OPTS_INV_MASK);
+}
+
+static void dst_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_opts *optinfo = (struct ip6t_opts *)match->data;
+
+	if (optinfo->flags & IP6T_OPTS_LEN) {
+		printf("%s --dst-len %u",
+			(optinfo->invflags & IP6T_OPTS_INV_LEN) ? " !" : "",
+			optinfo->hdrlen);
+	}
+
+	if (optinfo->flags & IP6T_OPTS_OPTS)
+		printf(" --dst-opts");
+
+	print_options(optinfo->optsnr, (uint16_t *)optinfo->opts);
+}
+
+static struct xtables_match dst_mt6_reg = {
+	.name          = "dst",
+	.version       = XTABLES_VERSION,
+	.family        = NFPROTO_IPV6,
+	.size          = XT_ALIGN(sizeof(struct ip6t_opts)),
+	.userspacesize = XT_ALIGN(sizeof(struct ip6t_opts)),
+	.help          = dst_help,
+	.print         = dst_print,
+	.save          = dst_save,
+	.x6_parse      = dst_parse,
+	.x6_options    = dst_opts,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&dst_mt6_reg);
+}
diff --git a/extensions/libip6t_dst.man b/extensions/libip6t_dst.man
new file mode 100644
index 0000000..bfbb501
--- /dev/null
+++ b/extensions/libip6t_dst.man
@@ -0,0 +1,7 @@
+This module matches the parameters in Destination Options header
+.TP
+[\fB!\fP] \fB\-\-dst\-len\fP \fIlength\fP
+Total length of this header in octets.
+.TP
+\fB\-\-dst\-opts\fP \fItype\fP[\fB:\fP\fIlength\fP][\fB,\fP\fItype\fP[\fB:\fP\fIlength\fP]...]
+numeric type of option and the length of the option data in octets.
diff --git a/extensions/libip6t_dst.t b/extensions/libip6t_dst.t
new file mode 100644
index 0000000..0b0013b
--- /dev/null
+++ b/extensions/libip6t_dst.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-m dst --dst-len 0;=;OK
+-m dst --dst-opts 149:92,12:12,123:12;=;OK
+-m dst ! --dst-len 42;=;OK
+-m dst --dst-len 42 --dst-opts 149:92,12:12,123:12;=;OK
diff --git a/extensions/libip6t_eui64.c b/extensions/libip6t_eui64.c
new file mode 100644
index 0000000..607bf86
--- /dev/null
+++ b/extensions/libip6t_eui64.c
@@ -0,0 +1,15 @@
+/* Shared library add-on to ip6tables to add EUI64 address checking support. */
+#include <xtables.h>
+
+static struct xtables_match eui64_mt6_reg = {
+	.name 		= "eui64",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(int)),
+	.userspacesize	= XT_ALIGN(sizeof(int)),
+};
+
+void _init(void)
+{
+	xtables_register_match(&eui64_mt6_reg);
+}
diff --git a/extensions/libip6t_eui64.man b/extensions/libip6t_eui64.man
new file mode 100644
index 0000000..cd80b98
--- /dev/null
+++ b/extensions/libip6t_eui64.man
@@ -0,0 +1,10 @@
+This module matches the EUI-64 part of a stateless autoconfigured IPv6 address.
+It compares the EUI-64 derived from the source MAC address in Ethernet frame
+with the lower 64 bits of the IPv6 source address. But "Universal/Local"
+bit is not compared. This module doesn't match other link layer frame, and
+is only valid in the
+.BR PREROUTING ,
+.BR INPUT
+and
+.BR FORWARD
+chains.
diff --git a/extensions/libip6t_eui64.t b/extensions/libip6t_eui64.t
new file mode 100644
index 0000000..e5aaaac
--- /dev/null
+++ b/extensions/libip6t_eui64.t
@@ -0,0 +1,8 @@
+:PREROUTING
+*raw
+-m eui64;=;OK
+:INPUT,FORWARD
+*filter
+-m eui64;=;OK
+:OUTPUT
+-m eui64;;FAIL
diff --git a/extensions/libip6t_frag.c b/extensions/libip6t_frag.c
new file mode 100644
index 0000000..3842496
--- /dev/null
+++ b/extensions/libip6t_frag.c
@@ -0,0 +1,234 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_frag.h>
+
+enum {
+	O_FRAGID = 0,
+	O_FRAGLEN,
+	O_FRAGRES,
+	O_FRAGFIRST,
+	O_FRAGMORE,
+	O_FRAGLAST,
+	F_FRAGMORE = 1 << O_FRAGMORE,
+	F_FRAGLAST = 1 << O_FRAGLAST,
+};
+
+static void frag_help(void)
+{
+	printf(
+"frag match options:\n"
+"[!] --fragid id[:id]           match the id (range)\n"
+"[!] --fraglen length           total length of this header\n"
+" --fragres                     check the reserved field too\n"
+" --fragfirst                   matches on the first fragment\n"
+" [--fragmore|--fraglast]       there are more fragments or this\n"
+"                               is the last one\n");
+}
+
+#define s struct ip6t_frag
+static const struct xt_option_entry frag_opts[] = {
+	{.name = "fragid", .id = O_FRAGID, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, ids)},
+	{.name = "fraglen", .id = O_FRAGLEN, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdrlen)},
+	{.name = "fragres", .id = O_FRAGRES, .type = XTTYPE_NONE},
+	{.name = "fragfirst", .id = O_FRAGFIRST, .type = XTTYPE_NONE},
+	{.name = "fragmore", .id = O_FRAGMORE, .type = XTTYPE_NONE,
+	 .excl = F_FRAGLAST},
+	{.name = "fraglast", .id = O_FRAGLAST, .type = XTTYPE_NONE,
+	 .excl = F_FRAGMORE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void frag_init(struct xt_entry_match *m)
+{
+	struct ip6t_frag *fraginfo = (void *)m->data;
+
+	fraginfo->ids[1] = ~0U;
+}
+
+static void frag_parse(struct xt_option_call *cb)
+{
+	struct ip6t_frag *fraginfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_FRAGID:
+		if (cb->nvals == 1)
+			fraginfo->ids[1] = fraginfo->ids[0];
+		if (cb->invert)
+			fraginfo->invflags |= IP6T_FRAG_INV_IDS;
+		/*
+		 * Note however that IP6T_FRAG_IDS is not tested by anything,
+		 * so it is merely here for completeness.
+		 */
+		fraginfo->flags |= IP6T_FRAG_IDS;
+		break;
+	case O_FRAGLEN:
+		/*
+		 * As of Linux 3.0, the kernel does not check for
+		 * fraglen at all.
+		 */
+		if (cb->invert)
+			fraginfo->invflags |= IP6T_FRAG_INV_LEN;
+		fraginfo->flags |= IP6T_FRAG_LEN;
+		break;
+	case O_FRAGRES:
+		fraginfo->flags |= IP6T_FRAG_RES;
+		break;
+	case O_FRAGFIRST:
+		fraginfo->flags |= IP6T_FRAG_FST;
+		break;
+	case O_FRAGMORE:
+		fraginfo->flags |= IP6T_FRAG_MF;
+		break;
+	case O_FRAGLAST:
+		fraginfo->flags |= IP6T_FRAG_NMF;
+		break;
+	}
+}
+
+static void
+print_ids(const char *name, uint32_t min, uint32_t max,
+	    int invert)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFFFFFF || invert) {
+		printf("%s", name);
+		if (min == max)
+			printf(":%s%u", inv, min);
+		else
+			printf("s:%s%u:%u", inv, min, max);
+	}
+}
+
+static void frag_print(const void *ip, const struct xt_entry_match *match,
+                       int numeric)
+{
+	const struct ip6t_frag *frag = (struct ip6t_frag *)match->data;
+
+	printf(" frag ");
+	print_ids("id", frag->ids[0], frag->ids[1],
+		    frag->invflags & IP6T_FRAG_INV_IDS);
+
+	if (frag->flags & IP6T_FRAG_LEN) {
+		printf(" length:%s%u",
+			frag->invflags & IP6T_FRAG_INV_LEN ? "!" : "",
+			frag->hdrlen);
+	}
+
+	if (frag->flags & IP6T_FRAG_RES)
+		printf(" reserved");
+
+	if (frag->flags & IP6T_FRAG_FST)
+		printf(" first");
+
+	if (frag->flags & IP6T_FRAG_MF)
+		printf(" more");
+
+	if (frag->flags & IP6T_FRAG_NMF)
+		printf(" last");
+
+	if (frag->invflags & ~IP6T_FRAG_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       frag->invflags & ~IP6T_FRAG_INV_MASK);
+}
+
+static void frag_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_frag *fraginfo = (struct ip6t_frag *)match->data;
+
+	if (!(fraginfo->ids[0] == 0
+	    && fraginfo->ids[1] == 0xFFFFFFFF)) {
+		printf("%s --fragid ",
+			(fraginfo->invflags & IP6T_FRAG_INV_IDS) ? " !" : "");
+		if (fraginfo->ids[0]
+		    != fraginfo->ids[1])
+			printf("%u:%u",
+			       fraginfo->ids[0],
+			       fraginfo->ids[1]);
+		else
+			printf("%u",
+			       fraginfo->ids[0]);
+	}
+
+	if (fraginfo->flags & IP6T_FRAG_LEN) {
+		printf("%s --fraglen %u",
+			(fraginfo->invflags & IP6T_FRAG_INV_LEN) ? " !" : "",
+			fraginfo->hdrlen);
+	}
+
+	if (fraginfo->flags & IP6T_FRAG_RES)
+		printf(" --fragres");
+
+	if (fraginfo->flags & IP6T_FRAG_FST)
+		printf(" --fragfirst");
+
+	if (fraginfo->flags & IP6T_FRAG_MF)
+		printf(" --fragmore");
+
+	if (fraginfo->flags & IP6T_FRAG_NMF)
+		printf(" --fraglast");
+}
+
+static int frag_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_frag *fraginfo =
+		(struct ip6t_frag *)params->match->data;
+	char *space= "";
+
+	if (!(fraginfo->ids[0] == 0 && fraginfo->ids[1] == 0xFFFFFFFF)) {
+		xt_xlate_add(xl, "frag id %s",
+			     (fraginfo->invflags & IP6T_FRAG_INV_IDS) ?
+			     "!= " : "");
+		if (fraginfo->ids[0] != fraginfo->ids[1])
+			xt_xlate_add(xl, "%u-%u", fraginfo->ids[0],
+				     fraginfo->ids[1]);
+		else
+			xt_xlate_add(xl, "%u", fraginfo->ids[0]);
+
+		space = " ";
+	}
+
+	if (fraginfo->flags & IP6T_FRAG_RES) {
+		xt_xlate_add(xl, "%sfrag reserved 1", space);
+		space = " ";
+	}
+	if (fraginfo->flags & IP6T_FRAG_FST) {
+		xt_xlate_add(xl, "%sfrag frag-off 0", space);
+		space = " ";
+	}
+	if (fraginfo->flags & IP6T_FRAG_MF) {
+		xt_xlate_add(xl, "%sfrag more-fragments 1", space);
+		space = " ";
+	}
+	if (fraginfo->flags & IP6T_FRAG_NMF) {
+		xt_xlate_add(xl, "%sfrag more-fragments 0", space);
+	}
+
+	return 1;
+}
+
+static struct xtables_match frag_mt6_reg = {
+	.name          = "frag",
+	.version       = XTABLES_VERSION,
+	.family        = NFPROTO_IPV6,
+	.size          = XT_ALIGN(sizeof(struct ip6t_frag)),
+	.userspacesize = XT_ALIGN(sizeof(struct ip6t_frag)),
+	.help          = frag_help,
+	.init          = frag_init,
+	.print         = frag_print,
+	.save          = frag_save,
+	.x6_parse      = frag_parse,
+	.x6_options    = frag_opts,
+	.xlate	       = frag_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&frag_mt6_reg);
+}
diff --git a/extensions/libip6t_frag.man b/extensions/libip6t_frag.man
new file mode 100644
index 0000000..7bfa227
--- /dev/null
+++ b/extensions/libip6t_frag.man
@@ -0,0 +1,20 @@
+This module matches the parameters in Fragment header.
+.TP
+[\fB!\fP] \fB\-\-fragid\fP \fIid\fP[\fB:\fP\fIid\fP]
+Matches the given Identification or range of it.
+.TP
+[\fB!\fP] \fB\-\-fraglen\fP \fIlength\fP
+This option cannot be used with kernel version 2.6.10 or later. The length of
+Fragment header is static and this option doesn't make sense.
+.TP
+\fB\-\-fragres\fP
+Matches if the reserved fields are filled with zero.
+.TP
+\fB\-\-fragfirst\fP
+Matches on the first fragment.
+.TP
+\fB\-\-fragmore\fP
+Matches if there are more fragments.
+.TP
+\fB\-\-fraglast\fP
+Matches if this is the last fragment.
diff --git a/extensions/libip6t_frag.t b/extensions/libip6t_frag.t
new file mode 100644
index 0000000..299fa03
--- /dev/null
+++ b/extensions/libip6t_frag.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+-m frag --fragid 1:42;=;OK
+-m frag --fraglen 42;=;OK
+-m frag --fragres;=;OK
+-m frag --fragfirst;=;OK
+-m frag --fragmore;=;OK
+-m frag --fraglast;=;OK
+-m frag ! --fragid 1 ! --fraglen 42 --fragres --fragfirst;=;OK
+-m frag --fragfirst --fragmore;=;OK
+-m frag --fragfirst --fraglast;=;OK
+-m frag --fraglast --fragmore;;FAIL
+-d ff02::fb/128 -p udp -m udp --dport 5353 -m frag --fragmore;=;OK
+-d fe80::/64 -p udp --dport 546 -m frag --fraglast;-d fe80::/64 -p udp -m udp --dport 546 -m frag --fraglast;OK
diff --git a/extensions/libip6t_frag.txlate b/extensions/libip6t_frag.txlate
new file mode 100644
index 0000000..e8bd9d4
--- /dev/null
+++ b/extensions/libip6t_frag.txlate
@@ -0,0 +1,17 @@
+ip6tables-translate -t filter -A INPUT -m frag --fragid 100:200 -j ACCEPT
+nft add rule ip6 filter INPUT frag id 100-200 counter accept
+
+ip6tables-translate -t filter -A INPUT -m frag --fragid 100 --fragres --fragmore -j ACCEPT
+nft add rule ip6 filter INPUT frag id 100 frag reserved 1 frag more-fragments 1 counter accept
+
+ip6tables-translate -t filter -A INPUT -m frag ! --fragid 100:200 -j ACCEPT
+nft add rule ip6 filter INPUT frag id != 100-200 counter accept
+
+ip6tables-translate -t filter -A INPUT -m frag --fragid 100:200 --fraglast -j ACCEPT
+nft add rule ip6 filter INPUT frag id 100-200 frag more-fragments 0 counter accept
+
+ip6tables-translate -t filter -A INPUT -m frag --fragid 100:200 --fragfirst -j ACCEPT
+nft add rule ip6 filter INPUT frag id 100-200 frag frag-off 0 counter accept
+
+ip6tables-translate -t filter -A INPUT -m frag --fraglast -j ACCEPT
+nft add rule ip6 filter INPUT frag more-fragments 0 counter accept
diff --git a/extensions/libip6t_hbh.c b/extensions/libip6t_hbh.c
new file mode 100644
index 0000000..4cebecf
--- /dev/null
+++ b/extensions/libip6t_hbh.c
@@ -0,0 +1,200 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_opts.h>
+
+enum {
+	O_HBH_LEN = 0,
+	O_HBH_OPTS,
+};
+
+static void hbh_help(void)
+{
+	printf(
+"hbh match options:\n"
+"[!] --hbh-len length            total length of this header\n"
+"  --hbh-opts TYPE[:LEN][,TYPE[:LEN]...] \n"
+"                                Options and its length (list, max: %d)\n",
+IP6T_OPTS_OPTSNR);
+}
+
+static const struct xt_option_entry hbh_opts[] = {
+	{.name = "hbh-len", .id = O_HBH_LEN, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct ip6t_opts, hdrlen)},
+	{.name = "hbh-opts", .id = O_HBH_OPTS, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+
+static uint32_t
+parse_opts_num(const char *idstr, const char *typestr)
+{
+	unsigned long int id;
+	char* ep;
+
+	id =  strtoul(idstr,&ep,0) ;
+
+	if ( idstr == ep ) {
+		xtables_error(PARAMETER_PROBLEM,
+			   "hbh: no valid digits in %s `%s'", typestr, idstr);
+	}
+	if ( id == ULONG_MAX  && errno == ERANGE ) {
+		xtables_error(PARAMETER_PROBLEM,
+			   "%s `%s' specified too big: would overflow",
+			   typestr, idstr);
+	}	
+	if ( *idstr != '\0'  && *ep != '\0' ) {
+		xtables_error(PARAMETER_PROBLEM,
+			   "hbh: error parsing %s `%s'", typestr, idstr);
+	}
+	return id;
+}
+
+static int
+parse_options(const char *optsstr, uint16_t *opts)
+{
+        char *buffer, *cp, *next, *range;
+        unsigned int i;
+	
+	buffer = strdup(optsstr);
+	if (!buffer) xtables_error(OTHER_PROBLEM, "strdup failed");
+			
+        for (cp=buffer, i=0; cp && i<IP6T_OPTS_OPTSNR; cp=next,i++)
+        {
+                next=strchr(cp, ',');
+                if (next) *next++='\0';
+                range = strchr(cp, ':');
+                if (range) {
+                        if (i == IP6T_OPTS_OPTSNR-1)
+				xtables_error(PARAMETER_PROBLEM,
+                                           "too many ports specified");
+                        *range++ = '\0';
+                }
+		opts[i] = (parse_opts_num(cp, "opt") & 0xFF) << 8;
+                if (range) {
+			if (opts[i] == 0)
+				xtables_error(PARAMETER_PROBLEM, "PAD0 has not got length");
+			opts[i] |= parse_opts_num(range, "length") & 0xFF;
+                } else {
+                        opts[i] |= (0x00FF);
+		}
+
+#ifdef DEBUG
+		printf("opts str: %s %s\n", cp, range);
+		printf("opts opt: %04X\n", opts[i]);
+#endif
+	}
+	if (cp) xtables_error(PARAMETER_PROBLEM, "too many addresses specified");
+
+	free(buffer);
+
+#ifdef DEBUG
+	printf("addr nr: %d\n", i);
+#endif
+
+	return i;
+}
+
+static void hbh_parse(struct xt_option_call *cb)
+{
+	struct ip6t_opts *optinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_HBH_LEN:
+		if (cb->invert)
+			optinfo->invflags |= IP6T_OPTS_INV_LEN;
+		optinfo->flags |= IP6T_OPTS_LEN;
+		break;
+	case O_HBH_OPTS:
+		optinfo->optsnr = parse_options(cb->arg, optinfo->opts);
+		optinfo->flags |= IP6T_OPTS_OPTS;
+		break;
+	}
+}
+
+static void
+print_options(unsigned int optsnr, uint16_t *optsp)
+{
+	unsigned int i;
+
+	for(i=0; i<optsnr; i++){
+		printf("%c", (i==0)?' ':',');
+		printf("%d", (optsp[i] & 0xFF00)>>8);
+		if ((optsp[i] & 0x00FF) != 0x00FF){
+			printf(":%d", (optsp[i] & 0x00FF));
+		} 
+	}
+}
+
+static void hbh_print(const void *ip, const struct xt_entry_match *match,
+                      int numeric)
+{
+	const struct ip6t_opts *optinfo = (struct ip6t_opts *)match->data;
+
+	printf(" hbh");
+	if (optinfo->flags & IP6T_OPTS_LEN) {
+		printf(" length");
+		printf(":%s", optinfo->invflags & IP6T_OPTS_INV_LEN ? "!" : "");
+		printf("%u", optinfo->hdrlen);
+	}
+	if (optinfo->flags & IP6T_OPTS_OPTS) printf(" opts");
+	print_options(optinfo->optsnr, (uint16_t *)optinfo->opts);
+	if (optinfo->invflags & ~IP6T_OPTS_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       optinfo->invflags & ~IP6T_OPTS_INV_MASK);
+}
+
+static void hbh_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_opts *optinfo = (struct ip6t_opts *)match->data;
+
+	if (optinfo->flags & IP6T_OPTS_LEN) {
+		printf("%s --hbh-len %u",
+			(optinfo->invflags & IP6T_OPTS_INV_LEN) ? " !" : "",
+			optinfo->hdrlen);
+	}
+
+	if (optinfo->flags & IP6T_OPTS_OPTS)
+		printf(" --hbh-opts");
+	print_options(optinfo->optsnr, (uint16_t *)optinfo->opts);
+}
+
+static int hbh_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_opts *optinfo =
+		(struct ip6t_opts *)params->match->data;
+
+	if (!(optinfo->flags & IP6T_OPTS_LEN) ||
+	    (optinfo->flags & IP6T_OPTS_OPTS))
+		return 0;
+
+	xt_xlate_add(xl, "hbh hdrlength %s%u",
+		     (optinfo->invflags & IP6T_OPTS_INV_LEN) ? "!= " : "",
+		     optinfo->hdrlen);
+
+	return 1;
+}
+
+static struct xtables_match hbh_mt6_reg = {
+	.name 		= "hbh",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct ip6t_opts)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ip6t_opts)),
+	.help		= hbh_help,
+	.print		= hbh_print,
+	.save		= hbh_save,
+	.x6_parse	= hbh_parse,
+	.x6_options	= hbh_opts,
+	.xlate		= hbh_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&hbh_mt6_reg);
+}
diff --git a/extensions/libip6t_hbh.man b/extensions/libip6t_hbh.man
new file mode 100644
index 0000000..2d92e04
--- /dev/null
+++ b/extensions/libip6t_hbh.man
@@ -0,0 +1,7 @@
+This module matches the parameters in Hop-by-Hop Options header
+.TP
+[\fB!\fP] \fB\-\-hbh\-len\fP \fIlength\fP
+Total length of this header in octets.
+.TP
+\fB\-\-hbh\-opts\fP \fItype\fP[\fB:\fP\fIlength\fP][\fB,\fP\fItype\fP[\fB:\fP\fIlength\fP]...]
+numeric type of option and the length of the option data in octets.
diff --git a/extensions/libip6t_hbh.t b/extensions/libip6t_hbh.t
new file mode 100644
index 0000000..4b58f25
--- /dev/null
+++ b/extensions/libip6t_hbh.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-m hbh;=;OK
+-m hbh --hbh-len 42;=;OK
+-m hbh ! --hbh-len 42;=;OK
+-m hbh --hbh-len 42 --hbh-opts 1:2,23:42,4:6,8:10,42,23,4:5;=;OK
diff --git a/extensions/libip6t_hbh.txlate b/extensions/libip6t_hbh.txlate
new file mode 100644
index 0000000..28101fd
--- /dev/null
+++ b/extensions/libip6t_hbh.txlate
@@ -0,0 +1,5 @@
+ip6tables-translate -t filter -A INPUT -m hbh --hbh-len 22
+nft add rule ip6 filter INPUT hbh hdrlength 22 counter
+
+ip6tables-translate -t filter -A INPUT -m hbh ! --hbh-len 22
+nft add rule ip6 filter INPUT hbh hdrlength != 22 counter
diff --git a/extensions/libip6t_hl.c b/extensions/libip6t_hl.c
new file mode 100644
index 0000000..37922f6
--- /dev/null
+++ b/extensions/libip6t_hl.c
@@ -0,0 +1,137 @@
+/*
+ * IPv6 Hop Limit matching module
+ * Maciej Soltysiak <solt@dns.toxicfilms.tv>
+ * Based on HW's ttl match
+ * This program is released under the terms of GNU GPL
+ * Cleanups by Stephane Ouellette <ouellettes@videotron.ca>
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_hl.h>
+
+enum {
+	O_HL_EQ = 0,
+	O_HL_LT,
+	O_HL_GT,
+	F_HL_EQ = 1 << O_HL_EQ,
+	F_HL_LT = 1 << O_HL_LT,
+	F_HL_GT = 1 << O_HL_GT,
+	F_ANY  = F_HL_EQ | F_HL_LT | F_HL_GT,
+};
+
+static void hl_help(void)
+{
+	printf(
+"hl match options:\n"
+"[!] --hl-eq value	Match hop limit value\n"
+"  --hl-lt value	Match HL < value\n"
+"  --hl-gt value	Match HL > value\n");
+}
+
+static void hl_parse(struct xt_option_call *cb)
+{
+	struct ip6t_hl_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_HL_EQ:
+		info->mode = cb->invert ? IP6T_HL_NE : IP6T_HL_EQ;
+		break;
+	case O_HL_LT:
+		info->mode = IP6T_HL_LT;
+		break;
+	case O_HL_GT:
+		info->mode = IP6T_HL_GT;
+		break;
+	}
+}
+
+static void hl_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & F_ANY))
+		xtables_error(PARAMETER_PROBLEM,
+			"HL match: You must specify one of "
+			"`--hl-eq', `--hl-lt', `--hl-gt'");
+}
+
+static void hl_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	static const char *const op[] = {
+		[IP6T_HL_EQ] = "==",
+		[IP6T_HL_NE] = "!=",
+		[IP6T_HL_LT] = "<",
+		[IP6T_HL_GT] = ">" };
+
+	const struct ip6t_hl_info *info = 
+		(struct ip6t_hl_info *) match->data;
+
+	printf(" HL match HL %s %u", op[info->mode], info->hop_limit);
+}
+
+static void hl_save(const void *ip, const struct xt_entry_match *match)
+{
+	static const char *const op[] = {
+		[IP6T_HL_EQ] = "--hl-eq",
+		[IP6T_HL_NE] = "! --hl-eq",
+		[IP6T_HL_LT] = "--hl-lt",
+		[IP6T_HL_GT] = "--hl-gt" };
+
+	const struct ip6t_hl_info *info =
+		(struct ip6t_hl_info *) match->data;
+
+	printf(" %s %u", op[info->mode], info->hop_limit);
+}
+
+static const char *const op[] = {
+	[IP6T_HL_EQ] = "",
+	[IP6T_HL_NE] = "!= ",
+	[IP6T_HL_LT] = "lt ",
+	[IP6T_HL_GT] = "gt "
+};
+
+static int hl_xlate(struct xt_xlate *xl,
+		    const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_hl_info *info =
+		(struct ip6t_hl_info *) params->match->data;
+
+	xt_xlate_add(xl, "ip6 hoplimit %s%u", op[info->mode], info->hop_limit);
+
+	return 1;
+}
+
+#define s struct ip6t_hl_info
+static const struct xt_option_entry hl_opts[] = {
+	{.name = "hl-lt", .id = O_HL_LT, .excl = F_ANY, .type = XTTYPE_UINT8,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, hop_limit)},
+	{.name = "hl-gt", .id = O_HL_GT, .excl = F_ANY, .type = XTTYPE_UINT8,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, hop_limit)},
+	{.name = "hl-eq", .id = O_HL_EQ, .excl = F_ANY, .type = XTTYPE_UINT8,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hop_limit)},
+	{.name = "hl", .id = O_HL_EQ, .excl = F_ANY, .type = XTTYPE_UINT8,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, hop_limit)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static struct xtables_match hl_mt6_reg = {
+	.name          = "hl",
+	.version       = XTABLES_VERSION,
+	.family        = NFPROTO_IPV6,
+	.size          = XT_ALIGN(sizeof(struct ip6t_hl_info)),
+	.userspacesize = XT_ALIGN(sizeof(struct ip6t_hl_info)),
+	.help          = hl_help,
+	.print         = hl_print,
+	.save          = hl_save,
+	.x6_parse      = hl_parse,
+	.x6_fcheck     = hl_check,
+	.x6_options    = hl_opts,
+	.xlate	       = hl_xlate,
+};
+
+
+void _init(void) 
+{
+	xtables_register_match(&hl_mt6_reg);
+}
diff --git a/extensions/libip6t_hl.man b/extensions/libip6t_hl.man
new file mode 100644
index 0000000..dfbfaf8
--- /dev/null
+++ b/extensions/libip6t_hl.man
@@ -0,0 +1,10 @@
+This module matches the Hop Limit field in the IPv6 header.
+.TP
+[\fB!\fP] \fB\-\-hl\-eq\fP \fIvalue\fP
+Matches if Hop Limit equals \fIvalue\fP.
+.TP
+\fB\-\-hl\-lt\fP \fIvalue\fP
+Matches if Hop Limit is less than \fIvalue\fP.
+.TP
+\fB\-\-hl\-gt\fP \fIvalue\fP
+Matches if Hop Limit is greater than \fIvalue\fP.
diff --git a/extensions/libip6t_hl.t b/extensions/libip6t_hl.t
new file mode 100644
index 0000000..b02816a
--- /dev/null
+++ b/extensions/libip6t_hl.t
@@ -0,0 +1,8 @@
+:INPUT,FORWARD,OUTPUT
+-m hl;;FAIL
+-m hl --hl-eq 42;=;OK
+-m hl ! --hl-eq 42;=;OK
+-m hl --hl-lt 42;=;OK
+-m hl --hl-gt 42;=;OK
+-m hl --hl-gt 42 --hl-eq 42;;FAIL
+-m hl --hl-gt;;FAIL
diff --git a/extensions/libip6t_hl.txlate b/extensions/libip6t_hl.txlate
new file mode 100644
index 0000000..1756393
--- /dev/null
+++ b/extensions/libip6t_hl.txlate
@@ -0,0 +1,5 @@
+ip6tables-translate -t nat -A postrouting -m hl --hl-gt 3
+nft add rule ip6 nat postrouting ip6 hoplimit gt 3 counter
+
+ip6tables-translate -t nat -A postrouting -m hl ! --hl-eq 3
+nft add rule ip6 nat postrouting ip6 hoplimit != 3 counter
diff --git a/extensions/libip6t_icmp6.c b/extensions/libip6t_icmp6.c
new file mode 100644
index 0000000..cc7bfae
--- /dev/null
+++ b/extensions/libip6t_icmp6.c
@@ -0,0 +1,282 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip6_tables.h */
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <netinet/icmp6.h>
+
+#include "libxt_icmp.h"
+
+enum {
+	O_ICMPV6_TYPE = 0,
+};
+
+static const struct xt_icmp_names icmpv6_codes[] = {
+	{ "destination-unreachable", 1, 0, 0xFF },
+	{   "no-route", 1, 0, 0 },
+	{   "communication-prohibited", 1, 1, 1 },
+	{   "beyond-scope", 1, 2, 2 },
+	{   "address-unreachable", 1, 3, 3 },
+	{   "port-unreachable", 1, 4, 4 },
+	{   "failed-policy", 1, 5, 5 },
+	{   "reject-route", 1, 6, 6 },
+
+	{ "packet-too-big", 2, 0, 0xFF },
+
+	{ "time-exceeded", 3, 0, 0xFF },
+	/* Alias */ { "ttl-exceeded", 3, 0, 0xFF },
+	{   "ttl-zero-during-transit", 3, 0, 0 },
+	{   "ttl-zero-during-reassembly", 3, 1, 1 },
+
+	{ "parameter-problem", 4, 0, 0xFF },
+	{   "bad-header", 4, 0, 0 },
+	{   "unknown-header-type", 4, 1, 1 },
+	{   "unknown-option", 4, 2, 2 },
+
+	{ "echo-request", 128, 0, 0xFF },
+	/* Alias */ { "ping", 128, 0, 0xFF },
+
+	{ "echo-reply", 129, 0, 0xFF },
+	/* Alias */ { "pong", 129, 0, 0xFF },
+
+	{ "router-solicitation", 133, 0, 0xFF },
+
+	{ "router-advertisement", 134, 0, 0xFF },
+
+	{ "neighbour-solicitation", 135, 0, 0xFF },
+	/* Alias */ { "neighbor-solicitation", 135, 0, 0xFF },
+
+	{ "neighbour-advertisement", 136, 0, 0xFF },
+	/* Alias */ { "neighbor-advertisement", 136, 0, 0xFF },
+
+	{ "redirect", 137, 0, 0xFF },
+
+};
+
+static void icmp6_help(void)
+{
+	printf(
+"icmpv6 match options:\n"
+"[!] --icmpv6-type typename	match icmpv6 type\n"
+"				(or numeric type or type/code)\n");
+	printf("Valid ICMPv6 Types:");
+	xt_print_icmp_types(icmpv6_codes, ARRAY_SIZE(icmpv6_codes));
+}
+
+static const struct xt_option_entry icmp6_opts[] = {
+	{.name = "icmpv6-type", .id = O_ICMPV6_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void
+parse_icmpv6(const char *icmpv6type, uint8_t *type, uint8_t code[])
+{
+	static const unsigned int limit = ARRAY_SIZE(icmpv6_codes);
+	unsigned int match = limit;
+	unsigned int i;
+
+	for (i = 0; i < limit; i++) {
+		if (strncasecmp(icmpv6_codes[i].name, icmpv6type, strlen(icmpv6type))
+		    == 0) {
+			if (match != limit)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Ambiguous ICMPv6 type `%s':"
+					   " `%s' or `%s'?",
+					   icmpv6type,
+					   icmpv6_codes[match].name,
+					   icmpv6_codes[i].name);
+			match = i;
+		}
+	}
+
+	if (match != limit) {
+		*type = icmpv6_codes[match].type;
+		code[0] = icmpv6_codes[match].code_min;
+		code[1] = icmpv6_codes[match].code_max;
+	} else {
+		char *slash;
+		char buffer[strlen(icmpv6type) + 1];
+		unsigned int number;
+
+		strcpy(buffer, icmpv6type);
+		slash = strchr(buffer, '/');
+
+		if (slash)
+			*slash = '\0';
+
+		if (!xtables_strtoui(buffer, NULL, &number, 0, UINT8_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid ICMPv6 type `%s'\n", buffer);
+		*type = number;
+		if (slash) {
+			if (!xtables_strtoui(slash+1, NULL, &number, 0, UINT8_MAX))
+				xtables_error(PARAMETER_PROBLEM,
+					   "Invalid ICMPv6 code `%s'\n",
+					   slash+1);
+			code[0] = code[1] = number;
+		} else {
+			code[0] = 0;
+			code[1] = 0xFF;
+		}
+	}
+}
+
+static void icmp6_init(struct xt_entry_match *m)
+{
+	struct ip6t_icmp *icmpv6info = (struct ip6t_icmp *)m->data;
+
+	icmpv6info->code[1] = 0xFF;
+}
+
+static void icmp6_parse(struct xt_option_call *cb)
+{
+	struct ip6t_icmp *icmpv6info = cb->data;
+
+	xtables_option_parse(cb);
+	parse_icmpv6(cb->arg, &icmpv6info->type, icmpv6info->code);
+	if (cb->invert)
+		icmpv6info->invflags |= IP6T_ICMP_INV;
+}
+
+static void print_icmpv6type(uint8_t type,
+			   uint8_t code_min, uint8_t code_max,
+			   int invert,
+			   int numeric)
+{
+	if (!numeric) {
+		unsigned int i;
+
+		for (i = 0; i < ARRAY_SIZE(icmpv6_codes); ++i)
+			if (icmpv6_codes[i].type == type
+			    && icmpv6_codes[i].code_min == code_min
+			    && icmpv6_codes[i].code_max == code_max)
+				break;
+
+		if (i != ARRAY_SIZE(icmpv6_codes)) {
+			printf(" %s%s",
+			       invert ? "!" : "",
+			       icmpv6_codes[i].name);
+			return;
+		}
+	}
+
+	if (invert)
+		printf(" !");
+
+	printf("type %u", type);
+	if (code_min == code_max)
+		printf(" code %u", code_min);
+	else if (code_min != 0 || code_max != 0xFF)
+		printf(" codes %u-%u", code_min, code_max);
+}
+
+static void icmp6_print(const void *ip, const struct xt_entry_match *match,
+                        int numeric)
+{
+	const struct ip6t_icmp *icmpv6 = (struct ip6t_icmp *)match->data;
+
+	printf(" ipv6-icmp");
+	print_icmpv6type(icmpv6->type, icmpv6->code[0], icmpv6->code[1],
+		       icmpv6->invflags & IP6T_ICMP_INV,
+		       numeric);
+
+	if (icmpv6->invflags & ~IP6T_ICMP_INV)
+		printf(" Unknown invflags: 0x%X",
+		       icmpv6->invflags & ~IP6T_ICMP_INV);
+}
+
+static void icmp6_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_icmp *icmpv6 = (struct ip6t_icmp *)match->data;
+
+	if (icmpv6->invflags & IP6T_ICMP_INV)
+		printf(" !");
+
+	printf(" --icmpv6-type %u", icmpv6->type);
+	if (icmpv6->code[0] != 0 || icmpv6->code[1] != 0xFF)
+		printf("/%u", icmpv6->code[0]);
+}
+
+#define XT_ICMPV6_TYPE(type)	(type - ND_ROUTER_SOLICIT)
+
+static const char *icmp6_type_xlate_array[] = {
+	[XT_ICMPV6_TYPE(ND_ROUTER_SOLICIT)]	= "nd-router-solicit",
+	[XT_ICMPV6_TYPE(ND_ROUTER_ADVERT)]	= "nd-router-advert",
+	[XT_ICMPV6_TYPE(ND_NEIGHBOR_SOLICIT)]	= "nd-neighbor-solicit",
+	[XT_ICMPV6_TYPE(ND_NEIGHBOR_ADVERT)]	= "nd-neighbor-advert",
+	[XT_ICMPV6_TYPE(ND_REDIRECT)]		= "nd-redirect",
+};
+
+static const char *icmp6_type_xlate(unsigned int type)
+{
+	if (type < ND_ROUTER_SOLICIT || type > ND_REDIRECT)
+		return NULL;
+
+	return icmp6_type_xlate_array[XT_ICMPV6_TYPE(type)];
+}
+
+static unsigned int type_xlate_print(struct xt_xlate *xl, unsigned int icmptype,
+				     unsigned int code_min,
+				     unsigned int code_max)
+{
+	unsigned int i;
+	const char *type_name;
+
+	if (code_min == code_max)
+		return 0;
+
+	type_name = icmp6_type_xlate(icmptype);
+
+	if (type_name) {
+		xt_xlate_add(xl, "%s", type_name);
+	} else {
+		for (i = 0; i < ARRAY_SIZE(icmpv6_codes); ++i)
+			if (icmpv6_codes[i].type == icmptype &&
+			    icmpv6_codes[i].code_min == code_min &&
+			    icmpv6_codes[i].code_max == code_max)
+				break;
+
+		if (i != ARRAY_SIZE(icmpv6_codes))
+			xt_xlate_add(xl, "%s", icmpv6_codes[i].name);
+		else
+			return 0;
+	}
+
+	return 1;
+}
+
+static int icmp6_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_icmp *info = (struct ip6t_icmp *)params->match->data;
+
+	xt_xlate_add(xl, "icmpv6 type%s ",
+		     (info->invflags & IP6T_ICMP_INV) ? " !=" : "");
+
+	if (!type_xlate_print(xl, info->type, info->code[0], info->code[1]))
+		return 0;
+
+	return 1;
+}
+
+static struct xtables_match icmp6_mt6_reg = {
+	.name 		= "icmp6",
+	.version 	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct ip6t_icmp)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ip6t_icmp)),
+	.help		= icmp6_help,
+	.init		= icmp6_init,
+	.print		= icmp6_print,
+	.save		= icmp6_save,
+	.x6_parse	= icmp6_parse,
+	.x6_options	= icmp6_opts,
+	.xlate		= icmp6_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&icmp6_mt6_reg);
+}
diff --git a/extensions/libip6t_icmp6.man b/extensions/libip6t_icmp6.man
new file mode 100644
index 0000000..817e21c
--- /dev/null
+++ b/extensions/libip6t_icmp6.man
@@ -0,0 +1,14 @@
+This extension can be used if `\-\-protocol ipv6\-icmp' or `\-\-protocol icmpv6' is
+specified. It provides the following option:
+.TP
+[\fB!\fP] \fB\-\-icmpv6\-type\fP \fItype\fP[\fB/\fP\fIcode\fP]|\fItypename\fP
+This allows specification of the ICMPv6 type, which can be a numeric
+ICMPv6
+.IR type ,
+.IR type
+and
+.IR code ,
+or one of the ICMPv6 type names shown by the command
+.nf
+ ip6tables \-p ipv6\-icmp \-h
+.fi
diff --git a/extensions/libip6t_icmp6.t b/extensions/libip6t_icmp6.t
new file mode 100644
index 0000000..028cfc1
--- /dev/null
+++ b/extensions/libip6t_icmp6.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m icmpv6;;FAIL
+-p ipv6-icmp -m icmp6 --icmpv6-type 1/0;=;OK
+-p ipv6-icmp -m icmp6 --icmpv6-type 2;=;OK
+# cannot use option twice:
+-p ipv6-icmp -m icmp6 --icmpv6-type no-route --icmpv6-type packet-too-big;;FAIL
diff --git a/extensions/libip6t_icmp6.txlate b/extensions/libip6t_icmp6.txlate
new file mode 100644
index 0000000..15481ad
--- /dev/null
+++ b/extensions/libip6t_icmp6.txlate
@@ -0,0 +1,8 @@
+ip6tables-translate -t filter -A INPUT -m icmp6 --icmpv6-type 1 -j LOG
+nft add rule ip6 filter INPUT icmpv6 type destination-unreachable counter log
+
+ip6tables-translate -t filter -A INPUT -m icmp6 --icmpv6-type neighbour-advertisement -j LOG
+nft add rule ip6 filter INPUT icmpv6 type nd-neighbor-advert counter log
+
+ip6tables-translate -t filter -A INPUT -m icmp6 ! --icmpv6-type packet-too-big -j LOG
+nft add rule ip6 filter INPUT icmpv6 type != packet-too-big counter log
diff --git a/extensions/libip6t_ipv6header.c b/extensions/libip6t_ipv6header.c
new file mode 100644
index 0000000..6f03087
--- /dev/null
+++ b/extensions/libip6t_ipv6header.c
@@ -0,0 +1,245 @@
+/* ipv6header match - matches IPv6 packets based
+on whether they contain certain headers */
+
+/* Original idea: Brad Chapman 
+ * Rewritten by: Andras Kis-Szabo <kisza@sch.bme.hu> */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_ipv6header.h>
+
+enum {
+	O_HEADER = 0,
+	O_SOFT,
+};
+
+/* A few hardcoded protocols for 'all' and in case the user has no
+ *    /etc/protocols */
+struct pprot {
+	char *name;
+	uint8_t num;
+};
+
+struct numflag {
+	uint8_t proto;
+	uint8_t flag;
+};
+
+static const struct pprot chain_protos[] = {
+	{ "hop-by-hop", IPPROTO_HOPOPTS },
+	{ "protocol", IPPROTO_RAW },
+	{ "hop", IPPROTO_HOPOPTS },
+	{ "dst", IPPROTO_DSTOPTS },
+	{ "route", IPPROTO_ROUTING },
+	{ "frag", IPPROTO_FRAGMENT },
+	{ "auth", IPPROTO_AH },
+	{ "esp", IPPROTO_ESP },
+	{ "none", IPPROTO_NONE },
+	{ "prot", IPPROTO_RAW },
+	{ "0", IPPROTO_HOPOPTS },
+	{ "60", IPPROTO_DSTOPTS },
+	{ "43", IPPROTO_ROUTING },
+	{ "44", IPPROTO_FRAGMENT },
+	{ "51", IPPROTO_AH },
+	{ "50", IPPROTO_ESP },
+	{ "59", IPPROTO_NONE },
+	{ "255", IPPROTO_RAW },
+	/* { "all", 0 }, */
+};
+
+static const struct numflag chain_flags[] = {
+	{ IPPROTO_HOPOPTS, MASK_HOPOPTS },
+	{ IPPROTO_DSTOPTS, MASK_DSTOPTS },
+	{ IPPROTO_ROUTING, MASK_ROUTING },
+	{ IPPROTO_FRAGMENT, MASK_FRAGMENT },
+	{ IPPROTO_AH, MASK_AH },
+	{ IPPROTO_ESP, MASK_ESP },
+	{ IPPROTO_NONE, MASK_NONE },
+	{ IPPROTO_RAW, MASK_PROTO },
+};
+
+static const char *
+proto_to_name(uint8_t proto, int nolookup)
+{
+        unsigned int i;
+
+        if (proto && !nolookup) {
+		const struct protoent *pent = getprotobynumber(proto);
+                if (pent)
+                        return pent->p_name;
+        }
+
+        for (i = 0; i < ARRAY_SIZE(chain_protos); ++i)
+                if (chain_protos[i].num == proto)
+                        return chain_protos[i].name;
+
+        return NULL;
+}
+
+static uint16_t
+name_to_proto(const char *s)
+{
+        unsigned int proto=0;
+	const struct protoent *pent;
+
+        if ((pent = getprotobyname(s)))
+        	proto = pent->p_proto;
+        else {
+        	unsigned int i;
+        	for (i = 0; i < ARRAY_SIZE(chain_protos); ++i)
+        		if (strcmp(s, chain_protos[i].name) == 0) {
+        			proto = chain_protos[i].num;
+        			break;
+        		}
+
+		if (i == ARRAY_SIZE(chain_protos))
+			xtables_error(PARAMETER_PROBLEM,
+        			"unknown header `%s' specified",
+        			s);
+        }
+
+        return proto;
+}
+
+static unsigned int 
+add_proto_to_mask(int proto){
+	unsigned int i=0, flag=0;
+
+	for (i = 0; i < ARRAY_SIZE(chain_flags); ++i)
+			if (proto == chain_flags[i].proto){
+				flag = chain_flags[i].flag;
+				break;
+			}
+
+	if (i == ARRAY_SIZE(chain_flags))
+		xtables_error(PARAMETER_PROBLEM,
+		"unknown header `%d' specified",
+		proto);
+	
+	return flag;
+}	
+
+static void ipv6header_help(void)
+{
+	printf(
+"ipv6header match options:\n"
+"[!] --header headers     Type of header to match, by name\n"
+"                         names: hop,dst,route,frag,auth,esp,none,prot\n"
+"                    long names: hop-by-hop,ipv6-opts,ipv6-route,\n"
+"                                ipv6-frag,ah,esp,ipv6-nonxt,protocol\n"
+"                       numbers: 0,60,43,44,51,50,59\n"
+"--soft                    The header CONTAINS the specified extensions\n");
+}
+
+static const struct xt_option_entry ipv6header_opts[] = {
+	{.name = "header", .id = O_HEADER, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	{.name = "soft", .id = O_SOFT, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static unsigned int
+parse_header(const char *flags) {
+        unsigned int ret = 0;
+        char *ptr;
+        char *buffer;
+
+        buffer = strdup(flags);
+
+        for (ptr = strtok(buffer, ","); ptr; ptr = strtok(NULL, ",")) 
+		ret |= add_proto_to_mask(name_to_proto(ptr));
+                
+        free(buffer);
+        return ret;
+}
+
+static void ipv6header_parse(struct xt_option_call *cb)
+{
+	struct ip6t_ipv6header_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_HEADER:
+		if (!(info->matchflags = parse_header(cb->arg)))
+			xtables_error(PARAMETER_PROBLEM, "ip6t_ipv6header: cannot parse header names");
+		if (cb->invert) 
+			info->invflags |= 0xFF;
+		break;
+	case O_SOFT:
+		info->modeflag |= 0xFF;
+		break;
+	}
+}
+
+static void
+print_header(uint8_t flags){
+        int have_flag = 0;
+
+        while (flags) {
+                unsigned int i;
+
+                for (i = 0; (flags & chain_flags[i].flag) == 0; i++);
+
+                if (have_flag)
+                        printf(",");
+
+                printf("%s", proto_to_name(chain_flags[i].proto,0));
+                have_flag = 1;
+
+                flags &= ~chain_flags[i].flag;
+        }
+
+        if (!have_flag)
+                printf("NONE");
+}
+
+static void ipv6header_print(const void *ip,
+                             const struct xt_entry_match *match, int numeric)
+{
+	const struct ip6t_ipv6header_info *info = (const struct ip6t_ipv6header_info *)match->data;
+	printf(" ipv6header");
+
+        if (info->matchflags || info->invflags) {
+		printf(" flags:%s", info->invflags ? "!" : "");
+                if (numeric)
+			printf("0x%02X", info->matchflags);
+                else {
+                        print_header(info->matchflags);
+                }
+        }
+
+	if (info->modeflag)
+		printf(" soft");
+}
+
+static void ipv6header_save(const void *ip, const struct xt_entry_match *match)
+{
+
+	const struct ip6t_ipv6header_info *info = (const struct ip6t_ipv6header_info *)match->data;
+
+	printf("%s --header ", info->invflags ? " !" : "");
+	print_header(info->matchflags);
+	if (info->modeflag)
+		printf(" --soft");
+}
+
+static struct xtables_match ipv6header_mt6_reg = {
+	.name		= "ipv6header",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct ip6t_ipv6header_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ip6t_ipv6header_info)),
+	.help		= ipv6header_help,
+	.print		= ipv6header_print,
+	.save		= ipv6header_save,
+	.x6_parse	= ipv6header_parse,
+	.x6_options	= ipv6header_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&ipv6header_mt6_reg);
+}
diff --git a/extensions/libip6t_ipv6header.man b/extensions/libip6t_ipv6header.man
new file mode 100644
index 0000000..807d9ab
--- /dev/null
+++ b/extensions/libip6t_ipv6header.man
@@ -0,0 +1,37 @@
+This module matches IPv6 extension headers and/or upper layer header.
+.TP
+\fB\-\-soft\fP
+Matches if the packet includes \fBany\fP of the headers specified with
+\fB\-\-header\fP.
+.TP
+[\fB!\fP] \fB\-\-header\fP \fIheader\fP[\fB,\fP\fIheader\fP...]
+Matches the packet which EXACTLY includes all specified headers. The headers
+encapsulated with ESP header are out of scope.
+Possible \fIheader\fP types can be:
+.TP
+\fBhop\fP|\fBhop\-by\-hop\fP
+Hop-by-Hop Options header
+.TP
+\fBdst\fP
+Destination Options header
+.TP
+\fBroute\fP
+Routing header
+.TP
+\fBfrag\fP
+Fragment header
+.TP
+\fBauth\fP
+Authentication header
+.TP
+\fBesp\fP
+Encapsulating Security Payload header
+.TP
+\fBnone\fP
+No Next header which matches 59 in the 'Next Header field' of IPv6 header or
+any IPv6 extension headers
+.TP
+\fBprot\fP
+which matches any upper layer protocol header. A protocol name from
+/etc/protocols and numeric value also allowed. The number 255 is equivalent to
+\fBprot\fP.
diff --git a/extensions/libip6t_ipv6header.t b/extensions/libip6t_ipv6header.t
new file mode 100644
index 0000000..67fa479
--- /dev/null
+++ b/extensions/libip6t_ipv6header.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-m ipv6header --header hop-by-hop;=;OK
+-m ipv6header --header hop-by-hop --soft;=;OK
+-m ipv6header --header ipv6-nonxt;=;OK
diff --git a/extensions/libip6t_mh.c b/extensions/libip6t_mh.c
new file mode 100644
index 0000000..f4c0fd9
--- /dev/null
+++ b/extensions/libip6t_mh.c
@@ -0,0 +1,249 @@
+/* Shared library add-on to ip6tables to add mobility header support. */
+/*
+ * Copyright (C)2006 USAGI/WIDE Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Author:
+ *	Masahide NAKAMURA @USAGI <masahide.nakamura.cz@hitachi.com>
+ *
+ * Based on libip6t_{icmpv6,udp}.c
+ */
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_mh.h>
+
+enum {
+	O_MH_TYPE = 0,
+};
+
+struct mh_name {
+	const char *name;
+	uint8_t type;
+};
+
+static const struct mh_name mh_names[] = {
+	{ "binding-refresh-request", 0, },
+	/* Alias */ { "brr", 0, },
+	{ "home-test-init", 1, },
+	/* Alias */ { "hoti", 1, },
+	{ "careof-test-init", 2, },
+	/* Alias */ { "coti", 2, },
+	{ "home-test", 3, },
+	/* Alias */ { "hot", 3, },
+	{ "careof-test", 4, },
+	/* Alias */ { "cot", 4, },
+	{ "binding-update", 5, },
+	/* Alias */ { "bu", 5, },
+	{ "binding-acknowledgement", 6, },
+	/* Alias */ { "ba", 6, },
+	{ "binding-error", 7, },
+	/* Alias */ { "be", 7, },
+};
+
+static void print_types_all(void)
+{
+	unsigned int i;
+	printf("Valid MH types:");
+
+	for (i = 0; i < ARRAY_SIZE(mh_names); ++i) {
+		if (i && mh_names[i].type == mh_names[i-1].type)
+			printf(" (%s)", mh_names[i].name);
+		else
+			printf("\n%s", mh_names[i].name);
+	}
+	printf("\n");
+}
+
+static void mh_help(void)
+{
+	printf(
+"mh match options:\n"
+"[!] --mh-type type[:type]	match mh type\n");
+	print_types_all();
+}
+
+static void mh_init(struct xt_entry_match *m)
+{
+	struct ip6t_mh *mhinfo = (struct ip6t_mh *)m->data;
+
+	mhinfo->types[1] = 0xFF;
+}
+
+static unsigned int name_to_type(const char *name)
+{
+	int namelen = strlen(name);
+	static const unsigned int limit = ARRAY_SIZE(mh_names);
+	unsigned int match = limit;
+	unsigned int i;
+
+	for (i = 0; i < limit; i++) {
+		if (strncasecmp(mh_names[i].name, name, namelen) == 0) {
+			int len = strlen(mh_names[i].name);
+			if (match == limit || len == namelen)
+				match = i;
+		}
+	}
+
+	if (match != limit) {
+		return mh_names[match].type;
+	} else {
+		unsigned int number;
+
+		if (!xtables_strtoui(name, NULL, &number, 0, UINT8_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid MH type `%s'\n", name);
+		return number;
+	}
+}
+
+static void parse_mh_types(const char *mhtype, uint8_t *types)
+{
+	char *buffer;
+	char *cp;
+
+	buffer = strdup(mhtype);
+	if ((cp = strchr(buffer, ':')) == NULL)
+		types[0] = types[1] = name_to_type(buffer);
+	else {
+		*cp = '\0';
+		cp++;
+
+		types[0] = buffer[0] ? name_to_type(buffer) : 0;
+		types[1] = cp[0] ? name_to_type(cp) : 0xFF;
+
+		if (types[0] > types[1])
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid MH type range (min > max)");
+	}
+	free(buffer);
+}
+
+static void mh_parse(struct xt_option_call *cb)
+{
+	struct ip6t_mh *mhinfo = cb->data;
+
+	xtables_option_parse(cb);
+	parse_mh_types(cb->arg, mhinfo->types);
+	if (cb->invert)
+		mhinfo->invflags |= IP6T_MH_INV_TYPE;
+}
+
+static const char *type_to_name(uint8_t type)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(mh_names); ++i)
+		if (mh_names[i].type == type)
+			return mh_names[i].name;
+
+	return NULL;
+}
+
+static void print_type(uint8_t type, int numeric)
+{
+	const char *name;
+	if (numeric || !(name = type_to_name(type)))
+		printf("%u", type);
+	else
+		printf("%s", name);
+}
+
+static void print_types(uint8_t min, uint8_t max, int invert, int numeric)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFF || invert) {
+		printf(" ");
+		if (min == max) {
+			printf("%s", inv);
+			print_type(min, numeric);
+		} else {
+			printf("%s", inv);
+			print_type(min, numeric);
+			printf(":");
+			print_type(max, numeric);
+		}
+	}
+}
+
+static void mh_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	const struct ip6t_mh *mhinfo = (struct ip6t_mh *)match->data;
+
+	printf(" mh");
+	print_types(mhinfo->types[0], mhinfo->types[1],
+		    mhinfo->invflags & IP6T_MH_INV_TYPE,
+		    numeric);
+	if (mhinfo->invflags & ~IP6T_MH_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       mhinfo->invflags & ~IP6T_MH_INV_MASK);
+}
+
+static void mh_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_mh *mhinfo = (struct ip6t_mh *)match->data;
+
+	if (mhinfo->types[0] == 0 && mhinfo->types[1] == 0xFF)
+		return;
+
+	if (mhinfo->invflags & IP6T_MH_INV_TYPE)
+		printf(" !");
+
+	if (mhinfo->types[0] != mhinfo->types[1])
+		printf(" --mh-type %u:%u", mhinfo->types[0], mhinfo->types[1]);
+	else
+		printf(" --mh-type %u", mhinfo->types[0]);
+}
+
+static int mh_xlate(struct xt_xlate *xl,
+		    const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_mh *mhinfo = (struct ip6t_mh *)params->match->data;
+
+	if (mhinfo->types[0] == 0 && mhinfo->types[1] == 0xff)
+		return 1;
+
+	if (mhinfo->types[0] != mhinfo->types[1])
+		xt_xlate_add(xl, "mh type %s%u-%u",
+			     mhinfo->invflags & IP6T_MH_INV_TYPE ? "!= " : "",
+			     mhinfo->types[0], mhinfo->types[1]);
+	else
+		xt_xlate_add(xl, "mh type %s%u",
+			     mhinfo->invflags & IP6T_MH_INV_TYPE ? "!= " : "",
+			     mhinfo->types[0]);
+
+	return 1;
+}
+
+static const struct xt_option_entry mh_opts[] = {
+	{.name = "mh-type", .id = O_MH_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static struct xtables_match mh_mt6_reg = {
+	.name		= "mh",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct ip6t_mh)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ip6t_mh)),
+	.help		= mh_help,
+	.init		= mh_init,
+	.x6_parse	= mh_parse,
+	.print		= mh_print,
+	.save		= mh_save,
+	.x6_options	= mh_opts,
+	.xlate		= mh_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&mh_mt6_reg);
+}
diff --git a/extensions/libip6t_mh.man b/extensions/libip6t_mh.man
new file mode 100644
index 0000000..8ec08c6
--- /dev/null
+++ b/extensions/libip6t_mh.man
@@ -0,0 +1,12 @@
+This extension is loaded if `\-\-protocol ipv6\-mh' or `\-\-protocol mh' is
+specified. It provides the following option:
+.TP
+[\fB!\fP] \fB\-\-mh\-type\fP \fItype\fP[\fB:\fP\fItype\fP]
+This allows specification of the Mobility Header(MH) type, which can be
+a numeric MH
+.IR type ,
+.IR type
+or one of the MH type names shown by the command
+.nf
+ ip6tables \-p mh \-h
+.fi
diff --git a/extensions/libip6t_mh.t b/extensions/libip6t_mh.t
new file mode 100644
index 0000000..6b76d13
--- /dev/null
+++ b/extensions/libip6t_mh.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m mh;;FAIL
+-p mobility-header -m mh;=;OK
+-p mobility-header -m mh --mh-type 1;=;OK
+-p mobility-header -m mh ! --mh-type 4;=;OK
+-p mobility-header -m mh --mh-type 4:123;=;OK
diff --git a/extensions/libip6t_mh.txlate b/extensions/libip6t_mh.txlate
new file mode 100644
index 0000000..f5d638c
--- /dev/null
+++ b/extensions/libip6t_mh.txlate
@@ -0,0 +1,5 @@
+ip6tables-translate -A INPUT -p mh --mh-type 1 -j ACCEPT
+nft add rule ip6 filter INPUT meta l4proto mobility-header mh type 1 counter accept
+
+ip6tables-translate -A INPUT -p mh --mh-type 1:3 -j ACCEPT
+nft add rule ip6 filter INPUT meta l4proto mobility-header mh type 1-3 counter accept
diff --git a/extensions/libip6t_rt.c b/extensions/libip6t_rt.c
new file mode 100644
index 0000000..3cb3b24
--- /dev/null
+++ b/extensions/libip6t_rt.c
@@ -0,0 +1,304 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_rt.h>
+#include <arpa/inet.h>
+
+enum {
+	O_RT_TYPE = 0,
+	O_RT_SEGSLEFT,
+	O_RT_LEN,
+	O_RT0RES,
+	O_RT0ADDRS,
+	O_RT0NSTRICT,
+	F_RT_TYPE  = 1 << O_RT_TYPE,
+	F_RT0ADDRS = 1 << O_RT0ADDRS,
+};
+
+static void rt_help(void)
+{
+	printf(
+"rt match options:\n"
+"[!] --rt-type type             match the type\n"
+"[!] --rt-segsleft num[:num]    match the Segments Left field (range)\n"
+"[!] --rt-len length            total length of this header\n"
+" --rt-0-res                    check the reserved field too (type 0)\n"
+" --rt-0-addrs ADDR[,ADDR...]   Type=0 addresses (list, max: %d)\n"
+" --rt-0-not-strict             List of Type=0 addresses not a strict list\n",
+IP6T_RT_HOPS);
+}
+
+#define s struct ip6t_rt
+static const struct xt_option_entry rt_opts[] = {
+	{.name = "rt-type", .id = O_RT_TYPE, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, rt_type)},
+	{.name = "rt-segsleft", .id = O_RT_SEGSLEFT, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segsleft)},
+	{.name = "rt-len", .id = O_RT_LEN, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdrlen)},
+	{.name = "rt-0-res", .id = O_RT0RES, .type = XTTYPE_NONE},
+	{.name = "rt-0-addrs", .id = O_RT0ADDRS, .type = XTTYPE_STRING},
+	{.name = "rt-0-not-strict", .id = O_RT0NSTRICT, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static const char *
+addr_to_numeric(const struct in6_addr *addrp)
+{
+	static char buf[50+1];
+	return inet_ntop(AF_INET6, addrp, buf, sizeof(buf));
+}
+
+static struct in6_addr *
+numeric_to_addr(const char *num)
+{
+	static struct in6_addr ap;
+	int err;
+
+	if ((err=inet_pton(AF_INET6, num, &ap)) == 1)
+		return &ap;
+#ifdef DEBUG
+	fprintf(stderr, "\nnumeric2addr: %d\n", err);
+#endif
+	xtables_error(PARAMETER_PROBLEM, "bad address: %s", num);
+
+	return (struct in6_addr *)NULL;
+}
+
+
+static int
+parse_addresses(const char *addrstr, struct in6_addr *addrp)
+{
+        char *buffer, *cp, *next;
+        unsigned int i;
+	
+	buffer = strdup(addrstr);
+	if (!buffer) xtables_error(OTHER_PROBLEM, "strdup failed");
+			
+        for (cp=buffer, i=0; cp && i<IP6T_RT_HOPS; cp=next,i++)
+        {
+                next=strchr(cp, ',');
+                if (next) *next++='\0';
+		memcpy(&(addrp[i]), numeric_to_addr(cp), sizeof(struct in6_addr));
+#if DEBUG
+		printf("addr str: %s\n", cp);
+		printf("addr ip6: %s\n", addr_to_numeric((numeric_to_addr(cp))));
+		printf("addr [%d]: %s\n", i, addr_to_numeric(&(addrp[i])));
+#endif
+	}
+	if (cp) xtables_error(PARAMETER_PROBLEM, "too many addresses specified");
+
+	free(buffer);
+
+#if DEBUG
+	printf("addr nr: %d\n", i);
+#endif
+
+	return i;
+}
+
+static void rt_init(struct xt_entry_match *m)
+{
+	struct ip6t_rt *rtinfo = (void *)m->data;
+
+	rtinfo->segsleft[1] = ~0U;
+}
+
+static void rt_parse(struct xt_option_call *cb)
+{
+	struct ip6t_rt *rtinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_RT_TYPE:
+		if (cb->invert)
+			rtinfo->invflags |= IP6T_RT_INV_TYP;
+		rtinfo->flags |= IP6T_RT_TYP;
+		break;
+	case O_RT_SEGSLEFT:
+		if (cb->nvals == 1)
+			rtinfo->segsleft[1] = rtinfo->segsleft[0];
+		if (cb->invert)
+			rtinfo->invflags |= IP6T_RT_INV_SGS;
+		rtinfo->flags |= IP6T_RT_SGS;
+		break;
+	case O_RT_LEN:
+		if (cb->invert)
+			rtinfo->invflags |= IP6T_RT_INV_LEN;
+		rtinfo->flags |= IP6T_RT_LEN;
+		break;
+	case O_RT0RES:
+		if (!(cb->xflags & F_RT_TYPE) || rtinfo->rt_type != 0 ||
+		    rtinfo->invflags & IP6T_RT_INV_TYP)
+			xtables_error(PARAMETER_PROBLEM,
+				   "`--rt-type 0' required before `--rt-0-res'");
+		rtinfo->flags |= IP6T_RT_RES;
+		break;
+	case O_RT0ADDRS:
+		if (!(cb->xflags & F_RT_TYPE) || rtinfo->rt_type != 0 ||
+		    rtinfo->invflags & IP6T_RT_INV_TYP)
+			xtables_error(PARAMETER_PROBLEM,
+				   "`--rt-type 0' required before `--rt-0-addrs'");
+		rtinfo->addrnr = parse_addresses(cb->arg, rtinfo->addrs);
+		rtinfo->flags |= IP6T_RT_FST;
+		break;
+	case O_RT0NSTRICT:
+		if (!(cb->xflags & F_RT0ADDRS))
+			xtables_error(PARAMETER_PROBLEM,
+				   "`--rt-0-addr ...' required before `--rt-0-not-strict'");
+		rtinfo->flags |= IP6T_RT_FST_NSTRICT;
+		break;
+	}
+}
+
+static void
+print_nums(const char *name, uint32_t min, uint32_t max,
+	    int invert)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFFFFFF || invert) {
+		printf(" %s", name);
+		if (min == max) {
+			printf(":%s", inv);
+			printf("%u", min);
+		} else {
+			printf("s:%s", inv);
+			printf("%u",min);
+			printf(":");
+			printf("%u",max);
+		}
+	}
+}
+
+static void
+print_addresses(unsigned int addrnr, struct in6_addr *addrp)
+{
+	unsigned int i;
+
+	for(i=0; i<addrnr; i++){
+		printf("%c%s", (i==0)?' ':',', addr_to_numeric(&(addrp[i])));
+	}
+}
+
+static void rt_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	const struct ip6t_rt *rtinfo = (struct ip6t_rt *)match->data;
+
+	printf(" rt");
+	if (rtinfo->flags & IP6T_RT_TYP)
+	    printf(" type:%s%d", rtinfo->invflags & IP6T_RT_INV_TYP ? "!" : "",
+		    rtinfo->rt_type);
+	print_nums("segsleft", rtinfo->segsleft[0], rtinfo->segsleft[1],
+		    rtinfo->invflags & IP6T_RT_INV_SGS);
+	if (rtinfo->flags & IP6T_RT_LEN) {
+		printf(" length");
+		printf(":%s", rtinfo->invflags & IP6T_RT_INV_LEN ? "!" : "");
+		printf("%u", rtinfo->hdrlen);
+	}
+	if (rtinfo->flags & IP6T_RT_RES) printf(" reserved");
+	if (rtinfo->flags & IP6T_RT_FST) printf(" 0-addrs");
+	print_addresses(rtinfo->addrnr, (struct in6_addr *)rtinfo->addrs);
+	if (rtinfo->flags & IP6T_RT_FST_NSTRICT) printf(" 0-not-strict");
+	if (rtinfo->invflags & ~IP6T_RT_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       rtinfo->invflags & ~IP6T_RT_INV_MASK);
+}
+
+static void rt_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_rt *rtinfo = (struct ip6t_rt *)match->data;
+
+	if (rtinfo->flags & IP6T_RT_TYP) {
+		printf("%s --rt-type %u",
+			(rtinfo->invflags & IP6T_RT_INV_TYP) ? " !" : "",
+			rtinfo->rt_type);
+	}
+
+	if (!(rtinfo->segsleft[0] == 0
+	    && rtinfo->segsleft[1] == 0xFFFFFFFF)) {
+		printf("%s --rt-segsleft ",
+			(rtinfo->invflags & IP6T_RT_INV_SGS) ? " !" : "");
+		if (rtinfo->segsleft[0]
+		    != rtinfo->segsleft[1])
+			printf("%u:%u",
+			       rtinfo->segsleft[0],
+			       rtinfo->segsleft[1]);
+		else
+			printf("%u",
+			       rtinfo->segsleft[0]);
+	}
+
+	if (rtinfo->flags & IP6T_RT_LEN) {
+		printf("%s --rt-len %u",
+			(rtinfo->invflags & IP6T_RT_INV_LEN) ? " !" : "", 
+			rtinfo->hdrlen);
+	}
+
+	if (rtinfo->flags & IP6T_RT_RES) printf(" --rt-0-res");
+	if (rtinfo->flags & IP6T_RT_FST) printf(" --rt-0-addrs");
+	print_addresses(rtinfo->addrnr, (struct in6_addr *)rtinfo->addrs);
+	if (rtinfo->flags & IP6T_RT_FST_NSTRICT) printf(" --rt-0-not-strict");
+
+}
+
+static int rt_xlate(struct xt_xlate *xl,
+		    const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_rt *rtinfo = (struct ip6t_rt *)params->match->data;
+	char *space = "";
+
+	if (rtinfo->flags & IP6T_RT_TYP) {
+		xt_xlate_add(xl, "rt type%s %u",
+			     (rtinfo->invflags & IP6T_RT_INV_TYP) ? " !=" : "",
+			      rtinfo->rt_type);
+		space = " ";
+	}
+
+	if (!(rtinfo->segsleft[0] == 0 && rtinfo->segsleft[1] == 0xFFFFFFFF)) {
+		xt_xlate_add(xl, "%srt seg-left%s ", space,
+			     (rtinfo->invflags & IP6T_RT_INV_SGS) ? " !=" : "");
+
+		if (rtinfo->segsleft[0] != rtinfo->segsleft[1])
+			xt_xlate_add(xl, "%u-%u", rtinfo->segsleft[0],
+					rtinfo->segsleft[1]);
+		else
+			xt_xlate_add(xl, "%u", rtinfo->segsleft[0]);
+		space = " ";
+	}
+
+	if (rtinfo->flags & IP6T_RT_LEN) {
+		xt_xlate_add(xl, "%srt hdrlength%s %u", space,
+			     (rtinfo->invflags & IP6T_RT_INV_LEN) ? " !=" : "",
+			      rtinfo->hdrlen);
+	}
+
+	if (rtinfo->flags & (IP6T_RT_RES | IP6T_RT_FST | IP6T_RT_FST_NSTRICT))
+		return 0;
+
+	return 1;
+}
+
+static struct xtables_match rt_mt6_reg = {
+	.name		= "rt",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct ip6t_rt)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ip6t_rt)),
+	.help		= rt_help,
+	.init		= rt_init,
+	.x6_parse	= rt_parse,
+	.print		= rt_print,
+	.save		= rt_save,
+	.x6_options	= rt_opts,
+	.xlate		= rt_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&rt_mt6_reg);
+}
diff --git a/extensions/libip6t_rt.man b/extensions/libip6t_rt.man
new file mode 100644
index 0000000..0443e0a
--- /dev/null
+++ b/extensions/libip6t_rt.man
@@ -0,0 +1,19 @@
+Match on IPv6 routing header
+.TP
+[\fB!\fP] \fB\-\-rt\-type\fP \fItype\fP
+Match the type (numeric).
+.TP
+[\fB!\fP] \fB\-\-rt\-segsleft\fP \fInum\fP[\fB:\fP\fInum\fP]
+Match the `segments left' field (range).
+.TP
+[\fB!\fP] \fB\-\-rt\-len\fP \fIlength\fP
+Match the length of this header.
+.TP
+\fB\-\-rt\-0\-res\fP
+Match the reserved field, too (type=0)
+.TP
+\fB\-\-rt\-0\-addrs\fP \fIaddr\fP[\fB,\fP\fIaddr\fP...]
+Match type=0 addresses (list).
+.TP
+\fB\-\-rt\-0\-not\-strict\fP
+List of type=0 addresses is not a strict list.
diff --git a/extensions/libip6t_rt.t b/extensions/libip6t_rt.t
new file mode 100644
index 0000000..3c7b2d9
--- /dev/null
+++ b/extensions/libip6t_rt.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-m rt --rt-type 0 --rt-segsleft 1:23 --rt-len 42 --rt-0-res;=;OK
+-m rt --rt-type 0 ! --rt-segsleft 1:23 ! --rt-len 42 --rt-0-res;=;OK
+-m rt ! --rt-type 1 ! --rt-segsleft 12:23 ! --rt-len 42;=;OK
+-m rt;=;OK
diff --git a/extensions/libip6t_rt.txlate b/extensions/libip6t_rt.txlate
new file mode 100644
index 0000000..6464cf9
--- /dev/null
+++ b/extensions/libip6t_rt.txlate
@@ -0,0 +1,14 @@
+ip6tables-translate -A INPUT -m rt --rt-type 0 -j DROP
+nft add rule ip6 filter INPUT rt type 0 counter drop
+
+ip6tables-translate -A INPUT -m rt ! --rt-len 22 -j DROP
+nft add rule ip6 filter INPUT rt hdrlength != 22 counter drop
+
+ip6tables-translate -A INPUT -m rt --rt-segsleft 26 -j ACCEPT
+nft add rule ip6 filter INPUT rt seg-left 26 counter accept
+
+ip6tables-translate -A INPUT -m rt --rt-type 0 --rt-len 22 -j DROP
+nft add rule ip6 filter INPUT rt type 0 rt hdrlength 22 counter drop
+
+ip6tables-translate -A INPUT -m rt --rt-type 0 --rt-len 22 ! --rt-segsleft 26 -j ACCEPT
+nft add rule ip6 filter INPUT rt type 0 rt seg-left != 26 rt hdrlength 22 counter accept
diff --git a/extensions/libip6t_srh.c b/extensions/libip6t_srh.c
new file mode 100644
index 0000000..94db6f1
--- /dev/null
+++ b/extensions/libip6t_srh.c
@@ -0,0 +1,501 @@
+/* Shared library to add Segment Routing Header (SRH) matching support.
+ *
+ * Author:
+ *       Ahmed Abdelsalam       <amsalam20@gmail.com>
+ */
+
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_srh.h>
+#include <string.h>
+
+/* srh command-line options */
+enum {
+	O_SRH_NEXTHDR,
+	O_SRH_LEN_EQ,
+	O_SRH_LEN_GT,
+	O_SRH_LEN_LT,
+	O_SRH_SEGS_EQ,
+	O_SRH_SEGS_GT,
+	O_SRH_SEGS_LT,
+	O_SRH_LAST_EQ,
+	O_SRH_LAST_GT,
+	O_SRH_LAST_LT,
+	O_SRH_TAG,
+	O_SRH_PSID,
+	O_SRH_NSID,
+	O_SRH_LSID,
+};
+
+static void srh_help(void)
+{
+	printf(
+"srh match options:\n"
+"[!] --srh-next-hdr		next-hdr        Next Header value of SRH\n"
+"[!] --srh-hdr-len-eq		hdr_len         Hdr Ext Len value of SRH\n"
+"[!] --srh-hdr-len-gt		hdr_len         Hdr Ext Len value of SRH\n"
+"[!] --srh-hdr-len-lt		hdr_len         Hdr Ext Len value of SRH\n"
+"[!] --srh-segs-left-eq		segs_left       Segments Left value of SRH\n"
+"[!] --srh-segs-left-gt		segs_left       Segments Left value of SRH\n"
+"[!] --srh-segs-left-lt		segs_left       Segments Left value of SRH\n"
+"[!] --srh-last-entry-eq 	last_entry      Last Entry value of SRH\n"
+"[!] --srh-last-entry-gt 	last_entry      Last Entry value of SRH\n"
+"[!] --srh-last-entry-lt 	last_entry      Last Entry value of SRH\n"
+"[!] --srh-tag			tag             Tag value of SRH\n"
+"[!] --srh-psid			addr[/mask]	SRH previous SID\n"
+"[!] --srh-nsid			addr[/mask]	SRH next SID\n"
+"[!] --srh-lsid			addr[/mask]	SRH Last SID\n");
+}
+
+#define s struct ip6t_srh
+static const struct xt_option_entry srh_opts[] = {
+	{ .name = "srh-next-hdr", .id = O_SRH_NEXTHDR, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, next_hdr)},
+	{ .name = "srh-hdr-len-eq", .id = O_SRH_LEN_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-hdr-len-gt", .id = O_SRH_LEN_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-hdr-len-lt", .id = O_SRH_LEN_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-segs-left-eq", .id = O_SRH_SEGS_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-segs-left-gt", .id = O_SRH_SEGS_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-segs-left-lt", .id = O_SRH_SEGS_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-last-entry-eq", .id = O_SRH_LAST_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-last-entry-gt", .id = O_SRH_LAST_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-last-entry-lt", .id = O_SRH_LAST_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-tag", .id = O_SRH_TAG, .type = XTTYPE_UINT16,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, tag)},
+	{ }
+};
+#undef s
+
+#define s struct ip6t_srh1
+static const struct xt_option_entry srh1_opts[] = {
+	{ .name = "srh-next-hdr", .id = O_SRH_NEXTHDR, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, next_hdr)},
+	{ .name = "srh-hdr-len-eq", .id = O_SRH_LEN_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-hdr-len-gt", .id = O_SRH_LEN_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-hdr-len-lt", .id = O_SRH_LEN_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-segs-left-eq", .id = O_SRH_SEGS_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-segs-left-gt", .id = O_SRH_SEGS_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-segs-left-lt", .id = O_SRH_SEGS_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-last-entry-eq", .id = O_SRH_LAST_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-last-entry-gt", .id = O_SRH_LAST_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-last-entry-lt", .id = O_SRH_LAST_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-tag", .id = O_SRH_TAG, .type = XTTYPE_UINT16,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, tag)},
+	{ .name = "srh-psid", .id = O_SRH_PSID, .type = XTTYPE_HOSTMASK,
+	.flags = XTOPT_INVERT},
+	{ .name = "srh-nsid", .id = O_SRH_NSID, .type = XTTYPE_HOSTMASK,
+	.flags = XTOPT_INVERT},
+	{ .name = "srh-lsid", .id = O_SRH_LSID, .type = XTTYPE_HOSTMASK,
+	.flags = XTOPT_INVERT},
+	{ }
+};
+#undef s
+
+static void srh_init(struct xt_entry_match *m)
+{
+	struct ip6t_srh *srhinfo = (void *)m->data;
+
+	srhinfo->mt_flags = 0;
+	srhinfo->mt_invflags = 0;
+}
+
+static void srh1_init(struct xt_entry_match *m)
+{
+	struct ip6t_srh1 *srhinfo = (void *)m->data;
+
+	srhinfo->mt_flags = 0;
+	srhinfo->mt_invflags = 0;
+	memset(srhinfo->psid_addr.s6_addr, 0, sizeof(srhinfo->psid_addr.s6_addr));
+	memset(srhinfo->nsid_addr.s6_addr, 0, sizeof(srhinfo->nsid_addr.s6_addr));
+	memset(srhinfo->lsid_addr.s6_addr, 0, sizeof(srhinfo->lsid_addr.s6_addr));
+	memset(srhinfo->psid_msk.s6_addr, 0, sizeof(srhinfo->psid_msk.s6_addr));
+	memset(srhinfo->nsid_msk.s6_addr, 0, sizeof(srhinfo->nsid_msk.s6_addr));
+	memset(srhinfo->lsid_msk.s6_addr, 0, sizeof(srhinfo->lsid_msk.s6_addr));
+}
+
+static void srh_parse(struct xt_option_call *cb)
+{
+	struct ip6t_srh *srhinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRH_NEXTHDR:
+		srhinfo->mt_flags |= IP6T_SRH_NEXTHDR;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_NEXTHDR;
+		break;
+	case O_SRH_LEN_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_EQ;
+		break;
+	case O_SRH_LEN_GT:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_GT;
+		break;
+	case O_SRH_LEN_LT:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_LT;
+		break;
+	case O_SRH_SEGS_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_EQ;
+		break;
+	case O_SRH_SEGS_GT:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_GT;
+		break;
+	case O_SRH_SEGS_LT:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_LT;
+		break;
+	case O_SRH_LAST_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_EQ;
+		break;
+	case O_SRH_LAST_GT:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_GT;
+		break;
+	case O_SRH_LAST_LT:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_LT;
+		break;
+	case O_SRH_TAG:
+		srhinfo->mt_flags |= IP6T_SRH_TAG;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_TAG;
+		break;
+	}
+}
+
+static void srh1_parse(struct xt_option_call *cb)
+{
+	struct ip6t_srh1 *srhinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRH_NEXTHDR:
+		srhinfo->mt_flags |= IP6T_SRH_NEXTHDR;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_NEXTHDR;
+		break;
+	case O_SRH_LEN_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_EQ;
+		break;
+	case O_SRH_LEN_GT:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_GT;
+		break;
+	case O_SRH_LEN_LT:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_LT;
+		break;
+	case O_SRH_SEGS_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_EQ;
+		break;
+	case O_SRH_SEGS_GT:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_GT;
+		break;
+	case O_SRH_SEGS_LT:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_LT;
+		break;
+	case O_SRH_LAST_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_EQ;
+		break;
+	case O_SRH_LAST_GT:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_GT;
+		break;
+	case O_SRH_LAST_LT:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_LT;
+		break;
+	case O_SRH_TAG:
+		srhinfo->mt_flags |= IP6T_SRH_TAG;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_TAG;
+		break;
+	case O_SRH_PSID:
+		srhinfo->mt_flags |= IP6T_SRH_PSID;
+		srhinfo->psid_addr = cb->val.haddr.in6;
+		srhinfo->psid_msk  = cb->val.hmask.in6;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_PSID;
+		break;
+	case O_SRH_NSID:
+		srhinfo->mt_flags |= IP6T_SRH_NSID;
+		srhinfo->nsid_addr = cb->val.haddr.in6;
+		srhinfo->nsid_msk  = cb->val.hmask.in6;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_NSID;
+		break;
+	case O_SRH_LSID:
+		srhinfo->mt_flags |= IP6T_SRH_LSID;
+		srhinfo->lsid_addr = cb->val.haddr.in6;
+		srhinfo->lsid_msk  = cb->val.hmask.in6;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LSID;
+		break;
+	}
+}
+
+static void srh_print(const void *ip, const struct xt_entry_match *match,
+			int numeric)
+{
+	const struct ip6t_srh *srhinfo = (struct ip6t_srh *)match->data;
+
+	printf(" srh");
+	if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR)
+		printf(" next-hdr:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_NEXTHDR ? "!" : "",
+			srhinfo->next_hdr);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ)
+		printf(" hdr-len-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_EQ ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_GT)
+		printf(" hdr-len-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_GT ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_LT)
+		printf(" hdr-len-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_LT ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ)
+		printf(" segs-left-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_EQ ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT)
+		printf(" segs-left-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_GT ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT)
+		printf(" segs-left-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_LT ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ)
+		printf(" last-entry-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_EQ ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_GT)
+		printf(" last-entry-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_GT ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_LT)
+		printf(" last-entry-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_LT ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_TAG)
+		printf(" tag:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_TAG ? "!" : "",
+			srhinfo->tag);
+}
+
+static void srh1_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct ip6t_srh1 *srhinfo = (struct ip6t_srh1 *)match->data;
+
+	printf(" srh");
+	if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR)
+		printf(" next-hdr:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_NEXTHDR ? "!" : "",
+			srhinfo->next_hdr);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ)
+		printf(" hdr-len-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_EQ ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_GT)
+		printf(" hdr-len-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_GT ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_LT)
+		printf(" hdr-len-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_LT ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ)
+		printf(" segs-left-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_EQ ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT)
+		printf(" segs-left-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_GT ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT)
+		printf(" segs-left-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_LT ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ)
+		printf(" last-entry-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_EQ ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_GT)
+		printf(" last-entry-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_GT ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_LT)
+		printf(" last-entry-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_LT ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_TAG)
+		printf(" tag:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_TAG ? "!" : "",
+			srhinfo->tag);
+	if (srhinfo->mt_flags & IP6T_SRH_PSID)
+		printf(" psid %s %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_PSID ? "!" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->psid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->psid_msk));
+	if (srhinfo->mt_flags & IP6T_SRH_NSID)
+		printf(" nsid %s %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_NSID ? "!" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->nsid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->nsid_msk));
+	if (srhinfo->mt_flags & IP6T_SRH_LSID)
+		printf(" lsid %s %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_LSID ? "!" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->lsid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->lsid_msk));
+}
+
+static void srh_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_srh *srhinfo = (struct ip6t_srh *)match->data;
+
+	if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR)
+		printf("%s --srh-next-hdr %u", (srhinfo->mt_invflags & IP6T_SRH_INV_NEXTHDR) ? " !" : "",
+			srhinfo->next_hdr);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ)
+		printf("%s --srh-hdr-len-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_EQ) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_GT)
+		printf("%s --srh-hdr-len-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_GT) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_LT)
+		printf("%s --srh-hdr-len-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_LT) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ)
+		printf("%s --srh-segs-left-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_EQ) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT)
+		printf("%s --srh-segs-left-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_GT) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT)
+		printf("%s --srh-segs-left-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_LT) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ)
+		printf("%s --srh-last-entry-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_EQ) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_GT)
+		printf("%s --srh-last-entry-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_GT) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_LT)
+		printf("%s --srh-last-entry-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_LT) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_TAG)
+		printf("%s --srh-tag %u", (srhinfo->mt_invflags & IP6T_SRH_INV_TAG) ? " !" : "",
+			srhinfo->tag);
+}
+
+static void srh1_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_srh1 *srhinfo = (struct ip6t_srh1 *)match->data;
+
+	if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR)
+		printf("%s --srh-next-hdr %u", (srhinfo->mt_invflags & IP6T_SRH_INV_NEXTHDR) ? " !" : "",
+			srhinfo->next_hdr);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ)
+		printf("%s --srh-hdr-len-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_EQ) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_GT)
+		printf("%s --srh-hdr-len-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_GT) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_LT)
+		printf("%s --srh-hdr-len-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_LT) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ)
+		printf("%s --srh-segs-left-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_EQ) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT)
+		printf("%s --srh-segs-left-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_GT) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT)
+		printf("%s --srh-segs-left-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_LT) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ)
+		printf("%s --srh-last-entry-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_EQ) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_GT)
+		printf("%s --srh-last-entry-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_GT) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_LT)
+		printf("%s --srh-last-entry-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_LT) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_TAG)
+		printf("%s --srh-tag %u", (srhinfo->mt_invflags & IP6T_SRH_INV_TAG) ? " !" : "",
+			srhinfo->tag);
+	if (srhinfo->mt_flags & IP6T_SRH_PSID)
+		printf("%s --srh-psid %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_PSID ? " !" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->psid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->psid_msk));
+	if (srhinfo->mt_flags & IP6T_SRH_NSID)
+		printf("%s --srh-nsid %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_NSID ? " !" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->nsid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->nsid_msk));
+	if (srhinfo->mt_flags & IP6T_SRH_LSID)
+		printf("%s --srh-lsid %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_LSID ? " !" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->lsid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->lsid_msk));
+}
+
+static struct xtables_match srh_mt6_reg[] = {
+	{
+		.name		= "srh",
+		.version	= XTABLES_VERSION,
+		.revision	= 0,
+		.family		= NFPROTO_IPV6,
+		.size		= XT_ALIGN(sizeof(struct ip6t_srh)),
+		.userspacesize	= XT_ALIGN(sizeof(struct ip6t_srh)),
+		.help		= srh_help,
+		.init		= srh_init,
+		.print		= srh_print,
+		.save		= srh_save,
+		.x6_parse	= srh_parse,
+		.x6_options	= srh_opts,
+	},
+	{
+		.name		= "srh",
+		.version	= XTABLES_VERSION,
+		.revision	= 1,
+		.family		= NFPROTO_IPV6,
+		.size		= XT_ALIGN(sizeof(struct ip6t_srh1)),
+		.userspacesize	= XT_ALIGN(sizeof(struct ip6t_srh1)),
+		.help		= srh_help,
+		.init		= srh1_init,
+		.print		= srh1_print,
+		.save		= srh1_save,
+		.x6_parse	= srh1_parse,
+		.x6_options	= srh1_opts,
+	}
+};
+
+void
+_init(void)
+{
+	xtables_register_matches(srh_mt6_reg, ARRAY_SIZE(srh_mt6_reg));
+}
diff --git a/extensions/libip6t_srh.t b/extensions/libip6t_srh.t
new file mode 100644
index 0000000..5b02a71
--- /dev/null
+++ b/extensions/libip6t_srh.t
@@ -0,0 +1,28 @@
+:INPUT,FORWARD,OUTPUT
+-m srh --srh-next-hdr 17;=;OK
+-m srh --srh-hdr-len-eq 8;=;OK
+-m srh --srh-hdr-len-gt 8;=;OK
+-m srh --srh-hdr-len-lt 8;=;OK
+-m srh --srh-segs-left-eq 1;=;OK
+-m srh --srh-segs-left-gt 1;=;OK
+-m srh --srh-segs-left-lt 1;=;OK
+-m srh --srh-last-entry-eq 4;=;OK
+-m srh --srh-last-entry-gt 4;=;OK
+-m srh --srh-last-entry-lt 4;=;OK
+-m srh --srh-tag 0;=;OK
+-m srh ! --srh-next-hdr 17;=;OK
+-m srh ! --srh-hdr-len-eq 8;=;OK
+-m srh ! --srh-hdr-len-gt 8;=;OK
+-m srh ! --srh-hdr-len-lt 8;=;OK
+-m srh ! --srh-segs-left-eq 1;=;OK
+-m srh ! --srh-segs-left-gt 1;=;OK
+-m srh ! --srh-segs-left-lt 1;=;OK
+-m srh ! --srh-last-entry-eq 4;=;OK
+-m srh ! --srh-last-entry-gt 4;=;OK
+-m srh ! --srh-last-entry-lt 4;=;OK
+-m srh ! --srh-tag 0;=;OK
+-m srh --srh-next-hdr 17 --srh-segs-left-eq 1 --srh-last-entry-eq 4 --srh-tag 0;=;OK
+-m srh ! --srh-next-hdr 17 ! --srh-segs-left-eq 0 --srh-tag 0;=;OK
+-m srh --srh-psid a::/64 --srh-nsid b::/128 --srh-lsid c::/0;=;OK
+-m srh ! --srh-psid a::/64 ! --srh-nsid b::/128 ! --srh-lsid c::/0;=;OK
+-m srh;=;OK
diff --git a/extensions/libip6t_standard.t b/extensions/libip6t_standard.t
new file mode 100644
index 0000000..a528af1
--- /dev/null
+++ b/extensions/libip6t_standard.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-s ::/128;=;OK
+! -d ::;! -d ::/128;OK
+! -s ::;! -s ::/128;OK
+-s ::/64;=;OK
diff --git a/extensions/libipt_CLUSTERIP.c b/extensions/libipt_CLUSTERIP.c
new file mode 100644
index 0000000..f4b638b
--- /dev/null
+++ b/extensions/libipt_CLUSTERIP.c
@@ -0,0 +1,195 @@
+/* Shared library add-on to iptables to add CLUSTERIP target support. 
+ * (C) 2003 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Development of this code was funded by SuSE AG, http://www.suse.com/
+ */
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <stddef.h>
+
+#if defined(__GLIBC__) && __GLIBC__ == 2
+#include <net/ethernet.h>
+#else
+#include <linux/if_ether.h>
+#endif
+
+#include <xtables.h>
+#include <linux/netfilter_ipv4/ipt_CLUSTERIP.h>
+
+enum {
+	O_NEW = 0,
+	O_HASHMODE,
+	O_CLUSTERMAC,
+	O_TOTAL_NODES,
+	O_LOCAL_NODE,
+	O_HASH_INIT,
+	F_NEW         = 1 << O_NEW,
+	F_HASHMODE    = 1 << O_HASHMODE,
+	F_CLUSTERMAC  = 1 << O_CLUSTERMAC,
+	F_TOTAL_NODES = 1 << O_TOTAL_NODES,
+	F_LOCAL_NODE  = 1 << O_LOCAL_NODE,
+	F_FULL        = F_NEW | F_HASHMODE | F_CLUSTERMAC |
+	                F_TOTAL_NODES | F_LOCAL_NODE,
+};
+
+static void CLUSTERIP_help(void)
+{
+	printf(
+"CLUSTERIP target options:\n"
+"  --new			 Create a new ClusterIP\n"
+"  --hashmode <mode>		 Specify hashing mode\n"
+"					sourceip\n"
+"					sourceip-sourceport\n"
+"					sourceip-sourceport-destport\n"
+"  --clustermac <mac>		 Set clusterIP MAC address\n"
+"  --total-nodes <num>		 Set number of total nodes in cluster\n"
+"  --local-node <num>		 Set the local node number\n"
+"  --hash-init <num>		 Set init value of the Jenkins hash\n");
+}
+
+#define s struct ipt_clusterip_tgt_info
+static const struct xt_option_entry CLUSTERIP_opts[] = {
+	{.name = "new", .id = O_NEW, .type = XTTYPE_NONE},
+	{.name = "hashmode", .id = O_HASHMODE, .type = XTTYPE_STRING,
+	 .also = O_NEW},
+	{.name = "clustermac", .id = O_CLUSTERMAC, .type = XTTYPE_ETHERMAC,
+	 .also = O_NEW, .flags = XTOPT_PUT, XTOPT_POINTER(s, clustermac)},
+	{.name = "total-nodes", .id = O_TOTAL_NODES, .type = XTTYPE_UINT16,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, num_total_nodes),
+	 .also = O_NEW, .max = CLUSTERIP_MAX_NODES},
+	{.name = "local-node", .id = O_LOCAL_NODE, .type = XTTYPE_UINT16,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, local_nodes[0]),
+	 .also = O_NEW, .max = CLUSTERIP_MAX_NODES},
+	{.name = "hash-init", .id = O_HASH_INIT, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, hash_initval),
+	 .also = O_NEW, .max = UINT_MAX},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void CLUSTERIP_parse(struct xt_option_call *cb)
+{
+	struct ipt_clusterip_tgt_info *cipinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_NEW:
+		cipinfo->flags |= CLUSTERIP_FLAG_NEW;
+		break;
+	case O_HASHMODE:
+		if (strcmp(cb->arg, "sourceip") == 0)
+			cipinfo->hash_mode = CLUSTERIP_HASHMODE_SIP;
+		else if (strcmp(cb->arg, "sourceip-sourceport") == 0)
+			cipinfo->hash_mode = CLUSTERIP_HASHMODE_SIP_SPT;
+		else if (strcmp(cb->arg, "sourceip-sourceport-destport") == 0)
+			cipinfo->hash_mode = CLUSTERIP_HASHMODE_SIP_SPT_DPT;
+		else
+			xtables_error(PARAMETER_PROBLEM, "Unknown hashmode \"%s\"\n",
+				   cb->arg);
+		break;
+	case O_CLUSTERMAC:
+		if (!(cipinfo->clustermac[0] & 0x01))
+			xtables_error(PARAMETER_PROBLEM, "MAC has to be a multicast ethernet address\n");
+		break;
+	case O_LOCAL_NODE:
+		cipinfo->num_local_nodes = 1;
+		break;
+	}
+}
+
+static void CLUSTERIP_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		return;
+	if ((cb->xflags & F_FULL) == F_FULL)
+		return;
+
+	xtables_error(PARAMETER_PROBLEM, "CLUSTERIP target: Invalid parameter combination\n");
+}
+
+static const char *hashmode2str(enum clusterip_hashmode mode)
+{
+	const char *retstr;
+	switch (mode) {
+		case CLUSTERIP_HASHMODE_SIP:
+			retstr = "sourceip";
+			break;
+		case CLUSTERIP_HASHMODE_SIP_SPT:
+			retstr = "sourceip-sourceport";
+			break;
+		case CLUSTERIP_HASHMODE_SIP_SPT_DPT:
+			retstr = "sourceip-sourceport-destport";
+			break;
+		default:
+			retstr = "unknown-error";
+			break;
+	}
+	return retstr;
+}
+
+static const char *mac2str(const uint8_t mac[ETH_ALEN])
+{
+	static char buf[ETH_ALEN*3];
+	sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X",
+		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+	return buf;
+}
+
+static void CLUSTERIP_print(const void *ip,
+                            const struct xt_entry_target *target, int numeric)
+{
+	const struct ipt_clusterip_tgt_info *cipinfo =
+		(const struct ipt_clusterip_tgt_info *)target->data;
+	
+	if (!(cipinfo->flags & CLUSTERIP_FLAG_NEW)) {
+		printf(" CLUSTERIP");
+		return;
+	}
+
+	printf(" CLUSTERIP hashmode=%s clustermac=%s total_nodes=%u local_node=%u hash_init=%u",
+		hashmode2str(cipinfo->hash_mode),
+		mac2str(cipinfo->clustermac),
+		cipinfo->num_total_nodes,
+		cipinfo->local_nodes[0],
+		cipinfo->hash_initval);
+}
+
+static void CLUSTERIP_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ipt_clusterip_tgt_info *cipinfo =
+		(const struct ipt_clusterip_tgt_info *)target->data;
+
+	/* if this is not a new entry, we don't need to save target
+	 * parameters */
+	if (!(cipinfo->flags & CLUSTERIP_FLAG_NEW))
+		return;
+
+	printf(" --new --hashmode %s --clustermac %s --total-nodes %d --local-node %d --hash-init %u",
+	       hashmode2str(cipinfo->hash_mode),
+	       mac2str(cipinfo->clustermac),
+	       cipinfo->num_total_nodes,
+	       cipinfo->local_nodes[0],
+	       cipinfo->hash_initval);
+}
+
+static struct xtables_target clusterip_tg_reg = {
+	.name		= "CLUSTERIP",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct ipt_clusterip_tgt_info)),
+	.userspacesize	= offsetof(struct ipt_clusterip_tgt_info, config),
+ 	.help		= CLUSTERIP_help,
+	.x6_parse	= CLUSTERIP_parse,
+	.x6_fcheck	= CLUSTERIP_check,
+	.print		= CLUSTERIP_print,
+	.save		= CLUSTERIP_save,
+	.x6_options	= CLUSTERIP_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&clusterip_tg_reg);
+}
diff --git a/extensions/libipt_CLUSTERIP.man b/extensions/libipt_CLUSTERIP.man
new file mode 100644
index 0000000..768bb23
--- /dev/null
+++ b/extensions/libipt_CLUSTERIP.man
@@ -0,0 +1,27 @@
+This module allows you to configure a simple cluster of nodes that share
+a certain IP and MAC address without an explicit load balancer in front of
+them.  Connections are statically distributed between the nodes in this
+cluster.
+.PP
+Please note that CLUSTERIP target is considered deprecated in favour of cluster
+match which is more flexible and not limited to IPv4.
+.TP
+\fB\-\-new\fP
+Create a new ClusterIP.  You always have to set this on the first rule
+for a given ClusterIP.
+.TP
+\fB\-\-hashmode\fP \fImode\fP
+Specify the hashing mode.  Has to be one of
+\fBsourceip\fP, \fBsourceip\-sourceport\fP, \fBsourceip\-sourceport\-destport\fP.
+.TP
+\fB\-\-clustermac\fP \fImac\fP
+Specify the ClusterIP MAC address. Has to be a link\-layer multicast address
+.TP
+\fB\-\-total\-nodes\fP \fInum\fP
+Number of total nodes within this cluster.
+.TP
+\fB\-\-local\-node\fP \fInum\fP
+Local node number within this cluster.
+.TP
+\fB\-\-hash\-init\fP \fIrnd\fP
+Specify the random seed used for hash initialization.
diff --git a/extensions/libipt_CLUSTERIP.t b/extensions/libipt_CLUSTERIP.t
new file mode 100644
index 0000000..5af555e
--- /dev/null
+++ b/extensions/libipt_CLUSTERIP.t
@@ -0,0 +1,4 @@
+:INPUT
+-d 10.31.3.236/32 -i lo -j CLUSTERIP --new --hashmode sourceip --clustermac 01:AA:7B:47:F7:D7 --total-nodes 2 --local-node 0 --hash-init 1;=;FAIL
+-d 10.31.3.236/32 -i lo -j CLUSTERIP --new --hashmode sourceip --clustermac 01:AA:7B:47:F7:D7 --total-nodes 2 --local-node 1 --hash-init 1;=;OK
+-d 10.31.3.236/32 -i lo -j CLUSTERIP --new --hashmode sourceip --clustermac 01:AA:7B:47:F7:D7 --total-nodes 2 --local-node 2 --hash-init 1;=;OK
diff --git a/extensions/libipt_DNAT.c b/extensions/libipt_DNAT.c
new file mode 100644
index 0000000..4907a2e
--- /dev/null
+++ b/extensions/libipt_DNAT.c
@@ -0,0 +1,555 @@
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <iptables.h> /* get_kernel_version */
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_DEST = 0,
+	O_RANDOM,
+	O_PERSISTENT,
+	O_X_TO_DEST, /* hidden flag */
+	F_TO_DEST   = 1 << O_TO_DEST,
+	F_RANDOM    = 1 << O_RANDOM,
+	F_X_TO_DEST = 1 << O_X_TO_DEST,
+};
+
+/* Dest NAT data consists of a multi-range, indicating where to map
+   to. */
+struct ipt_natinfo
+{
+	struct xt_entry_target t;
+	struct nf_nat_ipv4_multi_range_compat mr;
+};
+
+static void DNAT_help(void)
+{
+	printf(
+"DNAT target options:\n"
+" --to-destination [<ipaddr>[-<ipaddr>]][:port[-port]]\n"
+"				Address to map destination to.\n"
+"[--random] [--persistent]\n");
+}
+
+static void DNAT_help_v2(void)
+{
+	printf(
+"DNAT target options:\n"
+" --to-destination [<ipaddr>[-<ipaddr>]][:port[-port[/port]]]\n"
+"				Address to map destination to.\n"
+"[--random] [--persistent]\n");
+}
+
+static const struct xt_option_entry DNAT_opts[] = {
+	{.name = "to-destination", .id = O_TO_DEST, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_MULTI},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static struct ipt_natinfo *
+append_range(struct ipt_natinfo *info, const struct nf_nat_ipv4_range *range)
+{
+	unsigned int size;
+
+	/* One rangesize already in struct ipt_natinfo */
+	size = XT_ALIGN(sizeof(*info) + info->mr.rangesize * sizeof(*range));
+
+	info = realloc(info, size);
+	if (!info)
+		xtables_error(OTHER_PROBLEM, "Out of memory\n");
+
+	info->t.u.target_size = size;
+	info->mr.range[info->mr.rangesize] = *range;
+	info->mr.rangesize++;
+
+	return info;
+}
+
+/* Ranges expected in network order. */
+static struct xt_entry_target *
+parse_to(const char *orig_arg, int portok, struct ipt_natinfo *info)
+{
+	struct nf_nat_ipv4_range range;
+	char *arg, *colon, *dash, *error;
+	const struct in_addr *ip;
+
+	arg = strdup(orig_arg);
+	if (arg == NULL)
+		xtables_error(RESOURCE_PROBLEM, "strdup");
+	memset(&range, 0, sizeof(range));
+	colon = strchr(arg, ':');
+
+	if (colon) {
+		int port;
+
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+
+		range.flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+		port = atoi(colon+1);
+		if (port <= 0 || port > 65535)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Port `%s' not valid\n", colon+1);
+
+		error = strchr(colon+1, ':');
+		if (error)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid port:port syntax - use dash\n");
+
+		dash = strchr(colon, '-');
+		if (!dash) {
+			range.min.tcp.port
+				= range.max.tcp.port
+				= htons(port);
+		} else {
+			int maxport;
+
+			maxport = atoi(dash + 1);
+			if (maxport <= 0 || maxport > 65535)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port `%s' not valid\n", dash+1);
+			if (maxport < port)
+				/* People are stupid. */
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port range `%s' funky\n", colon+1);
+			range.min.tcp.port = htons(port);
+			range.max.tcp.port = htons(maxport);
+		}
+		/* Starts with a colon? No IP info...*/
+		if (colon == arg) {
+			free(arg);
+			return &(append_range(info, &range)->t);
+		}
+		*colon = '\0';
+	}
+
+	range.flags |= NF_NAT_RANGE_MAP_IPS;
+	dash = strchr(arg, '-');
+	if (colon && dash && dash > colon)
+		dash = NULL;
+
+	if (dash)
+		*dash = '\0';
+
+	ip = xtables_numeric_to_ipaddr(arg);
+	if (!ip)
+		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+			   arg);
+	range.min_ip = ip->s_addr;
+	if (dash) {
+		ip = xtables_numeric_to_ipaddr(dash+1);
+		if (!ip)
+			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+				   dash+1);
+		range.max_ip = ip->s_addr;
+	} else
+		range.max_ip = range.min_ip;
+
+	free(arg);
+	return &(append_range(info, &range)->t);
+}
+
+static void DNAT_parse(struct xt_option_call *cb)
+{
+	const struct ipt_entry *entry = cb->xt_entry;
+	struct ipt_natinfo *info = (void *)(*cb->target);
+	int portok;
+
+	if (entry->ip.proto == IPPROTO_TCP
+	    || entry->ip.proto == IPPROTO_UDP
+	    || entry->ip.proto == IPPROTO_SCTP
+	    || entry->ip.proto == IPPROTO_DCCP
+	    || entry->ip.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_DEST:
+		if (cb->xflags & F_X_TO_DEST) {
+			if (!kernel_version)
+				get_kernel_version();
+			if (kernel_version > LINUX_VERSION(2, 6, 10))
+				xtables_error(PARAMETER_PROBLEM,
+					   "DNAT: Multiple --to-destination not supported");
+		}
+		*cb->target = parse_to(cb->arg, portok, info);
+		cb->xflags |= F_X_TO_DEST;
+		break;
+	case O_PERSISTENT:
+		info->mr.range[0].flags |= NF_NAT_RANGE_PERSISTENT;
+		break;
+	}
+}
+
+static void DNAT_fcheck(struct xt_fcheck_call *cb)
+{
+	static const unsigned int f = F_TO_DEST | F_RANDOM;
+	struct nf_nat_ipv4_multi_range_compat *mr = cb->data;
+
+	if ((cb->xflags & f) == f)
+		mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
+}
+
+static void print_range(const struct nf_nat_ipv4_range *r)
+{
+	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
+		struct in_addr a;
+
+		a.s_addr = r->min_ip;
+		printf("%s", xtables_ipaddr_to_numeric(&a));
+		if (r->max_ip != r->min_ip) {
+			a.s_addr = r->max_ip;
+			printf("-%s", xtables_ipaddr_to_numeric(&a));
+		}
+	}
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(":");
+		printf("%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			printf("-%hu", ntohs(r->max.tcp.port));
+	}
+}
+
+static void DNAT_print(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+	const struct ipt_natinfo *info = (const void *)target;
+	unsigned int i = 0;
+
+	printf(" to:");
+	for (i = 0; i < info->mr.rangesize; i++) {
+		print_range(&info->mr.range[i]);
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM)
+			printf(" random");
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT)
+			printf(" persistent");
+	}
+}
+
+static void DNAT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ipt_natinfo *info = (const void *)target;
+	unsigned int i = 0;
+
+	for (i = 0; i < info->mr.rangesize; i++) {
+		printf(" --to-destination ");
+		print_range(&info->mr.range[i]);
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM)
+			printf(" --random");
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT)
+			printf(" --persistent");
+	}
+}
+
+static void print_range_xlate(const struct nf_nat_ipv4_range *r,
+			struct xt_xlate *xl)
+{
+	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
+		struct in_addr a;
+
+		a.s_addr = r->min_ip;
+		xt_xlate_add(xl, "%s", xtables_ipaddr_to_numeric(&a));
+		if (r->max_ip != r->min_ip) {
+			a.s_addr = r->max_ip;
+			xt_xlate_add(xl, "-%s", xtables_ipaddr_to_numeric(&a));
+		}
+	}
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, ":%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			xt_xlate_add(xl, "-%hu", ntohs(r->max.tcp.port));
+	}
+}
+
+static int DNAT_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_natinfo *info = (const void *)params->target;
+	unsigned int i = 0;
+	bool sep_need = false;
+	const char *sep = " ";
+
+	for (i = 0; i < info->mr.rangesize; i++) {
+		xt_xlate_add(xl, "dnat to ");
+		print_range_xlate(&info->mr.range[i], xl);
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM) {
+			xt_xlate_add(xl, " random");
+			sep_need = true;
+		}
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT) {
+			if (sep_need)
+				sep = ",";
+			xt_xlate_add(xl, "%spersistent", sep);
+		}
+	}
+
+	return 1;
+}
+
+static void
+parse_to_v2(const char *orig_arg, int portok, struct nf_nat_range2 *range)
+{
+	char *arg, *colon, *dash, *error;
+	const struct in_addr *ip;
+
+	arg = strdup(orig_arg);
+	if (arg == NULL)
+		xtables_error(RESOURCE_PROBLEM, "strdup");
+
+	colon = strchr(arg, ':');
+	if (colon) {
+		int port;
+
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+
+		range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+		port = atoi(colon+1);
+		if (port <= 0 || port > 65535)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Port `%s' not valid\n", colon+1);
+
+		error = strchr(colon+1, ':');
+		if (error)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid port:port syntax - use dash\n");
+
+		dash = strchr(colon, '-');
+		if (!dash) {
+			range->min_proto.tcp.port
+				= range->max_proto.tcp.port
+				= htons(port);
+		} else {
+			int maxport;
+			char *slash;
+
+			maxport = atoi(dash + 1);
+			if (maxport <= 0 || maxport > 65535)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port `%s' not valid\n", dash+1);
+			if (maxport < port)
+				/* People are stupid. */
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port range `%s' funky\n", colon+1);
+			range->min_proto.tcp.port = htons(port);
+			range->max_proto.tcp.port = htons(maxport);
+
+			slash = strchr(dash, '/');
+			if (slash) {
+				int baseport;
+
+				baseport = atoi(slash + 1);
+				if (baseport <= 0 || baseport > 65535)
+					xtables_error(PARAMETER_PROBLEM,
+							 "Port `%s' not valid\n", slash+1);
+				range->flags |= NF_NAT_RANGE_PROTO_OFFSET;
+				range->base_proto.tcp.port = htons(baseport);
+			}
+		}
+		/* Starts with a colon? No IP info...*/
+		if (colon == arg) {
+			free(arg);
+			return;
+		}
+		*colon = '\0';
+	}
+
+	range->flags |= NF_NAT_RANGE_MAP_IPS;
+	dash = strchr(arg, '-');
+	if (colon && dash && dash > colon)
+		dash = NULL;
+
+	if (dash)
+		*dash = '\0';
+
+	ip = xtables_numeric_to_ipaddr(arg);
+	if (!ip)
+		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+			   arg);
+	range->min_addr.in = *ip;
+	if (dash) {
+		ip = xtables_numeric_to_ipaddr(dash+1);
+		if (!ip)
+			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+				   dash+1);
+		range->max_addr.in = *ip;
+	} else
+		range->max_addr = range->min_addr;
+
+	free(arg);
+	return;
+}
+
+static void DNAT_parse_v2(struct xt_option_call *cb)
+{
+	const struct ipt_entry *entry = cb->xt_entry;
+	struct nf_nat_range2 *range = cb->data;
+	int portok;
+
+	if (entry->ip.proto == IPPROTO_TCP
+	    || entry->ip.proto == IPPROTO_UDP
+	    || entry->ip.proto == IPPROTO_SCTP
+	    || entry->ip.proto == IPPROTO_DCCP
+	    || entry->ip.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_DEST:
+		if (cb->xflags & F_X_TO_DEST) {
+			xtables_error(PARAMETER_PROBLEM,
+				   "DNAT: Multiple --to-destination not supported");
+		}
+		parse_to_v2(cb->arg, portok, range);
+		cb->xflags |= F_X_TO_DEST;
+		break;
+	case O_PERSISTENT:
+		range->flags |= NF_NAT_RANGE_PERSISTENT;
+		break;
+	}
+}
+
+static void DNAT_fcheck_v2(struct xt_fcheck_call *cb)
+{
+	static const unsigned int f = F_TO_DEST | F_RANDOM;
+	struct nf_nat_range2 *range = cb->data;
+
+	if ((cb->xflags & f) == f)
+		range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
+}
+
+static void print_range_v2(const struct nf_nat_range2 *range)
+{
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		printf("%s", xtables_ipaddr_to_numeric(&range->min_addr.in));
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr)))
+			printf("-%s", xtables_ipaddr_to_numeric(&range->max_addr.in));
+	}
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(":");
+		printf("%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			printf("-%hu", ntohs(range->max_proto.tcp.port));
+		if (range->flags & NF_NAT_RANGE_PROTO_OFFSET)
+			printf("/%hu", ntohs(range->base_proto.tcp.port));
+	}
+}
+
+static void DNAT_print_v2(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+	const struct nf_nat_range2 *range = (const void *)target->data;
+
+	printf(" to:");
+	print_range_v2(range);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" random");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" persistent");
+}
+
+static void DNAT_save_v2(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_range2 *range = (const void *)target->data;
+
+	printf(" --to-destination ");
+	print_range_v2(range);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" --random");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" --persistent");
+}
+
+static void print_range_xlate_v2(const struct nf_nat_range2 *range,
+			      struct xt_xlate *xl)
+{
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		xt_xlate_add(xl, "%s", xtables_ipaddr_to_numeric(&range->min_addr.in));
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr))) {
+			xt_xlate_add(xl, "-%s", xtables_ipaddr_to_numeric(&range->max_addr.in));
+		}
+	}
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, ":%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			xt_xlate_add(xl, "-%hu", ntohs(range->max_proto.tcp.port));
+		if (range->flags & NF_NAT_RANGE_PROTO_OFFSET)
+			xt_xlate_add(xl, ";%hu", ntohs(range->base_proto.tcp.port));
+	}
+}
+
+static int DNAT_xlate_v2(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_range2 *range = (const void *)params->target->data;
+	bool sep_need = false;
+	const char *sep = " ";
+
+	xt_xlate_add(xl, "dnat to ");
+	print_range_xlate_v2(range, xl);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM) {
+		xt_xlate_add(xl, " random");
+		sep_need = true;
+	}
+	if (range->flags & NF_NAT_RANGE_PERSISTENT) {
+		if (sep_need)
+			sep = ",";
+		xt_xlate_add(xl, "%spersistent", sep);
+	}
+
+	return 1;
+}
+
+static struct xtables_target dnat_tg_reg[] = {
+	{
+		.name		= "DNAT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV4,
+		.revision	= 0,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+		.help		= DNAT_help,
+		.print		= DNAT_print,
+		.save		= DNAT_save,
+		.x6_parse	= DNAT_parse,
+		.x6_fcheck	= DNAT_fcheck,
+		.x6_options	= DNAT_opts,
+		.xlate		= DNAT_xlate,
+	},
+	{
+		.name		= "DNAT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV4,
+		.revision	= 2,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_range2)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range2)),
+		.help		= DNAT_help_v2,
+		.print		= DNAT_print_v2,
+		.save		= DNAT_save_v2,
+		.x6_parse	= DNAT_parse_v2,
+		.x6_fcheck	= DNAT_fcheck_v2,
+		.x6_options	= DNAT_opts,
+		.xlate		= DNAT_xlate_v2,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(dnat_tg_reg, ARRAY_SIZE(dnat_tg_reg));
+}
diff --git a/extensions/libipt_DNAT.t b/extensions/libipt_DNAT.t
new file mode 100644
index 0000000..1c4413b
--- /dev/null
+++ b/extensions/libipt_DNAT.t
@@ -0,0 +1,16 @@
+:PREROUTING
+*nat
+-j DNAT --to-destination 1.1.1.1;=;OK
+-j DNAT --to-destination 1.1.1.1-1.1.1.10;=;OK
+-j DNAT --to-destination 1.1.1.1:1025-65535;;FAIL
+-j DNAT --to-destination 1.1.1.1 --to-destination 2.2.2.2;;FAIL
+-p tcp -j DNAT --to-destination 1.1.1.1:1025-65535;=;OK
+-p tcp -j DNAT --to-destination 1.1.1.1-1.1.1.10:1025-65535;=;OK
+-p tcp -j DNAT --to-destination 1.1.1.1-1.1.1.10:1025-65536;;FAIL
+-p tcp -j DNAT --to-destination 1.1.1.1-1.1.1.10:1025-65535 --to-destination 2.2.2.2-2.2.2.20:1025-65535;;FAIL
+-p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/1000;=;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/3000;=;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/65535;=;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/0;;FAIL
+-p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/65536;;FAIL
+-j DNAT;;FAIL
diff --git a/extensions/libipt_DNAT.txlate b/extensions/libipt_DNAT.txlate
new file mode 100644
index 0000000..e88314d
--- /dev/null
+++ b/extensions/libipt_DNAT.txlate
@@ -0,0 +1,14 @@
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4
+
+iptables-translate -t nat -A prerouting -p tcp -d 15.45.23.67 --dport 80 -j DNAT --to-destination 192.168.1.1-192.168.1.10
+nft add rule ip nat prerouting ip daddr 15.45.23.67 tcp dport 80 counter dnat to 192.168.1.1-192.168.1.10
+
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4:1-1023
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4:1-1023
+
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4 --random
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4 random
+
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4 --random --persistent
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4 random,persistent
diff --git a/extensions/libipt_ECN.c b/extensions/libipt_ECN.c
new file mode 100644
index 0000000..ee09f29
--- /dev/null
+++ b/extensions/libipt_ECN.c
@@ -0,0 +1,145 @@
+/* Shared library add-on to iptables for ECN, $Version$
+ *
+ * (C) 2002 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This program is distributed under the terms of GNU GPL v2, 1991
+ *
+ * libipt_ECN.c borrowed heavily from libipt_DSCP.c
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv4/ipt_ECN.h>
+
+enum {
+	O_ECN_TCP_REMOVE = 0,
+	O_ECN_TCP_CWR,
+	O_ECN_TCP_ECE,
+	O_ECN_IP_ECT,
+	F_ECN_TCP_REMOVE = 1 << O_ECN_TCP_REMOVE,
+	F_ECN_TCP_CWR    = 1 << O_ECN_TCP_CWR,
+	F_ECN_TCP_ECE    = 1 << O_ECN_TCP_ECE,
+};
+
+static void ECN_help(void)
+{
+	printf(
+"ECN target options\n"
+"  --ecn-tcp-remove		Remove all ECN bits from TCP header\n");
+}
+
+#if 0
+"ECN target v%s EXPERIMENTAL options (use with extreme care!)\n"
+"  --ecn-ip-ect			Set the IPv4 ECT codepoint (0 to 3)\n"
+"  --ecn-tcp-cwr		Set the IPv4 CWR bit (0 or 1)\n"
+"  --ecn-tcp-ece		Set the IPv4 ECE bit (0 or 1)\n",
+#endif
+
+static const struct xt_option_entry ECN_opts[] = {
+	{.name = "ecn-tcp-remove", .id = O_ECN_TCP_REMOVE, .type = XTTYPE_NONE,
+	 .excl = F_ECN_TCP_CWR | F_ECN_TCP_ECE},
+	{.name = "ecn-tcp-cwr", .id = O_ECN_TCP_CWR, .type = XTTYPE_UINT8,
+	 .min = 0, .max = 1, .excl = F_ECN_TCP_REMOVE},
+	{.name = "ecn-tcp-ece", .id = O_ECN_TCP_ECE, .type = XTTYPE_UINT8,
+	 .min = 0, .max = 1, .excl = F_ECN_TCP_REMOVE},
+	{.name = "ecn-ip-ect", .id = O_ECN_IP_ECT, .type = XTTYPE_UINT8,
+	 .min = 0, .max = 3},
+	XTOPT_TABLEEND,
+};
+
+static void ECN_parse(struct xt_option_call *cb)
+{
+	struct ipt_ECN_info *einfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_ECN_TCP_REMOVE:
+		einfo->operation = IPT_ECN_OP_SET_ECE | IPT_ECN_OP_SET_CWR;
+		einfo->proto.tcp.ece = 0;
+		einfo->proto.tcp.cwr = 0;
+		break;
+	case O_ECN_TCP_CWR:
+		einfo->operation |= IPT_ECN_OP_SET_CWR;
+		einfo->proto.tcp.cwr = cb->val.u8;
+		break;
+	case O_ECN_TCP_ECE:
+		einfo->operation |= IPT_ECN_OP_SET_ECE;
+		einfo->proto.tcp.ece = cb->val.u8;
+		break;
+	case O_ECN_IP_ECT:
+		einfo->operation |= IPT_ECN_OP_SET_IP;
+		einfo->ip_ect = cb->val.u8;
+		break;
+	}
+}
+
+static void ECN_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM,
+		           "ECN target: An operation is required");
+}
+
+static void ECN_print(const void *ip, const struct xt_entry_target *target,
+                      int numeric)
+{
+	const struct ipt_ECN_info *einfo =
+		(const struct ipt_ECN_info *)target->data;
+
+	printf(" ECN");
+
+	if (einfo->operation == (IPT_ECN_OP_SET_ECE|IPT_ECN_OP_SET_CWR)
+	    && einfo->proto.tcp.ece == 0
+	    && einfo->proto.tcp.cwr == 0)
+		printf(" TCP remove");
+	else {
+		if (einfo->operation & IPT_ECN_OP_SET_ECE)
+			printf(" ECE=%u", einfo->proto.tcp.ece);
+
+		if (einfo->operation & IPT_ECN_OP_SET_CWR)
+			printf(" CWR=%u", einfo->proto.tcp.cwr);
+
+		if (einfo->operation & IPT_ECN_OP_SET_IP)
+			printf(" ECT codepoint=%u", einfo->ip_ect);
+	}
+}
+
+static void ECN_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ipt_ECN_info *einfo =
+		(const struct ipt_ECN_info *)target->data;
+
+	if (einfo->operation == (IPT_ECN_OP_SET_ECE|IPT_ECN_OP_SET_CWR)
+	    && einfo->proto.tcp.ece == 0
+	    && einfo->proto.tcp.cwr == 0)
+		printf(" --ecn-tcp-remove");
+	else {
+
+		if (einfo->operation & IPT_ECN_OP_SET_ECE)
+			printf(" --ecn-tcp-ece %d", einfo->proto.tcp.ece);
+
+		if (einfo->operation & IPT_ECN_OP_SET_CWR)
+			printf(" --ecn-tcp-cwr %d", einfo->proto.tcp.cwr);
+
+		if (einfo->operation & IPT_ECN_OP_SET_IP)
+			printf(" --ecn-ip-ect %d", einfo->ip_ect);
+	}
+}
+
+static struct xtables_target ecn_tg_reg = {
+	.name		= "ECN",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct ipt_ECN_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ipt_ECN_info)),
+	.help		= ECN_help,
+	.print		= ECN_print,
+	.save		= ECN_save,
+	.x6_parse	= ECN_parse,
+	.x6_fcheck	= ECN_check,
+	.x6_options	= ECN_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&ecn_tg_reg);
+}
diff --git a/extensions/libipt_ECN.man b/extensions/libipt_ECN.man
new file mode 100644
index 0000000..8ae7996
--- /dev/null
+++ b/extensions/libipt_ECN.man
@@ -0,0 +1,7 @@
+This target selectively works around known ECN blackholes.
+It can only be used in the mangle table.
+.TP
+\fB\-\-ecn\-tcp\-remove\fP
+Remove all ECN bits from the TCP header.  Of course, it can only be used
+in conjunction with
+\fB\-p tcp\fP.
diff --git a/extensions/libipt_ECN.t b/extensions/libipt_ECN.t
new file mode 100644
index 0000000..2e09205
--- /dev/null
+++ b/extensions/libipt_ECN.t
@@ -0,0 +1,5 @@
+:PREROUTING,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j ECN;;FAIL
+-p tcp -j ECN;;FAIL
+-p tcp -j ECN --ecn-tcp-remove;=;OK
diff --git a/extensions/libipt_LOG.c b/extensions/libipt_LOG.c
new file mode 100644
index 0000000..36e2e73
--- /dev/null
+++ b/extensions/libipt_LOG.c
@@ -0,0 +1,250 @@
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv4/ipt_LOG.h>
+
+#define LOG_DEFAULT_LEVEL LOG_WARNING
+
+#ifndef IPT_LOG_UID /* Old kernel */
+#define IPT_LOG_UID	0x08	/* Log UID owning local socket */
+#undef  IPT_LOG_MASK
+#define IPT_LOG_MASK	0x0f
+#endif
+
+enum {
+	O_LOG_LEVEL = 0,
+	O_LOG_PREFIX,
+	O_LOG_TCPSEQ,
+	O_LOG_TCPOPTS,
+	O_LOG_IPOPTS,
+	O_LOG_UID,
+	O_LOG_MAC,
+};
+
+static void LOG_help(void)
+{
+	printf(
+"LOG target options:\n"
+" --log-level level		Level of logging (numeric or see syslog.conf)\n"
+" --log-prefix prefix		Prefix log messages with this prefix.\n\n"
+" --log-tcp-sequence		Log TCP sequence numbers.\n\n"
+" --log-tcp-options		Log TCP options.\n\n"
+" --log-ip-options		Log IP options.\n\n"
+" --log-uid			Log UID owning the local socket.\n\n"
+" --log-macdecode		Decode MAC addresses and protocol.\n\n");
+}
+
+#define s struct ipt_log_info
+static const struct xt_option_entry LOG_opts[] = {
+	{.name = "log-level", .id = O_LOG_LEVEL, .type = XTTYPE_SYSLOGLEVEL,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, level)},
+	{.name = "log-prefix", .id = O_LOG_PREFIX, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, prefix), .min = 1},
+	{.name = "log-tcp-sequence", .id = O_LOG_TCPSEQ, .type = XTTYPE_NONE},
+	{.name = "log-tcp-options", .id = O_LOG_TCPOPTS, .type = XTTYPE_NONE},
+	{.name = "log-ip-options", .id = O_LOG_IPOPTS, .type = XTTYPE_NONE},
+	{.name = "log-uid", .id = O_LOG_UID, .type = XTTYPE_NONE},
+	{.name = "log-macdecode", .id = O_LOG_MAC, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void LOG_init(struct xt_entry_target *t)
+{
+	struct ipt_log_info *loginfo = (struct ipt_log_info *)t->data;
+
+	loginfo->level = LOG_DEFAULT_LEVEL;
+
+}
+
+struct ipt_log_names {
+	const char *name;
+	unsigned int level;
+};
+
+struct ipt_log_xlate {
+	const char *name;
+	unsigned int level;
+};
+
+static const struct ipt_log_names ipt_log_names[]
+= { { .name = "alert",   .level = LOG_ALERT },
+    { .name = "crit",    .level = LOG_CRIT },
+    { .name = "debug",   .level = LOG_DEBUG },
+    { .name = "emerg",   .level = LOG_EMERG },
+    { .name = "error",   .level = LOG_ERR },		/* DEPRECATED */
+    { .name = "info",    .level = LOG_INFO },
+    { .name = "notice",  .level = LOG_NOTICE },
+    { .name = "panic",   .level = LOG_EMERG },		/* DEPRECATED */
+    { .name = "warning", .level = LOG_WARNING }
+};
+
+static void LOG_parse(struct xt_option_call *cb)
+{
+	struct ipt_log_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_LOG_PREFIX:
+		if (strchr(cb->arg, '\n') != NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Newlines not allowed in --log-prefix");
+		break;
+	case O_LOG_TCPSEQ:
+		info->logflags |= IPT_LOG_TCPSEQ;
+		break;
+	case O_LOG_TCPOPTS:
+		info->logflags |= IPT_LOG_TCPOPT;
+		break;
+	case O_LOG_IPOPTS:
+		info->logflags |= IPT_LOG_IPOPT;
+		break;
+	case O_LOG_UID:
+		info->logflags |= IPT_LOG_UID;
+		break;
+	case O_LOG_MAC:
+		info->logflags |= IPT_LOG_MACDECODE;
+		break;
+	}
+}
+
+static void LOG_print(const void *ip, const struct xt_entry_target *target,
+                      int numeric)
+{
+	const struct ipt_log_info *loginfo
+		= (const struct ipt_log_info *)target->data;
+	unsigned int i = 0;
+
+	printf(" LOG");
+	if (numeric)
+		printf(" flags %u level %u",
+		       loginfo->logflags, loginfo->level);
+	else {
+		for (i = 0; i < ARRAY_SIZE(ipt_log_names); ++i)
+			if (loginfo->level == ipt_log_names[i].level) {
+				printf(" level %s", ipt_log_names[i].name);
+				break;
+			}
+		if (i == ARRAY_SIZE(ipt_log_names))
+			printf(" UNKNOWN level %u", loginfo->level);
+		if (loginfo->logflags & IPT_LOG_TCPSEQ)
+			printf(" tcp-sequence");
+		if (loginfo->logflags & IPT_LOG_TCPOPT)
+			printf(" tcp-options");
+		if (loginfo->logflags & IPT_LOG_IPOPT)
+			printf(" ip-options");
+		if (loginfo->logflags & IPT_LOG_UID)
+			printf(" uid");
+		if (loginfo->logflags & IPT_LOG_MACDECODE)
+			printf(" macdecode");
+		if (loginfo->logflags & ~(IPT_LOG_MASK))
+			printf(" unknown-flags");
+	}
+
+	if (strcmp(loginfo->prefix, "") != 0)
+		printf(" prefix \"%s\"", loginfo->prefix);
+}
+
+static void LOG_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ipt_log_info *loginfo
+		= (const struct ipt_log_info *)target->data;
+
+	if (strcmp(loginfo->prefix, "") != 0) {
+		printf(" --log-prefix");
+		xtables_save_string(loginfo->prefix);
+	}
+
+	if (loginfo->level != LOG_DEFAULT_LEVEL)
+		printf(" --log-level %d", loginfo->level);
+
+	if (loginfo->logflags & IPT_LOG_TCPSEQ)
+		printf(" --log-tcp-sequence");
+	if (loginfo->logflags & IPT_LOG_TCPOPT)
+		printf(" --log-tcp-options");
+	if (loginfo->logflags & IPT_LOG_IPOPT)
+		printf(" --log-ip-options");
+	if (loginfo->logflags & IPT_LOG_UID)
+		printf(" --log-uid");
+	if (loginfo->logflags & IPT_LOG_MACDECODE)
+		printf(" --log-macdecode");
+}
+
+static const struct ipt_log_xlate ipt_log_xlate_names[] = {
+	{"alert",	LOG_ALERT },
+	{"crit",	LOG_CRIT },
+	{"debug",	LOG_DEBUG },
+	{"emerg",	LOG_EMERG },
+	{"err",		LOG_ERR },
+	{"info",	LOG_INFO },
+	{"notice",	LOG_NOTICE },
+	{"warn",	LOG_WARNING }
+};
+
+static int LOG_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_log_info *loginfo =
+		(const struct ipt_log_info *)params->target->data;
+	unsigned int i = 0;
+
+	xt_xlate_add(xl, "log");
+	if (strcmp(loginfo->prefix, "") != 0) {
+		if (params->escape_quotes)
+			xt_xlate_add(xl, " prefix \\\"%s\\\"", loginfo->prefix);
+		else
+			xt_xlate_add(xl, " prefix \"%s\"", loginfo->prefix);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(ipt_log_xlate_names); ++i)
+		if (loginfo->level != LOG_DEFAULT_LEVEL &&
+		    loginfo->level == ipt_log_xlate_names[i].level) {
+			xt_xlate_add(xl, " level %s",
+				   ipt_log_xlate_names[i].name);
+			break;
+		}
+
+	if ((loginfo->logflags & IPT_LOG_MASK) == IPT_LOG_MASK) {
+		xt_xlate_add(xl, " flags all");
+	} else {
+		if (loginfo->logflags & (IPT_LOG_TCPSEQ | IPT_LOG_TCPOPT)) {
+			const char *delim = " ";
+
+			xt_xlate_add(xl, " flags tcp");
+			if (loginfo->logflags & IPT_LOG_TCPSEQ) {
+				xt_xlate_add(xl, " sequence");
+				delim = ",";
+			}
+			if (loginfo->logflags & IPT_LOG_TCPOPT)
+				xt_xlate_add(xl, "%soptions", delim);
+		}
+		if (loginfo->logflags & IPT_LOG_IPOPT)
+			xt_xlate_add(xl, " flags ip options");
+		if (loginfo->logflags & IPT_LOG_UID)
+			xt_xlate_add(xl, " flags skuid");
+		if (loginfo->logflags & IPT_LOG_MACDECODE)
+			xt_xlate_add(xl, " flags ether");
+	}
+
+	return 1;
+}
+static struct xtables_target log_tg_reg = {
+	.name          = "LOG",
+	.version       = XTABLES_VERSION,
+	.family        = NFPROTO_IPV4,
+	.size          = XT_ALIGN(sizeof(struct ipt_log_info)),
+	.userspacesize = XT_ALIGN(sizeof(struct ipt_log_info)),
+	.help          = LOG_help,
+	.init          = LOG_init,
+	.print         = LOG_print,
+	.save          = LOG_save,
+	.x6_parse      = LOG_parse,
+	.x6_options    = LOG_opts,
+	.xlate	       = LOG_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&log_tg_reg);
+}
diff --git a/extensions/libipt_LOG.t b/extensions/libipt_LOG.t
new file mode 100644
index 0000000..fbf5118
--- /dev/null
+++ b/extensions/libipt_LOG.t
@@ -0,0 +1,12 @@
+:INPUT,FORWARD,OUTPUT
+-j LOG;-j LOG;OK
+-j LOG --log-prefix "test: ";=;OK
+-j LOG --log-prefix "test: " --log-level 1;=;OK
+# iptables displays the log-level output using the number; not the string
+-j LOG --log-prefix "test: " --log-level alert;-j LOG --log-prefix "test: " --log-level 1;OK
+-j LOG --log-prefix "test: " --log-tcp-sequence;=;OK
+-j LOG --log-prefix "test: " --log-tcp-options;=;OK
+-j LOG --log-prefix "test: " --log-ip-options;=;OK
+-j LOG --log-prefix "test: " --log-uid;=;OK
+-j LOG --log-prefix "test: " --log-level bad;;FAIL
+-j LOG --log-prefix;;FAIL
diff --git a/extensions/libipt_LOG.txlate b/extensions/libipt_LOG.txlate
new file mode 100644
index 0000000..81f64fb
--- /dev/null
+++ b/extensions/libipt_LOG.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A FORWARD -p tcp -j LOG --log-level error
+nft add rule ip filter FORWARD ip protocol tcp counter log level err
+
+iptables-translate -A FORWARD -p tcp -j LOG --log-prefix "Random prefix"
+nft add rule ip filter FORWARD ip protocol tcp counter log prefix \"Random prefix\"
diff --git a/extensions/libipt_MASQUERADE.c b/extensions/libipt_MASQUERADE.c
new file mode 100644
index 0000000..90bf606
--- /dev/null
+++ b/extensions/libipt_MASQUERADE.c
@@ -0,0 +1,190 @@
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_PORTS = 0,
+	O_RANDOM,
+	O_RANDOM_FULLY,
+};
+
+static void MASQUERADE_help(void)
+{
+	printf(
+"MASQUERADE target options:\n"
+" --to-ports <port>[-<port>]\n"
+"				Port (range) to map to.\n"
+" --random\n"
+"				Randomize source port.\n"
+" --random-fully\n"
+"				Fully randomize source port.\n");
+}
+
+static const struct xt_option_entry MASQUERADE_opts[] = {
+	{.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static void MASQUERADE_init(struct xt_entry_target *t)
+{
+	struct nf_nat_ipv4_multi_range_compat *mr = (struct nf_nat_ipv4_multi_range_compat *)t->data;
+
+	/* Actually, it's 0, but it's ignored at the moment. */
+	mr->rangesize = 1;
+}
+
+/* Parses ports */
+static void
+parse_ports(const char *arg, struct nf_nat_ipv4_multi_range_compat *mr)
+{
+	char *end;
+	unsigned int port, maxport;
+
+	mr->range[0].flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX))
+		xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--to-ports", arg);
+
+	switch (*end) {
+	case '\0':
+		mr->range[0].min.tcp.port
+			= mr->range[0].max.tcp.port
+			= htons(port);
+		return;
+	case '-':
+		if (!xtables_strtoui(end + 1, NULL, &maxport, 0, UINT16_MAX))
+			break;
+
+		if (maxport < port)
+			break;
+
+		mr->range[0].min.tcp.port = htons(port);
+		mr->range[0].max.tcp.port = htons(maxport);
+		return;
+	default:
+		break;
+	}
+	xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--to-ports", arg);
+}
+
+static void MASQUERADE_parse(struct xt_option_call *cb)
+{
+	const struct ipt_entry *entry = cb->xt_entry;
+	int portok;
+	struct nf_nat_ipv4_multi_range_compat *mr = cb->data;
+
+	if (entry->ip.proto == IPPROTO_TCP
+	    || entry->ip.proto == IPPROTO_UDP
+	    || entry->ip.proto == IPPROTO_SCTP
+	    || entry->ip.proto == IPPROTO_DCCP
+	    || entry->ip.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_PORTS:
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+		parse_ports(cb->arg, mr);
+		break;
+	case O_RANDOM:
+		mr->range[0].flags |=  NF_NAT_RANGE_PROTO_RANDOM;
+		break;
+	case O_RANDOM_FULLY:
+		mr->range[0].flags |=  NF_NAT_RANGE_PROTO_RANDOM_FULLY;
+		break;
+	}
+}
+
+static void
+MASQUERADE_print(const void *ip, const struct xt_entry_target *target,
+                 int numeric)
+{
+	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" masq ports: ");
+		printf("%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			printf("-%hu", ntohs(r->max.tcp.port));
+	}
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" random");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" random-fully");
+}
+
+static void
+MASQUERADE_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" --to-ports %hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			printf("-%hu", ntohs(r->max.tcp.port));
+	}
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" --random");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" --random-fully");
+}
+
+static int MASQUERADE_xlate(struct xt_xlate *xl,
+			    const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_ipv4_multi_range_compat *mr =
+		(const void *)params->target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
+
+	xt_xlate_add(xl, "masquerade");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, " to :%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			xt_xlate_add(xl, "-%hu", ntohs(r->max.tcp.port));
+        }
+
+	xt_xlate_add(xl, " ");
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		xt_xlate_add(xl, "random ");
+
+	return 1;
+}
+
+static struct xtables_target masquerade_tg_reg = {
+	.name		= "MASQUERADE",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.help		= MASQUERADE_help,
+	.init		= MASQUERADE_init,
+	.x6_parse	= MASQUERADE_parse,
+	.print		= MASQUERADE_print,
+	.save		= MASQUERADE_save,
+	.x6_options	= MASQUERADE_opts,
+	.xlate		= MASQUERADE_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&masquerade_tg_reg);
+}
diff --git a/extensions/libipt_MASQUERADE.t b/extensions/libipt_MASQUERADE.t
new file mode 100644
index 0000000..e25d2a0
--- /dev/null
+++ b/extensions/libipt_MASQUERADE.t
@@ -0,0 +1,9 @@
+:POSTROUTING
+*nat
+-j MASQUERADE;=;OK
+-j MASQUERADE --random;=;OK
+-j MASQUERADE --random-fully;=;OK
+-p tcp -j MASQUERADE --to-ports 1024;=;OK
+-p udp -j MASQUERADE --to-ports 1024-65535;=;OK
+-p udp -j MASQUERADE --to-ports 1024-65536;;FAIL
+-p udp -j MASQUERADE --to-ports -1;;FAIL
diff --git a/extensions/libipt_MASQUERADE.txlate b/extensions/libipt_MASQUERADE.txlate
new file mode 100644
index 0000000..40b6958
--- /dev/null
+++ b/extensions/libipt_MASQUERADE.txlate
@@ -0,0 +1,8 @@
+iptables-translate -t nat -A POSTROUTING -j MASQUERADE
+nft add rule ip nat POSTROUTING counter masquerade
+
+iptables-translate -t nat -A POSTROUTING -p tcp -j MASQUERADE --to-ports 10
+nft add rule ip nat POSTROUTING ip protocol tcp counter masquerade to :10
+
+iptables-translate -t nat -A POSTROUTING -p tcp -j MASQUERADE --to-ports 10-20 --random
+nft add rule ip nat POSTROUTING ip protocol tcp counter masquerade to :10-20 random
diff --git a/extensions/libipt_NETMAP.c b/extensions/libipt_NETMAP.c
new file mode 100644
index 0000000..f30615a
--- /dev/null
+++ b/extensions/libipt_NETMAP.c
@@ -0,0 +1,113 @@
+/* Shared library add-on to iptables to add static NAT support.
+   Author: Svenning Soerensen <svenning@post5.tele.dk>
+*/
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter/nf_nat.h>
+
+#define MODULENAME "NETMAP"
+
+enum {
+	O_TO = 0,
+};
+
+static const struct xt_option_entry NETMAP_opts[] = {
+	{.name = "to", .id = O_TO, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_MAND},
+	XTOPT_TABLEEND,
+};
+
+static void NETMAP_help(void)
+{
+	printf(MODULENAME" target options:\n"
+	       "  --%s address[/mask]\n"
+	       "				Network address to map to.\n\n",
+	       NETMAP_opts[0].name);
+}
+
+static int
+netmask2bits(uint32_t netmask)
+{
+	uint32_t bm;
+	int bits;
+
+	netmask = ntohl(netmask);
+	for (bits = 0, bm = 0x80000000; netmask & bm; netmask <<= 1)
+		bits++;
+	if (netmask)
+		return -1; /* holes in netmask */
+	return bits;
+}
+
+static void NETMAP_init(struct xt_entry_target *t)
+{
+	struct nf_nat_ipv4_multi_range_compat *mr = (struct nf_nat_ipv4_multi_range_compat *)t->data;
+
+	/* Actually, it's 0, but it's ignored at the moment. */
+	mr->rangesize = 1;
+}
+
+static void NETMAP_parse(struct xt_option_call *cb)
+{
+	struct nf_nat_ipv4_multi_range_compat *mr = cb->data;
+	struct nf_nat_ipv4_range *range = &mr->range[0];
+
+	xtables_option_parse(cb);
+	range->flags |= NF_NAT_RANGE_MAP_IPS;
+	range->min_ip = cb->val.haddr.ip & cb->val.hmask.ip;
+	range->max_ip = range->min_ip | ~cb->val.hmask.ip;
+}
+
+static void __NETMAP_print(const void *ip, const struct xt_entry_target *target,
+			   int numeric)
+{
+	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
+	struct in_addr a;
+	int bits;
+
+	a.s_addr = r->min_ip;
+	printf("%s", xtables_ipaddr_to_numeric(&a));
+	a.s_addr = ~(r->min_ip ^ r->max_ip);
+	bits = netmask2bits(a.s_addr);
+	if (bits < 0)
+		printf("/%s", xtables_ipaddr_to_numeric(&a));
+	else
+		printf("/%d", bits);
+}
+
+static void NETMAP_print(const void *ip, const struct xt_entry_target *target,
+			 int numeric)
+{
+	printf(" to:");
+	__NETMAP_print(ip, target, numeric);
+}
+
+static void NETMAP_save(const void *ip, const struct xt_entry_target *target)
+{
+	printf(" --%s ", NETMAP_opts[0].name);
+	__NETMAP_print(ip, target, 0);
+}
+
+static struct xtables_target netmap_tg_reg = {
+	.name		= MODULENAME,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.help		= NETMAP_help,
+	.init		= NETMAP_init,
+	.x6_parse	= NETMAP_parse,
+	.print		= NETMAP_print,
+	.save		= NETMAP_save,
+	.x6_options	= NETMAP_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&netmap_tg_reg);
+}
diff --git a/extensions/libipt_NETMAP.t b/extensions/libipt_NETMAP.t
new file mode 100644
index 0000000..31924b9
--- /dev/null
+++ b/extensions/libipt_NETMAP.t
@@ -0,0 +1,4 @@
+:PREROUTING,INPUT,OUTPUT,POSTROUTING
+*nat
+-j NETMAP --to 1.2.3.0/24;=;OK
+-j NETMAP --to 1.2.3.4;=;OK
diff --git a/extensions/libipt_REDIRECT.c b/extensions/libipt_REDIRECT.c
new file mode 100644
index 0000000..7850306
--- /dev/null
+++ b/extensions/libipt_REDIRECT.c
@@ -0,0 +1,174 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_PORTS = 0,
+	O_RANDOM,
+	F_TO_PORTS = 1 << O_TO_PORTS,
+	F_RANDOM   = 1 << O_RANDOM,
+};
+
+static void REDIRECT_help(void)
+{
+	printf(
+"REDIRECT target options:\n"
+" --to-ports <port>[-<port>]\n"
+"				Port (range) to map to.\n"
+" [--random]\n");
+}
+
+static const struct xt_option_entry REDIRECT_opts[] = {
+	{.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static void REDIRECT_init(struct xt_entry_target *t)
+{
+	struct nf_nat_ipv4_multi_range_compat *mr = (struct nf_nat_ipv4_multi_range_compat *)t->data;
+
+	/* Actually, it's 0, but it's ignored at the moment. */
+	mr->rangesize = 1;
+}
+
+/* Parses ports */
+static void
+parse_ports(const char *arg, struct nf_nat_ipv4_multi_range_compat *mr)
+{
+	char *end = "";
+	unsigned int port, maxport;
+
+	mr->range[0].flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX) &&
+	    (port = xtables_service_to_port(arg, NULL)) == (unsigned)-1)
+		xtables_param_act(XTF_BAD_VALUE, "REDIRECT", "--to-ports", arg);
+
+	switch (*end) {
+	case '\0':
+		mr->range[0].min.tcp.port
+			= mr->range[0].max.tcp.port
+			= htons(port);
+		return;
+	case '-':
+		if (!xtables_strtoui(end + 1, NULL, &maxport, 0, UINT16_MAX) &&
+		    (maxport = xtables_service_to_port(end + 1, NULL)) == (unsigned)-1)
+			break;
+
+		if (maxport < port)
+			break;
+
+		mr->range[0].min.tcp.port = htons(port);
+		mr->range[0].max.tcp.port = htons(maxport);
+		return;
+	default:
+		break;
+	}
+	xtables_param_act(XTF_BAD_VALUE, "REDIRECT", "--to-ports", arg);
+}
+
+static void REDIRECT_parse(struct xt_option_call *cb)
+{
+	const struct ipt_entry *entry = cb->xt_entry;
+	struct nf_nat_ipv4_multi_range_compat *mr = (void *)(*cb->target)->data;
+	int portok;
+
+	if (entry->ip.proto == IPPROTO_TCP
+	    || entry->ip.proto == IPPROTO_UDP
+	    || entry->ip.proto == IPPROTO_SCTP
+	    || entry->ip.proto == IPPROTO_DCCP
+	    || entry->ip.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_PORTS:
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+		parse_ports(cb->arg, mr);
+		if (cb->xflags & F_RANDOM)
+			mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
+		break;
+	case O_RANDOM:
+		if (cb->xflags & F_TO_PORTS)
+			mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
+		break;
+	}
+}
+
+static void REDIRECT_print(const void *ip, const struct xt_entry_target *target,
+                           int numeric)
+{
+	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" redir ports ");
+		printf("%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			printf("-%hu", ntohs(r->max.tcp.port));
+		if (mr->range[0].flags & NF_NAT_RANGE_PROTO_RANDOM)
+			printf(" random");
+	}
+}
+
+static void REDIRECT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" --to-ports ");
+		printf("%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			printf("-%hu", ntohs(r->max.tcp.port));
+		if (mr->range[0].flags & NF_NAT_RANGE_PROTO_RANDOM)
+			printf(" --random");
+	}
+}
+
+static int REDIRECT_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_ipv4_multi_range_compat *mr =
+		(const void *)params->target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, "redirect to :%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			xt_xlate_add(xl, "-%hu ", ntohs(r->max.tcp.port));
+		if (mr->range[0].flags & NF_NAT_RANGE_PROTO_RANDOM)
+			xt_xlate_add(xl, " random ");
+	}
+
+	return 1;
+}
+
+static struct xtables_target redirect_tg_reg = {
+	.name		= "REDIRECT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.help		= REDIRECT_help,
+	.init		= REDIRECT_init,
+ 	.x6_parse	= REDIRECT_parse,
+	.print		= REDIRECT_print,
+	.save		= REDIRECT_save,
+	.x6_options	= REDIRECT_opts,
+	.xlate		= REDIRECT_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&redirect_tg_reg);
+}
diff --git a/extensions/libipt_REDIRECT.t b/extensions/libipt_REDIRECT.t
new file mode 100644
index 0000000..a0fb0ed
--- /dev/null
+++ b/extensions/libipt_REDIRECT.t
@@ -0,0 +1,6 @@
+:PREROUTING,OUTPUT
+*nat
+-p tcp -j REDIRECT --to-ports 42;=;OK
+-p udp -j REDIRECT --to-ports 42-1234;=;OK
+-p tcp -j REDIRECT --to-ports 42-1234 --random;=;OK
+-j REDIRECT --to-ports 42;;FAIL
diff --git a/extensions/libipt_REDIRECT.txlate b/extensions/libipt_REDIRECT.txlate
new file mode 100644
index 0000000..815bb77
--- /dev/null
+++ b/extensions/libipt_REDIRECT.txlate
@@ -0,0 +1,5 @@
+iptables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080
+nft add rule ip nat prerouting tcp dport 80 counter redirect to :8080
+
+iptables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080 --random
+nft add rule ip nat prerouting tcp dport 80 counter redirect to :8080 random
diff --git a/extensions/libipt_REJECT.c b/extensions/libipt_REJECT.c
new file mode 100644
index 0000000..743dfff
--- /dev/null
+++ b/extensions/libipt_REJECT.c
@@ -0,0 +1,203 @@
+/* Shared library add-on to iptables to add customized REJECT support.
+ *
+ * (C) 2000 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv4/ipt_REJECT.h>
+#include <linux/version.h>
+
+/* If we are compiling against a kernel that does not support
+ * IPT_ICMP_ADMIN_PROHIBITED, we are emulating it.
+ * The result will be a plain DROP of the packet instead of
+ * reject. -- Maciej Soltysiak <solt@dns.toxicfilms.tv>
+ */
+#ifndef IPT_ICMP_ADMIN_PROHIBITED
+#define IPT_ICMP_ADMIN_PROHIBITED	IPT_TCP_RESET + 1
+#endif
+
+struct reject_names {
+	const char *name;
+	const char *alias;
+	const char *desc;
+	const char *xlate;
+};
+
+enum {
+	O_REJECT_WITH = 0,
+};
+
+static const struct reject_names reject_table[] = {
+	[IPT_ICMP_NET_UNREACHABLE] = {
+		"icmp-net-unreachable", "net-unreach",
+		"ICMP network unreachable",
+		"net-unreachable",
+	},
+	[IPT_ICMP_HOST_UNREACHABLE] = {
+		"icmp-host-unreachable", "host-unreach",
+		"ICMP host unreachable",
+		"host-unreachable",
+	},
+	[IPT_ICMP_PROT_UNREACHABLE] = {
+		"icmp-proto-unreachable", "proto-unreach",
+		"ICMP protocol unreachable",
+		"prot-unreachable",
+	},
+	[IPT_ICMP_PORT_UNREACHABLE] = {
+		"icmp-port-unreachable", "port-unreach",
+		"ICMP port unreachable (default)",
+		"port-unreachable",
+	},
+#if 0
+	[IPT_ICMP_ECHOREPLY] = {
+		"echo-reply", "echoreply",
+		"for ICMP echo only: faked ICMP echo reply",
+		"echo-reply",
+	},
+#endif
+	[IPT_ICMP_NET_PROHIBITED] = {
+		"icmp-net-prohibited", "net-prohib",
+		"ICMP network prohibited",
+		"net-prohibited",
+	},
+	[IPT_ICMP_HOST_PROHIBITED] = {
+		"icmp-host-prohibited", "host-prohib",
+		"ICMP host prohibited",
+		"host-prohibited",
+	},
+	[IPT_TCP_RESET] = {
+		"tcp-reset", "tcp-rst",
+		"TCP RST packet",
+		"tcp reset",
+	},
+	[IPT_ICMP_ADMIN_PROHIBITED] = {
+		"icmp-admin-prohibited", "admin-prohib",
+		"ICMP administratively prohibited (*)",
+		"admin-prohibited",
+	},
+};
+
+static void
+print_reject_types(void)
+{
+	unsigned int i;
+
+	printf("Valid reject types:\n");
+
+	for (i = 0; i < ARRAY_SIZE(reject_table); ++i) {
+		if (!reject_table[i].name)
+			continue;
+		printf("    %-25s\t%s\n", reject_table[i].name, reject_table[i].desc);
+		printf("    %-25s\talias\n", reject_table[i].alias);
+	}
+	printf("\n");
+}
+
+static void REJECT_help(void)
+{
+	printf(
+"REJECT target options:\n"
+"--reject-with type              drop input packet and send back\n"
+"                                a reply packet according to type:\n");
+
+	print_reject_types();
+
+	printf("(*) See man page or read the INCOMPATIBILITES file for compatibility issues.\n");
+}
+
+static const struct xt_option_entry REJECT_opts[] = {
+	{.name = "reject-with", .id = O_REJECT_WITH, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+
+static void REJECT_init(struct xt_entry_target *t)
+{
+	struct ipt_reject_info *reject = (struct ipt_reject_info *)t->data;
+
+	/* default */
+	reject->with = IPT_ICMP_PORT_UNREACHABLE;
+
+}
+
+static void REJECT_parse(struct xt_option_call *cb)
+{
+	struct ipt_reject_info *reject = cb->data;
+	unsigned int i;
+
+	xtables_option_parse(cb);
+	for (i = 0; i < ARRAY_SIZE(reject_table); ++i) {
+		if (!reject_table[i].name)
+			continue;
+		if (strncasecmp(reject_table[i].name,
+		      cb->arg, strlen(cb->arg)) == 0 ||
+		    strncasecmp(reject_table[i].alias,
+		      cb->arg, strlen(cb->arg)) == 0) {
+			reject->with = i;
+			return;
+		}
+	}
+	/* This due to be dropped late in 2.4 pre-release cycle --RR */
+	if (strncasecmp("echo-reply", cb->arg, strlen(cb->arg)) == 0 ||
+	    strncasecmp("echoreply", cb->arg, strlen(cb->arg)) == 0)
+		fprintf(stderr, "--reject-with echo-reply no longer"
+			" supported\n");
+	xtables_error(PARAMETER_PROBLEM,
+		"unknown reject type \"%s\"", cb->arg);
+}
+
+static void REJECT_print(const void *ip, const struct xt_entry_target *target,
+                         int numeric)
+{
+	const struct ipt_reject_info *reject
+		= (const struct ipt_reject_info *)target->data;
+
+	printf(" reject-with %s", reject_table[reject->with].name);
+}
+
+static void REJECT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ipt_reject_info *reject =
+		(const struct ipt_reject_info *)target->data;
+
+	printf(" --reject-with %s", reject_table[reject->with].name);
+}
+
+static int REJECT_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_reject_info *reject =
+		(const struct ipt_reject_info *)params->target->data;
+
+	if (reject->with == IPT_ICMP_PORT_UNREACHABLE)
+		xt_xlate_add(xl, "reject");
+	else if (reject->with == IPT_TCP_RESET)
+		xt_xlate_add(xl, "reject with %s",
+			     reject_table[reject->with].xlate);
+	else
+		xt_xlate_add(xl, "reject with icmp type %s",
+			     reject_table[reject->with].xlate);
+
+	return 1;
+}
+
+
+static struct xtables_target reject_tg_reg = {
+	.name		= "REJECT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct ipt_reject_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ipt_reject_info)),
+	.help		= REJECT_help,
+	.init		= REJECT_init,
+	.print		= REJECT_print,
+	.save		= REJECT_save,
+	.x6_parse	= REJECT_parse,
+	.x6_options	= REJECT_opts,
+	.xlate		= REJECT_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&reject_tg_reg);
+}
diff --git a/extensions/libipt_REJECT.man b/extensions/libipt_REJECT.man
new file mode 100644
index 0000000..cc47aea
--- /dev/null
+++ b/extensions/libipt_REJECT.man
@@ -0,0 +1,52 @@
+This is used to send back an error packet in response to the matched
+packet: otherwise it is equivalent to
+.B DROP
+so it is a terminating TARGET, ending rule traversal.
+This target is only valid in the
+.BR INPUT ,
+.B FORWARD
+and
+.B OUTPUT
+chains, and user-defined chains which are only called from those
+chains.  The following option controls the nature of the error packet
+returned:
+.TP
+\fB\-\-reject\-with\fP \fItype\fP
+The type given can be
+\fBicmp\-net\-unreachable\fP,
+\fBicmp\-host\-unreachable\fP,
+\fBicmp\-port\-unreachable\fP,
+\fBicmp\-proto\-unreachable\fP,
+\fBicmp\-net\-prohibited\fP,
+\fBicmp\-host\-prohibited\fP, or
+\fBicmp\-admin\-prohibited\fP (*),
+which return the appropriate ICMP error message (\fBicmp\-port\-unreachable\fP is
+the default).  The option
+\fBtcp\-reset\fP
+can be used on rules which only match the TCP protocol: this causes a
+TCP RST packet to be sent back.  This is mainly useful for blocking 
+.I ident
+(113/tcp) probes which frequently occur when sending mail to broken mail
+hosts (which won't accept your mail otherwise).
+.IP
+(*) Using icmp\-admin\-prohibited with kernels that do not support it will result in a plain DROP instead of REJECT
+.PP
+\fIWarning:\fP You should not indiscriminately apply the REJECT target to
+packets whose connection state is classified as INVALID; instead, you should
+only DROP these.
+.PP
+Consider a source host transmitting a packet P, with P experiencing so much
+delay along its path that the source host issues a retransmission, P_2, with
+P_2 being successful in reaching its destination and advancing the connection
+state normally. It is conceivable that the late-arriving P may be considered
+not to be associated with any connection tracking entry. Generating a reject
+response for a packet so classed would then terminate the healthy connection.
+.PP
+So, instead of:
+.PP
+-A INPUT ... -j REJECT
+.PP
+do consider using:
+.PP
+-A INPUT ... -m conntrack --ctstate INVALID -j DROP
+-A INPUT ... -j REJECT
diff --git a/extensions/libipt_REJECT.t b/extensions/libipt_REJECT.t
new file mode 100644
index 0000000..5b26b10
--- /dev/null
+++ b/extensions/libipt_REJECT.t
@@ -0,0 +1,9 @@
+:INPUT,FORWARD,OUTPUT
+-j REJECT;=;OK
+-j REJECT --reject-with icmp-net-unreachable;=;OK
+-j REJECT --reject-with icmp-host-unreachable;=;OK
+-j REJECT --reject-with icmp-port-unreachable;=;OK
+-j REJECT --reject-with icmp-proto-unreachable;=;OK
+-j REJECT --reject-with icmp-net-prohibited;=;OK
+-j REJECT --reject-with icmp-host-prohibited;=;OK
+-j REJECT --reject-with icmp-admin-prohibited;=;OK
diff --git a/extensions/libipt_REJECT.txlate b/extensions/libipt_REJECT.txlate
new file mode 100644
index 0000000..a1bfb5f
--- /dev/null
+++ b/extensions/libipt_REJECT.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A FORWARD -p TCP --dport 22 -j REJECT
+nft add rule ip filter FORWARD tcp dport 22 counter reject
+
+iptables-translate -A FORWARD -p TCP --dport 22 -j REJECT --reject-with icmp-net-unreachable
+nft add rule ip filter FORWARD tcp dport 22 counter reject with icmp type net-unreachable
+
+iptables-translate -A FORWARD -p TCP --dport 22 -j REJECT --reject-with tcp-reset
+nft add rule ip filter FORWARD tcp dport 22 counter reject with tcp reset
diff --git a/extensions/libipt_SNAT.c b/extensions/libipt_SNAT.c
new file mode 100644
index 0000000..e92d811
--- /dev/null
+++ b/extensions/libipt_SNAT.c
@@ -0,0 +1,325 @@
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <iptables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_SRC = 0,
+	O_RANDOM,
+	O_RANDOM_FULLY,
+	O_PERSISTENT,
+	O_X_TO_SRC,
+	F_TO_SRC       = 1 << O_TO_SRC,
+	F_RANDOM       = 1 << O_RANDOM,
+	F_RANDOM_FULLY = 1 << O_RANDOM_FULLY,
+	F_X_TO_SRC     = 1 << O_X_TO_SRC,
+};
+
+/* Source NAT data consists of a multi-range, indicating where to map
+   to. */
+struct ipt_natinfo
+{
+	struct xt_entry_target t;
+	struct nf_nat_ipv4_multi_range_compat mr;
+};
+
+static void SNAT_help(void)
+{
+	printf(
+"SNAT target options:\n"
+" --to-source [<ipaddr>[-<ipaddr>]][:port[-port]]\n"
+"				Address to map source to.\n"
+"[--random] [--random-fully] [--persistent]\n");
+}
+
+static const struct xt_option_entry SNAT_opts[] = {
+	{.name = "to-source", .id = O_TO_SRC, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_MULTI},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE},
+	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static struct ipt_natinfo *
+append_range(struct ipt_natinfo *info, const struct nf_nat_ipv4_range *range)
+{
+	unsigned int size;
+
+	/* One rangesize already in struct ipt_natinfo */
+	size = XT_ALIGN(sizeof(*info) + info->mr.rangesize * sizeof(*range));
+
+	info = realloc(info, size);
+	if (!info)
+		xtables_error(OTHER_PROBLEM, "Out of memory\n");
+
+	info->t.u.target_size = size;
+	info->mr.range[info->mr.rangesize] = *range;
+	info->mr.rangesize++;
+
+	return info;
+}
+
+/* Ranges expected in network order. */
+static struct xt_entry_target *
+parse_to(const char *orig_arg, int portok, struct ipt_natinfo *info)
+{
+	struct nf_nat_ipv4_range range;
+	char *arg, *colon, *dash, *error;
+	const struct in_addr *ip;
+
+	arg = strdup(orig_arg);
+	if (arg == NULL)
+		xtables_error(RESOURCE_PROBLEM, "strdup");
+	memset(&range, 0, sizeof(range));
+	colon = strchr(arg, ':');
+
+	if (colon) {
+		int port;
+
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+
+		range.flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+		port = atoi(colon+1);
+		if (port <= 0 || port > 65535)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Port `%s' not valid\n", colon+1);
+
+		error = strchr(colon+1, ':');
+		if (error)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid port:port syntax - use dash\n");
+
+		dash = strchr(colon, '-');
+		if (!dash) {
+			range.min.tcp.port
+				= range.max.tcp.port
+				= htons(port);
+		} else {
+			int maxport;
+
+			maxport = atoi(dash + 1);
+			if (maxport <= 0 || maxport > 65535)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port `%s' not valid\n", dash+1);
+			if (maxport < port)
+				/* People are stupid. */
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port range `%s' funky\n", colon+1);
+			range.min.tcp.port = htons(port);
+			range.max.tcp.port = htons(maxport);
+		}
+		/* Starts with a colon? No IP info...*/
+		if (colon == arg) {
+			free(arg);
+			return &(append_range(info, &range)->t);
+		}
+		*colon = '\0';
+	}
+
+	range.flags |= NF_NAT_RANGE_MAP_IPS;
+	dash = strchr(arg, '-');
+	if (colon && dash && dash > colon)
+		dash = NULL;
+
+	if (dash)
+		*dash = '\0';
+
+	ip = xtables_numeric_to_ipaddr(arg);
+	if (!ip)
+		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+			   arg);
+	range.min_ip = ip->s_addr;
+	if (dash) {
+		ip = xtables_numeric_to_ipaddr(dash+1);
+		if (!ip)
+			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+				   dash+1);
+		range.max_ip = ip->s_addr;
+	} else
+		range.max_ip = range.min_ip;
+
+	free(arg);
+	return &(append_range(info, &range)->t);
+}
+
+static void SNAT_parse(struct xt_option_call *cb)
+{
+	const struct ipt_entry *entry = cb->xt_entry;
+	struct ipt_natinfo *info = (void *)(*cb->target);
+	int portok;
+
+	if (entry->ip.proto == IPPROTO_TCP
+	    || entry->ip.proto == IPPROTO_UDP
+	    || entry->ip.proto == IPPROTO_SCTP
+	    || entry->ip.proto == IPPROTO_DCCP
+	    || entry->ip.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_SRC:
+		if (cb->xflags & F_X_TO_SRC) {
+			if (!kernel_version)
+				get_kernel_version();
+			if (kernel_version > LINUX_VERSION(2, 6, 10))
+				xtables_error(PARAMETER_PROBLEM,
+					   "SNAT: Multiple --to-source not supported");
+		}
+		*cb->target = parse_to(cb->arg, portok, info);
+		cb->xflags |= F_X_TO_SRC;
+		break;
+	case O_PERSISTENT:
+		info->mr.range[0].flags |= NF_NAT_RANGE_PERSISTENT;
+		break;
+	}
+}
+
+static void SNAT_fcheck(struct xt_fcheck_call *cb)
+{
+	static const unsigned int f = F_TO_SRC | F_RANDOM;
+	static const unsigned int r = F_TO_SRC | F_RANDOM_FULLY;
+	struct nf_nat_ipv4_multi_range_compat *mr = cb->data;
+
+	if ((cb->xflags & f) == f)
+		mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
+	if ((cb->xflags & r) == r)
+		mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY;
+}
+
+static void print_range(const struct nf_nat_ipv4_range *r)
+{
+	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
+		struct in_addr a;
+
+		a.s_addr = r->min_ip;
+		printf("%s", xtables_ipaddr_to_numeric(&a));
+		if (r->max_ip != r->min_ip) {
+			a.s_addr = r->max_ip;
+			printf("-%s", xtables_ipaddr_to_numeric(&a));
+		}
+	}
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(":");
+		printf("%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			printf("-%hu", ntohs(r->max.tcp.port));
+	}
+}
+
+static void SNAT_print(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+	const struct ipt_natinfo *info = (const void *)target;
+	unsigned int i = 0;
+
+	printf(" to:");
+	for (i = 0; i < info->mr.rangesize; i++) {
+		print_range(&info->mr.range[i]);
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM)
+			printf(" random");
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+			printf(" random-fully");
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT)
+			printf(" persistent");
+	}
+}
+
+static void SNAT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ipt_natinfo *info = (const void *)target;
+	unsigned int i = 0;
+
+	for (i = 0; i < info->mr.rangesize; i++) {
+		printf(" --to-source ");
+		print_range(&info->mr.range[i]);
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM)
+			printf(" --random");
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+			printf(" --random-fully");
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT)
+			printf(" --persistent");
+	}
+}
+
+static void print_range_xlate(const struct nf_nat_ipv4_range *r,
+			      struct xt_xlate *xl)
+{
+	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
+		struct in_addr a;
+
+		a.s_addr = r->min_ip;
+		xt_xlate_add(xl, "%s", xtables_ipaddr_to_numeric(&a));
+		if (r->max_ip != r->min_ip) {
+			a.s_addr = r->max_ip;
+			xt_xlate_add(xl, "-%s", xtables_ipaddr_to_numeric(&a));
+		}
+	}
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, ":");
+		xt_xlate_add(xl, "%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			xt_xlate_add(xl, "-%hu", ntohs(r->max.tcp.port));
+	}
+}
+
+static int SNAT_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_natinfo *info = (const void *)params->target;
+	unsigned int i = 0;
+	bool sep_need = false;
+	const char *sep = " ";
+
+	for (i = 0; i < info->mr.rangesize; i++) {
+		xt_xlate_add(xl, "snat to ");
+		print_range_xlate(&info->mr.range[i], xl);
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM) {
+			xt_xlate_add(xl, " random");
+			sep_need = true;
+		}
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) {
+			if (sep_need)
+				sep = ",";
+			xt_xlate_add(xl, "%sfully-random", sep);
+			sep_need = true;
+		}
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT) {
+			if (sep_need)
+				sep = ",";
+			xt_xlate_add(xl, "%spersistent", sep);
+		}
+	}
+
+	return 1;
+}
+
+static struct xtables_target snat_tg_reg = {
+	.name		= "SNAT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.help		= SNAT_help,
+	.x6_parse	= SNAT_parse,
+	.x6_fcheck	= SNAT_fcheck,
+	.print		= SNAT_print,
+	.save		= SNAT_save,
+	.x6_options	= SNAT_opts,
+	.xlate		= SNAT_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&snat_tg_reg);
+}
diff --git a/extensions/libipt_SNAT.t b/extensions/libipt_SNAT.t
new file mode 100644
index 0000000..186e1cb
--- /dev/null
+++ b/extensions/libipt_SNAT.t
@@ -0,0 +1,11 @@
+:POSTROUTING
+*nat
+-j SNAT --to-source 1.1.1.1;=;OK
+-j SNAT --to-source 1.1.1.1-1.1.1.10;=;OK
+-j SNAT --to-source 1.1.1.1:1025-65535;;FAIL
+-j SNAT --to-source 1.1.1.1 --to-source 2.2.2.2;;FAIL
+-p tcp -j SNAT --to-source 1.1.1.1:1025-65535;=;OK
+-p tcp -j SNAT --to-source 1.1.1.1-1.1.1.10:1025-65535;=;OK
+-p tcp -j SNAT --to-source 1.1.1.1-1.1.1.10:1025-65536;;FAIL
+-p tcp -j SNAT --to-source 1.1.1.1-1.1.1.10:1025-65535 --to-source 2.2.2.2-2.2.2.20:1025-65535;;FAIL
+-j SNAT;;FAIL
diff --git a/extensions/libipt_SNAT.txlate b/extensions/libipt_SNAT.txlate
new file mode 100644
index 0000000..01592fa
--- /dev/null
+++ b/extensions/libipt_SNAT.txlate
@@ -0,0 +1,14 @@
+iptables-translate -t nat -A postrouting -o eth0 -j SNAT --to 1.2.3.4
+nft add rule ip nat postrouting oifname "eth0" counter snat to 1.2.3.4
+
+iptables-translate -t nat -A postrouting -o eth0 -j SNAT --to 1.2.3.4-1.2.3.6
+nft add rule ip nat postrouting oifname "eth0" counter snat to 1.2.3.4-1.2.3.6
+
+iptables-translate -t nat -A postrouting -p tcp -o eth0 -j SNAT --to 1.2.3.4:1-1023
+nft add rule ip nat postrouting oifname "eth0" ip protocol tcp counter snat to 1.2.3.4:1-1023
+
+iptables-translate -t nat -A postrouting -o eth0 -j SNAT --to 1.2.3.4 --random
+nft add rule ip nat postrouting oifname "eth0" counter snat to 1.2.3.4 random
+
+iptables-translate -t nat -A postrouting -o eth0 -j SNAT --to 1.2.3.4 --random --persistent
+nft add rule ip nat postrouting oifname "eth0" counter snat to 1.2.3.4 random,persistent
diff --git a/extensions/libipt_TTL.c b/extensions/libipt_TTL.c
new file mode 100644
index 0000000..0f81280
--- /dev/null
+++ b/extensions/libipt_TTL.c
@@ -0,0 +1,126 @@
+/* Shared library add-on to iptables for the TTL target
+ * (C) 2000 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This program is distributed under the terms of GNU GPL
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv4/ipt_TTL.h>
+
+enum {
+	O_TTL_SET = 0,
+	O_TTL_INC,
+	O_TTL_DEC,
+	F_TTL_SET = 1 << O_TTL_SET,
+	F_TTL_INC = 1 << O_TTL_INC,
+	F_TTL_DEC = 1 << O_TTL_DEC,
+	F_ANY     = F_TTL_SET | F_TTL_INC | F_TTL_DEC,
+};
+
+#define s struct ipt_TTL_info
+static const struct xt_option_entry TTL_opts[] = {
+	{.name = "ttl-set", .type = XTTYPE_UINT8, .id = O_TTL_SET,
+	 .excl = F_ANY, .flags = XTOPT_PUT, XTOPT_POINTER(s, ttl)},
+	{.name = "ttl-dec", .type = XTTYPE_UINT8, .id = O_TTL_DEC,
+	 .excl = F_ANY, .flags = XTOPT_PUT, XTOPT_POINTER(s, ttl),
+	 .min = 1},
+	{.name = "ttl-inc", .type = XTTYPE_UINT8, .id = O_TTL_INC,
+	 .excl = F_ANY, .flags = XTOPT_PUT, XTOPT_POINTER(s, ttl),
+	 .min = 1},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void TTL_help(void)
+{
+	printf(
+"TTL target options\n"
+"  --ttl-set value		Set TTL to <value 0-255>\n"
+"  --ttl-dec value		Decrement TTL by <value 1-255>\n"
+"  --ttl-inc value		Increment TTL by <value 1-255>\n");
+}
+
+static void TTL_parse(struct xt_option_call *cb)
+{
+	struct ipt_TTL_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TTL_SET:
+		info->mode = IPT_TTL_SET;
+		break;
+	case O_TTL_DEC:
+		info->mode = IPT_TTL_DEC;
+		break;
+	case O_TTL_INC:
+		info->mode = IPT_TTL_INC;
+		break;
+	}
+}
+
+static void TTL_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & F_ANY))
+		xtables_error(PARAMETER_PROBLEM,
+				"TTL: You must specify an action");
+}
+
+static void TTL_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ipt_TTL_info *info = 
+		(struct ipt_TTL_info *) target->data;
+
+	switch (info->mode) {
+		case IPT_TTL_SET:
+			printf(" --ttl-set");
+			break;
+		case IPT_TTL_DEC:
+			printf(" --ttl-dec");
+			break;
+
+		case IPT_TTL_INC:
+			printf(" --ttl-inc");
+			break;
+	}
+	printf(" %u", info->ttl);
+}
+
+static void TTL_print(const void *ip, const struct xt_entry_target *target,
+                      int numeric)
+{
+	const struct ipt_TTL_info *info =
+		(struct ipt_TTL_info *) target->data;
+
+	printf(" TTL ");
+	switch (info->mode) {
+		case IPT_TTL_SET:
+			printf("set to");
+			break;
+		case IPT_TTL_DEC:
+			printf("decrement by");
+			break;
+		case IPT_TTL_INC:
+			printf("increment by");
+			break;
+	}
+	printf(" %u", info->ttl);
+}
+
+static struct xtables_target ttl_tg_reg = {
+	.name		= "TTL",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct ipt_TTL_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ipt_TTL_info)),
+	.help		= TTL_help,
+	.print		= TTL_print,
+	.save		= TTL_save,
+	.x6_parse	= TTL_parse,
+	.x6_fcheck	= TTL_check,
+	.x6_options	= TTL_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&ttl_tg_reg);
+}
diff --git a/extensions/libipt_TTL.man b/extensions/libipt_TTL.man
new file mode 100644
index 0000000..cf3d1a2
--- /dev/null
+++ b/extensions/libipt_TTL.man
@@ -0,0 +1,19 @@
+This is used to modify the IPv4 TTL header field.  The TTL field determines
+how many hops (routers) a packet can traverse until it's time to live is
+exceeded.
+.PP
+Setting or incrementing the TTL field can potentially be very dangerous,
+so it should be avoided at any cost. This target is only valid in
+.B mangle
+table.
+.PP
+.B Don't ever set or increment the value on packets that leave your local network!
+.TP
+\fB\-\-ttl\-set\fP \fIvalue\fP
+Set the TTL value to `value'.
+.TP
+\fB\-\-ttl\-dec\fP \fIvalue\fP
+Decrement the TTL value `value' times.
+.TP
+\fB\-\-ttl\-inc\fP \fIvalue\fP
+Increment the TTL value `value' times.
diff --git a/extensions/libipt_TTL.t b/extensions/libipt_TTL.t
new file mode 100644
index 0000000..3680979
--- /dev/null
+++ b/extensions/libipt_TTL.t
@@ -0,0 +1,10 @@
+:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j TTL --ttl-set 42;=;OK
+-j TTL --ttl-inc 1;=;OK
+-j TTL --ttl-dec 1;=;OK
+-j TTL --ttl-set 256;;FAIL
+-j TTL --ttl-inc 0;;FAIL
+-j TTL --ttl-dec 0;;FAIL
+-j TTL --ttl-dec 1 --ttl-inc 1;;FAIL
+-j TTL --ttl-set --ttl-inc 1;;FAIL
diff --git a/extensions/libipt_ULOG.c b/extensions/libipt_ULOG.c
new file mode 100644
index 0000000..5163eea
--- /dev/null
+++ b/extensions/libipt_ULOG.c
@@ -0,0 +1,129 @@
+/* Shared library add-on to iptables to add ULOG support.
+ * 
+ * (C) 2000 by Harald Welte <laforge@gnumonks.org>
+ *
+ * multipart netlink support based on ideas by Sebastian Zander 
+ * 						<zander@fokus.gmd.de>
+ *
+ * This software is released under the terms of GNU GPL
+ * 
+ * libipt_ULOG.c,v 1.7 2001/01/30 11:55:02 laforge Exp
+ */
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <xtables.h>
+/* For 64bit kernel / 32bit userspace */
+#include <linux/netfilter_ipv4/ipt_ULOG.h>
+
+enum {
+	O_ULOG_NLGROUP = 0,
+	O_ULOG_PREFIX,
+	O_ULOG_CPRANGE,
+	O_ULOG_QTHR,
+};
+
+static void ULOG_help(void)
+{
+	printf("ULOG target options:\n"
+	       " --ulog-nlgroup nlgroup		NETLINK group used for logging\n"
+	       " --ulog-cprange size		Bytes of each packet to be passed\n"
+	       " --ulog-qthreshold		Threshold of in-kernel queue\n"
+	       " --ulog-prefix prefix		Prefix log messages with this prefix.\n");
+}
+
+static const struct xt_option_entry ULOG_opts[] = {
+	{.name = "ulog-nlgroup", .id = O_ULOG_NLGROUP, .type = XTTYPE_UINT8,
+	 .min = 1, .max = 32},
+	{.name = "ulog-prefix", .id = O_ULOG_PREFIX, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(struct ipt_ulog_info, prefix),
+	 .min = 1},
+	{.name = "ulog-cprange", .id = O_ULOG_CPRANGE, .type = XTTYPE_UINT64},
+	{.name = "ulog-qthreshold", .id = O_ULOG_QTHR, .type = XTTYPE_UINT64,
+	 .min = 1, .max = ULOG_MAX_QLEN},
+	XTOPT_TABLEEND,
+};
+
+static void ULOG_init(struct xt_entry_target *t)
+{
+	struct ipt_ulog_info *loginfo = (struct ipt_ulog_info *) t->data;
+
+	loginfo->nl_group = ULOG_DEFAULT_NLGROUP;
+	loginfo->qthreshold = ULOG_DEFAULT_QTHRESHOLD;
+
+}
+
+static void ULOG_parse(struct xt_option_call *cb)
+{
+	struct ipt_ulog_info *loginfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_ULOG_NLGROUP:
+		loginfo->nl_group = 1 << (cb->val.u8 - 1);
+		break;
+	case O_ULOG_PREFIX:
+		if (strchr(cb->arg, '\n') != NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Newlines not allowed in --ulog-prefix");
+		break;
+	case O_ULOG_CPRANGE:
+		loginfo->copy_range = cb->val.u64;
+		break;
+	case O_ULOG_QTHR:
+		loginfo->qthreshold = cb->val.u64;
+		break;
+	}
+}
+
+static void ULOG_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ipt_ulog_info *loginfo
+	    = (const struct ipt_ulog_info *) target->data;
+
+	if (strcmp(loginfo->prefix, "") != 0) {
+		fputs(" --ulog-prefix", stdout);
+		xtables_save_string(loginfo->prefix);
+	}
+
+	if (loginfo->nl_group != ULOG_DEFAULT_NLGROUP)
+		printf(" --ulog-nlgroup %d", ffs(loginfo->nl_group));
+	if (loginfo->copy_range)
+		printf(" --ulog-cprange %u", (unsigned int)loginfo->copy_range);
+
+	if (loginfo->qthreshold != ULOG_DEFAULT_QTHRESHOLD)
+		printf(" --ulog-qthreshold %u", (unsigned int)loginfo->qthreshold);
+}
+
+static void ULOG_print(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+	const struct ipt_ulog_info *loginfo
+	    = (const struct ipt_ulog_info *) target->data;
+
+	printf(" ULOG ");
+	printf("copy_range %u nlgroup %d", (unsigned int)loginfo->copy_range,
+	       ffs(loginfo->nl_group));
+	if (strcmp(loginfo->prefix, "") != 0)
+		printf(" prefix \"%s\"", loginfo->prefix);
+	printf(" queue_threshold %u", (unsigned int)loginfo->qthreshold);
+}
+
+static struct xtables_target ulog_tg_reg = {
+	.name		= "ULOG",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct ipt_ulog_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ipt_ulog_info)),
+	.help		= ULOG_help,
+	.init		= ULOG_init,
+	.print		= ULOG_print,
+	.save		= ULOG_save,
+	.x6_parse	= ULOG_parse,
+	.x6_options	= ULOG_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&ulog_tg_reg);
+}
diff --git a/extensions/libipt_ULOG.man b/extensions/libipt_ULOG.man
new file mode 100644
index 0000000..c91f776
--- /dev/null
+++ b/extensions/libipt_ULOG.man
@@ -0,0 +1,28 @@
+This is the deprecated ipv4-only predecessor of the NFLOG target.
+It provides userspace logging of matching packets.  When this
+target is set for a rule, the Linux kernel will multicast this packet
+through a
+.IR netlink 
+socket. One or more userspace processes may then subscribe to various 
+multicast groups and receive the packets.
+Like LOG, this is a "non-terminating target", i.e. rule traversal
+continues at the next rule.
+.TP
+\fB\-\-ulog\-nlgroup\fP \fInlgroup\fP
+This specifies the netlink group (1-32) to which the packet is sent.
+Default value is 1.
+.TP
+\fB\-\-ulog\-prefix\fP \fIprefix\fP
+Prefix log messages with the specified prefix; up to 32 characters
+long, and useful for distinguishing messages in the logs.
+.TP
+\fB\-\-ulog\-cprange\fP \fIsize\fP
+Number of bytes to be copied to userspace.  A value of 0 always copies
+the entire packet, regardless of its size.  Default is 0.
+.TP
+\fB\-\-ulog\-qthreshold\fP \fIsize\fP
+Number of packet to queue inside kernel.  Setting this value to, e.g. 10
+accumulates ten packets inside the kernel and transmits them as one
+netlink multipart message to userspace.  Default is 1 (for backwards
+compatibility).
+.br
diff --git a/extensions/libipt_ah.c b/extensions/libipt_ah.c
new file mode 100644
index 0000000..fec5705
--- /dev/null
+++ b/extensions/libipt_ah.c
@@ -0,0 +1,132 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv4/ipt_ah.h>
+
+enum {
+	O_AHSPI = 0,
+};
+
+static void ah_help(void)
+{
+	printf(
+"ah match options:\n"
+"[!] --ahspi spi[:spi]\n"
+"				match spi (range)\n");
+}
+
+static const struct xt_option_entry ah_opts[] = {
+	{.name = "ahspi", .id = O_AHSPI, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct ipt_ah, spis)},
+	XTOPT_TABLEEND,
+};
+
+static void ah_init(struct xt_entry_match *m)
+{
+	struct ipt_ah *ahinfo = (void *)m->data;
+
+	ahinfo->spis[1] = ~0U;
+}
+
+static void ah_parse(struct xt_option_call *cb)
+{
+	struct ipt_ah *ahinfo = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->nvals == 1)
+		ahinfo->spis[1] = ahinfo->spis[0];
+	if (cb->invert)
+		ahinfo->invflags |= IPT_AH_INV_SPI;
+}
+
+static void
+print_spis(const char *name, uint32_t min, uint32_t max,
+	    int invert)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFFFFFF || invert) {
+		printf("%s", name);
+		if (min == max) {
+			printf(":%s", inv);
+			printf("%u", min);
+		} else {
+			printf("s:%s", inv);
+			printf("%u",min);
+			printf(":");
+			printf("%u",max);
+		}
+	}
+}
+
+static void ah_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	const struct ipt_ah *ah = (struct ipt_ah *)match->data;
+
+	printf(" ah ");
+	print_spis("spi", ah->spis[0], ah->spis[1],
+		    ah->invflags & IPT_AH_INV_SPI);
+	if (ah->invflags & ~IPT_AH_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       ah->invflags & ~IPT_AH_INV_MASK);
+}
+
+static void ah_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ipt_ah *ahinfo = (struct ipt_ah *)match->data;
+
+	if (!(ahinfo->spis[0] == 0
+	    && ahinfo->spis[1] == 0xFFFFFFFF)) {
+		printf("%s --ahspi ",
+			(ahinfo->invflags & IPT_AH_INV_SPI) ? " !" : "");
+		if (ahinfo->spis[0]
+		    != ahinfo->spis[1])
+			printf("%u:%u",
+			       ahinfo->spis[0],
+			       ahinfo->spis[1]);
+		else
+			printf("%u",
+			       ahinfo->spis[0]);
+	}
+
+}
+
+static int ah_xlate(struct xt_xlate *xl,
+		    const struct xt_xlate_mt_params *params)
+{
+	const struct ipt_ah *ahinfo = (struct ipt_ah *)params->match->data;
+
+	if (!(ahinfo->spis[0] == 0 && ahinfo->spis[1] == 0xFFFFFFFF)) {
+		xt_xlate_add(xl, "ah spi%s ",
+			   (ahinfo->invflags & IPT_AH_INV_SPI) ? " !=" : "");
+		if (ahinfo->spis[0] != ahinfo->spis[1])
+			xt_xlate_add(xl, "%u-%u", ahinfo->spis[0],
+				   ahinfo->spis[1]);
+		else
+			xt_xlate_add(xl, "%u", ahinfo->spis[0]);
+	}
+
+	return 1;
+}
+
+static struct xtables_match ah_mt_reg = {
+	.name		= "ah",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct ipt_ah)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ipt_ah)),
+	.help		= ah_help,
+	.init		= ah_init,
+	.print		= ah_print,
+	.save		= ah_save,
+	.x6_parse	= ah_parse,
+	.x6_options	= ah_opts,
+	.xlate		= ah_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&ah_mt_reg);
+}
diff --git a/extensions/libipt_ah.man b/extensions/libipt_ah.man
new file mode 100644
index 0000000..d26455e
--- /dev/null
+++ b/extensions/libipt_ah.man
@@ -0,0 +1,3 @@
+This module matches the SPIs in Authentication header of IPsec packets.
+.TP
+[\fB!\fP] \fB\-\-ahspi\fP \fIspi\fP[\fB:\fP\fIspi\fP]
diff --git a/extensions/libipt_ah.t b/extensions/libipt_ah.t
new file mode 100644
index 0000000..cd85386
--- /dev/null
+++ b/extensions/libipt_ah.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+-p ah -m ah --ahspi 0;=;OK
+-p ah -m ah --ahspi 4294967295;=;OK
+-p ah -m ah --ahspi 0:4294967295;-p ah -m ah;OK
+-p ah -m ah ! --ahspi 0;=;OK
+-p ah -m ah --ahspi -1;;FAIL
+-p ah -m ah --ahspi 4294967296;;FAIL
+-p ah -m ah --ahspi invalid;;FAIL
+-p ah -m ah --ahspi 0:invalid;;FAIL
+-m ah --ahspi 0;;FAIL
+-m ah --ahspi;;FAIL
+-m ah;;FAIL
+-p ah -m ah;=;OK
diff --git a/extensions/libipt_ah.txlate b/extensions/libipt_ah.txlate
new file mode 100644
index 0000000..ea3ef3e
--- /dev/null
+++ b/extensions/libipt_ah.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A INPUT -p 51 -m ah --ahspi 500 -j DROP
+nft add rule ip filter INPUT ah spi 500 counter drop
+
+iptables-translate -A INPUT -p 51 -m ah --ahspi 500:600 -j DROP
+nft add rule ip filter INPUT ah spi 500-600 counter drop
+
+iptables-translate -A INPUT -p 51 -m ah ! --ahspi 50 -j DROP
+nft add rule ip filter INPUT ah spi != 50 counter drop
diff --git a/extensions/libipt_icmp.c b/extensions/libipt_icmp.c
new file mode 100644
index 0000000..e5e2366
--- /dev/null
+++ b/extensions/libipt_icmp.c
@@ -0,0 +1,286 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip6_tables.h */
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+#include "libxt_icmp.h"
+
+/* special hack for icmp-type 'any': 
+ * Up to kernel <=2.4.20 the problem was:
+ * '-p icmp ' matches all icmp packets
+ * '-p icmp -m icmp' matches _only_ ICMP type 0 :(
+ * This is now fixed by initializing the field * to icmp type 0xFF
+ * See: https://bugzilla.netfilter.org/cgi-bin/bugzilla/show_bug.cgi?id=37
+ */
+
+enum {
+	O_ICMP_TYPE = 0,
+};
+
+static const struct xt_icmp_names icmp_codes[] = {
+	{ "any", 0xFF, 0, 0xFF },
+	{ "echo-reply", 0, 0, 0xFF },
+	/* Alias */ { "pong", 0, 0, 0xFF },
+
+	{ "destination-unreachable", 3, 0, 0xFF },
+	{   "network-unreachable", 3, 0, 0 },
+	{   "host-unreachable", 3, 1, 1 },
+	{   "protocol-unreachable", 3, 2, 2 },
+	{   "port-unreachable", 3, 3, 3 },
+	{   "fragmentation-needed", 3, 4, 4 },
+	{   "source-route-failed", 3, 5, 5 },
+	{   "network-unknown", 3, 6, 6 },
+	{   "host-unknown", 3, 7, 7 },
+	{   "network-prohibited", 3, 9, 9 },
+	{   "host-prohibited", 3, 10, 10 },
+	{   "TOS-network-unreachable", 3, 11, 11 },
+	{   "TOS-host-unreachable", 3, 12, 12 },
+	{   "communication-prohibited", 3, 13, 13 },
+	{   "host-precedence-violation", 3, 14, 14 },
+	{   "precedence-cutoff", 3, 15, 15 },
+
+	{ "source-quench", 4, 0, 0xFF },
+
+	{ "redirect", 5, 0, 0xFF },
+	{   "network-redirect", 5, 0, 0 },
+	{   "host-redirect", 5, 1, 1 },
+	{   "TOS-network-redirect", 5, 2, 2 },
+	{   "TOS-host-redirect", 5, 3, 3 },
+
+	{ "echo-request", 8, 0, 0xFF },
+	/* Alias */ { "ping", 8, 0, 0xFF },
+
+	{ "router-advertisement", 9, 0, 0xFF },
+
+	{ "router-solicitation", 10, 0, 0xFF },
+
+	{ "time-exceeded", 11, 0, 0xFF },
+	/* Alias */ { "ttl-exceeded", 11, 0, 0xFF },
+	{   "ttl-zero-during-transit", 11, 0, 0 },
+	{   "ttl-zero-during-reassembly", 11, 1, 1 },
+
+	{ "parameter-problem", 12, 0, 0xFF },
+	{   "ip-header-bad", 12, 0, 0 },
+	{   "required-option-missing", 12, 1, 1 },
+
+	{ "timestamp-request", 13, 0, 0xFF },
+
+	{ "timestamp-reply", 14, 0, 0xFF },
+
+	{ "address-mask-request", 17, 0, 0xFF },
+
+	{ "address-mask-reply", 18, 0, 0xFF }
+};
+
+static void icmp_help(void)
+{
+	printf(
+"icmp match options:\n"
+"[!] --icmp-type typename	match icmp type\n"
+"[!] --icmp-type type[/code]	(or numeric type or type/code)\n");
+	printf("Valid ICMP Types:");
+	xt_print_icmp_types(icmp_codes, ARRAY_SIZE(icmp_codes));
+}
+
+static const struct xt_option_entry icmp_opts[] = {
+	{.name = "icmp-type", .id = O_ICMP_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void 
+parse_icmp(const char *icmptype, uint8_t *type, uint8_t code[])
+{
+	static const unsigned int limit = ARRAY_SIZE(icmp_codes);
+	unsigned int match = limit;
+	unsigned int i;
+
+	for (i = 0; i < limit; i++) {
+		if (strncasecmp(icmp_codes[i].name, icmptype, strlen(icmptype))
+		    == 0) {
+			if (match != limit)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Ambiguous ICMP type `%s':"
+					   " `%s' or `%s'?",
+					   icmptype,
+					   icmp_codes[match].name,
+					   icmp_codes[i].name);
+			match = i;
+		}
+	}
+
+	if (match != limit) {
+		*type = icmp_codes[match].type;
+		code[0] = icmp_codes[match].code_min;
+		code[1] = icmp_codes[match].code_max;
+	} else {
+		char *slash;
+		char buffer[strlen(icmptype) + 1];
+		unsigned int number;
+
+		strcpy(buffer, icmptype);
+		slash = strchr(buffer, '/');
+
+		if (slash)
+			*slash = '\0';
+
+		if (!xtables_strtoui(buffer, NULL, &number, 0, UINT8_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid ICMP type `%s'\n", buffer);
+		*type = number;
+		if (slash) {
+			if (!xtables_strtoui(slash+1, NULL, &number, 0, UINT8_MAX))
+				xtables_error(PARAMETER_PROBLEM,
+					   "Invalid ICMP code `%s'\n",
+					   slash+1);
+			code[0] = code[1] = number;
+		} else {
+			code[0] = 0;
+			code[1] = 0xFF;
+		}
+	}
+}
+
+static void icmp_init(struct xt_entry_match *m)
+{
+	struct ipt_icmp *icmpinfo = (struct ipt_icmp *)m->data;
+
+	icmpinfo->type = 0xFF;
+	icmpinfo->code[1] = 0xFF;
+}
+
+static void icmp_parse(struct xt_option_call *cb)
+{
+	struct ipt_icmp *icmpinfo = cb->data;
+
+	xtables_option_parse(cb);
+	parse_icmp(cb->arg, &icmpinfo->type, icmpinfo->code);
+	if (cb->invert)
+		icmpinfo->invflags |= IPT_ICMP_INV;
+}
+
+static void print_icmptype(uint8_t type,
+			   uint8_t code_min, uint8_t code_max,
+			   int invert,
+			   int numeric)
+{
+	if (!numeric) {
+		unsigned int i;
+
+		for (i = 0; i < ARRAY_SIZE(icmp_codes); ++i)
+			if (icmp_codes[i].type == type
+			    && icmp_codes[i].code_min == code_min
+			    && icmp_codes[i].code_max == code_max)
+				break;
+
+		if (i != ARRAY_SIZE(icmp_codes)) {
+			printf(" %s%s",
+			       invert ? "!" : "",
+			       icmp_codes[i].name);
+			return;
+		}
+	}
+
+	if (invert)
+		printf(" !");
+
+	printf("type %u", type);
+	if (code_min == code_max)
+		printf(" code %u", code_min);
+	else if (code_min != 0 || code_max != 0xFF)
+		printf(" codes %u-%u", code_min, code_max);
+}
+
+static void icmp_print(const void *ip, const struct xt_entry_match *match,
+                       int numeric)
+{
+	const struct ipt_icmp *icmp = (struct ipt_icmp *)match->data;
+
+	printf(" icmp");
+	print_icmptype(icmp->type, icmp->code[0], icmp->code[1],
+		       icmp->invflags & IPT_ICMP_INV,
+		       numeric);
+
+	if (icmp->invflags & ~IPT_ICMP_INV)
+		printf(" Unknown invflags: 0x%X",
+		       icmp->invflags & ~IPT_ICMP_INV);
+}
+
+static void icmp_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ipt_icmp *icmp = (struct ipt_icmp *)match->data;
+
+	if (icmp->invflags & IPT_ICMP_INV)
+		printf(" !");
+
+	/* special hack for 'any' case */
+	if (icmp->type == 0xFF) {
+		printf(" --icmp-type any");
+	} else {
+		printf(" --icmp-type %u", icmp->type);
+		if (icmp->code[0] != 0 || icmp->code[1] != 0xFF)
+			printf("/%u", icmp->code[0]);
+	}
+}
+
+static unsigned int type_xlate_print(struct xt_xlate *xl, unsigned int icmptype,
+				     unsigned int code_min,
+				     unsigned int code_max)
+{
+	unsigned int i;
+
+	if (code_min != code_max) {
+		for (i = 0; i < ARRAY_SIZE(icmp_codes); ++i)
+			if (icmp_codes[i].type == icmptype &&
+			    icmp_codes[i].code_min == code_min &&
+			    icmp_codes[i].code_max == code_max) {
+				xt_xlate_add(xl, "%s", icmp_codes[i].name);
+				return 1;
+			}
+	}
+
+	return 0;
+}
+
+static int icmp_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct ipt_icmp *info = (struct ipt_icmp *)params->match->data;
+
+	if (info->type != 0xFF) {
+		xt_xlate_add(xl, "icmp type%s ",
+			     (info->invflags & IPT_ICMP_INV) ? " !=" : "");
+
+		if (!type_xlate_print(xl, info->type, info->code[0],
+				      info->code[1]))
+			return 0;
+	} else {
+		/* '-m icmp --icmp-type any' is a noop by itself,
+		 * but it eats a (mandatory) previous '-p icmp' so
+		 * emit it here */
+		xt_xlate_add(xl, "ip protocol icmp");
+	}
+	return 1;
+}
+
+static struct xtables_match icmp_mt_reg = {
+	.name		= "icmp",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct ipt_icmp)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ipt_icmp)),
+	.help		= icmp_help,
+	.init		= icmp_init,
+	.print		= icmp_print,
+	.save		= icmp_save,
+	.x6_parse	= icmp_parse,
+	.x6_options	= icmp_opts,
+	.xlate		= icmp_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&icmp_mt_reg);
+}
diff --git a/extensions/libipt_icmp.man b/extensions/libipt_icmp.man
new file mode 100644
index 0000000..1039704
--- /dev/null
+++ b/extensions/libipt_icmp.man
@@ -0,0 +1,9 @@
+This extension can be used if `\-\-protocol icmp' is specified. It
+provides the following option:
+.TP
+[\fB!\fP] \fB\-\-icmp\-type\fP {\fItype\fP[\fB/\fP\fIcode\fP]|\fItypename\fP}
+This allows specification of the ICMP type, which can be a numeric
+ICMP type, type/code pair, or one of the ICMP type names shown by the command
+.nf
+ iptables \-p icmp \-h
+.fi
diff --git a/extensions/libipt_icmp.t b/extensions/libipt_icmp.t
new file mode 100644
index 0000000..f4ba65c
--- /dev/null
+++ b/extensions/libipt_icmp.t
@@ -0,0 +1,15 @@
+:INPUT,FORWARD,OUTPUT
+-p icmp -m icmp --icmp-type any;=;OK
+# output uses the number, better use the name?
+# ERROR: cannot find: iptables -I INPUT -p icmp -m icmp --icmp-type echo-reply
+# -p icmp -m icmp --icmp-type echo-reply;=;OK
+# output uses the number, better use the name?
+# ERROR: annot find: iptables -I INPUT -p icmp -m icmp --icmp-type destination-unreachable
+# -p icmp -m icmp --icmp-type destination-unreachable;=;OK
+# it does not acccept name/name, should we accept this?
+# ERROR: cannot load: iptables -A INPUT -p icmp -m icmp --icmp-type destination-unreachable/network-unreachable
+# -p icmp -m icmp --icmp-type destination-unreachable/network-unreachable;=;OK
+-m icmp;;FAIL
+# we accept "iptables -I INPUT -p tcp -m tcp", why not this below?
+# ERROR: cannot load: iptables -A INPUT -p icmp -m icmp
+# -p icmp -m icmp;=;OK
diff --git a/extensions/libipt_icmp.txlate b/extensions/libipt_icmp.txlate
new file mode 100644
index 0000000..a2aec8e
--- /dev/null
+++ b/extensions/libipt_icmp.txlate
@@ -0,0 +1,11 @@
+iptables-translate -t filter -A INPUT -m icmp --icmp-type echo-reply -j ACCEPT
+nft add rule ip filter INPUT icmp type echo-reply counter accept
+
+iptables-translate -t filter -A INPUT -m icmp --icmp-type 3 -j ACCEPT
+nft add rule ip filter INPUT icmp type destination-unreachable counter accept
+
+iptables-translate -t filter -A INPUT -m icmp ! --icmp-type 3 -j ACCEPT
+nft add rule ip filter INPUT icmp type != destination-unreachable counter accept
+
+iptables-translate -t filter -A INPUT -m icmp --icmp-type any -j ACCEPT
+nft add rule ip filter INPUT ip protocol icmp counter accept
diff --git a/extensions/libipt_realm.c b/extensions/libipt_realm.c
new file mode 100644
index 0000000..e01d048
--- /dev/null
+++ b/extensions/libipt_realm.c
@@ -0,0 +1,131 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#if defined(__GLIBC__) && __GLIBC__ == 2
+#include <net/ethernet.h>
+#else
+#include <linux/if_ether.h>
+#endif
+#include <xtables.h>
+#include <linux/netfilter_ipv4/ipt_realm.h>
+
+enum {
+	O_REALM = 0,
+};
+
+static void realm_help(void)
+{
+	printf(
+"realm match options:\n"
+"[!] --realm value[/mask]\n"
+"				Match realm\n");
+}
+
+static const struct xt_option_entry realm_opts[] = {
+	{.name = "realm", .id = O_REALM, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static const char f_realms[] = "/etc/iproute2/rt_realms";
+/* array of realms from f_realms[] */
+static struct xtables_lmap *realms;
+
+static void realm_parse(struct xt_option_call *cb)
+{
+	struct xt_realm_info *ri = cb->data;
+	unsigned int id, mask;
+
+	xtables_option_parse(cb);
+	xtables_parse_val_mask(cb, &id, &mask, realms);
+
+	ri->id = id;
+	ri->mask = mask;
+
+	if (cb->invert)
+		ri->invert = 1;
+}
+
+static void realm_print(const void *ip, const struct xt_entry_match *match,
+			int numeric)
+{
+	const struct xt_realm_info *ri = (const void *)match->data;
+
+	if (ri->invert)
+		printf(" !");
+
+	printf(" realm");
+	xtables_print_val_mask(ri->id, ri->mask, numeric ? NULL : realms);
+}
+
+static void realm_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_realm_info *ri = (const void *)match->data;
+
+	if (ri->invert)
+		printf(" !");
+
+	printf(" --realm");
+	xtables_print_val_mask(ri->id, ri->mask, realms);
+}
+
+static void
+print_realm_xlate(unsigned long id, unsigned long mask,
+		  int numeric, struct xt_xlate *xl, uint32_t op)
+{
+	const char *name = NULL;
+
+	if (mask != 0xffffffff)
+		xt_xlate_add(xl, " and 0x%lx %s 0x%lx", mask,
+			   op == XT_OP_EQ ? "==" : "!=", id);
+	else {
+		if (numeric == 0)
+			name = xtables_lmap_id2name(realms, id);
+		if (name)
+			xt_xlate_add(xl, " %s%s",
+				   op == XT_OP_EQ ? "" : "!= ", name);
+		else
+			xt_xlate_add(xl, " %s0x%lx",
+				   op == XT_OP_EQ ? "" : "!= ", id);
+	}
+}
+
+static int realm_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_realm_info *ri = (const void *)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (ri->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "rtclassid");
+	print_realm_xlate(ri->id, ri->mask, 0, xl, op);
+
+	return 1;
+}
+
+static struct xtables_match realm_mt_reg = {
+	.name		= "realm",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct xt_realm_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_realm_info)),
+	.help		= realm_help,
+	.print		= realm_print,
+	.save		= realm_save,
+	.x6_parse	= realm_parse,
+	.x6_options	= realm_opts,
+	.xlate		= realm_xlate,
+};
+
+void _init(void)
+{
+	realms = xtables_lmap_init(f_realms);
+	if (realms == NULL && errno != ENOENT)
+		fprintf(stderr, "Warning: %s: %s\n", f_realms,
+			strerror(errno));
+
+	xtables_register_match(&realm_mt_reg);
+}
diff --git a/extensions/libipt_realm.man b/extensions/libipt_realm.man
new file mode 100644
index 0000000..72dff9b
--- /dev/null
+++ b/extensions/libipt_realm.man
@@ -0,0 +1,9 @@
+This matches the routing realm.  Routing realms are used in complex routing
+setups involving dynamic routing protocols like BGP.
+.TP
+[\fB!\fP] \fB\-\-realm\fP \fIvalue\fP[\fB/\fP\fImask\fP]
+Matches a given realm number (and optionally mask). If not a number, value
+can be a named realm from /etc/iproute2/rt_realms (mask can not be used in
+that case).
+Both value and mask are four byte unsigned integers and may be specified in
+decimal, hex (by prefixing with "0x") or octal (if a leading zero is given).
diff --git a/extensions/libipt_realm.t b/extensions/libipt_realm.t
new file mode 100644
index 0000000..ca66640
--- /dev/null
+++ b/extensions/libipt_realm.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-m realm --realm 0x1/0x2a;=;OK
+-m realm --realm 0x2a;=;OK
+-m realm;;FAIL
diff --git a/extensions/libipt_realm.txlate b/extensions/libipt_realm.txlate
new file mode 100644
index 0000000..7d71029
--- /dev/null
+++ b/extensions/libipt_realm.txlate
@@ -0,0 +1,11 @@
+iptables-translate -A PREROUTING -m realm --realm 4
+nft add rule ip filter PREROUTING rtclassid 0x4 counter
+
+iptables-translate -A PREROUTING -m realm --realm 5/5
+nft add rule ip filter PREROUTING rtclassid and 0x5 == 0x5 counter
+
+iptables-translate -A PREROUTING -m realm ! --realm 50
+nft add rule ip filter PREROUTING rtclassid != 0x32 counter
+
+iptables-translate -A INPUT -m realm --realm 1/0xf
+nft add rule ip filter INPUT rtclassid and 0xf == 0x1 counter
diff --git a/extensions/libipt_ttl.c b/extensions/libipt_ttl.c
new file mode 100644
index 0000000..6bdd219
--- /dev/null
+++ b/extensions/libipt_ttl.c
@@ -0,0 +1,165 @@
+/* Shared library add-on to iptables to add TTL matching support 
+ * (C) 2000 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This program is released under the terms of GNU GPL */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv4/ipt_ttl.h>
+
+enum {
+	O_TTL_EQ = 0,
+	O_TTL_LT,
+	O_TTL_GT,
+	F_TTL_EQ = 1 << O_TTL_EQ,
+	F_TTL_LT = 1 << O_TTL_LT,
+	F_TTL_GT = 1 << O_TTL_GT,
+	F_ANY    = F_TTL_EQ | F_TTL_LT | F_TTL_GT,
+};
+
+static void ttl_help(void)
+{
+	printf(
+"ttl match options:\n"
+"[!] --ttl-eq value	Match time to live value\n"
+"  --ttl-lt value	Match TTL < value\n"
+"  --ttl-gt value	Match TTL > value\n");
+}
+
+static void ttl_parse(struct xt_option_call *cb)
+{
+	struct ipt_ttl_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TTL_EQ:
+		info->mode = cb->invert ? IPT_TTL_NE : IPT_TTL_EQ;
+		break;
+	case O_TTL_LT:
+		info->mode = IPT_TTL_LT;
+		break;
+	case O_TTL_GT:
+		info->mode = IPT_TTL_GT;
+		break;
+	}
+}
+
+static void ttl_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & F_ANY))
+		xtables_error(PARAMETER_PROBLEM,
+			"TTL match: You must specify one of "
+			"`--ttl-eq', `--ttl-lt', `--ttl-gt");
+}
+
+static void ttl_print(const void *ip, const struct xt_entry_match *match,
+                      int numeric)
+{
+	const struct ipt_ttl_info *info = 
+		(struct ipt_ttl_info *) match->data;
+
+	printf(" TTL match ");
+	switch (info->mode) {
+		case IPT_TTL_EQ:
+			printf("TTL ==");
+			break;
+		case IPT_TTL_NE:
+			printf("TTL !=");
+			break;
+		case IPT_TTL_LT:
+			printf("TTL <");
+			break;
+		case IPT_TTL_GT:
+			printf("TTL >");
+			break;
+	}
+	printf(" %u", info->ttl);
+}
+
+static void ttl_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ipt_ttl_info *info =
+		(struct ipt_ttl_info *) match->data;
+
+	switch (info->mode) {
+		case IPT_TTL_EQ:
+			printf(" --ttl-eq");
+			break;
+		case IPT_TTL_NE:
+			printf(" ! --ttl-eq");
+			break;
+		case IPT_TTL_LT:
+			printf(" --ttl-lt");
+			break;
+		case IPT_TTL_GT:
+			printf(" --ttl-gt");
+			break;
+		default:
+			/* error */
+			break;
+	}
+	printf(" %u", info->ttl);
+}
+
+static int ttl_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct ipt_ttl_info *info =
+		(struct ipt_ttl_info *) params->match->data;
+
+		switch (info->mode) {
+		case IPT_TTL_EQ:
+			xt_xlate_add(xl, "ip ttl");
+			break;
+		case IPT_TTL_NE:
+			xt_xlate_add(xl, "ip ttl !=");
+			break;
+		case IPT_TTL_LT:
+			xt_xlate_add(xl, "ip ttl lt");
+			break;
+		case IPT_TTL_GT:
+			xt_xlate_add(xl, "ip ttl gt");
+			break;
+		default:
+			/* Should not happen. */
+			break;
+	}
+
+	xt_xlate_add(xl, " %u", info->ttl);
+
+	return 1;
+}
+
+#define s struct ipt_ttl_info
+static const struct xt_option_entry ttl_opts[] = {
+	{.name = "ttl-lt", .id = O_TTL_LT, .excl = F_ANY, .type = XTTYPE_UINT8,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, ttl)},
+	{.name = "ttl-gt", .id = O_TTL_GT, .excl = F_ANY, .type = XTTYPE_UINT8,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, ttl)},
+	{.name = "ttl-eq", .id = O_TTL_EQ, .excl = F_ANY, .type = XTTYPE_UINT8,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, ttl)},
+	{.name = "ttl", .id = O_TTL_EQ, .excl = F_ANY, .type = XTTYPE_UINT8,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, ttl)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static struct xtables_match ttl_mt_reg = {
+	.name		= "ttl",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV4,
+	.size		= XT_ALIGN(sizeof(struct ipt_ttl_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ipt_ttl_info)),
+	.help		= ttl_help,
+	.print		= ttl_print,
+	.save		= ttl_save,
+	.x6_parse	= ttl_parse,
+	.x6_fcheck	= ttl_check,
+	.x6_options	= ttl_opts,
+	.xlate		= ttl_xlate,
+};
+
+
+void _init(void) 
+{
+	xtables_register_match(&ttl_mt_reg);
+}
diff --git a/extensions/libipt_ttl.man b/extensions/libipt_ttl.man
new file mode 100644
index 0000000..1f32277
--- /dev/null
+++ b/extensions/libipt_ttl.man
@@ -0,0 +1,10 @@
+This module matches the time to live field in the IP header.
+.TP
+[\fB!\fP] \fB\-\-ttl\-eq\fP \fIttl\fP
+Matches the given TTL value.
+.TP
+\fB\-\-ttl\-gt\fP \fIttl\fP
+Matches if TTL is greater than the given TTL value.
+.TP
+\fB\-\-ttl\-lt\fP \fIttl\fP
+Matches if TTL is less than the given TTL value.
diff --git a/extensions/libipt_ttl.t b/extensions/libipt_ttl.t
new file mode 100644
index 0000000..ebe5b3a
--- /dev/null
+++ b/extensions/libipt_ttl.t
@@ -0,0 +1,15 @@
+:INPUT,FORWARD,OUTPUT
+-m ttl --ttl-eq 0;=;OK
+-m ttl --ttl-eq 255;=;OK
+-m ttl ! --ttl-eq 0;=;OK
+-m ttl ! --ttl-eq 255;=;OK
+-m ttl --ttl-gt 0;=;OK
+# not possible have anything greater than 255, TTL is 8-bit long
+# ERROR: should fail: iptables -A INPUT -m ttl --ttl-gt 255
+## -m ttl --ttl-gt 255;;FAIL
+# not possible have anything below 0
+# ERROR: should fail: iptables -A INPUT -m ttl --ttl-lt 0
+## -m ttl --ttl-lt 0;;FAIL
+-m ttl --ttl-eq 256;;FAIL
+-m ttl --ttl-eq -1;;FAIL
+-m ttl;;FAIL
diff --git a/extensions/libipt_ttl.txlate b/extensions/libipt_ttl.txlate
new file mode 100644
index 0000000..3d5d6a7
--- /dev/null
+++ b/extensions/libipt_ttl.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A INPUT -m ttl --ttl-eq 3 -j ACCEPT
+nft add rule ip filter INPUT ip ttl 3 counter accept
+
+iptables-translate -A INPUT -m ttl --ttl-gt 5 -j ACCEPT
+nft add rule ip filter INPUT ip ttl gt 5 counter accept
diff --git a/extensions/libxt_AUDIT.c b/extensions/libxt_AUDIT.c
new file mode 100644
index 0000000..f7832de
--- /dev/null
+++ b/extensions/libxt_AUDIT.c
@@ -0,0 +1,112 @@
+/* Shared library add-on to xtables for AUDIT
+ *
+ * (C) 2010-2011, Thomas Graf <tgraf@redhat.com>
+ * (C) 2010-2011, Red Hat, Inc.
+ *
+ * This program is distributed under the terms of GNU GPL v2, 1991
+ */
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_AUDIT.h>
+
+enum {
+	O_AUDIT_TYPE = 0,
+};
+
+static void audit_help(void)
+{
+	printf(
+"AUDIT target options\n"
+"  --type TYPE		Action type to be recorded.\n");
+}
+
+static const struct xt_option_entry audit_opts[] = {
+	{.name = "type", .id = O_AUDIT_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND},
+	XTOPT_TABLEEND,
+};
+
+static void audit_parse(struct xt_option_call *cb)
+{
+	struct xt_audit_info *einfo = cb->data;
+
+	xtables_option_parse(cb);
+	if (strcasecmp(cb->arg, "accept") == 0)
+		einfo->type = XT_AUDIT_TYPE_ACCEPT;
+	else if (strcasecmp(cb->arg, "drop") == 0)
+		einfo->type = XT_AUDIT_TYPE_DROP;
+	else if (strcasecmp(cb->arg, "reject") == 0)
+		einfo->type = XT_AUDIT_TYPE_REJECT;
+	else
+		xtables_error(PARAMETER_PROBLEM,
+			   "Bad action type value \"%s\"", cb->arg);
+}
+
+static void audit_print(const void *ip, const struct xt_entry_target *target,
+                      int numeric)
+{
+	const struct xt_audit_info *einfo =
+		(const struct xt_audit_info *)target->data;
+
+	printf(" AUDIT ");
+
+	switch(einfo->type) {
+	case XT_AUDIT_TYPE_ACCEPT:
+		printf("accept");
+		break;
+	case XT_AUDIT_TYPE_DROP:
+		printf("drop");
+		break;
+	case XT_AUDIT_TYPE_REJECT:
+		printf("reject");
+		break;
+	}
+}
+
+static void audit_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_audit_info *einfo =
+		(const struct xt_audit_info *)target->data;
+
+	switch(einfo->type) {
+	case XT_AUDIT_TYPE_ACCEPT:
+		printf(" --type accept");
+		break;
+	case XT_AUDIT_TYPE_DROP:
+		printf(" --type drop");
+		break;
+	case XT_AUDIT_TYPE_REJECT:
+		printf(" --type reject");
+		break;
+	}
+}
+
+static int audit_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	/* audit type is merely sanity checked by xt_AUDIT.ko,
+	 * so nftables doesn't even support it */
+
+	xt_xlate_add(xl, "log level audit");
+	return 1;
+}
+
+static struct xtables_target audit_tg_reg = {
+	.name		= "AUDIT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_UNSPEC,
+	.size		= XT_ALIGN(sizeof(struct xt_audit_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_audit_info)),
+	.help		= audit_help,
+	.print		= audit_print,
+	.save		= audit_save,
+	.x6_parse	= audit_parse,
+	.x6_options	= audit_opts,
+	.xlate		= audit_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&audit_tg_reg);
+}
diff --git a/extensions/libxt_AUDIT.man b/extensions/libxt_AUDIT.man
new file mode 100644
index 0000000..8c513d2
--- /dev/null
+++ b/extensions/libxt_AUDIT.man
@@ -0,0 +1,16 @@
+This target creates audit records for packets hitting the target.
+It can be used to record accepted, dropped, and rejected packets. See
+auditd(8) for additional details.
+.TP
+\fB\-\-type\fP {\fBaccept\fP|\fBdrop\fP|\fBreject\fP}
+Set type of audit record. Starting with linux-4.12, this option has no effect
+on generated audit messages anymore. It is still accepted by iptables for
+compatibility reasons, but ignored.
+.PP
+Example:
+.IP
+iptables \-N AUDIT_DROP
+.IP
+iptables \-A AUDIT_DROP \-j AUDIT
+.IP
+iptables \-A AUDIT_DROP \-j DROP
diff --git a/extensions/libxt_AUDIT.t b/extensions/libxt_AUDIT.t
new file mode 100644
index 0000000..97575b0
--- /dev/null
+++ b/extensions/libxt_AUDIT.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-j AUDIT --type accept;=;OK
+-j AUDIT --type drop;=;OK
+-j AUDIT --type reject;=;OK
+-j AUDIT;;FAIL
+-j AUDIT --type wrong;;FAIL
diff --git a/extensions/libxt_AUDIT.txlate b/extensions/libxt_AUDIT.txlate
new file mode 100644
index 0000000..abd11ea
--- /dev/null
+++ b/extensions/libxt_AUDIT.txlate
@@ -0,0 +1,8 @@
+iptables-translate -t filter -A INPUT -j AUDIT --type accept
+nft add rule ip filter INPUT counter log level audit
+
+iptables-translate -t filter -A INPUT -j AUDIT --type drop
+nft add rule ip filter INPUT counter log level audit
+
+iptables-translate -t filter -A INPUT -j AUDIT --type reject
+nft add rule ip filter INPUT counter log level audit
diff --git a/extensions/libxt_CHECKSUM.c b/extensions/libxt_CHECKSUM.c
new file mode 100644
index 0000000..df9f9b3
--- /dev/null
+++ b/extensions/libxt_CHECKSUM.c
@@ -0,0 +1,77 @@
+/* Shared library add-on to xtables for CHECKSUM
+ *
+ * (C) 2002 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Red Hat, Inc
+ * Author: Michael S. Tsirkin <mst@redhat.com>
+ *
+ * This program is distributed under the terms of GNU GPL v2, 1991
+ *
+ * libxt_CHECKSUM.c borrowed some bits from libipt_ECN.c
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_CHECKSUM.h>
+
+enum {
+	O_CHECKSUM_FILL = 0,
+};
+
+static void CHECKSUM_help(void)
+{
+	printf(
+"CHECKSUM target options\n"
+"  --checksum-fill			Fill in packet checksum.\n");
+}
+
+static const struct xt_option_entry CHECKSUM_opts[] = {
+	{.name = "checksum-fill", .id = O_CHECKSUM_FILL,
+	 .flags = XTOPT_MAND, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static void CHECKSUM_parse(struct xt_option_call *cb)
+{
+	struct xt_CHECKSUM_info *einfo = cb->data;
+
+	xtables_option_parse(cb);
+	einfo->operation = XT_CHECKSUM_OP_FILL;
+}
+
+static void CHECKSUM_print(const void *ip, const struct xt_entry_target *target,
+                      int numeric)
+{
+	const struct xt_CHECKSUM_info *einfo =
+		(const struct xt_CHECKSUM_info *)target->data;
+
+	printf(" CHECKSUM");
+
+	if (einfo->operation & XT_CHECKSUM_OP_FILL)
+		printf(" fill");
+}
+
+static void CHECKSUM_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_CHECKSUM_info *einfo =
+		(const struct xt_CHECKSUM_info *)target->data;
+
+	if (einfo->operation & XT_CHECKSUM_OP_FILL)
+		printf(" --checksum-fill");
+}
+
+static struct xtables_target checksum_tg_reg = {
+	.name		= "CHECKSUM",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_UNSPEC,
+	.size		= XT_ALIGN(sizeof(struct xt_CHECKSUM_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_CHECKSUM_info)),
+	.help		= CHECKSUM_help,
+	.print		= CHECKSUM_print,
+	.save		= CHECKSUM_save,
+	.x6_parse	= CHECKSUM_parse,
+	.x6_options	= CHECKSUM_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&checksum_tg_reg);
+}
diff --git a/extensions/libxt_CHECKSUM.man b/extensions/libxt_CHECKSUM.man
new file mode 100644
index 0000000..726f4ea
--- /dev/null
+++ b/extensions/libxt_CHECKSUM.man
@@ -0,0 +1,8 @@
+This target selectively works around broken/old applications.
+It can only be used in the mangle table.
+.TP
+\fB\-\-checksum\-fill\fP
+Compute and fill in the checksum in a packet that lacks a checksum.
+This is particularly useful, if you need to work around old applications
+such as dhcp clients, that do not work well with checksum offloads,
+but don't want to disable checksum offload in your device.
diff --git a/extensions/libxt_CHECKSUM.t b/extensions/libxt_CHECKSUM.t
new file mode 100644
index 0000000..9451ad8
--- /dev/null
+++ b/extensions/libxt_CHECKSUM.t
@@ -0,0 +1,4 @@
+:PREROUTING,FORWARD,POSTROUTING
+*mangle
+-j CHECKSUM --checksum-fill;=;OK
+-j CHECKSUM;;FAIL
diff --git a/extensions/libxt_CLASSIFY.c b/extensions/libxt_CLASSIFY.c
new file mode 100644
index 0000000..75aaf0c
--- /dev/null
+++ b/extensions/libxt_CLASSIFY.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2003-2013 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_CLASSIFY.h>
+#include <linux/pkt_sched.h>
+
+enum {
+	O_SET_CLASS = 0,
+};
+
+static void
+CLASSIFY_help(void)
+{
+	printf(
+"CLASSIFY target options:\n"
+"--set-class MAJOR:MINOR    Set skb->priority value (always hexadecimal!)\n");
+}
+
+static const struct xt_option_entry CLASSIFY_opts[] = {
+	{.name = "set-class", .id = O_SET_CLASS, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND},
+	XTOPT_TABLEEND,
+};
+
+static int CLASSIFY_string_to_priority(const char *s, unsigned int *p)
+{
+	unsigned int i, j;
+
+	if (sscanf(s, "%x:%x", &i, &j) != 2)
+		return 1;
+	
+	*p = TC_H_MAKE(i<<16, j);
+	return 0;
+}
+
+static void CLASSIFY_parse(struct xt_option_call *cb)
+{
+	struct xt_classify_target_info *clinfo = cb->data;
+
+	xtables_option_parse(cb);
+	if (CLASSIFY_string_to_priority(cb->arg, &clinfo->priority))
+		xtables_error(PARAMETER_PROBLEM,
+			   "Bad class value \"%s\"", cb->arg);
+}
+
+static void
+CLASSIFY_print_class(unsigned int priority, int numeric)
+{
+	printf(" %x:%x", TC_H_MAJ(priority)>>16, TC_H_MIN(priority));
+}
+
+static void
+CLASSIFY_print(const void *ip,
+      const struct xt_entry_target *target,
+      int numeric)
+{
+	const struct xt_classify_target_info *clinfo =
+		(const struct xt_classify_target_info *)target->data;
+	printf(" CLASSIFY set");
+	CLASSIFY_print_class(clinfo->priority, numeric);
+}
+
+static void
+CLASSIFY_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_classify_target_info *clinfo =
+		(const struct xt_classify_target_info *)target->data;
+
+	printf(" --set-class %.4x:%.4x",
+	       TC_H_MAJ(clinfo->priority)>>16, TC_H_MIN(clinfo->priority));
+}
+
+static void
+CLASSIFY_arp_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_classify_target_info *clinfo =
+		(const struct xt_classify_target_info *)target->data;
+
+	printf(" --set-class %x:%x",
+	       TC_H_MAJ(clinfo->priority)>>16, TC_H_MIN(clinfo->priority));
+}
+
+static void
+CLASSIFY_arp_print(const void *ip,
+      const struct xt_entry_target *target,
+      int numeric)
+{
+	CLASSIFY_arp_save(ip, target);
+}
+
+static int CLASSIFY_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_tg_params *params)
+{
+	const struct xt_classify_target_info *clinfo =
+		(const struct xt_classify_target_info *)params->target->data;
+	__u32 handle = clinfo->priority;
+
+	xt_xlate_add(xl, "meta priority set ");
+
+	switch (handle) {
+	case TC_H_ROOT:
+		xt_xlate_add(xl, "root");
+		break;
+	case TC_H_UNSPEC:
+		xt_xlate_add(xl, "none");
+		break;
+	default:
+		xt_xlate_add(xl, "%0x:%0x", TC_H_MAJ(handle) >> 16,
+			     TC_H_MIN(handle));
+		break;
+	}
+
+	return 1;
+}
+
+static struct xtables_target classify_tg_reg[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "CLASSIFY",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_classify_target_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_classify_target_info)),
+		.help		= CLASSIFY_help,
+		.print		= CLASSIFY_print,
+		.save		= CLASSIFY_save,
+		.x6_parse	= CLASSIFY_parse,
+		.x6_options	= CLASSIFY_opts,
+		.xlate          = CLASSIFY_xlate,
+	},
+	{
+		.family		= NFPROTO_ARP,
+		.name		= "CLASSIFY",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_classify_target_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_classify_target_info)),
+		.help		= CLASSIFY_help,
+		.print		= CLASSIFY_arp_print,
+		.save		= CLASSIFY_arp_save,
+		.x6_parse	= CLASSIFY_parse,
+		.x6_options	= CLASSIFY_opts,
+		.xlate          = CLASSIFY_xlate,
+	}
+};
+
+void _init(void)
+{
+	xtables_register_targets(classify_tg_reg, ARRAY_SIZE(classify_tg_reg));
+}
diff --git a/extensions/libxt_CLASSIFY.man b/extensions/libxt_CLASSIFY.man
new file mode 100644
index 0000000..0270fd1
--- /dev/null
+++ b/extensions/libxt_CLASSIFY.man
@@ -0,0 +1,5 @@
+This module allows you to set the skb\->priority value (and thus classify the packet into a specific CBQ class).
+.TP
+\fB\-\-set\-class\fP \fImajor\fP\fB:\fP\fIminor\fP
+Set the major and minor class value. The values are always interpreted as
+hexadecimal even if no 0x prefix is given.
diff --git a/extensions/libxt_CLASSIFY.t b/extensions/libxt_CLASSIFY.t
new file mode 100644
index 0000000..7b3ddbf
--- /dev/null
+++ b/extensions/libxt_CLASSIFY.t
@@ -0,0 +1,9 @@
+:FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j CLASSIFY --set-class 0000:ffff;=;OK
+# maximum handle accepted by tc is 0xffff
+# ERROR : should fail: iptables -A FORWARD -t mangle -j CLASSIFY --set-class  0000:ffffffff
+# -j CLASSIFY --set-class 0000:ffffffff;;FAIL
+# ERROR: should fail: iptables -A FORWARD -t mangle -j CLASSIFY --set-class 1:-1
+# -j CLASSIFY --set-class 1:-1;;FAIL
+-j CLASSIFY;;FAIL
diff --git a/extensions/libxt_CLASSIFY.txlate b/extensions/libxt_CLASSIFY.txlate
new file mode 100644
index 0000000..3b34923
--- /dev/null
+++ b/extensions/libxt_CLASSIFY.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A OUTPUT -j CLASSIFY --set-class 0:0
+nft add rule ip filter OUTPUT counter meta priority set none
+
+iptables-translate -A OUTPUT -j CLASSIFY --set-class ffff:ffff
+nft add rule ip filter OUTPUT counter meta priority set root
+
+iptables-translate -A OUTPUT -j CLASSIFY --set-class 1:234
+nft add rule ip filter OUTPUT counter meta priority set 1:234
diff --git a/extensions/libxt_CONNMARK.c b/extensions/libxt_CONNMARK.c
new file mode 100644
index 0000000..21e1091
--- /dev/null
+++ b/extensions/libxt_CONNMARK.c
@@ -0,0 +1,695 @@
+/* Shared library add-on to iptables to add CONNMARK target support.
+ *
+ * (C) 2002,2004 MARA Systems AB <http://www.marasystems.com>
+ * by Henrik Nordstrom <hno@marasystems.com>
+ *
+ * Version 1.1
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_CONNMARK.h>
+
+struct xt_connmark_target_info {
+	unsigned long mark;
+	unsigned long mask;
+	uint8_t mode;
+};
+
+enum {
+	D_SHIFT_LEFT = 0,
+	D_SHIFT_RIGHT,
+};
+
+enum {
+	O_SET_MARK = 0,
+	O_SAVE_MARK,
+	O_RESTORE_MARK,
+	O_AND_MARK,
+	O_OR_MARK,
+	O_XOR_MARK,
+	O_LEFT_SHIFT_MARK,
+	O_RIGHT_SHIFT_MARK,
+	O_SET_XMARK,
+	O_CTMASK,
+	O_NFMASK,
+	O_MASK,
+	F_SET_MARK         = 1 << O_SET_MARK,
+	F_SAVE_MARK        = 1 << O_SAVE_MARK,
+	F_RESTORE_MARK     = 1 << O_RESTORE_MARK,
+	F_AND_MARK         = 1 << O_AND_MARK,
+	F_OR_MARK          = 1 << O_OR_MARK,
+	F_XOR_MARK         = 1 << O_XOR_MARK,
+	F_LEFT_SHIFT_MARK  = 1 << O_LEFT_SHIFT_MARK,
+	F_RIGHT_SHIFT_MARK = 1 << O_RIGHT_SHIFT_MARK,
+	F_SET_XMARK        = 1 << O_SET_XMARK,
+	F_CTMASK           = 1 << O_CTMASK,
+	F_NFMASK           = 1 << O_NFMASK,
+	F_MASK             = 1 << O_MASK,
+	F_OP_ANY           = F_SET_MARK | F_SAVE_MARK | F_RESTORE_MARK |
+	                     F_AND_MARK | F_OR_MARK | F_XOR_MARK | F_SET_XMARK,
+};
+
+static const char *const xt_connmark_shift_ops[] = {
+	"left-shift-mark",
+	"right-shift-mark"
+};
+
+static void CONNMARK_help(void)
+{
+	printf(
+"CONNMARK target options:\n"
+"  --set-mark value[/mask]       Set conntrack mark value\n"
+"  --save-mark [--mask mask]     Save the packet nfmark in the connection\n"
+"  --restore-mark [--mask mask]  Restore saved nfmark value\n");
+}
+
+#define s struct xt_connmark_target_info
+static const struct xt_option_entry CONNMARK_opts[] = {
+	{.name = "set-mark", .id = O_SET_MARK, .type = XTTYPE_MARKMASK32,
+	 .excl = F_OP_ANY},
+	{.name = "save-mark", .id = O_SAVE_MARK, .type = XTTYPE_NONE,
+	 .excl = F_OP_ANY},
+	{.name = "restore-mark", .id = O_RESTORE_MARK, .type = XTTYPE_NONE,
+	 .excl = F_OP_ANY},
+	{.name = "mask", .id = O_MASK, .type = XTTYPE_UINT32},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_connmark_tginfo1
+static const struct xt_option_entry connmark_tg_opts[] = {
+	{.name = "set-xmark", .id = O_SET_XMARK, .type = XTTYPE_MARKMASK32,
+	 .excl = F_OP_ANY},
+	{.name = "set-mark", .id = O_SET_MARK, .type = XTTYPE_MARKMASK32,
+	 .excl = F_OP_ANY},
+	{.name = "and-mark", .id = O_AND_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_OP_ANY},
+	{.name = "or-mark", .id = O_OR_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_OP_ANY},
+	{.name = "xor-mark", .id = O_XOR_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_OP_ANY},
+	{.name = "save-mark", .id = O_SAVE_MARK, .type = XTTYPE_NONE,
+	 .excl = F_OP_ANY},
+	{.name = "restore-mark", .id = O_RESTORE_MARK, .type = XTTYPE_NONE,
+	 .excl = F_OP_ANY},
+	{.name = "ctmask", .id = O_CTMASK, .type = XTTYPE_UINT32,
+	 .excl = F_MASK, .flags = XTOPT_PUT, XTOPT_POINTER(s, ctmask)},
+	{.name = "nfmask", .id = O_NFMASK, .type = XTTYPE_UINT32,
+	 .excl = F_MASK, .flags = XTOPT_PUT, XTOPT_POINTER(s, nfmask)},
+	{.name = "mask", .id = O_MASK, .type = XTTYPE_UINT32,
+	 .excl = F_CTMASK | F_NFMASK},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_connmark_tginfo2
+static const struct xt_option_entry connmark_tg_opts_v2[] = {
+	{.name = "set-xmark", .id = O_SET_XMARK, .type = XTTYPE_MARKMASK32,
+	 .excl = F_OP_ANY},
+	{.name = "set-mark", .id = O_SET_MARK, .type = XTTYPE_MARKMASK32,
+	 .excl = F_OP_ANY},
+	{.name = "and-mark", .id = O_AND_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_OP_ANY},
+	{.name = "or-mark", .id = O_OR_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_OP_ANY},
+	{.name = "xor-mark", .id = O_XOR_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_OP_ANY},
+	{.name = "save-mark", .id = O_SAVE_MARK, .type = XTTYPE_NONE,
+	 .excl = F_OP_ANY},
+	{.name = "restore-mark", .id = O_RESTORE_MARK, .type = XTTYPE_NONE,
+	 .excl = F_OP_ANY},
+	{.name = "left-shift-mark", .id = O_LEFT_SHIFT_MARK, .type = XTTYPE_UINT8,
+	 .min = 0, .max = 32},
+	{.name = "right-shift-mark", .id = O_RIGHT_SHIFT_MARK, .type = XTTYPE_UINT8,
+	 .min = 0, .max = 32},
+	{.name = "ctmask", .id = O_CTMASK, .type = XTTYPE_UINT32,
+	 .excl = F_MASK, .flags = XTOPT_PUT, XTOPT_POINTER(s, ctmask)},
+	{.name = "nfmask", .id = O_NFMASK, .type = XTTYPE_UINT32,
+	 .excl = F_MASK, .flags = XTOPT_PUT, XTOPT_POINTER(s, nfmask)},
+	{.name = "mask", .id = O_MASK, .type = XTTYPE_UINT32,
+	 .excl = F_CTMASK | F_NFMASK},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void connmark_tg_help(void)
+{
+	printf(
+"CONNMARK target options:\n"
+"  --set-xmark value[/ctmask]    Zero mask bits and XOR ctmark with value\n"
+"  --save-mark [--ctmask mask] [--nfmask mask]\n"
+"                                Copy ctmark to nfmark using masks\n"
+"  --restore-mark [--ctmask mask] [--nfmask mask]\n"
+"                                Copy nfmark to ctmark using masks\n"
+"  --set-mark value[/mask]       Set conntrack mark value\n"
+"  --save-mark [--mask mask]     Save the packet nfmark in the connection\n"
+"  --restore-mark [--mask mask]  Restore saved nfmark value\n"
+"  --and-mark value              Binary AND the ctmark with bits\n"
+"  --or-mark value               Binary OR  the ctmark with bits\n"
+"  --xor-mark value              Binary XOR the ctmark with bits\n"
+);
+}
+
+static void connmark_tg_help_v2(void)
+{
+	connmark_tg_help();
+	printf(
+"  --left-shift-mark value       Left shift the ctmark with bits\n"
+"  --right-shift-mark value      Right shift the ctmark with bits\n"
+);
+}
+
+static void connmark_tg_init(struct xt_entry_target *target)
+{
+	struct xt_connmark_tginfo1 *info = (void *)target->data;
+
+	/*
+	 * Need these defaults for --save-mark/--restore-mark if no
+	 * --ctmark or --nfmask is given.
+	 */
+	info->ctmask = UINT32_MAX;
+	info->nfmask = UINT32_MAX;
+}
+
+static void connmark_tg_init_v2(struct xt_entry_target *target)
+{
+	struct xt_connmark_tginfo2 *info;
+
+	connmark_tg_init(target);
+	info = (void *)target->data;
+
+	/* Left shift by zero bit by default. */
+	info->shift_dir = D_SHIFT_LEFT;
+	info->shift_bits = 0;
+}
+
+static void CONNMARK_parse(struct xt_option_call *cb)
+{
+	struct xt_connmark_target_info *markinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SET_MARK:
+		markinfo->mode = XT_CONNMARK_SET;
+		markinfo->mark = cb->val.mark;
+		markinfo->mask = cb->val.mask;
+		break;
+	case O_SAVE_MARK:
+		markinfo->mode = XT_CONNMARK_SAVE;
+		break;
+	case O_RESTORE_MARK:
+		markinfo->mode = XT_CONNMARK_RESTORE;
+		break;
+	case O_MASK:
+		markinfo->mask = cb->val.u32;
+		break;
+	}
+}
+
+static void connmark_tg_parse(struct xt_option_call *cb)
+{
+	struct xt_connmark_tginfo1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SET_XMARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.mark;
+		info->ctmask = cb->val.mask;
+		break;
+	case O_SET_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.mark;
+		info->ctmask = cb->val.mark | cb->val.mask;
+		break;
+	case O_AND_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = 0;
+		info->ctmask = ~cb->val.u32;
+		break;
+	case O_OR_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.u32;
+		info->ctmask = cb->val.u32;
+		break;
+	case O_XOR_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.u32;
+		info->ctmask = 0;
+		break;
+	case O_SAVE_MARK:
+		info->mode = XT_CONNMARK_SAVE;
+		break;
+	case O_RESTORE_MARK:
+		info->mode = XT_CONNMARK_RESTORE;
+		break;
+	case O_MASK:
+		info->nfmask = info->ctmask = cb->val.u32;
+		break;
+	default:
+		break;
+	}
+}
+
+static void connmark_tg_parse_v2(struct xt_option_call *cb)
+{
+	struct xt_connmark_tginfo2 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SET_XMARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.mark;
+		info->ctmask = cb->val.mask;
+		break;
+	case O_SET_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.mark;
+		info->ctmask = cb->val.mark | cb->val.mask;
+		break;
+	case O_AND_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = 0;
+		info->ctmask = ~cb->val.u32;
+		break;
+	case O_OR_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.u32;
+		info->ctmask = cb->val.u32;
+		break;
+	case O_XOR_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.u32;
+		info->ctmask = 0;
+		break;
+	case O_SAVE_MARK:
+		info->mode = XT_CONNMARK_SAVE;
+		break;
+	case O_RESTORE_MARK:
+		info->mode = XT_CONNMARK_RESTORE;
+		break;
+	case O_MASK:
+		info->nfmask = info->ctmask = cb->val.u32;
+		break;
+	case O_LEFT_SHIFT_MARK:
+		info->shift_dir = D_SHIFT_LEFT;
+		info->shift_bits = cb->val.u8;
+		break;
+	case O_RIGHT_SHIFT_MARK:
+		info->shift_dir = D_SHIFT_RIGHT;
+		info->shift_bits = cb->val.u8;
+		break;
+	default:
+		break;
+	}
+}
+
+static void connmark_tg_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & F_OP_ANY))
+		xtables_error(PARAMETER_PROBLEM,
+		           "CONNMARK target: No operation specified");
+}
+
+static void
+print_mark(unsigned long mark)
+{
+	printf("0x%lx", mark);
+}
+
+static void
+print_mask(const char *text, unsigned long mask)
+{
+	if (mask != 0xffffffffUL)
+		printf("%s0x%lx", text, mask);
+}
+
+static void CONNMARK_print(const void *ip,
+                           const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_connmark_target_info *markinfo =
+		(const struct xt_connmark_target_info *)target->data;
+	switch (markinfo->mode) {
+	case XT_CONNMARK_SET:
+	    printf(" CONNMARK set ");
+	    print_mark(markinfo->mark);
+	    print_mask("/", markinfo->mask);
+	    break;
+	case XT_CONNMARK_SAVE:
+	    printf(" CONNMARK save ");
+	    print_mask("mask ", markinfo->mask);
+	    break;
+	case XT_CONNMARK_RESTORE:
+	    printf(" CONNMARK restore ");
+	    print_mask("mask ", markinfo->mask);
+	    break;
+	default:
+	    printf(" ERROR: UNKNOWN CONNMARK MODE");
+	    break;
+	}
+}
+
+static void
+connmark_tg_print(const void *ip, const struct xt_entry_target *target,
+                  int numeric)
+{
+	const struct xt_connmark_tginfo1 *info = (const void *)target->data;
+
+	switch (info->mode) {
+	case XT_CONNMARK_SET:
+		if (info->ctmark == 0)
+			printf(" CONNMARK and 0x%x",
+			       (unsigned int)(uint32_t)~info->ctmask);
+		else if (info->ctmark == info->ctmask)
+			printf(" CONNMARK or 0x%x", info->ctmark);
+		else if (info->ctmask == 0)
+			printf(" CONNMARK xor 0x%x", info->ctmark);
+		else if (info->ctmask == 0xFFFFFFFFU)
+			printf(" CONNMARK set 0x%x", info->ctmark);
+		else
+			printf(" CONNMARK xset 0x%x/0x%x",
+			       info->ctmark, info->ctmask);
+		break;
+	case XT_CONNMARK_SAVE:
+		if (info->nfmask == UINT32_MAX && info->ctmask == UINT32_MAX)
+			printf(" CONNMARK save");
+		else if (info->nfmask == info->ctmask)
+			printf(" CONNMARK save mask 0x%x", info->nfmask);
+		else
+			printf(" CONNMARK save nfmask 0x%x ctmask ~0x%x",
+			       info->nfmask, info->ctmask);
+		break;
+	case XT_CONNMARK_RESTORE:
+		if (info->ctmask == UINT32_MAX && info->nfmask == UINT32_MAX)
+			printf(" CONNMARK restore");
+		else if (info->ctmask == info->nfmask)
+			printf(" CONNMARK restore mask 0x%x", info->ctmask);
+		else
+			printf(" CONNMARK restore ctmask 0x%x nfmask ~0x%x",
+			       info->ctmask, info->nfmask);
+		break;
+
+	default:
+		printf(" ERROR: UNKNOWN CONNMARK MODE");
+		break;
+	}
+}
+
+static void
+connmark_tg_print_v2(const void *ip, const struct xt_entry_target *target,
+                  int numeric)
+{
+	const struct xt_connmark_tginfo2 *info = (const void *)target->data;
+	const char *shift_op = xt_connmark_shift_ops[info->shift_dir];
+
+	switch (info->mode) {
+	case XT_CONNMARK_SET:
+		if (info->ctmark == 0)
+			printf(" CONNMARK and 0x%x",
+			       (unsigned int)(uint32_t)~info->ctmask);
+		else if (info->ctmark == info->ctmask)
+			printf(" CONNMARK or 0x%x", info->ctmark);
+		else if (info->ctmask == 0)
+			printf(" CONNMARK xor 0x%x", info->ctmark);
+		else if (info->ctmask == 0xFFFFFFFFU)
+			printf(" CONNMARK set 0x%x", info->ctmark);
+		else
+			printf(" CONNMARK xset 0x%x/0x%x",
+			       info->ctmark, info->ctmask);
+		break;
+	case XT_CONNMARK_SAVE:
+		if (info->nfmask == UINT32_MAX && info->ctmask == UINT32_MAX)
+			printf(" CONNMARK save");
+		else if (info->nfmask == info->ctmask)
+			printf(" CONNMARK save mask 0x%x", info->nfmask);
+		else
+			printf(" CONNMARK save nfmask 0x%x ctmask ~0x%x",
+			       info->nfmask, info->ctmask);
+		break;
+	case XT_CONNMARK_RESTORE:
+		if (info->ctmask == UINT32_MAX && info->nfmask == UINT32_MAX)
+			printf(" CONNMARK restore");
+		else if (info->ctmask == info->nfmask)
+			printf(" CONNMARK restore mask 0x%x", info->ctmask);
+		else
+			printf(" CONNMARK restore ctmask 0x%x nfmask ~0x%x",
+			       info->ctmask, info->nfmask);
+		break;
+
+	default:
+		printf(" ERROR: UNKNOWN CONNMARK MODE");
+		break;
+	}
+
+	if (info->mode <= XT_CONNMARK_RESTORE &&
+	    info->shift_bits != 0) {
+		printf(" %s %u", shift_op, info->shift_bits);
+	}
+}
+
+static void CONNMARK_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_connmark_target_info *markinfo =
+		(const struct xt_connmark_target_info *)target->data;
+
+	switch (markinfo->mode) {
+	case XT_CONNMARK_SET:
+	    printf(" --set-mark ");
+	    print_mark(markinfo->mark);
+	    print_mask("/", markinfo->mask);
+	    break;
+	case XT_CONNMARK_SAVE:
+	    printf(" --save-mark ");
+	    print_mask("--mask ", markinfo->mask);
+	    break;
+	case XT_CONNMARK_RESTORE:
+	    printf(" --restore-mark ");
+	    print_mask("--mask ", markinfo->mask);
+	    break;
+	default:
+	    printf(" ERROR: UNKNOWN CONNMARK MODE");
+	    break;
+	}
+}
+
+static void CONNMARK_init(struct xt_entry_target *t)
+{
+	struct xt_connmark_target_info *markinfo
+		= (struct xt_connmark_target_info *)t->data;
+
+	markinfo->mask = 0xffffffffUL;
+}
+
+static void
+connmark_tg_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_connmark_tginfo1 *info = (const void *)target->data;
+
+	switch (info->mode) {
+	case XT_CONNMARK_SET:
+		printf(" --set-xmark 0x%x/0x%x", info->ctmark, info->ctmask);
+		break;
+	case XT_CONNMARK_SAVE:
+		printf(" --save-mark --nfmask 0x%x --ctmask 0x%x",
+		       info->nfmask, info->ctmask);
+		break;
+	case XT_CONNMARK_RESTORE:
+		printf(" --restore-mark --nfmask 0x%x --ctmask 0x%x",
+		       info->nfmask, info->ctmask);
+		break;
+	default:
+		printf(" ERROR: UNKNOWN CONNMARK MODE");
+		break;
+	}
+}
+
+static void
+connmark_tg_save_v2(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_connmark_tginfo2 *info = (const void *)target->data;
+	const char *shift_op = xt_connmark_shift_ops[info->shift_dir];
+
+	switch (info->mode) {
+	case XT_CONNMARK_SET:
+		printf(" --set-xmark 0x%x/0x%x", info->ctmark, info->ctmask);
+		break;
+	case XT_CONNMARK_SAVE:
+		printf(" --save-mark --nfmask 0x%x --ctmask 0x%x",
+		       info->nfmask, info->ctmask);
+		break;
+	case XT_CONNMARK_RESTORE:
+		printf(" --restore-mark --nfmask 0x%x --ctmask 0x%x",
+		       info->nfmask, info->ctmask);
+		break;
+	default:
+		printf(" ERROR: UNKNOWN CONNMARK MODE");
+		break;
+	}
+
+	if (info->mode <= XT_CONNMARK_RESTORE &&
+	    info->shift_bits != 0) {
+		printf(" --%s %u", shift_op, info->shift_bits);
+	}
+}
+
+static int connmark_tg_xlate(struct xt_xlate *xl,
+			     const struct xt_xlate_tg_params *params)
+{
+	const struct xt_connmark_tginfo1 *info =
+		(const void *)params->target->data;
+
+	switch (info->mode) {
+	case XT_CONNMARK_SET:
+		xt_xlate_add(xl, "ct mark set ");
+		if (info->ctmask == 0xFFFFFFFFU)
+			xt_xlate_add(xl, "0x%x ", info->ctmark);
+		else if (info->ctmark == 0)
+			xt_xlate_add(xl, "ct mark and 0x%x", ~info->ctmask);
+		else if (info->ctmark == info->ctmask)
+			xt_xlate_add(xl, "ct mark or 0x%x",
+				     info->ctmark);
+		else if (info->ctmask == 0)
+			xt_xlate_add(xl, "ct mark xor 0x%x",
+				     info->ctmark);
+		else
+			xt_xlate_add(xl, "ct mark xor 0x%x and 0x%x",
+				     info->ctmark, ~info->ctmask);
+		break;
+	case XT_CONNMARK_SAVE:
+		if (info->nfmask == info->ctmask &&
+		    info->nfmask == UINT32_MAX)
+			xt_xlate_add(xl, "ct mark set mark");
+		else
+			return 0;
+		break;
+	case XT_CONNMARK_RESTORE:
+		if (info->nfmask == info->ctmask &&
+		    info->nfmask == UINT32_MAX)
+			xt_xlate_add(xl, "meta mark set ct mark");
+		else
+			return 0;
+		break;
+	}
+
+	return 1;
+}
+
+static int connmark_tg_xlate_v2(struct xt_xlate *xl,
+			     const struct xt_xlate_tg_params *params)
+{
+	const struct xt_connmark_tginfo2 *info =
+		(const void *)params->target->data;
+	const char *shift_op = xt_connmark_shift_ops[info->shift_dir];
+
+	switch (info->mode) {
+	case XT_CONNMARK_SET:
+		xt_xlate_add(xl, "ct mark set ");
+		if (info->ctmask == 0xFFFFFFFFU)
+			xt_xlate_add(xl, "0x%x ", info->ctmark);
+		else if (info->ctmark == 0)
+			xt_xlate_add(xl, "ct mark and 0x%x", ~info->ctmask);
+		else if (info->ctmark == info->ctmask)
+			xt_xlate_add(xl, "ct mark or 0x%x",
+				     info->ctmark);
+		else if (info->ctmask == 0)
+			xt_xlate_add(xl, "ct mark xor 0x%x",
+				     info->ctmark);
+		else
+			xt_xlate_add(xl, "ct mark xor 0x%x and 0x%x",
+				     info->ctmark, ~info->ctmask);
+		break;
+	case XT_CONNMARK_SAVE:
+		xt_xlate_add(xl, "ct mark set mark");
+		if (!(info->nfmask == UINT32_MAX &&
+		    info->ctmask == UINT32_MAX)) {
+			if (info->nfmask == info->ctmask)
+				xt_xlate_add(xl, " and 0x%x", info->nfmask);
+		}
+		break;
+	case XT_CONNMARK_RESTORE:
+		xt_xlate_add(xl, "meta mark set ct mark");
+		if (!(info->nfmask == UINT32_MAX &&
+		    info->ctmask == UINT32_MAX)) {
+			if (info->nfmask == info->ctmask)
+				xt_xlate_add(xl, " and 0x%x", info->nfmask);
+		}
+		break;
+	}
+
+	if (info->mode <= XT_CONNMARK_RESTORE &&
+	    info->shift_bits != 0) {
+		xt_xlate_add(xl, " %s %u", shift_op, info->shift_bits);
+	}
+
+	return 1;
+}
+static struct xtables_target connmark_tg_reg[] = {
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "CONNMARK",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_connmark_target_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_connmark_target_info)),
+		.help          = CONNMARK_help,
+		.init          = CONNMARK_init,
+		.print         = CONNMARK_print,
+		.save          = CONNMARK_save,
+		.x6_parse      = CONNMARK_parse,
+		.x6_fcheck     = connmark_tg_check,
+		.x6_options    = CONNMARK_opts,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "CONNMARK",
+		.revision      = 1,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_connmark_tginfo1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_connmark_tginfo1)),
+		.help          = connmark_tg_help,
+		.init          = connmark_tg_init,
+		.print         = connmark_tg_print,
+		.save          = connmark_tg_save,
+		.x6_parse      = connmark_tg_parse,
+		.x6_fcheck     = connmark_tg_check,
+		.x6_options    = connmark_tg_opts,
+		.xlate         = connmark_tg_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "CONNMARK",
+		.revision      = 2,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_connmark_tginfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_connmark_tginfo2)),
+		.help          = connmark_tg_help_v2,
+		.init          = connmark_tg_init_v2,
+		.print         = connmark_tg_print_v2,
+		.save          = connmark_tg_save_v2,
+		.x6_parse      = connmark_tg_parse_v2,
+		.x6_fcheck     = connmark_tg_check,
+		.x6_options    = connmark_tg_opts_v2,
+		.xlate         = connmark_tg_xlate_v2,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(connmark_tg_reg, ARRAY_SIZE(connmark_tg_reg));
+}
diff --git a/extensions/libxt_CONNMARK.man b/extensions/libxt_CONNMARK.man
new file mode 100644
index 0000000..9317923
--- /dev/null
+++ b/extensions/libxt_CONNMARK.man
@@ -0,0 +1,53 @@
+This module sets the netfilter mark value associated with a connection. The
+mark is 32 bits wide.
+.TP
+\fB\-\-set\-xmark\fP \fIvalue\fP[\fB/\fP\fImask\fP]
+Zero out the bits given by \fImask\fP and XOR \fIvalue\fP into the ctmark.
+.TP
+\fB\-\-save\-mark\fP [\fB\-\-nfmask\fP \fInfmask\fP] [\fB\-\-ctmask\fP \fIctmask\fP]
+Copy the packet mark (nfmark) to the connection mark (ctmark) using the given
+masks. The new nfmark value is determined as follows:
+.IP
+ctmark = (ctmark & ~ctmask) ^ (nfmark & nfmask)
+.IP
+i.e. \fIctmask\fP defines what bits to clear and \fInfmask\fP what bits of the
+nfmark to XOR into the ctmark. \fIctmask\fP and \fInfmask\fP default to
+0xFFFFFFFF.
+.TP
+\fB\-\-restore\-mark\fP [\fB\-\-nfmask\fP \fInfmask\fP] [\fB\-\-ctmask\fP \fIctmask\fP]
+Copy the connection mark (ctmark) to the packet mark (nfmark) using the given
+masks. The new ctmark value is determined as follows:
+.IP
+nfmark = (nfmark & ~\fInfmask\fP) ^ (ctmark & \fIctmask\fP);
+.IP
+i.e. \fInfmask\fP defines what bits to clear and \fIctmask\fP what bits of the
+ctmark to XOR into the nfmark. \fIctmask\fP and \fInfmask\fP default to
+0xFFFFFFFF.
+.IP
+\fB\-\-restore\-mark\fP is only valid in the \fBmangle\fP table.
+.PP
+The following mnemonics are available for \fB\-\-set\-xmark\fP:
+.TP
+\fB\-\-and\-mark\fP \fIbits\fP
+Binary AND the ctmark with \fIbits\fP. (Mnemonic for \fB\-\-set\-xmark
+0/\fP\fIinvbits\fP, where \fIinvbits\fP is the binary negation of \fIbits\fP.)
+.TP
+\fB\-\-or\-mark\fP \fIbits\fP
+Binary OR the ctmark with \fIbits\fP. (Mnemonic for \fB\-\-set\-xmark\fP
+\fIbits\fP\fB/\fP\fIbits\fP.)
+.TP
+\fB\-\-xor\-mark\fP \fIbits\fP
+Binary XOR the ctmark with \fIbits\fP. (Mnemonic for \fB\-\-set\-xmark\fP
+\fIbits\fP\fB/0\fP.)
+.TP
+\fB\-\-set\-mark\fP \fIvalue\fP[\fB/\fP\fImask\fP]
+Set the connection mark. If a mask is specified then only those bits set in the
+mask are modified.
+.TP
+\fB\-\-save\-mark\fP [\fB\-\-mask\fP \fImask\fP]
+Copy the nfmark to the ctmark. If a mask is specified, only those bits are
+copied.
+.TP
+\fB\-\-restore\-mark\fP [\fB\-\-mask\fP \fImask\fP]
+Copy the ctmark to the nfmark. If a mask is specified, only those bits are
+copied. This is only valid in the \fBmangle\fP table.
diff --git a/extensions/libxt_CONNMARK.t b/extensions/libxt_CONNMARK.t
new file mode 100644
index 0000000..79a838f
--- /dev/null
+++ b/extensions/libxt_CONNMARK.t
@@ -0,0 +1,7 @@
+:PREROUTING,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j CONNMARK --restore-mark;=;OK
+-j CONNMARK --save-mark;=;OK
+-j CONNMARK --save-mark --nfmask 0xfffffff --ctmask 0xffffffff;-j CONNMARK --save-mark;OK
+-j CONNMARK --restore-mark --nfmask 0xfffffff --ctmask 0xffffffff;-j CONNMARK --restore-mark;OK
+-j CONNMARK;;FAIL
diff --git a/extensions/libxt_CONNMARK.txlate b/extensions/libxt_CONNMARK.txlate
new file mode 100644
index 0000000..ce40ae5
--- /dev/null
+++ b/extensions/libxt_CONNMARK.txlate
@@ -0,0 +1,20 @@
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --set-mark 0
+nft add rule ip mangle PREROUTING counter ct mark set 0x0
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --set-mark 0x16
+nft add rule ip mangle PREROUTING counter ct mark set 0x16
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --set-xmark 0x16/0x12
+nft add rule ip mangle PREROUTING counter ct mark set ct mark xor 0x16 and 0xffffffed
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --and-mark 0x16
+nft add rule ip mangle PREROUTING counter ct mark set ct mark and 0x16
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --or-mark 0x16
+nft add rule ip mangle PREROUTING counter ct mark set ct mark or 0x16
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --save-mark
+nft add rule ip mangle PREROUTING counter ct mark set mark
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --restore-mark
+nft add rule ip mangle PREROUTING counter meta mark set ct mark
diff --git a/extensions/libxt_CONNSECMARK.c b/extensions/libxt_CONNSECMARK.c
new file mode 100644
index 0000000..0b3cd79
--- /dev/null
+++ b/extensions/libxt_CONNSECMARK.c
@@ -0,0 +1,112 @@
+/*
+ * Shared library add-on to iptables to add CONNSECMARK target support.
+ *
+ * Based on the MARK and CONNMARK targets.
+ *
+ * Copyright (C) 2006 Red Hat, Inc., James Morris <jmorris@redhat.com>
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_CONNSECMARK.h>
+
+#define PFX "CONNSECMARK target: "
+
+enum {
+	O_SAVE = 0,
+	O_RESTORE,
+	F_SAVE    = 1 << O_SAVE,
+	F_RESTORE = 1 << O_RESTORE,
+};
+
+static void CONNSECMARK_help(void)
+{
+	printf(
+"CONNSECMARK target options:\n"
+"  --save                   Copy security mark from packet to conntrack\n"
+"  --restore                Copy security mark from connection to packet\n");
+}
+
+static const struct xt_option_entry CONNSECMARK_opts[] = {
+	{.name = "save", .id = O_SAVE, .excl = F_RESTORE, .type = XTTYPE_NONE},
+	{.name = "restore", .id = O_RESTORE, .excl = F_SAVE,
+	 .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static void CONNSECMARK_parse(struct xt_option_call *cb)
+{
+	struct xt_connsecmark_target_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SAVE:
+		info->mode = CONNSECMARK_SAVE;
+		break;
+	case O_RESTORE:
+		info->mode = CONNSECMARK_RESTORE;
+		break;
+	}
+}
+
+static void CONNSECMARK_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM, PFX "parameter required");
+}
+
+static void print_connsecmark(const struct xt_connsecmark_target_info *info)
+{
+	switch (info->mode) {
+	case CONNSECMARK_SAVE:
+		printf("save");
+		break;
+		
+	case CONNSECMARK_RESTORE:
+		printf("restore");
+		break;
+		
+	default:
+		xtables_error(OTHER_PROBLEM, PFX "invalid mode %hhu\n", info->mode);
+	}
+}
+
+static void
+CONNSECMARK_print(const void *ip, const struct xt_entry_target *target,
+                  int numeric)
+{
+	const struct xt_connsecmark_target_info *info =
+		(struct xt_connsecmark_target_info*)(target)->data;
+
+	printf(" CONNSECMARK ");
+	print_connsecmark(info);
+}
+
+static void
+CONNSECMARK_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_connsecmark_target_info *info =
+		(struct xt_connsecmark_target_info*)target->data;
+
+	printf(" --");
+	print_connsecmark(info);
+}
+
+static struct xtables_target connsecmark_target = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "CONNSECMARK",
+	.version	= XTABLES_VERSION,
+	.revision	= 0,
+	.size		= XT_ALIGN(sizeof(struct xt_connsecmark_target_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_connsecmark_target_info)),
+	.help		= CONNSECMARK_help,
+	.print		= CONNSECMARK_print,
+	.save		= CONNSECMARK_save,
+	.x6_parse	= CONNSECMARK_parse,
+	.x6_fcheck	= CONNSECMARK_check,
+	.x6_options	= CONNSECMARK_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&connsecmark_target);
+}
diff --git a/extensions/libxt_CONNSECMARK.man b/extensions/libxt_CONNSECMARK.man
new file mode 100644
index 0000000..2616ab9
--- /dev/null
+++ b/extensions/libxt_CONNSECMARK.man
@@ -0,0 +1,18 @@
+This module copies security markings from packets to connections
+(if unlabeled), and from connections back to packets (also only
+if unlabeled).  Typically used in conjunction with SECMARK, it is
+valid in the
+.B security
+table (for backwards compatibility with older kernels, it is also
+valid in the
+.B mangle
+table).
+.TP
+\fB\-\-save\fP
+If the packet has a security marking, copy it to the connection
+if the connection is not marked.
+.TP
+\fB\-\-restore\fP
+If the packet does not have a security marking, and the connection
+does, copy the security marking from the connection to the packet.
+
diff --git a/extensions/libxt_CONNSECMARK.t b/extensions/libxt_CONNSECMARK.t
new file mode 100644
index 0000000..2751b25
--- /dev/null
+++ b/extensions/libxt_CONNSECMARK.t
@@ -0,0 +1,5 @@
+:PREROUTING,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j CONNSECMARK --restore;=;OK
+-j CONNSECMARK --save;=;OK
+-j CONNSECMARK;;FAIL
diff --git a/extensions/libxt_CT.c b/extensions/libxt_CT.c
new file mode 100644
index 0000000..fbbbe26
--- /dev/null
+++ b/extensions/libxt_CT.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2010-2013 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter/nf_conntrack_common.h>
+#include <linux/netfilter/xt_CT.h>
+
+static void ct_help(void)
+{
+	printf(
+"CT target options:\n"
+" --notrack			Don't track connection\n"
+" --helper name			Use conntrack helper 'name' for connection\n"
+" --ctevents event[,event...]	Generate specified conntrack events for connection\n"
+" --expevents event[,event...]	Generate specified expectation events for connection\n"
+" --zone {ID|mark}		Assign/Lookup connection in zone ID/packet nfmark\n"
+" --zone-orig {ID|mark}		Same as 'zone' option, but only applies to ORIGINAL direction\n"
+" --zone-reply {ID|mark} 	Same as 'zone' option, but only applies to REPLY direction\n"
+	);
+}
+
+static void ct_help_v1(void)
+{
+	printf(
+"CT target options:\n"
+" --notrack			Don't track connection\n"
+" --helper name			Use conntrack helper 'name' for connection\n"
+" --timeout name 		Use timeout policy 'name' for connection\n"
+" --ctevents event[,event...]	Generate specified conntrack events for connection\n"
+" --expevents event[,event...]	Generate specified expectation events for connection\n"
+" --zone {ID|mark}		Assign/Lookup connection in zone ID/packet nfmark\n"
+" --zone-orig {ID|mark}		Same as 'zone' option, but only applies to ORIGINAL direction\n"
+" --zone-reply {ID|mark} 	Same as 'zone' option, but only applies to REPLY direction\n"
+	);
+}
+
+enum {
+	O_NOTRACK = 0,
+	O_HELPER,
+	O_TIMEOUT,
+	O_CTEVENTS,
+	O_EXPEVENTS,
+	O_ZONE,
+	O_ZONE_ORIG,
+	O_ZONE_REPLY,
+};
+
+#define s struct xt_ct_target_info
+static const struct xt_option_entry ct_opts[] = {
+	{.name = "notrack", .id = O_NOTRACK, .type = XTTYPE_NONE},
+	{.name = "helper", .id = O_HELPER, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, helper)},
+	{.name = "ctevents", .id = O_CTEVENTS, .type = XTTYPE_STRING},
+	{.name = "expevents", .id = O_EXPEVENTS, .type = XTTYPE_STRING},
+	{.name = "zone-orig", .id = O_ZONE_ORIG, .type = XTTYPE_STRING},
+	{.name = "zone-reply", .id = O_ZONE_REPLY, .type = XTTYPE_STRING},
+	{.name = "zone", .id = O_ZONE, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_ct_target_info_v1
+static const struct xt_option_entry ct_opts_v1[] = {
+	{.name = "notrack", .id = O_NOTRACK, .type = XTTYPE_NONE},
+	{.name = "helper", .id = O_HELPER, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, helper)},
+	{.name = "timeout", .id = O_TIMEOUT, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, timeout)},
+	{.name = "ctevents", .id = O_CTEVENTS, .type = XTTYPE_STRING},
+	{.name = "expevents", .id = O_EXPEVENTS, .type = XTTYPE_STRING},
+	{.name = "zone-orig", .id = O_ZONE_ORIG, .type = XTTYPE_STRING},
+	{.name = "zone-reply", .id = O_ZONE_REPLY, .type = XTTYPE_STRING},
+	{.name = "zone", .id = O_ZONE, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+struct event_tbl {
+	const char	*name;
+	unsigned int	event;
+};
+
+static const struct event_tbl ct_event_tbl[] = {
+	{ "new",		IPCT_NEW },
+	{ "related",		IPCT_RELATED },
+	{ "destroy",		IPCT_DESTROY },
+	{ "reply",		IPCT_REPLY },
+	{ "assured",		IPCT_ASSURED },
+	{ "protoinfo",		IPCT_PROTOINFO },
+	{ "helper",		IPCT_HELPER },
+	{ "mark",		IPCT_MARK },
+	{ "natseqinfo",		IPCT_NATSEQADJ },
+	{ "secmark",		IPCT_SECMARK },
+};
+
+static const struct event_tbl exp_event_tbl[] = {
+	{ "new",		IPEXP_NEW },
+};
+
+static void ct_parse_zone_id(const char *opt, unsigned int opt_id,
+			     uint16_t *zone_id, uint16_t *flags)
+{
+	if (opt_id == O_ZONE_ORIG)
+		*flags |= XT_CT_ZONE_DIR_ORIG;
+	if (opt_id == O_ZONE_REPLY)
+		*flags |= XT_CT_ZONE_DIR_REPL;
+
+	*zone_id = 0;
+
+	if (strcasecmp(opt, "mark") == 0) {
+		*flags |= XT_CT_ZONE_MARK;
+	} else {
+		uintmax_t val;
+
+		if (!xtables_strtoul(opt, NULL, &val, 0, UINT16_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Cannot parse %s as a zone ID\n", opt);
+
+		*zone_id = (uint16_t)val;
+	}
+}
+
+static void ct_print_zone_id(const char *pfx, uint16_t zone_id, uint16_t flags)
+{
+	printf(" %s", pfx);
+
+	if ((flags & (XT_CT_ZONE_DIR_ORIG |
+		      XT_CT_ZONE_DIR_REPL)) == XT_CT_ZONE_DIR_ORIG)
+		printf("-orig");
+	if ((flags & (XT_CT_ZONE_DIR_ORIG |
+		      XT_CT_ZONE_DIR_REPL)) == XT_CT_ZONE_DIR_REPL)
+		printf("-reply");
+	if (flags & XT_CT_ZONE_MARK)
+		printf(" mark");
+	else
+		printf(" %u", zone_id);
+}
+
+static uint32_t ct_parse_events(const struct event_tbl *tbl, unsigned int size,
+				const char *events)
+{
+	char str[strlen(events) + 1], *e = str, *t;
+	unsigned int mask = 0, i;
+
+	strcpy(str, events);
+	while ((t = strsep(&e, ","))) {
+		for (i = 0; i < size; i++) {
+			if (strcmp(t, tbl[i].name))
+				continue;
+			mask |= 1 << tbl[i].event;
+			break;
+		}
+
+		if (i == size)
+			xtables_error(PARAMETER_PROBLEM, "Unknown event type \"%s\"", t);
+	}
+
+	return mask;
+}
+
+static void ct_print_events(const char *pfx, const struct event_tbl *tbl,
+			    unsigned int size, uint32_t mask)
+{
+	const char *sep = "";
+	unsigned int i;
+
+	printf(" %s ", pfx);
+	for (i = 0; i < size; i++) {
+		if (mask & (1 << tbl[i].event)) {
+			printf("%s%s", sep, tbl[i].name);
+			sep = ",";
+		}
+	}
+}
+
+static void ct_parse(struct xt_option_call *cb)
+{
+	struct xt_ct_target_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_NOTRACK:
+		info->flags |= XT_CT_NOTRACK;
+		break;
+	case O_ZONE_ORIG:
+	case O_ZONE_REPLY:
+	case O_ZONE:
+		ct_parse_zone_id(cb->arg, cb->entry->id, &info->zone,
+				 &info->flags);
+		break;
+	case O_CTEVENTS:
+		info->ct_events = ct_parse_events(ct_event_tbl, ARRAY_SIZE(ct_event_tbl), cb->arg);
+		break;
+	case O_EXPEVENTS:
+		info->exp_events = ct_parse_events(exp_event_tbl, ARRAY_SIZE(exp_event_tbl), cb->arg);
+		break;
+	}
+}
+
+static void ct_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_ct_target_info_v1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_NOTRACK:
+		info->flags |= XT_CT_NOTRACK;
+		break;
+	case O_ZONE_ORIG:
+	case O_ZONE_REPLY:
+	case O_ZONE:
+		ct_parse_zone_id(cb->arg, cb->entry->id, &info->zone,
+				 &info->flags);
+		break;
+	case O_CTEVENTS:
+		info->ct_events = ct_parse_events(ct_event_tbl,
+						  ARRAY_SIZE(ct_event_tbl),
+						  cb->arg);
+		break;
+	case O_EXPEVENTS:
+		info->exp_events = ct_parse_events(exp_event_tbl,
+						   ARRAY_SIZE(exp_event_tbl),
+						   cb->arg);
+		break;
+	}
+}
+
+static void ct_print(const void *ip, const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_ct_target_info *info =
+		(const struct xt_ct_target_info *)target->data;
+
+	printf(" CT");
+	if (info->flags & XT_CT_NOTRACK)
+		printf(" notrack");
+	if (info->helper[0])
+		printf(" helper %s", info->helper);
+	if (info->ct_events)
+		ct_print_events("ctevents", ct_event_tbl,
+				ARRAY_SIZE(ct_event_tbl), info->ct_events);
+	if (info->exp_events)
+		ct_print_events("expevents", exp_event_tbl,
+				ARRAY_SIZE(exp_event_tbl), info->exp_events);
+	if (info->flags & XT_CT_ZONE_MARK || info->zone)
+		ct_print_zone_id("zone", info->zone, info->flags);
+}
+
+static void
+ct_print_v1(const void *ip, const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_ct_target_info_v1 *info =
+		(const struct xt_ct_target_info_v1 *)target->data;
+
+	if (info->flags & XT_CT_NOTRACK_ALIAS) {
+		printf (" NOTRACK");
+		return;
+	}
+	printf(" CT");
+	if (info->flags & XT_CT_NOTRACK)
+		printf(" notrack");
+	if (info->helper[0])
+		printf(" helper %s", info->helper);
+	if (info->timeout[0])
+		printf(" timeout %s", info->timeout);
+	if (info->ct_events)
+		ct_print_events("ctevents", ct_event_tbl,
+				ARRAY_SIZE(ct_event_tbl), info->ct_events);
+	if (info->exp_events)
+		ct_print_events("expevents", exp_event_tbl,
+				ARRAY_SIZE(exp_event_tbl), info->exp_events);
+	if (info->flags & XT_CT_ZONE_MARK || info->zone)
+		ct_print_zone_id("zone", info->zone, info->flags);
+}
+
+static void ct_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_ct_target_info *info =
+		(const struct xt_ct_target_info *)target->data;
+
+	if (info->flags & XT_CT_NOTRACK_ALIAS)
+		return;
+	if (info->flags & XT_CT_NOTRACK)
+		printf(" --notrack");
+	if (info->helper[0])
+		printf(" --helper %s", info->helper);
+	if (info->ct_events)
+		ct_print_events("--ctevents", ct_event_tbl,
+				ARRAY_SIZE(ct_event_tbl), info->ct_events);
+	if (info->exp_events)
+		ct_print_events("--expevents", exp_event_tbl,
+				ARRAY_SIZE(exp_event_tbl), info->exp_events);
+	if (info->flags & XT_CT_ZONE_MARK || info->zone)
+		ct_print_zone_id("--zone", info->zone, info->flags);
+}
+
+static void ct_save_v1(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_ct_target_info_v1 *info =
+		(const struct xt_ct_target_info_v1 *)target->data;
+
+	if (info->flags & XT_CT_NOTRACK_ALIAS)
+		return;
+	if (info->flags & XT_CT_NOTRACK)
+		printf(" --notrack");
+	if (info->helper[0])
+		printf(" --helper %s", info->helper);
+	if (info->timeout[0])
+		printf(" --timeout %s", info->timeout);
+	if (info->ct_events)
+		ct_print_events("--ctevents", ct_event_tbl,
+				ARRAY_SIZE(ct_event_tbl), info->ct_events);
+	if (info->exp_events)
+		ct_print_events("--expevents", exp_event_tbl,
+				ARRAY_SIZE(exp_event_tbl), info->exp_events);
+	if (info->flags & XT_CT_ZONE_MARK || info->zone)
+		ct_print_zone_id("--zone", info->zone, info->flags);
+}
+
+static const char *
+ct_print_name_alias(const struct xt_entry_target *target)
+{
+	struct xt_ct_target_info *info = (void *)target->data;
+
+	return info->flags & XT_CT_NOTRACK_ALIAS ? "NOTRACK" : "CT";
+}
+
+static void notrack_ct0_tg_init(struct xt_entry_target *target)
+{
+	struct xt_ct_target_info *info = (void *)target->data;
+
+	info->flags = XT_CT_NOTRACK;
+}
+
+static void notrack_ct1_tg_init(struct xt_entry_target *target)
+{
+	struct xt_ct_target_info_v1 *info = (void *)target->data;
+
+	info->flags = XT_CT_NOTRACK;
+}
+
+static void notrack_ct2_tg_init(struct xt_entry_target *target)
+{
+	struct xt_ct_target_info_v1 *info = (void *)target->data;
+
+	info->flags = XT_CT_NOTRACK | XT_CT_NOTRACK_ALIAS;
+}
+
+static int xlate_ct1_tg(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	struct xt_ct_target_info_v1 *info =
+		(struct xt_ct_target_info_v1 *)params->target->data;
+
+	if (info->flags & XT_CT_NOTRACK)
+		xt_xlate_add(xl, "notrack");
+	else
+		return 0;
+
+	return 1;
+}
+
+static struct xtables_target ct_target_reg[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "CT",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_ct_target_info)),
+		.userspacesize	= offsetof(struct xt_ct_target_info, ct),
+		.help		= ct_help,
+		.print		= ct_print,
+		.save		= ct_save,
+		.x6_parse	= ct_parse,
+		.x6_options	= ct_opts,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "CT",
+		.revision	= 1,
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_ct_target_info_v1)),
+		.userspacesize	= offsetof(struct xt_ct_target_info_v1, ct),
+		.help		= ct_help_v1,
+		.print		= ct_print_v1,
+		.save		= ct_save_v1,
+		.x6_parse	= ct_parse_v1,
+		.x6_options	= ct_opts_v1,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "CT",
+		.revision	= 2,
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_ct_target_info_v1)),
+		.userspacesize	= offsetof(struct xt_ct_target_info_v1, ct),
+		.help		= ct_help_v1,
+		.print		= ct_print_v1,
+		.save		= ct_save_v1,
+		.alias		= ct_print_name_alias,
+		.x6_parse	= ct_parse_v1,
+		.x6_options	= ct_opts_v1,
+		.xlate		= xlate_ct1_tg,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "NOTRACK",
+		.real_name     = "CT",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_ct_target_info)),
+		.userspacesize = offsetof(struct xt_ct_target_info, ct),
+		.init          = notrack_ct0_tg_init,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "NOTRACK",
+		.real_name     = "CT",
+		.revision      = 1,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_ct_target_info_v1)),
+		.userspacesize = offsetof(struct xt_ct_target_info_v1, ct),
+		.init          = notrack_ct1_tg_init,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "NOTRACK",
+		.real_name     = "CT",
+		.revision      = 2,
+		.ext_flags     = XTABLES_EXT_ALIAS,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_ct_target_info_v1)),
+		.userspacesize = offsetof(struct xt_ct_target_info_v1, ct),
+		.init          = notrack_ct2_tg_init,
+		.xlate	       = xlate_ct1_tg,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "NOTRACK",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(ct_target_reg, ARRAY_SIZE(ct_target_reg));
+}
diff --git a/extensions/libxt_CT.man b/extensions/libxt_CT.man
new file mode 100644
index 0000000..fc692f9
--- /dev/null
+++ b/extensions/libxt_CT.man
@@ -0,0 +1,42 @@
+The CT target sets parameters for a packet or its associated
+connection. The target attaches a "template" connection tracking entry to
+the packet, which is then used by the conntrack core when initializing
+a new ct entry. This target is thus only valid in the "raw" table.
+.TP
+\fB\-\-notrack\fP
+Disables connection tracking for this packet.
+.TP
+\fB\-\-helper\fP \fIname\fP
+Use the helper identified by \fIname\fP for the connection. This is more
+flexible than loading the conntrack helper modules with preset ports.
+.TP
+\fB\-\-ctevents\fP \fIevent\fP[\fB,\fP...]
+Only generate the specified conntrack events for this connection. Possible
+event types are: \fBnew\fP, \fBrelated\fP, \fBdestroy\fP, \fBreply\fP,
+\fBassured\fP, \fBprotoinfo\fP, \fBhelper\fP, \fBmark\fP (this refers to
+the ctmark, not nfmark), \fBnatseqinfo\fP, \fBsecmark\fP (ctsecmark).
+.TP
+\fB\-\-expevents\fP \fIevent\fP[\fB,\fP...]
+Only generate the specified expectation events for this connection.
+Possible event types are: \fBnew\fP.
+.TP
+\fB\-\-zone-orig\fP {\fIid\fP|\fBmark\fP}
+For traffic coming from ORIGINAL direction, assign this packet to zone
+\fIid\fP and only have lookups done in that zone. If \fBmark\fP is used
+instead of \fIid\fP, the zone is derived from the packet nfmark.
+.TP
+\fB\-\-zone-reply\fP {\fIid\fP|\fBmark\fP}
+For traffic coming from REPLY direction, assign this packet to zone
+\fIid\fP and only have lookups done in that zone. If \fBmark\fP is used
+instead of \fIid\fP, the zone is derived from the packet nfmark.
+.TP
+\fB\-\-zone\fP {\fIid\fP|\fBmark\fP}
+Assign this packet to zone \fIid\fP and only have lookups done in that zone.
+If \fBmark\fP is used instead of \fIid\fP, the zone is derived from the
+packet nfmark. By default, packets have zone 0. This option applies to both
+directions.
+.TP
+\fB\-\-timeout\fP \fIname\fP
+Use the timeout policy identified by \fIname\fP for the connection. This is
+provides more flexible timeout policy definition than global timeout values
+available at /proc/sys/net/netfilter/nf_conntrack_*_timeout_*.
diff --git a/extensions/libxt_CT.t b/extensions/libxt_CT.t
new file mode 100644
index 0000000..3c28534
--- /dev/null
+++ b/extensions/libxt_CT.t
@@ -0,0 +1,20 @@
+:PREROUTING,OUTPUT
+*raw
+-j CT --notrack;=;OK
+-j CT --ctevents new,related,destroy,reply,assured,protoinfo,helper,mark;=;OK
+-j CT --expevents new;=;OK
+# ERROR: cannot find: iptables -I PREROUTING -t raw -j CT --zone 0
+# -j CT --zone 0;=;OK
+-j CT --zone 65535;=;OK
+-j CT --zone 65536;;FAIL
+-j CT --zone -1;;FAIL
+# ERROR: should fail: iptables -A PREROUTING -t raw -j CT
+# -j CT;;FAIL
+@nfct timeout add test inet tcp ESTABLISHED 100
+# cannot load: iptables -A PREROUTING -t raw -j CT --timeout test
+# -j CT --timeout test;=;OK
+@nfct timeout del test
+@nfct helper add rpc inet tcp
+# cannot load: iptables -A PREROUTING -t raw -j CT --helper rpc
+# -j CT --helper rpc;=;OK
+@nfct helper del rpc
diff --git a/extensions/libxt_DNAT.man b/extensions/libxt_DNAT.man
new file mode 100644
index 0000000..225274f
--- /dev/null
+++ b/extensions/libxt_DNAT.man
@@ -0,0 +1,38 @@
+This target is only valid in the
+.B nat
+table, in the
+.B PREROUTING
+and
+.B OUTPUT
+chains, and user-defined chains which are only called from those
+chains.  It specifies that the destination address of the packet
+should be modified (and all future packets in this connection will
+also be mangled), and rules should cease being examined.  It takes the
+following options:
+.TP
+\fB\-\-to\-destination\fP [\fIipaddr\fP[\fB\-\fP\fIipaddr\fP]][\fB:\fP\fIport\fP[\fB\-\fP\fIport\fP]]
+which can specify a single new destination IP address, an inclusive
+range of IP addresses. Optionally a port range,
+if the rule also specifies one of the following protocols:
+\fBtcp\fP, \fBudp\fP, \fBdccp\fP or \fBsctp\fP.
+If no port range is specified, then the destination port will never be
+modified. If no IP address is specified then only the destination port
+will be modified.
+In Kernels up to 2.6.10 you can add several \-\-to\-destination options. For
+those kernels, if you specify more than one destination address, either via an
+address range or multiple \-\-to\-destination options, a simple round-robin (one
+after another in cycle) load balancing takes place between these addresses.
+Later Kernels (>= 2.6.11-rc1) don't have the ability to NAT to multiple ranges
+anymore.
+.TP
+\fB\-\-random\fP
+If option
+\fB\-\-random\fP
+is used then port mapping will be randomized (kernel >= 2.6.22).
+.TP
+\fB\-\-persistent\fP
+Gives a client the same source-/destination-address for each connection.
+This supersedes the SAME target. Support for persistent mappings is available
+from 2.6.29-rc2.
+.TP
+IPv6 support available since Linux kernels >= 3.7.
diff --git a/extensions/libxt_DSCP.c b/extensions/libxt_DSCP.c
new file mode 100644
index 0000000..cae0d83
--- /dev/null
+++ b/extensions/libxt_DSCP.c
@@ -0,0 +1,150 @@
+/* Shared library add-on to iptables for DSCP
+ *
+ * (C) 2000- 2002 by Matthew G. Marsh <mgm@paktronix.com>,
+ * 		     Harald Welte <laforge@gnumonks.org>
+ *
+ * This program is distributed under the terms of GNU GPL v2, 1991
+ *
+ * libipt_DSCP.c borrowed heavily from libipt_TOS.c
+ *
+ * --set-class added by Iain Barnes
+ */
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_DSCP.h>
+
+/* This is evil, but it's my code - HW*/
+#include "dscp_helper.c"
+
+enum {
+	O_SET_DSCP = 0,
+	O_SET_DSCP_CLASS,
+	F_SET_DSCP       = 1 << O_SET_DSCP,
+	F_SET_DSCP_CLASS = 1 << O_SET_DSCP_CLASS,
+};
+
+static void DSCP_help(void)
+{
+	printf(
+"DSCP target options\n"
+"  --set-dscp value		Set DSCP field in packet header to value\n"
+"  		                This value can be in decimal (ex: 32)\n"
+"               		or in hex (ex: 0x20)\n"
+"  --set-dscp-class class	Set the DSCP field in packet header to the\n"
+"				value represented by the DiffServ class value.\n"
+"				This class may be EF,BE or any of the CSxx\n"
+"				or AFxx classes.\n"
+"\n"
+"				These two options are mutually exclusive !\n"
+);
+}
+
+static const struct xt_option_entry DSCP_opts[] = {
+	{.name = "set-dscp", .id = O_SET_DSCP, .excl = F_SET_DSCP_CLASS,
+	 .type = XTTYPE_UINT8, .min = 0, .max = XT_DSCP_MAX,
+	 .flags = XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_DSCP_info, dscp)},
+	{.name = "set-dscp-class", .id = O_SET_DSCP_CLASS, .excl = F_SET_DSCP,
+	 .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+
+static void DSCP_parse(struct xt_option_call *cb)
+{
+	struct xt_DSCP_info *dinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SET_DSCP_CLASS:
+		dinfo->dscp = class_to_dscp(cb->arg);
+		break;
+	}
+}
+
+static void DSCP_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM,
+		           "DSCP target: Parameter --set-dscp is required");
+}
+
+static void
+print_dscp(uint8_t dscp, int numeric)
+{
+	printf(" 0x%02x", dscp);
+}
+
+static void DSCP_print(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+	const struct xt_DSCP_info *dinfo =
+		(const struct xt_DSCP_info *)target->data;
+	printf(" DSCP set");
+	print_dscp(dinfo->dscp, numeric);
+}
+
+static void DSCP_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_DSCP_info *dinfo =
+		(const struct xt_DSCP_info *)target->data;
+
+	printf(" --set-dscp 0x%02x", dinfo->dscp);
+}
+
+
+static int DSCP_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct xt_DSCP_info *dinfo =
+		(struct xt_DSCP_info *)params->target->data;
+
+	xt_xlate_add(xl, "ip dscp set 0x%02x", dinfo->dscp);
+	return 1;
+}
+
+static int DSCP_xlate6(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	const struct xt_DSCP_info *dinfo =
+		(struct xt_DSCP_info *)params->target->data;
+
+	xt_xlate_add(xl, "ip6 dscp set 0x%02x", dinfo->dscp);
+	return 1;
+}
+
+static struct xtables_target dscp_target[] = {
+	{
+		.family		= NFPROTO_IPV4,
+		.name		= "DSCP",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_DSCP_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_DSCP_info)),
+		.help		= DSCP_help,
+		.print		= DSCP_print,
+		.save		= DSCP_save,
+		.x6_parse	= DSCP_parse,
+		.x6_fcheck	= DSCP_check,
+		.x6_options	= DSCP_opts,
+		.xlate		= DSCP_xlate,
+	},
+	{
+		.family		= NFPROTO_IPV6,
+		.name		= "DSCP",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_DSCP_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_DSCP_info)),
+		.help		= DSCP_help,
+		.print		= DSCP_print,
+		.save		= DSCP_save,
+		.x6_parse	= DSCP_parse,
+		.x6_fcheck	= DSCP_check,
+		.x6_options	= DSCP_opts,
+		.xlate		= DSCP_xlate6,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(dscp_target, ARRAY_SIZE(dscp_target));
+}
diff --git a/extensions/libxt_DSCP.man b/extensions/libxt_DSCP.man
new file mode 100644
index 0000000..5385c97
--- /dev/null
+++ b/extensions/libxt_DSCP.man
@@ -0,0 +1,9 @@
+This target alters the value of the DSCP bits within the TOS
+header of the IPv4 packet.  As this manipulates a packet, it can only
+be used in the mangle table.
+.TP
+\fB\-\-set\-dscp\fP \fIvalue\fP
+Set the DSCP field to a numerical value (can be decimal or hex)
+.TP
+\fB\-\-set\-dscp\-class\fP \fIclass\fP
+Set the DSCP field to a DiffServ class.
diff --git a/extensions/libxt_DSCP.t b/extensions/libxt_DSCP.t
new file mode 100644
index 0000000..fcc5598
--- /dev/null
+++ b/extensions/libxt_DSCP.t
@@ -0,0 +1,11 @@
+:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j DSCP --set-dscp 0;=;OK
+-j DSCP --set-dscp 0x3f;=;OK
+-j DSCP --set-dscp -1;;FAIL
+-j DSCP --set-dscp 0x40;;FAIL
+-j DSCP --set-dscp 0x3f --set-dscp-class CS0;;FAIL
+-j DSCP --set-dscp-class CS0;-j DSCP --set-dscp 0x00;OK
+-j DSCP --set-dscp-class BE;-j DSCP --set-dscp 0x00;OK
+-j DSCP --set-dscp-class EF;-j DSCP --set-dscp 0x2e;OK
+-j DSCP;;FAIL
diff --git a/extensions/libxt_DSCP.txlate b/extensions/libxt_DSCP.txlate
new file mode 100644
index 0000000..442742e
--- /dev/null
+++ b/extensions/libxt_DSCP.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A OUTPUT -j DSCP --set-dscp 1
+nft add rule ip filter OUTPUT counter ip dscp set 0x01
+
+ip6tables-translate -A OUTPUT -j DSCP --set-dscp 6
+nft add rule ip6 filter OUTPUT counter ip6 dscp set 0x06
diff --git a/extensions/libxt_HMARK.c b/extensions/libxt_HMARK.c
new file mode 100644
index 0000000..94aebe9
--- /dev/null
+++ b/extensions/libxt_HMARK.c
@@ -0,0 +1,450 @@
+/*
+ * (C) 2012 by Hans Schillstrom <hans.schillstrom@ericsson.com>
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Description: shared library add-on to iptables to add HMARK target support
+ *
+ * Initial development by Hans Schillstrom. Pablo's improvements to this piece
+ * of software has been sponsored by Sophos Astaro <http://www.sophos.com>.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "xtables.h"
+#include <linux/netfilter/xt_HMARK.h>
+
+static void HMARK_help(void)
+{
+	printf(
+"HMARK target options, i.e. modify hash calculation by:\n"
+"  --hmark-tuple [src|dst|sport|dport|spi|proto|ct][,...]\n"
+"  --hmark-mod value		    nfmark modulus value\n"
+"  --hmark-offset value		    Last action add value to nfmark\n\n"
+"  --hmark-rnd			    Random see for hashing\n"
+" Alternatively, fine tuning of what will be included in hash calculation\n"
+"  --hmark-src-prefix length	    Source address mask CIDR prefix\n"
+"  --hmark-dst-prefix length	    Dest address mask CIDR prefix\n"
+"  --hmark-sport-mask value	    Mask src port with value\n"
+"  --hmark-dport-mask value	    Mask dst port with value\n"
+"  --hmark-spi-mask value	    For esp and ah AND spi with value\n"
+"  --hmark-sport value		    OR src port with value\n"
+"  --hmark-dport value		    OR dst port with value\n"
+"  --hmark-spi value		    For esp and ah OR spi with value\n"
+"  --hmark-proto-mask value	    Mask Protocol with value\n");
+}
+
+#define hi struct xt_hmark_info
+
+enum {
+	O_HMARK_SADDR_MASK,
+	O_HMARK_DADDR_MASK,
+	O_HMARK_SPI,
+	O_HMARK_SPI_MASK,
+	O_HMARK_SPORT,
+	O_HMARK_DPORT,
+	O_HMARK_SPORT_MASK,
+	O_HMARK_DPORT_MASK,
+	O_HMARK_PROTO_MASK,
+	O_HMARK_RND,
+	O_HMARK_MODULUS,
+	O_HMARK_OFFSET,
+	O_HMARK_CT,
+	O_HMARK_TYPE,
+};
+
+#define HMARK_OPT_PKT_MASK			\
+	((1 << O_HMARK_SADDR_MASK)		| \
+	 (1 << O_HMARK_DADDR_MASK)		| \
+	 (1 << O_HMARK_SPI_MASK)		| \
+	 (1 << O_HMARK_SPORT_MASK)		| \
+	 (1 << O_HMARK_DPORT_MASK)		| \
+	 (1 << O_HMARK_PROTO_MASK)		| \
+	 (1 << O_HMARK_SPI_MASK)		| \
+	 (1 << O_HMARK_SPORT)			| \
+	 (1 << O_HMARK_DPORT)			| \
+	 (1 << O_HMARK_SPI))
+
+static const struct xt_option_entry HMARK_opts[] = {
+	{ .name  = "hmark-tuple",
+	  .type  = XTTYPE_STRING,
+	  .id	 = O_HMARK_TYPE,
+	},
+	{ .name  = "hmark-src-prefix",
+	  .type  = XTTYPE_PLENMASK,
+	  .id	 = O_HMARK_SADDR_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, src_mask)
+	},
+	{ .name  = "hmark-dst-prefix",
+	  .type  = XTTYPE_PLENMASK,
+	  .id	 = O_HMARK_DADDR_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, dst_mask)
+	},
+	{ .name  = "hmark-sport-mask",
+	  .type  = XTTYPE_UINT16,
+	  .id	 = O_HMARK_SPORT_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_mask.p16.src)
+	},
+	{ .name  = "hmark-dport-mask",
+	  .type  = XTTYPE_UINT16,
+	  .id	 = O_HMARK_DPORT_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_mask.p16.dst)
+	},
+	{ .name  = "hmark-spi-mask",
+	  .type  = XTTYPE_UINT32,
+	  .id	 = O_HMARK_SPI_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_mask.v32)
+	},
+	{ .name  = "hmark-sport",
+	  .type  = XTTYPE_UINT16,
+	  .id	 = O_HMARK_SPORT,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_set.p16.src)
+	},
+	{ .name  = "hmark-dport",
+	  .type  = XTTYPE_UINT16,
+	  .id	 = O_HMARK_DPORT,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_set.p16.dst)
+	},
+	{ .name  = "hmark-spi",
+	  .type  = XTTYPE_UINT32,
+	  .id	 = O_HMARK_SPI,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_set.v32)
+	},
+	{ .name  = "hmark-proto-mask",
+	  .type  = XTTYPE_UINT16,
+	  .id	 = O_HMARK_PROTO_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, proto_mask)
+	},
+	{ .name  = "hmark-rnd",
+	  .type  = XTTYPE_UINT32,
+	  .id	 = O_HMARK_RND,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, hashrnd)
+	},
+	{ .name = "hmark-mod",
+	  .type = XTTYPE_UINT32,
+	  .id = O_HMARK_MODULUS,
+	  .min = 1,
+	  .flags = XTOPT_PUT | XTOPT_MAND, XTOPT_POINTER(hi, hmodulus)
+	},
+	{ .name  = "hmark-offset",
+	  .type  = XTTYPE_UINT32,
+	  .id	 = O_HMARK_OFFSET,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, hoffset)
+	},
+	XTOPT_TABLEEND,
+};
+
+static int
+hmark_parse(const char *type, size_t len, struct xt_hmark_info *info,
+	    unsigned int *xflags)
+{
+	if (strncasecmp(type, "ct", len) == 0) {
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_CT);
+		*xflags |= (1 << O_HMARK_CT);
+	} else if (strncasecmp(type, "src", len) == 0) {
+		memset(&info->src_mask, 0xff, sizeof(info->src_mask));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SADDR_MASK);
+		*xflags |= (1 << O_HMARK_SADDR_MASK);
+	} else if (strncasecmp(type, "dst", len) == 0) {
+		memset(&info->dst_mask, 0xff, sizeof(info->dst_mask));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_DADDR_MASK);
+		*xflags |= (1 << O_HMARK_DADDR_MASK);
+	} else if (strncasecmp(type, "sport", len) == 0) {
+		memset(&info->port_mask.p16.src, 0xff,
+			sizeof(info->port_mask.p16.src));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPORT_MASK);
+		*xflags |= (1 << O_HMARK_SPORT_MASK);
+	} else if (strncasecmp(type, "dport", len) == 0) {
+		memset(&info->port_mask.p16.dst, 0xff,
+			sizeof(info->port_mask.p16.dst));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_DPORT_MASK);
+		*xflags |= (1 << O_HMARK_DPORT_MASK);
+	} else if (strncasecmp(type, "proto", len) == 0) {
+		memset(&info->proto_mask, 0xff, sizeof(info->proto_mask));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_PROTO_MASK);
+		*xflags |= (1 << O_HMARK_PROTO_MASK);
+	} else if (strncasecmp(type, "spi", len) == 0) {
+		memset(&info->port_mask.v32, 0xff, sizeof(info->port_mask.v32));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPI_MASK);
+		*xflags |= (1 << O_HMARK_SPI_MASK);
+	} else
+		return 0;
+
+	return 1;
+}
+
+static void
+hmark_parse_type(struct xt_option_call *cb)
+{
+	const char *arg = cb->arg;
+	struct xt_hmark_info *info = cb->data;
+	const char *comma;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg ||
+		    !hmark_parse(arg, comma-arg, info, &cb->xflags))
+			xtables_error(PARAMETER_PROBLEM, "Bad type \"%s\"", arg);
+		arg = comma+1;
+	}
+	if (!*arg)
+		xtables_error(PARAMETER_PROBLEM, "\"--hmark-tuple\" requires "
+						 "a list of types with no "
+						 "spaces, e.g. "
+						 "src,dst,sport,dport,proto");
+	if (strlen(arg) == 0 ||
+	    !hmark_parse(arg, strlen(arg), info, &cb->xflags))
+		xtables_error(PARAMETER_PROBLEM, "Bad type \"%s\"", arg);
+}
+
+static void HMARK_parse(struct xt_option_call *cb, int plen)
+{
+	struct xt_hmark_info *info = cb->data;
+
+	xtables_option_parse(cb);
+
+	switch (cb->entry->id) {
+	case O_HMARK_TYPE:
+		hmark_parse_type(cb);
+		break;
+	case O_HMARK_SADDR_MASK:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SADDR_MASK);
+		break;
+	case O_HMARK_DADDR_MASK:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_DADDR_MASK);
+		break;
+	case O_HMARK_SPI:
+		info->port_set.v32 = htonl(cb->val.u32);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPI);
+		break;
+	case O_HMARK_SPORT:
+		info->port_set.p16.src = htons(cb->val.u16);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPORT);
+		break;
+	case O_HMARK_DPORT:
+		info->port_set.p16.dst = htons(cb->val.u16);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_DPORT);
+		break;
+	case O_HMARK_SPORT_MASK:
+		info->port_mask.p16.src = htons(cb->val.u16);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPORT_MASK);
+		break;
+	case O_HMARK_DPORT_MASK:
+		info->port_mask.p16.dst = htons(cb->val.u16);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_DPORT_MASK);
+		break;
+	case O_HMARK_SPI_MASK:
+		info->port_mask.v32 = htonl(cb->val.u32);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPI_MASK);
+		break;
+	case O_HMARK_PROTO_MASK:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_PROTO_MASK);
+		break;
+	case O_HMARK_RND:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_RND);
+		break;
+	case O_HMARK_MODULUS:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_MODULUS);
+		break;
+	case O_HMARK_OFFSET:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_OFFSET);
+		break;
+	case O_HMARK_CT:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_CT);
+		break;
+	}
+	cb->xflags |= (1 << cb->entry->id);
+}
+
+static void HMARK_ip4_parse(struct xt_option_call *cb)
+{
+	HMARK_parse(cb, 32);
+}
+static void HMARK_ip6_parse(struct xt_option_call *cb)
+{
+	HMARK_parse(cb, 128);
+}
+
+static void HMARK_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & (1 << O_HMARK_MODULUS)))
+		xtables_error(PARAMETER_PROBLEM, "--hmark-mod is mandatory");
+	if (!(cb->xflags & (1 << O_HMARK_RND)))
+		xtables_error(PARAMETER_PROBLEM, "--hmark-rnd is mandatory");
+	if (cb->xflags & (1 << O_HMARK_SPI_MASK) &&
+	    (cb->xflags & ((1 << O_HMARK_SPORT_MASK) |
+			   (1 << O_HMARK_DPORT_MASK))))
+		xtables_error(PARAMETER_PROBLEM, "you cannot use "
+				"--hmark-spi-mask and --hmark-?port-mask,"
+				"at the same time");
+	if (!((cb->xflags & HMARK_OPT_PKT_MASK) ||
+	       cb->xflags & (1 << O_HMARK_CT)))
+		xtables_error(PARAMETER_PROBLEM, "you have to specify "
+				"--hmark-tuple at least");
+}
+
+static void HMARK_print(const struct xt_hmark_info *info)
+{
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPORT_MASK))
+		printf("sport-mask 0x%x ", htons(info->port_mask.p16.src));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DPORT_MASK))
+		printf("dport-mask 0x%x ", htons(info->port_mask.p16.dst));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI_MASK))
+		printf("spi-mask 0x%x ", htonl(info->port_mask.v32));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPORT))
+		printf("sport 0x%x ", htons(info->port_set.p16.src));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DPORT))
+		printf("dport 0x%x ", htons(info->port_set.p16.dst));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI))
+		printf("spi 0x%x ", htonl(info->port_set.v32));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_PROTO_MASK))
+		printf("proto-mask 0x%x ", info->proto_mask);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_RND))
+		printf("rnd 0x%x ", info->hashrnd);
+}
+
+static void HMARK_ip6_print(const void *ip,
+			    const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_hmark_info *info =
+			(const struct xt_hmark_info *)target->data;
+
+	printf(" HMARK ");
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_MODULUS))
+		printf("mod %u ", info->hmodulus);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_OFFSET))
+		printf("+ 0x%x ", info->hoffset);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_CT))
+		printf("ct, ");
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SADDR_MASK))
+		printf("src-prefix %s ",
+		       xtables_ip6mask_to_numeric(&info->src_mask.in6) + 1);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DADDR_MASK))
+		printf("dst-prefix %s ",
+		       xtables_ip6mask_to_numeric(&info->dst_mask.in6) + 1);
+	HMARK_print(info);
+}
+static void HMARK_ip4_print(const void *ip,
+			    const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_hmark_info *info =
+		(const struct xt_hmark_info *)target->data;
+
+	printf(" HMARK ");
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_MODULUS))
+		printf("mod %u ", info->hmodulus);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_OFFSET))
+		printf("+ 0x%x ", info->hoffset);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_CT))
+		printf("ct, ");
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SADDR_MASK))
+		printf("src-prefix %u ",
+		       xtables_ipmask_to_cidr(&info->src_mask.in));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DADDR_MASK))
+		printf("dst-prefix %u ",
+		       xtables_ipmask_to_cidr(&info->dst_mask.in));
+	HMARK_print(info);
+}
+
+static void HMARK_save(const struct xt_hmark_info *info)
+{
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPORT_MASK))
+		printf(" --hmark-sport-mask 0x%04x",
+		       htons(info->port_mask.p16.src));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DPORT_MASK))
+		printf(" --hmark-dport-mask 0x%04x",
+		       htons(info->port_mask.p16.dst));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI_MASK))
+		printf(" --hmark-spi-mask 0x%08x",
+		       htonl(info->port_mask.v32));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPORT))
+		printf(" --hmark-sport 0x%04x",
+		       htons(info->port_set.p16.src));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DPORT))
+		printf(" --hmark-dport 0x%04x",
+		       htons(info->port_set.p16.dst));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI))
+		printf(" --hmark-spi 0x%08x", htonl(info->port_set.v32));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_PROTO_MASK))
+		printf(" --hmark-proto-mask 0x%02x", info->proto_mask);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_RND))
+		printf(" --hmark-rnd 0x%08x", info->hashrnd);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_MODULUS))
+		printf(" --hmark-mod %u", info->hmodulus);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_OFFSET))
+		printf(" --hmark-offset %u", info->hoffset);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_CT))
+		printf(" --hmark-tuple ct");
+}
+
+static void HMARK_ip6_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_hmark_info *info =
+		(const struct xt_hmark_info *)target->data;
+	int ret;
+
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SADDR_MASK)) {
+		ret = xtables_ip6mask_to_cidr(&info->src_mask.in6);
+		printf(" --hmark-src-prefix %d", ret);
+	}
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DADDR_MASK)) {
+		ret = xtables_ip6mask_to_cidr(&info->dst_mask.in6);
+		printf(" --hmark-dst-prefix %d", ret);
+	}
+	HMARK_save(info);
+}
+
+static void HMARK_ip4_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_hmark_info *info =
+		(const struct xt_hmark_info *)target->data;
+	int ret;
+
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SADDR_MASK)) {
+		ret = xtables_ipmask_to_cidr(&info->src_mask.in);
+		printf(" --hmark-src-prefix %d", ret);
+	}
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DADDR_MASK)) {
+		ret = xtables_ipmask_to_cidr(&info->dst_mask.in);
+		printf(" --hmark-dst-prefix %d", ret);
+	}
+	HMARK_save(info);
+}
+
+static struct xtables_target mark_tg_reg[] = {
+	{
+		.family        = NFPROTO_IPV4,
+		.name	       = "HMARK",
+		.version       = XTABLES_VERSION,
+		.size	       = XT_ALIGN(sizeof(struct xt_hmark_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_hmark_info)),
+		.help	       = HMARK_help,
+		.print	       = HMARK_ip4_print,
+		.save	       = HMARK_ip4_save,
+		.x6_parse      = HMARK_ip4_parse,
+		.x6_fcheck     = HMARK_check,
+		.x6_options    = HMARK_opts,
+	},
+	{
+		.family        = NFPROTO_IPV6,
+		.name	       = "HMARK",
+		.version       = XTABLES_VERSION,
+		.size	       = XT_ALIGN(sizeof(struct xt_hmark_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_hmark_info)),
+		.help	       = HMARK_help,
+		.print	       = HMARK_ip6_print,
+		.save	       = HMARK_ip6_save,
+		.x6_parse      = HMARK_ip6_parse,
+		.x6_fcheck     = HMARK_check,
+		.x6_options    = HMARK_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(mark_tg_reg, ARRAY_SIZE(mark_tg_reg));
+}
diff --git a/extensions/libxt_HMARK.man b/extensions/libxt_HMARK.man
new file mode 100644
index 0000000..cd7ffd5
--- /dev/null
+++ b/extensions/libxt_HMARK.man
@@ -0,0 +1,60 @@
+Like MARK, i.e. set the fwmark, but the mark is calculated from hashing
+packet selector at choice. You have also to specify the mark range and,
+optionally, the offset to start from. ICMP error messages are inspected
+and used to calculate the hashing.
+.PP
+Existing options are:
+.TP
+\fB\-\-hmark\-tuple\fP tuple\fI\fP
+Possible tuple members are:
+.B src
+meaning source address (IPv4, IPv6 address),
+.B dst
+meaning destination address (IPv4, IPv6 address),
+.B sport
+meaning source port (TCP, UDP, UDPlite, SCTP, DCCP),
+.B dport
+meaning destination port (TCP, UDP, UDPlite, SCTP, DCCP),
+.B spi
+meaning Security Parameter Index (AH, ESP), and
+.B ct
+meaning the usage of the conntrack tuple instead of the packet selectors.
+.TP
+\fB\-\-hmark\-mod\fP \fIvalue (must be > 0)\fP
+Modulus for hash calculation (to limit the range of possible marks)
+.TP
+\fB\-\-hmark\-offset\fP \fIvalue\fP
+Offset to start marks from.
+.TP
+For advanced usage, instead of using \-\-hmark\-tuple, you can specify custom
+prefixes and masks:
+.TP
+\fB\-\-hmark\-src\-prefix\fP \fIcidr\fP
+The source address mask in CIDR notation.
+.TP
+\fB\-\-hmark\-dst\-prefix\fP \fIcidr\fP
+The destination address mask in CIDR notation.
+.TP
+\fB\-\-hmark\-sport\-mask\fP \fIvalue\fP
+A 16 bit source port mask in hexadecimal.
+.TP
+\fB\-\-hmark\-dport\-mask\fP \fIvalue\fP
+A 16 bit destination port mask in hexadecimal.
+.TP
+\fB\-\-hmark\-spi\-mask\fP \fIvalue\fP
+A 32 bit field with spi mask.
+.TP
+\fB\-\-hmark\-proto\-mask\fP \fIvalue\fP
+An 8 bit field with layer 4 protocol number.
+.TP
+\fB\-\-hmark\-rnd\fP \fIvalue\fP
+A 32 bit random custom value to feed hash calculation.
+.PP
+\fIExamples:\fP
+.PP
+iptables \-t mangle \-A PREROUTING \-m conntrack \-\-ctstate NEW
+ \-j HMARK \-\-hmark-tuple ct,src,dst,proto \-\-hmark-offset 10000
+\-\-hmark\-mod 10 \-\-hmark\-rnd 0xfeedcafe
+.PP
+iptables \-t mangle \-A PREROUTING \-j HMARK \-\-hmark\-offset 10000
+\-\-hmark-tuple src,dst,proto \-\-hmark-mod 10 \-\-hmark\-rnd 0xdeafbeef
diff --git a/extensions/libxt_HMARK.t b/extensions/libxt_HMARK.t
new file mode 100644
index 0000000..3bcf1da
--- /dev/null
+++ b/extensions/libxt_HMARK.t
@@ -0,0 +1,8 @@
+:INPUT,FORWARD,OUTPUT
+-j HMARK;;FAIL
+-j HMARK --hmark-src-prefix 32 --hmark-rnd 0x00000004 --hmark-mod 42;=;OK
+-j HMARK --hmark-src-prefix 32 --hmark-dst-prefix 32 --hmark-sport-mask 0xffff --hmark-dport-mask 0xffff --hmark-proto-mask 0xffff --hmark-rnd 0x00000004 --hmark-mod 42 --hmark-offset 1 --hmark-tuple ct;=;OK
+-j HMARK --hmark-src-prefix 32 --hmark-dst-prefix 32 --hmark-spi-mask 0x00000004 --hmark-proto-mask 0xffff --hmark-rnd 0x00000004 --hmark-mod 42 --hmark-offset 1 --hmark-tuple ct;=;OK
+-j HMARK --hmark-src-prefix 1 --hmark-dst-prefix 2 --hmark-sport-mask 0x0003 --hmark-dport-mask 0x0004 --hmark-proto-mask 0x05 --hmark-rnd 0x00000004 --hmark-mod 42 --hmark-offset 1 --hmark-tuple ct;=;OK
+# cannot mix in spi mask:
+-j HMARK --hmark-src-prefix 32 --hmark-dst-prefix 32 --hmark-sport-mask 0xffff --hmark-dport-mask 0xffff --hmark-proto-mask 0xffff --hmark-rnd 0x00000004 --hmark-mod 42 --hmark-offset 1 --hmark-tuple ct --hmark-spi-mask 4;;FAIL
diff --git a/extensions/libxt_IDLETIMER.c b/extensions/libxt_IDLETIMER.c
new file mode 100644
index 0000000..c414801
--- /dev/null
+++ b/extensions/libxt_IDLETIMER.c
@@ -0,0 +1,171 @@
+/*
+ * Shared library add-on for iptables to add IDLETIMER support.
+ *
+ * Copyright (C) 2010 Nokia Corporation. All rights reserved.
+ *
+ * Contact: Luciano Coelho <luciano.coelho@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_IDLETIMER.h>
+
+enum {
+	O_TIMEOUT = 0,
+	O_LABEL,
+	O_ALARM,
+	O_NETLINK,
+};
+
+#define s struct idletimer_tg_info
+static const struct xt_option_entry idletimer_tg_opts[] = {
+	{.name = "timeout", .id = O_TIMEOUT, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, timeout)},
+	{.name = "label", .id = O_LABEL, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, label)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct idletimer_tg_info_v1
+static const struct xt_option_entry idletimer_tg_opts_v1[] = {
+	{.name = "timeout", .id = O_TIMEOUT, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, timeout)},
+	{.name = "label", .id = O_LABEL, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, label)},
+	{.name = "alarm", .id = O_ALARM, .type = XTTYPE_NONE},
+	{.name = "send_nl_msg", .id = O_NETLINK, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void idletimer_tg_help(void)
+{
+	printf(
+"IDLETIMER target options:\n"
+" --timeout time	Timeout until the notification is sent (in seconds)\n"
+" --label string	Unique rule identifier\n"
+"\n");
+}
+
+static void idletimer_tg_help_v1(void)
+{
+	printf(
+"IDLETIMER target options:\n"
+" --timeout time	Timeout until the notification is sent (in seconds)\n"
+" --label string	Unique rule identifier\n"
+" --alarm	Use alarm instead of default timer\n"
+" --send_nl_msg		Enable netlink messages and show remaining time in sysfs.\n"
+"\n");
+}
+
+static void idletimer_tg_print(const void *ip,
+			       const struct xt_entry_target *target,
+			       int numeric)
+{
+	struct idletimer_tg_info *info =
+		(struct idletimer_tg_info *) target->data;
+
+	printf(" timeout:%u", info->timeout);
+	printf(" label:%s", info->label);
+}
+
+static void idletimer_tg_print_v1(const void *ip,
+			       const struct xt_entry_target *target,
+			       int numeric)
+{
+	struct idletimer_tg_info_v1 *info =
+		(struct idletimer_tg_info_v1 *) target->data;
+
+	printf(" timeout:%u", info->timeout);
+	printf(" label:%s", info->label);
+	if (info->timer_type == XT_IDLETIMER_ALARM)
+		printf(" alarm");
+	if (info->send_nl_msg)
+		printf(" send_nl_msg");
+}
+
+
+static void idletimer_tg_save(const void *ip,
+			      const struct xt_entry_target *target)
+{
+	struct idletimer_tg_info *info =
+		(struct idletimer_tg_info *) target->data;
+
+	printf(" --timeout %u", info->timeout);
+	printf(" --label %s", info->label);
+}
+
+static void idletimer_tg_save_v1(const void *ip,
+			      const struct xt_entry_target *target)
+{
+	struct idletimer_tg_info_v1 *info =
+		(struct idletimer_tg_info_v1 *) target->data;
+
+	printf(" --timeout %u", info->timeout);
+	printf(" --label %s", info->label);
+	if (info->timer_type == XT_IDLETIMER_ALARM)
+		printf(" --alarm");
+	if (info->send_nl_msg)
+		printf(" --send_nl_msg");
+}
+
+static void idletimer_tg_parse_v1(struct xt_option_call *cb)
+{
+	struct idletimer_tg_info_v1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->entry->id == O_ALARM)
+		info->timer_type = XT_IDLETIMER_ALARM;
+	if (cb->entry->id == O_NETLINK)
+		info->send_nl_msg = 1;
+}
+
+static struct xtables_target idletimer_tg_reg[] = {
+	{
+		.family	       = NFPROTO_UNSPEC,
+		.name	       = "IDLETIMER",
+		.version       = XTABLES_VERSION,
+		.revision      = 0,
+		.size	       = XT_ALIGN(sizeof(struct idletimer_tg_info)),
+		.userspacesize = offsetof(struct idletimer_tg_info, timer),
+		.help	       = idletimer_tg_help,
+		.x6_parse      = xtables_option_parse,
+		.print	       = idletimer_tg_print,
+		.save	       = idletimer_tg_save,
+		.x6_options    = idletimer_tg_opts,
+	},
+	{
+		.family	       = NFPROTO_UNSPEC,
+		.name	       = "IDLETIMER",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.size	       = XT_ALIGN(sizeof(struct idletimer_tg_info_v1)),
+		.userspacesize = offsetof(struct idletimer_tg_info_v1, timer),
+		.help	       = idletimer_tg_help_v1,
+		.x6_parse      = idletimer_tg_parse_v1,
+		.print	       = idletimer_tg_print_v1,
+		.save	       = idletimer_tg_save_v1,
+		.x6_options    = idletimer_tg_opts_v1,
+	},
+
+};
+
+void _init(void)
+{
+	xtables_register_targets(idletimer_tg_reg, ARRAY_SIZE(idletimer_tg_reg));
+}
diff --git a/extensions/libxt_IDLETIMER.man b/extensions/libxt_IDLETIMER.man
new file mode 100644
index 0000000..bd4add9
--- /dev/null
+++ b/extensions/libxt_IDLETIMER.man
@@ -0,0 +1,24 @@
+This target can be used to identify when interfaces have been idle for a
+certain period of time.  Timers are identified by labels and are created when
+a rule is set with a new label.  The rules also take a timeout value (in
+seconds) as an option.  If more than one rule uses the same timer label, the
+timer will be restarted whenever any of the rules get a hit.  One entry for
+each timer is created in sysfs.  This attribute contains the timer remaining
+for the timer to expire.  The attributes are located under the xt_idletimer
+class:
+.PP
+/sys/class/xt_idletimer/timers/<label>
+.PP
+When the timer expires, the target module sends a sysfs notification to the
+userspace, which can then decide what to do (eg. disconnect to save power).
+.TP
+\fB\-\-timeout\fP \fIamount\fP
+This is the time in seconds that will trigger the notification.
+.TP
+\fB\-\-label\fP \fIstring\fP
+This is a unique identifier for the timer.  The maximum length for the
+label string is 27 characters.
+.TP
+\fB\-\---send_nl_msg\fP
+Send netlink messages in addition to sysfs notifications and show remaining
+time.
diff --git a/extensions/libxt_IDLETIMER.t b/extensions/libxt_IDLETIMER.t
new file mode 100644
index 0000000..e8f306d
--- /dev/null
+++ b/extensions/libxt_IDLETIMER.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-j IDLETIMER --timeout;;FAIL
+-j IDLETIMER --timeout 42;;FAIL
+-j IDLETIMER --timeout 42 --label foo;=;OK
+-j IDLETIMER --timeout 42 --label foo --alarm;;OK
diff --git a/extensions/libxt_LED.c b/extensions/libxt_LED.c
new file mode 100644
index 0000000..6ada795
--- /dev/null
+++ b/extensions/libxt_LED.c
@@ -0,0 +1,138 @@
+/*
+ * libxt_LED.c - shared library add-on to iptables to add customized LED
+ *               trigger support.
+ *
+ * (C) 2008 Adam Nielsen <a.nielsen@shikadi.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_LED.h>
+
+enum {
+	O_LED_TRIGGER_ID = 0,
+	O_LED_DELAY,
+	O_LED_ALWAYS_BLINK,
+};
+
+#define s struct xt_led_info
+static const struct xt_option_entry LED_opts[] = {
+	{.name = "led-trigger-id", .id = O_LED_TRIGGER_ID,
+	 .flags = XTOPT_MAND, .type = XTTYPE_STRING, .min = 0,
+	 .max = sizeof(((struct xt_led_info *)NULL)->id) -
+	        sizeof("netfilter-")},
+	{.name = "led-delay", .id = O_LED_DELAY, .type = XTTYPE_STRING},
+	{.name = "led-always-blink", .id = O_LED_ALWAYS_BLINK,
+	 .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void LED_help(void)
+{
+	printf(
+"LED target options:\n"
+"--led-trigger-id name           suffix for led trigger name\n"
+"--led-delay ms                  leave the LED on for this number of\n"
+"                                milliseconds after triggering.\n"
+"--led-always-blink              blink on arriving packets, even if\n"
+"                                the LED is already on.\n"
+	);
+}
+
+static void LED_parse(struct xt_option_call *cb)
+{
+	struct xt_led_info *led = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_LED_TRIGGER_ID:
+		snprintf(led->id, sizeof(led->id), "netfilter-%s", cb->arg);
+		break;
+	case O_LED_DELAY:
+		if (strncasecmp(cb->arg, "inf", 3) == 0)
+			led->delay = -1;
+		else if (!xtables_strtoui(cb->arg, NULL, &led->delay, 0, UINT32_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				"Delay value must be within range 0..%u",
+				UINT32_MAX);
+		break;
+	case O_LED_ALWAYS_BLINK:
+		led->always_blink = 1;
+		break;
+	}
+}
+
+static void LED_print(const void *ip, const struct xt_entry_target *target,
+		      int numeric)
+{
+	const struct xt_led_info *led = (void *)target->data;
+	const char *id = led->id + strlen("netfilter-"); /* trim off prefix */
+
+	printf(" led-trigger-id:\"");
+	/* Escape double quotes and backslashes in the ID */
+	while (*id != '\0') {
+		if (*id == '"' || *id == '\\')
+			printf("\\");
+		printf("%c", *id++);
+	}
+	printf("\"");
+
+	if (led->delay == -1)
+		printf(" led-delay:inf");
+	else
+		printf(" led-delay:%dms", led->delay);
+
+	if (led->always_blink)
+		printf(" led-always-blink");
+}
+
+static void LED_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_led_info *led = (void *)target->data;
+	const char *id = led->id + strlen("netfilter-"); /* trim off prefix */
+
+	printf(" --led-trigger-id \"");
+	/* Escape double quotes and backslashes in the ID */
+	while (*id != '\0') {
+		if (*id == '"' || *id == '\\')
+			printf("\\");
+		printf("%c", *id++);
+	}
+	printf("\"");
+
+	/* Only print the delay if it's not zero (the default) */
+	if (led->delay > 0)
+		printf(" --led-delay %d", led->delay);
+	else if (led->delay == -1)
+		printf(" --led-delay inf");
+
+	/* Only print always_blink if it's not set to the default */
+	if (led->always_blink)
+		printf(" --led-always-blink");
+}
+
+static struct xtables_target led_tg_reg = {
+	.version       = XTABLES_VERSION,
+	.name          = "LED",
+	.family        = PF_UNSPEC,
+	.revision      = 0,
+	.size          = XT_ALIGN(sizeof(struct xt_led_info)),
+	.userspacesize = offsetof(struct xt_led_info, internal_data),
+	.help          = LED_help,
+	.print         = LED_print,
+	.save          = LED_save,
+	.x6_parse      = LED_parse,
+	.x6_options    = LED_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&led_tg_reg);
+}
diff --git a/extensions/libxt_LED.man b/extensions/libxt_LED.man
new file mode 100644
index 0000000..81c2f29
--- /dev/null
+++ b/extensions/libxt_LED.man
@@ -0,0 +1,30 @@
+This creates an LED-trigger that can then be attached to system indicator
+lights, to blink or illuminate them when certain packets pass through the
+system. One example might be to light up an LED for a few minutes every time
+an SSH connection is made to the local machine. The following options control
+the trigger behavior:
+.TP
+\fB\-\-led\-trigger\-id\fP \fIname\fP
+This is the name given to the LED trigger. The actual name of the trigger
+will be prefixed with "netfilter-".
+.TP
+\fB\-\-led-delay\fP \fIms\fP
+This indicates how long (in milliseconds) the LED should be left illuminated
+when a packet arrives before being switched off again. The default is 0
+(blink as fast as possible.) The special value \fIinf\fP can be given to
+leave the LED on permanently once activated. (In this case the trigger will
+need to be manually detached and reattached to the LED device to switch it
+off again.)
+.TP
+\fB\-\-led\-always\-blink\fP
+Always make the LED blink on packet arrival, even if the LED is already on.
+This allows notification of new packets even with long delay values (which
+otherwise would result in a silent prolonging of the delay time.)
+.TP
+Example:
+.TP
+Create an LED trigger for incoming SSH traffic:
+iptables \-A INPUT \-p tcp \-\-dport 22 \-j LED \-\-led\-trigger\-id ssh
+.TP
+Then attach the new trigger to an LED:
+echo netfilter\-ssh >/sys/class/leds/\fIledname\fP/trigger
diff --git a/extensions/libxt_LED.t b/extensions/libxt_LED.t
new file mode 100644
index 0000000..1f6705f
--- /dev/null
+++ b/extensions/libxt_LED.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-j LED;;FAIL
+-j LED --led-trigger-id "foo";=;OK
+-j LED --led-trigger-id "foo" --led-delay 42 --led-always-blink;=;OK
diff --git a/extensions/libxt_LOG.man b/extensions/libxt_LOG.man
new file mode 100644
index 0000000..354edf4
--- /dev/null
+++ b/extensions/libxt_LOG.man
@@ -0,0 +1,32 @@
+Turn on kernel logging of matching packets.  When this option is set
+for a rule, the Linux kernel will print some information on all
+matching packets (like most IP/IPv6 header fields) via the kernel log
+(where it can be read with \fIdmesg(1)\fP or read in the syslog).
+.PP
+This is a "non-terminating target", i.e. rule traversal continues at
+the next rule.  So if you want to LOG the packets you refuse, use two
+separate rules with the same matching criteria, first using target LOG
+then DROP (or REJECT).
+.TP
+\fB\-\-log\-level\fP \fIlevel\fP
+Level of logging, which can be (system-specific) numeric or a mnemonic.
+Possible values are (in decreasing order of priority): \fBemerg\fP,
+\fBalert\fP, \fBcrit\fP, \fBerror\fP, \fBwarning\fP, \fBnotice\fP, \fBinfo\fP
+or \fBdebug\fP.
+.TP
+\fB\-\-log\-prefix\fP \fIprefix\fP
+Prefix log messages with the specified prefix; up to 29 letters long,
+and useful for distinguishing messages in the logs.
+.TP
+\fB\-\-log\-tcp\-sequence\fP
+Log TCP sequence numbers. This is a security risk if the log is
+readable by users.
+.TP
+\fB\-\-log\-tcp\-options\fP
+Log options from the TCP packet header.
+.TP
+\fB\-\-log\-ip\-options\fP
+Log options from the IP/IPv6 packet header.
+.TP
+\fB\-\-log\-uid\fP
+Log the userid of the process which generated the packet.
diff --git a/extensions/libxt_MARK.c b/extensions/libxt_MARK.c
new file mode 100644
index 0000000..b765af6
--- /dev/null
+++ b/extensions/libxt_MARK.c
@@ -0,0 +1,438 @@
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_MARK.h>
+
+/* Version 0 */
+struct xt_mark_target_info {
+	unsigned long mark;
+};
+
+/* Version 1 */
+enum {
+	XT_MARK_SET=0,
+	XT_MARK_AND,
+	XT_MARK_OR,
+};
+
+struct xt_mark_target_info_v1 {
+	unsigned long mark;
+	uint8_t mode;
+};
+
+enum {
+	O_SET_MARK = 0,
+	O_AND_MARK,
+	O_OR_MARK,
+	O_XOR_MARK,
+	O_SET_XMARK,
+	F_SET_MARK  = 1 << O_SET_MARK,
+	F_AND_MARK  = 1 << O_AND_MARK,
+	F_OR_MARK   = 1 << O_OR_MARK,
+	F_XOR_MARK  = 1 << O_XOR_MARK,
+	F_SET_XMARK = 1 << O_SET_XMARK,
+	F_ANY       = F_SET_MARK | F_AND_MARK | F_OR_MARK |
+	              F_XOR_MARK | F_SET_XMARK,
+};
+
+static void MARK_help(void)
+{
+	printf(
+"MARK target options:\n"
+"  --set-mark value                   Set nfmark value\n"
+"  --and-mark value                   Binary AND the nfmark with value\n"
+"  --or-mark  value                   Binary OR  the nfmark with value\n");
+}
+
+static const struct xt_option_entry MARK_opts[] = {
+	{.name = "set-mark", .id = O_SET_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_ANY},
+	{.name = "and-mark", .id = O_AND_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_ANY},
+	{.name = "or-mark", .id = O_OR_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_ANY},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry mark_tg_opts[] = {
+	{.name = "set-xmark", .id = O_SET_XMARK, .type = XTTYPE_MARKMASK32,
+	 .excl = F_ANY},
+	{.name = "set-mark", .id = O_SET_MARK, .type = XTTYPE_MARKMASK32,
+	 .excl = F_ANY},
+	{.name = "and-mark", .id = O_AND_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_ANY},
+	{.name = "or-mark", .id = O_OR_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_ANY},
+	{.name = "xor-mark", .id = O_XOR_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_ANY},
+	XTOPT_TABLEEND,
+};
+
+static void mark_tg_help(void)
+{
+	printf(
+"MARK target options:\n"
+"  --set-xmark value[/mask]  Clear bits in mask and XOR value into nfmark\n"
+"  --set-mark value[/mask]   Clear bits in mask and OR value into nfmark\n"
+"  --and-mark bits           Binary AND the nfmark with bits\n"
+"  --or-mark bits            Binary OR the nfmark with bits\n"
+"  --xor-mark bits           Binary XOR the nfmark with bits\n"
+"\n");
+}
+
+static void MARK_parse_v0(struct xt_option_call *cb)
+{
+	struct xt_mark_target_info *markinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SET_MARK:
+		markinfo->mark = cb->val.mark;
+		break;
+	default:
+		xtables_error(PARAMETER_PROBLEM,
+			   "MARK target: kernel too old for --%s",
+			   cb->entry->name);
+	}
+}
+
+static void MARK_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM,
+		           "MARK target: Parameter --set/and/or-mark"
+			   " is required");
+}
+
+static void MARK_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_mark_target_info_v1 *markinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SET_MARK:
+	        markinfo->mode = XT_MARK_SET;
+		break;
+	case O_AND_MARK:
+	        markinfo->mode = XT_MARK_AND;
+		break;
+	case O_OR_MARK:
+	        markinfo->mode = XT_MARK_OR;
+		break;
+	}
+	markinfo->mark = cb->val.u32;
+}
+
+static void mark_tg_parse(struct xt_option_call *cb)
+{
+	struct xt_mark_tginfo2 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SET_XMARK:
+		info->mark = cb->val.mark;
+		info->mask = cb->val.mask;
+		break;
+	case O_SET_MARK:
+		info->mark = cb->val.mark;
+		info->mask = cb->val.mark | cb->val.mask;
+		break;
+	case O_AND_MARK:
+		info->mark = 0;
+		info->mask = ~cb->val.u32;
+		break;
+	case O_OR_MARK:
+		info->mark = info->mask = cb->val.u32;
+		break;
+	case O_XOR_MARK:
+		info->mark = cb->val.u32;
+		info->mask = 0;
+		break;
+	}
+}
+
+static void mark_tg_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM, "MARK: One of the --set-xmark, "
+		           "--{and,or,xor,set}-mark options is required");
+}
+
+static void
+print_mark(unsigned long mark)
+{
+	printf(" 0x%lx", mark);
+}
+
+static void MARK_print_v0(const void *ip,
+                          const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_mark_target_info *markinfo =
+		(const struct xt_mark_target_info *)target->data;
+	printf(" MARK set");
+	print_mark(markinfo->mark);
+}
+
+static void MARK_save_v0(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_mark_target_info *markinfo =
+		(const struct xt_mark_target_info *)target->data;
+
+	printf(" --set-mark");
+	print_mark(markinfo->mark);
+}
+
+static void MARK_print_v1(const void *ip, const struct xt_entry_target *target,
+                          int numeric)
+{
+	const struct xt_mark_target_info_v1 *markinfo =
+		(const struct xt_mark_target_info_v1 *)target->data;
+
+	switch (markinfo->mode) {
+	case XT_MARK_SET:
+		printf(" MARK set");
+		break;
+	case XT_MARK_AND:
+		printf(" MARK and");
+		break;
+	case XT_MARK_OR:
+		printf(" MARK or");
+		break;
+	}
+	print_mark(markinfo->mark);
+}
+
+static void mark_tg_print(const void *ip, const struct xt_entry_target *target,
+                          int numeric)
+{
+	const struct xt_mark_tginfo2 *info = (const void *)target->data;
+
+	if (info->mark == 0)
+		printf(" MARK and 0x%x", (unsigned int)(uint32_t)~info->mask);
+	else if (info->mark == info->mask)
+		printf(" MARK or 0x%x", info->mark);
+	else if (info->mask == 0)
+		printf(" MARK xor 0x%x", info->mark);
+	else if (info->mask == 0xffffffffU)
+		printf(" MARK set 0x%x", info->mark);
+	else
+		printf(" MARK xset 0x%x/0x%x", info->mark, info->mask);
+}
+
+static void MARK_save_v1(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_mark_target_info_v1 *markinfo =
+		(const struct xt_mark_target_info_v1 *)target->data;
+
+	switch (markinfo->mode) {
+	case XT_MARK_SET:
+		printf(" --set-mark");
+		break;
+	case XT_MARK_AND:
+		printf(" --and-mark");
+		break;
+	case XT_MARK_OR:
+		printf(" --or-mark");
+		break;
+	}
+	print_mark(markinfo->mark);
+}
+
+static void mark_tg_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_mark_tginfo2 *info = (const void *)target->data;
+
+	printf(" --set-xmark 0x%x/0x%x", info->mark, info->mask);
+}
+
+static void mark_tg_arp_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_mark_tginfo2 *info = (const void *)target->data;
+
+	if (info->mark == 0)
+		printf(" --and-mark %x", (unsigned int)(uint32_t)~info->mask);
+	else if (info->mark == info->mask)
+		printf(" --or-mark %x", info->mark);
+	else
+		printf(" --set-mark %x", info->mark);
+}
+
+static void mark_tg_arp_print(const void *ip,
+			      const struct xt_entry_target *target, int numeric)
+{
+	mark_tg_arp_save(ip, target);
+}
+
+#define MARK_OPT 1
+#define AND_MARK_OPT 2
+#define OR_MARK_OPT 3
+
+static struct option mark_tg_arp_opts[] = {
+	{ .name = "set-mark", .has_arg = required_argument, .flag = 0, .val = MARK_OPT },
+	{ .name = "and-mark", .has_arg = required_argument, .flag = 0, .val = AND_MARK_OPT },
+	{ .name = "or-mark", .has_arg = required_argument, .flag = 0, .val =  OR_MARK_OPT },
+	{ .name = NULL}
+};
+
+static int
+mark_tg_arp_parse(int c, char **argv, int invert, unsigned int *flags,
+		  const void *entry, struct xt_entry_target **target)
+{
+	struct xt_mark_tginfo2 *info =
+		(struct xt_mark_tginfo2 *)(*target)->data;
+	int i;
+
+	switch (c) {
+	case MARK_OPT:
+		if (sscanf(argv[optind-1], "%x", &i) != 1) {
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad mark value `%s'", optarg);
+			return 0;
+		}
+		info->mark = i;
+		if (*flags)
+			xtables_error(PARAMETER_PROBLEM,
+				"MARK: Can't specify --set-mark twice");
+		*flags = 1;
+		break;
+	case AND_MARK_OPT:
+		if (sscanf(argv[optind-1], "%x", &i) != 1) {
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad mark value `%s'", optarg);
+			return 0;
+		}
+		info->mark = 0;
+		info->mask = ~i;
+		if (*flags)
+			xtables_error(PARAMETER_PROBLEM,
+				"MARK: Can't specify --and-mark twice");
+		*flags = 1;
+		break;
+	case OR_MARK_OPT:
+		if (sscanf(argv[optind-1], "%x", &i) != 1) {
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad mark value `%s'", optarg);
+			return 0;
+		}
+		info->mark = info->mask = i;
+		if (*flags)
+			xtables_error(PARAMETER_PROBLEM,
+				"MARK: Can't specify --or-mark twice");
+		*flags = 1;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static int mark_tg_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct xt_mark_tginfo2 *info = (const void *)params->target->data;
+
+	xt_xlate_add(xl, "meta mark set ");
+
+	if (info->mask == 0xffffffffU)
+		xt_xlate_add(xl, "0x%x ", info->mark);
+	else if (info->mark == 0)
+		xt_xlate_add(xl, "mark and 0x%x ", ~info->mask);
+	else if (info->mark == info->mask)
+		xt_xlate_add(xl, "mark or 0x%x ", info->mark);
+	else if (info->mask == 0)
+		xt_xlate_add(xl, "mark xor 0x%x ", info->mark);
+	else
+		xt_xlate_add(xl, "mark and 0x%x xor 0x%x ", ~info->mask,
+			     info->mark);
+
+	return 1;
+}
+
+static int MARK_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct xt_mark_target_info_v1 *markinfo =
+		(const struct xt_mark_target_info_v1 *)params->target->data;
+
+	xt_xlate_add(xl, "meta mark set ");
+
+	switch(markinfo->mode) {
+	case XT_MARK_SET:
+		xt_xlate_add(xl, "0x%x ", (uint32_t)markinfo->mark);
+		break;
+	case XT_MARK_AND:
+		xt_xlate_add(xl, "mark and 0x%x ", (uint32_t)markinfo->mark);
+		break;
+	case XT_MARK_OR:
+		xt_xlate_add(xl, "mark or 0x%x ", (uint32_t)markinfo->mark);
+		break;
+	}
+
+	return 1;
+}
+
+static struct xtables_target mark_tg_reg[] = {
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "MARK",
+		.version       = XTABLES_VERSION,
+		.revision      = 0,
+		.size          = XT_ALIGN(sizeof(struct xt_mark_target_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_mark_target_info)),
+		.help          = MARK_help,
+		.print         = MARK_print_v0,
+		.save          = MARK_save_v0,
+		.x6_parse      = MARK_parse_v0,
+		.x6_fcheck     = MARK_check,
+		.x6_options    = MARK_opts,
+	},
+	{
+		.family        = NFPROTO_IPV4,
+		.name          = "MARK",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.size          = XT_ALIGN(sizeof(struct xt_mark_target_info_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_mark_target_info_v1)),
+		.help          = MARK_help,
+		.print         = MARK_print_v1,
+		.save          = MARK_save_v1,
+		.x6_parse      = MARK_parse_v1,
+		.x6_fcheck     = MARK_check,
+		.x6_options    = MARK_opts,
+		.xlate	       = MARK_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "MARK",
+		.revision      = 2,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_mark_tginfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_mark_tginfo2)),
+		.help          = mark_tg_help,
+		.print         = mark_tg_print,
+		.save          = mark_tg_save,
+		.x6_parse      = mark_tg_parse,
+		.x6_fcheck     = mark_tg_check,
+		.x6_options    = mark_tg_opts,
+		.xlate	       = mark_tg_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "MARK",
+		.revision      = 2,
+		.family        = NFPROTO_ARP,
+		.size          = XT_ALIGN(sizeof(struct xt_mark_tginfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_mark_tginfo2)),
+		.help          = mark_tg_help,
+		.print         = mark_tg_arp_print,
+		.save          = mark_tg_arp_save,
+		.parse         = mark_tg_arp_parse,
+		.extra_opts    = mark_tg_arp_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(mark_tg_reg, ARRAY_SIZE(mark_tg_reg));
+}
diff --git a/extensions/libxt_MARK.man b/extensions/libxt_MARK.man
new file mode 100644
index 0000000..b240859
--- /dev/null
+++ b/extensions/libxt_MARK.man
@@ -0,0 +1,27 @@
+This target is used to set the Netfilter mark value associated with the packet.
+It can, for example, be used in conjunction with routing based on fwmark (needs
+iproute2). If you plan on doing so, note that the mark needs to be set in
+either the PREROUTING or the OUTPUT chain of the mangle table to affect routing.
+The mark field is 32 bits wide.
+.TP
+\fB\-\-set\-xmark\fP \fIvalue\fP[\fB/\fP\fImask\fP]
+Zeroes out the bits given by \fImask\fP and XORs \fIvalue\fP into the packet
+mark ("nfmark"). If \fImask\fP is omitted, 0xFFFFFFFF is assumed.
+.TP
+\fB\-\-set\-mark\fP \fIvalue\fP[\fB/\fP\fImask\fP]
+Zeroes out the bits given by \fImask\fP and ORs \fIvalue\fP into the packet
+mark. If \fImask\fP is omitted, 0xFFFFFFFF is assumed.
+.PP
+The following mnemonics are available:
+.TP
+\fB\-\-and\-mark\fP \fIbits\fP
+Binary AND the nfmark with \fIbits\fP. (Mnemonic for \fB\-\-set\-xmark
+0/\fP\fIinvbits\fP, where \fIinvbits\fP is the binary negation of \fIbits\fP.)
+.TP
+\fB\-\-or\-mark\fP \fIbits\fP
+Binary OR the nfmark with \fIbits\fP. (Mnemonic for \fB\-\-set\-xmark\fP
+\fIbits\fP\fB/\fP\fIbits\fP.)
+.TP
+\fB\-\-xor\-mark\fP \fIbits\fP
+Binary XOR the nfmark with \fIbits\fP. (Mnemonic for \fB\-\-set\-xmark\fP
+\fIbits\fP\fB/0\fP.)
diff --git a/extensions/libxt_MARK.t b/extensions/libxt_MARK.t
new file mode 100644
index 0000000..9d1aa7d
--- /dev/null
+++ b/extensions/libxt_MARK.t
@@ -0,0 +1,7 @@
+:INPUT,FORWARD,OUTPUT
+-j MARK --set-xmark 0xfeedcafe/0xfeedcafe;=;OK
+-j MARK --set-xmark 0;=;OK
+-j MARK --set-xmark 4294967295;-j MARK --set-xmark 0xffffffff;OK
+-j MARK --set-xmark 4294967296;;FAIL
+-j MARK --set-xmark -1;;FAIL
+-j MARK;;FAIL
diff --git a/extensions/libxt_MARK.txlate b/extensions/libxt_MARK.txlate
new file mode 100644
index 0000000..d3250ab
--- /dev/null
+++ b/extensions/libxt_MARK.txlate
@@ -0,0 +1,26 @@
+iptables-translate -t mangle -A OUTPUT -j MARK --set-mark 0
+nft add rule ip mangle OUTPUT counter meta mark set 0x0
+
+iptables-translate -t mangle -A OUTPUT -j MARK --set-mark 64
+nft add rule ip mangle OUTPUT counter meta mark set 0x40
+
+iptables-translate -t mangle -A OUTPUT -j MARK --set-xmark 0x40/0x32
+nft add rule ip mangle OUTPUT counter meta mark set mark and 0xffffffcd xor 0x40
+
+iptables-translate -t mangle -A OUTPUT -j MARK --or-mark 64
+nft add rule ip mangle OUTPUT counter meta mark set mark or 0x40
+
+iptables-translate -t mangle -A OUTPUT -j MARK --and-mark 64
+nft add rule ip mangle OUTPUT counter meta mark set mark and 0x40
+
+iptables-translate -t mangle -A OUTPUT -j MARK --xor-mark 64
+nft add rule ip mangle OUTPUT counter meta mark set mark xor 0x40
+
+iptables-translate -t mangle -A PREROUTING -j MARK --set-mark 0x64
+nft add rule ip mangle PREROUTING counter meta mark set 0x64
+
+iptables-translate -t mangle -A PREROUTING -j MARK --and-mark 0x64
+nft add rule ip mangle PREROUTING counter meta mark set mark and 0x64
+
+iptables-translate -t mangle -A PREROUTING -j MARK --or-mark 0x64
+nft add rule ip mangle PREROUTING counter meta mark set mark or 0x64
diff --git a/extensions/libxt_MASQUERADE.man b/extensions/libxt_MASQUERADE.man
new file mode 100644
index 0000000..7746f47
--- /dev/null
+++ b/extensions/libxt_MASQUERADE.man
@@ -0,0 +1,35 @@
+This target is only valid in the
+.B nat
+table, in the
+.B POSTROUTING
+chain.  It should only be used with dynamically assigned IP (dialup)
+connections: if you have a static IP address, you should use the SNAT
+target.  Masquerading is equivalent to specifying a mapping to the IP
+address of the interface the packet is going out, but also has the
+effect that connections are
+.I forgotten
+when the interface goes down.  This is the correct behavior when the
+next dialup is unlikely to have the same interface address (and hence
+any established connections are lost anyway).
+.TP
+\fB\-\-to\-ports\fP \fIport\fP[\fB\-\fP\fIport\fP]
+This specifies a range of source ports to use, overriding the default
+.B SNAT
+source port-selection heuristics (see above).  This is only valid
+if the rule also specifies one of the following protocols:
+\fBtcp\fP, \fBudp\fP, \fBdccp\fP or \fBsctp\fP.
+.TP
+\fB\-\-random\fP
+Randomize source port mapping
+If option
+\fB\-\-random\fP
+is used then port mapping will be randomized (kernel >= 2.6.21).
+Since kernel 5.0, \fB\-\-random\fP is identical to \fB\-\-random-fully\fP.
+.TP
+\fB\-\-random-fully\fP
+Full randomize source port mapping
+If option
+\fB\-\-random-fully\fP
+is used then port mapping will be fully randomized (kernel >= 3.13).
+.TP
+IPv6 support available since Linux kernels >= 3.7.
diff --git a/extensions/libxt_NETMAP.man b/extensions/libxt_NETMAP.man
new file mode 100644
index 0000000..06507db
--- /dev/null
+++ b/extensions/libxt_NETMAP.man
@@ -0,0 +1,11 @@
+This target allows you to statically map a whole network of addresses onto
+another network of addresses.  It can only be used from rules in the
+.B nat
+table.
+.TP
+\fB\-\-to\fP \fIaddress\fP[\fB/\fP\fImask\fP]
+Network address to map to.  The resulting address will be constructed in the
+following way: All 'one' bits in the mask are filled in from the new `address'.
+All bits that are zero in the mask are filled in from the original address.
+.TP
+IPv6 support available since Linux kernels >= 3.7.
diff --git a/extensions/libxt_NFLOG.c b/extensions/libxt_NFLOG.c
new file mode 100644
index 0000000..02a1b4a
--- /dev/null
+++ b/extensions/libxt_NFLOG.c
@@ -0,0 +1,157 @@
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/xt_NFLOG.h>
+
+enum {
+	O_GROUP = 0,
+	O_PREFIX,
+	O_RANGE,
+	O_SIZE,
+	O_THRESHOLD,
+	F_RANGE = 1 << O_RANGE,
+	F_SIZE = 1 << O_SIZE,
+};
+
+#define s struct xt_nflog_info
+static const struct xt_option_entry NFLOG_opts[] = {
+	{.name = "nflog-group", .id = O_GROUP, .type = XTTYPE_UINT16,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, group)},
+	{.name = "nflog-prefix", .id = O_PREFIX, .type = XTTYPE_STRING,
+	 .min = 1, .flags = XTOPT_PUT, XTOPT_POINTER(s, prefix)},
+	{.name = "nflog-range", .id = O_RANGE, .type = XTTYPE_UINT32,
+	 .excl = F_SIZE, .flags = XTOPT_PUT, XTOPT_POINTER(s, len)},
+	{.name = "nflog-size", .id = O_SIZE, .type = XTTYPE_UINT32,
+	 .excl = F_RANGE, .flags = XTOPT_PUT, XTOPT_POINTER(s, len)},
+	{.name = "nflog-threshold", .id = O_THRESHOLD, .type = XTTYPE_UINT16,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, threshold)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void NFLOG_help(void)
+{
+	printf("NFLOG target options:\n"
+	       " --nflog-group NUM		NETLINK group used for logging\n"
+	       " --nflog-range NUM		This option has no effect, use --nflog-size\n"
+	       " --nflog-size NUM		Number of bytes to copy\n"
+	       " --nflog-threshold NUM		Message threshold of in-kernel queue\n"
+	       " --nflog-prefix STRING		Prefix string for log messages\n");
+}
+
+static void NFLOG_init(struct xt_entry_target *t)
+{
+	struct xt_nflog_info *info = (struct xt_nflog_info *)t->data;
+
+	info->threshold	= XT_NFLOG_DEFAULT_THRESHOLD;
+}
+
+static void NFLOG_parse(struct xt_option_call *cb)
+{
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_PREFIX:
+		if (strchr(cb->arg, '\n') != NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Newlines not allowed in --log-prefix");
+		break;
+	}
+}
+
+static void NFLOG_check(struct xt_fcheck_call *cb)
+{
+	struct xt_nflog_info *info = cb->data;
+
+	if (cb->xflags & F_RANGE)
+		fprintf(stderr, "warn: --nflog-range has never worked and is no"
+			" longer supported, please use --nflog-size insted\n");
+
+	if (cb->xflags & F_SIZE)
+		info->flags |= XT_NFLOG_F_COPY_LEN;
+}
+
+static void nflog_print(const struct xt_nflog_info *info, char *prefix)
+{
+	if (info->prefix[0] != '\0') {
+		printf(" %snflog-prefix ", prefix);
+		xtables_save_string(info->prefix);
+	}
+	if (info->group)
+		printf(" %snflog-group %u", prefix, info->group);
+	if (info->flags & XT_NFLOG_F_COPY_LEN)
+		printf(" %snflog-size %u", prefix, info->len);
+	else if (info->len)
+		printf(" %snflog-range %u", prefix, info->len);
+	if (info->threshold != XT_NFLOG_DEFAULT_THRESHOLD)
+		printf(" %snflog-threshold %u", prefix, info->threshold);
+}
+
+static void NFLOG_print(const void *ip, const struct xt_entry_target *target,
+			int numeric)
+{
+	const struct xt_nflog_info *info = (struct xt_nflog_info *)target->data;
+
+	nflog_print(info, "");
+}
+
+static void NFLOG_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_nflog_info *info = (struct xt_nflog_info *)target->data;
+
+	nflog_print(info, "--");
+}
+
+static void nflog_print_xlate(const struct xt_nflog_info *info,
+			      struct xt_xlate *xl, bool escape_quotes)
+{
+	xt_xlate_add(xl, "log ");
+	if (info->prefix[0] != '\0') {
+		if (escape_quotes)
+			xt_xlate_add(xl, "prefix \\\"%s\\\" ", info->prefix);
+		else
+			xt_xlate_add(xl, "prefix \"%s\" ", info->prefix);
+
+	}
+	if (info->flags & XT_NFLOG_F_COPY_LEN)
+		xt_xlate_add(xl, "snaplen %u ", info->len);
+	if (info->threshold != XT_NFLOG_DEFAULT_THRESHOLD)
+		xt_xlate_add(xl, "queue-threshold %u ", info->threshold);
+	xt_xlate_add(xl, "group %u ", info->group);
+}
+
+static int NFLOG_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	const struct xt_nflog_info *info =
+		(struct xt_nflog_info *)params->target->data;
+
+	nflog_print_xlate(info, xl, params->escape_quotes);
+
+	return 1;
+}
+
+static struct xtables_target nflog_target = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "NFLOG",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_nflog_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_nflog_info)),
+	.help		= NFLOG_help,
+	.init		= NFLOG_init,
+	.x6_fcheck	= NFLOG_check,
+	.x6_parse	= NFLOG_parse,
+	.print		= NFLOG_print,
+	.save		= NFLOG_save,
+	.x6_options	= NFLOG_opts,
+	.xlate		= NFLOG_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&nflog_target);
+}
diff --git a/extensions/libxt_NFLOG.man b/extensions/libxt_NFLOG.man
new file mode 100644
index 0000000..318e630
--- /dev/null
+++ b/extensions/libxt_NFLOG.man
@@ -0,0 +1,32 @@
+This target provides logging of matching packets. When this target is
+set for a rule, the Linux kernel will pass the packet to the loaded
+logging backend to log the packet. This is usually used in combination
+with nfnetlink_log as logging backend, which will multicast the packet
+through a
+.IR netlink
+socket to the specified multicast group. One or more userspace processes
+may subscribe to the group to receive the packets. Like LOG, this is a
+non-terminating target, i.e. rule traversal continues at the next rule.
+.TP
+\fB\-\-nflog\-group\fP \fInlgroup\fP
+The netlink group (0 - 2^16\-1) to which packets are (only applicable for
+nfnetlink_log). The default value is 0.
+.TP
+\fB\-\-nflog\-prefix\fP \fIprefix\fP
+A prefix string to include in the log message, up to 64 characters
+long, useful for distinguishing messages in the logs.
+.TP
+\fB\-\-nflog\-range\fP \fIsize\fP
+This option has never worked, use --nflog-size instead
+.TP
+\fB\-\-nflog\-size\fP \fIsize\fP
+The number of bytes to be copied to userspace (only applicable for
+nfnetlink_log). nfnetlink_log instances may specify their own
+range, this option overrides it.
+.TP
+\fB\-\-nflog\-threshold\fP \fIsize\fP
+Number of packets to queue inside the kernel before sending them
+to userspace (only applicable for nfnetlink_log). Higher values
+result in less overhead per packet, but increase delay until the
+packets reach userspace. The default value is 1.
+.BR
diff --git a/extensions/libxt_NFLOG.t b/extensions/libxt_NFLOG.t
new file mode 100644
index 0000000..933fa22
--- /dev/null
+++ b/extensions/libxt_NFLOG.t
@@ -0,0 +1,24 @@
+:INPUT,FORWARD,OUTPUT
+-j NFLOG --nflog-group 1;=;OK
+-j NFLOG --nflog-group 65535;=;OK
+-j NFLOG --nflog-group 65536;;FAIL
+-j NFLOG --nflog-group 0;-j NFLOG;OK
+-j NFLOG --nflog-range 1;=;OK
+-j NFLOG --nflog-range 4294967295;=;OK
+-j NFLOG --nflog-range 4294967296;;FAIL
+-j NFLOG --nflog-range -1;;FAIL
+-j NFLOG --nflog-size 0;=;OK
+-j NFLOG --nflog-size 1;=;OK
+-j NFLOG --nflog-size 4294967295;=;OK
+-j NFLOG --nflog-size 4294967296;;FAIL
+-j NFLOG --nflog-size -1;;FAIL
+# ERROR: cannot find: iptables -I INPUT -j NFLOG --nflog-prefix  xxxxxx [...]
+# -j NFLOG --nflog-prefix xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;=;OK
+# ERROR: should fail: iptables -A INPUT -j NFLOG --nflog-prefix  xxxxxxx [...]
+#  -j NFLOG --nflog-prefix xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;;FAIL
+-j NFLOG --nflog-threshold 1;=;OK
+# ERROR: line 13 (should fail: iptables -A INPUT -j NFLOG --nflog-threshold 0
+# -j NFLOG --nflog-threshold 0;;FAIL
+-j NFLOG --nflog-threshold 65535;=;OK
+-j NFLOG --nflog-threshold 65536;;FAIL
+-j NFLOG;=;OK
diff --git a/extensions/libxt_NFLOG.txlate b/extensions/libxt_NFLOG.txlate
new file mode 100644
index 0000000..a0872c9
--- /dev/null
+++ b/extensions/libxt_NFLOG.txlate
@@ -0,0 +1,14 @@
+iptables-translate -A FORWARD -j NFLOG --nflog-group 32 --nflog-prefix "Prefix 1.0"
+nft add rule ip filter FORWARD counter log prefix \"Prefix 1.0\" group 32
+
+iptables-translate -A OUTPUT -j NFLOG --nflog-group 30
+nft add rule ip filter OUTPUT counter log group 30
+
+iptables-translate -I INPUT -j NFLOG --nflog-threshold 2
+nft insert rule ip filter INPUT counter log queue-threshold 2 group 0
+
+iptables-translate -I INPUT -j NFLOG --nflog-size 256
+nft insert rule ip filter INPUT counter log snaplen 256 group 0
+
+iptables-translate -I INPUT -j NFLOG --nflog-threshold 25
+nft insert rule ip filter INPUT counter log queue-threshold 25 group 0
diff --git a/extensions/libxt_NFQUEUE.c b/extensions/libxt_NFQUEUE.c
new file mode 100644
index 0000000..fe51907
--- /dev/null
+++ b/extensions/libxt_NFQUEUE.c
@@ -0,0 +1,401 @@
+/* Shared library add-on to iptables for NFQ
+ *
+ * (C) 2005 by Harald Welte <laforge@netfilter.org>
+ *
+ * This program is distributed under the terms of GNU GPL v2, 1991
+ *
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_NFQUEUE.h>
+
+enum {
+	O_QUEUE_NUM = 0,
+	O_QUEUE_BALANCE,
+	O_QUEUE_BYPASS,
+	O_QUEUE_CPU_FANOUT,
+	F_QUEUE_NUM     = 1 << O_QUEUE_NUM,
+	F_QUEUE_BALANCE = 1 << O_QUEUE_BALANCE,
+	F_QUEUE_CPU_FANOUT = 1 << O_QUEUE_CPU_FANOUT,
+};
+
+static void NFQUEUE_help(void)
+{
+	printf(
+"NFQUEUE target options\n"
+"  --queue-num value		Send packet to QUEUE number <value>.\n"
+"  		                Valid queue numbers are 0-65535\n"
+);
+}
+
+static void NFQUEUE_help_v1(void)
+{
+	printf(
+"NFQUEUE target options\n"
+"  --queue-num value            Send packet to QUEUE number <value>.\n"
+"                               Valid queue numbers are 0-65535\n"
+"  --queue-balance first:last	Balance flows between queues <value> to <value>.\n");
+}
+
+static void NFQUEUE_help_v2(void)
+{
+	printf(
+"NFQUEUE target options\n"
+"  --queue-num value            Send packet to QUEUE number <value>.\n"
+"                               Valid queue numbers are 0-65535\n"
+"  --queue-balance first:last   Balance flows between queues <value> to <value>.\n"
+"  --queue-bypass		Bypass Queueing if no queue instance exists.\n"
+"  --queue-cpu-fanout	Use current CPU (no hashing)\n");
+}
+
+static void NFQUEUE_help_v3(void)
+{
+	printf(
+"NFQUEUE target options\n"
+"  --queue-num value            Send packet to QUEUE number <value>.\n"
+"                               Valid queue numbers are 0-65535\n"
+"  --queue-balance first:last   Balance flows between queues <value> to <value>.\n"
+"  --queue-bypass               Bypass Queueing if no queue instance exists.\n"
+"  --queue-cpu-fanout	Use current CPU (no hashing)\n");
+}
+
+#define s struct xt_NFQ_info
+static const struct xt_option_entry NFQUEUE_opts[] = {
+	{.name = "queue-num", .id = O_QUEUE_NUM, .type = XTTYPE_UINT16,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, queuenum),
+	 .excl = F_QUEUE_BALANCE},
+	{.name = "queue-balance", .id = O_QUEUE_BALANCE,
+	 .type = XTTYPE_UINT16RC, .excl = F_QUEUE_NUM},
+	{.name = "queue-bypass", .id = O_QUEUE_BYPASS, .type = XTTYPE_NONE},
+	{.name = "queue-cpu-fanout", .id = O_QUEUE_CPU_FANOUT,
+	 .type = XTTYPE_NONE, .also = F_QUEUE_BALANCE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void NFQUEUE_parse(struct xt_option_call *cb)
+{
+	xtables_option_parse(cb);
+	if (cb->entry->id == O_QUEUE_BALANCE)
+		xtables_error(PARAMETER_PROBLEM, "NFQUEUE target: "
+				   "--queue-balance not supported (kernel too old?)");
+}
+
+static void NFQUEUE_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_NFQ_info_v1 *info = cb->data;
+	const uint16_t *r = cb->val.u16_range;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_QUEUE_BALANCE:
+		if (cb->nvals != 2)
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad range \"%s\"", cb->arg);
+		if (r[0] >= r[1])
+			xtables_error(PARAMETER_PROBLEM, "%u should be less than %u",
+				r[0], r[1]);
+		info->queuenum = r[0];
+		info->queues_total = r[1] - r[0] + 1;
+		break;
+	}
+}
+
+static void NFQUEUE_parse_v2(struct xt_option_call *cb)
+{
+	struct xt_NFQ_info_v2 *info = cb->data;
+	const uint16_t *r = cb->val.u16_range;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_QUEUE_BALANCE:
+		if (cb->nvals != 2)
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad range \"%s\"", cb->arg);
+		if (r[0] >= r[1])
+			xtables_error(PARAMETER_PROBLEM,
+				      "%u should be less than %u",
+				      r[0], r[1]);
+		info->queuenum = r[0];
+		info->queues_total = r[1] - r[0] + 1;
+		break;
+	case O_QUEUE_BYPASS:
+		info->bypass |= NFQ_FLAG_BYPASS;
+		break;
+	}
+}
+
+static void NFQUEUE_parse_v3(struct xt_option_call *cb)
+{
+	struct xt_NFQ_info_v3 *info = cb->data;
+	const uint16_t *r = cb->val.u16_range;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_QUEUE_BALANCE:
+		if (cb->nvals != 2)
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad range \"%s\"", cb->arg);
+		if (r[0] >= r[1])
+			xtables_error(PARAMETER_PROBLEM,
+				      "%u should be less than %u",
+				      r[0], r[1]);
+		info->queuenum = r[0];
+		info->queues_total = r[1] - r[0] + 1;
+		break;
+	case O_QUEUE_BYPASS:
+		info->flags |= NFQ_FLAG_BYPASS;
+		break;
+	case O_QUEUE_CPU_FANOUT:
+		info->flags |= NFQ_FLAG_CPU_FANOUT;
+		break;
+	}
+}
+
+static void NFQUEUE_print(const void *ip,
+                          const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_NFQ_info *tinfo =
+		(const struct xt_NFQ_info *)target->data;
+	printf(" NFQUEUE num %u", tinfo->queuenum);
+}
+
+static void NFQUEUE_print_v1(const void *ip,
+                             const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_NFQ_info_v1 *tinfo = (const void *)target->data;
+	unsigned int last = tinfo->queues_total;
+
+	if (last > 1) {
+		last += tinfo->queuenum - 1;
+		printf(" NFQUEUE balance %u:%u", tinfo->queuenum, last);
+	} else {
+		printf(" NFQUEUE num %u", tinfo->queuenum);
+	}
+}
+
+static void NFQUEUE_print_v2(const void *ip,
+                             const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_NFQ_info_v2 *info = (void *) target->data;
+	unsigned int last = info->queues_total;
+
+	if (last > 1) {
+		last += info->queuenum - 1;
+		printf(" NFQUEUE balance %u:%u", info->queuenum, last);
+	} else
+		printf(" NFQUEUE num %u", info->queuenum);
+
+	if (info->bypass & NFQ_FLAG_BYPASS)
+		printf(" bypass");
+}
+
+static void NFQUEUE_print_v3(const void *ip,
+                             const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_NFQ_info_v3 *info = (void *)target->data;
+	unsigned int last = info->queues_total;
+
+	if (last > 1) {
+		last += info->queuenum - 1;
+		printf(" NFQUEUE balance %u:%u", info->queuenum, last);
+	} else
+		printf(" NFQUEUE num %u", info->queuenum);
+
+	if (info->flags & NFQ_FLAG_BYPASS)
+		printf(" bypass");
+
+	if (info->flags & NFQ_FLAG_CPU_FANOUT)
+		printf(" cpu-fanout");
+}
+
+static void NFQUEUE_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_NFQ_info *tinfo =
+		(const struct xt_NFQ_info *)target->data;
+
+	printf(" --queue-num %u", tinfo->queuenum);
+}
+
+static void NFQUEUE_save_v1(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_NFQ_info_v1 *tinfo = (const void *)target->data;
+	unsigned int last = tinfo->queues_total;
+
+	if (last > 1) {
+		last += tinfo->queuenum - 1;
+		printf(" --queue-balance %u:%u", tinfo->queuenum, last);
+	} else {
+		printf(" --queue-num %u", tinfo->queuenum);
+	}
+}
+
+static void NFQUEUE_save_v2(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_NFQ_info_v2 *info = (void *) target->data;
+	unsigned int last = info->queues_total;
+
+	if (last > 1) {
+		last += info->queuenum - 1;
+		printf(" --queue-balance %u:%u", info->queuenum, last);
+	} else
+		printf(" --queue-num %u", info->queuenum);
+
+	if (info->bypass & NFQ_FLAG_BYPASS)
+		printf(" --queue-bypass");
+}
+
+static void NFQUEUE_save_v3(const void *ip,
+			    const struct xt_entry_target *target)
+{
+	const struct xt_NFQ_info_v3 *info = (void *)target->data;
+	unsigned int last = info->queues_total;
+
+	if (last > 1) {
+		last += info->queuenum - 1;
+		printf(" --queue-balance %u:%u", info->queuenum, last);
+	} else
+		printf(" --queue-num %u", info->queuenum);
+
+	if (info->flags & NFQ_FLAG_BYPASS)
+		printf(" --queue-bypass");
+
+	if (info->flags & NFQ_FLAG_CPU_FANOUT)
+		printf(" --queue-cpu-fanout");
+}
+
+static void NFQUEUE_init_v1(struct xt_entry_target *t)
+{
+	struct xt_NFQ_info_v1 *tinfo = (void *)t->data;
+	tinfo->queues_total = 1;
+}
+
+static int NFQUEUE_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct xt_NFQ_info *tinfo =
+		(const struct xt_NFQ_info *)params->target->data;
+
+	xt_xlate_add(xl, "queue num %u ", tinfo->queuenum);
+
+	return 1;
+}
+
+static int NFQUEUE_xlate_v1(struct xt_xlate *xl,
+			    const struct xt_xlate_tg_params *params)
+{
+	const struct xt_NFQ_info_v1 *tinfo = (const void *)params->target->data;
+	unsigned int last = tinfo->queues_total;
+
+	if (last > 1) {
+		last += tinfo->queuenum - 1;
+		xt_xlate_add(xl, "queue num %u-%u ", tinfo->queuenum, last);
+	} else {
+		xt_xlate_add(xl, "queue num %u ", tinfo->queuenum);
+	}
+
+	return 1;
+}
+
+static int NFQUEUE_xlate_v2(struct xt_xlate *xl,
+			    const struct xt_xlate_tg_params *params)
+{
+	const struct xt_NFQ_info_v2 *info = (void *)params->target->data;
+	unsigned int last = info->queues_total;
+
+	if (last > 1) {
+		last += info->queuenum - 1;
+		xt_xlate_add(xl, "queue num %u-%u ", info->queuenum, last);
+	} else
+		xt_xlate_add(xl, "queue num %u ", info->queuenum);
+
+	if (info->bypass & NFQ_FLAG_BYPASS)
+		xt_xlate_add(xl, "bypass");
+
+	return 1;
+}
+
+static int NFQUEUE_xlate_v3(struct xt_xlate *xl,
+			    const struct xt_xlate_tg_params *params)
+{
+	const struct xt_NFQ_info_v3 *info = (void *)params->target->data;
+	unsigned int last = info->queues_total;
+
+	if (last > 1) {
+		last += info->queuenum - 1;
+		xt_xlate_add(xl, "queue num %u-%u ", info->queuenum, last);
+	} else
+		xt_xlate_add(xl, "queue num %u ", info->queuenum);
+
+	if (info->flags & NFQ_FLAG_BYPASS)
+		xt_xlate_add(xl, "bypass");
+
+	if (info->flags & NFQ_FLAG_CPU_FANOUT)
+		xt_xlate_add(xl, "%sfanout ",
+			     info->flags & NFQ_FLAG_BYPASS ? "," : "");
+
+	return 1;
+}
+
+static struct xtables_target nfqueue_targets[] = {
+{
+	.family		= NFPROTO_UNSPEC,
+	.name		= "NFQUEUE",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_NFQ_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_NFQ_info)),
+	.help		= NFQUEUE_help,
+	.print		= NFQUEUE_print,
+	.save		= NFQUEUE_save,
+	.x6_parse	= NFQUEUE_parse,
+	.x6_options	= NFQUEUE_opts,
+	.xlate		= NFQUEUE_xlate,
+},{
+	.family		= NFPROTO_UNSPEC,
+	.revision	= 1,
+	.name		= "NFQUEUE",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_NFQ_info_v1)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_NFQ_info_v1)),
+	.help		= NFQUEUE_help_v1,
+	.init		= NFQUEUE_init_v1,
+	.print		= NFQUEUE_print_v1,
+	.save		= NFQUEUE_save_v1,
+	.x6_parse	= NFQUEUE_parse_v1,
+	.x6_options	= NFQUEUE_opts,
+	.xlate		= NFQUEUE_xlate_v1,
+},{
+	.family		= NFPROTO_UNSPEC,
+	.revision	= 2,
+	.name		= "NFQUEUE",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_NFQ_info_v2)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_NFQ_info_v2)),
+	.help		= NFQUEUE_help_v2,
+	.init		= NFQUEUE_init_v1,
+	.print		= NFQUEUE_print_v2,
+	.save		= NFQUEUE_save_v2,
+	.x6_parse	= NFQUEUE_parse_v2,
+	.x6_options	= NFQUEUE_opts,
+	.xlate		= NFQUEUE_xlate_v2,
+},{
+	.family		= NFPROTO_UNSPEC,
+	.revision	= 3,
+	.name		= "NFQUEUE",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_NFQ_info_v3)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_NFQ_info_v3)),
+	.help		= NFQUEUE_help_v3,
+	.init		= NFQUEUE_init_v1,
+	.print		= NFQUEUE_print_v3,
+	.save		= NFQUEUE_save_v3,
+	.x6_parse	= NFQUEUE_parse_v3,
+	.x6_options	= NFQUEUE_opts,
+	.xlate		= NFQUEUE_xlate_v3,
+}
+};
+
+void _init(void)
+{
+	xtables_register_targets(nfqueue_targets, ARRAY_SIZE(nfqueue_targets));
+}
diff --git a/extensions/libxt_NFQUEUE.man b/extensions/libxt_NFQUEUE.man
new file mode 100644
index 0000000..1bfb7b8
--- /dev/null
+++ b/extensions/libxt_NFQUEUE.man
@@ -0,0 +1,33 @@
+This target passes the packet to userspace using the
+\fBnfnetlink_queue\fP handler.  The packet is put into the queue
+identified by its 16-bit queue number.  Userspace can inspect
+and modify the packet if desired. Userspace must then drop or
+reinject the packet into the kernel.  Please see libnetfilter_queue
+for details.
+.B
+nfnetlink_queue
+was added in Linux 2.6.14. The \fBqueue-balance\fP option was added in Linux 2.6.31,
+\fBqueue-bypass\fP in 2.6.39.
+.TP
+\fB\-\-queue\-num\fP \fIvalue\fP
+This specifies the QUEUE number to use. Valid queue numbers are 0 to 65535. The default value is 0.
+.PP
+.TP
+\fB\-\-queue\-balance\fP \fIvalue\fP\fB:\fP\fIvalue\fP
+This specifies a range of queues to use. Packets are then balanced across the given queues.
+This is useful for multicore systems: start multiple instances of the userspace program on
+queues x, x+1, .. x+n and use "\-\-queue\-balance \fIx\fP\fB:\fP\fIx+n\fP".
+Packets belonging to the same connection are put into the same nfqueue.
+.PP
+.TP
+\fB\-\-queue\-bypass\fP
+By default, if no userspace program is listening on an NFQUEUE, then all packets that are to be queued
+are dropped.  When this option is used, the NFQUEUE rule behaves like ACCEPT instead, and the packet
+will move on to the next table.
+.PP
+.TP
+\fB\-\-queue\-cpu-fanout\fP
+Available starting Linux kernel 3.10. When used together with
+\fB--queue-balance\fP this will use the CPU ID as an index to map packets to
+the queues. The idea is that you can improve performance if there's a queue
+per CPU. This requires \fB--queue-balance\fP to be specified.
diff --git a/extensions/libxt_NFQUEUE.t b/extensions/libxt_NFQUEUE.t
new file mode 100644
index 0000000..b51b19f
--- /dev/null
+++ b/extensions/libxt_NFQUEUE.t
@@ -0,0 +1,16 @@
+:INPUT,FORWARD,OUTPUT
+-j NFQUEUE;=;OK
+-j NFQUEUE --queue-num 0;=;OK
+-j NFQUEUE --queue-num 65535;=;OK
+-j NFQUEUE --queue-num 65536;;FAIL
+-j NFQUEUE --queue-num -1;;FAIL
+# it says "NFQUEUE: number of total queues is 0", overflow in NFQUEUE_parse_v1?
+# ERROR: cannot load: iptables -A INPUT -j NFQUEUE --queue-balance 0:65535
+# -j NFQUEUE --queue-balance 0:65535;=;OK
+-j NFQUEUE --queue-balance 0:65536;;FAIL
+-j NFQUEUE --queue-balance -1:65535;;FAIL
+-j NFQUEUE --queue-num 10 --queue-bypass;=;OK
+-j NFQUEUE --queue-balance 0:6 --queue-cpu-fanout --queue-bypass;-j NFQUEUE --queue-balance 0:6 --queue-bypass --queue-cpu-fanout;OK
+-j NFQUEUE --queue-bypass --queue-balance 0:6 --queue-cpu-fanout;-j NFQUEUE --queue-balance 0:6 --queue-bypass --queue-cpu-fanout;OK
+-j NFQUEUE --queue-balance 0:6 --queue-bypass;=;OK
+-j NFQUEUE --queue-bypass;-j NFQUEUE --queue-num 0 --queue-bypass;OK
diff --git a/extensions/libxt_NFQUEUE.txlate b/extensions/libxt_NFQUEUE.txlate
new file mode 100644
index 0000000..3d188a7
--- /dev/null
+++ b/extensions/libxt_NFQUEUE.txlate
@@ -0,0 +1,8 @@
+iptables-translate -t nat -A PREROUTING -p tcp --dport 80 -j NFQUEUE --queue-num 30
+nft add rule ip nat PREROUTING tcp dport 80 counter queue num 30
+
+iptables-translate -A FORWARD -j NFQUEUE --queue-num 0 --queue-bypass -p TCP --sport 80
+nft add rule ip filter FORWARD tcp sport 80 counter queue num 0 bypass
+
+iptables-translate -A FORWARD -j NFQUEUE --queue-bypass -p TCP --sport 80 --queue-balance 0:3 --queue-cpu-fanout
+nft add rule ip filter FORWARD tcp sport 80 counter queue num 0-3 bypass,fanout
diff --git a/extensions/libxt_NOTRACK.man b/extensions/libxt_NOTRACK.man
new file mode 100644
index 0000000..4302b93
--- /dev/null
+++ b/extensions/libxt_NOTRACK.man
@@ -0,0 +1,3 @@
+This extension disables connection tracking for all packets matching that rule.
+It is equivalent with \-j CT \-\-notrack. Like CT, NOTRACK can only be used in
+the \fBraw\fP table.
diff --git a/extensions/libxt_NOTRACK.t b/extensions/libxt_NOTRACK.t
new file mode 100644
index 0000000..27c4734
--- /dev/null
+++ b/extensions/libxt_NOTRACK.t
@@ -0,0 +1,3 @@
+:PREROUTING,OUTPUT
+*raw
+-j NOTRACK;=;OK
diff --git a/extensions/libxt_NOTRACK.txlate b/extensions/libxt_NOTRACK.txlate
new file mode 100644
index 0000000..9d35619
--- /dev/null
+++ b/extensions/libxt_NOTRACK.txlate
@@ -0,0 +1,2 @@
+iptables-translate -A PREROUTING -t raw -j NOTRACK
+nft add rule ip raw PREROUTING counter notrack
diff --git a/extensions/libxt_RATEEST.c b/extensions/libxt_RATEEST.c
new file mode 100644
index 0000000..449ceab
--- /dev/null
+++ b/extensions/libxt_RATEEST.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2008-2013 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <xtables.h>
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/xt_RATEEST.h>
+
+struct rateest_tg_udata {
+	unsigned int interval;
+	unsigned int ewma_log;
+};
+
+static void
+RATEEST_help(void)
+{
+	printf(
+"RATEEST target options:\n"
+"  --rateest-name name		Rate estimator name\n"
+"  --rateest-interval sec	Rate measurement interval in seconds\n"
+"  --rateest-ewmalog value	Rate measurement averaging time constant\n");
+}
+
+enum {
+	O_NAME = 0,
+	O_INTERVAL,
+	O_EWMALOG,
+};
+
+#define s struct xt_rateest_target_info
+static const struct xt_option_entry RATEEST_opts[] = {
+	{.name = "rateest-name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, name)},
+	{.name = "rateest-interval", .id = O_INTERVAL, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND},
+	{.name = "rateest-ewmalog", .id = O_EWMALOG, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+/* Copied from iproute */
+#define TIME_UNITS_PER_SEC	1000000
+
+static int
+RATEEST_get_time(unsigned int *time, const char *str)
+{
+	double t;
+	char *p;
+
+	t = strtod(str, &p);
+	if (p == str)
+		return -1;
+
+	if (*p) {
+		if (strcasecmp(p, "s") == 0 || strcasecmp(p, "sec")==0 ||
+		    strcasecmp(p, "secs")==0)
+			t *= TIME_UNITS_PER_SEC;
+		else if (strcasecmp(p, "ms") == 0 || strcasecmp(p, "msec")==0 ||
+			 strcasecmp(p, "msecs") == 0)
+			t *= TIME_UNITS_PER_SEC/1000;
+		else if (strcasecmp(p, "us") == 0 || strcasecmp(p, "usec")==0 ||
+			 strcasecmp(p, "usecs") == 0)
+			t *= TIME_UNITS_PER_SEC/1000000;
+		else
+			return -1;
+	}
+
+	*time = t;
+	return 0;
+}
+
+static void
+RATEEST_print_time(unsigned int time)
+{
+	double tmp = time;
+
+	if (tmp >= TIME_UNITS_PER_SEC)
+		printf(" %.1fs", tmp / TIME_UNITS_PER_SEC);
+	else if (tmp >= TIME_UNITS_PER_SEC/1000)
+		printf(" %.1fms", tmp / (TIME_UNITS_PER_SEC / 1000));
+	else
+		printf(" %uus", time);
+}
+
+static void RATEEST_parse(struct xt_option_call *cb)
+{
+	struct rateest_tg_udata *udata = cb->udata;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_INTERVAL:
+		if (RATEEST_get_time(&udata->interval, cb->arg) < 0)
+			xtables_error(PARAMETER_PROBLEM,
+				   "RATEEST: bad interval value \"%s\"",
+				   cb->arg);
+		break;
+	case O_EWMALOG:
+		if (RATEEST_get_time(&udata->ewma_log, cb->arg) < 0)
+			xtables_error(PARAMETER_PROBLEM,
+				   "RATEEST: bad ewmalog value \"%s\"",
+				   cb->arg);
+		break;
+	}
+}
+
+static void RATEEST_final_check(struct xt_fcheck_call *cb)
+{
+	struct xt_rateest_target_info *info = cb->data;
+	struct rateest_tg_udata *udata = cb->udata;
+
+	for (info->interval = 0; info->interval <= 5; info->interval++) {
+		if (udata->interval <= (1 << info->interval) * (TIME_UNITS_PER_SEC / 4))
+			break;
+	}
+
+	if (info->interval > 5)
+		xtables_error(PARAMETER_PROBLEM,
+			   "RATEEST: interval value is too large");
+	info->interval -= 2;
+
+	for (info->ewma_log = 1; info->ewma_log < 32; info->ewma_log++) {
+		double w = 1.0 - 1.0 / (1 << info->ewma_log);
+		if (udata->interval / (-log(w)) > udata->ewma_log)
+			break;
+	}
+	info->ewma_log--;
+
+	if (info->ewma_log == 0 || info->ewma_log >= 31)
+		xtables_error(PARAMETER_PROBLEM,
+			   "RATEEST: ewmalog value is out of range");
+}
+
+static void
+__RATEEST_print(const struct xt_entry_target *target, const char *prefix)
+{
+	const struct xt_rateest_target_info *info = (const void *)target->data;
+	unsigned int local_interval;
+	unsigned int local_ewma_log;
+
+	local_interval = (TIME_UNITS_PER_SEC << (info->interval + 2)) / 4;
+	local_ewma_log = local_interval * (1 << (info->ewma_log));
+
+	printf(" %sname %s", prefix, info->name);
+	printf(" %sinterval", prefix);
+	RATEEST_print_time(local_interval);
+	printf(" %sewmalog", prefix);
+	RATEEST_print_time(local_ewma_log);
+}
+
+static void
+RATEEST_print(const void *ip, const struct xt_entry_target *target,
+	      int numeric)
+{
+	__RATEEST_print(target, "");
+}
+
+static void
+RATEEST_save(const void *ip, const struct xt_entry_target *target)
+{
+	__RATEEST_print(target, "--rateest-");
+}
+
+static struct xtables_target rateest_tg_reg = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "RATEEST",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_rateest_target_info)),
+	.userspacesize	= offsetof(struct xt_rateest_target_info, est),
+	.help		= RATEEST_help,
+	.x6_parse	= RATEEST_parse,
+	.x6_fcheck	= RATEEST_final_check,
+	.print		= RATEEST_print,
+	.save		= RATEEST_save,
+	.x6_options	= RATEEST_opts,
+	.udata_size	= sizeof(struct rateest_tg_udata),
+};
+
+void _init(void)
+{
+	xtables_register_target(&rateest_tg_reg);
+}
diff --git a/extensions/libxt_RATEEST.man b/extensions/libxt_RATEEST.man
new file mode 100644
index 0000000..37de759
--- /dev/null
+++ b/extensions/libxt_RATEEST.man
@@ -0,0 +1,12 @@
+The RATEEST target collects statistics, performs rate estimation calculation
+and saves the results for later evaluation using the \fBrateest\fP match.
+.TP
+\fB\-\-rateest\-name\fP \fIname\fP
+Count matched packets into the pool referred to by \fIname\fP, which is freely
+choosable.
+.TP
+\fB\-\-rateest\-interval\fP \fIamount\fP{\fBs\fP|\fBms\fP|\fBus\fP}
+Rate measurement interval, in seconds, milliseconds or microseconds.
+.TP
+\fB\-\-rateest\-ewmalog\fP \fIvalue\fP
+Rate measurement averaging time constant.
diff --git a/extensions/libxt_RATEEST.t b/extensions/libxt_RATEEST.t
new file mode 100644
index 0000000..c2b6bb3
--- /dev/null
+++ b/extensions/libxt_RATEEST.t
@@ -0,0 +1,2 @@
+:INPUT,FORWARD,OUTPUT
+-j RATEEST --rateest-name RE1 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms;=;OK
diff --git a/extensions/libxt_REDIRECT.man b/extensions/libxt_REDIRECT.man
new file mode 100644
index 0000000..28d4d10
--- /dev/null
+++ b/extensions/libxt_REDIRECT.man
@@ -0,0 +1,25 @@
+This target is only valid in the
+.B nat
+table, in the
+.B PREROUTING
+and
+.B OUTPUT
+chains, and user-defined chains which are only called from those
+chains.  It redirects the packet to the machine itself by changing the
+destination IP to the primary address of the incoming interface
+(locally-generated packets are mapped to the localhost address,
+127.0.0.1 for IPv4 and ::1 for IPv6, and packets arriving on
+interfaces that don't have an IP address configured are dropped).
+.TP
+\fB\-\-to\-ports\fP \fIport\fP[\fB\-\fP\fIport\fP]
+This specifies a destination port or range of ports to use: without
+this, the destination port is never altered.  This is only valid
+if the rule also specifies one of the following protocols:
+\fBtcp\fP, \fBudp\fP, \fBdccp\fP or \fBsctp\fP.
+.TP
+\fB\-\-random\fP
+If option
+\fB\-\-random\fP
+is used then port mapping will be randomized (kernel >= 2.6.22).
+.TP
+IPv6 support available starting Linux kernels >= 3.7.
diff --git a/extensions/libxt_SECMARK.c b/extensions/libxt_SECMARK.c
new file mode 100644
index 0000000..6ba8606
--- /dev/null
+++ b/extensions/libxt_SECMARK.c
@@ -0,0 +1,88 @@
+/*
+ * Shared library add-on to iptables to add SECMARK target support.
+ *
+ * Based on the MARK target.
+ *
+ * Copyright (C) 2006 Red Hat, Inc., James Morris <jmorris@redhat.com>
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_SECMARK.h>
+
+#define PFX "SECMARK target: "
+
+enum {
+	O_SELCTX = 0,
+};
+
+static void SECMARK_help(void)
+{
+	printf(
+"SECMARK target options:\n"
+"  --selctx value                     Set the SELinux security context\n");
+}
+
+static const struct xt_option_entry SECMARK_opts[] = {
+	{.name = "selctx", .id = O_SELCTX, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_secmark_target_info, secctx)},
+	XTOPT_TABLEEND,
+};
+
+static void SECMARK_parse(struct xt_option_call *cb)
+{
+	struct xt_secmark_target_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	info->mode = SECMARK_MODE_SEL;
+}
+
+static void print_secmark(const struct xt_secmark_target_info *info)
+{
+	switch (info->mode) {
+	case SECMARK_MODE_SEL:
+		printf("selctx %s", info->secctx);
+		break;
+	
+	default:
+		xtables_error(OTHER_PROBLEM, PFX "invalid mode %hhu\n", info->mode);
+	}
+}
+
+static void SECMARK_print(const void *ip, const struct xt_entry_target *target,
+                          int numeric)
+{
+	const struct xt_secmark_target_info *info =
+		(struct xt_secmark_target_info*)(target)->data;
+
+	printf(" SECMARK ");
+	print_secmark(info);
+}
+
+static void SECMARK_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_secmark_target_info *info =
+		(struct xt_secmark_target_info*)target->data;
+
+	printf(" --");
+	print_secmark(info);
+}
+
+static struct xtables_target secmark_target = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "SECMARK",
+	.version	= XTABLES_VERSION,
+	.revision	= 0,
+	.size		= XT_ALIGN(sizeof(struct xt_secmark_target_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_secmark_target_info)),
+	.help		= SECMARK_help,
+	.print		= SECMARK_print,
+	.save		= SECMARK_save,
+	.x6_parse	= SECMARK_parse,
+	.x6_options	= SECMARK_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&secmark_target);
+}
diff --git a/extensions/libxt_SECMARK.man b/extensions/libxt_SECMARK.man
new file mode 100644
index 0000000..d0e6fd6
--- /dev/null
+++ b/extensions/libxt_SECMARK.man
@@ -0,0 +1,10 @@
+This is used to set the security mark value associated with the
+packet for use by security subsystems such as SELinux.  It is
+valid in the
+.B security
+table (for backwards compatibility with older kernels, it is also
+valid in the
+.B mangle
+table). The mark is 32 bits wide.
+.TP
+\fB\-\-selctx\fP \fIsecurity_context\fP
diff --git a/extensions/libxt_SET.c b/extensions/libxt_SET.c
new file mode 100644
index 0000000..2a7640a
--- /dev/null
+++ b/extensions/libxt_SET.c
@@ -0,0 +1,583 @@
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ *                         Patrick Schaaf <bof@bof.de>
+ *                         Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 2003-2010 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* Shared library add-on to iptables to add IP set mangling target. */
+#include <stdbool.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <ctype.h>
+
+#include <xtables.h>
+#include <linux/netfilter/xt_set.h>
+#include "libxt_set.h"
+
+/* Revision 0 */
+
+static void
+set_target_help_v0(void)
+{
+	printf("SET target options:\n"
+	       " --add-set name flags\n"
+	       " --del-set name flags\n"
+	       "		add/del src/dst IP/port from/to named sets,\n"
+	       "		where flags are the comma separated list of\n"
+	       "		'src' and 'dst' specifications.\n");
+}
+
+static const struct option set_target_opts_v0[] = {
+	{.name = "add-set", .has_arg = true, .val = '1'},
+	{.name = "del-set", .has_arg = true, .val = '2'},
+	XT_GETOPT_TABLEEND,
+};
+
+static void
+set_target_check_v0(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			   "You must specify either `--add-set' or `--del-set'");
+}
+
+static void
+set_target_init_v0(struct xt_entry_target *target)
+{
+	struct xt_set_info_target_v0 *info =
+		(struct xt_set_info_target_v0 *) target->data;
+
+	info->add_set.index =
+	info->del_set.index = IPSET_INVALID_ID;
+
+}
+
+static void
+parse_target_v0(char **argv, int invert, unsigned int *flags,
+		struct xt_set_info_v0 *info, const char *what)
+{
+	if (info->u.flags[0])
+		xtables_error(PARAMETER_PROBLEM,
+			      "--%s can be specified only once", what);
+
+	if (!argv[optind]
+	    || argv[optind][0] == '-' || argv[optind][0] == '!')
+		xtables_error(PARAMETER_PROBLEM,
+			      "--%s requires two args.", what);
+
+	if (strlen(optarg) > IPSET_MAXNAMELEN - 1)
+		xtables_error(PARAMETER_PROBLEM,
+			      "setname `%s' too long, max %d characters.",
+			      optarg, IPSET_MAXNAMELEN - 1);
+
+	get_set_byname(optarg, (struct xt_set_info *)info);
+	parse_dirs_v0(argv[optind], info);
+	optind++;
+
+	*flags = 1;
+}
+
+static int
+set_target_parse_v0(int c, char **argv, int invert, unsigned int *flags,
+		    const void *entry, struct xt_entry_target **target)
+{
+	struct xt_set_info_target_v0 *myinfo =
+		(struct xt_set_info_target_v0 *) (*target)->data;
+
+	switch (c) {
+	case '1':		/* --add-set <set> <flags> */
+		parse_target_v0(argv, invert, flags,
+				&myinfo->add_set, "add-set");
+		break;
+	case '2':		/* --del-set <set>[:<flags>] <flags> */
+		parse_target_v0(argv, invert, flags,
+				&myinfo->del_set, "del-set");
+		break;
+	}
+	return 1;
+}
+
+static void
+print_target_v0(const char *prefix, const struct xt_set_info_v0 *info)
+{
+	int i;
+	char setname[IPSET_MAXNAMELEN];
+
+	if (info->index == IPSET_INVALID_ID)
+		return;
+	get_set_byid(setname, info->index);
+	printf(" %s %s", prefix, setname);
+	for (i = 0; i < IPSET_DIM_MAX; i++) {
+		if (!info->u.flags[i])
+			break;
+		printf("%s%s",
+		       i == 0 ? " " : ",",
+		       info->u.flags[i] & IPSET_SRC ? "src" : "dst");
+	}
+}
+
+static void
+set_target_print_v0(const void *ip, const struct xt_entry_target *target,
+		    int numeric)
+{
+	const struct xt_set_info_target_v0 *info = (const void *)target->data;
+
+	print_target_v0("add-set", &info->add_set);
+	print_target_v0("del-set", &info->del_set);
+}
+
+static void
+set_target_save_v0(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_set_info_target_v0 *info = (const void *)target->data;
+
+	print_target_v0("--add-set", &info->add_set);
+	print_target_v0("--del-set", &info->del_set);
+}
+
+/* Revision 1 */
+static void
+set_target_init_v1(struct xt_entry_target *target)
+{
+	struct xt_set_info_target_v1 *info =
+		(struct xt_set_info_target_v1 *) target->data;
+
+	info->add_set.index =
+	info->del_set.index = IPSET_INVALID_ID;
+
+}
+
+#define SET_TARGET_ADD		0x1
+#define SET_TARGET_DEL		0x2
+#define SET_TARGET_EXIST	0x4
+#define SET_TARGET_TIMEOUT	0x8
+#define SET_TARGET_MAP		0x10
+#define SET_TARGET_MAP_MARK	0x20
+#define SET_TARGET_MAP_PRIO	0x40
+#define SET_TARGET_MAP_QUEUE	0x80
+
+static void
+parse_target(char **argv, int invert, struct xt_set_info *info,
+	     const char *what)
+{
+	if (info->dim)
+		xtables_error(PARAMETER_PROBLEM,
+			      "--%s can be specified only once", what);
+	if (!argv[optind]
+	    || argv[optind][0] == '-' || argv[optind][0] == '!')
+		xtables_error(PARAMETER_PROBLEM,
+			      "--%s requires two args.", what);
+
+	if (strlen(optarg) > IPSET_MAXNAMELEN - 1)
+		xtables_error(PARAMETER_PROBLEM,
+			      "setname `%s' too long, max %d characters.",
+			      optarg, IPSET_MAXNAMELEN - 1);
+
+	get_set_byname(optarg, info);
+	parse_dirs(argv[optind], info);
+	optind++;
+}
+
+static int
+set_target_parse_v1(int c, char **argv, int invert, unsigned int *flags,
+		    const void *entry, struct xt_entry_target **target)
+{
+	struct xt_set_info_target_v1 *myinfo =
+		(struct xt_set_info_target_v1 *) (*target)->data;
+
+	switch (c) {
+	case '1':		/* --add-set <set> <flags> */
+		parse_target(argv, invert, &myinfo->add_set, "add-set");
+		*flags |= SET_TARGET_ADD;
+		break;
+	case '2':		/* --del-set <set>[:<flags>] <flags> */
+		parse_target(argv, invert, &myinfo->del_set, "del-set");
+		*flags |= SET_TARGET_DEL;
+		break;
+	}
+	return 1;
+}
+
+static void
+print_target(const char *prefix, const struct xt_set_info *info)
+{
+	int i;
+	char setname[IPSET_MAXNAMELEN];
+
+	if (info->index == IPSET_INVALID_ID)
+		return;
+	get_set_byid(setname, info->index);
+	printf(" %s %s", prefix, setname);
+	for (i = 1; i <= info->dim; i++) {
+		printf("%s%s",
+		       i == 1 ? " " : ",",
+		       info->flags & (1 << i) ? "src" : "dst");
+	}
+}
+
+static void
+set_target_print_v1(const void *ip, const struct xt_entry_target *target,
+		    int numeric)
+{
+	const struct xt_set_info_target_v1 *info = (const void *)target->data;
+
+	print_target("add-set", &info->add_set);
+	print_target("del-set", &info->del_set);
+}
+
+static void
+set_target_save_v1(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_set_info_target_v1 *info = (const void *)target->data;
+
+	print_target("--add-set", &info->add_set);
+	print_target("--del-set", &info->del_set);
+}
+
+/* Revision 2 */
+
+static void
+set_target_help_v2(void)
+{
+	printf("SET target options:\n"
+	       " --add-set name flags [--exist] [--timeout n]\n"
+	       " --del-set name flags\n"
+	       "		add/del src/dst IP/port from/to named sets,\n"
+	       "		where flags are the comma separated list of\n"
+	       "		'src' and 'dst' specifications.\n");
+}
+
+static const struct option set_target_opts_v2[] = {
+	{.name = "add-set", .has_arg = true,  .val = '1'},
+	{.name = "del-set", .has_arg = true,  .val = '2'},
+	{.name = "exist",   .has_arg = false, .val = '3'},
+	{.name = "timeout", .has_arg = true,  .val = '4'},
+	XT_GETOPT_TABLEEND,
+};
+
+static void
+set_target_check_v2(unsigned int flags)
+{
+	if (!(flags & (SET_TARGET_ADD|SET_TARGET_DEL)))
+		xtables_error(PARAMETER_PROBLEM,
+			   "You must specify either `--add-set' or `--del-set'");
+	if (!(flags & SET_TARGET_ADD)) {
+		if (flags & SET_TARGET_EXIST)
+			xtables_error(PARAMETER_PROBLEM,
+				"Flag `--exist' can be used with `--add-set' only");
+		if (flags & SET_TARGET_TIMEOUT)
+			xtables_error(PARAMETER_PROBLEM,
+				"Option `--timeout' can be used with `--add-set' only");
+	}
+}
+
+
+static void
+set_target_init_v2(struct xt_entry_target *target)
+{
+	struct xt_set_info_target_v2 *info =
+		(struct xt_set_info_target_v2 *) target->data;
+
+	info->add_set.index =
+	info->del_set.index = IPSET_INVALID_ID;
+	info->timeout = UINT32_MAX;
+}
+
+static int
+set_target_parse_v2(int c, char **argv, int invert, unsigned int *flags,
+		    const void *entry, struct xt_entry_target **target)
+{
+	struct xt_set_info_target_v2 *myinfo =
+		(struct xt_set_info_target_v2 *) (*target)->data;
+	unsigned int timeout;
+
+	switch (c) {
+	case '1':		/* --add-set <set> <flags> */
+		parse_target(argv, invert, &myinfo->add_set, "add-set");
+		*flags |= SET_TARGET_ADD;
+		break;
+	case '2':		/* --del-set <set>[:<flags>] <flags> */
+		parse_target(argv, invert, &myinfo->del_set, "del-set");
+		*flags |= SET_TARGET_DEL;
+		break;
+	case '3':
+		myinfo->flags |= IPSET_FLAG_EXIST;
+		*flags |= SET_TARGET_EXIST;
+		break;
+	case '4':
+		if (!xtables_strtoui(optarg, NULL, &timeout, 0, UINT32_MAX - 1))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid value for option --timeout "
+				      "or out of range 0-%u", UINT32_MAX - 1);
+		myinfo->timeout = timeout;
+		*flags |= SET_TARGET_TIMEOUT;
+		break;
+	}
+	return 1;
+}
+
+static void
+set_target_print_v2(const void *ip, const struct xt_entry_target *target,
+		    int numeric)
+{
+	const struct xt_set_info_target_v2 *info = (const void *)target->data;
+
+	print_target("add-set", &info->add_set);
+	if (info->flags & IPSET_FLAG_EXIST)
+		printf(" exist");
+	if (info->timeout != UINT32_MAX)
+		printf(" timeout %u", info->timeout);
+	print_target("del-set", &info->del_set);
+}
+
+static void
+set_target_save_v2(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_set_info_target_v2 *info = (const void *)target->data;
+
+	print_target("--add-set", &info->add_set);
+	if (info->flags & IPSET_FLAG_EXIST)
+		printf(" --exist");
+	if (info->timeout != UINT32_MAX)
+		printf(" --timeout %u", info->timeout);
+	print_target("--del-set", &info->del_set);
+}
+
+
+/* Revision 3 */
+
+static void
+set_target_help_v3(void)
+{
+	printf("SET target options:\n"
+	       " --add-set name flags [--exist] [--timeout n]\n"
+	       " --del-set name flags\n"
+	       " --map-set name flags"
+	       " [--map-mark] [--map-prio] [--map-queue]\n"
+	       "		add/del src/dst IP/port from/to named sets,\n"
+	       "		where flags are the comma separated list of\n"
+	       "		'src' and 'dst' specifications.\n");
+}
+
+static const struct option set_target_opts_v3[] = {
+	{.name = "add-set",	.has_arg = true,  .val = '1'},
+	{.name = "del-set",	.has_arg = true,  .val = '2'},
+	{.name = "exist",	.has_arg = false, .val = '3'},
+	{.name = "timeout",	.has_arg = true,  .val = '4'},
+	{.name = "map-set",	.has_arg = true,  .val = '5'},
+	{.name = "map-mark",	.has_arg = false, .val = '6'},
+	{.name = "map-prio",	.has_arg = false, .val = '7'},
+	{.name = "map-queue",	.has_arg = false, .val = '8'},
+	XT_GETOPT_TABLEEND,
+};
+
+static void
+set_target_check_v3(unsigned int flags)
+{
+	if (!(flags & (SET_TARGET_ADD|SET_TARGET_DEL|SET_TARGET_MAP)))
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify either `--add-set' or "
+			      "`--del-set' or `--map-set'");
+	if (!(flags & SET_TARGET_ADD)) {
+		if (flags & SET_TARGET_EXIST)
+			xtables_error(PARAMETER_PROBLEM,
+				"Flag `--exist' can be used with `--add-set' only");
+		if (flags & SET_TARGET_TIMEOUT)
+			xtables_error(PARAMETER_PROBLEM,
+				"Option `--timeout' can be used with `--add-set' only");
+	}
+	if (!(flags & SET_TARGET_MAP)) {
+		if (flags & SET_TARGET_MAP_MARK)
+			xtables_error(PARAMETER_PROBLEM,
+				"Flag `--map-mark' can be used with `--map-set' only");
+		if (flags & SET_TARGET_MAP_PRIO)
+			xtables_error(PARAMETER_PROBLEM,
+				"Flag `--map-prio' can be used with `--map-set' only");
+		if (flags & SET_TARGET_MAP_QUEUE)
+			xtables_error(PARAMETER_PROBLEM,
+				"Flag `--map-queue' can be used with `--map-set' only");
+	}
+	if ((flags & SET_TARGET_MAP) && !(flags & (SET_TARGET_MAP_MARK |
+						   SET_TARGET_MAP_PRIO |
+						   SET_TARGET_MAP_QUEUE)))
+		xtables_error(PARAMETER_PROBLEM,
+			"You must specify flags `--map-mark' or "
+			"'--map-prio` or `--map-queue'");
+}
+
+static void
+set_target_init_v3(struct xt_entry_target *target)
+{
+	struct xt_set_info_target_v3 *info =
+		(struct xt_set_info_target_v3 *) target->data;
+
+	info->add_set.index =
+	info->del_set.index =
+	info->map_set.index = IPSET_INVALID_ID;
+	info->timeout = UINT32_MAX;
+}
+
+static int
+set_target_parse_v3(int c, char **argv, int invert, unsigned int *flags,
+		    const void *entry, struct xt_entry_target **target)
+{
+	struct xt_set_info_target_v3 *myinfo =
+		(struct xt_set_info_target_v3 *) (*target)->data;
+	unsigned int timeout;
+
+	switch (c) {
+	case '1':		/* --add-set <set> <flags> */
+		parse_target(argv, invert, &myinfo->add_set, "add-set");
+		*flags |= SET_TARGET_ADD;
+		break;
+	case '2':		/* --del-set <set>[:<flags>] <flags> */
+		parse_target(argv, invert, &myinfo->del_set, "del-set");
+		*flags |= SET_TARGET_DEL;
+		break;
+	case '3':
+		myinfo->flags |= IPSET_FLAG_EXIST;
+		*flags |= SET_TARGET_EXIST;
+		break;
+	case '4':
+		if (!xtables_strtoui(optarg, NULL, &timeout, 0, UINT32_MAX - 1))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid value for option --timeout "
+				      "or out of range 0-%u", UINT32_MAX - 1);
+		myinfo->timeout = timeout;
+		*flags |= SET_TARGET_TIMEOUT;
+		break;
+	case '5':		/* --map-set <set> <flags> */
+		parse_target(argv, invert, &myinfo->map_set, "map-set");
+		*flags |= SET_TARGET_MAP;
+		break;
+	case '6':
+		myinfo->flags |= IPSET_FLAG_MAP_SKBMARK;
+		*flags |= SET_TARGET_MAP_MARK;
+		break;
+	case '7':
+		myinfo->flags |= IPSET_FLAG_MAP_SKBPRIO;
+		*flags |= SET_TARGET_MAP_PRIO;
+		break;
+	case '8':
+		myinfo->flags |= IPSET_FLAG_MAP_SKBQUEUE;
+		*flags |= SET_TARGET_MAP_QUEUE;
+		break;
+	}
+	return 1;
+}
+
+static void
+set_target_print_v3(const void *ip, const struct xt_entry_target *target,
+		    int numeric)
+{
+	const struct xt_set_info_target_v3 *info = (const void *)target->data;
+
+	print_target("add-set", &info->add_set);
+	if (info->flags & IPSET_FLAG_EXIST)
+		printf(" exist");
+	if (info->timeout != UINT32_MAX)
+		printf(" timeout %u", info->timeout);
+	print_target("del-set", &info->del_set);
+	print_target("map-set", &info->map_set);
+	if (info->flags & IPSET_FLAG_MAP_SKBMARK)
+		printf(" map-mark");
+	if (info->flags & IPSET_FLAG_MAP_SKBPRIO)
+		printf(" map-prio");
+	if (info->flags & IPSET_FLAG_MAP_SKBQUEUE)
+		printf(" map-queue");
+}
+
+static void
+set_target_save_v3(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_set_info_target_v3 *info = (const void *)target->data;
+
+	print_target("--add-set", &info->add_set);
+	if (info->flags & IPSET_FLAG_EXIST)
+		printf(" --exist");
+	if (info->timeout != UINT32_MAX)
+		printf(" --timeout %u", info->timeout);
+	print_target("--del-set", &info->del_set);
+	print_target("--map-set", &info->map_set);
+	if (info->flags & IPSET_FLAG_MAP_SKBMARK)
+		printf(" --map-mark");
+	if (info->flags & IPSET_FLAG_MAP_SKBPRIO)
+		printf(" --map-prio");
+	if (info->flags & IPSET_FLAG_MAP_SKBQUEUE)
+		printf(" --map-queue");
+}
+
+static struct xtables_target set_tg_reg[] = {
+	{
+		.name		= "SET",
+		.revision	= 0,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV4,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_target_v0)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_target_v0)),
+		.help		= set_target_help_v0,
+		.init		= set_target_init_v0,
+		.parse		= set_target_parse_v0,
+		.final_check	= set_target_check_v0,
+		.print		= set_target_print_v0,
+		.save		= set_target_save_v0,
+		.extra_opts	= set_target_opts_v0,
+	},
+	{
+		.name		= "SET",
+		.revision	= 1,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_target_v1)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_target_v1)),
+		.help		= set_target_help_v0,
+		.init		= set_target_init_v1,
+		.parse		= set_target_parse_v1,
+		.final_check	= set_target_check_v0,
+		.print		= set_target_print_v1,
+		.save		= set_target_save_v1,
+		.extra_opts	= set_target_opts_v0,
+	},
+	{
+		.name		= "SET",
+		.revision	= 2,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_target_v2)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_target_v2)),
+		.help		= set_target_help_v2,
+		.init		= set_target_init_v2,
+		.parse		= set_target_parse_v2,
+		.final_check	= set_target_check_v2,
+		.print		= set_target_print_v2,
+		.save		= set_target_save_v2,
+		.extra_opts	= set_target_opts_v2,
+	},
+	{
+		.name		= "SET",
+		.revision	= 3,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_target_v3)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_target_v3)),
+		.help		= set_target_help_v3,
+		.init		= set_target_init_v3,
+		.parse		= set_target_parse_v3,
+		.final_check	= set_target_check_v3,
+		.print		= set_target_print_v3,
+		.save		= set_target_save_v3,
+		.extra_opts	= set_target_opts_v3,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(set_tg_reg, ARRAY_SIZE(set_tg_reg));
+}
diff --git a/extensions/libxt_SET.man b/extensions/libxt_SET.man
new file mode 100644
index 0000000..c471337
--- /dev/null
+++ b/extensions/libxt_SET.man
@@ -0,0 +1,46 @@
+This module adds and/or deletes entries from IP sets which can be defined
+by ipset(8).
+.TP
+\fB\-\-add\-set\fP \fIsetname\fP \fIflag\fP[\fB,\fP\fIflag\fP...]
+add the address(es)/port(s) of the packet to the set
+.TP
+\fB\-\-del\-set\fP \fIsetname\fP \fIflag\fP[\fB,\fP\fIflag\fP...]
+delete the address(es)/port(s) of the packet from the set
+.TP
+\fB\-\-map\-set\fP \fIsetname\fP \fIflag\fP[\fB,\fP\fIflag\fP...] 
+[\-\-map\-mark] [\-\-map\-prio] [\-\-map\-queue]
+map packet properties (firewall mark, tc priority, hardware queue)
+.IP
+where \fIflag\fP(s) are
+.BR "src"
+and/or
+.BR "dst"
+specifications and there can be no more than six of them.
+.TP
+\fB\-\-timeout\fP \fIvalue\fP
+when adding an entry, the timeout value to use instead of the default
+one from the set definition
+.TP
+\fB\-\-exist\fP
+when adding an entry if it already exists, reset the timeout value
+to the specified one or to the default from the set definition
+.TP
+\fB\-\-map\-set\fP \fIset\-name\fP
+the set-name should be created with --skbinfo option
+\fB\-\-map\-mark\fP
+map firewall mark to packet by lookup of value in the set
+\fB\-\-map\-prio\fP
+map traffic control priority to packet by lookup of value in the set
+\fB\-\-map\-queue\fP
+map hardware NIC queue to packet by lookup of value in the set
+.IP
+The
+\fB\-\-map\-set\fP
+option can be used from the mangle table only. The
+\fB\-\-map\-prio\fP
+and
+\fB\-\-map\-queue\fP
+flags can be used in the OUTPUT, FORWARD and POSTROUTING chains.
+.PP
+Use of \-j SET requires that ipset kernel support is provided, which, for
+standard kernels, is the case since Linux 2.6.39.
diff --git a/extensions/libxt_SET.t b/extensions/libxt_SET.t
new file mode 100644
index 0000000..30c27ca
--- /dev/null
+++ b/extensions/libxt_SET.t
@@ -0,0 +1,3 @@
+:INPUT,FORWARD,OUTPUT
+# fails: foo does not exist
+-j SET --add-set foo src,dst;;FAIL
diff --git a/extensions/libxt_SNAT.man b/extensions/libxt_SNAT.man
new file mode 100644
index 0000000..8cd0b80
--- /dev/null
+++ b/extensions/libxt_SNAT.man
@@ -0,0 +1,50 @@
+This target is only valid in the
+.B nat
+table, in the
+.B POSTROUTING
+and
+.B INPUT
+chains, and user-defined chains which are only called from those
+chains.  It specifies that the source address of the packet should be
+modified (and all future packets in this connection will also be
+mangled), and rules should cease being examined.  It takes the
+following options:
+.TP
+\fB\-\-to\-source\fP [\fIipaddr\fP[\fB\-\fP\fIipaddr\fP]][\fB:\fP\fIport\fP[\fB\-\fP\fIport\fP]]
+which can specify a single new source IP address, an inclusive range
+of IP addresses. Optionally a port range,
+if the rule also specifies one of the following protocols:
+\fBtcp\fP, \fBudp\fP, \fBdccp\fP or \fBsctp\fP.
+If no port range is specified, then source ports below 512 will be
+mapped to other ports below 512: those between 512 and 1023 inclusive
+will be mapped to ports below 1024, and other ports will be mapped to
+1024 or above. Where possible, no port alteration will occur.
+In Kernels up to 2.6.10, you can add several \-\-to\-source options. For those
+kernels, if you specify more than one source address, either via an address
+range or multiple \-\-to\-source options, a simple round-robin (one after another
+in cycle) takes place between these addresses.
+Later Kernels (>= 2.6.11-rc1) don't have the ability to NAT to multiple ranges
+anymore.
+.TP
+\fB\-\-random\fP
+If option
+\fB\-\-random\fP
+is used then port mapping will be randomized through a hash-based algorithm (kernel >= 2.6.21).
+.TP
+\fB\-\-random-fully\fP
+If option
+\fB\-\-random-fully\fP
+is used then port mapping will be fully randomized through a PRNG (kernel >= 3.14).
+.TP
+\fB\-\-persistent\fP
+Gives a client the same source-/destination-address for each connection.
+This supersedes the SAME target. Support for persistent mappings is available
+from 2.6.29-rc2.
+.PP
+Kernels prior to 2.6.36-rc1 don't have the ability to
+.B SNAT
+in the
+.B INPUT
+chain.
+.TP
+IPv6 support available since Linux kernels >= 3.7.
diff --git a/extensions/libxt_SYNPROXY.c b/extensions/libxt_SYNPROXY.c
new file mode 100644
index 0000000..6a0b913
--- /dev/null
+++ b/extensions/libxt_SYNPROXY.c
@@ -0,0 +1,150 @@
+
+/*
+ * Copyright (c) 2013 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_SYNPROXY.h>
+
+enum {
+	O_SACK_PERM = 0,
+	O_TIMESTAMP,
+	O_WSCALE,
+	O_MSS,
+	O_ECN,
+};
+
+static void SYNPROXY_help(void)
+{
+	printf(
+"SYNPROXY target options:\n"
+"  --sack-perm                        Set SACK_PERM\n"
+"  --timestamp                        Set TIMESTAMP\n"
+"  --wscale value                     Set window scaling factor\n"
+"  --mss value                        Set MSS value\n"
+"  --ecn                              Set ECN\n");
+}
+
+static const struct xt_option_entry SYNPROXY_opts[] = {
+	{.name = "sack-perm", .id = O_SACK_PERM, .type = XTTYPE_NONE, },
+	{.name = "timestamp", .id = O_TIMESTAMP, .type = XTTYPE_NONE, },
+	{.name = "wscale",    .id = O_WSCALE,    .type = XTTYPE_UINT32, },
+	{.name = "mss",       .id = O_MSS,       .type = XTTYPE_UINT32, },
+	{.name = "ecn",       .id = O_ECN,	 .type = XTTYPE_NONE, },
+	XTOPT_TABLEEND,
+};
+
+static void SYNPROXY_parse(struct xt_option_call *cb)
+{
+	struct xt_synproxy_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SACK_PERM:
+		info->options |= XT_SYNPROXY_OPT_SACK_PERM;
+		break;
+	case O_TIMESTAMP:
+		info->options |= XT_SYNPROXY_OPT_TIMESTAMP;
+		break;
+	case O_WSCALE:
+		info->options |= XT_SYNPROXY_OPT_WSCALE;
+		info->wscale = cb->val.u32;
+		break;
+	case O_MSS:
+		info->options |= XT_SYNPROXY_OPT_MSS;
+		info->mss = cb->val.u32;
+		break;
+	case O_ECN:
+		info->options |= XT_SYNPROXY_OPT_ECN;
+		break;
+	}
+}
+
+static void SYNPROXY_check(struct xt_fcheck_call *cb)
+{
+}
+
+static void SYNPROXY_print(const void *ip, const struct xt_entry_target *target,
+                           int numeric)
+{
+	const struct xt_synproxy_info *info =
+		(const struct xt_synproxy_info *)target->data;
+
+	printf(" SYNPROXY ");
+	if (info->options & XT_SYNPROXY_OPT_SACK_PERM)
+		printf("sack-perm ");
+	if (info->options & XT_SYNPROXY_OPT_TIMESTAMP)
+		printf("timestamp ");
+	if (info->options & XT_SYNPROXY_OPT_WSCALE)
+		printf("wscale %u ", info->wscale);
+	if (info->options & XT_SYNPROXY_OPT_MSS)
+		printf("mss %u ", info->mss);
+	if (info->options & XT_SYNPROXY_OPT_ECN)
+		printf("ecn ");
+}
+
+static void SYNPROXY_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_synproxy_info *info =
+		(const struct xt_synproxy_info *)target->data;
+
+	if (info->options & XT_SYNPROXY_OPT_SACK_PERM)
+		printf(" --sack-perm");
+	if (info->options & XT_SYNPROXY_OPT_TIMESTAMP)
+		printf(" --timestamp");
+	if (info->options & XT_SYNPROXY_OPT_WSCALE)
+		printf(" --wscale %u", info->wscale);
+	if (info->options & XT_SYNPROXY_OPT_MSS)
+		printf(" --mss %u", info->mss);
+	if (info->options & XT_SYNPROXY_OPT_ECN)
+		printf(" --ecn");
+}
+
+static int SYNPROXY_xlate(struct xt_xlate *xl,
+		          const struct xt_xlate_tg_params *params)
+{
+	const struct xt_synproxy_info *info =
+		(const struct xt_synproxy_info *)params->target->data;
+
+	xt_xlate_add(xl, "synproxy ");
+
+	if (info->options & XT_SYNPROXY_OPT_SACK_PERM)
+		xt_xlate_add(xl, "sack-perm ");
+	if (info->options & XT_SYNPROXY_OPT_TIMESTAMP)
+		xt_xlate_add(xl, "timestamp ");
+	if (info->options & XT_SYNPROXY_OPT_WSCALE)
+		xt_xlate_add(xl, "wscale %u ", info->wscale);
+	if (info->options & XT_SYNPROXY_OPT_MSS)
+		xt_xlate_add(xl, "mss %u ", info->mss);
+	if (info->options & XT_SYNPROXY_OPT_ECN)
+		xt_xlate_add(xl, "ecn ");
+
+	return 1;
+}
+
+static struct xtables_target synproxy_tg_reg = {
+	.family        = NFPROTO_UNSPEC,
+	.name          = "SYNPROXY",
+	.version       = XTABLES_VERSION,
+	.revision      = 0,
+	.size          = XT_ALIGN(sizeof(struct xt_synproxy_info)),
+	.userspacesize = XT_ALIGN(sizeof(struct xt_synproxy_info)),
+	.help          = SYNPROXY_help,
+	.print         = SYNPROXY_print,
+	.save          = SYNPROXY_save,
+	.x6_parse      = SYNPROXY_parse,
+	.x6_fcheck     = SYNPROXY_check,
+	.x6_options    = SYNPROXY_opts,
+	.xlate         = SYNPROXY_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&synproxy_tg_reg);
+}
diff --git a/extensions/libxt_SYNPROXY.man b/extensions/libxt_SYNPROXY.man
new file mode 100644
index 0000000..30a71ed
--- /dev/null
+++ b/extensions/libxt_SYNPROXY.man
@@ -0,0 +1,66 @@
+This target will process TCP three-way-handshake parallel in netfilter
+context to protect either local or backend system. This target requires
+connection tracking because sequence numbers need to be translated.
+The kernels ability to absorb SYNFLOOD was greatly improved starting with
+Linux 4.4, so this target should not be needed anymore to protect Linux servers.
+.TP
+\fB\-\-mss\fP \fImaximum segment size\fP
+Maximum segment size announced to clients. This must match the backend.
+.TP
+\fB\-\-wscale\fP \fIwindow scale\fP
+Window scale announced to clients. This must match the backend.
+.TP
+\fB\-\-sack\-perm\fP
+Pass client selective acknowledgement option to backend (will be disabled
+if not present).
+.TP
+\fB\-\-timestamps\fP
+Pass client timestamp option to backend (will be disabled if not present,
+also needed for selective acknowledgement and window scaling).
+.PP
+Example:
+.PP
+Determine tcp options used by backend, from an external system
+.IP
+tcpdump -pni eth0 -c 1 'tcp[tcpflags] == (tcp-syn|tcp-ack)'
+.br
+    port 80 &
+.br
+telnet 192.0.2.42 80
+.br
+18:57:24.693307 IP 192.0.2.42.80 > 192.0.2.43.48757:
+.br
+    Flags [S.], seq 360414582, ack 788841994, win 14480,
+.br
+    options [mss 1460,sackOK,
+.br
+    TS val 1409056151 ecr 9690221,
+.br
+    nop,wscale 9],
+.br
+    length 0
+.PP
+Switch tcp_loose mode off, so conntrack will mark out\-of\-flow
+packets as state INVALID.
+.IP
+echo 0 > /proc/sys/net/netfilter/nf_conntrack_tcp_loose
+.PP
+Make SYN packets untracked
+.IP
+iptables \-t raw \-A PREROUTING \-i eth0 \-p tcp \-\-dport 80
+    \-\-syn \-j CT \-\-notrack
+.PP
+Catch UNTRACKED (SYN packets) and INVALID (3WHS ACK packets) states
+and send them to SYNPROXY. This rule will respond to SYN packets with
+SYN+ACK syncookies, create ESTABLISHED for valid client response (3WHS ACK
+packets) and drop incorrect cookies. Flags combinations not expected
+during 3WHS will not match and continue (e.g. SYN+FIN, SYN+ACK).
+.IP
+iptables \-A INPUT \-i eth0 \-p tcp \-\-dport 80
+    \-m state \-\-state UNTRACKED,INVALID \-j SYNPROXY
+    \-\-sack\-perm \-\-timestamp \-\-mss 1460 \-\-wscale 9
+.PP
+Drop invalid packets, this will be out\-of\-flow packets that were not
+matched by SYNPROXY.
+.IP
+iptables \-A INPUT \-i eth0 \-p tcp \-\-dport 80 \-m state \-\-state INVALID \-j DROP
diff --git a/extensions/libxt_SYNPROXY.t b/extensions/libxt_SYNPROXY.t
new file mode 100644
index 0000000..dd8b0e7
--- /dev/null
+++ b/extensions/libxt_SYNPROXY.t
@@ -0,0 +1,3 @@
+:INPUT,FORWARD
+-j SYNPROXY --sack-perm --timestamp --mss 1460 --wscale 9;;FAIL
+-p tcp -m tcp --dport 42 -m conntrack --ctstate INVALID,UNTRACKED -j SYNPROXY --sack-perm --timestamp --wscale 9 --mss 1460;=;OK
diff --git a/extensions/libxt_SYNPROXY.txlate b/extensions/libxt_SYNPROXY.txlate
new file mode 100644
index 0000000..b3de2b2
--- /dev/null
+++ b/extensions/libxt_SYNPROXY.txlate
@@ -0,0 +1,2 @@
+iptables-translate -t mangle -A INPUT -i iifname -p tcp -m tcp --dport 80 -m state --state INVALID,UNTRACKED -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460
+nft add rule ip mangle INPUT iifname "iifname" tcp dport 80 ct state invalid,untracked  counter synproxy sack-perm timestamp wscale 7 mss 1460
diff --git a/extensions/libxt_TCPMSS.c b/extensions/libxt_TCPMSS.c
new file mode 100644
index 0000000..0d9b200
--- /dev/null
+++ b/extensions/libxt_TCPMSS.c
@@ -0,0 +1,140 @@
+/* Shared library add-on to iptables to add TCPMSS target support.
+ *
+ * Copyright (c) 2000 Marc Boucher
+*/
+#include "config.h"
+#include <stdio.h>
+#include <xtables.h>
+#include <netinet/ip.h>
+#include <linux/netfilter/xt_TCPMSS.h>
+
+enum {
+	O_SET_MSS = 0,
+	O_CLAMP_MSS,
+};
+
+struct mssinfo {
+	struct xt_entry_target t;
+	struct xt_tcpmss_info mss;
+};
+
+static void __TCPMSS_help(int hdrsize)
+{
+	printf(
+"TCPMSS target mutually-exclusive options:\n"
+"  --set-mss value               explicitly set MSS option to specified value\n"
+"  --clamp-mss-to-pmtu           automatically clamp MSS value to (path_MTU - %d)\n",
+hdrsize);
+}
+
+static void TCPMSS_help(void)
+{
+	__TCPMSS_help(sizeof(struct iphdr));
+}
+
+static void TCPMSS_help6(void)
+{
+	__TCPMSS_help(SIZEOF_STRUCT_IP6_HDR);
+}
+
+static const struct xt_option_entry TCPMSS4_opts[] = {
+	{.name = "set-mss", .id = O_SET_MSS, .type = XTTYPE_UINT16,
+	 .min = 0, .max = UINT16_MAX - sizeof(struct iphdr),
+	 .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_tcpmss_info, mss)},
+	{.name = "clamp-mss-to-pmtu", .id = O_CLAMP_MSS, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry TCPMSS6_opts[] = {
+	{.name = "set-mss", .id = O_SET_MSS, .type = XTTYPE_UINT16,
+	 .min = 0, .max = UINT16_MAX - SIZEOF_STRUCT_IP6_HDR,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_tcpmss_info, mss)},
+	{.name = "clamp-mss-to-pmtu", .id = O_CLAMP_MSS, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static void TCPMSS_parse(struct xt_option_call *cb)
+{
+	struct xt_tcpmss_info *mssinfo = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->entry->id == O_CLAMP_MSS)
+		mssinfo->mss = XT_TCPMSS_CLAMP_PMTU;
+}
+
+static void TCPMSS_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM,
+		           "TCPMSS target: At least one parameter is required");
+}
+
+static void TCPMSS_print(const void *ip, const struct xt_entry_target *target,
+                         int numeric)
+{
+	const struct xt_tcpmss_info *mssinfo =
+		(const struct xt_tcpmss_info *)target->data;
+	if(mssinfo->mss == XT_TCPMSS_CLAMP_PMTU)
+		printf(" TCPMSS clamp to PMTU");
+	else
+		printf(" TCPMSS set %u", mssinfo->mss);
+}
+
+static void TCPMSS_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_tcpmss_info *mssinfo =
+		(const struct xt_tcpmss_info *)target->data;
+
+	if(mssinfo->mss == XT_TCPMSS_CLAMP_PMTU)
+		printf(" --clamp-mss-to-pmtu");
+	else
+		printf(" --set-mss %u", mssinfo->mss);
+}
+
+static int TCPMSS_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	const struct xt_tcpmss_info *mssinfo =
+		(const struct xt_tcpmss_info *)params->target->data;
+	if (mssinfo->mss == XT_TCPMSS_CLAMP_PMTU)
+		xt_xlate_add(xl, "tcp option maxseg size set rt mtu");
+	else
+		xt_xlate_add(xl, "tcp option maxseg size set %d", mssinfo->mss);
+
+	return 1;
+}
+
+static struct xtables_target tcpmss_tg_reg[] = {
+	{
+		.family        = NFPROTO_IPV4,
+		.name          = "TCPMSS",
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_tcpmss_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_tcpmss_info)),
+		.help          = TCPMSS_help,
+		.print         = TCPMSS_print,
+		.save          = TCPMSS_save,
+		.x6_parse      = TCPMSS_parse,
+		.x6_fcheck     = TCPMSS_check,
+		.x6_options    = TCPMSS4_opts,
+		.xlate         = TCPMSS_xlate,
+	},
+	{
+		.family        = NFPROTO_IPV6,
+		.name          = "TCPMSS",
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_tcpmss_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_tcpmss_info)),
+		.help          = TCPMSS_help6,
+		.print         = TCPMSS_print,
+		.save          = TCPMSS_save,
+		.x6_parse      = TCPMSS_parse,
+		.x6_fcheck     = TCPMSS_check,
+		.x6_options    = TCPMSS6_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
+}
diff --git a/extensions/libxt_TCPMSS.man b/extensions/libxt_TCPMSS.man
new file mode 100644
index 0000000..25b480d
--- /dev/null
+++ b/extensions/libxt_TCPMSS.man
@@ -0,0 +1,41 @@
+This target alters the MSS value of TCP SYN packets, to control
+the maximum size for that connection (usually limiting it to your
+outgoing interface's MTU minus 40 for IPv4 or 60 for IPv6, respectively).
+Of course, it can only be used
+in conjunction with
+\fB\-p tcp\fP.
+.PP
+This target is used to overcome criminally braindead ISPs or servers
+which block "ICMP Fragmentation Needed" or "ICMPv6 Packet Too Big"
+packets.  The symptoms of this
+problem are that everything works fine from your Linux
+firewall/router, but machines behind it can never exchange large
+packets:
+.IP 1. 4
+Web browsers connect, then hang with no data received.
+.IP 2. 4
+Small mail works fine, but large emails hang.
+.IP 3. 4
+ssh works fine, but scp hangs after initial handshaking.
+.PP
+Workaround: activate this option and add a rule to your firewall
+configuration like:
+.IP
+ iptables \-t mangle \-A FORWARD \-p tcp \-\-tcp\-flags SYN,RST SYN
+             \-j TCPMSS \-\-clamp\-mss\-to\-pmtu
+.TP
+\fB\-\-set\-mss\fP \fIvalue\fP
+Explicitly sets MSS option to specified value. If the MSS of the packet is
+already lower than \fIvalue\fP, it will \fBnot\fP be increased (from Linux
+2.6.25 onwards) to avoid more problems with hosts relying on a proper MSS.
+.TP
+\fB\-\-clamp\-mss\-to\-pmtu\fP
+Automatically clamp MSS value to (path_MTU \- 40 for IPv4; \-60 for IPv6).
+This may not function as desired where asymmetric routes with differing
+path MTU exist \(em the kernel uses the path MTU which it would use to send
+packets from itself to the source and destination IP addresses. Prior to
+Linux 2.6.25, only the path MTU to the destination IP address was
+considered by this option; subsequent kernels also consider the path MTU
+to the source IP address.
+.PP
+These options are mutually exclusive.
diff --git a/extensions/libxt_TCPMSS.t b/extensions/libxt_TCPMSS.t
new file mode 100644
index 0000000..553a345
--- /dev/null
+++ b/extensions/libxt_TCPMSS.t
@@ -0,0 +1,6 @@
+:FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j TCPMSS;;FAIL
+-p tcp -j TCPMSS --set-mss 42;;FAIL
+-p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j TCPMSS --set-mss 42;=;OK
+-p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j TCPMSS --clamp-mss-to-pmtu;=;OK
diff --git a/extensions/libxt_TCPMSS.txlate b/extensions/libxt_TCPMSS.txlate
new file mode 100644
index 0000000..6a64d2c
--- /dev/null
+++ b/extensions/libxt_TCPMSS.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
+nft add rule ip filter FORWARD tcp flags & (syn|rst) == syn counter tcp option maxseg size set rt mtu
+
+iptables-translate -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 90
+nft add rule ip filter FORWARD tcp flags & (syn|rst) == syn counter tcp option maxseg size set 90
diff --git a/extensions/libxt_TCPOPTSTRIP.c b/extensions/libxt_TCPOPTSTRIP.c
new file mode 100644
index 0000000..6ea3489
--- /dev/null
+++ b/extensions/libxt_TCPOPTSTRIP.c
@@ -0,0 +1,182 @@
+/*
+ * Shared library add-on to iptables to add TCPOPTSTRIP target support.
+ * Copyright (c) 2007 Sven Schnelle <svens@bitebene.org>
+ * Copyright © CC Computer Consultants GmbH, 2007
+ * Jan Engelhardt <jengelh@computergmbh.de>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <netinet/tcp.h>
+#include <linux/netfilter/xt_TCPOPTSTRIP.h>
+#ifndef TCPOPT_MD5SIG
+#	define TCPOPT_MD5SIG 19
+#endif
+#ifndef TCPOPT_MAXSEG
+#     define TCPOPT_MAXSEG 2
+#endif
+#ifndef TCPOPT_WINDOW
+#     define TCPOPT_WINDOW 3
+#endif
+#ifndef TCPOPT_SACK_PERMITTED
+#     define TCPOPT_SACK_PERMITTED 4
+#endif
+#ifndef TCPOPT_SACK
+#     define TCPOPT_SACK 5
+#endif
+#ifndef TCPOPT_TIMESTAMP
+#     define TCPOPT_TIMESTAMP 8
+#endif
+
+enum {
+	O_STRIP_OPTION = 0,
+};
+
+struct tcp_optionmap {
+	const char *name, *desc;
+	const unsigned int option;
+};
+
+static const struct xt_option_entry tcpoptstrip_tg_opts[] = {
+	{.name = "strip-options", .id = O_STRIP_OPTION, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+
+static const struct tcp_optionmap tcp_optionmap[] = {
+	{"wscale",         "Window scale",         TCPOPT_WINDOW},
+	{"mss",            "Maximum Segment Size", TCPOPT_MAXSEG},
+	{"sack-permitted", "SACK permitted",       TCPOPT_SACK_PERMITTED},
+	{"sack",           "Selective ACK",        TCPOPT_SACK},
+	{"timestamp",      "Timestamp",            TCPOPT_TIMESTAMP},
+	{"md5",            "MD5 signature",        TCPOPT_MD5SIG},
+	{NULL},
+};
+
+static void tcpoptstrip_tg_help(void)
+{
+	const struct tcp_optionmap *w;
+
+	printf(
+"TCPOPTSTRIP target options:\n"
+"  --strip-options value     strip specified TCP options denoted by value\n"
+"                            (separated by comma) from TCP header\n"
+"  Instead of the numeric value, you can also use the following names:\n"
+	);
+
+	for (w = tcp_optionmap; w->name != NULL; ++w)
+		printf("    %-14s    strip \"%s\" option\n", w->name, w->desc);
+}
+
+static void
+parse_list(struct xt_tcpoptstrip_target_info *info, const char *arg)
+{
+	unsigned int option;
+	char *p;
+	int i;
+
+	while (true) {
+		p = strchr(arg, ',');
+		if (p != NULL)
+			*p = '\0';
+
+		option = 0;
+		for (i = 0; tcp_optionmap[i].name != NULL; ++i)
+			if (strcmp(tcp_optionmap[i].name, arg) == 0) {
+				option = tcp_optionmap[i].option;
+				break;
+			}
+
+		if (option == 0 &&
+		    !xtables_strtoui(arg, NULL, &option, 0, UINT8_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+			           "Bad TCP option value \"%s\"", arg);
+
+		if (option < 2)
+			xtables_error(PARAMETER_PROBLEM,
+			           "Option value may not be 0 or 1");
+
+		if (tcpoptstrip_test_bit(info->strip_bmap, option))
+			xtables_error(PARAMETER_PROBLEM,
+			           "Option \"%s\" already specified", arg);
+
+		tcpoptstrip_set_bit(info->strip_bmap, option);
+		if (p == NULL)
+			break;
+		arg = p + 1;
+	}
+}
+
+static void tcpoptstrip_tg_parse(struct xt_option_call *cb)
+{
+	struct xt_tcpoptstrip_target_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	parse_list(info, cb->arg);
+}
+
+static void
+tcpoptstrip_print_list(const struct xt_tcpoptstrip_target_info *info,
+                       bool numeric)
+{
+	unsigned int i, j;
+	const char *name;
+	bool first = true;
+
+	for (i = 0; i < 256; ++i) {
+		if (!tcpoptstrip_test_bit(info->strip_bmap, i))
+			continue;
+		if (!first)
+			printf(",");
+
+		first = false;
+		name  = NULL;
+		if (!numeric)
+			for (j = 0; tcp_optionmap[j].name != NULL; ++j)
+				if (tcp_optionmap[j].option == i)
+					name = tcp_optionmap[j].name;
+
+		if (name != NULL)
+			printf("%s", name);
+		else
+			printf("%u", i);
+	}
+}
+
+static void
+tcpoptstrip_tg_print(const void *ip, const struct xt_entry_target *target,
+                     int numeric)
+{
+	const struct xt_tcpoptstrip_target_info *info =
+		(const void *)target->data;
+
+	printf(" TCPOPTSTRIP options ");
+	tcpoptstrip_print_list(info, numeric);
+}
+
+static void
+tcpoptstrip_tg_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_tcpoptstrip_target_info *info =
+		(const void *)target->data;
+
+	printf(" --strip-options ");
+	tcpoptstrip_print_list(info, true);
+}
+
+static struct xtables_target tcpoptstrip_tg_reg = {
+	.version       = XTABLES_VERSION,
+	.name          = "TCPOPTSTRIP",
+	.family        = NFPROTO_UNSPEC,
+	.size          = XT_ALIGN(sizeof(struct xt_tcpoptstrip_target_info)),
+	.userspacesize = XT_ALIGN(sizeof(struct xt_tcpoptstrip_target_info)),
+	.help          = tcpoptstrip_tg_help,
+	.print         = tcpoptstrip_tg_print,
+	.save          = tcpoptstrip_tg_save,
+	.x6_parse      = tcpoptstrip_tg_parse,
+	.x6_options    = tcpoptstrip_tg_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&tcpoptstrip_tg_reg);
+}
diff --git a/extensions/libxt_TCPOPTSTRIP.man b/extensions/libxt_TCPOPTSTRIP.man
new file mode 100644
index 0000000..2a07709
--- /dev/null
+++ b/extensions/libxt_TCPOPTSTRIP.man
@@ -0,0 +1,7 @@
+This target will strip TCP options off a TCP packet. (It will actually replace
+them by NO-OPs.) As such, you will need to add the \fB\-p tcp\fP parameters.
+.TP
+\fB\-\-strip\-options\fP \fIoption\fP[\fB,\fP\fIoption\fP...]
+Strip the given option(s). The options may be specified by TCP option number or
+by symbolic name. The list of recognized options can be obtained by calling
+iptables with \fB\-j TCPOPTSTRIP \-h\fP.
diff --git a/extensions/libxt_TCPOPTSTRIP.t b/extensions/libxt_TCPOPTSTRIP.t
new file mode 100644
index 0000000..b5c7a10
--- /dev/null
+++ b/extensions/libxt_TCPOPTSTRIP.t
@@ -0,0 +1,8 @@
+:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j TCPOPTSTRIP;;FAIL
+-p tcp -j TCPOPTSTRIP;=;OK
+-p tcp -j TCPOPTSTRIP --strip-options 2,3,4,5,6,7;=;OK
+-p tcp -j TCPOPTSTRIP --strip-options 0;;FAIL
+-p tcp -j TCPOPTSTRIP --strip-options 1;;FAIL
+-p tcp -j TCPOPTSTRIP --strip-options 1,2;;FAIL
diff --git a/extensions/libxt_TEE.c b/extensions/libxt_TEE.c
new file mode 100644
index 0000000..4676e33
--- /dev/null
+++ b/extensions/libxt_TEE.c
@@ -0,0 +1,163 @@
+/*
+ *	"TEE" target extension for iptables
+ *	Copyright © Sebastian Claßen <sebastian.classen [at] freenet.ag>, 2007
+ *	Jan Engelhardt <jengelh [at] medozas de>, 2007 - 2010
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License; either
+ *	version 2 of the License, or any later version, as published by the
+ *	Free Software Foundation.
+ */
+#include <sys/socket.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <xtables.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/xt_TEE.h>
+
+enum {
+	O_GATEWAY = 0,
+	O_OIF,
+};
+
+#define s struct xt_tee_tginfo
+static const struct xt_option_entry tee_tg_opts[] = {
+	{.name = "gateway", .id = O_GATEWAY, .type = XTTYPE_HOST,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, gw)},
+	{.name = "oif", .id = O_OIF, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, oif)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void tee_tg_help(void)
+{
+	printf(
+"TEE target options:\n"
+"  --gateway IPADDR    Route packet via the gateway given by address\n"
+"  --oif NAME          Include oif in route calculation\n"
+"\n");
+}
+
+static void tee_tg_print(const void *ip, const struct xt_entry_target *target,
+                         int numeric)
+{
+	const struct xt_tee_tginfo *info = (const void *)target->data;
+
+	if (numeric)
+		printf(" TEE gw:%s", xtables_ipaddr_to_numeric(&info->gw.in));
+	else
+		printf(" TEE gw:%s", xtables_ipaddr_to_anyname(&info->gw.in));
+	if (*info->oif != '\0')
+		printf(" oif=%s", info->oif);
+}
+
+static void tee_tg6_print(const void *ip, const struct xt_entry_target *target,
+                          int numeric)
+{
+	const struct xt_tee_tginfo *info = (const void *)target->data;
+
+	if (numeric)
+		printf(" TEE gw:%s", xtables_ip6addr_to_numeric(&info->gw.in6));
+	else
+		printf(" TEE gw:%s", xtables_ip6addr_to_anyname(&info->gw.in6));
+	if (*info->oif != '\0')
+		printf(" oif=%s", info->oif);
+}
+
+static void tee_tg_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_tee_tginfo *info = (const void *)target->data;
+
+	printf(" --gateway %s", xtables_ipaddr_to_numeric(&info->gw.in));
+	if (*info->oif != '\0')
+		printf(" --oif %s", info->oif);
+}
+
+static void tee_tg6_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_tee_tginfo *info = (const void *)target->data;
+
+	printf(" --gateway %s", xtables_ip6addr_to_numeric(&info->gw.in6));
+	if (*info->oif != '\0')
+		printf(" --oif %s", info->oif);
+}
+
+static int tee_tg_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	const struct xt_tee_tginfo *info = (const void *)params->target->data;
+
+	if (params->numeric)
+		xt_xlate_add(xl, "dup to %s",
+			     xtables_ipaddr_to_numeric(&info->gw.in));
+	else
+		xt_xlate_add(xl, "dup to %s",
+			     xtables_ipaddr_to_anyname(&info->gw.in));
+	if (*info->oif != '\0')
+		xt_xlate_add(xl, " device %s", info->oif);
+
+	return 1;
+}
+
+static int tee_tg6_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct xt_tee_tginfo *info = (const void *)params->target->data;
+
+	if (params->numeric)
+		xt_xlate_add(xl, "dup to %s",
+			     xtables_ip6addr_to_numeric(&info->gw.in6));
+	else
+		xt_xlate_add(xl, "dup to %s",
+			     xtables_ip6addr_to_anyname(&info->gw.in6));
+	if (*info->oif != '\0')
+		xt_xlate_add(xl, " device %s", info->oif);
+
+	return 1;
+}
+
+static struct xtables_target tee_tg_reg[] = {
+	{
+		.name          = "TEE",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_tee_tginfo)),
+		.userspacesize = offsetof(struct xt_tee_tginfo, priv),
+		.help          = tee_tg_help,
+		.print         = tee_tg_print,
+		.save          = tee_tg_save,
+		.x6_parse      = xtables_option_parse,
+		.x6_options    = tee_tg_opts,
+		.xlate         = tee_tg_xlate,
+	},
+	{
+		.name          = "TEE",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_tee_tginfo)),
+		.userspacesize = offsetof(struct xt_tee_tginfo, priv),
+		.help          = tee_tg_help,
+		.print         = tee_tg6_print,
+		.save          = tee_tg6_save,
+		.x6_parse      = xtables_option_parse,
+		.x6_options    = tee_tg_opts,
+		.xlate         = tee_tg6_xlate,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(tee_tg_reg, ARRAY_SIZE(tee_tg_reg));
+}
diff --git a/extensions/libxt_TEE.man b/extensions/libxt_TEE.man
new file mode 100644
index 0000000..456d150
--- /dev/null
+++ b/extensions/libxt_TEE.man
@@ -0,0 +1,12 @@
+The \fBTEE\fP target will clone a packet and redirect this clone to another
+machine on the \fBlocal\fP network segment. In other words, the nexthop
+must be the target, or you will have to configure the nexthop to forward it
+further if so desired.
+.TP
+\fB\-\-gateway\fP \fIipaddr\fP
+Send the cloned packet to the host reachable at the given IP address.
+Use of 0.0.0.0 (for IPv4 packets) or :: (IPv6) is invalid.
+.PP
+To forward all incoming traffic on eth0 to an Network Layer logging box:
+.PP
+\-t mangle \-A PREROUTING \-i eth0 \-j TEE \-\-gateway 2001:db8::1
diff --git a/extensions/libxt_TEE.t b/extensions/libxt_TEE.t
new file mode 100644
index 0000000..ce8b103
--- /dev/null
+++ b/extensions/libxt_TEE.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-j TEE --gateway 1.1.1.1;=;OK
+-j TEE ! --gateway 1.1.1.1;;FAIL
+-j TEE;;FAIL
diff --git a/extensions/libxt_TEE.txlate b/extensions/libxt_TEE.txlate
new file mode 100644
index 0000000..9fcee25
--- /dev/null
+++ b/extensions/libxt_TEE.txlate
@@ -0,0 +1,11 @@
+# iptables-translate -t mangle -A PREROUTING -j TEE --gateway 192.168.0.2 --oif eth0
+# nft add rule ip mangle PREROUTING counter dup to 192.168.0.2 device eth0
+#
+# iptables-translate -t mangle -A PREROUTING -j TEE --gateway 192.168.0.2
+# nft add rule ip mangle PREROUTING counter dup to 192.168.0.2
+
+ip6tables-translate -t mangle -A PREROUTING -j TEE --gateway ab12:00a1:1112:acba::
+nft add rule ip6 mangle PREROUTING counter dup to ab12:a1:1112:acba::
+
+ip6tables-translate -t mangle -A PREROUTING -j TEE --gateway ab12:00a1:1112:acba:: --oif eth0
+nft add rule ip6 mangle PREROUTING counter dup to ab12:a1:1112:acba:: device eth0
diff --git a/extensions/libxt_TOS.c b/extensions/libxt_TOS.c
new file mode 100644
index 0000000..b66fa32
--- /dev/null
+++ b/extensions/libxt_TOS.c
@@ -0,0 +1,246 @@
+/*
+ * Shared library add-on to iptables to add TOS target support
+ *
+ * Copyright © CC Computer Consultants GmbH, 2007
+ * Contact: Jan Engelhardt <jengelh@medozas.de>
+ */
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+
+#include <xtables.h>
+#include <linux/netfilter/xt_DSCP.h>
+#include "tos_values.c"
+
+struct ipt_tos_target_info {
+	uint8_t tos;
+};
+
+enum {
+	O_SET_TOS = 0,
+	O_AND_TOS,
+	O_OR_TOS,
+	O_XOR_TOS,
+	F_SET_TOS = 1 << O_SET_TOS,
+	F_AND_TOS = 1 << O_AND_TOS,
+	F_OR_TOS  = 1 << O_OR_TOS,
+	F_XOR_TOS = 1 << O_XOR_TOS,
+	F_ANY     = F_SET_TOS | F_AND_TOS | F_OR_TOS | F_XOR_TOS,
+};
+
+static const struct xt_option_entry tos_tg_opts_v0[] = {
+	{.name = "set-tos", .id = O_SET_TOS, .type = XTTYPE_TOSMASK,
+	 .excl = F_ANY, .max = 0xFF},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry tos_tg_opts[] = {
+	{.name = "set-tos", .id = O_SET_TOS, .type = XTTYPE_TOSMASK,
+	 .excl = F_ANY, .max = 0x3F},
+	{.name = "and-tos", .id = O_AND_TOS, .type = XTTYPE_UINT8,
+	 .excl = F_ANY},
+	{.name = "or-tos", .id = O_OR_TOS, .type = XTTYPE_UINT8,
+	 .excl = F_ANY},
+	{.name = "xor-tos", .id = O_XOR_TOS, .type = XTTYPE_UINT8,
+	 .excl = F_ANY},
+	XTOPT_TABLEEND,
+};
+
+static void tos_tg_help_v0(void)
+{
+	const struct tos_symbol_info *symbol;
+
+	printf(
+"TOS target options:\n"
+"  --set-tos value     Set Type of Service/Priority field to value\n"
+"  --set-tos symbol    Set TOS field (IPv4 only) by symbol\n"
+"                      Accepted symbolic names for value are:\n");
+
+	for (symbol = tos_symbol_names; symbol->name != NULL; ++symbol)
+		printf("                        (0x%02x) %2u %s\n",
+		       symbol->value, symbol->value, symbol->name);
+
+	printf("\n");
+}
+
+static void tos_tg_help(void)
+{
+	const struct tos_symbol_info *symbol;
+
+	printf(
+"TOS target v%s options:\n"
+"  --set-tos value[/mask]  Set Type of Service/Priority field to value\n"
+"                          (Zero out bits in mask and XOR value into TOS)\n"
+"  --set-tos symbol        Set TOS field (IPv4 only) by symbol\n"
+"                          (this zeroes the 4-bit Precedence part!)\n"
+"                          Accepted symbolic names for value are:\n",
+XTABLES_VERSION);
+
+	for (symbol = tos_symbol_names; symbol->name != NULL; ++symbol)
+		printf("                            (0x%02x) %2u %s\n",
+		       symbol->value, symbol->value, symbol->name);
+
+	printf(
+"\n"
+"  --and-tos bits          Binary AND the TOS value with bits\n"
+"  --or-tos  bits          Binary OR the TOS value with bits\n"
+"  --xor-tos bits          Binary XOR the TOS value with bits\n"
+);
+}
+
+static void tos_tg_parse_v0(struct xt_option_call *cb)
+{
+	struct ipt_tos_target_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->val.tos_mask != 0xFF)
+		xtables_error(PARAMETER_PROBLEM, "tos match: Your kernel "
+		           "is too old to support anything besides "
+			   "/0xFF as a mask.");
+	info->tos = cb->val.tos_value;
+}
+
+static void tos_tg_parse(struct xt_option_call *cb)
+{
+	struct xt_tos_target_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SET_TOS:
+		info->tos_value = cb->val.tos_value;
+		info->tos_mask  = cb->val.tos_mask;
+		break;
+	case O_AND_TOS:
+		info->tos_value = 0;
+		info->tos_mask  = ~cb->val.u8;
+		break;
+	case O_OR_TOS:
+		info->tos_value = cb->val.u8;
+		info->tos_mask  = cb->val.u8;
+		break;
+	case O_XOR_TOS:
+		info->tos_value = cb->val.u8;
+		info->tos_mask  = 0;
+		break;
+	}
+}
+
+static void tos_tg_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & F_ANY))
+		xtables_error(PARAMETER_PROBLEM,
+		           "TOS: An action is required");
+}
+
+static void tos_tg_print_v0(const void *ip,
+                            const struct xt_entry_target *target, int numeric)
+{
+	const struct ipt_tos_target_info *info = (const void *)target->data;
+
+	printf(" TOS set ");
+	if (numeric || !tos_try_print_symbolic("", info->tos, 0xFF))
+		printf("0x%02x", info->tos);
+}
+
+static void tos_tg_print(const void *ip, const struct xt_entry_target *target,
+                         int numeric)
+{
+	const struct xt_tos_target_info *info = (const void *)target->data;
+
+	if (numeric)
+		printf(" TOS set 0x%02x/0x%02x",
+		       info->tos_value, info->tos_mask);
+	else if (tos_try_print_symbolic(" TOS set",
+	    info->tos_value, info->tos_mask))
+		/* already printed by call */
+		return;
+	else if (info->tos_value == 0)
+		printf(" TOS and 0x%02x",
+		       (unsigned int)(uint8_t)~info->tos_mask);
+	else if (info->tos_value == info->tos_mask)
+		printf(" TOS or 0x%02x", info->tos_value);
+	else if (info->tos_mask == 0)
+		printf(" TOS xor 0x%02x", info->tos_value);
+	else
+		printf(" TOS set 0x%02x/0x%02x",
+		       info->tos_value, info->tos_mask);
+}
+
+static void tos_tg_save_v0(const void *ip, const struct xt_entry_target *target)
+{
+	const struct ipt_tos_target_info *info = (const void *)target->data;
+
+	printf(" --set-tos 0x%02x", info->tos);
+}
+
+static void tos_tg_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_tos_target_info *info = (const void *)target->data;
+
+	printf(" --set-tos 0x%02x/0x%02x", info->tos_value, info->tos_mask);
+}
+
+static int tos_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_tos_target_info *info =
+			(struct ipt_tos_target_info *) params->target->data;
+	uint8_t dscp = info->tos >> 2;
+
+	xt_xlate_add(xl, "ip dscp set 0x%02x", dscp);
+
+	return 1;
+}
+
+static int tos_xlate6(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_tos_target_info *info =
+			(struct ipt_tos_target_info *) params->target->data;
+	uint8_t dscp = info->tos >> 2;
+
+	xt_xlate_add(xl, "ip6 dscp set 0x%02x", dscp);
+
+	return 1;
+}
+
+static struct xtables_target tos_tg_reg[] = {
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "TOS",
+		.revision      = 0,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_tos_target_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_tos_target_info)),
+		.help          = tos_tg_help_v0,
+		.print         = tos_tg_print_v0,
+		.save          = tos_tg_save_v0,
+		.x6_parse      = tos_tg_parse_v0,
+		.x6_fcheck     = tos_tg_check,
+		.x6_options    = tos_tg_opts_v0,
+		.xlate	       = tos_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "TOS",
+		.revision      = 1,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_tos_target_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_tos_target_info)),
+		.help          = tos_tg_help,
+		.print         = tos_tg_print,
+		.save          = tos_tg_save,
+		.x6_parse      = tos_tg_parse,
+		.x6_fcheck     = tos_tg_check,
+		.x6_options    = tos_tg_opts,
+		.xlate	       = tos_xlate6,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(tos_tg_reg, ARRAY_SIZE(tos_tg_reg));
+}
diff --git a/extensions/libxt_TOS.man b/extensions/libxt_TOS.man
new file mode 100644
index 0000000..de2d22d
--- /dev/null
+++ b/extensions/libxt_TOS.man
@@ -0,0 +1,36 @@
+This module sets the Type of Service field in the IPv4 header (including the
+"precedence" bits) or the Priority field in the IPv6 header. Note that TOS
+shares the same bits as DSCP and ECN. The TOS target is only valid in the
+\fBmangle\fP table.
+.TP
+\fB\-\-set\-tos\fP \fIvalue\fP[\fB/\fP\fImask\fP]
+Zeroes out the bits given by \fImask\fP (see NOTE below) and XORs \fIvalue\fP
+into the TOS/Priority field. If \fImask\fP is omitted, 0xFF is assumed.
+.TP
+\fB\-\-set\-tos\fP \fIsymbol\fP
+You can specify a symbolic name when using the TOS target for IPv4. It implies
+a mask of 0xFF (see NOTE below). The list of recognized TOS names can be
+obtained by calling iptables with \fB\-j TOS \-h\fP.
+.PP
+The following mnemonics are available:
+.TP
+\fB\-\-and\-tos\fP \fIbits\fP
+Binary AND the TOS value with \fIbits\fP. (Mnemonic for \fB\-\-set\-tos
+0/\fP\fIinvbits\fP, where \fIinvbits\fP is the binary negation of \fIbits\fP.
+See NOTE below.)
+.TP
+\fB\-\-or\-tos\fP \fIbits\fP
+Binary OR the TOS value with \fIbits\fP. (Mnemonic for \fB\-\-set\-tos\fP
+\fIbits\fP\fB/\fP\fIbits\fP. See NOTE below.)
+.TP
+\fB\-\-xor\-tos\fP \fIbits\fP
+Binary XOR the TOS value with \fIbits\fP. (Mnemonic for \fB\-\-set\-tos\fP
+\fIbits\fP\fB/0\fP. See NOTE below.)
+.PP
+NOTE: In Linux kernels up to and including 2.6.38, with the exception of
+longterm releases 2.6.32 (>=.42), 2.6.33 (>=.15), and 2.6.35 (>=.14), there is
+a bug whereby IPv6 TOS mangling does not behave as documented and differs from
+the IPv4 version. The TOS mask indicates the bits one wants to zero out, so it
+needs to be inverted before applying it to the original TOS field. However, the
+aformentioned kernels forgo the inversion which breaks \-\-set\-tos and its
+mnemonics.
diff --git a/extensions/libxt_TOS.t b/extensions/libxt_TOS.t
new file mode 100644
index 0000000..ae8531c
--- /dev/null
+++ b/extensions/libxt_TOS.t
@@ -0,0 +1,16 @@
+:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j TOS --set-tos 0x1f;=;OK
+-j TOS --set-tos 0x1f/0x1f;=;OK
+# maximum TOS is 0x1f (5 bits)
+# ERROR: should fail: iptables -A PREROUTING -t mangle -j TOS --set-tos 0xff
+# -j TOS --set-tos 0xff;;FAIL
+-j TOS --set-tos Minimize-Delay;-j TOS --set-tos 0x10;OK
+-j TOS --set-tos Maximize-Throughput;-j TOS --set-tos 0x08;OK
+-j TOS --set-tos Maximize-Reliability;-j TOS --set-tos 0x04;OK
+-j TOS --set-tos Minimize-Cost;-j TOS --set-tos 0x02;OK
+-j TOS --set-tos Normal-Service;-j TOS --set-tos 0x00;OK
+-j TOS --and-tos 0x12;-j TOS --set-tos 0x00/0xed;OK
+-j TOS --or-tos 0x12;-j TOS --set-tos 0x12/0x12;OK
+-j TOS --xor-tos 0x12;-j TOS --set-tos 0x12/0x00;OK
+-j TOS;;FAIL
diff --git a/extensions/libxt_TOS.txlate b/extensions/libxt_TOS.txlate
new file mode 100644
index 0000000..0952310
--- /dev/null
+++ b/extensions/libxt_TOS.txlate
@@ -0,0 +1,23 @@
+ip6tables-translate -A INPUT -j TOS --set-tos 0x1f
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x07
+
+ip6tables-translate -A INPUT -j TOS --set-tos 0xff
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x3f
+
+ip6tables-translate -A INPUT -j TOS --set-tos Minimize-Delay
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x04
+
+ip6tables-translate -A INPUT -j TOS --set-tos Minimize-Cost
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x00
+
+ip6tables-translate -A INPUT -j TOS --set-tos Normal-Service
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x00
+
+ip6tables-translate -A INPUT -j TOS --and-tos 0x12
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x00
+
+ip6tables-translate -A INPUT -j TOS --or-tos 0x12
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x04
+
+ip6tables-translate -A INPUT -j TOS --xor-tos 0x12
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x04
diff --git a/extensions/libxt_TPROXY.c b/extensions/libxt_TPROXY.c
new file mode 100644
index 0000000..d13ec85
--- /dev/null
+++ b/extensions/libxt_TPROXY.c
@@ -0,0 +1,195 @@
+/*
+ * shared library add-on to iptables to add TPROXY target support.
+ *
+ * Copyright (C) 2002-2008 BalaBit IT Ltd.
+ */
+#include <stdio.h>
+#include <limits.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_TPROXY.h>
+#include <arpa/inet.h>
+
+enum {
+	P_PORT = 0,
+	P_ADDR,
+	P_MARK,
+	F_PORT = 1 << P_PORT,
+	F_ADDR = 1 << P_ADDR,
+	F_MARK = 1 << P_MARK,
+};
+
+#define s struct xt_tproxy_target_info
+static const struct xt_option_entry tproxy_tg0_opts[] = {
+	{.name = "on-port", .id = P_PORT, .type = XTTYPE_PORT,
+	 .flags = XTOPT_MAND | XTOPT_NBO | XTOPT_PUT, XTOPT_POINTER(s, lport)},
+	{.name = "on-ip", .id = P_ADDR, .type = XTTYPE_HOST},
+	{.name = "tproxy-mark", .id = P_MARK, .type = XTTYPE_MARKMASK32},
+	XTOPT_TABLEEND,
+};
+#undef s
+#define s struct xt_tproxy_target_info_v1
+static const struct xt_option_entry tproxy_tg1_opts[] = {
+	{.name = "on-port", .id = P_PORT, .type = XTTYPE_PORT,
+	 .flags = XTOPT_MAND | XTOPT_NBO | XTOPT_PUT, XTOPT_POINTER(s, lport)},
+	{.name = "on-ip", .id = P_ADDR, .type = XTTYPE_HOST,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, laddr)},
+	{.name = "tproxy-mark", .id = P_MARK, .type = XTTYPE_MARKMASK32},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void tproxy_tg_help(void)
+{
+	printf(
+"TPROXY target options:\n"
+"  --on-port port		    Redirect connection to port, or the original port if 0\n"
+"  --on-ip ip			    Optionally redirect to the given IP\n"
+"  --tproxy-mark value[/mask]	    Mark packets with the given value/mask\n\n");
+}
+
+static void tproxy_tg_print(const void *ip, const struct xt_entry_target *target,
+			 int numeric)
+{
+	const struct xt_tproxy_target_info *info = (const void *)target->data;
+	printf(" TPROXY redirect %s:%u mark 0x%x/0x%x",
+	       xtables_ipaddr_to_numeric((const struct in_addr *)&info->laddr),
+	       ntohs(info->lport), (unsigned int)info->mark_value,
+	       (unsigned int)info->mark_mask);
+}
+
+static void
+tproxy_tg_print4(const void *ip, const struct xt_entry_target *target,
+		 int numeric)
+{
+	const struct xt_tproxy_target_info_v1 *info =
+		(const void *)target->data;
+
+	printf(" TPROXY redirect %s:%u mark 0x%x/0x%x",
+	       xtables_ipaddr_to_numeric(&info->laddr.in),
+	       ntohs(info->lport), (unsigned int)info->mark_value,
+	       (unsigned int)info->mark_mask);
+}
+
+static void
+tproxy_tg_print6(const void *ip, const struct xt_entry_target *target,
+		 int numeric)
+{
+	const struct xt_tproxy_target_info_v1 *info =
+		(const void *)target->data;
+
+	printf(" TPROXY redirect %s:%u mark 0x%x/0x%x",
+	       xtables_ip6addr_to_numeric(&info->laddr.in6),
+	       ntohs(info->lport), (unsigned int)info->mark_value,
+	       (unsigned int)info->mark_mask);
+}
+
+static void tproxy_tg_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_tproxy_target_info *info = (const void *)target->data;
+
+	printf(" --on-port %u", ntohs(info->lport));
+	printf(" --on-ip %s",
+	       xtables_ipaddr_to_numeric((const struct in_addr *)&info->laddr));
+	printf(" --tproxy-mark 0x%x/0x%x",
+	       (unsigned int)info->mark_value, (unsigned int)info->mark_mask);
+}
+
+static void
+tproxy_tg_save4(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_tproxy_target_info_v1 *info;
+
+	info = (const void *)target->data;
+	printf(" --on-port %u", ntohs(info->lport));
+	printf(" --on-ip %s", xtables_ipaddr_to_numeric(&info->laddr.in));
+	printf(" --tproxy-mark 0x%x/0x%x",
+	       (unsigned int)info->mark_value, (unsigned int)info->mark_mask);
+}
+
+static void
+tproxy_tg_save6(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_tproxy_target_info_v1 *info;
+
+	info = (const void *)target->data;
+	printf(" --on-port %u", ntohs(info->lport));
+	printf(" --on-ip %s", xtables_ip6addr_to_numeric(&info->laddr.in6));
+	printf(" --tproxy-mark 0x%x/0x%x",
+	       (unsigned int)info->mark_value, (unsigned int)info->mark_mask);
+}
+
+static void tproxy_tg0_parse(struct xt_option_call *cb)
+{
+	struct xt_tproxy_target_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case P_MARK:
+		info->mark_value = cb->val.mark;
+		info->mark_mask  = cb->val.mask;
+		break;
+	case P_ADDR:
+		info->laddr = cb->val.haddr.ip;
+		break;
+	}
+}
+
+static void tproxy_tg1_parse(struct xt_option_call *cb)
+{
+	struct xt_tproxy_target_info_v1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case P_MARK:
+		info->mark_value = cb->val.mark;
+		info->mark_mask  = cb->val.mask;
+		break;
+	}
+}
+
+static struct xtables_target tproxy_tg_reg[] = {
+	{
+		.name          = "TPROXY",
+		.revision      = 0,
+		.family        = NFPROTO_IPV4,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_tproxy_target_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_tproxy_target_info)),
+		.help          = tproxy_tg_help,
+		.print         = tproxy_tg_print,
+		.save          = tproxy_tg_save,
+		.x6_options    = tproxy_tg0_opts,
+		.x6_parse      = tproxy_tg0_parse,
+	},
+	{
+		.name          = "TPROXY",
+		.revision      = 1,
+		.family        = NFPROTO_IPV4,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_tproxy_target_info_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_tproxy_target_info_v1)),
+		.help          = tproxy_tg_help,
+		.print         = tproxy_tg_print4,
+		.save          = tproxy_tg_save4,
+		.x6_options    = tproxy_tg1_opts,
+		.x6_parse      = tproxy_tg1_parse,
+	},
+	{
+		.name          = "TPROXY",
+		.revision      = 1,
+		.family        = NFPROTO_IPV6,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_tproxy_target_info_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_tproxy_target_info_v1)),
+		.help          = tproxy_tg_help,
+		.print         = tproxy_tg_print6,
+		.save          = tproxy_tg_save6,
+		.x6_options    = tproxy_tg1_opts,
+		.x6_parse      = tproxy_tg1_parse,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(tproxy_tg_reg, ARRAY_SIZE(tproxy_tg_reg));
+}
diff --git a/extensions/libxt_TPROXY.man b/extensions/libxt_TPROXY.man
new file mode 100644
index 0000000..2f7d82d
--- /dev/null
+++ b/extensions/libxt_TPROXY.man
@@ -0,0 +1,21 @@
+This target is only valid in the \fBmangle\fP table, in the \fBPREROUTING\fP
+chain and user-defined chains which are only called from this chain. It
+redirects the packet to a local socket without changing the packet header in
+any way. It can also change the mark value which can then be used in advanced
+routing rules.
+It takes three options:
+.TP
+\fB\-\-on\-port\fP \fIport\fP
+This specifies a destination port to use. It is a required option, 0 means the
+new destination port is the same as the original. This is only valid if the
+rule also specifies \fB\-p tcp\fP or \fB\-p udp\fP.
+.TP
+\fB\-\-on\-ip\fP \fIaddress\fP
+This specifies a destination address to use. By default the address is the IP
+address of the incoming interface. This is only valid if the rule also
+specifies \fB\-p tcp\fP or \fB\-p udp\fP.
+.TP
+\fB\-\-tproxy\-mark\fP \fIvalue\fP[\fB/\fP\fImask\fP]
+Marks packets with the given value/mask. The fwmark value set here can be used
+by advanced routing. (Required for transparent proxying to work: otherwise
+these packets will get forwarded, which is probably not what you want.)
diff --git a/extensions/libxt_TPROXY.t b/extensions/libxt_TPROXY.t
new file mode 100644
index 0000000..12f82b1
--- /dev/null
+++ b/extensions/libxt_TPROXY.t
@@ -0,0 +1,5 @@
+:PREROUTING
+*mangle
+-j TPROXY --on-port 12345 --on-ip 10.0.0.1 --tproxy-mark 0x23/0xff;;FAIL
+-p udp -j TPROXY --on-port 12345 --on-ip 10.0.0.1 --tproxy-mark 0x23/0xff;=;OK
+-p tcp -m tcp --dport 2342 -j TPROXY --on-port 12345 --on-ip 10.0.0.1 --tproxy-mark 0x23/0xff;=;OK
diff --git a/extensions/libxt_TRACE.c b/extensions/libxt_TRACE.c
new file mode 100644
index 0000000..ac4f6fa
--- /dev/null
+++ b/extensions/libxt_TRACE.c
@@ -0,0 +1,29 @@
+/* Shared library add-on to iptables to add TRACE target support. */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+#include <xtables.h>
+#include <linux/netfilter/x_tables.h>
+
+static int trace_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	xt_xlate_add(xl, "nftrace set 1");
+	return 1;
+}
+
+static struct xtables_target trace_target = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "TRACE",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(0),
+	.userspacesize	= XT_ALIGN(0),
+	.xlate		= trace_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&trace_target);
+}
diff --git a/extensions/libxt_TRACE.man b/extensions/libxt_TRACE.man
new file mode 100644
index 0000000..5187a8d
--- /dev/null
+++ b/extensions/libxt_TRACE.man
@@ -0,0 +1,20 @@
+This target marks packets so that the kernel will log every rule which match 
+the packets as those traverse the tables, chains, rules. It can only be used in
+the
+.BR raw
+table.
+.PP
+With iptables-legacy, a logging backend, such as ip(6)t_LOG or nfnetlink_log,
+must be loaded for this to be visible.
+The packets are logged with the string prefix:
+"TRACE: tablename:chainname:type:rulenum " where type can be "rule" for 
+plain rule, "return" for implicit rule at the end of a user defined chain 
+and "policy" for the policy of the built in chains. 
+.PP
+With iptables-nft, the target is translated into nftables'
+.B "meta nftrace"
+expression. Hence the kernel sends trace events via netlink to userspace where
+they may be displayed using
+.B "xtables-monitor --trace"
+command. For details, refer to
+.BR xtables-monitor (8).
diff --git a/extensions/libxt_TRACE.t b/extensions/libxt_TRACE.t
new file mode 100644
index 0000000..cadb733
--- /dev/null
+++ b/extensions/libxt_TRACE.t
@@ -0,0 +1,3 @@
+:PREROUTING,OUTPUT
+*raw
+-j TRACE;=;OK
diff --git a/extensions/libxt_TRACE.txlate b/extensions/libxt_TRACE.txlate
new file mode 100644
index 0000000..8e3d2a7
--- /dev/null
+++ b/extensions/libxt_TRACE.txlate
@@ -0,0 +1,2 @@
+iptables-translate -t raw -A PREROUTING -j TRACE
+nft add rule ip raw PREROUTING counter nftrace set 1
diff --git a/extensions/libxt_addrtype.c b/extensions/libxt_addrtype.c
new file mode 100644
index 0000000..5cafa21
--- /dev/null
+++ b/extensions/libxt_addrtype.c
@@ -0,0 +1,372 @@
+/* Shared library add-on to iptables to add addrtype matching support 
+ *
+ * Copyright (c) 2003-2013 Patrick McHardy <kaber@trash.net>
+ * 
+ * This program is released under the terms of GNU GPL */
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_addrtype.h>
+
+enum {
+	O_SRC_TYPE = 0,
+	O_DST_TYPE,
+	O_LIMIT_IFACE_IN,
+	O_LIMIT_IFACE_OUT,
+	F_SRC_TYPE        = 1 << O_SRC_TYPE,
+	F_DST_TYPE        = 1 << O_DST_TYPE,
+	F_LIMIT_IFACE_IN  = 1 << O_LIMIT_IFACE_IN,
+	F_LIMIT_IFACE_OUT = 1 << O_LIMIT_IFACE_OUT,
+};
+
+/* from linux/rtnetlink.h, must match order of enumeration */
+static const char *const rtn_names[] = {
+	"UNSPEC",
+	"UNICAST",
+	"LOCAL",
+	"BROADCAST",
+	"ANYCAST",
+	"MULTICAST",
+	"BLACKHOLE",
+	"UNREACHABLE",
+	"PROHIBIT",
+	"THROW",
+	"NAT",
+	"XRESOLVE",
+	NULL
+};
+
+static void addrtype_help_types(void)
+{
+	int i;
+
+	for (i = 0; rtn_names[i]; i++)
+		printf("                                %s\n", rtn_names[i]);
+}
+
+static void addrtype_help_v0(void)
+{
+	printf(
+"Address type match options:\n"
+" [!] --src-type type[,...]      Match source address type\n"
+" [!] --dst-type type[,...]      Match destination address type\n"
+"\n"
+"Valid types:           \n");
+	addrtype_help_types();
+}
+
+static void addrtype_help_v1(void)
+{
+	printf(
+"Address type match options:\n"
+" [!] --src-type type[,...]      Match source address type\n"
+" [!] --dst-type type[,...]      Match destination address type\n"
+"     --limit-iface-in           Match only on the packet's incoming device\n"
+"     --limit-iface-out          Match only on the packet's outgoing device\n"
+"\n"
+"Valid types:           \n");
+	addrtype_help_types();
+}
+
+static int
+parse_type(const char *name, size_t len, uint16_t *mask)
+{
+	int i;
+
+	for (i = 0; rtn_names[i]; i++)
+		if (strncasecmp(name, rtn_names[i], len) == 0) {
+			/* build up bitmask for kernel module */
+			*mask |= (1 << i);
+			return 1;
+		}
+
+	return 0;
+}
+
+static void parse_types(const char *arg, uint16_t *mask)
+{
+	const char *comma;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg || !parse_type(arg, comma-arg, mask))
+			xtables_error(PARAMETER_PROBLEM,
+			           "addrtype: bad type `%s'", arg);
+		arg = comma + 1;
+	}
+
+	if (strlen(arg) == 0 || !parse_type(arg, strlen(arg), mask))
+		xtables_error(PARAMETER_PROBLEM, "addrtype: bad type \"%s\"", arg);
+}
+	
+static void addrtype_parse_v0(struct xt_option_call *cb)
+{
+	struct xt_addrtype_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRC_TYPE:
+		parse_types(cb->arg, &info->source);
+		if (cb->invert)
+			info->invert_source = 1;
+		break;
+	case O_DST_TYPE:
+		parse_types(cb->arg, &info->dest);
+		if (cb->invert)
+			info->invert_dest = 1;
+		break;
+	}
+}
+
+static void addrtype_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_addrtype_info_v1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRC_TYPE:
+		parse_types(cb->arg, &info->source);
+		if (cb->invert)
+			info->flags |= XT_ADDRTYPE_INVERT_SOURCE;
+		break;
+	case O_DST_TYPE:
+		parse_types(cb->arg, &info->dest);
+		if (cb->invert)
+			info->flags |= XT_ADDRTYPE_INVERT_DEST;
+		break;
+	case O_LIMIT_IFACE_IN:
+		info->flags |= XT_ADDRTYPE_LIMIT_IFACE_IN;
+		break;
+	case O_LIMIT_IFACE_OUT:
+		info->flags |= XT_ADDRTYPE_LIMIT_IFACE_OUT;
+		break;
+	}
+}
+
+static void addrtype_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & (F_SRC_TYPE | F_DST_TYPE)))
+		xtables_error(PARAMETER_PROBLEM,
+			   "addrtype: you must specify --src-type or --dst-type");
+}
+
+static void print_types(uint16_t mask)
+{
+	const char *sep = "";
+	int i;
+
+	for (i = 0; rtn_names[i]; i++)
+		if (mask & (1 << i)) {
+			printf("%s%s", sep, rtn_names[i]);
+			sep = ",";
+		}
+}
+
+static void addrtype_print_v0(const void *ip, const struct xt_entry_match *match,
+                              int numeric)
+{
+	const struct xt_addrtype_info *info = (const void *)match->data;
+
+	printf(" ADDRTYPE match");
+	if (info->source) {
+		printf(" src-type ");
+		if (info->invert_source)
+			printf("!");
+		print_types(info->source);
+	}
+	if (info->dest) {
+		printf(" dst-type");
+		if (info->invert_dest)
+			printf("!");
+		print_types(info->dest);
+	}
+}
+
+static void addrtype_print_v1(const void *ip, const struct xt_entry_match *match,
+                              int numeric)
+{
+	const struct xt_addrtype_info_v1 *info = (const void *)match->data;
+
+	printf(" ADDRTYPE match");
+	if (info->source) {
+		printf(" src-type ");
+		if (info->flags & XT_ADDRTYPE_INVERT_SOURCE)
+			printf("!");
+		print_types(info->source);
+	}
+	if (info->dest) {
+		printf(" dst-type ");
+		if (info->flags & XT_ADDRTYPE_INVERT_DEST)
+			printf("!");
+		print_types(info->dest);
+	}
+	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN)
+		printf(" limit-in");
+	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
+		printf(" limit-out");
+}
+
+static void addrtype_save_v0(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_addrtype_info *info = (const void *)match->data;
+
+	if (info->source) {
+		if (info->invert_source)
+			printf(" !");
+		printf(" --src-type ");
+		print_types(info->source);
+	}
+	if (info->dest) {
+		if (info->invert_dest)
+			printf(" !");
+		printf(" --dst-type ");
+		print_types(info->dest);
+	}
+}
+
+static void addrtype_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_addrtype_info_v1 *info = (const void *)match->data;
+
+	if (info->source) {
+		if (info->flags & XT_ADDRTYPE_INVERT_SOURCE)
+			printf(" !");
+		printf(" --src-type ");
+		print_types(info->source);
+	}
+	if (info->dest) {
+		if (info->flags & XT_ADDRTYPE_INVERT_DEST)
+			printf(" !");
+		printf(" --dst-type ");
+		print_types(info->dest);
+	}
+	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN)
+		printf(" --limit-iface-in");
+	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
+		printf(" --limit-iface-out");
+}
+
+static const char *const rtn_lnames[] = {
+	"unspec",
+	"unicast",
+	"local",
+	"broadcast",
+	"anycast",
+	"multicast",
+	"blackhole",
+	"unreachable",
+	"prohibit",
+	NULL,
+};
+
+static bool multiple_bits_set(uint16_t val)
+{
+	int first = ffs(val);
+
+	return first && (val >> first) > 0;
+}
+
+static int addrtype_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_addrtype_info_v1 *info =
+		(const void *)params->match->data;
+	const char *sep = "";
+	bool need_braces;
+	uint16_t val;
+	int i;
+
+	xt_xlate_add(xl, "fib ");
+
+	if (info->source) {
+		xt_xlate_add(xl, "saddr ");
+		val = info->source;
+	} else {
+		xt_xlate_add(xl, "daddr ");
+		val = info->dest;
+	}
+
+	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN)
+		xt_xlate_add(xl, ". iif ");
+	else if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
+		xt_xlate_add(xl, ". oif ");
+
+	xt_xlate_add(xl, "type ");
+
+	if (info->flags & (XT_ADDRTYPE_INVERT_SOURCE | XT_ADDRTYPE_INVERT_DEST))
+			xt_xlate_add(xl, "!= ");
+
+	need_braces = multiple_bits_set(val);
+
+	if (need_braces)
+		xt_xlate_add(xl, "{ ");
+
+	for (i = 0; rtn_lnames[i]; i++) {
+		if (val & (1 << i)) {
+			xt_xlate_add(xl, "%s%s", sep, rtn_lnames[i]);
+			sep = ", ";
+		}
+	}
+
+	if (need_braces)
+		xt_xlate_add(xl, " }");
+
+	return 1;
+}
+
+static const struct xt_option_entry addrtype_opts_v0[] = {
+	{.name = "src-type", .id = O_SRC_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "dst-type", .id = O_DST_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry addrtype_opts_v1[] = {
+	{.name = "src-type", .id = O_SRC_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "dst-type", .id = O_DST_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "limit-iface-in", .id = O_LIMIT_IFACE_IN,
+	 .type = XTTYPE_NONE, .excl = F_LIMIT_IFACE_OUT},
+	{.name = "limit-iface-out", .id = O_LIMIT_IFACE_OUT,
+	 .type = XTTYPE_NONE, .excl = F_LIMIT_IFACE_IN},
+	XTOPT_TABLEEND,
+};
+
+static struct xtables_match addrtype_mt_reg[] = {
+	{
+		.name          = "addrtype",
+		.version       = XTABLES_VERSION,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_addrtype_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_addrtype_info)),
+		.help          = addrtype_help_v0,
+		.print         = addrtype_print_v0,
+		.save          = addrtype_save_v0,
+		.x6_parse      = addrtype_parse_v0,
+		.x6_fcheck     = addrtype_check,
+		.x6_options    = addrtype_opts_v0,
+	},
+	{
+		.name          = "addrtype",
+		.revision      = 1,
+		.version       = XTABLES_VERSION,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_addrtype_info_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_addrtype_info_v1)),
+		.help          = addrtype_help_v1,
+		.print         = addrtype_print_v1,
+		.save          = addrtype_save_v1,
+		.x6_parse      = addrtype_parse_v1,
+		.x6_fcheck     = addrtype_check,
+		.x6_options    = addrtype_opts_v1,
+		.xlate         = addrtype_xlate,
+	},
+};
+
+
+void _init(void) 
+{
+	xtables_register_matches(addrtype_mt_reg, ARRAY_SIZE(addrtype_mt_reg));
+}
diff --git a/extensions/libxt_addrtype.man b/extensions/libxt_addrtype.man
new file mode 100644
index 0000000..16fd9df
--- /dev/null
+++ b/extensions/libxt_addrtype.man
@@ -0,0 +1,69 @@
+This module matches packets based on their 
+.B address type.
+Address types are used within the kernel networking stack and categorize
+addresses into various groups.  The exact definition of that group depends on the specific layer three protocol.
+.PP
+The following address types are possible:
+.TP
+.BI "UNSPEC"
+an unspecified address (i.e. 0.0.0.0)
+.TP
+.BI "UNICAST"
+an unicast address
+.TP
+.BI "LOCAL"
+a local address
+.TP
+.BI "BROADCAST"
+a broadcast address
+.TP
+.BI "ANYCAST"
+an anycast packet
+.TP
+.BI "MULTICAST"
+a multicast address
+.TP
+.BI "BLACKHOLE"
+a blackhole address
+.TP
+.BI "UNREACHABLE"
+an unreachable address
+.TP
+.BI "PROHIBIT"
+a prohibited address
+.TP
+.BI "THROW"
+FIXME
+.TP
+.BI "NAT"
+FIXME
+.TP
+.BI "XRESOLVE"
+.TP
+[\fB!\fP] \fB\-\-src\-type\fP \fItype\fP
+Matches if the source address is of given type
+.TP
+[\fB!\fP] \fB\-\-dst\-type\fP \fItype\fP
+Matches if the destination address is of given type
+.TP
+.BI "\-\-limit\-iface\-in"
+The address type checking can be limited to the interface the packet is coming
+in. This option is only valid in the
+.BR PREROUTING ,
+.B INPUT
+and
+.B FORWARD
+chains. It cannot be specified with the
+\fB\-\-limit\-iface\-out\fP
+option.
+.TP
+\fB\-\-limit\-iface\-out\fP
+The address type checking can be limited to the interface the packet is going
+out. This option is only valid in the
+.BR POSTROUTING ,
+.B OUTPUT
+and
+.B FORWARD
+chains. It cannot be specified with the
+\fB\-\-limit\-iface\-in\fP
+option.
diff --git a/extensions/libxt_addrtype.t b/extensions/libxt_addrtype.t
new file mode 100644
index 0000000..390a63f
--- /dev/null
+++ b/extensions/libxt_addrtype.t
@@ -0,0 +1,17 @@
+:INPUT,FORWARD,OUTPUT
+-m addrtype;;FAIL
+-m addrtype --src-type wrong;;FAIL
+-m addrtype --src-type UNSPEC;=;OK
+-m addrtype --dst-type UNSPEC;=;OK
+-m addrtype --src-type LOCAL --dst-type LOCAL;=;OK
+-m addrtype --dst-type UNSPEC;=;OK
+-m addrtype --limit-iface-in;;FAIL
+-m addrtype --limit-iface-out;;FAIL
+-m addrtype --limit-iface-in --limit-iface-out;;FAIL
+-m addrtype --src-type LOCAL --limit-iface-in --limit-iface-out;;FAIL
+:INPUT
+-m addrtype --src-type LOCAL --limit-iface-in;=;OK
+-m addrtype --dst-type LOCAL --limit-iface-in;=;OK
+:OUTPUT
+-m addrtype --src-type LOCAL --limit-iface-out;=;OK
+-m addrtype --dst-type LOCAL --limit-iface-out;=;OK
diff --git a/extensions/libxt_addrtype.txlate b/extensions/libxt_addrtype.txlate
new file mode 100644
index 0000000..a719b2c
--- /dev/null
+++ b/extensions/libxt_addrtype.txlate
@@ -0,0 +1,11 @@
+iptables-translate -A INPUT -m addrtype --src-type LOCAL
+nft add rule ip filter INPUT fib saddr type local counter
+
+iptables-translate -A INPUT -m addrtype --dst-type LOCAL
+nft add rule ip filter INPUT fib daddr type local counter
+
+iptables-translate -A INPUT -m addrtype ! --dst-type ANYCAST,LOCAL
+nft add rule ip filter INPUT fib daddr type != { local, anycast } counter
+
+iptables-translate -A INPUT -m addrtype --limit-iface-in --dst-type ANYCAST,LOCAL
+nft add rule ip filter INPUT fib daddr . iif type { local, anycast } counter
diff --git a/extensions/libxt_bpf.c b/extensions/libxt_bpf.c
new file mode 100644
index 0000000..eeae86e
--- /dev/null
+++ b/extensions/libxt_bpf.c
@@ -0,0 +1,297 @@
+/*
+ * Xtables BPF extension
+ *
+ * Written by Willem de Bruijn (willemb@google.com)
+ * Copyright Google, Inc. 2013
+ * Licensed under the GNU General Public License version 2 (GPLv2)
+*/
+
+#include <linux/netfilter/xt_bpf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xtables.h>
+#include "config.h"
+
+#ifdef HAVE_LINUX_BPF_H
+#include <linux/bpf.h>
+#endif
+
+#include <linux/magic.h>
+#include <linux/unistd.h>
+
+#define BCODE_FILE_MAX_LEN_B	1024
+
+enum {
+	O_BCODE_STDIN = 0,
+	O_OBJ_PINNED = 1,
+};
+
+static void bpf_help(void)
+{
+	printf(
+"bpf match options:\n"
+"--bytecode <program>	: a bpf program as generated by\n"
+"                         $(nfbpf_compile RAW '<filter>')\n");
+}
+
+static void bpf_help_v1(void)
+{
+	printf(
+"bpf match options:\n"
+"--bytecode <program>	        : a bpf program as generated by\n"
+"                                 $(nfbpf_compile RAW '<filter>')\n"
+"--object-pinned <bpf object>	: a path to a pinned BPF object in bpf fs\n");
+}
+
+static const struct xt_option_entry bpf_opts[] = {
+	{.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry bpf_opts_v1[] = {
+	{.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING},
+	{.name = "object-pinned" , .id = O_OBJ_PINNED, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_bpf_info_v1, path)},
+	XTOPT_TABLEEND,
+};
+
+static int bpf_obj_get_readonly(const char *filepath)
+{
+#if defined HAVE_LINUX_BPF_H && defined __NR_bpf && defined BPF_FS_MAGIC
+	/* union bpf_attr includes this in an anonymous struct, but the
+	 * file_flags field and the BPF_F_RDONLY constant are only present
+	 * in Linux 4.15+ kernel headers (include/uapi/linux/bpf.h)
+	 */
+	struct {   // this part of union bpf_attr is for BPF_OBJ_* commands
+		__aligned_u64	pathname;
+		__u32		bpf_fd;
+		__u32		file_flags;
+	} attr = {
+		.pathname = (__u64)filepath,
+		.file_flags = (1U << 3),   // BPF_F_RDONLY
+	};
+	int fd = syscall(__NR_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
+	if (fd >= 0) return fd;
+
+	/* on any error fallback to default R/W access for pre-4.15-rc1 kernels */
+	attr.file_flags = 0;
+	return syscall(__NR_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
+#else
+	xtables_error(OTHER_PROBLEM,
+		      "No bpf header, kernel headers too old?\n");
+	return -EINVAL;
+#endif
+}
+
+static void bpf_parse_string(struct sock_filter *pc, __u16 *lenp, __u16 len_max,
+			     const char *bpf_program)
+{
+	const char separator = ',';
+	const char *token;
+	char sp;
+	int i;
+	__u16 len;
+
+	/* parse head: length. */
+	if (sscanf(bpf_program, "%hu%c", &len, &sp) != 2 ||
+		   sp != separator)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: error parsing program length");
+	if (!len)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: illegal zero length program");
+	if (len > len_max)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: number of instructions exceeds maximum");
+
+	/* parse instructions. */
+	i = 0;
+	token = bpf_program;
+	while ((token = strchr(token, separator)) && (++token)[0]) {
+		if (i >= len)
+			xtables_error(PARAMETER_PROBLEM,
+				      "bpf: real program length exceeds"
+				      " the encoded length parameter");
+		if (sscanf(token, "%hu %hhu %hhu %u,",
+			   &pc->code, &pc->jt, &pc->jf, &pc->k) != 4)
+			xtables_error(PARAMETER_PROBLEM,
+				      "bpf: error at instr %d", i);
+		i++;
+		pc++;
+	}
+
+	if (i != len)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: parsed program length is less than the"
+			      " encoded length parameter");
+
+	*lenp = len;
+}
+
+static void bpf_parse_obj_pinned(struct xt_bpf_info_v1 *bi,
+				 const char *filepath)
+{
+	bi->fd = bpf_obj_get_readonly(filepath);
+	if (bi->fd < 0)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: failed to get bpf object");
+
+	/* Cannot close bi->fd explicitly. Rely on exit */
+	if (fcntl(bi->fd, F_SETFD, FD_CLOEXEC) == -1) {
+		xtables_error(OTHER_PROBLEM,
+			      "Could not set close on exec: %s\n",
+			      strerror(errno));
+	}
+}
+
+static void bpf_parse(struct xt_option_call *cb)
+{
+	struct xt_bpf_info *bi = (void *) cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_BCODE_STDIN:
+		bpf_parse_string(bi->bpf_program, &bi->bpf_program_num_elem,
+				 ARRAY_SIZE(bi->bpf_program), cb->arg);
+		break;
+	default:
+		xtables_error(PARAMETER_PROBLEM, "bpf: unknown option");
+	}
+}
+
+static void bpf_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_bpf_info_v1 *bi = (void *) cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_BCODE_STDIN:
+		bpf_parse_string(bi->bpf_program, &bi->bpf_program_num_elem,
+				 ARRAY_SIZE(bi->bpf_program), cb->arg);
+		bi->mode = XT_BPF_MODE_BYTECODE;
+		break;
+	case O_OBJ_PINNED:
+		bpf_parse_obj_pinned(bi, cb->arg);
+		bi->mode = XT_BPF_MODE_FD_PINNED;
+		break;
+	default:
+		xtables_error(PARAMETER_PROBLEM, "bpf: unknown option");
+	}
+}
+
+static void bpf_print_code(const struct sock_filter *pc, __u16 len, char tail)
+{
+	for (; len; len--, pc++)
+		printf("%hu %hhu %hhu %u%c",
+		       pc->code, pc->jt, pc->jf, pc->k,
+		       len > 1 ? ',' : tail);
+}
+
+static void bpf_save_code(const struct sock_filter *pc, __u16 len)
+{
+	printf(" --bytecode \"%hu,", len);
+	bpf_print_code(pc, len, '\"');
+}
+
+static void bpf_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_bpf_info *info = (void *) match->data;
+
+	bpf_save_code(info->bpf_program, info->bpf_program_num_elem);
+}
+
+static void bpf_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_bpf_info_v1 *info = (void *) match->data;
+
+	if (info->mode == XT_BPF_MODE_BYTECODE)
+		bpf_save_code(info->bpf_program, info->bpf_program_num_elem);
+	else if (info->mode == XT_BPF_MODE_FD_PINNED)
+		printf(" --object-pinned %s", info->path);
+	else
+		xtables_error(OTHER_PROBLEM, "unknown bpf mode");
+}
+
+static void bpf_fcheck(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & (1 << O_BCODE_STDIN)))
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: missing --bytecode parameter");
+}
+
+static void bpf_fcheck_v1(struct xt_fcheck_call *cb)
+{
+	const unsigned int bit_bcode = 1 << O_BCODE_STDIN;
+	const unsigned int bit_pinned = 1 << O_OBJ_PINNED;
+	unsigned int flags;
+
+	flags = cb->xflags & (bit_bcode | bit_pinned);
+	if (flags != bit_bcode && flags != bit_pinned)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: one of --bytecode or --pinned is required");
+}
+
+static void bpf_print(const void *ip, const struct xt_entry_match *match,
+		      int numeric)
+{
+	const struct xt_bpf_info *info = (void *) match->data;
+
+	printf("match bpf ");
+	bpf_print_code(info->bpf_program, info->bpf_program_num_elem, '\0');
+}
+
+static void bpf_print_v1(const void *ip, const struct xt_entry_match *match,
+			 int numeric)
+{
+	const struct xt_bpf_info_v1 *info = (void *) match->data;
+
+	printf("match bpf ");
+	if (info->mode == XT_BPF_MODE_BYTECODE)
+		bpf_print_code(info->bpf_program, info->bpf_program_num_elem, '\0');
+	else if (info->mode == XT_BPF_MODE_FD_PINNED)
+		printf("pinned %s", info->path);
+	else
+		printf("unknown");
+}
+
+static struct xtables_match bpf_matches[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "bpf",
+		.version	= XTABLES_VERSION,
+		.revision	= 0,
+		.size		= XT_ALIGN(sizeof(struct xt_bpf_info)),
+		.userspacesize	= XT_ALIGN(offsetof(struct xt_bpf_info, filter)),
+		.help		= bpf_help,
+		.print		= bpf_print,
+		.save		= bpf_save,
+		.x6_parse	= bpf_parse,
+		.x6_fcheck	= bpf_fcheck,
+		.x6_options	= bpf_opts,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "bpf",
+		.version	= XTABLES_VERSION,
+		.revision	= 1,
+		.size		= XT_ALIGN(sizeof(struct xt_bpf_info_v1)),
+		.userspacesize	= XT_ALIGN(offsetof(struct xt_bpf_info_v1, filter)),
+		.help		= bpf_help_v1,
+		.print		= bpf_print_v1,
+		.save		= bpf_save_v1,
+		.x6_parse	= bpf_parse_v1,
+		.x6_fcheck	= bpf_fcheck_v1,
+		.x6_options	= bpf_opts_v1,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(bpf_matches, ARRAY_SIZE(bpf_matches));
+}
diff --git a/extensions/libxt_bpf.man b/extensions/libxt_bpf.man
new file mode 100644
index 0000000..d6da204
--- /dev/null
+++ b/extensions/libxt_bpf.man
@@ -0,0 +1,60 @@
+Match using Linux Socket Filter. Expects a path to an eBPF object or a cBPF
+program in decimal format.
+.TP
+\fB\-\-object\-pinned\fP \fIpath\fP
+Pass a path to a pinned eBPF object.
+.PP
+Applications load eBPF programs into the kernel with the bpf() system call and
+BPF_PROG_LOAD command and can pin them in a virtual filesystem with BPF_OBJ_PIN.
+To use a pinned object in iptables, mount the bpf filesystem using
+.IP
+mount \-t bpf bpf ${BPF_MOUNT}
+.PP
+then insert the filter in iptables by path:
+.IP
+iptables \-A OUTPUT \-m bpf \-\-object\-pinned ${BPF_MOUNT}/{PINNED_PATH} \-j ACCEPT
+.TP
+\fB\-\-bytecode\fP \fIcode\fP
+Pass the BPF byte code format as generated by the \fBnfbpf_compile\fP utility.
+.PP
+The code format is similar to the output of the tcpdump \-ddd command: one line
+that stores the number of instructions, followed by one line for each
+instruction. Instruction lines follow the pattern 'u16 u8 u8 u32' in decimal
+notation. Fields encode the operation, jump offset if true, jump offset if
+false and generic multiuse field 'K'. Comments are not supported.
+.PP
+For example, to read only packets matching 'ip proto 6', insert the following,
+without the comments or trailing whitespace:
+.IP
+4               # number of instructions
+.br
+48 0 0 9        # load byte  ip->proto
+.br
+21 0 1 6        # jump equal IPPROTO_TCP
+.br
+6 0 0 1         # return     pass (non-zero)
+.br
+6 0 0 0         # return     fail (zero)
+.PP
+You can pass this filter to the bpf match with the following command:
+.IP
+iptables \-A OUTPUT \-m bpf \-\-bytecode '4,48 0 0 9,21 0 1 6,6 0 0 1,6 0 0 0' \-j ACCEPT
+.PP
+Or instead, you can invoke the nfbpf_compile utility.
+.IP
+iptables \-A OUTPUT \-m bpf \-\-bytecode "`nfbpf_compile RAW 'ip proto 6'`" \-j ACCEPT
+.PP
+Or use tcpdump -ddd. In that case, generate BPF targeting a device with the
+same data link type as the xtables match. Iptables passes packets from the
+network layer up, without mac layer. Select a device with data link type RAW,
+such as a tun device:
+.IP
+ip tuntap add tun0 mode tun
+.br
+ip link set tun0 up
+.br
+tcpdump -ddd -i tun0 ip proto 6
+.PP
+See tcpdump -L -i $dev for a list of known data link types for a given device.
+.PP
+You may want to learn more about BPF from FreeBSD's bpf(4) manpage.
diff --git a/extensions/libxt_bpf.t b/extensions/libxt_bpf.t
new file mode 100644
index 0000000..80361ad
--- /dev/null
+++ b/extensions/libxt_bpf.t
@@ -0,0 +1,2 @@
+:INPUT,FORWARD,OUTPUT
+-m bpf --bytecode "4,48 0 0 9,21 0 1 6,6 0 0 1,6 0 0 0";=;OK
diff --git a/extensions/libxt_cgroup.c b/extensions/libxt_cgroup.c
new file mode 100644
index 0000000..327032e
--- /dev/null
+++ b/extensions/libxt_cgroup.c
@@ -0,0 +1,278 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_cgroup.h>
+
+enum {
+	O_CLASSID = 0,
+	O_PATH = 1,
+};
+
+static void cgroup_help_v0(void)
+{
+	printf(
+"cgroup match options:\n"
+"[!] --cgroup classid            Match cgroup classid\n");
+}
+
+static void cgroup_help_v1(void)
+{
+	printf(
+"cgroup match options:\n"
+"[!] --path path                 Recursively match path relative to cgroup2 root\n"
+"[!] --cgroup classid            Match cgroup classid, can't be used with --path\n");
+}
+
+static const struct xt_option_entry cgroup_opts_v0[] = {
+	{
+		.name = "cgroup",
+		.id = O_CLASSID,
+		.type = XTTYPE_UINT32,
+		.flags = XTOPT_INVERT | XTOPT_MAND | XTOPT_PUT,
+		XTOPT_POINTER(struct xt_cgroup_info_v0, id)
+	},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry cgroup_opts_v1[] = {
+	{
+		.name = "path",
+		.id = O_PATH,
+		.type = XTTYPE_STRING,
+		.flags = XTOPT_INVERT | XTOPT_PUT,
+		XTOPT_POINTER(struct xt_cgroup_info_v1, path)
+	},
+	{
+		.name = "cgroup",
+		.id = O_CLASSID,
+		.type = XTTYPE_UINT32,
+		.flags = XTOPT_INVERT | XTOPT_PUT,
+		XTOPT_POINTER(struct xt_cgroup_info_v1, classid)
+	},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry cgroup_opts_v2[] = {
+	{
+		.name = "path",
+		.id = O_PATH,
+		.type = XTTYPE_STRING,
+		.flags = XTOPT_INVERT | XTOPT_PUT,
+		XTOPT_POINTER(struct xt_cgroup_info_v2, path)
+	},
+	{
+		.name = "cgroup",
+		.id = O_CLASSID,
+		.type = XTTYPE_UINT32,
+		.flags = XTOPT_INVERT | XTOPT_PUT,
+		XTOPT_POINTER(struct xt_cgroup_info_v2, classid)
+	},
+	XTOPT_TABLEEND,
+};
+
+static void cgroup_parse_v0(struct xt_option_call *cb)
+{
+	struct xt_cgroup_info_v0 *cgroupinfo = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->invert)
+		cgroupinfo->invert = true;
+}
+
+static void cgroup_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_cgroup_info_v1 *info = cb->data;
+
+	xtables_option_parse(cb);
+
+	switch (cb->entry->id) {
+	case O_PATH:
+		info->has_path = true;
+		if (cb->invert)
+			info->invert_path = true;
+		break;
+	case O_CLASSID:
+		info->has_classid = true;
+		if (cb->invert)
+			info->invert_classid = true;
+		break;
+	}
+}
+
+static void cgroup_parse_v2(struct xt_option_call *cb)
+{
+	struct xt_cgroup_info_v2 *info = cb->data;
+
+	xtables_option_parse(cb);
+
+	switch (cb->entry->id) {
+	case O_PATH:
+		info->has_path = true;
+		if (cb->invert)
+			info->invert_path = true;
+		break;
+	case O_CLASSID:
+		info->has_classid = true;
+		if (cb->invert)
+			info->invert_classid = true;
+		break;
+	}
+}
+
+static void
+cgroup_print_v0(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_cgroup_info_v0 *info = (void *) match->data;
+
+	printf(" cgroup %s%u", info->invert ? "! ":"", info->id);
+}
+
+static void cgroup_save_v0(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_cgroup_info_v0 *info = (void *) match->data;
+
+	printf("%s --cgroup %u", info->invert ? " !" : "", info->id);
+}
+
+static void
+cgroup_print_v1(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_cgroup_info_v1 *info = (void *)match->data;
+
+	printf(" cgroup");
+	if (info->has_path)
+		printf(" %s%s", info->invert_path ? "! ":"", info->path);
+	if (info->has_classid)
+		printf(" %s%u", info->invert_classid ? "! ":"", info->classid);
+}
+
+static void cgroup_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_cgroup_info_v1 *info = (void *)match->data;
+
+	if (info->has_path) {
+		printf("%s --path", info->invert_path ? " !" : "");
+		xtables_save_string(info->path);
+	}
+
+	if (info->has_classid)
+		printf("%s --cgroup %u", info->invert_classid ? " !" : "",
+		       info->classid);
+}
+
+static void
+cgroup_print_v2(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_cgroup_info_v2 *info = (void *)match->data;
+
+	printf(" cgroup");
+	if (info->has_path)
+		printf(" %s%s", info->invert_path ? "! ":"", info->path);
+	if (info->has_classid)
+		printf(" %s%u", info->invert_classid ? "! ":"", info->classid);
+}
+
+static void cgroup_save_v2(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_cgroup_info_v2 *info = (void *)match->data;
+
+	if (info->has_path) {
+		printf("%s --path", info->invert_path ? " !" : "");
+		xtables_save_string(info->path);
+	}
+
+	if (info->has_classid)
+		printf("%s --cgroup %u", info->invert_classid ? " !" : "",
+		       info->classid);
+}
+
+static int cgroup_xlate_v0(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_cgroup_info_v0 *info = (void *)params->match->data;
+
+	xt_xlate_add(xl, "meta cgroup %s%u", info->invert ? "!= " : "",
+		     info->id);
+	return 1;
+}
+
+static int cgroup_xlate_v1(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_cgroup_info_v1 *info = (void *)params->match->data;
+
+	if (info->has_path)
+		return 0;
+
+	if (info->has_classid)
+		xt_xlate_add(xl, "meta cgroup %s%u",
+			     info->invert_classid ? "!= " : "",
+			     info->classid);
+
+	return 1;
+}
+
+static int cgroup_xlate_v2(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_cgroup_info_v2 *info = (void *)params->match->data;
+
+	if (info->has_path)
+		return 0;
+
+	if (info->has_classid)
+		xt_xlate_add(xl, "meta cgroup %s%u",
+			     info->invert_classid ? "!= " : "",
+			     info->classid);
+
+	return 1;
+}
+
+static struct xtables_match cgroup_match[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.revision	= 0,
+		.name		= "cgroup",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_cgroup_info_v0)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_cgroup_info_v0)),
+		.help		= cgroup_help_v0,
+		.print		= cgroup_print_v0,
+		.save		= cgroup_save_v0,
+		.x6_parse	= cgroup_parse_v0,
+		.x6_options	= cgroup_opts_v0,
+		.xlate		= cgroup_xlate_v0,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.revision	= 1,
+		.name		= "cgroup",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_cgroup_info_v1)),
+		.userspacesize	= offsetof(struct xt_cgroup_info_v1, priv),
+		.help		= cgroup_help_v1,
+		.print		= cgroup_print_v1,
+		.save		= cgroup_save_v1,
+		.x6_parse	= cgroup_parse_v1,
+		.x6_options	= cgroup_opts_v1,
+		.xlate		= cgroup_xlate_v1,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.revision	= 2,
+		.name		= "cgroup",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_cgroup_info_v2)),
+		.userspacesize	= offsetof(struct xt_cgroup_info_v2, priv),
+		.help		= cgroup_help_v1,
+		.print		= cgroup_print_v2,
+		.save		= cgroup_save_v2,
+		.x6_parse	= cgroup_parse_v2,
+		.x6_options	= cgroup_opts_v2,
+		.xlate		= cgroup_xlate_v2,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(cgroup_match, ARRAY_SIZE(cgroup_match));
+}
diff --git a/extensions/libxt_cgroup.man b/extensions/libxt_cgroup.man
new file mode 100644
index 0000000..4d5d1d8
--- /dev/null
+++ b/extensions/libxt_cgroup.man
@@ -0,0 +1,30 @@
+.TP
+[\fB!\fP] \fB\-\-path\fP \fIpath\fP
+Match cgroup2 membership.
+
+Each socket is associated with the v2 cgroup of the creating process.
+This matches packets coming from or going to all sockets in the
+sub-hierarchy of the specified path.  The path should be relative to
+the root of the cgroup2 hierarchy.
+.TP
+[\fB!\fP] \fB\-\-cgroup\fP \fIclassid\fP
+Match cgroup net_cls classid.
+
+classid is the marker set through the cgroup net_cls controller.  This
+option and \-\-path can't be used together.
+.PP
+Example:
+.IP
+iptables \-A OUTPUT \-p tcp \-\-sport 80 \-m cgroup ! \-\-path service/http-server \-j DROP
+.IP
+iptables \-A OUTPUT \-p tcp \-\-sport 80 \-m cgroup ! \-\-cgroup 1
+\-j DROP
+.PP
+\fBIMPORTANT\fP: when being used in the INPUT chain, the cgroup
+matcher is currently only of limited functionality, meaning it
+will only match on packets that are processed for local sockets
+through early socket demuxing. Therefore, general usage on the
+INPUT chain is not advised unless the implications are well
+understood.
+.PP
+Available since Linux 3.14.
diff --git a/extensions/libxt_cgroup.t b/extensions/libxt_cgroup.t
new file mode 100644
index 0000000..72c8e37
--- /dev/null
+++ b/extensions/libxt_cgroup.t
@@ -0,0 +1,8 @@
+:INPUT,OUTPUT,POSTROUTING
+*mangle
+-m cgroup --cgroup 1;=;OK
+-m cgroup ! --cgroup 1;=;OK
+-m cgroup --path "/";=;OK
+-m cgroup ! --path "/";=;OK
+-m cgroup --cgroup 1 --path "/";;FAIL
+-m cgroup ;;FAIL
diff --git a/extensions/libxt_cgroup.txlate b/extensions/libxt_cgroup.txlate
new file mode 100644
index 0000000..75f2e6a
--- /dev/null
+++ b/extensions/libxt_cgroup.txlate
@@ -0,0 +1,5 @@
+iptables-translate -t filter -A INPUT -m cgroup --cgroup 0 -j ACCEPT
+nft add rule ip filter INPUT meta cgroup 0 counter accept
+
+iptables-translate -t filter -A INPUT -m cgroup ! --cgroup 0 -j ACCEPT
+nft add rule ip filter INPUT meta cgroup != 0 counter accept
diff --git a/extensions/libxt_cluster.c b/extensions/libxt_cluster.c
new file mode 100644
index 0000000..d164bf6
--- /dev/null
+++ b/extensions/libxt_cluster.c
@@ -0,0 +1,197 @@
+/*
+ * (C) 2009 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_cluster.h>
+
+static void
+cluster_help(void)
+{
+	printf(
+"cluster match options:\n"
+"  --cluster-total-nodes <num>		Set number of total nodes in cluster\n"
+"  [!] --cluster-local-node <num>	Set the local node number\n"
+"  [!] --cluster-local-nodemask <num>	Set the local node mask\n"
+"  --cluster-hash-seed <num>		Set seed value of the Jenkins hash\n");
+}
+
+enum {
+	O_CL_TOTAL_NODES = 0,
+	O_CL_LOCAL_NODE,
+	O_CL_LOCAL_NODEMASK,
+	O_CL_HASH_SEED,
+	F_CL_TOTAL_NODES    = 1 << O_CL_TOTAL_NODES,
+	F_CL_LOCAL_NODE     = 1 << O_CL_LOCAL_NODE,
+	F_CL_LOCAL_NODEMASK = 1 << O_CL_LOCAL_NODEMASK,
+	F_CL_HASH_SEED      = 1 << O_CL_HASH_SEED,
+};
+
+#define s struct xt_cluster_match_info
+static const struct xt_option_entry cluster_opts[] = {
+	{.name = "cluster-total-nodes", .id = O_CL_TOTAL_NODES,
+	 .type = XTTYPE_UINT32, .min = 1, .max = XT_CLUSTER_NODES_MAX,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, total_nodes)},
+	{.name = "cluster-local-node", .id = O_CL_LOCAL_NODE,
+	 .excl = F_CL_LOCAL_NODEMASK, .flags = XTOPT_INVERT,
+	 .type = XTTYPE_UINT32, .min = 1, .max = XT_CLUSTER_NODES_MAX},
+	{.name = "cluster-local-nodemask", .id = O_CL_LOCAL_NODEMASK,
+	 .excl = F_CL_LOCAL_NODE, .type = XTTYPE_UINT32,
+	 .min = 1, .max = XT_CLUSTER_NODES_MAX,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, node_mask)},
+	{.name = "cluster-hash-seed", .id = O_CL_HASH_SEED,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_MAND | XTOPT_PUT,
+	 XTOPT_POINTER(s, hash_seed)},
+	XTOPT_TABLEEND,
+};
+
+static void cluster_parse(struct xt_option_call *cb)
+{
+	struct xt_cluster_match_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_CL_LOCAL_NODE:
+		if (cb->invert)
+			info->flags |= XT_CLUSTER_F_INV;
+		info->node_mask = 1 << (cb->val.u32 - 1);
+		break;
+	case O_CL_LOCAL_NODEMASK:
+		if (cb->invert)
+			info->flags |= XT_CLUSTER_F_INV;
+		break;
+	}
+}
+
+static void cluster_check(struct xt_fcheck_call *cb)
+{
+	const struct xt_cluster_match_info *info = cb->data;
+	unsigned int test;
+
+	test = F_CL_TOTAL_NODES | F_CL_LOCAL_NODE | F_CL_HASH_SEED;
+	if ((cb->xflags & test) == test) {
+		if (info->node_mask >= (1ULL << info->total_nodes))
+			xtables_error(PARAMETER_PROBLEM,
+				      "cluster match: "
+				      "`--cluster-local-node' "
+				      "must be <= `--cluster-total-nodes'");
+		return;
+	}
+
+	test = F_CL_TOTAL_NODES | F_CL_LOCAL_NODEMASK | F_CL_HASH_SEED;
+	if ((cb->xflags & test) == test) {
+		if (info->node_mask >= (1ULL << info->total_nodes))
+			xtables_error(PARAMETER_PROBLEM,
+				      "cluster match: "
+				      "`--cluster-local-nodemask' too big "
+				      "for `--cluster-total-nodes'");
+		return;
+	}
+	if (!(cb->xflags & (F_CL_LOCAL_NODE | F_CL_LOCAL_NODEMASK)))
+		xtables_error(PARAMETER_PROBLEM,
+			      "cluster match: `--cluster-local-node' or"
+			      "`--cluster-local-nodemask' is missing");
+}
+
+static void
+cluster_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_cluster_match_info *info = (void *)match->data;
+
+	printf(" cluster ");
+	if (info->flags & XT_CLUSTER_F_INV)
+		printf("!node_mask=0x%08x", info->node_mask);
+	else
+		printf("node_mask=0x%08x", info->node_mask);
+
+	printf(" total_nodes=%u hash_seed=0x%08x",
+		info->total_nodes, info->hash_seed);
+}
+
+static void
+cluster_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_cluster_match_info *info = (void *)match->data;
+
+	if (info->flags & XT_CLUSTER_F_INV)
+		printf(" ! --cluster-local-nodemask 0x%08x", info->node_mask);
+	else
+		printf(" --cluster-local-nodemask 0x%08x", info->node_mask);
+
+	printf(" --cluster-total-nodes %u --cluster-hash-seed 0x%08x",
+		info->total_nodes, info->hash_seed);
+}
+
+static int cluster_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_mt_params *params)
+{
+	int node, shift_value = 1, comma_needed = 0;
+	uint32_t temp_node_mask, node_id = 0, needs_set = 0;
+	const struct xt_cluster_match_info *info = (void *)params->match->data;
+	const char *jhash_st = "jhash ct original saddr mod";
+	const char *pkttype_st = "meta pkttype set host";
+
+	if (!(info->node_mask & (info->node_mask - 1))) {
+		if (info->node_mask <= 2)
+			xt_xlate_add(xl, "%s %u seed 0x%08x eq %u %s", jhash_st,
+					info->total_nodes, info->hash_seed,
+					info->node_mask, pkttype_st);
+		else {
+			temp_node_mask = info->node_mask;
+			while (1) {
+				temp_node_mask = temp_node_mask >> shift_value;
+				node_id++;
+				if (temp_node_mask == 0)
+					break;
+			}
+			xt_xlate_add(xl, "%s %u seed 0x%08x eq %u %s", jhash_st,
+					info->total_nodes, info->hash_seed,
+					node_id, pkttype_st);
+		}
+	} else {
+		xt_xlate_add(xl, "%s %u seed 0x%08x ", jhash_st,
+				info->total_nodes, info->hash_seed);
+		for (node = 0; node < 32; node++) {
+			if (info->node_mask & (1u << node)) {
+				if (needs_set == 0) {
+					xt_xlate_add(xl, "{ ");
+					needs_set = 1;
+				}
+
+				if (comma_needed)
+					xt_xlate_add(xl, ", ");
+				xt_xlate_add(xl, "%u", node);
+				comma_needed++;
+			}
+		}
+		if (needs_set)
+			xt_xlate_add(xl, " }");
+		xt_xlate_add(xl, " %s", pkttype_st);
+	}
+
+	return 1;
+}
+
+static struct xtables_match cluster_mt_reg = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "cluster",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_cluster_match_info)),
+	.userspacesize  = XT_ALIGN(sizeof(struct xt_cluster_match_info)),
+ 	.help		= cluster_help,
+	.print		= cluster_print,
+	.save		= cluster_save,
+	.x6_parse	= cluster_parse,
+	.x6_fcheck	= cluster_check,
+	.x6_options	= cluster_opts,
+	.xlate		= cluster_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&cluster_mt_reg);
+}
diff --git a/extensions/libxt_cluster.man b/extensions/libxt_cluster.man
new file mode 100644
index 0000000..23448e2
--- /dev/null
+++ b/extensions/libxt_cluster.man
@@ -0,0 +1,67 @@
+Allows you to deploy gateway and back-end load-sharing clusters without the
+need of load-balancers.
+.PP
+This match requires that all the nodes see the same packets. Thus, the cluster
+match decides if this node has to handle a packet given the following options:
+.TP
+\fB\-\-cluster\-total\-nodes\fP \fInum\fP
+Set number of total nodes in cluster.
+.TP
+[\fB!\fP] \fB\-\-cluster\-local\-node\fP \fInum\fP
+Set the local node number ID.
+.TP
+[\fB!\fP] \fB\-\-cluster\-local\-nodemask\fP \fImask\fP
+Set the local node number ID mask. You can use this option instead
+of \fB\-\-cluster\-local\-node\fP.
+.TP
+\fB\-\-cluster\-hash\-seed\fP \fIvalue\fP
+Set seed value of the Jenkins hash.
+.PP
+Example:
+.IP
+iptables \-A PREROUTING \-t mangle \-i eth1 \-m cluster
+\-\-cluster\-total\-nodes 2 \-\-cluster\-local\-node 1
+\-\-cluster\-hash\-seed 0xdeadbeef
+\-j MARK \-\-set-mark 0xffff
+.IP
+iptables \-A PREROUTING \-t mangle \-i eth2 \-m cluster
+\-\-cluster\-total\-nodes 2 \-\-cluster\-local\-node 1
+\-\-cluster\-hash\-seed 0xdeadbeef
+\-j MARK \-\-set\-mark 0xffff
+.IP
+iptables \-A PREROUTING \-t mangle \-i eth1
+\-m mark ! \-\-mark 0xffff \-j DROP
+.IP
+iptables \-A PREROUTING \-t mangle \-i eth2
+\-m mark ! \-\-mark 0xffff \-j DROP
+.PP
+And the following commands to make all nodes see the same packets:
+.IP
+ip maddr add 01:00:5e:00:01:01 dev eth1
+.IP
+ip maddr add 01:00:5e:00:01:02 dev eth2
+.IP
+arptables \-A OUTPUT \-o eth1 \-\-h\-length 6
+\-j mangle \-\-mangle-mac-s 01:00:5e:00:01:01
+.IP
+arptables \-A INPUT \-i eth1 \-\-h-length 6
+\-\-destination-mac 01:00:5e:00:01:01
+\-j mangle \-\-mangle\-mac\-d 00:zz:yy:xx:5a:27
+.IP
+arptables \-A OUTPUT \-o eth2 \-\-h\-length 6
+\-j mangle \-\-mangle\-mac\-s 01:00:5e:00:01:02
+.IP
+arptables \-A INPUT \-i eth2 \-\-h\-length 6
+\-\-destination\-mac 01:00:5e:00:01:02
+\-j mangle \-\-mangle\-mac\-d 00:zz:yy:xx:5a:27
+.PP
+\fBNOTE\fP: the arptables commands above use mainstream syntax. If you
+are using arptables-jf included in some RedHat, CentOS and Fedora
+versions, you will hit syntax errors. Therefore, you'll have to adapt
+these to the arptables-jf syntax to get them working.
+.PP
+In the case of TCP connections, pickup facility has to be disabled
+to avoid marking TCP ACK packets coming in the reply direction as
+valid.
+.IP
+echo 0 > /proc/sys/net/netfilter/nf_conntrack_tcp_loose
diff --git a/extensions/libxt_cluster.t b/extensions/libxt_cluster.t
new file mode 100644
index 0000000..ac60824
--- /dev/null
+++ b/extensions/libxt_cluster.t
@@ -0,0 +1,10 @@
+:PREROUTING,FORWARD,POSTROUTING
+*mangle
+-m cluster;;FAIL
+-m cluster --cluster-total-nodes 3;;FAIL
+-m cluster --cluster-total-nodes 2 --cluster-local-node 2;;FAIL
+-m cluster --cluster-total-nodes 2 --cluster-local-node 3 --cluster-hash-seed;;FAIL
+#
+# outputs --cluster-local-nodemask instead of --cluster-local-node
+#
+-m cluster --cluster-total-nodes 2 --cluster-local-node 2 --cluster-hash-seed 0xfeedcafe;-m cluster --cluster-local-nodemask 0x00000002 --cluster-total-nodes 2 --cluster-hash-seed 0xfeedcafe;OK
diff --git a/extensions/libxt_cluster.txlate b/extensions/libxt_cluster.txlate
new file mode 100644
index 0000000..9dcf570
--- /dev/null
+++ b/extensions/libxt_cluster.txlate
@@ -0,0 +1,26 @@
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 2 --cluster-local-node 1 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 2 seed 0xdeadbeef eq 1 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 1 --cluster-local-node 1 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 1 seed 0xdeadbeef eq 1 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 2 --cluster-local-nodemask 1 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 2 seed 0xdeadbeef eq 1 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 1 --cluster-local-nodemask 1 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 1 seed 0xdeadbeef eq 1 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 32 --cluster-local-node 32 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 32 seed 0xdeadbeef eq 32 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 32 --cluster-local-nodemask 32 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 32 seed 0xdeadbeef eq 6 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 32 --cluster-local-nodemask 5 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 32 seed 0xdeadbeef { 0, 2 } meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 7 --cluster-local-nodemask 9 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 7 seed 0xdeadbeef { 0, 3 } meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 7 --cluster-local-node 5 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 7 seed 0xdeadbeef eq 5 meta pkttype set host counter meta mark set 0xffff
diff --git a/extensions/libxt_comment.c b/extensions/libxt_comment.c
new file mode 100644
index 0000000..69795b6
--- /dev/null
+++ b/extensions/libxt_comment.c
@@ -0,0 +1,87 @@
+/* Shared library add-on to iptables to add comment match support.
+ *
+ * ChangeLog
+ *     2003-05-13: Brad Fisher <brad@info-link.net>
+ *         Initial comment match
+ *     2004-05-12: Brad Fisher <brad@info-link.net>
+ *         Port to patch-o-matic-ng
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_comment.h>
+
+enum {
+	O_COMMENT = 0,
+};
+
+static void comment_help(void)
+{
+	printf(
+		"comment match options:\n"
+		"--comment COMMENT             Attach a comment to a rule\n");
+}
+
+static const struct xt_option_entry comment_opts[] = {
+	{.name = "comment", .id = O_COMMENT, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_comment_info, comment)},
+	XTOPT_TABLEEND,
+};
+
+static void
+comment_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	struct xt_comment_info *commentinfo = (void *)match->data;
+
+	commentinfo->comment[XT_MAX_COMMENT_LEN-1] = '\0';
+	printf(" /* %s */", commentinfo->comment);
+}
+
+/* Saves the union ipt_matchinfo in parsable form to stdout. */
+static void
+comment_save(const void *ip, const struct xt_entry_match *match)
+{
+	struct xt_comment_info *commentinfo = (void *)match->data;
+
+	commentinfo->comment[XT_MAX_COMMENT_LEN-1] = '\0';
+	printf(" --comment");
+	xtables_save_string(commentinfo->comment);
+}
+
+static int comment_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_mt_params *params)
+{
+	struct xt_comment_info *commentinfo = (void *)params->match->data;
+	char comment[XT_MAX_COMMENT_LEN + sizeof("\\\"\\\"")];
+
+	commentinfo->comment[XT_MAX_COMMENT_LEN - 1] = '\0';
+	if (params->escape_quotes)
+		snprintf(comment, sizeof(comment), "\\\"%s\\\"",
+			 commentinfo->comment);
+	else
+		snprintf(comment, sizeof(comment), "\"%s\"",
+			 commentinfo->comment);
+
+	xt_xlate_add_comment(xl, comment);
+
+	return 1;
+}
+
+static struct xtables_match comment_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "comment",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_comment_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_comment_info)),
+	.help		= comment_help,
+	.print 		= comment_print,
+	.save 		= comment_save,
+	.x6_parse	= xtables_option_parse,
+	.x6_options	= comment_opts,
+	.xlate		= comment_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&comment_match);
+}
diff --git a/extensions/libxt_comment.man b/extensions/libxt_comment.man
new file mode 100644
index 0000000..faaee2a
--- /dev/null
+++ b/extensions/libxt_comment.man
@@ -0,0 +1,6 @@
+Allows you to add comments (up to 256 characters) to any rule.
+.TP
+\fB\-\-comment\fP \fIcomment\fP
+.TP
+Example:
+iptables \-A INPUT \-i eth1 \-m comment \-\-comment "my local LAN"
diff --git a/extensions/libxt_comment.t b/extensions/libxt_comment.t
new file mode 100644
index 0000000..f0c8fb9
--- /dev/null
+++ b/extensions/libxt_comment.t
@@ -0,0 +1,14 @@
+:INPUT,FORWARD,OUTPUT
+-m comment;;FAIL
+-m comment --comment;;FAIL
+-p tcp -m tcp --dport 22 -m comment --comment foo;=;OK
+-p tcp -m comment --comment foo -m tcp --dport 22;=;OK
+#
+# it fails with 256 characters
+#
+# should fail: iptables -A INPUT -m comment --comment xxxxxxxxxxxxxxxxx [....]
+# -m comment --comment xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;;FAIL
+#
+# success with 255 characters
+#
+-m comment --comment xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;=;OK
diff --git a/extensions/libxt_comment.txlate b/extensions/libxt_comment.txlate
new file mode 100644
index 0000000..c610b0e
--- /dev/null
+++ b/extensions/libxt_comment.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A INPUT -s 192.168.0.0 -m comment --comment "A privatized IP block"
+nft add rule ip filter INPUT ip saddr 192.168.0.0 counter comment \"A privatized IP block\"
+
+iptables-translate -A INPUT -p tcp -m tcp --sport http -s  192.168.0.0/16 -d 192.168.0.0/16 -j LONGNACCEPT -m comment --comment "foobar"
+nft add rule ip filter INPUT ip saddr 192.168.0.0/16 ip daddr 192.168.0.0/16 tcp sport 80 counter jump LONGNACCEPT comment \"foobar\"
+
+iptables-translate -A FORWARD -p tcp -m tcp --sport http -s 192.168.0.0/16 -d 192.168.0.0/16 -j DROP -m comment --comment singlecomment
+nft add rule ip filter FORWARD ip saddr 192.168.0.0/16 ip daddr 192.168.0.0/16 tcp sport 80 counter drop comment \"singlecomment\"
diff --git a/extensions/libxt_connbytes.c b/extensions/libxt_connbytes.c
new file mode 100644
index 0000000..b57f0fc
--- /dev/null
+++ b/extensions/libxt_connbytes.c
@@ -0,0 +1,231 @@
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_connbytes.h>
+
+enum {
+	O_CONNBYTES = 0,
+	O_CONNBYTES_DIR,
+	O_CONNBYTES_MODE,
+};
+
+static void connbytes_help(void)
+{
+	printf(
+"connbytes match options:\n"
+" [!] --connbytes from:[to]\n"
+"     --connbytes-dir [original, reply, both]\n"
+"     --connbytes-mode [packets, bytes, avgpkt]\n");
+}
+
+static const struct xt_option_entry connbytes_opts[] = {
+	{.name = "connbytes", .id = O_CONNBYTES, .type = XTTYPE_UINT64RC,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	{.name = "connbytes-dir", .id = O_CONNBYTES_DIR, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND},
+	{.name = "connbytes-mode", .id = O_CONNBYTES_MODE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_MAND},
+	XTOPT_TABLEEND,
+};
+
+static void connbytes_parse(struct xt_option_call *cb)
+{
+	struct xt_connbytes_info *sinfo = cb->data;
+	unsigned long long i;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_CONNBYTES:
+		sinfo->count.from = cb->val.u64_range[0];
+		sinfo->count.to   = UINT64_MAX;
+		if (cb->nvals == 2)
+			sinfo->count.to = cb->val.u64_range[1];
+
+		if (sinfo->count.to < sinfo->count.from)
+			xtables_error(PARAMETER_PROBLEM, "%llu should be less than %llu",
+					(unsigned long long)sinfo->count.from,
+					(unsigned long long)sinfo->count.to);
+		if (cb->invert) {
+			i = sinfo->count.from;
+			sinfo->count.from = sinfo->count.to;
+			sinfo->count.to = i;
+		}
+		break;
+	case O_CONNBYTES_DIR:
+		if (strcmp(cb->arg, "original") == 0)
+			sinfo->direction = XT_CONNBYTES_DIR_ORIGINAL;
+		else if (strcmp(cb->arg, "reply") == 0)
+			sinfo->direction = XT_CONNBYTES_DIR_REPLY;
+		else if (strcmp(cb->arg, "both") == 0)
+			sinfo->direction = XT_CONNBYTES_DIR_BOTH;
+		else
+			xtables_error(PARAMETER_PROBLEM,
+				   "Unknown --connbytes-dir `%s'", cb->arg);
+		break;
+	case O_CONNBYTES_MODE:
+		if (strcmp(cb->arg, "packets") == 0)
+			sinfo->what = XT_CONNBYTES_PKTS;
+		else if (strcmp(cb->arg, "bytes") == 0)
+			sinfo->what = XT_CONNBYTES_BYTES;
+		else if (strcmp(cb->arg, "avgpkt") == 0)
+			sinfo->what = XT_CONNBYTES_AVGPKT;
+		else
+			xtables_error(PARAMETER_PROBLEM,
+				   "Unknown --connbytes-mode `%s'", cb->arg);
+		break;
+	}
+}
+
+static void print_mode(const struct xt_connbytes_info *sinfo)
+{
+	switch (sinfo->what) {
+		case XT_CONNBYTES_PKTS:
+			fputs(" packets", stdout);
+			break;
+		case XT_CONNBYTES_BYTES:
+			fputs(" bytes", stdout);
+			break;
+		case XT_CONNBYTES_AVGPKT:
+			fputs(" avgpkt", stdout);
+			break;
+		default:
+			fputs(" unknown", stdout);
+			break;
+	}
+}
+
+static void print_direction(const struct xt_connbytes_info *sinfo)
+{
+	switch (sinfo->direction) {
+		case XT_CONNBYTES_DIR_ORIGINAL:
+			fputs(" original", stdout);
+			break;
+		case XT_CONNBYTES_DIR_REPLY:
+			fputs(" reply", stdout);
+			break;
+		case XT_CONNBYTES_DIR_BOTH:
+			fputs(" both", stdout);
+			break;
+		default:
+			fputs(" unknown", stdout);
+			break;
+	}
+}
+
+static void print_from_to(const struct xt_connbytes_info *sinfo, const char *prefix)
+{
+	unsigned long long from, to;
+
+	if (sinfo->count.from > sinfo->count.to) {
+		fputs(" !", stdout);
+		from = sinfo->count.to;
+		to = sinfo->count.from;
+	} else {
+		to = sinfo->count.to;
+		from = sinfo->count.from;
+	}
+	printf(" %sconnbytes %llu", prefix, from);
+	if (to && to < UINT64_MAX)
+		printf(":%llu", to);
+}
+
+static void
+connbytes_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_connbytes_info *sinfo = (const void *)match->data;
+
+	print_from_to(sinfo, "");
+
+	fputs(" connbytes mode", stdout);
+	print_mode(sinfo);
+
+	fputs(" connbytes direction", stdout);
+	print_direction(sinfo);
+}
+
+static void connbytes_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_connbytes_info *sinfo = (const void *)match->data;
+
+	print_from_to(sinfo, "--");
+
+	fputs(" --connbytes-mode", stdout);
+	print_mode(sinfo);
+
+	fputs(" --connbytes-dir", stdout);
+	print_direction(sinfo);
+}
+
+
+static int connbytes_xlate(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_connbytes_info *info = (void *)params->match->data;
+	unsigned long long from, to;
+	bool invert = false;
+
+	xt_xlate_add(xl, "ct ");
+
+	switch (info->direction) {
+	case XT_CONNBYTES_DIR_ORIGINAL:
+		xt_xlate_add(xl, "original ");
+		break;
+	case XT_CONNBYTES_DIR_REPLY:
+		xt_xlate_add(xl, "reply ");
+		break;
+	case XT_CONNBYTES_DIR_BOTH:
+		break;
+	default:
+		return 0;
+	}
+
+	switch (info->what) {
+	case XT_CONNBYTES_PKTS:
+		xt_xlate_add(xl, "packets ");
+		break;
+	case XT_CONNBYTES_BYTES:
+		xt_xlate_add(xl, "bytes ");
+		break;
+	case XT_CONNBYTES_AVGPKT:
+		xt_xlate_add(xl, "avgpkt ");
+		break;
+	default:
+		return 0;
+	}
+
+	if (info->count.from > info->count.to) {
+		invert = true;
+		from = info->count.to;
+		to = info->count.from;
+	} else {
+		to = info->count.to;
+		from = info->count.from;
+	}
+
+	if (from == to)
+		xt_xlate_add(xl, "%llu", from);
+	else if (to == UINT64_MAX)
+		xt_xlate_add(xl, "%s %llu", invert ? "lt" : "ge", from);
+	else
+		xt_xlate_add(xl, "%s%llu-%llu", invert ? "!= " : "", from, to);
+	return 1;
+}
+
+static struct xtables_match connbytes_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name 		= "connbytes",
+	.version 	= XTABLES_VERSION,
+	.size 		= XT_ALIGN(sizeof(struct xt_connbytes_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_connbytes_info)),
+	.help		= connbytes_help,
+	.print		= connbytes_print,
+	.save 		= connbytes_save,
+	.x6_parse	= connbytes_parse,
+	.x6_options	= connbytes_opts,
+	.xlate		= connbytes_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&connbytes_match);
+}
diff --git a/extensions/libxt_connbytes.man b/extensions/libxt_connbytes.man
new file mode 100644
index 0000000..0504a55
--- /dev/null
+++ b/extensions/libxt_connbytes.man
@@ -0,0 +1,36 @@
+Match by how many bytes or packets a connection (or one of the two
+flows constituting the connection) has transferred so far, or by
+average bytes per packet.
+.PP
+The counters are 64-bit and are thus not expected to overflow ;)
+.PP
+The primary use is to detect long-lived downloads and mark them to be
+scheduled using a lower priority band in traffic control.
+.PP
+The transferred bytes per connection can also be viewed through
+`conntrack \-L` and accessed via ctnetlink.
+.PP
+NOTE that for connections which have no accounting information, the match will
+always return false. The "net.netfilter.nf_conntrack_acct" sysctl flag controls
+whether \fBnew\fP connections will be byte/packet counted. Existing connection
+flows will not be gaining/losing a/the accounting structure when be sysctl flag
+is flipped.
+.TP
+[\fB!\fP] \fB\-\-connbytes\fP \fIfrom\fP[\fB:\fP\fIto\fP]
+match packets from a connection whose packets/bytes/average packet
+size is more than FROM and less than TO bytes/packets. if TO is
+omitted only FROM check is done. "!" is used to match packets not
+falling in the range.
+.TP
+\fB\-\-connbytes\-dir\fP {\fBoriginal\fP|\fBreply\fP|\fBboth\fP}
+which packets to consider
+.TP
+\fB\-\-connbytes\-mode\fP {\fBpackets\fP|\fBbytes\fP|\fBavgpkt\fP}
+whether to check the amount of packets, number of bytes transferred or
+the average size (in bytes) of all packets received so far. Note that
+when "both" is used together with "avgpkt", and data is going (mainly)
+only in one direction (for example HTTP), the average packet size will
+be about half of the actual data packets.
+.TP
+Example:
+iptables .. \-m connbytes \-\-connbytes 10000:100000 \-\-connbytes\-dir both \-\-connbytes\-mode bytes ...
diff --git a/extensions/libxt_connbytes.t b/extensions/libxt_connbytes.t
new file mode 100644
index 0000000..6b24e26
--- /dev/null
+++ b/extensions/libxt_connbytes.t
@@ -0,0 +1,21 @@
+:INPUT,FORWARD,OUTPUT
+-m connbytes --connbytes 0:1000 --connbytes-mode packets --connbytes-dir original;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode packets --connbytes-dir reply;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode packets --connbytes-dir both;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode bytes --connbytes-dir original;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode bytes --connbytes-dir reply;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode bytes --connbytes-dir both;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode avgpkt --connbytes-dir original;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode avgpkt --connbytes-dir reply;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode avgpkt --connbytes-dir both;=;OK
+-m connbytes --connbytes -1:0 --connbytes-mode packets --connbytes-dir original;;FAIL
+-m connbytes --connbytes 0:-1 --connbytes-mode packets --connbytes-dir original;;FAIL
+# ERROR: cannot find: iptables -I INPUT -m connbytes --connbytes 0:18446744073709551615 --connbytes-mode avgpkt --connbytes-dir both
+# -m connbytes --connbytes 0:18446744073709551615 --connbytes-mode avgpkt --connbytes-dir both;=;OK
+-m connbytes --connbytes 0:18446744073709551616 --connbytes-mode avgpkt --connbytes-dir both;;FAIL
+-m connbytes --connbytes 0:1000 --connbytes-mode wrong --connbytes-dir both;;FAIL
+-m connbytes --connbytes 0:1000 --connbytes-dir original;;FAIL
+-m connbytes --connbytes 0:1000 --connbytes-mode packets;;FAIL
+-m connbytes --connbytes-dir original;;FAIL
+-m connbytes --connbytes 0:1000;;FAIL
+-m connbytes;;FAIL
diff --git a/extensions/libxt_connbytes.txlate b/extensions/libxt_connbytes.txlate
new file mode 100644
index 0000000..f78958d
--- /dev/null
+++ b/extensions/libxt_connbytes.txlate
@@ -0,0 +1,14 @@
+iptables-translate -A OUTPUT -m connbytes --connbytes 200 --connbytes-dir original --connbytes-mode packets
+nft add rule ip filter OUTPUT ct original packets ge 200 counter
+
+iptables-translate -A OUTPUT -m connbytes ! --connbytes 200 --connbytes-dir reply --connbytes-mode packets
+nft add rule ip filter OUTPUT ct reply packets lt 200 counter
+
+iptables-translate -A OUTPUT -m connbytes --connbytes 200:600 --connbytes-dir both --connbytes-mode bytes
+nft add rule ip filter OUTPUT ct bytes 200-600 counter
+
+iptables-translate -A OUTPUT -m connbytes ! --connbytes 200:600 --connbytes-dir both --connbytes-mode bytes
+nft add rule ip filter OUTPUT ct bytes != 200-600 counter
+
+iptables-translate -A OUTPUT -m connbytes --connbytes 200:200 --connbytes-dir both --connbytes-mode avgpkt
+nft add rule ip filter OUTPUT ct avgpkt 200 counter
diff --git a/extensions/libxt_connlabel.c b/extensions/libxt_connlabel.c
new file mode 100644
index 0000000..565b8c7
--- /dev/null
+++ b/extensions/libxt_connlabel.c
@@ -0,0 +1,193 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_connlabel.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+
+enum {
+	O_LABEL = 0,
+	O_SET = 1,
+};
+
+static struct nfct_labelmap *map;
+
+static void connlabel_mt_help(void)
+{
+	puts(
+"connlabel match options:\n"
+"[!] --label name     Match if label has been set on connection\n"
+"    --set            Set label on connection");
+}
+
+static const struct xt_option_entry connlabel_mt_opts[] = {
+	{.name = "label", .id = O_LABEL, .type = XTTYPE_STRING,
+	 .min = 1, .flags = XTOPT_MAND|XTOPT_INVERT},
+	{.name = "set", .id = O_SET, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+/* cannot do this via _init, else static builds might spew error message
+ * for every iptables invocation.
+ */
+static int connlabel_open(void)
+{
+	const char *fname;
+
+	if (map)
+		return 0;
+
+	map = nfct_labelmap_new(NULL);
+	if (map != NULL)
+		return 0;
+
+	fname = nfct_labels_get_path();
+	if (errno) {
+		fprintf(stderr, "Warning: cannot open %s: %s\n",
+			fname, strerror(errno));
+	} else {
+		xtables_error(RESOURCE_PROBLEM,
+			"cannot parse %s: no labels found", fname);
+	}
+	return 1;
+}
+
+static int connlabel_value_parse(const char *in)
+{
+	char *end;
+	unsigned long value = strtoul(in, &end, 0);
+
+	if (in[0] == '\0' || *end != '\0')
+		return -1;
+
+	return value;
+}
+
+static void connlabel_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_connlabel_mtinfo *info = cb->data;
+	int tmp;
+
+	xtables_option_parse(cb);
+
+	switch (cb->entry->id) {
+	case O_LABEL:
+		tmp = connlabel_value_parse(cb->arg);
+		if (tmp < 0 && !connlabel_open())
+			tmp = nfct_labelmap_get_bit(map, cb->arg);
+		if (tmp < 0)
+			xtables_error(PARAMETER_PROBLEM,
+				      "label '%s' not found or invalid value",
+				      cb->arg);
+
+		info->bit = tmp;
+		if (cb->invert)
+			info->options |= XT_CONNLABEL_OP_INVERT;
+		break;
+	case O_SET:
+		info->options |= XT_CONNLABEL_OP_SET;
+		break;
+	}
+
+}
+
+static const char *connlabel_get_name(int b)
+{
+	const char *name;
+
+	if (connlabel_open())
+		return NULL;
+
+	name = nfct_labelmap_get_name(map, b);
+	if (name && strcmp(name, ""))
+		return name;
+	return NULL;
+}
+
+static void
+connlabel_mt_print_op(const struct xt_connlabel_mtinfo *info, const char *prefix)
+{
+	if (info->options & XT_CONNLABEL_OP_SET)
+		printf(" %sset", prefix);
+}
+
+static void
+connlabel_mt_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_connlabel_mtinfo *info = (const void *)match->data;
+	const char *name = connlabel_get_name(info->bit);
+
+	printf(" connlabel");
+	if (info->options & XT_CONNLABEL_OP_INVERT)
+		printf(" !");
+	if (numeric || name == NULL) {
+		printf(" %u", info->bit);
+	} else {
+		printf(" '%s'", name);
+	}
+	connlabel_mt_print_op(info, "");
+}
+
+static void
+connlabel_mt_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_connlabel_mtinfo *info = (const void *)match->data;
+	const char *name = connlabel_get_name(info->bit);
+
+	if (info->options & XT_CONNLABEL_OP_INVERT)
+		printf(" !");
+	if (name)
+		printf(" --label \"%s\"", name);
+	else
+		printf(" --label \"%u\"", info->bit);
+	connlabel_mt_print_op(info, "--");
+}
+
+static int connlabel_mt_xlate(struct xt_xlate *xl,
+			      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_connlabel_mtinfo *info =
+		(const void *)params->match->data;
+	const char *name = connlabel_get_name(info->bit);
+	char *valbuf = NULL;
+
+	if (name == NULL) {
+		if (asprintf(&valbuf, "%u", info->bit) < 0)
+			return 0;
+		name = valbuf;
+	}
+
+	if (info->options & XT_CONNLABEL_OP_SET)
+		xt_xlate_add(xl, "ct label set %s ", name);
+
+	xt_xlate_add(xl, "ct label ");
+	if (info->options & XT_CONNLABEL_OP_INVERT)
+		xt_xlate_add(xl, "and %s != ", name);
+	xt_xlate_add(xl, "%s", name);
+
+	free(valbuf);
+	return 1;
+}
+
+static struct xtables_match connlabel_mt_reg = {
+	.family        = NFPROTO_UNSPEC,
+	.name          = "connlabel",
+	.version       = XTABLES_VERSION,
+	.size          = XT_ALIGN(sizeof(struct xt_connlabel_mtinfo)),
+	.userspacesize = offsetof(struct xt_connlabel_mtinfo, bit),
+	.help          = connlabel_mt_help,
+	.print         = connlabel_mt_print,
+	.save          = connlabel_mt_save,
+	.x6_parse      = connlabel_mt_parse,
+	.x6_options    = connlabel_mt_opts,
+	.xlate	       = connlabel_mt_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&connlabel_mt_reg);
+}
diff --git a/extensions/libxt_connlabel.man b/extensions/libxt_connlabel.man
new file mode 100644
index 0000000..bdaa51e
--- /dev/null
+++ b/extensions/libxt_connlabel.man
@@ -0,0 +1,33 @@
+Module matches or adds connlabels to a connection.
+connlabels are similar to connmarks, except labels are bit-based; i.e.
+all labels may be attached to a flow at the same time.
+Up to 128 unique labels are currently supported.
+.TP
+[\fB!\fP] \fB\-\-label\fP \fBname\fP
+matches if label \fBname\fP has been set on a connection.
+Instead of a name (which will be translated to a number, see EXAMPLE below),
+a number may be used instead.  Using a number always overrides connlabel.conf.
+.TP
+\fB\-\-set\fP
+if the label has not been set on the connection, set it.
+Note that setting a label can fail.  This is because the kernel allocates the
+conntrack label storage area when the connection is created, and it only
+reserves the amount of memory required by the ruleset that exists at
+the time the connection is created.
+In this case, the match will fail (or succeed, in case \fB\-\-label\fP
+option was negated).
+.PP
+This match depends on libnetfilter_conntrack 1.0.4 or later.
+Label translation is done via the \fB/etc/xtables/connlabel.conf\fP configuration file.
+.PP
+Example:
+.IP
+.nf
+0	eth0-in
+1	eth0-out
+2	ppp-in
+3	ppp-out
+4	bulk-traffic
+5	interactive
+.fi
+.PP
diff --git a/extensions/libxt_connlabel.t b/extensions/libxt_connlabel.t
new file mode 100644
index 0000000..7265bd4
--- /dev/null
+++ b/extensions/libxt_connlabel.t
@@ -0,0 +1,7 @@
+:INPUT,FORWARD,OUTPUT
+-m connlabel --label "40";=;OK
+-m connlabel ! --label "40";=;OK
+-m connlabel --label "41" --set;=;OK
+-m connlabel ! --label "41" --set;=;OK
+-m connlabel --label "2048";;FAIL
+-m connlabel --label "foobar_not_there";;FAIL
diff --git a/extensions/libxt_connlabel.txlate b/extensions/libxt_connlabel.txlate
new file mode 100644
index 0000000..12e4ac0
--- /dev/null
+++ b/extensions/libxt_connlabel.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A INPUT -m connlabel --label 40
+nft add rule ip filter INPUT ct label 40 counter
+
+iptables-translate -A INPUT -m connlabel ! --label 40 --set
+nft add rule ip filter INPUT ct label set 40 ct label and 40 != 40 counter
diff --git a/extensions/libxt_connlimit.c b/extensions/libxt_connlimit.c
new file mode 100644
index 0000000..a569f86
--- /dev/null
+++ b/extensions/libxt_connlimit.c
@@ -0,0 +1,252 @@
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_connlimit.h>
+
+enum {
+	O_UPTO = 0,
+	O_ABOVE,
+	O_MASK,
+	O_SADDR,
+	O_DADDR,
+	F_UPTO  = 1 << O_UPTO,
+	F_ABOVE = 1 << O_ABOVE,
+	F_MASK  = 1 << O_MASK,
+	F_SADDR = 1 << O_SADDR,
+	F_DADDR = 1 << O_DADDR,
+};
+
+static void connlimit_help(void)
+{
+	printf(
+"connlimit match options:\n"
+"  --connlimit-upto n     match if the number of existing connections is 0..n\n"
+"  --connlimit-above n    match if the number of existing connections is >n\n"
+"  --connlimit-mask n     group hosts using prefix length (default: max len)\n"
+"  --connlimit-saddr      select source address for grouping\n"
+"  --connlimit-daddr      select destination addresses for grouping\n");
+}
+
+#define s struct xt_connlimit_info
+static const struct xt_option_entry connlimit_opts[] = {
+	{.name = "connlimit-upto", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(s, limit)},
+	{.name = "connlimit-above", .id = O_ABOVE, .excl = F_UPTO,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(s, limit)},
+	{.name = "connlimit-mask", .id = O_MASK, .type = XTTYPE_PLENMASK,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, mask)},
+	{.name = "connlimit-saddr", .id = O_SADDR, .excl = F_DADDR,
+	 .type = XTTYPE_NONE},
+	{.name = "connlimit-daddr", .id = O_DADDR, .excl = F_SADDR,
+	 .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void connlimit_init(struct xt_entry_match *match)
+{
+	struct xt_connlimit_info *info = (void *)match->data;
+
+	/* This will also initialize the v4 mask correctly */
+	memset(info->v6_mask, 0xFF, sizeof(info->v6_mask));
+}
+
+static void connlimit_parse(struct xt_option_call *cb, uint8_t family)
+{
+	struct xt_connlimit_info *info = cb->data;
+	const unsigned int revision = (*cb->match)->u.user.revision;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_ABOVE:
+		if (cb->invert)
+			info->flags |= XT_CONNLIMIT_INVERT;
+		break;
+	case O_UPTO:
+		if (!cb->invert)
+			info->flags |= XT_CONNLIMIT_INVERT;
+		break;
+	case O_SADDR:
+		if (revision < 1)
+			xtables_error(PARAMETER_PROBLEM,
+				"xt_connlimit.0 does not support "
+				"--connlimit-daddr");
+		info->flags &= ~XT_CONNLIMIT_DADDR;
+		break;
+	case O_DADDR:
+		if (revision < 1)
+			xtables_error(PARAMETER_PROBLEM,
+				"xt_connlimit.0 does not support "
+				"--connlimit-daddr");
+		info->flags |= XT_CONNLIMIT_DADDR;
+		break;
+	}
+}
+
+static void connlimit_parse4(struct xt_option_call *cb)
+{
+	return connlimit_parse(cb, NFPROTO_IPV4);
+}
+
+static void connlimit_parse6(struct xt_option_call *cb)
+{
+	return connlimit_parse(cb, NFPROTO_IPV6);
+}
+
+static void connlimit_check(struct xt_fcheck_call *cb)
+{
+	if ((cb->xflags & (F_UPTO | F_ABOVE)) == 0)
+		xtables_error(PARAMETER_PROBLEM,
+			"You must specify \"--connlimit-above\" or "
+			"\"--connlimit-upto\".");
+}
+
+static unsigned int count_bits4(uint32_t mask)
+{
+	unsigned int bits = 0;
+
+	for (mask = ~ntohl(mask); mask != 0; mask >>= 1)
+		++bits;
+
+	return 32 - bits;
+}
+
+static unsigned int count_bits6(const uint32_t *mask)
+{
+	unsigned int bits = 0, i;
+	uint32_t tmp[4];
+
+	for (i = 0; i < 4; ++i)
+		for (tmp[i] = ~ntohl(mask[i]); tmp[i] != 0; tmp[i] >>= 1)
+			++bits;
+	return 128 - bits;
+}
+
+static void connlimit_print4(const void *ip,
+                             const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_connlimit_info *info = (const void *)match->data;
+
+	printf(" #conn %s/%u %s %u",
+	       (info->flags & XT_CONNLIMIT_DADDR) ? "dst" : "src",
+	       count_bits4(info->v4_mask),
+	       (info->flags & XT_CONNLIMIT_INVERT) ? "<=" : ">", info->limit);
+}
+
+static void connlimit_print6(const void *ip,
+                             const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_connlimit_info *info = (const void *)match->data;
+
+	printf(" #conn %s/%u %s %u",
+	       (info->flags & XT_CONNLIMIT_DADDR) ? "dst" : "src",
+	       count_bits6(info->v6_mask),
+	       (info->flags & XT_CONNLIMIT_INVERT) ? "<=" : ">", info->limit);
+}
+
+static void connlimit_save4(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_connlimit_info *info = (const void *)match->data;
+	const int revision = match->u.user.revision;
+
+	if (info->flags & XT_CONNLIMIT_INVERT)
+		printf(" --connlimit-upto %u", info->limit);
+	else
+		printf(" --connlimit-above %u", info->limit);
+	printf(" --connlimit-mask %u", count_bits4(info->v4_mask));
+	if (revision >= 1) {
+		if (info->flags & XT_CONNLIMIT_DADDR)
+			printf(" --connlimit-daddr");
+		else
+			printf(" --connlimit-saddr");
+	}
+}
+
+static void connlimit_save6(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_connlimit_info *info = (const void *)match->data;
+	const int revision = match->u.user.revision;
+
+	if (info->flags & XT_CONNLIMIT_INVERT)
+		printf(" --connlimit-upto %u", info->limit);
+	else
+		printf(" --connlimit-above %u", info->limit);
+	printf(" --connlimit-mask %u", count_bits6(info->v6_mask));
+	if (revision >= 1) {
+		if (info->flags & XT_CONNLIMIT_DADDR)
+			printf(" --connlimit-daddr");
+		else
+			printf(" --connlimit-saddr");
+	}
+}
+
+static struct xtables_match connlimit_mt_reg[] = {
+	{
+		.name          = "connlimit",
+		.revision      = 0,
+		.family        = NFPROTO_IPV4,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_connlimit_info)),
+		.userspacesize = offsetof(struct xt_connlimit_info, data),
+		.help          = connlimit_help,
+		.init          = connlimit_init,
+		.x6_parse      = connlimit_parse4,
+		.x6_fcheck     = connlimit_check,
+		.print         = connlimit_print4,
+		.save          = connlimit_save4,
+		.x6_options    = connlimit_opts,
+	},
+	{
+		.name          = "connlimit",
+		.revision      = 0,
+		.family        = NFPROTO_IPV6,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_connlimit_info)),
+		.userspacesize = offsetof(struct xt_connlimit_info, data),
+		.help          = connlimit_help,
+		.init          = connlimit_init,
+		.x6_parse      = connlimit_parse6,
+		.x6_fcheck     = connlimit_check,
+		.print         = connlimit_print6,
+		.save          = connlimit_save6,
+		.x6_options    = connlimit_opts,
+	},
+	{
+		.name          = "connlimit",
+		.revision      = 1,
+		.family        = NFPROTO_IPV4,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_connlimit_info)),
+		.userspacesize = offsetof(struct xt_connlimit_info, data),
+		.help          = connlimit_help,
+		.init          = connlimit_init,
+		.x6_parse      = connlimit_parse4,
+		.x6_fcheck     = connlimit_check,
+		.print         = connlimit_print4,
+		.save          = connlimit_save4,
+		.x6_options    = connlimit_opts,
+	},
+	{
+		.name          = "connlimit",
+		.revision      = 1,
+		.family        = NFPROTO_IPV6,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_connlimit_info)),
+		.userspacesize = offsetof(struct xt_connlimit_info, data),
+		.help          = connlimit_help,
+		.init          = connlimit_init,
+		.x6_parse      = connlimit_parse6,
+		.x6_fcheck     = connlimit_check,
+		.print         = connlimit_print6,
+		.save          = connlimit_save6,
+		.x6_options    = connlimit_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(connlimit_mt_reg, ARRAY_SIZE(connlimit_mt_reg));
+}
diff --git a/extensions/libxt_connlimit.man b/extensions/libxt_connlimit.man
new file mode 100644
index 0000000..ad9f40f
--- /dev/null
+++ b/extensions/libxt_connlimit.man
@@ -0,0 +1,42 @@
+Allows you to restrict the number of parallel connections to a server per
+client IP address (or client address block).
+.TP
+\fB\-\-connlimit\-upto\fP \fIn\fP
+Match if the number of existing connections is below or equal \fIn\fP.
+.TP
+\fB\-\-connlimit\-above\fP \fIn\fP
+Match if the number of existing connections is above \fIn\fP.
+.TP
+\fB\-\-connlimit\-mask\fP \fIprefix_length\fP
+Group hosts using the prefix length. For IPv4, this must be a number between
+(including) 0 and 32. For IPv6, between 0 and 128. If not specified, the
+maximum prefix length for the applicable protocol is used.
+.TP
+\fB\-\-connlimit\-saddr\fP
+Apply the limit onto the source group. This is the default if
+\-\-connlimit\-daddr is not specified.
+.TP
+\fB\-\-connlimit\-daddr\fP
+Apply the limit onto the destination group.
+.PP
+Examples:
+.TP
+# allow 2 telnet connections per client host
+iptables \-A INPUT \-p tcp \-\-syn \-\-dport 23 \-m connlimit \-\-connlimit\-above 2 \-j REJECT
+.TP
+# you can also match the other way around:
+iptables \-A INPUT \-p tcp \-\-syn \-\-dport 23 \-m connlimit \-\-connlimit\-upto 2 \-j ACCEPT
+.TP
+# limit the number of parallel HTTP requests to 16 per class C sized \
+source network (24 bit netmask)
+iptables \-p tcp \-\-syn \-\-dport 80 \-m connlimit \-\-connlimit\-above 16
+\-\-connlimit\-mask 24 \-j REJECT
+.TP
+# limit the number of parallel HTTP requests to 16 for the link local network
+(ipv6)
+ip6tables \-p tcp \-\-syn \-\-dport 80 \-s fe80::/64 \-m connlimit \-\-connlimit\-above
+16 \-\-connlimit\-mask 64 \-j REJECT
+.TP
+# Limit the number of connections to a particular host:
+ip6tables \-p tcp \-\-syn \-\-dport 49152:65535 \-d 2001:db8::1 \-m connlimit
+\-\-connlimit-above 100 \-j REJECT
diff --git a/extensions/libxt_connlimit.t b/extensions/libxt_connlimit.t
new file mode 100644
index 0000000..c7ea61e
--- /dev/null
+++ b/extensions/libxt_connlimit.t
@@ -0,0 +1,16 @@
+:INPUT,FORWARD,OUTPUT
+-m connlimit --connlimit-upto 0;=;OK
+-m connlimit --connlimit-upto 4294967295;=;OK
+-m connlimit --connlimit-upto 4294967296;;FAIL
+-m connlimit --connlimit-upto -1;;FAIL
+-m connlimit --connlimit-above 0;=;OK
+-m connlimit --connlimit-above 4294967295;=;OK
+-m connlimit --connlimit-above 4294967296;;FAIL
+-m connlimit --connlimit-above -1;;FAIL
+-m connlimit --connlimit-upto 1 --conlimit-above 1;;FAIL
+-m connlimit --connlimit-above 10 --connlimit-saddr;-m connlimit --connlimit-above 10 --connlimit-mask 32 --connlimit-saddr;OK
+-m connlimit --connlimit-above 10 --connlimit-daddr;-m connlimit --connlimit-above 10 --connlimit-mask 32 --connlimit-daddr;OK
+-m connlimit --connlimit-above 10 --connlimit-saddr --connlimit-daddr;;FAIL
+-m connlimit --connlimit-above 10 --connlimit-mask 32 --connlimit-saddr;=;OK
+-m connlimit --connlimit-above 10 --connlimit-mask 32 --connlimit-daddr;=;OK
+-m connlimit;;FAIL
diff --git a/extensions/libxt_connmark.c b/extensions/libxt_connmark.c
new file mode 100644
index 0000000..cb4264e
--- /dev/null
+++ b/extensions/libxt_connmark.c
@@ -0,0 +1,197 @@
+/* Shared library add-on to iptables to add connmark matching support.
+ *
+ * (C) 2002,2004 MARA Systems AB <http://www.marasystems.com>
+ * by Henrik Nordstrom <hno@marasystems.com>
+ *
+ * Version 1.1
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_connmark.h>
+
+struct xt_connmark_info {
+	unsigned long mark, mask;
+	uint8_t invert;
+};
+
+enum {
+	O_MARK = 0,
+};
+
+static void connmark_mt_help(void)
+{
+	printf(
+"connmark match options:\n"
+"[!] --mark value[/mask]    Match ctmark value with optional mask\n");
+}
+
+static const struct xt_option_entry connmark_mt_opts[] = {
+	{.name = "mark", .id = O_MARK, .type = XTTYPE_MARKMASK32,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void connmark_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_connmark_mtinfo1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->invert)
+		info->invert = true;
+	info->mark = cb->val.mark;
+	info->mask = cb->val.mask;
+}
+
+static void connmark_parse(struct xt_option_call *cb)
+{
+	struct xt_connmark_info *markinfo = cb->data;
+
+	xtables_option_parse(cb);
+	markinfo->mark = cb->val.mark;
+	markinfo->mask = cb->val.mask;
+	if (cb->invert)
+		markinfo->invert = 1;
+}
+
+static void
+connmark_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_connmark_info *info = (const void *)match->data;
+
+	printf(" CONNMARK match ");
+	if (info->invert)
+		printf("!");
+
+	xtables_print_mark_mask(info->mark, info->mask);
+}
+
+static void
+connmark_mt_print(const void *ip, const struct xt_entry_match *match,
+		  int numeric)
+{
+	const struct xt_connmark_mtinfo1 *info = (const void *)match->data;
+
+	printf(" connmark match ");
+	if (info->invert)
+		printf("!");
+
+	xtables_print_mark_mask(info->mark, info->mask);
+}
+
+static void connmark_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_connmark_info *info = (const void *)match->data;
+
+	if (info->invert)
+		printf(" !");
+
+	printf(" --mark");
+	xtables_print_mark_mask(info->mark, info->mask);
+}
+
+static void
+connmark_mt_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_connmark_mtinfo1 *info = (const void *)match->data;
+
+	if (info->invert)
+		printf(" !");
+
+	printf(" --mark");
+	xtables_print_mark_mask(info->mark, info->mask);
+}
+
+static void print_mark_xlate(unsigned int mark, unsigned int mask,
+			     struct xt_xlate *xl, uint32_t op)
+{
+	if (mask != 0xffffffffU)
+		xt_xlate_add(xl, " and 0x%x %s 0x%x", mask,
+			   op == XT_OP_EQ ? "==" : "!=", mark);
+	else
+		xt_xlate_add(xl, " %s0x%x",
+			   op == XT_OP_EQ ? "" : "!= ", mark);
+}
+
+static int connmark_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_connmark_info *info = (const void *)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (info->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "ct mark");
+	print_mark_xlate(info->mark, info->mask, xl, op);
+
+	return 1;
+}
+
+static int
+connmark_mt_xlate(struct xt_xlate *xl,
+		  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_connmark_mtinfo1 *info =
+		(const void *)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (info->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "ct mark");
+	print_mark_xlate(info->mark, info->mask, xl, op);
+
+	return 1;
+}
+
+static struct xtables_match connmark_mt_reg[] = {
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "connmark",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_connmark_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_connmark_info)),
+		.help          = connmark_mt_help,
+		.print         = connmark_print,
+		.save          = connmark_save,
+		.x6_parse      = connmark_parse,
+		.x6_options    = connmark_mt_opts,
+		.xlate	       = connmark_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "connmark",
+		.revision      = 1,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_connmark_mtinfo1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_connmark_mtinfo1)),
+		.help          = connmark_mt_help,
+		.print         = connmark_mt_print,
+		.save          = connmark_mt_save,
+		.x6_parse      = connmark_mt_parse,
+		.x6_options    = connmark_mt_opts,
+		.xlate	       = connmark_mt_xlate,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(connmark_mt_reg, ARRAY_SIZE(connmark_mt_reg));
+}
diff --git a/extensions/libxt_connmark.man b/extensions/libxt_connmark.man
new file mode 100644
index 0000000..4e83801
--- /dev/null
+++ b/extensions/libxt_connmark.man
@@ -0,0 +1,6 @@
+This module matches the netfilter mark field associated with a connection
+(which can be set using the \fBCONNMARK\fP target below).
+.TP
+[\fB!\fP] \fB\-\-mark\fP \fIvalue\fP[\fB/\fP\fImask\fP]
+Matches packets in connections with the given mark value (if a mask is
+specified, this is logically ANDed with the mark before the comparison).
diff --git a/extensions/libxt_connmark.t b/extensions/libxt_connmark.t
new file mode 100644
index 0000000..4dd7d9a
--- /dev/null
+++ b/extensions/libxt_connmark.t
@@ -0,0 +1,9 @@
+:PREROUTING,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-m connmark --mark 0xffffffff;=;OK
+-m connmark --mark 0xffffffff/0xffffffff;-m connmark --mark 0xffffffff;OK
+-m connmark --mark 0xffffffff/0;=;OK
+-m connmark --mark 0/0xffffffff;-m connmark --mark 0;OK
+-m connmark --mark -1;;FAIL
+-m connmark --mark 0xfffffffff;;FAIL
+-m connmark;;FAIL
diff --git a/extensions/libxt_connmark.txlate b/extensions/libxt_connmark.txlate
new file mode 100644
index 0000000..8942325
--- /dev/null
+++ b/extensions/libxt_connmark.txlate
@@ -0,0 +1,14 @@
+iptables-translate -A INPUT -m connmark --mark 2 -j ACCEPT
+nft add rule ip filter INPUT ct mark 0x2 counter accept
+
+iptables-translate -A INPUT -m connmark ! --mark 2 -j ACCEPT
+nft add rule ip filter INPUT ct mark != 0x2 counter accept
+
+iptables-translate -A INPUT -m connmark --mark 10/10 -j ACCEPT
+nft add rule ip filter INPUT ct mark and 0xa == 0xa counter accept
+
+iptables-translate -A INPUT -m connmark ! --mark 10/10 -j ACCEPT
+nft add rule ip filter INPUT ct mark and 0xa != 0xa counter accept
+
+iptables-translate -t mangle -A PREROUTING -p tcp --dport 40 -m connmark --mark 0x40
+nft add rule ip mangle PREROUTING tcp dport 40 ct mark 0x40 counter
diff --git a/extensions/libxt_conntrack.c b/extensions/libxt_conntrack.c
new file mode 100644
index 0000000..7734509
--- /dev/null
+++ b/extensions/libxt_conntrack.c
@@ -0,0 +1,1569 @@
+/*
+ *	libxt_conntrack
+ *	Shared library add-on to iptables for conntrack matching support.
+ *
+ *	GPL (C) 2001  Marc Boucher (marc@mbsi.ca).
+ *	Copyright © CC Computer Consultants GmbH, 2007 - 2008
+ *	Jan Engelhardt <jengelh@computergmbh.de>
+ */
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_conntrack.h>
+#include <linux/netfilter/xt_state.h>
+#include <linux/netfilter/nf_conntrack_common.h>
+#ifndef XT_STATE_UNTRACKED
+#define XT_STATE_UNTRACKED (1 << (IP_CT_NUMBER + 1))
+#endif
+
+struct ip_conntrack_old_tuple {
+	struct {
+		__be32 ip;
+		union {
+			__u16 all;
+		} u;
+	} src;
+
+	struct {
+		__be32 ip;
+		union {
+			__u16 all;
+		} u;
+
+		/* The protocol. */
+		__u16 protonum;
+	} dst;
+};
+
+struct xt_conntrack_info {
+	unsigned int statemask, statusmask;
+
+	struct ip_conntrack_old_tuple tuple[IP_CT_DIR_MAX];
+	struct in_addr sipmsk[IP_CT_DIR_MAX], dipmsk[IP_CT_DIR_MAX];
+
+	unsigned long expires_min, expires_max;
+
+	/* Flags word */
+	uint8_t flags;
+	/* Inverse flags */
+	uint8_t invflags;
+};
+
+enum {
+	O_CTSTATE = 0,
+	O_CTPROTO,
+	O_CTORIGSRC,
+	O_CTORIGDST,
+	O_CTREPLSRC,
+	O_CTREPLDST,
+	O_CTORIGSRCPORT,
+	O_CTORIGDSTPORT,
+	O_CTREPLSRCPORT,
+	O_CTREPLDSTPORT,
+	O_CTSTATUS,
+	O_CTEXPIRE,
+	O_CTDIR,
+};
+
+static void conntrack_mt_help(void)
+{
+	printf(
+"conntrack match options:\n"
+"[!] --ctstate {INVALID|ESTABLISHED|NEW|RELATED|UNTRACKED|SNAT|DNAT}[,...]\n"
+"                               State(s) to match\n"
+"[!] --ctproto proto            Protocol to match; by number or name, e.g. \"tcp\"\n"
+"[!] --ctorigsrc address[/mask]\n"
+"[!] --ctorigdst address[/mask]\n"
+"[!] --ctreplsrc address[/mask]\n"
+"[!] --ctrepldst address[/mask]\n"
+"                               Original/Reply source/destination address\n"
+"[!] --ctorigsrcport port\n"
+"[!] --ctorigdstport port\n"
+"[!] --ctreplsrcport port\n"
+"[!] --ctrepldstport port\n"
+"                               TCP/UDP/SCTP orig./reply source/destination port\n"
+"[!] --ctstatus {NONE|EXPECTED|SEEN_REPLY|ASSURED|CONFIRMED}[,...]\n"
+"                               Status(es) to match\n"
+"[!] --ctexpire time[:time]     Match remaining lifetime in seconds against\n"
+"                               value or range of values (inclusive)\n"
+"    --ctdir {ORIGINAL|REPLY}   Flow direction of packet\n");
+}
+
+#define s struct xt_conntrack_info /* for v0 */
+static const struct xt_option_entry conntrack_mt_opts_v0[] = {
+	{.name = "ctstate", .id = O_CTSTATE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctproto", .id = O_CTPROTO, .type = XTTYPE_PROTOCOL,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctorigsrc", .id = O_CTORIGSRC, .type = XTTYPE_HOST,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctorigdst", .id = O_CTORIGDST, .type = XTTYPE_HOST,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctreplsrc", .id = O_CTREPLSRC, .type = XTTYPE_HOST,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctrepldst", .id = O_CTREPLDST, .type = XTTYPE_HOST,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctstatus", .id = O_CTSTATUS, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctexpire", .id = O_CTEXPIRE, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_conntrack_mtinfo2
+/* We exploit the fact that v1-v2 share the same xt_o_e layout */
+static const struct xt_option_entry conntrack2_mt_opts[] = {
+	{.name = "ctstate", .id = O_CTSTATE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctproto", .id = O_CTPROTO, .type = XTTYPE_PROTOCOL,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctorigsrc", .id = O_CTORIGSRC, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctorigdst", .id = O_CTORIGDST, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctreplsrc", .id = O_CTREPLSRC, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctrepldst", .id = O_CTREPLDST, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctstatus", .id = O_CTSTATUS, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctexpire", .id = O_CTEXPIRE, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT},
+	/*
+	 * Rev 1 and 2 only store one port, and we would normally use
+	 * %XTTYPE_PORT (rather than %XTTYPE_PORTRC) for that. The resulting
+	 * error message - in case a user passed a range nevertheless -
+	 * "port 22:23 resolved to nothing" is not quite as useful as using
+	 * %XTTYPE_PORTC and libxt_conntrack's own range test.
+	 */
+	{.name = "ctorigsrcport", .id = O_CTORIGSRCPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_NBO},
+	{.name = "ctorigdstport", .id = O_CTORIGDSTPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_NBO},
+	{.name = "ctreplsrcport", .id = O_CTREPLSRCPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_NBO},
+	{.name = "ctrepldstport", .id = O_CTREPLDSTPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_NBO},
+	{.name = "ctdir", .id = O_CTDIR, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_conntrack_mtinfo3
+/* Difference from v2 is the non-NBO form. */
+static const struct xt_option_entry conntrack3_mt_opts[] = {
+	{.name = "ctstate", .id = O_CTSTATE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctproto", .id = O_CTPROTO, .type = XTTYPE_PROTOCOL,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctorigsrc", .id = O_CTORIGSRC, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctorigdst", .id = O_CTORIGDST, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctreplsrc", .id = O_CTREPLSRC, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctrepldst", .id = O_CTREPLDST, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctstatus", .id = O_CTSTATUS, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctexpire", .id = O_CTEXPIRE, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctorigsrcport", .id = O_CTORIGSRCPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctorigdstport", .id = O_CTORIGDSTPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctreplsrcport", .id = O_CTREPLSRCPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctrepldstport", .id = O_CTREPLDSTPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctdir", .id = O_CTDIR, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static int
+parse_state(const char *state, size_t len, struct xt_conntrack_info *sinfo)
+{
+	if (strncasecmp(state, "INVALID", len) == 0)
+		sinfo->statemask |= XT_CONNTRACK_STATE_INVALID;
+	else if (strncasecmp(state, "NEW", len) == 0)
+		sinfo->statemask |= XT_CONNTRACK_STATE_BIT(IP_CT_NEW);
+	else if (strncasecmp(state, "ESTABLISHED", len) == 0)
+		sinfo->statemask |= XT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
+	else if (strncasecmp(state, "RELATED", len) == 0)
+		sinfo->statemask |= XT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
+	else if (strncasecmp(state, "UNTRACKED", len) == 0)
+		sinfo->statemask |= XT_CONNTRACK_STATE_UNTRACKED;
+	else if (strncasecmp(state, "SNAT", len) == 0)
+		sinfo->statemask |= XT_CONNTRACK_STATE_SNAT;
+	else if (strncasecmp(state, "DNAT", len) == 0)
+		sinfo->statemask |= XT_CONNTRACK_STATE_DNAT;
+	else
+		return 0;
+	return 1;
+}
+
+static void
+parse_states(const char *arg, struct xt_conntrack_info *sinfo)
+{
+	const char *comma;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg || !parse_state(arg, comma-arg, sinfo))
+			xtables_error(PARAMETER_PROBLEM, "Bad ctstate \"%s\"", arg);
+		arg = comma+1;
+	}
+	if (!*arg)
+		xtables_error(PARAMETER_PROBLEM, "\"--ctstate\" requires a list of "
+					      "states with no spaces, e.g. "
+					      "ESTABLISHED,RELATED");
+	if (strlen(arg) == 0 || !parse_state(arg, strlen(arg), sinfo))
+		xtables_error(PARAMETER_PROBLEM, "Bad ctstate \"%s\"", arg);
+}
+
+static bool
+conntrack_ps_state(struct xt_conntrack_mtinfo3 *info, const char *state,
+                   size_t z)
+{
+	if (strncasecmp(state, "INVALID", z) == 0)
+		info->state_mask |= XT_CONNTRACK_STATE_INVALID;
+	else if (strncasecmp(state, "NEW", z) == 0)
+		info->state_mask |= XT_CONNTRACK_STATE_BIT(IP_CT_NEW);
+	else if (strncasecmp(state, "ESTABLISHED", z) == 0)
+		info->state_mask |= XT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
+	else if (strncasecmp(state, "RELATED", z) == 0)
+		info->state_mask |= XT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
+	else if (strncasecmp(state, "UNTRACKED", z) == 0)
+		info->state_mask |= XT_CONNTRACK_STATE_UNTRACKED;
+	else if (strncasecmp(state, "SNAT", z) == 0)
+		info->state_mask |= XT_CONNTRACK_STATE_SNAT;
+	else if (strncasecmp(state, "DNAT", z) == 0)
+		info->state_mask |= XT_CONNTRACK_STATE_DNAT;
+	else
+		return false;
+	return true;
+}
+
+static void
+conntrack_ps_states(struct xt_conntrack_mtinfo3 *info, const char *arg)
+{
+	const char *comma;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg || !conntrack_ps_state(info, arg, comma - arg))
+			xtables_error(PARAMETER_PROBLEM,
+			           "Bad ctstate \"%s\"", arg);
+		arg = comma + 1;
+	}
+
+	if (strlen(arg) == 0 || !conntrack_ps_state(info, arg, strlen(arg)))
+		xtables_error(PARAMETER_PROBLEM, "Bad ctstate \"%s\"", arg);
+}
+
+static int
+parse_status(const char *status, size_t len, struct xt_conntrack_info *sinfo)
+{
+	if (strncasecmp(status, "NONE", len) == 0)
+		sinfo->statusmask |= 0;
+	else if (strncasecmp(status, "EXPECTED", len) == 0)
+		sinfo->statusmask |= IPS_EXPECTED;
+	else if (strncasecmp(status, "SEEN_REPLY", len) == 0)
+		sinfo->statusmask |= IPS_SEEN_REPLY;
+	else if (strncasecmp(status, "ASSURED", len) == 0)
+		sinfo->statusmask |= IPS_ASSURED;
+#ifdef IPS_CONFIRMED
+	else if (strncasecmp(status, "CONFIRMED", len) == 0)
+		sinfo->statusmask |= IPS_CONFIRMED;
+#endif
+	else
+		return 0;
+	return 1;
+}
+
+static void
+parse_statuses(const char *arg, struct xt_conntrack_info *sinfo)
+{
+	const char *comma;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg || !parse_status(arg, comma-arg, sinfo))
+			xtables_error(PARAMETER_PROBLEM, "Bad ctstatus \"%s\"", arg);
+		arg = comma+1;
+	}
+
+	if (strlen(arg) == 0 || !parse_status(arg, strlen(arg), sinfo))
+		xtables_error(PARAMETER_PROBLEM, "Bad ctstatus \"%s\"", arg);
+}
+
+static bool
+conntrack_ps_status(struct xt_conntrack_mtinfo3 *info, const char *status,
+                    size_t z)
+{
+	if (strncasecmp(status, "NONE", z) == 0)
+		info->status_mask |= 0;
+	else if (strncasecmp(status, "EXPECTED", z) == 0)
+		info->status_mask |= IPS_EXPECTED;
+	else if (strncasecmp(status, "SEEN_REPLY", z) == 0)
+		info->status_mask |= IPS_SEEN_REPLY;
+	else if (strncasecmp(status, "ASSURED", z) == 0)
+		info->status_mask |= IPS_ASSURED;
+	else if (strncasecmp(status, "CONFIRMED", z) == 0)
+		info->status_mask |= IPS_CONFIRMED;
+	else
+		return false;
+	return true;
+}
+
+static void
+conntrack_ps_statuses(struct xt_conntrack_mtinfo3 *info, const char *arg)
+{
+	const char *comma;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg || !conntrack_ps_status(info, arg, comma - arg))
+			xtables_error(PARAMETER_PROBLEM,
+			           "Bad ctstatus \"%s\"", arg);
+		arg = comma + 1;
+	}
+
+	if (strlen(arg) == 0 || !conntrack_ps_status(info, arg, strlen(arg)))
+		xtables_error(PARAMETER_PROBLEM, "Bad ctstatus \"%s\"", arg);
+}
+
+static void conntrack_parse(struct xt_option_call *cb)
+{
+	struct xt_conntrack_info *sinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_CTSTATE:
+		parse_states(cb->arg, sinfo);
+		if (cb->invert)
+			sinfo->invflags |= XT_CONNTRACK_STATE;
+		break;
+	case O_CTPROTO:
+		sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = cb->val.protocol;
+		if (cb->invert)
+			sinfo->invflags |= XT_CONNTRACK_PROTO;
+		if (sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum == 0
+		    && (sinfo->invflags & XT_INV_PROTO))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rule would never match protocol");
+
+		sinfo->flags |= XT_CONNTRACK_PROTO;
+		break;
+	case O_CTORIGSRC:
+		if (cb->invert)
+			sinfo->invflags |= XT_CONNTRACK_ORIGSRC;
+		sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip = cb->val.haddr.ip;
+		sinfo->flags |= XT_CONNTRACK_ORIGSRC;
+		break;
+	case O_CTORIGDST:
+		if (cb->invert)
+			sinfo->invflags |= XT_CONNTRACK_ORIGDST;
+		sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip = cb->val.haddr.ip;
+		sinfo->flags |= XT_CONNTRACK_ORIGDST;
+		break;
+	case O_CTREPLSRC:
+		if (cb->invert)
+			sinfo->invflags |= XT_CONNTRACK_REPLSRC;
+		sinfo->tuple[IP_CT_DIR_REPLY].src.ip = cb->val.haddr.ip;
+		sinfo->flags |= XT_CONNTRACK_REPLSRC;
+		break;
+	case O_CTREPLDST:
+		if (cb->invert)
+			sinfo->invflags |= XT_CONNTRACK_REPLDST;
+		sinfo->tuple[IP_CT_DIR_REPLY].dst.ip = cb->val.haddr.ip;
+		sinfo->flags |= XT_CONNTRACK_REPLDST;
+		break;
+	case O_CTSTATUS:
+		parse_statuses(cb->arg, sinfo);
+		if (cb->invert)
+			sinfo->invflags |= XT_CONNTRACK_STATUS;
+		sinfo->flags |= XT_CONNTRACK_STATUS;
+		break;
+	case O_CTEXPIRE:
+		sinfo->expires_min = cb->val.u32_range[0];
+		sinfo->expires_max = cb->val.u32_range[0];
+		if (cb->nvals >= 2)
+			sinfo->expires_max = cb->val.u32_range[1];
+		if (cb->invert)
+			sinfo->invflags |= XT_CONNTRACK_EXPIRES;
+		sinfo->flags |= XT_CONNTRACK_EXPIRES;
+		break;
+	}
+}
+
+static void conntrack_mt_parse(struct xt_option_call *cb, uint8_t rev)
+{
+	struct xt_conntrack_mtinfo3 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_CTSTATE:
+		conntrack_ps_states(info, cb->arg);
+		info->match_flags |= XT_CONNTRACK_STATE;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_STATE;
+		break;
+	case O_CTPROTO:
+		info->l4proto = cb->val.protocol;
+		if (info->l4proto == 0 && (info->invert_flags & XT_INV_PROTO))
+			xtables_error(PARAMETER_PROBLEM, "conntrack: rule would "
+			           "never match protocol");
+
+		info->match_flags |= XT_CONNTRACK_PROTO;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_PROTO;
+		break;
+	case O_CTORIGSRC:
+		info->origsrc_addr = cb->val.haddr;
+		info->origsrc_mask = cb->val.hmask;
+		info->match_flags |= XT_CONNTRACK_ORIGSRC;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_ORIGSRC;
+		break;
+	case O_CTORIGDST:
+		info->origdst_addr = cb->val.haddr;
+		info->origdst_mask = cb->val.hmask;
+		info->match_flags |= XT_CONNTRACK_ORIGDST;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_ORIGDST;
+		break;
+	case O_CTREPLSRC:
+		info->replsrc_addr = cb->val.haddr;
+		info->replsrc_mask = cb->val.hmask;
+		info->match_flags |= XT_CONNTRACK_REPLSRC;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_REPLSRC;
+		break;
+	case O_CTREPLDST:
+		info->repldst_addr = cb->val.haddr;
+		info->repldst_mask = cb->val.hmask;
+		info->match_flags |= XT_CONNTRACK_REPLDST;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_REPLDST;
+		break;
+	case O_CTSTATUS:
+		conntrack_ps_statuses(info, cb->arg);
+		info->match_flags |= XT_CONNTRACK_STATUS;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_STATUS;
+		break;
+	case O_CTEXPIRE:
+		info->expires_min = cb->val.u32_range[0];
+		info->expires_max = cb->val.u32_range[0];
+		if (cb->nvals >= 2)
+			info->expires_max = cb->val.u32_range[1];
+		info->match_flags |= XT_CONNTRACK_EXPIRES;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_EXPIRES;
+		break;
+	case O_CTORIGSRCPORT:
+		info->origsrc_port = cb->val.port_range[0];
+		info->origsrc_port_high = cb->val.port_range[cb->nvals >= 2];
+		info->match_flags |= XT_CONNTRACK_ORIGSRC_PORT;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_ORIGSRC_PORT;
+		break;
+	case O_CTORIGDSTPORT:
+		info->origdst_port = cb->val.port_range[0];
+		info->origdst_port_high = cb->val.port_range[cb->nvals >= 2];
+		info->match_flags |= XT_CONNTRACK_ORIGDST_PORT;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_ORIGDST_PORT;
+		break;
+	case O_CTREPLSRCPORT:
+		info->replsrc_port = cb->val.port_range[0];
+		info->replsrc_port_high = cb->val.port_range[cb->nvals >= 2];
+		info->match_flags |= XT_CONNTRACK_REPLSRC_PORT;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_REPLSRC_PORT;
+		break;
+	case O_CTREPLDSTPORT:
+		info->repldst_port = cb->val.port_range[0];
+		info->repldst_port_high = cb->val.port_range[cb->nvals >= 2];
+		info->match_flags |= XT_CONNTRACK_REPLDST_PORT;
+		if (cb->invert)
+			info->invert_flags |= XT_CONNTRACK_REPLDST_PORT;
+		break;
+	case O_CTDIR:
+		if (strcasecmp(cb->arg, "ORIGINAL") == 0) {
+			info->match_flags  |= XT_CONNTRACK_DIRECTION;
+			info->invert_flags &= ~XT_CONNTRACK_DIRECTION;
+		} else if (strcasecmp(cb->arg, "REPLY") == 0) {
+			info->match_flags  |= XT_CONNTRACK_DIRECTION;
+			info->invert_flags |= XT_CONNTRACK_DIRECTION;
+		} else {
+			xtables_param_act(XTF_BAD_VALUE, "conntrack", "--ctdir", cb->arg);
+		}
+		break;
+	}
+}
+
+#define cinfo_transform(r, l) \
+	do { \
+		memcpy((r), (l), offsetof(typeof(*(l)), state_mask)); \
+		(r)->state_mask  = (l)->state_mask; \
+		(r)->status_mask = (l)->status_mask; \
+	} while (false);
+
+static void conntrack1_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_conntrack_mtinfo1 *info = cb->data;
+	struct xt_conntrack_mtinfo3 up;
+
+	memset(&up, 0, sizeof(up));
+	cinfo_transform(&up, info);
+	up.origsrc_port_high = up.origsrc_port;
+	up.origdst_port_high = up.origdst_port;
+	up.replsrc_port_high = up.replsrc_port;
+	up.repldst_port_high = up.repldst_port;
+	cb->data = &up;
+	conntrack_mt_parse(cb, 3);
+	if (up.origsrc_port != up.origsrc_port_high ||
+	    up.origdst_port != up.origdst_port_high ||
+	    up.replsrc_port != up.replsrc_port_high ||
+	    up.repldst_port != up.repldst_port_high)
+		xtables_error(PARAMETER_PROBLEM,
+			"conntrack rev 1 does not support port ranges");
+	cinfo_transform(info, &up);
+	cb->data = info;
+}
+
+static void conntrack2_mt_parse(struct xt_option_call *cb)
+{
+#define cinfo2_transform(r, l) \
+		memcpy((r), (l), offsetof(typeof(*(l)), sizeof(*info));
+
+	struct xt_conntrack_mtinfo2 *info = cb->data;
+	struct xt_conntrack_mtinfo3 up;
+
+	memset(&up, 0, sizeof(up));
+	memcpy(&up, info, sizeof(*info));
+	up.origsrc_port_high = up.origsrc_port;
+	up.origdst_port_high = up.origdst_port;
+	up.replsrc_port_high = up.replsrc_port;
+	up.repldst_port_high = up.repldst_port;
+	cb->data = &up;
+	conntrack_mt_parse(cb, 3);
+	if (up.origsrc_port != up.origsrc_port_high ||
+	    up.origdst_port != up.origdst_port_high ||
+	    up.replsrc_port != up.replsrc_port_high ||
+	    up.repldst_port != up.repldst_port_high)
+		xtables_error(PARAMETER_PROBLEM,
+			"conntrack rev 2 does not support port ranges");
+	memcpy(info, &up, sizeof(*info));
+	cb->data = info;
+#undef cinfo2_transform
+}
+
+static void conntrack3_mt_parse(struct xt_option_call *cb)
+{
+	conntrack_mt_parse(cb, 3);
+}
+
+static void conntrack_mt_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM, "conntrack: At least one option "
+		           "is required");
+}
+
+static void
+print_state(unsigned int statemask)
+{
+	const char *sep = " ";
+
+	if (statemask & XT_CONNTRACK_STATE_INVALID) {
+		printf("%sINVALID", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
+		printf("%sNEW", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
+		printf("%sRELATED", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
+		printf("%sESTABLISHED", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_UNTRACKED) {
+		printf("%sUNTRACKED", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_SNAT) {
+		printf("%sSNAT", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_DNAT) {
+		printf("%sDNAT", sep);
+		sep = ",";
+	}
+}
+
+static void
+print_status(unsigned int statusmask)
+{
+	const char *sep = " ";
+
+	if (statusmask & IPS_EXPECTED) {
+		printf("%sEXPECTED", sep);
+		sep = ",";
+	}
+	if (statusmask & IPS_SEEN_REPLY) {
+		printf("%sSEEN_REPLY", sep);
+		sep = ",";
+	}
+	if (statusmask & IPS_ASSURED) {
+		printf("%sASSURED", sep);
+		sep = ",";
+	}
+	if (statusmask & IPS_CONFIRMED) {
+		printf("%sCONFIRMED", sep);
+		sep = ",";
+	}
+	if (statusmask == 0)
+		printf("%sNONE", sep);
+}
+
+static void
+conntrack_dump_addr(const union nf_inet_addr *addr,
+                    const union nf_inet_addr *mask,
+                    unsigned int family, bool numeric)
+{
+	if (family == NFPROTO_IPV4) {
+		if (!numeric && addr->ip == 0) {
+			printf(" anywhere");
+			return;
+		}
+		if (numeric)
+			printf(" %s%s",
+			       xtables_ipaddr_to_numeric(&addr->in),
+			       xtables_ipmask_to_numeric(&mask->in));
+		else
+			printf(" %s%s",
+			       xtables_ipaddr_to_anyname(&addr->in),
+			       xtables_ipmask_to_numeric(&mask->in));
+	} else if (family == NFPROTO_IPV6) {
+		if (!numeric && addr->ip6[0] == 0 && addr->ip6[1] == 0 &&
+		    addr->ip6[2] == 0 && addr->ip6[3] == 0) {
+			printf(" anywhere");
+			return;
+		}
+		if (numeric)
+			printf(" %s%s",
+			       xtables_ip6addr_to_numeric(&addr->in6),
+			       xtables_ip6mask_to_numeric(&mask->in6));
+		else
+			printf(" %s%s",
+			       xtables_ip6addr_to_anyname(&addr->in6),
+			       xtables_ip6mask_to_numeric(&mask->in6));
+	}
+}
+
+static void
+print_addr(const struct in_addr *addr, const struct in_addr *mask,
+           int inv, int numeric)
+{
+	if (inv)
+		printf(" !");
+
+	if (mask->s_addr == 0L && !numeric)
+		printf(" anywhere");
+	else {
+		if (numeric)
+			printf(" %s%s",
+			       xtables_ipaddr_to_numeric(addr),
+			       xtables_ipmask_to_numeric(mask));
+		else
+			printf(" %s%s",
+			       xtables_ipaddr_to_anyname(addr),
+			       xtables_ipmask_to_numeric(mask));
+	}
+}
+
+static void
+matchinfo_print(const void *ip, const struct xt_entry_match *match, int numeric, const char *optpfx)
+{
+	const struct xt_conntrack_info *sinfo = (const void *)match->data;
+
+	if(sinfo->flags & XT_CONNTRACK_STATE) {
+        	if (sinfo->invflags & XT_CONNTRACK_STATE)
+			printf(" !");
+		printf(" %sctstate", optpfx);
+		print_state(sinfo->statemask);
+	}
+
+	if(sinfo->flags & XT_CONNTRACK_PROTO) {
+        	if (sinfo->invflags & XT_CONNTRACK_PROTO)
+			printf(" !");
+		printf(" %sctproto", optpfx);
+		printf(" %u", sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum);
+	}
+
+	if(sinfo->flags & XT_CONNTRACK_ORIGSRC) {
+		if (sinfo->invflags & XT_CONNTRACK_ORIGSRC)
+			printf(" !");
+		printf(" %sctorigsrc", optpfx);
+
+		print_addr(
+		    (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip,
+		    &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
+		    false,
+		    numeric);
+	}
+
+	if(sinfo->flags & XT_CONNTRACK_ORIGDST) {
+		if (sinfo->invflags & XT_CONNTRACK_ORIGDST)
+			printf(" !");
+		printf(" %sctorigdst", optpfx);
+
+		print_addr(
+		    (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip,
+		    &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
+		    false,
+		    numeric);
+	}
+
+	if(sinfo->flags & XT_CONNTRACK_REPLSRC) {
+		if (sinfo->invflags & XT_CONNTRACK_REPLSRC)
+			printf(" !");
+		printf(" %sctreplsrc", optpfx);
+
+		print_addr(
+		    (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].src.ip,
+		    &sinfo->sipmsk[IP_CT_DIR_REPLY],
+		    false,
+		    numeric);
+	}
+
+	if(sinfo->flags & XT_CONNTRACK_REPLDST) {
+		if (sinfo->invflags & XT_CONNTRACK_REPLDST)
+			printf(" !");
+		printf(" %sctrepldst", optpfx);
+
+		print_addr(
+		    (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].dst.ip,
+		    &sinfo->dipmsk[IP_CT_DIR_REPLY],
+		    false,
+		    numeric);
+	}
+
+	if(sinfo->flags & XT_CONNTRACK_STATUS) {
+        	if (sinfo->invflags & XT_CONNTRACK_STATUS)
+			printf(" !");
+		printf(" %sctstatus", optpfx);
+		print_status(sinfo->statusmask);
+	}
+
+	if(sinfo->flags & XT_CONNTRACK_EXPIRES) {
+        	if (sinfo->invflags & XT_CONNTRACK_EXPIRES)
+			printf(" !");
+		printf(" %sctexpire ", optpfx);
+
+        	if (sinfo->expires_max == sinfo->expires_min)
+			printf("%lu", sinfo->expires_min);
+        	else
+			printf("%lu:%lu", sinfo->expires_min, sinfo->expires_max);
+	}
+}
+
+static void
+conntrack_dump_ports(const char *prefix, const char *opt,
+		     u_int16_t port_low, u_int16_t port_high)
+{
+	if (port_high == 0 || port_low == port_high)
+		printf(" %s%s %u", prefix, opt, port_low);
+	else
+		printf(" %s%s %u:%u", prefix, opt, port_low, port_high);
+}
+
+static void
+conntrack_dump(const struct xt_conntrack_mtinfo3 *info, const char *prefix,
+               unsigned int family, bool numeric, bool v3)
+{
+	if (info->match_flags & XT_CONNTRACK_STATE) {
+		if (info->invert_flags & XT_CONNTRACK_STATE)
+			printf(" !");
+		printf(" %s%s", prefix,
+			info->match_flags & XT_CONNTRACK_STATE_ALIAS
+				? "state" : "ctstate");
+		print_state(info->state_mask);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_PROTO) {
+		if (info->invert_flags & XT_CONNTRACK_PROTO)
+			printf(" !");
+		printf(" %sctproto %u", prefix, info->l4proto);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_ORIGSRC) {
+		if (info->invert_flags & XT_CONNTRACK_ORIGSRC)
+			printf(" !");
+		printf(" %sctorigsrc", prefix);
+		conntrack_dump_addr(&info->origsrc_addr, &info->origsrc_mask,
+		                    family, numeric);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_ORIGDST) {
+		if (info->invert_flags & XT_CONNTRACK_ORIGDST)
+			printf(" !");
+		printf(" %sctorigdst", prefix);
+		conntrack_dump_addr(&info->origdst_addr, &info->origdst_mask,
+		                    family, numeric);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_REPLSRC) {
+		if (info->invert_flags & XT_CONNTRACK_REPLSRC)
+			printf(" !");
+		printf(" %sctreplsrc", prefix);
+		conntrack_dump_addr(&info->replsrc_addr, &info->replsrc_mask,
+		                    family, numeric);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_REPLDST) {
+		if (info->invert_flags & XT_CONNTRACK_REPLDST)
+			printf(" !");
+		printf(" %sctrepldst", prefix);
+		conntrack_dump_addr(&info->repldst_addr, &info->repldst_mask,
+		                    family, numeric);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_ORIGSRC_PORT) {
+		if (info->invert_flags & XT_CONNTRACK_ORIGSRC_PORT)
+			printf(" !");
+		conntrack_dump_ports(prefix, "ctorigsrcport",
+				     v3 ? info->origsrc_port : ntohs(info->origsrc_port),
+				     v3 ? info->origsrc_port_high : 0);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_ORIGDST_PORT) {
+		if (info->invert_flags & XT_CONNTRACK_ORIGDST_PORT)
+			printf(" !");
+		conntrack_dump_ports(prefix, "ctorigdstport",
+				     v3 ? info->origdst_port : ntohs(info->origdst_port),
+				     v3 ? info->origdst_port_high : 0);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_REPLSRC_PORT) {
+		if (info->invert_flags & XT_CONNTRACK_REPLSRC_PORT)
+			printf(" !");
+		conntrack_dump_ports(prefix, "ctreplsrcport",
+				     v3 ? info->replsrc_port : ntohs(info->replsrc_port),
+				     v3 ? info->replsrc_port_high : 0);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_REPLDST_PORT) {
+		if (info->invert_flags & XT_CONNTRACK_REPLDST_PORT)
+			printf(" !");
+		conntrack_dump_ports(prefix, "ctrepldstport",
+				     v3 ? info->repldst_port : ntohs(info->repldst_port),
+				     v3 ? info->repldst_port_high : 0);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_STATUS) {
+		if (info->invert_flags & XT_CONNTRACK_STATUS)
+			printf(" !");
+		printf(" %sctstatus", prefix);
+		print_status(info->status_mask);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_EXPIRES) {
+		if (info->invert_flags & XT_CONNTRACK_EXPIRES)
+			printf(" !");
+		printf(" %sctexpire ", prefix);
+
+		if (info->expires_max == info->expires_min)
+			printf("%u", (unsigned int)info->expires_min);
+		else
+			printf("%u:%u", (unsigned int)info->expires_min,
+			       (unsigned int)info->expires_max);
+	}
+
+	if (info->match_flags & XT_CONNTRACK_DIRECTION) {
+		if (info->invert_flags & XT_CONNTRACK_DIRECTION)
+			printf(" %sctdir REPLY", prefix);
+		else
+			printf(" %sctdir ORIGINAL", prefix);
+	}
+}
+
+static const char *
+conntrack_print_name_alias(const struct xt_entry_match *match)
+{
+	struct xt_conntrack_mtinfo1 *info = (void *)match->data;
+
+	return info->match_flags & XT_CONNTRACK_STATE_ALIAS
+		? "state" : "conntrack";
+}
+
+static void conntrack_print(const void *ip, const struct xt_entry_match *match,
+                            int numeric)
+{
+	matchinfo_print(ip, match, numeric, "");
+}
+
+static void
+conntrack1_mt4_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	const struct xt_conntrack_mtinfo1 *info = (void *)match->data;
+	struct xt_conntrack_mtinfo3 up;
+
+	cinfo_transform(&up, info);
+	conntrack_dump(&up, "", NFPROTO_IPV4, numeric, false);
+}
+
+static void
+conntrack1_mt6_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	const struct xt_conntrack_mtinfo1 *info = (void *)match->data;
+	struct xt_conntrack_mtinfo3 up;
+
+	cinfo_transform(&up, info);
+	conntrack_dump(&up, "", NFPROTO_IPV6, numeric, false);
+}
+
+static void
+conntrack2_mt_print(const void *ip, const struct xt_entry_match *match,
+                    int numeric)
+{
+	conntrack_dump((const void *)match->data, "", NFPROTO_IPV4, numeric, false);
+}
+
+static void
+conntrack2_mt6_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	conntrack_dump((const void *)match->data, "", NFPROTO_IPV6, numeric, false);
+}
+
+static void
+conntrack3_mt_print(const void *ip, const struct xt_entry_match *match,
+                    int numeric)
+{
+	conntrack_dump((const void *)match->data, "", NFPROTO_IPV4, numeric, true);
+}
+
+static void
+conntrack3_mt6_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	conntrack_dump((const void *)match->data, "", NFPROTO_IPV6, numeric, true);
+}
+
+static void conntrack_save(const void *ip, const struct xt_entry_match *match)
+{
+	matchinfo_print(ip, match, 1, "--");
+}
+
+static void conntrack3_mt_save(const void *ip,
+                               const struct xt_entry_match *match)
+{
+	conntrack_dump((const void *)match->data, "--", NFPROTO_IPV4, true, true);
+}
+
+static void conntrack3_mt6_save(const void *ip,
+                                const struct xt_entry_match *match)
+{
+	conntrack_dump((const void *)match->data, "--", NFPROTO_IPV6, true, true);
+}
+
+static void conntrack2_mt_save(const void *ip,
+                               const struct xt_entry_match *match)
+{
+	conntrack_dump((const void *)match->data, "--", NFPROTO_IPV4, true, false);
+}
+
+static void conntrack2_mt6_save(const void *ip,
+                                const struct xt_entry_match *match)
+{
+	conntrack_dump((const void *)match->data, "--", NFPROTO_IPV6, true, false);
+}
+
+static void
+conntrack1_mt4_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_conntrack_mtinfo1 *info = (void *)match->data;
+	struct xt_conntrack_mtinfo3 up;
+
+	cinfo_transform(&up, info);
+	conntrack_dump(&up, "--", NFPROTO_IPV4, true, false);
+}
+
+static void
+conntrack1_mt6_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_conntrack_mtinfo1 *info = (void *)match->data;
+	struct xt_conntrack_mtinfo3 up;
+
+	cinfo_transform(&up, info);
+	conntrack_dump(&up, "--", NFPROTO_IPV6, true, false);
+}
+
+static void
+state_help(void)
+{
+	printf(
+"state match options:\n"
+" [!] --state [INVALID|ESTABLISHED|NEW|RELATED|UNTRACKED][,...]\n"
+"				State(s) to match\n");
+}
+
+static const struct xt_option_entry state_opts[] = {
+	{.name = "state", .id = O_CTSTATE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static unsigned int
+state_parse_state(const char *state, size_t len)
+{
+	if (strncasecmp(state, "INVALID", len) == 0)
+		return XT_CONNTRACK_STATE_INVALID;
+	else if (strncasecmp(state, "NEW", len) == 0)
+		return XT_CONNTRACK_STATE_BIT(IP_CT_NEW);
+	else if (strncasecmp(state, "ESTABLISHED", len) == 0)
+		return XT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
+	else if (strncasecmp(state, "RELATED", len) == 0)
+		return XT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
+	else if (strncasecmp(state, "UNTRACKED", len) == 0)
+		return XT_CONNTRACK_STATE_UNTRACKED;
+	return 0;
+}
+
+static unsigned int
+state_parse_states(const char *arg)
+{
+	const char *comma;
+	unsigned int mask = 0, flag;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg)
+			goto badstate;
+		flag = state_parse_state(arg, comma-arg);
+		if (flag == 0)
+			goto badstate;
+		mask |= flag;
+		arg = comma+1;
+	}
+	if (!*arg)
+		xtables_error(PARAMETER_PROBLEM, "\"--state\" requires a list of "
+					      "states with no spaces, e.g. "
+					      "ESTABLISHED,RELATED");
+	if (strlen(arg) == 0)
+		goto badstate;
+	flag = state_parse_state(arg, strlen(arg));
+	if (flag == 0)
+		goto badstate;
+	mask |= flag;
+	return mask;
+ badstate:
+	xtables_error(PARAMETER_PROBLEM, "Bad state \"%s\"", arg);
+}
+
+static void state_parse(struct xt_option_call *cb)
+{
+	struct xt_state_info *sinfo = cb->data;
+
+	xtables_option_parse(cb);
+	sinfo->statemask = state_parse_states(cb->arg);
+	if (cb->invert)
+		sinfo->statemask = ~sinfo->statemask;
+}
+
+static void state_ct1_parse(struct xt_option_call *cb)
+{
+	struct xt_conntrack_mtinfo1 *sinfo = cb->data;
+
+	xtables_option_parse(cb);
+	sinfo->match_flags = XT_CONNTRACK_STATE | XT_CONNTRACK_STATE_ALIAS;
+	sinfo->state_mask = state_parse_states(cb->arg);
+	if (cb->invert)
+		sinfo->invert_flags |= XT_CONNTRACK_STATE;
+}
+
+static void state_ct23_parse(struct xt_option_call *cb)
+{
+	struct xt_conntrack_mtinfo3 *sinfo = cb->data;
+
+	xtables_option_parse(cb);
+	sinfo->match_flags = XT_CONNTRACK_STATE | XT_CONNTRACK_STATE_ALIAS;
+	sinfo->state_mask = state_parse_states(cb->arg);
+	if (cb->invert)
+		sinfo->invert_flags |= XT_CONNTRACK_STATE;
+}
+
+static void state_print_state(unsigned int statemask)
+{
+	const char *sep = "";
+
+	if (statemask & XT_CONNTRACK_STATE_INVALID) {
+		printf("%sINVALID", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
+		printf("%sNEW", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
+		printf("%sRELATED", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
+		printf("%sESTABLISHED", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_UNTRACKED) {
+		printf("%sUNTRACKED", sep);
+		sep = ",";
+	}
+}
+
+static void
+state_print(const void *ip,
+      const struct xt_entry_match *match,
+      int numeric)
+{
+	const struct xt_state_info *sinfo = (const void *)match->data;
+
+	printf(" state ");
+	state_print_state(sinfo->statemask);
+}
+
+static void state_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_state_info *sinfo = (const void *)match->data;
+
+	printf(" --state ");
+	state_print_state(sinfo->statemask);
+}
+
+static void state_xlate_print(struct xt_xlate *xl, unsigned int statemask)
+{
+	const char *sep = "";
+
+	if (statemask & XT_CONNTRACK_STATE_INVALID) {
+		xt_xlate_add(xl, "%s%s", sep, "invalid");
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
+		xt_xlate_add(xl, "%s%s", sep, "new");
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
+		xt_xlate_add(xl, "%s%s", sep, "related");
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
+		xt_xlate_add(xl, "%s%s", sep, "established");
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_UNTRACKED) {
+		xt_xlate_add(xl, "%s%s", sep, "untracked");
+		sep = ",";
+	}
+}
+
+static int state_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_conntrack_mtinfo3 *sinfo =
+		(const void *)params->match->data;
+
+	xt_xlate_add(xl, "ct state %s", sinfo->invert_flags & XT_CONNTRACK_STATE ?
+					"!= " : "");
+	state_xlate_print(xl, sinfo->state_mask);
+	xt_xlate_add(xl, " ");
+	return 1;
+}
+
+static void status_xlate_print(struct xt_xlate *xl, unsigned int statusmask)
+{
+	const char *sep = "";
+
+	if (statusmask & IPS_EXPECTED) {
+		xt_xlate_add(xl, "%s%s", sep, "expected");
+		sep = ",";
+	}
+	if (statusmask & IPS_SEEN_REPLY) {
+		xt_xlate_add(xl, "%s%s", sep, "seen-reply");
+		sep = ",";
+	}
+	if (statusmask & IPS_ASSURED) {
+		xt_xlate_add(xl, "%s%s", sep, "assured");
+		sep = ",";
+	}
+	if (statusmask & IPS_CONFIRMED) {
+		xt_xlate_add(xl, "%s%s", sep, "confirmed");
+		sep = ",";
+	}
+}
+
+static void addr_xlate_print(struct xt_xlate *xl,
+			     const union nf_inet_addr *addr,
+			     const union nf_inet_addr *mask,
+			     unsigned int family)
+{
+	if (family == NFPROTO_IPV4) {
+		xt_xlate_add(xl, "%s%s", xtables_ipaddr_to_numeric(&addr->in),
+		     xtables_ipmask_to_numeric(&mask->in));
+	} else if (family == NFPROTO_IPV6) {
+		xt_xlate_add(xl, "%s%s", xtables_ip6addr_to_numeric(&addr->in6),
+		     xtables_ip6mask_to_numeric(&mask->in6));
+	}
+}
+
+static int _conntrack3_mt_xlate(struct xt_xlate *xl,
+				const struct xt_xlate_mt_params *params,
+				int family)
+{
+	const struct xt_conntrack_mtinfo3 *sinfo =
+		(const void *)params->match->data;
+	char *space = "";
+
+	if (sinfo->match_flags & XT_CONNTRACK_DIRECTION) {
+		xt_xlate_add(xl, "ct direction %s",
+			     sinfo->invert_flags & XT_CONNTRACK_DIRECTION ?
+			     "reply" : "original");
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_PROTO) {
+		xt_xlate_add(xl, "%sct %s protocol %s%u", space,
+			     sinfo->invert_flags & XT_CONNTRACK_DIRECTION ?
+			     "reply" : "original",
+			     sinfo->invert_flags & XT_CONNTRACK_PROTO ?
+			     "!= " : "",
+			     sinfo->l4proto);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_STATE) {
+		if ((sinfo->state_mask & XT_CONNTRACK_STATE_SNAT) ||
+		    (sinfo->state_mask & XT_CONNTRACK_STATE_DNAT)) {
+			xt_xlate_add(xl, "%sct status %s%s", space,
+				     sinfo->invert_flags & XT_CONNTRACK_STATUS ? "!=" : "",
+				     sinfo->state_mask & XT_CONNTRACK_STATE_SNAT ? "snat" : "dnat");
+			space = " ";
+		} else {
+			xt_xlate_add(xl, "%sct state %s", space,
+				     sinfo->invert_flags & XT_CONNTRACK_STATE ?
+				     "!= " : "");
+			state_xlate_print(xl, sinfo->state_mask);
+			space = " ";
+		}
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_STATUS) {
+		xt_xlate_add(xl, "%sct status %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_STATUS ?
+			     "!= " : "");
+		status_xlate_print(xl, sinfo->status_mask);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_EXPIRES) {
+		xt_xlate_add(xl, "%sct expiration %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_EXPIRES ?
+			     "!= " : "");
+		if (sinfo->expires_max == sinfo->expires_min)
+			xt_xlate_add(xl, "%u", sinfo->expires_min);
+		else
+			xt_xlate_add(xl, "%u-%u", sinfo->expires_min,
+				     sinfo->expires_max);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_ORIGSRC) {
+		if (&sinfo->origsrc_addr == 0L)
+			return 0;
+
+		xt_xlate_add(xl, "%sct original saddr %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_ORIGSRC ?
+			     "!= " : "");
+		addr_xlate_print(xl, &sinfo->origsrc_addr,
+				 &sinfo->origsrc_mask, family);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_ORIGDST) {
+		if (&sinfo->origdst_addr == 0L)
+			return 0;
+
+		xt_xlate_add(xl, "%sct original daddr %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_ORIGDST ?
+			     "!= " : "");
+		addr_xlate_print(xl, &sinfo->origdst_addr,
+				 &sinfo->origdst_mask, family);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_REPLSRC) {
+		if (&sinfo->replsrc_addr == 0L)
+			return 0;
+
+		xt_xlate_add(xl, "%sct reply saddr %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_REPLSRC ?
+			     "!= " : "");
+		addr_xlate_print(xl, &sinfo->replsrc_addr,
+				 &sinfo->replsrc_mask, family);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_REPLDST) {
+		if (&sinfo->repldst_addr == 0L)
+			return 0;
+
+		xt_xlate_add(xl, "%sct reply daddr %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_REPLDST ?
+			     "!= " : "");
+		addr_xlate_print(xl, &sinfo->repldst_addr,
+				 &sinfo->repldst_mask, family);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_ORIGSRC_PORT) {
+		xt_xlate_add(xl, "%sct original proto-src %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_ORIGSRC_PORT ?
+			     "!= " : "");
+		if (sinfo->origsrc_port == sinfo->origsrc_port_high)
+			xt_xlate_add(xl, "%u", sinfo->origsrc_port);
+		else
+			xt_xlate_add(xl, "%u-%u", sinfo->origsrc_port,
+				     sinfo->origsrc_port_high);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_ORIGDST_PORT) {
+		xt_xlate_add(xl, "%sct original proto-dst %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_ORIGDST_PORT ?
+			     "!= " : "");
+		if (sinfo->origdst_port == sinfo->origdst_port_high)
+			xt_xlate_add(xl, "%u", sinfo->origdst_port);
+		else
+			xt_xlate_add(xl, "%u-%u", sinfo->origdst_port,
+				     sinfo->origdst_port_high);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_REPLSRC_PORT) {
+		xt_xlate_add(xl, "%sct reply proto-src %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_REPLSRC_PORT ?
+			     "!= " : "");
+		if (sinfo->replsrc_port == sinfo->replsrc_port_high)
+			xt_xlate_add(xl, "%u", sinfo->replsrc_port);
+		else
+			xt_xlate_add(xl, "%u-%u", sinfo->replsrc_port,
+				     sinfo->replsrc_port_high);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_REPLDST_PORT) {
+		xt_xlate_add(xl, "%sct reply proto-dst %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_REPLDST_PORT ?
+			     "!= " : "");
+		if (sinfo->repldst_port == sinfo->repldst_port_high)
+			xt_xlate_add(xl, "%u", sinfo->repldst_port);
+		else
+			xt_xlate_add(xl, "%u-%u", sinfo->repldst_port,
+				     sinfo->repldst_port_high);
+	}
+
+	return 1;
+}
+
+static int conntrack3_mt4_xlate(struct xt_xlate *xl,
+				const struct xt_xlate_mt_params *params)
+{
+	return _conntrack3_mt_xlate(xl, params, NFPROTO_IPV4);
+}
+
+static int conntrack3_mt6_xlate(struct xt_xlate *xl,
+				const struct xt_xlate_mt_params *params)
+{
+	return _conntrack3_mt_xlate(xl, params, NFPROTO_IPV6);
+}
+
+static struct xtables_match conntrack_mt_reg[] = {
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "conntrack",
+		.revision      = 0,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_info)),
+		.help          = conntrack_mt_help,
+		.x6_parse      = conntrack_parse,
+		.x6_fcheck     = conntrack_mt_check,
+		.print         = conntrack_print,
+		.save          = conntrack_save,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack_mt_opts_v0,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "conntrack",
+		.revision      = 1,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo1)),
+		.help          = conntrack_mt_help,
+		.x6_parse      = conntrack1_mt_parse,
+		.x6_fcheck     = conntrack_mt_check,
+		.print         = conntrack1_mt4_print,
+		.save          = conntrack1_mt4_save,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack2_mt_opts,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "conntrack",
+		.revision      = 1,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo1)),
+		.help          = conntrack_mt_help,
+		.x6_parse      = conntrack1_mt_parse,
+		.x6_fcheck     = conntrack_mt_check,
+		.print         = conntrack1_mt6_print,
+		.save          = conntrack1_mt6_save,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack2_mt_opts,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "conntrack",
+		.revision      = 2,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo2)),
+		.help          = conntrack_mt_help,
+		.x6_parse      = conntrack2_mt_parse,
+		.x6_fcheck     = conntrack_mt_check,
+		.print         = conntrack2_mt_print,
+		.save          = conntrack2_mt_save,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack2_mt_opts,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "conntrack",
+		.revision      = 2,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo2)),
+		.help          = conntrack_mt_help,
+		.x6_parse      = conntrack2_mt_parse,
+		.x6_fcheck     = conntrack_mt_check,
+		.print         = conntrack2_mt6_print,
+		.save          = conntrack2_mt6_save,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack2_mt_opts,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "conntrack",
+		.revision      = 3,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo3)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo3)),
+		.help          = conntrack_mt_help,
+		.x6_parse      = conntrack3_mt_parse,
+		.x6_fcheck     = conntrack_mt_check,
+		.print         = conntrack3_mt_print,
+		.save          = conntrack3_mt_save,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack3_mt_opts,
+		.xlate	       = conntrack3_mt4_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "conntrack",
+		.revision      = 3,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo3)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo3)),
+		.help          = conntrack_mt_help,
+		.x6_parse      = conntrack3_mt_parse,
+		.x6_fcheck     = conntrack_mt_check,
+		.print         = conntrack3_mt6_print,
+		.save          = conntrack3_mt6_save,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack3_mt_opts,
+		.xlate	       = conntrack3_mt6_xlate,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "state",
+		.real_name     = "conntrack",
+		.revision      = 1,
+		.ext_flags     = XTABLES_EXT_ALIAS,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo1)),
+		.help          = state_help,
+		.print         = state_print,
+		.save          = state_save,
+		.x6_parse      = state_ct1_parse,
+		.x6_options    = state_opts,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "state",
+		.real_name     = "conntrack",
+		.revision      = 2,
+		.ext_flags     = XTABLES_EXT_ALIAS,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo2)),
+		.help          = state_help,
+		.print         = state_print,
+		.save          = state_save,
+		.x6_parse      = state_ct23_parse,
+		.x6_options    = state_opts,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "state",
+		.real_name     = "conntrack",
+		.revision      = 3,
+		.ext_flags     = XTABLES_EXT_ALIAS,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo3)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo3)),
+		.help          = state_help,
+		.print         = state_print,
+		.save          = state_save,
+		.x6_parse      = state_ct23_parse,
+		.x6_options    = state_opts,
+		.xlate         = state_xlate,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "state",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_state_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_state_info)),
+		.help          = state_help,
+		.print         = state_print,
+		.save          = state_save,
+		.x6_parse      = state_parse,
+		.x6_options    = state_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(conntrack_mt_reg, ARRAY_SIZE(conntrack_mt_reg));
+}
diff --git a/extensions/libxt_conntrack.man b/extensions/libxt_conntrack.man
new file mode 100644
index 0000000..4b13f0f
--- /dev/null
+++ b/extensions/libxt_conntrack.man
@@ -0,0 +1,86 @@
+This module, when combined with connection tracking, allows access to the
+connection tracking state for this packet/connection.
+.TP
+[\fB!\fP] \fB\-\-ctstate\fP \fIstatelist\fP
+\fIstatelist\fP is a comma separated list of the connection states to match.
+Possible states are listed below.
+.TP
+[\fB!\fP] \fB\-\-ctproto\fP \fIl4proto\fP
+Layer-4 protocol to match (by number or name)
+.TP
+[\fB!\fP] \fB\-\-ctorigsrc\fP \fIaddress\fP[\fB/\fP\fImask\fP]
+.TP
+[\fB!\fP] \fB\-\-ctorigdst\fP \fIaddress\fP[\fB/\fP\fImask\fP]
+.TP
+[\fB!\fP] \fB\-\-ctreplsrc\fP \fIaddress\fP[\fB/\fP\fImask\fP]
+.TP
+[\fB!\fP] \fB\-\-ctrepldst\fP \fIaddress\fP[\fB/\fP\fImask\fP]
+Match against original/reply source/destination address
+.TP
+[\fB!\fP] \fB\-\-ctorigsrcport\fP \fIport\fP[\fB:\fP\fIport\fP]
+.TP
+[\fB!\fP] \fB\-\-ctorigdstport\fP \fIport\fP[\fB:\fP\fIport\fP]
+.TP
+[\fB!\fP] \fB\-\-ctreplsrcport\fP \fIport\fP[\fB:\fP\fIport\fP]
+.TP
+[\fB!\fP] \fB\-\-ctrepldstport\fP \fIport\fP[\fB:\fP\fIport\fP]
+Match against original/reply source/destination port (TCP/UDP/etc.) or GRE key.
+Matching against port ranges is only supported in kernel versions above 2.6.38.
+.TP
+[\fB!\fP] \fB\-\-ctstatus\fP \fIstatelist\fP
+\fIstatuslist\fP is a comma separated list of the connection statuses to match.
+Possible statuses are listed below.
+.TP
+[\fB!\fP] \fB\-\-ctexpire\fP \fItime\fP[\fB:\fP\fItime\fP]
+Match remaining lifetime in seconds against given value or range of values
+(inclusive)
+.TP
+\fB\-\-ctdir\fP {\fBORIGINAL\fP|\fBREPLY\fP}
+Match packets that are flowing in the specified direction. If this flag is not
+specified at all, matches packets in both directions.
+.PP
+States for \fB\-\-ctstate\fP:
+.TP
+\fBINVALID\fP
+The packet is associated with no known connection.
+.TP
+\fBNEW\fP
+The packet has started a new connection or otherwise associated
+with a connection which has not seen packets in both directions.
+.TP
+\fBESTABLISHED\fP
+The packet is associated with a connection which has seen packets
+in both directions.
+.TP
+\fBRELATED\fP
+The packet is starting a new connection, but is associated with an
+existing connection, such as an FTP data transfer or an ICMP error.
+.TP
+\fBUNTRACKED\fP
+The packet is not tracked at all, which happens if you explicitly untrack it
+by using \-j CT \-\-notrack in the raw table.
+.TP
+\fBSNAT\fP
+A virtual state, matching if the original source address differs from the reply
+destination.
+.TP
+\fBDNAT\fP
+A virtual state, matching if the original destination differs from the reply
+source.
+.PP
+Statuses for \fB\-\-ctstatus\fP:
+.TP
+\fBNONE\fP
+None of the below.
+.TP
+\fBEXPECTED\fP
+This is an expected connection (i.e. a conntrack helper set it up).
+.TP
+\fBSEEN_REPLY\fP
+Conntrack has seen packets in both directions.
+.TP
+\fBASSURED\fP
+Conntrack entry should never be early-expired.
+.TP
+\fBCONFIRMED\fP
+Connection is confirmed: originating packet has left box.
diff --git a/extensions/libxt_conntrack.t b/extensions/libxt_conntrack.t
new file mode 100644
index 0000000..db53147
--- /dev/null
+++ b/extensions/libxt_conntrack.t
@@ -0,0 +1,27 @@
+:INPUT,FORWARD,OUTPUT
+-m conntrack --ctstate NEW;=;OK
+-m conntrack --ctstate NEW,ESTABLISHED;=;OK
+-m conntrack --ctstate NEW,RELATED,ESTABLISHED;=;OK
+-m conntrack --ctstate INVALID;=;OK
+-m conntrack --ctstate UNTRACKED;=;OK
+-m conntrack --ctstate SNAT,DNAT;=;OK
+-m conntrack --ctstate wrong;;FAIL
+# should we convert this to output "tcp" instead of 6?
+-m conntrack --ctproto tcp;-m conntrack --ctproto 6;OK
+-m conntrack --ctorigsrc 1.1.1.1;=;OK
+-m conntrack --ctorigdst 1.1.1.1;=;OK
+-m conntrack --ctreplsrc 1.1.1.1;=;OK
+-m conntrack --ctrepldst 1.1.1.1;=;OK
+-m conntrack --ctexpire 0;=;OK
+-m conntrack --ctexpire 4294967295;=;OK
+-m conntrack --ctexpire 0:4294967295;=;OK
+-m conntrack --ctexpire 42949672956;;FAIL
+-m conntrack --ctexpire -1;;FAIL
+-m conntrack --ctdir ORIGINAL;=;OK
+-m conntrack --ctdir REPLY;=;OK
+-m conntrack --ctstatus NONE;=;OK
+-m conntrack --ctstatus CONFIRMED;=;OK
+-m conntrack --ctstatus ASSURED;=;OK
+-m conntrack --ctstatus EXPECTED;=;OK
+-m conntrack --ctstatus SEEN_REPLY;=;OK
+-m conntrack;;FAIL
diff --git a/extensions/libxt_conntrack.txlate b/extensions/libxt_conntrack.txlate
new file mode 100644
index 0000000..d374f8a
--- /dev/null
+++ b/extensions/libxt_conntrack.txlate
@@ -0,0 +1,51 @@
+iptables-translate -t filter -A INPUT -m conntrack --ctstate NEW,RELATED -j ACCEPT
+nft add rule ip filter INPUT ct state new,related counter accept
+
+ip6tables-translate -t filter -A INPUT -m conntrack ! --ctstate NEW,RELATED -j ACCEPT
+nft add rule ip6 filter INPUT ct state != new,related counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctproto UDP -j ACCEPT
+nft add rule ip filter INPUT ct original protocol 17 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack ! --ctproto UDP -j ACCEPT
+nft add rule ip filter INPUT ct original protocol != 17 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctorigsrc 10.100.2.131 -j ACCEPT
+nft add rule ip filter INPUT ct original saddr 10.100.2.131 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctorigsrc 10.100.0.0/255.255.0.0 -j ACCEPT
+nft add rule ip filter INPUT ct original saddr 10.100.0.0/16 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctorigdst 10.100.2.131 -j ACCEPT
+nft add rule ip filter INPUT ct original daddr 10.100.2.131 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctreplsrc 10.100.2.131 -j ACCEPT
+nft add rule ip filter INPUT ct reply saddr 10.100.2.131 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctrepldst 10.100.2.131 -j ACCEPT
+nft add rule ip filter INPUT ct reply daddr 10.100.2.131 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctproto tcp --ctorigsrcport 443:444 -j ACCEPT
+nft add rule ip filter INPUT ct original protocol 6 ct original proto-src 443-444 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctstatus EXPECTED -j ACCEPT
+nft add rule ip filter INPUT ct status expected counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack ! --ctstatus CONFIRMED -j ACCEPT
+nft add rule ip filter INPUT ct status != confirmed counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctexpire 3 -j ACCEPT
+nft add rule ip filter INPUT ct expiration 3 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctdir ORIGINAL -j ACCEPT
+nft add rule ip filter INPUT ct direction original counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctstate NEW --ctproto tcp --ctorigsrc 192.168.0.1 --ctorigdst 192.168.0.1 --ctreplsrc 192.168.0.1 --ctrepldst 192.168.0.1 --ctorigsrcport 12 --ctorigdstport 14 --ctreplsrcport 16 --ctrepldstport 18 --ctexpire 10 --ctstatus SEEN_REPLY --ctdir ORIGINAL -j ACCEPT
+nft add rule ip filter INPUT ct direction original ct original protocol 6 ct state new ct status seen-reply ct expiration 10 ct original saddr 192.168.0.1 ct original daddr 192.168.0.1 ct reply saddr 192.168.0.1 ct reply daddr 192.168.0.1 ct original proto-src 12 ct original proto-dst 14 ct reply proto-src 16 ct reply proto-dst 18 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctstate SNAT -j ACCEPT
+nft add rule ip filter INPUT ct status snat counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctstate DNAT -j ACCEPT
+nft add rule ip filter INPUT ct status dnat counter accept
+
diff --git a/extensions/libxt_cpu.c b/extensions/libxt_cpu.c
new file mode 100644
index 0000000..41c13c3
--- /dev/null
+++ b/extensions/libxt_cpu.c
@@ -0,0 +1,74 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_cpu.h>
+
+enum {
+	O_CPU = 0,
+};
+
+static void cpu_help(void)
+{
+	printf(
+"cpu match options:\n"
+"[!] --cpu number   Match CPU number\n");
+}
+
+static const struct xt_option_entry cpu_opts[] = {
+	{.name = "cpu", .id = O_CPU, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_MAND | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_cpu_info, cpu)},
+	XTOPT_TABLEEND,
+};
+
+static void cpu_parse(struct xt_option_call *cb)
+{
+	struct xt_cpu_info *cpuinfo = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->invert)
+		cpuinfo->invert = true;
+}
+
+static void
+cpu_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_cpu_info *info = (void *)match->data;
+
+	printf(" cpu %s%u", info->invert ? "! ":"", info->cpu);
+}
+
+static void cpu_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_cpu_info *info = (void *)match->data;
+
+	printf("%s --cpu %u", info->invert ? " !" : "", info->cpu);
+}
+
+static int cpu_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_cpu_info *info = (void *)params->match->data;
+
+	xt_xlate_add(xl, "cpu%s %u", info->invert ? " !=" : "", info->cpu);
+
+	return 1;
+}
+
+static struct xtables_match cpu_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "cpu",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_cpu_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_cpu_info)),
+	.help		= cpu_help,
+	.print		= cpu_print,
+	.save		= cpu_save,
+	.x6_parse	= cpu_parse,
+	.x6_options	= cpu_opts,
+	.xlate		= cpu_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&cpu_match);
+}
diff --git a/extensions/libxt_cpu.man b/extensions/libxt_cpu.man
new file mode 100644
index 0000000..d9ea5c2
--- /dev/null
+++ b/extensions/libxt_cpu.man
@@ -0,0 +1,15 @@
+.TP
+[\fB!\fP] \fB\-\-cpu\fP \fInumber\fP
+Match cpu handling this packet. cpus are numbered from 0 to NR_CPUS-1
+Can be used in combination with RPS (Remote Packet Steering) or
+multiqueue NICs to spread network traffic on different queues.
+.PP
+Example:
+.PP
+iptables \-t nat \-A PREROUTING \-p tcp \-\-dport 80 \-m cpu \-\-cpu 0 
+\-j REDIRECT \-\-to\-port 8080
+.PP
+iptables \-t nat \-A PREROUTING \-p tcp \-\-dport 80 \-m cpu \-\-cpu 1 
+\-j REDIRECT \-\-to\-port 8081
+.PP
+Available since Linux 2.6.36.
diff --git a/extensions/libxt_cpu.t b/extensions/libxt_cpu.t
new file mode 100644
index 0000000..f5adb45
--- /dev/null
+++ b/extensions/libxt_cpu.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m cpu --cpu 0;=;OK
+-m cpu ! --cpu 0;=;OK
+-m cpu --cpu 4294967295;=;OK
+-m cpu --cpu 4294967296;;FAIL
+-m cpu;;FAIL
diff --git a/extensions/libxt_cpu.txlate b/extensions/libxt_cpu.txlate
new file mode 100644
index 0000000..c59b0e0
--- /dev/null
+++ b/extensions/libxt_cpu.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A INPUT -p tcp --dport 80 -m cpu --cpu 0 -j ACCEPT
+nft add rule ip filter INPUT tcp dport 80 cpu 0 counter accept
+
+iptables-translate -A INPUT -p tcp --dport 80 -m cpu ! --cpu 1 -j ACCEPT
+nft add rule ip filter INPUT tcp dport 80 cpu != 1 counter accept
diff --git a/extensions/libxt_dccp.c b/extensions/libxt_dccp.c
new file mode 100644
index 0000000..aea3e20
--- /dev/null
+++ b/extensions/libxt_dccp.c
@@ -0,0 +1,402 @@
+/* Shared library add-on to iptables for DCCP matching
+ *
+ * (C) 2005 by Harald Welte <laforge@netfilter.org>
+ *
+ * This program is distributed under the terms of GNU GPL v2, 1991
+ *
+ */
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <xtables.h>
+#include <linux/dccp.h>
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/xt_dccp.h>
+
+#if 0
+#define DEBUGP(format, first...) printf(format, ##first)
+#define static
+#else
+#define DEBUGP(format, fist...) 
+#endif
+
+enum {
+	O_SOURCE_PORT = 0,
+	O_DEST_PORT,
+	O_DCCP_TYPES,
+	O_DCCP_OPTION,
+};
+
+static void dccp_help(void)
+{
+	printf(
+"dccp match options\n"
+"[!] --source-port port[:port]                          match source port(s)\n"
+" --sport ...\n"
+"[!] --destination-port port[:port]                     match destination port(s)\n"
+" --dport ...\n"
+"[!] --dccp-types type[,...]                            match when packet is one of the given types\n"
+"[!] --dccp-option option                               match if option (by number!) is set\n"
+);
+}
+
+#define s struct xt_dccp_info
+static const struct xt_option_entry dccp_opts[] = {
+	{.name = "source-port", .id = O_SOURCE_PORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, spts)},
+	{.name = "sport", .id = O_SOURCE_PORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, spts)},
+	{.name = "destination-port", .id = O_DEST_PORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, dpts)},
+	{.name = "dport", .id = O_DEST_PORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, dpts)},
+	{.name = "dccp-types", .id = O_DCCP_TYPES, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "dccp-option", .id = O_DCCP_OPTION, .type = XTTYPE_UINT8,
+	 .min = 1, .max = UINT8_MAX, .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(s, option)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static const char *const dccp_pkt_types[] = {
+	[DCCP_PKT_REQUEST] 	= "REQUEST",
+	[DCCP_PKT_RESPONSE]	= "RESPONSE",
+	[DCCP_PKT_DATA]		= "DATA",
+	[DCCP_PKT_ACK]		= "ACK",
+	[DCCP_PKT_DATAACK]	= "DATAACK",
+	[DCCP_PKT_CLOSEREQ]	= "CLOSEREQ",
+	[DCCP_PKT_CLOSE]	= "CLOSE",
+	[DCCP_PKT_RESET]	= "RESET",
+	[DCCP_PKT_SYNC]		= "SYNC",
+	[DCCP_PKT_SYNCACK]	= "SYNCACK",
+	[DCCP_PKT_INVALID]	= "INVALID",
+};
+
+/* Bits for type values 11-15 */
+#define INVALID_OTHER_TYPE_MASK		0xf800
+
+static uint16_t
+parse_dccp_types(const char *typestring)
+{
+	uint16_t typemask = 0;
+	char *ptr, *buffer;
+
+	buffer = strdup(typestring);
+
+	for (ptr = strtok(buffer, ","); ptr; ptr = strtok(NULL, ",")) {
+		unsigned int i;
+		for (i = 0; i < ARRAY_SIZE(dccp_pkt_types); ++i)
+			if (!strcasecmp(dccp_pkt_types[i], ptr)) {
+				typemask |= (1 << i);
+				break;
+			}
+		if (i == ARRAY_SIZE(dccp_pkt_types))
+			xtables_error(PARAMETER_PROBLEM,
+				   "Unknown DCCP type `%s'", ptr);
+	}
+	if (typemask & (1 << DCCP_PKT_INVALID))
+		typemask |= INVALID_OTHER_TYPE_MASK;
+
+
+	free(buffer);
+	return typemask;
+}
+
+static void dccp_parse(struct xt_option_call *cb)
+{
+	struct xt_dccp_info *einfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SOURCE_PORT:
+		einfo->flags |= XT_DCCP_SRC_PORTS;
+		if (cb->invert)
+			einfo->invflags |= XT_DCCP_SRC_PORTS;
+		break;
+	case O_DEST_PORT:
+		einfo->flags |= XT_DCCP_DEST_PORTS;
+		if (cb->invert)
+			einfo->invflags |= XT_DCCP_DEST_PORTS;
+		break;
+	case O_DCCP_TYPES:
+		einfo->flags |= XT_DCCP_TYPE;
+		einfo->typemask = parse_dccp_types(cb->arg);
+		if (cb->invert)
+			einfo->invflags |= XT_DCCP_TYPE;
+		break;
+	case O_DCCP_OPTION:
+		einfo->flags |= XT_DCCP_OPTION;
+		if (cb->invert)
+			einfo->invflags |= XT_DCCP_OPTION;
+		break;
+	}
+}
+
+static const char *
+port_to_service(int port)
+{
+	const struct servent *service;
+
+	if ((service = getservbyport(htons(port), "dccp")))
+		return service->s_name;
+
+	return NULL;
+}
+
+static void
+print_port(uint16_t port, int numeric)
+{
+	const char *service;
+
+	if (numeric || (service = port_to_service(port)) == NULL)
+		printf("%u", port);
+	else
+		printf("%s", service);
+}
+
+static void
+print_ports(const char *name, uint16_t min, uint16_t max,
+	    int invert, int numeric)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFF || invert) {
+		printf(" %s", name);
+		if (min == max) {
+			printf(":%s", inv);
+			print_port(min, numeric);
+		} else {
+			printf("s:%s", inv);
+			print_port(min, numeric);
+			printf(":");
+			print_port(max, numeric);
+		}
+	}
+}
+
+static void
+print_types(uint16_t types, int inverted, int numeric)
+{
+	int have_type = 0;
+
+	if (inverted)
+		printf(" !");
+
+	printf(" ");
+	while (types) {
+		unsigned int i;
+
+		for (i = 0; !(types & (1 << i)); i++);
+
+		if (have_type)
+			printf(",");
+		else
+			have_type = 1;
+
+		if (numeric)
+			printf("%u", i);
+		else {
+			printf("%s", dccp_pkt_types[i]);
+
+			if (i == DCCP_PKT_INVALID)
+				break;
+		}
+
+		types &= ~(1 << i);
+	}
+}
+
+static void
+print_option(uint8_t option, int invert, int numeric)
+{
+	if (option || invert)
+		printf(" option=%s%u", invert ? "!" : "", option);
+}
+
+static void
+dccp_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_dccp_info *einfo =
+		(const struct xt_dccp_info *)match->data;
+
+	printf(" dccp");
+
+	if (einfo->flags & XT_DCCP_SRC_PORTS) {
+		print_ports("spt", einfo->spts[0], einfo->spts[1],
+			einfo->invflags & XT_DCCP_SRC_PORTS,
+			numeric);
+	}
+
+	if (einfo->flags & XT_DCCP_DEST_PORTS) {
+		print_ports("dpt", einfo->dpts[0], einfo->dpts[1],
+			einfo->invflags & XT_DCCP_DEST_PORTS,
+			numeric);
+	}
+
+	if (einfo->flags & XT_DCCP_TYPE) {
+		print_types(einfo->typemask,
+			   einfo->invflags & XT_DCCP_TYPE,
+			   numeric);
+	}
+
+	if (einfo->flags & XT_DCCP_OPTION) {
+		print_option(einfo->option,
+			     einfo->invflags & XT_DCCP_OPTION, numeric);
+	}
+}
+
+static void dccp_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_dccp_info *einfo =
+		(const struct xt_dccp_info *)match->data;
+
+	if (einfo->flags & XT_DCCP_SRC_PORTS) {
+		if (einfo->invflags & XT_DCCP_SRC_PORTS)
+			printf(" !");
+		if (einfo->spts[0] != einfo->spts[1])
+			printf(" --sport %u:%u",
+			       einfo->spts[0], einfo->spts[1]);
+		else
+			printf(" --sport %u", einfo->spts[0]);
+	}
+
+	if (einfo->flags & XT_DCCP_DEST_PORTS) {
+		if (einfo->invflags & XT_DCCP_DEST_PORTS)
+			printf(" !");
+		if (einfo->dpts[0] != einfo->dpts[1])
+			printf(" --dport %u:%u",
+			       einfo->dpts[0], einfo->dpts[1]);
+		else
+			printf(" --dport %u", einfo->dpts[0]);
+	}
+
+	if (einfo->flags & XT_DCCP_TYPE) {
+		printf("%s --dccp-types",
+		       einfo->invflags & XT_DCCP_TYPE ? " !" : "");
+		print_types(einfo->typemask, false, 0);
+	}
+
+	if (einfo->flags & XT_DCCP_OPTION) {
+		printf("%s --dccp-option %u",
+			einfo->invflags & XT_DCCP_OPTION ? " !" : "",
+			einfo->option);
+	}
+}
+
+static const char *const dccp_pkt_types_xlate[] = {
+	[DCCP_PKT_REQUEST]      = "request",
+	[DCCP_PKT_RESPONSE]     = "response",
+	[DCCP_PKT_DATA]         = "data",
+	[DCCP_PKT_ACK]          = "ack",
+	[DCCP_PKT_DATAACK]      = "dataack",
+	[DCCP_PKT_CLOSEREQ]     = "closereq",
+	[DCCP_PKT_CLOSE]        = "close",
+	[DCCP_PKT_RESET]        = "reset",
+	[DCCP_PKT_SYNC]         = "sync",
+	[DCCP_PKT_SYNCACK]      = "syncack",
+	[DCCP_PKT_INVALID]	= "10-15",
+};
+
+static int dccp_type_xlate(const struct xt_dccp_info *einfo,
+			   struct xt_xlate *xl)
+{
+	bool have_type = false, set_need = false;
+	uint16_t types = einfo->typemask;
+
+	if (types & INVALID_OTHER_TYPE_MASK) {
+		types &= ~INVALID_OTHER_TYPE_MASK;
+		types |= 1 << DCCP_PKT_INVALID;
+	}
+
+	if ((types != 0) && !(types == (types & -types))) {
+		xt_xlate_add(xl, "{");
+		set_need = true;
+	}
+
+	while (types) {
+		unsigned int i;
+
+		for (i = 0; !(types & (1 << i)); i++);
+
+		if (have_type)
+			xt_xlate_add(xl, ", ");
+		else
+			have_type = true;
+
+		xt_xlate_add(xl, "%s", dccp_pkt_types_xlate[i]);
+
+		types &= ~(1 << i);
+	}
+
+	if (set_need)
+		xt_xlate_add(xl, "}");
+
+	return 1;
+}
+
+static int dccp_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_dccp_info *einfo =
+		(const struct xt_dccp_info *)params->match->data;
+	char *space = "";
+	int ret = 1;
+
+	if (einfo->flags & XT_DCCP_SRC_PORTS) {
+		xt_xlate_add(xl, "dccp sport%s %u",
+			     einfo->invflags & XT_DCCP_SRC_PORTS ? " !=" : "",
+			     einfo->spts[0]);
+
+		if (einfo->spts[0] != einfo->spts[1])
+			xt_xlate_add(xl, "-%u", einfo->spts[1]);
+
+		space = " ";
+	}
+
+	if (einfo->flags & XT_DCCP_DEST_PORTS) {
+		xt_xlate_add(xl, "%sdccp dport%s %u", space,
+			     einfo->invflags & XT_DCCP_DEST_PORTS ? " !=" : "",
+			     einfo->dpts[0]);
+
+		if (einfo->dpts[0] != einfo->dpts[1])
+			xt_xlate_add(xl, "-%u", einfo->dpts[1]);
+
+		space = " ";
+	}
+
+	if (einfo->flags & XT_DCCP_TYPE && einfo->typemask) {
+		xt_xlate_add(xl, "%sdccp type%s ", space,
+			     einfo->invflags & XT_DCCP_TYPE ? " !=" : "");
+		ret = dccp_type_xlate(einfo, xl);
+
+		space = " ";
+	}
+
+	/* FIXME: no dccp option support in nftables yet */
+	if (einfo->flags & XT_DCCP_OPTION)
+		ret = 0;
+
+	return ret;
+}
+static struct xtables_match dccp_match = {
+	.name		= "dccp",
+	.family		= NFPROTO_UNSPEC,
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_dccp_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_dccp_info)),
+	.help		= dccp_help,
+	.print		= dccp_print,
+	.save		= dccp_save,
+	.x6_parse	= dccp_parse,
+	.x6_options	= dccp_opts,
+	.xlate		= dccp_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&dccp_match);
+}
diff --git a/extensions/libxt_dccp.man b/extensions/libxt_dccp.man
new file mode 100644
index 0000000..71beb4b
--- /dev/null
+++ b/extensions/libxt_dccp.man
@@ -0,0 +1,12 @@
+.TP
+[\fB!\fP] \fB\-\-source\-port\fP,\fB\-\-sport\fP \fIport\fP[\fB:\fP\fIport\fP]
+.TP
+[\fB!\fP] \fB\-\-destination\-port\fP,\fB\-\-dport\fP \fIport\fP[\fB:\fP\fIport\fP]
+.TP
+[\fB!\fP] \fB\-\-dccp\-types\fP \fImask\fP
+Match when the DCCP packet type is one of 'mask'. 'mask' is a comma-separated
+list of packet types.  Packet types are: 
+.BR "REQUEST RESPONSE DATA ACK DATAACK CLOSEREQ CLOSE RESET SYNC SYNCACK INVALID" .
+.TP
+[\fB!\fP] \fB\-\-dccp\-option\fP \fInumber\fP
+Match if DCCP option set.
diff --git a/extensions/libxt_dccp.t b/extensions/libxt_dccp.t
new file mode 100644
index 0000000..f60b480
--- /dev/null
+++ b/extensions/libxt_dccp.t
@@ -0,0 +1,30 @@
+:INPUT,FORWARD,OUTPUT
+-p dccp -m dccp --sport 1;=;OK
+-p dccp -m dccp --sport 65535;=;OK
+-p dccp -m dccp --dport 1;=;OK
+-p dccp -m dccp --dport 65535;=;OK
+-p dccp -m dccp --sport 1:1023;=;OK
+-p dccp -m dccp --sport 1024:65535;=;OK
+-p dccp -m dccp --sport 1024:;-p dccp -m dccp --sport 1024:65535;OK
+-p dccp -m dccp ! --sport 1;=;OK
+-p dccp -m dccp ! --sport 65535;=;OK
+-p dccp -m dccp ! --dport 1;=;OK
+-p dccp -m dccp ! --dport 65535;=;OK
+-p dccp -m dccp --sport 1 --dport 65535;=;OK
+-p dccp -m dccp --sport 65535 --dport 1;=;OK
+-p dccp -m dccp ! --sport 1 --dport 65535;=;OK
+-p dccp -m dccp ! --sport 65535 --dport 1;=;OK
+# ERROR: should fail: iptables -A INPUT -p dccp -m dccp --sport 65536
+# -p dccp -m dccp --sport 65536;;FAIL
+-p dccp -m dccp --sport -1;;FAIL
+-p dccp -m dccp --dport -1;;FAIL
+-p dccp -m dccp --dccp-types REQUEST,RESPONSE,DATA,ACK,DATAACK,CLOSEREQ,CLOSE,RESET,SYNC,SYNCACK,INVALID;=;OK
+-p dccp -m dccp ! --dccp-types REQUEST,RESPONSE,DATA,ACK,DATAACK,CLOSEREQ,CLOSE,RESET,SYNC,SYNCACK,INVALID;=;OK
+# DCCP option 0 is valid, see http://tools.ietf.org/html/rfc4340#page-29
+# ERROR: cannot load: iptables -A INPUT -p dccp -m dccp --dccp-option 0
+#-p dccp -m dccp --dccp-option 0;=;OK
+-p dccp -m dccp --dccp-option 255;=;OK
+-p dccp -m dccp --dccp-option 256;;FAIL
+-p dccp -m dccp --dccp-option -1;;FAIL
+# should we accept this below?
+-p dccp -m dccp;=;OK
diff --git a/extensions/libxt_dccp.txlate b/extensions/libxt_dccp.txlate
new file mode 100644
index 0000000..ea853f6
--- /dev/null
+++ b/extensions/libxt_dccp.txlate
@@ -0,0 +1,20 @@
+iptables-translate -A INPUT -p dccp -m dccp --sport 100
+nft add rule ip filter INPUT dccp sport 100 counter
+
+iptables-translate -A INPUT -p dccp -m dccp --dport 100:200
+nft add rule ip filter INPUT dccp dport 100-200 counter
+
+iptables-translate -A INPUT -p dccp -m dccp ! --dport 100
+nft add rule ip filter INPUT dccp dport != 100 counter
+
+iptables-translate -A INPUT -p dccp -m dccp --dccp-types CLOSE
+nft add rule ip filter INPUT dccp type close counter
+
+iptables-translate -A INPUT -p dccp -m dccp --dccp-types INVALID
+nft add rule ip filter INPUT dccp type 10-15 counter
+
+iptables-translate -A INPUT -p dccp -m dccp --dport 100 --dccp-types REQUEST,RESPONSE,DATA,ACK,DATAACK,CLOSEREQ,CLOSE,SYNC,SYNCACK,INVALID
+nft add rule ip filter INPUT dccp dport 100 dccp type {request, response, data, ack, dataack, closereq, close, sync, syncack, 10-15} counter
+
+iptables-translate -A INPUT -p dccp -m dccp --sport 200 --dport 100
+nft add rule ip filter INPUT dccp sport 200 dccp dport 100 counter
diff --git a/extensions/libxt_devgroup.c b/extensions/libxt_devgroup.c
new file mode 100644
index 0000000..a88211c
--- /dev/null
+++ b/extensions/libxt_devgroup.c
@@ -0,0 +1,185 @@
+/* Shared library add-on to iptables to add devgroup matching support.
+ *
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_devgroup.h>
+
+static void devgroup_help(void)
+{
+	printf(
+"devgroup match options:\n"
+"[!] --src-group value[/mask]	Match device group of incoming device\n"
+"[!] --dst-group value[/mask]	Match device group of outgoing device\n"
+		);
+}
+
+enum {
+	O_SRC_GROUP = 0,
+	O_DST_GROUP,
+};
+
+static const struct xt_option_entry devgroup_opts[] = {
+	{.name = "src-group", .id = O_SRC_GROUP, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "dst-group", .id = O_DST_GROUP, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static const char f_devgroups[] = "/etc/iproute2/group";
+/* array of devgroups from f_devgroups[] */
+static struct xtables_lmap *devgroups;
+
+static void devgroup_parse(struct xt_option_call *cb)
+{
+	struct xt_devgroup_info *info = cb->data;
+	unsigned int group, mask;
+
+	xtables_option_parse(cb);
+	xtables_parse_val_mask(cb, &group, &mask, devgroups);
+
+	switch (cb->entry->id) {
+	case O_SRC_GROUP:
+		info->src_group = group;
+		info->src_mask  = mask;
+		info->flags |= XT_DEVGROUP_MATCH_SRC;
+		if (cb->invert)
+			info->flags |= XT_DEVGROUP_INVERT_SRC;
+		break;
+	case O_DST_GROUP:
+		info->dst_group = group;
+		info->dst_mask  = mask;
+		info->flags |= XT_DEVGROUP_MATCH_DST;
+		if (cb->invert)
+			info->flags |= XT_DEVGROUP_INVERT_DST;
+		break;
+	}
+}
+
+static void devgroup_show(const char *pfx, const struct xt_devgroup_info *info,
+			  int numeric)
+{
+	if (info->flags & XT_DEVGROUP_MATCH_SRC) {
+		if (info->flags & XT_DEVGROUP_INVERT_SRC)
+			printf(" !");
+		printf(" %ssrc-group", pfx);
+		xtables_print_val_mask(info->src_group, info->src_mask,
+				       numeric ? NULL : devgroups);
+	}
+
+	if (info->flags & XT_DEVGROUP_MATCH_DST) {
+		if (info->flags & XT_DEVGROUP_INVERT_DST)
+			printf(" !");
+		printf(" %sdst-group", pfx);
+		xtables_print_val_mask(info->dst_group, info->dst_mask,
+				       numeric ? NULL : devgroups);
+	}
+}
+
+static void devgroup_print(const void *ip, const struct xt_entry_match *match,
+                        int numeric)
+{
+	const struct xt_devgroup_info *info = (const void *)match->data;
+
+	devgroup_show("", info, numeric);
+}
+
+static void devgroup_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_devgroup_info *info = (const void *)match->data;
+
+	devgroup_show("--", info, 0);
+}
+
+static void devgroup_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM,
+			      "devgroup match: You must specify either "
+			      "'--src-group' or '--dst-group'");
+}
+
+static void
+print_devgroup_xlate(unsigned int id, uint32_t op,  unsigned int mask,
+		     struct xt_xlate *xl, int numeric)
+{
+	const char *name = NULL;
+
+	if (mask != 0xffffffff)
+		xt_xlate_add(xl, "and 0x%x %s 0x%x", mask,
+			   op == XT_OP_EQ ? "==" : "!=", id);
+	else {
+		if (numeric == 0)
+			name = xtables_lmap_id2name(devgroups, id);
+
+		xt_xlate_add(xl, "%s", op == XT_OP_EQ ? "" : "!= ");
+		if (name)
+			xt_xlate_add(xl, "%s", name);
+		else
+			xt_xlate_add(xl, "0x%x", id);
+	}
+}
+
+static void devgroup_show_xlate(const struct xt_devgroup_info *info,
+				struct xt_xlate *xl, int numeric)
+{
+	enum xt_op op = XT_OP_EQ;
+	char *space = "";
+
+	if (info->flags & XT_DEVGROUP_MATCH_SRC) {
+		if (info->flags & XT_DEVGROUP_INVERT_SRC)
+			op = XT_OP_NEQ;
+		xt_xlate_add(xl, "iifgroup ");
+		print_devgroup_xlate(info->src_group, op,
+				     info->src_mask, xl, numeric);
+		space = " ";
+	}
+
+	if (info->flags & XT_DEVGROUP_MATCH_DST) {
+		if (info->flags & XT_DEVGROUP_INVERT_DST)
+			op = XT_OP_NEQ;
+		xt_xlate_add(xl, "%soifgroup ", space);
+		print_devgroup_xlate(info->dst_group, op,
+				     info->dst_mask, xl, numeric);
+	}
+}
+
+static int devgroup_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_devgroup_info *info = (const void *)params->match->data;
+
+	devgroup_show_xlate(info, xl, 0);
+
+	return 1;
+}
+
+static struct xtables_match devgroup_mt_reg = {
+	.name		= "devgroup",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_UNSPEC,
+	.size		= XT_ALIGN(sizeof(struct xt_devgroup_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_devgroup_info)),
+	.help		= devgroup_help,
+	.print		= devgroup_print,
+	.save		= devgroup_save,
+	.x6_parse	= devgroup_parse,
+	.x6_fcheck	= devgroup_check,
+	.x6_options	= devgroup_opts,
+	.xlate		= devgroup_xlate,
+};
+
+void _init(void)
+{
+	devgroups = xtables_lmap_init(f_devgroups);
+	if (devgroups == NULL && errno != ENOENT)
+		fprintf(stderr, "Warning: %s: %s\n", f_devgroups,
+			strerror(errno));
+
+	xtables_register_match(&devgroup_mt_reg);
+}
diff --git a/extensions/libxt_devgroup.man b/extensions/libxt_devgroup.man
new file mode 100644
index 0000000..4a66c9f
--- /dev/null
+++ b/extensions/libxt_devgroup.man
@@ -0,0 +1,7 @@
+Match device group of a packets incoming/outgoing interface.
+.TP
+[\fB!\fP] \fB\-\-src\-group\fP \fIname\fP
+Match device group of incoming device
+.TP
+[\fB!\fP] \fB\-\-dst\-group\fP \fIname\fP
+Match device group of outgoing device
diff --git a/extensions/libxt_devgroup.txlate b/extensions/libxt_devgroup.txlate
new file mode 100644
index 0000000..aeb597b
--- /dev/null
+++ b/extensions/libxt_devgroup.txlate
@@ -0,0 +1,17 @@
+iptables-translate -A FORWARD -m devgroup --src-group 0x2 -j ACCEPT
+nft add rule ip filter FORWARD iifgroup 0x2 counter accept
+
+iptables-translate -A FORWARD -m devgroup --dst-group 0xc/0xc -j ACCEPT
+nft add rule ip filter FORWARD oifgroup and 0xc == 0xc counter accept
+
+iptables-translate -t mangle -A PREROUTING -p tcp --dport 46000 -m devgroup --src-group 23 -j ACCEPT
+nft add rule ip mangle PREROUTING tcp dport 46000 iifgroup 0x17 counter accept
+
+iptables-translate -A FORWARD -m devgroup ! --dst-group 0xc/0xc -j ACCEPT
+nft add rule ip filter FORWARD oifgroup and 0xc != 0xc counter accept
+
+iptables-translate -A FORWARD -m devgroup ! --src-group 0x2 -j ACCEPT
+nft add rule ip filter FORWARD iifgroup != 0x2 counter accept
+
+iptables-translate -A FORWARD -m devgroup ! --src-group 0x2 --dst-group 0xc/0xc -j ACCEPT
+nft add rule ip filter FORWARD iifgroup != 0x2 oifgroup and 0xc != 0xc counter accept
diff --git a/extensions/libxt_dscp.c b/extensions/libxt_dscp.c
new file mode 100644
index 0000000..d5c7323
--- /dev/null
+++ b/extensions/libxt_dscp.c
@@ -0,0 +1,156 @@
+/* Shared library add-on to iptables for DSCP
+ *
+ * (C) 2002 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This program is distributed under the terms of GNU GPL v2, 1991
+ *
+ * libipt_dscp.c borrowed heavily from libipt_tos.c
+ *
+ * --class support added by Iain Barnes
+ * 
+ * For a list of DSCP codepoints see 
+ * http://www.iana.org/assignments/dscp-registry
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_dscp.h>
+
+/* This is evil, but it's my code - HW*/
+#include "dscp_helper.c"
+
+enum {
+	O_DSCP = 0,
+	O_DSCP_CLASS,
+	F_DSCP       = 1 << O_DSCP,
+	F_DSCP_CLASS = 1 << O_DSCP_CLASS,
+};
+
+static void dscp_help(void)
+{
+	printf(
+"dscp match options\n"
+"[!] --dscp value		Match DSCP codepoint with numerical value\n"
+"  		                This value can be in decimal (ex: 32)\n"
+"               		or in hex (ex: 0x20)\n"
+"[!] --dscp-class name		Match the DiffServ class. This value may\n"
+"				be any of the BE,EF, AFxx or CSx classes\n"
+"\n"
+"				These two options are mutually exclusive !\n");
+}
+
+static const struct xt_option_entry dscp_opts[] = {
+	{.name = "dscp", .id = O_DSCP, .excl = F_DSCP_CLASS,
+	 .type = XTTYPE_UINT8, .min = 0, .max = XT_DSCP_MAX,
+	 .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_dscp_info, dscp)},
+	{.name = "dscp-class", .id = O_DSCP_CLASS, .excl = F_DSCP,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void dscp_parse(struct xt_option_call *cb)
+{
+	struct xt_dscp_info *dinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_DSCP:
+		if (cb->invert)
+			dinfo->invert = 1;
+		break;
+	case O_DSCP_CLASS:
+		dinfo->dscp = class_to_dscp(cb->arg);
+		if (cb->invert)
+			dinfo->invert = 1;
+		break;
+	}
+}
+
+static void dscp_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM,
+		           "DSCP match: Parameter --dscp is required");
+}
+
+static void
+dscp_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_dscp_info *dinfo =
+		(const struct xt_dscp_info *)match->data;
+	printf(" DSCP match %s0x%02x", dinfo->invert ? "!" : "", dinfo->dscp);
+}
+
+static void dscp_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_dscp_info *dinfo =
+		(const struct xt_dscp_info *)match->data;
+
+	printf("%s --dscp 0x%02x", dinfo->invert ? " !" : "", dinfo->dscp);
+}
+
+static int __dscp_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_mt_params *params)
+{
+	const struct xt_dscp_info *dinfo =
+		(const struct xt_dscp_info *)params->match->data;
+
+	xt_xlate_add(xl, "dscp %s0x%02x", dinfo->invert ? "!= " : "",
+		     dinfo->dscp);
+
+	return 1;
+}
+
+static int dscp_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	xt_xlate_add(xl, "ip ");
+
+	return __dscp_xlate(xl, params);
+}
+
+static int dscp_xlate6(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	xt_xlate_add(xl, "ip6 ");
+
+	return __dscp_xlate(xl, params);
+}
+
+static struct xtables_match dscp_mt_reg[] = {
+	{
+		.family		= NFPROTO_IPV4,
+		.name           = "dscp",
+		.version        = XTABLES_VERSION,
+		.size           = XT_ALIGN(sizeof(struct xt_dscp_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_dscp_info)),
+		.help		= dscp_help,
+		.print		= dscp_print,
+		.save		= dscp_save,
+		.x6_parse	= dscp_parse,
+		.x6_fcheck	= dscp_check,
+		.x6_options	= dscp_opts,
+		.xlate		= dscp_xlate,
+	},
+	{
+		.family		= NFPROTO_IPV6,
+		.name           = "dscp",
+		.version        = XTABLES_VERSION,
+		.size           = XT_ALIGN(sizeof(struct xt_dscp_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_dscp_info)),
+		.help		= dscp_help,
+		.print		= dscp_print,
+		.save		= dscp_save,
+		.x6_parse	= dscp_parse,
+		.x6_fcheck	= dscp_check,
+		.x6_options	= dscp_opts,
+		.xlate		= dscp_xlate6,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(dscp_mt_reg, ARRAY_SIZE(dscp_mt_reg));
+}
diff --git a/extensions/libxt_dscp.man b/extensions/libxt_dscp.man
new file mode 100644
index 0000000..63a17da
--- /dev/null
+++ b/extensions/libxt_dscp.man
@@ -0,0 +1,10 @@
+This module matches the 6 bit DSCP field within the TOS field in the
+IP header.  DSCP has superseded TOS within the IETF.
+.TP
+[\fB!\fP] \fB\-\-dscp\fP \fIvalue\fP
+Match against a numeric (decimal or hex) value [0-63].
+.TP
+[\fB!\fP] \fB\-\-dscp\-class\fP \fIclass\fP
+Match the DiffServ class. This value may be any of the
+BE, EF, AFxx or CSx classes.  It will then be converted
+into its according numeric value.
diff --git a/extensions/libxt_dscp.t b/extensions/libxt_dscp.t
new file mode 100644
index 0000000..38d7f04
--- /dev/null
+++ b/extensions/libxt_dscp.t
@@ -0,0 +1,10 @@
+:INPUT,FORWARD,OUTPUT
+-m dscp --dscp 0;=;OK
+-m dscp --dscp 0x3f;=;OK
+-m dscp --dscp -1;;FAIL
+-m dscp --dscp 0x40;;FAIL
+-m dscp --dscp 0x3f --dscp-class CS0;;FAIL
+-m dscp --dscp-class CS0;-m dscp --dscp 0x00;OK
+-m dscp --dscp-class BE;-m dscp --dscp 0x00;OK
+-m dscp --dscp-class EF;-m dscp --dscp 0x2e;OK
+-m dscp;;FAIL
diff --git a/extensions/libxt_dscp.txlate b/extensions/libxt_dscp.txlate
new file mode 100644
index 0000000..2cccc3b
--- /dev/null
+++ b/extensions/libxt_dscp.txlate
@@ -0,0 +1,5 @@
+iptables-translate -t filter -A INPUT -m dscp --dscp 0x32 -j ACCEPT
+nft add rule ip filter INPUT ip dscp 0x32 counter accept
+
+ip6tables-translate -t filter -A INPUT -m dscp ! --dscp 0x32 -j ACCEPT
+nft add rule ip6 filter INPUT ip6 dscp != 0x32 counter accept
diff --git a/extensions/libxt_ecn.c b/extensions/libxt_ecn.c
new file mode 100644
index 0000000..ad3c7a0
--- /dev/null
+++ b/extensions/libxt_ecn.c
@@ -0,0 +1,182 @@
+/* Shared library add-on to iptables for ECN matching
+ *
+ * (C) 2002 by Harald Welte <laforge@netfilter.org>
+ * (C) 2011 by Patrick McHardy <kaber@trash.net>
+ *
+ * This program is distributed under the terms of GNU GPL v2, 1991
+ *
+ * libipt_ecn.c borrowed heavily from libipt_dscp.c
+ *
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_ecn.h>
+
+enum {
+	O_ECN_TCP_CWR = 0,
+	O_ECN_TCP_ECE,
+	O_ECN_IP_ECT,
+};
+
+static void ecn_help(void)
+{
+	printf(
+"ECN match options\n"
+"[!] --ecn-tcp-cwr 		Match CWR bit of TCP header\n"
+"[!] --ecn-tcp-ece		Match ECE bit of TCP header\n"
+"[!] --ecn-ip-ect [0..3]	Match ECN codepoint in IPv4/IPv6 header\n");
+}
+
+static const struct xt_option_entry ecn_opts[] = {
+	{.name = "ecn-tcp-cwr", .id = O_ECN_TCP_CWR, .type = XTTYPE_NONE,
+	 .flags = XTOPT_INVERT},
+	{.name = "ecn-tcp-ece", .id = O_ECN_TCP_ECE, .type = XTTYPE_NONE,
+	 .flags = XTOPT_INVERT},
+	{.name = "ecn-ip-ect", .id = O_ECN_IP_ECT, .type = XTTYPE_UINT8,
+	 .min = 0, .max = 3, .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void ecn_parse(struct xt_option_call *cb)
+{
+	struct xt_ecn_info *einfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_ECN_TCP_CWR:
+		einfo->operation |= XT_ECN_OP_MATCH_CWR;
+		if (cb->invert)
+			einfo->invert |= XT_ECN_OP_MATCH_CWR;
+		break;
+	case O_ECN_TCP_ECE:
+		einfo->operation |= XT_ECN_OP_MATCH_ECE;
+		if (cb->invert)
+			einfo->invert |= XT_ECN_OP_MATCH_ECE;
+		break;
+	case O_ECN_IP_ECT:
+		if (cb->invert)
+			einfo->invert |= XT_ECN_OP_MATCH_IP;
+		einfo->operation |= XT_ECN_OP_MATCH_IP;
+		einfo->ip_ect = cb->val.u8;
+		break;
+	}
+}
+
+static void ecn_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM,
+		           "ECN match: some option required");
+}
+
+static void ecn_print(const void *ip, const struct xt_entry_match *match,
+                      int numeric)
+{
+	const struct xt_ecn_info *einfo =
+		(const struct xt_ecn_info *)match->data;
+
+	printf(" ECN match");
+
+	if (einfo->operation & XT_ECN_OP_MATCH_ECE) {
+		printf(" %sECE",
+		       (einfo->invert & XT_ECN_OP_MATCH_ECE) ? "!" : "");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_CWR) {
+		printf(" %sCWR",
+		       (einfo->invert & XT_ECN_OP_MATCH_CWR) ? "!" : "");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_IP) {
+		printf(" %sECT=%d",
+		       (einfo->invert & XT_ECN_OP_MATCH_IP) ? "!" : "",
+		       einfo->ip_ect);
+	}
+}
+
+static void ecn_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_ecn_info *einfo =
+		(const struct xt_ecn_info *)match->data;
+
+	if (einfo->operation & XT_ECN_OP_MATCH_ECE) {
+		if (einfo->invert & XT_ECN_OP_MATCH_ECE)
+			printf(" !");
+		printf(" --ecn-tcp-ece");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_CWR) {
+		if (einfo->invert & XT_ECN_OP_MATCH_CWR)
+			printf(" !");
+		printf(" --ecn-tcp-cwr");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_IP) {
+		if (einfo->invert & XT_ECN_OP_MATCH_IP)
+			printf(" !");
+		printf(" --ecn-ip-ect %d", einfo->ip_ect);
+	}
+}
+
+static int ecn_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_ecn_info *einfo =
+		(const struct xt_ecn_info *)params->match->data;
+
+	if (einfo->operation & XT_ECN_OP_MATCH_ECE) {
+		xt_xlate_add(xl, "tcp flags ");
+		if (einfo->invert)
+			xt_xlate_add(xl,"!= ");
+		xt_xlate_add(xl, "ecn");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_CWR) {
+		xt_xlate_add(xl, "tcp flags ");
+		if (einfo->invert)
+			xt_xlate_add(xl,"!= ");
+		xt_xlate_add(xl, "cwr");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_IP) {
+		xt_xlate_add(xl, "ip ecn ");
+		if (einfo->invert)
+			xt_xlate_add(xl,"!= ");
+
+		switch (einfo->ip_ect) {
+		case 0:
+			xt_xlate_add(xl, "not-ect");
+			break;
+		case 1:
+			xt_xlate_add(xl, "ect1");
+			break;
+		case 2:
+			xt_xlate_add(xl, "ect0");
+			break;
+		case 3:
+			xt_xlate_add(xl, "ce");
+			break;
+		}
+	}
+	return 1;
+}
+
+static struct xtables_match ecn_mt_reg = {
+	.name          = "ecn",
+	.version       = XTABLES_VERSION,
+	.family        = NFPROTO_UNSPEC,
+	.size          = XT_ALIGN(sizeof(struct xt_ecn_info)),
+	.userspacesize = XT_ALIGN(sizeof(struct xt_ecn_info)),
+	.help          = ecn_help,
+	.print         = ecn_print,
+	.save          = ecn_save,
+	.x6_parse      = ecn_parse,
+	.x6_fcheck     = ecn_check,
+	.x6_options    = ecn_opts,
+	.xlate	       = ecn_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&ecn_mt_reg);
+}
diff --git a/extensions/libxt_ecn.man b/extensions/libxt_ecn.man
new file mode 100644
index 0000000..31c0a3e
--- /dev/null
+++ b/extensions/libxt_ecn.man
@@ -0,0 +1,11 @@
+This allows you to match the ECN bits of the IPv4/IPv6 and TCP header.  ECN is the Explicit Congestion Notification mechanism as specified in RFC3168
+.TP
+[\fB!\fP] \fB\-\-ecn\-tcp\-cwr\fP
+This matches if the TCP ECN CWR (Congestion Window Received) bit is set.
+.TP
+[\fB!\fP] \fB\-\-ecn\-tcp\-ece\fP
+This matches if the TCP ECN ECE (ECN Echo) bit is set.
+.TP
+[\fB!\fP] \fB\-\-ecn\-ip\-ect\fP \fInum\fP
+This matches a particular IPv4/IPv6 ECT (ECN-Capable Transport). You have to specify
+a number between `0' and `3'.
diff --git a/extensions/libxt_ecn.t b/extensions/libxt_ecn.t
new file mode 100644
index 0000000..b32aea3
--- /dev/null
+++ b/extensions/libxt_ecn.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-m ecn --ecn-tcp-cwr;;FAIL
+-p tcp -m ecn --ecn-tcp-cwr;=;OK
+-p tcp -m ecn --ecn-tcp-ece --ecn-tcp-cwr --ecn-ip-ect 2;=;OK
+-p tcp -m ecn ! --ecn-tcp-ece ! --ecn-tcp-cwr ! --ecn-ip-ect 2;=;OK
diff --git a/extensions/libxt_ecn.txlate b/extensions/libxt_ecn.txlate
new file mode 100644
index 0000000..f012f12
--- /dev/null
+++ b/extensions/libxt_ecn.txlate
@@ -0,0 +1,29 @@
+iptables-translate -A INPUT -m ecn --ecn-ip-ect 0
+nft add rule ip filter INPUT ip ecn not-ect counter
+
+iptables-translate -A INPUT -m ecn --ecn-ip-ect 1
+nft add rule ip filter INPUT ip ecn ect1 counter
+
+iptables-translate -A INPUT -m ecn --ecn-ip-ect 2
+nft add rule ip filter INPUT ip ecn ect0 counter
+
+iptables-translate -A INPUT -m ecn --ecn-ip-ect 3
+nft add rule ip filter INPUT ip ecn ce counter
+
+iptables-translate -A INPUT -m ecn ! --ecn-ip-ect 0
+nft add rule ip filter INPUT ip ecn != not-ect counter
+
+iptables-translate -A INPUT -m ecn ! --ecn-ip-ect 1
+nft add rule ip filter INPUT ip ecn != ect1 counter
+
+iptables-translate -A INPUT -m ecn ! --ecn-ip-ect 2
+nft add rule ip filter INPUT ip ecn != ect0 counter
+
+iptables-translate -A INPUT -m ecn ! --ecn-ip-ect 3
+nft add rule ip filter INPUT ip ecn != ce counter
+
+iptables-translate -A INPUT -m ecn ! --ecn-tcp-ece
+nft add rule ip filter INPUT tcp flags != ecn counter
+
+iptables-translate -A INPUT -m ecn --ecn-tcp-cwr
+nft add rule ip filter INPUT tcp flags cwr counter
diff --git a/extensions/libxt_esp.c b/extensions/libxt_esp.c
new file mode 100644
index 0000000..2c7ff94
--- /dev/null
+++ b/extensions/libxt_esp.c
@@ -0,0 +1,126 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_esp.h>
+
+enum {
+	O_ESPSPI = 0,
+};
+
+static void esp_help(void)
+{
+	printf(
+"esp match options:\n"
+"[!] --espspi spi[:spi]\n"
+"				match spi (range)\n");
+}
+
+static const struct xt_option_entry esp_opts[] = {
+	{.name = "espspi", .id = O_ESPSPI, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_esp, spis)},
+	XTOPT_TABLEEND,
+};
+
+static void esp_init(struct xt_entry_match *m)
+{
+	struct xt_esp *espinfo = (void *)m->data;
+
+	espinfo->spis[1] = ~0U;
+}
+
+static void esp_parse(struct xt_option_call *cb)
+{
+	struct xt_esp *espinfo = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->nvals == 1)
+		espinfo->spis[1] = espinfo->spis[0];
+	if (cb->invert)
+		espinfo->invflags |= XT_ESP_INV_SPI;
+}
+
+static void
+print_spis(const char *name, uint32_t min, uint32_t max,
+	    int invert)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFFFFFF || invert) {
+		if (min == max)
+			printf(" %s:%s%u", name, inv, min);
+		else
+			printf(" %ss:%s%u:%u", name, inv, min, max);
+	}
+}
+
+static void
+esp_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_esp *esp = (struct xt_esp *)match->data;
+
+	printf(" esp");
+	print_spis("spi", esp->spis[0], esp->spis[1],
+		    esp->invflags & XT_ESP_INV_SPI);
+	if (esp->invflags & ~XT_ESP_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       esp->invflags & ~XT_ESP_INV_MASK);
+}
+
+static void esp_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_esp *espinfo = (struct xt_esp *)match->data;
+
+	if (!(espinfo->spis[0] == 0
+	    && espinfo->spis[1] == 0xFFFFFFFF)) {
+		printf("%s --espspi ",
+			(espinfo->invflags & XT_ESP_INV_SPI) ? " !" : "");
+		if (espinfo->spis[0]
+		    != espinfo->spis[1])
+			printf("%u:%u",
+			       espinfo->spis[0],
+			       espinfo->spis[1]);
+		else
+			printf("%u",
+			       espinfo->spis[0]);
+	}
+
+}
+
+static int esp_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_esp *espinfo = (struct xt_esp *)params->match->data;
+
+	if (!(espinfo->spis[0] == 0 && espinfo->spis[1] == 0xFFFFFFFF)) {
+		xt_xlate_add(xl, "esp spi%s",
+			   (espinfo->invflags & XT_ESP_INV_SPI) ? " !=" : "");
+		if (espinfo->spis[0] != espinfo->spis[1])
+			xt_xlate_add(xl, " %u-%u", espinfo->spis[0],
+				   espinfo->spis[1]);
+		else
+			xt_xlate_add(xl, " %u", espinfo->spis[0]);
+	}
+
+	return 1;
+}
+
+static struct xtables_match esp_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "esp",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_esp)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_esp)),
+	.help		= esp_help,
+	.init		= esp_init,
+	.print		= esp_print,
+	.save		= esp_save,
+	.x6_parse	= esp_parse,
+	.x6_options	= esp_opts,
+	.xlate		= esp_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&esp_match);
+}
diff --git a/extensions/libxt_esp.man b/extensions/libxt_esp.man
new file mode 100644
index 0000000..699a41c
--- /dev/null
+++ b/extensions/libxt_esp.man
@@ -0,0 +1,3 @@
+This module matches the SPIs in ESP header of IPsec packets.
+.TP
+[\fB!\fP] \fB\-\-espspi\fP \fIspi\fP[\fB:\fP\fIspi\fP]
diff --git a/extensions/libxt_esp.t b/extensions/libxt_esp.t
new file mode 100644
index 0000000..92c5779
--- /dev/null
+++ b/extensions/libxt_esp.t
@@ -0,0 +1,8 @@
+:INPUT,FORWARD,OUTPUT
+-p esp -m esp --espspi 0;=;OK
+-p esp -m esp --espspi :32;-p esp -m esp --espspi 0:32;OK
+-p esp -m esp --espspi 0:4294967295;-p esp -m esp;OK
+-p esp -m esp ! --espspi 0:4294967294;=;OK
+-p esp -m esp --espspi -1;;FAIL
+-p esp -m esp;=;OK
+-m esp;;FAIL
diff --git a/extensions/libxt_esp.txlate b/extensions/libxt_esp.txlate
new file mode 100644
index 0000000..5e2f18f
--- /dev/null
+++ b/extensions/libxt_esp.txlate
@@ -0,0 +1,11 @@
+iptables-translate -A FORWARD -p esp -j ACCEPT
+nft add rule ip filter FORWARD ip protocol esp counter accept
+
+iptables-translate -A INPUT  --in-interface  wan --protocol esp -j ACCEPT
+nft add rule ip filter INPUT iifname "wan" ip protocol esp counter accept
+
+iptables-translate -A INPUT -p 50 -m esp --espspi 500 -j DROP
+nft add rule ip filter INPUT esp spi 500 counter drop
+
+iptables-translate -A INPUT -p 50 -m esp --espspi 500:600 -j DROP
+nft add rule ip filter INPUT esp spi 500-600 counter drop
diff --git a/extensions/libxt_hashlimit.c b/extensions/libxt_hashlimit.c
new file mode 100644
index 0000000..7f1d2a4
--- /dev/null
+++ b/extensions/libxt_hashlimit.c
@@ -0,0 +1,1576 @@
+/* ip6tables match extension for limiting packets per destination
+ *
+ * (C) 2003-2004 by Harald Welte <laforge@netfilter.org>
+ *
+ * Development of this code was funded by Astaro AG, http://www.astaro.com/
+ *
+ * Based on ipt_limit.c by
+ * Jérôme de Vivie   <devivie@info.enserb.u-bordeaux.fr>
+ * Hervé Eychenne    <rv@wallfire.org>
+ *
+ * Error corections by nmalykh@bilim.com (22.01.2005)
+ */
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#define _ISOC99_SOURCE 1
+#include <inttypes.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <xtables.h>
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/xt_hashlimit.h>
+
+#define XT_HASHLIMIT_BURST	5
+#define XT_HASHLIMIT_BURST_MAX_v1	10000
+#define XT_HASHLIMIT_BURST_MAX		1000000
+
+#define XT_HASHLIMIT_BYTE_EXPIRE	15
+#define XT_HASHLIMIT_BYTE_EXPIRE_BURST	60
+
+/* miliseconds */
+#define XT_HASHLIMIT_GCINTERVAL	1000
+
+struct hashlimit_mt_udata {
+	uint32_t mult;
+};
+
+static void hashlimit_help(void)
+{
+	printf(
+"hashlimit match options:\n"
+"--hashlimit <avg>		max average match rate\n"
+"                                [Packets per second unless followed by \n"
+"                                /sec /minute /hour /day postfixes]\n"
+"--hashlimit-mode <mode>		mode is a comma-separated list of\n"
+"					dstip,srcip,dstport,srcport\n"
+"--hashlimit-name <name>		name for /proc/net/ipt_hashlimit/\n"
+"[--hashlimit-burst <num>]	number to match in a burst, default %u\n"
+"[--hashlimit-htable-size <num>]	number of hashtable buckets\n"
+"[--hashlimit-htable-max <num>]	number of hashtable entries\n"
+"[--hashlimit-htable-gcinterval]	interval between garbage collection runs\n"
+"[--hashlimit-htable-expire]	after which time are idle entries expired?\n",
+XT_HASHLIMIT_BURST);
+}
+
+enum {
+	O_UPTO = 0,
+	O_ABOVE,
+	O_LIMIT,
+	O_MODE,
+	O_SRCMASK,
+	O_DSTMASK,
+	O_NAME,
+	O_BURST,
+	O_HTABLE_SIZE,
+	O_HTABLE_MAX,
+	O_HTABLE_GCINT,
+	O_HTABLE_EXPIRE,
+	O_RATEMATCH,
+	O_INTERVAL,
+	F_BURST         = 1 << O_BURST,
+	F_UPTO          = 1 << O_UPTO,
+	F_ABOVE         = 1 << O_ABOVE,
+	F_HTABLE_EXPIRE = 1 << O_HTABLE_EXPIRE,
+	F_RATEMATCH	= 1 << O_RATEMATCH,
+};
+
+static void hashlimit_mt_help(void)
+{
+	printf(
+"hashlimit match options:\n"
+"  --hashlimit-upto <avg>           max average match rate\n"
+"                                   [Packets per second unless followed by \n"
+"                                   /sec /minute /hour /day postfixes]\n"
+"  --hashlimit-above <avg>          min average match rate\n"
+"  --hashlimit-mode <mode>          mode is a comma-separated list of\n"
+"                                   dstip,srcip,dstport,srcport (or none)\n"
+"  --hashlimit-srcmask <length>     source address grouping prefix length\n"
+"  --hashlimit-dstmask <length>     destination address grouping prefix length\n"
+"  --hashlimit-name <name>          name for /proc/net/ipt_hashlimit\n"
+"  --hashlimit-burst <num>	    number to match in a burst, default %u\n"
+"  --hashlimit-htable-size <num>    number of hashtable buckets\n"
+"  --hashlimit-htable-max <num>     number of hashtable entries\n"
+"  --hashlimit-htable-gcinterval    interval between garbage collection runs\n"
+"  --hashlimit-htable-expire        after which time are idle entries expired?\n"
+"\n", XT_HASHLIMIT_BURST);
+}
+
+static void hashlimit_mt_help_v3(void)
+{
+	printf(
+"hashlimit match options:\n"
+"  --hashlimit-upto <avg>           max average match rate\n"
+"                                   [Packets per second unless followed by \n"
+"                                   /sec /minute /hour /day postfixes]\n"
+"  --hashlimit-above <avg>          min average match rate\n"
+"  --hashlimit-mode <mode>          mode is a comma-separated list of\n"
+"                                   dstip,srcip,dstport,srcport (or none)\n"
+"  --hashlimit-srcmask <length>     source address grouping prefix length\n"
+"  --hashlimit-dstmask <length>     destination address grouping prefix length\n"
+"  --hashlimit-name <name>          name for /proc/net/ipt_hashlimit\n"
+"  --hashlimit-burst <num>	    number to match in a burst, default %u\n"
+"  --hashlimit-htable-size <num>    number of hashtable buckets\n"
+"  --hashlimit-htable-max <num>     number of hashtable entries\n"
+"  --hashlimit-htable-gcinterval    interval between garbage collection runs\n"
+"  --hashlimit-htable-expire        after which time are idle entries expired?\n"
+"  --hashlimit-rate-match           rate match the flow without rate-limiting it\n"
+"  --hashlimit-rate-interval        interval in seconds for hashlimit-rate-match\n"
+"\n", XT_HASHLIMIT_BURST);
+}
+
+#define s struct xt_hashlimit_info
+static const struct xt_option_entry hashlimit_opts[] = {
+	{.name = "hashlimit", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING},
+	{.name = "hashlimit-burst", .id = O_BURST, .type = XTTYPE_UINT32,
+	 .min = 1, .max = XT_HASHLIMIT_BURST_MAX_v1, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.burst)},
+	{.name = "hashlimit-htable-size", .id = O_HTABLE_SIZE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.size)},
+	{.name = "hashlimit-htable-max", .id = O_HTABLE_MAX,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.max)},
+	{.name = "hashlimit-htable-gcinterval", .id = O_HTABLE_GCINT,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.gc_interval)},
+	{.name = "hashlimit-htable-expire", .id = O_HTABLE_EXPIRE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.expire)},
+	{.name = "hashlimit-mode", .id = O_MODE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND},
+	{.name = "hashlimit-name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, name), .min = 1},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_hashlimit_mtinfo1
+static const struct xt_option_entry hashlimit_mt_opts_v1[] = {
+	{.name = "hashlimit-upto", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	{.name = "hashlimit-above", .id = O_ABOVE, .excl = F_UPTO,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	{.name = "hashlimit", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT}, /* old name */
+	{.name = "hashlimit-srcmask", .id = O_SRCMASK, .type = XTTYPE_PLEN},
+	{.name = "hashlimit-dstmask", .id = O_DSTMASK, .type = XTTYPE_PLEN},
+	{.name = "hashlimit-burst", .id = O_BURST, .type = XTTYPE_STRING},
+	{.name = "hashlimit-htable-size", .id = O_HTABLE_SIZE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.size)},
+	{.name = "hashlimit-htable-max", .id = O_HTABLE_MAX,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.max)},
+	{.name = "hashlimit-htable-gcinterval", .id = O_HTABLE_GCINT,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.gc_interval)},
+	{.name = "hashlimit-htable-expire", .id = O_HTABLE_EXPIRE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.expire)},
+	{.name = "hashlimit-mode", .id = O_MODE, .type = XTTYPE_STRING},
+	{.name = "hashlimit-name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, name), .min = 1},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_hashlimit_mtinfo2
+static const struct xt_option_entry hashlimit_mt_opts_v2[] = {
+	{.name = "hashlimit-upto", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	{.name = "hashlimit-above", .id = O_ABOVE, .excl = F_UPTO,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	{.name = "hashlimit", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT}, /* old name */
+	{.name = "hashlimit-srcmask", .id = O_SRCMASK, .type = XTTYPE_PLEN},
+	{.name = "hashlimit-dstmask", .id = O_DSTMASK, .type = XTTYPE_PLEN},
+	{.name = "hashlimit-burst", .id = O_BURST, .type = XTTYPE_STRING},
+	{.name = "hashlimit-htable-size", .id = O_HTABLE_SIZE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.size)},
+	{.name = "hashlimit-htable-max", .id = O_HTABLE_MAX,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.max)},
+	{.name = "hashlimit-htable-gcinterval", .id = O_HTABLE_GCINT,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.gc_interval)},
+	{.name = "hashlimit-htable-expire", .id = O_HTABLE_EXPIRE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.expire)},
+	{.name = "hashlimit-mode", .id = O_MODE, .type = XTTYPE_STRING},
+	{.name = "hashlimit-name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, name), .min = 1},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_hashlimit_mtinfo3
+static const struct xt_option_entry hashlimit_mt_opts[] = {
+	{.name = "hashlimit-upto", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	{.name = "hashlimit-above", .id = O_ABOVE, .excl = F_UPTO,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	{.name = "hashlimit", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT}, /* old name */
+	{.name = "hashlimit-srcmask", .id = O_SRCMASK, .type = XTTYPE_PLEN},
+	{.name = "hashlimit-dstmask", .id = O_DSTMASK, .type = XTTYPE_PLEN},
+	{.name = "hashlimit-burst", .id = O_BURST, .type = XTTYPE_STRING},
+	{.name = "hashlimit-htable-size", .id = O_HTABLE_SIZE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.size)},
+	{.name = "hashlimit-htable-max", .id = O_HTABLE_MAX,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.max)},
+	{.name = "hashlimit-htable-gcinterval", .id = O_HTABLE_GCINT,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.gc_interval)},
+	{.name = "hashlimit-htable-expire", .id = O_HTABLE_EXPIRE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.expire)},
+	{.name = "hashlimit-mode", .id = O_MODE, .type = XTTYPE_STRING},
+	{.name = "hashlimit-name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, name), .min = 1},
+	{.name = "hashlimit-rate-match", .id = O_RATEMATCH, .type = XTTYPE_NONE},
+	{.name = "hashlimit-rate-interval", .id = O_INTERVAL, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static int
+cfg_copy(struct hashlimit_cfg3 *to, const void *from, int revision)
+{
+	if (revision == 1) {
+		struct hashlimit_cfg1 *cfg = (struct hashlimit_cfg1 *)from;
+
+		to->mode = cfg->mode;
+		to->avg = cfg->avg;
+		to->burst = cfg->burst;
+		to->size = cfg->size;
+		to->max = cfg->max;
+		to->gc_interval = cfg->gc_interval;
+		to->expire = cfg->expire;
+		to->srcmask = cfg->srcmask;
+		to->dstmask = cfg->dstmask;
+	} else if (revision == 2) {
+		struct hashlimit_cfg2 *cfg = (struct hashlimit_cfg2 *)from;
+
+		to->mode = cfg->mode;
+		to->avg = cfg->avg;
+		to->burst = cfg->burst;
+		to->size = cfg->size;
+		to->max = cfg->max;
+		to->gc_interval = cfg->gc_interval;
+		to->expire = cfg->expire;
+		to->srcmask = cfg->srcmask;
+		to->dstmask = cfg->dstmask;
+	} else if (revision == 3) {
+		memcpy(to, from, sizeof(struct hashlimit_cfg3));
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static uint64_t cost_to_bytes(uint64_t cost)
+{
+	uint64_t r;
+
+	r = cost ? UINT32_MAX / cost : UINT32_MAX;
+	r = (r - 1) << XT_HASHLIMIT_BYTE_SHIFT;
+	return r;
+}
+
+static uint64_t bytes_to_cost(uint64_t bytes)
+{
+	uint32_t r = bytes >> XT_HASHLIMIT_BYTE_SHIFT;
+	return UINT32_MAX / (r+1);
+}
+
+static uint32_t get_factor(int chr)
+{
+	switch (chr) {
+	case 'm': return 1024 * 1024;
+	case 'k': return 1024;
+	}
+	return 1;
+}
+
+static void burst_error_v1(void)
+{
+	xtables_error(PARAMETER_PROBLEM, "bad value for option "
+			"\"--hashlimit-burst\", or out of range (1-%u).", XT_HASHLIMIT_BURST_MAX_v1);
+}
+
+static void burst_error(void)
+{
+	xtables_error(PARAMETER_PROBLEM, "bad value for option "
+			"\"--hashlimit-burst\", or out of range (1-%u).", XT_HASHLIMIT_BURST_MAX);
+}
+
+static uint64_t parse_burst(const char *burst, int revision)
+{
+	uintmax_t v;
+	char *end;
+	uint64_t max = (revision == 1) ? UINT32_MAX : UINT64_MAX;
+	uint64_t burst_max = (revision == 1) ?
+			      XT_HASHLIMIT_BURST_MAX_v1 : XT_HASHLIMIT_BURST_MAX;
+
+	if (!xtables_strtoul(burst, &end, &v, 1, max) ||
+		(*end == 0 && v > burst_max)) {
+		if (revision == 1)
+			burst_error_v1();
+		else
+			burst_error();
+	}
+
+	v *= get_factor(*end);
+	if (v > max)
+		xtables_error(PARAMETER_PROBLEM, "bad value for option "
+			"\"--hashlimit-burst\", value \"%s\" too large "
+				"(max %"PRIu64"mb).", burst, max/1024/1024);
+	return v;
+}
+
+static bool parse_bytes(const char *rate, void *val, struct hashlimit_mt_udata *ud, int revision)
+{
+	unsigned int factor = 1;
+	uint64_t tmp, r;
+	const char *mode = strstr(rate, "b/s");
+	uint64_t max = (revision == 1) ? UINT32_MAX : UINT64_MAX;
+
+	if (!mode || mode == rate)
+		return false;
+
+	mode--;
+	r = atoll(rate);
+	if (r == 0)
+		return false;
+
+	factor = get_factor(*mode);
+	tmp = (uint64_t) r * factor;
+	if (tmp > max)
+		xtables_error(PARAMETER_PROBLEM,
+			"Rate value too large \"%"PRIu64"\" (max %"PRIu64")\n",
+					tmp, max);
+
+	tmp = bytes_to_cost(tmp);
+	if (tmp == 0)
+		xtables_error(PARAMETER_PROBLEM, "Rate too high \"%s\"\n", rate);
+
+	ud->mult = XT_HASHLIMIT_BYTE_EXPIRE;
+
+	if(revision == 1)
+		*((uint32_t*)val) = tmp;
+	else
+		*((uint64_t*)val) = tmp;
+
+	return true;
+}
+
+static
+int parse_rate(const char *rate, void *val, struct hashlimit_mt_udata *ud, int revision)
+{
+	const char *delim;
+	uint64_t tmp, r;
+	uint64_t scale = (revision == 1) ? XT_HASHLIMIT_SCALE : XT_HASHLIMIT_SCALE_v2;
+
+	ud->mult = 1;  /* Seconds by default. */
+	delim = strchr(rate, '/');
+	if (delim) {
+		if (strlen(delim+1) == 0)
+			return 0;
+
+		if (strncasecmp(delim+1, "second", strlen(delim+1)) == 0)
+			ud->mult = 1;
+		else if (strncasecmp(delim+1, "minute", strlen(delim+1)) == 0)
+			ud->mult = 60;
+		else if (strncasecmp(delim+1, "hour", strlen(delim+1)) == 0)
+			ud->mult = 60*60;
+		else if (strncasecmp(delim+1, "day", strlen(delim+1)) == 0)
+			ud->mult = 24*60*60;
+		else
+			return 0;
+	}
+	r = atoll(rate);
+	if (!r)
+		return 0;
+
+	tmp = scale * ud->mult / r;
+	if (tmp == 0)
+		/*
+		 * The rate maps to infinity. (1/day is the minimum they can
+		 * specify, so we are ok at that end).
+		 */
+		xtables_error(PARAMETER_PROBLEM, "Rate too fast \"%s\"\n", rate);
+
+	if(revision == 1)
+		*((uint32_t*)val) = tmp;
+	else
+		*((uint64_t*)val) = tmp;
+
+	return 1;
+}
+
+static int parse_interval(const char *rate, uint32_t *val)
+{
+	int r = atoi(rate);
+	if (r <= 0)
+		return 0;
+
+	*val = r;
+	return 1;
+}
+
+static void hashlimit_init(struct xt_entry_match *m)
+{
+	struct xt_hashlimit_info *r = (struct xt_hashlimit_info *)m->data;
+
+	r->cfg.burst = XT_HASHLIMIT_BURST;
+	r->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+
+}
+
+static void hashlimit_mt4_init_v1(struct xt_entry_match *match)
+{
+	struct xt_hashlimit_mtinfo1 *info = (void *)match->data;
+
+	info->cfg.mode        = 0;
+	info->cfg.burst       = XT_HASHLIMIT_BURST;
+	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+	info->cfg.srcmask     = 32;
+	info->cfg.dstmask     = 32;
+}
+
+static void hashlimit_mt6_init_v1(struct xt_entry_match *match)
+{
+	struct xt_hashlimit_mtinfo1 *info = (void *)match->data;
+
+	info->cfg.mode        = 0;
+	info->cfg.burst       = XT_HASHLIMIT_BURST;
+	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+	info->cfg.srcmask     = 128;
+	info->cfg.dstmask     = 128;
+}
+
+static void hashlimit_mt4_init_v2(struct xt_entry_match *match)
+{
+	struct xt_hashlimit_mtinfo2 *info = (void *)match->data;
+
+	info->cfg.mode        = 0;
+	info->cfg.burst       = XT_HASHLIMIT_BURST;
+	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+	info->cfg.srcmask     = 32;
+	info->cfg.dstmask     = 32;
+}
+
+static void hashlimit_mt6_init_v2(struct xt_entry_match *match)
+{
+	struct xt_hashlimit_mtinfo2 *info = (void *)match->data;
+
+	info->cfg.mode        = 0;
+	info->cfg.burst       = XT_HASHLIMIT_BURST;
+	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+	info->cfg.srcmask     = 128;
+	info->cfg.dstmask     = 128;
+}
+
+static void hashlimit_mt4_init(struct xt_entry_match *match)
+{
+	struct xt_hashlimit_mtinfo3 *info = (void *)match->data;
+
+	info->cfg.mode        = 0;
+	info->cfg.burst       = XT_HASHLIMIT_BURST;
+	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+	info->cfg.srcmask     = 32;
+	info->cfg.dstmask     = 32;
+	info->cfg.interval    = 0;
+}
+
+static void hashlimit_mt6_init(struct xt_entry_match *match)
+{
+	struct xt_hashlimit_mtinfo3 *info = (void *)match->data;
+
+	info->cfg.mode        = 0;
+	info->cfg.burst       = XT_HASHLIMIT_BURST;
+	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+	info->cfg.srcmask     = 128;
+	info->cfg.dstmask     = 128;
+	info->cfg.interval    = 0;
+}
+
+/* Parse a 'mode' parameter into the required bitmask */
+static int parse_mode(uint32_t *mode, const char *option_arg)
+{
+	char *tok;
+	char *arg = strdup(option_arg);
+
+	if (!arg)
+		return -1;
+
+	for (tok = strtok(arg, ",|");
+	     tok;
+	     tok = strtok(NULL, ",|")) {
+		if (!strcmp(tok, "dstip"))
+			*mode |= XT_HASHLIMIT_HASH_DIP;
+		else if (!strcmp(tok, "srcip"))
+			*mode |= XT_HASHLIMIT_HASH_SIP;
+		else if (!strcmp(tok, "srcport"))
+			*mode |= XT_HASHLIMIT_HASH_SPT;
+		else if (!strcmp(tok, "dstport"))
+			*mode |= XT_HASHLIMIT_HASH_DPT;
+		else {
+			free(arg);
+			return -1;
+		}
+	}
+	free(arg);
+	return 0;
+}
+
+static void hashlimit_parse(struct xt_option_call *cb)
+{
+	struct xt_hashlimit_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_UPTO:
+		if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 1))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-upto", cb->arg);
+		break;
+	case O_MODE:
+		if (parse_mode(&info->cfg.mode, cb->arg) < 0)
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-mode", cb->arg);
+		break;
+	}
+}
+
+static void hashlimit_mt_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_hashlimit_mtinfo1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_BURST:
+		info->cfg.burst = parse_burst(cb->arg, 1);
+		break;
+	case O_UPTO:
+		if (cb->invert)
+			info->cfg.mode |= XT_HASHLIMIT_INVERT;
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 1))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 1))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-upto", cb->arg);
+		break;
+	case O_ABOVE:
+		if (!cb->invert)
+			info->cfg.mode |= XT_HASHLIMIT_INVERT;
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 1))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 1))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-above", cb->arg);
+		break;
+	case O_MODE:
+		if (parse_mode(&info->cfg.mode, cb->arg) < 0)
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-mode", cb->arg);
+		break;
+	case O_SRCMASK:
+		info->cfg.srcmask = cb->val.hlen;
+		break;
+	case O_DSTMASK:
+		info->cfg.dstmask = cb->val.hlen;
+		break;
+	}
+}
+
+static void hashlimit_mt_parse_v2(struct xt_option_call *cb)
+{
+	struct xt_hashlimit_mtinfo2 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_BURST:
+		info->cfg.burst = parse_burst(cb->arg, 2);
+		break;
+	case O_UPTO:
+		if (cb->invert)
+			info->cfg.mode |= XT_HASHLIMIT_INVERT;
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 2))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 2))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-upto", cb->arg);
+		break;
+	case O_ABOVE:
+		if (!cb->invert)
+			info->cfg.mode |= XT_HASHLIMIT_INVERT;
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 2))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 2))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-above", cb->arg);
+		break;
+	case O_MODE:
+		if (parse_mode(&info->cfg.mode, cb->arg) < 0)
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-mode", cb->arg);
+		break;
+	case O_SRCMASK:
+		info->cfg.srcmask = cb->val.hlen;
+		break;
+	case O_DSTMASK:
+		info->cfg.dstmask = cb->val.hlen;
+		break;
+	}
+}
+
+static void hashlimit_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_hashlimit_mtinfo3 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_BURST:
+		info->cfg.burst = parse_burst(cb->arg, 2);
+		break;
+	case O_UPTO:
+		if (cb->invert)
+			info->cfg.mode |= XT_HASHLIMIT_INVERT;
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 2))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 2))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-upto", cb->arg);
+		break;
+	case O_ABOVE:
+		if (!cb->invert)
+			info->cfg.mode |= XT_HASHLIMIT_INVERT;
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 2))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 2))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-above", cb->arg);
+		break;
+	case O_MODE:
+		if (parse_mode(&info->cfg.mode, cb->arg) < 0)
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-mode", cb->arg);
+		break;
+	case O_SRCMASK:
+		info->cfg.srcmask = cb->val.hlen;
+		break;
+	case O_DSTMASK:
+		info->cfg.dstmask = cb->val.hlen;
+		break;
+	case O_RATEMATCH:
+		info->cfg.mode |= XT_HASHLIMIT_RATE_MATCH;
+		break;
+	case O_INTERVAL:
+		if (!parse_interval(cb->arg, &info->cfg.interval))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+				"--hashlimit-rate-interval", cb->arg);
+	}
+}
+
+static void hashlimit_check(struct xt_fcheck_call *cb)
+{
+	const struct hashlimit_mt_udata *udata = cb->udata;
+	struct xt_hashlimit_info *info = cb->data;
+
+	if (!(cb->xflags & (F_UPTO | F_ABOVE)))
+		xtables_error(PARAMETER_PROBLEM,
+				"You have to specify --hashlimit");
+	if (!(cb->xflags & F_HTABLE_EXPIRE))
+		info->cfg.expire = udata->mult * 1000; /* from s to msec */
+}
+
+static void hashlimit_mt_check_v1(struct xt_fcheck_call *cb)
+{
+	const struct hashlimit_mt_udata *udata = cb->udata;
+	struct xt_hashlimit_mtinfo1 *info = cb->data;
+
+	if (!(cb->xflags & (F_UPTO | F_ABOVE)))
+		xtables_error(PARAMETER_PROBLEM,
+				"You have to specify --hashlimit");
+	if (!(cb->xflags & F_HTABLE_EXPIRE))
+		info->cfg.expire = udata->mult * 1000; /* from s to msec */
+
+	if (info->cfg.mode & XT_HASHLIMIT_BYTES) {
+		uint32_t burst = 0;
+		if (cb->xflags & F_BURST) {
+			if (info->cfg.burst < cost_to_bytes(info->cfg.avg))
+				xtables_error(PARAMETER_PROBLEM,
+					"burst cannot be smaller than %"PRIu64"b",
+					cost_to_bytes(info->cfg.avg));
+
+			burst = info->cfg.burst;
+			burst /= cost_to_bytes(info->cfg.avg);
+			if (info->cfg.burst % cost_to_bytes(info->cfg.avg))
+				burst++;
+			if (!(cb->xflags & F_HTABLE_EXPIRE))
+				info->cfg.expire = XT_HASHLIMIT_BYTE_EXPIRE_BURST * 1000;
+		}
+		info->cfg.burst = burst;
+	} else if (info->cfg.burst > XT_HASHLIMIT_BURST_MAX_v1)
+		burst_error_v1();
+}
+
+static void hashlimit_mt_check_v2(struct xt_fcheck_call *cb)
+{
+	const struct hashlimit_mt_udata *udata = cb->udata;
+	struct xt_hashlimit_mtinfo2 *info = cb->data;
+
+	if (!(cb->xflags & (F_UPTO | F_ABOVE)))
+		xtables_error(PARAMETER_PROBLEM,
+				"You have to specify --hashlimit");
+	if (!(cb->xflags & F_HTABLE_EXPIRE))
+		info->cfg.expire = udata->mult * 1000; /* from s to msec */
+
+	if (info->cfg.mode & XT_HASHLIMIT_BYTES) {
+		uint32_t burst = 0;
+		if (cb->xflags & F_BURST) {
+			if (info->cfg.burst < cost_to_bytes(info->cfg.avg))
+				xtables_error(PARAMETER_PROBLEM,
+					"burst cannot be smaller than %"PRIu64"b",
+					cost_to_bytes(info->cfg.avg));
+
+			burst = info->cfg.burst;
+			burst /= cost_to_bytes(info->cfg.avg);
+			if (info->cfg.burst % cost_to_bytes(info->cfg.avg))
+				burst++;
+			if (!(cb->xflags & F_HTABLE_EXPIRE))
+				info->cfg.expire = XT_HASHLIMIT_BYTE_EXPIRE_BURST * 1000;
+		}
+		info->cfg.burst = burst;
+	} else if (info->cfg.burst > XT_HASHLIMIT_BURST_MAX)
+		burst_error();
+}
+
+static void hashlimit_mt_check(struct xt_fcheck_call *cb)
+{
+	const struct hashlimit_mt_udata *udata = cb->udata;
+	struct xt_hashlimit_mtinfo3 *info = cb->data;
+
+	if (!(cb->xflags & (F_UPTO | F_ABOVE)))
+		xtables_error(PARAMETER_PROBLEM,
+				"You have to specify --hashlimit");
+	if (!(cb->xflags & F_HTABLE_EXPIRE))
+		info->cfg.expire = udata->mult * 1000; /* from s to msec */
+
+	if (info->cfg.mode & XT_HASHLIMIT_BYTES) {
+		uint32_t burst = 0;
+		if (cb->xflags & F_BURST) {
+			if (info->cfg.burst < cost_to_bytes(info->cfg.avg))
+				xtables_error(PARAMETER_PROBLEM,
+					"burst cannot be smaller than %"PRIu64"b", cost_to_bytes(info->cfg.avg));
+
+			burst = info->cfg.burst;
+			burst /= cost_to_bytes(info->cfg.avg);
+			if (info->cfg.burst % cost_to_bytes(info->cfg.avg))
+				burst++;
+			if (!(cb->xflags & F_HTABLE_EXPIRE))
+				info->cfg.expire = XT_HASHLIMIT_BYTE_EXPIRE_BURST * 1000;
+		}
+		info->cfg.burst = burst;
+	} else if (info->cfg.burst > XT_HASHLIMIT_BURST_MAX)
+		burst_error();
+
+	if (cb->xflags & F_RATEMATCH) {
+		if (!(info->cfg.mode & XT_HASHLIMIT_BYTES))
+			info->cfg.avg /= udata->mult;
+
+		if (info->cfg.interval == 0) {
+			if (info->cfg.mode & XT_HASHLIMIT_BYTES)
+				info->cfg.interval = 1;
+			else
+				info->cfg.interval = udata->mult;
+		}
+	}
+}
+
+struct rates {
+	const char *name;
+	uint64_t mult;
+} rates_v1[] = { { "day", XT_HASHLIMIT_SCALE*24*60*60 },
+		 { "hour", XT_HASHLIMIT_SCALE*60*60 },
+		 { "min", XT_HASHLIMIT_SCALE*60 },
+		 { "sec", XT_HASHLIMIT_SCALE } };
+
+static const struct rates rates[] = {
+	{ "day", XT_HASHLIMIT_SCALE_v2*24*60*60 },
+	{ "hour", XT_HASHLIMIT_SCALE_v2*60*60 },
+	{ "min", XT_HASHLIMIT_SCALE_v2*60 },
+	{ "sec", XT_HASHLIMIT_SCALE_v2 } };
+
+static uint32_t print_rate(uint64_t period, int revision)
+{
+	unsigned int i;
+	const struct rates *_rates = (revision == 1) ? rates_v1 : rates;
+	uint64_t scale = (revision == 1) ? XT_HASHLIMIT_SCALE : XT_HASHLIMIT_SCALE_v2;
+
+	if (period == 0) {
+		printf(" %f", INFINITY);
+		return 0;
+	}
+
+	for (i = 1; i < ARRAY_SIZE(rates); ++i)
+		if (period > _rates[i].mult
+            || _rates[i].mult/period < _rates[i].mult%period)
+			break;
+
+	printf(" %"PRIu64"/%s", _rates[i-1].mult / period, _rates[i-1].name);
+	/* return in msec */
+	return _rates[i-1].mult / scale * 1000;
+}
+
+static const struct {
+	const char *name;
+	uint32_t thresh;
+} units[] = {
+	{ "m", 1024 * 1024 },
+	{ "k", 1024 },
+	{ "", 1 },
+};
+
+static uint32_t print_bytes(uint64_t avg, uint64_t burst, const char *prefix)
+{
+	unsigned int i;
+	unsigned long long r;
+
+	r = cost_to_bytes(avg);
+
+	for (i = 0; i < ARRAY_SIZE(units) -1; ++i)
+		if (r >= units[i].thresh &&
+		    bytes_to_cost(r & ~(units[i].thresh - 1)) == avg)
+			break;
+	printf(" %llu%sb/s", r/units[i].thresh, units[i].name);
+
+	if (burst == 0)
+		return XT_HASHLIMIT_BYTE_EXPIRE * 1000;
+
+	r *= burst;
+	printf(" %s", prefix);
+	for (i = 0; i < ARRAY_SIZE(units) -1; ++i)
+		if (r >= units[i].thresh)
+			break;
+
+	printf("burst %llu%sb", r / units[i].thresh, units[i].name);
+	return XT_HASHLIMIT_BYTE_EXPIRE_BURST * 1000;
+}
+
+static void print_mode(unsigned int mode, char separator)
+{
+	bool prevmode = false;
+
+	putchar(' ');
+	if (mode & XT_HASHLIMIT_HASH_SIP) {
+		fputs("srcip", stdout);
+		prevmode = 1;
+	}
+	if (mode & XT_HASHLIMIT_HASH_SPT) {
+		if (prevmode)
+			putchar(separator);
+		fputs("srcport", stdout);
+		prevmode = 1;
+	}
+	if (mode & XT_HASHLIMIT_HASH_DIP) {
+		if (prevmode)
+			putchar(separator);
+		fputs("dstip", stdout);
+		prevmode = 1;
+	}
+	if (mode & XT_HASHLIMIT_HASH_DPT) {
+		if (prevmode)
+			putchar(separator);
+		fputs("dstport", stdout);
+	}
+}
+
+static void hashlimit_print(const void *ip,
+                            const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_hashlimit_info *r = (const void *)match->data;
+	uint32_t quantum;
+
+	fputs(" limit: avg", stdout);
+	quantum = print_rate(r->cfg.avg, 1);
+	printf(" burst %u", r->cfg.burst);
+	fputs(" mode", stdout);
+	print_mode(r->cfg.mode, '-');
+	if (r->cfg.size)
+		printf(" htable-size %u", r->cfg.size);
+	if (r->cfg.max)
+		printf(" htable-max %u", r->cfg.max);
+	if (r->cfg.gc_interval != XT_HASHLIMIT_GCINTERVAL)
+		printf(" htable-gcinterval %u", r->cfg.gc_interval);
+	if (r->cfg.expire != quantum)
+		printf(" htable-expire %u", r->cfg.expire);
+}
+
+static void
+hashlimit_mt_print(const struct hashlimit_cfg3 *cfg, unsigned int dmask, int revision)
+{
+	uint64_t quantum;
+	uint64_t period;
+
+	if (cfg->mode & XT_HASHLIMIT_INVERT)
+		fputs(" limit: above", stdout);
+	else
+		fputs(" limit: up to", stdout);
+
+	if (cfg->mode & XT_HASHLIMIT_BYTES) {
+		quantum = print_bytes(cfg->avg, cfg->burst, "");
+	} else {
+		if (revision == 3) {
+			period = cfg->avg;
+			if (cfg->interval != 0)
+				period *= cfg->interval;
+
+			quantum = print_rate(period, revision);
+		} else {
+			quantum = print_rate(cfg->avg, revision);
+		}
+		printf(" burst %llu", cfg->burst);
+	}
+	if (cfg->mode & (XT_HASHLIMIT_HASH_SIP | XT_HASHLIMIT_HASH_SPT |
+	    XT_HASHLIMIT_HASH_DIP | XT_HASHLIMIT_HASH_DPT)) {
+		fputs(" mode", stdout);
+		print_mode(cfg->mode, '-');
+	}
+	if (cfg->size != 0)
+		printf(" htable-size %u", cfg->size);
+	if (cfg->max != 0)
+		printf(" htable-max %u", cfg->max);
+	if (cfg->gc_interval != XT_HASHLIMIT_GCINTERVAL)
+		printf(" htable-gcinterval %u", cfg->gc_interval);
+	if (cfg->expire != quantum)
+		printf(" htable-expire %u", cfg->expire);
+
+	if (cfg->srcmask != dmask)
+		printf(" srcmask %u", cfg->srcmask);
+	if (cfg->dstmask != dmask)
+		printf(" dstmask %u", cfg->dstmask);
+
+	if ((revision == 3) && (cfg->mode & XT_HASHLIMIT_RATE_MATCH))
+		printf(" rate-match");
+
+	if ((revision == 3) && (cfg->mode & XT_HASHLIMIT_RATE_MATCH))
+		if (cfg->interval != 1)
+			printf(" rate-interval %u", cfg->interval);
+}
+
+static void
+hashlimit_mt4_print_v1(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 1);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_print(&cfg, 32, 1);
+}
+
+static void
+hashlimit_mt6_print_v1(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 1);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_print(&cfg, 128, 1);
+}
+
+static void
+hashlimit_mt4_print_v2(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct xt_hashlimit_mtinfo2 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 2);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_print(&cfg, 32, 2);
+}
+
+static void
+hashlimit_mt6_print_v2(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct xt_hashlimit_mtinfo2 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 2);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_print(&cfg, 128, 2);
+}
+static void
+hashlimit_mt4_print(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct xt_hashlimit_mtinfo3 *info = (const void *)match->data;
+
+	hashlimit_mt_print(&info->cfg, 32, 3);
+}
+
+static void
+hashlimit_mt6_print(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct xt_hashlimit_mtinfo3 *info = (const void *)match->data;
+
+	hashlimit_mt_print(&info->cfg, 128, 3);
+}
+
+static void hashlimit_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_info *r = (const void *)match->data;
+	uint32_t quantum;
+
+	fputs(" --hashlimit", stdout);
+	quantum = print_rate(r->cfg.avg, 1);
+	printf(" --hashlimit-burst %u", r->cfg.burst);
+
+	fputs(" --hashlimit-mode", stdout);
+	print_mode(r->cfg.mode, ',');
+
+	printf(" --hashlimit-name %s", r->name);
+
+	if (r->cfg.size)
+		printf(" --hashlimit-htable-size %u", r->cfg.size);
+	if (r->cfg.max)
+		printf(" --hashlimit-htable-max %u", r->cfg.max);
+	if (r->cfg.gc_interval != XT_HASHLIMIT_GCINTERVAL)
+		printf(" --hashlimit-htable-gcinterval %u", r->cfg.gc_interval);
+	if (r->cfg.expire != quantum)
+		printf(" --hashlimit-htable-expire %u", r->cfg.expire);
+}
+
+static void
+hashlimit_mt_save(const struct hashlimit_cfg3 *cfg, const char* name, unsigned int dmask, int revision)
+{
+	uint32_t quantum;
+
+	if (cfg->mode & XT_HASHLIMIT_INVERT)
+		fputs(" --hashlimit-above", stdout);
+	else
+		fputs(" --hashlimit-upto", stdout);
+
+	if (cfg->mode & XT_HASHLIMIT_BYTES) {
+		quantum = print_bytes(cfg->avg, cfg->burst, "--hashlimit-");
+	} else {
+		quantum = print_rate(cfg->avg, revision);
+		printf(" --hashlimit-burst %llu", cfg->burst);
+	}
+
+	if (cfg->mode & (XT_HASHLIMIT_HASH_SIP | XT_HASHLIMIT_HASH_SPT |
+	    XT_HASHLIMIT_HASH_DIP | XT_HASHLIMIT_HASH_DPT)) {
+		fputs(" --hashlimit-mode", stdout);
+		print_mode(cfg->mode, ',');
+	}
+
+	printf(" --hashlimit-name %s", name);
+
+	if (cfg->size != 0)
+		printf(" --hashlimit-htable-size %u", cfg->size);
+	if (cfg->max != 0)
+		printf(" --hashlimit-htable-max %u", cfg->max);
+	if (cfg->gc_interval != XT_HASHLIMIT_GCINTERVAL)
+		printf(" --hashlimit-htable-gcinterval %u", cfg->gc_interval);
+	if (cfg->expire != quantum)
+		printf(" --hashlimit-htable-expire %u", cfg->expire);
+
+	if (cfg->srcmask != dmask)
+		printf(" --hashlimit-srcmask %u", cfg->srcmask);
+	if (cfg->dstmask != dmask)
+		printf(" --hashlimit-dstmask %u", cfg->dstmask);
+
+	if ((revision == 3) && (cfg->mode & XT_HASHLIMIT_RATE_MATCH))
+		printf(" --hashlimit-rate-match");
+
+	if ((revision == 3) && (cfg->mode & XT_HASHLIMIT_RATE_MATCH))
+		if (cfg->interval != 1)
+			printf(" --hashlimit-rate-interval %u", cfg->interval);
+}
+
+static void
+hashlimit_mt4_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 1);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_save(&cfg, info->name, 32, 1);
+}
+
+static void
+hashlimit_mt6_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 1);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_save(&cfg, info->name, 128, 1);
+}
+
+static void
+hashlimit_mt4_save_v2(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_mtinfo2 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 2);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_save(&cfg, info->name, 32, 2);
+}
+
+static void
+hashlimit_mt6_save_v2(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_mtinfo2 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 2);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_save(&cfg, info->name, 128, 2);
+}
+
+static void
+hashlimit_mt4_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_mtinfo3 *info = (const void *)match->data;
+
+	hashlimit_mt_save(&info->cfg, info->name, 32, 3);
+}
+
+static void
+hashlimit_mt6_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_mtinfo3 *info = (const void *)match->data;
+
+	hashlimit_mt_save(&info->cfg, info->name, 128, 3);
+}
+
+static const struct rates rates_v1_xlate[] = {
+	{ "day", XT_HASHLIMIT_SCALE * 24 * 60 * 60 },
+	{ "hour", XT_HASHLIMIT_SCALE * 60 * 60 },
+	{ "minute", XT_HASHLIMIT_SCALE * 60 },
+	{ "second", XT_HASHLIMIT_SCALE } };
+
+static const struct rates rates_xlate[] = {
+	{ "day", XT_HASHLIMIT_SCALE_v2 * 24 * 60 * 60 },
+	{ "hour", XT_HASHLIMIT_SCALE_v2 * 60 * 60 },
+	{ "minute", XT_HASHLIMIT_SCALE_v2 * 60 },
+	{ "second", XT_HASHLIMIT_SCALE_v2 } };
+
+static void print_packets_rate_xlate(struct xt_xlate *xl, uint64_t avg,
+				     int revision)
+{
+	unsigned int i;
+	const struct rates *_rates = (revision == 1) ?
+		rates_v1_xlate : rates_xlate;
+
+	for (i = 1; i < ARRAY_SIZE(rates); ++i)
+		if (avg > _rates[i].mult ||
+		    _rates[i].mult / avg < _rates[i].mult % avg)
+			break;
+
+	xt_xlate_add(xl, " %" PRIu64 "/%s ",
+		     _rates[i-1].mult / avg, _rates[i-1].name);
+}
+
+static void print_bytes_rate_xlate(struct xt_xlate *xl,
+				   const struct hashlimit_cfg3 *cfg)
+{
+	unsigned int i;
+	unsigned long long r;
+
+	r = cost_to_bytes(cfg->avg);
+
+	for (i = 0; i < ARRAY_SIZE(units) -1; ++i)
+		if (r >= units[i].thresh &&
+		    bytes_to_cost(r & ~(units[i].thresh - 1)) == cfg->avg)
+			break;
+
+	xt_xlate_add(xl, " %llu %sbytes/second", r / units[i].thresh,
+		     units[i].name);
+
+	r *= cfg->burst;
+	for (i = 0; i < ARRAY_SIZE(units) -1; ++i)
+		if (r >= units[i].thresh)
+			break;
+
+	if (cfg->burst > 0)
+		xt_xlate_add(xl, " burst %llu %sbytes", r / units[i].thresh,
+			     units[i].name);
+}
+
+static void hashlimit_print_subnet_xlate(struct xt_xlate *xl,
+					 uint32_t nsub, int family)
+{
+	char sep = (family == NFPROTO_IPV4) ? '.' : ':';
+	char *fmt = (family == NFPROTO_IPV4) ? "%u" : "%04x";
+	unsigned int nblocks = (family == NFPROTO_IPV4) ? 4 : 8;
+	unsigned int nbits = (family == NFPROTO_IPV4) ? 8 : 16;
+	unsigned int acm, i;
+
+	xt_xlate_add(xl, " and ");
+	while (nblocks--) {
+		acm = 0;
+
+		for (i = 0; i < nbits; i++) {
+			acm <<= 1;
+
+			if (nsub > 0) {
+				acm++;
+				nsub--;
+			}
+		}
+
+		xt_xlate_add(xl, fmt, acm);
+		if (nblocks > 0)
+			xt_xlate_add(xl, "%c", sep);
+	}
+}
+
+static const char *const hashlimit_modes4_xlate[] = {
+	[XT_HASHLIMIT_HASH_DIP]	= "ip daddr",
+	[XT_HASHLIMIT_HASH_DPT]	= "tcp dport",
+	[XT_HASHLIMIT_HASH_SIP]	= "ip saddr",
+	[XT_HASHLIMIT_HASH_SPT]	= "tcp sport",
+};
+
+static const char *const hashlimit_modes6_xlate[] = {
+	[XT_HASHLIMIT_HASH_DIP]	= "ip6 daddr",
+	[XT_HASHLIMIT_HASH_DPT]	= "tcp dport",
+	[XT_HASHLIMIT_HASH_SIP]	= "ip6 saddr",
+	[XT_HASHLIMIT_HASH_SPT]	= "tcp sport",
+};
+
+static int hashlimit_mode_xlate(struct xt_xlate *xl,
+				uint32_t mode, int family,
+				unsigned int nsrc, unsigned int ndst)
+{
+	const char * const *_modes = (family == NFPROTO_IPV4) ?
+		hashlimit_modes4_xlate : hashlimit_modes6_xlate;
+	bool prevopt = false;
+	unsigned int mask;
+
+	mode &= ~XT_HASHLIMIT_INVERT & ~XT_HASHLIMIT_BYTES;
+
+	for (mask = 1; mode > 0; mask <<= 1) {
+		if (!(mode & mask))
+			continue;
+
+		if (!prevopt) {
+			xt_xlate_add(xl, " ");
+			prevopt = true;
+		}
+		else {
+			xt_xlate_add(xl, " . ");
+		}
+
+		xt_xlate_add(xl, "%s", _modes[mask]);
+
+		if (mask == XT_HASHLIMIT_HASH_DIP &&
+		    ((family == NFPROTO_IPV4 && ndst != 32) ||
+		     (family == NFPROTO_IPV6 && ndst != 128)))
+			hashlimit_print_subnet_xlate(xl, ndst, family);
+		else if (mask == XT_HASHLIMIT_HASH_SIP &&
+			 ((family == NFPROTO_IPV4 && nsrc != 32) ||
+			  (family == NFPROTO_IPV6 && nsrc != 128)))
+			hashlimit_print_subnet_xlate(xl, nsrc, family);
+
+		mode &= ~mask;
+	}
+
+	return prevopt;
+}
+
+static int hashlimit_mt_xlate(struct xt_xlate *xl, const char *name,
+			      const struct hashlimit_cfg3 *cfg,
+			      int revision, int family)
+{
+	int ret = 1;
+
+	xt_xlate_add(xl, "meter %s {", name);
+	ret = hashlimit_mode_xlate(xl, cfg->mode, family,
+				   cfg->srcmask, cfg->dstmask);
+	if (cfg->expire != 1000)
+		xt_xlate_add(xl, " timeout %us", cfg->expire / 1000);
+	xt_xlate_add(xl, " limit rate");
+
+	if (cfg->mode & XT_HASHLIMIT_INVERT)
+		xt_xlate_add(xl, " over");
+
+	if (cfg->mode & XT_HASHLIMIT_BYTES)
+		print_bytes_rate_xlate(xl, cfg);
+	else {
+		print_packets_rate_xlate(xl, cfg->avg, revision);
+		if (cfg->burst != XT_HASHLIMIT_BURST)
+			xt_xlate_add(xl, "burst %" PRIu64 " packets", (uint64_t)cfg->burst);
+
+	}
+	xt_xlate_add(xl, "}");
+
+	return ret;
+}
+
+static int hashlimit_xlate(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_info *info = (const void *)params->match->data;
+	int ret = 1;
+
+	xt_xlate_add(xl, "meter %s {", info->name);
+	ret = hashlimit_mode_xlate(xl, info->cfg.mode, NFPROTO_IPV4, 32, 32);
+	xt_xlate_add(xl, " timeout %us limit rate", info->cfg.expire / 1000);
+	print_packets_rate_xlate(xl, info->cfg.avg, 1);
+	xt_xlate_add(xl, " burst %u packets", info->cfg.burst);
+	xt_xlate_add(xl, "}");
+
+	return ret;
+}
+
+static int hashlimit_mt4_xlate_v1(struct xt_xlate *xl,
+				  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo1 *info =
+		(const void *)params->match->data;
+	struct hashlimit_cfg3 cfg;
+
+	if (cfg_copy(&cfg, (const void *)&info->cfg, 1))
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	return hashlimit_mt_xlate(xl, info->name, &cfg, 1, NFPROTO_IPV4);
+}
+
+static int hashlimit_mt6_xlate_v1(struct xt_xlate *xl,
+				  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo1 *info =
+		(const void *)params->match->data;
+	struct hashlimit_cfg3 cfg;
+
+	if (cfg_copy(&cfg, (const void *)&info->cfg, 1))
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	return hashlimit_mt_xlate(xl, info->name, &cfg, 1, NFPROTO_IPV6);
+}
+
+static int hashlimit_mt4_xlate_v2(struct xt_xlate *xl,
+				  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo2 *info =
+		(const void *)params->match->data;
+	struct hashlimit_cfg3 cfg;
+
+	if (cfg_copy(&cfg, (const void *)&info->cfg, 2))
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	return hashlimit_mt_xlate(xl, info->name, &cfg, 2, NFPROTO_IPV4);
+}
+
+static int hashlimit_mt6_xlate_v2(struct xt_xlate *xl,
+				  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo2 *info =
+		(const void *)params->match->data;
+	struct hashlimit_cfg3 cfg;
+
+	if (cfg_copy(&cfg, (const void *)&info->cfg, 2))
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	return hashlimit_mt_xlate(xl, info->name, &cfg, 2, NFPROTO_IPV6);
+}
+
+static int hashlimit_mt4_xlate(struct xt_xlate *xl,
+			       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo3 *info =
+		(const void *)params->match->data;
+
+	return hashlimit_mt_xlate(xl, info->name, &info->cfg, 3, NFPROTO_IPV4);
+}
+
+static int hashlimit_mt6_xlate(struct xt_xlate *xl,
+			       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo3 *info =
+		(const void *)params->match->data;
+
+	return hashlimit_mt_xlate(xl, info->name, &info->cfg, 3, NFPROTO_IPV6);
+}
+
+static struct xtables_match hashlimit_mt_reg[] = {
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "hashlimit",
+		.version       = XTABLES_VERSION,
+		.revision      = 0,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_info)),
+		.userspacesize = offsetof(struct xt_hashlimit_info, hinfo),
+		.help          = hashlimit_help,
+		.init          = hashlimit_init,
+		.x6_parse      = hashlimit_parse,
+		.x6_fcheck     = hashlimit_check,
+		.print         = hashlimit_print,
+		.save          = hashlimit_save,
+		.x6_options    = hashlimit_opts,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "hashlimit",
+		.revision      = 1,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo1)),
+		.userspacesize = offsetof(struct xt_hashlimit_mtinfo1, hinfo),
+		.help          = hashlimit_mt_help,
+		.init          = hashlimit_mt4_init_v1,
+		.x6_parse      = hashlimit_mt_parse_v1,
+		.x6_fcheck     = hashlimit_mt_check_v1,
+		.print         = hashlimit_mt4_print_v1,
+		.save          = hashlimit_mt4_save_v1,
+		.x6_options    = hashlimit_mt_opts_v1,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt4_xlate_v1,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "hashlimit",
+		.revision      = 1,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo1)),
+		.userspacesize = offsetof(struct xt_hashlimit_mtinfo1, hinfo),
+		.help          = hashlimit_mt_help,
+		.init          = hashlimit_mt6_init_v1,
+		.x6_parse      = hashlimit_mt_parse_v1,
+		.x6_fcheck     = hashlimit_mt_check_v1,
+		.print         = hashlimit_mt6_print_v1,
+		.save          = hashlimit_mt6_save_v1,
+		.x6_options    = hashlimit_mt_opts_v1,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt6_xlate_v1,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "hashlimit",
+		.revision      = 2,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo2)),
+		.userspacesize = offsetof(struct xt_hashlimit_mtinfo2, hinfo),
+		.help          = hashlimit_mt_help,
+		.init          = hashlimit_mt4_init_v2,
+		.x6_parse      = hashlimit_mt_parse_v2,
+		.x6_fcheck     = hashlimit_mt_check_v2,
+		.print         = hashlimit_mt4_print_v2,
+		.save          = hashlimit_mt4_save_v2,
+		.x6_options    = hashlimit_mt_opts_v2,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt4_xlate_v2,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "hashlimit",
+		.revision      = 2,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo2)),
+		.userspacesize = offsetof(struct xt_hashlimit_mtinfo2, hinfo),
+		.help          = hashlimit_mt_help,
+		.init          = hashlimit_mt6_init_v2,
+		.x6_parse      = hashlimit_mt_parse_v2,
+		.x6_fcheck     = hashlimit_mt_check_v2,
+		.print         = hashlimit_mt6_print_v2,
+		.save          = hashlimit_mt6_save_v2,
+		.x6_options    = hashlimit_mt_opts_v2,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt6_xlate_v2,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "hashlimit",
+		.revision      = 3,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo3)),
+		.userspacesize = offsetof(struct xt_hashlimit_mtinfo3, hinfo),
+		.help          = hashlimit_mt_help_v3,
+		.init          = hashlimit_mt4_init,
+		.x6_parse      = hashlimit_mt_parse,
+		.x6_fcheck     = hashlimit_mt_check,
+		.print         = hashlimit_mt4_print,
+		.save          = hashlimit_mt4_save,
+		.x6_options    = hashlimit_mt_opts,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt4_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "hashlimit",
+		.revision      = 3,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo3)),
+		.userspacesize = offsetof(struct xt_hashlimit_mtinfo3, hinfo),
+		.help          = hashlimit_mt_help_v3,
+		.init          = hashlimit_mt6_init,
+		.x6_parse      = hashlimit_mt_parse,
+		.x6_fcheck     = hashlimit_mt_check,
+		.print         = hashlimit_mt6_print,
+		.save          = hashlimit_mt6_save,
+		.x6_options    = hashlimit_mt_opts,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt6_xlate,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(hashlimit_mt_reg, ARRAY_SIZE(hashlimit_mt_reg));
+}
diff --git a/extensions/libxt_hashlimit.man b/extensions/libxt_hashlimit.man
new file mode 100644
index 0000000..8a35d56
--- /dev/null
+++ b/extensions/libxt_hashlimit.man
@@ -0,0 +1,84 @@
+\fBhashlimit\fP uses hash buckets to express a rate limiting match (like the
+\fBlimit\fP match) for a group of connections using a \fBsingle\fP iptables
+rule. Grouping can be done per-hostgroup (source and/or destination address)
+and/or per-port. It gives you the ability to express "\fIN\fP packets per time
+quantum per group" or "\fIN\fP bytes per seconds" (see below for some examples).
+.PP
+A hash limit option (\fB\-\-hashlimit\-upto\fP, \fB\-\-hashlimit\-above\fP) and
+\fB\-\-hashlimit\-name\fP are required.
+.TP
+\fB\-\-hashlimit\-upto\fP \fIamount\fP[\fB/second\fP|\fB/minute\fP|\fB/hour\fP|\fB/day\fP]
+Match if the rate is below or equal to \fIamount\fP/quantum. It is specified either as
+a number, with an optional time quantum suffix (the default is 3/hour), or as
+\fIamount\fPb/second (number of bytes per second).
+.TP
+\fB\-\-hashlimit\-above\fP \fIamount\fP[\fB/second\fP|\fB/minute\fP|\fB/hour\fP|\fB/day\fP]
+Match if the rate is above \fIamount\fP/quantum.
+.TP
+\fB\-\-hashlimit\-burst\fP \fIamount\fP
+Maximum initial number of packets to match: this number gets recharged by one
+every time the limit specified above is not reached, up to this number; the
+default is 5.  When byte-based rate matching is requested, this option specifies
+the amount of bytes that can exceed the given rate.  This option should be used
+with caution -- if the entry expires, the burst value is reset too.
+.TP
+\fB\-\-hashlimit\-mode\fP {\fBsrcip\fP|\fBsrcport\fP|\fBdstip\fP|\fBdstport\fP}\fB,\fP...
+A comma-separated list of objects to take into consideration. If no
+\-\-hashlimit\-mode option is given, hashlimit acts like limit, but at the
+expensive of doing the hash housekeeping.
+.TP
+\fB\-\-hashlimit\-srcmask\fP \fIprefix\fP
+When \-\-hashlimit\-mode srcip is used, all source addresses encountered will be
+grouped according to the given prefix length and the so-created subnet will be
+subject to hashlimit. \fIprefix\fP must be between (inclusive) 0 and 32. Note
+that \-\-hashlimit\-srcmask 0 is basically doing the same thing as not specifying
+srcip for \-\-hashlimit\-mode, but is technically more expensive.
+.TP
+\fB\-\-hashlimit\-dstmask\fP \fIprefix\fP
+Like \-\-hashlimit\-srcmask, but for destination addresses.
+.TP
+\fB\-\-hashlimit\-name\fP \fIfoo\fP
+The name for the /proc/net/ipt_hashlimit/foo entry.
+.TP
+\fB\-\-hashlimit\-htable\-size\fP \fIbuckets\fP
+The number of buckets of the hash table
+.TP
+\fB\-\-hashlimit\-htable\-max\fP \fIentries\fP
+Maximum entries in the hash.
+.TP
+\fB\-\-hashlimit\-htable\-expire\fP \fImsec\fP
+After how many milliseconds do hash entries expire.
+.TP
+\fB\-\-hashlimit\-htable\-gcinterval\fP \fImsec\fP
+How many milliseconds between garbage collection intervals.
+.TP
+\fB\-\-hashlimit\-rate\-match\fP
+Classify the flow instead of rate-limiting it. This acts like a
+true/false match on whether the rate is above/below a certain number
+.TP
+\fB\-\-hashlimit\-rate\-interval\fP \fIsec\fP
+Can be used with \-\-hashlimit\-rate\-match to specify the interval
+at which the rate should be sampled
+.PP
+Examples:
+.TP
+matching on source host
+"1000 packets per second for every host in 192.168.0.0/16" =>
+\-s 192.168.0.0/16 \-\-hashlimit\-mode srcip \-\-hashlimit\-upto 1000/sec
+.TP
+matching on source port
+"100 packets per second for every service of 192.168.1.1" =>
+\-s 192.168.1.1 \-\-hashlimit\-mode srcport \-\-hashlimit\-upto 100/sec
+.TP
+matching on subnet
+"10000 packets per minute for every /28 subnet (groups of 8 addresses)
+in 10.0.0.0/8" =>
+\-s 10.0.0.0/8 \-\-hashlimit\-mask 28 \-\-hashlimit\-upto 10000/min
+.TP
+matching bytes per second
+"flows exceeding 512kbyte/s" =>
+\-\-hashlimit-mode srcip,dstip,srcport,dstport \-\-hashlimit\-above 512kb/s
+.TP
+matching bytes per second
+"hosts that exceed 512kbyte/s, but permit up to 1Megabytes without matching"
+\-\-hashlimit-mode dstip \-\-hashlimit\-above 512kb/s \-\-hashlimit-burst 1mb
diff --git a/extensions/libxt_hashlimit.t b/extensions/libxt_hashlimit.t
new file mode 100644
index 0000000..ccd0d1e
--- /dev/null
+++ b/extensions/libxt_hashlimit.t
@@ -0,0 +1,33 @@
+:INPUT,FORWARD,OUTPUT
+-m hashlimit --hashlimit-above 1/sec --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-above 1000000/sec --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-above 1/min --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+# kernel says "xt_hashlimit: overflow, try lower: 864000000/5"
+-m hashlimit --hashlimit-above 1/day --hashlimit-burst 5 --hashlimit-name mini1;;FAIL
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-upto 1000000/sec --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-upto 1/min --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-upto 1/hour --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+# kernel says "xt_hashlimit: overflow, try lower: 864000000/5"
+-m hashlimit --hashlimit-upto 1/day --hashlimit-burst 5 --hashlimit-name mini1;;FAIL
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 1 --hashlimit-name mini1 --hashlimit-htable-expire 2000;=;OK
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 1 --hashlimit-mode srcip --hashlimit-name mini1 --hashlimit-htable-expire 2000;=;OK
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 1 --hashlimit-mode dstip --hashlimit-name mini1 --hashlimit-htable-expire 2000;=;OK
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 1 --hashlimit-mode dstip --hashlimit-name mini1 --hashlimit-htable-max 2000 --hashlimit-htable-expire 2000;=;OK
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 1 --hashlimit-mode dstip --hashlimit-name mini1 --hashlimit-htable-max 2000 --hashlimit-htable-gcinterval 60000 --hashlimit-htable-expire 2000;=;OK
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-name mini1;-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 5 --hashlimit-name mini1;OK
+-m hashlimit --hashlimit-upto 4kb/s --hashlimit-burst 400kb --hashlimit-name mini5;=;OK
+-m hashlimit --hashlimit-upto 10mb/s --hashlimit-name mini6;=;OK
+-m hashlimit --hashlimit-upto 123456b/s --hashlimit-burst 1mb --hashlimit-name mini7;=;OK
+# should work, it says "iptables v1.4.15: burst cannot be smaller than 96b"
+# ERROR: cannot load: iptables -A INPUT -m hashlimit --hashlimit-upto 96b/s --hashlimit-burst 5 --hashlimit-name mini1
+# -m hashlimit --hashlimit-upto 96b/s --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-name mini1;;FAIL
+-m hashlimit --hashlimit-upto 1/sec;;FAIL
+-m hashlimit;;FAIL
+-m hashlimit --hashlimit-upto 40/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name syn-flood;=;OK
+-m hashlimit --hashlimit-upto 40/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name rate1 --hashlimit-rate-match;=;OK
+-m hashlimit --hashlimit-upto 40mb/s --hashlimit-mode srcip --hashlimit-name rate2 --hashlimit-rate-match;=;OK
+-m hashlimit --hashlimit-upto 40/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name rate3 --hashlimit-rate-match --hashlimit-rate-interval 10;=;OK
+-m hashlimit --hashlimit-upto 40mb/s --hashlimit-mode srcip --hashlimit-name rate4 --hashlimit-rate-match --hashlimit-rate-interval 10;=;OK
diff --git a/extensions/libxt_hashlimit.txlate b/extensions/libxt_hashlimit.txlate
new file mode 100644
index 0000000..6c8d07f
--- /dev/null
+++ b/extensions/libxt_hashlimit.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A OUTPUT -m tcp -p tcp --dport 443 -m hashlimit --hashlimit-above 20kb/s --hashlimit-burst 1mb --hashlimit-mode dstip --hashlimit-name https --hashlimit-dstmask 24 -m state --state NEW -j DROP
+nft add rule ip filter OUTPUT tcp dport 443 meter https { ip daddr and 255.255.255.0 timeout 60s limit rate over 20 kbytes/second burst 1 mbytes} ct state new  counter drop
+
+iptables-translate -A OUTPUT -m tcp -p tcp --dport 443 -m hashlimit --hashlimit-upto 300 --hashlimit-burst 15 --hashlimit-mode srcip,dstip --hashlimit-name https --hashlimit-htable-expire 300000 -m state --state NEW -j DROP
+nft add rule ip filter OUTPUT tcp dport 443 meter https { ip daddr . ip saddr timeout 300s limit rate 300/second burst 15 packets} ct state new  counter drop
diff --git a/extensions/libxt_helper.c b/extensions/libxt_helper.c
new file mode 100644
index 0000000..2afbf99
--- /dev/null
+++ b/extensions/libxt_helper.c
@@ -0,0 +1,79 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_helper.h>
+
+enum {
+	O_HELPER = 0,
+};
+
+static void helper_help(void)
+{
+	printf(
+"helper match options:\n"
+"[!] --helper string        Match helper identified by string\n");
+}
+
+static const struct xt_option_entry helper_opts[] = {
+	{.name = "helper", .id = O_HELPER, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_helper_info, name)},
+	XTOPT_TABLEEND,
+};
+
+static void helper_parse(struct xt_option_call *cb)
+{
+	struct xt_helper_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->invert)
+		info->invert = 1;
+}
+
+static void
+helper_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_helper_info *info = (const void *)match->data;
+
+	printf(" helper match %s\"%s\"", info->invert ? "! " : "", info->name);
+}
+
+static void helper_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_helper_info *info = (const void *)match->data;
+
+	printf("%s --helper", info->invert ? " !" : "");
+	xtables_save_string(info->name);
+}
+
+static int helper_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_mt_params *params)
+{
+	const struct xt_helper_info *info = (const void *)params->match->data;
+
+	if (params->escape_quotes)
+		xt_xlate_add(xl, "ct helper%s \\\"%s\\\"",
+			   info->invert ? " !=" : "", info->name);
+	else
+		xt_xlate_add(xl, "ct helper%s \"%s\"",
+			   info->invert ? " !=" : "", info->name);
+
+	return 1;
+}
+
+static struct xtables_match helper_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "helper",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_helper_info)),
+	.help		= helper_help,
+	.print		= helper_print,
+	.save		= helper_save,
+	.x6_parse	= helper_parse,
+	.x6_options	= helper_opts,
+	.xlate		= helper_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&helper_match);
+}
diff --git a/extensions/libxt_helper.man b/extensions/libxt_helper.man
new file mode 100644
index 0000000..772b135
--- /dev/null
+++ b/extensions/libxt_helper.man
@@ -0,0 +1,11 @@
+This module matches packets related to a specific conntrack-helper.
+.TP
+[\fB!\fP] \fB\-\-helper\fP \fIstring\fP
+Matches packets related to the specified conntrack-helper.
+.RS
+.PP
+string can be "ftp" for packets related to a ftp-session on default port.
+For other ports append \-portnr to the value, ie. "ftp\-2121".
+.PP
+Same rules apply for other conntrack-helpers.
+.RE
diff --git a/extensions/libxt_helper.t b/extensions/libxt_helper.t
new file mode 100644
index 0000000..8c8420a
--- /dev/null
+++ b/extensions/libxt_helper.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m helper --helper ftp;=;OK
+# should be OK?
+# ERROR: should fail: iptables -A INPUT -m helper --helper wrong
+# -m helper --helper wrong;;FAIL
+-m helper;;FAIL
diff --git a/extensions/libxt_helper.txlate b/extensions/libxt_helper.txlate
new file mode 100644
index 0000000..8259aba
--- /dev/null
+++ b/extensions/libxt_helper.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A FORWARD -m helper --helper sip
+nft add rule ip filter FORWARD ct helper \"sip\" counter
+
+iptables-translate -A FORWARD -m helper ! --helper ftp
+nft add rule ip filter FORWARD ct helper != \"ftp\" counter
diff --git a/extensions/libxt_icmp.h b/extensions/libxt_icmp.h
new file mode 100644
index 0000000..5820206
--- /dev/null
+++ b/extensions/libxt_icmp.h
@@ -0,0 +1,25 @@
+struct xt_icmp_names {
+	const char *name;
+	uint8_t type;
+	uint8_t code_min, code_max;
+};
+
+static void xt_print_icmp_types(const struct xt_icmp_names *icmp_codes,
+				unsigned int n_codes)
+{
+	unsigned int i;
+
+	for (i = 0; i < n_codes; ++i) {
+		if (i && icmp_codes[i].type == icmp_codes[i-1].type) {
+			if (icmp_codes[i].code_min == icmp_codes[i-1].code_min
+			    && (icmp_codes[i].code_max
+				== icmp_codes[i-1].code_max))
+				printf(" (%s)", icmp_codes[i].name);
+			else
+				printf("\n   %s", icmp_codes[i].name);
+		}
+		else
+			printf("\n%s", icmp_codes[i].name);
+	}
+	printf("\n");
+}
diff --git a/extensions/libxt_ipcomp.c b/extensions/libxt_ipcomp.c
new file mode 100644
index 0000000..b5c4312
--- /dev/null
+++ b/extensions/libxt_ipcomp.c
@@ -0,0 +1,134 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_ipcomp.h>
+
+enum {
+	O_compSPI = 0,
+	O_compRES,
+};
+
+static void comp_help(void)
+{
+	printf(
+"comp match options:\n"
+"[!] --ipcompspi spi[:spi]\n"
+"				match spi (range)\n");
+}
+
+static const struct xt_option_entry comp_opts[] = {
+	{.name = "ipcompspi", .id = O_compSPI, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_ipcomp, spis)},
+	{.name = "compres", .id = O_compRES, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void comp_parse(struct xt_option_call *cb)
+{
+	struct xt_ipcomp *compinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_compSPI:
+		if (cb->nvals == 1)
+			compinfo->spis[1] = compinfo->spis[0];
+		if (cb->invert)
+			compinfo->invflags |= XT_IPCOMP_INV_SPI;
+		break;
+	case O_compRES:
+		compinfo->hdrres = 1;
+		break;
+	}
+}
+
+static void
+print_spis(const char *name, uint32_t min, uint32_t max,
+	    int invert)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFFFFFF || invert) {
+		if (min == max)
+			printf("%s:%s%u", name, inv, min);
+		else
+			printf("%ss:%s%u:%u", name, inv, min, max);
+	}
+}
+
+static void comp_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	const struct xt_ipcomp *comp = (struct xt_ipcomp *)match->data;
+
+	printf(" comp ");
+	print_spis("spi", comp->spis[0], comp->spis[1],
+		    comp->invflags & XT_IPCOMP_INV_SPI);
+
+	if (comp->hdrres)
+		printf(" reserved");
+
+	if (comp->invflags & ~XT_IPCOMP_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       comp->invflags & ~XT_IPCOMP_INV_MASK);
+}
+
+static void comp_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_ipcomp *compinfo = (struct xt_ipcomp *)match->data;
+
+	if (!(compinfo->spis[0] == 0
+	    && compinfo->spis[1] == 0xFFFFFFFF)) {
+		printf("%s --ipcompspi ",
+			(compinfo->invflags & XT_IPCOMP_INV_SPI) ? " !" : "");
+		if (compinfo->spis[0]
+		    != compinfo->spis[1])
+			printf("%u:%u",
+			       compinfo->spis[0],
+			       compinfo->spis[1]);
+		else
+			printf("%u",
+			       compinfo->spis[0]);
+	}
+
+	if (compinfo->hdrres != 0 )
+		printf(" --compres");
+}
+
+static int comp_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_ipcomp *compinfo =
+		(struct xt_ipcomp *)params->match->data;
+
+	xt_xlate_add(xl, "comp cpi %s",
+		     compinfo->invflags & XT_IPCOMP_INV_SPI ? "!= " : "");
+	if (compinfo->spis[0] != compinfo->spis[1])
+		xt_xlate_add(xl, "%u-%u", compinfo->spis[0],
+			     compinfo->spis[1]);
+	else
+		xt_xlate_add(xl, "%u", compinfo->spis[0]);
+
+	return 1;
+}
+
+static struct xtables_match comp_mt_reg = {
+	.name          = "ipcomp",
+	.version       = XTABLES_VERSION,
+	.family        = NFPROTO_UNSPEC,
+	.size          = XT_ALIGN(sizeof(struct xt_ipcomp)),
+	.userspacesize = XT_ALIGN(sizeof(struct xt_ipcomp)),
+	.help          = comp_help,
+	.print         = comp_print,
+	.save          = comp_save,
+	.x6_parse      = comp_parse,
+	.x6_options    = comp_opts,
+	.xlate         = comp_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&comp_mt_reg);
+};
+
diff --git a/extensions/libxt_ipcomp.c.man b/extensions/libxt_ipcomp.c.man
new file mode 100644
index 0000000..f3b17d2
--- /dev/null
+++ b/extensions/libxt_ipcomp.c.man
@@ -0,0 +1,7 @@
+This module matches the parameters in IPcomp header of IPsec packets.
+.TP
+[\fB!\fP] \fB\-\-ipcompspi\fP \fIspi\fP[\fB:\fP\fIspi\fP]
+Matches IPcomp header CPI value.
+.TP
+\fB\-\-compres\fP
+Matches if the reserved field is filled with zero.
diff --git a/extensions/libxt_ipcomp.t b/extensions/libxt_ipcomp.t
new file mode 100644
index 0000000..8546ba9
--- /dev/null
+++ b/extensions/libxt_ipcomp.t
@@ -0,0 +1,3 @@
+:INPUT,OUTPUT
+-p ipcomp -m ipcomp --ipcompspi 18 -j DROP;=;OK
+-p ipcomp -m ipcomp ! --ipcompspi 18 -j ACCEPT;=;OK
diff --git a/extensions/libxt_ipcomp.txlate b/extensions/libxt_ipcomp.txlate
new file mode 100644
index 0000000..f9efe53
--- /dev/null
+++ b/extensions/libxt_ipcomp.txlate
@@ -0,0 +1,5 @@
+iptables-translate -t filter -A INPUT -m ipcomp --ipcompspi 0x12 -j ACCEPT
+nft add rule ip filter INPUT comp cpi 18 counter accept
+
+iptables-translate -t filter -A INPUT -m ipcomp ! --ipcompspi 0x12 -j ACCEPT
+nft add rule ip filter INPUT comp cpi != 18 counter accept
diff --git a/extensions/libxt_iprange.c b/extensions/libxt_iprange.c
new file mode 100644
index 0000000..8be2481
--- /dev/null
+++ b/extensions/libxt_iprange.c
@@ -0,0 +1,442 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/xt_iprange.h>
+
+struct ipt_iprange {
+	/* Inclusive: network order. */
+	__be32 min_ip, max_ip;
+};
+
+struct ipt_iprange_info {
+	struct ipt_iprange src;
+	struct ipt_iprange dst;
+
+	/* Flags from above */
+	uint8_t flags;
+};
+
+enum {
+	O_SRC_RANGE = 0,
+	O_DST_RANGE,
+};
+
+static void iprange_mt_help(void)
+{
+	printf(
+"iprange match options:\n"
+"[!] --src-range ip[-ip]    Match source IP in the specified range\n"
+"[!] --dst-range ip[-ip]    Match destination IP in the specified range\n");
+}
+
+static const struct xt_option_entry iprange_mt_opts[] = {
+	{.name = "src-range", .id = O_SRC_RANGE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "dst-range", .id = O_DST_RANGE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void
+iprange_parse_spec(const char *from, const char *to, union nf_inet_addr *range,
+		   uint8_t family, const char *optname)
+{
+	const char *spec[2] = {from, to};
+	struct in6_addr *ia6;
+	struct in_addr *ia4;
+	unsigned int i;
+
+	memset(range, 0, sizeof(union nf_inet_addr) * 2);
+
+	if (family == NFPROTO_IPV6) {
+		for (i = 0; i < ARRAY_SIZE(spec); ++i) {
+			ia6 = xtables_numeric_to_ip6addr(spec[i]);
+			if (ia6 == NULL)
+				xtables_param_act(XTF_BAD_VALUE, "iprange",
+					optname, spec[i]);
+			range[i].in6 = *ia6;
+		}
+	} else {
+		for (i = 0; i < ARRAY_SIZE(spec); ++i) {
+			ia4 = xtables_numeric_to_ipaddr(spec[i]);
+			if (ia4 == NULL)
+				xtables_param_act(XTF_BAD_VALUE, "iprange",
+					optname, spec[i]);
+			range[i].in = *ia4;
+		}
+	}
+}
+
+static void iprange_parse_range(const char *oarg, union nf_inet_addr *range,
+				uint8_t family, const char *optname)
+{
+	char *arg = strdup(oarg);
+	char *dash;
+
+	if (arg == NULL)
+		xtables_error(RESOURCE_PROBLEM, "strdup");
+	dash = strchr(arg, '-');
+	if (dash == NULL) {
+		iprange_parse_spec(arg, arg, range, family, optname);
+		free(arg);
+		return;
+	}
+
+	*dash = '\0';
+	iprange_parse_spec(arg, dash + 1, range, family, optname);
+	if (memcmp(&range[0], &range[1], sizeof(*range)) > 0)
+		fprintf(stderr, "xt_iprange: range %s-%s is reversed and "
+			"will never match\n", arg, dash + 1);
+	free(arg);
+}
+
+static void iprange_parse(struct xt_option_call *cb)
+{
+	struct ipt_iprange_info *info = cb->data;
+	union nf_inet_addr range[2];
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRC_RANGE:
+		info->flags |= IPRANGE_SRC;
+		if (cb->invert)
+			info->flags |= IPRANGE_SRC_INV;
+		iprange_parse_range(cb->arg, range,
+				    NFPROTO_IPV4, "--src-range");
+		info->src.min_ip = range[0].ip;
+		info->src.max_ip = range[1].ip;
+		break;
+	case O_DST_RANGE:
+		info->flags |= IPRANGE_DST;
+		if (cb->invert)
+			info->flags |= IPRANGE_DST_INV;
+		iprange_parse_range(cb->arg, range,
+				    NFPROTO_IPV4, "--dst-range");
+		info->dst.min_ip = range[0].ip;
+		info->dst.max_ip = range[1].ip;
+		break;
+	}
+}
+
+static void iprange_mt_parse(struct xt_option_call *cb, uint8_t nfproto)
+{
+	struct xt_iprange_mtinfo *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRC_RANGE:
+		iprange_parse_range(cb->arg, &info->src_min, nfproto,
+			"--src-range");
+		info->flags |= IPRANGE_SRC;
+		if (cb->invert)
+			info->flags |= IPRANGE_SRC_INV;
+		break;
+	case O_DST_RANGE:
+		iprange_parse_range(cb->arg, &info->dst_min, nfproto,
+			"--dst-range");
+		info->flags |= IPRANGE_DST;
+		if (cb->invert)
+			info->flags |= IPRANGE_DST_INV;
+		break;
+	}
+}
+
+static void iprange_mt4_parse(struct xt_option_call *cb)
+{
+	iprange_mt_parse(cb, NFPROTO_IPV4);
+}
+
+static void iprange_mt6_parse(struct xt_option_call *cb)
+{
+	iprange_mt_parse(cb, NFPROTO_IPV6);
+}
+
+static void iprange_mt_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM,
+			   "iprange match: You must specify `--src-range' or `--dst-range'");
+}
+
+static void
+print_iprange(const struct ipt_iprange *range)
+{
+	const unsigned char *byte_min, *byte_max;
+
+	byte_min = (const unsigned char *)&range->min_ip;
+	byte_max = (const unsigned char *)&range->max_ip;
+	printf(" %u.%u.%u.%u-%u.%u.%u.%u",
+		byte_min[0], byte_min[1], byte_min[2], byte_min[3],
+		byte_max[0], byte_max[1], byte_max[2], byte_max[3]);
+}
+
+static void iprange_print(const void *ip, const struct xt_entry_match *match,
+			  int numeric)
+{
+	const struct ipt_iprange_info *info = (const void *)match->data;
+
+	if (info->flags & IPRANGE_SRC) {
+		printf(" source IP range");
+		if (info->flags & IPRANGE_SRC_INV)
+			printf(" !");
+		print_iprange(&info->src);
+	}
+	if (info->flags & IPRANGE_DST) {
+		printf(" destination IP range");
+		if (info->flags & IPRANGE_DST_INV)
+			printf(" !");
+		print_iprange(&info->dst);
+	}
+}
+
+static void
+iprange_mt4_print(const void *ip, const struct xt_entry_match *match,
+		  int numeric)
+{
+	const struct xt_iprange_mtinfo *info = (const void *)match->data;
+
+	if (info->flags & IPRANGE_SRC) {
+		printf(" source IP range");
+		if (info->flags & IPRANGE_SRC_INV)
+			printf(" !");
+		/*
+		 * ipaddr_to_numeric() uses a static buffer, so cannot
+		 * combine the printf() calls.
+		 */
+		printf(" %s", xtables_ipaddr_to_numeric(&info->src_min.in));
+		printf("-%s", xtables_ipaddr_to_numeric(&info->src_max.in));
+	}
+	if (info->flags & IPRANGE_DST) {
+		printf(" destination IP range");
+		if (info->flags & IPRANGE_DST_INV)
+			printf(" !");
+		printf(" %s", xtables_ipaddr_to_numeric(&info->dst_min.in));
+		printf("-%s", xtables_ipaddr_to_numeric(&info->dst_max.in));
+	}
+}
+
+static void
+iprange_mt6_print(const void *ip, const struct xt_entry_match *match,
+		  int numeric)
+{
+	const struct xt_iprange_mtinfo *info = (const void *)match->data;
+
+	if (info->flags & IPRANGE_SRC) {
+		printf(" source IP range");
+		if (info->flags & IPRANGE_SRC_INV)
+			printf(" !");
+		/*
+		 * ipaddr_to_numeric() uses a static buffer, so cannot
+		 * combine the printf() calls.
+		 */
+		printf(" %s", xtables_ip6addr_to_numeric(&info->src_min.in6));
+		printf("-%s", xtables_ip6addr_to_numeric(&info->src_max.in6));
+	}
+	if (info->flags & IPRANGE_DST) {
+		printf(" destination IP range");
+		if (info->flags & IPRANGE_DST_INV)
+			printf(" !");
+		printf(" %s", xtables_ip6addr_to_numeric(&info->dst_min.in6));
+		printf("-%s", xtables_ip6addr_to_numeric(&info->dst_max.in6));
+	}
+}
+
+static void iprange_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ipt_iprange_info *info = (const void *)match->data;
+
+	if (info->flags & IPRANGE_SRC) {
+		if (info->flags & IPRANGE_SRC_INV)
+			printf(" !");
+		printf(" --src-range");
+		print_iprange(&info->src);
+	}
+	if (info->flags & IPRANGE_DST) {
+		if (info->flags & IPRANGE_DST_INV)
+			printf(" !");
+		printf(" --dst-range");
+		print_iprange(&info->dst);
+	}
+}
+
+static void iprange_mt4_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_iprange_mtinfo *info = (const void *)match->data;
+
+	if (info->flags & IPRANGE_SRC) {
+		if (info->flags & IPRANGE_SRC_INV)
+			printf(" !");
+		printf(" --src-range %s",
+		       xtables_ipaddr_to_numeric(&info->src_min.in));
+		printf("-%s", xtables_ipaddr_to_numeric(&info->src_max.in));
+	}
+	if (info->flags & IPRANGE_DST) {
+		if (info->flags & IPRANGE_DST_INV)
+			printf(" !");
+		printf(" --dst-range %s",
+		       xtables_ipaddr_to_numeric(&info->dst_min.in));
+		printf("-%s", xtables_ipaddr_to_numeric(&info->dst_max.in));
+	}
+}
+
+static void iprange_mt6_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_iprange_mtinfo *info = (const void *)match->data;
+
+	if (info->flags & IPRANGE_SRC) {
+		if (info->flags & IPRANGE_SRC_INV)
+			printf(" !");
+		printf(" --src-range %s",
+		       xtables_ip6addr_to_numeric(&info->src_min.in6));
+		printf("-%s", xtables_ip6addr_to_numeric(&info->src_max.in6));
+	}
+	if (info->flags & IPRANGE_DST) {
+		if (info->flags & IPRANGE_DST_INV)
+			printf(" !");
+		printf(" --dst-range %s",
+		       xtables_ip6addr_to_numeric(&info->dst_min.in6));
+		printf("-%s", xtables_ip6addr_to_numeric(&info->dst_max.in6));
+	}
+}
+
+static void
+print_iprange_xlate(const struct ipt_iprange *range,
+		    struct xt_xlate *xl)
+{
+	const unsigned char *byte_min, *byte_max;
+
+	byte_min = (const unsigned char *)&range->min_ip;
+	byte_max = (const unsigned char *)&range->max_ip;
+	xt_xlate_add(xl, " %u.%u.%u.%u-%u.%u.%u.%u ",
+		   byte_min[0], byte_min[1], byte_min[2], byte_min[3],
+		   byte_max[0], byte_max[1], byte_max[2], byte_max[3]);
+}
+
+static int iprange_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_mt_params *params)
+{
+	const struct ipt_iprange_info *info = (const void *)params->match->data;
+	char *space = "";
+
+	if (info->flags & IPRANGE_SRC) {
+		xt_xlate_add(xl, "ip saddr%s",
+			     info->flags & IPRANGE_SRC_INV ? " !=" : "");
+		print_iprange_xlate(&info->src, xl);
+		space = " ";
+	}
+	if (info->flags & IPRANGE_DST) {
+		xt_xlate_add(xl, "%sip daddr%s", space,
+			     info->flags & IPRANGE_DST_INV ? " !=" : "");
+		print_iprange_xlate(&info->dst, xl);
+	}
+
+	return 1;
+}
+
+static int iprange_mt4_xlate(struct xt_xlate *xl,
+			     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_iprange_mtinfo *info =
+		(const void *)params->match->data;
+	char *space = "";
+
+	if (info->flags & IPRANGE_SRC) {
+		xt_xlate_add(xl, "ip saddr%s %s",
+			     info->flags & IPRANGE_SRC_INV ? " !=" : "",
+			     xtables_ipaddr_to_numeric(&info->src_min.in));
+		xt_xlate_add(xl, "-%s",
+			     xtables_ipaddr_to_numeric(&info->src_max.in));
+		space = " ";
+	}
+	if (info->flags & IPRANGE_DST) {
+		xt_xlate_add(xl, "%sip daddr%s %s", space,
+			     info->flags & IPRANGE_DST_INV ? " !=" : "",
+			     xtables_ipaddr_to_numeric(&info->dst_min.in));
+		xt_xlate_add(xl, "-%s",
+			     xtables_ipaddr_to_numeric(&info->dst_max.in));
+	}
+
+	return 1;
+}
+
+static int iprange_mt6_xlate(struct xt_xlate *xl,
+			     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_iprange_mtinfo *info =
+		(const void *)params->match->data;
+	char *space = "";
+
+	if (info->flags & IPRANGE_SRC) {
+		xt_xlate_add(xl, "ip6 saddr%s %s",
+			     info->flags & IPRANGE_SRC_INV ? " !=" : "",
+			     xtables_ip6addr_to_numeric(&info->src_min.in6));
+		xt_xlate_add(xl, "-%s",
+			     xtables_ip6addr_to_numeric(&info->src_max.in6));
+		space = " ";
+	}
+	if (info->flags & IPRANGE_DST) {
+		xt_xlate_add(xl, "%sip6 daddr%s %s", space,
+			     info->flags & IPRANGE_DST_INV ? " !=" : "",
+			     xtables_ip6addr_to_numeric(&info->dst_min.in6));
+		xt_xlate_add(xl, "-%s",
+			     xtables_ip6addr_to_numeric(&info->dst_max.in6));
+	}
+
+	return 1;
+}
+
+static struct xtables_match iprange_mt_reg[] = {
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "iprange",
+		.revision      = 0,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct ipt_iprange_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct ipt_iprange_info)),
+		.help          = iprange_mt_help,
+		.x6_parse      = iprange_parse,
+		.x6_fcheck     = iprange_mt_check,
+		.print         = iprange_print,
+		.save          = iprange_save,
+		.x6_options    = iprange_mt_opts,
+		.xlate	       = iprange_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "iprange",
+		.revision      = 1,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_iprange_mtinfo)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_iprange_mtinfo)),
+		.help          = iprange_mt_help,
+		.x6_parse      = iprange_mt4_parse,
+		.x6_fcheck     = iprange_mt_check,
+		.print         = iprange_mt4_print,
+		.save          = iprange_mt4_save,
+		.x6_options    = iprange_mt_opts,
+		.xlate	       = iprange_mt4_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "iprange",
+		.revision      = 1,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_iprange_mtinfo)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_iprange_mtinfo)),
+		.help          = iprange_mt_help,
+		.x6_parse      = iprange_mt6_parse,
+		.x6_fcheck     = iprange_mt_check,
+		.print         = iprange_mt6_print,
+		.save          = iprange_mt6_save,
+		.x6_options    = iprange_mt_opts,
+		.xlate	       = iprange_mt6_xlate,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(iprange_mt_reg, ARRAY_SIZE(iprange_mt_reg));
+}
diff --git a/extensions/libxt_iprange.man b/extensions/libxt_iprange.man
new file mode 100644
index 0000000..9bbaac3
--- /dev/null
+++ b/extensions/libxt_iprange.man
@@ -0,0 +1,7 @@
+This matches on a given arbitrary range of IP addresses.
+.TP
+[\fB!\fP] \fB\-\-src\-range\fP \fIfrom\fP[\fB\-\fP\fIto\fP]
+Match source IP in the specified range.
+.TP
+[\fB!\fP] \fB\-\-dst\-range\fP \fIfrom\fP[\fB\-\fP\fIto\fP]
+Match destination IP in the specified range.
diff --git a/extensions/libxt_iprange.t b/extensions/libxt_iprange.t
new file mode 100644
index 0000000..6fd98be
--- /dev/null
+++ b/extensions/libxt_iprange.t
@@ -0,0 +1,11 @@
+:INPUT,FORWARD,OUTPUT
+-m iprange --src-range 1.1.1.1-1.1.1.10;=;OK
+-m iprange ! --src-range 1.1.1.1-1.1.1.10;=;OK
+-m iprange --dst-range 1.1.1.1-1.1.1.10;=;OK
+-m iprange ! --dst-range 1.1.1.1-1.1.1.10;=;OK
+# it shows -A INPUT -m iprange --src-range 1.1.1.1-1.1.1.1, should we support this?
+# ERROR: should fail: iptables -A INPUT -m iprange --src-range 1.1.1.1
+# -m iprange --src-range 1.1.1.1;;FAIL
+# ERROR: should fail: iptables -A INPUT -m iprange --dst-range 1.1.1.1
+#-m iprange --dst-range 1.1.1.1;;FAIL
+-m iprange;;FAIL
diff --git a/extensions/libxt_iprange.txlate b/extensions/libxt_iprange.txlate
new file mode 100644
index 0000000..999f4b7
--- /dev/null
+++ b/extensions/libxt_iprange.txlate
@@ -0,0 +1,14 @@
+iptables-translate -A INPUT -m iprange --src-range 192.168.25.149-192.168.25.151 -j ACCEPT
+nft add rule ip filter INPUT ip saddr 192.168.25.149-192.168.25.151 counter accept
+
+iptables-translate -A INPUT -m iprange --dst-range 192.168.25.149-192.168.25.151 -j ACCEPT
+nft add rule ip filter INPUT ip daddr 192.168.25.149-192.168.25.151 counter accept
+
+iptables-translate -A INPUT -m iprange --dst-range 3.3.3.3-6.6.6.6 --src-range 4.4.4.4-7.7.7.7 -j ACCEPT
+nft add rule ip filter INPUT ip saddr 4.4.4.4-7.7.7.7 ip daddr 3.3.3.3-6.6.6.6 counter accept
+
+ip6tables-translate -A INPUT -m iprange ! --dst-range ::2d01-::2d03 -j ACCEPT
+nft add rule ip6 filter INPUT ip6 daddr != ::2d01-::2d03 counter accept
+
+ip6tables-translate -A INPUT -m iprange ! --dst-range ::2d01-::2d03 --src-range ::2d01-::2d03 -j ACCEPT
+nft add rule ip6 filter INPUT ip6 saddr ::2d01-::2d03 ip6 daddr != ::2d01-::2d03 counter accept
diff --git a/extensions/libxt_ipvs.c b/extensions/libxt_ipvs.c
new file mode 100644
index 0000000..51952be
--- /dev/null
+++ b/extensions/libxt_ipvs.c
@@ -0,0 +1,284 @@
+/*
+ * Shared library add-on to iptables to add IPVS matching.
+ *
+ * Detailed doc is in the kernel module source net/netfilter/xt_ipvs.c
+ *
+ * Author: Hannes Eder <heder@google.com>
+ */
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/ip_vs.h>
+#include <linux/netfilter/xt_ipvs.h>
+
+enum {
+	/* For xt_ipvs: make sure this matches up with %XT_IPVS_*'s order */
+	O_IPVS = 0,
+	O_VPROTO,
+	O_VADDR,
+	O_VPORT,
+	O_VDIR,
+	O_VMETHOD,
+	O_VPORTCTL,
+};
+
+#define s struct xt_ipvs_mtinfo
+static const struct xt_option_entry ipvs_mt_opts[] = {
+	{.name = "ipvs", .id = O_IPVS, .type = XTTYPE_NONE,
+	 .flags = XTOPT_INVERT},
+	{.name = "vproto", .id = O_VPROTO, .type = XTTYPE_PROTOCOL,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, l4proto)},
+	{.name = "vaddr", .id = O_VADDR, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "vport", .id = O_VPORT, .type = XTTYPE_PORT,
+	 .flags = XTOPT_NBO | XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(s, vport)},
+	{.name = "vdir", .id = O_VDIR, .type = XTTYPE_STRING},
+	{.name = "vmethod", .id = O_VMETHOD, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "vportctl", .id = O_VPORTCTL, .type = XTTYPE_PORT,
+	 .flags = XTOPT_NBO | XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(s, vportctl)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void ipvs_mt_help(void)
+{
+	printf(
+"IPVS match options:\n"
+"[!] --ipvs                      packet belongs to an IPVS connection\n"
+"\n"
+"Any of the following options implies --ipvs (even negated)\n"
+"[!] --vproto protocol           VIP protocol to match; by number or name,\n"
+"                                e.g. \"tcp\"\n"
+"[!] --vaddr address[/mask]      VIP address to match\n"
+"[!] --vport port                VIP port to match; by number or name,\n"
+"                                e.g. \"http\"\n"
+"    --vdir {ORIGINAL|REPLY}     flow direction of packet\n"
+"[!] --vmethod {GATE|IPIP|MASQ}  IPVS forwarding method used\n"
+"[!] --vportctl port             VIP port of the controlling connection to\n"
+"                                match, e.g. 21 for FTP\n"
+		);
+}
+
+static void ipvs_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_ipvs_mtinfo *data = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_VADDR:
+		memcpy(&data->vaddr, &cb->val.haddr, sizeof(cb->val.haddr));
+		memcpy(&data->vmask, &cb->val.hmask, sizeof(cb->val.hmask));
+		break;
+	case O_VDIR:
+		if (strcasecmp(cb->arg, "ORIGINAL") == 0) {
+			data->bitmask |= XT_IPVS_DIR;
+			data->invert   &= ~XT_IPVS_DIR;
+		} else if (strcasecmp(cb->arg, "REPLY") == 0) {
+			data->bitmask |= XT_IPVS_DIR;
+			data->invert  |= XT_IPVS_DIR;
+		} else {
+			xtables_param_act(XTF_BAD_VALUE,
+					  "ipvs", "--vdir", cb->arg);
+		}
+		break;
+	case O_VMETHOD:
+		if (strcasecmp(cb->arg, "GATE") == 0)
+			data->fwd_method = IP_VS_CONN_F_DROUTE;
+		else if (strcasecmp(cb->arg, "IPIP") == 0)
+			data->fwd_method = IP_VS_CONN_F_TUNNEL;
+		else if (strcasecmp(cb->arg, "MASQ") == 0)
+			data->fwd_method = IP_VS_CONN_F_MASQ;
+		else
+			xtables_param_act(XTF_BAD_VALUE,
+					  "ipvs", "--vmethod", cb->arg);
+		break;
+	}
+	data->bitmask |= 1 << cb->entry->id;
+	if (cb->invert)
+		data->invert |= 1 << cb->entry->id;
+}
+
+static void ipvs_mt_check(struct xt_fcheck_call *cb)
+{
+	struct xt_ipvs_mtinfo *info = cb->data;
+
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM,
+			      "IPVS: At least one option is required");
+	if (info->bitmask & XT_IPVS_ONCE_MASK) {
+		if (info->invert & XT_IPVS_IPVS_PROPERTY)
+			xtables_error(PARAMETER_PROBLEM,
+				      "! --ipvs cannot be together with"
+				      " other options");
+		info->bitmask |= XT_IPVS_IPVS_PROPERTY;
+	}
+}
+
+/* Shamelessly copied from libxt_conntrack.c */
+static void ipvs_mt_dump_addr(const union nf_inet_addr *addr,
+			      const union nf_inet_addr *mask,
+			      unsigned int family, bool numeric)
+{
+	if (family == NFPROTO_IPV4) {
+		if (!numeric && addr->ip == 0) {
+			printf(" anywhere");
+			return;
+		}
+		if (numeric)
+			printf(" %s%s",
+			       xtables_ipaddr_to_numeric(&addr->in),
+			       xtables_ipmask_to_numeric(&mask->in));
+		else
+			printf(" %s%s",
+			       xtables_ipaddr_to_anyname(&addr->in),
+			       xtables_ipmask_to_numeric(&mask->in));
+	} else if (family == NFPROTO_IPV6) {
+		if (!numeric && addr->ip6[0] == 0 && addr->ip6[1] == 0 &&
+		    addr->ip6[2] == 0 && addr->ip6[3] == 0) {
+			printf(" anywhere");
+			return;
+		}
+		if (numeric)
+			printf(" %s%s",
+			       xtables_ip6addr_to_numeric(&addr->in6),
+			       xtables_ip6mask_to_numeric(&mask->in6));
+		else
+			printf(" %s%s",
+			       xtables_ip6addr_to_anyname(&addr->in6),
+			       xtables_ip6mask_to_numeric(&mask->in6));
+	}
+}
+
+static void ipvs_mt_dump(const void *ip, const struct xt_ipvs_mtinfo *data,
+			 unsigned int family, bool numeric, const char *prefix)
+{
+	if (data->bitmask == XT_IPVS_IPVS_PROPERTY) {
+		if (data->invert & XT_IPVS_IPVS_PROPERTY)
+			printf(" !");
+		printf(" %sipvs", prefix);
+	}
+
+	if (data->bitmask & XT_IPVS_PROTO) {
+		if (data->invert & XT_IPVS_PROTO)
+			printf(" !");
+		printf(" %svproto %u", prefix, data->l4proto);
+	}
+
+	if (data->bitmask & XT_IPVS_VADDR) {
+		if (data->invert & XT_IPVS_VADDR)
+			printf(" !");
+
+		printf(" %svaddr", prefix);
+		ipvs_mt_dump_addr(&data->vaddr, &data->vmask, family, numeric);
+	}
+
+	if (data->bitmask & XT_IPVS_VPORT) {
+		if (data->invert & XT_IPVS_VPORT)
+			printf(" !");
+
+		printf(" %svport %u", prefix, ntohs(data->vport));
+	}
+
+	if (data->bitmask & XT_IPVS_DIR) {
+		if (data->invert & XT_IPVS_DIR)
+			printf(" %svdir REPLY", prefix);
+		else
+			printf(" %svdir ORIGINAL", prefix);
+	}
+
+	if (data->bitmask & XT_IPVS_METHOD) {
+		if (data->invert & XT_IPVS_METHOD)
+			printf(" !");
+
+		printf(" %svmethod", prefix);
+		switch (data->fwd_method) {
+		case IP_VS_CONN_F_DROUTE:
+			printf(" GATE");
+			break;
+		case IP_VS_CONN_F_TUNNEL:
+			printf(" IPIP");
+			break;
+		case IP_VS_CONN_F_MASQ:
+			printf(" MASQ");
+			break;
+		default:
+			/* Hu? */
+			printf(" UNKNOWN");
+			break;
+		}
+	}
+
+	if (data->bitmask & XT_IPVS_VPORTCTL) {
+		if (data->invert & XT_IPVS_VPORTCTL)
+			printf(" !");
+
+		printf(" %svportctl %u", prefix, ntohs(data->vportctl));
+	}
+}
+
+static void ipvs_mt4_print(const void *ip, const struct xt_entry_match *match,
+			   int numeric)
+{
+	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
+	ipvs_mt_dump(ip, data, NFPROTO_IPV4, numeric, "");
+}
+
+static void ipvs_mt6_print(const void *ip, const struct xt_entry_match *match,
+			   int numeric)
+{
+	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
+	ipvs_mt_dump(ip, data, NFPROTO_IPV6, numeric, "");
+}
+
+static void ipvs_mt4_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
+	ipvs_mt_dump(ip, data, NFPROTO_IPV4, true, "--");
+}
+
+static void ipvs_mt6_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
+	ipvs_mt_dump(ip, data, NFPROTO_IPV6, true, "--");
+}
+
+static struct xtables_match ipvs_matches_reg[] = {
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "ipvs",
+		.revision      = 0,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
+		.help          = ipvs_mt_help,
+		.x6_parse      = ipvs_mt_parse,
+		.x6_fcheck     = ipvs_mt_check,
+		.print         = ipvs_mt4_print,
+		.save          = ipvs_mt4_save,
+		.x6_options    = ipvs_mt_opts,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "ipvs",
+		.revision      = 0,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
+		.help          = ipvs_mt_help,
+		.x6_parse      = ipvs_mt_parse,
+		.x6_fcheck     = ipvs_mt_check,
+		.print         = ipvs_mt6_print,
+		.save          = ipvs_mt6_save,
+		.x6_options    = ipvs_mt_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(ipvs_matches_reg,
+				 ARRAY_SIZE(ipvs_matches_reg));
+}
diff --git a/extensions/libxt_ipvs.man b/extensions/libxt_ipvs.man
new file mode 100644
index 0000000..db9bc66
--- /dev/null
+++ b/extensions/libxt_ipvs.man
@@ -0,0 +1,24 @@
+Match IPVS connection properties.
+.TP
+[\fB!\fP] \fB\-\-ipvs\fP
+packet belongs to an IPVS connection
+.TP
+Any of the following options implies \-\-ipvs (even negated)
+.TP
+[\fB!\fP] \fB\-\-vproto\fP \fIprotocol\fP
+VIP protocol to match; by number or name, e.g. "tcp"
+.TP
+[\fB!\fP] \fB\-\-vaddr\fP \fIaddress\fP[\fB/\fP\fImask\fP]
+VIP address to match
+.TP
+[\fB!\fP] \fB\-\-vport\fP \fIport\fP
+VIP port to match; by number or name, e.g. "http"
+.TP
+\fB\-\-vdir\fP {\fBORIGINAL\fP|\fBREPLY\fP}
+flow direction of packet
+.TP
+[\fB!\fP] \fB\-\-vmethod\fP {\fBGATE\fP|\fBIPIP\fP|\fBMASQ\fP}
+IPVS forwarding method used
+.TP
+[\fB!\fP] \fB\-\-vportctl\fP \fIport\fP
+VIP port of the controlling connection to match, e.g. 21 for FTP
diff --git a/extensions/libxt_ipvs.t b/extensions/libxt_ipvs.t
new file mode 100644
index 0000000..c2acc66
--- /dev/null
+++ b/extensions/libxt_ipvs.t
@@ -0,0 +1,20 @@
+:INPUT,FORWARD,OUTPUT
+-m ipvs --ipvs;=;OK
+-m ipvs ! --ipvs;=;OK
+-m ipvs --vproto tcp;-m ipvs --vproto 6;OK
+-m ipvs ! --vproto TCP;-m ipvs ! --vproto 6;OK
+-m ipvs --vproto 23;=;OK
+-m ipvs --vaddr 1.2.3.4;=;OK
+-m ipvs ! --vaddr 1.2.3.4/255.255.255.0;-m ipvs ! --vaddr 1.2.3.4/24;OK
+-m ipvs --vport http;-m ipvs --vport 80;OK
+-m ipvs ! --vport ssh;-m ipvs ! --vport 22;OK
+-m ipvs --vport 22;=;OK
+-m ipvs ! --vport 443;=;OK
+-m ipvs --vdir ORIGINAL;=;OK
+-m ipvs --vdir REPLY;=;OK
+-m ipvs --vmethod GATE;=;OK
+-m ipvs ! --vmethod IPIP;=;OK
+-m ipvs --vmethod MASQ;=;OK
+-m ipvs --vportctl 21;=;OK
+-m ipvs ! --vportctl 21;=;OK
+-m ipvs --vproto 6 --vaddr 1.2.3.4/16 --vport 22 --vdir ORIGINAL --vmethod GATE;=;OK
diff --git a/extensions/libxt_length.c b/extensions/libxt_length.c
new file mode 100644
index 0000000..04eac4a
--- /dev/null
+++ b/extensions/libxt_length.c
@@ -0,0 +1,91 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_length.h>
+
+enum {
+	O_LENGTH = 0,
+};
+
+static void length_help(void)
+{
+	printf(
+"length match options:\n"
+"[!] --length length[:length]    Match packet length against value or range\n"
+"                                of values (inclusive)\n");
+}
+  
+static const struct xt_option_entry length_opts[] = {
+	{.name = "length", .id = O_LENGTH, .type = XTTYPE_UINT16RC,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void length_parse(struct xt_option_call *cb)
+{
+	struct xt_length_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	info->min = cb->val.u16_range[0];
+	info->max = cb->val.u16_range[0];
+	if (cb->nvals >= 2)
+		info->max = cb->val.u16_range[1];
+	if (cb->invert)
+		info->invert = 1;
+}
+
+static void
+length_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_length_info *info = (void *)match->data;
+
+	printf(" length %s", info->invert ? "!" : "");
+	if (info->min == info->max)
+		printf("%u", info->min);
+	else
+		printf("%u:%u", info->min, info->max);
+}
+
+static void length_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_length_info *info = (void *)match->data;
+
+	printf("%s --length ", info->invert ? " !" : "");
+	if (info->min == info->max)
+		printf("%u", info->min);
+	else
+		printf("%u:%u", info->min, info->max);
+}
+
+static int length_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_mt_params *params)
+{
+	const struct xt_length_info *info = (void *)params->match->data;
+
+	xt_xlate_add(xl, "meta length %s", info->invert ? "!= " : "");
+	if (info->min == info->max)
+		xt_xlate_add(xl, "%u", info->min);
+	else
+		xt_xlate_add(xl, "%u-%u", info->min, info->max);
+
+	return 1;
+}
+
+
+static struct xtables_match length_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "length",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_length_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_length_info)),
+	.help		= length_help,
+	.print		= length_print,
+	.save		= length_save,
+	.x6_parse	= length_parse,
+	.x6_options	= length_opts,
+	.xlate		= length_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&length_match);
+}
diff --git a/extensions/libxt_length.man b/extensions/libxt_length.man
new file mode 100644
index 0000000..07b6ea6
--- /dev/null
+++ b/extensions/libxt_length.man
@@ -0,0 +1,5 @@
+This module matches the length of the layer-3 payload (e.g. layer-4 packet)
+of a packet against a specific value
+or range of values.
+.TP
+[\fB!\fP] \fB\-\-length\fP \fIlength\fP[\fB:\fP\fIlength\fP]
diff --git a/extensions/libxt_length.t b/extensions/libxt_length.t
new file mode 100644
index 0000000..0b6624e
--- /dev/null
+++ b/extensions/libxt_length.t
@@ -0,0 +1,10 @@
+:INPUT,FORWARD,OUTPUT
+-m length --length 1;=;OK
+-m length --length :2;-m length --length 0:2;OK
+-m length --length 0:3;=;OK
+-m length --length 4:;=;OK
+-m length --length 0:65535;=;OK
+-m length ! --length 0:65535;=;OK
+-m length --length 0:65536;;FAIL
+-m length --length -1:65535;;FAIL
+-m length;;FAIL
diff --git a/extensions/libxt_length.txlate b/extensions/libxt_length.txlate
new file mode 100644
index 0000000..e777c26
--- /dev/null
+++ b/extensions/libxt_length.txlate
@@ -0,0 +1,11 @@
+iptables-translate -A INPUT -p icmp -m length --length 86:0xffff -j DROP
+nft add rule ip filter INPUT ip protocol icmp meta length 86-65535 counter drop
+
+iptables-translate -A INPUT -p udp -m length --length :400
+nft add rule ip filter INPUT ip protocol udp meta length 0-400 counter
+
+iptables-translate -A INPUT -p udp -m length --length 40
+nft add rule ip filter INPUT ip protocol udp meta length 40 counter
+
+iptables-translate -A INPUT -p udp -m length ! --length 40
+nft add rule ip filter INPUT ip protocol udp meta length != 40 counter
diff --git a/extensions/libxt_limit.c b/extensions/libxt_limit.c
new file mode 100644
index 0000000..1b32465
--- /dev/null
+++ b/extensions/libxt_limit.c
@@ -0,0 +1,293 @@
+/* Shared library add-on to iptables to add limit support.
+ *
+ * Jérôme de Vivie   <devivie@info.enserb.u-bordeaux.fr>
+ * Hervé Eychenne    <rv@wallfire.org>
+ */
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#define _ISOC99_SOURCE 1
+#include <errno.h>
+#include <getopt.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/xt_limit.h>
+#include "iptables/nft-bridge.h"
+
+#define XT_LIMIT_AVG	"3/hour"
+#define XT_LIMIT_BURST	5
+
+enum {
+	O_LIMIT = 0,
+	O_BURST,
+};
+
+static void limit_help(void)
+{
+	printf(
+"limit match options:\n"
+"--limit avg			max average match rate: default "XT_LIMIT_AVG"\n"
+"                                [Packets per second unless followed by \n"
+"                                /sec /minute /hour /day postfixes]\n"
+"--limit-burst number		number to match in a burst, default %u\n",
+XT_LIMIT_BURST);
+}
+
+static const struct xt_option_entry limit_opts[] = {
+	{.name = "limit", .id = O_LIMIT, .type = XTTYPE_STRING},
+	{.name = "limit-burst", .id = O_BURST, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_rateinfo, burst),
+	 .min = 0, .max = 10000},
+	XTOPT_TABLEEND,
+};
+
+static
+int parse_rate(const char *rate, uint32_t *val)
+{
+	const char *delim;
+	uint32_t r;
+	uint32_t mult = 1;  /* Seconds by default. */
+
+	delim = strchr(rate, '/');
+	if (delim) {
+		if (strlen(delim+1) == 0)
+			return 0;
+
+		if (strncasecmp(delim+1, "second", strlen(delim+1)) == 0)
+			mult = 1;
+		else if (strncasecmp(delim+1, "minute", strlen(delim+1)) == 0)
+			mult = 60;
+		else if (strncasecmp(delim+1, "hour", strlen(delim+1)) == 0)
+			mult = 60*60;
+		else if (strncasecmp(delim+1, "day", strlen(delim+1)) == 0)
+			mult = 24*60*60;
+		else
+			return 0;
+	}
+	r = atoi(rate);
+	if (!r)
+		return 0;
+
+	*val = XT_LIMIT_SCALE * mult / r;
+	if (*val == 0)
+		/*
+		 * The rate maps to infinity. (1/day is the minimum they can
+		 * specify, so we are ok at that end).
+		 */
+		xtables_error(PARAMETER_PROBLEM, "Rate too fast \"%s\"\n", rate);
+	return 1;
+}
+
+static void limit_init(struct xt_entry_match *m)
+{
+	struct xt_rateinfo *r = (struct xt_rateinfo *)m->data;
+
+	parse_rate(XT_LIMIT_AVG, &r->avg);
+	r->burst = XT_LIMIT_BURST;
+
+}
+
+/* FIXME: handle overflow:
+	if (r->avg*r->burst/r->burst != r->avg)
+		xtables_error(PARAMETER_PROBLEM,
+			   "Sorry: burst too large for that avg rate.\n");
+*/
+
+static void limit_parse(struct xt_option_call *cb)
+{
+	struct xt_rateinfo *r = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_LIMIT:
+		if (!parse_rate(cb->arg, &r->avg))
+			xtables_error(PARAMETER_PROBLEM,
+				   "bad rate \"%s\"'", cb->arg);
+		break;
+	}
+	if (cb->invert)
+		xtables_error(PARAMETER_PROBLEM,
+			   "limit does not support invert");
+}
+
+static const struct rates
+{
+	const char *name;
+	uint32_t mult;
+} rates[] = { { "day", XT_LIMIT_SCALE*24*60*60 },
+	      { "hour", XT_LIMIT_SCALE*60*60 },
+	      { "min", XT_LIMIT_SCALE*60 },
+	      { "sec", XT_LIMIT_SCALE } };
+
+static void print_rate(uint32_t period)
+{
+	unsigned int i;
+
+	if (period == 0) {
+		printf(" %f", INFINITY);
+		return;
+	}
+
+	for (i = 1; i < ARRAY_SIZE(rates); ++i)
+		if (period > rates[i].mult
+            || rates[i].mult/period < rates[i].mult%period)
+			break;
+
+	printf(" %u/%s", rates[i-1].mult / period, rates[i-1].name);
+}
+
+static void
+limit_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_rateinfo *r = (const void *)match->data;
+	printf(" limit: avg"); print_rate(r->avg);
+	printf(" burst %u", r->burst);
+}
+
+static void limit_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_rateinfo *r = (const void *)match->data;
+
+	printf(" --limit"); print_rate(r->avg);
+	if (r->burst != XT_LIMIT_BURST)
+		printf(" --limit-burst %u", r->burst);
+}
+
+static const struct rates rates_xlate[] = {
+	{ "day",	XT_LIMIT_SCALE * 24 * 60 * 60 },
+	{ "hour",	XT_LIMIT_SCALE * 60 * 60 },
+	{ "minute",	XT_LIMIT_SCALE * 60 },
+	{ "second",	XT_LIMIT_SCALE }
+};
+
+static void print_rate_xlate(uint32_t period, struct xt_xlate *xl)
+{
+	unsigned int i;
+
+	if (period == 0) {
+		xt_xlate_add(xl, " %f", INFINITY);
+		return;
+	}
+
+	for (i = 1; i < ARRAY_SIZE(rates); ++i)
+		if (period > rates_xlate[i].mult ||
+		    rates_xlate[i].mult / period < rates_xlate[i].mult % period)
+			break;
+
+	xt_xlate_add(xl, " %u/%s", rates_xlate[i - 1].mult / period,
+		   rates_xlate[i - 1].name);
+}
+
+static int limit_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_rateinfo *r = (const void *)params->match->data;
+
+	xt_xlate_add(xl, "limit rate");
+	print_rate_xlate(r->avg, xl);
+	if (r->burst != 0)
+		xt_xlate_add(xl, " burst %u packets", r->burst);
+
+	return 1;
+}
+
+static int limit_xlate_eb(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	limit_xlate(xl, params);
+	xt_xlate_add(xl, " ");
+	return 1;
+}
+
+#define FLAG_LIMIT		0x01
+#define FLAG_LIMIT_BURST	0x02
+#define ARG_LIMIT		'1'
+#define ARG_LIMIT_BURST		'2'
+
+static int brlimit_parse(int c, char **argv, int invert, unsigned int *flags,
+			 const void *entry, struct xt_entry_match **match)
+{
+	struct xt_rateinfo *r = (struct xt_rateinfo *)(*match)->data;
+	uintmax_t num;
+
+	switch (c) {
+	case ARG_LIMIT:
+		EBT_CHECK_OPTION(flags, FLAG_LIMIT);
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Unexpected `!' after --limit");
+		if (!parse_rate(optarg, &r->avg))
+			xtables_error(PARAMETER_PROBLEM,
+				      "bad rate `%s'", optarg);
+		break;
+	case ARG_LIMIT_BURST:
+		EBT_CHECK_OPTION(flags, FLAG_LIMIT_BURST);
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Unexpected `!' after --limit-burst");
+		if (!xtables_strtoul(optarg, NULL, &num, 0, 10000))
+			xtables_error(PARAMETER_PROBLEM,
+				      "bad --limit-burst `%s'", optarg);
+		r->burst = num;
+		break;
+	default:
+		return 0;
+	}
+
+	return 1;
+}
+
+static void brlimit_print(const void *ip, const struct xt_entry_match *match,
+			  int numeric)
+{
+	const struct xt_rateinfo *r = (struct xt_rateinfo *)match->data;
+
+	printf("--limit");
+	print_rate(r->avg);
+	printf(" --limit-burst %u ", r->burst);
+}
+
+static const struct option brlimit_opts[] =
+{
+	{ .name = "limit",	.has_arg = true,	.val = ARG_LIMIT },
+	{ .name = "limit-burst",.has_arg = true,	.val = ARG_LIMIT_BURST },
+	XT_GETOPT_TABLEEND,
+};
+
+static struct xtables_match limit_match[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "limit",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_rateinfo)),
+		.userspacesize	= offsetof(struct xt_rateinfo, prev),
+		.help		= limit_help,
+		.init		= limit_init,
+		.x6_parse	= limit_parse,
+		.print		= limit_print,
+		.save		= limit_save,
+		.x6_options	= limit_opts,
+		.xlate		= limit_xlate,
+	},
+	{
+		.family		= NFPROTO_BRIDGE,
+		.name		= "limit",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_rateinfo)),
+		.userspacesize	= offsetof(struct xt_rateinfo, prev),
+		.help		= limit_help,
+		.init		= limit_init,
+		.parse		= brlimit_parse,
+		.print		= brlimit_print,
+		.extra_opts	= brlimit_opts,
+		.xlate		= limit_xlate_eb,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(limit_match, ARRAY_SIZE(limit_match));
+}
diff --git a/extensions/libxt_limit.man b/extensions/libxt_limit.man
new file mode 100644
index 0000000..6fb94cc
--- /dev/null
+++ b/extensions/libxt_limit.man
@@ -0,0 +1,18 @@
+This module matches at a limited rate using a token bucket filter.
+A rule using this extension will match until this limit is reached.
+It can be used in combination with the
+.B LOG
+target to give limited logging, for example.
+.PP
+xt_limit has no negation support - you will have to use \-m hashlimit !
+\-\-hashlimit \fIrate\fP in this case whilst omitting \-\-hashlimit\-mode.
+.TP
+\fB\-\-limit\fP \fIrate\fP[\fB/second\fP|\fB/minute\fP|\fB/hour\fP|\fB/day\fP]
+Maximum average matching rate: specified as a number, with an optional
+`/second', `/minute', `/hour', or `/day' suffix; the default is
+3/hour.
+.TP
+\fB\-\-limit\-burst\fP \fInumber\fP
+Maximum initial number of packets to match: this number gets
+recharged by one every time the limit specified above is not reached,
+up to this number; the default is 5.
diff --git a/extensions/libxt_limit.t b/extensions/libxt_limit.t
new file mode 100644
index 0000000..b0af653
--- /dev/null
+++ b/extensions/libxt_limit.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m limit --limit 1/sec;=;OK
+-m limit --limit 1/min;=;OK
+-m limit --limit 1000/hour;=;OK
+-m limit --limit 1000/day;=;OK
+-m limit --limit 1/sec --limit-burst 1;=;OK
diff --git a/extensions/libxt_limit.txlate b/extensions/libxt_limit.txlate
new file mode 100644
index 0000000..df9ed2d
--- /dev/null
+++ b/extensions/libxt_limit.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A INPUT -m limit --limit 3/m --limit-burst 3
+nft add rule ip filter INPUT limit rate 3/minute burst 3 packets counter
+
+iptables-translate -A INPUT -m limit --limit 10/s --limit-burst 5
+nft add rule ip filter INPUT limit rate 10/second burst 5 packets counter
+
+iptables-translate -A INPUT -m limit --limit 10/s --limit-burst 0
+nft add rule ip filter INPUT limit rate 10/second counter
diff --git a/extensions/libxt_mac.c b/extensions/libxt_mac.c
new file mode 100644
index 0000000..b90eef2
--- /dev/null
+++ b/extensions/libxt_mac.c
@@ -0,0 +1,102 @@
+#include <stdio.h>
+#if defined(__GLIBC__) && __GLIBC__ == 2
+#include <net/ethernet.h>
+#else
+#include <linux/if_ether.h>
+#endif
+#include <xtables.h>
+#include <linux/netfilter/xt_mac.h>
+
+enum {
+	O_MAC = 0,
+};
+
+static void mac_help(void)
+{
+	printf(
+"mac match options:\n"
+"[!] --mac-source XX:XX:XX:XX:XX:XX\n"
+"				Match source MAC address\n");
+}
+
+#define s struct xt_mac_info
+static const struct xt_option_entry mac_opts[] = {
+	{.name = "mac-source", .id = O_MAC, .type = XTTYPE_ETHERMAC,
+	 .flags = XTOPT_MAND | XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(s, srcaddr)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void mac_parse(struct xt_option_call *cb)
+{
+	struct xt_mac_info *macinfo = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->invert)
+		macinfo->invert = 1;
+}
+
+static void
+mac_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_mac_info *info = (void *)match->data;
+
+	printf(" MAC");
+
+	if (info->invert)
+		printf(" !");
+
+	xtables_print_mac(info->srcaddr);
+}
+
+static void mac_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_mac_info *info = (void *)match->data;
+
+	if (info->invert)
+		printf(" !");
+
+	printf(" --mac-source ");
+	xtables_print_mac(info->srcaddr);
+}
+
+static void print_mac_xlate(const unsigned char *macaddress,
+			    struct xt_xlate *xl)
+{
+	unsigned int i;
+
+	xt_xlate_add(xl, "%02x", macaddress[0]);
+	for (i = 1; i < ETH_ALEN; ++i)
+		xt_xlate_add(xl, ":%02x", macaddress[i]);
+}
+
+static int mac_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_mac_info *info = (void *)params->match->data;
+
+	xt_xlate_add(xl, "ether saddr%s ", info->invert ? " !=" : "");
+	print_mac_xlate(info->srcaddr, xl);
+
+	return 1;
+}
+
+static struct xtables_match mac_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "mac",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_mac_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_mac_info)),
+	.help		= mac_help,
+	.x6_parse	= mac_parse,
+	.print		= mac_print,
+	.save		= mac_save,
+	.x6_options	= mac_opts,
+	.xlate		= mac_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&mac_match);
+}
diff --git a/extensions/libxt_mac.man b/extensions/libxt_mac.man
new file mode 100644
index 0000000..66072a2
--- /dev/null
+++ b/extensions/libxt_mac.man
@@ -0,0 +1,10 @@
+.TP
+[\fB!\fP] \fB\-\-mac\-source\fP \fIaddress\fP
+Match source MAC address.  It must be of the form XX:XX:XX:XX:XX:XX.
+Note that this only makes sense for packets coming from an Ethernet device
+and entering the
+.BR PREROUTING ,
+.B FORWARD
+or
+.B INPUT
+chains.
diff --git a/extensions/libxt_mac.t b/extensions/libxt_mac.t
new file mode 100644
index 0000000..a5ec81d
--- /dev/null
+++ b/extensions/libxt_mac.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD
+-m mac --mac-source 42:01:02:03:04:05;=;OK
+-m mac --mac-source 42:01:02:03:04;=;FAIL
+-m mac --mac-source 42:01:02:03:04:05:06;=;FAIL
+-m mac;;FAIL
diff --git a/extensions/libxt_mac.txlate b/extensions/libxt_mac.txlate
new file mode 100644
index 0000000..08696f3
--- /dev/null
+++ b/extensions/libxt_mac.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A INPUT -m mac --mac-source 0a:12:3e:4f:b2:c6 -j DROP
+nft add rule ip filter INPUT ether saddr 0a:12:3e:4f:b2:c6 counter drop
+
+iptables-translate -A INPUT -p tcp --dport 80 -m mac --mac-source 0a:12:3e:4f:b2:c6 -j ACCEPT
+nft add rule ip filter INPUT tcp dport 80 ether saddr 0a:12:3e:4f:b2:c6 counter accept
diff --git a/extensions/libxt_mark.c b/extensions/libxt_mark.c
new file mode 100644
index 0000000..134ad43
--- /dev/null
+++ b/extensions/libxt_mark.c
@@ -0,0 +1,174 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_mark.h>
+
+struct xt_mark_info {
+	unsigned long mark, mask;
+	uint8_t invert;
+};
+
+enum {
+	O_MARK = 0,
+};
+
+static void mark_mt_help(void)
+{
+	printf(
+"mark match options:\n"
+"[!] --mark value[/mask]    Match nfmark value with optional mask\n");
+}
+
+static const struct xt_option_entry mark_mt_opts[] = {
+	{.name = "mark", .id = O_MARK, .type = XTTYPE_MARKMASK32,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void mark_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_mark_mtinfo1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->invert)
+		info->invert = true;
+	info->mark = cb->val.mark;
+	info->mask = cb->val.mask;
+}
+
+static void mark_parse(struct xt_option_call *cb)
+{
+	struct xt_mark_info *markinfo = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->invert)
+		markinfo->invert = 1;
+	markinfo->mark = cb->val.mark;
+	markinfo->mask = cb->val.mask;
+}
+
+static void
+mark_mt_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_mark_mtinfo1 *info = (const void *)match->data;
+
+	printf(" mark match");
+	if (info->invert)
+		printf(" !");
+
+	xtables_print_mark_mask(info->mark, info->mask);
+}
+
+static void
+mark_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_mark_info *info = (const void *)match->data;
+
+	printf(" MARK match");
+
+	if (info->invert)
+		printf(" !");
+
+	xtables_print_mark_mask(info->mark, info->mask);
+}
+
+static void mark_mt_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_mark_mtinfo1 *info = (const void *)match->data;
+
+	if (info->invert)
+		printf(" !");
+
+	printf(" --mark");
+	xtables_print_mark_mask(info->mark, info->mask);
+}
+
+static void
+mark_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_mark_info *info = (const void *)match->data;
+
+	if (info->invert)
+		printf(" !");
+
+	printf(" --mark");
+	xtables_print_mark_mask(info->mark, info->mask);
+}
+
+static void
+print_mark_xlate(struct xt_xlate *xl, unsigned int mark,
+		 unsigned int mask, uint32_t op)
+{
+	if (mask != 0xffffffffU)
+		xt_xlate_add(xl, " and 0x%x %s 0x%x", mask,
+			   op == XT_OP_EQ ? "==" : "!=", mark);
+	else
+		xt_xlate_add(xl, " %s0x%x",
+			   op == XT_OP_EQ ? "" : "!= ", mark);
+}
+
+static int mark_mt_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_mt_params *params)
+{
+	const struct xt_mark_mtinfo1 *info = (const void *)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (info->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "mark");
+	print_mark_xlate(xl, info->mark, info->mask, op);
+
+	return 1;
+}
+
+static int mark_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_mark_info *info = (const void *)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (info->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "mark");
+	print_mark_xlate(xl, info->mark, info->mask, op);
+
+	return 1;
+}
+
+static struct xtables_match mark_mt_reg[] = {
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "mark",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_mark_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_mark_info)),
+		.help          = mark_mt_help,
+		.print         = mark_print,
+		.save          = mark_save,
+		.x6_parse      = mark_parse,
+		.x6_options    = mark_mt_opts,
+		.xlate	       = mark_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "mark",
+		.revision      = 1,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_mark_mtinfo1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_mark_mtinfo1)),
+		.help          = mark_mt_help,
+		.print         = mark_mt_print,
+		.save          = mark_mt_save,
+		.x6_parse      = mark_mt_parse,
+		.x6_options    = mark_mt_opts,
+		.xlate	       = mark_mt_xlate,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(mark_mt_reg, ARRAY_SIZE(mark_mt_reg));
+}
diff --git a/extensions/libxt_mark.man b/extensions/libxt_mark.man
new file mode 100644
index 0000000..264b17d
--- /dev/null
+++ b/extensions/libxt_mark.man
@@ -0,0 +1,9 @@
+This module matches the netfilter mark field associated with a packet
+(which can be set using the
+.B MARK
+target below).
+.TP
+[\fB!\fP] \fB\-\-mark\fP \fIvalue\fP[\fB/\fP\fImask\fP]
+Matches packets with the given unsigned mark value (if a \fImask\fP is
+specified, this is logically ANDed with the \fImask\fP before the
+comparison).
diff --git a/extensions/libxt_mark.t b/extensions/libxt_mark.t
new file mode 100644
index 0000000..7c00537
--- /dev/null
+++ b/extensions/libxt_mark.t
@@ -0,0 +1,7 @@
+:INPUT,FORWARD,OUTPUT
+-m mark --mark 0xfeedcafe/0xfeedcafe;=;OK
+-m mark --mark 0;=;OK
+-m mark --mark 4294967295;-m mark --mark 0xffffffff;OK
+-m mark --mark 4294967296;;FAIL
+-m mark --mark -1;;FAIL
+-m mark;;FAIL
diff --git a/extensions/libxt_mark.txlate b/extensions/libxt_mark.txlate
new file mode 100644
index 0000000..6bfb524
--- /dev/null
+++ b/extensions/libxt_mark.txlate
@@ -0,0 +1,5 @@
+iptables-translate -I INPUT -p tcp -m mark ! --mark 0xa/0xa
+nft insert rule ip filter INPUT ip protocol tcp mark and 0xa != 0xa counter
+
+iptables-translate -I INPUT -p tcp -m mark ! --mark 0x1
+nft insert rule ip filter INPUT ip protocol tcp mark != 0x1 counter
diff --git a/extensions/libxt_multiport.c b/extensions/libxt_multiport.c
new file mode 100644
index 0000000..07ad4cf
--- /dev/null
+++ b/extensions/libxt_multiport.c
@@ -0,0 +1,640 @@
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h/ip6_tables.h */
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter/xt_multiport.h>
+
+enum {
+	O_SOURCE_PORTS = 0,
+	O_DEST_PORTS,
+	O_SD_PORTS,
+	F_SOURCE_PORTS = 1 << O_SOURCE_PORTS,
+	F_DEST_PORTS   = 1 << O_DEST_PORTS,
+	F_SD_PORTS     = 1 << O_SD_PORTS,
+	F_ANY          = F_SOURCE_PORTS | F_DEST_PORTS | F_SD_PORTS,
+};
+
+/* Function which prints out usage message. */
+static void multiport_help(void)
+{
+	printf(
+"multiport match options:\n"
+" --source-ports port[,port,port...]\n"
+" --sports ...\n"
+"				match source port(s)\n"
+" --destination-ports port[,port,port...]\n"
+" --dports ...\n"
+"				match destination port(s)\n"
+" --ports port[,port,port]\n"
+"				match both source and destination port(s)\n"
+" NOTE: this kernel does not support port ranges in multiport.\n");
+}
+
+static void multiport_help_v1(void)
+{
+	printf(
+"multiport match options:\n"
+"[!] --source-ports port[,port:port,port...]\n"
+" --sports ...\n"
+"				match source port(s)\n"
+"[!] --destination-ports port[,port:port,port...]\n"
+" --dports ...\n"
+"				match destination port(s)\n"
+"[!] --ports port[,port:port,port]\n"
+"				match both source and destination port(s)\n");
+}
+
+static const struct xt_option_entry multiport_opts[] = {
+	{.name = "source-ports", .id = O_SOURCE_PORTS, .type = XTTYPE_STRING,
+	 .excl = F_ANY, .flags = XTOPT_INVERT},
+	{.name = "sports", .id = O_SOURCE_PORTS, .type = XTTYPE_STRING,
+	 .excl = F_ANY, .flags = XTOPT_INVERT},
+	{.name = "destination-ports", .id = O_DEST_PORTS,
+	 .type = XTTYPE_STRING, .excl = F_ANY, .flags = XTOPT_INVERT},
+	{.name = "dports", .id = O_DEST_PORTS, .type = XTTYPE_STRING,
+	 .excl = F_ANY, .flags = XTOPT_INVERT},
+	{.name = "ports", .id = O_SD_PORTS, .type = XTTYPE_STRING,
+	 .excl = F_ANY, .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static const char *
+proto_to_name(uint8_t proto)
+{
+	switch (proto) {
+	case IPPROTO_TCP:
+		return "tcp";
+	case IPPROTO_UDP:
+		return "udp";
+	case IPPROTO_UDPLITE:
+		return "udplite";
+	case IPPROTO_SCTP:
+		return "sctp";
+	case IPPROTO_DCCP:
+		return "dccp";
+	default:
+		return NULL;
+	}
+}
+
+static unsigned int
+parse_multi_ports(const char *portstring, uint16_t *ports, const char *proto)
+{
+	char *buffer, *cp, *next;
+	unsigned int i;
+
+	buffer = strdup(portstring);
+	if (!buffer) xtables_error(OTHER_PROBLEM, "strdup failed");
+
+	for (cp=buffer, i=0; cp && i<XT_MULTI_PORTS; cp=next,i++)
+	{
+		next=strchr(cp, ',');
+		if (next) *next++='\0';
+		ports[i] = xtables_parse_port(cp, proto);
+	}
+	if (cp) xtables_error(PARAMETER_PROBLEM, "too many ports specified");
+	free(buffer);
+	return i;
+}
+
+static void
+parse_multi_ports_v1(const char *portstring, 
+		     struct xt_multiport_v1 *multiinfo,
+		     const char *proto)
+{
+	char *buffer, *cp, *next, *range;
+	unsigned int i;
+
+	buffer = strdup(portstring);
+	if (!buffer) xtables_error(OTHER_PROBLEM, "strdup failed");
+
+	for (i=0; i<XT_MULTI_PORTS; i++)
+		multiinfo->pflags[i] = 0;
+ 
+	for (cp=buffer, i=0; cp && i<XT_MULTI_PORTS; cp=next, i++) {
+		next=strchr(cp, ',');
+ 		if (next) *next++='\0';
+		range = strchr(cp, ':');
+		if (range) {
+			if (i == XT_MULTI_PORTS-1)
+				xtables_error(PARAMETER_PROBLEM,
+					   "too many ports specified");
+			*range++ = '\0';
+		}
+		multiinfo->ports[i] = xtables_parse_port(cp, proto);
+		if (range) {
+			multiinfo->pflags[i] = 1;
+			multiinfo->ports[++i] = xtables_parse_port(range, proto);
+			if (multiinfo->ports[i-1] >= multiinfo->ports[i])
+				xtables_error(PARAMETER_PROBLEM,
+					   "invalid portrange specified");
+		}
+ 	}
+	multiinfo->count = i;
+	if (cp) xtables_error(PARAMETER_PROBLEM, "too many ports specified");
+ 	free(buffer);
+}
+
+static const char *
+check_proto(uint16_t pnum, uint8_t invflags)
+{
+	const char *proto;
+
+	if (invflags & XT_INV_PROTO)
+		xtables_error(PARAMETER_PROBLEM,
+			   "multiport only works with TCP, UDP, UDPLITE, SCTP and DCCP");
+
+	if ((proto = proto_to_name(pnum)) != NULL)
+		return proto;
+	else if (!pnum)
+		xtables_error(PARAMETER_PROBLEM,
+			   "multiport needs `-p tcp', `-p udp', `-p udplite', "
+			   "`-p sctp' or `-p dccp'");
+	else
+		xtables_error(PARAMETER_PROBLEM,
+			   "multiport only works with TCP, UDP, UDPLITE, SCTP and DCCP");
+}
+
+static void __multiport_parse(struct xt_option_call *cb, uint16_t pnum,
+			      uint8_t invflags)
+{
+	const char *proto;
+	struct xt_multiport *multiinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SOURCE_PORTS:
+		proto = check_proto(pnum, invflags);
+		multiinfo->count = parse_multi_ports(cb->arg,
+						     multiinfo->ports, proto);
+		multiinfo->flags = XT_MULTIPORT_SOURCE;
+		break;
+	case O_DEST_PORTS:
+		proto = check_proto(pnum, invflags);
+		multiinfo->count = parse_multi_ports(cb->arg,
+						     multiinfo->ports, proto);
+		multiinfo->flags = XT_MULTIPORT_DESTINATION;
+		break;
+	case O_SD_PORTS:
+		proto = check_proto(pnum, invflags);
+		multiinfo->count = parse_multi_ports(cb->arg,
+						     multiinfo->ports, proto);
+		multiinfo->flags = XT_MULTIPORT_EITHER;
+		break;
+	}
+	if (cb->invert)
+		xtables_error(PARAMETER_PROBLEM,
+			   "multiport.0 does not support invert");
+}
+
+static void multiport_parse(struct xt_option_call *cb)
+{
+	const struct ipt_entry *entry = cb->xt_entry;
+	return __multiport_parse(cb,
+	       entry->ip.proto, entry->ip.invflags);
+}
+
+static void multiport_parse6(struct xt_option_call *cb)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	return __multiport_parse(cb,
+	       entry->ipv6.proto, entry->ipv6.invflags);
+}
+
+static void __multiport_parse_v1(struct xt_option_call *cb, uint16_t pnum,
+				 uint8_t invflags)
+{
+	const char *proto;
+	struct xt_multiport_v1 *multiinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SOURCE_PORTS:
+		proto = check_proto(pnum, invflags);
+		parse_multi_ports_v1(cb->arg, multiinfo, proto);
+		multiinfo->flags = XT_MULTIPORT_SOURCE;
+		break;
+	case O_DEST_PORTS:
+		proto = check_proto(pnum, invflags);
+		parse_multi_ports_v1(cb->arg, multiinfo, proto);
+		multiinfo->flags = XT_MULTIPORT_DESTINATION;
+		break;
+	case O_SD_PORTS:
+		proto = check_proto(pnum, invflags);
+		parse_multi_ports_v1(cb->arg, multiinfo, proto);
+		multiinfo->flags = XT_MULTIPORT_EITHER;
+		break;
+	}
+	if (cb->invert)
+		multiinfo->invert = 1;
+}
+
+static void multiport_parse_v1(struct xt_option_call *cb)
+{
+	const struct ipt_entry *entry = cb->xt_entry;
+	return __multiport_parse_v1(cb,
+	       entry->ip.proto, entry->ip.invflags);
+}
+
+static void multiport_parse6_v1(struct xt_option_call *cb)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	return __multiport_parse_v1(cb,
+	       entry->ipv6.proto, entry->ipv6.invflags);
+}
+
+static void multiport_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM, "multiport expection an option");
+}
+
+static const char *
+port_to_service(int port, uint8_t proto)
+{
+	const struct servent *service;
+
+	if ((service = getservbyport(htons(port), proto_to_name(proto))))
+		return service->s_name;
+
+	return NULL;
+}
+
+static void
+print_port(uint16_t port, uint8_t protocol, int numeric)
+{
+	const char *service;
+
+	if (numeric || (service = port_to_service(port, protocol)) == NULL)
+		printf("%u", port);
+	else
+		printf("%s", service);
+}
+
+static void
+__multiport_print(const struct xt_entry_match *match, int numeric,
+                  uint16_t proto)
+{
+	const struct xt_multiport *multiinfo
+		= (const struct xt_multiport *)match->data;
+	unsigned int i;
+
+	printf(" multiport ");
+
+	switch (multiinfo->flags) {
+	case XT_MULTIPORT_SOURCE:
+		printf("sports ");
+		break;
+
+	case XT_MULTIPORT_DESTINATION:
+		printf("dports ");
+		break;
+
+	case XT_MULTIPORT_EITHER:
+		printf("ports ");
+		break;
+
+	default:
+		printf("ERROR ");
+		break;
+	}
+
+	for (i=0; i < multiinfo->count; i++) {
+		printf("%s", i ? "," : "");
+		print_port(multiinfo->ports[i], proto, numeric);
+	}
+}
+
+static void multiport_print(const void *ip_void,
+                            const struct xt_entry_match *match, int numeric)
+{
+	const struct ipt_ip *ip = ip_void;
+	__multiport_print(match, numeric, ip->proto);
+}
+
+static void multiport_print6(const void *ip_void,
+                             const struct xt_entry_match *match, int numeric)
+{
+	const struct ip6t_ip6 *ip = ip_void;
+	__multiport_print(match, numeric, ip->proto);
+}
+
+static void __multiport_print_v1(const struct xt_entry_match *match,
+                                 int numeric, uint16_t proto)
+{
+	const struct xt_multiport_v1 *multiinfo
+		= (const struct xt_multiport_v1 *)match->data;
+	unsigned int i;
+
+	printf(" multiport ");
+
+	switch (multiinfo->flags) {
+	case XT_MULTIPORT_SOURCE:
+		printf("sports ");
+		break;
+
+	case XT_MULTIPORT_DESTINATION:
+		printf("dports ");
+		break;
+
+	case XT_MULTIPORT_EITHER:
+		printf("ports ");
+		break;
+
+	default:
+		printf("ERROR ");
+		break;
+	}
+
+	if (multiinfo->invert)
+		printf(" !");
+
+	for (i=0; i < multiinfo->count; i++) {
+		printf("%s", i ? "," : "");
+		print_port(multiinfo->ports[i], proto, numeric);
+		if (multiinfo->pflags[i]) {
+			printf(":");
+			print_port(multiinfo->ports[++i], proto, numeric);
+		}
+	}
+}
+
+static void multiport_print_v1(const void *ip_void,
+                               const struct xt_entry_match *match, int numeric)
+{
+	const struct ipt_ip *ip = ip_void;
+	__multiport_print_v1(match, numeric, ip->proto);
+}
+
+static void multiport_print6_v1(const void *ip_void,
+                                const struct xt_entry_match *match, int numeric)
+{
+	const struct ip6t_ip6 *ip = ip_void;
+	__multiport_print_v1(match, numeric, ip->proto);
+}
+
+static void __multiport_save(const struct xt_entry_match *match,
+                             uint16_t proto)
+{
+	const struct xt_multiport *multiinfo
+		= (const struct xt_multiport *)match->data;
+	unsigned int i;
+
+	switch (multiinfo->flags) {
+	case XT_MULTIPORT_SOURCE:
+		printf(" --sports ");
+		break;
+
+	case XT_MULTIPORT_DESTINATION:
+		printf(" --dports ");
+		break;
+
+	case XT_MULTIPORT_EITHER:
+		printf(" --ports ");
+		break;
+	}
+
+	for (i=0; i < multiinfo->count; i++) {
+		printf("%s", i ? "," : "");
+		print_port(multiinfo->ports[i], proto, 1);
+	}
+}
+
+static void multiport_save(const void *ip_void,
+                           const struct xt_entry_match *match)
+{
+	const struct ipt_ip *ip = ip_void;
+	__multiport_save(match, ip->proto);
+}
+
+static void multiport_save6(const void *ip_void,
+                            const struct xt_entry_match *match)
+{
+	const struct ip6t_ip6 *ip = ip_void;
+	__multiport_save(match, ip->proto);
+}
+
+static void __multiport_save_v1(const struct xt_entry_match *match,
+                                uint16_t proto)
+{
+	const struct xt_multiport_v1 *multiinfo
+		= (const struct xt_multiport_v1 *)match->data;
+	unsigned int i;
+
+	if (multiinfo->invert)
+		printf(" !");
+
+	switch (multiinfo->flags) {
+	case XT_MULTIPORT_SOURCE:
+		printf(" --sports ");
+		break;
+
+	case XT_MULTIPORT_DESTINATION:
+		printf(" --dports ");
+		break;
+
+	case XT_MULTIPORT_EITHER:
+		printf(" --ports ");
+		break;
+	}
+
+	for (i=0; i < multiinfo->count; i++) {
+		printf("%s", i ? "," : "");
+		print_port(multiinfo->ports[i], proto, 1);
+		if (multiinfo->pflags[i]) {
+			printf(":");
+			print_port(multiinfo->ports[++i], proto, 1);
+		}
+	}
+}
+
+static void multiport_save_v1(const void *ip_void,
+                              const struct xt_entry_match *match)
+{
+	const struct ipt_ip *ip = ip_void;
+	__multiport_save_v1(match, ip->proto);
+}
+
+static void multiport_save6_v1(const void *ip_void,
+                               const struct xt_entry_match *match)
+{
+	const struct ip6t_ip6 *ip = ip_void;
+	__multiport_save_v1(match, ip->proto);
+}
+
+static int __multiport_xlate(struct xt_xlate *xl,
+			     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_multiport *multiinfo
+		= (const struct xt_multiport *)params->match->data;
+	unsigned int i;
+
+	switch (multiinfo->flags) {
+	case XT_MULTIPORT_SOURCE:
+		xt_xlate_add(xl, " sport ");
+		break;
+	case XT_MULTIPORT_DESTINATION:
+		xt_xlate_add(xl, " dport ");
+		break;
+	case XT_MULTIPORT_EITHER:
+		return 0;
+	}
+
+	if (multiinfo->count > 1)
+		xt_xlate_add(xl, "{ ");
+
+	for (i = 0; i < multiinfo->count; i++)
+		xt_xlate_add(xl, "%s%u", i ? "," : "", multiinfo->ports[i]);
+
+	if (multiinfo->count > 1)
+		xt_xlate_add(xl, "}");
+
+	return 1;
+}
+
+static int multiport_xlate(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	uint8_t proto = ((const struct ipt_ip *)params->ip)->proto;
+
+	xt_xlate_add(xl, "%s", proto_to_name(proto));
+	return __multiport_xlate(xl, params);
+}
+
+static int multiport_xlate6(struct xt_xlate *xl,
+			    const struct xt_xlate_mt_params *params)
+{
+	uint8_t proto = ((const struct ip6t_ip6 *)params->ip)->proto;
+
+	xt_xlate_add(xl, "%s", proto_to_name(proto));
+	return __multiport_xlate(xl, params);
+}
+
+static int __multiport_xlate_v1(struct xt_xlate *xl,
+				const struct xt_xlate_mt_params *params)
+{
+	const struct xt_multiport_v1 *multiinfo =
+		(const struct xt_multiport_v1 *)params->match->data;
+	unsigned int i;
+
+	switch (multiinfo->flags) {
+	case XT_MULTIPORT_SOURCE:
+		xt_xlate_add(xl, " sport ");
+		break;
+	case XT_MULTIPORT_DESTINATION:
+		xt_xlate_add(xl, " dport ");
+		break;
+	case XT_MULTIPORT_EITHER:
+		return 0;
+	}
+
+	if (multiinfo->invert)
+		xt_xlate_add(xl, "!= ");
+
+	if (multiinfo->count > 2 ||
+	    (multiinfo->count > 1 && !multiinfo->pflags[0]))
+		xt_xlate_add(xl, "{ ");
+
+	for (i = 0; i < multiinfo->count; i++) {
+		xt_xlate_add(xl, "%s%u", i ? "," : "", multiinfo->ports[i]);
+		if (multiinfo->pflags[i])
+			xt_xlate_add(xl, "-%u", multiinfo->ports[++i]);
+	}
+
+	if (multiinfo->count > 2 ||
+	    (multiinfo->count > 1 && !multiinfo->pflags[0]))
+		xt_xlate_add(xl, "}");
+
+	return 1;
+}
+
+static int multiport_xlate_v1(struct xt_xlate *xl,
+			      const struct xt_xlate_mt_params *params)
+{
+	uint8_t proto = ((const struct ipt_ip *)params->ip)->proto;
+
+	xt_xlate_add(xl, "%s", proto_to_name(proto));
+	return __multiport_xlate_v1(xl, params);
+}
+
+static int multiport_xlate6_v1(struct xt_xlate *xl,
+			       const struct xt_xlate_mt_params *params)
+{
+	uint8_t proto = ((const struct ip6t_ip6 *)params->ip)->proto;
+
+	xt_xlate_add(xl, "%s", proto_to_name(proto));
+	return __multiport_xlate_v1(xl, params);
+}
+
+static struct xtables_match multiport_mt_reg[] = {
+	{
+		.family        = NFPROTO_IPV4,
+		.name          = "multiport",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_multiport)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_multiport)),
+		.help          = multiport_help,
+		.x6_parse      = multiport_parse,
+		.x6_fcheck     = multiport_check,
+		.print         = multiport_print,
+		.save          = multiport_save,
+		.x6_options    = multiport_opts,
+		.xlate         = multiport_xlate,
+	},
+	{
+		.family        = NFPROTO_IPV6,
+		.name          = "multiport",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_multiport)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_multiport)),
+		.help          = multiport_help,
+		.x6_parse      = multiport_parse6,
+		.x6_fcheck     = multiport_check,
+		.print         = multiport_print6,
+		.save          = multiport_save6,
+		.x6_options    = multiport_opts,
+		.xlate         = multiport_xlate6,
+	},
+	{
+		.family        = NFPROTO_IPV4,
+		.name          = "multiport",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.size          = XT_ALIGN(sizeof(struct xt_multiport_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_multiport_v1)),
+		.help          = multiport_help_v1,
+		.x6_parse      = multiport_parse_v1,
+		.x6_fcheck     = multiport_check,
+		.print         = multiport_print_v1,
+		.save          = multiport_save_v1,
+		.x6_options    = multiport_opts,
+		.xlate         = multiport_xlate_v1,
+	},
+	{
+		.family        = NFPROTO_IPV6,
+		.name          = "multiport",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.size          = XT_ALIGN(sizeof(struct xt_multiport_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_multiport_v1)),
+		.help          = multiport_help_v1,
+		.x6_parse      = multiport_parse6_v1,
+		.x6_fcheck     = multiport_check,
+		.print         = multiport_print6_v1,
+		.save          = multiport_save6_v1,
+		.x6_options    = multiport_opts,
+		.xlate         = multiport_xlate6_v1,
+	},
+};
+
+void
+_init(void)
+{
+	xtables_register_matches(multiport_mt_reg, ARRAY_SIZE(multiport_mt_reg));
+}
diff --git a/extensions/libxt_multiport.man b/extensions/libxt_multiport.man
new file mode 100644
index 0000000..7eb083e
--- /dev/null
+++ b/extensions/libxt_multiport.man
@@ -0,0 +1,22 @@
+This module matches a set of source or destination ports.  Up to 15
+ports can be specified.  A port range (port:port) counts as two
+ports.  It can only be used in conjunction with one of the
+following protocols:
+\fBtcp\fP, \fBudp\fP, \fBudplite\fP, \fBdccp\fP and \fBsctp\fP.
+.TP
+[\fB!\fP] \fB\-\-source\-ports\fP,\fB\-\-sports\fP \fIport\fP[\fB,\fP\fIport\fP|\fB,\fP\fIport\fP\fB:\fP\fIport\fP]...
+Match if the source port is one of the given ports.  The flag
+\fB\-\-sports\fP
+is a convenient alias for this option. Multiple ports or port ranges are
+separated using a comma, and a port range is specified using a colon.
+\fB53,1024:65535\fP would therefore match ports 53 and all from 1024 through
+65535.
+.TP
+[\fB!\fP] \fB\-\-destination\-ports\fP,\fB\-\-dports\fP \fIport\fP[\fB,\fP\fIport\fP|\fB,\fP\fIport\fP\fB:\fP\fIport\fP]...
+Match if the destination port is one of the given ports.  The flag
+\fB\-\-dports\fP
+is a convenient alias for this option.
+.TP
+[\fB!\fP] \fB\-\-ports\fP \fIport\fP[\fB,\fP\fIport\fP|\fB,\fP\fIport\fP\fB:\fP\fIport\fP]...
+Match if either the source or destination ports are equal to one of
+the given ports.
diff --git a/extensions/libxt_multiport.t b/extensions/libxt_multiport.t
new file mode 100644
index 0000000..e9b80a4
--- /dev/null
+++ b/extensions/libxt_multiport.t
@@ -0,0 +1,23 @@
+:INPUT,FORWARD,OUTPUT
+-p tcp -m multiport --sports 53,1024:65535;=;OK
+-p tcp -m multiport --dports 53,1024:65535;=;OK
+-p udp -m multiport --sports 53,1024:65535;=;OK
+-p udp -m multiport --dports 53,1024:65535;=;OK
+-p udp -m multiport --ports 53,1024:65535;=;OK
+-p udp -m multiport --ports 53,1024:65535;=;OK
+-p sctp -m multiport --sports 53,1024:65535;=;OK
+-p sctp -m multiport --dports 53,1024:65535;=;OK
+-p dccp -m multiport --sports 53,1024:65535;=;OK
+-p dccp -m multiport --dports 53,1024:65535;=;OK
+-p udplite -m multiport --sports 53,1024:65535;=;OK
+-p udplite -m multiport --dports 53,1024:65535;=;OK
+-p tcp -m multiport --sports 1024:65536;;FAIL
+-p udp -m multiport --sports 1024:65536;;FAIL
+-p tcp -m multiport --ports 1024:65536;;FAIL
+-p udp -m multiport --ports 1024:65536;;FAIL
+-p tcp -m multiport --ports 1,2,3,4,6,7,8,9,10,11,12,13,14,15;=;OK
+# fix manpage, it says "up to 15 ports supported"
+# ERROR: should fail: iptables -A INPUT -p tcp -m multiport --ports 1,2,3,4,6,7,8,9,10,11,12,13,14,15,16
+# -p tcp -m multiport --ports 1,2,3,4,6,7,8,9,10,11,12,13,14,15,16;;FAIL
+-p tcp --multiport;;FAIL
+-m multiport;;FAIL
diff --git a/extensions/libxt_multiport.txlate b/extensions/libxt_multiport.txlate
new file mode 100644
index 0000000..752e714
--- /dev/null
+++ b/extensions/libxt_multiport.txlate
@@ -0,0 +1,11 @@
+iptables-translate -t filter -A INPUT -p tcp -m multiport --dports 80,81 -j ACCEPT
+nft add rule ip filter INPUT ip protocol tcp tcp dport { 80,81} counter accept
+
+iptables-translate -t filter -A INPUT -p tcp -m multiport --dports 80:88 -j ACCEPT
+nft add rule ip filter INPUT ip protocol tcp tcp dport 80-88 counter accept
+
+iptables-translate -t filter -A INPUT -p tcp -m multiport ! --dports 80:88 -j ACCEPT
+nft add rule ip filter INPUT ip protocol tcp tcp dport != 80-88 counter accept
+
+iptables-translate -t filter -A INPUT -p tcp -m multiport --sports 50 -j ACCEPT
+nft add rule ip filter INPUT ip protocol tcp tcp sport 50 counter accept
diff --git a/extensions/libxt_nfacct.c b/extensions/libxt_nfacct.c
new file mode 100644
index 0000000..d9c0309
--- /dev/null
+++ b/extensions/libxt_nfacct.c
@@ -0,0 +1,105 @@
+/*
+ * (C) 2011 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2011 by Intra2Net AG <http://www.intra2net.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 (or
+ * any later at your option) as published by the Free Software Foundation.
+ */
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/xt_nfacct.h>
+
+enum {
+	O_NAME = 0,
+};
+
+#define s struct xt_nfacct_match_info
+static const struct xt_option_entry nfacct_opts[] = {
+	{.name = "nfacct-name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .min = 1, .flags = XTOPT_MAND|XTOPT_PUT, XTOPT_POINTER(s, name)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void nfacct_help(void)
+{
+	printf("nfacct match options:\n"
+	       " --nfacct-name STRING		Name of accouting area\n");
+}
+
+static void nfacct_parse(struct xt_option_call *cb)
+{
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_NAME:
+		if (strchr(cb->arg, '\n') != NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Newlines not allowed in --nfacct-name");
+		break;
+	}
+}
+
+static void
+nfacct_print_name(const struct xt_nfacct_match_info *info, char *name)
+{
+	printf(" %snfacct-name ", name);
+	xtables_save_string(info->name);
+}
+
+static void nfacct_print(const void *ip, const struct xt_entry_match *match,
+                        int numeric)
+{
+	const struct xt_nfacct_match_info *info =
+		(struct xt_nfacct_match_info *)match->data;
+
+	nfacct_print_name(info, "");
+}
+
+static void nfacct_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_nfacct_match_info *info =
+		(struct xt_nfacct_match_info *)match->data;
+
+	nfacct_print_name(info, "--");
+}
+
+static struct xtables_match nfacct_matches[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.revision	= 0,
+		.name		= "nfacct",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_nfacct_match_info)),
+		.userspacesize	= offsetof(struct xt_nfacct_match_info, nfacct),
+		.help		= nfacct_help,
+		.x6_parse	= nfacct_parse,
+		.print		= nfacct_print,
+		.save		= nfacct_save,
+		.x6_options	= nfacct_opts,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.revision	= 1,
+		.name		= "nfacct",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_nfacct_match_info_v1)),
+		.userspacesize	= offsetof(struct xt_nfacct_match_info_v1, nfacct),
+		.help		= nfacct_help,
+		.x6_parse	= nfacct_parse,
+		.print		= nfacct_print,
+		.save		= nfacct_save,
+		.x6_options	= nfacct_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(nfacct_matches, ARRAY_SIZE(nfacct_matches));
+}
diff --git a/extensions/libxt_nfacct.man b/extensions/libxt_nfacct.man
new file mode 100644
index 0000000..b755f97
--- /dev/null
+++ b/extensions/libxt_nfacct.man
@@ -0,0 +1,30 @@
+The nfacct match provides the extended accounting infrastructure for iptables.
+You have to use this match together with the standalone user-space utility
+.B nfacct(8)
+.PP
+The only option available for this match is the following:
+.TP
+\fB\-\-nfacct\-name\fP \fIname\fP
+This allows you to specify the existing object name that will be use for
+accounting the traffic that this rule-set is matching.
+.PP
+To use this extension, you have to create an accounting object:
+.IP
+nfacct add http\-traffic
+.PP
+Then, you have to attach it to the accounting object via iptables:
+.IP
+iptables \-I INPUT \-p tcp \-\-sport 80 \-m nfacct \-\-nfacct\-name http\-traffic
+.IP
+iptables \-I OUTPUT \-p tcp \-\-dport 80 \-m nfacct \-\-nfacct\-name http\-traffic
+.PP
+Then, you can check for the amount of traffic that the rules match:
+.IP
+nfacct get http\-traffic
+.IP
+{ pkts = 00000000000000000156, bytes = 00000000000000151786 } = http-traffic;
+.PP
+You can obtain
+.B nfacct(8)
+from http://www.netfilter.org or, alternatively, from the git.netfilter.org
+repository.
diff --git a/extensions/libxt_nfacct.t b/extensions/libxt_nfacct.t
new file mode 100644
index 0000000..3419b4c
--- /dev/null
+++ b/extensions/libxt_nfacct.t
@@ -0,0 +1,10 @@
+:INPUT,FORWARD,OUTPUT
+@nfacct add test
+#
+# extra space in iptables-save output, fix it
+#
+# ERROR: cannot load: iptables -A INPUT -m nfacct --nfacct-name test
+#-m nfacct --nfacct-name test;=;OK
+-m nfacct --nfacct-name wrong;;FAIL
+-m nfacct;;FAIL
+@nfacct del test
diff --git a/extensions/libxt_osf.c b/extensions/libxt_osf.c
new file mode 100644
index 0000000..c567d9e
--- /dev/null
+++ b/extensions/libxt_osf.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2003+ Evgeniy Polyakov <zbr@ioremap.net>
+ *
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/*
+ * xtables interface for OS fingerprint matching module.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <linux/netfilter/xt_osf.h>
+
+enum {
+	O_GENRE = 0,
+	O_TTL,
+	O_LOGLEVEL,
+};
+
+static void osf_help(void)
+{
+	printf("OS fingerprint match options:\n"
+		"[!] --genre string     Match a OS genre by passive fingerprinting.\n"
+		"--ttl level            Use some TTL check extensions to determine OS:\n"
+		"       0                       true ip and fingerprint TTL comparison. Works for LAN.\n"
+		"       1                       check if ip TTL is less than fingerprint one. Works for global addresses.\n"
+		"       2                       do not compare TTL at all. This allows NMAP detection, but can produce false results.\n"
+		"--log level            Log determined genres into dmesg even if they do not match desired one:\n"
+		"       0                       log all matched or unknown signatures.\n"
+		"       1                       log only first one.\n"
+		"       2                       log all known matched signatures.\n"
+		);
+}
+
+#define s struct xt_osf_info
+static const struct xt_option_entry osf_opts[] = {
+	{.name = "genre", .id = O_GENRE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(s, genre)},
+	{.name = "ttl", .id = O_TTL, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, ttl), .min = 0, .max = 2},
+	{.name = "log", .id = O_LOGLEVEL, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, loglevel), .min = 0, .max = 2},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void osf_parse(struct xt_option_call *cb)
+{
+	struct xt_osf_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+		case O_GENRE:
+			if (cb->invert)
+				info->flags |= XT_OSF_INVERT;
+			info->len = strlen(info->genre);
+			break;
+		case O_TTL:
+			info->flags |= XT_OSF_TTL;
+			break;
+		case O_LOGLEVEL:
+			info->flags |= XT_OSF_LOG;
+			break;
+	}
+}
+
+static void osf_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_osf_info *info = (const struct xt_osf_info*) match->data;
+
+	printf(" OS fingerprint match %s%s", (info->flags & XT_OSF_INVERT) ? "! " : "", info->genre);
+}
+
+static void osf_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_osf_info *info = (const struct xt_osf_info*) match->data;
+
+	if (info->flags & XT_OSF_INVERT)
+		printf(" !");
+
+	printf(" --genre %s", info->genre);
+	if (info->flags & XT_OSF_TTL)
+		printf(" --ttl %u", info->ttl);
+	if (info->flags & XT_OSF_LOG)
+		printf(" --log %u", info->loglevel);
+}
+
+static struct xtables_match osf_match = {
+	.name		= "osf",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_osf_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_osf_info)),
+	.help		= osf_help,
+	.x6_parse	= osf_parse,
+	.print		= osf_print,
+	.save		= osf_save,
+	.x6_options	= osf_opts,
+	.family		= NFPROTO_IPV4,
+};
+
+void _init(void)
+{
+	xtables_register_match(&osf_match);
+}
diff --git a/extensions/libxt_osf.man b/extensions/libxt_osf.man
new file mode 100644
index 0000000..41103f2
--- /dev/null
+++ b/extensions/libxt_osf.man
@@ -0,0 +1,45 @@
+The osf module does passive operating system fingerprinting. This module
+compares some data (Window Size, MSS, options and their order, TTL, DF,
+and others) from packets with the SYN bit set. 
+.TP
+[\fB!\fP] \fB\-\-genre\fP \fIstring\fP
+Match an operating system genre by using a passive fingerprinting.
+.TP
+\fB\-\-ttl\fP \fIlevel\fP
+Do additional TTL checks on the packet to determine the operating system.
+\fIlevel\fP can be one of the following values:
+.IP \(bu 4
+0 - True IP address and fingerprint TTL comparison. This generally works for
+LANs.
+.IP \(bu 4
+1 - Check if the IP header's TTL is less than the fingerprint one. Works for
+globally-routable addresses.
+.IP \(bu 4
+2 - Do not compare the TTL at all.
+.TP
+\fB\-\-log\fP \fIlevel\fP
+Log determined genres into dmesg even if they do not match the desired one.
+\fIlevel\fP can be one of the following values:
+.IP \(bu 4
+0 - Log all matched or unknown signatures
+.IP \(bu 4
+1 - Log only the first one
+.IP \(bu 4
+2 - Log all known matched signatures
+.PP
+You may find something like this in syslog:
+.PP
+Windows [2000:SP3:Windows XP Pro SP1, 2000 SP3]: 11.22.33.55:4024 ->
+11.22.33.44:139 hops=3 Linux [2.5-2.6:] : 1.2.3.4:42624 -> 1.2.3.5:22 hops=4
+.PP
+OS fingerprints are loadable using the \fBnfnl_osf\fP program. To load
+fingerprints from a file, use:
+.PP
+\fBnfnl_osf \-f /usr/share/xtables/pf.os\fP
+.PP
+To remove them again,
+.PP
+\fBnfnl_osf \-f /usr/share/xtables/pf.os \-d\fP
+.PP
+The fingerprint database can be downloaded from
+http://www.openbsd.org/cgi-bin/cvsweb/src/etc/pf.os .
diff --git a/extensions/libxt_osf.t b/extensions/libxt_osf.t
new file mode 100644
index 0000000..ede6d32
--- /dev/null
+++ b/extensions/libxt_osf.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD
+-m osf --genre linux --ttl 0 --log 0;;FAIL
+-p tcp -m osf --genre linux --ttl 0 --log 0;=;OK
+-p tcp -m osf --genre linux --ttl 3 --log 0;;FAIL
diff --git a/extensions/libxt_owner.c b/extensions/libxt_owner.c
new file mode 100644
index 0000000..1702b47
--- /dev/null
+++ b/extensions/libxt_owner.c
@@ -0,0 +1,604 @@
+/*
+ *	libxt_owner - iptables addon for xt_owner
+ *
+ *	Copyright © CC Computer Consultants GmbH, 2007 - 2008
+ *	Jan Engelhardt <jengelh@computergmbh.de>
+ */
+#include <grp.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <limits.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_owner.h>
+
+/* match and invert flags */
+enum {
+	IPT_OWNER_UID   = 0x01,
+	IPT_OWNER_GID   = 0x02,
+	IPT_OWNER_PID   = 0x04,
+	IPT_OWNER_SID   = 0x08,
+	IPT_OWNER_COMM  = 0x10,
+	IP6T_OWNER_UID  = IPT_OWNER_UID,
+	IP6T_OWNER_GID  = IPT_OWNER_GID,
+	IP6T_OWNER_PID  = IPT_OWNER_PID,
+	IP6T_OWNER_SID  = IPT_OWNER_SID,
+	IP6T_OWNER_COMM = IPT_OWNER_COMM,
+};
+
+struct ipt_owner_info {
+	uid_t uid;
+	gid_t gid;
+	pid_t pid;
+	pid_t sid;
+	char comm[16];
+	uint8_t match, invert;	/* flags */
+};
+
+struct ip6t_owner_info {
+	uid_t uid;
+	gid_t gid;
+	pid_t pid;
+	pid_t sid;
+	char comm[16];
+	uint8_t match, invert;	/* flags */
+};
+
+/*
+ *	Note: "UINT32_MAX - 1" is used in the code because -1 is a reserved
+ *	UID/GID value anyway.
+ */
+
+enum {
+	O_USER = 0,
+	O_GROUP,
+	O_SOCK_EXISTS,
+	O_PROCESS,
+	O_SESSION,
+	O_COMM,
+	O_SUPPL_GROUPS,
+};
+
+static void owner_mt_help_v0(void)
+{
+	printf(
+"owner match options:\n"
+"[!] --uid-owner userid       Match local UID\n"
+"[!] --gid-owner groupid      Match local GID\n"
+"[!] --pid-owner processid    Match local PID\n"
+"[!] --sid-owner sessionid    Match local SID\n"
+"[!] --cmd-owner name         Match local command name\n"
+"NOTE: PID, SID and command matching are broken on SMP\n");
+}
+
+static void owner_mt6_help_v0(void)
+{
+	printf(
+"owner match options:\n"
+"[!] --uid-owner userid       Match local UID\n"
+"[!] --gid-owner groupid      Match local GID\n"
+"[!] --pid-owner processid    Match local PID\n"
+"[!] --sid-owner sessionid    Match local SID\n"
+"NOTE: PID and SID matching are broken on SMP\n");
+}
+
+static void owner_mt_help(void)
+{
+	printf(
+"owner match options:\n"
+"[!] --uid-owner userid[-userid]      Match local UID\n"
+"[!] --gid-owner groupid[-groupid]    Match local GID\n"
+"[!] --socket-exists                  Match if socket exists\n"
+"    --suppl-groups                   Also match supplementary groups set with --gid-owner\n");
+}
+
+#define s struct ipt_owner_info
+static const struct xt_option_entry owner_mt_opts_v0[] = {
+	{.name = "uid-owner", .id = O_USER, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "gid-owner", .id = O_GROUP, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "pid-owner", .id = O_PROCESS, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, pid),
+	 .max = INT_MAX},
+	{.name = "sid-owner", .id = O_SESSION, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, sid),
+	 .max = INT_MAX},
+	{.name = "cmd-owner", .id = O_COMM, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, comm)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct ip6t_owner_info
+static const struct xt_option_entry owner_mt6_opts_v0[] = {
+	{.name = "uid-owner", .id = O_USER, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "gid-owner", .id = O_GROUP, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "pid-owner", .id = O_PROCESS, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, pid),
+	 .max = INT_MAX},
+	{.name = "sid-owner", .id = O_SESSION, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, sid),
+	 .max = INT_MAX},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static const struct xt_option_entry owner_mt_opts[] = {
+	{.name = "uid-owner", .id = O_USER, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "gid-owner", .id = O_GROUP, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "socket-exists", .id = O_SOCK_EXISTS, .type = XTTYPE_NONE,
+	 .flags = XTOPT_INVERT},
+	{.name = "suppl-groups", .id = O_SUPPL_GROUPS, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static void owner_mt_parse_v0(struct xt_option_call *cb)
+{
+	struct ipt_owner_info *info = cb->data;
+	struct passwd *pwd;
+	struct group *grp;
+	unsigned int id;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_USER:
+		if ((pwd = getpwnam(cb->arg)) != NULL)
+			id = pwd->pw_uid;
+		else if (!xtables_strtoui(cb->arg, NULL, &id, 0, UINT32_MAX - 1))
+			xtables_param_act(XTF_BAD_VALUE, "owner", "--uid-owner", cb->arg);
+		if (cb->invert)
+			info->invert |= IPT_OWNER_UID;
+		info->match |= IPT_OWNER_UID;
+		info->uid    = id;
+		break;
+	case O_GROUP:
+		if ((grp = getgrnam(cb->arg)) != NULL)
+			id = grp->gr_gid;
+		else if (!xtables_strtoui(cb->arg, NULL, &id, 0, UINT32_MAX - 1))
+			xtables_param_act(XTF_BAD_VALUE, "owner", "--gid-owner", cb->arg);
+		if (cb->invert)
+			info->invert |= IPT_OWNER_GID;
+		info->match |= IPT_OWNER_GID;
+		info->gid    = id;
+		break;
+	case O_PROCESS:
+		if (cb->invert)
+			info->invert |= IPT_OWNER_PID;
+		info->match |= IPT_OWNER_PID;
+		break;
+	case O_SESSION:
+		if (cb->invert)
+			info->invert |= IPT_OWNER_SID;
+		info->match |= IPT_OWNER_SID;
+		break;
+	case O_COMM:
+		if (cb->invert)
+			info->invert |= IPT_OWNER_COMM;
+		info->match |= IPT_OWNER_COMM;
+		break;
+	}
+}
+
+static void owner_mt6_parse_v0(struct xt_option_call *cb)
+{
+	struct ip6t_owner_info *info = cb->data;
+	struct passwd *pwd;
+	struct group *grp;
+	unsigned int id;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_USER:
+		if ((pwd = getpwnam(cb->arg)) != NULL)
+			id = pwd->pw_uid;
+		else if (!xtables_strtoui(cb->arg, NULL, &id, 0, UINT32_MAX - 1))
+			xtables_param_act(XTF_BAD_VALUE, "owner", "--uid-owner", cb->arg);
+		if (cb->invert)
+			info->invert |= IP6T_OWNER_UID;
+		info->match |= IP6T_OWNER_UID;
+		info->uid    = id;
+		break;
+	case O_GROUP:
+		if ((grp = getgrnam(cb->arg)) != NULL)
+			id = grp->gr_gid;
+		else if (!xtables_strtoui(cb->arg, NULL, &id, 0, UINT32_MAX - 1))
+			xtables_param_act(XTF_BAD_VALUE, "owner", "--gid-owner", cb->arg);
+		if (cb->invert)
+			info->invert |= IP6T_OWNER_GID;
+		info->match |= IP6T_OWNER_GID;
+		info->gid    = id;
+		break;
+	case O_PROCESS:
+		if (cb->invert)
+			info->invert |= IP6T_OWNER_PID;
+		info->match |= IP6T_OWNER_PID;
+		break;
+	case O_SESSION:
+		if (cb->invert)
+			info->invert |= IP6T_OWNER_SID;
+		info->match |= IP6T_OWNER_SID;
+		break;
+	}
+}
+
+static void owner_parse_range(const char *s, unsigned int *from,
+                              unsigned int *to, const char *opt)
+{
+	char *end;
+
+	/* -1 is reversed, so the max is one less than that. */
+	if (!xtables_strtoui(s, &end, from, 0, UINT32_MAX - 1))
+		xtables_param_act(XTF_BAD_VALUE, "owner", opt, s);
+	*to = *from;
+	if (*end == '-' || *end == ':')
+		if (!xtables_strtoui(end + 1, &end, to, 0, UINT32_MAX - 1))
+			xtables_param_act(XTF_BAD_VALUE, "owner", opt, s);
+	if (*end != '\0')
+		xtables_param_act(XTF_BAD_VALUE, "owner", opt, s);
+}
+
+static void owner_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_owner_match_info *info = cb->data;
+	struct passwd *pwd;
+	struct group *grp;
+	unsigned int from, to;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_USER:
+		if ((pwd = getpwnam(cb->arg)) != NULL)
+			from = to = pwd->pw_uid;
+		else
+			owner_parse_range(cb->arg, &from, &to, "--uid-owner");
+		if (cb->invert)
+			info->invert |= XT_OWNER_UID;
+		info->match  |= XT_OWNER_UID;
+		info->uid_min = from;
+		info->uid_max = to;
+		break;
+	case O_GROUP:
+		if ((grp = getgrnam(cb->arg)) != NULL)
+			from = to = grp->gr_gid;
+		else
+			owner_parse_range(cb->arg, &from, &to, "--gid-owner");
+		if (cb->invert)
+			info->invert |= XT_OWNER_GID;
+		info->match  |= XT_OWNER_GID;
+		info->gid_min = from;
+		info->gid_max = to;
+		break;
+	case O_SOCK_EXISTS:
+		if (cb->invert)
+			info->invert |= XT_OWNER_SOCKET;
+		info->match |= XT_OWNER_SOCKET;
+		break;
+	case O_SUPPL_GROUPS:
+		if (!(info->match & XT_OWNER_GID))
+			xtables_param_act(XTF_BAD_VALUE, "owner", "--suppl-groups", "you need to use --gid-owner first");
+		info->match |= XT_OWNER_SUPPL_GROUPS;
+		break;
+	}
+}
+
+static void owner_mt_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM, "owner: At least one of "
+		           "--uid-owner, --gid-owner or --socket-exists "
+		           "is required");
+}
+
+static void
+owner_mt_print_item_v0(const struct ipt_owner_info *info, const char *label,
+                       uint8_t flag, bool numeric)
+{
+	if (!(info->match & flag))
+		return;
+	if (info->invert & flag)
+		printf(" !");
+	printf(" %s", label);
+
+	switch (info->match & flag) {
+	case IPT_OWNER_UID:
+		if (!numeric) {
+			struct passwd *pwd = getpwuid(info->uid);
+
+			if (pwd != NULL && pwd->pw_name != NULL) {
+				printf(" %s", pwd->pw_name);
+				break;
+			}
+		}
+		printf(" %u", (unsigned int)info->uid);
+		break;
+
+	case IPT_OWNER_GID:
+		if (!numeric) {
+			struct group *grp = getgrgid(info->gid);
+
+			if (grp != NULL && grp->gr_name != NULL) {
+				printf(" %s", grp->gr_name);
+				break;
+			}
+		}
+		printf(" %u", (unsigned int)info->gid);
+		break;
+
+	case IPT_OWNER_PID:
+		printf(" %u", (unsigned int)info->pid);
+		break;
+
+	case IPT_OWNER_SID:
+		printf(" %u", (unsigned int)info->sid);
+		break;
+
+	case IPT_OWNER_COMM:
+		printf(" %.*s", (int)sizeof(info->comm), info->comm);
+		break;
+	}
+}
+
+static void
+owner_mt6_print_item_v0(const struct ip6t_owner_info *info, const char *label,
+                        uint8_t flag, bool numeric)
+{
+	if (!(info->match & flag))
+		return;
+	if (info->invert & flag)
+		printf(" !");
+	printf(" %s", label);
+
+	switch (info->match & flag) {
+	case IP6T_OWNER_UID:
+		if (!numeric) {
+			struct passwd *pwd = getpwuid(info->uid);
+
+			if (pwd != NULL && pwd->pw_name != NULL) {
+				printf(" %s", pwd->pw_name);
+				break;
+			}
+		}
+		printf(" %u", (unsigned int)info->uid);
+		break;
+
+	case IP6T_OWNER_GID:
+		if (!numeric) {
+			struct group *grp = getgrgid(info->gid);
+
+			if (grp != NULL && grp->gr_name != NULL) {
+				printf(" %s", grp->gr_name);
+				break;
+			}
+		}
+		printf(" %u", (unsigned int)info->gid);
+		break;
+
+	case IP6T_OWNER_PID:
+		printf(" %u", (unsigned int)info->pid);
+		break;
+
+	case IP6T_OWNER_SID:
+		printf(" %u", (unsigned int)info->sid);
+		break;
+	}
+}
+
+static void
+owner_mt_print_item(const struct xt_owner_match_info *info, const char *label,
+                    uint8_t flag, bool numeric)
+{
+	if (!(info->match & flag))
+		return;
+	if (info->invert & flag)
+		printf(" !");
+	printf(" %s", label);
+
+	switch (info->match & flag) {
+	case XT_OWNER_UID:
+		if (info->uid_min != info->uid_max) {
+			printf(" %u-%u", (unsigned int)info->uid_min,
+			       (unsigned int)info->uid_max);
+			break;
+		} else if (!numeric) {
+			const struct passwd *pwd = getpwuid(info->uid_min);
+
+			if (pwd != NULL && pwd->pw_name != NULL) {
+				printf(" %s", pwd->pw_name);
+				break;
+			}
+		}
+		printf(" %u", (unsigned int)info->uid_min);
+		break;
+
+	case XT_OWNER_GID:
+		if (info->gid_min != info->gid_max) {
+			printf(" %u-%u", (unsigned int)info->gid_min,
+			       (unsigned int)info->gid_max);
+			break;
+		} else if (!numeric) {
+			const struct group *grp = getgrgid(info->gid_min);
+
+			if (grp != NULL && grp->gr_name != NULL) {
+				printf(" %s", grp->gr_name);
+				break;
+			}
+		}
+		printf(" %u", (unsigned int)info->gid_min);
+		break;
+	}
+}
+
+static void
+owner_mt_print_v0(const void *ip, const struct xt_entry_match *match,
+                  int numeric)
+{
+	const struct ipt_owner_info *info = (void *)match->data;
+
+	owner_mt_print_item_v0(info, "owner UID match", IPT_OWNER_UID, numeric);
+	owner_mt_print_item_v0(info, "owner GID match", IPT_OWNER_GID, numeric);
+	owner_mt_print_item_v0(info, "owner PID match", IPT_OWNER_PID, numeric);
+	owner_mt_print_item_v0(info, "owner SID match", IPT_OWNER_SID, numeric);
+	owner_mt_print_item_v0(info, "owner CMD match", IPT_OWNER_COMM, numeric);
+}
+
+static void
+owner_mt6_print_v0(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct ip6t_owner_info *info = (void *)match->data;
+
+	owner_mt6_print_item_v0(info, "owner UID match", IPT_OWNER_UID, numeric);
+	owner_mt6_print_item_v0(info, "owner GID match", IPT_OWNER_GID, numeric);
+	owner_mt6_print_item_v0(info, "owner PID match", IPT_OWNER_PID, numeric);
+	owner_mt6_print_item_v0(info, "owner SID match", IPT_OWNER_SID, numeric);
+}
+
+static void owner_mt_print(const void *ip, const struct xt_entry_match *match,
+                           int numeric)
+{
+	const struct xt_owner_match_info *info = (void *)match->data;
+
+	owner_mt_print_item(info, "owner socket exists", XT_OWNER_SOCKET,       numeric);
+	owner_mt_print_item(info, "owner UID match",     XT_OWNER_UID,          numeric);
+	owner_mt_print_item(info, "owner GID match",     XT_OWNER_GID,          numeric);
+	owner_mt_print_item(info, "incl. suppl. groups", XT_OWNER_SUPPL_GROUPS, numeric);
+}
+
+static void
+owner_mt_save_v0(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ipt_owner_info *info = (void *)match->data;
+
+	owner_mt_print_item_v0(info, "--uid-owner", IPT_OWNER_UID, true);
+	owner_mt_print_item_v0(info, "--gid-owner", IPT_OWNER_GID, true);
+	owner_mt_print_item_v0(info, "--pid-owner", IPT_OWNER_PID, true);
+	owner_mt_print_item_v0(info, "--sid-owner", IPT_OWNER_SID, true);
+	owner_mt_print_item_v0(info, "--cmd-owner", IPT_OWNER_COMM, true);
+}
+
+static void
+owner_mt6_save_v0(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_owner_info *info = (void *)match->data;
+
+	owner_mt6_print_item_v0(info, "--uid-owner", IPT_OWNER_UID, true);
+	owner_mt6_print_item_v0(info, "--gid-owner", IPT_OWNER_GID, true);
+	owner_mt6_print_item_v0(info, "--pid-owner", IPT_OWNER_PID, true);
+	owner_mt6_print_item_v0(info, "--sid-owner", IPT_OWNER_SID, true);
+}
+
+static void owner_mt_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_owner_match_info *info = (void *)match->data;
+
+	owner_mt_print_item(info, "--socket-exists",  XT_OWNER_SOCKET,       true);
+	owner_mt_print_item(info, "--uid-owner",      XT_OWNER_UID,          true);
+	owner_mt_print_item(info, "--gid-owner",      XT_OWNER_GID,          true);
+	owner_mt_print_item(info, "--suppl-groups",   XT_OWNER_SUPPL_GROUPS, true);
+}
+
+static int
+owner_mt_print_uid_xlate(const struct xt_owner_match_info *info,
+			 struct xt_xlate *xl)
+{
+	xt_xlate_add(xl, "skuid%s ", info->invert ? " !=" : "");
+
+	if (info->uid_min != info->uid_max)
+		xt_xlate_add(xl, "%u-%u", (unsigned int)info->uid_min,
+			     (unsigned int)info->uid_max);
+	else
+		xt_xlate_add(xl, "%u", (unsigned int)info->uid_min);
+
+	return 1;
+}
+
+static int
+owner_mt_print_gid_xlate(const struct xt_owner_match_info *info,
+			 struct xt_xlate *xl)
+{
+	xt_xlate_add(xl, "skgid%s ", info->invert ? " !=" : "");
+
+	if (info->gid_min != info->gid_max)
+		xt_xlate_add(xl, "%u-%u", (unsigned int)info->gid_min,
+			     (unsigned int)info->gid_max);
+	else
+		xt_xlate_add(xl, "%u", (unsigned int)info->gid_min);
+
+	return 1;
+}
+
+static int owner_mt_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_owner_match_info *info = (void *)params->match->data;
+	int ret;
+
+	switch (info->match) {
+	case XT_OWNER_UID:
+		ret = owner_mt_print_uid_xlate(info, xl);
+		break;
+	case XT_OWNER_GID:
+		ret = owner_mt_print_gid_xlate(info, xl);
+		break;
+	default:
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static struct xtables_match owner_mt_reg[] = {
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "owner",
+		.revision      = 0,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct ipt_owner_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct ipt_owner_info)),
+		.help          = owner_mt_help_v0,
+		.x6_parse      = owner_mt_parse_v0,
+		.x6_fcheck     = owner_mt_check,
+		.print         = owner_mt_print_v0,
+		.save          = owner_mt_save_v0,
+		.x6_options    = owner_mt_opts_v0,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "owner",
+		.revision      = 0,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct ip6t_owner_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct ip6t_owner_info)),
+		.help          = owner_mt6_help_v0,
+		.x6_parse      = owner_mt6_parse_v0,
+		.x6_fcheck     = owner_mt_check,
+		.print         = owner_mt6_print_v0,
+		.save          = owner_mt6_save_v0,
+		.x6_options    = owner_mt6_opts_v0,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "owner",
+		.revision      = 1,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_owner_match_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_owner_match_info)),
+		.help          = owner_mt_help,
+		.x6_parse      = owner_mt_parse,
+		.x6_fcheck     = owner_mt_check,
+		.print         = owner_mt_print,
+		.save          = owner_mt_save,
+		.x6_options    = owner_mt_opts,
+		.xlate	       = owner_mt_xlate,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(owner_mt_reg, ARRAY_SIZE(owner_mt_reg));
+}
diff --git a/extensions/libxt_owner.man b/extensions/libxt_owner.man
new file mode 100644
index 0000000..e247986
--- /dev/null
+++ b/extensions/libxt_owner.man
@@ -0,0 +1,23 @@
+This module attempts to match various characteristics of the packet creator,
+for locally generated packets. This match is only valid in the OUTPUT and
+POSTROUTING chains. Forwarded packets do not have any socket associated with
+them. Packets from kernel threads do have a socket, but usually no owner.
+.TP
+[\fB!\fP] \fB\-\-uid\-owner\fP \fIusername\fP
+.TP
+[\fB!\fP] \fB\-\-uid\-owner\fP \fIuserid\fP[\fB\-\fP\fIuserid\fP]
+Matches if the packet socket's file structure (if it has one) is owned by the
+given user. You may also specify a numerical UID, or an UID range.
+.TP
+[\fB!\fP] \fB\-\-gid\-owner\fP \fIgroupname\fP
+.TP
+[\fB!\fP] \fB\-\-gid\-owner\fP \fIgroupid\fP[\fB\-\fP\fIgroupid\fP]
+Matches if the packet socket's file structure is owned by the given group.
+You may also specify a numerical GID, or a GID range.
+.TP
+\fB\-\-suppl\-groups\fP
+Causes group(s) specified with \fB\-\-gid-owner\fP to be also checked in the
+supplementary groups of a process.
+.TP
+[\fB!\fP] \fB\-\-socket\-exists\fP
+Matches if the packet is associated with a socket.
diff --git a/extensions/libxt_owner.t b/extensions/libxt_owner.t
new file mode 100644
index 0000000..2779e5c
--- /dev/null
+++ b/extensions/libxt_owner.t
@@ -0,0 +1,16 @@
+:OUTPUT,POSTROUTING
+*mangle
+-m owner --uid-owner root;-m owner --uid-owner 0;OK
+-m owner --uid-owner 0-10;=;OK
+-m owner --gid-owner root;-m owner --gid-owner 0;OK
+-m owner --gid-owner 0-10;=;OK
+-m owner --uid-owner root --gid-owner root;-m owner --uid-owner 0 --gid-owner 0;OK
+-m owner --uid-owner 0-10 --gid-owner 0-10;=;OK
+-m owner ! --uid-owner root;-m owner ! --uid-owner 0;OK
+-m owner --socket-exists;=;OK
+-m owner --gid-owner 0-10 --suppl-groups;=;OK
+-m owner --suppl-groups --gid-owner 0-10;;FAIL
+-m owner --gid-owner 0-10 ! --suppl-groups;;FAIL
+-m owner --suppl-groups;;FAIL
+:INPUT
+-m owner --uid-owner root;;FAIL
diff --git a/extensions/libxt_owner.txlate b/extensions/libxt_owner.txlate
new file mode 100644
index 0000000..86fb058
--- /dev/null
+++ b/extensions/libxt_owner.txlate
@@ -0,0 +1,8 @@
+iptables-translate -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner root -j ACCEPT
+nft add rule ip nat OUTPUT tcp dport 80 skuid 0 counter accept
+
+iptables-translate -t nat -A OUTPUT -p tcp --dport 80 -m owner --gid-owner 0-10 -j ACCEPT
+nft add rule ip nat OUTPUT tcp dport 80 skgid 0-10 counter accept
+
+iptables-translate -t nat -A OUTPUT -p tcp --dport 80 -m owner ! --uid-owner 1000 -j ACCEPT
+nft add rule ip nat OUTPUT tcp dport 80 skuid != 1000 counter accept
diff --git a/extensions/libxt_physdev.c b/extensions/libxt_physdev.c
new file mode 100644
index 0000000..a11faf4
--- /dev/null
+++ b/extensions/libxt_physdev.c
@@ -0,0 +1,149 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_physdev.h>
+
+enum {
+	O_PHYSDEV_IN = 0,
+	O_PHYSDEV_OUT,
+	O_PHYSDEV_IS_IN,
+	O_PHYSDEV_IS_OUT,
+	O_PHYSDEV_IS_BRIDGED,
+};
+
+static void physdev_help(void)
+{
+	printf(
+"physdev match options:\n"
+" [!] --physdev-in inputname[+]		bridge port name ([+] for wildcard)\n"
+" [!] --physdev-out outputname[+]	bridge port name ([+] for wildcard)\n"
+" [!] --physdev-is-in			arrived on a bridge device\n"
+" [!] --physdev-is-out			will leave on a bridge device\n"
+" [!] --physdev-is-bridged		it's a bridged packet\n");
+}
+
+#define s struct xt_physdev_info
+static const struct xt_option_entry physdev_opts[] = {
+	{.name = "physdev-in", .id = O_PHYSDEV_IN, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, physindev)},
+	{.name = "physdev-out", .id = O_PHYSDEV_OUT, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, physoutdev)},
+	{.name = "physdev-is-in", .id = O_PHYSDEV_IS_IN, .type = XTTYPE_NONE,
+	 .flags = XTOPT_INVERT},
+	{.name = "physdev-is-out", .id = O_PHYSDEV_IS_OUT,
+	 .type = XTTYPE_NONE, .flags = XTOPT_INVERT},
+	{.name = "physdev-is-bridged", .id = O_PHYSDEV_IS_BRIDGED,
+	 .type = XTTYPE_NONE, .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void physdev_parse(struct xt_option_call *cb)
+{
+	struct xt_physdev_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_PHYSDEV_IN:
+		xtables_parse_interface(cb->arg, info->physindev,
+				(unsigned char *)info->in_mask);
+		if (cb->invert)
+			info->invert |= XT_PHYSDEV_OP_IN;
+		info->bitmask |= XT_PHYSDEV_OP_IN;
+		break;
+	case O_PHYSDEV_OUT:
+		xtables_parse_interface(cb->arg, info->physoutdev,
+				(unsigned char *)info->out_mask);
+		if (cb->invert)
+			info->invert |= XT_PHYSDEV_OP_OUT;
+		info->bitmask |= XT_PHYSDEV_OP_OUT;
+		break;
+	case O_PHYSDEV_IS_IN:
+		info->bitmask |= XT_PHYSDEV_OP_ISIN;
+		if (cb->invert)
+			info->invert |= XT_PHYSDEV_OP_ISIN;
+		break;
+	case O_PHYSDEV_IS_OUT:
+		info->bitmask |= XT_PHYSDEV_OP_ISOUT;
+		if (cb->invert)
+			info->invert |= XT_PHYSDEV_OP_ISOUT;
+		break;
+	case O_PHYSDEV_IS_BRIDGED:
+		if (cb->invert)
+			info->invert |= XT_PHYSDEV_OP_BRIDGED;
+		info->bitmask |= XT_PHYSDEV_OP_BRIDGED;
+		break;
+	}
+}
+
+static void physdev_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM, "PHYSDEV: no physdev option specified");
+}
+
+static void
+physdev_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_physdev_info *info = (const void *)match->data;
+
+	printf(" PHYSDEV match");
+	if (info->bitmask & XT_PHYSDEV_OP_ISIN)
+		printf("%s --physdev-is-in",
+		       info->invert & XT_PHYSDEV_OP_ISIN ? " !":"");
+	if (info->bitmask & XT_PHYSDEV_OP_IN)
+		printf("%s --physdev-in %s",
+		(info->invert & XT_PHYSDEV_OP_IN) ? " !":"", info->physindev);
+
+	if (info->bitmask & XT_PHYSDEV_OP_ISOUT)
+		printf("%s --physdev-is-out",
+		       info->invert & XT_PHYSDEV_OP_ISOUT ? " !":"");
+	if (info->bitmask & XT_PHYSDEV_OP_OUT)
+		printf("%s --physdev-out %s",
+		(info->invert & XT_PHYSDEV_OP_OUT) ? " !":"", info->physoutdev);
+	if (info->bitmask & XT_PHYSDEV_OP_BRIDGED)
+		printf("%s --physdev-is-bridged",
+		       info->invert & XT_PHYSDEV_OP_BRIDGED ? " !":"");
+}
+
+static void physdev_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_physdev_info *info = (const void *)match->data;
+
+	if (info->bitmask & XT_PHYSDEV_OP_ISIN)
+		printf("%s --physdev-is-in",
+		       (info->invert & XT_PHYSDEV_OP_ISIN) ? " !" : "");
+	if (info->bitmask & XT_PHYSDEV_OP_IN)
+		printf("%s --physdev-in %s",
+		       (info->invert & XT_PHYSDEV_OP_IN) ? " !" : "",
+		       info->physindev);
+
+	if (info->bitmask & XT_PHYSDEV_OP_ISOUT)
+		printf("%s --physdev-is-out",
+		       (info->invert & XT_PHYSDEV_OP_ISOUT) ? " !" : "");
+	if (info->bitmask & XT_PHYSDEV_OP_OUT)
+		printf("%s --physdev-out %s",
+		       (info->invert & XT_PHYSDEV_OP_OUT) ? " !" : "",
+		       info->physoutdev);
+	if (info->bitmask & XT_PHYSDEV_OP_BRIDGED)
+		printf("%s --physdev-is-bridged",
+		       (info->invert & XT_PHYSDEV_OP_BRIDGED) ? " !" : "");
+}
+
+static struct xtables_match physdev_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "physdev",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_physdev_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_physdev_info)),
+	.help		= physdev_help,
+	.print		= physdev_print,
+	.save		= physdev_save,
+	.x6_parse	= physdev_parse,
+	.x6_fcheck	= physdev_check,
+	.x6_options	= physdev_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&physdev_match);
+}
diff --git a/extensions/libxt_physdev.man b/extensions/libxt_physdev.man
new file mode 100644
index 0000000..06b778a
--- /dev/null
+++ b/extensions/libxt_physdev.man
@@ -0,0 +1,34 @@
+This module matches on the bridge port input and output devices enslaved
+to a bridge device. This module is a part of the infrastructure that enables
+a transparent bridging IP firewall and is only useful for kernel versions
+above version 2.5.44.
+.TP
+[\fB!\fP] \fB\-\-physdev\-in\fP \fIname\fP
+Name of a bridge port via which a packet is received (only for
+packets entering the
+.BR INPUT ,
+.B FORWARD
+and
+.B PREROUTING
+chains). If the interface name ends in a "+", then any
+interface which begins with this name will match. If the packet didn't arrive
+through a bridge device, this packet won't match this option, unless '!' is used.
+.TP
+[\fB!\fP] \fB\-\-physdev\-out\fP \fIname\fP
+Name of a bridge port via which a packet is going to be sent (for bridged packets
+entering the
+.BR FORWARD
+and
+.B POSTROUTING
+chains).  If the interface name ends in a "+", then any
+interface which begins with this name will match.
+.TP
+[\fB!\fP] \fB\-\-physdev\-is\-in\fP
+Matches if the packet has entered through a bridge interface.
+.TP
+[\fB!\fP] \fB\-\-physdev\-is\-out\fP
+Matches if the packet will leave through a bridge interface.
+.TP
+[\fB!\fP] \fB\-\-physdev\-is\-bridged\fP
+Matches if the packet is being bridged and therefore is not being routed.
+This is only useful in the FORWARD and POSTROUTING chains.
diff --git a/extensions/libxt_physdev.t b/extensions/libxt_physdev.t
new file mode 100644
index 0000000..1fab7e1
--- /dev/null
+++ b/extensions/libxt_physdev.t
@@ -0,0 +1,14 @@
+:INPUT,FORWARD
+-m physdev --physdev-in lo;=;OK
+-m physdev --physdev-is-in --physdev-in lo;=;OK
+:OUTPUT,FORWARD
+# xt_physdev: using --physdev-out in the OUTPUT, FORWARD and POSTROUTING chains for non-bridged traffic is not supported anymore.
+# ERROR: should fail: iptables -A FORWARD -m physdev --physdev-out lo
+#-m physdev --physdev-out lo;;FAIL
+# ERROR: cannot load: iptables -A OUTPUT -m physdev --physdev-is-out --physdev-out lo
+#-m physdev --physdev-is-out --physdev-out lo;=;OK
+:FORWARD
+-m physdev --physdev-in lo --physdev-is-bridged;=;OK
+:POSTROUTING
+*mangle
+-m physdev --physdev-out lo --physdev-is-bridged;=;OK
diff --git a/extensions/libxt_pkttype.c b/extensions/libxt_pkttype.c
new file mode 100644
index 0000000..bf6f5b9
--- /dev/null
+++ b/extensions/libxt_pkttype.c
@@ -0,0 +1,171 @@
+/* 
+ * Shared library add-on to iptables to match 
+ * packets by their type (BROADCAST, UNICAST, MULTICAST). 
+ *
+ * Michal Ludvig <michal@logix.cz>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/if_packet.h>
+#include <linux/netfilter/xt_pkttype.h>
+
+enum {
+	O_PKTTYPE = 0,
+};
+
+struct pkttypes {
+	const char *name;
+	unsigned char pkttype;
+	unsigned char printhelp;
+	const char *help;
+};
+
+struct pkttypes_xlate {
+	const char *name;
+	unsigned char pkttype;
+};
+
+static const struct pkttypes supported_types[] = {
+	{"unicast", PACKET_HOST, 1, "to us"},
+	{"broadcast", PACKET_BROADCAST, 1, "to all"},
+	{"multicast", PACKET_MULTICAST, 1, "to group"},
+/*
+	{"otherhost", PACKET_OTHERHOST, 1, "to someone else"},
+	{"outgoing", PACKET_OUTGOING, 1, "outgoing of any type"},
+*/
+	/* aliases */
+	{"bcast", PACKET_BROADCAST, 0, NULL},
+	{"mcast", PACKET_MULTICAST, 0, NULL},
+	{"host", PACKET_HOST, 0, NULL}
+};
+
+static void print_types(void)
+{
+	unsigned int	i;
+	
+	printf("Valid packet types:\n");
+	for (i = 0; i < ARRAY_SIZE(supported_types); ++i)
+		if(supported_types[i].printhelp == 1)
+			printf("\t%-14s\t\t%s\n", supported_types[i].name, supported_types[i].help);
+	printf("\n");
+}
+
+static void pkttype_help(void)
+{
+	printf(
+"pkttype match options:\n"
+"[!] --pkt-type packettype    match packet type\n");
+	print_types();
+}
+
+static const struct xt_option_entry pkttype_opts[] = {
+	{.name = "pkt-type", .id = O_PKTTYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void parse_pkttype(const char *pkttype, struct xt_pkttype_info *info)
+{
+	unsigned int	i;
+	
+	for (i = 0; i < ARRAY_SIZE(supported_types); ++i)
+		if(strcasecmp(pkttype, supported_types[i].name)==0)
+		{
+			info->pkttype=supported_types[i].pkttype;
+			return;
+		}
+	
+	xtables_error(PARAMETER_PROBLEM, "Bad packet type '%s'", pkttype);
+}
+
+static void pkttype_parse(struct xt_option_call *cb)
+{
+	struct xt_pkttype_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	parse_pkttype(cb->arg, info);
+	if (cb->invert)
+		info->invert = 1;
+}
+
+static void print_pkttype(const struct xt_pkttype_info *info)
+{
+	unsigned int	i;
+	
+	for (i = 0; i < ARRAY_SIZE(supported_types); ++i)
+		if(supported_types[i].pkttype==info->pkttype)
+		{
+			printf("%s", supported_types[i].name);
+			return;
+		}
+
+	printf("%d", info->pkttype);	/* in case we didn't find an entry in named-packtes */
+}
+
+static void pkttype_print(const void *ip, const struct xt_entry_match *match,
+                          int numeric)
+{
+	const struct xt_pkttype_info *info = (const void *)match->data;
+	
+	printf(" PKTTYPE %s= ", info->invert ? "!" : "");
+	print_pkttype(info);
+}
+
+static void pkttype_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_pkttype_info *info = (const void *)match->data;
+	
+	printf("%s --pkt-type ", info->invert ? " !" : "");
+	print_pkttype(info);
+}
+
+static const struct pkttypes_xlate supported_types_xlate[] = {
+	{"unicast",	PACKET_HOST},
+	{"broadcast",	PACKET_BROADCAST},
+	{"multicast",	PACKET_MULTICAST},
+};
+
+static void print_pkttype_xlate(const struct xt_pkttype_info *info,
+				struct xt_xlate *xl)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(supported_types_xlate); ++i) {
+		if (supported_types_xlate[i].pkttype == info->pkttype) {
+			xt_xlate_add(xl, "%s", supported_types_xlate[i].name);
+			return;
+		}
+	}
+	xt_xlate_add(xl, "%d", info->pkttype);
+}
+
+static int pkttype_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_mt_params *params)
+{
+	const struct xt_pkttype_info *info = (const void *)params->match->data;
+
+	xt_xlate_add(xl, "pkttype%s ", info->invert ? " !=" : "");
+	print_pkttype_xlate(info, xl);
+
+	return 1;
+}
+
+static struct xtables_match pkttype_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "pkttype",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_pkttype_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_pkttype_info)),
+	.help		= pkttype_help,
+	.print		= pkttype_print,
+	.save		= pkttype_save,
+	.x6_parse	= pkttype_parse,
+	.x6_options	= pkttype_opts,
+	.xlate		= pkttype_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&pkttype_match);
+}
diff --git a/extensions/libxt_pkttype.man b/extensions/libxt_pkttype.man
new file mode 100644
index 0000000..4560c76
--- /dev/null
+++ b/extensions/libxt_pkttype.man
@@ -0,0 +1,3 @@
+This module matches the link-layer packet type.
+.TP
+[\fB!\fP] \fB\-\-pkt\-type\fP {\fBunicast\fP|\fBbroadcast\fP|\fBmulticast\fP}
diff --git a/extensions/libxt_pkttype.t b/extensions/libxt_pkttype.t
new file mode 100644
index 0000000..d93baea
--- /dev/null
+++ b/extensions/libxt_pkttype.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m pkttype --pkt-type unicast;=;OK
+-m pkttype --pkt-type broadcast;=;OK
+-m pkttype --pkt-type multicast;=;OK
+-m pkttype --pkt-type wrong;;FAIL
+-m pkttype;;FAIL
diff --git a/extensions/libxt_pkttype.txlate b/extensions/libxt_pkttype.txlate
new file mode 100644
index 0000000..6506a38
--- /dev/null
+++ b/extensions/libxt_pkttype.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A INPUT -m pkttype --pkt-type broadcast -j DROP
+nft add rule ip filter INPUT pkttype broadcast counter drop
+
+iptables-translate -A INPUT -m pkttype ! --pkt-type unicast -j DROP
+nft add rule ip filter INPUT pkttype != unicast counter drop
+
+iptables-translate -A INPUT -m pkttype --pkt-type multicast -j ACCEPT
+nft add rule ip filter INPUT pkttype multicast counter accept
diff --git a/extensions/libxt_policy.c b/extensions/libxt_policy.c
new file mode 100644
index 0000000..f9a4819
--- /dev/null
+++ b/extensions/libxt_policy.c
@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) 2005-2013 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <netdb.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_policy.h>
+
+enum {
+	O_DIRECTION = 0,
+	O_POLICY,
+	O_STRICT,
+	O_REQID,
+	O_SPI,
+	O_PROTO,
+	O_MODE,
+	O_TUNNELSRC,
+	O_TUNNELDST,
+	O_NEXT,
+	F_STRICT = 1 << O_STRICT,
+};
+
+static void policy_help(void)
+{
+	printf(
+"policy match options:\n"
+"  --dir in|out			match policy applied during decapsulation/\n"
+"				policy to be applied during encapsulation\n"
+"  --pol none|ipsec		match policy\n"
+"  --strict 			match entire policy instead of single element\n"
+"				at any position\n"
+"These options may be used repeatedly, to describe policy elements:\n"
+"[!] --reqid reqid		match reqid\n"
+"[!] --spi spi			match SPI\n"
+"[!] --proto proto		match protocol (ah/esp/ipcomp)\n"
+"[!] --mode mode 		match mode (transport/tunnel)\n"
+"[!] --tunnel-src addr/mask	match tunnel source\n"
+"[!] --tunnel-dst addr/mask	match tunnel destination\n"
+"  --next 			begin next element in policy\n");
+}
+
+static const struct xt_option_entry policy_opts[] = {
+	{.name = "dir", .id = O_DIRECTION, .type = XTTYPE_STRING},
+	{.name = "pol", .id = O_POLICY, .type = XTTYPE_STRING},
+	{.name = "strict", .id = O_STRICT, .type = XTTYPE_NONE},
+	{.name = "reqid", .id = O_REQID, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_MULTI | XTOPT_INVERT},
+	{.name = "spi", .id = O_SPI, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_MULTI | XTOPT_INVERT},
+	{.name = "tunnel-src", .id = O_TUNNELSRC, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_MULTI | XTOPT_INVERT},
+	{.name = "tunnel-dst", .id = O_TUNNELDST, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_MULTI | XTOPT_INVERT},
+	{.name = "proto", .id = O_PROTO, .type = XTTYPE_PROTOCOL,
+	 .flags = XTOPT_MULTI | XTOPT_INVERT},
+	{.name = "mode", .id = O_MODE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MULTI | XTOPT_INVERT},
+	{.name = "next", .id = O_NEXT, .type = XTTYPE_NONE,
+	 .flags = XTOPT_MULTI, .also = F_STRICT},
+	XTOPT_TABLEEND,
+};
+
+static int parse_direction(const char *s)
+{
+	if (strcmp(s, "in") == 0)
+		return XT_POLICY_MATCH_IN;
+	if (strcmp(s, "out") == 0)
+		return XT_POLICY_MATCH_OUT;
+	xtables_error(PARAMETER_PROBLEM, "policy_match: invalid dir \"%s\"", s);
+}
+
+static int parse_policy(const char *s)
+{
+	if (strcmp(s, "none") == 0)
+		return XT_POLICY_MATCH_NONE;
+	if (strcmp(s, "ipsec") == 0)
+		return 0;
+	xtables_error(PARAMETER_PROBLEM, "policy match: invalid policy \"%s\"", s);
+}
+
+static int parse_mode(const char *s)
+{
+	if (strcmp(s, "transport") == 0)
+		return XT_POLICY_MODE_TRANSPORT;
+	if (strcmp(s, "tunnel") == 0)
+		return XT_POLICY_MODE_TUNNEL;
+	xtables_error(PARAMETER_PROBLEM, "policy match: invalid mode \"%s\"", s);
+}
+
+static void policy_parse(struct xt_option_call *cb)
+{
+	struct xt_policy_info *info = cb->data;
+	struct xt_policy_elem *e = &info->pol[info->len];
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_DIRECTION:
+		info->flags |= parse_direction(cb->arg);
+		break;
+	case O_POLICY:
+		info->flags |= parse_policy(cb->arg);
+		break;
+	case O_STRICT:
+		info->flags |= XT_POLICY_MATCH_STRICT;
+		break;
+	case O_REQID:
+		if (e->match.reqid)
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: double --reqid option");
+		e->match.reqid = 1;
+		e->invert.reqid = cb->invert;
+		e->reqid = cb->val.u32;
+		break;
+	case O_SPI:
+		if (e->match.spi)
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: double --spi option");
+		e->match.spi = 1;
+		e->invert.spi = cb->invert;
+		e->spi = cb->val.u32;
+		break;
+	case O_TUNNELSRC:
+		if (e->match.saddr)
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: double --tunnel-src option");
+
+		e->match.saddr = 1;
+		e->invert.saddr = cb->invert;
+		memcpy(&e->saddr, &cb->val.haddr, sizeof(cb->val.haddr));
+		memcpy(&e->smask, &cb->val.hmask, sizeof(cb->val.hmask));
+                break;
+	case O_TUNNELDST:
+		if (e->match.daddr)
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: double --tunnel-dst option");
+		e->match.daddr = 1;
+		e->invert.daddr = cb->invert;
+		memcpy(&e->daddr, &cb->val.haddr, sizeof(cb->val.haddr));
+		memcpy(&e->dmask, &cb->val.hmask, sizeof(cb->val.hmask));
+		break;
+	case O_PROTO:
+		if (e->match.proto)
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: double --proto option");
+		e->proto = cb->val.protocol;
+		if (e->proto != IPPROTO_AH && e->proto != IPPROTO_ESP &&
+		    e->proto != IPPROTO_COMP)
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: protocol must be ah/esp/ipcomp");
+		e->match.proto = 1;
+		e->invert.proto = cb->invert;
+		break;
+	case O_MODE:
+		if (e->match.mode)
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: double --mode option");
+		e->match.mode = 1;
+		e->invert.mode = cb->invert;
+		e->mode = parse_mode(cb->arg);
+		break;
+	case O_NEXT:
+		if (++info->len == XT_POLICY_MAX_ELEM)
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: maximum policy depth reached");
+		break;
+	}
+}
+
+static void policy_check(struct xt_fcheck_call *cb)
+{
+	struct xt_policy_info *info = cb->data;
+	const struct xt_policy_elem *e;
+	int i;
+
+	/*
+	 * The old "no parameters given" check is carried out
+	 * by testing for --dir.
+	 */
+	if (!(info->flags & (XT_POLICY_MATCH_IN | XT_POLICY_MATCH_OUT)))
+		xtables_error(PARAMETER_PROBLEM,
+		           "policy match: neither --dir in nor --dir out specified");
+
+	if (info->flags & XT_POLICY_MATCH_NONE) {
+		if (info->flags & XT_POLICY_MATCH_STRICT)
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: policy none but --strict given");
+
+		if (info->len != 0)
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: policy none but policy given");
+	} else
+		info->len++;	/* increase len by 1, no --next after last element */
+
+	/*
+	 * This is already represented with O_NEXT requiring F_STRICT in the
+	 * options table, but will keep this code as a comment for reference.
+	 *
+	if (!(info->flags & XT_POLICY_MATCH_STRICT) && info->len > 1)
+		xtables_error(PARAMETER_PROBLEM,
+		           "policy match: multiple elements but no --strict");
+	 */
+
+	for (i = 0; i < info->len; i++) {
+		e = &info->pol[i];
+
+		if (info->flags & XT_POLICY_MATCH_STRICT &&
+		    !(e->match.reqid || e->match.spi || e->match.saddr ||
+		      e->match.daddr || e->match.proto || e->match.mode))
+			xtables_error(PARAMETER_PROBLEM,
+				"policy match: empty policy element %u. "
+				"--strict is in effect, but at least one of "
+				"reqid, spi, tunnel-src, tunnel-dst, proto or "
+				"mode is required.", i);
+
+		if ((e->match.saddr || e->match.daddr)
+		    && ((e->mode == XT_POLICY_MODE_TUNNEL && e->invert.mode) ||
+		        (e->mode == XT_POLICY_MODE_TRANSPORT && !e->invert.mode)))
+			xtables_error(PARAMETER_PROBLEM,
+			           "policy match: --tunnel-src/--tunnel-dst "
+			           "is only valid in tunnel mode");
+	}
+}
+
+static void print_mode(const char *prefix, uint8_t mode, int numeric)
+{
+	printf(" %smode ", prefix);
+
+	switch (mode) {
+	case XT_POLICY_MODE_TRANSPORT:
+		printf("transport");
+		break;
+	case XT_POLICY_MODE_TUNNEL:
+		printf("tunnel");
+		break;
+	default:
+		printf("???");
+		break;
+	}
+}
+
+static void print_proto(const char *prefix, uint8_t proto, int numeric)
+{
+	const struct protoent *p = NULL;
+
+	printf(" %sproto ", prefix);
+	if (!numeric)
+		p = getprotobynumber(proto);
+	if (p != NULL)
+		printf("%s", p->p_name);
+	else
+		printf("%u", proto);
+}
+
+#define PRINT_INVERT(x)		\
+do {				\
+	if (x)			\
+		printf(" !");	\
+} while(0)
+
+static void print_entry(const char *prefix, const struct xt_policy_elem *e,
+                        bool numeric, uint8_t family)
+{
+	if (e->match.reqid) {
+		PRINT_INVERT(e->invert.reqid);
+		printf(" %sreqid %u", prefix, e->reqid);
+	}
+	if (e->match.spi) {
+		PRINT_INVERT(e->invert.spi);
+		printf(" %sspi 0x%x", prefix, e->spi);
+	}
+	if (e->match.proto) {
+		PRINT_INVERT(e->invert.proto);
+		print_proto(prefix, e->proto, numeric);
+	}
+	if (e->match.mode) {
+		PRINT_INVERT(e->invert.mode);
+		print_mode(prefix, e->mode, numeric);
+	}
+	if (e->match.daddr) {
+		PRINT_INVERT(e->invert.daddr);
+		if (family == NFPROTO_IPV6)
+			printf(" %stunnel-dst %s%s", prefix,
+			       xtables_ip6addr_to_numeric(&e->daddr.a6),
+			       xtables_ip6mask_to_numeric(&e->dmask.a6));
+		else
+			printf(" %stunnel-dst %s%s", prefix,
+			       xtables_ipaddr_to_numeric(&e->daddr.a4),
+			       xtables_ipmask_to_numeric(&e->dmask.a4));
+	}
+	if (e->match.saddr) {
+		PRINT_INVERT(e->invert.saddr);
+		if (family == NFPROTO_IPV6)
+			printf(" %stunnel-src %s%s", prefix,
+			       xtables_ip6addr_to_numeric(&e->saddr.a6),
+			       xtables_ip6mask_to_numeric(&e->smask.a6));
+		else
+			printf(" %stunnel-src %s%s", prefix,
+			       xtables_ipaddr_to_numeric(&e->saddr.a4),
+			       xtables_ipmask_to_numeric(&e->smask.a4));
+	}
+}
+
+static void print_flags(const char *prefix, const struct xt_policy_info *info)
+{
+	if (info->flags & XT_POLICY_MATCH_IN)
+		printf(" %sdir in", prefix);
+	else
+		printf(" %sdir out", prefix);
+
+	if (info->flags & XT_POLICY_MATCH_NONE)
+		printf(" %spol none", prefix);
+	else
+		printf(" %spol ipsec", prefix);
+
+	if (info->flags & XT_POLICY_MATCH_STRICT)
+		printf(" %sstrict", prefix);
+}
+
+static void policy4_print(const void *ip, const struct xt_entry_match *match,
+                          int numeric)
+{
+	const struct xt_policy_info *info = (void *)match->data;
+	unsigned int i;
+
+	printf(" policy match");
+	print_flags("", info);
+	for (i = 0; i < info->len; i++) {
+		if (info->len > 1)
+			printf(" [%u]", i);
+		print_entry("", &info->pol[i], numeric, NFPROTO_IPV4);
+	}
+}
+
+static void policy6_print(const void *ip, const struct xt_entry_match *match,
+                          int numeric)
+{
+	const struct xt_policy_info *info = (void *)match->data;
+	unsigned int i;
+
+	printf(" policy match");
+	print_flags("", info);
+	for (i = 0; i < info->len; i++) {
+		if (info->len > 1)
+			printf(" [%u]", i);
+		print_entry("", &info->pol[i], numeric, NFPROTO_IPV6);
+	}
+}
+
+static void policy4_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_policy_info *info = (void *)match->data;
+	unsigned int i;
+
+	print_flags("--", info);
+	for (i = 0; i < info->len; i++) {
+		print_entry("--", &info->pol[i], false, NFPROTO_IPV4);
+		if (i + 1 < info->len)
+			printf(" --next");
+	}
+}
+
+static void policy6_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_policy_info *info = (void *)match->data;
+	unsigned int i;
+
+	print_flags("--", info);
+	for (i = 0; i < info->len; i++) {
+		print_entry("--", &info->pol[i], false, NFPROTO_IPV6);
+		if (i + 1 < info->len)
+			printf(" --next");
+	}
+}
+
+static int policy_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_mt_params *params)
+{
+	static const unsigned int allowed = XT_POLICY_MATCH_STRICT |
+					    XT_POLICY_MATCH_NONE |
+					    XT_POLICY_MATCH_IN;
+	static const struct xt_policy_elem empty;
+	const struct xt_policy_info *info = (const void *)params->match->data;
+
+	if ((info->flags & ~allowed) || info->len > 1)
+		return 0;
+
+	if (memcmp(&info->pol[0], &empty, sizeof(empty)))
+		return 0;
+
+	xt_xlate_add(xl, "meta secpath ");
+
+	if (info->flags & XT_POLICY_MATCH_NONE)
+		xt_xlate_add(xl, "missing");
+	else
+		xt_xlate_add(xl, "exists");
+
+	return 1;
+}
+
+static struct xtables_match policy_mt_reg[] = {
+	{
+		.name          = "policy",
+		.version       = XTABLES_VERSION,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_policy_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_policy_info)),
+		.help          = policy_help,
+		.x6_parse      = policy_parse,
+		.x6_fcheck     = policy_check,
+		.print         = policy4_print,
+		.save          = policy4_save,
+		.x6_options    = policy_opts,
+		.xlate         = policy_xlate,
+	},
+	{
+		.name          = "policy",
+		.version       = XTABLES_VERSION,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_policy_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_policy_info)),
+		.help          = policy_help,
+		.x6_parse      = policy_parse,
+		.x6_fcheck     = policy_check,
+		.print         = policy6_print,
+		.save          = policy6_save,
+		.x6_options    = policy_opts,
+		.xlate         = policy_xlate,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(policy_mt_reg, ARRAY_SIZE(policy_mt_reg));
+}
diff --git a/extensions/libxt_policy.man b/extensions/libxt_policy.man
new file mode 100644
index 0000000..12c01b4
--- /dev/null
+++ b/extensions/libxt_policy.man
@@ -0,0 +1,53 @@
+This module matches the policy used by IPsec for handling a packet.
+.TP
+\fB\-\-dir\fP {\fBin\fP|\fBout\fP}
+Used to select whether to match the policy used for decapsulation or the
+policy that will be used for encapsulation.
+.B in
+is valid in the
+.B PREROUTING, INPUT and FORWARD
+chains,
+.B out
+is valid in the
+.B POSTROUTING, OUTPUT and FORWARD
+chains.
+.TP
+\fB\-\-pol\fP {\fBnone\fP|\fBipsec\fP}
+Matches if the packet is subject to IPsec processing. \fB\-\-pol none\fP
+cannot be combined with \fB\-\-strict\fP.
+.TP
+\fB\-\-strict\fP
+Selects whether to match the exact policy or match if any rule of
+the policy matches the given policy.
+.PP
+For each policy element that is to be described, one can use one or more of
+the following options. When \fB\-\-strict\fP is in effect, at least one must be
+used per element.
+.TP
+[\fB!\fP] \fB\-\-reqid\fP \fIid\fP
+Matches the reqid of the policy rule. The reqid can be specified with
+.B setkey(8)
+using
+.B unique:id
+as level.
+.TP
+[\fB!\fP] \fB\-\-spi\fP \fIspi\fP
+Matches the SPI of the SA.
+.TP
+[\fB!\fP] \fB\-\-proto\fP {\fBah\fP|\fBesp\fP|\fBipcomp\fP}
+Matches the encapsulation protocol.
+.TP
+[\fB!\fP] \fB\-\-mode\fP {\fBtunnel\fP|\fBtransport\fP}
+Matches the encapsulation mode.
+.TP
+[\fB!\fP] \fB\-\-tunnel\-src\fP \fIaddr\fP[\fB/\fP\fImask\fP]
+Matches the source end-point address of a tunnel mode SA.
+Only valid with \fB\-\-mode tunnel\fP.
+.TP
+[\fB!\fP] \fB\-\-tunnel\-dst\fP \fIaddr\fP[\fB/\fP\fImask\fP]
+Matches the destination end-point address of a tunnel mode SA.
+Only valid with \fB\-\-mode tunnel\fP.
+.TP
+\fB\-\-next\fP
+Start the next element in the policy specification. Can only be used with
+\fB\-\-strict\fP.
diff --git a/extensions/libxt_policy.t b/extensions/libxt_policy.t
new file mode 100644
index 0000000..6524122
--- /dev/null
+++ b/extensions/libxt_policy.t
@@ -0,0 +1,8 @@
+:INPUT,FORWARD
+-m policy --dir in --pol ipsec;=;OK
+-m policy --dir in --pol ipsec --proto ipcomp;=;OK
+-m policy --dir in --pol ipsec --strict;;FAIL
+-m policy --dir in --pol ipsec --strict --reqid 1 --spi 0x1 --proto ipcomp;=;OK
+-m policy --dir in --pol ipsec --strict --reqid 1 --spi 0x1 --proto esp --mode tunnel --tunnel-dst 10.0.0.0/8 --tunnel-src 10.0.0.0/8 --next --reqid 2;=;OK
+-m policy --dir in --pol ipsec --strict --reqid 1 --spi 0x1 --proto esp --tunnel-dst 10.0.0.0/8;;FAIL
+-m policy --dir in --pol ipsec --strict --reqid 1 --spi 0x1 --proto ipcomp --mode tunnel --tunnel-dst 10.0.0.0/8 --tunnel-src 10.0.0.0/8 --next --reqid 2;=;OK
diff --git a/extensions/libxt_policy.txlate b/extensions/libxt_policy.txlate
new file mode 100644
index 0000000..66788a7
--- /dev/null
+++ b/extensions/libxt_policy.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A INPUT -m policy --pol ipsec --dir in
+nft add rule ip filter INPUT meta secpath exists counter
+
+iptables-translate -A INPUT -m policy --pol none --dir in
+nft add rule ip filter INPUT meta secpath missing counter
diff --git a/extensions/libxt_quota.c b/extensions/libxt_quota.c
new file mode 100644
index 0000000..bad77d2
--- /dev/null
+++ b/extensions/libxt_quota.c
@@ -0,0 +1,82 @@
+/*
+ * Shared library add-on to iptables to add quota support
+ *
+ * Sam Johnston <samj@samj.net>
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_quota.h>
+
+enum {
+	O_QUOTA = 0,
+};
+
+static const struct xt_option_entry quota_opts[] = {
+	{.name = "quota", .id = O_QUOTA, .type = XTTYPE_UINT64,
+	 .flags = XTOPT_MAND | XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_quota_info, quota)},
+	XTOPT_TABLEEND,
+};
+
+static void quota_help(void)
+{
+	printf("quota match options:\n"
+	       "[!] --quota quota		quota (bytes)\n");
+}
+
+static void
+quota_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_quota_info *q = (const void *)match->data;
+	printf(" quota: %llu bytes", (unsigned long long)q->quota);
+}
+
+static void
+quota_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_quota_info *q = (const void *)match->data;
+
+	if (q->flags & XT_QUOTA_INVERT)
+		printf(" !");
+	printf(" --quota %llu", (unsigned long long) q->quota);
+}
+
+static void quota_parse(struct xt_option_call *cb)
+{
+	struct xt_quota_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->invert)
+		info->flags |= XT_QUOTA_INVERT;
+}
+
+static int quota_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_quota_info *q = (void *)params->match->data;
+
+	xt_xlate_add(xl, "quota %s%llu bytes",
+		     q->flags & XT_QUOTA_INVERT ? "over " : "",
+		     (unsigned long long) q->quota);
+	return 1;
+}
+
+static struct xtables_match quota_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "quota",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof (struct xt_quota_info)),
+	.userspacesize	= offsetof(struct xt_quota_info, master),
+	.help		= quota_help,
+	.print		= quota_print,
+	.save		= quota_save,
+	.x6_parse	= quota_parse,
+	.x6_options	= quota_opts,
+	.xlate		= quota_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&quota_match);
+}
diff --git a/extensions/libxt_quota.man b/extensions/libxt_quota.man
new file mode 100644
index 0000000..fbecf37
--- /dev/null
+++ b/extensions/libxt_quota.man
@@ -0,0 +1,7 @@
+Implements network quotas by decrementing a byte counter with each
+packet. The condition matches until the byte counter reaches zero. Behavior
+is reversed with negation (i.e. the condition does not match until the
+byte counter reaches zero).
+.TP
+[\fB!\fP] \fB\-\-quota\fP \fIbytes\fP
+The quota in bytes.
diff --git a/extensions/libxt_quota.t b/extensions/libxt_quota.t
new file mode 100644
index 0000000..c568427
--- /dev/null
+++ b/extensions/libxt_quota.t
@@ -0,0 +1,7 @@
+:INPUT,FORWARD,OUTPUT
+-m quota --quota 0;=;OK
+-m quota ! --quota 0;=;OK
+-m quota --quota 18446744073709551615;=;OK
+-m quota ! --quota 18446744073709551615;=;OK
+-m quota --quota 18446744073709551616;;FAIL
+-m quota;;FAIL
diff --git a/extensions/libxt_quota.txlate b/extensions/libxt_quota.txlate
new file mode 100644
index 0000000..9114214
--- /dev/null
+++ b/extensions/libxt_quota.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A OUTPUT -m quota --quota 111
+nft add rule ip filter OUTPUT quota 111 bytes counter
+
+iptables-translate -A OUTPUT -m quota ! --quota 111
+nft add rule ip filter OUTPUT quota over 111 bytes counter
diff --git a/extensions/libxt_quota2.c b/extensions/libxt_quota2.c
new file mode 100644
index 0000000..d004cca
--- /dev/null
+++ b/extensions/libxt_quota2.c
@@ -0,0 +1,141 @@
+/*
+ *	"quota2" match extension for iptables
+ *	Sam Johnston <samj [at] samj net>
+ *	Jan Engelhardt <jengelh [at] medozas de>, 2008
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License; either
+ *	version 2 of the License, or any later version, as published by the
+ *	Free Software Foundation.
+ */
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_quota2.h>
+
+enum {
+	FL_QUOTA     = 1 << 0,
+	FL_NAME      = 1 << 1,
+	FL_GROW      = 1 << 2,
+	FL_PACKET    = 1 << 3,
+	FL_NO_CHANGE = 1 << 4,
+};
+
+enum {
+	O_QUOTA     = 0,
+	O_NAME,
+	O_GROW,
+	O_PACKET,
+	O_NO_CHANGE,
+};
+
+
+static const struct xt_option_entry quota_mt2_opts[] = {
+	{.name = "grow", .id = O_GROW, .type = XTTYPE_NONE},
+	{.name = "no-change", .id = O_NO_CHANGE, .type = XTTYPE_NONE},
+	{.name = "name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_quota_mtinfo2, name)},
+	{.name = "quota", .id = O_QUOTA, .type = XTTYPE_UINT64,
+	 .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_quota_mtinfo2, quota)},
+	{.name = "packets", .id = O_PACKET, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static void quota_mt2_help(void)
+{
+	printf(
+	"quota match options:\n"
+	"    --grow           provide an increasing counter\n"
+	"    --no-change      never change counter/quota value for matching packets\n"
+	"    --name name      name for the file in sysfs\n"
+	"[!] --quota quota    initial quota (bytes or packets)\n"
+	"    --packets        count packets instead of bytes\n"
+	);
+}
+
+static void quota_mt2_parse(struct xt_option_call *cb)
+{
+	struct xt_quota_mtinfo2 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_GROW:
+		info->flags |= XT_QUOTA_GROW;
+		break;
+	case O_NO_CHANGE:
+		info->flags |= XT_QUOTA_NO_CHANGE;
+		break;
+	case O_NAME:
+		break;
+	case O_PACKET:
+		info->flags |= XT_QUOTA_PACKET;
+		break;
+	case O_QUOTA:
+		if (cb->invert)
+			info->flags |= XT_QUOTA_INVERT;
+		break;
+	}
+}
+
+static void
+quota_mt2_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_quota_mtinfo2 *q = (void *)match->data;
+
+	if (q->flags & XT_QUOTA_INVERT)
+		printf(" !");
+	if (q->flags & XT_QUOTA_GROW)
+		printf(" --grow ");
+	if (q->flags & XT_QUOTA_NO_CHANGE)
+		printf(" --no-change ");
+	if (q->flags & XT_QUOTA_PACKET)
+		printf(" --packets ");
+	if (*q->name != '\0')
+		printf(" --name %s ", q->name);
+	printf(" --quota %llu ", (unsigned long long)q->quota);
+}
+
+static void quota_mt2_print(const void *ip, const struct xt_entry_match *match,
+                            int numeric)
+{
+	const struct xt_quota_mtinfo2 *q = (const void *)match->data;
+
+	if (q->flags & XT_QUOTA_INVERT)
+		printf(" !");
+	if (q->flags & XT_QUOTA_GROW)
+		printf(" counter");
+	else
+		printf(" quota");
+	if (*q->name != '\0')
+		printf(" %s:", q->name);
+	printf(" %llu ", (unsigned long long)q->quota);
+	if (q->flags & XT_QUOTA_PACKET)
+		printf("packets ");
+	else
+		printf("bytes ");
+	if (q->flags & XT_QUOTA_NO_CHANGE)
+		printf("(no-change mode) ");
+}
+
+static struct xtables_match quota_mt2_reg = {
+	.family        = NFPROTO_UNSPEC,
+	.revision      = 3,
+	.name          = "quota2",
+	.version       = XTABLES_VERSION,
+	.size          = XT_ALIGN(sizeof (struct xt_quota_mtinfo2)),
+	.userspacesize = offsetof(struct xt_quota_mtinfo2, quota),
+	.help          = quota_mt2_help,
+	.x6_parse      = quota_mt2_parse,
+	.print         = quota_mt2_print,
+	.save          = quota_mt2_save,
+	.x6_options    = quota_mt2_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&quota_mt2_reg);
+}
diff --git a/extensions/libxt_quota2.man b/extensions/libxt_quota2.man
new file mode 100644
index 0000000..c2e6b44
--- /dev/null
+++ b/extensions/libxt_quota2.man
@@ -0,0 +1,37 @@
+The "quota2" implements a named counter which can be increased or decreased
+on a per-match basis. Available modes are packet counting or byte counting.
+The value of the counter can be read and reset through procfs, thereby making
+this match a minimalist accounting tool.
+.PP
+When counting down from the initial quota, the counter will stop at 0 and
+the match will return false, just like the original "quota" match. In growing
+(upcounting) mode, it will always return true.
+.TP
+\fB\-\-grow\fP
+Count upwards instead of downwards.
+.TP
+\fB\-\-no\-change\fP
+Makes it so the counter or quota amount is never changed by packets matching
+this rule. This is only really useful in "quota" mode, as it will allow you to
+use complex prerouting rules in association with the quota system, without
+counting a packet twice.
+.TP
+\fB\-\-name\fP \fIname\fP
+Assign the counter a specific name. This option must be present, as an empty
+name is not allowed. Names starting with a dot or names containing a slash are
+prohibited.
+.TP
+[\fB!\fP] \fB\-\-quota\fP \fIiq\fP
+Specify the initial quota for this counter. If the counter already exists,
+it is not reset. An "!" may be used to invert the result of the match. The
+negation has no effect when \fB\-\-grow\fP is used.
+.TP
+\fB\-\-packets\fP
+Count packets instead of bytes that passed the quota2 match.
+.PP
+Because counters in quota2 can be shared, you can combine them for various
+purposes, for example, a bytebucket filter that only lets as much traffic go
+out as has come in:
+.PP
+\-A INPUT \-p tcp \-\-dport 6881 \-m quota \-\-name bt \-\-grow;
+\-A OUTPUT \-p tcp \-\-sport 6881 \-m quota \-\-name bt;
diff --git a/extensions/libxt_rateest.c b/extensions/libxt_rateest.c
new file mode 100644
index 0000000..fb24412
--- /dev/null
+++ b/extensions/libxt_rateest.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2008-2013 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <getopt.h>
+
+#include <xtables.h>
+#include <linux/netfilter/xt_rateest.h>
+
+static void rateest_help(void)
+{
+	printf(
+"rateest match options:\n"
+" --rateest1 name		Rate estimator name\n"
+" --rateest2 name		Rate estimator name\n"
+" --rateest-delta		Compare difference(s) to given rate(s)\n"
+" --rateest-bps1 [bps]		Compare bps\n"
+" --rateest-pps1 [pps]		Compare pps\n"
+" --rateest-bps2 [bps]		Compare bps\n"
+" --rateest-pps2 [pps]		Compare pps\n"
+" [!] --rateest-lt		Match if rate is less than given rate/estimator\n"
+" [!] --rateest-gt		Match if rate is greater than given rate/estimator\n"
+" [!] --rateest-eq		Match if rate is equal to given rate/estimator\n");
+}
+
+enum rateest_options {
+	OPT_RATEEST1,
+	OPT_RATEEST2,
+	OPT_RATEEST_BPS1,
+	OPT_RATEEST_PPS1,
+	OPT_RATEEST_BPS2,
+	OPT_RATEEST_PPS2,
+	OPT_RATEEST_DELTA,
+	OPT_RATEEST_LT,
+	OPT_RATEEST_GT,
+	OPT_RATEEST_EQ,
+};
+
+static const struct option rateest_opts[] = {
+	{.name = "rateest1",      .has_arg = true,  .val = OPT_RATEEST1},
+	{.name = "rateest",       .has_arg = true,  .val = OPT_RATEEST1}, /* alias for absolute mode */
+	{.name = "rateest2",      .has_arg = true,  .val = OPT_RATEEST2},
+	{.name = "rateest-bps1",  .has_arg = false, .val = OPT_RATEEST_BPS1},
+	{.name = "rateest-pps1",  .has_arg = false, .val = OPT_RATEEST_PPS1},
+	{.name = "rateest-bps2",  .has_arg = false, .val = OPT_RATEEST_BPS2},
+	{.name = "rateest-pps2",  .has_arg = false, .val = OPT_RATEEST_PPS2},
+	{.name = "rateest-bps",   .has_arg = false, .val = OPT_RATEEST_BPS2}, /* alias for absolute mode */
+	{.name = "rateest-pps",   .has_arg = false, .val = OPT_RATEEST_PPS2}, /* alias for absolute mode */
+	{.name = "rateest-delta", .has_arg = false, .val = OPT_RATEEST_DELTA},
+	{.name = "rateest-lt",    .has_arg = false, .val = OPT_RATEEST_LT},
+	{.name = "rateest-gt",    .has_arg = false, .val = OPT_RATEEST_GT},
+	{.name = "rateest-eq",    .has_arg = false, .val = OPT_RATEEST_EQ},
+	XT_GETOPT_TABLEEND,
+};
+
+/* Copied from iproute. See http://physics.nist.gov/cuu/Units/binary.html */
+static const struct rate_suffix {
+	const char *name;
+	double scale;
+} suffixes[] = {
+	{ "bit",	1. },
+	{ "Kibit",	1024. },
+	{ "kbit",	1000. },
+	{ "Mibit",	1024.*1024. },
+	{ "mbit",	1000000. },
+	{ "Gibit",	1024.*1024.*1024. },
+	{ "gbit",	1000000000. },
+	{ "Tibit",	1024.*1024.*1024.*1024. },
+	{ "tbit",	1000000000000. },
+	{ "Bps",	8. },
+	{ "KiBps",	8.*1024. },
+	{ "KBps",	8000. },
+	{ "MiBps",	8.*1024*1024. },
+	{ "MBps",	8000000. },
+	{ "GiBps",	8.*1024.*1024.*1024. },
+	{ "GBps",	8000000000. },
+	{ "TiBps",	8.*1024.*1024.*1024.*1024. },
+	{ "TBps",	8000000000000. },
+	{NULL},
+};
+
+static int
+rateest_get_rate(uint32_t *rate, const char *str)
+{
+	char *p;
+	double bps = strtod(str, &p);
+	const struct rate_suffix *s;
+
+	if (p == str)
+		return -1;
+
+	if (*p == '\0') {
+		*rate = bps / 8.;	/* assume bytes/sec */
+		return 0;
+	}
+
+	for (s = suffixes; s->name; ++s) {
+		if (strcasecmp(s->name, p) == 0) {
+			*rate = (bps * s->scale) / 8.;
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+static int
+rateest_parse(int c, char **argv, int invert, unsigned int *flags,
+	      const void *entry, struct xt_entry_match **match)
+{
+	struct xt_rateest_match_info *info = (void *)(*match)->data;
+	unsigned int val;
+
+	switch (c) {
+	case OPT_RATEEST1:
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: rateest can't be inverted");
+
+		if (*flags & (1 << c))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: can't specify --rateest1 twice");
+		*flags |= 1 << c;
+
+		strncpy(info->name1, optarg, sizeof(info->name1) - 1);
+		break;
+
+	case OPT_RATEEST2:
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: rateest can't be inverted");
+
+		if (*flags & (1 << c))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: can't specify --rateest2 twice");
+		*flags |= 1 << c;
+
+		strncpy(info->name2, optarg, sizeof(info->name2) - 1);
+		info->flags |= XT_RATEEST_MATCH_REL;
+		break;
+
+	case OPT_RATEEST_BPS1:
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: rateest-bps can't be inverted");
+
+		if (*flags & (1 << c))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: can't specify --rateest-bps1 twice");
+		*flags |= 1 << c;
+
+		info->flags |= XT_RATEEST_MATCH_BPS;
+
+		/* The rate is optional and only required in absolute mode */
+		if (!argv[optind] || *argv[optind] == '-' || *argv[optind] == '!')
+			break;
+
+		if (rateest_get_rate(&info->bps1, argv[optind]) < 0)
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: could not parse rate `%s'",
+				   argv[optind]);
+		optind++;
+		break;
+
+	case OPT_RATEEST_PPS1:
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: rateest-pps can't be inverted");
+
+		if (*flags & (1 << c))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: can't specify --rateest-pps1 twice");
+		*flags |= 1 << c;
+
+		info->flags |= XT_RATEEST_MATCH_PPS;
+
+		/* The rate is optional and only required in absolute mode */
+		if (!argv[optind] || *argv[optind] == '-' || *argv[optind] == '!')
+			break;
+
+		if (!xtables_strtoui(argv[optind], NULL, &val, 0, UINT32_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: could not parse pps `%s'",
+				   argv[optind]);
+		info->pps1 = val;
+		optind++;
+		break;
+
+	case OPT_RATEEST_BPS2:
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: rateest-bps can't be inverted");
+
+		if (*flags & (1 << c))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: can't specify --rateest-bps2 twice");
+		*flags |= 1 << c;
+
+		info->flags |= XT_RATEEST_MATCH_BPS;
+
+		/* The rate is optional and only required in absolute mode */
+		if (!argv[optind] || *argv[optind] == '-' || *argv[optind] == '!')
+			break;
+
+		if (rateest_get_rate(&info->bps2, argv[optind]) < 0)
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: could not parse rate `%s'",
+				   argv[optind]);
+		optind++;
+		break;
+
+	case OPT_RATEEST_PPS2:
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: rateest-pps can't be inverted");
+
+		if (*flags & (1 << c))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: can't specify --rateest-pps2 twice");
+		*flags |= 1 << c;
+
+		info->flags |= XT_RATEEST_MATCH_PPS;
+
+		/* The rate is optional and only required in absolute mode */
+		if (!argv[optind] || *argv[optind] == '-' || *argv[optind] == '!')
+			break;
+
+		if (!xtables_strtoui(argv[optind], NULL, &val, 0, UINT32_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: could not parse pps `%s'",
+				   argv[optind]);
+		info->pps2 = val;
+		optind++;
+		break;
+
+	case OPT_RATEEST_DELTA:
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: rateest-delta can't be inverted");
+
+		if (*flags & (1 << c))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: can't specify --rateest-delta twice");
+		*flags |= 1 << c;
+
+		info->flags |= XT_RATEEST_MATCH_DELTA;
+		break;
+
+	case OPT_RATEEST_EQ:
+		if (*flags & (1 << c))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: can't specify lt/gt/eq twice");
+		*flags |= 1 << c;
+
+		info->mode = XT_RATEEST_MATCH_EQ;
+		if (invert)
+			info->flags |= XT_RATEEST_MATCH_INVERT;
+		break;
+
+	case OPT_RATEEST_LT:
+		if (*flags & (1 << c))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: can't specify lt/gt/eq twice");
+		*flags |= 1 << c;
+
+		info->mode = XT_RATEEST_MATCH_LT;
+		if (invert)
+			info->flags |= XT_RATEEST_MATCH_INVERT;
+		break;
+
+	case OPT_RATEEST_GT:
+		if (*flags & (1 << c))
+			xtables_error(PARAMETER_PROBLEM,
+				   "rateest: can't specify lt/gt/eq twice");
+		*flags |= 1 << c;
+
+		info->mode = XT_RATEEST_MATCH_GT;
+		if (invert)
+			info->flags |= XT_RATEEST_MATCH_INVERT;
+		break;
+	}
+
+	return 1;
+}
+
+static void rateest_final_check(struct xt_fcheck_call *cb)
+{
+	struct xt_rateest_match_info *info = cb->data;
+
+	if (info == NULL)
+		xtables_error(PARAMETER_PROBLEM, "rateest match: "
+		           "you need to specify some flags");
+	if (!(info->flags & XT_RATEEST_MATCH_REL))
+		info->flags |= XT_RATEEST_MATCH_ABS;
+}
+
+static void
+rateest_print_rate(uint32_t rate, int numeric)
+{
+	double tmp = (double)rate*8;
+
+	if (numeric)
+		printf(" %u", rate);
+	else if (tmp >= 1000.0*1000000.0)
+		printf(" %.0fMbit", tmp/1000000.0);
+	else if (tmp >= 1000.0 * 1000.0)
+		printf(" %.0fKbit", tmp/1000.0);
+	else
+		printf(" %.0fbit", tmp);
+}
+
+static void
+rateest_print_mode(const struct xt_rateest_match_info *info,
+                   const char *prefix)
+{
+	if (info->flags & XT_RATEEST_MATCH_INVERT)
+		printf(" !");
+
+	switch (info->mode) {
+	case XT_RATEEST_MATCH_EQ:
+		printf(" %seq", prefix);
+		break;
+	case XT_RATEEST_MATCH_LT:
+		printf(" %slt", prefix);
+		break;
+	case XT_RATEEST_MATCH_GT:
+		printf(" %sgt", prefix);
+		break;
+	default:
+		exit(1);
+	}
+}
+
+static void
+rateest_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_rateest_match_info *info = (const void *)match->data;
+
+	printf(" rateest match ");
+
+	printf("%s", info->name1);
+	if (info->flags & XT_RATEEST_MATCH_DELTA)
+		printf(" delta");
+
+	if (info->flags & XT_RATEEST_MATCH_BPS) {
+		printf(" bps");
+		if (info->flags & XT_RATEEST_MATCH_DELTA)
+			rateest_print_rate(info->bps1, numeric);
+		if (info->flags & XT_RATEEST_MATCH_ABS) {
+			rateest_print_rate(info->bps2, numeric);
+			rateest_print_mode(info, "");
+		}
+	}
+	if (info->flags & XT_RATEEST_MATCH_PPS) {
+		printf(" pps");
+		if (info->flags & XT_RATEEST_MATCH_DELTA)
+			printf(" %u", info->pps1);
+		if (info->flags & XT_RATEEST_MATCH_ABS) {
+			rateest_print_mode(info, "");
+			printf(" %u", info->pps2);
+		}
+	}
+
+	if (info->flags & XT_RATEEST_MATCH_REL) {
+		rateest_print_mode(info, "");
+
+		printf(" %s", info->name2);
+
+		if (info->flags & XT_RATEEST_MATCH_BPS) {
+			printf(" bps");
+			if (info->flags & XT_RATEEST_MATCH_DELTA)
+				rateest_print_rate(info->bps2, numeric);
+		}
+		if (info->flags & XT_RATEEST_MATCH_PPS) {
+			printf(" pps");
+			if (info->flags & XT_RATEEST_MATCH_DELTA)
+				printf(" %u", info->pps2);
+		}
+	}
+}
+
+static void __rateest_save_rate(const struct xt_rateest_match_info *info,
+                                const char *name, uint32_t r1, uint32_t r2,
+                                int numeric)
+{
+	if (info->flags & XT_RATEEST_MATCH_DELTA) {
+		printf(" --rateest-%s1", name);
+		rateest_print_rate(r1, numeric);
+		rateest_print_mode(info, "--rateest-");
+		printf(" --rateest-%s2", name);
+	} else {
+		rateest_print_mode(info, "--rateest-");
+		printf(" --rateest-%s", name);
+	}
+
+	if (info->flags & (XT_RATEEST_MATCH_ABS|XT_RATEEST_MATCH_DELTA))
+		rateest_print_rate(r2, numeric);
+}
+
+static void rateest_save_rates(const struct xt_rateest_match_info *info)
+{
+	if (info->flags & XT_RATEEST_MATCH_BPS)
+		__rateest_save_rate(info, "bps", info->bps1, info->bps2, 0);
+	if (info->flags & XT_RATEEST_MATCH_PPS)
+		__rateest_save_rate(info, "pps", info->pps1, info->pps2, 1);
+}
+
+
+static void
+rateest_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_rateest_match_info *info = (const void *)match->data;
+
+	if (info->flags & XT_RATEEST_MATCH_DELTA)
+		printf(" --rateest-delta");
+
+	if (info->flags & XT_RATEEST_MATCH_REL) {
+		printf(" --rateest1 %s", info->name1);
+		rateest_save_rates(info);
+		printf(" --rateest2 %s", info->name2);
+	} else { /* XT_RATEEST_MATCH_ABS */
+		printf(" --rateest %s", info->name1);
+		rateest_save_rates(info);
+	}
+}
+
+static struct xtables_match rateest_mt_reg = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "rateest",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_rateest_match_info)),
+	.userspacesize	= XT_ALIGN(offsetof(struct xt_rateest_match_info, est1)),
+	.help		= rateest_help,
+	.parse		= rateest_parse,
+	.x6_fcheck	= rateest_final_check,
+	.print		= rateest_print,
+	.save		= rateest_save,
+	.extra_opts	= rateest_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&rateest_mt_reg);
+}
diff --git a/extensions/libxt_rateest.man b/extensions/libxt_rateest.man
new file mode 100644
index 0000000..42a82f3
--- /dev/null
+++ b/extensions/libxt_rateest.man
@@ -0,0 +1,96 @@
+The rate estimator can match on estimated rates as collected by the RATEEST
+target. It supports matching on absolute bps/pps values, comparing two rate
+estimators and matching on the difference between two rate estimators.
+.PP
+For a better understanding of the available options, these are all possible
+combinations:
+.\" * Absolute:
+.IP \(bu 4
+\fBrateest\fP \fIoperator\fP \fBrateest-bps\fP
+.IP \(bu 4
+\fBrateest\fP \fIoperator\fP \fBrateest-pps\fP
+.\" * Absolute + Delta:
+.IP \(bu 4
+(\fBrateest\fP minus \fBrateest-bps1\fP) \fIoperator\fP \fBrateest-bps2\fP
+.IP \(bu 4
+(\fBrateest\fP minus \fBrateest-pps1\fP) \fIoperator\fP \fBrateest-pps2\fP
+.\" * Relative:
+.IP \(bu 4
+\fBrateest1\fP \fIoperator\fP \fBrateest2\fP \fBrateest-bps\fP(without rate!)
+.IP \(bu 4
+\fBrateest1\fP \fIoperator\fP \fBrateest2\fP \fBrateest-pps\fP(without rate!)
+.\" * Relative + Delta:
+.IP \(bu 4
+(\fBrateest1\fP minus \fBrateest-bps1\fP) \fIoperator\fP
+(\fBrateest2\fP minus \fBrateest-bps2\fP)
+.IP \(bu 4
+(\fBrateest1\fP minus \fBrateest-pps1\fP) \fIoperator\fP
+(\fBrateest2\fP minus \fBrateest-pps2\fP)
+.TP
+\fB\-\-rateest\-delta\fP
+For each estimator (either absolute or relative mode), calculate the difference
+between the estimator-determined flow rate and the static value chosen with the
+BPS/PPS options. If the flow rate is higher than the specified BPS/PPS, 0 will
+be used instead of a negative value. In other words, "max(0, rateest#_rate -
+rateest#_bps)" is used.
+.TP
+[\fB!\fP] \fB\-\-rateest\-lt\fP
+Match if rate is less than given rate/estimator.
+.TP
+[\fB!\fP] \fB\-\-rateest\-gt\fP
+Match if rate is greater than given rate/estimator.
+.TP
+[\fB!\fP] \fB\-\-rateest\-eq\fP
+Match if rate is equal to given rate/estimator.
+.PP
+In the so-called "absolute mode", only one rate estimator is used and compared
+against a static value, while in "relative mode", two rate estimators are
+compared against another.
+.TP
+\fB\-\-rateest\fP \fIname\fP
+Name of the one rate estimator for absolute mode.
+.TP
+\fB\-\-rateest1\fP \fIname\fP
+.TP
+\fB\-\-rateest2\fP \fIname\fP
+The names of the two rate estimators for relative mode.
+.TP
+\fB\-\-rateest\-bps\fP [\fIvalue\fP]
+.TP
+\fB\-\-rateest\-pps\fP [\fIvalue\fP]
+.TP
+\fB\-\-rateest\-bps1\fP [\fIvalue\fP]
+.TP
+\fB\-\-rateest\-bps2\fP [\fIvalue\fP]
+.TP
+\fB\-\-rateest\-pps1\fP [\fIvalue\fP]
+.TP
+\fB\-\-rateest\-pps2\fP [\fIvalue\fP]
+Compare the estimator(s) by bytes or packets per second, and compare against
+the chosen value. See the above bullet list for which option is to be used in
+which case. A unit suffix may be used - available ones are: bit, [kmgt]bit,
+[KMGT]ibit, Bps, [KMGT]Bps, [KMGT]iBps.
+.PP
+Example: This is what can be used to route outgoing data connections from an
+FTP server over two lines based on the available bandwidth at the time the data
+connection was started:
+.PP
+# Estimate outgoing rates
+.PP
+iptables \-t mangle \-A POSTROUTING \-o eth0 \-j RATEEST \-\-rateest\-name eth0
+\-\-rateest\-interval 250ms \-\-rateest\-ewma 0.5s
+.PP
+iptables \-t mangle \-A POSTROUTING \-o ppp0 \-j RATEEST \-\-rateest\-name ppp0
+\-\-rateest\-interval 250ms \-\-rateest\-ewma 0.5s
+.PP
+# Mark based on available bandwidth
+.PP
+iptables \-t mangle \-A balance \-m conntrack \-\-ctstate NEW \-m helper \-\-helper ftp
+\-m rateest \-\-rateest\-delta \-\-rateest1 eth0 \-\-rateest\-bps1 2.5mbit \-\-rateest\-gt
+\-\-rateest2 ppp0 \-\-rateest\-bps2 2mbit \-j CONNMARK \-\-set\-mark 1
+.PP
+iptables \-t mangle \-A balance \-m conntrack \-\-ctstate NEW \-m helper \-\-helper ftp
+\-m rateest \-\-rateest\-delta \-\-rateest1 ppp0 \-\-rateest\-bps1 2mbit \-\-rateest\-gt
+\-\-rateest2 eth0 \-\-rateest\-bps2 2.5mbit \-j CONNMARK \-\-set\-mark 2
+.PP
+iptables \-t mangle \-A balance \-j CONNMARK \-\-restore\-mark
diff --git a/extensions/libxt_rateest.t b/extensions/libxt_rateest.t
new file mode 100644
index 0000000..c515861
--- /dev/null
+++ b/extensions/libxt_rateest.t
@@ -0,0 +1,16 @@
+:INPUT,FORWARD,OUTPUT
+%iptables -I INPUT -j RATEEST --rateest-name RE1 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms
+-m rateest --rateest RE1 --rateest-lt --rateest-bps 8bit;=;OK
+-m rateest --rateest RE1 --rateest-eq --rateest-pps 5;=;OK
+-m rateest --rateest RE1 --rateest-gt --rateest-bps 5kbit;-m rateest --rateest RE1 --rateest-gt --rateest-bps 5000bit;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-bps1 8bit --rateest-lt --rateest-bps2 16bit;=;OK
+%iptables -I INPUT -j RATEEST --rateest-name RE2 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms
+-m rateest --rateest1 RE1 --rateest-lt --rateest-bps --rateest2 RE2;=;OK
+-m rateest --rateest-delta --rateest1 RE1 --rateest-pps1 0 --rateest-lt --rateest-pps2 42 --rateest2 RE2;=;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-bps1 8bit --rateest-eq --rateest-bps2 16bit;=;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-bps1 8bit --rateest-gt --rateest-bps2 16bit;=;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-pps1 8 --rateest-lt --rateest-pps2 9;=;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-pps1 8 --rateest-eq --rateest-pps2 9;=;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-pps1 8 --rateest-gt --rateest-pps2 9;=;OK
+%iptables -D INPUT -j RATEEST --rateest-name RE1 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms
+%iptables -D INPUT -j RATEEST --rateest-name RE2 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms
diff --git a/extensions/libxt_recent.c b/extensions/libxt_recent.c
new file mode 100644
index 0000000..055ae35
--- /dev/null
+++ b/extensions/libxt_recent.c
@@ -0,0 +1,355 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_recent.h>
+
+enum {
+	O_SET = 0,
+	O_RCHECK,
+	O_UPDATE,
+	O_REMOVE,
+	O_SECONDS,
+	O_REAP,
+	O_HITCOUNT,
+	O_RTTL,
+	O_NAME,
+	O_RSOURCE,
+	O_RDEST,
+	O_MASK,
+	F_SET    = 1 << O_SET,
+	F_RCHECK = 1 << O_RCHECK,
+	F_UPDATE = 1 << O_UPDATE,
+	F_REMOVE = 1 << O_REMOVE,
+	F_SECONDS = 1 << O_SECONDS,
+	F_ANY_OP = F_SET | F_RCHECK | F_UPDATE | F_REMOVE,
+};
+
+#define s struct xt_recent_mtinfo
+static const struct xt_option_entry recent_opts_v0[] = {
+	{.name = "set", .id = O_SET, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "rcheck", .id = O_RCHECK, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "update", .id = O_UPDATE, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "remove", .id = O_REMOVE, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "seconds", .id = O_SECONDS, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, seconds), .min = 1},
+	{.name = "reap", .id = O_REAP, .type = XTTYPE_NONE,
+	 .also = F_SECONDS },
+	{.name = "hitcount", .id = O_HITCOUNT, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, hit_count)},
+	{.name = "rttl", .id = O_RTTL, .type = XTTYPE_NONE,
+	 .excl = F_SET | F_REMOVE},
+	{.name = "name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, name)},
+	{.name = "rsource", .id = O_RSOURCE, .type = XTTYPE_NONE},
+	{.name = "rdest", .id = O_RDEST, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_recent_mtinfo_v1
+static const struct xt_option_entry recent_opts_v1[] = {
+	{.name = "set", .id = O_SET, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "rcheck", .id = O_RCHECK, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "update", .id = O_UPDATE, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "remove", .id = O_REMOVE, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "seconds", .id = O_SECONDS, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, seconds), .min = 1},
+	{.name = "reap", .id = O_REAP, .type = XTTYPE_NONE,
+	 .also = F_SECONDS },
+	{.name = "hitcount", .id = O_HITCOUNT, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, hit_count)},
+	{.name = "rttl", .id = O_RTTL, .type = XTTYPE_NONE,
+	 .excl = F_SET | F_REMOVE},
+	{.name = "name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, name)},
+	{.name = "rsource", .id = O_RSOURCE, .type = XTTYPE_NONE},
+	{.name = "rdest", .id = O_RDEST, .type = XTTYPE_NONE},
+	{.name = "mask", .id = O_MASK, .type = XTTYPE_HOST,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, mask)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void recent_help(void)
+{
+	printf(
+"recent match options:\n"
+"[!] --set                       Add source address to list, always matches.\n"
+"[!] --rcheck                    Match if source address in list.\n"
+"[!] --update                    Match if source address in list, also update last-seen time.\n"
+"[!] --remove                    Match if source address in list, also removes that address from list.\n"
+"    --seconds seconds           For check and update commands above.\n"
+"                                Specifies that the match will only occur if source address last seen within\n"
+"                                the last 'seconds' seconds.\n"
+"    --reap                      Purge entries older then 'seconds'.\n"
+"                                Can only be used in conjunction with the seconds option.\n"
+"    --hitcount hits             For check and update commands above.\n"
+"                                Specifies that the match will only occur if source address seen hits times.\n"
+"                                May be used in conjunction with the seconds option.\n"
+"    --rttl                      For check and update commands above.\n"
+"                                Specifies that the match will only occur if the source address and the TTL\n"
+"                                match between this packet and the one which was set.\n"
+"                                Useful if you have problems with people spoofing their source address in order\n"
+"                                to DoS you via this module.\n"
+"    --name name                 Name of the recent list to be used.  DEFAULT used if none given.\n"
+"    --rsource                   Match/Save the source address of each packet in the recent list table (default).\n"
+"    --rdest                     Match/Save the destination address of each packet in the recent list table.\n"
+"    --mask netmask              Netmask that will be applied to this recent list.\n"
+"xt_recent by: Stephen Frost <sfrost@snowman.net>.\n");
+}
+
+enum {
+	XT_RECENT_REV_0 = 0,
+	XT_RECENT_REV_1,
+};
+
+static void recent_init(struct xt_entry_match *match, unsigned int rev)
+{
+	struct xt_recent_mtinfo *info = (struct xt_recent_mtinfo *)match->data;
+	struct xt_recent_mtinfo_v1 *info_v1 =
+		(struct xt_recent_mtinfo_v1 *)match->data;
+
+	strncpy(info->name,"DEFAULT", XT_RECENT_NAME_LEN);
+	/* even though XT_RECENT_NAME_LEN is currently defined as 200,
+	 * better be safe, than sorry */
+	info->name[XT_RECENT_NAME_LEN-1] = '\0';
+	info->side = XT_RECENT_SOURCE;
+	if (rev == XT_RECENT_REV_1)
+		memset(&info_v1->mask, 0xFF, sizeof(info_v1->mask));
+}
+
+static void recent_parse(struct xt_option_call *cb)
+{
+	struct xt_recent_mtinfo *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SET:
+		info->check_set |= XT_RECENT_SET;
+		if (cb->invert)
+			info->invert = true;
+		break;
+	case O_RCHECK:
+		info->check_set |= XT_RECENT_CHECK;
+		if (cb->invert)
+			info->invert = true;
+		break;
+	case O_UPDATE:
+		info->check_set |= XT_RECENT_UPDATE;
+		if (cb->invert)
+			info->invert = true;
+		break;
+	case O_REMOVE:
+		info->check_set |= XT_RECENT_REMOVE;
+		if (cb->invert)
+			info->invert = true;
+		break;
+	case O_RTTL:
+		info->check_set |= XT_RECENT_TTL;
+		break;
+	case O_RSOURCE:
+		info->side = XT_RECENT_SOURCE;
+		break;
+	case O_RDEST:
+		info->side = XT_RECENT_DEST;
+		break;
+	case O_REAP:
+		info->check_set |= XT_RECENT_REAP;
+		break;
+	}
+}
+
+static void recent_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & F_ANY_OP))
+		xtables_error(PARAMETER_PROBLEM,
+			"recent: you must specify one of `--set', `--rcheck' "
+			"`--update' or `--remove'");
+}
+
+static void recent_print(const void *ip, const struct xt_entry_match *match,
+                         unsigned int family)
+{
+	const struct xt_recent_mtinfo_v1 *info = (const void *)match->data;
+
+	if (info->invert)
+		printf(" !");
+
+	printf(" recent:");
+	if (info->check_set & XT_RECENT_SET)
+		printf(" SET");
+	if (info->check_set & XT_RECENT_CHECK)
+		printf(" CHECK");
+	if (info->check_set & XT_RECENT_UPDATE)
+		printf(" UPDATE");
+	if (info->check_set & XT_RECENT_REMOVE)
+		printf(" REMOVE");
+	if(info->seconds) printf(" seconds: %d", info->seconds);
+	if (info->check_set & XT_RECENT_REAP)
+		printf(" reap");
+	if(info->hit_count) printf(" hit_count: %d", info->hit_count);
+	if (info->check_set & XT_RECENT_TTL)
+		printf(" TTL-Match");
+	printf(" name: %s", info->name);
+	if (info->side == XT_RECENT_SOURCE)
+		printf(" side: source");
+	if (info->side == XT_RECENT_DEST)
+		printf(" side: dest");
+
+	switch(family) {
+	case NFPROTO_IPV4:
+		printf(" mask: %s",
+			xtables_ipaddr_to_numeric(&info->mask.in));
+		break;
+	case NFPROTO_IPV6:
+		printf(" mask: %s",
+			xtables_ip6addr_to_numeric(&info->mask.in6));
+		break;
+	}
+}
+
+static void recent_save(const void *ip, const struct xt_entry_match *match,
+			unsigned int family)
+{
+	const struct xt_recent_mtinfo_v1 *info = (const void *)match->data;
+
+	if (info->invert)
+		printf(" !");
+
+	if (info->check_set & XT_RECENT_SET)
+		printf(" --set");
+	if (info->check_set & XT_RECENT_CHECK)
+		printf(" --rcheck");
+	if (info->check_set & XT_RECENT_UPDATE)
+		printf(" --update");
+	if (info->check_set & XT_RECENT_REMOVE)
+		printf(" --remove");
+	if(info->seconds) printf(" --seconds %d", info->seconds);
+	if (info->check_set & XT_RECENT_REAP)
+		printf(" --reap");
+	if(info->hit_count) printf(" --hitcount %d", info->hit_count);
+	if (info->check_set & XT_RECENT_TTL)
+		printf(" --rttl");
+	printf(" --name %s",info->name);
+
+	switch(family) {
+	case NFPROTO_IPV4:
+		printf(" --mask %s",
+			xtables_ipaddr_to_numeric(&info->mask.in));
+		break;
+	case NFPROTO_IPV6:
+		printf(" --mask %s",
+			xtables_ip6addr_to_numeric(&info->mask.in6));
+		break;
+	}
+
+	if (info->side == XT_RECENT_SOURCE)
+		printf(" --rsource");
+	if (info->side == XT_RECENT_DEST)
+		printf(" --rdest");
+}
+
+static void recent_init_v0(struct xt_entry_match *match)
+{
+	recent_init(match, XT_RECENT_REV_0);
+}
+
+static void recent_init_v1(struct xt_entry_match *match)
+{
+	recent_init(match, XT_RECENT_REV_1);
+}
+
+static void recent_save_v0(const void *ip, const struct xt_entry_match *match)
+{
+	recent_save(ip, match, NFPROTO_UNSPEC);
+}
+
+static void recent_save_v4(const void *ip, const struct xt_entry_match *match)
+{
+	recent_save(ip, match, NFPROTO_IPV4);
+}
+
+static void recent_save_v6(const void *ip, const struct xt_entry_match *match)
+{
+	recent_save(ip, match, NFPROTO_IPV6);
+}
+
+static void recent_print_v0(const void *ip, const struct xt_entry_match *match,
+			    int numeric)
+{
+	recent_print(ip, match, NFPROTO_UNSPEC);
+}
+
+static void recent_print_v4(const void *ip, const struct xt_entry_match *match,
+                         int numeric)
+{
+	recent_print(ip, match, NFPROTO_IPV4);
+}
+
+static void recent_print_v6(const void *ip, const struct xt_entry_match *match,
+                         int numeric)
+{
+	recent_print(ip, match, NFPROTO_IPV6);
+}
+
+static struct xtables_match recent_mt_reg[] = {
+	{
+		.name          = "recent",
+		.version       = XTABLES_VERSION,
+		.revision      = 0,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_recent_mtinfo)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_recent_mtinfo)),
+		.help          = recent_help,
+		.init          = recent_init_v0,
+		.x6_parse      = recent_parse,
+		.x6_fcheck     = recent_check,
+		.print         = recent_print_v0,
+		.save          = recent_save_v0,
+		.x6_options    = recent_opts_v0,
+	},
+	{
+		.name          = "recent",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_recent_mtinfo_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_recent_mtinfo_v1)),
+		.help          = recent_help,
+		.init          = recent_init_v1,
+		.x6_parse      = recent_parse,
+		.x6_fcheck     = recent_check,
+		.print         = recent_print_v4,
+		.save          = recent_save_v4,
+		.x6_options    = recent_opts_v1,
+	},
+	{
+		.name          = "recent",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_recent_mtinfo_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_recent_mtinfo_v1)),
+		.help          = recent_help,
+		.init          = recent_init_v1,
+		.x6_parse      = recent_parse,
+		.x6_fcheck     = recent_check,
+		.print         = recent_print_v6,
+		.save          = recent_save_v6,
+		.x6_options    = recent_opts_v1,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
+}
diff --git a/extensions/libxt_recent.man b/extensions/libxt_recent.man
new file mode 100644
index 0000000..419be25
--- /dev/null
+++ b/extensions/libxt_recent.man
@@ -0,0 +1,109 @@
+Allows you to dynamically create a list of IP addresses and then match against
+that list in a few different ways.
+.PP
+For example, you can create a "badguy" list out of people attempting to connect
+to port 139 on your firewall and then DROP all future packets from them without
+considering them.
+.PP
+\fB\-\-set\fP, \fB\-\-rcheck\fP, \fB\-\-update\fP and \fB\-\-remove\fP are
+mutually exclusive.
+.TP
+\fB\-\-name\fP \fIname\fP
+Specify the list to use for the commands. If no name is given then
+\fBDEFAULT\fP will be used.
+.TP
+[\fB!\fP] \fB\-\-set\fP
+This will add the source address of the packet to the list. If the source
+address is already in the list, this will update the existing entry. This will
+always return success (or failure if \fB!\fP is passed in).
+.TP
+\fB\-\-rsource\fP
+Match/save the source address of each packet in the recent list table. This
+is the default.
+.TP
+\fB\-\-rdest\fP
+Match/save the destination address of each packet in the recent list table.
+.TP
+\fB\-\-mask\fP \fInetmask\fP
+Netmask that will be applied to this recent list.
+.TP
+[\fB!\fP] \fB\-\-rcheck\fP
+Check if the source address of the packet is currently in the list.
+.TP
+[\fB!\fP] \fB\-\-update\fP
+Like \fB\-\-rcheck\fP, except it will update the "last seen" timestamp if it
+matches.
+.TP
+[\fB!\fP] \fB\-\-remove\fP
+Check if the source address of the packet is currently in the list and if so
+that address will be removed from the list and the rule will return true. If
+the address is not found, false is returned.
+.TP
+\fB\-\-seconds\fP \fIseconds\fP
+This option must be used in conjunction with one of \fB\-\-rcheck\fP or
+\fB\-\-update\fP. When used, this will narrow the match to only happen when the
+address is in the list and was seen within the last given number of seconds.
+.TP
+\fB\-\-reap\fP
+This option can only be used in conjunction with \fB\-\-seconds\fP.
+When used, this will cause entries older than the last given number of seconds
+to be purged.
+.TP
+\fB\-\-hitcount\fP \fIhits\fP
+This option must be used in conjunction with one of \fB\-\-rcheck\fP or
+\fB\-\-update\fP. When used, this will narrow the match to only happen when the
+address is in the list and packets had been received greater than or equal to
+the given value. This option may be used along with \fB\-\-seconds\fP to create
+an even narrower match requiring a certain number of hits within a specific
+time frame. The maximum value for the hitcount parameter is given by the
+"ip_pkt_list_tot" parameter of the xt_recent kernel module. Exceeding this
+value on the command line will cause the rule to be rejected.
+.TP
+\fB\-\-rttl\fP
+This option may only be used in conjunction with one of \fB\-\-rcheck\fP or
+\fB\-\-update\fP. When used, this will narrow the match to only happen when the
+address is in the list and the TTL of the current packet matches that of the
+packet which hit the \fB\-\-set\fP rule. This may be useful if you have problems
+with people faking their source address in order to DoS you via this module by
+disallowing others access to your site by sending bogus packets to you.
+.PP
+Examples:
+.IP
+iptables \-A FORWARD \-m recent \-\-name badguy \-\-rcheck \-\-seconds 60 \-j DROP
+.IP
+iptables \-A FORWARD \-p tcp \-i eth0 \-\-dport 139 \-m recent \-\-name badguy \-\-set \-j DROP
+.PP
+\fB/proc/net/xt_recent/*\fP are the current lists of addresses and information
+about each entry of each list.
+.PP
+Each file in \fB/proc/net/xt_recent/\fP can be read from to see the current
+list or written two using the following commands to modify the list:
+.TP
+\fBecho +\fP\fIaddr\fP\fB >/proc/net/xt_recent/DEFAULT\fP
+to add \fIaddr\fP to the DEFAULT list
+.TP
+\fBecho \-\fP\fIaddr\fP\fB >/proc/net/xt_recent/DEFAULT\fP
+to remove \fIaddr\fP from the DEFAULT list
+.TP
+\fBecho / >/proc/net/xt_recent/DEFAULT\fP
+to flush the DEFAULT list (remove all entries).
+.PP
+The module itself accepts parameters, defaults shown:
+.TP
+\fBip_list_tot\fP=\fI100\fP
+Number of addresses remembered per table.
+.TP
+\fBip_pkt_list_tot\fP=\fI20\fP
+Number of packets per address remembered.
+.TP
+\fBip_list_hash_size\fP=\fI0\fP
+Hash table size. 0 means to calculate it based on ip_list_tot, default: 512.
+.TP
+\fBip_list_perms\fP=\fI0644\fP
+Permissions for /proc/net/xt_recent/* files.
+.TP
+\fBip_list_uid\fP=\fI0\fP
+Numerical UID for ownership of /proc/net/xt_recent/* files.
+.TP
+\fBip_list_gid\fP=\fI0\fP
+Numerical GID for ownership of /proc/net/xt_recent/* files.
diff --git a/extensions/libxt_recent.t b/extensions/libxt_recent.t
new file mode 100644
index 0000000..9a83918
--- /dev/null
+++ b/extensions/libxt_recent.t
@@ -0,0 +1,11 @@
+:INPUT,FORWARD,OUTPUT
+-m recent --set;=;OK
+-m recent --rcheck --hitcount 8 --name foo --mask 255.255.255.255 --rsource;=;OK
+-m recent --rcheck --hitcount 12 --name foo --mask 255.255.255.255 --rsource;=;OK
+-m recent --update --rttl;=;OK
+-m recent --set --rttl;;FAIL
+-m recent --rcheck --hitcount 999 --name foo --mask 255.255.255.255 --rsource;;FAIL
+# nonsensical, but all should load successfully:
+-m recent --rcheck --hitcount 3 --name foo --mask 255.255.255.255 --rsource -m recent --rcheck --hitcount 4 --name foo --mask 255.255.255.255 --rsource;=;OK
+-m recent --rcheck --hitcount 4 --name foo --mask 255.255.255.255 --rsource -m recent --rcheck --hitcount 4 --name foo --mask 255.255.255.255 --rsource;=;OK
+-m recent --rcheck --hitcount 8 --name foo --mask 255.255.255.255 --rsource -m recent --rcheck --hitcount 12 --name foo --mask 255.255.255.255 --rsource;=;OK
diff --git a/extensions/libxt_rpfilter.c b/extensions/libxt_rpfilter.c
new file mode 100644
index 0000000..d166baa
--- /dev/null
+++ b/extensions/libxt_rpfilter.c
@@ -0,0 +1,122 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_rpfilter.h>
+
+enum {
+	O_RPF_LOOSE = 0,
+	O_RPF_VMARK = 1,
+	O_RPF_ACCEPT_LOCAL = 2,
+	O_RPF_INVERT = 3,
+};
+
+static void rpfilter_help(void)
+{
+	printf(
+"rpfilter match options:\n"
+"    --loose          permit reverse path via any interface\n"
+"    --validmark      use skb nfmark when performing route lookup\n"
+"    --accept-local   do not reject packets with a local source address\n"
+"    --invert         match packets that failed the reverse path test\n"
+	);
+}
+
+static const struct xt_option_entry rpfilter_opts[] = {
+	{.name = "loose", .id = O_RPF_LOOSE, .type = XTTYPE_NONE, },
+	{.name = "validmark", .id = O_RPF_VMARK, .type = XTTYPE_NONE, },
+	{.name = "accept-local", .id = O_RPF_ACCEPT_LOCAL, .type = XTTYPE_NONE, },
+	{.name = "invert", .id = O_RPF_INVERT, .type = XTTYPE_NONE, },
+	XTOPT_TABLEEND,
+};
+
+static void rpfilter_parse(struct xt_option_call *cb)
+{
+	struct xt_rpfilter_info *rpfinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_RPF_LOOSE:
+		rpfinfo->flags |= XT_RPFILTER_LOOSE;
+		break;
+	case O_RPF_VMARK:
+		rpfinfo->flags |= XT_RPFILTER_VALID_MARK;
+		break;
+	case O_RPF_ACCEPT_LOCAL:
+		rpfinfo->flags |= XT_RPFILTER_ACCEPT_LOCAL;
+		break;
+	case O_RPF_INVERT:
+		rpfinfo->flags |= XT_RPFILTER_INVERT;
+		break;
+	}
+}
+
+static void
+rpfilter_print_prefix(const void *ip, const void *matchinfo,
+			const char *prefix)
+{
+	const struct xt_rpfilter_info *info = matchinfo;
+	if (info->flags & XT_RPFILTER_LOOSE)
+		printf(" %s%s", prefix, rpfilter_opts[O_RPF_LOOSE].name);
+	if (info->flags & XT_RPFILTER_VALID_MARK)
+		printf(" %s%s", prefix, rpfilter_opts[O_RPF_VMARK].name);
+	if (info->flags & XT_RPFILTER_ACCEPT_LOCAL)
+		printf(" %s%s", prefix, rpfilter_opts[O_RPF_ACCEPT_LOCAL].name);
+	if (info->flags & XT_RPFILTER_INVERT)
+		printf(" %s%s", prefix, rpfilter_opts[O_RPF_INVERT].name);
+}
+
+
+static void
+rpfilter_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	printf(" rpfilter");
+	return rpfilter_print_prefix(ip, match->data, "");
+}
+
+static void rpfilter_save(const void *ip, const struct xt_entry_match *match)
+{
+	return rpfilter_print_prefix(ip, match->data, "--");
+}
+
+static int rpfilter_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_rpfilter_info *info = (void *)params->match->data;
+	bool invert = info->flags & XT_RPFILTER_INVERT;
+
+	if (info->flags & XT_RPFILTER_ACCEPT_LOCAL) {
+		if (invert)
+			xt_xlate_add(xl, "fib saddr type != local ");
+		else
+			return 0;
+	}
+
+	xt_xlate_add(xl, "fib saddr ");
+
+	if (info->flags & XT_RPFILTER_VALID_MARK)
+		xt_xlate_add(xl, ". mark ");
+	if (!(info->flags & XT_RPFILTER_LOOSE))
+		xt_xlate_add(xl, ". iif ");
+
+	xt_xlate_add(xl, "oif %s0", invert ? "" : "!= ");
+
+	return 1;
+}
+
+static struct xtables_match rpfilter_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "rpfilter",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_rpfilter_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_rpfilter_info)),
+	.help		= rpfilter_help,
+	.print		= rpfilter_print,
+	.save		= rpfilter_save,
+	.x6_parse	= rpfilter_parse,
+	.x6_options	= rpfilter_opts,
+	.xlate		= rpfilter_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&rpfilter_match);
+}
diff --git a/extensions/libxt_rpfilter.man b/extensions/libxt_rpfilter.man
new file mode 100644
index 0000000..a3c0fcd
--- /dev/null
+++ b/extensions/libxt_rpfilter.man
@@ -0,0 +1,39 @@
+Performs a reverse path filter test on a packet.
+If a reply to the packet would be sent via the same interface
+that the packet arrived on, the packet will match.
+Note that, unlike the in-kernel rp_filter, packets protected
+by IPSec are not treated specially.  Combine this match with
+the policy match if you want this.
+Also, packets arriving via the loopback interface are always permitted.
+This match can only be used in the PREROUTING chain of the raw or mangle table.
+.TP
+\fB\-\-loose\fP
+Used to specify that the reverse path filter test should match
+even if the selected output device is not the expected one.
+.TP
+\fB\-\-validmark\fP
+Also use the packets' nfmark value when performing the reverse path route lookup.
+.TP
+\fB\-\-accept\-local\fP
+This will permit packets arriving from the network with a source address that is also
+assigned to the local machine.
+.TP
+\fB\-\-invert\fP
+This will invert the sense of the match.  Instead of matching packets that passed the
+reverse path filter test, match those that have failed it.
+.PP
+Example to log and drop packets failing the reverse path filter test:
+
+iptables \-t raw \-N RPFILTER
+
+iptables \-t raw \-A RPFILTER \-m rpfilter \-j RETURN
+
+iptables \-t raw \-A RPFILTER \-m limit \-\-limit 10/minute \-j NFLOG \-\-nflog\-prefix "rpfilter drop"
+
+iptables \-t raw \-A RPFILTER \-j DROP
+
+iptables \-t raw \-A PREROUTING \-j RPFILTER
+
+Example to drop failed packets, without logging:
+
+iptables \-t raw \-A RPFILTER \-m rpfilter \-\-invert \-j DROP
diff --git a/extensions/libxt_rpfilter.t b/extensions/libxt_rpfilter.t
new file mode 100644
index 0000000..390268f
--- /dev/null
+++ b/extensions/libxt_rpfilter.t
@@ -0,0 +1,4 @@
+:PREROUTING
+*mangle
+-m rpfilter;=;OK
+-m rpfilter --loose --validmark --accept-local --invert;=;OK
diff --git a/extensions/libxt_rpfilter.txlate b/extensions/libxt_rpfilter.txlate
new file mode 100644
index 0000000..8d7733b
--- /dev/null
+++ b/extensions/libxt_rpfilter.txlate
@@ -0,0 +1,8 @@
+iptables-translate -t mangle -A PREROUTING -m rpfilter
+nft add rule ip mangle PREROUTING fib saddr . iif oif != 0 counter
+
+iptables-translate -t mangle -A PREROUTING -m rpfilter --validmark --loose
+nft add rule ip mangle PREROUTING fib saddr . mark oif != 0 counter
+
+ip6tables-translate -t mangle -A PREROUTING -m rpfilter --validmark --invert
+nft add rule ip6 mangle PREROUTING fib saddr . mark . iif oif 0 counter
diff --git a/extensions/libxt_sctp.c b/extensions/libxt_sctp.c
new file mode 100644
index 0000000..140de26
--- /dev/null
+++ b/extensions/libxt_sctp.c
@@ -0,0 +1,544 @@
+/* Shared library add-on to iptables for SCTP matching
+ *
+ * (C) 2003 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This program is distributed under the terms of GNU GPL v2, 1991
+ *
+ * libipt_ecn.c borrowed heavily from libipt_dscp.c
+ *
+ */
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <ctype.h>
+
+#include <netinet/in.h>
+#include <xtables.h>
+
+#include <linux/netfilter/xt_sctp.h>
+
+#if 0
+#define DEBUGP(format, first...) printf(format, ##first)
+#define static
+#else
+#define DEBUGP(format, fist...) 
+#endif
+
+static void
+print_chunk(uint32_t chunknum, int numeric);
+
+static void sctp_init(struct xt_entry_match *m)
+{
+	int i;
+	struct xt_sctp_info *einfo = (struct xt_sctp_info *)m->data;
+
+	for (i = 0; i < XT_NUM_SCTP_FLAGS; i++) {
+		einfo->flag_info[i].chunktype = -1;
+	}
+}
+
+static void sctp_help(void)
+{
+	printf(
+"sctp match options\n"
+"[!] --source-port port[:port]                          match source port(s)\n"
+" --sport ...\n"
+"[!] --destination-port port[:port]                     match destination port(s)\n"
+" --dport ...\n" 
+"[!] --chunk-types (all|any|none) (chunktype[:flags])+	match if all, any or none of\n"
+"						        chunktypes are present\n"
+"chunktypes - 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 ALL NONE\n");
+}
+
+static const struct option sctp_opts[] = {
+	{.name = "source-port",      .has_arg = true, .val = '1'},
+	{.name = "sport",            .has_arg = true, .val = '1'},
+	{.name = "destination-port", .has_arg = true, .val = '2'},
+	{.name = "dport",            .has_arg = true, .val = '2'},
+	{.name = "chunk-types",      .has_arg = true, .val = '3'},
+	XT_GETOPT_TABLEEND,
+};
+
+static void
+parse_sctp_ports(const char *portstring, 
+		 uint16_t *ports)
+{
+	char *buffer;
+	char *cp;
+
+	buffer = strdup(portstring);
+	DEBUGP("%s\n", portstring);
+	if ((cp = strchr(buffer, ':')) == NULL) {
+		ports[0] = ports[1] = xtables_parse_port(buffer, "sctp");
+	}
+	else {
+		*cp = '\0';
+		cp++;
+
+		ports[0] = buffer[0] ? xtables_parse_port(buffer, "sctp") : 0;
+		ports[1] = cp[0] ? xtables_parse_port(cp, "sctp") : 0xFFFF;
+
+		if (ports[0] > ports[1])
+			xtables_error(PARAMETER_PROBLEM,
+				   "invalid portrange (min > max)");
+	}
+	free(buffer);
+}
+
+struct sctp_chunk_names {
+	const char *name;
+	unsigned int chunk_type;
+	const char *valid_flags;
+};
+
+/*'ALL' and 'NONE' will be treated specially. */
+static const struct sctp_chunk_names sctp_chunk_names[]
+= { { .name = "DATA", 		.chunk_type = 0,   .valid_flags = "----IUBE"},
+    { .name = "INIT", 		.chunk_type = 1,   .valid_flags = "--------"},
+    { .name = "INIT_ACK", 	.chunk_type = 2,   .valid_flags = "--------"},
+    { .name = "SACK",		.chunk_type = 3,   .valid_flags = "--------"},
+    { .name = "HEARTBEAT",	.chunk_type = 4,   .valid_flags = "--------"},
+    { .name = "HEARTBEAT_ACK",	.chunk_type = 5,   .valid_flags = "--------"},
+    { .name = "ABORT",		.chunk_type = 6,   .valid_flags = "-------T"},
+    { .name = "SHUTDOWN",	.chunk_type = 7,   .valid_flags = "--------"},
+    { .name = "SHUTDOWN_ACK",	.chunk_type = 8,   .valid_flags = "--------"},
+    { .name = "ERROR",		.chunk_type = 9,   .valid_flags = "--------"},
+    { .name = "COOKIE_ECHO",	.chunk_type = 10,  .valid_flags = "--------"},
+    { .name = "COOKIE_ACK",	.chunk_type = 11,  .valid_flags = "--------"},
+    { .name = "ECN_ECNE",	.chunk_type = 12,  .valid_flags = "--------"},
+    { .name = "ECN_CWR",	.chunk_type = 13,  .valid_flags = "--------"},
+    { .name = "SHUTDOWN_COMPLETE", .chunk_type = 14,  .valid_flags = "-------T"},
+    { .name = "ASCONF",		.chunk_type = 193,  .valid_flags = "--------"},
+    { .name = "ASCONF_ACK",	.chunk_type = 128,  .valid_flags = "--------"},
+    { .name = "FORWARD_TSN",	.chunk_type = 192,  .valid_flags = "--------"},
+};
+
+static void
+save_chunk_flag_info(struct xt_sctp_flag_info *flag_info,
+		     int *flag_count,
+		     int chunktype, 
+		     int bit, 
+		     int set)
+{
+	int i;
+
+	for (i = 0; i < *flag_count; i++) {
+		if (flag_info[i].chunktype == chunktype) {
+			DEBUGP("Previous match found\n");
+			flag_info[i].chunktype = chunktype;
+			flag_info[i].flag_mask |= (1 << bit);
+			if (set) {
+				flag_info[i].flag |= (1 << bit);
+			}
+
+			return;
+		}
+	}
+	
+	if (*flag_count == XT_NUM_SCTP_FLAGS) {
+		xtables_error (PARAMETER_PROBLEM,
+			"Number of chunk types with flags exceeds currently allowed limit."
+			"Increasing this limit involves changing IPT_NUM_SCTP_FLAGS and"
+			"recompiling both the kernel space and user space modules\n");
+	}
+
+	flag_info[*flag_count].chunktype = chunktype;
+	flag_info[*flag_count].flag_mask |= (1 << bit);
+	if (set) {
+		flag_info[*flag_count].flag |= (1 << bit);
+	}
+	(*flag_count)++;
+}
+
+static void
+parse_sctp_chunk(struct xt_sctp_info *einfo, 
+		 const char *chunks)
+{
+	char *ptr;
+	char *buffer;
+	unsigned int i, j;
+	int found = 0;
+	char *chunk_flags;
+
+	buffer = strdup(chunks);
+	DEBUGP("Buffer: %s\n", buffer);
+
+	SCTP_CHUNKMAP_RESET(einfo->chunkmap);
+
+	if (!strcasecmp(buffer, "ALL")) {
+		SCTP_CHUNKMAP_SET_ALL(einfo->chunkmap);
+		goto out;
+	}
+	
+	if (!strcasecmp(buffer, "NONE")) {
+		SCTP_CHUNKMAP_RESET(einfo->chunkmap);
+		goto out;
+	}
+
+	for (ptr = strtok(buffer, ","); ptr; ptr = strtok(NULL, ",")) {
+		found = 0;
+		DEBUGP("Next Chunk type %s\n", ptr);
+		
+		if ((chunk_flags = strchr(ptr, ':')) != NULL) {
+			*chunk_flags++ = 0;
+		}
+		
+		for (i = 0; i < ARRAY_SIZE(sctp_chunk_names); ++i)
+			if (strcasecmp(sctp_chunk_names[i].name, ptr) == 0) {
+				DEBUGP("Chunk num %d\n", sctp_chunk_names[i].chunk_type);
+				SCTP_CHUNKMAP_SET(einfo->chunkmap, 
+					sctp_chunk_names[i].chunk_type);
+				found = 1;
+				break;
+			}
+		if (!found)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Unknown sctp chunk `%s'", ptr);
+
+		if (chunk_flags) {
+			DEBUGP("Chunk flags %s\n", chunk_flags);
+			for (j = 0; j < strlen(chunk_flags); j++) {
+				char *p;
+				int bit;
+
+				if ((p = strchr(sctp_chunk_names[i].valid_flags, 
+						toupper(chunk_flags[j]))) != NULL) {
+					bit = p - sctp_chunk_names[i].valid_flags;
+					bit = 7 - bit;
+
+					save_chunk_flag_info(einfo->flag_info, 
+						&(einfo->flag_count), i, bit, 
+						isupper(chunk_flags[j]));
+				} else {
+					xtables_error(PARAMETER_PROBLEM,
+						"Invalid flags for chunk type %d\n", i);
+				}
+			}
+		}
+	}
+out:
+	free(buffer);
+}
+
+static void
+parse_sctp_chunks(struct xt_sctp_info *einfo,
+		  const char *match_type,
+		  const char *chunks)
+{
+	DEBUGP("Match type: %s Chunks: %s\n", match_type, chunks);
+	if (!strcasecmp(match_type, "ANY")) {
+		einfo->chunk_match_type = SCTP_CHUNK_MATCH_ANY;
+	} else 	if (!strcasecmp(match_type, "ALL")) {
+		einfo->chunk_match_type = SCTP_CHUNK_MATCH_ALL;
+	} else 	if (!strcasecmp(match_type, "ONLY")) {
+		einfo->chunk_match_type = SCTP_CHUNK_MATCH_ONLY;
+	} else {
+		xtables_error (PARAMETER_PROBLEM,
+			"Match type has to be one of \"ALL\", \"ANY\" or \"ONLY\"");
+	}
+
+	SCTP_CHUNKMAP_RESET(einfo->chunkmap);
+	parse_sctp_chunk(einfo, chunks);
+}
+
+static int
+sctp_parse(int c, char **argv, int invert, unsigned int *flags,
+           const void *entry, struct xt_entry_match **match)
+{
+	struct xt_sctp_info *einfo
+		= (struct xt_sctp_info *)(*match)->data;
+
+	switch (c) {
+	case '1':
+		if (*flags & XT_SCTP_SRC_PORTS)
+			xtables_error(PARAMETER_PROBLEM,
+			           "Only one `--source-port' allowed");
+		einfo->flags |= XT_SCTP_SRC_PORTS;
+		parse_sctp_ports(optarg, einfo->spts);
+		if (invert)
+			einfo->invflags |= XT_SCTP_SRC_PORTS;
+		*flags |= XT_SCTP_SRC_PORTS;
+		break;
+
+	case '2':
+		if (*flags & XT_SCTP_DEST_PORTS)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Only one `--destination-port' allowed");
+		einfo->flags |= XT_SCTP_DEST_PORTS;
+		parse_sctp_ports(optarg, einfo->dpts);
+		if (invert)
+			einfo->invflags |= XT_SCTP_DEST_PORTS;
+		*flags |= XT_SCTP_DEST_PORTS;
+		break;
+
+	case '3':
+		if (*flags & XT_SCTP_CHUNK_TYPES)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Only one `--chunk-types' allowed");
+		if (!argv[optind] 
+		    || argv[optind][0] == '-' || argv[optind][0] == '!')
+			xtables_error(PARAMETER_PROBLEM,
+				   "--chunk-types requires two args");
+
+		einfo->flags |= XT_SCTP_CHUNK_TYPES;
+		parse_sctp_chunks(einfo, optarg, argv[optind]);
+		if (invert)
+			einfo->invflags |= XT_SCTP_CHUNK_TYPES;
+		optind++;
+		*flags |= XT_SCTP_CHUNK_TYPES;
+		break;
+	}
+	return 1;
+}
+
+static const char *
+port_to_service(int port)
+{
+	const struct servent *service;
+
+	if ((service = getservbyport(htons(port), "sctp")))
+		return service->s_name;
+
+	return NULL;
+}
+
+static void
+print_port(uint16_t port, int numeric)
+{
+	const char *service;
+
+	if (numeric || (service = port_to_service(port)) == NULL)
+		printf("%u", port);
+	else
+		printf("%s", service);
+}
+
+static void
+print_ports(const char *name, uint16_t min, uint16_t max,
+	    int invert, int numeric)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFF || invert) {
+		printf(" %s", name);
+		if (min == max) {
+			printf(":%s", inv);
+			print_port(min, numeric);
+		} else {
+			printf("s:%s", inv);
+			print_port(min, numeric);
+			printf(":");
+			print_port(max, numeric);
+		}
+	}
+}
+
+static void
+print_chunk_flags(uint32_t chunknum, uint8_t chunk_flags, uint8_t chunk_flags_mask)
+{
+	int i;
+
+	DEBUGP("type: %d\tflags: %x\tflag mask: %x\n", chunknum, chunk_flags, 
+			chunk_flags_mask);
+
+	if (chunk_flags_mask) {
+		printf(":");
+	}
+
+	for (i = 7; i >= 0; i--) {
+		if (chunk_flags_mask & (1 << i)) {
+			if (chunk_flags & (1 << i)) {
+				printf("%c", sctp_chunk_names[chunknum].valid_flags[7-i]);
+			} else {
+				printf("%c", tolower(sctp_chunk_names[chunknum].valid_flags[7-i]));
+			}
+		}
+	}
+}
+
+static void
+print_chunk(uint32_t chunknum, int numeric)
+{
+	if (numeric) {
+		printf("0x%04X", chunknum);
+	}
+	else {
+		int i;
+
+		for (i = 0; i < ARRAY_SIZE(sctp_chunk_names); ++i)
+			if (sctp_chunk_names[i].chunk_type == chunknum)
+				printf("%s", sctp_chunk_names[i].name);
+	}
+}
+
+static void
+print_chunks(const struct xt_sctp_info *einfo, int numeric)
+{
+	uint32_t chunk_match_type = einfo->chunk_match_type;
+	const struct xt_sctp_flag_info *flag_info = einfo->flag_info;
+	int flag_count = einfo->flag_count;
+	int i, j;
+	int flag;
+
+	switch (chunk_match_type) {
+		case SCTP_CHUNK_MATCH_ANY:	printf(" any"); break;
+		case SCTP_CHUNK_MATCH_ALL:	printf(" all"); break;
+		case SCTP_CHUNK_MATCH_ONLY:	printf(" only"); break;
+		default:	printf("Never reach here\n"); break;
+	}
+
+	if (SCTP_CHUNKMAP_IS_CLEAR(einfo->chunkmap)) {
+		printf(" NONE");
+		goto out;
+	}
+	
+	if (SCTP_CHUNKMAP_IS_ALL_SET(einfo->chunkmap)) {
+		printf(" ALL");
+		goto out;
+	}
+	
+	flag = 0;
+	for (i = 0; i < 256; i++) {
+		if (SCTP_CHUNKMAP_IS_SET(einfo->chunkmap, i)) {
+			if (flag)
+				printf(",");
+			else
+				putchar(' ');
+			flag = 1;
+			print_chunk(i, numeric);
+			for (j = 0; j < flag_count; j++) {
+				if (flag_info[j].chunktype == i) {
+					print_chunk_flags(i, flag_info[j].flag,
+						flag_info[j].flag_mask);
+				}
+			}
+		}
+	}
+out:
+	return;
+}
+
+static void
+sctp_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_sctp_info *einfo =
+		(const struct xt_sctp_info *)match->data;
+
+	printf(" sctp");
+
+	if (einfo->flags & XT_SCTP_SRC_PORTS) {
+		print_ports("spt", einfo->spts[0], einfo->spts[1],
+			einfo->invflags & XT_SCTP_SRC_PORTS,
+			numeric);
+	}
+
+	if (einfo->flags & XT_SCTP_DEST_PORTS) {
+		print_ports("dpt", einfo->dpts[0], einfo->dpts[1],
+			einfo->invflags & XT_SCTP_DEST_PORTS,
+			numeric);
+	}
+
+	if (einfo->flags & XT_SCTP_CHUNK_TYPES) {
+		/* FIXME: print_chunks() is used in save() where the printing of '!'
+		s taken care of, so we need to do that here as well */
+		if (einfo->invflags & XT_SCTP_CHUNK_TYPES) {
+			printf(" !");
+		}
+		print_chunks(einfo, numeric);
+	}
+}
+
+static void sctp_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_sctp_info *einfo =
+		(const struct xt_sctp_info *)match->data;
+
+	if (einfo->flags & XT_SCTP_SRC_PORTS) {
+		if (einfo->invflags & XT_SCTP_SRC_PORTS)
+			printf(" !");
+		if (einfo->spts[0] != einfo->spts[1])
+			printf(" --sport %u:%u",
+			       einfo->spts[0], einfo->spts[1]);
+		else
+			printf(" --sport %u", einfo->spts[0]);
+	}
+
+	if (einfo->flags & XT_SCTP_DEST_PORTS) {
+		if (einfo->invflags & XT_SCTP_DEST_PORTS)
+			printf(" !");
+		if (einfo->dpts[0] != einfo->dpts[1])
+			printf(" --dport %u:%u",
+			       einfo->dpts[0], einfo->dpts[1]);
+		else
+			printf(" --dport %u", einfo->dpts[0]);
+	}
+
+	if (einfo->flags & XT_SCTP_CHUNK_TYPES) {
+		if (einfo->invflags & XT_SCTP_CHUNK_TYPES)
+			printf(" !");
+		printf(" --chunk-types");
+
+		print_chunks(einfo, 0);
+	}
+}
+
+static int sctp_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_sctp_info *einfo =
+		(const struct xt_sctp_info *)params->match->data;
+	char *space = "";
+
+	if (!einfo->flags)
+		return 0;
+
+	xt_xlate_add(xl, "sctp ");
+
+	if (einfo->flags & XT_SCTP_SRC_PORTS) {
+		if (einfo->spts[0] != einfo->spts[1])
+			xt_xlate_add(xl, "sport%s %u-%u",
+				     einfo->invflags & XT_SCTP_SRC_PORTS ? " !=" : "",
+				     einfo->spts[0], einfo->spts[1]);
+		else
+			xt_xlate_add(xl, "sport%s %u",
+				     einfo->invflags & XT_SCTP_SRC_PORTS ? " !=" : "",
+				     einfo->spts[0]);
+		space = " ";
+	}
+
+	if (einfo->flags & XT_SCTP_DEST_PORTS) {
+		if (einfo->dpts[0] != einfo->dpts[1])
+			xt_xlate_add(xl, "%sdport%s %u-%u", space,
+				     einfo->invflags & XT_SCTP_DEST_PORTS ? " !=" : "",
+				     einfo->dpts[0], einfo->dpts[1]);
+		else
+			xt_xlate_add(xl, "%sdport%s %u", space,
+				     einfo->invflags & XT_SCTP_DEST_PORTS ? " !=" : "",
+				     einfo->dpts[0]);
+	}
+
+	return 1;
+}
+
+static struct xtables_match sctp_match = {
+	.name		= "sctp",
+	.family		= NFPROTO_UNSPEC,
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_sctp_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_sctp_info)),
+	.help		= sctp_help,
+	.init		= sctp_init,
+	.parse		= sctp_parse,
+	.print		= sctp_print,
+	.save		= sctp_save,
+	.extra_opts	= sctp_opts,
+	.xlate		= sctp_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&sctp_match);
+}
diff --git a/extensions/libxt_sctp.man b/extensions/libxt_sctp.man
new file mode 100644
index 0000000..3779d05
--- /dev/null
+++ b/extensions/libxt_sctp.man
@@ -0,0 +1,29 @@
+This module matches Stream Control Transmission Protocol headers.
+.TP
+[\fB!\fP] \fB\-\-source\-port\fP,\fB\-\-sport\fP \fIport\fP[\fB:\fP\fIport\fP]
+.TP
+[\fB!\fP] \fB\-\-destination\-port\fP,\fB\-\-dport\fP \fIport\fP[\fB:\fP\fIport\fP]
+.TP
+[\fB!\fP] \fB\-\-chunk\-types\fP {\fBall\fP|\fBany\fP|\fBonly\fP} \fIchunktype\fP[\fB:\fP\fIflags\fP] [...]
+The flag letter in upper case indicates that the flag is to match if set,
+in the lower case indicates to match if unset.
+
+Chunk types: 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
+
+chunk type            available flags      
+.br
+DATA                  I U B E i u b e
+.br
+ABORT                 T t                 
+.br
+SHUTDOWN_COMPLETE     T t                 
+
+(lowercase means flag should be "off", uppercase means "on")
+.P
+Examples:
+
+iptables \-A INPUT \-p sctp \-\-dport 80 \-j DROP
+
+iptables \-A INPUT \-p sctp \-\-chunk\-types any DATA,INIT \-j DROP
+
+iptables \-A INPUT \-p sctp \-\-chunk\-types any DATA:Be \-j ACCEPT
diff --git a/extensions/libxt_sctp.t b/extensions/libxt_sctp.t
new file mode 100644
index 0000000..4016e4f
--- /dev/null
+++ b/extensions/libxt_sctp.t
@@ -0,0 +1,29 @@
+:INPUT,FORWARD,OUTPUT
+-p sctp -m sctp --sport 1;=;OK
+-p sctp -m sctp --sport 65535;=;OK
+-p sctp -m sctp --sport 1:65535;=;OK
+-p sctp -m sctp --sport -1;;FAIL
+-p sctp -m sctp --sport 65536;;FAIL
+-p sctp -m sctp --dport 1;=;OK
+-p sctp -m sctp --dport 1:65535;=;OK
+-p sctp -m sctp --dport 65535;=;OK
+-p sctp -m sctp --dport -1;;FAIL
+-p sctp -m sctp --dport 65536;;FAIL
+-p sctp -m sctp --chunk-types all DATA;=;OK
+-p sctp -m sctp --chunk-types all INIT;=;OK
+-p sctp -m sctp --chunk-types all INIT_ACK;=;OK
+-p sctp -m sctp --chunk-types all SACK;=;OK
+-p sctp -m sctp --chunk-types all HEARTBEAT;=;OK
+-p sctp -m sctp --chunk-types all HEARTBEAT_ACK;=;OK
+-p sctp -m sctp --chunk-types all ABORT;=;OK
+-p sctp -m sctp --chunk-types all SHUTDOWN;=;OK
+-p sctp -m sctp --chunk-types all SHUTDOWN_ACK;=;OK
+-p sctp -m sctp --chunk-types all ERROR;=;OK
+-p sctp -m sctp --chunk-types all COOKIE_ECHO;=;OK
+-p sctp -m sctp --chunk-types all COOKIE_ACK;=;OK
+-p sctp -m sctp --chunk-types all ECN_ECNE;=;OK
+-p sctp -m sctp --chunk-types all ECN_CWR;=;OK
+-p sctp -m sctp --chunk-types all ASCONF;=;OK
+-p sctp -m sctp --chunk-types all ASCONF_ACK;=;OK
+-p sctp -m sctp --chunk-types all FORWARD_TSN;=;OK
+-p sctp -m sctp --chunk-types all SHUTDOWN_COMPLETE;=;OK
diff --git a/extensions/libxt_sctp.txlate b/extensions/libxt_sctp.txlate
new file mode 100644
index 0000000..72f4641
--- /dev/null
+++ b/extensions/libxt_sctp.txlate
@@ -0,0 +1,38 @@
+iptables-translate -A INPUT -p sctp --dport 80 -j DROP
+nft add rule ip filter INPUT sctp dport 80 counter drop
+
+iptables-translate -A INPUT -p sctp --sport 50 -j DROP
+nft add rule ip filter INPUT sctp sport 50 counter drop
+
+iptables-translate -A INPUT -p sctp ! --dport 80 -j DROP
+nft add rule ip filter INPUT sctp dport != 80 counter drop
+
+iptables-translate -A INPUT -p sctp ! --sport 50 -j DROP
+nft add rule ip filter INPUT sctp sport != 50 counter drop
+
+iptables-translate -A INPUT -p sctp --sport 80:100 -j ACCEPT
+nft add rule ip filter INPUT sctp sport 80-100 counter accept
+
+iptables-translate -A INPUT -p sctp --dport 50:56 -j ACCEPT
+nft add rule ip filter INPUT sctp dport 50-56 counter accept
+
+iptables-translate -A INPUT -p sctp ! --sport 80:100 -j ACCEPT
+nft add rule ip filter INPUT sctp sport != 80-100 counter accept
+
+iptables-translate -A INPUT -p sctp ! --dport 50:56 -j ACCEPT
+nft add rule ip filter INPUT sctp dport != 50-56 counter accept
+
+iptables-translate -A INPUT -p sctp --dport 80 --sport 50 -j ACCEPT
+nft add rule ip filter INPUT sctp sport 50 dport 80 counter accept
+
+iptables-translate -A INPUT -p sctp --dport 80:100 --sport 50 -j ACCEPT
+nft add rule ip filter INPUT sctp sport 50 dport 80-100 counter accept
+
+iptables-translate -A INPUT -p sctp --dport 80 --sport 50:55 -j ACCEPT
+nft add rule ip filter INPUT sctp sport 50-55 dport 80 counter accept
+
+iptables-translate -A INPUT -p sctp ! --dport 80:100 --sport 50 -j ACCEPT
+nft add rule ip filter INPUT sctp sport 50 dport != 80-100 counter accept
+
+iptables-translate -A INPUT -p sctp --dport 80 ! --sport 50:55 -j ACCEPT
+nft add rule ip filter INPUT sctp sport != 50-55 dport 80 counter accept
diff --git a/extensions/libxt_set.c b/extensions/libxt_set.c
new file mode 100644
index 0000000..1692102
--- /dev/null
+++ b/extensions/libxt_set.c
@@ -0,0 +1,749 @@
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ *                         Patrick Schaaf <bof@bof.de>
+ *                         Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 2003-2010 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.  
+ */
+
+/* Shared library add-on to iptables to add IP set matching. */
+#include <stdbool.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <xtables.h>
+#include <linux/netfilter/xt_set.h>
+#include "libxt_set.h"
+
+/* Revision 0 */
+
+static void
+set_help_v0(void)
+{
+	printf("set match options:\n"
+	       " [!] --match-set name flags\n"
+	       "		 'name' is the set name from to match,\n" 
+	       "		 'flags' are the comma separated list of\n"
+	       "		 'src' and 'dst' specifications.\n");
+}
+
+static const struct option set_opts_v0[] = {
+	{.name = "match-set", .has_arg = true, .val = '1'},
+	{.name = "set",       .has_arg = true, .val = '2'},
+	XT_GETOPT_TABLEEND,
+};
+
+static void
+set_check_v0(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			"You must specify `--match-set' with proper arguments");
+}
+
+static int
+set_parse_v0(int c, char **argv, int invert, unsigned int *flags,
+	     const void *entry, struct xt_entry_match **match)
+{
+	struct xt_set_info_match_v0 *myinfo =
+		(struct xt_set_info_match_v0 *) (*match)->data;
+	struct xt_set_info_v0 *info = &myinfo->match_set;
+
+	switch (c) {
+	case '2':
+		fprintf(stderr,
+			"--set option deprecated, please use --match-set\n");
+		/* fall through */
+	case '1':		/* --match-set <set> <flag>[,<flag> */
+		if (info->u.flags[0])
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set can be specified only once");
+		if (invert)
+			info->u.flags[0] |= IPSET_MATCH_INV;
+
+		if (!argv[optind]
+		    || argv[optind][0] == '-'
+		    || argv[optind][0] == '!')
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set requires two args.");
+
+		if (strlen(optarg) > IPSET_MAXNAMELEN - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "setname `%s' too long, max %d characters.",
+				      optarg, IPSET_MAXNAMELEN - 1);
+
+		get_set_byname(optarg, (struct xt_set_info *)info);
+		parse_dirs_v0(argv[optind], info);
+		DEBUGP("parse: set index %u\n", info->index);
+		optind++;
+
+		*flags = 1;
+		break;
+	}
+
+	return 1;
+}
+
+static void
+print_match_v0(const char *prefix, const struct xt_set_info_v0 *info)
+{
+	int i;
+	char setname[IPSET_MAXNAMELEN];
+
+	get_set_byid(setname, info->index);
+	printf("%s %s %s",
+	       (info->u.flags[0] & IPSET_MATCH_INV) ? " !" : "",
+	       prefix,
+	       setname); 
+	for (i = 0; i < IPSET_DIM_MAX; i++) {
+		if (!info->u.flags[i])
+			break;		
+		printf("%s%s",
+		       i == 0 ? " " : ",",
+		       info->u.flags[i] & IPSET_SRC ? "src" : "dst");
+	}
+}
+
+/* Prints out the matchinfo. */
+static void
+set_print_v0(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_set_info_match_v0 *info = (const void *)match->data;
+
+	print_match_v0("match-set", &info->match_set);
+}
+
+static void
+set_save_v0(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_set_info_match_v0 *info = (const void *)match->data;
+
+	print_match_v0("--match-set", &info->match_set);
+}
+
+/* Revision 1 */
+static int
+set_parse_v1(int c, char **argv, int invert, unsigned int *flags,
+	     const void *entry, struct xt_entry_match **match)
+{
+	struct xt_set_info_match_v1 *myinfo =
+		(struct xt_set_info_match_v1 *) (*match)->data;
+	struct xt_set_info *info = &myinfo->match_set;
+
+	switch (c) {
+	case '2':
+		fprintf(stderr,
+			"--set option deprecated, please use --match-set\n");
+		/* fall through */
+	case '1':		/* --match-set <set> <flag>[,<flag> */
+		if (info->dim)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set can be specified only once");
+		if (invert)
+			info->flags |= IPSET_INV_MATCH;
+
+		if (!argv[optind]
+		    || argv[optind][0] == '-'
+		    || argv[optind][0] == '!')
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set requires two args.");
+
+		if (strlen(optarg) > IPSET_MAXNAMELEN - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "setname `%s' too long, max %d characters.",
+				      optarg, IPSET_MAXNAMELEN - 1);
+
+		get_set_byname(optarg, info);
+		parse_dirs(argv[optind], info);
+		DEBUGP("parse: set index %u\n", info->index);
+		optind++;
+
+		*flags = 1;
+		break;
+	}
+
+	return 1;
+}
+
+static void
+print_match(const char *prefix, const struct xt_set_info *info)
+{
+	int i;
+	char setname[IPSET_MAXNAMELEN];
+
+	get_set_byid(setname, info->index);
+	printf("%s %s %s",
+	       (info->flags & IPSET_INV_MATCH) ? " !" : "",
+	       prefix,
+	       setname); 
+	for (i = 1; i <= info->dim; i++) {		
+		printf("%s%s",
+		       i == 1 ? " " : ",",
+		       info->flags & (1 << i) ? "src" : "dst");
+	}
+}
+
+/* Prints out the matchinfo. */
+static void
+set_print_v1(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_set_info_match_v1 *info = (const void *)match->data;
+
+	print_match("match-set", &info->match_set);
+}
+
+static void
+set_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_set_info_match_v1 *info = (const void *)match->data;
+
+	print_match("--match-set", &info->match_set);
+}
+
+/* Revision 2 */
+static void
+set_help_v2(void)
+{
+	printf("set match options:\n"
+	       " [!] --match-set name flags [--return-nomatch]\n"
+	       "		 'name' is the set name from to match,\n" 
+	       "		 'flags' are the comma separated list of\n"
+	       "		 'src' and 'dst' specifications.\n");
+}
+
+static const struct option set_opts_v2[] = {
+	{.name = "match-set",		.has_arg = true,	.val = '1'},
+	{.name = "set",			.has_arg = true,	.val = '2'},
+	{.name = "return-nomatch",	.has_arg = false,	.val = '3'},
+	XT_GETOPT_TABLEEND,
+};
+
+static int
+set_parse_v2(int c, char **argv, int invert, unsigned int *flags,
+	     const void *entry, struct xt_entry_match **match)
+{
+	struct xt_set_info_match_v1 *myinfo =
+		(struct xt_set_info_match_v1 *) (*match)->data;
+	struct xt_set_info *info = &myinfo->match_set;
+
+	switch (c) {
+	case '3':
+		info->flags |= IPSET_RETURN_NOMATCH;
+		break;
+	case '2':
+		fprintf(stderr,
+			"--set option deprecated, please use --match-set\n");
+		/* fall through */
+	case '1':		/* --match-set <set> <flag>[,<flag> */
+		if (info->dim)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set can be specified only once");
+		if (invert)
+			info->flags |= IPSET_INV_MATCH;
+
+		if (!argv[optind]
+		    || argv[optind][0] == '-'
+		    || argv[optind][0] == '!')
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set requires two args.");
+
+		if (strlen(optarg) > IPSET_MAXNAMELEN - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "setname `%s' too long, max %d characters.",
+				      optarg, IPSET_MAXNAMELEN - 1);
+
+		get_set_byname(optarg, info);
+		parse_dirs(argv[optind], info);
+		DEBUGP("parse: set index %u\n", info->index);
+		optind++;
+
+		*flags = 1;
+		break;
+	}
+
+	return 1;
+}
+
+/* Prints out the matchinfo. */
+static void
+set_print_v2(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_set_info_match_v1 *info = (const void *)match->data;
+
+	print_match("match-set", &info->match_set);
+	if (info->match_set.flags & IPSET_RETURN_NOMATCH)
+		printf(" return-nomatch");
+}
+
+static void
+set_save_v2(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_set_info_match_v1 *info = (const void *)match->data;
+
+	print_match("--match-set", &info->match_set);
+	if (info->match_set.flags & IPSET_RETURN_NOMATCH)
+		printf(" --return-nomatch");
+}
+
+/* Revision 3 */
+static void
+set_help_v3(void)
+{
+	printf("set match options:\n"
+	       " [!] --match-set name flags [--return-nomatch]\n"
+	       "   [! --update-counters] [! --update-subcounters]\n"
+	       "   [[!] --packets-eq value | --packets-lt value | --packets-gt value\n"
+	       "   [[!] --bytes-eq value | --bytes-lt value | --bytes-gt value\n"
+	       "		 'name' is the set name from to match,\n" 
+	       "		 'flags' are the comma separated list of\n"
+	       "		 'src' and 'dst' specifications.\n");
+}
+
+static const struct option set_opts_v3[] = {
+	{.name = "match-set",		.has_arg = true,	.val = '1'},
+	{.name = "set",			.has_arg = true,	.val = '2'},
+	{.name = "return-nomatch",	.has_arg = false,	.val = '3'},
+	{.name = "update-counters",	.has_arg = false,	.val = '4'},
+	{.name = "packets-eq",		.has_arg = true,	.val = '5'},
+	{.name = "packets-lt",		.has_arg = true,	.val = '6'},
+	{.name = "packets-gt",		.has_arg = true,	.val = '7'},
+	{.name = "bytes-eq",		.has_arg = true,	.val = '8'},
+	{.name = "bytes-lt",		.has_arg = true,	.val = '9'},
+	{.name = "bytes-gt",		.has_arg = true,	.val = '0'},
+	{.name = "update-subcounters",	.has_arg = false,	.val = 'a'},
+	XT_GETOPT_TABLEEND,
+};
+
+static uint64_t
+parse_counter(const char *opt)
+{
+	uintmax_t value;
+
+	if (!xtables_strtoul(opt, NULL, &value, 0, UINT64_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Cannot parse %s as a counter value\n",
+			      opt);
+	return (uint64_t)value;
+}
+
+static int
+set_parse_v3(int c, char **argv, int invert, unsigned int *flags,
+	     const void *entry, struct xt_entry_match **match)
+{
+	struct xt_set_info_match_v3 *info =
+		(struct xt_set_info_match_v3 *) (*match)->data;
+
+	switch (c) {
+	case 'a':
+		if (invert)
+			info->flags |= IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE;
+		break;
+	case '0':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--bytes-gt option cannot be inverted\n");
+		info->bytes.op = IPSET_COUNTER_GT;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '9':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--bytes-lt option cannot be inverted\n");
+		info->bytes.op = IPSET_COUNTER_LT;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '8':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		info->bytes.op = invert ? IPSET_COUNTER_NE : IPSET_COUNTER_EQ;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '7':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--packets-gt option cannot be inverted\n");
+		info->packets.op = IPSET_COUNTER_GT;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '6':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--packets-lt option cannot be inverted\n");
+		info->packets.op = IPSET_COUNTER_LT;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '5':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		info->packets.op = invert ? IPSET_COUNTER_NE : IPSET_COUNTER_EQ;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '4':
+		if (invert)
+			info->flags |= IPSET_FLAG_SKIP_COUNTER_UPDATE;
+		break;
+	case '3':
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--return-nomatch flag cannot be inverted\n");
+		info->flags |= IPSET_FLAG_RETURN_NOMATCH;
+		break;
+	case '2':
+		fprintf(stderr,
+			"--set option deprecated, please use --match-set\n");
+		/* fall through */
+	case '1':		/* --match-set <set> <flag>[,<flag> */
+		if (info->match_set.dim)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set can be specified only once");
+		if (invert)
+			info->match_set.flags |= IPSET_INV_MATCH;
+
+		if (!argv[optind]
+		    || argv[optind][0] == '-'
+		    || argv[optind][0] == '!')
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set requires two args.");
+
+		if (strlen(optarg) > IPSET_MAXNAMELEN - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "setname `%s' too long, max %d characters.",
+				      optarg, IPSET_MAXNAMELEN - 1);
+
+		get_set_byname(optarg, &info->match_set);
+		parse_dirs(argv[optind], &info->match_set);
+		DEBUGP("parse: set index %u\n", info->match_set.index);
+		optind++;
+
+		*flags = 1;
+		break;
+	}
+
+	return 1;
+}
+
+static void
+set_printv3_counter(const struct ip_set_counter_match0 *c, const char *name,
+		    const char *sep)
+{
+	switch (c->op) {
+	case IPSET_COUNTER_EQ:
+		printf(" %s%s-eq %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_NE:
+		printf(" ! %s%s-eq %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_LT:
+		printf(" %s%s-lt %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_GT:
+		printf(" %s%s-gt %llu", sep, name, c->value);
+		break;
+	}
+}
+
+static void
+set_print_v3_matchinfo(const struct xt_set_info_match_v3 *info,
+		       const char *opt, const char *sep)
+{
+	print_match(opt, &info->match_set);
+	if (info->flags & IPSET_FLAG_RETURN_NOMATCH)
+		printf(" %sreturn-nomatch", sep);
+	if ((info->flags & IPSET_FLAG_SKIP_COUNTER_UPDATE))
+		printf(" ! %supdate-counters", sep);
+	if ((info->flags & IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE))
+		printf(" ! %supdate-subcounters", sep);
+	set_printv3_counter(&info->packets, "packets", sep);
+	set_printv3_counter(&info->bytes, "bytes", sep);
+}
+
+/* Prints out the matchinfo. */
+static void
+set_print_v3(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_set_info_match_v3 *info = (const void *)match->data;
+
+	set_print_v3_matchinfo(info, "match-set", "");
+}
+
+static void
+set_save_v3(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_set_info_match_v3 *info = (const void *)match->data;
+
+	set_print_v3_matchinfo(info, "--match-set", "--");
+}
+
+/* Revision 4 */
+static int
+set_parse_v4(int c, char **argv, int invert, unsigned int *flags,
+	     const void *entry, struct xt_entry_match **match)
+{
+	struct xt_set_info_match_v4 *info =
+		(struct xt_set_info_match_v4 *) (*match)->data;
+
+	switch (c) {
+	case 'a':
+		if (invert)
+			info->flags |= IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE;
+		break;
+	case '0':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--bytes-gt option cannot be inverted\n");
+		info->bytes.op = IPSET_COUNTER_GT;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '9':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--bytes-lt option cannot be inverted\n");
+		info->bytes.op = IPSET_COUNTER_LT;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '8':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		info->bytes.op = invert ? IPSET_COUNTER_NE : IPSET_COUNTER_EQ;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '7':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--packets-gt option cannot be inverted\n");
+		info->packets.op = IPSET_COUNTER_GT;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '6':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--packets-lt option cannot be inverted\n");
+		info->packets.op = IPSET_COUNTER_LT;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '5':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		info->packets.op = invert ? IPSET_COUNTER_NE : IPSET_COUNTER_EQ;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '4':
+		if (invert)
+			info->flags |= IPSET_FLAG_SKIP_COUNTER_UPDATE;
+		break;
+	case '3':
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--return-nomatch flag cannot be inverted\n");
+		info->flags |= IPSET_FLAG_RETURN_NOMATCH;
+		break;
+	case '2':
+		fprintf(stderr,
+			"--set option deprecated, please use --match-set\n");
+		/* fall through */
+	case '1':		/* --match-set <set> <flag>[,<flag> */
+		if (info->match_set.dim)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set can be specified only once");
+		if (invert)
+			info->match_set.flags |= IPSET_INV_MATCH;
+
+		if (!argv[optind]
+		    || argv[optind][0] == '-'
+		    || argv[optind][0] == '!')
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set requires two args.");
+
+		if (strlen(optarg) > IPSET_MAXNAMELEN - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "setname `%s' too long, max %d characters.",
+				      optarg, IPSET_MAXNAMELEN - 1);
+
+		get_set_byname(optarg, &info->match_set);
+		parse_dirs(argv[optind], &info->match_set);
+		DEBUGP("parse: set index %u\n", info->match_set.index);
+		optind++;
+
+		*flags = 1;
+		break;
+	}
+
+	return 1;
+}
+
+static void
+set_printv4_counter(const struct ip_set_counter_match *c, const char *name,
+		    const char *sep)
+{
+	switch (c->op) {
+	case IPSET_COUNTER_EQ:
+		printf(" %s%s-eq %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_NE:
+		printf(" ! %s%s-eq %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_LT:
+		printf(" %s%s-lt %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_GT:
+		printf(" %s%s-gt %llu", sep, name, c->value);
+		break;
+	}
+}
+
+static void
+set_print_v4_matchinfo(const struct xt_set_info_match_v4 *info,
+		       const char *opt, const char *sep)
+{
+	print_match(opt, &info->match_set);
+	if (info->flags & IPSET_FLAG_RETURN_NOMATCH)
+		printf(" %sreturn-nomatch", sep);
+	if ((info->flags & IPSET_FLAG_SKIP_COUNTER_UPDATE))
+		printf(" ! %supdate-counters", sep);
+	if ((info->flags & IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE))
+		printf(" ! %supdate-subcounters", sep);
+	set_printv4_counter(&info->packets, "packets", sep);
+	set_printv4_counter(&info->bytes, "bytes", sep);
+}
+
+/* Prints out the matchinfo. */
+static void
+set_print_v4(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_set_info_match_v4 *info = (const void *)match->data;
+
+	set_print_v4_matchinfo(info, "match-set", "");
+}
+
+static void
+set_save_v4(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_set_info_match_v4 *info = (const void *)match->data;
+
+	set_print_v4_matchinfo(info, "--match-set", "--");
+}
+
+static struct xtables_match set_mt_reg[] = {
+	{
+		.name		= "set",
+		.revision	= 0,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV4,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_match_v0)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_match_v0)),
+		.help		= set_help_v0,
+		.parse		= set_parse_v0,
+		.final_check	= set_check_v0,
+		.print		= set_print_v0,
+		.save		= set_save_v0,
+		.extra_opts	= set_opts_v0,
+	},
+	{
+		.name		= "set",
+		.revision	= 1,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_match_v1)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_match_v1)),
+		.help		= set_help_v0,
+		.parse		= set_parse_v1,
+		.final_check	= set_check_v0,
+		.print		= set_print_v1,
+		.save		= set_save_v1,
+		.extra_opts	= set_opts_v0,
+	},
+	{
+		.name		= "set",
+		.revision	= 2,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_match_v1)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_match_v1)),
+		.help		= set_help_v2,
+		.parse		= set_parse_v2,
+		.final_check	= set_check_v0,
+		.print		= set_print_v2,
+		.save		= set_save_v2,
+		.extra_opts	= set_opts_v2,
+	},
+	{
+		.name		= "set",
+		.revision	= 3,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_match_v3)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_match_v3)),
+		.help		= set_help_v3,
+		.parse		= set_parse_v3,
+		.final_check	= set_check_v0,
+		.print		= set_print_v3,
+		.save		= set_save_v3,
+		.extra_opts	= set_opts_v3,
+	},
+	{
+		.name		= "set",
+		.revision	= 4,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_match_v4)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_match_v4)),
+		.help		= set_help_v3,
+		.parse		= set_parse_v4,
+		.final_check	= set_check_v0,
+		.print		= set_print_v4,
+		.save		= set_save_v4,
+		.extra_opts	= set_opts_v3,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(set_mt_reg, ARRAY_SIZE(set_mt_reg));
+}
diff --git a/extensions/libxt_set.h b/extensions/libxt_set.h
new file mode 100644
index 0000000..41dfbd3
--- /dev/null
+++ b/extensions/libxt_set.h
@@ -0,0 +1,191 @@
+#ifndef _LIBXT_SET_H
+#define _LIBXT_SET_H
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include "../iptables/xshared.h"
+
+static int
+get_version(unsigned *version)
+{
+	int res, sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+	struct ip_set_req_version req_version;
+	socklen_t size = sizeof(req_version);
+	
+	if (sockfd < 0)
+		xtables_error(OTHER_PROBLEM,
+			      "Can't open socket to ipset.\n");
+
+	if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) {
+		xtables_error(OTHER_PROBLEM,
+			      "Could not set close on exec: %s\n",
+			      strerror(errno));
+	}
+
+	req_version.op = IP_SET_OP_VERSION;
+	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req_version, &size);
+	if (res != 0)
+		xtables_error(OTHER_PROBLEM,
+			      "Kernel module xt_set is not loaded in.\n");
+
+	*version = req_version.version;
+	
+	return sockfd;
+}
+
+static void
+get_set_byid(char *setname, ip_set_id_t idx)
+{
+	struct ip_set_req_get_set req;
+	socklen_t size = sizeof(struct ip_set_req_get_set);
+	int res, sockfd;
+
+	sockfd = get_version(&req.version);
+	req.op = IP_SET_OP_GET_BYINDEX;
+	req.set.index = idx;
+	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req, &size);
+	close(sockfd);
+
+	if (res != 0)
+		xtables_error(OTHER_PROBLEM,
+			"Problem when communicating with ipset, errno=%d.\n",
+			errno);
+	if (size != sizeof(struct ip_set_req_get_set))
+		xtables_error(OTHER_PROBLEM,
+			"Incorrect return size from kernel during ipset lookup, "
+			"(want %zu, got %zu)\n",
+			sizeof(struct ip_set_req_get_set), (size_t)size);
+	if (req.set.name[0] == '\0')
+		xtables_error(PARAMETER_PROBLEM,
+			"Set with index %i in kernel doesn't exist.\n", idx);
+
+	strncpy(setname, req.set.name, IPSET_MAXNAMELEN);
+}
+
+static void
+get_set_byname_only(const char *setname, struct xt_set_info *info,
+		    int sockfd, unsigned int version)
+{
+	struct ip_set_req_get_set req = { .version = version };
+	socklen_t size = sizeof(struct ip_set_req_get_set);
+	int res;
+
+	req.op = IP_SET_OP_GET_BYNAME;
+	strncpy(req.set.name, setname, IPSET_MAXNAMELEN);
+	req.set.name[IPSET_MAXNAMELEN - 1] = '\0';
+	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req, &size);
+	close(sockfd);
+
+	if (res != 0)
+		xtables_error(OTHER_PROBLEM,
+			"Problem when communicating with ipset, errno=%d.\n",
+			errno);
+	if (size != sizeof(struct ip_set_req_get_set))
+		xtables_error(OTHER_PROBLEM,
+			"Incorrect return size from kernel during ipset lookup, "
+			"(want %zu, got %zu)\n",
+			sizeof(struct ip_set_req_get_set), (size_t)size);
+	if (req.set.index == IPSET_INVALID_ID)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Set %s doesn't exist.\n", setname);
+
+	info->index = req.set.index;
+}
+
+static void
+get_set_byname(const char *setname, struct xt_set_info *info)
+{
+	struct ip_set_req_get_set_family req;
+	socklen_t size = sizeof(struct ip_set_req_get_set_family);
+	int res, sockfd, version;
+
+	sockfd = get_version(&req.version);
+	version = req.version;
+	req.op = IP_SET_OP_GET_FNAME;
+	strncpy(req.set.name, setname, IPSET_MAXNAMELEN);
+	req.set.name[IPSET_MAXNAMELEN - 1] = '\0';
+	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req, &size);
+
+	if (res != 0 && errno == EBADMSG)
+		/* Backward compatibility */
+		return get_set_byname_only(setname, info, sockfd, version);
+
+	close(sockfd);
+	if (res != 0)
+		xtables_error(OTHER_PROBLEM,
+			"Problem when communicating with ipset, errno=%d.\n",
+			errno);
+	if (size != sizeof(struct ip_set_req_get_set_family))
+		xtables_error(OTHER_PROBLEM,
+			"Incorrect return size from kernel during ipset lookup, "
+			"(want %zu, got %zu)\n",
+			sizeof(struct ip_set_req_get_set_family),
+			(size_t)size);
+	if (req.set.index == IPSET_INVALID_ID)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Set %s doesn't exist.\n", setname);
+	if (!(req.family == afinfo->family ||
+	      req.family == NFPROTO_UNSPEC))
+		xtables_error(PARAMETER_PROBLEM,
+			      "The protocol family of set %s is %s, "
+			      "which is not applicable.\n",
+			      setname,
+			      req.family == NFPROTO_IPV4 ? "IPv4" : "IPv6");
+
+	info->index = req.set.index;
+}
+
+static void
+parse_dirs_v0(const char *opt_arg, struct xt_set_info_v0 *info)
+{
+	char *saved = strdup(opt_arg);
+	char *ptr, *tmp = saved;
+	int i = 0;
+	
+	while (i < (IPSET_DIM_MAX - 1) && tmp != NULL) {
+		ptr = strsep(&tmp, ",");
+		if (strncmp(ptr, "src", 3) == 0)
+			info->u.flags[i++] |= IPSET_SRC;
+		else if (strncmp(ptr, "dst", 3) == 0)
+			info->u.flags[i++] |= IPSET_DST;
+		else
+			xtables_error(PARAMETER_PROBLEM,
+				"You must spefify (the comma separated list of) 'src' or 'dst'.");
+	}
+
+	if (tmp)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Can't be more src/dst options than %i.", 
+			      IPSET_DIM_MAX);
+
+	free(saved);
+}
+
+static void
+parse_dirs(const char *opt_arg, struct xt_set_info *info)
+{
+	char *saved = strdup(opt_arg);
+	char *ptr, *tmp = saved;
+	
+	while (info->dim < IPSET_DIM_MAX && tmp != NULL) {
+		info->dim++;
+		ptr = strsep(&tmp, ",");
+		if (strncmp(ptr, "src", 3) == 0)
+			info->flags |= (1 << info->dim);
+		else if (strncmp(ptr, "dst", 3) != 0)
+			xtables_error(PARAMETER_PROBLEM,
+				"You must spefify (the comma separated list of) 'src' or 'dst'.");
+	}
+
+	if (tmp)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Can't be more src/dst options than %i.", 
+			      IPSET_DIM_MAX);
+
+	free(saved);
+}
+
+#endif /*_LIBXT_SET_H*/
diff --git a/extensions/libxt_set.man b/extensions/libxt_set.man
new file mode 100644
index 0000000..5c6f64e
--- /dev/null
+++ b/extensions/libxt_set.man
@@ -0,0 +1,65 @@
+This module matches IP sets which can be defined by ipset(8).
+.TP
+[\fB!\fP] \fB\-\-match\-set\fP \fIsetname\fP \fIflag\fP[\fB,\fP\fIflag\fP]...
+where flags are the comma separated list of
+.BR "src"
+and/or
+.BR "dst" 
+specifications and there can be no more than six of them. Hence the command
+.IP
+ iptables \-A FORWARD \-m set \-\-match\-set test src,dst
+.IP
+will match packets, for which (if the set type is ipportmap) the source
+address and destination port pair can be found in the specified set. If
+the set type of the specified set is single dimension (for example ipmap),
+then the command will match packets for which the source address can be
+found in the specified set. 
+.TP
+\fB\-\-return\-nomatch\fP
+If the \fB\-\-return\-nomatch\fP option is specified and the set type
+supports the \fBnomatch\fP flag, then the matching is reversed: a match
+with an element flagged with \fBnomatch\fP returns \fBtrue\fP, while a
+match with a plain element returns \fBfalse\fP.
+.TP
+\fB!\fP \fB\-\-update\-counters\fP
+If the \fB\-\-update\-counters\fP flag is negated, then the packet and
+byte counters of the matching element in the set won't be updated. Default
+the packet and byte counters are updated.
+.TP
+\fB!\fP \fB\-\-update\-subcounters\fP
+If the \fB\-\-update\-subcounters\fP flag is negated, then the packet and
+byte counters of the matching element in the member set of a list type of
+set won't be updated. Default the packet and byte counters are updated.
+.TP
+[\fB!\fP] \fB\-\-packets\-eq\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+packet counter of the element matches the given value too.
+.TP
+\fB\-\-packets\-lt\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+packet counter of the element is less than the given value as well.
+.TP
+\fB\-\-packets\-gt\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+packet counter of the element is greater than the given value as well.
+.TP
+[\fB!\fP] \fB\-\-bytes\-eq\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+byte counter of the element matches the given value too.
+.TP
+\fB\-\-bytes\-lt\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+byte counter of the element is less than the given value as well.
+.TP
+\fB\-\-bytes\-gt\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+byte counter of the element is greater than the given value as well.
+.PP
+The packet and byte counters related options and flags are ignored
+when the set was defined without counter support.
+.PP
+The option \fB\-\-match\-set\fP can be replaced by \fB\-\-set\fP if that does 
+not clash with an option of other extensions.
+.PP
+Use of \-m set requires that ipset kernel support is provided, which, for
+standard kernels, is the case since Linux 2.6.39.
diff --git a/extensions/libxt_set.t b/extensions/libxt_set.t
new file mode 100644
index 0000000..dd9e9f1
--- /dev/null
+++ b/extensions/libxt_set.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-m set --match-set foo;;FAIL
+# fails: foo does not exist
+-m set --match-set foo src,dst;;FAIL
diff --git a/extensions/libxt_socket.c b/extensions/libxt_socket.c
new file mode 100644
index 0000000..a99135c
--- /dev/null
+++ b/extensions/libxt_socket.c
@@ -0,0 +1,215 @@
+/*
+ * Shared library add-on to iptables to add early socket matching support.
+ *
+ * Copyright (C) 2007 BalaBit IT Ltd.
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_socket.h>
+
+enum {
+	O_TRANSPARENT = 0,
+	O_NOWILDCARD = 1,
+	O_RESTORESKMARK = 2,
+};
+
+static const struct xt_option_entry socket_mt_opts[] = {
+	{.name = "transparent", .id = O_TRANSPARENT, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry socket_mt_opts_v2[] = {
+	{.name = "transparent", .id = O_TRANSPARENT, .type = XTTYPE_NONE},
+	{.name = "nowildcard", .id = O_NOWILDCARD, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry socket_mt_opts_v3[] = {
+	{.name = "transparent", .id = O_TRANSPARENT, .type = XTTYPE_NONE},
+	{.name = "nowildcard", .id = O_NOWILDCARD, .type = XTTYPE_NONE},
+	{.name = "restore-skmark", .id = O_RESTORESKMARK, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static void socket_mt_help(void)
+{
+	printf(
+		"socket match options:\n"
+		"  --transparent    Ignore non-transparent sockets\n\n");
+}
+
+static void socket_mt_help_v2(void)
+{
+	printf(
+		"socket match options:\n"
+		"  --nowildcard     Do not ignore LISTEN sockets bound on INADDR_ANY\n"
+		"  --transparent    Ignore non-transparent sockets\n\n");
+}
+
+static void socket_mt_help_v3(void)
+{
+	printf(
+		"socket match options:\n"
+		"  --nowildcard     Do not ignore LISTEN sockets bound on INADDR_ANY\n"
+		"  --transparent    Ignore non-transparent sockets\n"
+		"  --restore-skmark Set the packet mark to the socket mark if\n"
+		"                   the socket matches and transparent / \n"
+		"                   nowildcard conditions are satisfied\n\n");
+}
+
+static void socket_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_socket_mtinfo1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TRANSPARENT:
+		info->flags |= XT_SOCKET_TRANSPARENT;
+		break;
+	}
+}
+
+static void socket_mt_parse_v2(struct xt_option_call *cb)
+{
+	struct xt_socket_mtinfo2 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TRANSPARENT:
+		info->flags |= XT_SOCKET_TRANSPARENT;
+		break;
+	case O_NOWILDCARD:
+		info->flags |= XT_SOCKET_NOWILDCARD;
+		break;
+	}
+}
+
+static void socket_mt_parse_v3(struct xt_option_call *cb)
+{
+	struct xt_socket_mtinfo2 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TRANSPARENT:
+		info->flags |= XT_SOCKET_TRANSPARENT;
+		break;
+	case O_NOWILDCARD:
+		info->flags |= XT_SOCKET_NOWILDCARD;
+		break;
+	case O_RESTORESKMARK:
+		info->flags |= XT_SOCKET_RESTORESKMARK;
+		break;
+	}
+}
+
+static void
+socket_mt_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_socket_mtinfo1 *info = (const void *)match->data;
+
+	if (info->flags & XT_SOCKET_TRANSPARENT)
+		printf(" --transparent");
+}
+
+static void
+socket_mt_print(const void *ip, const struct xt_entry_match *match,
+		int numeric)
+{
+	printf(" socket");
+	socket_mt_save(ip, match);
+}
+
+static void
+socket_mt_save_v2(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_socket_mtinfo2 *info = (const void *)match->data;
+
+	if (info->flags & XT_SOCKET_TRANSPARENT)
+		printf(" --transparent");
+	if (info->flags & XT_SOCKET_NOWILDCARD)
+		printf(" --nowildcard");
+}
+
+static void
+socket_mt_print_v2(const void *ip, const struct xt_entry_match *match,
+		   int numeric)
+{
+	printf(" socket");
+	socket_mt_save_v2(ip, match);
+}
+
+static void
+socket_mt_save_v3(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_socket_mtinfo3 *info = (const void *)match->data;
+
+	if (info->flags & XT_SOCKET_TRANSPARENT)
+		printf(" --transparent");
+	if (info->flags & XT_SOCKET_NOWILDCARD)
+		printf(" --nowildcard");
+	if (info->flags & XT_SOCKET_RESTORESKMARK)
+		printf(" --restore-skmark");
+}
+
+static void
+socket_mt_print_v3(const void *ip, const struct xt_entry_match *match,
+		   int numeric)
+{
+	printf(" socket");
+	socket_mt_save_v3(ip, match);
+}
+
+static struct xtables_match socket_mt_reg[] = {
+	{
+		.name          = "socket",
+		.revision      = 0,
+		.family        = NFPROTO_IPV4,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(0),
+		.userspacesize = XT_ALIGN(0),
+	},
+	{
+		.name          = "socket",
+		.revision      = 1,
+		.family        = NFPROTO_UNSPEC,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_socket_mtinfo1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_socket_mtinfo1)),
+		.help          = socket_mt_help,
+		.print         = socket_mt_print,
+		.save          = socket_mt_save,
+		.x6_parse      = socket_mt_parse,
+		.x6_options    = socket_mt_opts,
+	},
+	{
+		.name          = "socket",
+		.revision      = 2,
+		.family        = NFPROTO_UNSPEC,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
+		.help          = socket_mt_help_v2,
+		.print         = socket_mt_print_v2,
+		.save          = socket_mt_save_v2,
+		.x6_parse      = socket_mt_parse_v2,
+		.x6_options    = socket_mt_opts_v2,
+	},
+	{
+		.name          = "socket",
+		.revision      = 3,
+		.family        = NFPROTO_UNSPEC,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
+		.help          = socket_mt_help_v3,
+		.print         = socket_mt_print_v3,
+		.save          = socket_mt_save_v3,
+		.x6_parse      = socket_mt_parse_v3,
+		.x6_options    = socket_mt_opts_v3,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(socket_mt_reg, ARRAY_SIZE(socket_mt_reg));
+}
diff --git a/extensions/libxt_socket.man b/extensions/libxt_socket.man
new file mode 100644
index 0000000..f809df6
--- /dev/null
+++ b/extensions/libxt_socket.man
@@ -0,0 +1,36 @@
+This matches if an open TCP/UDP socket can be found by doing a socket lookup on the
+packet. It matches if there is an established or non\-zero bound listening
+socket (possibly with a non\-local address). The lookup is performed using
+the \fBpacket\fP tuple of TCP/UDP packets, or the original TCP/UDP header
+\fBembedded\fP in an ICMP/ICPMv6 error packet.
+.TP
+\fB\-\-transparent\fP
+Ignore non-transparent sockets.
+.TP
+\fB\-\-nowildcard\fP
+Do not ignore sockets bound to 'any' address.
+The socket match won't accept zero\-bound listeners by default, since
+then local services could intercept traffic that would otherwise be forwarded.
+This option therefore has security implications when used to match traffic being
+forwarded to redirect such packets to local machine with policy routing.
+When using the socket match to implement fully transparent
+proxies bound to non\-local addresses it is recommended to use the \-\-transparent
+option instead.
+.PP
+Example (assuming packets with mark 1 are delivered locally):
+.IP
+\-t mangle \-A PREROUTING \-m socket \-\-transparent \-j MARK \-\-set\-mark 1
+.TP
+\fB\-\-restore\-skmark\fP
+Set the packet mark to the matching socket's mark. Can be combined with the
+\fB\-\-transparent\fP and \fB\-\-nowildcard\fP options to restrict the sockets
+to be matched when restoring the packet mark.
+.PP
+Example: An application opens 2 transparent (\fBIP_TRANSPARENT\fP) sockets and
+sets a mark on them with \fBSO_MARK\fP socket option. We can filter matching packets:
+.IP
+\-t mangle \-I PREROUTING \-m socket \-\-transparent \-\-restore-skmark \-j action
+.IP
+\-t mangle \-A action \-m mark \-\-mark 10 \-j action2
+.IP
+\-t mangle \-A action \-m mark \-\-mark 11 \-j action3
diff --git a/extensions/libxt_socket.t b/extensions/libxt_socket.t
new file mode 100644
index 0000000..fe4eb3e
--- /dev/null
+++ b/extensions/libxt_socket.t
@@ -0,0 +1,8 @@
+:PREROUTING,INPUT
+*mangle
+-m socket;=;OK
+-m socket --transparent --nowildcard;=;OK
+-m socket --transparent --nowildcard --restore-skmark;=;OK
+-m socket --transparent --restore-skmark;=;OK
+-m socket --nowildcard --restore-skmark;=;OK
+-m socket --restore-skmark;=;OK
diff --git a/extensions/libxt_standard.c b/extensions/libxt_standard.c
new file mode 100644
index 0000000..c64ba29
--- /dev/null
+++ b/extensions/libxt_standard.c
@@ -0,0 +1,24 @@
+/* Shared library add-on to iptables for standard target support. */
+#include <stdio.h>
+#include <xtables.h>
+
+static void standard_help(void)
+{
+	printf(
+"standard match options:\n"
+"(If target is DROP, ACCEPT, RETURN or nothing)\n");
+}
+
+static struct xtables_target standard_target = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "standard",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(int)),
+	.userspacesize	= XT_ALIGN(sizeof(int)),
+	.help		= standard_help,
+};
+
+void _init(void)
+{
+	xtables_register_target(&standard_target);
+}
diff --git a/extensions/libxt_standard.t b/extensions/libxt_standard.t
new file mode 100644
index 0000000..4313f7b
--- /dev/null
+++ b/extensions/libxt_standard.t
@@ -0,0 +1,11 @@
+:INPUT,FORWARD,OUTPUT
+-s 127.0.0.1/32 -d 0.0.0.0/8 -j DROP;=;OK
+! -s 0.0.0.0 -j ACCEPT;! -s 0.0.0.0/32 -j ACCEPT;OK
+! -d 0.0.0.0/32 -j ACCEPT;=;OK
+-s 0.0.0.0/24 -j RETURN;=;OK
+-p tcp -j ACCEPT;=;OK
+! -p udp -j ACCEPT;=;OK
+-j DROP;=;OK
+-j ACCEPT;=;OK
+-j RETURN;=;OK
+! -p 0 -j ACCEPT;=;FAIL
diff --git a/extensions/libxt_state.man b/extensions/libxt_state.man
new file mode 100644
index 0000000..ec096ca
--- /dev/null
+++ b/extensions/libxt_state.man
@@ -0,0 +1,8 @@
+The "state" extension is a subset of the "conntrack" module.
+"state" allows access to the connection tracking state for this packet.
+.TP
+[\fB!\fP] \fB\-\-state\fP \fIstate\fP
+Where state is a comma separated list of the connection states to match. Only a
+subset of the states unterstood by "conntrack" are recognized: \fBINVALID\fP,
+\fBESTABLISHED\fP, \fBNEW\fP, \fBRELATED\fP or \fBUNTRACKED\fP. For their
+description, see the "conntrack" heading in this manpage.
diff --git a/extensions/libxt_state.t b/extensions/libxt_state.t
new file mode 100644
index 0000000..8e4bce3
--- /dev/null
+++ b/extensions/libxt_state.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m state --state INVALID;=;OK
+-m state --state NEW,RELATED;=;OK
+-m state --state UNTRACKED;=;OK
+-m state wrong;;FAIL
+-m state;;FAIL
diff --git a/extensions/libxt_statistic.c b/extensions/libxt_statistic.c
new file mode 100644
index 0000000..4f3341a
--- /dev/null
+++ b/extensions/libxt_statistic.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2006-2013 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_statistic.h>
+
+enum {
+	O_MODE = 0,
+	O_PROBABILITY,
+	O_EVERY,
+	O_PACKET,
+	F_PROBABILITY = 1 << O_PROBABILITY,
+	F_EVERY       = 1 << O_EVERY,
+	F_PACKET      = 1 << O_PACKET,
+};
+
+static void statistic_help(void)
+{
+	printf(
+"statistic match options:\n"
+" --mode mode                    Match mode (random, nth)\n"
+" random mode:\n"
+"[!] --probability p		 Probability\n"
+" nth mode:\n"
+"[!] --every n			 Match every nth packet\n"
+" --packet p			 Initial counter value (0 <= p <= n-1, default 0)\n");
+}
+
+#define s struct xt_statistic_info
+static const struct xt_option_entry statistic_opts[] = {
+	{.name = "mode", .id = O_MODE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND},
+	{.name = "probability", .id = O_PROBABILITY, .type = XTTYPE_DOUBLE,
+	 .flags = XTOPT_INVERT, .min = 0, .max = 1,
+	 .excl = F_EVERY | F_PACKET},
+	{.name = "every", .id = O_EVERY, .type = XTTYPE_UINT32, .min = 1,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, u.nth.every),
+	 .excl = F_PROBABILITY, .also = F_PACKET},
+	{.name = "packet", .id = O_PACKET, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, u.nth.packet),
+	 .excl = F_PROBABILITY, .also = F_EVERY},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void statistic_parse(struct xt_option_call *cb)
+{
+	struct xt_statistic_info *info = cb->data;
+
+	if (cb->invert)
+		info->flags |= XT_STATISTIC_INVERT;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_MODE:
+		if (strcmp(cb->arg, "random") == 0)
+			info->mode = XT_STATISTIC_MODE_RANDOM;
+		else if (strcmp(cb->arg, "nth") == 0)
+			info->mode = XT_STATISTIC_MODE_NTH;
+		else
+			xtables_error(PARAMETER_PROBLEM, "Bad mode \"%s\"",
+				cb->arg);
+		break;
+	case O_PROBABILITY:
+		info->u.random.probability = lround(0x80000000 * cb->val.dbl);
+		break;
+	case O_EVERY:
+		--info->u.nth.every;
+		break;
+	}
+}
+
+static void statistic_check(struct xt_fcheck_call *cb)
+{
+	struct xt_statistic_info *info = cb->data;
+
+	if (info->mode == XT_STATISTIC_MODE_RANDOM &&
+	    !(cb->xflags & F_PROBABILITY))
+		xtables_error(PARAMETER_PROBLEM,
+			"--probability must be specified when using "
+			"random mode");
+	if (info->mode == XT_STATISTIC_MODE_NTH &&
+	    !(cb->xflags & (F_EVERY | F_PACKET)))
+		xtables_error(PARAMETER_PROBLEM,
+			"--every and --packet must be specified when "
+			"using nth mode");
+
+	/* at this point, info->u.nth.every have been decreased. */
+	if (info->u.nth.packet > info->u.nth.every)
+		xtables_error(PARAMETER_PROBLEM,
+			  "the --packet p must be 0 <= p <= n-1");
+
+	info->u.nth.count = info->u.nth.every - info->u.nth.packet;
+}
+
+static void print_match(const struct xt_statistic_info *info, char *prefix)
+{
+	switch (info->mode) {
+	case XT_STATISTIC_MODE_RANDOM:
+		printf(" %smode random%s %sprobability %.11f", prefix,
+		       (info->flags & XT_STATISTIC_INVERT) ? " !" : "",
+		       prefix,
+		       1.0 * info->u.random.probability / 0x80000000);
+		break;
+	case XT_STATISTIC_MODE_NTH:
+		printf(" %smode nth%s %severy %u", prefix,
+		       (info->flags & XT_STATISTIC_INVERT) ? " !" : "",
+		       prefix,
+		       info->u.nth.every + 1);
+		if (info->u.nth.packet || *prefix)
+			printf(" %spacket %u", prefix, info->u.nth.packet);
+		break;
+	}
+}
+
+static void
+statistic_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_statistic_info *info = (const void *)match->data;
+
+	printf(" statistic");
+	print_match(info, "");
+}
+
+static void statistic_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_statistic_info *info = (const void *)match->data;
+
+	print_match(info, "--");
+}
+
+static int statistic_xlate(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_statistic_info *info =
+		(struct xt_statistic_info *)params->match->data;
+
+	switch (info->mode) {
+	case XT_STATISTIC_MODE_RANDOM:
+		return 0;
+	case XT_STATISTIC_MODE_NTH:
+		xt_xlate_add(xl, "numgen inc mod %u %s%u",
+			     info->u.nth.every + 1,
+			     info->flags & XT_STATISTIC_INVERT ? "!= " : "",
+			     info->u.nth.packet);
+		break;
+	}
+
+	return 1;
+}
+
+static struct xtables_match statistic_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "statistic",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_statistic_info)),
+	.userspacesize	= offsetof(struct xt_statistic_info, u.nth.count),
+	.help		= statistic_help,
+	.x6_parse	= statistic_parse,
+	.x6_fcheck	= statistic_check,
+	.print		= statistic_print,
+	.save		= statistic_save,
+	.x6_options	= statistic_opts,
+	.xlate		= statistic_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&statistic_match);
+}
diff --git a/extensions/libxt_statistic.man b/extensions/libxt_statistic.man
new file mode 100644
index 0000000..47182bf
--- /dev/null
+++ b/extensions/libxt_statistic.man
@@ -0,0 +1,29 @@
+This module matches packets based on some statistic condition.
+It supports two distinct modes settable with the 
+\fB\-\-mode\fP
+option.
+.PP
+Supported options:
+.TP
+\fB\-\-mode\fP \fImode\fP
+Set the matching mode of the matching rule, supported modes are
+.B random
+and
+.B nth. 
+.TP
+[\fB!\fP] \fB\-\-probability\fP \fIp\fP
+Set the probability for a packet to be randomly matched. It only works with the
+\fBrandom\fP mode. \fIp\fP must be within 0.0 and 1.0. The supported
+granularity is in 1/2147483648th increments.
+.TP
+[\fB!\fP] \fB\-\-every\fP \fIn\fP
+Match one packet every nth packet. It works only with the
+.B nth
+mode (see also the 
+\fB\-\-packet\fP
+option).
+.TP
+\fB\-\-packet\fP \fIp\fP
+Set the initial counter value (0 <= p <= n\-1, default 0) for the
+.B nth 
+mode.
diff --git a/extensions/libxt_statistic.t b/extensions/libxt_statistic.t
new file mode 100644
index 0000000..bb6673d
--- /dev/null
+++ b/extensions/libxt_statistic.t
@@ -0,0 +1,8 @@
+:INPUT,FORWARD,OUTPUT
+-m statistic;;FAIL
+-m statistic --mode random ! --probability 0.50000000000;=;OK
+-m statistic --mode random ! --probability 1.1;;FAIL
+-m statistic --probability 1;;FAIL
+-m statistic --mode nth ! --every 5 --packet 2;=;OK
+-m statistic --mode nth ! --every 5;;FAIL
+-m statistic --mode nth ! --every 5 --packet 5;;FAIL
diff --git a/extensions/libxt_statistic.txlate b/extensions/libxt_statistic.txlate
new file mode 100644
index 0000000..4c3dea4
--- /dev/null
+++ b/extensions/libxt_statistic.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A OUTPUT -m statistic --mode nth --every 10 --packet 1
+nft add rule ip filter OUTPUT numgen inc mod 10 1 counter
+
+iptables-translate -A OUTPUT -m statistic --mode nth ! --every 10 --packet 5
+nft add rule ip filter OUTPUT numgen inc mod 10 != 5 counter
+
+iptables-translate -A OUTPUT -m statistic --mode random --probability 0.1
+nft # -A OUTPUT -m statistic --mode random --probability 0.1
diff --git a/extensions/libxt_string.c b/extensions/libxt_string.c
new file mode 100644
index 0000000..7c6366c
--- /dev/null
+++ b/extensions/libxt_string.c
@@ -0,0 +1,339 @@
+/* Shared library add-on to iptables to add string matching support. 
+ * 
+ * Copyright (C) 2000 Emmanuel Roger  <winfield@freegates.be>
+ *
+ * 2005-08-05 Pablo Neira Ayuso <pablo@eurodev.net>
+ * 	- reimplemented to use new string matching iptables match
+ * 	- add functionality to match packets by using window offsets
+ * 	- add functionality to select the string matching algorithm
+ *
+ * ChangeLog
+ *     29.12.2003: Michael Rash <mbr@cipherdyne.org>
+ *             Fixed iptables save/restore for ascii strings
+ *             that contain space chars, and hex strings that
+ *             contain embedded NULL chars.  Updated to print
+ *             strings in hex mode if any non-printable char
+ *             is contained within the string.
+ *
+ *     27.01.2001: Gianni Tedesco <gianni@ecsc.co.uk>
+ *             Changed --tos to --string in save(). Also
+ *             updated to work with slightly modified
+ *             ipt_string_info.
+ */
+#define _GNU_SOURCE 1 /* strnlen for older glibcs */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_string.h>
+
+enum {
+	O_FROM = 0,
+	O_TO,
+	O_ALGO,
+	O_ICASE,
+	O_STRING,
+	O_HEX_STRING,
+	F_STRING     = 1 << O_STRING,
+	F_HEX_STRING = 1 << O_HEX_STRING,
+	F_OP_ANY     = F_STRING | F_HEX_STRING,
+};
+
+static void string_help(void)
+{
+	printf(
+"string match options:\n"
+"--from                       Offset to start searching from\n"
+"--to                         Offset to stop searching\n"
+"--algo                       Algorithm\n"
+"--icase                      Ignore case (default: 0)\n"
+"[!] --string string          Match a string in a packet\n"
+"[!] --hex-string string      Match a hex string in a packet\n");
+}
+
+#define s struct xt_string_info
+static const struct xt_option_entry string_opts[] = {
+	{.name = "from", .id = O_FROM, .type = XTTYPE_UINT16,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, from_offset)},
+	{.name = "to", .id = O_TO, .type = XTTYPE_UINT16,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, to_offset)},
+	{.name = "algo", .id = O_ALGO, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, algo)},
+	{.name = "string", .id = O_STRING, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT, .excl = F_HEX_STRING},
+	{.name = "hex-string", .id = O_HEX_STRING, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT, .excl = F_STRING},
+	{.name = "icase", .id = O_ICASE, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void string_init(struct xt_entry_match *m)
+{
+	struct xt_string_info *i = (struct xt_string_info *) m->data;
+
+	i->to_offset = UINT16_MAX;
+}
+
+static void
+parse_string(const char *s, struct xt_string_info *info)
+{	
+	/* xt_string does not need \0 at the end of the pattern */
+	if (strlen(s) <= XT_STRING_MAX_PATTERN_SIZE) {
+		strncpy(info->pattern, s, XT_STRING_MAX_PATTERN_SIZE);
+		info->patlen = strnlen(s, XT_STRING_MAX_PATTERN_SIZE);
+		return;
+	}
+	xtables_error(PARAMETER_PROBLEM, "STRING too long \"%s\"", s);
+}
+
+static void
+parse_hex_string(const char *s, struct xt_string_info *info)
+{
+	int i=0, slen, sindex=0, schar;
+	short hex_f = 0, literal_f = 0;
+	char hextmp[3];
+
+	slen = strlen(s);
+
+	if (slen == 0) {
+		xtables_error(PARAMETER_PROBLEM,
+			"STRING must contain at least one char");
+	}
+
+	while (i < slen) {
+		if (sindex >= XT_STRING_MAX_PATTERN_SIZE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "STRING too long \"%s\"", s);
+		if (s[i] == '\\' && !hex_f) {
+			literal_f = 1;
+		} else if (s[i] == '\\') {
+			xtables_error(PARAMETER_PROBLEM,
+				"Cannot include literals in hex data");
+		} else if (s[i] == '|') {
+			if (hex_f)
+				hex_f = 0;
+			else {
+				hex_f = 1;
+				/* get past any initial whitespace just after the '|' */
+				while (s[i+1] == ' ')
+					i++;
+			}
+			if (i+1 >= slen)
+				break;
+			else
+				i++;  /* advance to the next character */
+		}
+
+		if (literal_f) {
+			if (i+1 >= slen) {
+				xtables_error(PARAMETER_PROBLEM,
+					"Bad literal placement at end of string");
+			}
+			info->pattern[sindex] = s[i+1];
+			i += 2;  /* skip over literal char */
+			literal_f = 0;
+		} else if (hex_f) {
+			if (i+1 >= slen) {
+				xtables_error(PARAMETER_PROBLEM,
+					"Odd number of hex digits");
+			}
+			if (i+2 >= slen) {
+				/* must end with a "|" */
+				xtables_error(PARAMETER_PROBLEM, "Invalid hex block");
+			}
+			if (! isxdigit(s[i])) /* check for valid hex char */
+				xtables_error(PARAMETER_PROBLEM, "Invalid hex char '%c'", s[i]);
+			if (! isxdigit(s[i+1])) /* check for valid hex char */
+				xtables_error(PARAMETER_PROBLEM, "Invalid hex char '%c'", s[i+1]);
+			hextmp[0] = s[i];
+			hextmp[1] = s[i+1];
+			hextmp[2] = '\0';
+			if (! sscanf(hextmp, "%x", &schar))
+				xtables_error(PARAMETER_PROBLEM,
+					"Invalid hex char `%c'", s[i]);
+			info->pattern[sindex] = (char) schar;
+			if (s[i+2] == ' ')
+				i += 3;  /* spaces included in the hex block */
+			else
+				i += 2;
+		} else {  /* the char is not part of hex data, so just copy */
+			info->pattern[sindex] = s[i];
+			i++;
+		}
+		sindex++;
+	}
+	info->patlen = sindex;
+}
+
+static void string_parse(struct xt_option_call *cb)
+{
+	struct xt_string_info *stringinfo = cb->data;
+	const unsigned int revision = (*cb->match)->u.user.revision;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_STRING:
+		parse_string(cb->arg, stringinfo);
+		if (cb->invert) {
+			if (revision == 0)
+				stringinfo->u.v0.invert = 1;
+			else
+				stringinfo->u.v1.flags |= XT_STRING_FLAG_INVERT;
+		}
+		break;
+	case O_HEX_STRING:
+		parse_hex_string(cb->arg, stringinfo);  /* sets length */
+		if (cb->invert) {
+			if (revision == 0)
+				stringinfo->u.v0.invert = 1;
+			else
+				stringinfo->u.v1.flags |= XT_STRING_FLAG_INVERT;
+		}
+		break;
+	case O_ICASE:
+		if (revision == 0)
+			xtables_error(VERSION_PROBLEM,
+				   "Kernel doesn't support --icase");
+
+		stringinfo->u.v1.flags |= XT_STRING_FLAG_IGNORECASE;
+		break;
+	}
+}
+
+static void string_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & F_OP_ANY))
+		xtables_error(PARAMETER_PROBLEM,
+			   "STRING match: You must specify `--string' or "
+			   "`--hex-string'");
+}
+
+/* Test to see if the string contains non-printable chars or quotes */
+static unsigned short int
+is_hex_string(const char *str, const unsigned short int len)
+{
+	unsigned int i;
+	for (i=0; i < len; i++)
+		if (! isprint(str[i]))
+			return 1;  /* string contains at least one non-printable char */
+	/* use hex output if the last char is a "\" */
+	if (str[len-1] == '\\')
+		return 1;
+	return 0;
+}
+
+/* Print string with "|" chars included as one would pass to --hex-string */
+static void
+print_hex_string(const char *str, const unsigned short int len)
+{
+	unsigned int i;
+	/* start hex block */
+	printf(" \"|");
+	for (i=0; i < len; i++)
+		printf("%02x", (unsigned char)str[i]);
+	/* close hex block */
+	printf("|\"");
+}
+
+static void
+print_string(const char *str, const unsigned short int len)
+{
+	unsigned int i;
+	printf(" \"");
+	for (i=0; i < len; i++) {
+		if (str[i] == '\"' || str[i] == '\\')
+			putchar('\\');
+		printf("%c", (unsigned char) str[i]);
+	}
+	printf("\"");  /* closing quote */
+}
+
+static void
+string_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_string_info *info =
+	    (const struct xt_string_info*) match->data;
+	const int revision = match->u.user.revision;
+	int invert = (revision == 0 ? info->u.v0.invert :
+				    info->u.v1.flags & XT_STRING_FLAG_INVERT);
+
+	if (is_hex_string(info->pattern, info->patlen)) {
+		printf(" STRING match %s", invert ? "!" : "");
+		print_hex_string(info->pattern, info->patlen);
+	} else {
+		printf(" STRING match %s", invert ? "!" : "");
+		print_string(info->pattern, info->patlen);
+	}
+	printf(" ALGO name %s", info->algo);
+	if (info->from_offset != 0)
+		printf(" FROM %u", info->from_offset);
+	if (info->to_offset != 0)
+		printf(" TO %u", info->to_offset);
+	if (revision > 0 && info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
+		printf(" ICASE");
+}
+
+static void string_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_string_info *info =
+	    (const struct xt_string_info*) match->data;
+	const int revision = match->u.user.revision;
+	int invert = (revision == 0 ? info->u.v0.invert :
+				    info->u.v1.flags & XT_STRING_FLAG_INVERT);
+
+	if (is_hex_string(info->pattern, info->patlen)) {
+		printf("%s --hex-string", (invert) ? " !" : "");
+		print_hex_string(info->pattern, info->patlen);
+	} else {
+		printf("%s --string", (invert) ? " !": "");
+		print_string(info->pattern, info->patlen);
+	}
+	printf(" --algo %s", info->algo);
+	if (info->from_offset != 0)
+		printf(" --from %u", info->from_offset);
+	if (info->to_offset != 0)
+		printf(" --to %u", info->to_offset);
+	if (revision > 0 && info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
+		printf(" --icase");
+}
+
+
+static struct xtables_match string_mt_reg[] = {
+	{
+		.name          = "string",
+		.revision      = 0,
+		.family        = NFPROTO_UNSPEC,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_string_info)),
+		.userspacesize = offsetof(struct xt_string_info, config),
+		.help          = string_help,
+		.init          = string_init,
+		.print         = string_print,
+		.save          = string_save,
+		.x6_parse      = string_parse,
+		.x6_fcheck     = string_check,
+		.x6_options    = string_opts,
+	},
+	{
+		.name          = "string",
+		.revision      = 1,
+		.family        = NFPROTO_UNSPEC,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_string_info)),
+		.userspacesize = offsetof(struct xt_string_info, config),
+		.help          = string_help,
+		.init          = string_init,
+		.print         = string_print,
+		.save          = string_save,
+		.x6_parse      = string_parse,
+		.x6_fcheck     = string_check,
+		.x6_options    = string_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(string_mt_reg, ARRAY_SIZE(string_mt_reg));
+}
diff --git a/extensions/libxt_string.man b/extensions/libxt_string.man
new file mode 100644
index 0000000..5f1a993
--- /dev/null
+++ b/extensions/libxt_string.man
@@ -0,0 +1,31 @@
+This module matches a given string by using some pattern matching strategy. It requires a linux kernel >= 2.6.14.
+.TP
+\fB\-\-algo\fP {\fBbm\fP|\fBkmp\fP}
+Select the pattern matching strategy. (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)
+.TP
+\fB\-\-from\fP \fIoffset\fP
+Set the offset from which it starts looking for any matching. If not passed, default is 0.
+.TP
+\fB\-\-to\fP \fIoffset\fP
+Set the offset up to which should be scanned. That is, byte \fIoffset\fP-1
+(counting from 0) is the last one that is scanned.
+If not passed, default is the packet size.
+.TP
+[\fB!\fP] \fB\-\-string\fP \fIpattern\fP
+Matches the given pattern.
+.TP
+[\fB!\fP] \fB\-\-hex\-string\fP \fIpattern\fP
+Matches the given pattern in hex notation.
+.TP
+\fB\-\-icase\fP
+Ignore case when searching.
+.TP
+Examples:
+.IP
+# The string pattern can be used for simple text characters.
+.br
+iptables \-A INPUT \-p tcp \-\-dport 80 \-m string \-\-algo bm \-\-string 'GET /index.html' \-j LOG
+.IP
+# The hex string pattern can be used for non-printable characters, like |0D 0A| or |0D0A|.
+.br
+iptables \-p udp \-\-dport 53 \-m string \-\-algo bm \-\-from 40 \-\-to 57 \-\-hex\-string '|03|www|09|netfilter|03|org|00|'
diff --git a/extensions/libxt_string.t b/extensions/libxt_string.t
new file mode 100644
index 0000000..d68f099
--- /dev/null
+++ b/extensions/libxt_string.t
@@ -0,0 +1,18 @@
+:INPUT,FORWARD,OUTPUT
+# ERROR: cannot find: iptables -I INPUT -m string --algo bm --string "test"
+# -m string --algo bm --string "test";=;OK
+# ERROR: cannot find: iptables -I INPUT -m string --algo kmp --string "test")
+# -m string --algo kmp --string "test";=;OK
+# ERROR: cannot find: iptables -I INPUT -m string --algo kmp ! --string "test"
+# -m string --algo kmp ! --string "test";=;OK
+# cannot find: iptables -I INPUT -m string --algo bm --string "xxxxxxxxxxx" ....]
+# -m string --algo bm --string "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";=;OK
+# ERROR: cannot load: iptables -A INPUT -m string --algo bm --string "xxxx"
+# -m string --algo bm --string "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";=;OK
+# ERROR: cannot load: iptables -A INPUT -m string --algo bm --hexstring "|0a0a0a0a|"
+# -m string --algo bm --hexstring "|0a0a0a0a|";=;OK
+# ERROR: cannot find: iptables -I INPUT -m string --algo bm --from 0 --to 65535 --string "test"
+# -m string --algo bm --from 0 --to 65535 --string "test";=;OK
+-m string --algo wrong;;FAIL
+-m string --algo bm;;FAIL
+-m string;;FAIL
diff --git a/extensions/libxt_tcp.c b/extensions/libxt_tcp.c
new file mode 100644
index 0000000..58f3c0a
--- /dev/null
+++ b/extensions/libxt_tcp.c
@@ -0,0 +1,467 @@
+/* Shared library add-on to iptables to add TCP support. */
+#include <stdbool.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_tcpudp.h>
+
+static void tcp_help(void)
+{
+	printf(
+"tcp match options:\n"
+"[!] --tcp-flags mask comp	match when TCP flags & mask == comp\n"
+"				(Flags: SYN ACK FIN RST URG PSH ALL NONE)\n"
+"[!] --syn			match when only SYN flag set\n"
+"				(equivalent to --tcp-flags SYN,RST,ACK,FIN SYN)\n"
+"[!] --source-port port[:port]\n"
+" --sport ...\n"
+"				match source port(s)\n"
+"[!] --destination-port port[:port]\n"
+" --dport ...\n"
+"				match destination port(s)\n"
+"[!] --tcp-option number        match if TCP option set\n");
+}
+
+static const struct option tcp_opts[] = {
+	{.name = "source-port",      .has_arg = true,  .val = '1'},
+	{.name = "sport",            .has_arg = true,  .val = '1'}, /* synonym */
+	{.name = "destination-port", .has_arg = true,  .val = '2'},
+	{.name = "dport",            .has_arg = true,  .val = '2'}, /* synonym */
+	{.name = "syn",              .has_arg = false, .val = '3'},
+	{.name = "tcp-flags",        .has_arg = true,  .val = '4'},
+	{.name = "tcp-option",       .has_arg = true,  .val = '5'},
+	XT_GETOPT_TABLEEND,
+};
+
+static void
+parse_tcp_ports(const char *portstring, uint16_t *ports)
+{
+	char *buffer;
+	char *cp;
+
+	buffer = strdup(portstring);
+	if ((cp = strchr(buffer, ':')) == NULL)
+		ports[0] = ports[1] = xtables_parse_port(buffer, "tcp");
+	else {
+		*cp = '\0';
+		cp++;
+
+		ports[0] = buffer[0] ? xtables_parse_port(buffer, "tcp") : 0;
+		ports[1] = cp[0] ? xtables_parse_port(cp, "tcp") : 0xFFFF;
+
+		if (ports[0] > ports[1])
+			xtables_error(PARAMETER_PROBLEM,
+				   "invalid portrange (min > max)");
+	}
+	free(buffer);
+}
+
+struct tcp_flag_names {
+	const char *name;
+	unsigned int flag;
+};
+
+static const struct tcp_flag_names tcp_flag_names[]
+= { { "FIN", 0x01 },
+    { "SYN", 0x02 },
+    { "RST", 0x04 },
+    { "PSH", 0x08 },
+    { "ACK", 0x10 },
+    { "URG", 0x20 },
+    { "ALL", 0x3F },
+    { "NONE", 0 },
+};
+
+static unsigned int
+parse_tcp_flag(const char *flags)
+{
+	unsigned int ret = 0;
+	char *ptr;
+	char *buffer;
+
+	buffer = strdup(flags);
+
+	for (ptr = strtok(buffer, ","); ptr; ptr = strtok(NULL, ",")) {
+		unsigned int i;
+		for (i = 0; i < ARRAY_SIZE(tcp_flag_names); ++i)
+			if (strcasecmp(tcp_flag_names[i].name, ptr) == 0) {
+				ret |= tcp_flag_names[i].flag;
+				break;
+			}
+		if (i == ARRAY_SIZE(tcp_flag_names))
+			xtables_error(PARAMETER_PROBLEM,
+				   "Unknown TCP flag `%s'", ptr);
+	}
+
+	free(buffer);
+	return ret;
+}
+
+static void
+parse_tcp_flags(struct xt_tcp *tcpinfo,
+		const char *mask,
+		const char *cmp,
+		int invert)
+{
+	tcpinfo->flg_mask = parse_tcp_flag(mask);
+	tcpinfo->flg_cmp = parse_tcp_flag(cmp);
+
+	if (invert)
+		tcpinfo->invflags |= XT_TCP_INV_FLAGS;
+}
+
+static void
+parse_tcp_option(const char *option, uint8_t *result)
+{
+	unsigned int ret;
+
+	if (!xtables_strtoui(option, NULL, &ret, 1, UINT8_MAX))
+		xtables_error(PARAMETER_PROBLEM, "Bad TCP option \"%s\"", option);
+
+	*result = ret;
+}
+
+static void tcp_init(struct xt_entry_match *m)
+{
+	struct xt_tcp *tcpinfo = (struct xt_tcp *)m->data;
+
+	tcpinfo->spts[1] = tcpinfo->dpts[1] = 0xFFFF;
+}
+
+#define TCP_SRC_PORTS 0x01
+#define TCP_DST_PORTS 0x02
+#define TCP_FLAGS 0x04
+#define TCP_OPTION	0x08
+
+static int
+tcp_parse(int c, char **argv, int invert, unsigned int *flags,
+          const void *entry, struct xt_entry_match **match)
+{
+	struct xt_tcp *tcpinfo = (struct xt_tcp *)(*match)->data;
+
+	switch (c) {
+	case '1':
+		if (*flags & TCP_SRC_PORTS)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Only one `--source-port' allowed");
+		parse_tcp_ports(optarg, tcpinfo->spts);
+		if (invert)
+			tcpinfo->invflags |= XT_TCP_INV_SRCPT;
+		*flags |= TCP_SRC_PORTS;
+		break;
+
+	case '2':
+		if (*flags & TCP_DST_PORTS)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Only one `--destination-port' allowed");
+		parse_tcp_ports(optarg, tcpinfo->dpts);
+		if (invert)
+			tcpinfo->invflags |= XT_TCP_INV_DSTPT;
+		*flags |= TCP_DST_PORTS;
+		break;
+
+	case '3':
+		if (*flags & TCP_FLAGS)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Only one of `--syn' or `--tcp-flags' "
+				   " allowed");
+		parse_tcp_flags(tcpinfo, "SYN,RST,ACK,FIN", "SYN", invert);
+		*flags |= TCP_FLAGS;
+		break;
+
+	case '4':
+		if (*flags & TCP_FLAGS)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Only one of `--syn' or `--tcp-flags' "
+				   " allowed");
+		if (!argv[optind]
+		    || argv[optind][0] == '-' || argv[optind][0] == '!')
+			xtables_error(PARAMETER_PROBLEM,
+				   "--tcp-flags requires two args.");
+
+		parse_tcp_flags(tcpinfo, optarg, argv[optind],
+				invert);
+		optind++;
+		*flags |= TCP_FLAGS;
+		break;
+
+	case '5':
+		if (*flags & TCP_OPTION)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Only one `--tcp-option' allowed");
+		parse_tcp_option(optarg, &tcpinfo->option);
+		if (invert)
+			tcpinfo->invflags |= XT_TCP_INV_OPTION;
+		*flags |= TCP_OPTION;
+		break;
+	}
+
+	return 1;
+}
+
+static const char *
+port_to_service(int port)
+{
+	const struct servent *service;
+
+	if ((service = getservbyport(htons(port), "tcp")))
+		return service->s_name;
+
+	return NULL;
+}
+
+static void
+print_port(uint16_t port, int numeric)
+{
+	const char *service;
+
+	if (numeric || (service = port_to_service(port)) == NULL)
+		printf("%u", port);
+	else
+		printf("%s", service);
+}
+
+static void
+print_ports(const char *name, uint16_t min, uint16_t max,
+	    int invert, int numeric)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFF || invert) {
+		printf(" %s", name);
+		if (min == max) {
+			printf(":%s", inv);
+			print_port(min, numeric);
+		} else {
+			printf("s:%s", inv);
+			print_port(min, numeric);
+			printf(":");
+			print_port(max, numeric);
+		}
+	}
+}
+
+static void
+print_option(uint8_t option, int invert, int numeric)
+{
+	if (option || invert)
+		printf(" option=%s%u", invert ? "!" : "", option);
+}
+
+static void
+print_tcpf(uint8_t flags)
+{
+	int have_flag = 0;
+
+	while (flags) {
+		unsigned int i;
+
+		for (i = 0; (flags & tcp_flag_names[i].flag) == 0; i++);
+
+		if (have_flag)
+			printf(",");
+		printf("%s", tcp_flag_names[i].name);
+		have_flag = 1;
+
+		flags &= ~tcp_flag_names[i].flag;
+	}
+
+	if (!have_flag)
+		printf("NONE");
+}
+
+static void
+print_flags(uint8_t mask, uint8_t cmp, int invert, int numeric)
+{
+	if (mask || invert) {
+		printf(" flags:%s", invert ? "!" : "");
+		if (numeric)
+			printf("0x%02X/0x%02X", mask, cmp);
+		else {
+			print_tcpf(mask);
+			printf("/");
+			print_tcpf(cmp);
+		}
+	}
+}
+
+static void
+tcp_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_tcp *tcp = (struct xt_tcp *)match->data;
+
+	printf(" tcp");
+	print_ports("spt", tcp->spts[0], tcp->spts[1],
+		    tcp->invflags & XT_TCP_INV_SRCPT,
+		    numeric);
+	print_ports("dpt", tcp->dpts[0], tcp->dpts[1],
+		    tcp->invflags & XT_TCP_INV_DSTPT,
+		    numeric);
+	print_option(tcp->option,
+		     tcp->invflags & XT_TCP_INV_OPTION,
+		     numeric);
+	print_flags(tcp->flg_mask, tcp->flg_cmp,
+		    tcp->invflags & XT_TCP_INV_FLAGS,
+		    numeric);
+	if (tcp->invflags & ~XT_TCP_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       tcp->invflags & ~XT_TCP_INV_MASK);
+}
+
+static void tcp_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_tcp *tcpinfo = (struct xt_tcp *)match->data;
+
+	if (tcpinfo->spts[0] != 0
+	    || tcpinfo->spts[1] != 0xFFFF) {
+		if (tcpinfo->invflags & XT_TCP_INV_SRCPT)
+			printf(" !");
+		if (tcpinfo->spts[0]
+		    != tcpinfo->spts[1])
+			printf(" --sport %u:%u",
+			       tcpinfo->spts[0],
+			       tcpinfo->spts[1]);
+		else
+			printf(" --sport %u",
+			       tcpinfo->spts[0]);
+	}
+
+	if (tcpinfo->dpts[0] != 0
+	    || tcpinfo->dpts[1] != 0xFFFF) {
+		if (tcpinfo->invflags & XT_TCP_INV_DSTPT)
+			printf(" !");
+		if (tcpinfo->dpts[0]
+		    != tcpinfo->dpts[1])
+			printf(" --dport %u:%u",
+			       tcpinfo->dpts[0],
+			       tcpinfo->dpts[1]);
+		else
+			printf(" --dport %u",
+			       tcpinfo->dpts[0]);
+	}
+
+	if (tcpinfo->option
+	    || (tcpinfo->invflags & XT_TCP_INV_OPTION)) {
+		if (tcpinfo->invflags & XT_TCP_INV_OPTION)
+			printf(" !");
+		printf(" --tcp-option %u", tcpinfo->option);
+	}
+
+	if (tcpinfo->flg_mask
+	    || (tcpinfo->invflags & XT_TCP_INV_FLAGS)) {
+		if (tcpinfo->invflags & XT_TCP_INV_FLAGS)
+			printf(" !");
+		printf(" --tcp-flags ");
+		print_tcpf(tcpinfo->flg_mask);
+		printf(" ");
+		print_tcpf(tcpinfo->flg_cmp);
+	}
+}
+
+static const struct tcp_flag_names tcp_flag_names_xlate[] = {
+	{ "fin", 0x01 },
+	{ "syn", 0x02 },
+	{ "rst", 0x04 },
+	{ "psh", 0x08 },
+	{ "ack", 0x10 },
+	{ "urg", 0x20 },
+};
+
+static void print_tcp_xlate(struct xt_xlate *xl, uint8_t flags)
+{
+	int have_flag = 0;
+
+	while (flags) {
+		unsigned int i;
+
+		for (i = 0; (flags & tcp_flag_names_xlate[i].flag) == 0; i++);
+
+		if (have_flag)
+			xt_xlate_add(xl, "|");
+
+		xt_xlate_add(xl, "%s", tcp_flag_names_xlate[i].name);
+		have_flag = 1;
+
+		flags &= ~tcp_flag_names_xlate[i].flag;
+	}
+
+	if (!have_flag)
+		xt_xlate_add(xl, "0x0");
+}
+
+static int tcp_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_tcp *tcpinfo =
+		(const struct xt_tcp *)params->match->data;
+	char *space= "";
+
+	if (tcpinfo->spts[0] != 0 || tcpinfo->spts[1] != 0xffff) {
+		if (tcpinfo->spts[0] != tcpinfo->spts[1]) {
+			xt_xlate_add(xl, "tcp sport %s%u-%u",
+				   tcpinfo->invflags & XT_TCP_INV_SRCPT ?
+					"!= " : "",
+				   tcpinfo->spts[0], tcpinfo->spts[1]);
+		} else {
+			xt_xlate_add(xl, "tcp sport %s%u",
+				   tcpinfo->invflags & XT_TCP_INV_SRCPT ?
+					"!= " : "",
+				   tcpinfo->spts[0]);
+		}
+		space = " ";
+	}
+
+	if (tcpinfo->dpts[0] != 0 || tcpinfo->dpts[1] != 0xffff) {
+		if (tcpinfo->dpts[0] != tcpinfo->dpts[1]) {
+			xt_xlate_add(xl, "%stcp dport %s%u-%u", space,
+				   tcpinfo->invflags & XT_TCP_INV_DSTPT ?
+					"!= " : "",
+				   tcpinfo->dpts[0], tcpinfo->dpts[1]);
+		} else {
+			xt_xlate_add(xl, "%stcp dport %s%u", space,
+				   tcpinfo->invflags & XT_TCP_INV_DSTPT ?
+					"!= " : "",
+				   tcpinfo->dpts[0]);
+		}
+		space = " ";
+	}
+
+	/* XXX not yet implemented */
+	if (tcpinfo->option || (tcpinfo->invflags & XT_TCP_INV_OPTION))
+		return 0;
+
+	if (tcpinfo->flg_mask || (tcpinfo->invflags & XT_TCP_INV_FLAGS)) {
+		xt_xlate_add(xl, "%stcp flags & (", space);
+		print_tcp_xlate(xl, tcpinfo->flg_mask);
+		xt_xlate_add(xl, ") %s ",
+			   tcpinfo->invflags & XT_TCP_INV_FLAGS ? "!=": "==");
+		print_tcp_xlate(xl, tcpinfo->flg_cmp);
+	}
+
+	return 1;
+}
+
+static struct xtables_match tcp_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "tcp",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_tcp)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_tcp)),
+	.help		= tcp_help,
+	.init		= tcp_init,
+	.parse		= tcp_parse,
+	.print		= tcp_print,
+	.save		= tcp_save,
+	.extra_opts	= tcp_opts,
+	.xlate		= tcp_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&tcp_match);
+}
diff --git a/extensions/libxt_tcp.man b/extensions/libxt_tcp.man
new file mode 100644
index 0000000..8019461
--- /dev/null
+++ b/extensions/libxt_tcp.man
@@ -0,0 +1,43 @@
+These extensions can be used if `\-\-protocol tcp' is specified. It
+provides the following options:
+.TP
+[\fB!\fP] \fB\-\-source\-port\fP,\fB\-\-sport\fP \fIport\fP[\fB:\fP\fIport\fP]
+Source port or port range specification. This can either be a service
+name or a port number. An inclusive range can also be specified,
+using the format \fIfirst\fP\fB:\fP\fIlast\fP.
+If the first port is omitted, "0" is assumed; if the last is omitted,
+"65535" is assumed.
+The flag
+\fB\-\-sport\fP
+is a convenient alias for this option.
+.TP
+[\fB!\fP] \fB\-\-destination\-port\fP,\fB\-\-dport\fP \fIport\fP[\fB:\fP\fIport\fP]
+Destination port or port range specification.  The flag
+\fB\-\-dport\fP
+is a convenient alias for this option.
+.TP
+[\fB!\fP] \fB\-\-tcp\-flags\fP \fImask\fP \fIcomp\fP
+Match when the TCP flags are as specified.  The first argument \fImask\fP is the
+flags which we should examine, written as a comma-separated list, and
+the second argument \fIcomp\fP is a comma-separated list of flags which must be
+set.  Flags are:
+.BR "SYN ACK FIN RST URG PSH ALL NONE" .
+Hence the command
+.nf
+ iptables \-A FORWARD \-p tcp \-\-tcp\-flags SYN,ACK,FIN,RST SYN
+.fi
+will only match packets with the SYN flag set, and the ACK, FIN and
+RST flags unset.
+.TP
+[\fB!\fP] \fB\-\-syn\fP
+Only match TCP packets with the SYN bit set and the ACK,RST and FIN bits
+cleared.  Such packets are used to request TCP connection initiation;
+for example, blocking such packets coming in an interface will prevent
+incoming TCP connections, but outgoing TCP connections will be
+unaffected.
+It is equivalent to \fB\-\-tcp\-flags SYN,RST,ACK,FIN SYN\fP.
+If the "!" flag precedes the "\-\-syn", the sense of the
+option is inverted.
+.TP
+[\fB!\fP] \fB\-\-tcp\-option\fP \fInumber\fP
+Match if TCP option set.
diff --git a/extensions/libxt_tcp.t b/extensions/libxt_tcp.t
new file mode 100644
index 0000000..b0e8006
--- /dev/null
+++ b/extensions/libxt_tcp.t
@@ -0,0 +1,26 @@
+:INPUT,FORWARD,OUTPUT
+-p tcp -m tcp --sport 1;=;OK
+-p tcp -m tcp --sport 65535;=;OK
+-p tcp -m tcp --dport 1;=;OK
+-p tcp -m tcp --dport 65535;=;OK
+-p tcp -m tcp --sport 1:1023;=;OK
+-p tcp -m tcp --sport 1024:65535;=;OK
+-p tcp -m tcp --sport 1024:;-p tcp -m tcp --sport 1024:65535;OK
+-p tcp -m tcp ! --sport 1;=;OK
+-p tcp -m tcp ! --sport 65535;=;OK
+-p tcp -m tcp ! --dport 1;=;OK
+-p tcp -m tcp ! --dport 65535;=;OK
+-p tcp -m tcp --sport 1 --dport 65535;=;OK
+-p tcp -m tcp --sport 65535 --dport 1;=;OK
+-p tcp -m tcp ! --sport 1 --dport 65535;=;OK
+-p tcp -m tcp ! --sport 65535 --dport 1;=;OK
+-p tcp -m tcp --sport 65536;;FAIL
+-p tcp -m tcp --sport -1;;FAIL
+-p tcp -m tcp --dport -1;;FAIL
+-p tcp -m tcp --syn;-p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN;OK
+-p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN;=;OK
+-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG SYN;=;OK
+-p tcp -m tcp ! --tcp-flags FIN,SYN,RST,PSH,ACK,URG SYN;=;OK
+-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG RST;=;OK
+# should we accept this below?
+-p tcp -m tcp;=;OK
diff --git a/extensions/libxt_tcp.txlate b/extensions/libxt_tcp.txlate
new file mode 100644
index 0000000..bba6332
--- /dev/null
+++ b/extensions/libxt_tcp.txlate
@@ -0,0 +1,26 @@
+iptables-translate -A INPUT -p tcp -i eth0 --sport 53 -j ACCEPT
+nft add rule ip filter INPUT iifname "eth0" tcp sport 53 counter accept
+
+iptables-translate -A OUTPUT -p tcp -o eth0 --dport 53:66 -j DROP
+nft add rule ip filter OUTPUT oifname "eth0" tcp dport 53-66 counter drop
+
+iptables-translate -I OUTPUT -p tcp -d 8.8.8.8 -j ACCEPT
+nft insert rule ip filter OUTPUT ip protocol tcp ip daddr 8.8.8.8 counter accept
+
+iptables-translate -I OUTPUT -p tcp --dport 1020:1023 --sport 53 -j ACCEPT
+nft insert rule ip filter OUTPUT tcp sport 53 tcp dport 1020-1023 counter accept
+
+iptables -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j DROP
+nft add rule ip filter INPUT tcp flags & fin|ack == fin counter drop
+
+iptables-translate -A INPUT -p tcp --syn -j ACCEPT
+nft add rule ip filter INPUT tcp flags & (fin|syn|rst|ack) == syn counter accept
+
+iptables-translate -A INPUT -p tcp --syn --dport 80 -j ACCEPT
+nft add rule ip filter INPUT tcp dport 80 tcp flags & (fin|syn|rst|ack) == syn counter accept
+
+iptables-translate -A INPUT -f -p tcp
+nft add rule ip filter INPUT ip frag-off & 0x1fff != 0 ip protocol tcp counter
+
+iptables-translate -A INPUT ! -f -p tcp --dport 22
+nft add rule ip filter INPUT ip frag-off & 0x1fff 0 tcp dport 22 counter
diff --git a/extensions/libxt_tcpmss.c b/extensions/libxt_tcpmss.c
new file mode 100644
index 0000000..bcd357a
--- /dev/null
+++ b/extensions/libxt_tcpmss.c
@@ -0,0 +1,79 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_tcpmss.h>
+
+enum {
+	O_TCPMSS = 0,
+};
+
+static void tcpmss_help(void)
+{
+	printf(
+"tcpmss match options:\n"
+"[!] --mss value[:value]	Match TCP MSS range.\n"
+"				(only valid for TCP SYN or SYN/ACK packets)\n");
+}
+
+static const struct xt_option_entry tcpmss_opts[] = {
+	{.name = "mss", .id = O_TCPMSS, .type = XTTYPE_UINT16RC,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void tcpmss_parse(struct xt_option_call *cb)
+{
+	struct xt_tcpmss_match_info *mssinfo = cb->data;
+
+	xtables_option_parse(cb);
+	mssinfo->mss_min = cb->val.u16_range[0];
+	mssinfo->mss_max = mssinfo->mss_min;
+	if (cb->nvals == 2) {
+		mssinfo->mss_max = cb->val.u16_range[1];
+		if (mssinfo->mss_max < mssinfo->mss_min)
+			xtables_error(PARAMETER_PROBLEM,
+				      "tcpmss: invalid range given");
+	}
+	if (cb->invert)
+		mssinfo->invert = 1;
+}
+
+static void
+tcpmss_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_tcpmss_match_info *info = (void *)match->data;
+
+	printf(" tcpmss match %s", info->invert ? "!" : "");
+	if (info->mss_min == info->mss_max)
+		printf("%u", info->mss_min);
+	else
+		printf("%u:%u", info->mss_min, info->mss_max);
+}
+
+static void tcpmss_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_tcpmss_match_info *info = (void *)match->data;
+
+	printf("%s --mss ", info->invert ? " !" : "");
+	if (info->mss_min == info->mss_max)
+		printf("%u", info->mss_min);
+	else
+		printf("%u:%u", info->mss_min, info->mss_max);
+}
+
+static struct xtables_match tcpmss_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "tcpmss",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_tcpmss_match_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_tcpmss_match_info)),
+	.help		= tcpmss_help,
+	.print		= tcpmss_print,
+	.save		= tcpmss_save,
+	.x6_parse	= tcpmss_parse,
+	.x6_options	= tcpmss_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&tcpmss_match);
+}
diff --git a/extensions/libxt_tcpmss.man b/extensions/libxt_tcpmss.man
new file mode 100644
index 0000000..8253c36
--- /dev/null
+++ b/extensions/libxt_tcpmss.man
@@ -0,0 +1,4 @@
+This matches the TCP MSS (maximum segment size) field of the TCP header.  You can only use this on TCP SYN or SYN/ACK packets, since the MSS is only negotiated during the TCP handshake at connection startup time.
+.TP
+[\fB!\fP] \fB\-\-mss\fP \fIvalue\fP[\fB:\fP\fIvalue\fP]
+Match a given TCP MSS value or range. If a range is given, the second \fIvalue\fP must be greater than or equal to the first \fIvalue\fP.
diff --git a/extensions/libxt_tcpmss.t b/extensions/libxt_tcpmss.t
new file mode 100644
index 0000000..2b41595
--- /dev/null
+++ b/extensions/libxt_tcpmss.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m tcpmss --mss 42;;FAIL
+-p tcp -m tcpmss --mss 42;=;OK
+-p tcp -m tcpmss --mss 42:12345;=;OK
+-p tcp -m tcpmss --mss 42:65536;;FAIL
+-p tcp -m tcpmss --mss 65535:1000;;FAIL
diff --git a/extensions/libxt_time.c b/extensions/libxt_time.c
new file mode 100644
index 0000000..d27d84c
--- /dev/null
+++ b/extensions/libxt_time.c
@@ -0,0 +1,543 @@
+/*
+ *	libxt_time - iptables part for xt_time
+ *	Copyright © CC Computer Consultants GmbH, 2007
+ *	Contact: <jengelh@computergmbh.de>
+ *
+ *	libxt_time.c 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 2 or 3 of the License.
+ *
+ *	Based on libipt_time.c.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <linux/types.h>
+#include <linux/netfilter/xt_time.h>
+#include <xtables.h>
+
+enum {
+	O_DATE_START = 0,
+	O_DATE_STOP,
+	O_TIME_START,
+	O_TIME_STOP,
+	O_TIME_CONTIGUOUS,
+	O_MONTHDAYS,
+	O_WEEKDAYS,
+	O_LOCAL_TZ,
+	O_UTC,
+	O_KERNEL_TZ,
+	F_LOCAL_TZ  = 1 << O_LOCAL_TZ,
+	F_UTC       = 1 << O_UTC,
+	F_KERNEL_TZ = 1 << O_KERNEL_TZ,
+	F_TIME_CONTIGUOUS = 1 << O_TIME_CONTIGUOUS,
+};
+
+static const char *const week_days[] = {
+	NULL, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
+};
+
+static const struct xt_option_entry time_opts[] = {
+	{.name = "datestart", .id = O_DATE_START, .type = XTTYPE_STRING},
+	{.name = "datestop", .id = O_DATE_STOP, .type = XTTYPE_STRING},
+	{.name = "timestart", .id = O_TIME_START, .type = XTTYPE_STRING},
+	{.name = "timestop", .id = O_TIME_STOP, .type = XTTYPE_STRING},
+	{.name = "contiguous", .id = O_TIME_CONTIGUOUS, .type = XTTYPE_NONE},
+	{.name = "weekdays", .id = O_WEEKDAYS, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "monthdays", .id = O_MONTHDAYS, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "localtz", .id = O_LOCAL_TZ, .type = XTTYPE_NONE,
+	 .excl = F_UTC},
+	{.name = "utc", .id = O_UTC, .type = XTTYPE_NONE,
+	 .excl = F_LOCAL_TZ | F_KERNEL_TZ},
+	{.name = "kerneltz", .id = O_KERNEL_TZ, .type = XTTYPE_NONE,
+	 .excl = F_UTC},
+	XTOPT_TABLEEND,
+};
+
+static void time_help(void)
+{
+	printf(
+"time match options:\n"
+"    --datestart time     Start and stop time, to be given in ISO 8601\n"
+"    --datestop time      (YYYY[-MM[-DD[Thh[:mm[:ss]]]]])\n"
+"    --timestart time     Start and stop daytime (hh:mm[:ss])\n"
+"    --timestop time      (between 00:00:00 and 23:59:59)\n"
+"[!] --monthdays value    List of days on which to match, separated by comma\n"
+"                         (Possible days: 1 to 31; defaults to all)\n"
+"[!] --weekdays value     List of weekdays on which to match, sep. by comma\n"
+"                         (Possible days: Mon,Tue,Wed,Thu,Fri,Sat,Sun or 1 to 7\n"
+"                         Defaults to all weekdays.)\n"
+"    --kerneltz           Work with the kernel timezone instead of UTC\n");
+}
+
+static void time_init(struct xt_entry_match *m)
+{
+	struct xt_time_info *info = (void *)m->data;
+
+	/* By default, we match on every day, every daytime */
+	info->monthdays_match = XT_TIME_ALL_MONTHDAYS;
+	info->weekdays_match  = XT_TIME_ALL_WEEKDAYS;
+	info->daytime_start   = XT_TIME_MIN_DAYTIME;
+	info->daytime_stop    = XT_TIME_MAX_DAYTIME;
+
+	/* ...and have no date-begin or date-end boundary */
+	info->date_start = 0;
+	info->date_stop  = INT_MAX;
+}
+
+static time_t time_parse_date(const char *s)
+{
+	unsigned int month = 1, day = 1, hour = 0, minute = 0, second = 0;
+	unsigned int year;
+	const char *os = s;
+	struct tm tm;
+	time_t ret;
+	char *e;
+
+	year = strtoul(s, &e, 10);
+	if ((*e != '-' && *e != '\0') || year < 1970 || year > 2038)
+		goto out;
+	if (*e == '\0')
+		goto eval;
+
+	s = e + 1;
+	month = strtoul(s, &e, 10);
+	if ((*e != '-' && *e != '\0') || month > 12)
+		goto out;
+	if (*e == '\0')
+		goto eval;
+
+	s = e + 1;
+	day = strtoul(s, &e, 10);
+	if ((*e != 'T' && *e != '\0') || day > 31)
+		goto out;
+	if (*e == '\0')
+		goto eval;
+
+	s = e + 1;
+	hour = strtoul(s, &e, 10);
+	if ((*e != ':' && *e != '\0') || hour > 23)
+		goto out;
+	if (*e == '\0')
+		goto eval;
+
+	s = e + 1;
+	minute = strtoul(s, &e, 10);
+	if ((*e != ':' && *e != '\0') || minute > 59)
+		goto out;
+	if (*e == '\0')
+		goto eval;
+
+	s = e + 1;
+	second = strtoul(s, &e, 10);
+	if (*e != '\0' || second > 59)
+		goto out;
+
+ eval:
+	tm.tm_year = year - 1900;
+	tm.tm_mon  = month - 1;
+	tm.tm_mday = day;
+	tm.tm_hour = hour;
+	tm.tm_min  = minute;
+	tm.tm_sec  = second;
+	tm.tm_isdst = 0;
+	/*
+	 * Offsetting, if any, is done by xt_time.ko,
+	 * so we have to disable it here in userspace.
+	 */
+	setenv("TZ", "UTC", true);
+	tzset();
+	ret = mktime(&tm);
+	if (ret >= 0)
+		return ret;
+	perror("mktime");
+	xtables_error(OTHER_PROBLEM, "mktime returned an error");
+
+ out:
+	xtables_error(PARAMETER_PROBLEM, "Invalid date \"%s\" specified. Should "
+	           "be YYYY[-MM[-DD[Thh[:mm[:ss]]]]]", os);
+	return -1;
+}
+
+static unsigned int time_parse_minutes(const char *s)
+{
+	unsigned int hour, minute, second = 0;
+	char *e;
+
+	hour = strtoul(s, &e, 10);
+	if (*e != ':' || hour > 23)
+		goto out;
+
+	s = e + 1;
+	minute = strtoul(s, &e, 10);
+	if ((*e != ':' && *e != '\0') || minute > 59)
+		goto out;
+	if (*e == '\0')
+		goto eval;
+
+	s = e + 1;
+	second = strtoul(s, &e, 10);
+	if (*e != '\0' || second > 59)
+		goto out;
+
+ eval:
+	return 60 * 60 * hour + 60 * minute + second;
+
+ out:
+	xtables_error(PARAMETER_PROBLEM, "invalid time \"%s\" specified, "
+	           "should be hh:mm[:ss] format and within the boundaries", s);
+	return -1;
+}
+
+static const char *my_strseg(char *buf, unsigned int buflen,
+    const char **arg, char delim)
+{
+	const char *sep;
+
+	if (*arg == NULL || **arg == '\0')
+		return NULL;
+	sep = strchr(*arg, delim);
+	if (sep == NULL) {
+		snprintf(buf, buflen, "%s", *arg);
+		*arg = NULL;
+		return buf;
+	}
+	snprintf(buf, buflen, "%.*s", (unsigned int)(sep - *arg), *arg);
+	*arg = sep + 1;
+	return buf;
+}
+
+static uint32_t time_parse_monthdays(const char *arg)
+{
+	char day[3], *err = NULL;
+	uint32_t ret = 0;
+	unsigned int i;
+
+	while (my_strseg(day, sizeof(day), &arg, ',') != NULL) {
+		i = strtoul(day, &err, 0);
+		if ((*err != ',' && *err != '\0') || i > 31)
+			xtables_error(PARAMETER_PROBLEM,
+			           "%s is not a valid day for --monthdays", day);
+		ret |= 1 << i;
+	}
+
+	return ret;
+}
+
+static unsigned int time_parse_weekdays(const char *arg)
+{
+	char day[4], *err = NULL;
+	unsigned int i, ret = 0;
+	bool valid;
+
+	while (my_strseg(day, sizeof(day), &arg, ',') != NULL) {
+		i = strtoul(day, &err, 0);
+		if (*err == '\0') {
+			if (i == 0)
+				xtables_error(PARAMETER_PROBLEM,
+				           "No, the week does NOT begin with Sunday.");
+			ret |= 1 << i;
+			continue;
+		}
+
+		valid = false;
+		for (i = 1; i < ARRAY_SIZE(week_days); ++i)
+			if (strncmp(day, week_days[i], 2) == 0) {
+				ret |= 1 << i;
+				valid = true;
+			}
+
+		if (!valid)
+			xtables_error(PARAMETER_PROBLEM,
+			           "%s is not a valid day specifier", day);
+	}
+
+	return ret;
+}
+
+static unsigned int time_count_weekdays(unsigned int weekdays_mask)
+{
+	unsigned int ret;
+
+	for (ret = 0; weekdays_mask; weekdays_mask >>= 1)
+		ret += weekdays_mask & 1;
+
+	return ret;
+}
+
+static void time_parse(struct xt_option_call *cb)
+{
+	struct xt_time_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_DATE_START:
+		info->date_start = time_parse_date(cb->arg);
+		break;
+	case O_DATE_STOP:
+		info->date_stop = time_parse_date(cb->arg);
+		break;
+	case O_TIME_START:
+		info->daytime_start = time_parse_minutes(cb->arg);
+		break;
+	case O_TIME_STOP:
+		info->daytime_stop = time_parse_minutes(cb->arg);
+		break;
+	case O_TIME_CONTIGUOUS:
+		info->flags |= XT_TIME_CONTIGUOUS;
+		break;
+	case O_LOCAL_TZ:
+		fprintf(stderr, "WARNING: --localtz is being replaced by "
+		        "--kerneltz, since \"local\" is ambiguous. Note the "
+		        "kernel timezone has caveats - "
+		        "see manpage for details.\n");
+		/* fallthrough */
+	case O_KERNEL_TZ:
+		info->flags |= XT_TIME_LOCAL_TZ;
+		break;
+	case O_MONTHDAYS:
+		info->monthdays_match = time_parse_monthdays(cb->arg);
+		if (cb->invert)
+			info->monthdays_match ^= XT_TIME_ALL_MONTHDAYS;
+		break;
+	case O_WEEKDAYS:
+		info->weekdays_match = time_parse_weekdays(cb->arg);
+		if (cb->invert)
+			info->weekdays_match ^= XT_TIME_ALL_WEEKDAYS;
+		break;
+	}
+}
+
+static void time_print_date(time_t date, const char *command)
+{
+	struct tm *t;
+
+	/* If it is the default value, do not print it. */
+	if (date == 0 || date == LONG_MAX)
+		return;
+
+	t = gmtime(&date);
+	if (command != NULL)
+		/*
+		 * Need a contiguous string (no whitespaces), hence using
+		 * the ISO 8601 "T" variant.
+		 */
+		printf(" %s %04u-%02u-%02uT%02u:%02u:%02u",
+		       command, t->tm_year + 1900, t->tm_mon + 1,
+		       t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
+	else
+		printf(" %04u-%02u-%02u %02u:%02u:%02u",
+		       t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
+		       t->tm_hour, t->tm_min, t->tm_sec);
+}
+
+static void time_print_monthdays(uint32_t mask, bool human_readable)
+{
+	unsigned int i, nbdays = 0;
+
+	printf(" ");
+	for (i = 1; i <= 31; ++i)
+		if (mask & (1u << i)) {
+			if (nbdays++ > 0)
+				printf(",");
+			printf("%u", i);
+			if (human_readable)
+				switch (i % 10) {
+					case 1:
+						printf("st");
+						break;
+					case 2:
+						printf("nd");
+						break;
+					case 3:
+						printf("rd");
+						break;
+					default:
+						printf("th");
+						break;
+				}
+		}
+}
+
+static void time_print_weekdays(unsigned int mask)
+{
+	unsigned int i, nbdays = 0;
+
+	printf(" ");
+	for (i = 1; i <= 7; ++i)
+		if (mask & (1 << i)) {
+			if (nbdays > 0)
+				printf(",%s", week_days[i]);
+			else
+				printf("%s", week_days[i]);
+			++nbdays;
+		}
+}
+
+static inline void divide_time(unsigned int fulltime, unsigned int *hours,
+    unsigned int *minutes, unsigned int *seconds)
+{
+	*seconds  = fulltime % 60;
+	fulltime /= 60;
+	*minutes  = fulltime % 60;
+	*hours    = fulltime / 60;
+}
+
+static void time_print(const void *ip, const struct xt_entry_match *match,
+                       int numeric)
+{
+	const struct xt_time_info *info = (const void *)match->data;
+	unsigned int h, m, s;
+
+	printf(" TIME");
+
+	if (info->daytime_start != XT_TIME_MIN_DAYTIME ||
+	    info->daytime_stop != XT_TIME_MAX_DAYTIME) {
+		divide_time(info->daytime_start, &h, &m, &s);
+		printf(" from %02u:%02u:%02u", h, m, s);
+		divide_time(info->daytime_stop, &h, &m, &s);
+		printf(" to %02u:%02u:%02u", h, m, s);
+	}
+	if (info->weekdays_match != XT_TIME_ALL_WEEKDAYS) {
+		printf(" on");
+		time_print_weekdays(info->weekdays_match);
+	}
+	if (info->monthdays_match != XT_TIME_ALL_MONTHDAYS) {
+		printf(" on");
+		time_print_monthdays(info->monthdays_match, true);
+	}
+	if (info->date_start != 0) {
+		printf(" starting from");
+		time_print_date(info->date_start, NULL);
+	}
+	if (info->date_stop != INT_MAX) {
+		printf(" until date");
+		time_print_date(info->date_stop, NULL);
+	}
+	if (!(info->flags & XT_TIME_LOCAL_TZ))
+		printf(" UTC");
+	if (info->flags & XT_TIME_CONTIGUOUS)
+		printf(" contiguous");
+}
+
+static void time_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_time_info *info = (const void *)match->data;
+	unsigned int h, m, s;
+
+	if (info->daytime_start != XT_TIME_MIN_DAYTIME ||
+	    info->daytime_stop != XT_TIME_MAX_DAYTIME) {
+		divide_time(info->daytime_start, &h, &m, &s);
+		printf(" --timestart %02u:%02u:%02u", h, m, s);
+		divide_time(info->daytime_stop, &h, &m, &s);
+		printf(" --timestop %02u:%02u:%02u", h, m, s);
+	}
+	if (info->monthdays_match != XT_TIME_ALL_MONTHDAYS) {
+		printf(" --monthdays");
+		time_print_monthdays(info->monthdays_match, false);
+	}
+	if (info->weekdays_match != XT_TIME_ALL_WEEKDAYS) {
+		printf(" --weekdays");
+		time_print_weekdays(info->weekdays_match);
+	}
+	time_print_date(info->date_start, "--datestart");
+	time_print_date(info->date_stop, "--datestop");
+	if (info->flags & XT_TIME_LOCAL_TZ)
+		printf(" --kerneltz");
+	if (info->flags & XT_TIME_CONTIGUOUS)
+		printf(" --contiguous");
+}
+
+static void time_check(struct xt_fcheck_call *cb)
+{
+	const struct xt_time_info *info = (const void *) cb->data;
+	if ((cb->xflags & F_TIME_CONTIGUOUS) &&
+	     info->daytime_start < info->daytime_stop)
+		xtables_error(PARAMETER_PROBLEM,
+			"time: --contiguous only makes sense when stoptime is smaller than starttime");
+}
+
+static int time_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_time_info *info =
+		(const struct xt_time_info *)params->match->data;
+	unsigned int h, m, s,
+		     i, sep, mask, count;
+	time_t tt_start, tt_stop;
+	struct tm *t_start, *t_stop;
+
+	if (info->date_start != 0 ||
+	    info->date_stop != INT_MAX) {
+		tt_start = (time_t) info->date_start;
+		tt_stop = (time_t) info->date_stop;
+
+		xt_xlate_add(xl, "meta time ");
+		t_start = gmtime(&tt_start);
+		xt_xlate_add(xl, "\"%04u-%02u-%02u %02u:%02u:%02u\"",
+			     t_start->tm_year + 1900, t_start->tm_mon + 1,
+			     t_start->tm_mday, t_start->tm_hour,
+			     t_start->tm_min, t_start->tm_sec);
+		t_stop = gmtime(&tt_stop);
+		xt_xlate_add(xl, "-\"%04u-%02u-%02u %02u:%02u:%02u\"",
+			     t_stop->tm_year + 1900, t_stop->tm_mon + 1,
+			     t_stop->tm_mday, t_stop->tm_hour,
+			     t_stop->tm_min, t_stop->tm_sec);
+	}
+	if (info->daytime_start != XT_TIME_MIN_DAYTIME ||
+	    info->daytime_stop != XT_TIME_MAX_DAYTIME) {
+		divide_time(info->daytime_start, &h, &m, &s);
+		xt_xlate_add(xl, " meta hour \"%02u:%02u:%02u\"", h, m, s);
+		divide_time(info->daytime_stop, &h, &m, &s);
+		xt_xlate_add(xl, "-\"%02u:%02u:%02u\"", h, m, s);
+	}
+	/* nft_time does not support --monthdays */
+	if (info->monthdays_match != XT_TIME_ALL_MONTHDAYS)
+		return 0;
+	if (info->weekdays_match != XT_TIME_ALL_WEEKDAYS) {
+		sep = 0;
+		mask = info->weekdays_match;
+		count = time_count_weekdays(mask);
+
+		xt_xlate_add(xl, " meta day ");
+		if (count > 1)
+			xt_xlate_add(xl, "{");
+		for (i = 1; i <= 7; ++i)
+			if (mask & (1 << i)) {
+				if (sep)
+					xt_xlate_add(xl, ",%u", i%7);
+				else {
+					xt_xlate_add(xl, "%u", i%7);
+					++sep;
+				}
+			}
+		if (count > 1)
+			xt_xlate_add(xl, "}");
+	}
+
+	return 1;
+}
+
+static struct xtables_match time_match = {
+	.name          = "time",
+	.family        = NFPROTO_UNSPEC,
+	.version       = XTABLES_VERSION,
+	.size          = XT_ALIGN(sizeof(struct xt_time_info)),
+	.userspacesize = XT_ALIGN(sizeof(struct xt_time_info)),
+	.help          = time_help,
+	.init          = time_init,
+	.print         = time_print,
+	.save          = time_save,
+	.x6_parse      = time_parse,
+	.x6_fcheck     = time_check,
+	.x6_options    = time_opts,
+	.xlate	       = time_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&time_match);
+}
diff --git a/extensions/libxt_time.man b/extensions/libxt_time.man
new file mode 100644
index 0000000..4c0cae0
--- /dev/null
+++ b/extensions/libxt_time.man
@@ -0,0 +1,98 @@
+This matches if the packet arrival time/date is within a given range. All
+options are optional, but are ANDed when specified. All times are interpreted
+as UTC by default.
+.TP
+\fB\-\-datestart\fP \fIYYYY\fP[\fB\-\fP\fIMM\fP[\fB\-\fP\fIDD\fP[\fBT\fP\fIhh\fP[\fB:\fP\fImm\fP[\fB:\fP\fIss\fP]]]]]
+.TP
+\fB\-\-datestop\fP \fIYYYY\fP[\fB\-\fP\fIMM\fP[\fB\-\fP\fIDD\fP[\fBT\fP\fIhh\fP[\fB:\fP\fImm\fP[\fB:\fP\fIss\fP]]]]]
+Only match during the given time, which must be in ISO 8601 "T" notation.
+The possible time range is 1970-01-01T00:00:00 to 2038-01-19T04:17:07.
+.IP
+If \-\-datestart or \-\-datestop are not specified, it will default to 1970-01-01
+and 2038-01-19, respectively.
+.TP
+\fB\-\-timestart\fP \fIhh\fP\fB:\fP\fImm\fP[\fB:\fP\fIss\fP]
+.TP
+\fB\-\-timestop\fP \fIhh\fP\fB:\fP\fImm\fP[\fB:\fP\fIss\fP]
+Only match during the given daytime. The possible time range is 00:00:00 to
+23:59:59. Leading zeroes are allowed (e.g. "06:03") and correctly interpreted
+as base-10.
+.TP
+[\fB!\fP] \fB\-\-monthdays\fP \fIday\fP[\fB,\fP\fIday\fP...]
+Only match on the given days of the month. Possible values are \fB1\fP
+to \fB31\fP. Note that specifying \fB31\fP will of course not match
+on months which do not have a 31st day; the same goes for 28- or 29-day
+February.
+.TP
+[\fB!\fP] \fB\-\-weekdays\fP \fIday\fP[\fB,\fP\fIday\fP...]
+Only match on the given weekdays. Possible values are \fBMon\fP, \fBTue\fP,
+\fBWed\fP, \fBThu\fP, \fBFri\fP, \fBSat\fP, \fBSun\fP, or values from \fB1\fP
+to \fB7\fP, respectively. You may also use two-character variants (\fBMo\fP,
+\fBTu\fP, etc.).
+.TP
+\fB\-\-contiguous\fP
+When \fB\-\-timestop\fP is smaller than \fB\-\-timestart\fP value, match
+this as a single time period instead distinct intervals.  See EXAMPLES.
+.TP
+\fB\-\-kerneltz\fP
+Use the kernel timezone instead of UTC to determine whether a packet meets the
+time regulations.
+.PP
+About kernel timezones: Linux keeps the system time in UTC, and always does so.
+On boot, system time is initialized from a referential time source. Where this
+time source has no timezone information, such as the x86 CMOS RTC, UTC will be
+assumed. If the time source is however not in UTC, userspace should provide the
+correct system time and timezone to the kernel once it has the information.
+.PP
+Local time is a feature on top of the (timezone independent) system time. Each
+process has its own idea of local time, specified via the TZ environment
+variable. The kernel also has its own timezone offset variable. The TZ
+userspace environment variable specifies how the UTC-based system time is
+displayed, e.g. when you run date(1), or what you see on your desktop clock.
+The TZ string may resolve to different offsets at different dates, which is
+what enables the automatic time-jumping in userspace. when DST changes. The
+kernel's timezone offset variable is used when it has to convert between
+non-UTC sources, such as FAT filesystems, to UTC (since the latter is what the
+rest of the system uses).
+.PP
+The caveat with the kernel timezone is that Linux distributions may ignore to
+set the kernel timezone, and instead only set the system time. Even if a
+particular distribution does set the timezone at boot, it is usually does not
+keep the kernel timezone offset - which is what changes on DST - up to date.
+ntpd will not touch the kernel timezone, so running it will not resolve the
+issue. As such, one may encounter a timezone that is always +0000, or one that
+is wrong half of the time of the year. As such, \fBusing \-\-kerneltz is highly
+discouraged.\fP
+.PP
+EXAMPLES. To match on weekends, use:
+.IP
+\-m time \-\-weekdays Sa,Su
+.PP
+Or, to match (once) on a national holiday block:
+.IP
+\-m time \-\-datestart 2007\-12\-24 \-\-datestop 2007\-12\-27
+.PP
+Since the stop time is actually inclusive, you would need the following stop
+time to not match the first second of the new day:
+.IP
+\-m time \-\-datestart 2007\-01\-01T17:00 \-\-datestop 2007\-01\-01T23:59:59
+.PP
+During lunch hour:
+.IP
+\-m time \-\-timestart 12:30 \-\-timestop 13:30
+.PP
+The fourth Friday in the month:
+.IP
+\-m time \-\-weekdays Fr \-\-monthdays 22,23,24,25,26,27,28
+.PP
+(Note that this exploits a certain mathematical property. It is not possible to
+say "fourth Thursday OR fourth Friday" in one rule. It is possible with
+multiple rules, though.)
+.PP
+Matching across days might not do what is expected.  For instance,
+.IP
+\-m time \-\-weekdays Mo \-\-timestart 23:00  \-\-timestop 01:00
+Will match Monday, for one hour from midnight to 1 a.m., and then
+again for another hour from 23:00 onwards.  If this is unwanted, e.g. if you
+would like 'match for two hours from Montay 23:00 onwards' you need to also specify
+the \-\-contiguous option in the example above.
diff --git a/extensions/libxt_time.t b/extensions/libxt_time.t
new file mode 100644
index 0000000..673af09
--- /dev/null
+++ b/extensions/libxt_time.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-m time --timestart 01:02:03 --timestop 04:05:06 --monthdays 1,2,3,4,5 --weekdays Mon,Fri,Sun --datestart 2001-02-03T04:05:06 --datestop 2012-09-08T09:06:05 --kerneltz;=;OK
+-m time --timestart 01:02:03 --timestop 04:05:06 --monthdays 1,2,3,4,5 --weekdays Mon,Fri,Sun --datestart 2001-02-03T04:05:06 --datestop 2012-09-08T09:06:05;=;OK
+-m time --timestart 02:00:00 --timestop 03:00:00 --datestart 1970-01-01T02:00:00 --datestop 1970-01-01T03:00:00;=;OK
diff --git a/extensions/libxt_time.txlate b/extensions/libxt_time.txlate
new file mode 100644
index 0000000..ff4a7b8
--- /dev/null
+++ b/extensions/libxt_time.txlate
@@ -0,0 +1,26 @@
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --weekdays Sa,Su -j REJECT
+nft add rule ip filter INPUT icmp type echo-request  meta day {6,0} counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --timestart 12:00 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request  meta hour "12:00:00"-"23:59:59" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --timestop 12:00 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request  meta hour "00:00:00"-"12:00:00" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestart 2021 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "2021-01-01 00:00:00"-"2038-01-19 03:14:07" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestop 2021 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "1970-01-01 00:00:00"-"2021-01-01 00:00:00" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestop 2021-01-29T00:00:00 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "1970-01-01 00:00:00"-"2021-01-29 00:00:00" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestart 2020-01-29T00:00:00 --timestart 12:00 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "2020-01-29 00:00:00"-"2038-01-19 03:14:07" meta hour "12:00:00"-"23:59:59" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestart 2020-01-29T00:00:00 --timestart 12:00 --timestop 19:00 --weekdays Mon,Tue,Wed,Thu,Fri -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "2020-01-29 00:00:00"-"2038-01-19 03:14:07" meta hour "12:00:00"-"19:00:00" meta day {1,2,3,4,5} counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestart 2020-01-29T00:00:00 --timestart 12:00 --timestop 19:00 ! --weekdays Mon,Tue,Wed,Thu,Fri -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "2020-01-29 00:00:00"-"2038-01-19 03:14:07" meta hour "12:00:00"-"19:00:00" meta day {6,0} counter reject
diff --git a/extensions/libxt_tos.c b/extensions/libxt_tos.c
new file mode 100644
index 0000000..81c096f
--- /dev/null
+++ b/extensions/libxt_tos.c
@@ -0,0 +1,156 @@
+/*
+ * Shared library add-on to iptables to add tos match support
+ *
+ * Copyright © CC Computer Consultants GmbH, 2007
+ * Contact: Jan Engelhardt <jengelh@computergmbh.de>
+ */
+#include <getopt.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xtables.h>
+#include <linux/netfilter/xt_dscp.h>
+#include "tos_values.c"
+
+struct ipt_tos_info {
+	uint8_t tos;
+	uint8_t invert;
+};
+
+enum {
+	O_TOS = 1 << 0,
+};
+
+static const struct xt_option_entry tos_mt_opts_v0[] = {
+	{.name = "tos", .id = O_TOS, .type = XTTYPE_TOSMASK,
+	 .flags = XTOPT_INVERT | XTOPT_MAND, .max = 0xFF},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry tos_mt_opts[] = {
+	{.name = "tos", .id = O_TOS, .type = XTTYPE_TOSMASK,
+	 .flags = XTOPT_INVERT | XTOPT_MAND, .max = 0x3F},
+	XTOPT_TABLEEND,
+};
+
+static void tos_mt_help(void)
+{
+	const struct tos_symbol_info *symbol;
+
+	printf(
+"tos match options:\n"
+"[!] --tos value[/mask]    Match Type of Service/Priority field value\n"
+"[!] --tos symbol          Match TOS field (IPv4 only) by symbol\n"
+"                          Accepted symbolic names for value are:\n");
+
+	for (symbol = tos_symbol_names; symbol->name != NULL; ++symbol)
+		printf("                          (0x%02x) %2u %s\n",
+		       symbol->value, symbol->value, symbol->name);
+
+	printf("\n");
+}
+
+static void tos_mt_parse_v0(struct xt_option_call *cb)
+{
+	struct ipt_tos_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->val.tos_mask != 0xFF)
+		xtables_error(PARAMETER_PROBLEM, "tos: Your kernel is "
+		           "too old to support anything besides /0xFF "
+			   "as a mask.");
+	info->tos = cb->val.tos_value;
+	if (cb->invert)
+		info->invert = true;
+}
+
+static void tos_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_tos_match_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	info->tos_value = cb->val.tos_value;
+	info->tos_mask  = cb->val.tos_mask;
+	if (cb->invert)
+		info->invert = true;
+}
+
+static void tos_mt_print_v0(const void *ip, const struct xt_entry_match *match,
+                            int numeric)
+{
+	const struct ipt_tos_info *info = (const void *)match->data;
+
+	printf(" tos match ");
+	if (info->invert)
+		printf("!");
+	if (numeric || !tos_try_print_symbolic("", info->tos, 0x3F))
+		printf("0x%02x", info->tos);
+}
+
+static void tos_mt_print(const void *ip, const struct xt_entry_match *match,
+                         int numeric)
+{
+	const struct xt_tos_match_info *info = (const void *)match->data;
+
+	printf(" tos match");
+	if (info->invert)
+		printf("!");
+	if (numeric ||
+	    !tos_try_print_symbolic("", info->tos_value, info->tos_mask))
+		printf("0x%02x/0x%02x", info->tos_value, info->tos_mask);
+}
+
+static void tos_mt_save_v0(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ipt_tos_info *info = (const void *)match->data;
+
+	if (info->invert)
+		printf(" !");
+	printf(" --tos 0x%02x", info->tos);
+}
+
+static void tos_mt_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_tos_match_info *info = (const void *)match->data;
+
+	if (info->invert)
+		printf(" !");
+	printf(" --tos 0x%02x/0x%02x", info->tos_value, info->tos_mask);
+}
+
+static struct xtables_match tos_mt_reg[] = {
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "tos",
+		.family        = NFPROTO_IPV4,
+		.revision      = 0,
+		.size          = XT_ALIGN(sizeof(struct ipt_tos_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct ipt_tos_info)),
+		.help          = tos_mt_help,
+		.print         = tos_mt_print_v0,
+		.save          = tos_mt_save_v0,
+		.x6_parse      = tos_mt_parse_v0,
+		.x6_options    = tos_mt_opts_v0,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "tos",
+		.family        = NFPROTO_UNSPEC,
+		.revision      = 1,
+		.size          = XT_ALIGN(sizeof(struct xt_tos_match_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_tos_match_info)),
+		.help          = tos_mt_help,
+		.print         = tos_mt_print,
+		.save          = tos_mt_save,
+		.x6_parse      = tos_mt_parse,
+		.x6_options    = tos_mt_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(tos_mt_reg, ARRAY_SIZE(tos_mt_reg));
+}
diff --git a/extensions/libxt_tos.man b/extensions/libxt_tos.man
new file mode 100644
index 0000000..ae73e63
--- /dev/null
+++ b/extensions/libxt_tos.man
@@ -0,0 +1,12 @@
+This module matches the 8-bit Type of Service field in the IPv4 header (i.e.
+including the "Precedence" bits) or the (also 8-bit) Priority field in the IPv6
+header.
+.TP
+[\fB!\fP] \fB\-\-tos\fP \fIvalue\fP[\fB/\fP\fImask\fP]
+Matches packets with the given TOS mark value. If a mask is specified, it is
+logically ANDed with the TOS mark before the comparison.
+.TP
+[\fB!\fP] \fB\-\-tos\fP \fIsymbol\fP
+You can specify a symbolic name when using the tos match for IPv4. The list of
+recognized TOS names can be obtained by calling iptables with \fB\-m tos \-h\fP.
+Note that this implies a mask of 0x3F, i.e. all but the ECN bits.
diff --git a/extensions/libxt_tos.t b/extensions/libxt_tos.t
new file mode 100644
index 0000000..ccbe800
--- /dev/null
+++ b/extensions/libxt_tos.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+-m tos --tos Minimize-Delay;-m tos --tos 0x10/0x3f;OK
+-m tos --tos Maximize-Throughput;-m tos --tos 0x08/0x3f;OK
+-m tos --tos Maximize-Reliability;-m tos --tos 0x04/0x3f;OK
+-m tos --tos Minimize-Cost;-m tos --tos 0x02/0x3f;OK
+-m tos --tos Normal-Service;-m tos --tos 0x00/0x3f;OK
+-m tos --tos 0xff;=;OK
+-m tos ! --tos 0xff;=;OK
+-m tos --tos 0x00;=;OK
+-m tos --tos 0x0f;=;OK
+-m tos --tos 0x0f/0x0f;=;OK
+-m tos --tos wrong;;FAIL
+-m tos;;FAIL
diff --git a/extensions/libxt_u32.c b/extensions/libxt_u32.c
new file mode 100644
index 0000000..2a7f5d8
--- /dev/null
+++ b/extensions/libxt_u32.c
@@ -0,0 +1,279 @@
+/* Shared library add-on to iptables to add u32 matching,
+ * generalized matching on values found at packet offsets
+ *
+ * Detailed doc is in the kernel module source
+ * net/netfilter/xt_u32.c
+ *
+ * (C) 2002 by Don Cohen <don-netf@isis.cs3-inc.com>
+ * Released under the terms of GNU GPL v2
+ *
+ * Copyright © CC Computer Consultants GmbH, 2007
+ * Contact: <jengelh@computergmbh.de>
+ */
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_u32.h>
+
+enum {
+	O_U32 = 0,
+};
+
+static const struct xt_option_entry u32_opts[] = {
+	{.name = "u32", .id = O_U32, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void u32_help(void)
+{
+	printf(
+		"u32 match options:\n"
+		"[!] --u32 tests\n"
+		"\t\t""tests := location \"=\" value | tests \"&&\" location \"=\" value\n"
+		"\t\t""value := range | value \",\" range\n"
+		"\t\t""range := number | number \":\" number\n"
+		"\t\t""location := number | location operator number\n"
+		"\t\t""operator := \"&\" | \"<<\" | \">>\" | \"@\"\n");
+}
+
+static void u32_dump(const struct xt_u32 *data)
+{
+	const struct xt_u32_test *ct;
+	unsigned int testind, i;
+
+	printf(" \"");
+	for (testind = 0; testind < data->ntests; ++testind) {
+		ct = &data->tests[testind];
+
+		if (testind > 0)
+			printf("&&");
+
+		printf("0x%x", ct->location[0].number);
+		for (i = 1; i < ct->nnums; ++i) {
+			switch (ct->location[i].nextop) {
+			case XT_U32_AND:
+				printf("&");
+				break;
+			case XT_U32_LEFTSH:
+				printf("<<");
+				break;
+			case XT_U32_RIGHTSH:
+				printf(">>");
+				break;
+			case XT_U32_AT:
+				printf("@");
+				break;
+			}
+			printf("0x%x", ct->location[i].number);
+		}
+
+		printf("=");
+		for (i = 0; i < ct->nvalues; ++i) {
+			if (i > 0)
+				printf(",");
+			if (ct->value[i].min == ct->value[i].max)
+				printf("0x%x", ct->value[i].min);
+			else
+				printf("0x%x:0x%x", ct->value[i].min,
+				       ct->value[i].max);
+		}
+	}
+	putchar('\"');
+}
+
+/* string_to_number() is not quite what we need here ... */
+static uint32_t parse_number(const char **s, int pos)
+{
+	unsigned int number;
+	char *end;
+
+	if (!xtables_strtoui(*s, &end, &number, 0, UINT32_MAX) ||
+	    end == *s)
+		xtables_error(PARAMETER_PROBLEM,
+			"u32: at char %d: not a number or out of range", pos);
+	*s = end;
+	return number;
+}
+
+static void u32_parse(struct xt_option_call *cb)
+{
+	struct xt_u32 *data = cb->data;
+	unsigned int testind = 0, locind = 0, valind = 0;
+	struct xt_u32_test *ct = &data->tests[testind]; /* current test */
+	const char *arg = cb->arg; /* the argument string */
+	const char *start = cb->arg;
+	int state = 0;
+
+	xtables_option_parse(cb);
+	data->invert = cb->invert;
+
+	/*
+	 * states:
+	 * 0 = looking for numbers and operations,
+	 * 1 = looking for ranges
+	 */
+	while (1) {
+		/* read next operand/number or range */
+		while (isspace(*arg))
+			++arg;
+
+		if (*arg == '\0') {
+			/* end of argument found */
+			if (state == 0)
+				xtables_error(PARAMETER_PROBLEM,
+					   "u32: abrupt end of input after location specifier");
+			if (valind == 0)
+				xtables_error(PARAMETER_PROBLEM,
+					   "u32: test ended with no value specified");
+
+			ct->nnums    = locind;
+			ct->nvalues  = valind;
+			data->ntests = ++testind;
+
+			if (testind > XT_U32_MAXSIZE)
+				xtables_error(PARAMETER_PROBLEM,
+				           "u32: at char %u: too many \"&&\"s",
+				           (unsigned int)(arg - start));
+			return;
+		}
+
+		if (state == 0) {
+			/*
+			 * reading location: read a number if nothing read yet,
+			 * otherwise either op number or = to end location spec
+			 */
+			if (*arg == '=') {
+				if (locind == 0) {
+					xtables_error(PARAMETER_PROBLEM,
+					           "u32: at char %u: "
+					           "location spec missing",
+					           (unsigned int)(arg - start));
+				} else {
+					++arg;
+					state = 1;
+				}
+			} else {
+				if (locind != 0) {
+					/* need op before number */
+					if (*arg == '&') {
+						ct->location[locind].nextop = XT_U32_AND;
+					} else if (*arg == '<') {
+						if (*++arg != '<')
+							xtables_error(PARAMETER_PROBLEM,
+								   "u32: at char %u: a second '<' was expected", (unsigned int)(arg - start));
+						ct->location[locind].nextop = XT_U32_LEFTSH;
+					} else if (*arg == '>') {
+						if (*++arg != '>')
+							xtables_error(PARAMETER_PROBLEM,
+								   "u32: at char %u: a second '>' was expected", (unsigned int)(arg - start));
+						ct->location[locind].nextop = XT_U32_RIGHTSH;
+					} else if (*arg == '@') {
+						ct->location[locind].nextop = XT_U32_AT;
+					} else {
+						xtables_error(PARAMETER_PROBLEM,
+							"u32: at char %u: operator expected", (unsigned int)(arg - start));
+					}
+					++arg;
+				}
+				/* now a number; string_to_number skips white space? */
+				ct->location[locind].number =
+					parse_number(&arg, arg - start);
+				if (++locind > XT_U32_MAXSIZE)
+					xtables_error(PARAMETER_PROBLEM,
+						   "u32: at char %u: too many operators", (unsigned int)(arg - start));
+			}
+		} else {
+			/*
+			 * state 1 - reading values: read a range if nothing
+			 * read yet, otherwise either ,range or && to end
+			 * test spec
+			 */
+			if (*arg == '&') {
+				if (*++arg != '&')
+					xtables_error(PARAMETER_PROBLEM,
+						   "u32: at char %u: a second '&' was expected", (unsigned int)(arg - start));
+				if (valind == 0) {
+					xtables_error(PARAMETER_PROBLEM,
+						   "u32: at char %u: value spec missing", (unsigned int)(arg - start));
+				} else {
+					ct->nnums   = locind;
+					ct->nvalues = valind;
+					ct = &data->tests[++testind];
+					if (testind > XT_U32_MAXSIZE)
+						xtables_error(PARAMETER_PROBLEM,
+							   "u32: at char %u: too many \"&&\"s", (unsigned int)(arg - start));
+					++arg;
+					state  = 0;
+					locind = 0;
+					valind = 0;
+				}
+			} else { /* read value range */
+				if (valind > 0) { /* need , before number */
+					if (*arg != ',')
+						xtables_error(PARAMETER_PROBLEM,
+							   "u32: at char %u: expected \",\" or \"&&\"", (unsigned int)(arg - start));
+					++arg;
+				}
+				ct->value[valind].min =
+					parse_number(&arg, arg - start);
+
+				while (isspace(*arg))
+					++arg;
+
+				if (*arg == ':') {
+					++arg;
+					ct->value[valind].max =
+						parse_number(&arg, arg-start);
+				} else {
+					ct->value[valind].max =
+						ct->value[valind].min;
+				}
+
+				if (++valind > XT_U32_MAXSIZE)
+					xtables_error(PARAMETER_PROBLEM,
+						   "u32: at char %u: too many \",\"s", (unsigned int)(arg - start));
+			}
+		}
+	}
+}
+
+static void u32_print(const void *ip, const struct xt_entry_match *match,
+                      int numeric)
+{
+	const struct xt_u32 *data = (const void *)match->data;
+	printf(" u32");
+	if (data->invert)
+		printf(" !");
+	u32_dump(data);
+}
+
+static void u32_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_u32 *data = (const void *)match->data;
+	if (data->invert)
+		printf(" !");
+	printf(" --u32");
+	u32_dump(data);
+}
+
+static struct xtables_match u32_match = {
+	.name          = "u32",
+	.family        = NFPROTO_UNSPEC,
+	.version       = XTABLES_VERSION,
+	.size          = XT_ALIGN(sizeof(struct xt_u32)),
+	.userspacesize = XT_ALIGN(sizeof(struct xt_u32)),
+	.help          = u32_help,
+	.print         = u32_print,
+	.save          = u32_save,
+	.x6_parse      = u32_parse,
+	.x6_options    = u32_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&u32_match);
+}
diff --git a/extensions/libxt_u32.man b/extensions/libxt_u32.man
new file mode 100644
index 0000000..40a69f8
--- /dev/null
+++ b/extensions/libxt_u32.man
@@ -0,0 +1,134 @@
+U32 tests whether quantities of up to 4 bytes extracted from a packet have
+specified values. The specification of what to extract is general enough to
+find data at given offsets from tcp headers or payloads.
+.TP
+[\fB!\fP] \fB\-\-u32\fP \fItests\fP
+The argument amounts to a program in a small language described below.
+.IP
+tests := location "=" value | tests "&&" location "=" value
+.IP
+value := range | value "," range
+.IP
+range := number | number ":" number
+.PP
+a single number, \fIn\fP, is interpreted the same as \fIn:n\fP. \fIn:m\fP is
+interpreted as the range of numbers \fB>=n\fP and \fB<=m\fP.
+.IP "" 4
+location := number | location operator number
+.IP "" 4
+operator := "&" | "<<" | ">>" | "@"
+.PP
+The operators \fB&\fP, \fB<<\fP, \fB>>\fP and \fB&&\fP mean the same as in C.
+The \fB=\fP is really a set membership operator and the value syntax describes
+a set. The \fB@\fP operator is what allows moving to the next header and is
+described further below.
+.PP
+There are currently some artificial implementation limits on the size of the
+tests:
+.IP "    *"
+no more than 10 of "\fB=\fP" (and 9 "\fB&&\fP"s) in the u32 argument
+.IP "    *"
+no more than 10 ranges (and 9 commas) per value
+.IP "    *"
+no more than 10 numbers (and 9 operators) per location
+.PP
+To describe the meaning of location, imagine the following machine that
+interprets it. There are three registers:
+.IP
+A is of type \fBchar *\fP, initially the address of the IP header
+.IP
+B and C are unsigned 32 bit integers, initially zero
+.PP
+The instructions are:
+.TP
+.B number
+B = number;
+.IP
+C = (*(A+B)<<24) + (*(A+B+1)<<16) + (*(A+B+2)<<8) + *(A+B+3)
+.TP
+.B &number
+C = C & number
+.TP
+.B << number
+C = C << number
+.TP
+.B >> number
+C = C >> number
+.TP
+.B @number
+A = A + C; then do the instruction number
+.PP
+Any access of memory outside [skb\->data,skb\->end] causes the match to fail.
+Otherwise the result of the computation is the final value of C.
+.PP
+Whitespace is allowed but not required in the tests. However, the characters
+that do occur there are likely to require shell quoting, so it is a good idea
+to enclose the arguments in quotes.
+.PP
+Example:
+.IP
+match IP packets with total length >= 256
+.IP
+The IP header contains a total length field in bytes 2-3.
+.IP
+\-\-u32 "\fB0 & 0xFFFF = 0x100:0xFFFF\fP"
+.IP
+read bytes 0-3
+.IP
+AND that with 0xFFFF (giving bytes 2-3), and test whether that is in the range
+[0x100:0xFFFF]
+.PP
+Example: (more realistic, hence more complicated)
+.IP
+match ICMP packets with icmp type 0
+.IP
+First test that it is an ICMP packet, true iff byte 9 (protocol) = 1
+.IP
+\-\-u32 "\fB6 & 0xFF = 1 &&\fP ...
+.IP
+read bytes 6-9, use \fB&\fP to throw away bytes 6-8 and compare the result to
+1. Next test that it is not a fragment. (If so, it might be part of such a
+packet but we cannot always tell.) N.B.: This test is generally needed if you
+want to match anything beyond the IP header. The last 6 bits of byte 6 and all
+of byte 7 are 0 iff this is a complete packet (not a fragment). Alternatively,
+you can allow first fragments by only testing the last 5 bits of byte 6.
+.IP
+ ... \fB4 & 0x3FFF = 0 &&\fP ...
+.IP
+Last test: the first byte past the IP header (the type) is 0. This is where we
+have to use the @syntax. The length of the IP header (IHL) in 32 bit words is
+stored in the right half of byte 0 of the IP header itself.
+.IP
+ ... \fB0 >> 22 & 0x3C @ 0 >> 24 = 0\fP"
+.IP
+The first 0 means read bytes 0-3, \fB>>22\fP means shift that 22 bits to the
+right. Shifting 24 bits would give the first byte, so only 22 bits is four
+times that plus a few more bits. \fB&3C\fP then eliminates the two extra bits
+on the right and the first four bits of the first byte. For instance, if IHL=5,
+then the IP header is 20 (4 x 5) bytes long. In this case, bytes 0-1 are (in
+binary) xxxx0101 yyzzzzzz, \fB>>22\fP gives the 10 bit value xxxx0101yy and
+\fB&3C\fP gives 010100. \fB@\fP means to use this number as a new offset into
+the packet, and read four bytes starting from there. This is the first 4 bytes
+of the ICMP payload, of which byte 0 is the ICMP type. Therefore, we simply
+shift the value 24 to the right to throw out all but the first byte and compare
+the result with 0.
+.PP
+Example:
+.IP
+TCP payload bytes 8-12 is any of 1, 2, 5 or 8
+.IP
+First we test that the packet is a tcp packet (similar to ICMP).
+.IP
+\-\-u32 "\fB6 & 0xFF = 6 &&\fP ...
+.IP
+Next, test that it is not a fragment (same as above).
+.IP
+ ... \fB0 >> 22 & 0x3C @ 12 >> 26 & 0x3C @ 8 = 1,2,5,8\fP"
+.IP
+\fB0>>22&3C\fP as above computes the number of bytes in the IP header. \fB@\fP
+makes this the new offset into the packet, which is the start of the TCP
+header. The length of the TCP header (again in 32 bit words) is the left half
+of byte 12 of the TCP header. The \fB12>>26&3C\fP computes this length in bytes
+(similar to the IP header before). "@" makes this the new offset, which is the
+start of the TCP payload. Finally, 8 reads bytes 8-12 of the payload and
+\fB=\fP checks whether the result is any of 1, 2, 5 or 8.
diff --git a/extensions/libxt_u32.t b/extensions/libxt_u32.t
new file mode 100644
index 0000000..0d9be47
--- /dev/null
+++ b/extensions/libxt_u32.t
@@ -0,0 +1,2 @@
+:INPUT,FORWARD,OUTPUT
+-m u32 --u32 "0x0=0x0&&0x0=0x1";=;OK
diff --git a/extensions/libxt_udp.c b/extensions/libxt_udp.c
new file mode 100644
index 0000000..0c7a4bc
--- /dev/null
+++ b/extensions/libxt_udp.c
@@ -0,0 +1,212 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_tcpudp.h>
+
+enum {
+	O_SOURCE_PORT = 0,
+	O_DEST_PORT,
+};
+
+static void udp_help(void)
+{
+	printf(
+"udp match options:\n"
+"[!] --source-port port[:port]\n"
+" --sport ...\n"
+"				match source port(s)\n"
+"[!] --destination-port port[:port]\n"
+" --dport ...\n"
+"				match destination port(s)\n");
+}
+
+#define s struct xt_udp
+static const struct xt_option_entry udp_opts[] = {
+	{.name = "source-port", .id = O_SOURCE_PORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, spts)},
+	{.name = "sport", .id = O_SOURCE_PORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, spts)},
+	{.name = "destination-port", .id = O_DEST_PORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, dpts)},
+	{.name = "dport", .id = O_DEST_PORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, dpts)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void udp_init(struct xt_entry_match *m)
+{
+	struct xt_udp *udpinfo = (struct xt_udp *)m->data;
+
+	udpinfo->spts[1] = udpinfo->dpts[1] = 0xFFFF;
+}
+
+static void udp_parse(struct xt_option_call *cb)
+{
+	struct xt_udp *udpinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SOURCE_PORT:
+		if (cb->invert)
+			udpinfo->invflags |= XT_UDP_INV_SRCPT;
+		break;
+	case O_DEST_PORT:
+		if (cb->invert)
+			udpinfo->invflags |= XT_UDP_INV_DSTPT;
+		break;
+	}
+}
+
+static const char *
+port_to_service(int port)
+{
+	const struct servent *service;
+
+	if ((service = getservbyport(htons(port), "udp")))
+		return service->s_name;
+
+	return NULL;
+}
+
+static void
+print_port(uint16_t port, int numeric)
+{
+	const char *service;
+
+	if (numeric || (service = port_to_service(port)) == NULL)
+		printf("%u", port);
+	else
+		printf("%s", service);
+}
+
+static void
+print_ports(const char *name, uint16_t min, uint16_t max,
+	    int invert, int numeric)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFF || invert) {
+		printf(" %s", name);
+		if (min == max) {
+			printf(":%s", inv);
+			print_port(min, numeric);
+		} else {
+			printf("s:%s", inv);
+			print_port(min, numeric);
+			printf(":");
+			print_port(max, numeric);
+		}
+	}
+}
+
+static void
+udp_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_udp *udp = (struct xt_udp *)match->data;
+
+	printf(" udp");
+	print_ports("spt", udp->spts[0], udp->spts[1],
+		    udp->invflags & XT_UDP_INV_SRCPT,
+		    numeric);
+	print_ports("dpt", udp->dpts[0], udp->dpts[1],
+		    udp->invflags & XT_UDP_INV_DSTPT,
+		    numeric);
+	if (udp->invflags & ~XT_UDP_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       udp->invflags & ~XT_UDP_INV_MASK);
+}
+
+static void udp_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_udp *udpinfo = (struct xt_udp *)match->data;
+
+	if (udpinfo->spts[0] != 0
+	    || udpinfo->spts[1] != 0xFFFF) {
+		if (udpinfo->invflags & XT_UDP_INV_SRCPT)
+			printf(" !");
+		if (udpinfo->spts[0]
+		    != udpinfo->spts[1])
+			printf(" --sport %u:%u",
+			       udpinfo->spts[0],
+			       udpinfo->spts[1]);
+		else
+			printf(" --sport %u",
+			       udpinfo->spts[0]);
+	}
+
+	if (udpinfo->dpts[0] != 0
+	    || udpinfo->dpts[1] != 0xFFFF) {
+		if (udpinfo->invflags & XT_UDP_INV_DSTPT)
+			printf(" !");
+		if (udpinfo->dpts[0]
+		    != udpinfo->dpts[1])
+			printf(" --dport %u:%u",
+			       udpinfo->dpts[0],
+			       udpinfo->dpts[1]);
+		else
+			printf(" --dport %u",
+			       udpinfo->dpts[0]);
+	}
+}
+
+static int udp_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_udp *udpinfo = (struct xt_udp *)params->match->data;
+	char *space= "";
+
+	if (udpinfo->spts[0] != 0 || udpinfo->spts[1] != 0xFFFF) {
+		if (udpinfo->spts[0] != udpinfo->spts[1]) {
+			xt_xlate_add(xl,"udp sport %s%u-%u",
+				   udpinfo->invflags & XT_UDP_INV_SRCPT ?
+					 "!= ": "",
+				   udpinfo->spts[0], udpinfo->spts[1]);
+		} else {
+			xt_xlate_add(xl, "udp sport %s%u",
+				   udpinfo->invflags & XT_UDP_INV_SRCPT ?
+					 "!= ": "",
+				   udpinfo->spts[0]);
+		}
+		space = " ";
+	}
+
+	if (udpinfo->dpts[0] != 0 || udpinfo->dpts[1] != 0xFFFF) {
+		if (udpinfo->dpts[0]  != udpinfo->dpts[1]) {
+			xt_xlate_add(xl,"%sudp dport %s%u-%u", space,
+				   udpinfo->invflags & XT_UDP_INV_SRCPT ?
+					 "!= ": "",
+				   udpinfo->dpts[0], udpinfo->dpts[1]);
+		} else {
+			xt_xlate_add(xl,"%sudp dport %s%u", space,
+				   udpinfo->invflags & XT_UDP_INV_SRCPT ?
+					 "!= ": "",
+				   udpinfo->dpts[0]);
+		}
+	}
+
+	return 1;
+}
+
+static struct xtables_match udp_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "udp",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_udp)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_udp)),
+	.help		= udp_help,
+	.init		= udp_init,
+	.print		= udp_print,
+	.save		= udp_save,
+	.x6_parse	= udp_parse,
+	.x6_options	= udp_opts,
+	.xlate		= udp_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&udp_match);
+}
diff --git a/extensions/libxt_udp.man b/extensions/libxt_udp.man
new file mode 100644
index 0000000..5339c8e
--- /dev/null
+++ b/extensions/libxt_udp.man
@@ -0,0 +1,14 @@
+These extensions can be used if `\-\-protocol udp' is specified. It
+provides the following options:
+.TP
+[\fB!\fP] \fB\-\-source\-port\fP,\fB\-\-sport\fP \fIport\fP[\fB:\fP\fIport\fP]
+Source port or port range specification.
+See the description of the
+\fB\-\-source\-port\fP
+option of the TCP extension for details.
+.TP
+[\fB!\fP] \fB\-\-destination\-port\fP,\fB\-\-dport\fP \fIport\fP[\fB:\fP\fIport\fP]
+Destination port or port range specification.
+See the description of the
+\fB\-\-destination\-port\fP
+option of the TCP extension for details.
diff --git a/extensions/libxt_udp.t b/extensions/libxt_udp.t
new file mode 100644
index 0000000..1b4d3dd
--- /dev/null
+++ b/extensions/libxt_udp.t
@@ -0,0 +1,22 @@
+:INPUT,OUTPUT,FORWARD
+-p udp -m udp --sport 1;=;OK
+-p udp -m udp --sport 65535;=;OK
+-p udp -m udp --dport 1;=;OK
+-p udp -m udp --dport 65535;=;OK
+-p udp -m udp --sport 1:1023;=;OK
+-p udp -m udp --sport 1024:65535;=;OK
+-p udp -m udp --sport 1024:;-p udp -m udp --sport 1024:65535;OK
+-p udp -m udp ! --sport 1;=;OK
+-p udp -m udp ! --sport 65535;=;OK
+-p udp -m udp ! --dport 1;=;OK
+-p udp -m udp ! --dport 65535;=;OK
+-p udp -m udp --sport 1 --dport 65535;=;OK
+-p udp -m udp --sport 65535 --dport 1;=;OK
+-p udp -m udp ! --sport 1 --dport 65535;=;OK
+-p udp -m udp ! --sport 65535 --dport 1;=;OK
+# ERRROR: should fail: iptables -A INPUT -p udp -m udp --sport 65536
+# -p udp -m udp --sport 65536;;FAIL
+-p udp -m udp --sport -1;;FAIL
+-p udp -m udp --dport -1;;FAIL
+# should we accept this below?
+-p udp -m udp;=;OK
diff --git a/extensions/libxt_udp.txlate b/extensions/libxt_udp.txlate
new file mode 100644
index 0000000..fbca5c1
--- /dev/null
+++ b/extensions/libxt_udp.txlate
@@ -0,0 +1,11 @@
+iptables-translate -A INPUT -p udp -i eth0 --sport 53 -j ACCEPT
+nft add rule ip filter INPUT iifname "eth0" udp sport 53 counter accept
+
+iptables-translate -A OUTPUT -p udp -o eth0 --dport 53:66 -j DROP
+nft add rule ip filter OUTPUT oifname "eth0" udp dport 53-66 counter drop
+
+iptables-translate -I OUTPUT -p udp -d 8.8.8.8 -j ACCEPT
+nft insert rule ip filter OUTPUT ip protocol udp ip daddr 8.8.8.8 counter accept
+
+iptables-translate -I OUTPUT -p udp --dport 1020:1023 --sport 53 -j ACCEPT
+nft insert rule ip filter OUTPUT udp sport 53 udp dport 1020-1023 counter accept
diff --git a/extensions/tos_values.c b/extensions/tos_values.c
new file mode 100644
index 0000000..6dc4743
--- /dev/null
+++ b/extensions/tos_values.c
@@ -0,0 +1,41 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <linux/ip.h>
+
+#ifndef IPTOS_NORMALSVC
+#	define IPTOS_NORMALSVC 0
+#endif
+
+struct tos_value_mask {
+	uint8_t value, mask;
+};
+
+static const struct tos_symbol_info {
+	unsigned char value;
+	const char *name;
+} tos_symbol_names[] = {
+	{IPTOS_LOWDELAY,    "Minimize-Delay"},
+	{IPTOS_THROUGHPUT,  "Maximize-Throughput"},
+	{IPTOS_RELIABILITY, "Maximize-Reliability"},
+	{IPTOS_MINCOST,     "Minimize-Cost"},
+	{IPTOS_NORMALSVC,   "Normal-Service"},
+	{},
+};
+
+static bool tos_try_print_symbolic(const char *prefix,
+    uint8_t value, uint8_t mask)
+{
+	const struct tos_symbol_info *symbol;
+
+	if (mask != 0x3F)
+		return false;
+
+	for (symbol = tos_symbol_names; symbol->name != NULL; ++symbol)
+		if (value == symbol->value) {
+			printf(" %s%s", prefix, symbol->name);
+			return true;
+		}
+
+	return false;
+}
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 0000000..ea34c2f
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1,17 @@
+# -*- Makefile -*-
+
+include_HEADERS =
+nobase_include_HEADERS = xtables.h xtables-version.h
+
+if ENABLE_LIBIPQ
+include_HEADERS += libipq/libipq.h
+endif
+
+nobase_include_HEADERS += \
+	libiptc/ipt_kernel_headers.h libiptc/libiptc.h \
+	libiptc/libip6tc.h libiptc/libxtc.h libiptc/xtcshared.h
+
+uninstall-hook:
+	dir=${includedir}/libiptc; { \
+		test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; \
+	} || rmdir -p --ignore-fail-on-non-empty "$$dir"
diff --git a/include/ip6tables.h b/include/ip6tables.h
new file mode 100644
index 0000000..5f1c5b6
--- /dev/null
+++ b/include/ip6tables.h
@@ -0,0 +1,20 @@
+#ifndef _IP6TABLES_USER_H
+#define _IP6TABLES_USER_H
+
+#include <netinet/ip.h>
+#include <xtables.h>
+#include <libiptc/libip6tc.h>
+#include <iptables/internal.h>
+
+/* Your shared library should call one of these. */
+extern int do_command6(int argc, char *argv[], char **table,
+		       struct xtc_handle **handle, bool restore);
+
+extern int for_each_chain6(int (*fn)(const xt_chainlabel, int, struct xtc_handle *), int verbose, int builtinstoo, struct xtc_handle *handle);
+extern int flush_entries6(const xt_chainlabel chain, int verbose, struct xtc_handle *handle);
+extern int delete_chain6(const xt_chainlabel chain, int verbose, struct xtc_handle *handle);
+void print_rule6(const struct ip6t_entry *e, struct xtc_handle *h, const char *chain, int counters);
+
+extern struct xtables_globals ip6tables_globals;
+
+#endif /*_IP6TABLES_USER_H*/
diff --git a/include/iptables.h b/include/iptables.h
new file mode 100644
index 0000000..78c10ab
--- /dev/null
+++ b/include/iptables.h
@@ -0,0 +1,25 @@
+#ifndef _IPTABLES_USER_H
+#define _IPTABLES_USER_H
+
+#include <netinet/ip.h>
+#include <xtables.h>
+#include <libiptc/libiptc.h>
+#include <iptables/internal.h>
+
+/* Your shared library should call one of these. */
+extern int do_command4(int argc, char *argv[], char **table,
+		      struct xtc_handle **handle, bool restore);
+extern int delete_chain4(const xt_chainlabel chain, int verbose,
+			struct xtc_handle *handle);
+extern int flush_entries4(const xt_chainlabel chain, int verbose, 
+			struct xtc_handle *handle);
+extern int for_each_chain4(int (*fn)(const xt_chainlabel, int, struct xtc_handle *),
+		int verbose, int builtinstoo, struct xtc_handle *handle);
+extern void print_rule4(const struct ipt_entry *e,
+		struct xtc_handle *handle, const char *chain, int counters);
+
+extern struct xtables_globals iptables_globals;
+
+extern struct xtables_globals xtables_globals;
+
+#endif /*_IPTABLES_USER_H*/
diff --git a/include/iptables/internal.h b/include/iptables/internal.h
new file mode 100644
index 0000000..86ba074
--- /dev/null
+++ b/include/iptables/internal.h
@@ -0,0 +1,11 @@
+#ifndef IPTABLES_INTERNAL_H
+#define IPTABLES_INTERNAL_H 1
+
+/**
+ * Program's own name and version.
+ */
+extern const char *program_name, *program_version;
+
+extern int line;
+
+#endif /* IPTABLES_INTERNAL_H */
diff --git a/include/libipq/libipq.h b/include/libipq/libipq.h
new file mode 100644
index 0000000..3cd1329
--- /dev/null
+++ b/include/libipq/libipq.h
@@ -0,0 +1,83 @@
+/*
+ * libipq.h
+ *
+ * IPQ library for userspace.
+ *
+ * Author: James Morris <jmorris@intercode.com.au>
+ *
+ * Copyright (c) 2000-2001 Netfilter Core Team
+ *
+ * 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 2 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.
+ *
+ */
+#ifndef _LIBIPQ_H
+#define _LIBIPQ_H
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <asm/types.h>
+#include <linux/netlink.h>
+
+#include <linux/netfilter_ipv4/ip_queue.h>
+typedef unsigned long ipq_id_t;
+
+#ifdef DEBUG_LIBIPQ
+#include <stdio.h>
+#define LDEBUG(x...) fprintf(stderr, ## x)
+#else
+#define LDEBUG(x...)
+#endif	/* DEBUG_LIBIPQ */
+
+/* FIXME: glibc sucks */
+#ifndef MSG_TRUNC
+#define MSG_TRUNC 0x20
+#endif
+
+struct ipq_handle
+{
+	int fd;
+	u_int8_t blocking;
+	struct sockaddr_nl local;
+	struct sockaddr_nl peer;
+};
+
+struct ipq_handle *ipq_create_handle(u_int32_t flags, u_int32_t protocol);
+
+int ipq_destroy_handle(struct ipq_handle *h);
+
+ssize_t ipq_read(const struct ipq_handle *h,
+                unsigned char *buf, size_t len, int timeout);
+
+int ipq_set_mode(const struct ipq_handle *h, u_int8_t mode, size_t len);
+
+ipq_packet_msg_t *ipq_get_packet(const unsigned char *buf);
+
+int ipq_message_type(const unsigned char *buf);
+
+int ipq_get_msgerr(const unsigned char *buf);
+
+int ipq_set_verdict(const struct ipq_handle *h,
+                    ipq_id_t id,
+                    unsigned int verdict,
+                    size_t data_len,
+                    unsigned char *buf);
+
+int ipq_ctl(const struct ipq_handle *h, int request, ...);
+
+char *ipq_errstr(void);
+void ipq_perror(const char *s);
+
+#endif	/* _LIBIPQ_H */
+
diff --git a/include/libiptc/ipt_kernel_headers.h b/include/libiptc/ipt_kernel_headers.h
new file mode 100644
index 0000000..a5963e9
--- /dev/null
+++ b/include/libiptc/ipt_kernel_headers.h
@@ -0,0 +1,15 @@
+/* This is the userspace/kernel interface for Generic IP Chains,
+   required for libc6. */
+#ifndef _FWCHAINS_KERNEL_HEADERS_H
+#define _FWCHAINS_KERNEL_HEADERS_H
+
+#include <limits.h>
+
+#include <netinet/ip.h>
+#include <netinet/in.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <net/if.h>
+#include <sys/types.h>
+#endif
diff --git a/include/libiptc/libip6tc.h b/include/libiptc/libip6tc.h
new file mode 100644
index 0000000..9aed80a
--- /dev/null
+++ b/include/libiptc/libip6tc.h
@@ -0,0 +1,161 @@
+#ifndef _LIBIP6TC_H
+#define _LIBIP6TC_H
+/* Library which manipulates firewall rules. Version 0.2. */
+
+#include <linux/types.h>
+#include <libiptc/ipt_kernel_headers.h>
+#ifdef __cplusplus
+#	include <climits>
+#else
+#	include <limits.h> /* INT_MAX in ip6_tables.h */
+#endif
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <libiptc/xtcshared.h>
+
+#define ip6tc_handle xtc_handle
+#define ip6t_chainlabel xt_chainlabel
+
+#define IP6TC_LABEL_ACCEPT "ACCEPT"
+#define IP6TC_LABEL_DROP "DROP"
+#define IP6TC_LABEL_QUEUE   "QUEUE"
+#define IP6TC_LABEL_RETURN "RETURN"
+
+/* Does this chain exist? */
+int ip6tc_is_chain(const char *chain, struct xtc_handle *const handle);
+
+/* Take a snapshot of the rules. Returns NULL on error. */
+struct xtc_handle *ip6tc_init(const char *tablename);
+
+/* Cleanup after ip6tc_init(). */
+void ip6tc_free(struct xtc_handle *h);
+
+/* Iterator functions to run through the chains.  Returns NULL at end. */
+const char *ip6tc_first_chain(struct xtc_handle *handle);
+const char *ip6tc_next_chain(struct xtc_handle *handle);
+
+/* Get first rule in the given chain: NULL for empty chain. */
+const struct ip6t_entry *ip6tc_first_rule(const char *chain,
+					  struct xtc_handle *handle);
+
+/* Returns NULL when rules run out. */
+const struct ip6t_entry *ip6tc_next_rule(const struct ip6t_entry *prev,
+					 struct xtc_handle *handle);
+
+/* Returns a pointer to the target name of this position. */
+const char *ip6tc_get_target(const struct ip6t_entry *e,
+			     struct xtc_handle *handle);
+
+/* Is this a built-in chain? */
+int ip6tc_builtin(const char *chain, struct xtc_handle *const handle);
+
+/* Get the policy of a given built-in chain */
+const char *ip6tc_get_policy(const char *chain,
+			     struct xt_counters *counters,
+			     struct xtc_handle *handle);
+
+/* These functions return TRUE for OK or 0 and set errno. If errno ==
+   0, it means there was a version error (ie. upgrade libiptc). */
+/* Rule numbers start at 1 for the first rule. */
+
+/* Insert the entry `fw' in chain `chain' into position `rulenum'. */
+int ip6tc_insert_entry(const xt_chainlabel chain,
+		       const struct ip6t_entry *e,
+		       unsigned int rulenum,
+		       struct xtc_handle *handle);
+
+/* Atomically replace rule `rulenum' in `chain' with `fw'. */
+int ip6tc_replace_entry(const xt_chainlabel chain,
+			const struct ip6t_entry *e,
+			unsigned int rulenum,
+			struct xtc_handle *handle);
+
+/* Append entry `fw' to chain `chain'. Equivalent to insert with
+   rulenum = length of chain. */
+int ip6tc_append_entry(const xt_chainlabel chain,
+		       const struct ip6t_entry *e,
+		       struct xtc_handle *handle);
+
+/* Check whether a matching rule exists */
+int ip6tc_check_entry(const xt_chainlabel chain,
+		       const struct ip6t_entry *origfw,
+		       unsigned char *matchmask,
+		       struct xtc_handle *handle);
+
+/* Delete the first rule in `chain' which matches `fw'. */
+int ip6tc_delete_entry(const xt_chainlabel chain,
+		       const struct ip6t_entry *origfw,
+		       unsigned char *matchmask,
+		       struct xtc_handle *handle);
+
+/* Delete the rule in position `rulenum' in `chain'. */
+int ip6tc_delete_num_entry(const xt_chainlabel chain,
+			   unsigned int rulenum,
+			   struct xtc_handle *handle);
+
+/* Check the packet `fw' on chain `chain'. Returns the verdict, or
+   NULL and sets errno. */
+const char *ip6tc_check_packet(const xt_chainlabel chain,
+			       struct ip6t_entry *,
+			       struct xtc_handle *handle);
+
+/* Flushes the entries in the given chain (ie. empties chain). */
+int ip6tc_flush_entries(const xt_chainlabel chain,
+			struct xtc_handle *handle);
+
+/* Zeroes the counters in a chain. */
+int ip6tc_zero_entries(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
+
+/* Creates a new chain. */
+int ip6tc_create_chain(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
+
+/* Deletes a chain. */
+int ip6tc_delete_chain(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
+
+/* Renames a chain. */
+int ip6tc_rename_chain(const xt_chainlabel oldname,
+		       const xt_chainlabel newname,
+		       struct xtc_handle *handle);
+
+/* Sets the policy on a built-in chain. */
+int ip6tc_set_policy(const xt_chainlabel chain,
+		     const xt_chainlabel policy,
+		     struct xt_counters *counters,
+		     struct xtc_handle *handle);
+
+/* Get the number of references to this chain */
+int ip6tc_get_references(unsigned int *ref, const xt_chainlabel chain,
+			 struct xtc_handle *handle);
+
+/* read packet and byte counters for a specific rule */
+struct xt_counters *ip6tc_read_counter(const xt_chainlabel chain,
+					unsigned int rulenum,
+					struct xtc_handle *handle);
+
+/* zero packet and byte counters for a specific rule */
+int ip6tc_zero_counter(const xt_chainlabel chain,
+		       unsigned int rulenum,
+		       struct xtc_handle *handle);
+
+/* set packet and byte counters for a specific rule */
+int ip6tc_set_counter(const xt_chainlabel chain,
+		      unsigned int rulenum,
+		      struct xt_counters *counters,
+		      struct xtc_handle *handle);
+
+/* Makes the actual changes. */
+int ip6tc_commit(struct xtc_handle *handle);
+
+/* Get raw socket. */
+int ip6tc_get_raw_socket(void);
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *ip6tc_strerror(int err);
+
+extern void dump_entries6(struct xtc_handle *const);
+
+extern const struct xtc_ops ip6tc_ops;
+
+#endif /* _LIBIP6TC_H */
diff --git a/include/libiptc/libiptc.h b/include/libiptc/libiptc.h
new file mode 100644
index 0000000..24cdbdb
--- /dev/null
+++ b/include/libiptc/libiptc.h
@@ -0,0 +1,172 @@
+#ifndef _LIBIPTC_H
+#define _LIBIPTC_H
+/* Library which manipulates filtering rules. */
+
+#include <linux/types.h>
+#include <libiptc/ipt_kernel_headers.h>
+#ifdef __cplusplus
+#	include <climits>
+#else
+#	include <limits.h> /* INT_MAX in ip_tables.h */
+#endif
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <libiptc/xtcshared.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define iptc_handle xtc_handle
+#define ipt_chainlabel xt_chainlabel
+
+#define IPTC_LABEL_ACCEPT  "ACCEPT"
+#define IPTC_LABEL_DROP    "DROP"
+#define IPTC_LABEL_QUEUE   "QUEUE"
+#define IPTC_LABEL_RETURN  "RETURN"
+
+/* Does this chain exist? */
+int iptc_is_chain(const char *chain, struct xtc_handle *const handle);
+
+/* Take a snapshot of the rules.  Returns NULL on error. */
+struct xtc_handle *iptc_init(const char *tablename);
+
+/* Cleanup after iptc_init(). */
+void iptc_free(struct xtc_handle *h);
+
+/* Iterator functions to run through the chains.  Returns NULL at end. */
+const char *iptc_first_chain(struct xtc_handle *handle);
+const char *iptc_next_chain(struct xtc_handle *handle);
+
+/* Get first rule in the given chain: NULL for empty chain. */
+const struct ipt_entry *iptc_first_rule(const char *chain,
+					struct xtc_handle *handle);
+
+/* Returns NULL when rules run out. */
+const struct ipt_entry *iptc_next_rule(const struct ipt_entry *prev,
+				       struct xtc_handle *handle);
+
+/* Returns a pointer to the target name of this entry. */
+const char *iptc_get_target(const struct ipt_entry *e,
+			    struct xtc_handle *handle);
+
+/* Is this a built-in chain? */
+int iptc_builtin(const char *chain, struct xtc_handle *const handle);
+
+/* Get the policy of a given built-in chain */
+const char *iptc_get_policy(const char *chain,
+			    struct xt_counters *counter,
+			    struct xtc_handle *handle);
+
+/* These functions return TRUE for OK or 0 and set errno.  If errno ==
+   0, it means there was a version error (ie. upgrade libiptc). */
+/* Rule numbers start at 1 for the first rule. */
+
+/* Insert the entry `e' in chain `chain' into position `rulenum'. */
+int iptc_insert_entry(const xt_chainlabel chain,
+		      const struct ipt_entry *e,
+		      unsigned int rulenum,
+		      struct xtc_handle *handle);
+
+/* Atomically replace rule `rulenum' in `chain' with `e'. */
+int iptc_replace_entry(const xt_chainlabel chain,
+		       const struct ipt_entry *e,
+		       unsigned int rulenum,
+		       struct xtc_handle *handle);
+
+/* Append entry `e' to chain `chain'.  Equivalent to insert with
+   rulenum = length of chain. */
+int iptc_append_entry(const xt_chainlabel chain,
+		      const struct ipt_entry *e,
+		      struct xtc_handle *handle);
+
+/* Check whether a mathching rule exists */
+int iptc_check_entry(const xt_chainlabel chain,
+		      const struct ipt_entry *origfw,
+		      unsigned char *matchmask,
+		      struct xtc_handle *handle);
+
+/* Delete the first rule in `chain' which matches `e', subject to
+   matchmask (array of length == origfw) */
+int iptc_delete_entry(const xt_chainlabel chain,
+		      const struct ipt_entry *origfw,
+		      unsigned char *matchmask,
+		      struct xtc_handle *handle);
+
+/* Delete the rule in position `rulenum' in `chain'. */
+int iptc_delete_num_entry(const xt_chainlabel chain,
+			  unsigned int rulenum,
+			  struct xtc_handle *handle);
+
+/* Check the packet `e' on chain `chain'.  Returns the verdict, or
+   NULL and sets errno. */
+const char *iptc_check_packet(const xt_chainlabel chain,
+			      struct ipt_entry *entry,
+			      struct xtc_handle *handle);
+
+/* Flushes the entries in the given chain (ie. empties chain). */
+int iptc_flush_entries(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
+
+/* Zeroes the counters in a chain. */
+int iptc_zero_entries(const xt_chainlabel chain,
+		      struct xtc_handle *handle);
+
+/* Creates a new chain. */
+int iptc_create_chain(const xt_chainlabel chain,
+		      struct xtc_handle *handle);
+
+/* Deletes a chain. */
+int iptc_delete_chain(const xt_chainlabel chain,
+		      struct xtc_handle *handle);
+
+/* Renames a chain. */
+int iptc_rename_chain(const xt_chainlabel oldname,
+		      const xt_chainlabel newname,
+		      struct xtc_handle *handle);
+
+/* Sets the policy on a built-in chain. */
+int iptc_set_policy(const xt_chainlabel chain,
+		    const xt_chainlabel policy,
+		    struct xt_counters *counters,
+		    struct xtc_handle *handle);
+
+/* Get the number of references to this chain */
+int iptc_get_references(unsigned int *ref,
+			const xt_chainlabel chain,
+			struct xtc_handle *handle);
+
+/* read packet and byte counters for a specific rule */
+struct xt_counters *iptc_read_counter(const xt_chainlabel chain,
+				       unsigned int rulenum,
+				       struct xtc_handle *handle);
+
+/* zero packet and byte counters for a specific rule */
+int iptc_zero_counter(const xt_chainlabel chain,
+		      unsigned int rulenum,
+		      struct xtc_handle *handle);
+
+/* set packet and byte counters for a specific rule */
+int iptc_set_counter(const xt_chainlabel chain,
+		     unsigned int rulenum,
+		     struct xt_counters *counters,
+		     struct xtc_handle *handle);
+
+/* Makes the actual changes. */
+int iptc_commit(struct xtc_handle *handle);
+
+/* Get raw socket. */
+int iptc_get_raw_socket(void);
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *iptc_strerror(int err);
+
+extern void dump_entries(struct xtc_handle *const);
+
+extern const struct xtc_ops iptc_ops;
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _LIBIPTC_H */
diff --git a/include/libiptc/libxtc.h b/include/libiptc/libxtc.h
new file mode 100644
index 0000000..3701018
--- /dev/null
+++ b/include/libiptc/libxtc.h
@@ -0,0 +1,33 @@
+#ifndef _LIBXTC_H
+#define _LIBXTC_H
+/* Library which manipulates filtering rules. */
+
+#include <libiptc/ipt_kernel_headers.h>
+#include <linux/netfilter/x_tables.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef XT_MIN_ALIGN
+/* xt_entry has pointers and u_int64_t's in it, so if you align to
+   it, you'll also align to any crazy matches and targets someone
+   might write */
+#define XT_MIN_ALIGN (__alignof__(struct xt_entry))
+#endif
+
+#ifndef XT_ALIGN
+#define XT_ALIGN(s) (((s) + ((XT_MIN_ALIGN)-1)) & ~((XT_MIN_ALIGN)-1))
+#endif
+
+#define XTC_LABEL_ACCEPT  "ACCEPT"
+#define XTC_LABEL_DROP    "DROP"
+#define XTC_LABEL_QUEUE   "QUEUE"
+#define XTC_LABEL_RETURN  "RETURN"
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBXTC_H */
diff --git a/include/libiptc/xtcshared.h b/include/libiptc/xtcshared.h
new file mode 100644
index 0000000..341f9d4
--- /dev/null
+++ b/include/libiptc/xtcshared.h
@@ -0,0 +1,25 @@
+#ifndef _LIBXTC_SHARED_H
+#define _LIBXTC_SHARED_H 1
+
+typedef char xt_chainlabel[32];
+struct xtc_handle;
+struct xt_counters;
+
+struct xtc_ops {
+	int (*commit)(struct xtc_handle *);
+	struct xtc_handle *(*init)(const char *);
+	void (*free)(struct xtc_handle *);
+	int (*builtin)(const char *, struct xtc_handle *const);
+	int (*is_chain)(const char *, struct xtc_handle *const);
+	int (*flush_entries)(const xt_chainlabel, struct xtc_handle *);
+	int (*create_chain)(const xt_chainlabel, struct xtc_handle *);
+	const char *(*first_chain)(struct xtc_handle *);
+	const char *(*next_chain)(struct xtc_handle *);
+	const char *(*get_policy)(const char *, struct xt_counters *,
+				  struct xtc_handle *);
+	int (*set_policy)(const xt_chainlabel, const xt_chainlabel,
+			  struct xt_counters *, struct xtc_handle *);
+	const char *(*strerror)(int);
+};
+
+#endif /* _LIBXTC_SHARED_H */
diff --git a/include/libipulog/libipulog.h b/include/libipulog/libipulog.h
new file mode 100644
index 0000000..3f4cc2c
--- /dev/null
+++ b/include/libipulog/libipulog.h
@@ -0,0 +1,39 @@
+#ifndef _LIBIPULOG_H
+#define _LIBIPULOG_H
+
+/* libipulog.h,v 1.3 2001/05/21 19:15:16 laforge Exp */
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <net/if.h>
+#include <linux/netfilter_ipv4/ipt_ULOG.h>
+
+/* FIXME: glibc sucks */
+#ifndef MSG_TRUNC 
+#define MSG_TRUNC	0x20
+#endif
+
+struct ipulog_handle;
+
+u_int32_t ipulog_group2gmask(u_int32_t group);
+
+struct ipulog_handle *ipulog_create_handle(u_int32_t gmask);
+
+void ipulog_destroy_handle(struct ipulog_handle *h);
+
+ssize_t ipulog_read(struct ipulog_handle *h,
+		    unsigned char *buf, size_t len, int timeout);
+
+ulog_packet_msg_t *ipulog_get_packet(struct ipulog_handle *h,
+				     const unsigned char *buf,
+				     size_t len);
+
+void ipulog_perror(const char *s);
+
+#endif /* _LIBULOG_H */
diff --git a/include/linux/filter.h b/include/linux/filter.h
new file mode 100644
index 0000000..a9ae93c
--- /dev/null
+++ b/include/linux/filter.h
@@ -0,0 +1,139 @@
+/*
+ * Linux Socket Filter Data Structures
+ */
+
+#ifndef __LINUX_FILTER_H__
+#define __LINUX_FILTER_H__
+
+
+#include <linux/types.h>
+
+
+/*
+ * Current version of the filter code architecture.
+ */
+#define BPF_MAJOR_VERSION 1
+#define BPF_MINOR_VERSION 1
+
+/*
+ *	Try and keep these values and structures similar to BSD, especially
+ *	the BPF code definitions which need to match so you can share filters
+ */
+ 
+struct sock_filter {	/* Filter block */
+	__u16	code;   /* Actual filter code */
+	__u8	jt;	/* Jump true */
+	__u8	jf;	/* Jump false */
+	__u32	k;      /* Generic multiuse field */
+};
+
+struct sock_fprog {	/* Required for SO_ATTACH_FILTER. */
+	unsigned short		len;	/* Number of filter blocks */
+	struct sock_filter *filter;
+};
+
+/*
+ * Instruction classes
+ */
+
+#define BPF_CLASS(code) ((code) & 0x07)
+#define         BPF_LD          0x00
+#define         BPF_LDX         0x01
+#define         BPF_ST          0x02
+#define         BPF_STX         0x03
+#define         BPF_ALU         0x04
+#define         BPF_JMP         0x05
+#define         BPF_RET         0x06
+#define         BPF_MISC        0x07
+
+/* ld/ldx fields */
+#define BPF_SIZE(code)  ((code) & 0x18)
+#define         BPF_W           0x00
+#define         BPF_H           0x08
+#define         BPF_B           0x10
+#define BPF_MODE(code)  ((code) & 0xe0)
+#define         BPF_IMM         0x00
+#define         BPF_ABS         0x20
+#define         BPF_IND         0x40
+#define         BPF_MEM         0x60
+#define         BPF_LEN         0x80
+#define         BPF_MSH         0xa0
+
+/* alu/jmp fields */
+#define BPF_OP(code)    ((code) & 0xf0)
+#define         BPF_ADD         0x00
+#define         BPF_SUB         0x10
+#define         BPF_MUL         0x20
+#define         BPF_DIV         0x30
+#define         BPF_OR          0x40
+#define         BPF_AND         0x50
+#define         BPF_LSH         0x60
+#define         BPF_RSH         0x70
+#define         BPF_NEG         0x80
+#define		BPF_MOD		0x90
+#define		BPF_XOR		0xa0
+
+#define         BPF_JA          0x00
+#define         BPF_JEQ         0x10
+#define         BPF_JGT         0x20
+#define         BPF_JGE         0x30
+#define         BPF_JSET        0x40
+#define BPF_SRC(code)   ((code) & 0x08)
+#define         BPF_K           0x00
+#define         BPF_X           0x08
+
+/* ret - BPF_K and BPF_X also apply */
+#define BPF_RVAL(code)  ((code) & 0x18)
+#define         BPF_A           0x10
+
+/* misc */
+#define BPF_MISCOP(code) ((code) & 0xf8)
+#define         BPF_TAX         0x00
+#define         BPF_TXA         0x80
+
+#ifndef BPF_MAXINSNS
+#define BPF_MAXINSNS 4096
+#endif
+
+/*
+ * Macros for filter block array initializers.
+ */
+#ifndef BPF_STMT
+#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k }
+#endif
+#ifndef BPF_JUMP
+#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k }
+#endif
+
+/*
+ * Number of scratch memory words for: BPF_ST and BPF_STX
+ */
+#define BPF_MEMWORDS 16
+
+/* RATIONALE. Negative offsets are invalid in BPF.
+   We use them to reference ancillary data.
+   Unlike introduction new instructions, it does not break
+   existing compilers/optimizers.
+ */
+#define SKF_AD_OFF    (-0x1000)
+#define SKF_AD_PROTOCOL 0
+#define SKF_AD_PKTTYPE 	4
+#define SKF_AD_IFINDEX 	8
+#define SKF_AD_NLATTR	12
+#define SKF_AD_NLATTR_NEST	16
+#define SKF_AD_MARK 	20
+#define SKF_AD_QUEUE	24
+#define SKF_AD_HATYPE	28
+#define SKF_AD_RXHASH	32
+#define SKF_AD_CPU	36
+#define SKF_AD_ALU_XOR_X	40
+#define SKF_AD_VLAN_TAG	44
+#define SKF_AD_VLAN_TAG_PRESENT 48
+#define SKF_AD_PAY_OFFSET	52
+#define SKF_AD_RANDOM	56
+#define SKF_AD_MAX	60
+#define SKF_NET_OFF   (-0x100000)
+#define SKF_LL_OFF    (-0x200000)
+
+
+#endif /* __LINUX_FILTER_H__ */
diff --git a/include/linux/kernel.h b/include/linux/kernel.h
new file mode 100644
index 0000000..d4c59f6
--- /dev/null
+++ b/include/linux/kernel.h
@@ -0,0 +1,29 @@
+#ifndef _LINUX_KERNEL_H
+#define _LINUX_KERNEL_H
+
+/*
+ * 'kernel.h' contains some often-used function prototypes etc
+ */
+#define __ALIGN_KERNEL(x, a)		__ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
+#define __ALIGN_KERNEL_MASK(x, mask)	(((x) + (mask)) & ~(mask))
+
+
+#define SI_LOAD_SHIFT	16
+struct sysinfo {
+	long uptime;			/* Seconds since boot */
+	unsigned long loads[3];		/* 1, 5, and 15 minute load averages */
+	unsigned long totalram;		/* Total usable main memory size */
+	unsigned long freeram;		/* Available memory size */
+	unsigned long sharedram;	/* Amount of shared memory */
+	unsigned long bufferram;	/* Memory used by buffers */
+	unsigned long totalswap;	/* Total swap space size */
+	unsigned long freeswap;		/* swap space still available */
+	unsigned short procs;		/* Number of current processes */
+	unsigned short pad;		/* explicit padding for m68k */
+	unsigned long totalhigh;	/* Total high memory size */
+	unsigned long freehigh;		/* Available high memory size */
+	unsigned int mem_unit;		/* Memory unit size in bytes */
+	char _f[20-2*sizeof(long)-sizeof(int)];	/* Padding: libc5 uses this.. */
+};
+
+#endif
diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h
new file mode 100644
index 0000000..042d8b1
--- /dev/null
+++ b/include/linux/netfilter.h
@@ -0,0 +1,80 @@
+#ifndef __LINUX_NETFILTER_H
+#define __LINUX_NETFILTER_H
+
+#include <linux/types.h>
+
+#ifndef _NETINET_IN_H
+#include <linux/in.h>
+#include <linux/in6.h>
+#endif
+#include <limits.h>
+
+/* Responses from hook functions. */
+#define NF_DROP 0
+#define NF_ACCEPT 1
+#define NF_STOLEN 2
+#define NF_QUEUE 3
+#define NF_REPEAT 4
+#define NF_STOP 5	/* Deprecated, for userspace nf_queue compatibility. */
+#define NF_MAX_VERDICT NF_STOP
+
+/* we overload the higher bits for encoding auxiliary data such as the queue
+ * number or errno values. Not nice, but better than additional function
+ * arguments. */
+#define NF_VERDICT_MASK 0x000000ff
+
+/* extra verdict flags have mask 0x0000ff00 */
+#define NF_VERDICT_FLAG_QUEUE_BYPASS	0x00008000
+
+/* queue number (NF_QUEUE) or errno (NF_DROP) */
+#define NF_VERDICT_QMASK 0xffff0000
+#define NF_VERDICT_QBITS 16
+
+#define NF_QUEUE_NR(x) ((((x) << 16) & NF_VERDICT_QMASK) | NF_QUEUE)
+
+#define NF_DROP_ERR(x) (((-x) << 16) | NF_DROP)
+
+/* only for userspace compatibility */
+/* Generic cache responses from hook functions.
+   <= 0x2000 is used for protocol-flags. */
+#define NFC_UNKNOWN 0x4000
+#define NFC_ALTERED 0x8000
+
+/* NF_VERDICT_BITS should be 8 now, but userspace might break if this changes */
+#define NF_VERDICT_BITS 16
+
+enum nf_inet_hooks {
+	NF_INET_PRE_ROUTING,
+	NF_INET_LOCAL_IN,
+	NF_INET_FORWARD,
+	NF_INET_LOCAL_OUT,
+	NF_INET_POST_ROUTING,
+	NF_INET_NUMHOOKS
+};
+
+enum nf_dev_hooks {
+	NF_NETDEV_INGRESS,
+	NF_NETDEV_NUMHOOKS
+};
+
+enum {
+	NFPROTO_UNSPEC =  0,
+	NFPROTO_INET   =  1,
+	NFPROTO_IPV4   =  2,
+	NFPROTO_ARP    =  3,
+	NFPROTO_NETDEV =  5,
+	NFPROTO_BRIDGE =  7,
+	NFPROTO_IPV6   = 10,
+	NFPROTO_DECNET = 12,
+	NFPROTO_NUMPROTO,
+};
+
+union nf_inet_addr {
+	__u32		all[4];
+	__be32		ip;
+	__be32		ip6[4];
+	struct in_addr	in;
+	struct in6_addr	in6;
+};
+
+#endif /* __LINUX_NETFILTER_H */
diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
new file mode 100644
index 0000000..b69bbc7
--- /dev/null
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -0,0 +1,284 @@
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ *                         Patrick Schaaf <bof@bof.de>
+ *                         Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 2003-2011 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _UAPI_IP_SET_H
+#define _UAPI_IP_SET_H
+
+
+#include <linux/types.h>
+
+/* The protocol version */
+#define IPSET_PROTOCOL		6
+
+/* The max length of strings including NUL: set and type identifiers */
+#define IPSET_MAXNAMELEN	32
+
+/* Message types and commands */
+enum ipset_cmd {
+	IPSET_CMD_NONE,
+	IPSET_CMD_PROTOCOL,	/* 1: Return protocol version */
+	IPSET_CMD_CREATE,	/* 2: Create a new (empty) set */
+	IPSET_CMD_DESTROY,	/* 3: Destroy a (empty) set */
+	IPSET_CMD_FLUSH,	/* 4: Remove all elements from a set */
+	IPSET_CMD_RENAME,	/* 5: Rename a set */
+	IPSET_CMD_SWAP,		/* 6: Swap two sets */
+	IPSET_CMD_LIST,		/* 7: List sets */
+	IPSET_CMD_SAVE,		/* 8: Save sets */
+	IPSET_CMD_ADD,		/* 9: Add an element to a set */
+	IPSET_CMD_DEL,		/* 10: Delete an element from a set */
+	IPSET_CMD_TEST,		/* 11: Test an element in a set */
+	IPSET_CMD_HEADER,	/* 12: Get set header data only */
+	IPSET_CMD_TYPE,		/* 13: Get set type */
+	IPSET_MSG_MAX,		/* Netlink message commands */
+
+	/* Commands in userspace: */
+	IPSET_CMD_RESTORE = IPSET_MSG_MAX, /* 14: Enter restore mode */
+	IPSET_CMD_HELP,		/* 15: Get help */
+	IPSET_CMD_VERSION,	/* 16: Get program version */
+	IPSET_CMD_QUIT,		/* 17: Quit from interactive mode */
+
+	IPSET_CMD_MAX,
+
+	IPSET_CMD_COMMIT = IPSET_CMD_MAX, /* 18: Commit buffered commands */
+};
+
+/* Attributes at command level */
+enum {
+	IPSET_ATTR_UNSPEC,
+	IPSET_ATTR_PROTOCOL,	/* 1: Protocol version */
+	IPSET_ATTR_SETNAME,	/* 2: Name of the set */
+	IPSET_ATTR_TYPENAME,	/* 3: Typename */
+	IPSET_ATTR_SETNAME2 = IPSET_ATTR_TYPENAME, /* Setname at rename/swap */
+	IPSET_ATTR_REVISION,	/* 4: Settype revision */
+	IPSET_ATTR_FAMILY,	/* 5: Settype family */
+	IPSET_ATTR_FLAGS,	/* 6: Flags at command level */
+	IPSET_ATTR_DATA,	/* 7: Nested attributes */
+	IPSET_ATTR_ADT,		/* 8: Multiple data containers */
+	IPSET_ATTR_LINENO,	/* 9: Restore lineno */
+	IPSET_ATTR_PROTOCOL_MIN, /* 10: Minimal supported version number */
+	IPSET_ATTR_REVISION_MIN	= IPSET_ATTR_PROTOCOL_MIN, /* type rev min */
+	__IPSET_ATTR_CMD_MAX,
+};
+#define IPSET_ATTR_CMD_MAX	(__IPSET_ATTR_CMD_MAX - 1)
+
+/* CADT specific attributes */
+enum {
+	IPSET_ATTR_IP = IPSET_ATTR_UNSPEC + 1,
+	IPSET_ATTR_IP_FROM = IPSET_ATTR_IP,
+	IPSET_ATTR_IP_TO,	/* 2 */
+	IPSET_ATTR_CIDR,	/* 3 */
+	IPSET_ATTR_PORT,	/* 4 */
+	IPSET_ATTR_PORT_FROM = IPSET_ATTR_PORT,
+	IPSET_ATTR_PORT_TO,	/* 5 */
+	IPSET_ATTR_TIMEOUT,	/* 6 */
+	IPSET_ATTR_PROTO,	/* 7 */
+	IPSET_ATTR_CADT_FLAGS,	/* 8 */
+	IPSET_ATTR_CADT_LINENO = IPSET_ATTR_LINENO,	/* 9 */
+	/* Reserve empty slots */
+	IPSET_ATTR_CADT_MAX = 16,
+	/* Create-only specific attributes */
+	IPSET_ATTR_GC,
+	IPSET_ATTR_HASHSIZE,
+	IPSET_ATTR_MAXELEM,
+	IPSET_ATTR_NETMASK,
+	IPSET_ATTR_PROBES,
+	IPSET_ATTR_RESIZE,
+	IPSET_ATTR_SIZE,
+	/* Kernel-only */
+	IPSET_ATTR_ELEMENTS,
+	IPSET_ATTR_REFERENCES,
+	IPSET_ATTR_MEMSIZE,
+
+	__IPSET_ATTR_CREATE_MAX,
+};
+#define IPSET_ATTR_CREATE_MAX	(__IPSET_ATTR_CREATE_MAX - 1)
+
+/* ADT specific attributes */
+enum {
+	IPSET_ATTR_ETHER = IPSET_ATTR_CADT_MAX + 1,
+	IPSET_ATTR_NAME,
+	IPSET_ATTR_NAMEREF,
+	IPSET_ATTR_IP2,
+	IPSET_ATTR_CIDR2,
+	IPSET_ATTR_IP2_TO,
+	IPSET_ATTR_IFACE,
+	IPSET_ATTR_BYTES,
+	IPSET_ATTR_PACKETS,
+	IPSET_ATTR_SKBMARK,
+	IPSET_ATTR_SKBPRIO,
+	IPSET_ATTR_SKBQUEUE,
+	__IPSET_ATTR_ADT_MAX,
+};
+#define IPSET_ATTR_ADT_MAX	(__IPSET_ATTR_ADT_MAX - 1)
+
+/* IP specific attributes */
+enum {
+	IPSET_ATTR_IPADDR_IPV4 = IPSET_ATTR_UNSPEC + 1,
+	IPSET_ATTR_IPADDR_IPV6,
+	__IPSET_ATTR_IPADDR_MAX,
+};
+#define IPSET_ATTR_IPADDR_MAX	(__IPSET_ATTR_IPADDR_MAX - 1)
+
+/* Error codes */
+enum ipset_errno {
+	IPSET_ERR_PRIVATE = 4096,
+	IPSET_ERR_PROTOCOL,
+	IPSET_ERR_FIND_TYPE,
+	IPSET_ERR_MAX_SETS,
+	IPSET_ERR_BUSY,
+	IPSET_ERR_EXIST_SETNAME2,
+	IPSET_ERR_TYPE_MISMATCH,
+	IPSET_ERR_EXIST,
+	IPSET_ERR_INVALID_CIDR,
+	IPSET_ERR_INVALID_NETMASK,
+	IPSET_ERR_INVALID_FAMILY,
+	IPSET_ERR_TIMEOUT,
+	IPSET_ERR_REFERENCED,
+	IPSET_ERR_IPADDR_IPV4,
+	IPSET_ERR_IPADDR_IPV6,
+	IPSET_ERR_COUNTER,
+	IPSET_ERR_SKBINFO,
+
+	/* Type specific error codes */
+	IPSET_ERR_TYPE_SPECIFIC = 4352,
+};
+
+/* Flags at command level or match/target flags, lower half of cmdattrs */
+enum ipset_cmd_flags {
+	IPSET_FLAG_BIT_EXIST	= 0,
+	IPSET_FLAG_EXIST	= (1 << IPSET_FLAG_BIT_EXIST),
+	IPSET_FLAG_BIT_LIST_SETNAME = 1,
+	IPSET_FLAG_LIST_SETNAME	= (1 << IPSET_FLAG_BIT_LIST_SETNAME),
+	IPSET_FLAG_BIT_LIST_HEADER = 2,
+	IPSET_FLAG_LIST_HEADER	= (1 << IPSET_FLAG_BIT_LIST_HEADER),
+	IPSET_FLAG_BIT_SKIP_COUNTER_UPDATE = 3,
+	IPSET_FLAG_SKIP_COUNTER_UPDATE =
+		(1 << IPSET_FLAG_BIT_SKIP_COUNTER_UPDATE),
+	IPSET_FLAG_BIT_SKIP_SUBCOUNTER_UPDATE = 4,
+	IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE =
+		(1 << IPSET_FLAG_BIT_SKIP_SUBCOUNTER_UPDATE),
+	IPSET_FLAG_BIT_MATCH_COUNTERS = 5,
+	IPSET_FLAG_MATCH_COUNTERS = (1 << IPSET_FLAG_BIT_MATCH_COUNTERS),
+	IPSET_FLAG_BIT_RETURN_NOMATCH = 7,
+	IPSET_FLAG_RETURN_NOMATCH = (1 << IPSET_FLAG_BIT_RETURN_NOMATCH),
+	IPSET_FLAG_BIT_MAP_SKBMARK = 8,
+	IPSET_FLAG_MAP_SKBMARK = (1 << IPSET_FLAG_BIT_MAP_SKBMARK),
+	IPSET_FLAG_BIT_MAP_SKBPRIO = 9,
+	IPSET_FLAG_MAP_SKBPRIO = (1 << IPSET_FLAG_BIT_MAP_SKBPRIO),
+	IPSET_FLAG_BIT_MAP_SKBQUEUE = 10,
+	IPSET_FLAG_MAP_SKBQUEUE = (1 << IPSET_FLAG_BIT_MAP_SKBQUEUE),
+	IPSET_FLAG_CMD_MAX = 15,
+};
+
+/* Flags at CADT attribute level, upper half of cmdattrs */
+enum ipset_cadt_flags {
+	IPSET_FLAG_BIT_BEFORE	= 0,
+	IPSET_FLAG_BEFORE	= (1 << IPSET_FLAG_BIT_BEFORE),
+	IPSET_FLAG_BIT_PHYSDEV	= 1,
+	IPSET_FLAG_PHYSDEV	= (1 << IPSET_FLAG_BIT_PHYSDEV),
+	IPSET_FLAG_BIT_NOMATCH	= 2,
+	IPSET_FLAG_NOMATCH	= (1 << IPSET_FLAG_BIT_NOMATCH),
+	IPSET_FLAG_BIT_WITH_COUNTERS = 3,
+	IPSET_FLAG_WITH_COUNTERS = (1 << IPSET_FLAG_BIT_WITH_COUNTERS),
+	IPSET_FLAG_CADT_MAX	= 15,
+};
+
+/* Commands with settype-specific attributes */
+enum ipset_adt {
+	IPSET_ADD,
+	IPSET_DEL,
+	IPSET_TEST,
+	IPSET_ADT_MAX,
+	IPSET_CREATE = IPSET_ADT_MAX,
+	IPSET_CADT_MAX,
+};
+
+/* Sets are identified by an index in kernel space. Tweak with ip_set_id_t
+ * and IPSET_INVALID_ID if you want to increase the max number of sets.
+ */
+typedef __u16 ip_set_id_t;
+
+#define IPSET_INVALID_ID		65535
+
+enum ip_set_dim {
+	IPSET_DIM_ZERO = 0,
+	IPSET_DIM_ONE,
+	IPSET_DIM_TWO,
+	IPSET_DIM_THREE,
+	/* Max dimension in elements.
+	 * If changed, new revision of iptables match/target is required.
+	 */
+	IPSET_DIM_MAX = 6,
+	/* Backward compatibility: set match revision 2 */
+	IPSET_BIT_RETURN_NOMATCH = 7,
+};
+
+/* Option flags for kernel operations */
+enum ip_set_kopt {
+	IPSET_INV_MATCH = (1 << IPSET_DIM_ZERO),
+	IPSET_DIM_ONE_SRC = (1 << IPSET_DIM_ONE),
+	IPSET_DIM_TWO_SRC = (1 << IPSET_DIM_TWO),
+	IPSET_DIM_THREE_SRC = (1 << IPSET_DIM_THREE),
+	IPSET_RETURN_NOMATCH = (1 << IPSET_BIT_RETURN_NOMATCH),
+};
+
+enum {
+	IPSET_COUNTER_NONE = 0,
+	IPSET_COUNTER_EQ,
+	IPSET_COUNTER_NE,
+	IPSET_COUNTER_LT,
+	IPSET_COUNTER_GT,
+};
+
+/* Backward compatibility for set match v3 */
+struct ip_set_counter_match0 {
+	__u8 op;
+	__u64 value;
+};
+
+struct ip_set_counter_match {
+	__aligned_u64 value;
+	__u8 op;
+};
+
+/* Interface to iptables/ip6tables */
+
+#define SO_IP_SET		83
+
+union ip_set_name_index {
+	char name[IPSET_MAXNAMELEN];
+	ip_set_id_t index;
+};
+
+#define IP_SET_OP_GET_BYNAME	0x00000006	/* Get set index by name */
+struct ip_set_req_get_set {
+	unsigned int op;
+	unsigned int version;
+	union ip_set_name_index set;
+};
+
+#define IP_SET_OP_GET_BYINDEX	0x00000007	/* Get set name by index */
+/* Uses ip_set_req_get_set */
+
+#define IP_SET_OP_GET_FNAME	0x00000008	/* Get set index and family */
+struct ip_set_req_get_set_family {
+	unsigned int op;
+	unsigned int version;
+	unsigned int family;
+	union ip_set_name_index set;
+};
+
+
+#define IP_SET_OP_VERSION	0x00000100	/* Ask kernel version */
+struct ip_set_req_version {
+	unsigned int op;
+	unsigned int version;
+};
+
+#endif /* _UAPI_IP_SET_H */
diff --git a/include/linux/netfilter/nf_conntrack_common.h b/include/linux/netfilter/nf_conntrack_common.h
new file mode 100644
index 0000000..38aa52d
--- /dev/null
+++ b/include/linux/netfilter/nf_conntrack_common.h
@@ -0,0 +1,113 @@
+#ifndef _NF_CONNTRACK_COMMON_H
+#define _NF_CONNTRACK_COMMON_H
+/* Connection state tracking for netfilter.  This is separated from,
+   but required by, the NAT layer; it can also be used by an iptables
+   extension. */
+enum ip_conntrack_info {
+	/* Part of an established connection (either direction). */
+	IP_CT_ESTABLISHED,
+
+	/* Like NEW, but related to an existing connection, or ICMP error
+	   (in either direction). */
+	IP_CT_RELATED,
+
+	/* Started a new connection to track (only
+           IP_CT_DIR_ORIGINAL); may be a retransmission. */
+	IP_CT_NEW,
+
+	/* >= this indicates reply direction */
+	IP_CT_IS_REPLY,
+
+	IP_CT_ESTABLISHED_REPLY = IP_CT_ESTABLISHED + IP_CT_IS_REPLY,
+	IP_CT_RELATED_REPLY = IP_CT_RELATED + IP_CT_IS_REPLY,
+	IP_CT_NEW_REPLY = IP_CT_NEW + IP_CT_IS_REPLY,	
+	/* Number of distinct IP_CT types (no NEW in reply dirn). */
+	IP_CT_NUMBER = IP_CT_IS_REPLY * 2 - 1
+};
+
+/* Bitset representing status of connection. */
+enum ip_conntrack_status {
+	/* It's an expected connection: bit 0 set.  This bit never changed */
+	IPS_EXPECTED_BIT = 0,
+	IPS_EXPECTED = (1 << IPS_EXPECTED_BIT),
+
+	/* We've seen packets both ways: bit 1 set.  Can be set, not unset. */
+	IPS_SEEN_REPLY_BIT = 1,
+	IPS_SEEN_REPLY = (1 << IPS_SEEN_REPLY_BIT),
+
+	/* Conntrack should never be early-expired. */
+	IPS_ASSURED_BIT = 2,
+	IPS_ASSURED = (1 << IPS_ASSURED_BIT),
+
+	/* Connection is confirmed: originating packet has left box */
+	IPS_CONFIRMED_BIT = 3,
+	IPS_CONFIRMED = (1 << IPS_CONFIRMED_BIT),
+
+	/* Connection needs src nat in orig dir.  This bit never changed. */
+	IPS_SRC_NAT_BIT = 4,
+	IPS_SRC_NAT = (1 << IPS_SRC_NAT_BIT),
+
+	/* Connection needs dst nat in orig dir.  This bit never changed. */
+	IPS_DST_NAT_BIT = 5,
+	IPS_DST_NAT = (1 << IPS_DST_NAT_BIT),
+
+	/* Both together. */
+	IPS_NAT_MASK = (IPS_DST_NAT | IPS_SRC_NAT),
+
+	/* Connection needs TCP sequence adjusted. */
+	IPS_SEQ_ADJUST_BIT = 6,
+	IPS_SEQ_ADJUST = (1 << IPS_SEQ_ADJUST_BIT),
+
+	/* NAT initialization bits. */
+	IPS_SRC_NAT_DONE_BIT = 7,
+	IPS_SRC_NAT_DONE = (1 << IPS_SRC_NAT_DONE_BIT),
+
+	IPS_DST_NAT_DONE_BIT = 8,
+	IPS_DST_NAT_DONE = (1 << IPS_DST_NAT_DONE_BIT),
+
+	/* Both together */
+	IPS_NAT_DONE_MASK = (IPS_DST_NAT_DONE | IPS_SRC_NAT_DONE),
+
+	/* Connection is dying (removed from lists), can not be unset. */
+	IPS_DYING_BIT = 9,
+	IPS_DYING = (1 << IPS_DYING_BIT),
+
+	/* Connection has fixed timeout. */
+	IPS_FIXED_TIMEOUT_BIT = 10,
+	IPS_FIXED_TIMEOUT = (1 << IPS_FIXED_TIMEOUT_BIT),
+
+	/* Conntrack is a template */
+	IPS_TEMPLATE_BIT = 11,
+	IPS_TEMPLATE = (1 << IPS_TEMPLATE_BIT),
+
+	/* Conntrack is a fake untracked entry */
+	IPS_UNTRACKED_BIT = 12,
+	IPS_UNTRACKED = (1 << IPS_UNTRACKED_BIT),
+};
+
+/* Connection tracking event types */
+enum ip_conntrack_events {
+	IPCT_NEW,		/* new conntrack */
+	IPCT_RELATED,		/* related conntrack */
+	IPCT_DESTROY,		/* destroyed conntrack */
+	IPCT_REPLY,		/* connection has seen two-way traffic */
+	IPCT_ASSURED,		/* connection status has changed to assured */
+	IPCT_PROTOINFO,		/* protocol information has changed */
+	IPCT_HELPER,		/* new helper has been set */
+	IPCT_MARK,		/* new mark has been set */
+	IPCT_NATSEQADJ,		/* NAT is doing sequence adjustment */
+	IPCT_SECMARK,		/* new security mark has been set */
+};
+
+enum ip_conntrack_expect_events {
+	IPEXP_NEW,		/* new expectation */
+	IPEXP_DESTROY,		/* destroyed expectation */
+};
+
+/* expectation flags */
+#define NF_CT_EXPECT_PERMANENT		0x1
+#define NF_CT_EXPECT_INACTIVE		0x2
+#define NF_CT_EXPECT_USERSPACE		0x4
+
+
+#endif /* _NF_CONNTRACK_COMMON_H */
diff --git a/include/linux/netfilter/nf_conntrack_tuple_common.h b/include/linux/netfilter/nf_conntrack_tuple_common.h
new file mode 100644
index 0000000..2f6bbc5
--- /dev/null
+++ b/include/linux/netfilter/nf_conntrack_tuple_common.h
@@ -0,0 +1,39 @@
+#ifndef _NF_CONNTRACK_TUPLE_COMMON_H
+#define _NF_CONNTRACK_TUPLE_COMMON_H
+
+enum ip_conntrack_dir {
+	IP_CT_DIR_ORIGINAL,
+	IP_CT_DIR_REPLY,
+	IP_CT_DIR_MAX
+};
+
+/* The protocol-specific manipulable parts of the tuple: always in
+ * network order
+ */
+union nf_conntrack_man_proto {
+	/* Add other protocols here. */
+	__be16 all;
+
+	struct {
+		__be16 port;
+	} tcp;
+	struct {
+		__be16 port;
+	} udp;
+	struct {
+		__be16 id;
+	} icmp;
+	struct {
+		__be16 port;
+	} dccp;
+	struct {
+		__be16 port;
+	} sctp;
+	struct {
+		__be16 key;	/* GRE key is 32bit, PPtP only uses 16bit */
+	} gre;
+};
+
+#define CTINFO2DIR(ctinfo) ((ctinfo) >= IP_CT_IS_REPLY ? IP_CT_DIR_REPLY : IP_CT_DIR_ORIGINAL)
+
+#endif /* _NF_CONNTRACK_TUPLE_COMMON_H */
diff --git a/include/linux/netfilter/nf_nat.h b/include/linux/netfilter/nf_nat.h
new file mode 100644
index 0000000..b600000
--- /dev/null
+++ b/include/linux/netfilter/nf_nat.h
@@ -0,0 +1,52 @@
+#ifndef _NETFILTER_NF_NAT_H
+#define _NETFILTER_NF_NAT_H
+
+#include <linux/netfilter.h>
+#include <linux/netfilter/nf_conntrack_tuple_common.h>
+
+#define NF_NAT_RANGE_MAP_IPS			(1 << 0)
+#define NF_NAT_RANGE_PROTO_SPECIFIED		(1 << 1)
+#define NF_NAT_RANGE_PROTO_RANDOM		(1 << 2)
+#define NF_NAT_RANGE_PERSISTENT			(1 << 3)
+#define NF_NAT_RANGE_PROTO_RANDOM_FULLY		(1 << 4)
+#define NF_NAT_RANGE_PROTO_OFFSET		(1 << 5)
+
+#define NF_NAT_RANGE_PROTO_RANDOM_ALL		\
+	(NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+
+#define NF_NAT_RANGE_MASK					\
+	(NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED |	\
+	 NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PERSISTENT |	\
+	 NF_NAT_RANGE_PROTO_RANDOM_FULLY | NF_NAT_RANGE_PROTO_OFFSET)
+
+struct nf_nat_ipv4_range {
+	unsigned int			flags;
+	__be32				min_ip;
+	__be32				max_ip;
+	union nf_conntrack_man_proto	min;
+	union nf_conntrack_man_proto	max;
+};
+
+struct nf_nat_ipv4_multi_range_compat {
+	unsigned int			rangesize;
+	struct nf_nat_ipv4_range	range[1];
+};
+
+struct nf_nat_range {
+	unsigned int			flags;
+	union nf_inet_addr		min_addr;
+	union nf_inet_addr		max_addr;
+	union nf_conntrack_man_proto	min_proto;
+	union nf_conntrack_man_proto	max_proto;
+};
+
+struct nf_nat_range2 {
+	unsigned int			flags;
+	union nf_inet_addr		min_addr;
+	union nf_inet_addr		max_addr;
+	union nf_conntrack_man_proto	min_proto;
+	union nf_conntrack_man_proto	max_proto;
+	union nf_conntrack_man_proto	base_proto;
+};
+
+#endif /* _NETFILTER_NF_NAT_H */
diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
new file mode 100644
index 0000000..66dceee
--- /dev/null
+++ b/include/linux/netfilter/nf_tables.h
@@ -0,0 +1,1463 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_NF_TABLES_H
+#define _LINUX_NF_TABLES_H
+
+#define NFT_NAME_MAXLEN		256
+#define NFT_TABLE_MAXNAMELEN	NFT_NAME_MAXLEN
+#define NFT_CHAIN_MAXNAMELEN	NFT_NAME_MAXLEN
+#define NFT_SET_MAXNAMELEN	NFT_NAME_MAXLEN
+#define NFT_OBJ_MAXNAMELEN	NFT_NAME_MAXLEN
+#define NFT_USERDATA_MAXLEN	256
+
+/**
+ * enum nft_registers - nf_tables registers
+ *
+ * nf_tables used to have five registers: a verdict register and four data
+ * registers of size 16. The data registers have been changed to 16 registers
+ * of size 4. For compatibility reasons, the NFT_REG_[1-4] registers still
+ * map to areas of size 16, the 4 byte registers are addressed using
+ * NFT_REG32_00 - NFT_REG32_15.
+ */
+enum nft_registers {
+	NFT_REG_VERDICT,
+	NFT_REG_1,
+	NFT_REG_2,
+	NFT_REG_3,
+	NFT_REG_4,
+	__NFT_REG_MAX,
+
+	NFT_REG32_00	= 8,
+	NFT_REG32_01,
+	NFT_REG32_02,
+	NFT_REG32_03,
+	NFT_REG32_04,
+	NFT_REG32_05,
+	NFT_REG32_06,
+	NFT_REG32_07,
+	NFT_REG32_08,
+	NFT_REG32_09,
+	NFT_REG32_10,
+	NFT_REG32_11,
+	NFT_REG32_12,
+	NFT_REG32_13,
+	NFT_REG32_14,
+	NFT_REG32_15,
+};
+#define NFT_REG_MAX	(__NFT_REG_MAX - 1)
+
+#define NFT_REG_SIZE	16
+#define NFT_REG32_SIZE	4
+
+/**
+ * enum nft_verdicts - nf_tables internal verdicts
+ *
+ * @NFT_CONTINUE: continue evaluation of the current rule
+ * @NFT_BREAK: terminate evaluation of the current rule
+ * @NFT_JUMP: push the current chain on the jump stack and jump to a chain
+ * @NFT_GOTO: jump to a chain without pushing the current chain on the jump stack
+ * @NFT_RETURN: return to the topmost chain on the jump stack
+ *
+ * The nf_tables verdicts share their numeric space with the netfilter verdicts.
+ */
+enum nft_verdicts {
+	NFT_CONTINUE	= -1,
+	NFT_BREAK	= -2,
+	NFT_JUMP	= -3,
+	NFT_GOTO	= -4,
+	NFT_RETURN	= -5,
+};
+
+/**
+ * enum nf_tables_msg_types - nf_tables netlink message types
+ *
+ * @NFT_MSG_NEWTABLE: create a new table (enum nft_table_attributes)
+ * @NFT_MSG_GETTABLE: get a table (enum nft_table_attributes)
+ * @NFT_MSG_DELTABLE: delete a table (enum nft_table_attributes)
+ * @NFT_MSG_NEWCHAIN: create a new chain (enum nft_chain_attributes)
+ * @NFT_MSG_GETCHAIN: get a chain (enum nft_chain_attributes)
+ * @NFT_MSG_DELCHAIN: delete a chain (enum nft_chain_attributes)
+ * @NFT_MSG_NEWRULE: create a new rule (enum nft_rule_attributes)
+ * @NFT_MSG_GETRULE: get a rule (enum nft_rule_attributes)
+ * @NFT_MSG_DELRULE: delete a rule (enum nft_rule_attributes)
+ * @NFT_MSG_NEWSET: create a new set (enum nft_set_attributes)
+ * @NFT_MSG_GETSET: get a set (enum nft_set_attributes)
+ * @NFT_MSG_DELSET: delete a set (enum nft_set_attributes)
+ * @NFT_MSG_NEWSETELEM: create a new set element (enum nft_set_elem_attributes)
+ * @NFT_MSG_GETSETELEM: get a set element (enum nft_set_elem_attributes)
+ * @NFT_MSG_DELSETELEM: delete a set element (enum nft_set_elem_attributes)
+ * @NFT_MSG_NEWGEN: announce a new generation, only for events (enum nft_gen_attributes)
+ * @NFT_MSG_GETGEN: get the rule-set generation (enum nft_gen_attributes)
+ * @NFT_MSG_TRACE: trace event (enum nft_trace_attributes)
+ * @NFT_MSG_NEWOBJ: create a stateful object (enum nft_obj_attributes)
+ * @NFT_MSG_GETOBJ: get a stateful object (enum nft_obj_attributes)
+ * @NFT_MSG_DELOBJ: delete a stateful object (enum nft_obj_attributes)
+ * @NFT_MSG_GETOBJ_RESET: get and reset a stateful object (enum nft_obj_attributes)
+ * @NFT_MSG_NEWFLOWTABLE: add new flow table (enum nft_flowtable_attributes)
+ * @NFT_MSG_GETFLOWTABLE: get flow table (enum nft_flowtable_attributes)
+ * @NFT_MSG_DELFLOWTABLE: delete flow table (enum nft_flowtable_attributes)
+ */
+enum nf_tables_msg_types {
+	NFT_MSG_NEWTABLE,
+	NFT_MSG_GETTABLE,
+	NFT_MSG_DELTABLE,
+	NFT_MSG_NEWCHAIN,
+	NFT_MSG_GETCHAIN,
+	NFT_MSG_DELCHAIN,
+	NFT_MSG_NEWRULE,
+	NFT_MSG_GETRULE,
+	NFT_MSG_DELRULE,
+	NFT_MSG_NEWSET,
+	NFT_MSG_GETSET,
+	NFT_MSG_DELSET,
+	NFT_MSG_NEWSETELEM,
+	NFT_MSG_GETSETELEM,
+	NFT_MSG_DELSETELEM,
+	NFT_MSG_NEWGEN,
+	NFT_MSG_GETGEN,
+	NFT_MSG_TRACE,
+	NFT_MSG_NEWOBJ,
+	NFT_MSG_GETOBJ,
+	NFT_MSG_DELOBJ,
+	NFT_MSG_GETOBJ_RESET,
+	NFT_MSG_NEWFLOWTABLE,
+	NFT_MSG_GETFLOWTABLE,
+	NFT_MSG_DELFLOWTABLE,
+	NFT_MSG_MAX,
+};
+
+/**
+ * enum nft_list_attributes - nf_tables generic list netlink attributes
+ *
+ * @NFTA_LIST_ELEM: list element (NLA_NESTED)
+ */
+enum nft_list_attributes {
+	NFTA_LIST_UNPEC,
+	NFTA_LIST_ELEM,
+	__NFTA_LIST_MAX
+};
+#define NFTA_LIST_MAX		(__NFTA_LIST_MAX - 1)
+
+/**
+ * enum nft_hook_attributes - nf_tables netfilter hook netlink attributes
+ *
+ * @NFTA_HOOK_HOOKNUM: netfilter hook number (NLA_U32)
+ * @NFTA_HOOK_PRIORITY: netfilter hook priority (NLA_U32)
+ * @NFTA_HOOK_DEV: netdevice name (NLA_STRING)
+ */
+enum nft_hook_attributes {
+	NFTA_HOOK_UNSPEC,
+	NFTA_HOOK_HOOKNUM,
+	NFTA_HOOK_PRIORITY,
+	NFTA_HOOK_DEV,
+	__NFTA_HOOK_MAX
+};
+#define NFTA_HOOK_MAX		(__NFTA_HOOK_MAX - 1)
+
+/**
+ * enum nft_table_flags - nf_tables table flags
+ *
+ * @NFT_TABLE_F_DORMANT: this table is not active
+ */
+enum nft_table_flags {
+	NFT_TABLE_F_DORMANT	= 0x1,
+};
+
+/**
+ * enum nft_table_attributes - nf_tables table netlink attributes
+ *
+ * @NFTA_TABLE_NAME: name of the table (NLA_STRING)
+ * @NFTA_TABLE_FLAGS: bitmask of enum nft_table_flags (NLA_U32)
+ * @NFTA_TABLE_USE: number of chains in this table (NLA_U32)
+ */
+enum nft_table_attributes {
+	NFTA_TABLE_UNSPEC,
+	NFTA_TABLE_NAME,
+	NFTA_TABLE_FLAGS,
+	NFTA_TABLE_USE,
+	NFTA_TABLE_HANDLE,
+	NFTA_TABLE_PAD,
+	__NFTA_TABLE_MAX
+};
+#define NFTA_TABLE_MAX		(__NFTA_TABLE_MAX - 1)
+
+/**
+ * enum nft_chain_attributes - nf_tables chain netlink attributes
+ *
+ * @NFTA_CHAIN_TABLE: name of the table containing the chain (NLA_STRING)
+ * @NFTA_CHAIN_HANDLE: numeric handle of the chain (NLA_U64)
+ * @NFTA_CHAIN_NAME: name of the chain (NLA_STRING)
+ * @NFTA_CHAIN_HOOK: hook specification for basechains (NLA_NESTED: nft_hook_attributes)
+ * @NFTA_CHAIN_POLICY: numeric policy of the chain (NLA_U32)
+ * @NFTA_CHAIN_USE: number of references to this chain (NLA_U32)
+ * @NFTA_CHAIN_TYPE: type name of the string (NLA_NUL_STRING)
+ * @NFTA_CHAIN_COUNTERS: counter specification of the chain (NLA_NESTED: nft_counter_attributes)
+ */
+enum nft_chain_attributes {
+	NFTA_CHAIN_UNSPEC,
+	NFTA_CHAIN_TABLE,
+	NFTA_CHAIN_HANDLE,
+	NFTA_CHAIN_NAME,
+	NFTA_CHAIN_HOOK,
+	NFTA_CHAIN_POLICY,
+	NFTA_CHAIN_USE,
+	NFTA_CHAIN_TYPE,
+	NFTA_CHAIN_COUNTERS,
+	NFTA_CHAIN_PAD,
+	__NFTA_CHAIN_MAX
+};
+#define NFTA_CHAIN_MAX		(__NFTA_CHAIN_MAX - 1)
+
+/**
+ * enum nft_rule_attributes - nf_tables rule netlink attributes
+ *
+ * @NFTA_RULE_TABLE: name of the table containing the rule (NLA_STRING)
+ * @NFTA_RULE_CHAIN: name of the chain containing the rule (NLA_STRING)
+ * @NFTA_RULE_HANDLE: numeric handle of the rule (NLA_U64)
+ * @NFTA_RULE_EXPRESSIONS: list of expressions (NLA_NESTED: nft_expr_attributes)
+ * @NFTA_RULE_COMPAT: compatibility specifications of the rule (NLA_NESTED: nft_rule_compat_attributes)
+ * @NFTA_RULE_POSITION: numeric handle of the previous rule (NLA_U64)
+ * @NFTA_RULE_USERDATA: user data (NLA_BINARY, NFT_USERDATA_MAXLEN)
+ * @NFTA_RULE_ID: uniquely identifies a rule in a transaction (NLA_U32)
+ */
+enum nft_rule_attributes {
+	NFTA_RULE_UNSPEC,
+	NFTA_RULE_TABLE,
+	NFTA_RULE_CHAIN,
+	NFTA_RULE_HANDLE,
+	NFTA_RULE_EXPRESSIONS,
+	NFTA_RULE_COMPAT,
+	NFTA_RULE_POSITION,
+	NFTA_RULE_USERDATA,
+	NFTA_RULE_PAD,
+	NFTA_RULE_ID,
+	__NFTA_RULE_MAX
+};
+#define NFTA_RULE_MAX		(__NFTA_RULE_MAX - 1)
+
+/**
+ * enum nft_rule_compat_flags - nf_tables rule compat flags
+ *
+ * @NFT_RULE_COMPAT_F_INV: invert the check result
+ */
+enum nft_rule_compat_flags {
+	NFT_RULE_COMPAT_F_INV	= (1 << 1),
+	NFT_RULE_COMPAT_F_MASK	= NFT_RULE_COMPAT_F_INV,
+};
+
+/**
+ * enum nft_rule_compat_attributes - nf_tables rule compat attributes
+ *
+ * @NFTA_RULE_COMPAT_PROTO: numeric value of handled protocol (NLA_U32)
+ * @NFTA_RULE_COMPAT_FLAGS: bitmask of enum nft_rule_compat_flags (NLA_U32)
+ */
+enum nft_rule_compat_attributes {
+	NFTA_RULE_COMPAT_UNSPEC,
+	NFTA_RULE_COMPAT_PROTO,
+	NFTA_RULE_COMPAT_FLAGS,
+	__NFTA_RULE_COMPAT_MAX
+};
+#define NFTA_RULE_COMPAT_MAX	(__NFTA_RULE_COMPAT_MAX - 1)
+
+/**
+ * enum nft_set_flags - nf_tables set flags
+ *
+ * @NFT_SET_ANONYMOUS: name allocation, automatic cleanup on unlink
+ * @NFT_SET_CONSTANT: set contents may not change while bound
+ * @NFT_SET_INTERVAL: set contains intervals
+ * @NFT_SET_MAP: set is used as a dictionary
+ * @NFT_SET_TIMEOUT: set uses timeouts
+ * @NFT_SET_EVAL: set contains expressions for evaluation
+ * @NFT_SET_OBJECT: set contains stateful objects
+ */
+enum nft_set_flags {
+	NFT_SET_ANONYMOUS		= 0x1,
+	NFT_SET_CONSTANT		= 0x2,
+	NFT_SET_INTERVAL		= 0x4,
+	NFT_SET_MAP			= 0x8,
+	NFT_SET_TIMEOUT			= 0x10,
+	NFT_SET_EVAL			= 0x20,
+	NFT_SET_OBJECT			= 0x40,
+};
+
+/**
+ * enum nft_set_policies - set selection policy
+ *
+ * @NFT_SET_POL_PERFORMANCE: prefer high performance over low memory use
+ * @NFT_SET_POL_MEMORY: prefer low memory use over high performance
+ */
+enum nft_set_policies {
+	NFT_SET_POL_PERFORMANCE,
+	NFT_SET_POL_MEMORY,
+};
+
+/**
+ * enum nft_set_desc_attributes - set element description
+ *
+ * @NFTA_SET_DESC_SIZE: number of elements in set (NLA_U32)
+ */
+enum nft_set_desc_attributes {
+	NFTA_SET_DESC_UNSPEC,
+	NFTA_SET_DESC_SIZE,
+	__NFTA_SET_DESC_MAX
+};
+#define NFTA_SET_DESC_MAX	(__NFTA_SET_DESC_MAX - 1)
+
+/**
+ * enum nft_set_attributes - nf_tables set netlink attributes
+ *
+ * @NFTA_SET_TABLE: table name (NLA_STRING)
+ * @NFTA_SET_NAME: set name (NLA_STRING)
+ * @NFTA_SET_FLAGS: bitmask of enum nft_set_flags (NLA_U32)
+ * @NFTA_SET_KEY_TYPE: key data type, informational purpose only (NLA_U32)
+ * @NFTA_SET_KEY_LEN: key data length (NLA_U32)
+ * @NFTA_SET_DATA_TYPE: mapping data type (NLA_U32)
+ * @NFTA_SET_DATA_LEN: mapping data length (NLA_U32)
+ * @NFTA_SET_POLICY: selection policy (NLA_U32)
+ * @NFTA_SET_DESC: set description (NLA_NESTED)
+ * @NFTA_SET_ID: uniquely identifies a set in a transaction (NLA_U32)
+ * @NFTA_SET_TIMEOUT: default timeout value (NLA_U64)
+ * @NFTA_SET_GC_INTERVAL: garbage collection interval (NLA_U32)
+ * @NFTA_SET_USERDATA: user data (NLA_BINARY)
+ * @NFTA_SET_OBJ_TYPE: stateful object type (NLA_U32: NFT_OBJECT_*)
+ * @NFTA_SET_HANDLE: set handle (NLA_U64)
+ */
+enum nft_set_attributes {
+	NFTA_SET_UNSPEC,
+	NFTA_SET_TABLE,
+	NFTA_SET_NAME,
+	NFTA_SET_FLAGS,
+	NFTA_SET_KEY_TYPE,
+	NFTA_SET_KEY_LEN,
+	NFTA_SET_DATA_TYPE,
+	NFTA_SET_DATA_LEN,
+	NFTA_SET_POLICY,
+	NFTA_SET_DESC,
+	NFTA_SET_ID,
+	NFTA_SET_TIMEOUT,
+	NFTA_SET_GC_INTERVAL,
+	NFTA_SET_USERDATA,
+	NFTA_SET_PAD,
+	NFTA_SET_OBJ_TYPE,
+	NFTA_SET_HANDLE,
+	__NFTA_SET_MAX
+};
+#define NFTA_SET_MAX		(__NFTA_SET_MAX - 1)
+
+/**
+ * enum nft_set_elem_flags - nf_tables set element flags
+ *
+ * @NFT_SET_ELEM_INTERVAL_END: element ends the previous interval
+ */
+enum nft_set_elem_flags {
+	NFT_SET_ELEM_INTERVAL_END	= 0x1,
+};
+
+/**
+ * enum nft_set_elem_attributes - nf_tables set element netlink attributes
+ *
+ * @NFTA_SET_ELEM_KEY: key value (NLA_NESTED: nft_data)
+ * @NFTA_SET_ELEM_DATA: data value of mapping (NLA_NESTED: nft_data_attributes)
+ * @NFTA_SET_ELEM_FLAGS: bitmask of nft_set_elem_flags (NLA_U32)
+ * @NFTA_SET_ELEM_TIMEOUT: timeout value (NLA_U64)
+ * @NFTA_SET_ELEM_EXPIRATION: expiration time (NLA_U64)
+ * @NFTA_SET_ELEM_USERDATA: user data (NLA_BINARY)
+ * @NFTA_SET_ELEM_EXPR: expression (NLA_NESTED: nft_expr_attributes)
+ * @NFTA_SET_ELEM_OBJREF: stateful object reference (NLA_STRING)
+ */
+enum nft_set_elem_attributes {
+	NFTA_SET_ELEM_UNSPEC,
+	NFTA_SET_ELEM_KEY,
+	NFTA_SET_ELEM_DATA,
+	NFTA_SET_ELEM_FLAGS,
+	NFTA_SET_ELEM_TIMEOUT,
+	NFTA_SET_ELEM_EXPIRATION,
+	NFTA_SET_ELEM_USERDATA,
+	NFTA_SET_ELEM_EXPR,
+	NFTA_SET_ELEM_PAD,
+	NFTA_SET_ELEM_OBJREF,
+	__NFTA_SET_ELEM_MAX
+};
+#define NFTA_SET_ELEM_MAX	(__NFTA_SET_ELEM_MAX - 1)
+
+/**
+ * enum nft_set_elem_list_attributes - nf_tables set element list netlink attributes
+ *
+ * @NFTA_SET_ELEM_LIST_TABLE: table of the set to be changed (NLA_STRING)
+ * @NFTA_SET_ELEM_LIST_SET: name of the set to be changed (NLA_STRING)
+ * @NFTA_SET_ELEM_LIST_ELEMENTS: list of set elements (NLA_NESTED: nft_set_elem_attributes)
+ * @NFTA_SET_ELEM_LIST_SET_ID: uniquely identifies a set in a transaction (NLA_U32)
+ */
+enum nft_set_elem_list_attributes {
+	NFTA_SET_ELEM_LIST_UNSPEC,
+	NFTA_SET_ELEM_LIST_TABLE,
+	NFTA_SET_ELEM_LIST_SET,
+	NFTA_SET_ELEM_LIST_ELEMENTS,
+	NFTA_SET_ELEM_LIST_SET_ID,
+	__NFTA_SET_ELEM_LIST_MAX
+};
+#define NFTA_SET_ELEM_LIST_MAX	(__NFTA_SET_ELEM_LIST_MAX - 1)
+
+/**
+ * enum nft_data_types - nf_tables data types
+ *
+ * @NFT_DATA_VALUE: generic data
+ * @NFT_DATA_VERDICT: netfilter verdict
+ *
+ * The type of data is usually determined by the kernel directly and is not
+ * explicitly specified by userspace. The only difference are sets, where
+ * userspace specifies the key and mapping data types.
+ *
+ * The values 0xffffff00-0xffffffff are reserved for internally used types.
+ * The remaining range can be freely used by userspace to encode types, all
+ * values are equivalent to NFT_DATA_VALUE.
+ */
+enum nft_data_types {
+	NFT_DATA_VALUE,
+	NFT_DATA_VERDICT	= 0xffffff00U,
+};
+
+#define NFT_DATA_RESERVED_MASK	0xffffff00U
+
+/**
+ * enum nft_data_attributes - nf_tables data netlink attributes
+ *
+ * @NFTA_DATA_VALUE: generic data (NLA_BINARY)
+ * @NFTA_DATA_VERDICT: nf_tables verdict (NLA_NESTED: nft_verdict_attributes)
+ */
+enum nft_data_attributes {
+	NFTA_DATA_UNSPEC,
+	NFTA_DATA_VALUE,
+	NFTA_DATA_VERDICT,
+	__NFTA_DATA_MAX
+};
+#define NFTA_DATA_MAX		(__NFTA_DATA_MAX - 1)
+
+/* Maximum length of a value */
+#define NFT_DATA_VALUE_MAXLEN	64
+
+/**
+ * enum nft_verdict_attributes - nf_tables verdict netlink attributes
+ *
+ * @NFTA_VERDICT_CODE: nf_tables verdict (NLA_U32: enum nft_verdicts)
+ * @NFTA_VERDICT_CHAIN: jump target chain name (NLA_STRING)
+ */
+enum nft_verdict_attributes {
+	NFTA_VERDICT_UNSPEC,
+	NFTA_VERDICT_CODE,
+	NFTA_VERDICT_CHAIN,
+	__NFTA_VERDICT_MAX
+};
+#define NFTA_VERDICT_MAX	(__NFTA_VERDICT_MAX - 1)
+
+/**
+ * enum nft_expr_attributes - nf_tables expression netlink attributes
+ *
+ * @NFTA_EXPR_NAME: name of the expression type (NLA_STRING)
+ * @NFTA_EXPR_DATA: type specific data (NLA_NESTED)
+ */
+enum nft_expr_attributes {
+	NFTA_EXPR_UNSPEC,
+	NFTA_EXPR_NAME,
+	NFTA_EXPR_DATA,
+	__NFTA_EXPR_MAX
+};
+#define NFTA_EXPR_MAX		(__NFTA_EXPR_MAX - 1)
+
+/**
+ * enum nft_immediate_attributes - nf_tables immediate expression netlink attributes
+ *
+ * @NFTA_IMMEDIATE_DREG: destination register to load data into (NLA_U32)
+ * @NFTA_IMMEDIATE_DATA: data to load (NLA_NESTED: nft_data_attributes)
+ */
+enum nft_immediate_attributes {
+	NFTA_IMMEDIATE_UNSPEC,
+	NFTA_IMMEDIATE_DREG,
+	NFTA_IMMEDIATE_DATA,
+	__NFTA_IMMEDIATE_MAX
+};
+#define NFTA_IMMEDIATE_MAX	(__NFTA_IMMEDIATE_MAX - 1)
+
+/**
+ * enum nft_bitwise_attributes - nf_tables bitwise expression netlink attributes
+ *
+ * @NFTA_BITWISE_SREG: source register (NLA_U32: nft_registers)
+ * @NFTA_BITWISE_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_BITWISE_LEN: length of operands (NLA_U32)
+ * @NFTA_BITWISE_MASK: mask value (NLA_NESTED: nft_data_attributes)
+ * @NFTA_BITWISE_XOR: xor value (NLA_NESTED: nft_data_attributes)
+ *
+ * The bitwise expression performs the following operation:
+ *
+ * dreg = (sreg & mask) ^ xor
+ *
+ * which allow to express all bitwise operations:
+ *
+ * 		mask	xor
+ * NOT:		1	1
+ * OR:		0	x
+ * XOR:		1	x
+ * AND:		x	0
+ */
+enum nft_bitwise_attributes {
+	NFTA_BITWISE_UNSPEC,
+	NFTA_BITWISE_SREG,
+	NFTA_BITWISE_DREG,
+	NFTA_BITWISE_LEN,
+	NFTA_BITWISE_MASK,
+	NFTA_BITWISE_XOR,
+	__NFTA_BITWISE_MAX
+};
+#define NFTA_BITWISE_MAX	(__NFTA_BITWISE_MAX - 1)
+
+/**
+ * enum nft_byteorder_ops - nf_tables byteorder operators
+ *
+ * @NFT_BYTEORDER_NTOH: network to host operator
+ * @NFT_BYTEORDER_HTON: host to network operator
+ */
+enum nft_byteorder_ops {
+	NFT_BYTEORDER_NTOH,
+	NFT_BYTEORDER_HTON,
+};
+
+/**
+ * enum nft_byteorder_attributes - nf_tables byteorder expression netlink attributes
+ *
+ * @NFTA_BYTEORDER_SREG: source register (NLA_U32: nft_registers)
+ * @NFTA_BYTEORDER_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_BYTEORDER_OP: operator (NLA_U32: enum nft_byteorder_ops)
+ * @NFTA_BYTEORDER_LEN: length of the data (NLA_U32)
+ * @NFTA_BYTEORDER_SIZE: data size in bytes (NLA_U32: 2 or 4)
+ */
+enum nft_byteorder_attributes {
+	NFTA_BYTEORDER_UNSPEC,
+	NFTA_BYTEORDER_SREG,
+	NFTA_BYTEORDER_DREG,
+	NFTA_BYTEORDER_OP,
+	NFTA_BYTEORDER_LEN,
+	NFTA_BYTEORDER_SIZE,
+	__NFTA_BYTEORDER_MAX
+};
+#define NFTA_BYTEORDER_MAX	(__NFTA_BYTEORDER_MAX - 1)
+
+/**
+ * enum nft_cmp_ops - nf_tables relational operator
+ *
+ * @NFT_CMP_EQ: equal
+ * @NFT_CMP_NEQ: not equal
+ * @NFT_CMP_LT: less than
+ * @NFT_CMP_LTE: less than or equal to
+ * @NFT_CMP_GT: greater than
+ * @NFT_CMP_GTE: greater than or equal to
+ */
+enum nft_cmp_ops {
+	NFT_CMP_EQ,
+	NFT_CMP_NEQ,
+	NFT_CMP_LT,
+	NFT_CMP_LTE,
+	NFT_CMP_GT,
+	NFT_CMP_GTE,
+};
+
+/**
+ * enum nft_cmp_attributes - nf_tables cmp expression netlink attributes
+ *
+ * @NFTA_CMP_SREG: source register of data to compare (NLA_U32: nft_registers)
+ * @NFTA_CMP_OP: cmp operation (NLA_U32: nft_cmp_ops)
+ * @NFTA_CMP_DATA: data to compare against (NLA_NESTED: nft_data_attributes)
+ */
+enum nft_cmp_attributes {
+	NFTA_CMP_UNSPEC,
+	NFTA_CMP_SREG,
+	NFTA_CMP_OP,
+	NFTA_CMP_DATA,
+	__NFTA_CMP_MAX
+};
+#define NFTA_CMP_MAX		(__NFTA_CMP_MAX - 1)
+
+/**
+ * enum nft_range_ops - nf_tables range operator
+ *
+ * @NFT_RANGE_EQ: equal
+ * @NFT_RANGE_NEQ: not equal
+ */
+enum nft_range_ops {
+	NFT_RANGE_EQ,
+	NFT_RANGE_NEQ,
+};
+
+/**
+ * enum nft_range_attributes - nf_tables range expression netlink attributes
+ *
+ * @NFTA_RANGE_SREG: source register of data to compare (NLA_U32: nft_registers)
+ * @NFTA_RANGE_OP: cmp operation (NLA_U32: nft_cmp_ops)
+ * @NFTA_RANGE_FROM_DATA: data range from (NLA_NESTED: nft_data_attributes)
+ * @NFTA_RANGE_TO_DATA: data range to (NLA_NESTED: nft_data_attributes)
+ */
+enum nft_range_attributes {
+	NFTA_RANGE_UNSPEC,
+	NFTA_RANGE_SREG,
+	NFTA_RANGE_OP,
+	NFTA_RANGE_FROM_DATA,
+	NFTA_RANGE_TO_DATA,
+	__NFTA_RANGE_MAX
+};
+#define NFTA_RANGE_MAX		(__NFTA_RANGE_MAX - 1)
+
+enum nft_lookup_flags {
+	NFT_LOOKUP_F_INV = (1 << 0),
+};
+
+/**
+ * enum nft_lookup_attributes - nf_tables set lookup expression netlink attributes
+ *
+ * @NFTA_LOOKUP_SET: name of the set where to look for (NLA_STRING)
+ * @NFTA_LOOKUP_SREG: source register of the data to look for (NLA_U32: nft_registers)
+ * @NFTA_LOOKUP_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_LOOKUP_SET_ID: uniquely identifies a set in a transaction (NLA_U32)
+ * @NFTA_LOOKUP_FLAGS: flags (NLA_U32: enum nft_lookup_flags)
+ */
+enum nft_lookup_attributes {
+	NFTA_LOOKUP_UNSPEC,
+	NFTA_LOOKUP_SET,
+	NFTA_LOOKUP_SREG,
+	NFTA_LOOKUP_DREG,
+	NFTA_LOOKUP_SET_ID,
+	NFTA_LOOKUP_FLAGS,
+	__NFTA_LOOKUP_MAX
+};
+#define NFTA_LOOKUP_MAX		(__NFTA_LOOKUP_MAX - 1)
+
+enum nft_dynset_ops {
+	NFT_DYNSET_OP_ADD,
+	NFT_DYNSET_OP_UPDATE,
+};
+
+enum nft_dynset_flags {
+	NFT_DYNSET_F_INV	= (1 << 0),
+};
+
+/**
+ * enum nft_dynset_attributes - dynset expression attributes
+ *
+ * @NFTA_DYNSET_SET_NAME: name of set the to add data to (NLA_STRING)
+ * @NFTA_DYNSET_SET_ID: uniquely identifier of the set in the transaction (NLA_U32)
+ * @NFTA_DYNSET_OP: operation (NLA_U32)
+ * @NFTA_DYNSET_SREG_KEY: source register of the key (NLA_U32)
+ * @NFTA_DYNSET_SREG_DATA: source register of the data (NLA_U32)
+ * @NFTA_DYNSET_TIMEOUT: timeout value for the new element (NLA_U64)
+ * @NFTA_DYNSET_EXPR: expression (NLA_NESTED: nft_expr_attributes)
+ * @NFTA_DYNSET_FLAGS: flags (NLA_U32)
+ */
+enum nft_dynset_attributes {
+	NFTA_DYNSET_UNSPEC,
+	NFTA_DYNSET_SET_NAME,
+	NFTA_DYNSET_SET_ID,
+	NFTA_DYNSET_OP,
+	NFTA_DYNSET_SREG_KEY,
+	NFTA_DYNSET_SREG_DATA,
+	NFTA_DYNSET_TIMEOUT,
+	NFTA_DYNSET_EXPR,
+	NFTA_DYNSET_PAD,
+	NFTA_DYNSET_FLAGS,
+	__NFTA_DYNSET_MAX,
+};
+#define NFTA_DYNSET_MAX		(__NFTA_DYNSET_MAX - 1)
+
+/**
+ * enum nft_payload_bases - nf_tables payload expression offset bases
+ *
+ * @NFT_PAYLOAD_LL_HEADER: link layer header
+ * @NFT_PAYLOAD_NETWORK_HEADER: network header
+ * @NFT_PAYLOAD_TRANSPORT_HEADER: transport header
+ */
+enum nft_payload_bases {
+	NFT_PAYLOAD_LL_HEADER,
+	NFT_PAYLOAD_NETWORK_HEADER,
+	NFT_PAYLOAD_TRANSPORT_HEADER,
+};
+
+/**
+ * enum nft_payload_csum_types - nf_tables payload expression checksum types
+ *
+ * @NFT_PAYLOAD_CSUM_NONE: no checksumming
+ * @NFT_PAYLOAD_CSUM_INET: internet checksum (RFC 791)
+ */
+enum nft_payload_csum_types {
+	NFT_PAYLOAD_CSUM_NONE,
+	NFT_PAYLOAD_CSUM_INET,
+};
+
+enum nft_payload_csum_flags {
+	NFT_PAYLOAD_L4CSUM_PSEUDOHDR = (1 << 0),
+};
+
+/**
+ * enum nft_payload_attributes - nf_tables payload expression netlink attributes
+ *
+ * @NFTA_PAYLOAD_DREG: destination register to load data into (NLA_U32: nft_registers)
+ * @NFTA_PAYLOAD_BASE: payload base (NLA_U32: nft_payload_bases)
+ * @NFTA_PAYLOAD_OFFSET: payload offset relative to base (NLA_U32)
+ * @NFTA_PAYLOAD_LEN: payload length (NLA_U32)
+ * @NFTA_PAYLOAD_SREG: source register to load data from (NLA_U32: nft_registers)
+ * @NFTA_PAYLOAD_CSUM_TYPE: checksum type (NLA_U32)
+ * @NFTA_PAYLOAD_CSUM_OFFSET: checksum offset relative to base (NLA_U32)
+ * @NFTA_PAYLOAD_CSUM_FLAGS: checksum flags (NLA_U32)
+ */
+enum nft_payload_attributes {
+	NFTA_PAYLOAD_UNSPEC,
+	NFTA_PAYLOAD_DREG,
+	NFTA_PAYLOAD_BASE,
+	NFTA_PAYLOAD_OFFSET,
+	NFTA_PAYLOAD_LEN,
+	NFTA_PAYLOAD_SREG,
+	NFTA_PAYLOAD_CSUM_TYPE,
+	NFTA_PAYLOAD_CSUM_OFFSET,
+	NFTA_PAYLOAD_CSUM_FLAGS,
+	__NFTA_PAYLOAD_MAX
+};
+#define NFTA_PAYLOAD_MAX	(__NFTA_PAYLOAD_MAX - 1)
+
+enum nft_exthdr_flags {
+	NFT_EXTHDR_F_PRESENT = (1 << 0),
+};
+
+/**
+ * enum nft_exthdr_op - nf_tables match options
+ *
+ * @NFT_EXTHDR_OP_IPV6: match against ipv6 extension headers
+ * @NFT_EXTHDR_OP_TCP: match against tcp options
+ */
+enum nft_exthdr_op {
+	NFT_EXTHDR_OP_IPV6,
+	NFT_EXTHDR_OP_TCPOPT,
+	__NFT_EXTHDR_OP_MAX
+};
+#define NFT_EXTHDR_OP_MAX	(__NFT_EXTHDR_OP_MAX - 1)
+
+/**
+ * enum nft_exthdr_attributes - nf_tables extension header expression netlink attributes
+ *
+ * @NFTA_EXTHDR_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_EXTHDR_TYPE: extension header type (NLA_U8)
+ * @NFTA_EXTHDR_OFFSET: extension header offset (NLA_U32)
+ * @NFTA_EXTHDR_LEN: extension header length (NLA_U32)
+ * @NFTA_EXTHDR_FLAGS: extension header flags (NLA_U32)
+ * @NFTA_EXTHDR_OP: option match type (NLA_U32)
+ * @NFTA_EXTHDR_SREG: option match type (NLA_U32)
+ */
+enum nft_exthdr_attributes {
+	NFTA_EXTHDR_UNSPEC,
+	NFTA_EXTHDR_DREG,
+	NFTA_EXTHDR_TYPE,
+	NFTA_EXTHDR_OFFSET,
+	NFTA_EXTHDR_LEN,
+	NFTA_EXTHDR_FLAGS,
+	NFTA_EXTHDR_OP,
+	NFTA_EXTHDR_SREG,
+	__NFTA_EXTHDR_MAX
+};
+#define NFTA_EXTHDR_MAX		(__NFTA_EXTHDR_MAX - 1)
+
+/**
+ * enum nft_meta_keys - nf_tables meta expression keys
+ *
+ * @NFT_META_LEN: packet length (skb->len)
+ * @NFT_META_PROTOCOL: packet ethertype protocol (skb->protocol), invalid in OUTPUT
+ * @NFT_META_PRIORITY: packet priority (skb->priority)
+ * @NFT_META_MARK: packet mark (skb->mark)
+ * @NFT_META_IIF: packet input interface index (dev->ifindex)
+ * @NFT_META_OIF: packet output interface index (dev->ifindex)
+ * @NFT_META_IIFNAME: packet input interface name (dev->name)
+ * @NFT_META_OIFNAME: packet output interface name (dev->name)
+ * @NFT_META_IIFTYPE: packet input interface type (dev->type)
+ * @NFT_META_OIFTYPE: packet output interface type (dev->type)
+ * @NFT_META_SKUID: originating socket UID (fsuid)
+ * @NFT_META_SKGID: originating socket GID (fsgid)
+ * @NFT_META_NFTRACE: packet nftrace bit
+ * @NFT_META_RTCLASSID: realm value of packet's route (skb->dst->tclassid)
+ * @NFT_META_SECMARK: packet secmark (skb->secmark)
+ * @NFT_META_NFPROTO: netfilter protocol
+ * @NFT_META_L4PROTO: layer 4 protocol number
+ * @NFT_META_BRI_IIFNAME: packet input bridge interface name
+ * @NFT_META_BRI_OIFNAME: packet output bridge interface name
+ * @NFT_META_PKTTYPE: packet type (skb->pkt_type), special handling for loopback
+ * @NFT_META_CPU: cpu id through smp_processor_id()
+ * @NFT_META_IIFGROUP: packet input interface group
+ * @NFT_META_OIFGROUP: packet output interface group
+ * @NFT_META_CGROUP: socket control group (skb->sk->sk_classid)
+ * @NFT_META_PRANDOM: a 32bit pseudo-random number
+ * @NFT_META_SECPATH: boolean, secpath_exists (!!skb->sp)
+ */
+enum nft_meta_keys {
+	NFT_META_LEN,
+	NFT_META_PROTOCOL,
+	NFT_META_PRIORITY,
+	NFT_META_MARK,
+	NFT_META_IIF,
+	NFT_META_OIF,
+	NFT_META_IIFNAME,
+	NFT_META_OIFNAME,
+	NFT_META_IIFTYPE,
+	NFT_META_OIFTYPE,
+	NFT_META_SKUID,
+	NFT_META_SKGID,
+	NFT_META_NFTRACE,
+	NFT_META_RTCLASSID,
+	NFT_META_SECMARK,
+	NFT_META_NFPROTO,
+	NFT_META_L4PROTO,
+	NFT_META_BRI_IIFNAME,
+	NFT_META_BRI_OIFNAME,
+	NFT_META_PKTTYPE,
+	NFT_META_CPU,
+	NFT_META_IIFGROUP,
+	NFT_META_OIFGROUP,
+	NFT_META_CGROUP,
+	NFT_META_PRANDOM,
+	NFT_META_SECPATH,
+};
+
+/**
+ * enum nft_rt_keys - nf_tables routing expression keys
+ *
+ * @NFT_RT_CLASSID: realm value of packet's route (skb->dst->tclassid)
+ * @NFT_RT_NEXTHOP4: routing nexthop for IPv4
+ * @NFT_RT_NEXTHOP6: routing nexthop for IPv6
+ * @NFT_RT_TCPMSS: fetch current path tcp mss
+ */
+enum nft_rt_keys {
+	NFT_RT_CLASSID,
+	NFT_RT_NEXTHOP4,
+	NFT_RT_NEXTHOP6,
+	NFT_RT_TCPMSS,
+};
+
+/**
+ * enum nft_hash_types - nf_tables hash expression types
+ *
+ * @NFT_HASH_JENKINS: Jenkins Hash
+ * @NFT_HASH_SYM: Symmetric Hash
+ */
+enum nft_hash_types {
+	NFT_HASH_JENKINS,
+	NFT_HASH_SYM,
+};
+
+/**
+ * enum nft_hash_attributes - nf_tables hash expression netlink attributes
+ *
+ * @NFTA_HASH_SREG: source register (NLA_U32)
+ * @NFTA_HASH_DREG: destination register (NLA_U32)
+ * @NFTA_HASH_LEN: source data length (NLA_U32)
+ * @NFTA_HASH_MODULUS: modulus value (NLA_U32)
+ * @NFTA_HASH_SEED: seed value (NLA_U32)
+ * @NFTA_HASH_OFFSET: add this offset value to hash result (NLA_U32)
+ * @NFTA_HASH_TYPE: hash operation (NLA_U32: nft_hash_types)
+ */
+enum nft_hash_attributes {
+	NFTA_HASH_UNSPEC,
+	NFTA_HASH_SREG,
+	NFTA_HASH_DREG,
+	NFTA_HASH_LEN,
+	NFTA_HASH_MODULUS,
+	NFTA_HASH_SEED,
+	NFTA_HASH_OFFSET,
+	NFTA_HASH_TYPE,
+	__NFTA_HASH_MAX,
+};
+#define NFTA_HASH_MAX	(__NFTA_HASH_MAX - 1)
+
+/**
+ * enum nft_meta_attributes - nf_tables meta expression netlink attributes
+ *
+ * @NFTA_META_DREG: destination register (NLA_U32)
+ * @NFTA_META_KEY: meta data item to load (NLA_U32: nft_meta_keys)
+ * @NFTA_META_SREG: source register (NLA_U32)
+ */
+enum nft_meta_attributes {
+	NFTA_META_UNSPEC,
+	NFTA_META_DREG,
+	NFTA_META_KEY,
+	NFTA_META_SREG,
+	__NFTA_META_MAX
+};
+#define NFTA_META_MAX		(__NFTA_META_MAX - 1)
+
+/**
+ * enum nft_rt_attributes - nf_tables routing expression netlink attributes
+ *
+ * @NFTA_RT_DREG: destination register (NLA_U32)
+ * @NFTA_RT_KEY: routing data item to load (NLA_U32: nft_rt_keys)
+ */
+enum nft_rt_attributes {
+	NFTA_RT_UNSPEC,
+	NFTA_RT_DREG,
+	NFTA_RT_KEY,
+	__NFTA_RT_MAX
+};
+#define NFTA_RT_MAX		(__NFTA_RT_MAX - 1)
+
+/**
+ * enum nft_ct_keys - nf_tables ct expression keys
+ *
+ * @NFT_CT_STATE: conntrack state (bitmask of enum ip_conntrack_info)
+ * @NFT_CT_DIRECTION: conntrack direction (enum ip_conntrack_dir)
+ * @NFT_CT_STATUS: conntrack status (bitmask of enum ip_conntrack_status)
+ * @NFT_CT_MARK: conntrack mark value
+ * @NFT_CT_SECMARK: conntrack secmark value
+ * @NFT_CT_EXPIRATION: relative conntrack expiration time in ms
+ * @NFT_CT_HELPER: connection tracking helper assigned to conntrack
+ * @NFT_CT_L3PROTOCOL: conntrack layer 3 protocol
+ * @NFT_CT_SRC: conntrack layer 3 protocol source (IPv4/IPv6 address)
+ * @NFT_CT_DST: conntrack layer 3 protocol destination (IPv4/IPv6 address)
+ * @NFT_CT_PROTOCOL: conntrack layer 4 protocol
+ * @NFT_CT_PROTO_SRC: conntrack layer 4 protocol source
+ * @NFT_CT_PROTO_DST: conntrack layer 4 protocol destination
+ * @NFT_CT_LABELS: conntrack labels
+ * @NFT_CT_PKTS: conntrack packets
+ * @NFT_CT_BYTES: conntrack bytes
+ * @NFT_CT_AVGPKT: conntrack average bytes per packet
+ * @NFT_CT_ZONE: conntrack zone
+ * @NFT_CT_EVENTMASK: ctnetlink events to be generated for this conntrack
+ */
+enum nft_ct_keys {
+	NFT_CT_STATE,
+	NFT_CT_DIRECTION,
+	NFT_CT_STATUS,
+	NFT_CT_MARK,
+	NFT_CT_SECMARK,
+	NFT_CT_EXPIRATION,
+	NFT_CT_HELPER,
+	NFT_CT_L3PROTOCOL,
+	NFT_CT_SRC,
+	NFT_CT_DST,
+	NFT_CT_PROTOCOL,
+	NFT_CT_PROTO_SRC,
+	NFT_CT_PROTO_DST,
+	NFT_CT_LABELS,
+	NFT_CT_PKTS,
+	NFT_CT_BYTES,
+	NFT_CT_AVGPKT,
+	NFT_CT_ZONE,
+	NFT_CT_EVENTMASK,
+};
+
+/**
+ * enum nft_ct_attributes - nf_tables ct expression netlink attributes
+ *
+ * @NFTA_CT_DREG: destination register (NLA_U32)
+ * @NFTA_CT_KEY: conntrack data item to load (NLA_U32: nft_ct_keys)
+ * @NFTA_CT_DIRECTION: direction in case of directional keys (NLA_U8)
+ * @NFTA_CT_SREG: source register (NLA_U32)
+ */
+enum nft_ct_attributes {
+	NFTA_CT_UNSPEC,
+	NFTA_CT_DREG,
+	NFTA_CT_KEY,
+	NFTA_CT_DIRECTION,
+	NFTA_CT_SREG,
+	__NFTA_CT_MAX
+};
+#define NFTA_CT_MAX		(__NFTA_CT_MAX - 1)
+
+/**
+ * enum nft_flow_attributes - ct offload expression attributes
+ * @NFTA_FLOW_TABLE_NAME: flow table name (NLA_STRING)
+ */
+enum nft_offload_attributes {
+	NFTA_FLOW_UNSPEC,
+	NFTA_FLOW_TABLE_NAME,
+	__NFTA_FLOW_MAX,
+};
+#define NFTA_FLOW_MAX		(__NFTA_FLOW_MAX - 1)
+
+enum nft_limit_type {
+	NFT_LIMIT_PKTS,
+	NFT_LIMIT_PKT_BYTES
+};
+
+enum nft_limit_flags {
+	NFT_LIMIT_F_INV	= (1 << 0),
+};
+
+/**
+ * enum nft_limit_attributes - nf_tables limit expression netlink attributes
+ *
+ * @NFTA_LIMIT_RATE: refill rate (NLA_U64)
+ * @NFTA_LIMIT_UNIT: refill unit (NLA_U64)
+ * @NFTA_LIMIT_BURST: burst (NLA_U32)
+ * @NFTA_LIMIT_TYPE: type of limit (NLA_U32: enum nft_limit_type)
+ * @NFTA_LIMIT_FLAGS: flags (NLA_U32: enum nft_limit_flags)
+ */
+enum nft_limit_attributes {
+	NFTA_LIMIT_UNSPEC,
+	NFTA_LIMIT_RATE,
+	NFTA_LIMIT_UNIT,
+	NFTA_LIMIT_BURST,
+	NFTA_LIMIT_TYPE,
+	NFTA_LIMIT_FLAGS,
+	NFTA_LIMIT_PAD,
+	__NFTA_LIMIT_MAX
+};
+#define NFTA_LIMIT_MAX		(__NFTA_LIMIT_MAX - 1)
+
+/**
+ * enum nft_counter_attributes - nf_tables counter expression netlink attributes
+ *
+ * @NFTA_COUNTER_BYTES: number of bytes (NLA_U64)
+ * @NFTA_COUNTER_PACKETS: number of packets (NLA_U64)
+ */
+enum nft_counter_attributes {
+	NFTA_COUNTER_UNSPEC,
+	NFTA_COUNTER_BYTES,
+	NFTA_COUNTER_PACKETS,
+	NFTA_COUNTER_PAD,
+	__NFTA_COUNTER_MAX
+};
+#define NFTA_COUNTER_MAX	(__NFTA_COUNTER_MAX - 1)
+
+/**
+ * enum nft_log_attributes - nf_tables log expression netlink attributes
+ *
+ * @NFTA_LOG_GROUP: netlink group to send messages to (NLA_U32)
+ * @NFTA_LOG_PREFIX: prefix to prepend to log messages (NLA_STRING)
+ * @NFTA_LOG_SNAPLEN: length of payload to include in netlink message (NLA_U32)
+ * @NFTA_LOG_QTHRESHOLD: queue threshold (NLA_U32)
+ * @NFTA_LOG_LEVEL: log level (NLA_U32)
+ * @NFTA_LOG_FLAGS: logging flags (NLA_U32)
+ */
+enum nft_log_attributes {
+	NFTA_LOG_UNSPEC,
+	NFTA_LOG_GROUP,
+	NFTA_LOG_PREFIX,
+	NFTA_LOG_SNAPLEN,
+	NFTA_LOG_QTHRESHOLD,
+	NFTA_LOG_LEVEL,
+	NFTA_LOG_FLAGS,
+	__NFTA_LOG_MAX
+};
+#define NFTA_LOG_MAX		(__NFTA_LOG_MAX - 1)
+
+/**
+ * enum nft_queue_attributes - nf_tables queue expression netlink attributes
+ *
+ * @NFTA_QUEUE_NUM: netlink queue to send messages to (NLA_U16)
+ * @NFTA_QUEUE_TOTAL: number of queues to load balance packets on (NLA_U16)
+ * @NFTA_QUEUE_FLAGS: various flags (NLA_U16)
+ * @NFTA_QUEUE_SREG_QNUM: source register of queue number (NLA_U32: nft_registers)
+ */
+enum nft_queue_attributes {
+	NFTA_QUEUE_UNSPEC,
+	NFTA_QUEUE_NUM,
+	NFTA_QUEUE_TOTAL,
+	NFTA_QUEUE_FLAGS,
+	NFTA_QUEUE_SREG_QNUM,
+	__NFTA_QUEUE_MAX
+};
+#define NFTA_QUEUE_MAX		(__NFTA_QUEUE_MAX - 1)
+
+#define NFT_QUEUE_FLAG_BYPASS		0x01 /* for compatibility with v2 */
+#define NFT_QUEUE_FLAG_CPU_FANOUT	0x02 /* use current CPU (no hashing) */
+#define NFT_QUEUE_FLAG_MASK		0x03
+
+enum nft_quota_flags {
+	NFT_QUOTA_F_INV		= (1 << 0),
+	NFT_QUOTA_F_DEPLETED	= (1 << 1),
+};
+
+/**
+ * enum nft_quota_attributes - nf_tables quota expression netlink attributes
+ *
+ * @NFTA_QUOTA_BYTES: quota in bytes (NLA_U16)
+ * @NFTA_QUOTA_FLAGS: flags (NLA_U32)
+ * @NFTA_QUOTA_CONSUMED: quota already consumed in bytes (NLA_U64)
+ */
+enum nft_quota_attributes {
+	NFTA_QUOTA_UNSPEC,
+	NFTA_QUOTA_BYTES,
+	NFTA_QUOTA_FLAGS,
+	NFTA_QUOTA_PAD,
+	NFTA_QUOTA_CONSUMED,
+	__NFTA_QUOTA_MAX
+};
+#define NFTA_QUOTA_MAX		(__NFTA_QUOTA_MAX - 1)
+
+/**
+ * enum nft_reject_types - nf_tables reject expression reject types
+ *
+ * @NFT_REJECT_ICMP_UNREACH: reject using ICMP unreachable
+ * @NFT_REJECT_TCP_RST: reject using TCP RST
+ * @NFT_REJECT_ICMPX_UNREACH: abstracted ICMP unreachable for bridge and inet
+ */
+enum nft_reject_types {
+	NFT_REJECT_ICMP_UNREACH,
+	NFT_REJECT_TCP_RST,
+	NFT_REJECT_ICMPX_UNREACH,
+};
+
+/**
+ * enum nft_reject_code - Generic reject codes for IPv4/IPv6
+ *
+ * @NFT_REJECT_ICMPX_NO_ROUTE: no route to host / network unreachable
+ * @NFT_REJECT_ICMPX_PORT_UNREACH: port unreachable
+ * @NFT_REJECT_ICMPX_HOST_UNREACH: host unreachable
+ * @NFT_REJECT_ICMPX_ADMIN_PROHIBITED: administratively prohibited
+ *
+ * These codes are mapped to real ICMP and ICMPv6 codes.
+ */
+enum nft_reject_inet_code {
+	NFT_REJECT_ICMPX_NO_ROUTE	= 0,
+	NFT_REJECT_ICMPX_PORT_UNREACH,
+	NFT_REJECT_ICMPX_HOST_UNREACH,
+	NFT_REJECT_ICMPX_ADMIN_PROHIBITED,
+	__NFT_REJECT_ICMPX_MAX
+};
+#define NFT_REJECT_ICMPX_MAX	(__NFT_REJECT_ICMPX_MAX - 1)
+
+/**
+ * enum nft_reject_attributes - nf_tables reject expression netlink attributes
+ *
+ * @NFTA_REJECT_TYPE: packet type to use (NLA_U32: nft_reject_types)
+ * @NFTA_REJECT_ICMP_CODE: ICMP code to use (NLA_U8)
+ */
+enum nft_reject_attributes {
+	NFTA_REJECT_UNSPEC,
+	NFTA_REJECT_TYPE,
+	NFTA_REJECT_ICMP_CODE,
+	__NFTA_REJECT_MAX
+};
+#define NFTA_REJECT_MAX		(__NFTA_REJECT_MAX - 1)
+
+/**
+ * enum nft_nat_types - nf_tables nat expression NAT types
+ *
+ * @NFT_NAT_SNAT: source NAT
+ * @NFT_NAT_DNAT: destination NAT
+ */
+enum nft_nat_types {
+	NFT_NAT_SNAT,
+	NFT_NAT_DNAT,
+};
+
+/**
+ * enum nft_nat_attributes - nf_tables nat expression netlink attributes
+ *
+ * @NFTA_NAT_TYPE: NAT type (NLA_U32: nft_nat_types)
+ * @NFTA_NAT_FAMILY: NAT family (NLA_U32)
+ * @NFTA_NAT_REG_ADDR_MIN: source register of address range start (NLA_U32: nft_registers)
+ * @NFTA_NAT_REG_ADDR_MAX: source register of address range end (NLA_U32: nft_registers)
+ * @NFTA_NAT_REG_PROTO_MIN: source register of proto range start (NLA_U32: nft_registers)
+ * @NFTA_NAT_REG_PROTO_MAX: source register of proto range end (NLA_U32: nft_registers)
+ * @NFTA_NAT_FLAGS: NAT flags (see NF_NAT_RANGE_* in linux/netfilter/nf_nat.h) (NLA_U32)
+ */
+enum nft_nat_attributes {
+	NFTA_NAT_UNSPEC,
+	NFTA_NAT_TYPE,
+	NFTA_NAT_FAMILY,
+	NFTA_NAT_REG_ADDR_MIN,
+	NFTA_NAT_REG_ADDR_MAX,
+	NFTA_NAT_REG_PROTO_MIN,
+	NFTA_NAT_REG_PROTO_MAX,
+	NFTA_NAT_FLAGS,
+	__NFTA_NAT_MAX
+};
+#define NFTA_NAT_MAX		(__NFTA_NAT_MAX - 1)
+
+/**
+ * enum nft_masq_attributes - nf_tables masquerade expression attributes
+ *
+ * @NFTA_MASQ_FLAGS: NAT flags (see NF_NAT_RANGE_* in linux/netfilter/nf_nat.h) (NLA_U32)
+ * @NFTA_MASQ_REG_PROTO_MIN: source register of proto range start (NLA_U32: nft_registers)
+ * @NFTA_MASQ_REG_PROTO_MAX: source register of proto range end (NLA_U32: nft_registers)
+ */
+enum nft_masq_attributes {
+	NFTA_MASQ_UNSPEC,
+	NFTA_MASQ_FLAGS,
+	NFTA_MASQ_REG_PROTO_MIN,
+	NFTA_MASQ_REG_PROTO_MAX,
+	__NFTA_MASQ_MAX
+};
+#define NFTA_MASQ_MAX		(__NFTA_MASQ_MAX - 1)
+
+/**
+ * enum nft_redir_attributes - nf_tables redirect expression netlink attributes
+ *
+ * @NFTA_REDIR_REG_PROTO_MIN: source register of proto range start (NLA_U32: nft_registers)
+ * @NFTA_REDIR_REG_PROTO_MAX: source register of proto range end (NLA_U32: nft_registers)
+ * @NFTA_REDIR_FLAGS: NAT flags (see NF_NAT_RANGE_* in linux/netfilter/nf_nat.h) (NLA_U32)
+ */
+enum nft_redir_attributes {
+	NFTA_REDIR_UNSPEC,
+	NFTA_REDIR_REG_PROTO_MIN,
+	NFTA_REDIR_REG_PROTO_MAX,
+	NFTA_REDIR_FLAGS,
+	__NFTA_REDIR_MAX
+};
+#define NFTA_REDIR_MAX		(__NFTA_REDIR_MAX - 1)
+
+/**
+ * enum nft_dup_attributes - nf_tables dup expression netlink attributes
+ *
+ * @NFTA_DUP_SREG_ADDR: source register of address (NLA_U32: nft_registers)
+ * @NFTA_DUP_SREG_DEV: source register of output interface (NLA_U32: nft_register)
+ */
+enum nft_dup_attributes {
+	NFTA_DUP_UNSPEC,
+	NFTA_DUP_SREG_ADDR,
+	NFTA_DUP_SREG_DEV,
+	__NFTA_DUP_MAX
+};
+#define NFTA_DUP_MAX		(__NFTA_DUP_MAX - 1)
+
+/**
+ * enum nft_fwd_attributes - nf_tables fwd expression netlink attributes
+ *
+ * @NFTA_FWD_SREG_DEV: source register of output interface (NLA_U32: nft_register)
+ */
+enum nft_fwd_attributes {
+	NFTA_FWD_UNSPEC,
+	NFTA_FWD_SREG_DEV,
+	__NFTA_FWD_MAX
+};
+#define NFTA_FWD_MAX	(__NFTA_FWD_MAX - 1)
+
+/**
+ * enum nft_objref_attributes - nf_tables stateful object expression netlink attributes
+ *
+ * @NFTA_OBJREF_IMM_TYPE: object type for immediate reference (NLA_U32: nft_register)
+ * @NFTA_OBJREF_IMM_NAME: object name for immediate reference (NLA_STRING)
+ * @NFTA_OBJREF_SET_SREG: source register of the data to look for (NLA_U32: nft_registers)
+ * @NFTA_OBJREF_SET_NAME: name of the set where to look for (NLA_STRING)
+ * @NFTA_OBJREF_SET_ID: id of the set where to look for in this transaction (NLA_U32)
+ */
+enum nft_objref_attributes {
+	NFTA_OBJREF_UNSPEC,
+	NFTA_OBJREF_IMM_TYPE,
+	NFTA_OBJREF_IMM_NAME,
+	NFTA_OBJREF_SET_SREG,
+	NFTA_OBJREF_SET_NAME,
+	NFTA_OBJREF_SET_ID,
+	__NFTA_OBJREF_MAX
+};
+#define NFTA_OBJREF_MAX	(__NFTA_OBJREF_MAX - 1)
+
+/**
+ * enum nft_gen_attributes - nf_tables ruleset generation attributes
+ *
+ * @NFTA_GEN_ID: Ruleset generation ID (NLA_U32)
+ */
+enum nft_gen_attributes {
+	NFTA_GEN_UNSPEC,
+	NFTA_GEN_ID,
+	NFTA_GEN_PROC_PID,
+	NFTA_GEN_PROC_NAME,
+	__NFTA_GEN_MAX
+};
+#define NFTA_GEN_MAX		(__NFTA_GEN_MAX - 1)
+
+/*
+ * enum nft_fib_attributes - nf_tables fib expression netlink attributes
+ *
+ * @NFTA_FIB_DREG: destination register (NLA_U32)
+ * @NFTA_FIB_RESULT: desired result (NLA_U32)
+ * @NFTA_FIB_FLAGS: flowi fields to initialize when querying the FIB (NLA_U32)
+ *
+ * The FIB expression performs a route lookup according
+ * to the packet data.
+ */
+enum nft_fib_attributes {
+	NFTA_FIB_UNSPEC,
+	NFTA_FIB_DREG,
+	NFTA_FIB_RESULT,
+	NFTA_FIB_FLAGS,
+	__NFTA_FIB_MAX
+};
+#define NFTA_FIB_MAX (__NFTA_FIB_MAX - 1)
+
+enum nft_fib_result {
+	NFT_FIB_RESULT_UNSPEC,
+	NFT_FIB_RESULT_OIF,
+	NFT_FIB_RESULT_OIFNAME,
+	NFT_FIB_RESULT_ADDRTYPE,
+	__NFT_FIB_RESULT_MAX
+};
+#define NFT_FIB_RESULT_MAX	(__NFT_FIB_RESULT_MAX - 1)
+
+enum nft_fib_flags {
+	NFTA_FIB_F_SADDR	= 1 << 0,	/* look up src */
+	NFTA_FIB_F_DADDR	= 1 << 1,	/* look up dst */
+	NFTA_FIB_F_MARK		= 1 << 2,	/* use skb->mark */
+	NFTA_FIB_F_IIF		= 1 << 3,	/* restrict to iif */
+	NFTA_FIB_F_OIF		= 1 << 4,	/* restrict to oif */
+	NFTA_FIB_F_PRESENT	= 1 << 5,	/* check existence only */
+};
+
+enum nft_ct_helper_attributes {
+	NFTA_CT_HELPER_UNSPEC,
+	NFTA_CT_HELPER_NAME,
+	NFTA_CT_HELPER_L3PROTO,
+	NFTA_CT_HELPER_L4PROTO,
+	__NFTA_CT_HELPER_MAX,
+};
+#define NFTA_CT_HELPER_MAX	(__NFTA_CT_HELPER_MAX - 1)
+
+#define NFT_OBJECT_UNSPEC	0
+#define NFT_OBJECT_COUNTER	1
+#define NFT_OBJECT_QUOTA	2
+#define NFT_OBJECT_CT_HELPER	3
+#define NFT_OBJECT_LIMIT	4
+#define __NFT_OBJECT_MAX	5
+#define NFT_OBJECT_MAX		(__NFT_OBJECT_MAX - 1)
+
+/**
+ * enum nft_object_attributes - nf_tables stateful object netlink attributes
+ *
+ * @NFTA_OBJ_TABLE: name of the table containing the expression (NLA_STRING)
+ * @NFTA_OBJ_NAME: name of this expression type (NLA_STRING)
+ * @NFTA_OBJ_TYPE: stateful object type (NLA_U32)
+ * @NFTA_OBJ_DATA: stateful object data (NLA_NESTED)
+ * @NFTA_OBJ_USE: number of references to this expression (NLA_U32)
+ * @NFTA_OBJ_HANDLE: object handle (NLA_U64)
+ */
+enum nft_object_attributes {
+	NFTA_OBJ_UNSPEC,
+	NFTA_OBJ_TABLE,
+	NFTA_OBJ_NAME,
+	NFTA_OBJ_TYPE,
+	NFTA_OBJ_DATA,
+	NFTA_OBJ_USE,
+	NFTA_OBJ_HANDLE,
+	NFTA_OBJ_PAD,
+	__NFTA_OBJ_MAX
+};
+#define NFTA_OBJ_MAX		(__NFTA_OBJ_MAX - 1)
+
+/**
+ * enum nft_flowtable_attributes - nf_tables flow table netlink attributes
+ *
+ * @NFTA_FLOWTABLE_TABLE: name of the table containing the expression (NLA_STRING)
+ * @NFTA_FLOWTABLE_NAME: name of this flow table (NLA_STRING)
+ * @NFTA_FLOWTABLE_HOOK: netfilter hook configuration(NLA_U32)
+ * @NFTA_FLOWTABLE_USE: number of references to this flow table (NLA_U32)
+ * @NFTA_FLOWTABLE_HANDLE: object handle (NLA_U64)
+ */
+enum nft_flowtable_attributes {
+	NFTA_FLOWTABLE_UNSPEC,
+	NFTA_FLOWTABLE_TABLE,
+	NFTA_FLOWTABLE_NAME,
+	NFTA_FLOWTABLE_HOOK,
+	NFTA_FLOWTABLE_USE,
+	NFTA_FLOWTABLE_HANDLE,
+	NFTA_FLOWTABLE_PAD,
+	__NFTA_FLOWTABLE_MAX
+};
+#define NFTA_FLOWTABLE_MAX	(__NFTA_FLOWTABLE_MAX - 1)
+
+/**
+ * enum nft_flowtable_hook_attributes - nf_tables flow table hook netlink attributes
+ *
+ * @NFTA_FLOWTABLE_HOOK_NUM: netfilter hook number (NLA_U32)
+ * @NFTA_FLOWTABLE_HOOK_PRIORITY: netfilter hook priority (NLA_U32)
+ * @NFTA_FLOWTABLE_HOOK_DEVS: input devices this flow table is bound to (NLA_NESTED)
+ */
+enum nft_flowtable_hook_attributes {
+	NFTA_FLOWTABLE_HOOK_UNSPEC,
+	NFTA_FLOWTABLE_HOOK_NUM,
+	NFTA_FLOWTABLE_HOOK_PRIORITY,
+	NFTA_FLOWTABLE_HOOK_DEVS,
+	__NFTA_FLOWTABLE_HOOK_MAX
+};
+#define NFTA_FLOWTABLE_HOOK_MAX	(__NFTA_FLOWTABLE_HOOK_MAX - 1)
+
+/**
+ * enum nft_device_attributes - nf_tables device netlink attributes
+ *
+ * @NFTA_DEVICE_NAME: name of this device (NLA_STRING)
+ */
+enum nft_devices_attributes {
+	NFTA_DEVICE_UNSPEC,
+	NFTA_DEVICE_NAME,
+	__NFTA_DEVICE_MAX
+};
+#define NFTA_DEVICE_MAX		(__NFTA_DEVICE_MAX - 1)
+
+
+/**
+ * enum nft_trace_attributes - nf_tables trace netlink attributes
+ *
+ * @NFTA_TRACE_TABLE: name of the table (NLA_STRING)
+ * @NFTA_TRACE_CHAIN: name of the chain (NLA_STRING)
+ * @NFTA_TRACE_RULE_HANDLE: numeric handle of the rule (NLA_U64)
+ * @NFTA_TRACE_TYPE: type of the event (NLA_U32: nft_trace_types)
+ * @NFTA_TRACE_VERDICT: verdict returned by hook (NLA_NESTED: nft_verdicts)
+ * @NFTA_TRACE_ID: pseudo-id, same for each skb traced (NLA_U32)
+ * @NFTA_TRACE_LL_HEADER: linklayer header (NLA_BINARY)
+ * @NFTA_TRACE_NETWORK_HEADER: network header (NLA_BINARY)
+ * @NFTA_TRACE_TRANSPORT_HEADER: transport header (NLA_BINARY)
+ * @NFTA_TRACE_IIF: indev ifindex (NLA_U32)
+ * @NFTA_TRACE_IIFTYPE: netdev->type of indev (NLA_U16)
+ * @NFTA_TRACE_OIF: outdev ifindex (NLA_U32)
+ * @NFTA_TRACE_OIFTYPE: netdev->type of outdev (NLA_U16)
+ * @NFTA_TRACE_MARK: nfmark (NLA_U32)
+ * @NFTA_TRACE_NFPROTO: nf protocol processed (NLA_U32)
+ * @NFTA_TRACE_POLICY: policy that decided fate of packet (NLA_U32)
+ */
+enum nft_trace_attributes {
+	NFTA_TRACE_UNSPEC,
+	NFTA_TRACE_TABLE,
+	NFTA_TRACE_CHAIN,
+	NFTA_TRACE_RULE_HANDLE,
+	NFTA_TRACE_TYPE,
+	NFTA_TRACE_VERDICT,
+	NFTA_TRACE_ID,
+	NFTA_TRACE_LL_HEADER,
+	NFTA_TRACE_NETWORK_HEADER,
+	NFTA_TRACE_TRANSPORT_HEADER,
+	NFTA_TRACE_IIF,
+	NFTA_TRACE_IIFTYPE,
+	NFTA_TRACE_OIF,
+	NFTA_TRACE_OIFTYPE,
+	NFTA_TRACE_MARK,
+	NFTA_TRACE_NFPROTO,
+	NFTA_TRACE_POLICY,
+	NFTA_TRACE_PAD,
+	__NFTA_TRACE_MAX
+};
+#define NFTA_TRACE_MAX (__NFTA_TRACE_MAX - 1)
+
+enum nft_trace_types {
+	NFT_TRACETYPE_UNSPEC,
+	NFT_TRACETYPE_POLICY,
+	NFT_TRACETYPE_RETURN,
+	NFT_TRACETYPE_RULE,
+	__NFT_TRACETYPE_MAX
+};
+#define NFT_TRACETYPE_MAX (__NFT_TRACETYPE_MAX - 1)
+
+/**
+ * enum nft_ng_attributes - nf_tables number generator expression netlink attributes
+ *
+ * @NFTA_NG_DREG: destination register (NLA_U32)
+ * @NFTA_NG_MODULUS: maximum counter value (NLA_U32)
+ * @NFTA_NG_TYPE: operation type (NLA_U32)
+ * @NFTA_NG_OFFSET: offset to be added to the counter (NLA_U32)
+ */
+enum nft_ng_attributes {
+	NFTA_NG_UNSPEC,
+	NFTA_NG_DREG,
+	NFTA_NG_MODULUS,
+	NFTA_NG_TYPE,
+	NFTA_NG_OFFSET,
+	__NFTA_NG_MAX
+};
+#define NFTA_NG_MAX	(__NFTA_NG_MAX - 1)
+
+enum nft_ng_types {
+	NFT_NG_INCREMENTAL,
+	NFT_NG_RANDOM,
+	__NFT_NG_MAX
+};
+#define NFT_NG_MAX	(__NFT_NG_MAX - 1)
+
+#endif /* _LINUX_NF_TABLES_H */
diff --git a/include/linux/netfilter/nf_tables_compat.h b/include/linux/netfilter/nf_tables_compat.h
new file mode 100644
index 0000000..36fb81d
--- /dev/null
+++ b/include/linux/netfilter/nf_tables_compat.h
@@ -0,0 +1,20 @@
+#ifndef _NFT_COMPAT_NFNETLINK_H_
+#define _NFT_COMPAT_NFNETLINK_H_
+
+#define NFT_COMPAT_NAME_MAX	32
+
+enum {
+	NFNL_MSG_COMPAT_GET,
+	NFNL_MSG_COMPAT_MAX
+};
+
+enum {
+	NFTA_COMPAT_UNSPEC = 0,
+	NFTA_COMPAT_NAME,
+	NFTA_COMPAT_REV,
+	NFTA_COMPAT_TYPE,
+	__NFTA_COMPAT_MAX,
+};
+#define NFTA_COMPAT_MAX (__NFTA_COMPAT_MAX - 1)
+
+#endif
diff --git a/include/linux/netfilter/nfnetlink.h b/include/linux/netfilter/nfnetlink.h
new file mode 100644
index 0000000..c6d1991
--- /dev/null
+++ b/include/linux/netfilter/nfnetlink.h
@@ -0,0 +1,80 @@
+#ifndef _NFNETLINK_H
+#define _NFNETLINK_H
+#include <linux/types.h>
+#include <linux/netfilter/nfnetlink_compat.h>
+
+enum nfnetlink_groups {
+	NFNLGRP_NONE,
+#define NFNLGRP_NONE			NFNLGRP_NONE
+	NFNLGRP_CONNTRACK_NEW,
+#define NFNLGRP_CONNTRACK_NEW		NFNLGRP_CONNTRACK_NEW
+	NFNLGRP_CONNTRACK_UPDATE,
+#define NFNLGRP_CONNTRACK_UPDATE	NFNLGRP_CONNTRACK_UPDATE
+	NFNLGRP_CONNTRACK_DESTROY,
+#define NFNLGRP_CONNTRACK_DESTROY	NFNLGRP_CONNTRACK_DESTROY
+	NFNLGRP_CONNTRACK_EXP_NEW,
+#define	NFNLGRP_CONNTRACK_EXP_NEW	NFNLGRP_CONNTRACK_EXP_NEW
+	NFNLGRP_CONNTRACK_EXP_UPDATE,
+#define NFNLGRP_CONNTRACK_EXP_UPDATE	NFNLGRP_CONNTRACK_EXP_UPDATE
+	NFNLGRP_CONNTRACK_EXP_DESTROY,
+#define NFNLGRP_CONNTRACK_EXP_DESTROY	NFNLGRP_CONNTRACK_EXP_DESTROY
+	NFNLGRP_NFTABLES,
+#define NFNLGRP_NFTABLES		NFNLGRP_NFTABLES
+	NFNLGRP_ACCT_QUOTA,
+#define NFNLGRP_ACCT_QUOTA		NFNLGRP_ACCT_QUOTA
+	NFNLGRP_NFTRACE,
+#define NFNLGRP_NFTRACE			NFNLGRP_NFTRACE
+	__NFNLGRP_MAX,
+};
+#define NFNLGRP_MAX	(__NFNLGRP_MAX - 1)
+
+/* General form of address family dependent message.
+ */
+struct nfgenmsg {
+	__u8  nfgen_family;		/* AF_xxx */
+	__u8  version;		/* nfnetlink version */
+	__be16    res_id;		/* resource id */
+};
+
+#define NFNETLINK_V0	0
+
+/* netfilter netlink message types are split in two pieces:
+ * 8 bit subsystem, 8bit operation.
+ */
+
+#define NFNL_SUBSYS_ID(x)	((x & 0xff00) >> 8)
+#define NFNL_MSG_TYPE(x)	(x & 0x00ff)
+
+/* No enum here, otherwise __stringify() trick of MODULE_ALIAS_NFNL_SUBSYS()
+ * won't work anymore */
+#define NFNL_SUBSYS_NONE 		0
+#define NFNL_SUBSYS_CTNETLINK		1
+#define NFNL_SUBSYS_CTNETLINK_EXP	2
+#define NFNL_SUBSYS_QUEUE		3
+#define NFNL_SUBSYS_ULOG		4
+#define NFNL_SUBSYS_OSF			5
+#define NFNL_SUBSYS_IPSET		6
+#define NFNL_SUBSYS_ACCT		7
+#define NFNL_SUBSYS_CTNETLINK_TIMEOUT	8
+#define NFNL_SUBSYS_CTHELPER		9
+#define NFNL_SUBSYS_NFTABLES		10
+#define NFNL_SUBSYS_NFT_COMPAT		11
+#define NFNL_SUBSYS_COUNT		12
+
+/* Reserved control nfnetlink messages */
+#define NFNL_MSG_BATCH_BEGIN		NLMSG_MIN_TYPE
+#define NFNL_MSG_BATCH_END		NLMSG_MIN_TYPE+1
+
+/**
+ * enum nfnl_batch_attributes - nfnetlink batch netlink attributes
+ *
+ * @NFNL_BATCH_GENID: generation ID for this changeset (NLA_U32)
+ */
+enum nfnl_batch_attributes {
+        NFNL_BATCH_UNSPEC,
+        NFNL_BATCH_GENID,
+        __NFNL_BATCH_MAX
+};
+#define NFNL_BATCH_MAX			(__NFNL_BATCH_MAX - 1)
+
+#endif /* _NFNETLINK_H */
diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h
new file mode 100644
index 0000000..4120970
--- /dev/null
+++ b/include/linux/netfilter/x_tables.h
@@ -0,0 +1,185 @@
+#ifndef _X_TABLES_H
+#define _X_TABLES_H
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#define XT_FUNCTION_MAXNAMELEN 30
+#define XT_EXTENSION_MAXNAMELEN 29
+#define XT_TABLE_MAXNAMELEN 32
+
+struct xt_entry_match {
+	union {
+		struct {
+			__u16 match_size;
+
+			/* Used by userspace */
+			char name[XT_EXTENSION_MAXNAMELEN];
+			__u8 revision;
+		} user;
+		struct {
+			__u16 match_size;
+
+			/* Used inside the kernel */
+			struct xt_match *match;
+		} kernel;
+
+		/* Total length */
+		__u16 match_size;
+	} u;
+
+	unsigned char data[0];
+};
+
+struct xt_entry_target {
+	union {
+		struct {
+			__u16 target_size;
+
+			/* Used by userspace */
+			char name[XT_EXTENSION_MAXNAMELEN];
+			__u8 revision;
+		} user;
+		struct {
+			__u16 target_size;
+
+			/* Used inside the kernel */
+			struct xt_target *target;
+		} kernel;
+
+		/* Total length */
+		__u16 target_size;
+	} u;
+
+	unsigned char data[0];
+};
+
+#define XT_TARGET_INIT(__name, __size)					       \
+{									       \
+	.target.u.user = {						       \
+		.target_size	= XT_ALIGN(__size),			       \
+		.name		= __name,				       \
+	},								       \
+}
+
+struct xt_standard_target {
+	struct xt_entry_target target;
+	int verdict;
+};
+
+struct xt_error_target {
+	struct xt_entry_target target;
+	char errorname[XT_FUNCTION_MAXNAMELEN];
+};
+
+/* The argument to IPT_SO_GET_REVISION_*.  Returns highest revision
+ * kernel supports, if >= revision. */
+struct xt_get_revision {
+	char name[XT_EXTENSION_MAXNAMELEN];
+	__u8 revision;
+};
+
+/* CONTINUE verdict for targets */
+#define XT_CONTINUE 0xFFFFFFFF
+
+/* For standard target */
+#define XT_RETURN (-NF_REPEAT - 1)
+
+/* this is a dummy structure to find out the alignment requirement for a struct
+ * containing all the fundamental data types that are used in ipt_entry,
+ * ip6t_entry and arpt_entry.  This sucks, and it is a hack.  It will be my
+ * personal pleasure to remove it -HW
+ */
+struct _xt_align {
+	__u8 u8;
+	__u16 u16;
+	__u32 u32;
+	__u64 u64;
+};
+
+#define XT_ALIGN(s) __ALIGN_KERNEL((s), __alignof__(struct _xt_align))
+
+/* Standard return verdict, or do jump. */
+#define XT_STANDARD_TARGET ""
+/* Error verdict. */
+#define XT_ERROR_TARGET "ERROR"
+
+#define SET_COUNTER(c,b,p) do { (c).bcnt = (b); (c).pcnt = (p); } while(0)
+#define ADD_COUNTER(c,b,p) do { (c).bcnt += (b); (c).pcnt += (p); } while(0)
+
+struct xt_counters {
+	__u64 pcnt, bcnt;			/* Packet and byte counters */
+};
+
+/* The argument to IPT_SO_ADD_COUNTERS. */
+struct xt_counters_info {
+	/* Which table. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	unsigned int num_counters;
+
+	/* The counters (actually `number' of these). */
+	struct xt_counters counters[0];
+};
+
+#define XT_INV_PROTO		0x40	/* Invert the sense of PROTO. */
+
+/* fn returns 0 to continue iteration */
+#define XT_MATCH_ITERATE(type, e, fn, args...)			\
+({								\
+	unsigned int __i;					\
+	int __ret = 0;						\
+	struct xt_entry_match *__m;				\
+								\
+	for (__i = sizeof(type);				\
+	     __i < (e)->target_offset;				\
+	     __i += __m->u.match_size) {			\
+		__m = (void *)e + __i;				\
+								\
+		__ret = fn(__m , ## args);			\
+		if (__ret != 0)					\
+			break;					\
+	}							\
+	__ret;							\
+})
+
+/* fn returns 0 to continue iteration */
+#define XT_ENTRY_ITERATE_CONTINUE(type, entries, size, n, fn, args...) \
+({								\
+	unsigned int __i, __n;					\
+	int __ret = 0;						\
+	type *__entry;						\
+								\
+	for (__i = 0, __n = 0; __i < (size);			\
+	     __i += __entry->next_offset, __n++) { 		\
+		__entry = (void *)(entries) + __i;		\
+		if (__n < n)					\
+			continue;				\
+								\
+		__ret = fn(__entry , ## args);			\
+		if (__ret != 0)					\
+			break;					\
+	}							\
+	__ret;							\
+})
+
+/* fn returns 0 to continue iteration */
+#define XT_ENTRY_ITERATE(type, entries, size, fn, args...) \
+	XT_ENTRY_ITERATE_CONTINUE(type, entries, size, 0, fn, args)
+
+
+/* pos is normally a struct ipt_entry/ip6t_entry/etc. */
+#define xt_entry_foreach(pos, ehead, esize) \
+	for ((pos) = (typeof(pos))(ehead); \
+	     (pos) < (typeof(pos))((char *)(ehead) + (esize)); \
+	     (pos) = (typeof(pos))((char *)(pos) + (pos)->next_offset))
+
+/* can only be xt_entry_match, so no use of typeof here */
+#define xt_ematch_foreach(pos, entry) \
+	for ((pos) = (struct xt_entry_match *)entry->elems; \
+	     (pos) < (struct xt_entry_match *)((char *)(entry) + \
+	             (entry)->target_offset); \
+	     (pos) = (struct xt_entry_match *)((char *)(pos) + \
+	             (pos)->u.match_size))
+
+
+#endif /* _X_TABLES_H */
diff --git a/include/linux/netfilter/xt_AUDIT.h b/include/linux/netfilter/xt_AUDIT.h
new file mode 100644
index 0000000..38751d2
--- /dev/null
+++ b/include/linux/netfilter/xt_AUDIT.h
@@ -0,0 +1,30 @@
+/*
+ * Header file for iptables xt_AUDIT target
+ *
+ * (C) 2010-2011 Thomas Graf <tgraf@redhat.com>
+ * (C) 2010-2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _XT_AUDIT_TARGET_H
+#define _XT_AUDIT_TARGET_H
+
+#include <linux/types.h>
+
+enum {
+	XT_AUDIT_TYPE_ACCEPT = 0,
+	XT_AUDIT_TYPE_DROP,
+	XT_AUDIT_TYPE_REJECT,
+	__XT_AUDIT_TYPE_MAX,
+};
+
+#define XT_AUDIT_TYPE_MAX (__XT_AUDIT_TYPE_MAX - 1)
+
+struct xt_audit_info {
+	__u8 type; /* XT_AUDIT_TYPE_* */
+};
+
+#endif /* _XT_AUDIT_TARGET_H */
diff --git a/include/linux/netfilter/xt_CHECKSUM.h b/include/linux/netfilter/xt_CHECKSUM.h
new file mode 100644
index 0000000..9a2e466
--- /dev/null
+++ b/include/linux/netfilter/xt_CHECKSUM.h
@@ -0,0 +1,20 @@
+/* Header file for iptables ipt_CHECKSUM target
+ *
+ * (C) 2002 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 Red Hat Inc
+ * Author: Michael S. Tsirkin <mst@redhat.com>
+ *
+ * This software is distributed under GNU GPL v2, 1991
+*/
+#ifndef _XT_CHECKSUM_TARGET_H
+#define _XT_CHECKSUM_TARGET_H
+
+#include <linux/types.h>
+
+#define XT_CHECKSUM_OP_FILL	0x01	/* fill in checksum in IP header */
+
+struct xt_CHECKSUM_info {
+	__u8 operation;	/* bitset of operations */
+};
+
+#endif /* _XT_CHECKSUM_TARGET_H */
diff --git a/include/linux/netfilter/xt_CLASSIFY.h b/include/linux/netfilter/xt_CLASSIFY.h
new file mode 100644
index 0000000..a813bf1
--- /dev/null
+++ b/include/linux/netfilter/xt_CLASSIFY.h
@@ -0,0 +1,10 @@
+#ifndef _XT_CLASSIFY_H
+#define _XT_CLASSIFY_H
+
+#include <linux/types.h>
+
+struct xt_classify_target_info {
+	__u32 priority;
+};
+
+#endif /*_XT_CLASSIFY_H */
diff --git a/include/linux/netfilter/xt_CONNMARK.h b/include/linux/netfilter/xt_CONNMARK.h
new file mode 100644
index 0000000..2f2e48e
--- /dev/null
+++ b/include/linux/netfilter/xt_CONNMARK.h
@@ -0,0 +1,6 @@
+#ifndef _XT_CONNMARK_H_target
+#define _XT_CONNMARK_H_target
+
+#include <linux/netfilter/xt_connmark.h>
+
+#endif /*_XT_CONNMARK_H_target*/
diff --git a/include/linux/netfilter/xt_CONNSECMARK.h b/include/linux/netfilter/xt_CONNSECMARK.h
new file mode 100644
index 0000000..b973ff8
--- /dev/null
+++ b/include/linux/netfilter/xt_CONNSECMARK.h
@@ -0,0 +1,15 @@
+#ifndef _XT_CONNSECMARK_H_target
+#define _XT_CONNSECMARK_H_target
+
+#include <linux/types.h>
+
+enum {
+	CONNSECMARK_SAVE = 1,
+	CONNSECMARK_RESTORE,
+};
+
+struct xt_connsecmark_target_info {
+	__u8 mode;
+};
+
+#endif /*_XT_CONNSECMARK_H_target */
diff --git a/include/linux/netfilter/xt_CT.h b/include/linux/netfilter/xt_CT.h
new file mode 100644
index 0000000..c3907db
--- /dev/null
+++ b/include/linux/netfilter/xt_CT.h
@@ -0,0 +1,37 @@
+#ifndef _XT_CT_H
+#define _XT_CT_H
+
+#include <linux/types.h>
+
+enum {
+	XT_CT_NOTRACK		= 1 << 0,
+	XT_CT_NOTRACK_ALIAS	= 1 << 1,
+	XT_CT_ZONE_DIR_ORIG	= 1 << 2,
+	XT_CT_ZONE_DIR_REPL	= 1 << 3,
+	XT_CT_ZONE_MARK		= 1 << 4,
+};
+
+struct xt_ct_target_info {
+	__u16 flags;
+	__u16 zone;
+	__u32 ct_events;
+	__u32 exp_events;
+	char helper[16];
+
+	/* Used internally by the kernel */
+	struct nf_conn	*ct __attribute__((aligned(8)));
+};
+
+struct xt_ct_target_info_v1 {
+	__u16 flags;
+	__u16 zone;
+	__u32 ct_events;
+	__u32 exp_events;
+	char helper[16];
+	char timeout[32];
+
+	/* Used internally by the kernel */
+	struct nf_conn	*ct __attribute__((aligned(8)));
+};
+
+#endif /* _XT_CT_H */
diff --git a/include/linux/netfilter/xt_DSCP.h b/include/linux/netfilter/xt_DSCP.h
new file mode 100644
index 0000000..648e0b3
--- /dev/null
+++ b/include/linux/netfilter/xt_DSCP.h
@@ -0,0 +1,26 @@
+/* x_tables module for setting the IPv4/IPv6 DSCP field
+ *
+ * (C) 2002 Harald Welte <laforge@gnumonks.org>
+ * based on ipt_FTOS.c (C) 2000 by Matthew G. Marsh <mgm@paktronix.com>
+ * This software is distributed under GNU GPL v2, 1991
+ *
+ * See RFC2474 for a description of the DSCP field within the IP Header.
+ *
+ * xt_DSCP.h,v 1.7 2002/03/14 12:03:13 laforge Exp
+*/
+#ifndef _XT_DSCP_TARGET_H
+#define _XT_DSCP_TARGET_H
+#include <linux/netfilter/xt_dscp.h>
+#include <linux/types.h>
+
+/* target info */
+struct xt_DSCP_info {
+	__u8 dscp;
+};
+
+struct xt_tos_target_info {
+	__u8 tos_value;
+	__u8 tos_mask;
+};
+
+#endif /* _XT_DSCP_TARGET_H */
diff --git a/include/linux/netfilter/xt_HMARK.h b/include/linux/netfilter/xt_HMARK.h
new file mode 100644
index 0000000..826fc58
--- /dev/null
+++ b/include/linux/netfilter/xt_HMARK.h
@@ -0,0 +1,50 @@
+#ifndef XT_HMARK_H_
+#define XT_HMARK_H_
+
+#include <linux/types.h>
+
+enum {
+	XT_HMARK_SADDR_MASK,
+	XT_HMARK_DADDR_MASK,
+	XT_HMARK_SPI,
+	XT_HMARK_SPI_MASK,
+	XT_HMARK_SPORT,
+	XT_HMARK_DPORT,
+	XT_HMARK_SPORT_MASK,
+	XT_HMARK_DPORT_MASK,
+	XT_HMARK_PROTO_MASK,
+	XT_HMARK_RND,
+	XT_HMARK_MODULUS,
+	XT_HMARK_OFFSET,
+	XT_HMARK_CT,
+	XT_HMARK_METHOD_L3,
+	XT_HMARK_METHOD_L3_4,
+};
+#define XT_HMARK_FLAG(flag)	(1 << flag)
+
+union hmark_ports {
+	struct {
+		__u16	src;
+		__u16	dst;
+	} p16;
+	struct {
+		__be16	src;
+		__be16	dst;
+	} b16;
+	__u32	v32;
+	__be32	b32;
+};
+
+struct xt_hmark_info {
+	union nf_inet_addr	src_mask;
+	union nf_inet_addr	dst_mask;
+	union hmark_ports	port_mask;
+	union hmark_ports	port_set;
+	__u32			flags;
+	__u16			proto_mask;
+	__u32			hashrnd;
+	__u32			hmodulus;
+	__u32			hoffset;	/* Mark offset to start from */
+};
+
+#endif /* XT_HMARK_H_ */
diff --git a/include/linux/netfilter/xt_IDLETIMER.h b/include/linux/netfilter/xt_IDLETIMER.h
new file mode 100644
index 0000000..49ddcdc
--- /dev/null
+++ b/include/linux/netfilter/xt_IDLETIMER.h
@@ -0,0 +1,57 @@
+/*
+ * linux/include/linux/netfilter/xt_IDLETIMER.h
+ *
+ * Header file for Xtables timer target module.
+ *
+ * Copyright (C) 2004, 2010 Nokia Corporation
+ * Written by Timo Teras <ext-timo.teras@nokia.com>
+ *
+ * Converted to x_tables and forward-ported to 2.6.34
+ * by Luciano Coelho <luciano.coelho@nokia.com>
+ *
+ * Contact: Luciano Coelho <luciano.coelho@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef _XT_IDLETIMER_H
+#define _XT_IDLETIMER_H
+
+#include <linux/types.h>
+
+#define MAX_IDLETIMER_LABEL_SIZE 28
+#define XT_IDLETIMER_ALARM 0x01
+
+struct idletimer_tg_info {
+	__u32 timeout;
+
+	char label[MAX_IDLETIMER_LABEL_SIZE];
+
+	/* for kernel module internal use only */
+	struct idletimer_tg *timer __attribute__((aligned(8)));
+};
+
+struct idletimer_tg_info_v1 {
+	__u32 timeout;
+
+	char label[MAX_IDLETIMER_LABEL_SIZE];
+
+	__u8 send_nl_msg;   /* unused: for compatibility with Android */
+	__u8 timer_type;
+
+	/* for kernel module internal use only */
+	struct idletimer_tg *timer __attribute__((aligned(8)));
+};
+#endif
diff --git a/include/linux/netfilter/xt_LED.h b/include/linux/netfilter/xt_LED.h
new file mode 100644
index 0000000..f5509e7
--- /dev/null
+++ b/include/linux/netfilter/xt_LED.h
@@ -0,0 +1,15 @@
+#ifndef _XT_LED_H
+#define _XT_LED_H
+
+#include <linux/types.h>
+
+struct xt_led_info {
+	char id[27];        /* Unique ID for this trigger in the LED class */
+	__u8 always_blink;  /* Blink even if the LED is already on */
+	__u32 delay;        /* Delay until LED is switched off after trigger */
+
+	/* Kernel data used in the module */
+	void *internal_data __attribute__((aligned(8)));
+};
+
+#endif /* _XT_LED_H */
diff --git a/include/linux/netfilter/xt_MARK.h b/include/linux/netfilter/xt_MARK.h
new file mode 100644
index 0000000..41c456d
--- /dev/null
+++ b/include/linux/netfilter/xt_MARK.h
@@ -0,0 +1,6 @@
+#ifndef _XT_MARK_H_target
+#define _XT_MARK_H_target
+
+#include <linux/netfilter/xt_mark.h>
+
+#endif /*_XT_MARK_H_target */
diff --git a/include/linux/netfilter/xt_NFLOG.h b/include/linux/netfilter/xt_NFLOG.h
new file mode 100644
index 0000000..f330707
--- /dev/null
+++ b/include/linux/netfilter/xt_NFLOG.h
@@ -0,0 +1,24 @@
+#ifndef _XT_NFLOG_TARGET
+#define _XT_NFLOG_TARGET
+
+#include <linux/types.h>
+
+#define XT_NFLOG_DEFAULT_GROUP		0x1
+#define XT_NFLOG_DEFAULT_THRESHOLD	0
+
+#define XT_NFLOG_MASK			0x1
+
+/* This flag indicates that 'len' field in xt_nflog_info is set*/
+#define XT_NFLOG_F_COPY_LEN		0x1
+
+struct xt_nflog_info {
+	/* 'len' will be used iff you set XT_NFLOG_F_COPY_LEN in flags */
+	__u32	len;
+	__u16	group;
+	__u16	threshold;
+	__u16	flags;
+	__u16	pad;
+	char		prefix[64];
+};
+
+#endif /* _XT_NFLOG_TARGET */
diff --git a/include/linux/netfilter/xt_NFQUEUE.h b/include/linux/netfilter/xt_NFQUEUE.h
new file mode 100644
index 0000000..8bb5fe6
--- /dev/null
+++ b/include/linux/netfilter/xt_NFQUEUE.h
@@ -0,0 +1,38 @@
+/* iptables module for using NFQUEUE mechanism
+ *
+ * (C) 2005 Harald Welte <laforge@netfilter.org>
+ *
+ * This software is distributed under GNU GPL v2, 1991
+ * 
+*/
+#ifndef _XT_NFQ_TARGET_H
+#define _XT_NFQ_TARGET_H
+
+#include <linux/types.h>
+
+/* target info */
+struct xt_NFQ_info {
+	__u16 queuenum;
+};
+
+struct xt_NFQ_info_v1 {
+	__u16 queuenum;
+	__u16 queues_total;
+};
+
+struct xt_NFQ_info_v2 {
+	__u16 queuenum;
+	__u16 queues_total;
+	__u16 bypass;
+};
+
+struct xt_NFQ_info_v3 {
+	__u16 queuenum;
+	__u16 queues_total;
+	__u16 flags;
+#define NFQ_FLAG_BYPASS		0x01 /* for compatibility with v2 */
+#define NFQ_FLAG_CPU_FANOUT	0x02 /* use current CPU (no hashing) */
+#define NFQ_FLAG_MASK		0x03
+};
+
+#endif /* _XT_NFQ_TARGET_H */
diff --git a/include/linux/netfilter/xt_RATEEST.h b/include/linux/netfilter/xt_RATEEST.h
new file mode 100644
index 0000000..6605e20
--- /dev/null
+++ b/include/linux/netfilter/xt_RATEEST.h
@@ -0,0 +1,15 @@
+#ifndef _XT_RATEEST_TARGET_H
+#define _XT_RATEEST_TARGET_H
+
+#include <linux/types.h>
+
+struct xt_rateest_target_info {
+	char			name[IFNAMSIZ];
+	__s8			interval;
+	__u8		ewma_log;
+
+	/* Used internally by the kernel */
+	struct xt_rateest	*est __attribute__((aligned(8)));
+};
+
+#endif /* _XT_RATEEST_TARGET_H */
diff --git a/include/linux/netfilter/xt_SECMARK.h b/include/linux/netfilter/xt_SECMARK.h
new file mode 100644
index 0000000..989092b
--- /dev/null
+++ b/include/linux/netfilter/xt_SECMARK.h
@@ -0,0 +1,22 @@
+#ifndef _XT_SECMARK_H_target
+#define _XT_SECMARK_H_target
+
+#include <linux/types.h>
+
+/*
+ * This is intended for use by various security subsystems (but not
+ * at the same time).
+ *
+ * 'mode' refers to the specific security subsystem which the
+ * packets are being marked for.
+ */
+#define SECMARK_MODE_SEL	0x01		/* SELinux */
+#define SECMARK_SECCTX_MAX	256
+
+struct xt_secmark_target_info {
+	__u8 mode;
+	__u32 secid;
+	char secctx[SECMARK_SECCTX_MAX];
+};
+
+#endif /*_XT_SECMARK_H_target */
diff --git a/include/linux/netfilter/xt_SYNPROXY.h b/include/linux/netfilter/xt_SYNPROXY.h
new file mode 100644
index 0000000..2d59fba
--- /dev/null
+++ b/include/linux/netfilter/xt_SYNPROXY.h
@@ -0,0 +1,16 @@
+#ifndef _XT_SYNPROXY_H
+#define _XT_SYNPROXY_H
+
+#define XT_SYNPROXY_OPT_MSS		0x01
+#define XT_SYNPROXY_OPT_WSCALE		0x02
+#define XT_SYNPROXY_OPT_SACK_PERM	0x04
+#define XT_SYNPROXY_OPT_TIMESTAMP	0x08
+#define XT_SYNPROXY_OPT_ECN		0x10
+
+struct xt_synproxy_info {
+	__u8	options;
+	__u8	wscale;
+	__u16	mss;
+};
+
+#endif /* _XT_SYNPROXY_H */
diff --git a/include/linux/netfilter/xt_TCPMSS.h b/include/linux/netfilter/xt_TCPMSS.h
new file mode 100644
index 0000000..9a6960a
--- /dev/null
+++ b/include/linux/netfilter/xt_TCPMSS.h
@@ -0,0 +1,12 @@
+#ifndef _XT_TCPMSS_H
+#define _XT_TCPMSS_H
+
+#include <linux/types.h>
+
+struct xt_tcpmss_info {
+	__u16 mss;
+};
+
+#define XT_TCPMSS_CLAMP_PMTU 0xffff
+
+#endif /* _XT_TCPMSS_H */
diff --git a/include/linux/netfilter/xt_TCPOPTSTRIP.h b/include/linux/netfilter/xt_TCPOPTSTRIP.h
new file mode 100644
index 0000000..7157318
--- /dev/null
+++ b/include/linux/netfilter/xt_TCPOPTSTRIP.h
@@ -0,0 +1,15 @@
+#ifndef _XT_TCPOPTSTRIP_H
+#define _XT_TCPOPTSTRIP_H
+
+#include <linux/types.h>
+
+#define tcpoptstrip_set_bit(bmap, idx) \
+	(bmap[(idx) >> 5] |= 1U << (idx & 31))
+#define tcpoptstrip_test_bit(bmap, idx) \
+	(((1U << (idx & 31)) & bmap[(idx) >> 5]) != 0)
+
+struct xt_tcpoptstrip_target_info {
+	__u32 strip_bmap[8];
+};
+
+#endif /* _XT_TCPOPTSTRIP_H */
diff --git a/include/linux/netfilter/xt_TEE.h b/include/linux/netfilter/xt_TEE.h
new file mode 100644
index 0000000..5c21d5c
--- /dev/null
+++ b/include/linux/netfilter/xt_TEE.h
@@ -0,0 +1,12 @@
+#ifndef _XT_TEE_TARGET_H
+#define _XT_TEE_TARGET_H
+
+struct xt_tee_tginfo {
+	union nf_inet_addr gw;
+	char oif[16];
+
+	/* used internally by the kernel */
+	struct xt_tee_priv *priv __attribute__((aligned(8)));
+};
+
+#endif /* _XT_TEE_TARGET_H */
diff --git a/include/linux/netfilter/xt_TPROXY.h b/include/linux/netfilter/xt_TPROXY.h
new file mode 100644
index 0000000..902043c
--- /dev/null
+++ b/include/linux/netfilter/xt_TPROXY.h
@@ -0,0 +1,23 @@
+#ifndef _XT_TPROXY_H
+#define _XT_TPROXY_H
+
+#include <linux/types.h>
+
+/* TPROXY target is capable of marking the packet to perform
+ * redirection. We can get rid of that whenever we get support for
+ * mutliple targets in the same rule. */
+struct xt_tproxy_target_info {
+	__u32 mark_mask;
+	__u32 mark_value;
+	__be32 laddr;
+	__be16 lport;
+};
+
+struct xt_tproxy_target_info_v1 {
+	__u32 mark_mask;
+	__u32 mark_value;
+	union nf_inet_addr laddr;
+	__be16 lport;
+};
+
+#endif /* _XT_TPROXY_H */
diff --git a/include/linux/netfilter/xt_addrtype.h b/include/linux/netfilter/xt_addrtype.h
new file mode 100644
index 0000000..b156baa
--- /dev/null
+++ b/include/linux/netfilter/xt_addrtype.h
@@ -0,0 +1,44 @@
+#ifndef _XT_ADDRTYPE_H
+#define _XT_ADDRTYPE_H
+
+#include <linux/types.h>
+
+enum {
+	XT_ADDRTYPE_INVERT_SOURCE	= 0x0001,
+	XT_ADDRTYPE_INVERT_DEST		= 0x0002,
+	XT_ADDRTYPE_LIMIT_IFACE_IN	= 0x0004,
+	XT_ADDRTYPE_LIMIT_IFACE_OUT	= 0x0008,
+};
+
+
+/* rtn_type enum values from rtnetlink.h, but shifted */
+enum {
+	XT_ADDRTYPE_UNSPEC = 1 << 0,
+	XT_ADDRTYPE_UNICAST = 1 << 1,	/* 1 << RTN_UNICAST */
+	XT_ADDRTYPE_LOCAL  = 1 << 2,	/* 1 << RTN_LOCAL, etc */
+	XT_ADDRTYPE_BROADCAST = 1 << 3,
+	XT_ADDRTYPE_ANYCAST = 1 << 4,
+	XT_ADDRTYPE_MULTICAST = 1 << 5,
+	XT_ADDRTYPE_BLACKHOLE = 1 << 6,
+	XT_ADDRTYPE_UNREACHABLE = 1 << 7,
+	XT_ADDRTYPE_PROHIBIT = 1 << 8,
+	XT_ADDRTYPE_THROW = 1 << 9,
+	XT_ADDRTYPE_NAT = 1 << 10,
+	XT_ADDRTYPE_XRESOLVE = 1 << 11,
+};
+
+struct xt_addrtype_info_v1 {
+	__u16	source;		/* source-type mask */
+	__u16	dest;		/* dest-type mask */
+	__u32	flags;
+};
+
+/* revision 0 */
+struct xt_addrtype_info {
+	__u16	source;		/* source-type mask */
+	__u16	dest;		/* dest-type mask */
+	__u32	invert_source;
+	__u32	invert_dest;
+};
+
+#endif
diff --git a/include/linux/netfilter/xt_bpf.h b/include/linux/netfilter/xt_bpf.h
new file mode 100644
index 0000000..b97725a
--- /dev/null
+++ b/include/linux/netfilter/xt_bpf.h
@@ -0,0 +1,40 @@
+#ifndef _XT_BPF_H
+#define _XT_BPF_H
+
+#include <linux/filter.h>
+#include <linux/limits.h>
+#include <linux/types.h>
+
+#define XT_BPF_MAX_NUM_INSTR	64
+#define XT_BPF_PATH_MAX		(XT_BPF_MAX_NUM_INSTR * sizeof(struct sock_filter))
+
+struct bpf_prog;
+
+struct xt_bpf_info {
+	__u16 bpf_program_num_elem;
+	struct sock_filter bpf_program[XT_BPF_MAX_NUM_INSTR];
+
+	/* only used in the kernel */
+	struct bpf_prog *filter __attribute__((aligned(8)));
+};
+
+enum xt_bpf_modes {
+	XT_BPF_MODE_BYTECODE,
+	XT_BPF_MODE_FD_PINNED,
+	XT_BPF_MODE_FD_ELF,
+};
+
+struct xt_bpf_info_v1 {
+	__u16 mode;
+	__u16 bpf_program_num_elem;
+	__s32 fd;
+	union {
+		struct sock_filter bpf_program[XT_BPF_MAX_NUM_INSTR];
+		char path[XT_BPF_PATH_MAX];
+	};
+
+	/* only used in the kernel */
+	struct bpf_prog *filter __attribute__((aligned(8)));
+};
+
+#endif /*_XT_BPF_H */
diff --git a/include/linux/netfilter/xt_cgroup.h b/include/linux/netfilter/xt_cgroup.h
new file mode 100644
index 0000000..b74e370
--- /dev/null
+++ b/include/linux/netfilter/xt_cgroup.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_XT_CGROUP_H
+#define _UAPI_XT_CGROUP_H
+
+#include <linux/types.h>
+#include <linux/limits.h>
+
+struct xt_cgroup_info_v0 {
+	__u32 id;
+	__u32 invert;
+};
+
+struct xt_cgroup_info_v1 {
+	__u8		has_path;
+	__u8		has_classid;
+	__u8		invert_path;
+	__u8		invert_classid;
+	char		path[PATH_MAX];
+	__u32		classid;
+
+	/* kernel internal data */
+	void		*priv __attribute__((aligned(8)));
+};
+
+#define XT_CGROUP_PATH_MAX	512
+
+struct xt_cgroup_info_v2 {
+	__u8		has_path;
+	__u8		has_classid;
+	__u8		invert_path;
+	__u8		invert_classid;
+	union {
+		char	path[XT_CGROUP_PATH_MAX];
+		__u32	classid;
+	};
+
+	/* kernel internal data */
+	void		*priv __attribute__((aligned(8)));
+};
+
+#endif /* _UAPI_XT_CGROUP_H */
diff --git a/include/linux/netfilter/xt_cluster.h b/include/linux/netfilter/xt_cluster.h
new file mode 100644
index 0000000..9b883c8
--- /dev/null
+++ b/include/linux/netfilter/xt_cluster.h
@@ -0,0 +1,19 @@
+#ifndef _XT_CLUSTER_MATCH_H
+#define _XT_CLUSTER_MATCH_H
+
+#include <linux/types.h>
+
+enum xt_cluster_flags {
+	XT_CLUSTER_F_INV	= (1 << 0)
+};
+
+struct xt_cluster_match_info {
+	__u32 total_nodes;
+	__u32 node_mask;
+	__u32 hash_seed;
+	__u32 flags;
+};
+
+#define XT_CLUSTER_NODES_MAX	32
+
+#endif /* _XT_CLUSTER_MATCH_H */
diff --git a/include/linux/netfilter/xt_comment.h b/include/linux/netfilter/xt_comment.h
new file mode 100644
index 0000000..0ea5e79
--- /dev/null
+++ b/include/linux/netfilter/xt_comment.h
@@ -0,0 +1,10 @@
+#ifndef _XT_COMMENT_H
+#define _XT_COMMENT_H
+
+#define XT_MAX_COMMENT_LEN 256
+
+struct xt_comment_info {
+	char comment[XT_MAX_COMMENT_LEN];
+};
+
+#endif /* XT_COMMENT_H */
diff --git a/include/linux/netfilter/xt_connbytes.h b/include/linux/netfilter/xt_connbytes.h
new file mode 100644
index 0000000..f1d6c15
--- /dev/null
+++ b/include/linux/netfilter/xt_connbytes.h
@@ -0,0 +1,26 @@
+#ifndef _XT_CONNBYTES_H
+#define _XT_CONNBYTES_H
+
+#include <linux/types.h>
+
+enum xt_connbytes_what {
+	XT_CONNBYTES_PKTS,
+	XT_CONNBYTES_BYTES,
+	XT_CONNBYTES_AVGPKT,
+};
+
+enum xt_connbytes_direction {
+	XT_CONNBYTES_DIR_ORIGINAL,
+	XT_CONNBYTES_DIR_REPLY,
+	XT_CONNBYTES_DIR_BOTH,
+};
+
+struct xt_connbytes_info {
+	struct {
+		__aligned_u64 from;	/* count to be matched */
+		__aligned_u64 to;	/* count to be matched */
+	} count;
+	__u8 what;		/* ipt_connbytes_what */
+	__u8 direction;	/* ipt_connbytes_direction */
+};
+#endif
diff --git a/include/linux/netfilter/xt_connlabel.h b/include/linux/netfilter/xt_connlabel.h
new file mode 100644
index 0000000..c4bc9ee
--- /dev/null
+++ b/include/linux/netfilter/xt_connlabel.h
@@ -0,0 +1,12 @@
+#include <linux/types.h>
+
+#define XT_CONNLABEL_MAXBIT 127
+enum xt_connlabel_mtopts {
+	XT_CONNLABEL_OP_INVERT = 1 << 0,
+	XT_CONNLABEL_OP_SET    = 1 << 1,
+};
+
+struct xt_connlabel_mtinfo {
+	__u16 bit;
+	__u16 options;
+};
diff --git a/include/linux/netfilter/xt_connlimit.h b/include/linux/netfilter/xt_connlimit.h
new file mode 100644
index 0000000..f9e8c67
--- /dev/null
+++ b/include/linux/netfilter/xt_connlimit.h
@@ -0,0 +1,34 @@
+#ifndef _XT_CONNLIMIT_H
+#define _XT_CONNLIMIT_H
+
+#include <linux/types.h>
+
+struct xt_connlimit_data;
+
+enum {
+	XT_CONNLIMIT_INVERT = 1 << 0,
+	XT_CONNLIMIT_DADDR  = 1 << 1,
+};
+
+struct xt_connlimit_info {
+	union {
+		union nf_inet_addr mask;
+		union {
+			__be32 v4_mask;
+			__be32 v6_mask[4];
+		};
+	};
+	unsigned int limit;
+	union {
+		/* revision 0 */
+		unsigned int inverse;
+
+		/* revision 1 */
+		__u32 flags;
+	};
+
+	/* Used internally by the kernel */
+	struct xt_connlimit_data *data __attribute__((aligned(8)));
+};
+
+#endif /* _XT_CONNLIMIT_H */
diff --git a/include/linux/netfilter/xt_connmark.h b/include/linux/netfilter/xt_connmark.h
new file mode 100644
index 0000000..bbf2acc
--- /dev/null
+++ b/include/linux/netfilter/xt_connmark.h
@@ -0,0 +1,36 @@
+#ifndef _XT_CONNMARK_H
+#define _XT_CONNMARK_H
+
+#include <linux/types.h>
+
+/* Copyright (C) 2002,2004 MARA Systems AB <http://www.marasystems.com>
+ * by Henrik Nordstrom <hno@marasystems.com>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ */
+
+enum {
+	XT_CONNMARK_SET = 0,
+	XT_CONNMARK_SAVE,
+	XT_CONNMARK_RESTORE
+};
+
+struct xt_connmark_tginfo1 {
+	__u32 ctmark, ctmask, nfmask;
+	__u8 mode;
+};
+
+struct xt_connmark_tginfo2 {
+	__u32 ctmark, ctmask, nfmask;
+	__u8 shift_dir, shift_bits, mode;
+};
+
+struct xt_connmark_mtinfo1 {
+	__u32 mark, mask;
+	__u8 invert;
+};
+
+#endif /*_XT_CONNMARK_H*/
diff --git a/include/linux/netfilter/xt_conntrack.h b/include/linux/netfilter/xt_conntrack.h
new file mode 100644
index 0000000..e971501
--- /dev/null
+++ b/include/linux/netfilter/xt_conntrack.h
@@ -0,0 +1,77 @@
+/* Header file for kernel module to match connection tracking information.
+ * GPL (C) 2001  Marc Boucher (marc@mbsi.ca).
+ */
+
+#ifndef _XT_CONNTRACK_H
+#define _XT_CONNTRACK_H
+
+#include <linux/types.h>
+#include <linux/netfilter/nf_conntrack_tuple_common.h>
+
+#define XT_CONNTRACK_STATE_BIT(ctinfo) (1 << ((ctinfo)%IP_CT_IS_REPLY+1))
+#define XT_CONNTRACK_STATE_INVALID (1 << 0)
+
+#define XT_CONNTRACK_STATE_SNAT (1 << (IP_CT_NUMBER + 1))
+#define XT_CONNTRACK_STATE_DNAT (1 << (IP_CT_NUMBER + 2))
+#define XT_CONNTRACK_STATE_UNTRACKED (1 << (IP_CT_NUMBER + 3))
+
+/* flags, invflags: */
+enum {
+	XT_CONNTRACK_STATE        = 1 << 0,
+	XT_CONNTRACK_PROTO        = 1 << 1,
+	XT_CONNTRACK_ORIGSRC      = 1 << 2,
+	XT_CONNTRACK_ORIGDST      = 1 << 3,
+	XT_CONNTRACK_REPLSRC      = 1 << 4,
+	XT_CONNTRACK_REPLDST      = 1 << 5,
+	XT_CONNTRACK_STATUS       = 1 << 6,
+	XT_CONNTRACK_EXPIRES      = 1 << 7,
+	XT_CONNTRACK_ORIGSRC_PORT = 1 << 8,
+	XT_CONNTRACK_ORIGDST_PORT = 1 << 9,
+	XT_CONNTRACK_REPLSRC_PORT = 1 << 10,
+	XT_CONNTRACK_REPLDST_PORT = 1 << 11,
+	XT_CONNTRACK_DIRECTION    = 1 << 12,
+	XT_CONNTRACK_STATE_ALIAS  = 1 << 13,
+};
+
+struct xt_conntrack_mtinfo1 {
+	union nf_inet_addr origsrc_addr, origsrc_mask;
+	union nf_inet_addr origdst_addr, origdst_mask;
+	union nf_inet_addr replsrc_addr, replsrc_mask;
+	union nf_inet_addr repldst_addr, repldst_mask;
+	__u32 expires_min, expires_max;
+	__u16 l4proto;
+	__be16 origsrc_port, origdst_port;
+	__be16 replsrc_port, repldst_port;
+	__u16 match_flags, invert_flags;
+	__u8 state_mask, status_mask;
+};
+
+struct xt_conntrack_mtinfo2 {
+	union nf_inet_addr origsrc_addr, origsrc_mask;
+	union nf_inet_addr origdst_addr, origdst_mask;
+	union nf_inet_addr replsrc_addr, replsrc_mask;
+	union nf_inet_addr repldst_addr, repldst_mask;
+	__u32 expires_min, expires_max;
+	__u16 l4proto;
+	__be16 origsrc_port, origdst_port;
+	__be16 replsrc_port, repldst_port;
+	__u16 match_flags, invert_flags;
+	__u16 state_mask, status_mask;
+};
+
+struct xt_conntrack_mtinfo3 {
+	union nf_inet_addr origsrc_addr, origsrc_mask;
+	union nf_inet_addr origdst_addr, origdst_mask;
+	union nf_inet_addr replsrc_addr, replsrc_mask;
+	union nf_inet_addr repldst_addr, repldst_mask;
+	__u32 expires_min, expires_max;
+	__u16 l4proto;
+	__u16 origsrc_port, origdst_port;
+	__u16 replsrc_port, repldst_port;
+	__u16 match_flags, invert_flags;
+	__u16 state_mask, status_mask;
+	__u16 origsrc_port_high, origdst_port_high;
+	__u16 replsrc_port_high, repldst_port_high;
+};
+
+#endif /*_XT_CONNTRACK_H*/
diff --git a/include/linux/netfilter/xt_cpu.h b/include/linux/netfilter/xt_cpu.h
new file mode 100644
index 0000000..93c7f11
--- /dev/null
+++ b/include/linux/netfilter/xt_cpu.h
@@ -0,0 +1,11 @@
+#ifndef _XT_CPU_H
+#define _XT_CPU_H
+
+#include <linux/types.h>
+
+struct xt_cpu_info {
+	__u32	cpu;
+	__u32	invert;
+};
+
+#endif /*_XT_CPU_H*/
diff --git a/include/linux/netfilter/xt_dccp.h b/include/linux/netfilter/xt_dccp.h
new file mode 100644
index 0000000..a579e1b
--- /dev/null
+++ b/include/linux/netfilter/xt_dccp.h
@@ -0,0 +1,25 @@
+#ifndef _XT_DCCP_H_
+#define _XT_DCCP_H_
+
+#include <linux/types.h>
+
+#define XT_DCCP_SRC_PORTS	        0x01
+#define XT_DCCP_DEST_PORTS	        0x02
+#define XT_DCCP_TYPE			0x04
+#define XT_DCCP_OPTION			0x08
+
+#define XT_DCCP_VALID_FLAGS		0x0f
+
+struct xt_dccp_info {
+	__u16 dpts[2];  /* Min, Max */
+	__u16 spts[2];  /* Min, Max */
+
+	__u16 flags;
+	__u16 invflags;
+
+	__u16 typemask;
+	__u8 option;
+};
+
+#endif /* _XT_DCCP_H_ */
+
diff --git a/include/linux/netfilter/xt_devgroup.h b/include/linux/netfilter/xt_devgroup.h
new file mode 100644
index 0000000..1babde0
--- /dev/null
+++ b/include/linux/netfilter/xt_devgroup.h
@@ -0,0 +1,21 @@
+#ifndef _XT_DEVGROUP_H
+#define _XT_DEVGROUP_H
+
+#include <linux/types.h>
+
+enum xt_devgroup_flags {
+	XT_DEVGROUP_MATCH_SRC	= 0x1,
+	XT_DEVGROUP_INVERT_SRC	= 0x2,
+	XT_DEVGROUP_MATCH_DST	= 0x4,
+	XT_DEVGROUP_INVERT_DST	= 0x8,
+};
+
+struct xt_devgroup_info {
+	__u32	flags;
+	__u32	src_group;
+	__u32	src_mask;
+	__u32	dst_group;
+	__u32	dst_mask;
+};
+
+#endif /* _XT_DEVGROUP_H */
diff --git a/include/linux/netfilter/xt_dscp.h b/include/linux/netfilter/xt_dscp.h
new file mode 100644
index 0000000..15f8932
--- /dev/null
+++ b/include/linux/netfilter/xt_dscp.h
@@ -0,0 +1,31 @@
+/* x_tables module for matching the IPv4/IPv6 DSCP field
+ *
+ * (C) 2002 Harald Welte <laforge@gnumonks.org>
+ * This software is distributed under GNU GPL v2, 1991
+ *
+ * See RFC2474 for a description of the DSCP field within the IP Header.
+ *
+ * xt_dscp.h,v 1.3 2002/08/05 19:00:21 laforge Exp
+*/
+#ifndef _XT_DSCP_H
+#define _XT_DSCP_H
+
+#include <linux/types.h>
+
+#define XT_DSCP_MASK	0xfc	/* 11111100 */
+#define XT_DSCP_SHIFT	2
+#define XT_DSCP_MAX	0x3f	/* 00111111 */
+
+/* match info */
+struct xt_dscp_info {
+	__u8 dscp;
+	__u8 invert;
+};
+
+struct xt_tos_match_info {
+	__u8 tos_mask;
+	__u8 tos_value;
+	__u8 invert;
+};
+
+#endif /* _XT_DSCP_H */
diff --git a/include/linux/netfilter/xt_ecn.h b/include/linux/netfilter/xt_ecn.h
new file mode 100644
index 0000000..c21cc28
--- /dev/null
+++ b/include/linux/netfilter/xt_ecn.h
@@ -0,0 +1,33 @@
+/* iptables module for matching the ECN header in IPv4 and TCP header
+ *
+ * (C) 2002 Harald Welte <laforge@netfilter.org>
+ *
+ * This software is distributed under GNU GPL v2, 1991
+*/
+#ifndef _XT_ECN_H
+#define _XT_ECN_H
+
+#include <linux/types.h>
+#include <linux/netfilter/xt_dscp.h>
+
+#define XT_ECN_IP_MASK	(~XT_DSCP_MASK)
+
+#define XT_ECN_OP_MATCH_IP	0x01
+#define XT_ECN_OP_MATCH_ECE	0x10
+#define XT_ECN_OP_MATCH_CWR	0x20
+
+#define XT_ECN_OP_MATCH_MASK	0xce
+
+/* match info */
+struct xt_ecn_info {
+	__u8 operation;
+	__u8 invert;
+	__u8 ip_ect;
+	union {
+		struct {
+			__u8 ect;
+		} tcp;
+	} proto;
+};
+
+#endif /* _XT_ECN_H */
diff --git a/include/linux/netfilter/xt_esp.h b/include/linux/netfilter/xt_esp.h
new file mode 100644
index 0000000..ee68824
--- /dev/null
+++ b/include/linux/netfilter/xt_esp.h
@@ -0,0 +1,15 @@
+#ifndef _XT_ESP_H
+#define _XT_ESP_H
+
+#include <linux/types.h>
+
+struct xt_esp {
+	__u32 spis[2];	/* Security Parameter Index */
+	__u8  invflags;	/* Inverse flags */
+};
+
+/* Values for "invflags" field in struct xt_esp. */
+#define XT_ESP_INV_SPI	0x01	/* Invert the sense of spi. */
+#define XT_ESP_INV_MASK	0x01	/* All possible flags. */
+
+#endif /*_XT_ESP_H*/
diff --git a/include/linux/netfilter/xt_hashlimit.h b/include/linux/netfilter/xt_hashlimit.h
new file mode 100644
index 0000000..ade33f6
--- /dev/null
+++ b/include/linux/netfilter/xt_hashlimit.h
@@ -0,0 +1,120 @@
+#ifndef _XT_HASHLIMIT_H
+#define _XT_HASHLIMIT_H
+
+#include <linux/types.h>
+
+/* timings are in milliseconds. */
+#define XT_HASHLIMIT_SCALE 10000
+#define XT_HASHLIMIT_SCALE_v2 1000000llu
+/* 1/10,000 sec period => max of 10,000/sec.  Min rate is then 429490
+ * seconds, or one packet every 59 hours.
+ */
+
+/* packet length accounting is done in 16-byte steps */
+#define XT_HASHLIMIT_BYTE_SHIFT 4
+
+/* details of this structure hidden by the implementation */
+struct xt_hashlimit_htable;
+
+enum {
+	XT_HASHLIMIT_HASH_DIP	= 1 << 0,
+	XT_HASHLIMIT_HASH_DPT	= 1 << 1,
+	XT_HASHLIMIT_HASH_SIP	= 1 << 2,
+	XT_HASHLIMIT_HASH_SPT	= 1 << 3,
+	XT_HASHLIMIT_INVERT	= 1 << 4,
+	XT_HASHLIMIT_BYTES	= 1 << 5,
+	XT_HASHLIMIT_RATE_MATCH	= 1 << 6,
+};
+
+struct hashlimit_cfg {
+	__u32 mode;	  /* bitmask of XT_HASHLIMIT_HASH_* */
+	__u32 avg;    /* Average secs between packets * scale */
+	__u32 burst;  /* Period multiplier for upper limit. */
+
+	/* user specified */
+	__u32 size;		/* how many buckets */
+	__u32 max;		/* max number of entries */
+	__u32 gc_interval;	/* gc interval */
+	__u32 expire;	/* when do entries expire? */
+};
+
+struct xt_hashlimit_info {
+	char name [IFNAMSIZ];		/* name */
+	struct hashlimit_cfg cfg;
+
+	/* Used internally by the kernel */
+	struct xt_hashlimit_htable *hinfo;
+	union {
+		void *ptr;
+		struct xt_hashlimit_info *master;
+	} u;
+};
+
+struct hashlimit_cfg1 {
+	__u32 mode;	  /* bitmask of XT_HASHLIMIT_HASH_* */
+	__u32 avg;    /* Average secs between packets * scale */
+	__u32 burst;  /* Period multiplier for upper limit. */
+
+	/* user specified */
+	__u32 size;		/* how many buckets */
+	__u32 max;		/* max number of entries */
+	__u32 gc_interval;	/* gc interval */
+	__u32 expire;	/* when do entries expire? */
+
+	__u8 srcmask, dstmask;
+};
+
+struct hashlimit_cfg2 {
+	__u64 avg;		/* Average secs between packets * scale */
+	__u64 burst;		/* Period multiplier for upper limit. */
+	__u32 mode;		/* bitmask of XT_HASHLIMIT_HASH_* */
+
+	/* user specified */
+	__u32 size;		/* how many buckets */
+	__u32 max;		/* max number of entries */
+	__u32 gc_interval;	/* gc interval */
+	__u32 expire;		/* when do entries expire? */
+
+	__u8 srcmask, dstmask;
+};
+
+struct hashlimit_cfg3 {
+	__u64 avg;		/* Average secs between packets * scale */
+	__u64 burst;		/* Period multiplier for upper limit. */
+	__u32 mode;		/* bitmask of XT_HASHLIMIT_HASH_* */
+
+	/* user specified */
+	__u32 size;		/* how many buckets */
+	__u32 max;		/* max number of entries */
+	__u32 gc_interval;	/* gc interval */
+	__u32 expire;		/* when do entries expire? */
+
+	__u32 interval;		/* in seconds*/
+	__u8 srcmask, dstmask;
+};
+
+struct xt_hashlimit_mtinfo1 {
+	char name[IFNAMSIZ];
+	struct hashlimit_cfg1 cfg;
+
+	/* Used internally by the kernel */
+	struct xt_hashlimit_htable *hinfo __attribute__((aligned(8)));
+};
+
+struct xt_hashlimit_mtinfo2 {
+	char name[NAME_MAX];
+	struct hashlimit_cfg2 cfg;
+
+	/* Used internally by the kernel */
+	struct xt_hashlimit_htable *hinfo __attribute__((aligned(8)));
+};
+
+struct xt_hashlimit_mtinfo3 {
+	char name[NAME_MAX];
+	struct hashlimit_cfg3 cfg;
+
+	/* Used internally by the kernel */
+	struct xt_hashlimit_htable *hinfo __attribute__((aligned(8)));
+};
+
+#endif /*_XT_HASHLIMIT_H*/
diff --git a/include/linux/netfilter/xt_helper.h b/include/linux/netfilter/xt_helper.h
new file mode 100644
index 0000000..6b42763
--- /dev/null
+++ b/include/linux/netfilter/xt_helper.h
@@ -0,0 +1,8 @@
+#ifndef _XT_HELPER_H
+#define _XT_HELPER_H
+
+struct xt_helper_info {
+	int invert;
+	char name[30];
+};
+#endif /* _XT_HELPER_H */
diff --git a/include/linux/netfilter/xt_ipcomp.h b/include/linux/netfilter/xt_ipcomp.h
new file mode 100644
index 0000000..45c7e40
--- /dev/null
+++ b/include/linux/netfilter/xt_ipcomp.h
@@ -0,0 +1,16 @@
+#ifndef _XT_IPCOMP_H
+#define _XT_IPCOMP_H
+
+#include <linux/types.h>
+
+struct xt_ipcomp {
+	__u32 spis[2];	/* Security Parameter Index */
+	__u8 invflags;	/* Inverse flags */
+	__u8 hdrres;	/* Test of the Reserved Filed */
+};
+
+/* Values for "invflags" field in struct xt_ipcomp. */
+#define XT_IPCOMP_INV_SPI	0x01	/* Invert the sense of spi. */
+#define XT_IPCOMP_INV_MASK	0x01	/* All possible flags. */
+
+#endif /*_XT_IPCOMP_H*/
diff --git a/include/linux/netfilter/xt_iprange.h b/include/linux/netfilter/xt_iprange.h
new file mode 100644
index 0000000..c1f21a7
--- /dev/null
+++ b/include/linux/netfilter/xt_iprange.h
@@ -0,0 +1,19 @@
+#ifndef _LINUX_NETFILTER_XT_IPRANGE_H
+#define _LINUX_NETFILTER_XT_IPRANGE_H 1
+
+#include <linux/types.h>
+
+enum {
+	IPRANGE_SRC     = 1 << 0,	/* match source IP address */
+	IPRANGE_DST     = 1 << 1,	/* match destination IP address */
+	IPRANGE_SRC_INV = 1 << 4,	/* negate the condition */
+	IPRANGE_DST_INV = 1 << 5,	/* -"- */
+};
+
+struct xt_iprange_mtinfo {
+	union nf_inet_addr src_min, src_max;
+	union nf_inet_addr dst_min, dst_max;
+	__u8 flags;
+};
+
+#endif /* _LINUX_NETFILTER_XT_IPRANGE_H */
diff --git a/include/linux/netfilter/xt_ipvs.h b/include/linux/netfilter/xt_ipvs.h
new file mode 100644
index 0000000..eff34ac
--- /dev/null
+++ b/include/linux/netfilter/xt_ipvs.h
@@ -0,0 +1,29 @@
+#ifndef _XT_IPVS_H
+#define _XT_IPVS_H
+
+#include <linux/types.h>
+
+enum {
+	XT_IPVS_IPVS_PROPERTY =	1 << 0, /* all other options imply this one */
+	XT_IPVS_PROTO =		1 << 1,
+	XT_IPVS_VADDR =		1 << 2,
+	XT_IPVS_VPORT =		1 << 3,
+	XT_IPVS_DIR =		1 << 4,
+	XT_IPVS_METHOD =	1 << 5,
+	XT_IPVS_VPORTCTL =	1 << 6,
+	XT_IPVS_MASK =		(1 << 7) - 1,
+	XT_IPVS_ONCE_MASK =	XT_IPVS_MASK & ~XT_IPVS_IPVS_PROPERTY
+};
+
+struct xt_ipvs_mtinfo {
+	union nf_inet_addr	vaddr, vmask;
+	__be16			vport;
+	__u8			l4proto;
+	__u8			fwd_method;
+	__be16			vportctl;
+
+	__u8			invert;
+	__u8			bitmask;
+};
+
+#endif /* _XT_IPVS_H */
diff --git a/include/linux/netfilter/xt_length.h b/include/linux/netfilter/xt_length.h
new file mode 100644
index 0000000..b82ed7c
--- /dev/null
+++ b/include/linux/netfilter/xt_length.h
@@ -0,0 +1,11 @@
+#ifndef _XT_LENGTH_H
+#define _XT_LENGTH_H
+
+#include <linux/types.h>
+
+struct xt_length_info {
+    __u16	min, max;
+    __u8	invert;
+};
+
+#endif /*_XT_LENGTH_H*/
diff --git a/include/linux/netfilter/xt_limit.h b/include/linux/netfilter/xt_limit.h
new file mode 100644
index 0000000..bb47fc4
--- /dev/null
+++ b/include/linux/netfilter/xt_limit.h
@@ -0,0 +1,24 @@
+#ifndef _XT_RATE_H
+#define _XT_RATE_H
+
+#include <linux/types.h>
+
+/* timings are in milliseconds. */
+#define XT_LIMIT_SCALE 10000
+
+struct xt_limit_priv;
+
+/* 1/10,000 sec period => max of 10,000/sec.  Min rate is then 429490
+   seconds, or one every 59 hours. */
+struct xt_rateinfo {
+	__u32 avg;    /* Average secs between packets * scale */
+	__u32 burst;  /* Period multiplier for upper limit. */
+
+	/* Used internally by the kernel */
+	unsigned long prev; /* moved to xt_limit_priv */
+	__u32 credit; /* moved to xt_limit_priv */
+	__u32 credit_cap, cost;
+
+	struct xt_limit_priv *master;
+};
+#endif /*_XT_RATE_H*/
diff --git a/include/linux/netfilter/xt_mac.h b/include/linux/netfilter/xt_mac.h
new file mode 100644
index 0000000..b892cdc
--- /dev/null
+++ b/include/linux/netfilter/xt_mac.h
@@ -0,0 +1,8 @@
+#ifndef _XT_MAC_H
+#define _XT_MAC_H
+
+struct xt_mac_info {
+    unsigned char srcaddr[ETH_ALEN];
+    int invert;
+};
+#endif /*_XT_MAC_H*/
diff --git a/include/linux/netfilter/xt_mark.h b/include/linux/netfilter/xt_mark.h
new file mode 100644
index 0000000..ecadc40
--- /dev/null
+++ b/include/linux/netfilter/xt_mark.h
@@ -0,0 +1,15 @@
+#ifndef _XT_MARK_H
+#define _XT_MARK_H
+
+#include <linux/types.h>
+
+struct xt_mark_tginfo2 {
+	__u32 mark, mask;
+};
+
+struct xt_mark_mtinfo1 {
+	__u32 mark, mask;
+	__u8 invert;
+};
+
+#endif /*_XT_MARK_H*/
diff --git a/include/linux/netfilter/xt_multiport.h b/include/linux/netfilter/xt_multiport.h
new file mode 100644
index 0000000..5b7e72d
--- /dev/null
+++ b/include/linux/netfilter/xt_multiport.h
@@ -0,0 +1,29 @@
+#ifndef _XT_MULTIPORT_H
+#define _XT_MULTIPORT_H
+
+#include <linux/types.h>
+
+enum xt_multiport_flags {
+	XT_MULTIPORT_SOURCE,
+	XT_MULTIPORT_DESTINATION,
+	XT_MULTIPORT_EITHER
+};
+
+#define XT_MULTI_PORTS	15
+
+/* Must fit inside union xt_matchinfo: 16 bytes */
+struct xt_multiport {
+	__u8 flags;				/* Type of comparison */
+	__u8 count;				/* Number of ports */
+	__u16 ports[XT_MULTI_PORTS];	/* Ports */
+};
+
+struct xt_multiport_v1 {
+	__u8 flags;				/* Type of comparison */
+	__u8 count;				/* Number of ports */
+	__u16 ports[XT_MULTI_PORTS];	/* Ports */
+	__u8 pflags[XT_MULTI_PORTS];	/* Port flags */
+	__u8 invert;			/* Invert flag */
+};
+
+#endif /*_XT_MULTIPORT_H*/
diff --git a/include/linux/netfilter/xt_nfacct.h b/include/linux/netfilter/xt_nfacct.h
new file mode 100644
index 0000000..04ec2b0
--- /dev/null
+++ b/include/linux/netfilter/xt_nfacct.h
@@ -0,0 +1,22 @@
+#ifndef _XT_NFACCT_MATCH_H
+#define _XT_NFACCT_MATCH_H
+
+#include <linux/types.h>
+
+#ifndef NFACCT_NAME_MAX
+#define NFACCT_NAME_MAX 32
+#endif
+
+struct nf_acct;
+
+struct xt_nfacct_match_info {
+	char		name[NFACCT_NAME_MAX];
+	struct nf_acct	*nfacct;
+};
+
+struct xt_nfacct_match_info_v1 {
+	char		name[NFACCT_NAME_MAX];
+	struct nf_acct	*nfacct __attribute__((aligned(8)));
+};
+
+#endif /* _XT_NFACCT_MATCH_H */
diff --git a/include/linux/netfilter/xt_osf.h b/include/linux/netfilter/xt_osf.h
new file mode 100644
index 0000000..d0c4c76
--- /dev/null
+++ b/include/linux/netfilter/xt_osf.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2003+ Evgeniy Polyakov <johnpol@2ka.mxt.ru>
+ *
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef _XT_OSF_H
+#define _XT_OSF_H
+
+#include <linux/types.h>
+
+#define MAXGENRELEN		32
+
+#define XT_OSF_GENRE		(1<<0)
+#define	XT_OSF_TTL		(1<<1)
+#define XT_OSF_LOG		(1<<2)
+#define XT_OSF_INVERT		(1<<3)
+
+#define XT_OSF_LOGLEVEL_ALL	0	/* log all matched fingerprints */
+#define XT_OSF_LOGLEVEL_FIRST	1	/* log only the first matced fingerprint */
+#define XT_OSF_LOGLEVEL_ALL_KNOWN	2 /* do not log unknown packets */
+
+#define XT_OSF_TTL_TRUE		0	/* True ip and fingerprint TTL comparison */
+#define XT_OSF_TTL_LESS		1	/* Check if ip TTL is less than fingerprint one */
+#define XT_OSF_TTL_NOCHECK	2	/* Do not compare ip and fingerprint TTL at all */
+
+struct xt_osf_info {
+	char			genre[MAXGENRELEN];
+	__u32			len;
+	__u32			flags;
+	__u32			loglevel;
+	__u32			ttl;
+};
+
+/*
+ * Wildcard MSS (kind of).
+ * It is used to implement a state machine for the different wildcard values
+ * of the MSS and window sizes.
+ */
+struct xt_osf_wc {
+	__u32			wc;
+	__u32			val;
+};
+
+/*
+ * This struct represents IANA options
+ * http://www.iana.org/assignments/tcp-parameters
+ */
+struct xt_osf_opt {
+	__u16			kind, length;
+	struct xt_osf_wc	wc;
+};
+
+struct xt_osf_user_finger {
+	struct xt_osf_wc	wss;
+
+	__u8			ttl, df;
+	__u16			ss, mss;
+	__u16			opt_num;
+
+	char			genre[MAXGENRELEN];
+	char			version[MAXGENRELEN];
+	char			subtype[MAXGENRELEN];
+
+	/* MAX_IPOPTLEN is maximum if all options are NOPs or EOLs */
+	struct xt_osf_opt	opt[MAX_IPOPTLEN];
+};
+
+struct xt_osf_nlmsg {
+	struct xt_osf_user_finger	f;
+	struct iphdr		ip;
+	struct tcphdr		tcp;
+};
+
+/* Defines for IANA option kinds */
+
+enum iana_options {
+	OSFOPT_EOL = 0,		/* End of options */
+	OSFOPT_NOP, 		/* NOP */
+	OSFOPT_MSS, 		/* Maximum segment size */
+	OSFOPT_WSO, 		/* Window scale option */
+	OSFOPT_SACKP,		/* SACK permitted */
+	OSFOPT_SACK,		/* SACK */
+	OSFOPT_ECHO,
+	OSFOPT_ECHOREPLY,
+	OSFOPT_TS,		/* Timestamp option */
+	OSFOPT_POCP,		/* Partial Order Connection Permitted */
+	OSFOPT_POSP,		/* Partial Order Service Profile */
+
+	/* Others are not used in the current OSF */
+	OSFOPT_EMPTY = 255,
+};
+
+/*
+ * Initial window size option state machine: multiple of mss, mtu or
+ * plain numeric value. Can also be made as plain numeric value which
+ * is not a multiple of specified value.
+ */
+enum xt_osf_window_size_options {
+	OSF_WSS_PLAIN	= 0,
+	OSF_WSS_MSS,
+	OSF_WSS_MTU,
+	OSF_WSS_MODULO,
+	OSF_WSS_MAX,
+};
+
+/*
+ * Add/remove fingerprint from the kernel.
+ */
+enum xt_osf_msg_types {
+	OSF_MSG_ADD,
+	OSF_MSG_REMOVE,
+	OSF_MSG_MAX,
+};
+
+enum xt_osf_attr_type {
+	OSF_ATTR_UNSPEC,
+	OSF_ATTR_FINGER,
+	OSF_ATTR_MAX,
+};
+
+#endif				/* _XT_OSF_H */
diff --git a/include/linux/netfilter/xt_owner.h b/include/linux/netfilter/xt_owner.h
new file mode 100644
index 0000000..e7731dc
--- /dev/null
+++ b/include/linux/netfilter/xt_owner.h
@@ -0,0 +1,19 @@
+#ifndef _XT_OWNER_MATCH_H
+#define _XT_OWNER_MATCH_H
+
+#include <linux/types.h>
+
+enum {
+	XT_OWNER_UID          = 1 << 0,
+	XT_OWNER_GID          = 1 << 1,
+	XT_OWNER_SOCKET       = 1 << 2,
+	XT_OWNER_SUPPL_GROUPS = 1 << 3,
+};
+
+struct xt_owner_match_info {
+	__u32 uid_min, uid_max;
+	__u32 gid_min, gid_max;
+	__u8 match, invert;
+};
+
+#endif /* _XT_OWNER_MATCH_H */
diff --git a/include/linux/netfilter/xt_physdev.h b/include/linux/netfilter/xt_physdev.h
new file mode 100644
index 0000000..7d53660
--- /dev/null
+++ b/include/linux/netfilter/xt_physdev.h
@@ -0,0 +1,23 @@
+#ifndef _XT_PHYSDEV_H
+#define _XT_PHYSDEV_H
+
+#include <linux/types.h>
+
+
+#define XT_PHYSDEV_OP_IN		0x01
+#define XT_PHYSDEV_OP_OUT		0x02
+#define XT_PHYSDEV_OP_BRIDGED		0x04
+#define XT_PHYSDEV_OP_ISIN		0x08
+#define XT_PHYSDEV_OP_ISOUT		0x10
+#define XT_PHYSDEV_OP_MASK		(0x20 - 1)
+
+struct xt_physdev_info {
+	char physindev[IFNAMSIZ];
+	char in_mask[IFNAMSIZ];
+	char physoutdev[IFNAMSIZ];
+	char out_mask[IFNAMSIZ];
+	__u8 invert;
+	__u8 bitmask;
+};
+
+#endif /*_XT_PHYSDEV_H*/
diff --git a/include/linux/netfilter/xt_pkttype.h b/include/linux/netfilter/xt_pkttype.h
new file mode 100644
index 0000000..f265cf5
--- /dev/null
+++ b/include/linux/netfilter/xt_pkttype.h
@@ -0,0 +1,8 @@
+#ifndef _XT_PKTTYPE_H
+#define _XT_PKTTYPE_H
+
+struct xt_pkttype_info {
+	int	pkttype;
+	int	invert;
+};
+#endif /*_XT_PKTTYPE_H*/
diff --git a/include/linux/netfilter/xt_policy.h b/include/linux/netfilter/xt_policy.h
new file mode 100644
index 0000000..d246eac
--- /dev/null
+++ b/include/linux/netfilter/xt_policy.h
@@ -0,0 +1,58 @@
+#ifndef _XT_POLICY_H
+#define _XT_POLICY_H
+
+#include <linux/types.h>
+
+#define XT_POLICY_MAX_ELEM	4
+
+enum xt_policy_flags {
+	XT_POLICY_MATCH_IN	= 0x1,
+	XT_POLICY_MATCH_OUT	= 0x2,
+	XT_POLICY_MATCH_NONE	= 0x4,
+	XT_POLICY_MATCH_STRICT	= 0x8,
+};
+
+enum xt_policy_modes {
+	XT_POLICY_MODE_TRANSPORT,
+	XT_POLICY_MODE_TUNNEL
+};
+
+struct xt_policy_spec {
+	__u8	saddr:1,
+			daddr:1,
+			proto:1,
+			mode:1,
+			spi:1,
+			reqid:1;
+};
+
+union xt_policy_addr {
+	struct in_addr	a4;
+	struct in6_addr	a6;
+};
+
+struct xt_policy_elem {
+	union {
+		struct {
+			union xt_policy_addr saddr;
+			union xt_policy_addr smask;
+			union xt_policy_addr daddr;
+			union xt_policy_addr dmask;
+		};
+	};
+	__be32			spi;
+	__u32		reqid;
+	__u8		proto;
+	__u8		mode;
+
+	struct xt_policy_spec	match;
+	struct xt_policy_spec	invert;
+};
+
+struct xt_policy_info {
+	struct xt_policy_elem pol[XT_POLICY_MAX_ELEM];
+	__u16 flags;
+	__u16 len;
+};
+
+#endif /* _XT_POLICY_H */
diff --git a/include/linux/netfilter/xt_quota.h b/include/linux/netfilter/xt_quota.h
new file mode 100644
index 0000000..9314723
--- /dev/null
+++ b/include/linux/netfilter/xt_quota.h
@@ -0,0 +1,22 @@
+#ifndef _XT_QUOTA_H
+#define _XT_QUOTA_H
+
+#include <linux/types.h>
+
+enum xt_quota_flags {
+	XT_QUOTA_INVERT		= 0x1,
+};
+#define XT_QUOTA_MASK		0x1
+
+struct xt_quota_priv;
+
+struct xt_quota_info {
+	__u32 flags;
+	__u32 pad;
+	__aligned_u64 quota;
+
+	/* Used internally by the kernel */
+	struct xt_quota_priv	*master;
+};
+
+#endif /* _XT_QUOTA_H */
diff --git a/include/linux/netfilter/xt_quota2.h b/include/linux/netfilter/xt_quota2.h
new file mode 100644
index 0000000..eadc690
--- /dev/null
+++ b/include/linux/netfilter/xt_quota2.h
@@ -0,0 +1,25 @@
+#ifndef _XT_QUOTA_H
+#define _XT_QUOTA_H
+
+enum xt_quota_flags {
+	XT_QUOTA_INVERT    = 1 << 0,
+	XT_QUOTA_GROW      = 1 << 1,
+	XT_QUOTA_PACKET    = 1 << 2,
+	XT_QUOTA_NO_CHANGE = 1 << 3,
+	XT_QUOTA_MASK      = 0x0F,
+};
+
+struct xt_quota_counter;
+
+struct xt_quota_mtinfo2 {
+	char name[15];
+	u_int8_t flags;
+
+	/* Comparison-invariant */
+	aligned_u64 quota;
+
+	/* Used internally by the kernel */
+	struct xt_quota_counter *master __attribute__((aligned(8)));
+};
+
+#endif /* _XT_QUOTA_H */
diff --git a/include/linux/netfilter/xt_rateest.h b/include/linux/netfilter/xt_rateest.h
new file mode 100644
index 0000000..d40a619
--- /dev/null
+++ b/include/linux/netfilter/xt_rateest.h
@@ -0,0 +1,37 @@
+#ifndef _XT_RATEEST_MATCH_H
+#define _XT_RATEEST_MATCH_H
+
+#include <linux/types.h>
+
+enum xt_rateest_match_flags {
+	XT_RATEEST_MATCH_INVERT	= 1<<0,
+	XT_RATEEST_MATCH_ABS	= 1<<1,
+	XT_RATEEST_MATCH_REL	= 1<<2,
+	XT_RATEEST_MATCH_DELTA	= 1<<3,
+	XT_RATEEST_MATCH_BPS	= 1<<4,
+	XT_RATEEST_MATCH_PPS	= 1<<5,
+};
+
+enum xt_rateest_match_mode {
+	XT_RATEEST_MATCH_NONE,
+	XT_RATEEST_MATCH_EQ,
+	XT_RATEEST_MATCH_LT,
+	XT_RATEEST_MATCH_GT,
+};
+
+struct xt_rateest_match_info {
+	char			name1[IFNAMSIZ];
+	char			name2[IFNAMSIZ];
+	__u16		flags;
+	__u16		mode;
+	__u32		bps1;
+	__u32		pps1;
+	__u32		bps2;
+	__u32		pps2;
+
+	/* Used internally by the kernel */
+	struct xt_rateest	*est1 __attribute__((aligned(8)));
+	struct xt_rateest	*est2 __attribute__((aligned(8)));
+};
+
+#endif /* _XT_RATEEST_MATCH_H */
diff --git a/include/linux/netfilter/xt_realm.h b/include/linux/netfilter/xt_realm.h
new file mode 100644
index 0000000..d4a82ee
--- /dev/null
+++ b/include/linux/netfilter/xt_realm.h
@@ -0,0 +1,12 @@
+#ifndef _XT_REALM_H
+#define _XT_REALM_H
+
+#include <linux/types.h>
+
+struct xt_realm_info {
+	__u32 id;
+	__u32 mask;
+	__u8 invert;
+};
+
+#endif /* _XT_REALM_H */
diff --git a/include/linux/netfilter/xt_recent.h b/include/linux/netfilter/xt_recent.h
new file mode 100644
index 0000000..6ef36c1
--- /dev/null
+++ b/include/linux/netfilter/xt_recent.h
@@ -0,0 +1,45 @@
+#ifndef _LINUX_NETFILTER_XT_RECENT_H
+#define _LINUX_NETFILTER_XT_RECENT_H 1
+
+#include <linux/types.h>
+
+enum {
+	XT_RECENT_CHECK    = 1 << 0,
+	XT_RECENT_SET      = 1 << 1,
+	XT_RECENT_UPDATE   = 1 << 2,
+	XT_RECENT_REMOVE   = 1 << 3,
+	XT_RECENT_TTL      = 1 << 4,
+	XT_RECENT_REAP     = 1 << 5,
+
+	XT_RECENT_SOURCE   = 0,
+	XT_RECENT_DEST     = 1,
+
+	XT_RECENT_NAME_LEN = 200,
+};
+
+/* Only allowed with --rcheck and --update */
+#define XT_RECENT_MODIFIERS (XT_RECENT_TTL|XT_RECENT_REAP)
+
+#define XT_RECENT_VALID_FLAGS (XT_RECENT_CHECK|XT_RECENT_SET|XT_RECENT_UPDATE|\
+			       XT_RECENT_REMOVE|XT_RECENT_TTL|XT_RECENT_REAP)
+
+struct xt_recent_mtinfo {
+	__u32 seconds;
+	__u32 hit_count;
+	__u8 check_set;
+	__u8 invert;
+	char name[XT_RECENT_NAME_LEN];
+	__u8 side;
+};
+
+struct xt_recent_mtinfo_v1 {
+	__u32 seconds;
+	__u32 hit_count;
+	__u8 check_set;
+	__u8 invert;
+	char name[XT_RECENT_NAME_LEN];
+	__u8 side;
+	union nf_inet_addr mask;
+};
+
+#endif /* _LINUX_NETFILTER_XT_RECENT_H */
diff --git a/include/linux/netfilter/xt_rpfilter.h b/include/linux/netfilter/xt_rpfilter.h
new file mode 100644
index 0000000..672b605
--- /dev/null
+++ b/include/linux/netfilter/xt_rpfilter.h
@@ -0,0 +1,17 @@
+#ifndef _XT_RPATH_H
+#define _XT_RPATH_H
+
+#include <linux/types.h>
+
+enum {
+	XT_RPFILTER_LOOSE = 1 << 0,
+	XT_RPFILTER_VALID_MARK = 1 << 1,
+	XT_RPFILTER_ACCEPT_LOCAL = 1 << 2,
+	XT_RPFILTER_INVERT = 1 << 3,
+};
+
+struct xt_rpfilter_info {
+	__u8 flags;
+};
+
+#endif
diff --git a/include/linux/netfilter/xt_sctp.h b/include/linux/netfilter/xt_sctp.h
new file mode 100644
index 0000000..5b28525
--- /dev/null
+++ b/include/linux/netfilter/xt_sctp.h
@@ -0,0 +1,92 @@
+#ifndef _XT_SCTP_H_
+#define _XT_SCTP_H_
+
+#include <linux/types.h>
+
+#define XT_SCTP_SRC_PORTS	        0x01
+#define XT_SCTP_DEST_PORTS	        0x02
+#define XT_SCTP_CHUNK_TYPES		0x04
+
+#define XT_SCTP_VALID_FLAGS		0x07
+
+struct xt_sctp_flag_info {
+	__u8 chunktype;
+	__u8 flag;
+	__u8 flag_mask;
+};
+
+#define XT_NUM_SCTP_FLAGS	4
+
+struct xt_sctp_info {
+	__u16 dpts[2];  /* Min, Max */
+	__u16 spts[2];  /* Min, Max */
+
+	__u32 chunkmap[256 / sizeof (__u32)];  /* Bit mask of chunks to be matched according to RFC 2960 */
+
+#define SCTP_CHUNK_MATCH_ANY   0x01  /* Match if any of the chunk types are present */
+#define SCTP_CHUNK_MATCH_ALL   0x02  /* Match if all of the chunk types are present */
+#define SCTP_CHUNK_MATCH_ONLY  0x04  /* Match if these are the only chunk types present */
+
+	__u32 chunk_match_type;
+	struct xt_sctp_flag_info flag_info[XT_NUM_SCTP_FLAGS];
+	int flag_count;
+
+	__u32 flags;
+	__u32 invflags;
+};
+
+#define bytes(type) (sizeof(type) * 8)
+
+#define SCTP_CHUNKMAP_SET(chunkmap, type) 		\
+	do { 						\
+		(chunkmap)[type / bytes(__u32)] |= 	\
+			1u << (type % bytes(__u32));	\
+	} while (0)
+
+#define SCTP_CHUNKMAP_CLEAR(chunkmap, type)		 	\
+	do {							\
+		(chunkmap)[type / bytes(__u32)] &= 		\
+			~(1u << (type % bytes(__u32)));	\
+	} while (0)
+
+#define SCTP_CHUNKMAP_IS_SET(chunkmap, type) 			\
+({								\
+	((chunkmap)[type / bytes (__u32)] & 		\
+		(1u << (type % bytes (__u32)))) ? 1: 0;	\
+})
+
+#define SCTP_CHUNKMAP_RESET(chunkmap) \
+	memset((chunkmap), 0, sizeof(chunkmap))
+
+#define SCTP_CHUNKMAP_SET_ALL(chunkmap) \
+	memset((chunkmap), ~0U, sizeof(chunkmap))
+
+#define SCTP_CHUNKMAP_COPY(destmap, srcmap) \
+	memcpy((destmap), (srcmap), sizeof(srcmap))
+
+#define SCTP_CHUNKMAP_IS_CLEAR(chunkmap) \
+	__sctp_chunkmap_is_clear((chunkmap), ARRAY_SIZE(chunkmap))
+static __inline__ bool
+__sctp_chunkmap_is_clear(const __u32 *chunkmap, unsigned int n)
+{
+	unsigned int i;
+	for (i = 0; i < n; ++i)
+		if (chunkmap[i])
+			return false;
+	return true;
+}
+
+#define SCTP_CHUNKMAP_IS_ALL_SET(chunkmap) \
+	__sctp_chunkmap_is_all_set((chunkmap), ARRAY_SIZE(chunkmap))
+static __inline__ bool
+__sctp_chunkmap_is_all_set(const __u32 *chunkmap, unsigned int n)
+{
+	unsigned int i;
+	for (i = 0; i < n; ++i)
+		if (chunkmap[i] != ~0U)
+			return false;
+	return true;
+}
+
+#endif /* _XT_SCTP_H_ */
+
diff --git a/include/linux/netfilter/xt_set.h b/include/linux/netfilter/xt_set.h
new file mode 100644
index 0000000..4210c9b
--- /dev/null
+++ b/include/linux/netfilter/xt_set.h
@@ -0,0 +1,93 @@
+#ifndef _XT_SET_H
+#define _XT_SET_H
+
+#include <linux/types.h>
+#include <linux/netfilter/ipset/ip_set.h>
+
+/* Revision 0 interface: backward compatible with netfilter/iptables */
+
+/*
+ * Option flags for kernel operations (xt_set_info_v0)
+ */
+#define IPSET_SRC		0x01	/* Source match/add */
+#define IPSET_DST		0x02	/* Destination match/add */
+#define IPSET_MATCH_INV		0x04	/* Inverse matching */
+
+struct xt_set_info_v0 {
+	ip_set_id_t index;
+	union {
+		__u32 flags[IPSET_DIM_MAX + 1];
+		struct {
+			__u32 __flags[IPSET_DIM_MAX];
+			__u8 dim;
+			__u8 flags;
+		} compat;
+	} u;
+};
+
+/* match and target infos */
+struct xt_set_info_match_v0 {
+	struct xt_set_info_v0 match_set;
+};
+
+struct xt_set_info_target_v0 {
+	struct xt_set_info_v0 add_set;
+	struct xt_set_info_v0 del_set;
+};
+
+/* Revision 1  match and target */
+
+struct xt_set_info {
+	ip_set_id_t index;
+	__u8 dim;
+	__u8 flags;
+};
+
+/* match and target infos */
+struct xt_set_info_match_v1 {
+	struct xt_set_info match_set;
+};
+
+struct xt_set_info_target_v1 {
+	struct xt_set_info add_set;
+	struct xt_set_info del_set;
+};
+
+/* Revision 2 target */
+
+struct xt_set_info_target_v2 {
+	struct xt_set_info add_set;
+	struct xt_set_info del_set;
+	__u32 flags;
+	__u32 timeout;
+};
+
+/* Revision 3 match */
+
+struct xt_set_info_match_v3 {
+	struct xt_set_info match_set;
+	struct ip_set_counter_match0 packets;
+	struct ip_set_counter_match0 bytes;
+	__u32 flags;
+};
+
+/* Revision 4 match */
+
+struct xt_set_info_match_v4 {
+	struct xt_set_info match_set;
+	struct ip_set_counter_match packets;
+	struct ip_set_counter_match bytes;
+	__u32 flags;
+};
+
+/* Revision 3 target */
+
+struct xt_set_info_target_v3 {
+	struct xt_set_info add_set;
+	struct xt_set_info del_set;
+	struct xt_set_info map_set;
+	__u32 flags;
+	__u32 timeout;
+};
+
+#endif /*_XT_SET_H*/
diff --git a/include/linux/netfilter/xt_socket.h b/include/linux/netfilter/xt_socket.h
new file mode 100644
index 0000000..87644f8
--- /dev/null
+++ b/include/linux/netfilter/xt_socket.h
@@ -0,0 +1,29 @@
+#ifndef _XT_SOCKET_H
+#define _XT_SOCKET_H
+
+#include <linux/types.h>
+
+enum {
+	XT_SOCKET_TRANSPARENT = 1 << 0,
+	XT_SOCKET_NOWILDCARD = 1 << 1,
+	XT_SOCKET_RESTORESKMARK = 1 << 2,
+};
+
+struct xt_socket_mtinfo1 {
+	__u8 flags;
+};
+#define XT_SOCKET_FLAGS_V1 XT_SOCKET_TRANSPARENT
+
+struct xt_socket_mtinfo2 {
+	__u8 flags;
+};
+#define XT_SOCKET_FLAGS_V2 (XT_SOCKET_TRANSPARENT | XT_SOCKET_NOWILDCARD)
+
+struct xt_socket_mtinfo3 {
+	__u8 flags;
+};
+#define XT_SOCKET_FLAGS_V3 (XT_SOCKET_TRANSPARENT \
+			   | XT_SOCKET_NOWILDCARD \
+			   | XT_SOCKET_RESTORESKMARK)
+
+#endif /* _XT_SOCKET_H */
diff --git a/include/linux/netfilter/xt_state.h b/include/linux/netfilter/xt_state.h
new file mode 100644
index 0000000..7b32de8
--- /dev/null
+++ b/include/linux/netfilter/xt_state.h
@@ -0,0 +1,12 @@
+#ifndef _XT_STATE_H
+#define _XT_STATE_H
+
+#define XT_STATE_BIT(ctinfo) (1 << ((ctinfo)%IP_CT_IS_REPLY+1))
+#define XT_STATE_INVALID (1 << 0)
+
+#define XT_STATE_UNTRACKED (1 << (IP_CT_NUMBER + 1))
+
+struct xt_state_info {
+	unsigned int statemask;
+};
+#endif /*_XT_STATE_H*/
diff --git a/include/linux/netfilter/xt_statistic.h b/include/linux/netfilter/xt_statistic.h
new file mode 100644
index 0000000..4e983ef
--- /dev/null
+++ b/include/linux/netfilter/xt_statistic.h
@@ -0,0 +1,36 @@
+#ifndef _XT_STATISTIC_H
+#define _XT_STATISTIC_H
+
+#include <linux/types.h>
+
+enum xt_statistic_mode {
+	XT_STATISTIC_MODE_RANDOM,
+	XT_STATISTIC_MODE_NTH,
+	__XT_STATISTIC_MODE_MAX
+};
+#define XT_STATISTIC_MODE_MAX (__XT_STATISTIC_MODE_MAX - 1)
+
+enum xt_statistic_flags {
+	XT_STATISTIC_INVERT		= 0x1,
+};
+#define XT_STATISTIC_MASK		0x1
+
+struct xt_statistic_priv;
+
+struct xt_statistic_info {
+	__u16			mode;
+	__u16			flags;
+	union {
+		struct {
+			__u32	probability;
+		} random;
+		struct {
+			__u32	every;
+			__u32	packet;
+			__u32	count; /* unused */
+		} nth;
+	} u;
+	struct xt_statistic_priv *master __attribute__((aligned(8)));
+};
+
+#endif /* _XT_STATISTIC_H */
diff --git a/include/linux/netfilter/xt_string.h b/include/linux/netfilter/xt_string.h
new file mode 100644
index 0000000..235347c
--- /dev/null
+++ b/include/linux/netfilter/xt_string.h
@@ -0,0 +1,34 @@
+#ifndef _XT_STRING_H
+#define _XT_STRING_H
+
+#include <linux/types.h>
+
+#define XT_STRING_MAX_PATTERN_SIZE 128
+#define XT_STRING_MAX_ALGO_NAME_SIZE 16
+
+enum {
+	XT_STRING_FLAG_INVERT		= 0x01,
+	XT_STRING_FLAG_IGNORECASE	= 0x02
+};
+
+struct xt_string_info {
+	__u16 from_offset;
+	__u16 to_offset;
+	char	  algo[XT_STRING_MAX_ALGO_NAME_SIZE];
+	char 	  pattern[XT_STRING_MAX_PATTERN_SIZE];
+	__u8  patlen;
+	union {
+		struct {
+			__u8  invert;
+		} v0;
+
+		struct {
+			__u8  flags;
+		} v1;
+	} u;
+
+	/* Used internally by the kernel */
+	struct ts_config __attribute__((aligned(8))) *config;
+};
+
+#endif /*_XT_STRING_H*/
diff --git a/include/linux/netfilter/xt_tcpmss.h b/include/linux/netfilter/xt_tcpmss.h
new file mode 100644
index 0000000..fbac56b
--- /dev/null
+++ b/include/linux/netfilter/xt_tcpmss.h
@@ -0,0 +1,11 @@
+#ifndef _XT_TCPMSS_MATCH_H
+#define _XT_TCPMSS_MATCH_H
+
+#include <linux/types.h>
+
+struct xt_tcpmss_match_info {
+    __u16 mss_min, mss_max;
+    __u8 invert;
+};
+
+#endif /*_XT_TCPMSS_MATCH_H*/
diff --git a/include/linux/netfilter/xt_tcpudp.h b/include/linux/netfilter/xt_tcpudp.h
new file mode 100644
index 0000000..38aa7b3
--- /dev/null
+++ b/include/linux/netfilter/xt_tcpudp.h
@@ -0,0 +1,36 @@
+#ifndef _XT_TCPUDP_H
+#define _XT_TCPUDP_H
+
+#include <linux/types.h>
+
+/* TCP matching stuff */
+struct xt_tcp {
+	__u16 spts[2];			/* Source port range. */
+	__u16 dpts[2];			/* Destination port range. */
+	__u8 option;			/* TCP Option iff non-zero*/
+	__u8 flg_mask;			/* TCP flags mask byte */
+	__u8 flg_cmp;			/* TCP flags compare byte */
+	__u8 invflags;			/* Inverse flags */
+};
+
+/* Values for "inv" field in struct ipt_tcp. */
+#define XT_TCP_INV_SRCPT	0x01	/* Invert the sense of source ports. */
+#define XT_TCP_INV_DSTPT	0x02	/* Invert the sense of dest ports. */
+#define XT_TCP_INV_FLAGS	0x04	/* Invert the sense of TCP flags. */
+#define XT_TCP_INV_OPTION	0x08	/* Invert the sense of option test. */
+#define XT_TCP_INV_MASK		0x0F	/* All possible flags. */
+
+/* UDP matching stuff */
+struct xt_udp {
+	__u16 spts[2];			/* Source port range. */
+	__u16 dpts[2];			/* Destination port range. */
+	__u8 invflags;			/* Inverse flags */
+};
+
+/* Values for "invflags" field in struct ipt_udp. */
+#define XT_UDP_INV_SRCPT	0x01	/* Invert the sense of source ports. */
+#define XT_UDP_INV_DSTPT	0x02	/* Invert the sense of dest ports. */
+#define XT_UDP_INV_MASK	0x03	/* All possible flags. */
+
+
+#endif
diff --git a/include/linux/netfilter/xt_time.h b/include/linux/netfilter/xt_time.h
new file mode 100644
index 0000000..a21d5bf
--- /dev/null
+++ b/include/linux/netfilter/xt_time.h
@@ -0,0 +1,28 @@
+#ifndef _XT_TIME_H
+#define _XT_TIME_H 1
+
+#include <linux/types.h>
+
+struct xt_time_info {
+	__u32 date_start;
+	__u32 date_stop;
+	__u32 daytime_start;
+	__u32 daytime_stop;
+	__u32 monthdays_match;
+	__u8 weekdays_match;
+	__u8 flags;
+};
+
+enum {
+	/* Match against local time (instead of UTC) */
+	XT_TIME_LOCAL_TZ = 1 << 0,
+	XT_TIME_CONTIGUOUS = 1 << 1,
+
+	/* Shortcuts */
+	XT_TIME_ALL_MONTHDAYS = 0xFFFFFFFE,
+	XT_TIME_ALL_WEEKDAYS  = 0xFE,
+	XT_TIME_MIN_DAYTIME   = 0,
+	XT_TIME_MAX_DAYTIME   = 24 * 60 * 60 - 1,
+};
+
+#endif /* _XT_TIME_H */
diff --git a/include/linux/netfilter/xt_u32.h b/include/linux/netfilter/xt_u32.h
new file mode 100644
index 0000000..04d1bfe
--- /dev/null
+++ b/include/linux/netfilter/xt_u32.h
@@ -0,0 +1,42 @@
+#ifndef _XT_U32_H
+#define _XT_U32_H 1
+
+#include <linux/types.h>
+
+enum xt_u32_ops {
+	XT_U32_AND,
+	XT_U32_LEFTSH,
+	XT_U32_RIGHTSH,
+	XT_U32_AT,
+};
+
+struct xt_u32_location_element {
+	__u32 number;
+	__u8 nextop;
+};
+
+struct xt_u32_value_element {
+	__u32 min;
+	__u32 max;
+};
+
+/*
+ * Any way to allow for an arbitrary number of elements?
+ * For now, I settle with a limit of 10 each.
+ */
+#define XT_U32_MAXSIZE 10
+
+struct xt_u32_test {
+	struct xt_u32_location_element location[XT_U32_MAXSIZE+1];
+	struct xt_u32_value_element value[XT_U32_MAXSIZE+1];
+	__u8 nnums;
+	__u8 nvalues;
+};
+
+struct xt_u32 {
+	struct xt_u32_test tests[XT_U32_MAXSIZE+1];
+	__u8 ntests;
+	__u8 invert;
+};
+
+#endif /* _XT_U32_H */
diff --git a/include/linux/netfilter_arp.h b/include/linux/netfilter_arp.h
new file mode 100644
index 0000000..92bc6dd
--- /dev/null
+++ b/include/linux/netfilter_arp.h
@@ -0,0 +1,19 @@
+#ifndef __LINUX_ARP_NETFILTER_H
+#define __LINUX_ARP_NETFILTER_H
+
+/* ARP-specific defines for netfilter.
+ * (C)2002 Rusty Russell IBM -- This code is GPL.
+ */
+
+#include <linux/netfilter.h>
+
+/* There is no PF_ARP. */
+#define NF_ARP		0
+
+/* ARP Hooks */
+#define NF_ARP_IN	0
+#define NF_ARP_OUT	1
+#define NF_ARP_FORWARD	2
+#define NF_ARP_NUMHOOKS	3
+
+#endif /* __LINUX_ARP_NETFILTER_H */
diff --git a/include/linux/netfilter_arp/arp_tables.h b/include/linux/netfilter_arp/arp_tables.h
new file mode 100644
index 0000000..bb1ec64
--- /dev/null
+++ b/include/linux/netfilter_arp/arp_tables.h
@@ -0,0 +1,204 @@
+/*
+ * 	Format of an ARP firewall descriptor
+ *
+ * 	src, tgt, src_mask, tgt_mask, arpop, arpop_mask are always stored in
+ *	network byte order.
+ * 	flags are stored in host byte order (of course).
+ */
+
+#ifndef _ARPTABLES_H
+#define _ARPTABLES_H
+
+#include <linux/types.h>
+
+#include <linux/netfilter_arp.h>
+
+#include <linux/netfilter/x_tables.h>
+
+#define ARPT_FUNCTION_MAXNAMELEN XT_FUNCTION_MAXNAMELEN
+#define ARPT_TABLE_MAXNAMELEN XT_TABLE_MAXNAMELEN
+#define arpt_entry_target xt_entry_target
+#define arpt_standard_target xt_standard_target
+#define arpt_error_target xt_error_target
+#define ARPT_CONTINUE XT_CONTINUE
+#define ARPT_RETURN XT_RETURN
+#define arpt_counters_info xt_counters_info
+#define arpt_counters xt_counters
+#define ARPT_STANDARD_TARGET XT_STANDARD_TARGET
+#define ARPT_ERROR_TARGET XT_ERROR_TARGET
+#define ARPT_ENTRY_ITERATE(entries, size, fn, args...) \
+	XT_ENTRY_ITERATE(struct arpt_entry, entries, size, fn, ## args)
+
+#define ARPT_DEV_ADDR_LEN_MAX 16
+
+struct arpt_devaddr_info {
+	char addr[ARPT_DEV_ADDR_LEN_MAX];
+	char mask[ARPT_DEV_ADDR_LEN_MAX];
+};
+
+/* Yes, Virginia, you have to zero the padding. */
+struct arpt_arp {
+	/* Source and target IP addr */
+	struct in_addr src, tgt;
+	/* Mask for src and target IP addr */
+	struct in_addr smsk, tmsk;
+
+	/* Device hw address length, src+target device addresses */
+	__u8 arhln, arhln_mask;
+	struct arpt_devaddr_info src_devaddr;
+	struct arpt_devaddr_info tgt_devaddr;
+
+	/* ARP operation code. */
+	__be16 arpop, arpop_mask;
+
+	/* ARP hardware address and protocol address format. */
+	__be16 arhrd, arhrd_mask;
+	__be16 arpro, arpro_mask;
+
+	/* The protocol address length is only accepted if it is 4
+	 * so there is no use in offering a way to do filtering on it.
+	 */
+
+	char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+
+	/* Flags word */
+	__u8 flags;
+	/* Inverse flags */
+	__u16 invflags;
+};
+
+/* Values for "flag" field in struct arpt_ip (general arp structure).
+ * No flags defined yet.
+ */
+#define ARPT_F_MASK		0x00	/* All possible flag bits mask. */
+
+/* Values for "inv" field in struct arpt_arp. */
+#define ARPT_INV_VIA_IN		0x0001	/* Invert the sense of IN IFACE. */
+#define ARPT_INV_VIA_OUT	0x0002	/* Invert the sense of OUT IFACE */
+#define ARPT_INV_SRCIP		0x0004	/* Invert the sense of SRC IP. */
+#define ARPT_INV_TGTIP		0x0008	/* Invert the sense of TGT IP. */
+#define ARPT_INV_SRCDEVADDR	0x0010	/* Invert the sense of SRC DEV ADDR. */
+#define ARPT_INV_TGTDEVADDR	0x0020	/* Invert the sense of TGT DEV ADDR. */
+#define ARPT_INV_ARPOP		0x0040	/* Invert the sense of ARP OP. */
+#define ARPT_INV_ARPHRD		0x0080	/* Invert the sense of ARP HRD. */
+#define ARPT_INV_ARPPRO		0x0100	/* Invert the sense of ARP PRO. */
+#define ARPT_INV_ARPHLN		0x0200	/* Invert the sense of ARP HLN. */
+#define ARPT_INV_MASK		0x03FF	/* All possible flag bits mask. */
+
+/* This structure defines each of the firewall rules.  Consists of 3
+   parts which are 1) general ARP header stuff 2) match specific
+   stuff 3) the target to perform if the rule matches */
+struct arpt_entry
+{
+	struct arpt_arp arp;
+
+	/* Size of arpt_entry + matches */
+	__u16 target_offset;
+	/* Size of arpt_entry + matches + target */
+	__u16 next_offset;
+
+	/* Back pointer */
+	unsigned int comefrom;
+
+	/* Packet and byte counters. */
+	struct xt_counters counters;
+
+	/* The matches (if any), then the target. */
+	unsigned char elems[0];
+};
+
+/*
+ * New IP firewall options for [gs]etsockopt at the RAW IP level.
+ * Unlike BSD Linux inherits IP options so you don't have to use a raw
+ * socket for this. Instead we check rights in the calls.
+ *
+ * ATTENTION: check linux/in.h before adding new number here.
+ */
+#define ARPT_BASE_CTL		96
+
+#define ARPT_SO_SET_REPLACE		(ARPT_BASE_CTL)
+#define ARPT_SO_SET_ADD_COUNTERS	(ARPT_BASE_CTL + 1)
+#define ARPT_SO_SET_MAX			ARPT_SO_SET_ADD_COUNTERS
+
+#define ARPT_SO_GET_INFO		(ARPT_BASE_CTL)
+#define ARPT_SO_GET_ENTRIES		(ARPT_BASE_CTL + 1)
+/* #define ARPT_SO_GET_REVISION_MATCH	(APRT_BASE_CTL + 2) */
+#define ARPT_SO_GET_REVISION_TARGET	(ARPT_BASE_CTL + 3)
+#define ARPT_SO_GET_MAX			(ARPT_SO_GET_REVISION_TARGET)
+
+/* The argument to ARPT_SO_GET_INFO */
+struct arpt_getinfo {
+	/* Which table: caller fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Kernel fills these in. */
+	/* Which hook entry points are valid: bitmask */
+	unsigned int valid_hooks;
+
+	/* Hook entry points: one per netfilter hook. */
+	unsigned int hook_entry[NF_ARP_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_ARP_NUMHOOKS];
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Size of entries. */
+	unsigned int size;
+};
+
+/* The argument to ARPT_SO_SET_REPLACE. */
+struct arpt_replace {
+	/* Which table. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Which hook entry points are valid: bitmask.  You can't
+           change this. */
+	unsigned int valid_hooks;
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Total size of new entries */
+	unsigned int size;
+
+	/* Hook entry points. */
+	unsigned int hook_entry[NF_ARP_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_ARP_NUMHOOKS];
+
+	/* Information about old entries: */
+	/* Number of counters (must be equal to current number of entries). */
+	unsigned int num_counters;
+	/* The old entries' counters. */
+	struct xt_counters *counters;
+
+	/* The entries (hang off end: not really an array). */
+	struct arpt_entry entries[0];
+};
+
+/* The argument to ARPT_SO_GET_ENTRIES. */
+struct arpt_get_entries {
+	/* Which table: user fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* User fills this in: total entry size. */
+	unsigned int size;
+
+	/* The entries. */
+	struct arpt_entry entrytable[0];
+};
+
+/* Helper functions */
+static __inline__ struct xt_entry_target *arpt_get_target(struct arpt_entry *e)
+{
+	return (void *)e + e->target_offset;
+}
+
+/*
+ *	Main firewall chains definitions and global var's definitions.
+ */
+#endif /* _ARPTABLES_H */
diff --git a/include/linux/netfilter_arp/arpt_mangle.h b/include/linux/netfilter_arp/arpt_mangle.h
new file mode 100644
index 0000000..250f502
--- /dev/null
+++ b/include/linux/netfilter_arp/arpt_mangle.h
@@ -0,0 +1,26 @@
+#ifndef _ARPT_MANGLE_H
+#define _ARPT_MANGLE_H
+#include <linux/netfilter_arp/arp_tables.h>
+
+#define ARPT_MANGLE_ADDR_LEN_MAX sizeof(struct in_addr)
+struct arpt_mangle
+{
+	char src_devaddr[ARPT_DEV_ADDR_LEN_MAX];
+	char tgt_devaddr[ARPT_DEV_ADDR_LEN_MAX];
+	union {
+		struct in_addr src_ip;
+	} u_s;
+	union {
+		struct in_addr tgt_ip;
+	} u_t;
+	u_int8_t flags;
+	int target;
+};
+
+#define ARPT_MANGLE_SDEV 0x01
+#define ARPT_MANGLE_TDEV 0x02
+#define ARPT_MANGLE_SIP 0x04
+#define ARPT_MANGLE_TIP 0x08
+#define ARPT_MANGLE_MASK 0x0f
+
+#endif /* _ARPT_MANGLE_H */
diff --git a/include/linux/netfilter_bridge.h b/include/linux/netfilter_bridge.h
new file mode 100644
index 0000000..71e9299
--- /dev/null
+++ b/include/linux/netfilter_bridge.h
@@ -0,0 +1,33 @@
+#ifndef __LINUX_BRIDGE_NETFILTER_H
+#define __LINUX_BRIDGE_NETFILTER_H
+
+/* bridge-specific defines for netfilter. 
+ */
+#include <limits.h>
+
+/* Bridge Hooks */
+/* After promisc drops, checksum checks. */
+#define NF_BR_PRE_ROUTING	0
+/* If the packet is destined for this box. */
+#define NF_BR_LOCAL_IN		1
+/* If the packet is destined for another interface. */
+#define NF_BR_FORWARD		2
+/* Packets coming from a local process. */
+#define NF_BR_LOCAL_OUT		3
+/* Packets about to hit the wire. */
+#define NF_BR_POST_ROUTING	4
+/* Not really a hook, but used for the ebtables broute table */
+#define NF_BR_BROUTING		5
+#define NF_BR_NUMHOOKS		6
+
+enum nf_br_hook_priorities {
+	NF_BR_PRI_FIRST = INT_MIN,
+	NF_BR_PRI_FILTER_BRIDGED = -200,
+	NF_BR_PRI_FILTER_OTHER = 200,
+	NF_BR_PRI_NAT_DST_BRIDGED = -300,
+	NF_BR_PRI_NAT_DST_OTHER = 100,
+	NF_BR_PRI_NAT_SRC = 300,
+	NF_BR_PRI_LAST = INT_MAX,
+};
+
+#endif
diff --git a/include/linux/netfilter_bridge/ebt_802_3.h b/include/linux/netfilter_bridge/ebt_802_3.h
new file mode 100644
index 0000000..f37522a
--- /dev/null
+++ b/include/linux/netfilter_bridge/ebt_802_3.h
@@ -0,0 +1,63 @@
+#ifndef _UAPI__LINUX_BRIDGE_EBT_802_3_H
+#define _UAPI__LINUX_BRIDGE_EBT_802_3_H
+
+#include <linux/types.h>
+#include <linux/if_ether.h>
+
+#define EBT_802_3_SAP 0x01
+#define EBT_802_3_TYPE 0x02
+
+#define EBT_802_3_MATCH "802_3"
+
+/*
+ * If frame has DSAP/SSAP value 0xaa you must check the SNAP type
+ * to discover what kind of packet we're carrying. 
+ */
+#define CHECK_TYPE 0xaa
+
+/*
+ * Control field may be one or two bytes.  If the first byte has
+ * the value 0x03 then the entire length is one byte, otherwise it is two.
+ * One byte controls are used in Unnumbered Information frames.
+ * Two byte controls are used in Numbered Information frames.
+ */
+#define IS_UI 0x03
+
+#define EBT_802_3_MASK (EBT_802_3_SAP | EBT_802_3_TYPE | EBT_802_3)
+
+/* ui has one byte ctrl, ni has two */
+struct hdr_ui {
+	__u8 dsap;
+	__u8 ssap;
+	__u8 ctrl;
+	__u8 orig[3];
+	__be16 type;
+};
+
+struct hdr_ni {
+	__u8 dsap;
+	__u8 ssap;
+	__be16 ctrl;
+	__u8  orig[3];
+	__be16 type;
+};
+
+struct ebt_802_3_hdr {
+	__u8  daddr[ETH_ALEN];
+	__u8  saddr[ETH_ALEN];
+	__be16 len;
+	union {
+		struct hdr_ui ui;
+		struct hdr_ni ni;
+	} llc;
+};
+
+
+struct ebt_802_3_info {
+	__u8  sap;
+	__be16 type;
+	__u8  bitmask;
+	__u8  invflags;
+};
+
+#endif /* _UAPI__LINUX_BRIDGE_EBT_802_3_H */
diff --git a/include/linux/netfilter_bridge/ebt_ip.h b/include/linux/netfilter_bridge/ebt_ip.h
new file mode 100644
index 0000000..c4bbc41
--- /dev/null
+++ b/include/linux/netfilter_bridge/ebt_ip.h
@@ -0,0 +1,44 @@
+/*
+ *  ebt_ip
+ *
+ *	Authors:
+ *	Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ *  April, 2002
+ *
+ *  Changes:
+ *    added ip-sport and ip-dport
+ *    Innominate Security Technologies AG <mhopf@innominate.com>
+ *    September, 2002
+ */
+
+#ifndef __LINUX_BRIDGE_EBT_IP_H
+#define __LINUX_BRIDGE_EBT_IP_H
+
+#include <linux/types.h>
+
+#define EBT_IP_SOURCE 0x01
+#define EBT_IP_DEST 0x02
+#define EBT_IP_TOS 0x04
+#define EBT_IP_PROTO 0x08
+#define EBT_IP_SPORT 0x10
+#define EBT_IP_DPORT 0x20
+#define EBT_IP_MASK (EBT_IP_SOURCE | EBT_IP_DEST | EBT_IP_TOS | EBT_IP_PROTO |\
+ EBT_IP_SPORT | EBT_IP_DPORT )
+#define EBT_IP_MATCH "ip"
+
+/* the same values are used for the invflags */
+struct ebt_ip_info {
+	__be32 saddr;
+	__be32 daddr;
+	__be32 smsk;
+	__be32 dmsk;
+	__u8  tos;
+	__u8  protocol;
+	__u8  bitmask;
+	__u8  invflags;
+	__u16 sport[2];
+	__u16 dport[2];
+};
+
+#endif
diff --git a/include/linux/netfilter_bridge/ebt_mark_m.h b/include/linux/netfilter_bridge/ebt_mark_m.h
new file mode 100644
index 0000000..410f9e5
--- /dev/null
+++ b/include/linux/netfilter_bridge/ebt_mark_m.h
@@ -0,0 +1,16 @@
+#ifndef __LINUX_BRIDGE_EBT_MARK_M_H
+#define __LINUX_BRIDGE_EBT_MARK_M_H
+
+#include <linux/types.h>
+
+#define EBT_MARK_AND 0x01
+#define EBT_MARK_OR 0x02
+#define EBT_MARK_MASK (EBT_MARK_AND | EBT_MARK_OR)
+struct ebt_mark_m_info {
+	unsigned long mark, mask;
+	__u8 invert;
+	__u8 bitmask;
+};
+#define EBT_MARK_MATCH "mark_m"
+
+#endif
diff --git a/include/linux/netfilter_bridge/ebt_mark_t.h b/include/linux/netfilter_bridge/ebt_mark_t.h
new file mode 100644
index 0000000..7d5a268
--- /dev/null
+++ b/include/linux/netfilter_bridge/ebt_mark_t.h
@@ -0,0 +1,23 @@
+#ifndef __LINUX_BRIDGE_EBT_MARK_T_H
+#define __LINUX_BRIDGE_EBT_MARK_T_H
+
+/* The target member is reused for adding new actions, the
+ * value of the real target is -1 to -NUM_STANDARD_TARGETS.
+ * For backward compatibility, the 4 lsb (2 would be enough,
+ * but let's play it safe) are kept to designate this target.
+ * The remaining bits designate the action. By making the set
+ * action 0xfffffff0, the result will look ok for older
+ * versions. [September 2006] */
+#define MARK_SET_VALUE (0xfffffff0)
+#define MARK_OR_VALUE  (0xffffffe0)
+#define MARK_AND_VALUE (0xffffffd0)
+#define MARK_XOR_VALUE (0xffffffc0)
+
+struct ebt_mark_t_info {
+	unsigned long mark;
+	/* EBT_ACCEPT, EBT_DROP, EBT_CONTINUE or EBT_RETURN */
+	int target;
+};
+#define EBT_MARK_TARGET "mark"
+
+#endif
diff --git a/include/linux/netfilter_ipv4.h b/include/linux/netfilter_ipv4.h
new file mode 100644
index 0000000..4d7ba3e
--- /dev/null
+++ b/include/linux/netfilter_ipv4.h
@@ -0,0 +1,75 @@
+#ifndef __LINUX_IP_NETFILTER_H
+#define __LINUX_IP_NETFILTER_H
+
+/* IPv4-specific defines for netfilter. 
+ * (C)1998 Rusty Russell -- This code is GPL.
+ */
+
+#include <linux/netfilter.h>
+
+/* only for userspace compatibility */
+/* IP Cache bits. */
+/* Src IP address. */
+#define NFC_IP_SRC		0x0001
+/* Dest IP address. */
+#define NFC_IP_DST		0x0002
+/* Input device. */
+#define NFC_IP_IF_IN		0x0004
+/* Output device. */
+#define NFC_IP_IF_OUT		0x0008
+/* TOS. */
+#define NFC_IP_TOS		0x0010
+/* Protocol. */
+#define NFC_IP_PROTO		0x0020
+/* IP options. */
+#define NFC_IP_OPTIONS		0x0040
+/* Frag & flags. */
+#define NFC_IP_FRAG		0x0080
+
+/* Per-protocol information: only matters if proto match. */
+/* TCP flags. */
+#define NFC_IP_TCPFLAGS		0x0100
+/* Source port. */
+#define NFC_IP_SRC_PT		0x0200
+/* Dest port. */
+#define NFC_IP_DST_PT		0x0400
+/* Something else about the proto */
+#define NFC_IP_PROTO_UNKNOWN	0x2000
+
+/* IP Hooks */
+/* After promisc drops, checksum checks. */
+#define NF_IP_PRE_ROUTING	0
+/* If the packet is destined for this box. */
+#define NF_IP_LOCAL_IN		1
+/* If the packet is destined for another interface. */
+#define NF_IP_FORWARD		2
+/* Packets coming from a local process. */
+#define NF_IP_LOCAL_OUT		3
+/* Packets about to hit the wire. */
+#define NF_IP_POST_ROUTING	4
+#define NF_IP_NUMHOOKS		5
+
+enum nf_ip_hook_priorities {
+	NF_IP_PRI_FIRST = INT_MIN,
+	NF_IP_PRI_CONNTRACK_DEFRAG = -400,
+	NF_IP_PRI_RAW = -300,
+	NF_IP_PRI_SELINUX_FIRST = -225,
+	NF_IP_PRI_CONNTRACK = -200,
+	NF_IP_PRI_MANGLE = -150,
+	NF_IP_PRI_NAT_DST = -100,
+	NF_IP_PRI_FILTER = 0,
+	NF_IP_PRI_SECURITY = 50,
+	NF_IP_PRI_NAT_SRC = 100,
+	NF_IP_PRI_SELINUX_LAST = 225,
+	NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
+	NF_IP_PRI_LAST = INT_MAX,
+};
+
+/* Arguments for setsockopt SOL_IP: */
+/* 2.0 firewalling went from 64 through 71 (and +256, +512, etc). */
+/* 2.2 firewalling (+ masq) went from 64 through 76 */
+/* 2.4 firewalling went 64 through 67. */
+#define SO_ORIGINAL_DST 80
+
+
+#endif /*__LINUX_IP_NETFILTER_H*/
diff --git a/include/linux/netfilter_ipv4/ip_queue.h b/include/linux/netfilter_ipv4/ip_queue.h
new file mode 100644
index 0000000..a03507f
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ip_queue.h
@@ -0,0 +1,72 @@
+/*
+ * This is a module which is used for queueing IPv4 packets and
+ * communicating with userspace via netlink.
+ *
+ * (C) 2000 James Morris, this code is GPL.
+ */
+#ifndef _IP_QUEUE_H
+#define _IP_QUEUE_H
+
+#ifdef __KERNEL__
+#ifdef DEBUG_IPQ
+#define QDEBUG(x...) printk(KERN_DEBUG ## x)
+#else
+#define QDEBUG(x...)
+#endif  /* DEBUG_IPQ */
+#else
+#include <net/if.h>
+#endif	/* ! __KERNEL__ */
+
+/* Messages sent from kernel */
+typedef struct ipq_packet_msg {
+	unsigned long packet_id;	/* ID of queued packet */
+	unsigned long mark;		/* Netfilter mark value */
+	long timestamp_sec;		/* Packet arrival time (seconds) */
+	long timestamp_usec;		/* Packet arrvial time (+useconds) */
+	unsigned int hook;		/* Netfilter hook we rode in on */
+	char indev_name[IFNAMSIZ];	/* Name of incoming interface */
+	char outdev_name[IFNAMSIZ];	/* Name of outgoing interface */
+	__be16 hw_protocol;		/* Hardware protocol (network order) */
+	unsigned short hw_type;		/* Hardware type */
+	unsigned char hw_addrlen;	/* Hardware address length */
+	unsigned char hw_addr[8];	/* Hardware address */
+	size_t data_len;		/* Length of packet data */
+	unsigned char payload[0];	/* Optional packet data */
+} ipq_packet_msg_t;
+
+/* Messages sent from userspace */
+typedef struct ipq_mode_msg {
+	unsigned char value;		/* Requested mode */
+	size_t range;			/* Optional range of packet requested */
+} ipq_mode_msg_t;
+
+typedef struct ipq_verdict_msg {
+	unsigned int value;		/* Verdict to hand to netfilter */
+	unsigned long id;		/* Packet ID for this verdict */
+	size_t data_len;		/* Length of replacement data */
+	unsigned char payload[0];	/* Optional replacement packet */
+} ipq_verdict_msg_t;
+
+typedef struct ipq_peer_msg {
+	union {
+		ipq_verdict_msg_t verdict;
+		ipq_mode_msg_t mode;
+	} msg;
+} ipq_peer_msg_t;
+
+/* Packet delivery modes */
+enum {
+	IPQ_COPY_NONE,		/* Initial mode, packets are dropped */
+	IPQ_COPY_META,		/* Copy metadata */
+	IPQ_COPY_PACKET		/* Copy metadata + packet (range) */
+};
+#define IPQ_COPY_MAX IPQ_COPY_PACKET
+
+/* Types of messages */
+#define IPQM_BASE	0x10	/* standard netlink messages below this */
+#define IPQM_MODE	(IPQM_BASE + 1)		/* Mode request from peer */
+#define IPQM_VERDICT	(IPQM_BASE + 2)		/* Verdict from peer */ 
+#define IPQM_PACKET	(IPQM_BASE + 3)		/* Packet from kernel */
+#define IPQM_MAX	(IPQM_BASE + 4)
+
+#endif /*_IP_QUEUE_H*/
diff --git a/include/linux/netfilter_ipv4/ip_tables.h b/include/linux/netfilter_ipv4/ip_tables.h
new file mode 100644
index 0000000..38542b4
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ip_tables.h
@@ -0,0 +1,227 @@
+/*
+ * 25-Jul-1998 Major changes to allow for ip chain table
+ *
+ * 3-Jan-2000 Named tables to allow packet selection for different uses.
+ */
+
+/*
+ * 	Format of an IP firewall descriptor
+ *
+ * 	src, dst, src_mask, dst_mask are always stored in network byte order.
+ * 	flags are stored in host byte order (of course).
+ * 	Port numbers are stored in HOST byte order.
+ */
+
+#ifndef _IPTABLES_H
+#define _IPTABLES_H
+
+#include <linux/types.h>
+
+#include <linux/netfilter_ipv4.h>
+
+#include <linux/netfilter/x_tables.h>
+
+#define IPT_FUNCTION_MAXNAMELEN XT_FUNCTION_MAXNAMELEN
+#define IPT_TABLE_MAXNAMELEN XT_TABLE_MAXNAMELEN
+#define ipt_match xt_match
+#define ipt_target xt_target
+#define ipt_table xt_table
+#define ipt_get_revision xt_get_revision
+#define ipt_entry_match xt_entry_match
+#define ipt_entry_target xt_entry_target
+#define ipt_standard_target xt_standard_target
+#define ipt_error_target xt_error_target
+#define ipt_counters xt_counters
+#define IPT_CONTINUE XT_CONTINUE
+#define IPT_RETURN XT_RETURN
+
+/* This group is older than old (iptables < v1.4.0-rc1~89) */
+#include <linux/netfilter/xt_tcpudp.h>
+#define ipt_udp xt_udp
+#define ipt_tcp xt_tcp
+#define IPT_TCP_INV_SRCPT	XT_TCP_INV_SRCPT
+#define IPT_TCP_INV_DSTPT	XT_TCP_INV_DSTPT
+#define IPT_TCP_INV_FLAGS	XT_TCP_INV_FLAGS
+#define IPT_TCP_INV_OPTION	XT_TCP_INV_OPTION
+#define IPT_TCP_INV_MASK	XT_TCP_INV_MASK
+#define IPT_UDP_INV_SRCPT	XT_UDP_INV_SRCPT
+#define IPT_UDP_INV_DSTPT	XT_UDP_INV_DSTPT
+#define IPT_UDP_INV_MASK	XT_UDP_INV_MASK
+
+/* The argument to IPT_SO_ADD_COUNTERS. */
+#define ipt_counters_info xt_counters_info
+/* Standard return verdict, or do jump. */
+#define IPT_STANDARD_TARGET XT_STANDARD_TARGET
+/* Error verdict. */
+#define IPT_ERROR_TARGET XT_ERROR_TARGET
+
+/* fn returns 0 to continue iteration */
+#define IPT_MATCH_ITERATE(e, fn, args...) \
+	XT_MATCH_ITERATE(struct ipt_entry, e, fn, ## args)
+
+/* fn returns 0 to continue iteration */
+#define IPT_ENTRY_ITERATE(entries, size, fn, args...) \
+	XT_ENTRY_ITERATE(struct ipt_entry, entries, size, fn, ## args)
+
+/* Yes, Virginia, you have to zero the padding. */
+struct ipt_ip {
+	/* Source and destination IP addr */
+	struct in_addr src, dst;
+	/* Mask for src and dest IP addr */
+	struct in_addr smsk, dmsk;
+	char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+
+	/* Protocol, 0 = ANY */
+	__u16 proto;
+
+	/* Flags word */
+	__u8 flags;
+	/* Inverse flags */
+	__u8 invflags;
+};
+
+/* Values for "flag" field in struct ipt_ip (general ip structure). */
+#define IPT_F_FRAG		0x01	/* Set if rule is a fragment rule */
+#define IPT_F_GOTO		0x02	/* Set if jump is a goto */
+#define IPT_F_MASK		0x03	/* All possible flag bits mask. */
+
+/* Values for "inv" field in struct ipt_ip. */
+#define IPT_INV_VIA_IN		0x01	/* Invert the sense of IN IFACE. */
+#define IPT_INV_VIA_OUT		0x02	/* Invert the sense of OUT IFACE */
+#define IPT_INV_TOS		0x04	/* Invert the sense of TOS. */
+#define IPT_INV_SRCIP		0x08	/* Invert the sense of SRC IP. */
+#define IPT_INV_DSTIP		0x10	/* Invert the sense of DST OP. */
+#define IPT_INV_FRAG		0x20	/* Invert the sense of FRAG. */
+#define IPT_INV_PROTO		XT_INV_PROTO
+#define IPT_INV_MASK		0x7F	/* All possible flag bits mask. */
+
+/* This structure defines each of the firewall rules.  Consists of 3
+   parts which are 1) general IP header stuff 2) match specific
+   stuff 3) the target to perform if the rule matches */
+struct ipt_entry {
+	struct ipt_ip ip;
+
+	/* Mark with fields that we care about. */
+	unsigned int nfcache;
+
+	/* Size of ipt_entry + matches */
+	__u16 target_offset;
+	/* Size of ipt_entry + matches + target */
+	__u16 next_offset;
+
+	/* Back pointer */
+	unsigned int comefrom;
+
+	/* Packet and byte counters. */
+	struct xt_counters counters;
+
+	/* The matches (if any), then the target. */
+	unsigned char elems[0];
+};
+
+/*
+ * New IP firewall options for [gs]etsockopt at the RAW IP level.
+ * Unlike BSD Linux inherits IP options so you don't have to use a raw
+ * socket for this. Instead we check rights in the calls.
+ *
+ * ATTENTION: check linux/in.h before adding new number here.
+ */
+#define IPT_BASE_CTL		64
+
+#define IPT_SO_SET_REPLACE	(IPT_BASE_CTL)
+#define IPT_SO_SET_ADD_COUNTERS	(IPT_BASE_CTL + 1)
+#define IPT_SO_SET_MAX		IPT_SO_SET_ADD_COUNTERS
+
+#define IPT_SO_GET_INFO			(IPT_BASE_CTL)
+#define IPT_SO_GET_ENTRIES		(IPT_BASE_CTL + 1)
+#define IPT_SO_GET_REVISION_MATCH	(IPT_BASE_CTL + 2)
+#define IPT_SO_GET_REVISION_TARGET	(IPT_BASE_CTL + 3)
+#define IPT_SO_GET_MAX			IPT_SO_GET_REVISION_TARGET
+
+/* ICMP matching stuff */
+struct ipt_icmp {
+	__u8 type;				/* type to match */
+	__u8 code[2];				/* range of code */
+	__u8 invflags;				/* Inverse flags */
+};
+
+/* Values for "inv" field for struct ipt_icmp. */
+#define IPT_ICMP_INV	0x01	/* Invert the sense of type/code test */
+
+/* The argument to IPT_SO_GET_INFO */
+struct ipt_getinfo {
+	/* Which table: caller fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Kernel fills these in. */
+	/* Which hook entry points are valid: bitmask */
+	unsigned int valid_hooks;
+
+	/* Hook entry points: one per netfilter hook. */
+	unsigned int hook_entry[NF_INET_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_INET_NUMHOOKS];
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Size of entries. */
+	unsigned int size;
+};
+
+/* The argument to IPT_SO_SET_REPLACE. */
+struct ipt_replace {
+	/* Which table. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Which hook entry points are valid: bitmask.  You can't
+           change this. */
+	unsigned int valid_hooks;
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Total size of new entries */
+	unsigned int size;
+
+	/* Hook entry points. */
+	unsigned int hook_entry[NF_INET_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_INET_NUMHOOKS];
+
+	/* Information about old entries: */
+	/* Number of counters (must be equal to current number of entries). */
+	unsigned int num_counters;
+	/* The old entries' counters. */
+	struct xt_counters *counters;
+
+	/* The entries (hang off end: not really an array). */
+	struct ipt_entry entries[0];
+};
+
+/* The argument to IPT_SO_GET_ENTRIES. */
+struct ipt_get_entries {
+	/* Which table: user fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* User fills this in: total entry size. */
+	unsigned int size;
+
+	/* The entries. */
+	struct ipt_entry entrytable[0];
+};
+
+/* Helper functions */
+static __inline__ struct xt_entry_target *
+ipt_get_target(struct ipt_entry *e)
+{
+	return (void *)e + e->target_offset;
+}
+
+/*
+ *	Main firewall chains definitions and global var's definitions.
+ */
+#endif /* _IPTABLES_H */
diff --git a/include/linux/netfilter_ipv4/ipt_CLUSTERIP.h b/include/linux/netfilter_ipv4/ipt_CLUSTERIP.h
new file mode 100644
index 0000000..c6a204c
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ipt_CLUSTERIP.h
@@ -0,0 +1,36 @@
+#ifndef _IPT_CLUSTERIP_H_target
+#define _IPT_CLUSTERIP_H_target
+
+#include <linux/types.h>
+
+enum clusterip_hashmode {
+    CLUSTERIP_HASHMODE_SIP = 0,
+    CLUSTERIP_HASHMODE_SIP_SPT,
+    CLUSTERIP_HASHMODE_SIP_SPT_DPT,
+};
+
+#define CLUSTERIP_HASHMODE_MAX CLUSTERIP_HASHMODE_SIP_SPT_DPT
+
+#define CLUSTERIP_MAX_NODES 16
+
+#define CLUSTERIP_FLAG_NEW 0x00000001
+
+struct clusterip_config;
+
+struct ipt_clusterip_tgt_info {
+
+	__u32 flags;
+
+	/* only relevant for new ones */
+	__u8 clustermac[6];
+	__u16 num_total_nodes;
+	__u16 num_local_nodes;
+	__u16 local_nodes[CLUSTERIP_MAX_NODES];
+	__u32 hash_mode;
+	__u32 hash_initval;
+
+	/* Used internally by the kernel */
+	struct clusterip_config *config;
+};
+
+#endif /*_IPT_CLUSTERIP_H_target*/
diff --git a/include/linux/netfilter_ipv4/ipt_ECN.h b/include/linux/netfilter_ipv4/ipt_ECN.h
new file mode 100644
index 0000000..bb88d53
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ipt_ECN.h
@@ -0,0 +1,33 @@
+/* Header file for iptables ipt_ECN target
+ *
+ * (C) 2002 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This software is distributed under GNU GPL v2, 1991
+ * 
+ * ipt_ECN.h,v 1.3 2002/05/29 12:17:40 laforge Exp
+*/
+#ifndef _IPT_ECN_TARGET_H
+#define _IPT_ECN_TARGET_H
+
+#include <linux/types.h>
+#include <linux/netfilter/xt_DSCP.h>
+
+#define IPT_ECN_IP_MASK	(~XT_DSCP_MASK)
+
+#define IPT_ECN_OP_SET_IP	0x01	/* set ECN bits of IPv4 header */
+#define IPT_ECN_OP_SET_ECE	0x10	/* set ECE bit of TCP header */
+#define IPT_ECN_OP_SET_CWR	0x20	/* set CWR bit of TCP header */
+
+#define IPT_ECN_OP_MASK		0xce
+
+struct ipt_ECN_info {
+	__u8 operation;	/* bitset of operations */
+	__u8 ip_ect;	/* ECT codepoint of IPv4 header, pre-shifted */
+	union {
+		struct {
+			__u8 ece:1, cwr:1; /* TCP ECT bits */
+		} tcp;
+	} proto;
+};
+
+#endif /* _IPT_ECN_TARGET_H */
diff --git a/include/linux/netfilter_ipv4/ipt_LOG.h b/include/linux/netfilter_ipv4/ipt_LOG.h
new file mode 100644
index 0000000..dcdbadf
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ipt_LOG.h
@@ -0,0 +1,19 @@
+#ifndef _IPT_LOG_H
+#define _IPT_LOG_H
+
+/* make sure not to change this without changing netfilter.h:NF_LOG_* (!) */
+#define IPT_LOG_TCPSEQ		0x01	/* Log TCP sequence numbers */
+#define IPT_LOG_TCPOPT		0x02	/* Log TCP options */
+#define IPT_LOG_IPOPT		0x04	/* Log IP options */
+#define IPT_LOG_UID		0x08	/* Log UID owning local socket */
+#define IPT_LOG_NFLOG		0x10	/* Unsupported, don't reuse */
+#define IPT_LOG_MACDECODE	0x20	/* Decode MAC header */
+#define IPT_LOG_MASK		0x2f
+
+struct ipt_log_info {
+	unsigned char level;
+	unsigned char logflags;
+	char prefix[30];
+};
+
+#endif /*_IPT_LOG_H*/
diff --git a/include/linux/netfilter_ipv4/ipt_REJECT.h b/include/linux/netfilter_ipv4/ipt_REJECT.h
new file mode 100644
index 0000000..4293a1a
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ipt_REJECT.h
@@ -0,0 +1,20 @@
+#ifndef _IPT_REJECT_H
+#define _IPT_REJECT_H
+
+enum ipt_reject_with {
+	IPT_ICMP_NET_UNREACHABLE,
+	IPT_ICMP_HOST_UNREACHABLE,
+	IPT_ICMP_PROT_UNREACHABLE,
+	IPT_ICMP_PORT_UNREACHABLE,
+	IPT_ICMP_ECHOREPLY,
+	IPT_ICMP_NET_PROHIBITED,
+	IPT_ICMP_HOST_PROHIBITED,
+	IPT_TCP_RESET,
+	IPT_ICMP_ADMIN_PROHIBITED
+};
+
+struct ipt_reject_info {
+	enum ipt_reject_with with;      /* reject type */
+};
+
+#endif /*_IPT_REJECT_H*/
diff --git a/include/linux/netfilter_ipv4/ipt_TTL.h b/include/linux/netfilter_ipv4/ipt_TTL.h
new file mode 100644
index 0000000..f6ac169
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ipt_TTL.h
@@ -0,0 +1,23 @@
+/* TTL modification module for IP tables
+ * (C) 2000 by Harald Welte <laforge@netfilter.org> */
+
+#ifndef _IPT_TTL_H
+#define _IPT_TTL_H
+
+#include <linux/types.h>
+
+enum {
+	IPT_TTL_SET = 0,
+	IPT_TTL_INC,
+	IPT_TTL_DEC
+};
+
+#define IPT_TTL_MAXMODE	IPT_TTL_DEC
+
+struct ipt_TTL_info {
+	__u8	mode;
+	__u8	ttl;
+};
+
+
+#endif
diff --git a/include/linux/netfilter_ipv4/ipt_ULOG.h b/include/linux/netfilter_ipv4/ipt_ULOG.h
new file mode 100644
index 0000000..417aad2
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ipt_ULOG.h
@@ -0,0 +1,49 @@
+/* Header file for IP tables userspace logging, Version 1.8
+ *
+ * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
+ * 
+ * Distributed under the terms of GNU GPL */
+
+#ifndef _IPT_ULOG_H
+#define _IPT_ULOG_H
+
+#ifndef NETLINK_NFLOG
+#define NETLINK_NFLOG 	5
+#endif
+
+#define ULOG_DEFAULT_NLGROUP	1
+#define ULOG_DEFAULT_QTHRESHOLD	1
+
+#define ULOG_MAC_LEN	80
+#define ULOG_PREFIX_LEN	32
+
+#define ULOG_MAX_QLEN	50
+/* Why 50? Well... there is a limit imposed by the slab cache 131000
+ * bytes. So the multipart netlink-message has to be < 131000 bytes.
+ * Assuming a standard ethernet-mtu of 1500, we could define this up
+ * to 80... but even 50 seems to be big enough. */
+
+/* private data structure for each rule with a ULOG target */
+struct ipt_ulog_info {
+	unsigned int nl_group;
+	size_t copy_range;
+	size_t qthreshold;
+	char prefix[ULOG_PREFIX_LEN];
+};
+
+/* Format of the ULOG packets passed through netlink */
+typedef struct ulog_packet_msg {
+	unsigned long mark;
+	long timestamp_sec;
+	long timestamp_usec;
+	unsigned int hook;
+	char indev_name[IFNAMSIZ];
+	char outdev_name[IFNAMSIZ];
+	size_t data_len;
+	char prefix[ULOG_PREFIX_LEN];
+	unsigned char mac_len;
+	unsigned char mac[ULOG_MAC_LEN];
+	unsigned char payload[0];
+} ulog_packet_msg_t;
+
+#endif /*_IPT_ULOG_H*/
diff --git a/include/linux/netfilter_ipv4/ipt_addrtype.h b/include/linux/netfilter_ipv4/ipt_addrtype.h
new file mode 100644
index 0000000..0da4223
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ipt_addrtype.h
@@ -0,0 +1,27 @@
+#ifndef _IPT_ADDRTYPE_H
+#define _IPT_ADDRTYPE_H
+
+#include <linux/types.h>
+
+enum {
+	IPT_ADDRTYPE_INVERT_SOURCE	= 0x0001,
+	IPT_ADDRTYPE_INVERT_DEST	= 0x0002,
+	IPT_ADDRTYPE_LIMIT_IFACE_IN	= 0x0004,
+	IPT_ADDRTYPE_LIMIT_IFACE_OUT	= 0x0008,
+};
+
+struct ipt_addrtype_info_v1 {
+	__u16	source;		/* source-type mask */
+	__u16	dest;		/* dest-type mask */
+	__u32	flags;
+};
+
+/* revision 0 */
+struct ipt_addrtype_info {
+	__u16	source;		/* source-type mask */
+	__u16	dest;		/* dest-type mask */
+	__u32	invert_source;
+	__u32	invert_dest;
+};
+
+#endif
diff --git a/include/linux/netfilter_ipv4/ipt_ah.h b/include/linux/netfilter_ipv4/ipt_ah.h
new file mode 100644
index 0000000..4e02bb0
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ipt_ah.h
@@ -0,0 +1,17 @@
+#ifndef _IPT_AH_H
+#define _IPT_AH_H
+
+#include <linux/types.h>
+
+struct ipt_ah {
+	__u32 spis[2];			/* Security Parameter Index */
+	__u8  invflags;			/* Inverse flags */
+};
+
+
+
+/* Values for "invflags" field in struct ipt_ah. */
+#define IPT_AH_INV_SPI		0x01	/* Invert the sense of spi. */
+#define IPT_AH_INV_MASK	0x01	/* All possible flags. */
+
+#endif /*_IPT_AH_H*/
diff --git a/include/linux/netfilter_ipv4/ipt_realm.h b/include/linux/netfilter_ipv4/ipt_realm.h
new file mode 100644
index 0000000..b3996ea
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ipt_realm.h
@@ -0,0 +1,7 @@
+#ifndef _IPT_REALM_H
+#define _IPT_REALM_H
+
+#include <linux/netfilter/xt_realm.h>
+#define ipt_realm_info xt_realm_info
+
+#endif /* _IPT_REALM_H */
diff --git a/include/linux/netfilter_ipv4/ipt_ttl.h b/include/linux/netfilter_ipv4/ipt_ttl.h
new file mode 100644
index 0000000..37bee44
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ipt_ttl.h
@@ -0,0 +1,23 @@
+/* IP tables module for matching the value of the TTL
+ * (C) 2000 by Harald Welte <laforge@gnumonks.org> */
+
+#ifndef _IPT_TTL_H
+#define _IPT_TTL_H
+
+#include <linux/types.h>
+
+enum {
+	IPT_TTL_EQ = 0,		/* equals */
+	IPT_TTL_NE,		/* not equals */
+	IPT_TTL_LT,		/* less than */
+	IPT_TTL_GT,		/* greater than */
+};
+
+
+struct ipt_ttl_info {
+	__u8	mode;
+	__u8	ttl;
+};
+
+
+#endif
diff --git a/include/linux/netfilter_ipv6.h b/include/linux/netfilter_ipv6.h
new file mode 100644
index 0000000..f155b9d
--- /dev/null
+++ b/include/linux/netfilter_ipv6.h
@@ -0,0 +1,73 @@
+#ifndef __LINUX_IP6_NETFILTER_H
+#define __LINUX_IP6_NETFILTER_H
+
+/* IPv6-specific defines for netfilter. 
+ * (C)1998 Rusty Russell -- This code is GPL.
+ * (C)1999 David Jeffery
+ *   this header was blatantly ripped from netfilter_ipv4.h 
+ *   it's amazing what adding a bunch of 6s can do =8^)
+ */
+
+#include <linux/netfilter.h>
+
+/* only for userspace compatibility */
+/* IP Cache bits. */
+/* Src IP address. */
+#define NFC_IP6_SRC              0x0001
+/* Dest IP address. */
+#define NFC_IP6_DST              0x0002
+/* Input device. */
+#define NFC_IP6_IF_IN            0x0004
+/* Output device. */
+#define NFC_IP6_IF_OUT           0x0008
+/* TOS. */
+#define NFC_IP6_TOS              0x0010
+/* Protocol. */
+#define NFC_IP6_PROTO            0x0020
+/* IP options. */
+#define NFC_IP6_OPTIONS          0x0040
+/* Frag & flags. */
+#define NFC_IP6_FRAG             0x0080
+
+
+/* Per-protocol information: only matters if proto match. */
+/* TCP flags. */
+#define NFC_IP6_TCPFLAGS         0x0100
+/* Source port. */
+#define NFC_IP6_SRC_PT           0x0200
+/* Dest port. */
+#define NFC_IP6_DST_PT           0x0400
+/* Something else about the proto */
+#define NFC_IP6_PROTO_UNKNOWN    0x2000
+
+/* IP6 Hooks */
+/* After promisc drops, checksum checks. */
+#define NF_IP6_PRE_ROUTING	0
+/* If the packet is destined for this box. */
+#define NF_IP6_LOCAL_IN		1
+/* If the packet is destined for another interface. */
+#define NF_IP6_FORWARD		2
+/* Packets coming from a local process. */
+#define NF_IP6_LOCAL_OUT		3
+/* Packets about to hit the wire. */
+#define NF_IP6_POST_ROUTING	4
+#define NF_IP6_NUMHOOKS		5
+
+
+enum nf_ip6_hook_priorities {
+	NF_IP6_PRI_FIRST = INT_MIN,
+	NF_IP6_PRI_CONNTRACK_DEFRAG = -400,
+	NF_IP6_PRI_RAW = -300,
+	NF_IP6_PRI_SELINUX_FIRST = -225,
+	NF_IP6_PRI_CONNTRACK = -200,
+	NF_IP6_PRI_MANGLE = -150,
+	NF_IP6_PRI_NAT_DST = -100,
+	NF_IP6_PRI_FILTER = 0,
+	NF_IP6_PRI_SECURITY = 50,
+	NF_IP6_PRI_NAT_SRC = 100,
+	NF_IP6_PRI_SELINUX_LAST = 225,
+	NF_IP6_PRI_LAST = INT_MAX,
+};
+
+
+#endif /*__LINUX_IP6_NETFILTER_H*/
diff --git a/include/linux/netfilter_ipv6/ip6_tables.h b/include/linux/netfilter_ipv6/ip6_tables.h
new file mode 100644
index 0000000..640a1d0
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6_tables.h
@@ -0,0 +1,268 @@
+/*
+ * 25-Jul-1998 Major changes to allow for ip chain table
+ *
+ * 3-Jan-2000 Named tables to allow packet selection for different uses.
+ */
+
+/*
+ * 	Format of an IP6 firewall descriptor
+ *
+ * 	src, dst, src_mask, dst_mask are always stored in network byte order.
+ * 	flags are stored in host byte order (of course).
+ * 	Port numbers are stored in HOST byte order.
+ */
+
+#ifndef _IP6_TABLES_H
+#define _IP6_TABLES_H
+
+#include <linux/types.h>
+
+#include <linux/netfilter_ipv6.h>
+
+#include <linux/netfilter/x_tables.h>
+
+#define IP6T_FUNCTION_MAXNAMELEN XT_FUNCTION_MAXNAMELEN
+#define IP6T_TABLE_MAXNAMELEN XT_TABLE_MAXNAMELEN
+#define ip6t_match xt_match
+#define ip6t_target xt_target
+#define ip6t_table xt_table
+#define ip6t_get_revision xt_get_revision
+#define ip6t_entry_match xt_entry_match
+#define ip6t_entry_target xt_entry_target
+#define ip6t_standard_target xt_standard_target
+#define ip6t_error_target xt_error_target
+#define ip6t_counters xt_counters
+#define IP6T_CONTINUE XT_CONTINUE
+#define IP6T_RETURN XT_RETURN
+
+/* Pre-iptables-1.4.0 */
+#include <linux/netfilter/xt_tcpudp.h>
+#define ip6t_tcp xt_tcp
+#define ip6t_udp xt_udp
+#define IP6T_TCP_INV_SRCPT	XT_TCP_INV_SRCPT
+#define IP6T_TCP_INV_DSTPT	XT_TCP_INV_DSTPT
+#define IP6T_TCP_INV_FLAGS	XT_TCP_INV_FLAGS
+#define IP6T_TCP_INV_OPTION	XT_TCP_INV_OPTION
+#define IP6T_TCP_INV_MASK	XT_TCP_INV_MASK
+#define IP6T_UDP_INV_SRCPT	XT_UDP_INV_SRCPT
+#define IP6T_UDP_INV_DSTPT	XT_UDP_INV_DSTPT
+#define IP6T_UDP_INV_MASK	XT_UDP_INV_MASK
+
+#define ip6t_counters_info xt_counters_info
+#define IP6T_STANDARD_TARGET XT_STANDARD_TARGET
+#define IP6T_ERROR_TARGET XT_ERROR_TARGET
+#define IP6T_MATCH_ITERATE(e, fn, args...) \
+	XT_MATCH_ITERATE(struct ip6t_entry, e, fn, ## args)
+#define IP6T_ENTRY_ITERATE(entries, size, fn, args...) \
+	XT_ENTRY_ITERATE(struct ip6t_entry, entries, size, fn, ## args)
+
+/* Yes, Virginia, you have to zero the padding. */
+struct ip6t_ip6 {
+	/* Source and destination IP6 addr */
+	struct in6_addr src, dst;		
+	/* Mask for src and dest IP6 addr */
+	struct in6_addr smsk, dmsk;
+	char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+
+	/* Upper protocol number
+	 * - The allowed value is 0 (any) or protocol number of last parsable
+	 *   header, which is 50 (ESP), 59 (No Next Header), 135 (MH), or
+	 *   the non IPv6 extension headers.
+	 * - The protocol numbers of IPv6 extension headers except of ESP and
+	 *   MH do not match any packets.
+	 * - You also need to set IP6T_FLAGS_PROTO to "flags" to check protocol.
+	 */
+	__u16 proto;
+	/* TOS to match iff flags & IP6T_F_TOS */
+	__u8 tos;
+
+	/* Flags word */
+	__u8 flags;
+	/* Inverse flags */
+	__u8 invflags;
+};
+
+/* Values for "flag" field in struct ip6t_ip6 (general ip6 structure). */
+#define IP6T_F_PROTO		0x01	/* Set if rule cares about upper 
+					   protocols */
+#define IP6T_F_TOS		0x02	/* Match the TOS. */
+#define IP6T_F_GOTO		0x04	/* Set if jump is a goto */
+#define IP6T_F_MASK		0x07	/* All possible flag bits mask. */
+
+/* Values for "inv" field in struct ip6t_ip6. */
+#define IP6T_INV_VIA_IN		0x01	/* Invert the sense of IN IFACE. */
+#define IP6T_INV_VIA_OUT		0x02	/* Invert the sense of OUT IFACE */
+#define IP6T_INV_TOS		0x04	/* Invert the sense of TOS. */
+#define IP6T_INV_SRCIP		0x08	/* Invert the sense of SRC IP. */
+#define IP6T_INV_DSTIP		0x10	/* Invert the sense of DST OP. */
+#define IP6T_INV_FRAG		0x20	/* Invert the sense of FRAG. */
+#define IP6T_INV_PROTO		XT_INV_PROTO
+#define IP6T_INV_MASK		0x7F	/* All possible flag bits mask. */
+
+/* This structure defines each of the firewall rules.  Consists of 3
+   parts which are 1) general IP header stuff 2) match specific
+   stuff 3) the target to perform if the rule matches */
+struct ip6t_entry {
+	struct ip6t_ip6 ipv6;
+
+	/* Mark with fields that we care about. */
+	unsigned int nfcache;
+
+	/* Size of ipt_entry + matches */
+	__u16 target_offset;
+	/* Size of ipt_entry + matches + target */
+	__u16 next_offset;
+
+	/* Back pointer */
+	unsigned int comefrom;
+
+	/* Packet and byte counters. */
+	struct xt_counters counters;
+
+	/* The matches (if any), then the target. */
+	unsigned char elems[0];
+};
+
+/* Standard entry */
+struct ip6t_standard {
+	struct ip6t_entry entry;
+	struct xt_standard_target target;
+};
+
+struct ip6t_error {
+	struct ip6t_entry entry;
+	struct xt_error_target target;
+};
+
+#define IP6T_ENTRY_INIT(__size)						       \
+{									       \
+	.target_offset	= sizeof(struct ip6t_entry),			       \
+	.next_offset	= (__size),					       \
+}
+
+#define IP6T_STANDARD_INIT(__verdict)					       \
+{									       \
+	.entry		= IP6T_ENTRY_INIT(sizeof(struct ip6t_standard)),       \
+	.target		= XT_TARGET_INIT(XT_STANDARD_TARGET,		       \
+					 sizeof(struct xt_standard_target)),   \
+	.target.verdict	= -(__verdict) - 1,				       \
+}
+
+#define IP6T_ERROR_INIT							       \
+{									       \
+	.entry		= IP6T_ENTRY_INIT(sizeof(struct ip6t_error)),	       \
+	.target		= XT_TARGET_INIT(XT_ERROR_TARGET,		       \
+					 sizeof(struct xt_error_target)),      \
+	.target.errorname = "ERROR",					       \
+}
+
+/*
+ * New IP firewall options for [gs]etsockopt at the RAW IP level.
+ * Unlike BSD Linux inherits IP options so you don't have to use
+ * a raw socket for this. Instead we check rights in the calls.
+ *
+ * ATTENTION: check linux/in6.h before adding new number here.
+ */
+#define IP6T_BASE_CTL			64
+
+#define IP6T_SO_SET_REPLACE		(IP6T_BASE_CTL)
+#define IP6T_SO_SET_ADD_COUNTERS	(IP6T_BASE_CTL + 1)
+#define IP6T_SO_SET_MAX			IP6T_SO_SET_ADD_COUNTERS
+
+#define IP6T_SO_GET_INFO		(IP6T_BASE_CTL)
+#define IP6T_SO_GET_ENTRIES		(IP6T_BASE_CTL + 1)
+#define IP6T_SO_GET_REVISION_MATCH	(IP6T_BASE_CTL + 4)
+#define IP6T_SO_GET_REVISION_TARGET	(IP6T_BASE_CTL + 5)
+#define IP6T_SO_GET_MAX			IP6T_SO_GET_REVISION_TARGET
+
+/* obtain original address if REDIRECT'd connection */
+#define IP6T_SO_ORIGINAL_DST            80
+
+/* ICMP matching stuff */
+struct ip6t_icmp {
+	__u8 type;				/* type to match */
+	__u8 code[2];				/* range of code */
+	__u8 invflags;				/* Inverse flags */
+};
+
+/* Values for "inv" field for struct ipt_icmp. */
+#define IP6T_ICMP_INV	0x01	/* Invert the sense of type/code test */
+
+/* The argument to IP6T_SO_GET_INFO */
+struct ip6t_getinfo {
+	/* Which table: caller fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Kernel fills these in. */
+	/* Which hook entry points are valid: bitmask */
+	unsigned int valid_hooks;
+
+	/* Hook entry points: one per netfilter hook. */
+	unsigned int hook_entry[NF_INET_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_INET_NUMHOOKS];
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Size of entries. */
+	unsigned int size;
+};
+
+/* The argument to IP6T_SO_SET_REPLACE. */
+struct ip6t_replace {
+	/* Which table. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Which hook entry points are valid: bitmask.  You can't
+           change this. */
+	unsigned int valid_hooks;
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Total size of new entries */
+	unsigned int size;
+
+	/* Hook entry points. */
+	unsigned int hook_entry[NF_INET_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_INET_NUMHOOKS];
+
+	/* Information about old entries: */
+	/* Number of counters (must be equal to current number of entries). */
+	unsigned int num_counters;
+	/* The old entries' counters. */
+	struct xt_counters *counters;
+
+	/* The entries (hang off end: not really an array). */
+	struct ip6t_entry entries[0];
+};
+
+/* The argument to IP6T_SO_GET_ENTRIES. */
+struct ip6t_get_entries {
+	/* Which table: user fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* User fills this in: total entry size. */
+	unsigned int size;
+
+	/* The entries. */
+	struct ip6t_entry entrytable[0];
+};
+
+/* Helper functions */
+static __inline__ struct xt_entry_target *
+ip6t_get_target(struct ip6t_entry *e)
+{
+	return (void *)e + e->target_offset;
+}
+
+/*
+ *	Main firewall chains definitions and global var's definitions.
+ */
+
+#endif /* _IP6_TABLES_H */
diff --git a/include/linux/netfilter_ipv6/ip6t_HL.h b/include/linux/netfilter_ipv6/ip6t_HL.h
new file mode 100644
index 0000000..ebd8ead
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_HL.h
@@ -0,0 +1,24 @@
+/* Hop Limit modification module for ip6tables
+ * Maciej Soltysiak <solt@dns.toxicfilms.tv>
+ * Based on HW's TTL module */
+
+#ifndef _IP6T_HL_H
+#define _IP6T_HL_H
+
+#include <linux/types.h>
+
+enum {
+	IP6T_HL_SET = 0,
+	IP6T_HL_INC,
+	IP6T_HL_DEC
+};
+
+#define IP6T_HL_MAXMODE	IP6T_HL_DEC
+
+struct ip6t_HL_info {
+	__u8	mode;
+	__u8	hop_limit;
+};
+
+
+#endif
diff --git a/include/linux/netfilter_ipv6/ip6t_LOG.h b/include/linux/netfilter_ipv6/ip6t_LOG.h
new file mode 100644
index 0000000..9dd5579
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_LOG.h
@@ -0,0 +1,19 @@
+#ifndef _IP6T_LOG_H
+#define _IP6T_LOG_H
+
+/* make sure not to change this without changing netfilter.h:NF_LOG_* (!) */
+#define IP6T_LOG_TCPSEQ		0x01	/* Log TCP sequence numbers */
+#define IP6T_LOG_TCPOPT		0x02	/* Log TCP options */
+#define IP6T_LOG_IPOPT		0x04	/* Log IP options */
+#define IP6T_LOG_UID		0x08	/* Log UID owning local socket */
+#define IP6T_LOG_NFLOG		0x10	/* Unsupported, don't use */
+#define IP6T_LOG_MACDECODE	0x20	/* Decode MAC header */
+#define IP6T_LOG_MASK		0x2f
+
+struct ip6t_log_info {
+	unsigned char level;
+	unsigned char logflags;
+	char prefix[30];
+};
+
+#endif /*_IPT_LOG_H*/
diff --git a/include/linux/netfilter_ipv6/ip6t_NPT.h b/include/linux/netfilter_ipv6/ip6t_NPT.h
new file mode 100644
index 0000000..f763355
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_NPT.h
@@ -0,0 +1,16 @@
+#ifndef __NETFILTER_IP6T_NPT
+#define __NETFILTER_IP6T_NPT
+
+#include <linux/types.h>
+#include <linux/netfilter.h>
+
+struct ip6t_npt_tginfo {
+	union nf_inet_addr	src_pfx;
+	union nf_inet_addr	dst_pfx;
+	__u8			src_pfx_len;
+	__u8			dst_pfx_len;
+	/* Used internally by the kernel */
+	__sum16			adjustment;
+};
+
+#endif /* __NETFILTER_IP6T_NPT */
diff --git a/include/linux/netfilter_ipv6/ip6t_REJECT.h b/include/linux/netfilter_ipv6/ip6t_REJECT.h
new file mode 100644
index 0000000..cd2e940
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_REJECT.h
@@ -0,0 +1,22 @@
+#ifndef _IP6T_REJECT_H
+#define _IP6T_REJECT_H
+
+#include <linux/types.h>
+
+enum ip6t_reject_with {
+	IP6T_ICMP6_NO_ROUTE,
+	IP6T_ICMP6_ADM_PROHIBITED,
+	IP6T_ICMP6_NOT_NEIGHBOUR,
+	IP6T_ICMP6_ADDR_UNREACH,
+	IP6T_ICMP6_PORT_UNREACH,
+	IP6T_ICMP6_ECHOREPLY,
+	IP6T_TCP_RESET,
+	IP6T_ICMP6_POLICY_FAIL,
+	IP6T_ICMP6_REJECT_ROUTE
+};
+
+struct ip6t_reject_info {
+	__u32	with;	/* reject type */
+};
+
+#endif /*_IP6T_REJECT_H*/
diff --git a/include/linux/netfilter_ipv6/ip6t_ah.h b/include/linux/netfilter_ipv6/ip6t_ah.h
new file mode 100644
index 0000000..5da2b65
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_ah.h
@@ -0,0 +1,22 @@
+#ifndef _IP6T_AH_H
+#define _IP6T_AH_H
+
+#include <linux/types.h>
+
+struct ip6t_ah {
+	__u32 spis[2];			/* Security Parameter Index */
+	__u32 hdrlen;			/* Header Length */
+	__u8  hdrres;			/* Test of the Reserved Filed */
+	__u8  invflags;			/* Inverse flags */
+};
+
+#define IP6T_AH_SPI 0x01
+#define IP6T_AH_LEN 0x02
+#define IP6T_AH_RES 0x04
+
+/* Values for "invflags" field in struct ip6t_ah. */
+#define IP6T_AH_INV_SPI		0x01	/* Invert the sense of spi. */
+#define IP6T_AH_INV_LEN		0x02	/* Invert the sense of length. */
+#define IP6T_AH_INV_MASK	0x03	/* All possible flags. */
+
+#endif /*_IP6T_AH_H*/
diff --git a/include/linux/netfilter_ipv6/ip6t_frag.h b/include/linux/netfilter_ipv6/ip6t_frag.h
new file mode 100644
index 0000000..b47f61b
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_frag.h
@@ -0,0 +1,25 @@
+#ifndef _IP6T_FRAG_H
+#define _IP6T_FRAG_H
+
+#include <linux/types.h>
+
+struct ip6t_frag {
+	__u32 ids[2];			/* Security Parameter Index */
+	__u32 hdrlen;			/* Header Length */
+	__u8  flags;			/*  */
+	__u8  invflags;			/* Inverse flags */
+};
+
+#define IP6T_FRAG_IDS 		0x01
+#define IP6T_FRAG_LEN 		0x02
+#define IP6T_FRAG_RES 		0x04
+#define IP6T_FRAG_FST 		0x08
+#define IP6T_FRAG_MF  		0x10
+#define IP6T_FRAG_NMF  		0x20
+
+/* Values for "invflags" field in struct ip6t_frag. */
+#define IP6T_FRAG_INV_IDS	0x01	/* Invert the sense of ids. */
+#define IP6T_FRAG_INV_LEN	0x02	/* Invert the sense of length. */
+#define IP6T_FRAG_INV_MASK	0x03	/* All possible flags. */
+
+#endif /*_IP6T_FRAG_H*/
diff --git a/include/linux/netfilter_ipv6/ip6t_hl.h b/include/linux/netfilter_ipv6/ip6t_hl.h
new file mode 100644
index 0000000..6e76dbc
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_hl.h
@@ -0,0 +1,24 @@
+/* ip6tables module for matching the Hop Limit value
+ * Maciej Soltysiak <solt@dns.toxicfilms.tv>
+ * Based on HW's ttl module */
+
+#ifndef _IP6T_HL_H
+#define _IP6T_HL_H
+
+#include <linux/types.h>
+
+enum {
+	IP6T_HL_EQ = 0,		/* equals */
+	IP6T_HL_NE,		/* not equals */
+	IP6T_HL_LT,		/* less than */
+	IP6T_HL_GT,		/* greater than */
+};
+
+
+struct ip6t_hl_info {
+	__u8	mode;
+	__u8	hop_limit;
+};
+
+
+#endif
diff --git a/include/linux/netfilter_ipv6/ip6t_ipv6header.h b/include/linux/netfilter_ipv6/ip6t_ipv6header.h
new file mode 100644
index 0000000..efae3a2
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_ipv6header.h
@@ -0,0 +1,28 @@
+/* ipv6header match - matches IPv6 packets based
+on whether they contain certain headers */
+
+/* Original idea: Brad Chapman 
+ * Rewritten by: Andras Kis-Szabo <kisza@sch.bme.hu> */
+
+
+#ifndef __IPV6HEADER_H
+#define __IPV6HEADER_H
+
+#include <linux/types.h>
+
+struct ip6t_ipv6header_info {
+	__u8 matchflags;
+	__u8 invflags;
+	__u8 modeflag;
+};
+
+#define MASK_HOPOPTS    128
+#define MASK_DSTOPTS    64
+#define MASK_ROUTING    32
+#define MASK_FRAGMENT   16
+#define MASK_AH         8
+#define MASK_ESP        4
+#define MASK_NONE       2
+#define MASK_PROTO      1
+
+#endif /* __IPV6HEADER_H */
diff --git a/include/linux/netfilter_ipv6/ip6t_mh.h b/include/linux/netfilter_ipv6/ip6t_mh.h
new file mode 100644
index 0000000..a7729a5
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_mh.h
@@ -0,0 +1,16 @@
+#ifndef _IP6T_MH_H
+#define _IP6T_MH_H
+
+#include <linux/types.h>
+
+/* MH matching stuff */
+struct ip6t_mh {
+	__u8 types[2];	/* MH type range */
+	__u8 invflags;	/* Inverse flags */
+};
+
+/* Values for "invflags" field in struct ip6t_mh. */
+#define IP6T_MH_INV_TYPE	0x01	/* Invert the sense of type. */
+#define IP6T_MH_INV_MASK	0x01	/* All possible flags. */
+
+#endif /*_IP6T_MH_H*/
diff --git a/include/linux/netfilter_ipv6/ip6t_opts.h b/include/linux/netfilter_ipv6/ip6t_opts.h
new file mode 100644
index 0000000..17d419a
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_opts.h
@@ -0,0 +1,24 @@
+#ifndef _IP6T_OPTS_H
+#define _IP6T_OPTS_H
+
+#include <linux/types.h>
+
+#define IP6T_OPTS_OPTSNR 16
+
+struct ip6t_opts {
+	__u32 hdrlen;			/* Header Length */
+	__u8 flags;				/*  */
+	__u8 invflags;			/* Inverse flags */
+	__u16 opts[IP6T_OPTS_OPTSNR];	/* opts */
+	__u8 optsnr;			/* Nr of OPts */
+};
+
+#define IP6T_OPTS_LEN 		0x01
+#define IP6T_OPTS_OPTS 		0x02
+#define IP6T_OPTS_NSTRICT	0x04
+
+/* Values for "invflags" field in struct ip6t_rt. */
+#define IP6T_OPTS_INV_LEN	0x01	/* Invert the sense of length. */
+#define IP6T_OPTS_INV_MASK	0x01	/* All possible flags. */
+
+#endif /*_IP6T_OPTS_H*/
diff --git a/include/linux/netfilter_ipv6/ip6t_rt.h b/include/linux/netfilter_ipv6/ip6t_rt.h
new file mode 100644
index 0000000..7605a5f
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_rt.h
@@ -0,0 +1,33 @@
+#ifndef _IP6T_RT_H
+#define _IP6T_RT_H
+
+#include <linux/types.h>
+/*#include <linux/in6.h>*/
+
+#define IP6T_RT_HOPS 16
+
+struct ip6t_rt {
+	__u32 rt_type;			/* Routing Type */
+	__u32 segsleft[2];			/* Segments Left */
+	__u32 hdrlen;			/* Header Length */
+	__u8  flags;			/*  */
+	__u8  invflags;			/* Inverse flags */
+	struct in6_addr addrs[IP6T_RT_HOPS];	/* Hops */
+	__u8 addrnr;			/* Nr of Addresses */
+};
+
+#define IP6T_RT_TYP 		0x01
+#define IP6T_RT_SGS 		0x02
+#define IP6T_RT_LEN 		0x04
+#define IP6T_RT_RES 		0x08
+#define IP6T_RT_FST_MASK	0x30
+#define IP6T_RT_FST 		0x10
+#define IP6T_RT_FST_NSTRICT	0x20
+
+/* Values for "invflags" field in struct ip6t_rt. */
+#define IP6T_RT_INV_TYP		0x01	/* Invert the sense of type. */
+#define IP6T_RT_INV_SGS		0x02	/* Invert the sense of Segments. */
+#define IP6T_RT_INV_LEN		0x04	/* Invert the sense of length. */
+#define IP6T_RT_INV_MASK	0x07	/* All possible flags. */
+
+#endif /*_IP6T_RT_H*/
diff --git a/include/linux/netfilter_ipv6/ip6t_srh.h b/include/linux/netfilter_ipv6/ip6t_srh.h
new file mode 100644
index 0000000..3bfe411
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_srh.h
@@ -0,0 +1,95 @@
+#ifndef _IP6T_SRH_H
+#define _IP6T_SRH_H
+
+#include <linux/types.h>
+#include <linux/netfilter.h>
+
+/* Values for "mt_flags" field in struct ip6t_srh */
+#define IP6T_SRH_NEXTHDR        0x0001
+#define IP6T_SRH_LEN_EQ         0x0002
+#define IP6T_SRH_LEN_GT         0x0004
+#define IP6T_SRH_LEN_LT         0x0008
+#define IP6T_SRH_SEGS_EQ        0x0010
+#define IP6T_SRH_SEGS_GT        0x0020
+#define IP6T_SRH_SEGS_LT        0x0040
+#define IP6T_SRH_LAST_EQ        0x0080
+#define IP6T_SRH_LAST_GT        0x0100
+#define IP6T_SRH_LAST_LT        0x0200
+#define IP6T_SRH_TAG            0x0400
+#define IP6T_SRH_PSID           0x0800
+#define IP6T_SRH_NSID           0x1000
+#define IP6T_SRH_LSID           0x2000
+#define IP6T_SRH_MASK           0x3FFF
+
+/* Values for "mt_invflags" field in struct ip6t_srh */
+#define IP6T_SRH_INV_NEXTHDR    0x0001
+#define IP6T_SRH_INV_LEN_EQ     0x0002
+#define IP6T_SRH_INV_LEN_GT     0x0004
+#define IP6T_SRH_INV_LEN_LT     0x0008
+#define IP6T_SRH_INV_SEGS_EQ    0x0010
+#define IP6T_SRH_INV_SEGS_GT    0x0020
+#define IP6T_SRH_INV_SEGS_LT    0x0040
+#define IP6T_SRH_INV_LAST_EQ    0x0080
+#define IP6T_SRH_INV_LAST_GT    0x0100
+#define IP6T_SRH_INV_LAST_LT    0x0200
+#define IP6T_SRH_INV_TAG        0x0400
+#define IP6T_SRH_INV_PSID       0x0800
+#define IP6T_SRH_INV_NSID       0x1000
+#define IP6T_SRH_INV_LSID       0x2000
+#define IP6T_SRH_INV_MASK       0x3FFF
+
+/**
+ *      struct ip6t_srh - SRH match options
+ *      @ next_hdr: Next header field of SRH
+ *      @ hdr_len: Extension header length field of SRH
+ *      @ segs_left: Segments left field of SRH
+ *      @ last_entry: Last entry field of SRH
+ *      @ tag: Tag field of SRH
+ *      @ mt_flags: match options
+ *      @ mt_invflags: Invert the sense of match options
+ */
+
+struct ip6t_srh {
+	__u8                    next_hdr;
+	__u8                    hdr_len;
+	__u8                    segs_left;
+	__u8                    last_entry;
+	__u16                   tag;
+	__u16                   mt_flags;
+	__u16                   mt_invflags;
+};
+
+/**
+ *      struct ip6t_srh1 - SRH match options (revision 1)
+ *      @ next_hdr: Next header field of SRH
+ *      @ hdr_len: Extension header length field of SRH
+ *      @ segs_left: Segments left field of SRH
+ *      @ last_entry: Last entry field of SRH
+ *      @ tag: Tag field of SRH
+ *      @ psid_addr: Address of previous SID in SRH SID list
+ *      @ nsid_addr: Address of NEXT SID in SRH SID list
+ *      @ lsid_addr: Address of LAST SID in SRH SID list
+ *      @ psid_msk: Mask of previous SID in SRH SID list
+ *      @ nsid_msk: Mask of next SID in SRH SID list
+ *      @ lsid_msk: MAsk of last SID in SRH SID list
+ *      @ mt_flags: match options
+ *      @ mt_invflags: Invert the sense of match options
+ */
+
+struct ip6t_srh1 {
+	__u8                    next_hdr;
+	__u8                    hdr_len;
+	__u8                    segs_left;
+	__u8                    last_entry;
+	__u16                   tag;
+	struct in6_addr         psid_addr;
+	struct in6_addr         nsid_addr;
+	struct in6_addr         lsid_addr;
+	struct in6_addr         psid_msk;
+	struct in6_addr         nsid_msk;
+	struct in6_addr         lsid_msk;
+	__u16                   mt_flags;
+	__u16                   mt_invflags;
+};
+
+#endif /*_IP6T_SRH_H*/
diff --git a/include/linux/types.h b/include/linux/types.h
new file mode 100644
index 0000000..1ae9250
--- /dev/null
+++ b/include/linux/types.h
@@ -0,0 +1,53 @@
+#ifndef _LINUX_TYPES_H
+#define _LINUX_TYPES_H
+
+#include <asm/types.h>
+
+#ifndef __ASSEMBLY__
+
+#include <linux/posix_types.h>
+
+
+/*
+ * Below are truly Linux-specific types that should never collide with
+ * any application/library that wants linux/types.h.
+ */
+
+#ifdef __CHECKER__
+#define __bitwise__ __attribute__((bitwise))
+#else
+#define __bitwise__
+#endif
+#ifdef __CHECK_ENDIAN__
+#define __bitwise __bitwise__
+#else
+#define __bitwise
+#endif
+
+#define __aligned_u64 __u64 __attribute__((aligned(8)))
+
+typedef __u16 __bitwise __le16;
+typedef __u16 __bitwise __be16;
+typedef __u32 __bitwise __le32;
+typedef __u32 __bitwise __be32;
+typedef __u64 __bitwise __le64;
+typedef __u64 __bitwise __be64;
+
+typedef __u16 __bitwise __sum16;
+typedef __u32 __bitwise __wsum;
+
+/*
+ * aligned_u64 should be used in defining kernel<->userspace ABIs to avoid
+ * common 32/64-bit compat problems.
+ * 64-bit values align to 4-byte boundaries on x86_32 (and possibly other
+ * architectures) and to 8-byte boundaries on 64-bit architetures.  The new
+ * aligned_64 type enforces 8-byte alignment so that structs containing
+ * aligned_64 values have the same alignment on 32-bit and 64-bit architectures.
+ * No conversions are necessary between 32-bit user-space and a 64-bit kernel.
+ */
+#define __aligned_u64 __u64 __attribute__((aligned(8)))
+#define __aligned_be64 __be64 __attribute__((aligned(8)))
+#define __aligned_le64 __le64 __attribute__((aligned(8)))
+
+#endif /*  __ASSEMBLY__ */
+#endif /* _LINUX_TYPES_H */
diff --git a/include/xtables-version.h b/include/xtables-version.h
new file mode 100644
index 0000000..ed31ad8
--- /dev/null
+++ b/include/xtables-version.h
@@ -0,0 +1,2 @@
+#define XTABLES_VERSION "libxtables.so.12"
+#define XTABLES_VERSION_CODE 12
diff --git a/include/xtables-version.h.in b/include/xtables-version.h.in
new file mode 100644
index 0000000..cb13827
--- /dev/null
+++ b/include/xtables-version.h.in
@@ -0,0 +1,2 @@
+#define XTABLES_VERSION "libxtables.so.@libxtables_vmajor@"
+#define XTABLES_VERSION_CODE @libxtables_vmajor@
diff --git a/include/xtables.h b/include/xtables.h
new file mode 100644
index 0000000..df1eaee
--- /dev/null
+++ b/include/xtables.h
@@ -0,0 +1,655 @@
+#ifndef _XTABLES_H
+#define _XTABLES_H
+
+/*
+ * Changing any structs/functions may incur a needed change
+ * in libxtables_vcurrent/vage too.
+ */
+
+#include <sys/socket.h> /* PF_* */
+#include <sys/types.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <linux/types.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/x_tables.h>
+
+#ifndef IPPROTO_SCTP
+#define IPPROTO_SCTP 132
+#endif
+#ifndef IPPROTO_DCCP
+#define IPPROTO_DCCP 33
+#endif
+#ifndef IPPROTO_MH
+#	define IPPROTO_MH 135
+#endif
+#ifndef IPPROTO_UDPLITE
+#define IPPROTO_UDPLITE	136
+#endif
+
+#include <xtables-version.h>
+
+struct in_addr;
+
+/*
+ * .size is here so that there is a somewhat reasonable check
+ * against the chosen .type.
+ */
+#define XTOPT_POINTER(stype, member) \
+	.ptroff = offsetof(stype, member), \
+	.size = sizeof(((stype *)NULL)->member)
+#define XTOPT_TABLEEND {.name = NULL}
+
+/**
+ * Select the format the input has to conform to, as well as the target type
+ * (area pointed to with XTOPT_POINTER). Note that the storing is not always
+ * uniform. @cb->val will be populated with as much as there is space, i.e.
+ * exactly 2 items for ranges, but the target area can receive more values
+ * (e.g. in case of ranges), or less values (e.g. %XTTYPE_HOSTMASK).
+ *
+ * %XTTYPE_NONE:	option takes no argument
+ * %XTTYPE_UINT*:	standard integer
+ * %XTTYPE_UINT*RC:	colon-separated range of standard integers
+ * %XTTYPE_DOUBLE:	double-precision floating point number
+ * %XTTYPE_STRING:	arbitrary string
+ * %XTTYPE_TOSMASK:	8-bit TOS value with optional mask
+ * %XTTYPE_MARKMASK32:	32-bit mark with optional mask
+ * %XTTYPE_SYSLOGLEVEL:	syslog level by name or number
+ * %XTTYPE_HOST:	one host or address (ptr: union nf_inet_addr)
+ * %XTTYPE_HOSTMASK:	one host or address, with an optional prefix length
+ * 			(ptr: union nf_inet_addr; only host portion is stored)
+ * %XTTYPE_PROTOCOL:	protocol number/name from /etc/protocols (ptr: uint8_t)
+ * %XTTYPE_PORT:	16-bit port name or number (supports %XTOPT_NBO)
+ * %XTTYPE_PORTRC:	colon-separated port range (names acceptable),
+ * 			(supports %XTOPT_NBO)
+ * %XTTYPE_PLEN:	prefix length
+ * %XTTYPE_PLENMASK:	prefix length (ptr: union nf_inet_addr)
+ * %XTTYPE_ETHERMAC:	Ethernet MAC address in hex form
+ */
+enum xt_option_type {
+	XTTYPE_NONE,
+	XTTYPE_UINT8,
+	XTTYPE_UINT16,
+	XTTYPE_UINT32,
+	XTTYPE_UINT64,
+	XTTYPE_UINT8RC,
+	XTTYPE_UINT16RC,
+	XTTYPE_UINT32RC,
+	XTTYPE_UINT64RC,
+	XTTYPE_DOUBLE,
+	XTTYPE_STRING,
+	XTTYPE_TOSMASK,
+	XTTYPE_MARKMASK32,
+	XTTYPE_SYSLOGLEVEL,
+	XTTYPE_HOST,
+	XTTYPE_HOSTMASK,
+	XTTYPE_PROTOCOL,
+	XTTYPE_PORT,
+	XTTYPE_PORTRC,
+	XTTYPE_PLEN,
+	XTTYPE_PLENMASK,
+	XTTYPE_ETHERMAC,
+};
+
+/**
+ * %XTOPT_INVERT:	option is invertible (usable with !)
+ * %XTOPT_MAND:		option is mandatory
+ * %XTOPT_MULTI:	option may be specified multiple times
+ * %XTOPT_PUT:		store value into memory at @ptroff
+ * %XTOPT_NBO:		store value in network-byte order
+ * 			(only certain XTTYPEs recognize this)
+ */
+enum xt_option_flags {
+	XTOPT_INVERT = 1 << 0,
+	XTOPT_MAND   = 1 << 1,
+	XTOPT_MULTI  = 1 << 2,
+	XTOPT_PUT    = 1 << 3,
+	XTOPT_NBO    = 1 << 4,
+};
+
+/**
+ * @name:	name of option
+ * @type:	type of input and validation method, see %XTTYPE_*
+ * @id:		unique number (within extension) for option, 0-31
+ * @excl:	bitmask of flags that cannot be used with this option
+ * @also:	bitmask of flags that must be used with this option
+ * @flags:	bitmask of option flags, see %XTOPT_*
+ * @ptroff:	offset into private structure for member
+ * @size:	size of the item pointed to by @ptroff; this is a safeguard
+ * @min:	lowest allowed value (for singular integral types)
+ * @max:	highest allowed value (for singular integral types)
+ */
+struct xt_option_entry {
+	const char *name;
+	enum xt_option_type type;
+	unsigned int id, excl, also, flags;
+	unsigned int ptroff;
+	size_t size;
+	unsigned int min, max;
+};
+
+/**
+ * @arg:	input from command line
+ * @ext_name:	name of extension currently being processed
+ * @entry:	current option being processed
+ * @data:	per-extension kernel data block
+ * @xflags:	options of the extension that have been used
+ * @invert:	whether option was used with !
+ * @nvals:	number of results in uXX_multi
+ * @val:	parsed result
+ * @udata:	per-extension private scratch area
+ * 		(cf. xtables_{match,target}->udata_size)
+ */
+struct xt_option_call {
+	const char *arg, *ext_name;
+	const struct xt_option_entry *entry;
+	void *data;
+	unsigned int xflags;
+	bool invert;
+	uint8_t nvals;
+	union {
+		uint8_t u8, u8_range[2], syslog_level, protocol;
+		uint16_t u16, u16_range[2], port, port_range[2];
+		uint32_t u32, u32_range[2];
+		uint64_t u64, u64_range[2];
+		double dbl;
+		struct {
+			union nf_inet_addr haddr, hmask;
+			uint8_t hlen;
+		};
+		struct {
+			uint8_t tos_value, tos_mask;
+		};
+		struct {
+			uint32_t mark, mask;
+		};
+		uint8_t ethermac[6];
+	} val;
+	/* Wished for a world where the ones below were gone: */
+	union {
+		struct xt_entry_match **match;
+		struct xt_entry_target **target;
+	};
+	void *xt_entry;
+	void *udata;
+};
+
+/**
+ * @ext_name:	name of extension currently being processed
+ * @data:	per-extension (kernel) data block
+ * @udata:	per-extension private scratch area
+ * 		(cf. xtables_{match,target}->udata_size)
+ * @xflags:	options of the extension that have been used
+ */
+struct xt_fcheck_call {
+	const char *ext_name;
+	void *data, *udata;
+	unsigned int xflags;
+};
+
+/**
+ * A "linear"/linked-list based name<->id map, for files similar to
+ * /etc/iproute2/.
+ */
+struct xtables_lmap {
+	char *name;
+	int id;
+	struct xtables_lmap *next;
+};
+
+enum xtables_ext_flags {
+	XTABLES_EXT_ALIAS = 1 << 0,
+};
+
+struct xt_xlate;
+
+struct xt_xlate_mt_params {
+	const void			*ip;
+	const struct xt_entry_match	*match;
+	int				numeric;
+	bool				escape_quotes;
+};
+
+struct xt_xlate_tg_params {
+	const void			*ip;
+	const struct xt_entry_target	*target;
+	int				numeric;
+	bool				escape_quotes;
+};
+
+/* Include file for additions: new matches and targets. */
+struct xtables_match {
+	/*
+	 * ABI/API version this module requires. Must be first member,
+	 * as the rest of this struct may be subject to ABI changes.
+	 */
+	const char *version;
+
+	struct xtables_match *next;
+
+	const char *name;
+	const char *real_name;
+
+	/* Revision of match (0 by default). */
+	uint8_t revision;
+
+	/* Extension flags */
+	uint8_t ext_flags;
+
+	uint16_t family;
+
+	/* Size of match data. */
+	size_t size;
+
+	/* Size of match data relevant for userspace comparison purposes */
+	size_t userspacesize;
+
+	/* Function which prints out usage message. */
+	void (*help)(void);
+
+	/* Initialize the match. */
+	void (*init)(struct xt_entry_match *m);
+
+	/* Function which parses command options; returns true if it
+           ate an option */
+	/* entry is struct ipt_entry for example */
+	int (*parse)(int c, char **argv, int invert, unsigned int *flags,
+		     const void *entry,
+		     struct xt_entry_match **match);
+
+	/* Final check; exit if not ok. */
+	void (*final_check)(unsigned int flags);
+
+	/* Prints out the match iff non-NULL: put space at end */
+	/* ip is struct ipt_ip * for example */
+	void (*print)(const void *ip,
+		      const struct xt_entry_match *match, int numeric);
+
+	/* Saves the match info in parsable form to stdout. */
+	/* ip is struct ipt_ip * for example */
+	void (*save)(const void *ip, const struct xt_entry_match *match);
+
+	/* Print match name or alias */
+	const char *(*alias)(const struct xt_entry_match *match);
+
+	/* Pointer to list of extra command-line options */
+	const struct option *extra_opts;
+
+	/* New parser */
+	void (*x6_parse)(struct xt_option_call *);
+	void (*x6_fcheck)(struct xt_fcheck_call *);
+	const struct xt_option_entry *x6_options;
+
+	/* Translate iptables to nft */
+	int (*xlate)(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params);
+
+	/* Size of per-extension instance extra "global" scratch space */
+	size_t udata_size;
+
+	/* Ignore these men behind the curtain: */
+	void *udata;
+	unsigned int option_offset;
+	struct xt_entry_match *m;
+	unsigned int mflags;
+	unsigned int loaded; /* simulate loading so options are merged properly */
+};
+
+struct xtables_target {
+	/*
+	 * ABI/API version this module requires. Must be first member,
+	 * as the rest of this struct may be subject to ABI changes.
+	 */
+	const char *version;
+
+	struct xtables_target *next;
+
+
+	const char *name;
+
+	/* Real target behind this, if any. */
+	const char *real_name;
+
+	/* Revision of target (0 by default). */
+	uint8_t revision;
+
+	/* Extension flags */
+	uint8_t ext_flags;
+
+	uint16_t family;
+
+
+	/* Size of target data. */
+	size_t size;
+
+	/* Size of target data relevant for userspace comparison purposes */
+	size_t userspacesize;
+
+	/* Function which prints out usage message. */
+	void (*help)(void);
+
+	/* Initialize the target. */
+	void (*init)(struct xt_entry_target *t);
+
+	/* Function which parses command options; returns true if it
+           ate an option */
+	/* entry is struct ipt_entry for example */
+	int (*parse)(int c, char **argv, int invert, unsigned int *flags,
+		     const void *entry,
+		     struct xt_entry_target **targetinfo);
+
+	/* Final check; exit if not ok. */
+	void (*final_check)(unsigned int flags);
+
+	/* Prints out the target iff non-NULL: put space at end */
+	void (*print)(const void *ip,
+		      const struct xt_entry_target *target, int numeric);
+
+	/* Saves the targinfo in parsable form to stdout. */
+	void (*save)(const void *ip,
+		     const struct xt_entry_target *target);
+
+	/* Print target name or alias */
+	const char *(*alias)(const struct xt_entry_target *target);
+
+	/* Pointer to list of extra command-line options */
+	const struct option *extra_opts;
+
+	/* New parser */
+	void (*x6_parse)(struct xt_option_call *);
+	void (*x6_fcheck)(struct xt_fcheck_call *);
+	const struct xt_option_entry *x6_options;
+
+	/* Translate iptables to nft */
+	int (*xlate)(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params);
+
+	size_t udata_size;
+
+	/* Ignore these men behind the curtain: */
+	void *udata;
+	unsigned int option_offset;
+	struct xt_entry_target *t;
+	unsigned int tflags;
+	unsigned int used;
+	unsigned int loaded; /* simulate loading so options are merged properly */
+};
+
+struct xtables_rule_match {
+	struct xtables_rule_match *next;
+	struct xtables_match *match;
+	/* Multiple matches of the same type: the ones before
+	   the current one are completed from parsing point of view */
+	bool completed;
+};
+
+/**
+ * struct xtables_pprot -
+ *
+ * A few hardcoded protocols for 'all' and in case the user has no
+ * /etc/protocols.
+ */
+struct xtables_pprot {
+	const char *name;
+	uint8_t num;
+};
+
+enum xtables_tryload {
+	XTF_DONT_LOAD,
+	XTF_DURING_LOAD,
+	XTF_TRY_LOAD,
+	XTF_LOAD_MUST_SUCCEED,
+};
+
+enum xtables_exittype {
+	OTHER_PROBLEM = 1,
+	PARAMETER_PROBLEM,
+	VERSION_PROBLEM,
+	RESOURCE_PROBLEM,
+	XTF_ONLY_ONCE,
+	XTF_NO_INVERT,
+	XTF_BAD_VALUE,
+	XTF_ONE_ACTION,
+};
+
+struct xtables_globals
+{
+	unsigned int option_offset;
+	const char *program_name, *program_version;
+	struct option *orig_opts;
+	struct option *opts;
+	void (*exit_err)(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+	int (*compat_rev)(const char *name, uint8_t rev, int opt);
+};
+
+#define XT_GETOPT_TABLEEND {.name = NULL, .has_arg = false}
+
+/*
+ * enum op-
+ *
+ * For writing clean nftables translations code
+ */
+enum xt_op {
+	XT_OP_EQ,
+	XT_OP_NEQ,
+	XT_OP_MAX,
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const char *xtables_modprobe_program;
+extern struct xtables_match *xtables_matches;
+extern struct xtables_target *xtables_targets;
+
+extern void xtables_init(void);
+extern void xtables_fini(void);
+extern void xtables_set_nfproto(uint8_t);
+extern void *xtables_calloc(size_t, size_t);
+extern void *xtables_malloc(size_t);
+extern void *xtables_realloc(void *, size_t);
+
+extern int xtables_insmod(const char *, const char *, bool);
+extern int xtables_load_ko(const char *, bool);
+extern int xtables_set_params(struct xtables_globals *xtp);
+extern void xtables_free_opts(int reset_offset);
+extern struct option *xtables_merge_options(struct option *origopts,
+	struct option *oldopts, const struct option *newopts,
+	unsigned int *option_offset);
+
+extern int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto);
+extern struct xtables_match *xtables_find_match(const char *name,
+	enum xtables_tryload, struct xtables_rule_match **match);
+extern struct xtables_match *xtables_find_match_revision(const char *name,
+	enum xtables_tryload tryload, struct xtables_match *match,
+	int revision);
+extern struct xtables_target *xtables_find_target(const char *name,
+	enum xtables_tryload);
+struct xtables_target *xtables_find_target_revision(const char *name,
+	enum xtables_tryload tryload, struct xtables_target *target,
+	int revision);
+extern int xtables_compatible_revision(const char *name, uint8_t revision,
+				       int opt);
+
+extern void xtables_rule_matches_free(struct xtables_rule_match **matches);
+
+/* Your shared library should call one of these. */
+extern void xtables_register_match(struct xtables_match *me);
+extern void xtables_register_matches(struct xtables_match *, unsigned int);
+extern void xtables_register_target(struct xtables_target *me);
+extern void xtables_register_targets(struct xtables_target *, unsigned int);
+
+extern bool xtables_strtoul(const char *, char **, uintmax_t *,
+	uintmax_t, uintmax_t);
+extern bool xtables_strtoui(const char *, char **, unsigned int *,
+	unsigned int, unsigned int);
+extern int xtables_service_to_port(const char *name, const char *proto);
+extern uint16_t xtables_parse_port(const char *port, const char *proto);
+extern void
+xtables_parse_interface(const char *arg, char *vianame, unsigned char *mask);
+
+/* this is a special 64bit data type that is 8-byte aligned */
+#define aligned_u64 uint64_t __attribute__((aligned(8)))
+
+extern struct xtables_globals *xt_params;
+#define xtables_error (xt_params->exit_err)
+
+extern void xtables_param_act(unsigned int, const char *, ...);
+
+extern const char *xtables_ipaddr_to_numeric(const struct in_addr *);
+extern const char *xtables_ipaddr_to_anyname(const struct in_addr *);
+extern const char *xtables_ipmask_to_numeric(const struct in_addr *);
+extern struct in_addr *xtables_numeric_to_ipaddr(const char *);
+extern struct in_addr *xtables_numeric_to_ipmask(const char *);
+extern int xtables_ipmask_to_cidr(const struct in_addr *);
+extern void xtables_ipparse_any(const char *, struct in_addr **,
+	struct in_addr *, unsigned int *);
+extern void xtables_ipparse_multiple(const char *, struct in_addr **,
+	struct in_addr **, unsigned int *);
+
+extern struct in6_addr *xtables_numeric_to_ip6addr(const char *);
+extern const char *xtables_ip6addr_to_numeric(const struct in6_addr *);
+extern const char *xtables_ip6addr_to_anyname(const struct in6_addr *);
+extern const char *xtables_ip6mask_to_numeric(const struct in6_addr *);
+extern int xtables_ip6mask_to_cidr(const struct in6_addr *);
+extern void xtables_ip6parse_any(const char *, struct in6_addr **,
+	struct in6_addr *, unsigned int *);
+extern void xtables_ip6parse_multiple(const char *, struct in6_addr **,
+	struct in6_addr **, unsigned int *);
+
+/* Absolute file name for network data base files.  */
+#define XT_PATH_ETHERTYPES     "/etc/ethertypes"
+
+struct xt_ethertypeent {
+	char *e_name;           /* Official ethernet type name.  */
+	char **e_aliases;       /* Alias list.  */
+	int e_ethertype;        /* Ethernet type number.  */
+};
+
+extern struct xt_ethertypeent *xtables_getethertypebyname(const char *name);
+extern struct xt_ethertypeent *xtables_getethertypebynumber(int ethertype);
+
+/**
+ * Print the specified value to standard output, quoting dangerous
+ * characters if required.
+ */
+extern void xtables_save_string(const char *value);
+
+#define FMT_NUMERIC		0x0001
+#define FMT_NOCOUNTS		0x0002
+#define FMT_KILOMEGAGIGA	0x0004
+#define FMT_OPTIONS		0x0008
+#define FMT_NOTABLE		0x0010
+#define FMT_NOTARGET		0x0020
+#define FMT_VIA			0x0040
+#define FMT_NONEWLINE		0x0080
+#define FMT_LINENUMBERS		0x0100
+#define FMT_EBT_SAVE		0x0200
+#define FMT_C_COUNTS		0x0400
+
+#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \
+                        | FMT_NUMERIC | FMT_NOTABLE)
+#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab))
+
+extern void xtables_print_num(uint64_t number, unsigned int format);
+extern int xtables_parse_mac_and_mask(const char *from, void *to, void *mask);
+extern int xtables_print_well_known_mac_and_mask(const void *mac,
+						 const void *mask);
+extern void xtables_print_mac(const unsigned char *macaddress);
+extern void xtables_print_mac_and_mask(const unsigned char *mac,
+				       const unsigned char *mask);
+
+extern void xtables_parse_val_mask(struct xt_option_call *cb,
+				   unsigned int *val, unsigned int *mask,
+				   const struct xtables_lmap *lmap);
+
+static inline void xtables_parse_mark_mask(struct xt_option_call *cb,
+					   unsigned int *mark,
+					   unsigned int *mask)
+{
+	xtables_parse_val_mask(cb, mark, mask, NULL);
+}
+
+extern void xtables_print_val_mask(unsigned int val, unsigned int mask,
+				   const struct xtables_lmap *lmap);
+
+static inline void xtables_print_mark_mask(unsigned int mark,
+					   unsigned int mask)
+{
+	xtables_print_val_mask(mark, mask, NULL);
+}
+
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+#	ifdef _INIT
+#		undef _init
+#		define _init _INIT
+#	endif
+	extern void init_extensions(void);
+	extern void init_extensions4(void);
+	extern void init_extensions6(void);
+#else
+#	define _init __attribute__((constructor)) _INIT
+#endif
+
+extern const struct xtables_pprot xtables_chain_protos[];
+extern uint16_t xtables_parse_protocol(const char *s);
+
+/* kernel revision handling */
+extern int kernel_version;
+extern void get_kernel_version(void);
+#define LINUX_VERSION(x,y,z)	(0x10000*(x) + 0x100*(y) + z)
+#define LINUX_VERSION_MAJOR(x)	(((x)>>16) & 0xFF)
+#define LINUX_VERSION_MINOR(x)	(((x)>> 8) & 0xFF)
+#define LINUX_VERSION_PATCH(x)	( (x)      & 0xFF)
+
+/* xtoptions.c */
+extern void xtables_option_metavalidate(const char *,
+					const struct xt_option_entry *);
+extern struct option *xtables_options_xfrm(struct option *, struct option *,
+					   const struct xt_option_entry *,
+					   unsigned int *);
+extern void xtables_option_parse(struct xt_option_call *);
+extern void xtables_option_tpcall(unsigned int, char **, bool,
+				  struct xtables_target *, void *);
+extern void xtables_option_mpcall(unsigned int, char **, bool,
+				  struct xtables_match *, void *);
+extern void xtables_option_tfcall(struct xtables_target *);
+extern void xtables_option_mfcall(struct xtables_match *);
+extern void xtables_options_fcheck(const char *, unsigned int,
+				   const struct xt_option_entry *);
+
+extern struct xtables_lmap *xtables_lmap_init(const char *);
+extern void xtables_lmap_free(struct xtables_lmap *);
+extern int xtables_lmap_name2id(const struct xtables_lmap *, const char *);
+extern const char *xtables_lmap_id2name(const struct xtables_lmap *, int);
+
+/* xlate infrastructure */
+struct xt_xlate *xt_xlate_alloc(int size);
+void xt_xlate_free(struct xt_xlate *xl);
+void xt_xlate_add(struct xt_xlate *xl, const char *fmt, ...) __attribute__((format(printf,2,3)));
+void xt_xlate_add_comment(struct xt_xlate *xl, const char *comment);
+const char *xt_xlate_get_comment(struct xt_xlate *xl);
+const char *xt_xlate_get(struct xt_xlate *xl);
+
+#ifdef XTABLES_INTERNAL
+
+/* Shipped modules rely on this... */
+
+#	ifndef ARRAY_SIZE
+#		define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+#	endif
+
+extern void _init(void);
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* _XTABLES_H */
diff --git a/iptables-test.py b/iptables-test.py
new file mode 100755
index 0000000..ca5efb1
--- /dev/null
+++ b/iptables-test.py
@@ -0,0 +1,387 @@
+#!/usr/bin/env python
+#
+# (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+#
+# 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 2 of the License, or
+# (at your option) any later version.
+#
+# This software has been sponsored by Sophos Astaro <http://www.sophos.com>
+#
+
+from __future__ import print_function
+import sys
+import os
+import subprocess
+import argparse
+
+IPTABLES = "iptables"
+IP6TABLES = "ip6tables"
+ARPTABLES = "arptables"
+EBTABLES = "ebtables"
+
+IPTABLES_SAVE = "iptables-save"
+IP6TABLES_SAVE = "ip6tables-save"
+ARPTABLES_SAVE = "arptables-save"
+EBTABLES_SAVE = "ebtables-save"
+#IPTABLES_SAVE = ['xtables-save','-4']
+#IP6TABLES_SAVE = ['xtables-save','-6']
+
+EXTENSIONS_PATH = "extensions"
+LOGFILE="/tmp/iptables-test.log"
+log_file = None
+
+
+class Colors:
+    HEADER = '\033[95m'
+    BLUE = '\033[94m'
+    GREEN = '\033[92m'
+    YELLOW = '\033[93m'
+    RED = '\033[91m'
+    ENDC = '\033[0m'
+
+
+def print_error(reason, filename=None, lineno=None):
+    '''
+    Prints an error with nice colors, indicating file and line number.
+    '''
+    print(filename + ": " + Colors.RED + "ERROR" +
+        Colors.ENDC + ": line %d (%s)" % (lineno, reason))
+
+
+def delete_rule(iptables, rule, filename, lineno):
+    '''
+    Removes an iptables rule
+    '''
+    cmd = iptables + " -D " + rule
+    ret = execute_cmd(cmd, filename, lineno)
+    if ret == 1:
+        reason = "cannot delete: " + iptables + " -I " + rule
+        print_error(reason, filename, lineno)
+        return -1
+
+    return 0
+
+
+def run_test(iptables, rule, rule_save, res, filename, lineno, netns):
+    '''
+    Executes an unit test. Returns the output of delete_rule().
+
+    Parameters:
+    :param  iptables: string with the iptables command to execute
+    :param rule: string with iptables arguments for the rule to test
+    :param rule_save: string to find the rule in the output of iptables -save
+    :param res: expected result of the rule. Valid values: "OK", "FAIL"
+    :param filename: name of the file tested (used for print_error purposes)
+    :param lineno: line number being tested (used for print_error purposes)
+    '''
+    ret = 0
+
+    cmd = iptables + " -A " + rule
+    if netns:
+            cmd = "ip netns exec ____iptables-container-test " + EXECUTEABLE + " " + cmd
+
+    ret = execute_cmd(cmd, filename, lineno)
+
+    #
+    # report failed test
+    #
+    if ret:
+        if res == "OK":
+            reason = "cannot load: " + cmd
+            print_error(reason, filename, lineno)
+            return -1
+        else:
+            # do not report this error
+            return 0
+    else:
+        if res == "FAIL":
+            reason = "should fail: " + cmd
+            print_error(reason, filename, lineno)
+            delete_rule(iptables, rule, filename, lineno)
+            return -1
+
+    matching = 0
+    splitted = iptables.split(" ")
+    if len(splitted) == 2:
+        if splitted[1] == '-4':
+            command = IPTABLES_SAVE
+        elif splitted[1] == '-6':
+            command = IP6TABLES_SAVE
+    elif len(splitted) == 1:
+        if splitted[0] == IPTABLES:
+            command = IPTABLES_SAVE
+        elif splitted[0] == IP6TABLES:
+            command = IP6TABLES_SAVE
+        elif splitted[0] == ARPTABLES:
+            command = ARPTABLES_SAVE
+        elif splitted[0] == EBTABLES:
+            command = EBTABLES_SAVE
+
+    command = EXECUTEABLE + " " + command
+
+    if netns:
+            command = "ip netns exec ____iptables-container-test " + command
+
+    args = splitted[1:]
+    proc = subprocess.Popen(command, shell=True,
+                            stdin=subprocess.PIPE,
+                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, err = proc.communicate()
+
+    #
+    # check for segfaults
+    #
+    if proc.returncode == -11:
+        reason = "iptables-save segfaults: " + cmd
+        print_error(reason, filename, lineno)
+        delete_rule(iptables, rule, filename, lineno)
+        return -1
+
+    # find the rule
+    matching = out.find(rule_save.encode('utf-8'))
+    if matching < 0:
+        reason = "cannot find: " + iptables + " -I " + rule
+        print_error(reason, filename, lineno)
+        delete_rule(iptables, rule, filename, lineno)
+        return -1
+
+    # Test "ip netns del NETNS" path with rules in place
+    if netns:
+        return 0
+
+    return delete_rule(iptables, rule, filename, lineno)
+
+def execute_cmd(cmd, filename, lineno):
+    '''
+    Executes a command, checking for segfaults and returning the command exit
+    code.
+
+    :param cmd: string with the command to be executed
+    :param filename: name of the file tested (used for print_error purposes)
+    :param lineno: line number being tested (used for print_error purposes)
+    '''
+    global log_file
+    if cmd.startswith('iptables ') or cmd.startswith('ip6tables ') or cmd.startswith('ebtables ') or cmd.startswith('arptables '):
+        cmd = EXECUTEABLE + " " + cmd
+
+    print("command: {}".format(cmd), file=log_file)
+    ret = subprocess.call(cmd, shell=True, universal_newlines=True,
+        stderr=subprocess.STDOUT, stdout=log_file)
+    log_file.flush()
+
+    # generic check for segfaults
+    if ret  == -11:
+        reason = "command segfaults: " + cmd
+        print_error(reason, filename, lineno)
+    return ret
+
+
+def run_test_file(filename, netns):
+    '''
+    Runs a test file
+
+    :param filename: name of the file with the test rules
+    '''
+    #
+    # if this is not a test file, skip.
+    #
+    if not filename.endswith(".t"):
+        return 0, 0
+
+    if "libipt_" in filename:
+        iptables = IPTABLES
+    elif "libip6t_" in filename:
+        iptables = IP6TABLES
+    elif "libxt_"  in filename:
+        iptables = IPTABLES
+    elif "libarpt_" in filename:
+        # only supported with nf_tables backend
+        if EXECUTEABLE != "xtables-nft-multi":
+           return 0, 0
+        iptables = ARPTABLES
+    elif "libebt_" in filename:
+        # only supported with nf_tables backend
+        if EXECUTEABLE != "xtables-nft-multi":
+           return 0, 0
+        iptables = EBTABLES
+    else:
+        # default to iptables if not known prefix
+        iptables = IPTABLES
+
+    f = open(filename)
+
+    tests = 0
+    passed = 0
+    table = ""
+    total_test_passed = True
+
+    if netns:
+        execute_cmd("ip netns add ____iptables-container-test", filename, 0)
+
+    for lineno, line in enumerate(f):
+        if line[0] == "#" or len(line.strip()) == 0:
+            continue
+
+        if line[0] == ":":
+            chain_array = line.rstrip()[1:].split(",")
+            continue
+
+        # external non-iptables invocation, executed as is.
+        if line[0] == "@":
+            external_cmd = line.rstrip()[1:]
+            if netns:
+                external_cmd = "ip netns exec ____iptables-container-test " + external_cmd
+            execute_cmd(external_cmd, filename, lineno)
+            continue
+
+        # external iptables invocation, executed as is.
+        if line[0] == "%":
+            external_cmd = line.rstrip()[1:]
+            if netns:
+                external_cmd = "ip netns exec ____iptables-container-test " + EXECUTEABLE + " " + external_cmd
+            execute_cmd(external_cmd, filename, lineno)
+            continue
+
+        if line[0] == "*":
+            table = line.rstrip()[1:]
+            continue
+
+        if len(chain_array) == 0:
+            print("broken test, missing chain, leaving")
+            sys.exit()
+
+        test_passed = True
+        tests += 1
+
+        for chain in chain_array:
+            item = line.split(";")
+            if table == "":
+                rule = chain + " " + item[0]
+            else:
+                rule = chain + " -t " + table + " " + item[0]
+
+            if item[1] == "=":
+                rule_save = chain + " " + item[0]
+            else:
+                rule_save = chain + " " + item[1]
+
+            res = item[2].rstrip()
+            ret = run_test(iptables, rule, rule_save,
+                           res, filename, lineno + 1, netns)
+
+            if ret < 0:
+                test_passed = False
+                total_test_passed = False
+                break
+
+        if test_passed:
+            passed += 1
+
+    if netns:
+        execute_cmd("ip netns del ____iptables-container-test", filename, 0)
+    if total_test_passed:
+        print(filename + ": " + Colors.GREEN + "OK" + Colors.ENDC)
+
+    f.close()
+    return tests, passed
+
+
+def show_missing():
+    '''
+    Show the list of missing test files
+    '''
+    file_list = os.listdir(EXTENSIONS_PATH)
+    testfiles = [i for i in file_list if i.endswith('.t')]
+    libfiles = [i for i in file_list
+                if i.startswith('lib') and i.endswith('.c')]
+
+    def test_name(x):
+        return x[0:-2] + '.t'
+    missing = [test_name(i) for i in libfiles
+               if not test_name(i) in testfiles]
+
+    print('\n'.join(missing))
+
+
+#
+# main
+#
+def main():
+    parser = argparse.ArgumentParser(description='Run iptables tests')
+    parser.add_argument('filename', nargs='*',
+                        metavar='path/to/file.t',
+                        help='Run only this test')
+    parser.add_argument('-H', '--host', action='store_true',
+                        help='Run tests against installed binaries')
+    parser.add_argument('-l', '--legacy', action='store_true',
+                        help='Test iptables-legacy')
+    parser.add_argument('-m', '--missing', action='store_true',
+                        help='Check for missing tests')
+    parser.add_argument('-n', '--nftables', action='store_true',
+                        help='Test iptables-over-nftables')
+    parser.add_argument('-N', '--netns', action='store_true',
+                        help='Test netnamespace path')
+    args = parser.parse_args()
+
+    #
+    # show list of missing test files
+    #
+    if args.missing:
+        show_missing()
+        return
+
+    global EXECUTEABLE
+    EXECUTEABLE = "xtables-legacy-multi"
+    if args.nftables:
+        EXECUTEABLE = "xtables-nft-multi"
+
+    if os.getuid() != 0:
+        print("You need to be root to run this, sorry")
+        return
+
+    if not args.host:
+        os.putenv("XTABLES_LIBDIR", os.path.abspath(EXTENSIONS_PATH))
+        os.putenv("PATH", "%s/iptables:%s" % (os.path.abspath(os.path.curdir),
+                                              os.getenv("PATH")))
+
+    test_files = 0
+    tests = 0
+    passed = 0
+
+    # setup global var log file
+    global log_file
+    try:
+        log_file = open(LOGFILE, 'w')
+    except IOError:
+        print("Couldn't open log file %s" % LOGFILE)
+        return
+
+    if args.filename:
+        file_list = args.filename
+    else:
+        file_list = [os.path.join(EXTENSIONS_PATH, i)
+                     for i in os.listdir(EXTENSIONS_PATH)
+                     if i.endswith('.t')]
+        file_list.sort()
+
+    if not args.netns:
+        try:
+            import unshare
+            unshare.unshare(unshare.CLONE_NEWNET)
+        except:
+            print("Cannot run in own namespace, connectivity might break")
+
+    for filename in file_list:
+        file_tests, file_passed = run_test_file(filename, args.netns)
+        if file_tests:
+            tests += file_tests
+            passed += file_passed
+            test_files += 1
+
+    print("%d test files, %d unit tests, %d passed" % (test_files, tests, passed))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/iptables/.gitignore b/iptables/.gitignore
new file mode 100644
index 0000000..cd7d87b
--- /dev/null
+++ b/iptables/.gitignore
@@ -0,0 +1,25 @@
+/ip6tables
+/ip6tables-save
+/ip6tables-restore
+/ip6tables-static
+/ip6tables-translate.8
+/ip6tables-restore-translate.8
+/iptables
+/iptables.8
+/iptables-extensions.8
+/iptables-extensions.8.tmpl
+/iptables-save
+/iptables-save.8
+/iptables-restore
+/iptables-restore.8
+/iptables-static
+/iptables-translate.8
+/iptables-restore-translate.8
+/iptables-xml
+/iptables-xml.1
+/xtables-multi
+/xtables-legacy-multi
+/xtables-nft-multi
+/xtables-monitor.8
+
+/xtables.pc
diff --git a/iptables/Android.bp b/iptables/Android.bp
new file mode 100644
index 0000000..2276702
--- /dev/null
+++ b/iptables/Android.bp
@@ -0,0 +1,83 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_iptables_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Artistic
+    //   SPDX-license-identifier-Artistic-2.0
+    //   SPDX-license-identifier-GPL
+    //   SPDX-license-identifier-GPL-2.0
+    //   SPDX-license-identifier-LGPL
+    default_applicable_licenses: ["external_iptables_license"],
+}
+
+cc_defaults {
+    name: "iptables_cmd_defaults",
+    defaults: ["iptables_defaults"],
+
+    cflags: [
+        "-Wno-missing-field-initializers",
+        "-Wno-parentheses-equality",
+
+        "-DNO_SHARED_LIBS=1",
+        "-DALL_INCLUSIVE",
+        "-DXTABLES_INTERNAL",
+    ],
+
+    header_libs: ["iptables_config_header"],
+
+    required: ["xtables.lock"],
+
+    srcs: [
+        "xtables-legacy-multi.c",
+        "iptables-xml.c",
+        "xshared.c",
+    ],
+
+    static_libs: [
+        "libext",
+        "libxtables",
+    ],
+}
+
+//----------------------------------------------------------------
+// The iptables lock file
+
+prebuilt_etc {
+    name: "xtables.lock",
+    src: "xtables.lock",
+}
+
+//----------------------------------------------------------------
+// iptables
+
+cc_binary {
+    name: "iptables",
+    defaults: ["iptables_cmd_defaults"],
+
+    srcs: [
+        "iptables-save.c",
+        "iptables-restore.c",
+        "iptables-standalone.c",
+        "iptables.c",
+        "ip6tables-standalone.c",
+        "ip6tables.c",
+    ],
+
+    static_libs: [
+        "libext4",
+        "libext6",
+        "libip4tc",
+        "libip6tc",
+    ],
+
+    symlinks: [
+        "iptables-save",
+        "iptables-restore",
+        "ip6tables",
+        "ip6tables-save",
+        "ip6tables-restore",
+    ],
+}
+
+//----------------------------------------------------------------
diff --git a/iptables/Android.mk b/iptables/Android.mk
new file mode 100644
index 0000000..cf61f03
--- /dev/null
+++ b/iptables/Android.mk
@@ -0,0 +1,79 @@
+LOCAL_PATH:= $(call my-dir)
+
+commonFlags:= \
+	-Wno-missing-field-initializers \
+	-Wno-sign-compare \
+	-Wno-pointer-arith \
+	-Wno-unused-parameter \
+	-Wno-parentheses-equality \
+	-Werror
+
+#----------------------------------------------------------------
+# The iptables lock file
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := xtables.lock
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT)/etc
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+
+include $(BUILD_PREBUILT)
+
+#----------------------------------------------------------------
+# iptables
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include/ \
+	$(LOCAL_PATH)/../
+
+LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
+LOCAL_CFLAGS+=-DALL_INCLUSIVE
+LOCAL_CFLAGS+=-DXTABLES_INTERNAL
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
+LOCAL_CFLAGS+=-D__ANDROID__
+LOCAL_CFLAGS += $(commonFlags)
+
+LOCAL_REQUIRED_MODULES := xtables.lock
+
+LOCAL_SRC_FILES:= \
+	xtables-legacy-multi.c iptables-xml.c xshared.c \
+	iptables-save.c iptables-restore.c \
+	iptables-standalone.c iptables.c \
+	ip6tables-standalone.c ip6tables.c
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE:=iptables
+
+LOCAL_STATIC_LIBRARIES := \
+	libext \
+	libext4 \
+	libext6 \
+	libip4tc \
+	libip6tc \
+	libxtables
+
+include $(BUILD_EXECUTABLE)
+
+IPTABLES_SUBCOMMANDS := \
+  iptables-restore \
+  iptables-save \
+  ip6tables \
+  ip6tables-restore \
+  ip6tables-save
+
+SYMLINKS := $(addprefix $(TARGET_OUT)/bin/,$(IPTABLES_SUBCOMMANDS))
+$(SYMLINKS): $(LOCAL_INSTALLED_MODULE) $(LOCAL_PATH)/Android.mk
+	@echo "Symlink: $@ -> iptables"
+	@mkdir -p $(dir $@)
+	@rm -rf $@
+	$(hide) ln -sf iptables $@
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := iptables_symlinks
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := iptables
+include $(BUILD_PHONY_PACKAGE)
+$(LOCAL_BUILT_MODULE): $(SYMLINKS)
diff --git a/iptables/Makefile.am b/iptables/Makefile.am
new file mode 100644
index 0000000..f789521
--- /dev/null
+++ b/iptables/Makefile.am
@@ -0,0 +1,140 @@
+# -*- Makefile -*-
+
+AM_CFLAGS        = ${regular_CFLAGS}
+AM_CPPFLAGS      = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include -I${top_srcdir} ${kinclude_CPPFLAGS} ${libmnl_CFLAGS} ${libnftnl_CFLAGS} ${libnetfilter_conntrack_CFLAGS}
+
+BUILT_SOURCES =
+
+xtables_legacy_multi_SOURCES  = xtables-legacy-multi.c iptables-xml.c
+xtables_legacy_multi_CFLAGS   = ${AM_CFLAGS}
+xtables_legacy_multi_LDADD    = ../extensions/libext.a
+if ENABLE_STATIC
+xtables_legacy_multi_CFLAGS  += -DALL_INCLUSIVE
+endif
+if ENABLE_IPV4
+xtables_legacy_multi_SOURCES += iptables-standalone.c iptables.c
+xtables_legacy_multi_CFLAGS  += -DENABLE_IPV4
+xtables_legacy_multi_LDADD   += ../libiptc/libip4tc.la ../extensions/libext4.a
+endif
+if ENABLE_IPV6
+xtables_legacy_multi_SOURCES += ip6tables-standalone.c ip6tables.c
+xtables_legacy_multi_CFLAGS  += -DENABLE_IPV6
+xtables_legacy_multi_LDADD   += ../libiptc/libip6tc.la ../extensions/libext6.a
+endif
+xtables_legacy_multi_SOURCES += xshared.c iptables-restore.c iptables-save.c
+xtables_legacy_multi_LDADD   += ../libxtables/libxtables.la -lm
+
+# iptables using nf_tables api
+if ENABLE_NFTABLES
+xtables_nft_multi_SOURCES  = xtables-nft-multi.c iptables-xml.c
+xtables_nft_multi_CFLAGS   = ${AM_CFLAGS}
+xtables_nft_multi_LDADD    = ../extensions/libext.a ../extensions/libext_ebt.a
+if ENABLE_STATIC
+xtables_nft_multi_CFLAGS  += -DALL_INCLUSIVE
+endif
+xtables_nft_multi_CFLAGS  += -DENABLE_NFTABLES -DENABLE_IPV4 -DENABLE_IPV6
+xtables_nft_multi_SOURCES += xtables-save.c xtables-restore.c \
+				xtables-standalone.c xtables.c nft.c \
+				nft-shared.c nft-ipv4.c nft-ipv6.c nft-arp.c \
+				xtables-monitor.c nft-cache.c \
+				xtables-arp-standalone.c xtables-arp.c \
+				nft-bridge.c nft-cmd.c nft-chain.c \
+				xtables-eb-standalone.c xtables-eb.c \
+				xtables-eb-translate.c \
+				xtables-translate.c
+xtables_nft_multi_LDADD   += ${libmnl_LIBS} ${libnftnl_LIBS} ${libnetfilter_conntrack_LIBS} ../extensions/libext4.a ../extensions/libext6.a ../extensions/libext_ebt.a ../extensions/libext_arpt.a
+xtables_nft_multi_SOURCES += xshared.c
+xtables_nft_multi_LDADD   += ../libxtables/libxtables.la -lm
+endif
+
+sbin_PROGRAMS    = xtables-legacy-multi
+if ENABLE_NFTABLES
+sbin_PROGRAMS	+= xtables-nft-multi
+endif
+man_MANS         = iptables.8 iptables-restore.8 iptables-save.8 \
+                   iptables-xml.1 ip6tables.8 ip6tables-restore.8 \
+                   ip6tables-save.8 iptables-extensions.8 \
+                   iptables-apply.8 ip6tables-apply.8
+
+sbin_SCRIPTS     = iptables-apply
+
+if ENABLE_NFTABLES
+man_MANS	+= xtables-nft.8 xtables-translate.8 xtables-legacy.8 \
+                   iptables-translate.8 ip6tables-translate.8 \
+		   iptables-restore-translate.8 ip6tables-restore-translate.8 \
+                   xtables-monitor.8 \
+                   arptables-nft.8 arptables-nft-restore.8 arptables-nft-save.8 \
+                   ebtables-nft.8
+endif
+CLEANFILES       = iptables.8 xtables-monitor.8 \
+		   iptables-xml.1 iptables-apply.8 \
+		   iptables-extensions.8 iptables-extensions.8.tmpl \
+		   iptables-restore.8 iptables-save.8 \
+		   iptables-restore-translate.8 ip6tables-restore-translate.8 \
+		   iptables-translate.8 ip6tables-translate.8
+
+vx_bin_links   = iptables-xml
+if ENABLE_IPV4
+v4_sbin_links  = iptables-legacy iptables-legacy-restore iptables-legacy-save \
+		 iptables iptables-restore iptables-save
+endif
+if ENABLE_IPV6
+v6_sbin_links  = ip6tables-legacy ip6tables-legacy-restore ip6tables-legacy-save \
+		 ip6tables ip6tables-restore ip6tables-save
+endif
+if ENABLE_NFTABLES
+x_sbin_links  = iptables-nft iptables-nft-restore iptables-nft-save \
+		ip6tables-nft ip6tables-nft-restore ip6tables-nft-save \
+		iptables-translate ip6tables-translate \
+		iptables-restore-translate ip6tables-restore-translate \
+		arptables-nft arptables \
+		arptables-nft-restore arptables-restore \
+		arptables-nft-save arptables-save \
+		ebtables-nft ebtables \
+		ebtables-nft-restore ebtables-restore \
+		ebtables-nft-save ebtables-save \
+		xtables-monitor
+endif
+
+iptables-extensions.8: iptables-extensions.8.tmpl ../extensions/matches.man ../extensions/targets.man
+	${AM_VERBOSE_GEN} sed \
+		-e '/@MATCH@/ r ../extensions/matches.man' \
+		-e '/@TARGET@/ r ../extensions/targets.man' $< >$@;
+
+iptables-translate.8 ip6tables-translate.8 iptables-restore-translate.8 ip6tables-restore-translate.8:
+	${AM_VERBOSE_GEN} echo '.so man8/xtables-translate.8' >$@
+
+pkgconfig_DATA = xtables.pc
+
+# Using if..fi avoids an ugly "error (ignored)" message :)
+install-exec-hook:
+	-if test -z "${DESTDIR}"; then /sbin/ldconfig; fi;
+	${INSTALL} -dm0755 "${DESTDIR}${bindir}";
+	for i in ${vx_bin_links}; do ${LN_S} -f "${sbindir}/xtables-legacy-multi" "${DESTDIR}${bindir}/$$i"; done;
+	for i in ${v4_sbin_links}; do ${LN_S} -f xtables-legacy-multi "${DESTDIR}${sbindir}/$$i"; done;
+	for i in ${v6_sbin_links}; do ${LN_S} -f xtables-legacy-multi "${DESTDIR}${sbindir}/$$i"; done;
+	for i in ${x_sbin_links}; do ${LN_S} -f xtables-nft-multi "${DESTDIR}${sbindir}/$$i"; done;
+	${LN_S} -f iptables-apply "${DESTDIR}${sbindir}/ip6tables-apply"
+
+uninstall-hook:
+	dir=${DESTDIR}${bindir}; { \
+		test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; \
+	} || { \
+		test -z "${vx_bin_links}" || ( \
+			cd "$$dir" && rm -f ${vx_bin_links} \
+		) \
+	}
+	dir=${DESTDIR}${sbindir}; { \
+		test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; \
+	} || { \
+		test -z "${v4_sbin_links}" || ( \
+			cd "$$dir" && rm -f ${v4_sbin_links} \
+		); \
+		test -z "${v6_sbin_links}" || ( \
+			cd "$$dir" && rm -f ${v6_sbin_links} \
+		); \
+		test -z "${x_sbin_links}" || ( \
+			cd "$$dir" && rm -f ${x_sbin_links} \
+		); \
+		( cd "$$dir" && rm -f ip6tables-apply ); \
+	}
diff --git a/iptables/NOTICE b/iptables/NOTICE
new file mode 120000
index 0000000..012065c
--- /dev/null
+++ b/iptables/NOTICE
@@ -0,0 +1 @@
+../COPYING
\ No newline at end of file
diff --git a/iptables/arptables-nft-restore.8 b/iptables/arptables-nft-restore.8
new file mode 100644
index 0000000..09d9082
--- /dev/null
+++ b/iptables/arptables-nft-restore.8
@@ -0,0 +1,39 @@
+.TH ARPTABLES-RESTORE 8 "March 2019" "" ""
+.\"
+.\" Man page written by Jesper Dangaard Brouer <brouer@redhat.com> based on a
+.\" Man page written by Harald Welte <laforge@gnumonks.org>
+.\" It is based on the iptables-restore man page.
+.\"
+.\"	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 2 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, write to the Free Software
+.\"	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+arptables-restore \- Restore ARP Tables (nft-based)
+.SH SYNOPSIS
+\fBarptables\-restore
+.SH DESCRIPTION
+.PP
+.B arptables-restore
+is used to restore ARP Tables from data specified on STDIN or
+via a file as first argument.
+Use I/O redirection provided by your shell to read from a file
+.TP
+.B arptables-restore
+flushes (deletes) all previous contents of the respective ARP Table.
+.SH AUTHOR
+Jesper Dangaard Brouer <brouer@redhat.com>
+.SH SEE ALSO
+\fBarptables\-save\fP(8), \fBarptables\fP(8)
+.PP
diff --git a/iptables/arptables-nft-save.8 b/iptables/arptables-nft-save.8
new file mode 100644
index 0000000..905e598
--- /dev/null
+++ b/iptables/arptables-nft-save.8
@@ -0,0 +1,47 @@
+.TH ARPTABLES-SAVE 8 "March 2019" "" ""
+.\"
+.\" Man page written by Jesper Dangaard Brouer <brouer@redhat.com> based on a
+.\" Man page written by Harald Welte <laforge@gnumonks.org>
+.\" It is based on the iptables-save man page.
+.\"
+.\"	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 2 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, write to the Free Software
+.\"	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+arptables-save \- dump arptables rules to stdout (nft-based)
+.SH SYNOPSIS
+\fBarptables\-save\fP [\fB\-M\fP \fImodprobe\fP] [\fB\-c\fP]
+.P
+\fBarptables\-save\fP [\fB\-V\fP]
+.SH DESCRIPTION
+.PP
+.B arptables-save
+is used to dump the contents of an ARP Table in easily parseable format
+to STDOUT. Use I/O-redirection provided by your shell to write to a file.
+.TP
+\fB\-M\fR, \fB\-\-modprobe\fR \fImodprobe_program\fP
+Specify the path to the modprobe program. By default, arptables-save will
+inspect /proc/sys/kernel/modprobe to determine the executable's path.
+.TP
+\fB\-c\fR, \fB\-\-counters\fR
+Include the current values of all packet and byte counters in the output.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Print version information and exit.
+.SH AUTHOR
+Jesper Dangaard Brouer <brouer@redhat.com>
+.SH SEE ALSO
+\fBarptables\-restore\fP(8), \fBarptables\fP(8)
+.PP
diff --git a/iptables/arptables-nft.8 b/iptables/arptables-nft.8
new file mode 100644
index 0000000..ea31e08
--- /dev/null
+++ b/iptables/arptables-nft.8
@@ -0,0 +1,348 @@
+.TH ARPTABLES 8  "March 2019"
+.\"
+.\" Man page originally written by Jochen Friedrich <jochen@scram.de>,
+.\" maintained by Bart De Schuymer.
+.\" It is based on the iptables man page.
+.\"
+.\" Iptables page by Herve Eychenne March 2000.
+.\"
+.\"     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 2 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, write to the Free Software
+.\"     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+arptables \- ARP table administration (nft-based)
+.SH SYNOPSIS
+.BR "arptables " [ "-t table" ] " -" [ AD ] " chain rule-specification " [ options ]
+.br
+.BR "arptables " [ "-t table" ] " -" [ RI ] " chain rulenum rule-specification " [ options ]
+.br
+.BR "arptables " [ "-t table" ] " -D chain rulenum " [ options ]
+.br
+.BR "arptables " [ "-t table" ] " -" [ "LFZ" ] " " [ chain ] " " [ options ]
+.br
+.BR "arptables " [ "-t table" ] " -" [ "NX" ] " chain"
+.br
+.BR "arptables " [ "-t table" ] " -E old-chain-name new-chain-name"
+.br
+.BR "arptables " [ "-t table" ] " -P chain target " [ options ]
+
+.SH DESCRIPTION
+.B arptables
+is a user space tool, it is used to set up and maintain the
+tables of ARP rules in the Linux kernel. These rules inspect
+the ARP frames which they see.
+.B arptables
+is analogous to the
+.B iptables
+user space tool, but
+.B arptables
+is less complicated.
+
+.SS CHAINS
+The kernel table is used to divide functionality into
+different sets of rules. Each set of rules is called a chain.
+Each chain is an ordered list of rules that can match ARP frames. If a
+rule matches an ARP frame, then a processing specification tells
+what to do with that matching frame. The processing specification is
+called a 'target'. However, if the frame does not match the current
+rule in the chain, then the next rule in the chain is examined and so forth.
+The user can create new (user-defined) chains which can be used as the 'target' of a rule.
+
+.SS TARGETS
+A firewall rule specifies criteria for an ARP frame and a frame
+processing specification called a target.  When a frame matches a rule,
+then the next action performed by the kernel is specified by the target.
+The target can be one of these values:
+.IR ACCEPT ,
+.IR DROP ,
+.IR CONTINUE ,
+.IR RETURN ,
+an 'extension' (see below) or a user-defined chain.
+.PP
+.I ACCEPT
+means to let the frame through.
+.I DROP
+means the frame has to be dropped.
+.I CONTINUE
+means the next rule has to be checked. This can be handy to know how many
+frames pass a certain point in the chain or to log those frames.
+.I RETURN
+means stop traversing this chain and resume at the next rule in the
+previous (calling) chain.
+For the extension targets please see the
+.B "TARGET EXTENSIONS"
+section of this man page.
+.SS TABLES
+There is only one ARP table in the Linux
+kernel.  The table is
+.BR filter.
+You can drop the '-t filter' argument to the arptables command.
+The -t argument must be the
+first argument on the arptables command line, if used.
+.TP
+.B "-t, --table"
+.br
+.BR filter ,
+is the only table and contains two built-in chains:
+.B INPUT 
+(for frames destined for the host) and
+.B OUTPUT 
+(for locally-generated frames).
+.br
+.br
+.SH ARPTABLES COMMAND LINE ARGUMENTS
+After the initial arptables command line argument, the remaining
+arguments can be divided into several different groups.  These groups
+are commands, miscellaneous commands, rule-specifications, match-extensions,
+and watcher-extensions.
+.SS COMMANDS
+The arptables command arguments specify the actions to perform on the table
+defined with the -t argument.  If you do not use the -t argument to name
+a table, the commands apply to the default filter table.
+With the exception of the
+.B "-Z"
+command, only one command may be used on the command line at a time.
+.TP
+.B "-A, --append"
+Append a rule to the end of the selected chain.
+.TP
+.B "-D, --delete"
+Delete the specified rule from the selected chain. There are two ways to
+use this command. The first is by specifying an interval of rule numbers
+to delete, syntax: start_nr[:end_nr]. Using negative numbers is allowed, for more
+details about using negative numbers, see the -I command. The second usage is by
+specifying the complete rule as it would have been specified when it was added.
+.TP
+.B "-I, --insert"
+Insert the specified rule into the selected chain at the specified rule number.
+If the current number of rules equals N, then the specified number can be
+between -N and N+1. For a positive number i, it holds that i and i-N-1 specify the
+same place in the chain where the rule should be inserted. The number 0 specifies
+the place past the last rule in the chain and using this number is therefore
+equivalent with using the -A command.
+.TP
+.B "-R, --replace"
+Replaces the specified rule into the selected chain at the specified rule number.
+If the current number of rules equals N, then the specified number can be
+between 1 and N. i specifies the place in the chain where the rule should be replaced.
+.TP
+.B "-P, --policy"
+Set the policy for the chain to the given target. The policy can be
+.BR ACCEPT ", " DROP " or " RETURN .
+.TP
+.B "-F, --flush"
+Flush the selected chain. If no chain is selected, then every chain will be
+flushed. Flushing the chain does not change the policy of the
+chain, however.
+.TP
+.B "-Z, --zero"
+Set the counters of the selected chain to zero. If no chain is selected, all the counters
+are set to zero. The
+.B "-Z"
+command can be used in conjunction with the 
+.B "-L"
+command.
+When both the
+.B "-Z"
+and
+.B "-L"
+commands are used together in this way, the rule counters are printed on the screen
+before they are set to zero.
+.TP
+.B "-L, --list"
+List all rules in the selected chain. If no chain is selected, all chains
+are listed.
+.TP
+.B "-N, --new-chain"
+Create a new user-defined chain with the given name. The number of
+user-defined chains is unlimited. A user-defined chain name has maximum
+length of 31 characters.
+.TP
+.B "-X, --delete-chain"
+Delete the specified user-defined chain. There must be no remaining references
+to the specified chain, otherwise
+.B arptables
+will refuse to delete it. If no chain is specified, all user-defined
+chains that aren't referenced will be removed.
+.TP
+.B "-E, --rename-chain"
+Rename the specified chain to a new name.  Besides renaming a user-defined
+chain, you may rename a standard chain name to a name that suits your
+taste. For example, if you like PREBRIDGING more than PREROUTING,
+then you can use the -E command to rename the PREROUTING chain. If you do
+rename one of the standard
+.B arptables
+chain names, please be sure to mention
+this fact should you post a question on the
+.B arptables
+mailing lists.
+It would be wise to use the standard name in your post. Renaming a standard
+.B arptables
+chain in this fashion has no effect on the structure or function
+of the
+.B arptables
+kernel table.
+
+.SS MISCELLANOUS COMMANDS
+.TP
+.B "-V, --version"
+Show the version of the arptables userspace program.
+.TP
+.B "-h, --help"
+Give a brief description of the command syntax.
+.TP
+.BR "-j, --jump " "\fItarget\fP"
+The target of the rule. This is one of the following values:
+.BR ACCEPT ,
+.BR DROP ,
+.BR CONTINUE ,
+.BR RETURN ,
+a target extension (see
+.BR "TARGET EXTENSIONS" ")"
+or a user-defined chain name.
+.TP
+.BI "-c, --set-counters " "PKTS BYTES"
+This enables the administrator to initialize the packet and byte
+counters of a rule (during
+.B INSERT,
+.B APPEND,
+.B REPLACE
+operations).
+
+.SS RULE-SPECIFICATIONS
+The following command line arguments make up a rule specification (as used 
+in the add and delete commands). A "!" option before the specification 
+inverts the test for that specification. Apart from these standard rule 
+specifications there are some other command line arguments of interest.
+.TP
+.BR "-s, --source-ip " "[!] \fIaddress\fP[/\fImask]\fP"
+The Source IP specification.
+.TP 
+.BR "-d, --destination-ip " "[!] \fIaddress\fP[/\fImask]\fP"
+The Destination IP specification.
+.TP 
+.BR "--source-mac " "[!] \fIaddress\fP[/\fImask\fP]"
+The source mac address. Both mask and address are written as 6 hexadecimal
+numbers separated by colons.
+.TP
+.BR "--destination-mac " "[!] \fIaddress\fP[/\fImask\fP]"
+The destination mac address. Both mask and address are written as 6 hexadecimal
+numbers separated by colons.
+.TP 
+.BR "-i, --in-interface " "[!] \fIname\fP"
+The interface via which a frame is received (for the
+.B INPUT
+chain). The flag
+.B --in-if
+is an alias for this option.
+.TP
+.BR "-o, --out-interface " "[!] \fIname\fP"
+The interface via which a frame is going to be sent (for the
+.B OUTPUT
+chain). The flag
+.B --out-if
+is an alias for this option.
+.TP
+.BR "-l, --h-length " "\fIlength\fP[/\fImask\fP]"
+The hardware length (nr of bytes)
+.TP
+.BR "--opcode " "\fIcode\fP[/\fImask\fP]
+The operation code (2 bytes). Available values are:
+.BR 1 = Request
+.BR 2 = Reply
+.BR 3 = Request_Reverse
+.BR 4 = Reply_Reverse
+.BR 5 = DRARP_Request
+.BR 6 = DRARP_Reply
+.BR 7 = DRARP_Error
+.BR 8 = InARP_Request
+.BR 9 = ARP_NAK .
+.TP
+.BR "--h-type " "\fItype\fP[/\fImask\fP]"
+The hardware type (2 bytes, hexadecimal). Available values are:
+.BR 1 = Ethernet .
+.TP
+.BR "--proto-type " "\fItype\fP[/\fImask\fP]"
+The protocol type (2 bytes). Available values are:
+.BR 0x800 = IPv4 .
+
+.SS TARGET-EXTENSIONS
+.B arptables
+extensions are precompiled into the userspace tool. So there is no need
+to explicitly load them with a -m option like in
+.BR iptables .
+However, these
+extensions deal with functionality supported by supplemental kernel modules.
+.SS mangle
+.TP
+.BR "--mangle-ip-s IP address"
+Mangles Source IP Address to given value.
+.TP
+.BR "--mangle-ip-d IP address"
+Mangles Destination IP Address to given value.
+.TP
+.BR "--mangle-mac-s MAC address"
+Mangles Source MAC Address to given value.
+.TP
+.BR "--mangle-mac-d MAC address"
+Mangles Destination MAC Address to given value.
+.TP
+.BR "--mangle-target target "
+Target of ARP mangle operation
+.BR "" ( DROP ", " CONTINUE " or " ACCEPT " -- default is " ACCEPT ).
+.SS CLASSIFY
+This  module  allows you to set the skb->priority value (and thus clas-
+sify the packet into a specific CBQ class).
+
+.TP
+.BR "--set-class major:minor"
+
+Set the major and minor  class  value.  The  values  are  always
+interpreted as hexadecimal even if no 0x prefix is given.
+
+.SS MARK
+This  module  allows you to set the skb->mark value (and thus classify
+the packet by the mark in u32)
+
+.TP
+.BR "--set-mark mark"
+Set the mark value. The  values  are  always
+interpreted as hexadecimal even if no 0x prefix is given
+
+.TP
+.BR "--and-mark mark"
+Binary AND the mark with bits.
+
+.TP
+.BR "--or-mark mark"
+Binary OR the mark with bits.
+
+.SH NOTES
+In this nft-based version of
+.BR arptables ,
+support for
+.B FORWARD
+chain has not been implemented. Since ARP packets are "forwarded" only by Linux
+bridges, the same may be achieved using
+.B FORWARD
+chain in
+.BR ebtables .
+
+.SH MAILINGLISTS
+.BR "" "See " http://netfilter.org/mailinglists.html
+.SH SEE ALSO
+.BR xtables-nft "(8), " iptables "(8), " ebtables "(8), " ip (8)
+.PP
+.BR "" "See " https://wiki.nftables.org
diff --git a/iptables/ebtables-nft.8 b/iptables/ebtables-nft.8
new file mode 100644
index 0000000..1fa5ad9
--- /dev/null
+++ b/iptables/ebtables-nft.8
@@ -0,0 +1,1116 @@
+.TH EBTABLES 8  "December 2011"
+.\"
+.\" Man page written by Bart De Schuymer <bdschuym@pandora.be>
+.\" It is based on the iptables man page.
+.\"
+.\" The man page was edited, February 25th 2003, by 
+.\"      Greg Morgan <" dr_kludge_at_users_sourceforge_net >
+.\"
+.\" Iptables page by Herve Eychenne March 2000.
+.\"
+.\"     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 2 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, write to the Free Software
+.\"     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"     
+.\"
+.SH NAME
+ebtables \- Ethernet bridge frame table administration (nft-based)
+.SH SYNOPSIS
+.BR "ebtables " [ -t " table ] " - [ ACDI "] chain rule specification [match extensions] [watcher extensions] target"
+.br
+.BR "ebtables " [ -t " table ] " -P " chain " ACCEPT " | " DROP " | " RETURN
+.br
+.BR "ebtables " [ -t " table ] " -F " [chain]"
+.br
+.BR "ebtables " [ -t " table ] " -Z " [chain]"
+.br
+.BR "ebtables " [ -t " table ] " -L " [" -Z "] [chain] [ [" --Ln "] | [" --Lx "] ] [" --Lc "] [" --Lmac2 ]
+.br
+.BR "ebtables " [ -t " table ] " -N " chain [" "-P ACCEPT " | " DROP " | " RETURN" ]
+.br
+.BR "ebtables " [ -t " table ] " -X " [chain]"
+.br
+.BR "ebtables " [ -t " table ] " -E " old-chain-name new-chain-name"
+.br
+.BR "ebtables " [ -t " table ] " --init-table
+.br
+.BR "ebtables " [ -t " table ] [" --atomic-file " file] " --atomic-commit
+.br
+.BR "ebtables " [ -t " table ] [" --atomic-file " file] " --atomic-init
+.br
+.BR "ebtables " [ -t " table ] [" --atomic-file " file] " --atomic-save
+.br
+
+.SH DESCRIPTION
+.B ebtables
+is an application program used to set up and maintain the
+tables of rules (inside the Linux kernel) that inspect
+Ethernet frames.
+It is analogous to the
+.B iptables
+application, but less complicated, due to the fact that the Ethernet protocol
+is much simpler than the IP protocol.
+.SS CHAINS
+There are two ebtables tables with built-in chains in the
+Linux kernel. These tables are used to divide functionality into
+different sets of rules. Each set of rules is called a chain.
+Each chain is an ordered list of rules that can match Ethernet frames. If a
+rule matches an Ethernet frame, then a processing specification tells
+what to do with that matching frame. The processing specification is
+called a 'target'. However, if the frame does not match the current
+rule in the chain, then the next rule in the chain is examined and so forth.
+The user can create new (user-defined) chains that can be used as the 'target'
+of a rule. User-defined chains are very useful to get better performance
+over the linear traversal of the rules and are also essential for structuring
+the filtering rules into well-organized and maintainable sets of rules.
+.SS TARGETS
+A firewall rule specifies criteria for an Ethernet frame and a frame
+processing specification called a target.  When a frame matches a rule,
+then the next action performed by the kernel is specified by the target.
+The target can be one of these values:
+.BR ACCEPT ,
+.BR DROP ,
+.BR CONTINUE ,
+.BR RETURN ,
+an 'extension' (see below) or a jump to a user-defined chain.
+.PP
+.B ACCEPT
+means to let the frame through.
+.B DROP
+means the frame has to be dropped.
+.B CONTINUE
+means the next rule has to be checked. This can be handy, f.e., to know how many
+frames pass a certain point in the chain, to log those frames or to apply multiple
+targets on a frame.
+.B RETURN
+means stop traversing this chain and resume at the next rule in the
+previous (calling) chain.
+For the extension targets please refer to the
+.B "TARGET EXTENSIONS"
+section of this man page.
+.SS TABLES
+As stated earlier, there are two ebtables tables in the Linux
+kernel.  The table names are
+.BR filter " and " nat .
+Of these two tables,
+the filter table is the default table that the command operates on.
+If you are working with the filter table, then you can drop the '-t filter'
+argument to the ebtables command.  However, you will need to provide
+the -t argument for
+.B nat
+table.  Moreover, the -t argument must be the
+first argument on the ebtables command line, if used. 
+.TP
+.B "-t, --table"
+.br
+.B filter
+is the default table and contains three built-in chains:
+.B INPUT 
+(for frames destined for the bridge itself, on the level of the MAC destination address), 
+.B OUTPUT 
+(for locally-generated or (b)routed frames) and
+.B FORWARD 
+(for frames being forwarded by the bridge).
+.br
+.br
+.B nat
+is mostly used to change the mac addresses and contains three built-in chains:
+.B PREROUTING 
+(for altering frames as soon as they come in), 
+.B OUTPUT 
+(for altering locally generated or (b)routed frames before they are bridged) and 
+.B POSTROUTING
+(for altering frames as they are about to go out). A small note on the naming
+of chains PREROUTING and POSTROUTING: it would be more accurate to call them
+PREFORWARDING and POSTFORWARDING, but for all those who come from the
+iptables world to ebtables it is easier to have the same names. Note that you
+can change the name
+.BR "" ( -E )
+if you don't like the default.
+.SH EBTABLES COMMAND LINE ARGUMENTS
+After the initial ebtables '-t table' command line argument, the remaining
+arguments can be divided into several groups.  These groups
+are commands, miscellaneous commands, rule specifications, match extensions,
+watcher extensions and target extensions.
+.SS COMMANDS
+The ebtables command arguments specify the actions to perform on the table
+defined with the -t argument.  If you do not use the -t argument to name
+a table, the commands apply to the default filter table.
+Only one command may be used on the command line at a time, except when
+the commands
+.BR -L " and " -Z
+are combined, the commands
+.BR -N " and " -P
+are combined, or when
+.B --atomic-file
+is used.
+.TP
+.B "-A, --append"
+Append a rule to the end of the selected chain.
+.TP
+.B "-D, --delete"
+Delete the specified rule or rules from the selected chain. There are two ways to
+use this command. The first is by specifying an interval of rule numbers
+to delete (directly after
+.BR -D ).
+Syntax: \fIstart_nr\fP[\fI:end_nr\fP] (use
+.B -L --Ln
+to list the rules with their rule number). When \fIend_nr\fP is omitted, all rules starting
+from \fIstart_nr\fP are deleted. Using negative numbers is allowed, for more
+details about using negative numbers, see the
+.B -I
+command. The second usage is by
+specifying the complete rule as it would have been specified when it was added. Only
+the first encountered rule that is the same as this specified rule, in other
+words the matching rule with the lowest (positive) rule number, is deleted.
+.TP
+.B "-C, --change-counters"
+Change the counters of the specified rule or rules from the selected chain. There are two ways to
+use this command. The first is by specifying an interval of rule numbers
+to do the changes on (directly after
+.BR -C ).
+Syntax: \fIstart_nr\fP[\fI:end_nr\fP] (use
+.B -L --Ln
+to list the rules with their rule number). The details are the same as for the
+.BR -D " command. The second usage is by"
+specifying the complete rule as it would have been specified when it was added. Only
+the counters of the first encountered rule that is the same as this specified rule, in other
+words the matching rule with the lowest (positive) rule number, are changed.
+In the first usage, the counters are specified directly after the interval specification,
+in the second usage directly after
+.BR -C .
+First the packet counter is specified, then the byte counter. If the specified counters start
+with a '+', the counter values are added to the respective current counter values.
+If the specified counters start with a '-', the counter values are decreased from the respective
+current counter values. No bounds checking is done. If the counters don't start with '+' or '-',
+the current counters are changed to the specified counters.
+.TP
+.B "-I, --insert"
+Insert the specified rule into the selected chain at the specified rule number. If the
+rule number is not specified, the rule is added at the head of the chain.
+If the current number of rules equals
+.IR N ,
+then the specified number can be
+between
+.IR -N " and " N+1 .
+For a positive number
+.IR i ,
+it holds that
+.IR i " and " i-N-1
+specify the same place in the chain where the rule should be inserted. The rule number
+0 specifies the place past the last rule in the chain and using this number is therefore
+equivalent to using the
+.BR -A " command."
+Rule numbers structly smaller than 0 can be useful when more than one rule needs to be inserted
+in a chain.
+.TP
+.B "-P, --policy"
+Set the policy for the chain to the given target. The policy can be
+.BR ACCEPT ", " DROP " or " RETURN .
+.TP
+.B "-F, --flush"
+Flush the selected chain. If no chain is selected, then every chain will be
+flushed. Flushing a chain does not change the policy of the
+chain, however.
+.TP
+.B "-Z, --zero"
+Set the counters of the selected chain to zero. If no chain is selected, all the counters
+are set to zero. The
+.B "-Z"
+command can be used in conjunction with the 
+.B "-L"
+command.
+When both the
+.B "-Z"
+and
+.B "-L"
+commands are used together in this way, the rule counters are printed on the screen
+before they are set to zero.
+.TP
+.B "-L, --list"
+List all rules in the selected chain. If no chain is selected, all chains
+are listed.
+.br
+The following options change the output of the
+.B "-L"
+command.
+.br
+.B "--Ln"
+.br
+Places the rule number in front of every rule. This option is incompatible with the
+.BR --Lx " option."
+.br
+.B "--Lc"
+.br
+Shows the counters at the end of each rule displayed by the
+.B "-L"
+command. Both a frame counter (pcnt) and a byte counter (bcnt) are displayed.
+The frame counter shows how many frames have matched the specific rule, the byte
+counter shows the sum of the frame sizes of these matching frames. Using this option
+.BR "" "in combination with the " --Lx " option causes the counters to be written out"
+.BR "" "in the '" -c " <pcnt> <bcnt>' option format."
+.br
+.B "--Lx"
+.br
+Changes the output so that it produces a set of ebtables commands that construct
+the contents of the chain, when specified.
+If no chain is specified, ebtables commands to construct the contents of the
+table are given, including commands for creating the user-defined chains (if any).
+You can use this set of commands in an ebtables boot or reload
+script.  For example the output could be used at system startup.
+The 
+.B "--Lx"
+option is incompatible with the
+.B "--Ln"
+listing option. Using the
+.BR --Lx " option together with the " --Lc " option will cause the counters to be written out"
+.BR "" "in the '" -c " <pcnt> <bcnt>' option format."
+.br
+.B "--Lmac2"
+.br
+Shows all MAC addresses with the same length, adding leading zeroes
+if necessary. The default representation omits leading zeroes in the addresses.
+.TP
+.B "-N, --new-chain"
+Create a new user-defined chain with the given name. The number of
+user-defined chains is limited only by the number of possible chain names.
+A user-defined chain name has a maximum
+length of 31 characters. The standard policy of the user-defined chain is
+ACCEPT. The policy of the new chain can be initialized to a different standard
+target by using the
+.B -P
+command together with the
+.B -N
+command. In this case, the chain name does not have to be specified for the
+.B -P
+command.
+.TP
+.B "-X, --delete-chain"
+Delete the specified user-defined chain. There must be no remaining references (jumps)
+to the specified chain, otherwise ebtables will refuse to delete it. If no chain is
+specified, all user-defined chains that aren't referenced will be removed.
+.TP
+.B "-E, --rename-chain"
+Rename the specified chain to a new name.  Besides renaming a user-defined
+chain, you can rename a standard chain to a name that suits your
+taste. For example, if you like PREFORWARDING more than PREROUTING,
+then you can use the -E command to rename the PREROUTING chain. If you do
+rename one of the standard ebtables chain names, please be sure to mention
+this fact should you post a question on the ebtables mailing lists.
+It would be wise to use the standard name in your post. Renaming a standard
+ebtables chain in this fashion has no effect on the structure or functioning
+of the ebtables kernel table.
+.TP
+.B "--init-table"
+Replace the current table data by the initial table data.
+.TP
+.B "--atomic-init"
+Copy the kernel's initial data of the table to the specified
+file. This can be used as the first action, after which rules are added
+to the file. The file can be specified using the
+.B --atomic-file
+command or through the
+.IR EBTABLES_ATOMIC_FILE " environment variable."
+.TP
+.B "--atomic-save"
+Copy the kernel's current data of the table to the specified
+file. This can be used as the first action, after which rules are added
+to the file. The file can be specified using the
+.B --atomic-file
+command or through the
+.IR EBTABLES_ATOMIC_FILE " environment variable."
+.TP
+.B "--atomic-commit"
+Replace the kernel table data with the data contained in the specified
+file. This is a useful command that allows you to load all your rules of a
+certain table into the kernel at once, saving the kernel a lot of precious
+time and allowing atomic updates of the tables. The file which contains
+the table data is constructed by using either the
+.B "--atomic-init"
+or the
+.B "--atomic-save"
+command to generate a starting file. After that, using the
+.B "--atomic-file"
+command when constructing rules or setting the
+.IR EBTABLES_ATOMIC_FILE " environment variable"
+allows you to extend the file and build the complete table before
+committing it to the kernel. This command can be very useful in boot scripts
+to populate the ebtables tables in a fast way.
+.SS MISCELLANOUS COMMANDS
+.TP
+.B "-V, --version"
+Show the version of the ebtables userspace program.
+.TP
+.BR "-h, --help " "[\fIlist of module names\fP]"
+Give a brief description of the command syntax. Here you can also specify
+names of extensions and ebtables will try to write help about those
+extensions. E.g.
+.IR "ebtables -h snat log ip arp" .
+Specify
+.I list_extensions
+to list all extensions supported by the userspace
+utility.
+.TP
+.BR "-j, --jump " "\fItarget\fP"
+The target of the rule. This is one of the following values:
+.BR ACCEPT ,
+.BR DROP ,
+.BR CONTINUE ,
+.BR RETURN ,
+a target extension (see
+.BR "TARGET EXTENSIONS" ")"
+or a user-defined chain name.
+.TP
+.B --atomic-file "\fIfile\fP"
+Let the command operate on the specified
+.IR file .
+The data of the table to
+operate on will be extracted from the file and the result of the operation
+will be saved back into the file. If specified, this option should come
+before the command specification. An alternative that should be preferred,
+is setting the
+.IR EBTABLES_ATOMIC_FILE " environment variable."
+.TP
+.B -M, --modprobe "\fIprogram\fP"
+When talking to the kernel, use this
+.I program
+to try to automatically load missing kernel modules.
+.TP
+.B --concurrent
+Use a file lock to support concurrent scripts updating the ebtables kernel tables.
+
+.SS
+RULE SPECIFICATIONS
+The following command line arguments make up a rule specification (as used 
+in the add and delete commands). A "!" option before the specification 
+inverts the test for that specification. Apart from these standard rule 
+specifications there are some other command line arguments of interest.
+See both the 
+.BR "MATCH EXTENSIONS" 
+and the
+.BR "WATCHER EXTENSIONS" 
+below.
+.TP
+.BR "-p, --protocol " "[!] \fIprotocol\fP"
+The protocol that was responsible for creating the frame. This can be a
+hexadecimal number, above 
+.IR 0x0600 ,
+a name (e.g.
+.I ARP
+) or
+.BR LENGTH .
+The protocol field of the Ethernet frame can be used to denote the
+length of the header (802.2/802.3 networks). When the value of that field is
+below or equals
+.IR 0x0600 ,
+the value equals the size of the header and shouldn't be used as a
+protocol number. Instead, all frames where the protocol field is used as
+the length field are assumed to be of the same 'protocol'. The protocol
+name used in ebtables for these frames is
+.BR LENGTH .
+.br
+The file
+.B /etc/ethertypes
+can be used to show readable
+characters instead of hexadecimal numbers for the protocols. For example,
+.I 0x0800
+will be represented by 
+.IR IPV4 .
+The use of this file is not case sensitive. 
+See that file for more information. The flag 
+.B --proto
+is an alias for this option.
+.TP 
+.BR "-i, --in-interface " "[!] \fIname\fP"
+The interface (bridge port) via which a frame is received (this option is useful in the
+.BR INPUT ,
+.BR FORWARD ,
+.BR PREROUTING " and " BROUTING
+chains). If the interface name ends with '+', then
+any interface name that begins with this name (disregarding '+') will match.
+The flag
+.B --in-if
+is an alias for this option.
+.TP
+.BR "--logical-in " "[!] \fIname\fP"
+The (logical) bridge interface via which a frame is received (this option is useful in the
+.BR INPUT ,
+.BR FORWARD ,
+.BR PREROUTING " and " BROUTING
+chains).
+If the interface name ends with '+', then
+any interface name that begins with this name (disregarding '+') will match.
+.TP
+.BR "-o, --out-interface " "[!] \fIname\fP"
+The interface (bridge port) via which a frame is going to be sent (this option is useful in the
+.BR OUTPUT ,
+.B FORWARD
+and
+.B POSTROUTING
+chains). If the interface name ends with '+', then
+any interface name that begins with this name (disregarding '+') will match.
+The flag
+.B --out-if
+is an alias for this option.
+.TP
+.BR "--logical-out " "[!] \fIname\fP"
+The (logical) bridge interface via which a frame is going to be sent (this option
+is useful in the
+.BR OUTPUT ,
+.B FORWARD
+and
+.B POSTROUTING
+chains).
+If the interface name ends with '+', then
+any interface name that begins with this name (disregarding '+') will match.
+.TP
+.BR "-s, --source " "[!] \fIaddress\fP[/\fImask\fP]"
+The source MAC address. Both mask and address are written as 6 hexadecimal
+numbers separated by colons. Alternatively one can specify Unicast,
+Multicast, Broadcast or BGA (Bridge Group Address):
+.br
+.IR "Unicast" "=00:00:00:00:00:00/01:00:00:00:00:00,"
+.IR "Multicast" "=01:00:00:00:00:00/01:00:00:00:00:00,"
+.IR "Broadcast" "=ff:ff:ff:ff:ff:ff/ff:ff:ff:ff:ff:ff or"
+.IR "BGA" "=01:80:c2:00:00:00/ff:ff:ff:ff:ff:ff."
+Note that a broadcast
+address will also match the multicast specification. The flag
+.B --src
+is an alias for this option.
+.TP
+.BR "-d, --destination " "[!] \fIaddress\fP[/\fImask\fP]"
+The destination MAC address. See
+.B -s
+(above) for more details on MAC addresses. The flag
+.B --dst
+is an alias for this option.
+.TP
+.BR "-c, --set-counter " "\fIpcnt bcnt\fP"
+If used with
+.BR -A " or " -I ", then the packet and byte counters of the new rule will be set to
+.IR pcnt ", resp. " bcnt ".
+If used with the
+.BR -C " or " -D " commands, only rules with a packet and byte count equal to"
+.IR pcnt ", resp. " bcnt " will match."
+
+.SS MATCH EXTENSIONS
+Ebtables extensions are dynamically loaded into the userspace tool,
+there is therefore no need to explicitly load them with a
+-m option like is done in iptables.
+These extensions deal with functionality supported by kernel modules supplemental to
+the core ebtables code.
+.SS 802_3
+Specify 802.3 DSAP/SSAP fields or SNAP type.  The protocol must be specified as
+.IR "LENGTH " "(see the option " " -p " above).
+.TP
+.BR "--802_3-sap " "[!] \fIsap\fP"
+DSAP and SSAP are two one byte 802.3 fields.  The bytes are always
+equal, so only one byte (hexadecimal) is needed as an argument.
+.TP
+.BR "--802_3-type " "[!] \fItype\fP"
+If the 802.3 DSAP and SSAP values are 0xaa then the SNAP type field must
+be consulted to determine the payload protocol.  This is a two byte
+(hexadecimal) argument.  Only 802.3 frames with DSAP/SSAP 0xaa are
+checked for type.
+.SS among
+Match a MAC address or MAC/IP address pair versus a list of MAC addresses
+and MAC/IP address pairs.
+A list entry has the following format:
+.IR xx:xx:xx:xx:xx:xx[=ip.ip.ip.ip][,] ". Multiple"
+list entries are separated by a comma, specifying an IP address corresponding to
+the MAC address is optional. Multiple MAC/IP address pairs with the same MAC address
+but different IP address (and vice versa) can be specified. If the MAC address doesn't
+match any entry from the list, the frame doesn't match the rule (unless "!" was used).
+.TP
+.BR "--among-dst " "[!] \fIlist\fP"
+Compare the MAC destination to the given list. If the Ethernet frame has type
+.IR IPv4 " or " ARP ,
+then comparison with MAC/IP destination address pairs from the
+list is possible.
+.TP
+.BR "--among-src " "[!] \fIlist\fP"
+Compare the MAC source to the given list. If the Ethernet frame has type
+.IR IPv4 " or " ARP ,
+then comparison with MAC/IP source address pairs from the list
+is possible.
+.TP
+.BR "--among-dst-file " "[!] \fIfile\fP"
+Same as
+.BR --among-dst " but the list is read in from the specified file."
+.TP
+.BR "--among-src-file " "[!] \fIfile\fP"
+Same as
+.BR --among-src " but the list is read in from the specified file."
+.SS arp
+Specify (R)ARP fields. The protocol must be specified as
+.IR ARP " or " RARP .
+.TP
+.BR "--arp-opcode " "[!] \fIopcode\fP"
+The (R)ARP opcode (decimal or a string, for more details see
+.BR "ebtables -h arp" ).
+.TP
+.BR "--arp-htype " "[!] \fIhardware type\fP"
+The hardware type, this can be a decimal or the string
+.I Ethernet
+(which sets
+.I type
+to 1). Most (R)ARP packets have Eternet as hardware type.
+.TP
+.BR "--arp-ptype " "[!] \fIprotocol type\fP"
+The protocol type for which the (r)arp is used (hexadecimal or the string
+.IR IPv4 ,
+denoting 0x0800).
+Most (R)ARP packets have protocol type IPv4.
+.TP
+.BR "--arp-ip-src " "[!] \fIaddress\fP[/\fImask\fP]"
+The (R)ARP IP source address specification.
+.TP
+.BR "--arp-ip-dst " "[!] \fIaddress\fP[/\fImask\fP]"
+The (R)ARP IP destination address specification.
+.TP
+.BR "--arp-mac-src " "[!] \fIaddress\fP[/\fImask\fP]"
+The (R)ARP MAC source address specification.
+.TP
+.BR "--arp-mac-dst " "[!] \fIaddress\fP[/\fImask\fP]"
+The (R)ARP MAC destination address specification.
+.TP
+.BR "" "[!]" " --arp-gratuitous"
+Checks for ARP gratuitous packets: checks equality of IPv4 source
+address and IPv4 destination address inside the ARP header.
+.SS ip
+Specify IPv4 fields. The protocol must be specified as
+.IR IPv4 .
+.TP
+.BR "--ip-source " "[!] \fIaddress\fP[/\fImask\fP]"
+The source IP address.
+The flag
+.B --ip-src
+is an alias for this option.
+.TP
+.BR "--ip-destination " "[!] \fIaddress\fP[/\fImask\fP]"
+The destination IP address.
+The flag
+.B --ip-dst
+is an alias for this option.
+.TP
+.BR "--ip-tos " "[!] \fItos\fP"
+The IP type of service, in hexadecimal numbers.
+.BR IPv4 .
+.TP
+.BR "--ip-protocol " "[!] \fIprotocol\fP"
+The IP protocol.
+The flag
+.B --ip-proto
+is an alias for this option.
+.TP
+.BR "--ip-source-port " "[!] \fIport1\fP[:\fIport2\fP]"
+The source port or port range for the IP protocols 6 (TCP), 17
+(UDP), 33 (DCCP) or 132 (SCTP). The
+.B --ip-protocol
+option must be specified as
+.IR TCP ", " UDP ", " DCCP " or " SCTP .
+If
+.IR port1 " is omitted, " 0:port2 " is used; if " port2 " is omitted but a colon is specified, " port1:65535 " is used."
+The flag
+.B --ip-sport
+is an alias for this option.
+.TP
+.BR "--ip-destination-port " "[!] \fIport1\fP[:\fIport2\fP]"
+The destination port or port range for ip protocols 6 (TCP), 17
+(UDP), 33 (DCCP) or 132 (SCTP). The
+.B --ip-protocol
+option must be specified as
+.IR TCP ", " UDP ", " DCCP " or " SCTP .
+If
+.IR port1 " is omitted, " 0:port2 " is used; if " port2 " is omitted but a colon is specified, " port1:65535 " is used."
+The flag
+.B --ip-dport
+is an alias for this option.
+.SS ip6
+Specify IPv6 fields. The protocol must be specified as
+.IR IPv6 .
+.TP
+.BR "--ip6-source " "[!] \fIaddress\fP[/\fImask\fP]"
+The source IPv6 address.
+The flag
+.B --ip6-src
+is an alias for this option.
+.TP
+.BR "--ip6-destination " "[!] \fIaddress\fP[/\fImask\fP]"
+The destination IPv6 address.
+The flag
+.B --ip6-dst
+is an alias for this option.
+.TP
+.BR "--ip6-tclass " "[!] \fItclass\fP"
+The IPv6 traffic class, in hexadecimal numbers.
+.TP
+.BR "--ip6-protocol " "[!] \fIprotocol\fP"
+The IP protocol.
+The flag
+.B --ip6-proto
+is an alias for this option.
+.TP
+.BR "--ip6-source-port " "[!] \fIport1\fP[:\fIport2\fP]"
+The source port or port range for the IPv6 protocols 6 (TCP), 17
+(UDP), 33 (DCCP) or 132 (SCTP). The
+.B --ip6-protocol
+option must be specified as
+.IR TCP ", " UDP ", " DCCP " or " SCTP .
+If
+.IR port1 " is omitted, " 0:port2 " is used; if " port2 " is omitted but a colon is specified, " port1:65535 " is used."
+The flag
+.B --ip6-sport
+is an alias for this option.
+.TP
+.BR "--ip6-destination-port " "[!] \fIport1\fP[:\fIport2\fP]"
+The destination port or port range for IPv6 protocols 6 (TCP), 17
+(UDP), 33 (DCCP) or 132 (SCTP). The
+.B --ip6-protocol
+option must be specified as
+.IR TCP ", " UDP ", " DCCP " or " SCTP .
+If
+.IR port1 " is omitted, " 0:port2 " is used; if " port2 " is omitted but a colon is specified, " port1:65535 " is used."
+The flag
+.B --ip6-dport
+is an alias for this option.
+.TP
+.BR "--ip6-icmp-type " "[!] {\fItype\fP[:\fItype\fP]/\fIcode\fP[:\fIcode\fP]|\fItypename\fP}"
+Specify ipv6\-icmp type and code to match.
+Ranges for both type and code are supported. Type and code are
+separated by a slash. Valid numbers for type and range are 0 to 255.
+To match a single type including all valid codes, symbolic names can
+be used instead of numbers. The list of known type names is shown by the command
+.nf
+  ebtables \-\-help ip6
+.fi
+This option is only valid for \-\-ip6-prococol ipv6-icmp.
+.SS limit
+This module matches at a limited rate using a token bucket filter.
+A rule using this extension will match until this limit is reached.
+It can be used with the
+.B --log
+watcher to give limited logging, for example. Its use is the same
+as the limit match of iptables.
+.TP
+.BR "--limit " "[\fIvalue\fP]"
+Maximum average matching rate: specified as a number, with an optional
+.IR /second ", " /minute ", " /hour ", or " /day " suffix; the default is " 3/hour .
+.TP
+.BR "--limit-burst " "[\fInumber\fP]"
+Maximum initial number of packets to match: this number gets recharged by
+one every time the limit specified above is not reached, up to this
+number; the default is
+.IR 5 .
+.SS mark_m
+.TP
+.BR "--mark " "[!] [\fIvalue\fP][/\fImask\fP]"
+Matches frames with the given unsigned mark value. If a
+.IR value " and " mask " are specified, the logical AND of the mark value of the frame and"
+the user-specified
+.IR mask " is taken before comparing it with the"
+user-specified mark
+.IR value ". When only a mark "
+.IR value " is specified, the packet"
+only matches when the mark value of the frame equals the user-specified
+mark
+.IR value .
+If only a
+.IR mask " is specified, the logical"
+AND of the mark value of the frame and the user-specified
+.IR mask " is taken and the frame matches when the result of this logical AND is"
+non-zero. Only specifying a
+.IR mask " is useful to match multiple mark values."
+.SS pkttype
+.TP
+.BR "--pkttype-type " "[!] \fItype\fP"
+Matches on the Ethernet "class" of the frame, which is determined by the
+generic networking code. Possible values:
+.IR broadcast " (MAC destination is the broadcast address),"
+.IR multicast " (MAC destination is a multicast address),"
+.IR host " (MAC destination is the receiving network device), or "
+.IR otherhost " (none of the above)."
+.SS stp
+Specify stp BPDU (bridge protocol data unit) fields. The destination
+address
+.BR "" ( -d ") must be specified as the bridge group address"
+.IR "" ( BGA ).
+For all options for which a range of values can be specified, it holds that
+if the lower bound is omitted (but the colon is not), then the lowest possible lower bound
+for that option is used, while if the upper bound is omitted (but the colon again is not), the
+highest possible upper bound for that option is used.
+.TP
+.BR "--stp-type " "[!] \fItype\fP"
+The BPDU type (0-255), recognized non-numerical types are
+.IR config ", denoting a configuration BPDU (=0), and"
+.IR tcn ", denothing a topology change notification BPDU (=128)."
+.TP
+.BR "--stp-flags " "[!] \fIflag\fP"
+The BPDU flag (0-255), recognized non-numerical flags are
+.IR topology-change ", denoting the topology change flag (=1), and"
+.IR topology-change-ack ", denoting the topology change acknowledgement flag (=128)."
+.TP
+.BR "--stp-root-prio " "[!] [\fIprio\fP][:\fIprio\fP]"
+The root priority (0-65535) range.
+.TP
+.BR "--stp-root-addr " "[!] [\fIaddress\fP][/\fImask\fP]"
+The root mac address, see the option
+.BR -s " for more details."
+.TP
+.BR "--stp-root-cost " "[!] [\fIcost\fP][:\fIcost\fP]"
+The root path cost (0-4294967295) range.
+.TP
+.BR "--stp-sender-prio " "[!] [\fIprio\fP][:\fIprio\fP]"
+The BPDU's sender priority (0-65535) range.
+.TP
+.BR "--stp-sender-addr " "[!] [\fIaddress\fP][/\fImask\fP]"
+The BPDU's sender mac address, see the option
+.BR -s " for more details."
+.TP
+.BR "--stp-port " "[!] [\fIport\fP][:\fIport\fP]"
+The port identifier (0-65535) range.
+.TP
+.BR "--stp-msg-age " "[!] [\fIage\fP][:\fIage\fP]"
+The message age timer (0-65535) range.
+.TP
+.BR "--stp-max-age " "[!] [\fIage\fP][:\fIage\fP]"
+The max age timer (0-65535) range.
+.TP
+.BR "--stp-hello-time " "[!] [\fItime\fP][:\fItime\fP]"
+The hello time timer (0-65535) range.
+.TP
+.BR "--stp-forward-delay " "[!] [\fIdelay\fP][:\fIdelay\fP]"
+The forward delay timer (0-65535) range.
+.\" .SS string
+.\" This module matches on a given string using some pattern matching strategy.
+.\" .TP
+.\" .BR "--string-algo " "\fIalgorithm\fP"
+.\" The pattern matching strategy. (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)
+.\" .TP
+.\" .BR "--string-from " "\fIoffset\fP"
+.\" The lowest offset from which a match can start. (default: 0)
+.\" .TP
+.\" .BR "--string-to " "\fIoffset\fP"
+.\" The highest offset from which a match can start. (default: size of frame)
+.\" .TP
+.\" .BR "--string " "[!] \fIpattern\fP"
+.\" Matches the given pattern.
+.\" .TP
+.\" .BR "--string-hex " "[!] \fIpattern\fP"
+.\" Matches the given pattern in hex notation, e.g. '|0D 0A|', '|0D0A|', 'www|09|netfilter|03|org|00|'
+.\" .TP
+.\" .BR "--string-icase"
+.\" Ignore case when searching.
+.SS vlan
+Specify 802.1Q Tag Control Information fields.
+The protocol must be specified as
+.IR 802_1Q " (0x8100)."
+.TP
+.BR "--vlan-id " "[!] \fIid\fP"
+The VLAN identifier field (VID). Decimal number from 0 to 4095.
+.TP
+.BR "--vlan-prio " "[!] \fIprio\fP"
+The user priority field, a decimal number from 0 to 7.
+The VID should be set to 0 ("null VID") or unspecified
+(in the latter case the VID is deliberately set to 0).
+.TP
+.BR "--vlan-encap " "[!] \fItype\fP"
+The encapsulated Ethernet frame type/length.
+Specified as a hexadecimal
+number from 0x0000 to 0xFFFF or as a symbolic name
+from
+.BR /etc/ethertypes .
+
+.SS WATCHER EXTENSIONS
+Watchers only look at frames passing by, they don't modify them nor decide
+to accept the frames or not. These watchers only
+see the frame if the frame matches the rule, and they see it before the
+target is executed.
+.SS log
+The log watcher writes descriptive data about a frame to the syslog.
+.TP
+.B "--log"
+.br
+Log with the default loggin options: log-level=
+.IR info ,
+log-prefix="", no ip logging, no arp logging.
+.TP
+.B --log-level "\fIlevel\fP"
+.br
+Defines the logging level. For the possible values, see
+.BR "ebtables -h log" .
+The default level is 
+.IR info .
+.TP
+.BR --log-prefix " \fItext\fP"
+.br
+Defines the prefix
+.I text
+to be printed at the beginning of the line with the logging information.
+.TP
+.B --log-ip 
+.br
+Will log the ip information when a frame made by the ip protocol matches 
+the rule. The default is no ip information logging.
+.TP
+.B --log-ip6 
+.br
+Will log the ipv6 information when a frame made by the ipv6 protocol matches 
+the rule. The default is no ipv6 information logging.
+.TP
+.B --log-arp
+.br
+Will log the (r)arp information when a frame made by the (r)arp protocols
+matches the rule. The default is no (r)arp information logging.
+.SS nflog
+The nflog watcher passes the packet to the loaded logging backend
+in order to log the packet. This is usually used in combination with
+nfnetlink_log as logging backend, which will multicast the packet
+through a
+.IR netlink
+socket to the specified multicast group. One or more userspace processes
+may subscribe to the group to receive the packets.
+.TP
+.B "--nflog"
+.br
+Log with the default logging options
+.TP
+.B --nflog-group "\fInlgroup\fP"
+.br
+The netlink group (1 - 2^32-1) to which packets are (only applicable for
+nfnetlink_log). The default value is 1.
+.TP
+.B --nflog-prefix "\fIprefix\fP"
+.br
+A prefix string to include in the log message, up to 30 characters
+long, useful for distinguishing messages in the logs.
+.TP
+.B --nflog-range "\fIsize\fP"
+.br
+The number of bytes to be copied to userspace (only applicable for
+nfnetlink_log). nfnetlink_log instances may specify their own
+range, this option overrides it.
+.TP
+.B --nflog-threshold "\fIsize\fP"
+.br
+Number of packets to queue inside the kernel before sending them
+to userspace (only applicable for nfnetlink_log). Higher values
+result in less overhead per packet, but increase delay until the
+packets reach userspace. The default value is 1.
+.SS ulog
+The ulog watcher passes the packet to a userspace
+logging daemon using netlink multicast sockets. This differs
+from the log watcher in the sense that the complete packet is
+sent to userspace instead of a descriptive text and that
+netlink multicast sockets are used instead of the syslog.
+This watcher enables parsing of packets with userspace programs, the
+physical bridge in and out ports are also included in the netlink messages.
+The ulog watcher module accepts 2 parameters when the module is loaded
+into the kernel (e.g. with modprobe):
+.B nlbufsiz
+specifies how big the buffer for each netlink multicast
+group is. If you say
+.IR nlbufsiz=8192 ,
+for example, up to eight kB of packets will
+get accumulated in the kernel until they are sent to userspace. It is
+not possible to allocate more than 128kB. Please also keep in mind that
+this buffer size is allocated for each nlgroup you are using, so the
+total kernel memory usage increases by that factor. The default is 4096.
+.B flushtimeout
+specifies after how many hundredths of a second the queue should be
+flushed, even if it is not full yet. The default is 10 (one tenth of
+a second).
+.TP
+.B "--ulog"
+.br
+Use the default settings: ulog-prefix="", ulog-nlgroup=1,
+ulog-cprange=4096, ulog-qthreshold=1.
+.TP
+.B --ulog-prefix "\fItext\fP"
+.br
+Defines the prefix included with the packets sent to userspace.
+.TP
+.BR --ulog-nlgroup " \fIgroup\fP"
+.br
+Defines which netlink group number to use (a number from 1 to 32).
+Make sure the netlink group numbers used for the iptables ULOG
+target differ from those used for the ebtables ulog watcher.
+The default group number is 1.
+.TP
+.BR --ulog-cprange " \fIrange\fP"
+.br
+Defines the maximum copy range to userspace, for packets matching the
+rule. The default range is 0, which means the maximum copy range is
+given by
+.BR nlbufsiz .
+A maximum copy range larger than
+128*1024 is meaningless as the packets sent to userspace have an upper
+size limit of 128*1024.
+.TP
+.BR --ulog-qthreshold " \fIthreshold\fP"
+.br
+Queue at most
+.I threshold
+number of packets before sending them to
+userspace with a netlink socket. Note that packets can be sent to
+userspace before the queue is full, this happens when the ulog
+kernel timer goes off (the frequency of this timer depends on
+.BR flushtimeout ).
+.SS TARGET EXTENSIONS
+.SS arpreply
+The
+.B arpreply
+target can be used in the
+.BR PREROUTING " chain of the " nat " table."
+If this target sees an ARP request it will automatically reply
+with an ARP reply. The used MAC address for the reply can be specified.
+The protocol must be specified as
+.IR ARP .
+When the ARP message is not an ARP request or when the ARP request isn't
+for an IP address on an Ethernet network, it is ignored by this target
+.BR "" ( CONTINUE ).
+When the ARP request is malformed, it is dropped
+.BR "" ( DROP ).
+.TP
+.BR "--arpreply-mac " "\fIaddress\fP"
+Specifies the MAC address to reply with: the Ethernet source MAC and the
+ARP payload source MAC will be filled in with this address.
+.TP
+.BR "--arpreply-target " "\fItarget\fP"
+Specifies the standard target. After sending the ARP reply, the rule still
+has to give a standard target so ebtables knows what to do with the ARP request.
+The default target
+.BR "" "is " DROP .
+.SS dnat
+The
+.B dnat
+target can only be used in the
+.BR PREROUTING " and " OUTPUT " chains of the " nat " table."
+It specifies that the destination MAC address has to be changed.
+.TP
+.BR "--to-destination " "\fIaddress\fP"
+.br
+Change the destination MAC address to the specified
+.IR address .
+The flag
+.B --to-dst
+is an alias for this option.
+.TP
+.BR "--dnat-target " "\fItarget\fP"
+.br
+Specifies the standard target. After doing the dnat, the rule still has to
+give a standard target so ebtables knows what to do with the dnated frame.
+The default target is
+.BR ACCEPT .
+Making it
+.BR CONTINUE " could let you use"
+multiple target extensions on the same frame. Making it
+.BR DROP " only makes"
+sense in the
+.BR BROUTING " chain but using the " redirect " target is more logical there. " RETURN " is also allowed. Note that using " RETURN
+in a base chain is not allowed (for obvious reasons).
+.SS mark
+.BR "" "The " mark " target can be used in every chain of every table. It is possible"
+to use the marking of a frame/packet in both ebtables and iptables,
+if the bridge-nf code is compiled into the kernel. Both put the marking at the
+same place. This allows for a form of communication between ebtables and iptables.
+.TP
+.BR "--mark-set " "\fIvalue\fP"
+.br
+Mark the frame with the specified non-negative
+.IR value .
+.TP
+.BR "--mark-or " "\fIvalue\fP"
+.br
+Or the frame with the specified non-negative
+.IR value .
+.TP
+.BR "--mark-and " "\fIvalue\fP"
+.br
+And the frame with the specified non-negative
+.IR value .
+.TP
+.BR "--mark-xor " "\fIvalue\fP"
+.br
+Xor the frame with the specified non-negative
+.IR value .
+.TP
+.BR "--mark-target " "\fItarget\fP"
+.br
+Specifies the standard target. After marking the frame, the rule
+still has to give a standard target so ebtables knows what to do.
+The default target is
+.BR ACCEPT ". Making it " CONTINUE " can let you do other"
+things with the frame in subsequent rules of the chain.
+.SS redirect
+The
+.B redirect
+target will change the MAC target address to that of the bridge device the
+frame arrived on. This target can only be used in the
+.BR PREROUTING " chain of the " nat " table."
+The MAC address of the bridge is used as destination address."
+.TP
+.BR "--redirect-target " "\fItarget\fP"
+.br
+Specifies the standard target. After doing the MAC redirect, the rule
+still has to give a standard target so ebtables knows what to do.
+The default target is
+.BR ACCEPT ". Making it " CONTINUE " could let you use"
+multiple target extensions on the same frame. Making it
+.BR DROP " in the " BROUTING " chain will let the frames be routed. " RETURN " is also allowed. Note"
+.BR "" "that using " RETURN " in a base chain is not allowed."
+.SS snat
+The
+.B snat
+target can only be used in the
+.BR POSTROUTING " chain of the " nat " table."
+It specifies that the source MAC address has to be changed.
+.TP
+.BR "--to-source " "\fIaddress\fP"
+.br
+Changes the source MAC address to the specified
+.IR address ". The flag"
+.B --to-src
+is an alias for this option.
+.TP
+.BR "--snat-target " "\fItarget\fP"
+.br
+Specifies the standard target. After doing the snat, the rule still has 
+to give a standard target so ebtables knows what to do.
+.BR "" "The default target is " ACCEPT ". Making it " CONTINUE " could let you use"
+.BR "" "multiple target extensions on the same frame. Making it " DROP " doesn't"
+.BR "" "make sense, but you could do that too. " RETURN " is also allowed. Note"
+.BR "" "that using " RETURN " in a base chain is not allowed."
+.br
+.TP
+.BR "--snat-arp "
+.br
+Also change the hardware source address inside the arp header if the packet is an
+arp message and the hardware address length in the arp header is 6 bytes.
+.br
+.SH FILES
+.I /etc/ethertypes
+.SH ENVIRONMENT VARIABLES
+.I EBTABLES_ATOMIC_FILE
+.SH MAILINGLISTS
+.BR "" "See " http://netfilter.org/mailinglists.html
+.SH BUGS
+The version of ebtables this man page ships with does not support the
+.B broute
+table. Also there is no support for
+.B string
+match. And finally, this list is probably not complete.
+.SH SEE ALSO
+.BR xtables-nft "(8), " iptables "(8), " ip (8)
+.PP
+.BR "" "See " https://wiki.nftables.org
diff --git a/iptables/ip6tables-apply.8 b/iptables/ip6tables-apply.8
new file mode 100644
index 0000000..994b487
--- /dev/null
+++ b/iptables/ip6tables-apply.8
@@ -0,0 +1 @@
+.so man8/iptables-apply.8
diff --git a/iptables/ip6tables-multi.h b/iptables/ip6tables-multi.h
new file mode 100644
index 0000000..551029a
--- /dev/null
+++ b/iptables/ip6tables-multi.h
@@ -0,0 +1,8 @@
+#ifndef _IP6TABLES_MULTI_H
+#define _IP6TABLES_MULTI_H 1
+
+extern int ip6tables_main(int, char **);
+extern int ip6tables_save_main(int, char **);
+extern int ip6tables_restore_main(int, char **);
+
+#endif /* _IP6TABLES_MULTI_H */
diff --git a/iptables/ip6tables-restore.8 b/iptables/ip6tables-restore.8
new file mode 100644
index 0000000..cf4ea3e
--- /dev/null
+++ b/iptables/ip6tables-restore.8
@@ -0,0 +1 @@
+.so man8/iptables-restore.8
diff --git a/iptables/ip6tables-save.8 b/iptables/ip6tables-save.8
new file mode 100644
index 0000000..182f55c
--- /dev/null
+++ b/iptables/ip6tables-save.8
@@ -0,0 +1 @@
+.so man8/iptables-save.8
diff --git a/iptables/ip6tables-standalone.c b/iptables/ip6tables-standalone.c
new file mode 100644
index 0000000..105b83b
--- /dev/null
+++ b/iptables/ip6tables-standalone.c
@@ -0,0 +1,83 @@
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ * Based on the ipchains code by Paul Russell and Michael Neuling
+ *
+ *	iptables -- IP firewall administration for kernels with
+ *	firewall table (aimed for the 2.3 kernels)
+ *
+ *	See the accompanying manual page iptables(8) for information
+ *	about proper usage of this program.
+ *
+ *	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 2 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, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ip6tables.h>
+#include "ip6tables-multi.h"
+
+int
+ip6tables_main(int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct xtc_handle *handle = NULL;
+
+	ip6tables_globals.program_name = "ip6tables";
+	ret = xtables_init_all(&ip6tables_globals, NFPROTO_IPV6);
+	if (ret < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				ip6tables_globals.program_name,
+				ip6tables_globals.program_version);
+		exit(1);
+	}
+
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions6();
+#endif
+
+	ret = do_command6(argc, argv, &table, &handle, false);
+	if (ret) {
+		ret = ip6tc_commit(handle);
+		ip6tc_free(handle);
+	}
+
+	xtables_fini();
+
+	if (!ret) {
+		if (errno == EINVAL) {
+			fprintf(stderr, "ip6tables: %s. "
+					"Run `dmesg' for more information.\n",
+				ip6tc_strerror(errno));
+		} else {
+			fprintf(stderr, "ip6tables: %s.\n",
+				ip6tc_strerror(errno));
+		}
+		if (errno == EAGAIN)
+			exit(RESOURCE_PROBLEM);
+	}
+
+	exit(!ret);
+}
diff --git a/iptables/ip6tables.8 b/iptables/ip6tables.8
new file mode 100644
index 0000000..0dee41a
--- /dev/null
+++ b/iptables/ip6tables.8
@@ -0,0 +1 @@
+.so man8/iptables.8
diff --git a/iptables/ip6tables.c b/iptables/ip6tables.c
new file mode 100644
index 0000000..c95355b
--- /dev/null
+++ b/iptables/ip6tables.c
@@ -0,0 +1,1713 @@
+/* Code to take an ip6tables-style command line and do it. */
+
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	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 2 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, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "config.h"
+#include <getopt.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <ip6tables.h>
+#include <xtables.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include "ip6tables-multi.h"
+#include "xshared.h"
+
+static const char unsupported_rev[] = " [unsupported revision]";
+
+static struct option original_opts[] = {
+	{.name = "append",        .has_arg = 1, .val = 'A'},
+	{.name = "delete",        .has_arg = 1, .val = 'D'},
+	{.name = "check" ,        .has_arg = 1, .val = 'C'},
+	{.name = "insert",        .has_arg = 1, .val = 'I'},
+	{.name = "replace",       .has_arg = 1, .val = 'R'},
+	{.name = "list",          .has_arg = 2, .val = 'L'},
+	{.name = "list-rules",    .has_arg = 2, .val = 'S'},
+	{.name = "flush",         .has_arg = 2, .val = 'F'},
+	{.name = "zero",          .has_arg = 2, .val = 'Z'},
+	{.name = "new-chain",     .has_arg = 1, .val = 'N'},
+	{.name = "delete-chain",  .has_arg = 2, .val = 'X'},
+	{.name = "rename-chain",  .has_arg = 1, .val = 'E'},
+	{.name = "policy",        .has_arg = 1, .val = 'P'},
+	{.name = "source",        .has_arg = 1, .val = 's'},
+	{.name = "destination",   .has_arg = 1, .val = 'd'},
+	{.name = "src",           .has_arg = 1, .val = 's'}, /* synonym */
+	{.name = "dst",           .has_arg = 1, .val = 'd'}, /* synonym */
+	{.name = "protocol",      .has_arg = 1, .val = 'p'},
+	{.name = "in-interface",  .has_arg = 1, .val = 'i'},
+	{.name = "jump",          .has_arg = 1, .val = 'j'},
+	{.name = "table",         .has_arg = 1, .val = 't'},
+	{.name = "match",         .has_arg = 1, .val = 'm'},
+	{.name = "numeric",       .has_arg = 0, .val = 'n'},
+	{.name = "out-interface", .has_arg = 1, .val = 'o'},
+	{.name = "verbose",       .has_arg = 0, .val = 'v'},
+	{.name = "wait",          .has_arg = 2, .val = 'w'},
+	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
+	{.name = "exact",         .has_arg = 0, .val = 'x'},
+	{.name = "version",       .has_arg = 0, .val = 'V'},
+	{.name = "help",          .has_arg = 2, .val = 'h'},
+	{.name = "line-numbers",  .has_arg = 0, .val = '0'},
+	{.name = "modprobe",      .has_arg = 1, .val = 'M'},
+	{.name = "set-counters",  .has_arg = 1, .val = 'c'},
+	{.name = "goto",          .has_arg = 1, .val = 'g'},
+	{.name = "ipv4",          .has_arg = 0, .val = '4'},
+	{.name = "ipv6",          .has_arg = 0, .val = '6'},
+	{NULL},
+};
+
+void ip6tables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+struct xtables_globals ip6tables_globals = {
+	.option_offset = 0,
+	.program_version = PACKAGE_VERSION,
+	.orig_opts = original_opts,
+	.exit_err = ip6tables_exit_error,
+	.compat_rev = xtables_compatible_revision,
+};
+
+static const unsigned int inverse_for_options[NUMBER_OF_OPT] =
+{
+/* -n */ 0,
+/* -s */ IP6T_INV_SRCIP,
+/* -d */ IP6T_INV_DSTIP,
+/* -p */ XT_INV_PROTO,
+/* -j */ 0,
+/* -v */ 0,
+/* -x */ 0,
+/* -i */ IP6T_INV_VIA_IN,
+/* -o */ IP6T_INV_VIA_OUT,
+/*--line*/ 0,
+/* -c */ 0,
+};
+
+#define opts ip6tables_globals.opts
+#define prog_name ip6tables_globals.program_name
+#define prog_vers ip6tables_globals.program_version
+
+static void __attribute__((noreturn))
+exit_tryhelp(int status)
+{
+	if (line != -1)
+		fprintf(stderr, "Error occurred at line: %d\n", line);
+	fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
+			prog_name, prog_name);
+	xtables_free_opts(1);
+	exit(status);
+}
+
+static void
+exit_printhelp(const struct xtables_rule_match *matches)
+{
+	printf("%s v%s\n\n"
+"Usage: %s -[ACD] chain rule-specification [options]\n"
+"       %s -I chain [rulenum] rule-specification [options]\n"
+"       %s -R chain rulenum rule-specification [options]\n"
+"       %s -D chain rulenum [options]\n"
+"       %s -[LS] [chain [rulenum]] [options]\n"
+"       %s -[FZ] [chain] [options]\n"
+"       %s -[NX] chain\n"
+"       %s -E old-chain-name new-chain-name\n"
+"       %s -P chain target [options]\n"
+"       %s -h (print this help information)\n\n",
+	       prog_name, prog_vers, prog_name, prog_name,
+	       prog_name, prog_name, prog_name, prog_name,
+	       prog_name, prog_name, prog_name, prog_name);
+
+	printf(
+"Commands:\n"
+"Either long or short options are allowed.\n"
+"  --append  -A chain		Append to chain\n"
+"  --check   -C chain		Check for the existence of a rule\n"
+"  --delete  -D chain		Delete matching rule from chain\n"
+"  --delete  -D chain rulenum\n"
+"				Delete rule rulenum (1 = first) from chain\n"
+"  --insert  -I chain [rulenum]\n"
+"				Insert in chain as rulenum (default 1=first)\n"
+"  --replace -R chain rulenum\n"
+"				Replace rule rulenum (1 = first) in chain\n"
+"  --list    -L [chain [rulenum]]\n"
+"				List the rules in a chain or all chains\n"
+"  --list-rules -S [chain [rulenum]]\n"
+"				Print the rules in a chain or all chains\n"
+"  --flush   -F [chain]		Delete all rules in  chain or all chains\n"
+"  --zero    -Z [chain [rulenum]]\n"
+"				Zero counters in chain or all chains\n"
+"  --new     -N chain		Create a new user-defined chain\n"
+"  --delete-chain\n"
+"            -X [chain]		Delete a user-defined chain\n"
+"  --policy  -P chain target\n"
+"				Change policy on chain to target\n"
+"  --rename-chain\n"
+"            -E old-chain new-chain\n"
+"				Change chain name, (moving any references)\n"
+
+"Options:\n"
+"    --ipv4	-4		Error (line is ignored by ip6tables-restore)\n"
+"    --ipv6	-6		Nothing (line is ignored by iptables-restore)\n"
+"[!] --protocol	-p proto	protocol: by number or name, eg. `tcp'\n"
+"[!] --source	-s address[/mask][,...]\n"
+"				source specification\n"
+"[!] --destination -d address[/mask][,...]\n"
+"				destination specification\n"
+"[!] --in-interface -i input name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --jump	-j target\n"
+"				target for rule (may load target extension)\n"
+#ifdef IP6T_F_GOTO
+"  --goto	-g chain\n"
+"				jump to chain with no return\n"
+#endif
+"  --match	-m match\n"
+"				extended match (may load extension)\n"
+"  --numeric	-n		numeric output of addresses and ports\n"
+"[!] --out-interface -o output name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --table	-t table	table to manipulate (default: `filter')\n"
+"  --verbose	-v		verbose mode\n"
+"  --wait	-w [seconds]	maximum wait to acquire xtables lock before give up\n"
+"  --wait-interval -W [usecs]	wait time to try to acquire xtables lock\n"
+"				interval to wait for xtables lock\n"
+"				default is 1 second\n"
+"  --line-numbers		print line numbers when listing\n"
+"  --exact	-x		expand numbers (display exact values)\n"
+/*"[!] --fragment	-f		match second or further fragments only\n"*/
+"  --modprobe=<command>		try to insert modules using this command\n"
+"  --set-counters PKTS BYTES	set the counter during insert/append\n"
+"[!] --version	-V		print package version.\n");
+
+	print_extension_helps(xtables_targets, matches);
+	exit(0);
+}
+
+void
+ip6tables_exit_error(enum xtables_exittype status, const char *msg, ...)
+{
+	va_list args;
+
+	va_start(args, msg);
+	fprintf(stderr, "%s v%s (legacy): ", prog_name, prog_vers);
+	vfprintf(stderr, msg, args);
+	va_end(args);
+	fprintf(stderr, "\n");
+	if (status == PARAMETER_PROBLEM)
+		exit_tryhelp(status);
+	if (status == VERSION_PROBLEM)
+		fprintf(stderr,
+			"Perhaps ip6tables or your kernel needs to be upgraded.\n");
+	/* On error paths, make sure that we don't leak memory */
+	xtables_free_opts(1);
+	exit(status);
+}
+
+/*
+ *	All functions starting with "parse" should succeed, otherwise
+ *	the program fails.
+ *	Most routines return pointers to static data that may change
+ *	between calls to the same or other routines with a few exceptions:
+ *	"host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask"
+ *	return global static data.
+*/
+
+/* These are invalid numbers as upper layer protocol */
+static int is_exthdr(uint16_t proto)
+{
+	return (proto == IPPROTO_ROUTING ||
+		proto == IPPROTO_FRAGMENT ||
+		proto == IPPROTO_AH ||
+		proto == IPPROTO_DSTOPTS);
+}
+
+static void
+parse_chain(const char *chainname)
+{
+	const char *ptr;
+
+	if (strlen(chainname) >= XT_EXTENSION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name `%s' too long (must be under %u chars)",
+			   chainname, XT_EXTENSION_MAXNAMELEN);
+
+	if (*chainname == '-' || *chainname == '!')
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name not allowed to start "
+			   "with `%c'\n", *chainname);
+
+	if (xtables_find_target(chainname, XTF_TRY_LOAD))
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name may not clash "
+			   "with target name\n");
+
+	for (ptr = chainname; *ptr; ptr++)
+		if (isspace(*ptr))
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid chain name `%s'", chainname);
+}
+
+static void
+set_option(unsigned int *options, unsigned int option, uint8_t *invflg,
+	   int invert)
+{
+	if (*options & option)
+		xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed",
+			   opt2char(option));
+	*options |= option;
+
+	if (invert) {
+		unsigned int i;
+		for (i = 0; 1 << i != option; i++);
+
+		if (!inverse_for_options[i])
+			xtables_error(PARAMETER_PROBLEM,
+				   "cannot have ! before -%c",
+				   opt2char(option));
+		*invflg |= inverse_for_options[i];
+	}
+}
+
+
+static void
+print_header(unsigned int format, const char *chain, struct xtc_handle *handle)
+{
+	struct xt_counters counters;
+	const char *pol = ip6tc_get_policy(chain, &counters, handle);
+	printf("Chain %s", chain);
+	if (pol) {
+		printf(" (policy %s", pol);
+		if (!(format & FMT_NOCOUNTS)) {
+			fputc(' ', stdout);
+			xtables_print_num(counters.pcnt, (format|FMT_NOTABLE));
+			fputs("packets, ", stdout);
+			xtables_print_num(counters.bcnt, (format|FMT_NOTABLE));
+			fputs("bytes", stdout);
+		}
+		printf(")\n");
+	} else {
+		unsigned int refs;
+		if (!ip6tc_get_references(&refs, chain, handle))
+			printf(" (ERROR obtaining refs)\n");
+		else
+			printf(" (%u references)\n", refs);
+	}
+
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4s ", "%s "), "num");
+	if (!(format & FMT_NOCOUNTS)) {
+		if (format & FMT_KILOMEGAGIGA) {
+			printf(FMT("%5s ","%s "), "pkts");
+			printf(FMT("%5s ","%s "), "bytes");
+		} else {
+			printf(FMT("%8s ","%s "), "pkts");
+			printf(FMT("%10s ","%s "), "bytes");
+		}
+	}
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ","%s "), "target");
+	fputs(" prot ", stdout);
+	if (format & FMT_OPTIONS)
+		fputs("opt", stdout);
+	if (format & FMT_VIA) {
+		printf(FMT(" %-6s ","%s "), "in");
+		printf(FMT("%-6s ","%s "), "out");
+	}
+	printf(FMT(" %-19s ","%s "), "source");
+	printf(FMT(" %-19s "," %s "), "destination");
+	printf("\n");
+}
+
+
+static int
+print_match(const struct xt_entry_match *m,
+	    const struct ip6t_ip6 *ip,
+	    int numeric)
+{
+	const char *name = m->u.user.name;
+	const int revision = m->u.user.revision;
+	struct xtables_match *match, *mt;
+
+	match = xtables_find_match(name, XTF_TRY_LOAD, NULL);
+	if (match) {
+		mt = xtables_find_match_revision(name, XTF_TRY_LOAD,
+						 match, revision);
+		if (mt && mt->print)
+			mt->print(ip, m, numeric);
+		else if (match->print)
+			printf("%s%s ", match->name, unsupported_rev);
+		else
+			printf("%s ", match->name);
+	} else {
+		if (name[0])
+			printf("UNKNOWN match `%s' ", name);
+	}
+	/* Don't stop iterating. */
+	return 0;
+}
+
+/* e is called `fw' here for historical reasons */
+static void
+print_firewall(const struct ip6t_entry *fw,
+	       const char *targname,
+	       unsigned int num,
+	       unsigned int format,
+	       struct xtc_handle *const handle)
+{
+	struct xtables_target *target, *tg;
+	const struct xt_entry_target *t;
+
+	if (!ip6tc_is_chain(targname, handle))
+		target = xtables_find_target(targname, XTF_TRY_LOAD);
+	else
+		target = xtables_find_target(XT_STANDARD_TARGET,
+		         XTF_LOAD_MUST_SUCCEED);
+
+	t = ip6t_get_target((struct ip6t_entry *)fw);
+
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4u ", "%u "), num);
+
+	if (!(format & FMT_NOCOUNTS)) {
+		xtables_print_num(fw->counters.pcnt, format);
+		xtables_print_num(fw->counters.bcnt, format);
+	}
+
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ", "%s "), targname);
+
+	fputc(fw->ipv6.invflags & XT_INV_PROTO ? '!' : ' ', stdout);
+	{
+		const char *pname = proto_to_name(fw->ipv6.proto, format&FMT_NUMERIC);
+		if (pname)
+			printf(FMT("%-5s", "%s "), pname);
+		else
+			printf(FMT("%-5hu", "%hu "), fw->ipv6.proto);
+	}
+
+	if (format & FMT_OPTIONS) {
+		if (format & FMT_NOTABLE)
+			fputs("opt ", stdout);
+		fputc(' ', stdout); /* Invert flag of FRAG */
+		fputc(' ', stdout); /* -f */
+		fputc(' ', stdout);
+	}
+
+	print_ifaces(fw->ipv6.iniface, fw->ipv6.outiface,
+		     fw->ipv6.invflags, format);
+
+	print_ipv6_addresses(fw, format);
+
+	if (format & FMT_NOTABLE)
+		fputs("  ", stdout);
+
+#ifdef IP6T_F_GOTO
+	if(fw->ipv6.flags & IP6T_F_GOTO)
+		printf("[goto] ");
+#endif
+
+	IP6T_MATCH_ITERATE(fw, print_match, &fw->ipv6, format & FMT_NUMERIC);
+
+	if (target) {
+		const int revision = t->u.user.revision;
+
+		tg = xtables_find_target_revision(targname, XTF_TRY_LOAD,
+						  target, revision);
+		if (tg && tg->print)
+			/* Print the target information. */
+			tg->print(&fw->ipv6, t, format & FMT_NUMERIC);
+		else if (target->print)
+			printf(" %s%s", target->name, unsupported_rev);
+	} else if (t->u.target_size != sizeof(*t))
+		printf("[%u bytes of unknown target data] ",
+		       (unsigned int)(t->u.target_size - sizeof(*t)));
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+}
+
+static void
+print_firewall_line(const struct ip6t_entry *fw,
+		    struct xtc_handle *const h)
+{
+	struct xt_entry_target *t;
+
+	t = ip6t_get_target((struct ip6t_entry *)fw);
+	print_firewall(fw, t->u.user.name, 0, FMT_PRINT_RULE, h);
+}
+
+static int
+append_entry(const xt_chainlabel chain,
+	     struct ip6t_entry *fw,
+	     unsigned int nsaddrs,
+	     const struct in6_addr saddrs[],
+	     const struct in6_addr smasks[],
+	     unsigned int ndaddrs,
+	     const struct in6_addr daddrs[],
+	     const struct in6_addr dmasks[],
+	     int verbose,
+	     struct xtc_handle *handle)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		fw->ipv6.src = saddrs[i];
+		fw->ipv6.smsk = smasks[i];
+		for (j = 0; j < ndaddrs; j++) {
+			fw->ipv6.dst = daddrs[j];
+			fw->ipv6.dmsk = dmasks[j];
+			if (verbose)
+				print_firewall_line(fw, handle);
+			ret &= ip6tc_append_entry(chain, fw, handle);
+		}
+	}
+
+	return ret;
+}
+
+static int
+replace_entry(const xt_chainlabel chain,
+	      struct ip6t_entry *fw,
+	      unsigned int rulenum,
+	      const struct in6_addr *saddr, const struct in6_addr *smask,
+	      const struct in6_addr *daddr, const struct in6_addr *dmask,
+	      int verbose,
+	      struct xtc_handle *handle)
+{
+	fw->ipv6.src = *saddr;
+	fw->ipv6.dst = *daddr;
+	fw->ipv6.smsk = *smask;
+	fw->ipv6.dmsk = *dmask;
+
+	if (verbose)
+		print_firewall_line(fw, handle);
+	return ip6tc_replace_entry(chain, fw, rulenum, handle);
+}
+
+static int
+insert_entry(const xt_chainlabel chain,
+	     struct ip6t_entry *fw,
+	     unsigned int rulenum,
+	     unsigned int nsaddrs,
+	     const struct in6_addr saddrs[],
+	     const struct in6_addr smasks[],
+	     unsigned int ndaddrs,
+	     const struct in6_addr daddrs[],
+	     const struct in6_addr dmasks[],
+	     int verbose,
+	     struct xtc_handle *handle)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		fw->ipv6.src = saddrs[i];
+		fw->ipv6.smsk = smasks[i];
+		for (j = 0; j < ndaddrs; j++) {
+			fw->ipv6.dst = daddrs[j];
+			fw->ipv6.dmsk = dmasks[j];
+			if (verbose)
+				print_firewall_line(fw, handle);
+			ret &= ip6tc_insert_entry(chain, fw, rulenum, handle);
+		}
+	}
+
+	return ret;
+}
+
+static unsigned char *
+make_delete_mask(const struct xtables_rule_match *matches,
+		 const struct xtables_target *target)
+{
+	/* Establish mask for comparison */
+	unsigned int size;
+	const struct xtables_rule_match *matchp;
+	unsigned char *mask, *mptr;
+
+	size = sizeof(struct ip6t_entry);
+	for (matchp = matches; matchp; matchp = matchp->next)
+		size += XT_ALIGN(sizeof(struct xt_entry_match)) + matchp->match->size;
+
+	mask = xtables_calloc(1, size
+			 + XT_ALIGN(sizeof(struct xt_entry_target))
+			 + target->size);
+
+	memset(mask, 0xFF, sizeof(struct ip6t_entry));
+	mptr = mask + sizeof(struct ip6t_entry);
+
+	for (matchp = matches; matchp; matchp = matchp->next) {
+		memset(mptr, 0xFF,
+		       XT_ALIGN(sizeof(struct xt_entry_match))
+		       + matchp->match->userspacesize);
+		mptr += XT_ALIGN(sizeof(struct xt_entry_match)) + matchp->match->size;
+	}
+
+	memset(mptr, 0xFF,
+	       XT_ALIGN(sizeof(struct xt_entry_target))
+	       + target->userspacesize);
+
+	return mask;
+}
+
+static int
+delete_entry(const xt_chainlabel chain,
+	     struct ip6t_entry *fw,
+	     unsigned int nsaddrs,
+	     const struct in6_addr saddrs[],
+	     const struct in6_addr smasks[],
+	     unsigned int ndaddrs,
+	     const struct in6_addr daddrs[],
+	     const struct in6_addr dmasks[],
+	     int verbose,
+	     struct xtc_handle *handle,
+	     struct xtables_rule_match *matches,
+	     const struct xtables_target *target)
+{
+	unsigned int i, j;
+	int ret = 1;
+	unsigned char *mask;
+
+	mask = make_delete_mask(matches, target);
+	for (i = 0; i < nsaddrs; i++) {
+		fw->ipv6.src = saddrs[i];
+		fw->ipv6.smsk = smasks[i];
+		for (j = 0; j < ndaddrs; j++) {
+			fw->ipv6.dst = daddrs[j];
+			fw->ipv6.dmsk = dmasks[j];
+			if (verbose)
+				print_firewall_line(fw, handle);
+			ret &= ip6tc_delete_entry(chain, fw, mask, handle);
+		}
+	}
+	free(mask);
+
+	return ret;
+}
+
+static int
+check_entry(const xt_chainlabel chain, struct ip6t_entry *fw,
+	    unsigned int nsaddrs, const struct in6_addr *saddrs,
+	    const struct in6_addr *smasks, unsigned int ndaddrs,
+	    const struct in6_addr *daddrs, const struct in6_addr *dmasks,
+	    bool verbose, struct xtc_handle *handle,
+	    struct xtables_rule_match *matches,
+	    const struct xtables_target *target)
+{
+	unsigned int i, j;
+	int ret = 1;
+	unsigned char *mask;
+
+	mask = make_delete_mask(matches, target);
+	for (i = 0; i < nsaddrs; i++) {
+		fw->ipv6.src = saddrs[i];
+		fw->ipv6.smsk = smasks[i];
+		for (j = 0; j < ndaddrs; j++) {
+			fw->ipv6.dst = daddrs[j];
+			fw->ipv6.dmsk = dmasks[j];
+			if (verbose)
+				print_firewall_line(fw, handle);
+			ret &= ip6tc_check_entry(chain, fw, mask, handle);
+		}
+	}
+
+	free(mask);
+	return ret;
+}
+
+int
+for_each_chain6(int (*fn)(const xt_chainlabel, int, struct xtc_handle *),
+	       int verbose, int builtinstoo, struct xtc_handle *handle)
+{
+	int ret = 1;
+	const char *chain;
+	char *chains;
+	unsigned int i, chaincount = 0;
+
+	chain = ip6tc_first_chain(handle);
+	while (chain) {
+		chaincount++;
+		chain = ip6tc_next_chain(handle);
+	}
+
+	chains = xtables_malloc(sizeof(xt_chainlabel) * chaincount);
+	i = 0;
+	chain = ip6tc_first_chain(handle);
+	while (chain) {
+		strcpy(chains + i*sizeof(xt_chainlabel), chain);
+		i++;
+		chain = ip6tc_next_chain(handle);
+	}
+
+	for (i = 0; i < chaincount; i++) {
+		if (!builtinstoo
+		    && ip6tc_builtin(chains + i*sizeof(xt_chainlabel),
+				    handle) == 1)
+			continue;
+		ret &= fn(chains + i*sizeof(xt_chainlabel), verbose, handle);
+	}
+
+	free(chains);
+	return ret;
+}
+
+int
+flush_entries6(const xt_chainlabel chain, int verbose,
+	      struct xtc_handle *handle)
+{
+	if (!chain)
+		return for_each_chain6(flush_entries6, verbose, 1, handle);
+
+	if (verbose)
+		fprintf(stdout, "Flushing chain `%s'\n", chain);
+	return ip6tc_flush_entries(chain, handle);
+}
+
+static int
+zero_entries(const xt_chainlabel chain, int verbose,
+	     struct xtc_handle *handle)
+{
+	if (!chain)
+		return for_each_chain6(zero_entries, verbose, 1, handle);
+
+	if (verbose)
+		fprintf(stdout, "Zeroing chain `%s'\n", chain);
+	return ip6tc_zero_entries(chain, handle);
+}
+
+int
+delete_chain6(const xt_chainlabel chain, int verbose,
+	     struct xtc_handle *handle)
+{
+	if (!chain)
+		return for_each_chain6(delete_chain6, verbose, 0, handle);
+
+	if (verbose)
+		fprintf(stdout, "Deleting chain `%s'\n", chain);
+	return ip6tc_delete_chain(chain, handle);
+}
+
+static int
+list_entries(const xt_chainlabel chain, int rulenum, int verbose, int numeric,
+	     int expanded, int linenumbers, struct xtc_handle *handle)
+{
+	int found = 0;
+	unsigned int format;
+	const char *this;
+
+	format = FMT_OPTIONS;
+	if (!verbose)
+		format |= FMT_NOCOUNTS;
+	else
+		format |= FMT_VIA;
+
+	if (numeric)
+		format |= FMT_NUMERIC;
+
+	if (!expanded)
+		format |= FMT_KILOMEGAGIGA;
+
+	if (linenumbers)
+		format |= FMT_LINENUMBERS;
+
+	for (this = ip6tc_first_chain(handle);
+	     this;
+	     this = ip6tc_next_chain(handle)) {
+		const struct ip6t_entry *i;
+		unsigned int num;
+
+		if (chain && strcmp(chain, this) != 0)
+			continue;
+
+		if (found) printf("\n");
+
+		if (!rulenum)
+		    print_header(format, this, handle);
+		i = ip6tc_first_rule(this, handle);
+
+		num = 0;
+		while (i) {
+			num++;
+			if (!rulenum || num == rulenum)
+				print_firewall(i,
+					       ip6tc_get_target(i, handle),
+					       num,
+					       format,
+					       handle);
+			i = ip6tc_next_rule(i, handle);
+		}
+		found = 1;
+	}
+
+	errno = ENOENT;
+	return found;
+}
+
+/* This assumes that mask is contiguous, and byte-bounded. */
+static void
+print_iface(char letter, const char *iface, const unsigned char *mask,
+	    int invert)
+{
+	unsigned int i;
+
+	if (mask[0] == 0)
+		return;
+
+	printf("%s -%c ", invert ? " !" : "", letter);
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (mask[i] != 0) {
+			if (iface[i] != '\0')
+				printf("%c", iface[i]);
+		} else {
+			/* we can access iface[i-1] here, because
+			 * a few lines above we make sure that mask[0] != 0 */
+			if (iface[i-1] != '\0')
+				printf("+");
+			break;
+		}
+	}
+}
+
+/* The ip6tables looks up the /etc/protocols. */
+static void print_proto(uint16_t proto, int invert)
+{
+	if (proto) {
+		unsigned int i;
+		const char *invertstr = invert ? " !" : "";
+
+		const struct protoent *pent = getprotobynumber(proto);
+		if (pent) {
+			printf("%s -p %s",
+			       invertstr, pent->p_name);
+			return;
+		}
+
+		for (i = 0; xtables_chain_protos[i].name != NULL; ++i)
+			if (xtables_chain_protos[i].num == proto) {
+				printf("%s -p %s",
+				       invertstr, xtables_chain_protos[i].name);
+				return;
+			}
+
+		printf("%s -p %u", invertstr, proto);
+	}
+}
+
+static int print_match_save(const struct xt_entry_match *e,
+			const struct ip6t_ip6 *ip)
+{
+	const char *name = e->u.user.name;
+	const int revision = e->u.user.revision;
+	struct xtables_match *match, *mt, *mt2;
+
+	match = xtables_find_match(name, XTF_TRY_LOAD, NULL);
+	if (match) {
+		mt = mt2 = xtables_find_match_revision(name, XTF_TRY_LOAD,
+						       match, revision);
+		if (!mt2)
+			mt2 = match;
+		printf(" -m %s", mt2->alias ? mt2->alias(e) : name);
+
+		/* some matches don't provide a save function */
+		if (mt && mt->save)
+			mt->save(ip, e);
+		else if (match->save)
+			printf(unsupported_rev);
+	} else {
+		if (e->u.match_size) {
+			fprintf(stderr,
+				"Can't find library for match `%s'\n",
+				name);
+			exit(1);
+		}
+	}
+	return 0;
+}
+
+/* Print a given ip including mask if necessary. */
+static void print_ip(const char *prefix, const struct in6_addr *ip,
+		     const struct in6_addr *mask, int invert)
+{
+	char buf[51];
+	int l = xtables_ip6mask_to_cidr(mask);
+
+	if (l == 0 && !invert)
+		return;
+
+	printf("%s %s %s",
+		invert ? " !" : "",
+		prefix,
+		inet_ntop(AF_INET6, ip, buf, sizeof buf));
+
+	if (l == -1)
+		printf("/%s", inet_ntop(AF_INET6, mask, buf, sizeof buf));
+	else
+		printf("/%d", l);
+}
+
+/* We want this to be readable, so only print out necessary fields.
+ * Because that's the kind of world I want to live in.
+ */
+void print_rule6(const struct ip6t_entry *e,
+		       struct xtc_handle *h, const char *chain, int counters)
+{
+	const struct xt_entry_target *t;
+	const char *target_name;
+
+	/* print counters for iptables-save */
+	if (counters > 0)
+		printf("[%llu:%llu] ", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);
+
+	/* print chain name */
+	printf("-A %s", chain);
+
+	/* Print IP part. */
+	print_ip("-s", &(e->ipv6.src), &(e->ipv6.smsk),
+			e->ipv6.invflags & IP6T_INV_SRCIP);
+
+	print_ip("-d", &(e->ipv6.dst), &(e->ipv6.dmsk),
+			e->ipv6.invflags & IP6T_INV_DSTIP);
+
+	print_iface('i', e->ipv6.iniface, e->ipv6.iniface_mask,
+		    e->ipv6.invflags & IP6T_INV_VIA_IN);
+
+	print_iface('o', e->ipv6.outiface, e->ipv6.outiface_mask,
+		    e->ipv6.invflags & IP6T_INV_VIA_OUT);
+
+	print_proto(e->ipv6.proto, e->ipv6.invflags & XT_INV_PROTO);
+
+#if 0
+	/* not definied in ipv6
+	 * FIXME: linux/netfilter_ipv6/ip6_tables: IP6T_INV_FRAG why definied? */
+	if (e->ipv6.flags & IPT_F_FRAG)
+		printf("%s -f",
+		       e->ipv6.invflags & IP6T_INV_FRAG ? " !" : "");
+#endif
+
+	if (e->ipv6.flags & IP6T_F_TOS)
+		printf("%s -? %d",
+		       e->ipv6.invflags & IP6T_INV_TOS ? " !" : "",
+		       e->ipv6.tos);
+
+	/* Print matchinfo part */
+	if (e->target_offset) {
+		IP6T_MATCH_ITERATE(e, print_match_save, &e->ipv6);
+	}
+
+	/* print counters for iptables -R */
+	if (counters < 0)
+		printf(" -c %llu %llu", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);
+
+	/* Print target name and targinfo part */
+	target_name = ip6tc_get_target(e, h);
+	t = ip6t_get_target((struct ip6t_entry *)e);
+	if (t->u.user.name[0]) {
+		const char *name = t->u.user.name;
+		const int revision = t->u.user.revision;
+		struct xtables_target *target, *tg, *tg2;
+
+		target = xtables_find_target(name, XTF_TRY_LOAD);
+		if (!target) {
+			fprintf(stderr, "Can't find library for target `%s'\n",
+				name);
+			exit(1);
+		}
+
+		tg = tg2 = xtables_find_target_revision(name, XTF_TRY_LOAD,
+							target, revision);
+		if (!tg2)
+			tg2 = target;
+		printf(" -j %s", tg2->alias ? tg2->alias(t) : target_name);
+
+		if (tg && tg->save)
+			tg->save(&e->ipv6, t);
+		else if (target->save)
+			printf(unsupported_rev);
+		else {
+			/* If the target size is greater than xt_entry_target
+			 * there is something to be saved, we just don't know
+			 * how to print it */
+			if (t->u.target_size !=
+			    sizeof(struct xt_entry_target)) {
+				fprintf(stderr, "Target `%s' is missing "
+						"save function\n",
+					name);
+				exit(1);
+			}
+		}
+	} else if (target_name && (*target_name != '\0'))
+#ifdef IP6T_F_GOTO
+		printf(" -%c %s", e->ipv6.flags & IP6T_F_GOTO ? 'g' : 'j', target_name);
+#else
+		printf(" -j %s", target_name);
+#endif
+
+	printf("\n");
+}
+
+static int
+list_rules(const xt_chainlabel chain, int rulenum, int counters,
+	     struct xtc_handle *handle)
+{
+	const char *this = NULL;
+	int found = 0;
+
+	if (counters)
+	    counters = -1;		/* iptables -c format */
+
+	/* Dump out chain names first,
+	 * thereby preventing dependency conflicts */
+	if (!rulenum) for (this = ip6tc_first_chain(handle);
+	     this;
+	     this = ip6tc_next_chain(handle)) {
+		if (chain && strcmp(this, chain) != 0)
+			continue;
+
+		if (ip6tc_builtin(this, handle)) {
+			struct xt_counters count;
+			printf("-P %s %s", this, ip6tc_get_policy(this, &count, handle));
+			if (counters)
+			    printf(" -c %llu %llu", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt);
+			printf("\n");
+		} else {
+			printf("-N %s\n", this);
+		}
+	}
+
+	for (this = ip6tc_first_chain(handle);
+	     this;
+	     this = ip6tc_next_chain(handle)) {
+		const struct ip6t_entry *e;
+		int num = 0;
+
+		if (chain && strcmp(this, chain) != 0)
+			continue;
+
+		/* Dump out rules */
+		e = ip6tc_first_rule(this, handle);
+		while(e) {
+			num++;
+			if (!rulenum || num == rulenum)
+			    print_rule6(e, handle, this, counters);
+			e = ip6tc_next_rule(e, handle);
+		}
+		found = 1;
+	}
+
+	errno = ENOENT;
+	return found;
+}
+
+static struct ip6t_entry *
+generate_entry(const struct ip6t_entry *fw,
+	       struct xtables_rule_match *matches,
+	       struct xt_entry_target *target)
+{
+	unsigned int size;
+	struct xtables_rule_match *matchp;
+	struct ip6t_entry *e;
+
+	size = sizeof(struct ip6t_entry);
+	for (matchp = matches; matchp; matchp = matchp->next)
+		size += matchp->match->m->u.match_size;
+
+	e = xtables_malloc(size + target->u.target_size);
+	*e = *fw;
+	e->target_offset = size;
+	e->next_offset = size + target->u.target_size;
+
+	size = 0;
+	for (matchp = matches; matchp; matchp = matchp->next) {
+		memcpy(e->elems + size, matchp->match->m, matchp->match->m->u.match_size);
+		size += matchp->match->m->u.match_size;
+	}
+	memcpy(e->elems + size, target, target->u.target_size);
+
+	return e;
+}
+
+int do_command6(int argc, char *argv[], char **table,
+		struct xtc_handle **handle, bool restore)
+{
+	struct iptables_command_state cs = {
+		.jumpto	= "",
+		.argv	= argv,
+	};
+	struct ip6t_entry *e = NULL;
+	unsigned int nsaddrs = 0, ndaddrs = 0;
+	struct in6_addr *saddrs = NULL, *daddrs = NULL;
+	struct in6_addr *smasks = NULL, *dmasks = NULL;
+
+	int verbose = 0;
+	int wait = 0;
+	struct timeval wait_interval = {
+		.tv_sec	= 1,
+	};
+	bool wait_interval_set = false;
+	const char *chain = NULL;
+	const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;
+	const char *policy = NULL, *newname = NULL;
+	unsigned int rulenum = 0, command = 0;
+	const char *pcnt = NULL, *bcnt = NULL;
+	int ret = 1;
+	struct xtables_match *m;
+	struct xtables_rule_match *matchp;
+	struct xtables_target *t;
+	unsigned long long cnt;
+	bool table_set = false;
+
+	/* re-set optind to 0 in case do_command6 gets called
+	 * a second time */
+	optind = 0;
+
+	/* clear mflags in case do_command6 gets called a second time
+	 * (we clear the global list of all matches for security)*/
+	for (m = xtables_matches; m; m = m->next)
+		m->mflags = 0;
+
+	for (t = xtables_targets; t; t = t->next) {
+		t->tflags = 0;
+		t->used = 0;
+	}
+
+	/* Suppress error messages: we may add new options if we
+           demand-load a protocol. */
+	opterr = 0;
+
+	opts = xt_params->orig_opts;
+	while ((cs.c = getopt_long(argc, argv,
+	   "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:bvw::W::nt:m:xc:g:46",
+					   opts, NULL)) != -1) {
+		switch (cs.c) {
+			/*
+			 * Command selection
+			 */
+		case 'A':
+			add_command(&command, CMD_APPEND, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			break;
+
+		case 'C':
+			add_command(&command, CMD_CHECK, CMD_NONE,
+			            cs.invert);
+			chain = optarg;
+			break;
+
+		case 'D':
+			add_command(&command, CMD_DELETE, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv)) {
+				rulenum = parse_rulenumber(argv[optind++]);
+				command = CMD_DELETE_NUM;
+			}
+			break;
+
+		case 'R':
+			add_command(&command, CMD_REPLACE, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires a rule number",
+					   cmd2char(CMD_REPLACE));
+			break;
+
+		case 'I':
+			add_command(&command, CMD_INSERT, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			else rulenum = 1;
+			break;
+
+		case 'L':
+			add_command(&command, CMD_LIST,
+				    CMD_ZERO | CMD_ZERO_NUM, cs.invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			break;
+
+		case 'S':
+			add_command(&command, CMD_LIST_RULES,
+				    CMD_ZERO | CMD_ZERO_NUM, cs.invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			break;
+
+		case 'F':
+			add_command(&command, CMD_FLUSH, CMD_NONE,
+				    cs.invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'Z':
+			add_command(&command, CMD_ZERO, CMD_LIST|CMD_LIST_RULES,
+				    cs.invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			if (xs_has_arg(argc, argv)) {
+				rulenum = parse_rulenumber(argv[optind++]);
+				command = CMD_ZERO_NUM;
+			}
+			break;
+
+		case 'N':
+			parse_chain(optarg);
+			add_command(&command, CMD_NEW_CHAIN, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			break;
+
+		case 'X':
+			add_command(&command, CMD_DELETE_CHAIN, CMD_NONE,
+				    cs.invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'E':
+			add_command(&command, CMD_RENAME_CHAIN, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				newname = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires old-chain-name and "
+					   "new-chain-name",
+					    cmd2char(CMD_RENAME_CHAIN));
+			break;
+
+		case 'P':
+			add_command(&command, CMD_SET_POLICY, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				policy = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires a chain and a policy",
+					   cmd2char(CMD_SET_POLICY));
+			break;
+
+		case 'h':
+			if (!optarg)
+				optarg = argv[optind];
+
+			/* ip6tables -p icmp -h */
+			if (!cs.matches && cs.protocol)
+				xtables_find_match(cs.protocol, XTF_TRY_LOAD,
+					&cs.matches);
+
+			exit_printhelp(cs.matches);
+
+			/*
+			 * Option selection
+			 */
+		case 'p':
+			set_option(&cs.options, OPT_PROTOCOL, &cs.fw6.ipv6.invflags,
+				   cs.invert);
+
+			/* Canonicalize into lower case */
+			for (cs.protocol = optarg; *cs.protocol; cs.protocol++)
+				*cs.protocol = tolower(*cs.protocol);
+
+			cs.protocol = optarg;
+			cs.fw6.ipv6.proto = xtables_parse_protocol(cs.protocol);
+			cs.fw6.ipv6.flags |= IP6T_F_PROTO;
+
+			if (cs.fw6.ipv6.proto == 0
+			    && (cs.fw6.ipv6.invflags & XT_INV_PROTO))
+				xtables_error(PARAMETER_PROBLEM,
+					   "rule would never match protocol");
+
+			if (is_exthdr(cs.fw6.ipv6.proto)
+			    && (cs.fw6.ipv6.invflags & XT_INV_PROTO) == 0)
+				fprintf(stderr,
+					"Warning: never matched protocol: %s. "
+					"use extension match instead.\n",
+					cs.protocol);
+			break;
+
+		case 's':
+			set_option(&cs.options, OPT_SOURCE, &cs.fw6.ipv6.invflags,
+				   cs.invert);
+			shostnetworkmask = optarg;
+			break;
+
+		case 'd':
+			set_option(&cs.options, OPT_DESTINATION, &cs.fw6.ipv6.invflags,
+				   cs.invert);
+			dhostnetworkmask = optarg;
+			break;
+
+#ifdef IP6T_F_GOTO
+		case 'g':
+			set_option(&cs.options, OPT_JUMP, &cs.fw6.ipv6.invflags,
+					cs.invert);
+			cs.fw6.ipv6.flags |= IP6T_F_GOTO;
+			cs.jumpto = xt_parse_target(optarg);
+			break;
+#endif
+
+		case 'j':
+			set_option(&cs.options, OPT_JUMP, &cs.fw6.ipv6.invflags,
+					cs.invert);
+			command_jump(&cs, optarg);
+			break;
+
+
+		case 'i':
+			if (*optarg == '\0')
+				xtables_error(PARAMETER_PROBLEM,
+					"Empty interface is likely to be "
+					"undesired");
+			set_option(&cs.options, OPT_VIANAMEIN, &cs.fw6.ipv6.invflags,
+				   cs.invert);
+			xtables_parse_interface(optarg,
+					cs.fw6.ipv6.iniface,
+					cs.fw6.ipv6.iniface_mask);
+			break;
+
+		case 'o':
+			if (*optarg == '\0')
+				xtables_error(PARAMETER_PROBLEM,
+					"Empty interface is likely to be "
+					"undesired");
+			set_option(&cs.options, OPT_VIANAMEOUT, &cs.fw6.ipv6.invflags,
+				   cs.invert);
+			xtables_parse_interface(optarg,
+					cs.fw6.ipv6.outiface,
+					cs.fw6.ipv6.outiface_mask);
+			break;
+
+		case 'v':
+			if (!verbose)
+				set_option(&cs.options, OPT_VERBOSE,
+					   &cs.fw6.ipv6.invflags, cs.invert);
+			verbose++;
+			break;
+
+		case 'w':
+			if (restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-w' from "
+					      "ip6tables-restore");
+			}
+			wait = parse_wait_time(argc, argv);
+			break;
+
+		case 'W':
+			if (restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-W' from "
+					      "ip6tables-restore");
+			}
+			parse_wait_interval(argc, argv, &wait_interval);
+			wait_interval_set = true;
+			break;
+
+		case 'm':
+			command_match(&cs);
+			break;
+
+		case 'n':
+			set_option(&cs.options, OPT_NUMERIC, &cs.fw6.ipv6.invflags,
+				   cs.invert);
+			break;
+
+		case 't':
+			if (cs.invert)
+				xtables_error(PARAMETER_PROBLEM,
+					   "unexpected ! flag before --table");
+			if (restore && table_set)
+				xtables_error(PARAMETER_PROBLEM,
+					      "The -t option (seen in line %u) cannot be used in %s.\n",
+					      line, xt_params->program_name);
+			*table = optarg;
+			table_set = true;
+			break;
+
+		case 'x':
+			set_option(&cs.options, OPT_EXPANDED, &cs.fw6.ipv6.invflags,
+				   cs.invert);
+			break;
+
+		case 'V':
+			if (cs.invert)
+				printf("Not %s ;-)\n", prog_vers);
+			else
+				printf("%s v%s (legacy)\n",
+				       prog_name, prog_vers);
+			exit(0);
+
+		case '0':
+			set_option(&cs.options, OPT_LINENUMBERS, &cs.fw6.ipv6.invflags,
+				   cs.invert);
+			break;
+
+		case 'M':
+			xtables_modprobe_program = optarg;
+			break;
+
+		case 'c':
+
+			set_option(&cs.options, OPT_COUNTERS, &cs.fw6.ipv6.invflags,
+				   cs.invert);
+			pcnt = optarg;
+			bcnt = strchr(pcnt + 1, ',');
+			if (bcnt)
+			    bcnt++;
+			if (!bcnt && xs_has_arg(argc, argv))
+				bcnt = argv[optind++];
+			if (!bcnt)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c requires packet and byte counter",
+					opt2char(OPT_COUNTERS));
+
+			if (sscanf(pcnt, "%llu", &cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c packet counter not numeric",
+					opt2char(OPT_COUNTERS));
+			cs.fw6.counters.pcnt = cnt;
+
+			if (sscanf(bcnt, "%llu", &cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c byte counter not numeric",
+					opt2char(OPT_COUNTERS));
+			cs.fw6.counters.bcnt = cnt;
+			break;
+
+		case '4':
+			/* This is not the IPv4 iptables */
+			if (line != -1)
+				return 1; /* success: line ignored */
+			fprintf(stderr, "This is the IPv6 version of ip6tables.\n");
+			exit_tryhelp(2);
+
+		case '6':
+			/* This is indeed the IPv6 ip6tables */
+			break;
+
+		case 1: /* non option */
+			if (optarg[0] == '!' && optarg[1] == '\0') {
+				if (cs.invert)
+					xtables_error(PARAMETER_PROBLEM,
+						   "multiple consecutive ! not"
+						   " allowed");
+				cs.invert = true;
+				optarg[0] = '\0';
+				continue;
+			}
+			fprintf(stderr, "Bad argument `%s'\n", optarg);
+			exit_tryhelp(2);
+
+		default:
+			if (command_default(&cs, &ip6tables_globals) == 1)
+				/*
+				 * If new options were loaded, we must retry
+				 * getopt immediately and not allow
+				 * cs.invert=false to be executed.
+				 */
+				continue;
+			break;
+		}
+		cs.invert = false;
+	}
+
+	if (!wait && wait_interval_set)
+		xtables_error(PARAMETER_PROBLEM,
+			      "--wait-interval only makes sense with --wait\n");
+
+	if (strcmp(*table, "nat") == 0 &&
+	    ((policy != NULL && strcmp(policy, "DROP") == 0) ||
+	    (cs.jumpto != NULL && strcmp(cs.jumpto, "DROP") == 0)))
+		xtables_error(PARAMETER_PROBLEM,
+			"\nThe \"nat\" table is not intended for filtering, "
+		        "the use of DROP is therefore inhibited.\n\n");
+
+	for (matchp = cs.matches; matchp; matchp = matchp->next)
+		xtables_option_mfcall(matchp->match);
+	if (cs.target != NULL)
+		xtables_option_tfcall(cs.target);
+
+	/* Fix me: must put inverse options checking here --MN */
+
+	if (optind < argc)
+		xtables_error(PARAMETER_PROBLEM,
+			   "unknown arguments found on commandline");
+	if (!command)
+		xtables_error(PARAMETER_PROBLEM, "no command specified");
+	if (cs.invert)
+		xtables_error(PARAMETER_PROBLEM,
+			   "nothing appropriate following !");
+
+	if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+		if (!(cs.options & OPT_DESTINATION))
+			dhostnetworkmask = "::0/0";
+		if (!(cs.options & OPT_SOURCE))
+			shostnetworkmask = "::0/0";
+	}
+
+	if (shostnetworkmask)
+		xtables_ip6parse_multiple(shostnetworkmask, &saddrs,
+					  &smasks, &nsaddrs);
+
+	if (dhostnetworkmask)
+		xtables_ip6parse_multiple(dhostnetworkmask, &daddrs,
+					  &dmasks, &ndaddrs);
+
+	if ((nsaddrs > 1 || ndaddrs > 1) &&
+	    (cs.fw6.ipv6.invflags & (IP6T_INV_SRCIP | IP6T_INV_DSTIP)))
+		xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple"
+			   " source or destination IP addresses");
+
+	if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1))
+		xtables_error(PARAMETER_PROBLEM, "Replacement rule does not "
+			   "specify a unique address");
+
+	generic_opt_check(command, cs.options);
+
+	/* Attempt to acquire the xtables lock */
+	if (!restore)
+		xtables_lock_or_exit(wait, &wait_interval);
+
+	/* only allocate handle if we weren't called with a handle */
+	if (!*handle)
+		*handle = ip6tc_init(*table);
+
+	/* try to insmod the module if iptc_init failed */
+	if (!*handle && xtables_load_ko(xtables_modprobe_program, false) != -1)
+		*handle = ip6tc_init(*table);
+
+	if (!*handle)
+		xtables_error(VERSION_PROBLEM,
+			"can't initialize ip6tables table `%s': %s",
+			*table, ip6tc_strerror(errno));
+
+	if (command == CMD_APPEND
+	    || command == CMD_DELETE
+	    || command == CMD_CHECK
+	    || command == CMD_INSERT
+	    || command == CMD_REPLACE) {
+		if (strcmp(chain, "PREROUTING") == 0
+		    || strcmp(chain, "INPUT") == 0) {
+			/* -o not valid with incoming packets. */
+			if (cs.options & OPT_VIANAMEOUT)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Can't use -%c with %s\n",
+					   opt2char(OPT_VIANAMEOUT),
+					   chain);
+		}
+
+		if (strcmp(chain, "POSTROUTING") == 0
+		    || strcmp(chain, "OUTPUT") == 0) {
+			/* -i not valid with outgoing packets */
+			if (cs.options & OPT_VIANAMEIN)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Can't use -%c with %s\n",
+					   opt2char(OPT_VIANAMEIN),
+					   chain);
+		}
+
+		if (cs.target && ip6tc_is_chain(cs.jumpto, *handle)) {
+			fprintf(stderr,
+				"Warning: using chain %s, not extension\n",
+				cs.jumpto);
+
+			if (cs.target->t)
+				free(cs.target->t);
+
+			cs.target = NULL;
+		}
+
+		/* If they didn't specify a target, or it's a chain
+		   name, use standard. */
+		if (!cs.target
+		    && (strlen(cs.jumpto) == 0
+			|| ip6tc_is_chain(cs.jumpto, *handle))) {
+			size_t size;
+
+			cs.target = xtables_find_target(XT_STANDARD_TARGET,
+					XTF_LOAD_MUST_SUCCEED);
+
+			size = sizeof(struct xt_entry_target)
+				+ cs.target->size;
+			cs.target->t = xtables_calloc(1, size);
+			cs.target->t->u.target_size = size;
+			strcpy(cs.target->t->u.user.name, cs.jumpto);
+			xs_init_target(cs.target);
+		}
+
+		if (!cs.target) {
+			/* It is no chain, and we can't load a plugin.
+			 * We cannot know if the plugin is corrupt, non
+			 * existent OR if the user just misspelled a
+			 * chain.
+			 */
+#ifdef IP6T_F_GOTO
+			if (cs.fw6.ipv6.flags & IP6T_F_GOTO)
+				xtables_error(PARAMETER_PROBLEM,
+						"goto '%s' is not a chain\n",
+						cs.jumpto);
+#endif
+			xtables_find_target(cs.jumpto, XTF_LOAD_MUST_SUCCEED);
+		} else {
+			e = generate_entry(&cs.fw6, cs.matches, cs.target->t);
+			free(cs.target->t);
+		}
+	}
+
+	switch (command) {
+	case CMD_APPEND:
+		ret = append_entry(chain, e,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   cs.options&OPT_VERBOSE,
+				   *handle);
+		break;
+	case CMD_DELETE:
+		ret = delete_entry(chain, e,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   cs.options&OPT_VERBOSE,
+				   *handle, cs.matches, cs.target);
+		break;
+	case CMD_DELETE_NUM:
+		ret = ip6tc_delete_num_entry(chain, rulenum - 1, *handle);
+		break;
+	case CMD_CHECK:
+		ret = check_entry(chain, e,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   cs.options&OPT_VERBOSE,
+				   *handle, cs.matches, cs.target);
+		break;
+	case CMD_REPLACE:
+		ret = replace_entry(chain, e, rulenum - 1,
+				    saddrs, smasks, daddrs, dmasks,
+				    cs.options&OPT_VERBOSE, *handle);
+		break;
+	case CMD_INSERT:
+		ret = insert_entry(chain, e, rulenum - 1,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   cs.options&OPT_VERBOSE,
+				   *handle);
+		break;
+	case CMD_FLUSH:
+		ret = flush_entries6(chain, cs.options&OPT_VERBOSE, *handle);
+		break;
+	case CMD_ZERO:
+		ret = zero_entries(chain, cs.options&OPT_VERBOSE, *handle);
+		break;
+	case CMD_ZERO_NUM:
+		ret = ip6tc_zero_counter(chain, rulenum, *handle);
+		break;
+	case CMD_LIST:
+	case CMD_LIST|CMD_ZERO:
+	case CMD_LIST|CMD_ZERO_NUM:
+		ret = list_entries(chain,
+				   rulenum,
+				   cs.options&OPT_VERBOSE,
+				   cs.options&OPT_NUMERIC,
+				   cs.options&OPT_EXPANDED,
+				   cs.options&OPT_LINENUMBERS,
+				   *handle);
+		if (ret && (command & CMD_ZERO))
+			ret = zero_entries(chain,
+					   cs.options&OPT_VERBOSE, *handle);
+		if (ret && (command & CMD_ZERO_NUM))
+			ret = ip6tc_zero_counter(chain, rulenum, *handle);
+		break;
+	case CMD_LIST_RULES:
+	case CMD_LIST_RULES|CMD_ZERO:
+	case CMD_LIST_RULES|CMD_ZERO_NUM:
+		ret = list_rules(chain,
+				   rulenum,
+				   cs.options&OPT_VERBOSE,
+				   *handle);
+		if (ret && (command & CMD_ZERO))
+			ret = zero_entries(chain,
+					   cs.options&OPT_VERBOSE, *handle);
+		if (ret && (command & CMD_ZERO_NUM))
+			ret = ip6tc_zero_counter(chain, rulenum, *handle);
+		break;
+	case CMD_NEW_CHAIN:
+		ret = ip6tc_create_chain(chain, *handle);
+		break;
+	case CMD_DELETE_CHAIN:
+		ret = delete_chain6(chain, cs.options&OPT_VERBOSE, *handle);
+		break;
+	case CMD_RENAME_CHAIN:
+		ret = ip6tc_rename_chain(chain, newname,	*handle);
+		break;
+	case CMD_SET_POLICY:
+		ret = ip6tc_set_policy(chain, policy, cs.options&OPT_COUNTERS ? &cs.fw6.counters : NULL, *handle);
+		break;
+	default:
+		/* We should never reach this... */
+		exit_tryhelp(2);
+	}
+
+	if (verbose > 1)
+		dump_entries6(*handle);
+
+	xtables_rule_matches_free(&cs.matches);
+
+	if (e != NULL) {
+		free(e);
+		e = NULL;
+	}
+
+	free(saddrs);
+	free(smasks);
+	free(daddrs);
+	free(dmasks);
+	xtables_free_opts(1);
+
+	return ret;
+}
diff --git a/iptables/iptables-apply b/iptables/iptables-apply
new file mode 100755
index 0000000..4683b1b
--- /dev/null
+++ b/iptables/iptables-apply
@@ -0,0 +1,296 @@
+#!/bin/bash
+# iptables-apply -- a safer way to update iptables remotely
+#
+# Usage:
+#   iptables-apply [-hV] [-t timeout] [-w savefile] {[rulesfile]|-c [runcmd]}
+#
+# Versions:
+#   * 1.0 Copyright 2006 Martin F. Krafft <madduck@madduck.net>
+#         Original version
+#   * 1.1 Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>
+#         Added parameter -c (run command)
+#         Added parameter -w (save successfully applied rules to file)
+#         Major code cleanup
+#
+# Released under the terms of the Artistic Licence 2.0
+#
+set -eu
+
+PROGNAME="${0##*/}"
+VERSION=1.1
+
+
+### Default settings
+
+DEF_TIMEOUT=10
+
+MODE=0  # apply rulesfile mode
+# MODE=1  # run command mode
+
+case "$PROGNAME" in
+	(*6*)
+		SAVE=ip6tables-save
+		RESTORE=ip6tables-restore
+		DEF_RULESFILE="/etc/network/ip6tables.up.rules"
+		DEF_SAVEFILE="$DEF_RULESFILE"
+		DEF_RUNCMD="/etc/network/ip6tables.up.run"
+		;;
+	(*)
+		SAVE=iptables-save
+		RESTORE=iptables-restore
+		DEF_RULESFILE="/etc/network/iptables.up.rules"
+		DEF_SAVEFILE="$DEF_RULESFILE"
+		DEF_RUNCMD="/etc/network/iptables.up.run"
+		;;
+esac
+
+
+### Functions
+
+function blurb() {
+	cat <<-__EOF__
+	$PROGNAME $VERSION -- a safer way to update iptables remotely
+	__EOF__
+}
+
+function copyright() {
+	cat <<-__EOF__
+	$PROGNAME has been published under the terms of the Artistic Licence 2.0.
+
+	Original version - Copyright 2006 Martin F. Krafft <madduck@madduck.net>.
+	Version 1.1 - Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>.
+	__EOF__
+}
+
+function about() {
+	blurb
+	echo
+	copyright
+}
+
+function usage() {
+	blurb
+	echo
+	cat <<-__EOF__
+	Usage:
+	  $PROGNAME [-hV] [-t timeout] [-w savefile] {[rulesfile]|-c [runcmd]}
+
+	The script will try to apply a new rulesfile (as output by iptables-save,
+	read by iptables-restore) or run a command to configure iptables and then
+	prompt the user whether the changes are okay. If the new iptables rules cut
+	the existing connection, the user will not be able to answer affirmatively.
+	In this case, the script rolls back to the previous working iptables rules
+	after the timeout expires.
+
+	Successfully applied rules can also be written to savefile and later used
+	to roll back to this state. This can be used to implement a store last good
+	configuration mechanism when experimenting with an iptables setup script:
+	  $PROGNAME -w $DEF_SAVEFILE -c $DEF_RUNCMD
+
+	When called as ip6tables-apply, the script will use ip6tables-save/-restore
+	and IPv6 default values instead. Default value for rulesfile is
+	'$DEF_RULESFILE'.
+
+	Options:
+
+	-t seconds, --timeout seconds
+	  Specify the timeout in seconds (default: $DEF_TIMEOUT).
+	-w savefile, --write savefile
+	  Specify the savefile where successfully applied rules will be written to
+	  (default if empty string is given: $DEF_SAVEFILE).
+	-c runcmd, --command runcmd
+	  Run command runcmd to configure iptables instead of applying a rulesfile
+	  (default: $DEF_RUNCMD).
+	-h, --help
+	  Display this help text.
+	-V, --version
+	  Display version information.
+
+	__EOF__
+}
+
+function checkcommands() {
+	for cmd in "${COMMANDS[@]}"; do
+		if ! command -v "$cmd" >/dev/null; then
+			echo "Error: needed command not found: $cmd" >&2
+			exit 127
+		fi
+	done
+}
+
+function revertrules() {
+	echo -n "Reverting to old iptables rules... "
+	"$RESTORE" <"$TMPFILE"
+	echo "done."
+}
+
+
+### Parsing and checking parameters
+
+TIMEOUT="$DEF_TIMEOUT"
+SAVEFILE=""
+
+SHORTOPTS="t:w:chV";
+LONGOPTS="timeout:,write:,command,help,version";
+
+OPTS=$(getopt -s bash -o "$SHORTOPTS" -l "$LONGOPTS" -n "$PROGNAME" -- "$@") || exit $?
+for opt in $OPTS; do
+	case "$opt" in
+		(-*)
+			unset OPT_STATE
+			;;
+		(*)
+			case "${OPT_STATE:-}" in
+				(SET_TIMEOUT) eval TIMEOUT=$opt;;
+				(SET_SAVEFILE)
+					eval SAVEFILE=$opt
+					[ -z "$SAVEFILE" ] && SAVEFILE="$DEF_SAVEFILE"
+					;;
+			esac
+			;;
+	esac
+
+	case "$opt" in
+		(-t|--timeout) OPT_STATE="SET_TIMEOUT";;
+		(-w|--write) OPT_STATE="SET_SAVEFILE";;
+		(-c|--command) MODE=1;;
+		(-h|--help) usage >&2; exit 0;;
+		(-V|--version) about >&2; exit 0;;
+		(--) break;;
+	esac
+	shift
+done
+
+# Validate parameters
+if [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then
+	TIMEOUT=$(($TIMEOUT))
+else
+	echo "Error: timeout must be a positive number" >&2
+	exit 1
+fi
+
+if [ -n "$SAVEFILE" -a -e "$SAVEFILE" -a ! -w "$SAVEFILE" ]; then
+	echo "Error: savefile not writable: $SAVEFILE" >&2
+	exit 8
+fi
+
+case "$MODE" in
+	(1)
+		# Treat parameter as runcmd (run command mode)
+		RUNCMD="${1:-$DEF_RUNCMD}"
+		if [ ! -x "$RUNCMD" ]; then
+			echo "Error: runcmd not executable: $RUNCMD" >&2
+			exit 6
+		fi
+
+		# Needed commands
+		COMMANDS=(mktemp "$SAVE" "$RESTORE" "$RUNCMD")
+		checkcommands
+		;;
+	(*)
+		# Treat parameter as rulesfile (apply rulesfile mode)
+		RULESFILE="${1:-$DEF_RULESFILE}";
+		if [ ! -r "$RULESFILE" ]; then
+			echo "Error: rulesfile not readable: $RULESFILE" >&2
+			exit 2
+		fi
+
+		# Needed commands
+		COMMANDS=(mktemp "$SAVE" "$RESTORE")
+		checkcommands
+		;;
+esac
+
+
+### Begin work
+
+# Store old iptables rules to temporary file
+TMPFILE=`mktemp /tmp/$PROGNAME-XXXXXXXX`
+trap "rm -f $TMPFILE" EXIT HUP INT QUIT ILL TRAP ABRT BUS \
+		      FPE USR1 SEGV USR2 PIPE ALRM TERM
+
+if ! "$SAVE" >"$TMPFILE"; then
+	# An error occured
+	if ! grep -q ipt /proc/modules 2>/dev/null; then
+		echo "Error: iptables support lacking from the kernel" >&2
+		exit 3
+	else
+		echo "Error: unknown error saving old iptables rules: $TMPFILE" >&2
+		exit 4
+	fi
+fi
+
+# Legacy to stop the fail2ban daemon if present
+[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban stop
+
+# Configure iptables
+case "$MODE" in
+	(1)
+		# Run command in background and kill it if it times out
+		echo -n "Running command '$RUNCMD'... "
+		"$RUNCMD" &
+		CMD_PID=$!
+		( sleep "$TIMEOUT"; kill "$CMD_PID" 2>/dev/null; exit 0 ) &
+		CMDTIMEOUT_PID=$!
+		if ! wait "$CMD_PID"; then
+			echo "failed."
+			echo "Error: unknown error running command: $RUNCMD" >&2
+			revertrules
+			exit 7
+		else
+			echo "done."
+		fi
+		;;
+	(*)
+		# Apply iptables rulesfile
+		echo -n "Applying new iptables rules from '$RULESFILE'... "
+		if ! "$RESTORE" <"$RULESFILE"; then
+			echo "failed."
+			echo "Error: unknown error applying new iptables rules: $RULESFILE" >&2
+			revertrules
+			exit 5
+		else
+			echo "done."
+		fi
+		;;
+esac
+
+# Prompt user for confirmation
+echo -n "Can you establish NEW connections to the machine? (y/N) "
+
+read -n1 -t "$TIMEOUT" ret 2>&1 || :
+case "${ret:-}" in
+	(y*|Y*)
+		# Success
+		echo
+
+		if [ ! -z "$SAVEFILE" ]; then
+			# Write successfully applied rules to the savefile
+			echo "Writing successfully applied rules to '$SAVEFILE'..."
+			if ! "$SAVE" >"$SAVEFILE"; then
+				echo "Error: unknown error writing successfully applied rules: $SAVEFILE" >&2
+				exit 9
+			fi
+		fi
+
+		echo "... then my job is done. See you next time."
+		;;
+	(*)
+		# Failed
+		echo
+		if [ -z "${ret:-}" ]; then
+			echo "Timeout! Something happened (or did not). Better play it safe..."
+		else
+			echo "No affirmative response! Better play it safe..."
+		fi
+		revertrules
+		exit 255
+		;;
+esac
+
+# Legacy to start the fail2ban daemon again
+[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban start
+
+exit 0
+
+# vim:noet:sw=8
diff --git a/iptables/iptables-apply.8.in b/iptables/iptables-apply.8.in
new file mode 100644
index 0000000..f0ed4e5
--- /dev/null
+++ b/iptables/iptables-apply.8.in
@@ -0,0 +1,60 @@
+.\"     Title: iptables-apply
+.\"    Author: Martin F. Krafft, GW
+.\"      Date: May 10, 2010
+.\"
+.TH IPTABLES\-APPLY 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.\" disable hyphenation
+.nh
+.SH NAME
+iptables-apply \- a safer way to update iptables remotely
+.SH SYNOPSIS
+\fBiptables\-apply\fP [\-\fBhV\fP] [\fB-t\fP \fItimeout\fP] [\fB-w\fP \fIsavefile\fP] {[\fIrulesfile]|-c [runcmd]}\fP
+.SH "DESCRIPTION"
+.PP
+iptables\-apply will try to apply a new rulesfile (as output by
+iptables-save, read by iptables-restore) or run a command to configure
+iptables and then prompt the user whether the changes are okay. If the
+new iptables rules cut the existing connection, the user will not be
+able to answer affirmatively. In this case, the script rolls back to
+the previous working iptables rules after the timeout expires.
+.PP
+Successfully applied rules can also be written to savefile and later used
+to roll back to this state. This can be used to implement a store last good
+configuration mechanism when experimenting with an iptables setup script:
+iptables-apply \-w /etc/network/iptables.up.rules \-c /etc/network/iptables.up.run
+.PP
+When called as ip6tables\-apply, the script will use
+ip6tables\-save/\-restore and IPv6 default values instead. Default
+value for rulesfile is '/etc/network/iptables.up.rules'.
+.SH OPTIONS
+.TP
+\fB\-t\fP \fIseconds\fR, \fB\-\-timeout\fP \fIseconds\fR
+Sets the timeout in seconds after which the script will roll back
+to the previous ruleset (default: 10).
+.TP
+\fB\-w\fP \fIsavefile\fR, \fB\-\-write\fP \fIsavefile\fR
+Specify the savefile where successfully applied rules will be written to
+(default if empty string is given: /etc/network/iptables.up.rules).
+.TP
+\fB\-c\fP \fIruncmd\fR, \fB\-\-command\fP \fIruncmd\fR
+Run command runcmd to configure iptables instead of applying a rulesfile
+(default: /etc/network/iptables.up.run).
+.TP
+\fB\-h\fP, \fB\-\-help\fP
+Display usage information.
+.TP
+\fB\-V\fP, \fB\-\-version\fP
+Display version information.
+.SH "SEE ALSO"
+.PP
+\fBiptables-restore\fP(8), \fBiptables-save\fP(8), \fBiptables\fR(8).
+.SH LEGALESE
+.PP
+Original iptables-apply - Copyright 2006 Martin F. Krafft <madduck@madduck.net>.
+Version 1.1 - Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>.
+.PP
+This manual page was written by Martin F. Krafft <madduck@madduck.net> and
+extended by GW <gw.2010@tnode.com or http://gw.tnode.com/>.
+.PP
+Permission is granted to copy, distribute and/or modify this document
+under the terms of the Artistic License 2.0.
diff --git a/iptables/iptables-extensions.8.tmpl.in b/iptables/iptables-extensions.8.tmpl.in
new file mode 100644
index 0000000..99d89a1
--- /dev/null
+++ b/iptables/iptables-extensions.8.tmpl.in
@@ -0,0 +1,28 @@
+.TH iptables-extensions 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.SH NAME
+iptables-extensions \(em list of extensions in the standard iptables distribution
+.SH SYNOPSIS
+\fBip6tables\fP [\fB\-m\fP \fIname\fP [\fImodule-options\fP...]]
+[\fB\-j\fP \fItarget-name\fP [\fItarget-options\fP...]
+.PP
+\fBiptables\fP [\fB\-m\fP \fIname\fP [\fImodule-options\fP...]]
+[\fB\-j\fP \fItarget-name\fP [\fItarget-options\fP...]
+.SH MATCH EXTENSIONS
+iptables can use extended packet matching modules
+with the \fB\-m\fP or \fB\-\-match\fP
+options, followed by the matching module name; after these, various
+extra command line options become available, depending on the specific
+module.  You can specify multiple extended match modules in one line,
+and you can use the \fB\-h\fP or \fB\-\-help\fP
+options after the module has been specified to receive help specific
+to that module.  The extended match modules are evaluated in the order
+they are specified in the rule.
+.PP
+If the \fB\-p\fP or \fB\-\-protocol\fP was specified and if and only if an
+unknown option is encountered, iptables will try load a match module of the
+same name as the protocol, to try making the option available.
+.\" @MATCH@
+.SH TARGET EXTENSIONS
+iptables can use extended target modules: the following are included
+in the standard distribution.
+.\" @TARGET@
diff --git a/iptables/iptables-multi.h b/iptables/iptables-multi.h
new file mode 100644
index 0000000..a2bb878
--- /dev/null
+++ b/iptables/iptables-multi.h
@@ -0,0 +1,8 @@
+#ifndef _IPTABLES_MULTI_H
+#define _IPTABLES_MULTI_H 1
+
+extern int iptables_main(int, char **);
+extern int iptables_save_main(int, char **);
+extern int iptables_restore_main(int, char **);
+
+#endif /* _IPTABLES_MULTI_H */
diff --git a/iptables/iptables-restore.8.in b/iptables/iptables-restore.8.in
new file mode 100644
index 0000000..b4b62f9
--- /dev/null
+++ b/iptables/iptables-restore.8.in
@@ -0,0 +1,94 @@
+.TH IPTABLES-RESTORE 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.\"
+.\" Man page written by Harald Welte <laforge@gnumonks.org>
+.\" It is based on the iptables man page.
+.\"
+.\"	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 2 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, write to the Free Software
+.\"	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+iptables-restore \(em Restore IP Tables
+.P
+ip6tables-restore \(em Restore IPv6 Tables
+.SH SYNOPSIS
+\fBiptables\-restore\fP [\fB\-chntvV\fP] [\fB\-w\fP \fIsecs\fP]
+[\fB\-W\fP \fIusecs\fP] [\fB\-M\fP \fImodprobe\fP] [\fB\-T\fP \fIname\fP]
+[\fBfile\fP]
+.P
+\fBip6tables\-restore\fP [\fB\-chntvV\fP] [\fB\-w\fP \fIsecs\fP]
+[\fB\-W\fP \fIusecs\fP] [\fB\-M\fP \fImodprobe\fP] [\fB\-T\fP \fIname\fP]
+[\fBfile\fP]
+.SH DESCRIPTION
+.PP
+.B iptables-restore
+and
+.B ip6tables-restore
+are used to restore IP and IPv6 Tables from data specified on STDIN or in
+\fIfile\fP. Use I/O redirection provided by your shell to read from a file or
+specify \fIfile\fP as an argument.
+.TP
+\fB\-c\fR, \fB\-\-counters\fR
+restore the values of all packet and byte counters
+.TP
+\fB\-h\fP, \fB\-\-help\fP
+Print a short option summary.
+.TP
+\fB\-n\fR, \fB\-\-noflush\fR
+don't flush the previous contents of the table. If not specified,
+both commands flush (delete) all previous contents of the respective table.
+.TP
+\fB\-t\fP, \fB\-\-test\fP
+Only parse and construct the ruleset, but do not commit it.
+.TP
+\fB\-v\fP, \fB\-\-verbose\fP
+Print additional debug info during ruleset processing.
+.TP
+\fB\-V\fP, \fB\-\-version\fP
+Print the program version number.
+.TP
+\fB\-w\fP, \fB\-\-wait\fP [\fIseconds\fP]
+Wait for the xtables lock.
+To prevent multiple instances of the program from running concurrently,
+an attempt will be made to obtain an exclusive lock at launch.  By default,
+the program will exit if the lock cannot be obtained.  This option will
+make the program wait (indefinitely or for optional \fIseconds\fP) until
+the exclusive lock can be obtained.
+.TP
+\fB\-W\fP, \fB\-\-wait-interval\fP \fImicroseconds\fP
+Interval to wait per each iteration.
+When running latency sensitive applications, waiting for the xtables lock
+for extended durations may not be acceptable. This option will make each
+iteration take the amount of time specified. The default interval is
+1 second. This option only works with \fB\-w\fP.
+.TP
+\fB\-M\fP, \fB\-\-modprobe\fP \fImodprobe_program\fP
+Specify the path to the modprobe program. By default, iptables-restore will
+inspect /proc/sys/kernel/modprobe to determine the executable's path.
+.TP
+\fB\-T\fP, \fB\-\-table\fP \fIname\fP
+Restore only the named table even if the input stream contains other ones.
+.SH BUGS
+None known as of iptables-1.2.1 release
+.SH AUTHORS
+Harald Welte <laforge@gnumonks.org> wrote iptables-restore based on code
+from Rusty Russell.
+.br
+Andras Kis-Szabo <kisza@sch.bme.hu> contributed ip6tables-restore.
+.SH SEE ALSO
+\fBiptables\-apply\fP(8),\fBiptables\-save\fP(8), \fBiptables\fP(8)
+.PP
+The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO,
+which details NAT, and the netfilter-hacking-HOWTO which details the
+internals.
diff --git a/iptables/iptables-restore.c b/iptables/iptables-restore.c
new file mode 100644
index 0000000..cc2c2b8
--- /dev/null
+++ b/iptables/iptables-restore.c
@@ -0,0 +1,429 @@
+/* Code to restore the iptables state, from file by iptables-save.
+ * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
+ * based on previous code from Rusty Russell <rusty@linuxcare.com.au>
+ *
+ * This code is distributed under the terms of GNU GPL v2
+ */
+#include "config.h"
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "iptables.h"
+#include "ip6tables.h"
+#include "xshared.h"
+#include "xtables.h"
+#include "libiptc/libiptc.h"
+#include "libiptc/libip6tc.h"
+#include "iptables-multi.h"
+#include "ip6tables-multi.h"
+
+static int counters, verbose, noflush, wait;
+
+static struct timeval wait_interval = {
+	.tv_sec	= 1,
+};
+
+/* Keeping track of external matches and targets.  */
+static const struct option options[] = {
+	{.name = "counters",      .has_arg = 0, .val = 'c'},
+	{.name = "verbose",       .has_arg = 0, .val = 'v'},
+	{.name = "version",       .has_arg = 0, .val = 'V'},
+	{.name = "test",          .has_arg = 0, .val = 't'},
+	{.name = "help",          .has_arg = 0, .val = 'h'},
+	{.name = "noflush",       .has_arg = 0, .val = 'n'},
+	{.name = "modprobe",      .has_arg = 1, .val = 'M'},
+	{.name = "table",         .has_arg = 1, .val = 'T'},
+	{.name = "wait",          .has_arg = 2, .val = 'w'},
+	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
+	{NULL},
+};
+
+static void print_usage(const char *name, const char *version)
+{
+	fprintf(stderr, "Usage: %s [-c] [-v] [-V] [-t] [-h] [-n] [-w secs] [-W usecs] [-T table] [-M command] [file]\n"
+			"	   [ --counters ]\n"
+			"	   [ --verbose ]\n"
+			"	   [ --version]\n"
+			"	   [ --test ]\n"
+			"	   [ --help ]\n"
+			"	   [ --noflush ]\n"
+			"	   [ --wait=<seconds>\n"
+			"	   [ --wait-interval=<usecs>\n"
+			"	   [ --table=<TABLE> ]\n"
+			"	   [ --modprobe=<command> ]\n", name);
+}
+
+struct iptables_restore_cb {
+	const struct xtc_ops *ops;
+
+	int (*for_each_chain)(int (*fn)(const xt_chainlabel,
+					int, struct xtc_handle *),
+			      int verbose, int builtinstoo,
+			      struct xtc_handle *handle);
+	int (*flush_entries)(const xt_chainlabel, int, struct xtc_handle *);
+	int (*delete_chain)(const xt_chainlabel, int, struct xtc_handle *);
+	int (*do_command)(int argc, char *argv[], char **table,
+			  struct xtc_handle **handle, bool restore);
+};
+
+static struct xtc_handle *
+create_handle(const struct iptables_restore_cb *cb, const char *tablename)
+{
+	struct xtc_handle *handle;
+
+	handle = cb->ops->init(tablename);
+
+	if (!handle) {
+		/* try to insmod the module if iptc_init failed */
+		xtables_load_ko(xtables_modprobe_program, false);
+		handle = cb->ops->init(tablename);
+	}
+
+	if (!handle)
+		xtables_error(PARAMETER_PROBLEM, "%s: unable to initialize "
+			"table '%s'\n", xt_params->program_name, tablename);
+
+	return handle;
+}
+
+static int
+ip46tables_restore_main(const struct iptables_restore_cb *cb,
+			int argc, char *argv[])
+{
+	struct xtc_handle *handle = NULL;
+	struct argv_store av_store = {};
+	char buffer[10240];
+	int c, lock;
+	char curtable[XT_TABLE_MAXNAMELEN + 1] = {};
+	FILE *in;
+	int in_table = 0, testing = 0;
+	const char *tablename = NULL;
+
+	line = 0;
+	lock = XT_LOCK_NOT_ACQUIRED;
+
+	while ((c = getopt_long(argc, argv, "bcvVthnwWM:T:", options, NULL)) != -1) {
+		switch (c) {
+			case 'b':
+				fprintf(stderr, "-b/--binary option is not implemented\n");
+				break;
+			case 'c':
+				counters = 1;
+				break;
+			case 'v':
+				verbose = 1;
+				break;
+			case 'V':
+				printf("%s v%s (legacy)\n",
+				       xt_params->program_name,
+				       xt_params->program_version);
+				exit(0);
+			case 't':
+				testing = 1;
+				break;
+			case 'h':
+				print_usage(xt_params->program_name,
+					    PACKAGE_VERSION);
+				exit(0);
+			case 'n':
+				noflush = 1;
+				break;
+			case 'w':
+				wait = parse_wait_time(argc, argv);
+				break;
+			case 'W':
+				parse_wait_interval(argc, argv, &wait_interval);
+				break;
+			case 'M':
+				xtables_modprobe_program = optarg;
+				break;
+			case 'T':
+				tablename = optarg;
+				break;
+			default:
+				fprintf(stderr,
+					"Try `%s -h' for more information.\n",
+					xt_params->program_name);
+				exit(1);
+		}
+	}
+
+	if (optind == argc - 1) {
+		in = fopen(argv[optind], "re");
+		if (!in) {
+			fprintf(stderr, "Can't open %s: %s\n", argv[optind],
+				strerror(errno));
+			exit(1);
+		}
+	}
+	else if (optind < argc) {
+		fprintf(stderr, "Unknown arguments found on commandline\n");
+		exit(1);
+	}
+	else in = stdin;
+
+	if (!wait_interval.tv_sec && !wait) {
+		fprintf(stderr, "Option --wait-interval requires option --wait\n");
+		exit(1);
+	}
+
+	/* Grab standard input. */
+	while (fgets(buffer, sizeof(buffer), in)) {
+		int ret = 0;
+
+		line++;
+		if (buffer[0] == '\n')
+			continue;
+		else if (buffer[0] == '#') {
+			if (verbose) {
+				fputs(buffer, stdout);
+				fflush(stdout);
+			}
+			continue;
+		} else if ((strcmp(buffer, "COMMIT\n") == 0) && (in_table)) {
+			if (!testing) {
+				DEBUGP("Calling commit\n");
+				ret = cb->ops->commit(handle);
+				cb->ops->free(handle);
+				handle = NULL;
+			} else {
+				DEBUGP("Not calling commit, testing\n");
+				ret = 1;
+			}
+
+			/* Done with the current table, release the lock. */
+			if (lock >= 0) {
+				xtables_unlock(lock);
+				lock = XT_LOCK_NOT_ACQUIRED;
+			}
+
+			in_table = 0;
+		} else if ((buffer[0] == '*') && (!in_table)) {
+			/* Acquire a lock before we create a new table handle */
+			lock = xtables_lock_or_exit(wait, &wait_interval);
+
+			/* New table */
+			char *table;
+
+			table = strtok(buffer+1, " \t\n");
+			DEBUGP("line %u, table '%s'\n", line, table);
+			if (!table)
+				xtables_error(PARAMETER_PROBLEM,
+					"%s: line %u table name invalid\n",
+					xt_params->program_name, line);
+
+			strncpy(curtable, table, XT_TABLE_MAXNAMELEN);
+			curtable[XT_TABLE_MAXNAMELEN] = '\0';
+
+			if (tablename && strcmp(tablename, table) != 0) {
+				if (lock >= 0) {
+					xtables_unlock(lock);
+					lock = XT_LOCK_NOT_ACQUIRED;
+				}
+				continue;
+			}
+			if (handle)
+				cb->ops->free(handle);
+
+			handle = create_handle(cb, table);
+			if (noflush == 0) {
+				DEBUGP("Cleaning all chains of table '%s'\n",
+					table);
+				cb->for_each_chain(cb->flush_entries, verbose, 1,
+						handle);
+
+				DEBUGP("Deleting all user-defined chains "
+				       "of table '%s'\n", table);
+				cb->for_each_chain(cb->delete_chain, verbose, 0,
+						handle);
+			}
+
+			ret = 1;
+			in_table = 1;
+
+		} else if ((buffer[0] == ':') && (in_table)) {
+			/* New chain. */
+			char *policy, *chain;
+
+			chain = strtok(buffer+1, " \t\n");
+			DEBUGP("line %u, chain '%s'\n", line, chain);
+			if (!chain)
+				xtables_error(PARAMETER_PROBLEM,
+					   "%s: line %u chain name invalid\n",
+					   xt_params->program_name, line);
+
+			if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Invalid chain name `%s' "
+					   "(%u chars max)",
+					   chain, XT_EXTENSION_MAXNAMELEN - 1);
+
+			if (cb->ops->builtin(chain, handle) <= 0) {
+				if (noflush && cb->ops->is_chain(chain, handle)) {
+					DEBUGP("Flushing existing user defined chain '%s'\n", chain);
+					if (!cb->ops->flush_entries(chain, handle))
+						xtables_error(PARAMETER_PROBLEM,
+							   "error flushing chain "
+							   "'%s':%s\n", chain,
+							   strerror(errno));
+				} else {
+					DEBUGP("Creating new chain '%s'\n", chain);
+					if (!cb->ops->create_chain(chain, handle))
+						xtables_error(PARAMETER_PROBLEM,
+							   "error creating chain "
+							   "'%s':%s\n", chain,
+							   strerror(errno));
+				}
+			}
+
+			policy = strtok(NULL, " \t\n");
+			DEBUGP("line %u, policy '%s'\n", line, policy);
+			if (!policy)
+				xtables_error(PARAMETER_PROBLEM,
+					   "%s: line %u policy invalid\n",
+					   xt_params->program_name, line);
+
+			if (strcmp(policy, "-") != 0) {
+				struct xt_counters count = {};
+
+				if (counters) {
+					char *ctrs;
+					ctrs = strtok(NULL, " \t\n");
+
+					if (!ctrs || !parse_counters(ctrs, &count))
+						xtables_error(PARAMETER_PROBLEM,
+							  "invalid policy counters "
+							  "for chain '%s'\n", chain);
+				}
+
+				DEBUGP("Setting policy of chain %s to %s\n",
+					chain, policy);
+
+				if (!cb->ops->set_policy(chain, policy, &count,
+						     handle))
+					xtables_error(OTHER_PROBLEM,
+						"Can't set policy `%s'"
+						" on `%s' line %u: %s\n",
+						policy, chain, line,
+						cb->ops->strerror(errno));
+			}
+
+			ret = 1;
+
+		} else if (in_table) {
+			char *pcnt = NULL;
+			char *bcnt = NULL;
+			char *parsestart = buffer;
+
+			add_argv(&av_store, argv[0], 0);
+			add_argv(&av_store, "-t", 0);
+			add_argv(&av_store, curtable, 0);
+
+			tokenize_rule_counters(&parsestart, &pcnt, &bcnt, line);
+			if (counters && pcnt && bcnt) {
+				add_argv(&av_store, "--set-counters", 0);
+				add_argv(&av_store, pcnt, 0);
+				add_argv(&av_store, bcnt, 0);
+			}
+
+			add_param_to_argv(&av_store, parsestart, line);
+
+			DEBUGP("calling do_command(%u, argv, &%s, handle):\n",
+				av_store.argc, curtable);
+			debug_print_argv(&av_store);
+
+			ret = cb->do_command(av_store.argc, av_store.argv,
+					 &av_store.argv[2], &handle, true);
+
+			free_argv(&av_store);
+			fflush(stdout);
+		}
+		if (tablename && strcmp(tablename, curtable) != 0)
+			continue;
+		if (!ret) {
+			fprintf(stderr, "%s: line %u failed\n",
+					xt_params->program_name, line);
+			exit(1);
+		}
+	}
+	if (in_table) {
+		fprintf(stderr, "%s: COMMIT expected at line %u\n",
+				xt_params->program_name, line + 1);
+		exit(1);
+	}
+
+	fclose(in);
+	return 0;
+}
+
+
+#if defined ENABLE_IPV4
+static const struct iptables_restore_cb ipt_restore_cb = {
+	.ops		= &iptc_ops,
+	.for_each_chain	= for_each_chain4,
+	.flush_entries	= flush_entries4,
+	.delete_chain	= delete_chain4,
+	.do_command	= do_command4,
+};
+
+int
+iptables_restore_main(int argc, char *argv[])
+{
+	int c, ret;
+
+	iptables_globals.program_name = "iptables-restore";
+	c = xtables_init_all(&iptables_globals, NFPROTO_IPV4);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				iptables_globals.program_name,
+				iptables_globals.program_version);
+		exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+
+	ret = ip46tables_restore_main(&ipt_restore_cb, argc, argv);
+
+	xtables_fini();
+	return ret;
+}
+#endif
+
+#if defined ENABLE_IPV6
+static const struct iptables_restore_cb ip6t_restore_cb = {
+	.ops		= &ip6tc_ops,
+	.for_each_chain	= for_each_chain6,
+	.flush_entries	= flush_entries6,
+	.delete_chain	= delete_chain6,
+	.do_command	= do_command6,
+};
+
+int
+ip6tables_restore_main(int argc, char *argv[])
+{
+	int c, ret;
+
+	ip6tables_globals.program_name = "ip6tables-restore";
+	c = xtables_init_all(&ip6tables_globals, NFPROTO_IPV6);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				ip6tables_globals.program_name,
+				ip6tables_globals.program_version);
+		exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions6();
+#endif
+
+	ret = ip46tables_restore_main(&ip6t_restore_cb, argc, argv);
+
+	xtables_fini();
+	return ret;
+}
+#endif
diff --git a/iptables/iptables-save.8.in b/iptables/iptables-save.8.in
new file mode 100644
index 0000000..7683fd3
--- /dev/null
+++ b/iptables/iptables-save.8.in
@@ -0,0 +1,69 @@
+.TH IPTABLES-SAVE 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.\"
+.\" Man page written by Harald Welte <laforge@gnumonks.org>
+.\" It is based on the iptables man page.
+.\"
+.\"	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 2 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, write to the Free Software
+.\"	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+iptables-save \(em dump iptables rules
+.P
+ip6tables-save \(em dump iptables rules
+.SH SYNOPSIS
+\fBiptables\-save\fP [\fB\-M\fP \fImodprobe\fP] [\fB\-c\fP]
+[\fB\-t\fP \fItable\fP] [\fB\-f\fP \fIfilename\fP]
+.P
+\fBip6tables\-save\fP [\fB\-M\fP \fImodprobe\fP] [\fB\-c\fP]
+[\fB\-t\fP \fItable\fP] [\fB\-f\fP \fIfilename\fP]
+.SH DESCRIPTION
+.PP
+.B iptables-save
+and
+.B ip6tables-save
+are used to dump the contents of IP or IPv6 Table in easily parseable format
+either to STDOUT or to a specified file.
+.TP
+\fB\-M\fR, \fB\-\-modprobe\fR \fImodprobe_program\fP
+Specify the path to the modprobe program. By default, iptables-save will
+inspect /proc/sys/kernel/modprobe to determine the executable's path.
+.TP
+\fB\-f\fR, \fB\-\-file\fR \fIfilename\fP
+Specify a filename to log the output to. If not specified, iptables-save
+will log to STDOUT.
+.TP
+\fB\-c\fR, \fB\-\-counters\fR
+include the current values of all packet and byte counters in the output
+.TP
+\fB\-t\fR, \fB\-\-table\fR \fItablename\fP
+restrict output to only one table. If the kernel is configured with automatic
+module loading, an attempt will be made to load the appropriate module for
+that table if it is not already there.
+.br
+If not specified, output includes all available tables.
+.SH BUGS
+None known as of iptables-1.2.1 release
+.SH AUTHORS
+Harald Welte <laforge@gnumonks.org>
+.br
+Rusty Russell <rusty@rustcorp.com.au>
+.br
+Andras Kis-Szabo <kisza@sch.bme.hu> contributed ip6tables-save.
+.SH SEE ALSO
+\fBiptables\-apply\fP(8),\fBiptables\-restore\fP(8), \fBiptables\fP(8)
+.PP
+The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO,
+which details NAT, and the netfilter-hacking-HOWTO which details the
+internals.
diff --git a/iptables/iptables-save.c b/iptables/iptables-save.c
new file mode 100644
index 0000000..4efd666
--- /dev/null
+++ b/iptables/iptables-save.c
@@ -0,0 +1,286 @@
+/* Code to save the iptables state, in human readable-form. */
+/* (C) 1999 by Paul 'Rusty' Russell <rusty@rustcorp.com.au> and
+ * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This code is distributed under the terms of GNU GPL v2
+ *
+ */
+#include "config.h"
+#include <getopt.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <netdb.h>
+#include <unistd.h>
+#include "libiptc/libiptc.h"
+#include "libiptc/libip6tc.h"
+#include "iptables.h"
+#include "ip6tables.h"
+#include "iptables-multi.h"
+#include "ip6tables-multi.h"
+#include "xshared.h"
+
+static int show_counters;
+
+static const struct option options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "dump",     .has_arg = false, .val = 'd'},
+	{.name = "table",    .has_arg = true,  .val = 't'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{.name = "file",     .has_arg = true,  .val = 'f'},
+	{.name = "version",  .has_arg = false, .val = 'V'},
+	{NULL},
+};
+
+struct iptables_save_cb {
+	const struct xtc_ops *ops;
+
+	void (*dump_rules)(const char *chain, struct xtc_handle *handle);
+};
+
+static int
+for_each_table(int (*func)(struct iptables_save_cb *cb, const char *tablename),
+	       struct iptables_save_cb *cb)
+{
+	int ret = 1;
+	FILE *procfile = NULL;
+	char tablename[XT_TABLE_MAXNAMELEN+1];
+
+	procfile = fopen(afinfo->proc_exists, "re");
+	if (!procfile) {
+		if (errno == ENOENT)
+			return ret;
+		fprintf(stderr, "Failed to list table names in %s: %s\n",
+		        afinfo->proc_exists, strerror(errno));
+		exit(1);
+	}
+
+	while (fgets(tablename, sizeof(tablename), procfile)) {
+		if (tablename[strlen(tablename) - 1] != '\n')
+			xtables_error(OTHER_PROBLEM,
+				   "Badly formed tablename `%s'\n",
+				   tablename);
+		tablename[strlen(tablename) - 1] = '\0';
+		ret &= func(cb, tablename);
+	}
+
+	fclose(procfile);
+	return ret;
+}
+
+static int do_output(struct iptables_save_cb *cb, const char *tablename)
+{
+	struct xtc_handle *h;
+	const char *chain = NULL;
+
+	if (!tablename)
+		return for_each_table(&do_output, cb);
+
+	h = cb->ops->init(tablename);
+	if (h == NULL) {
+		xtables_load_ko(xtables_modprobe_program, false);
+		h = cb->ops->init(tablename);
+	}
+	if (!h)
+		xtables_error(OTHER_PROBLEM, "Cannot initialize: %s\n",
+			      cb->ops->strerror(errno));
+
+	time_t now = time(NULL);
+
+	printf("# Generated by %s v%s on %s",
+	       xt_params->program_name, PACKAGE_VERSION, ctime(&now));
+	printf("*%s\n", tablename);
+
+	/* Dump out chain names first,
+	 * thereby preventing dependency conflicts */
+	for (chain = cb->ops->first_chain(h);
+	     chain;
+	     chain = cb->ops->next_chain(h)) {
+
+		printf(":%s ", chain);
+		if (cb->ops->builtin(chain, h)) {
+			struct xt_counters count;
+
+			printf("%s ", cb->ops->get_policy(chain, &count, h));
+			printf("[%llu:%llu]\n",
+			       (unsigned long long)count.pcnt,
+			       (unsigned long long)count.bcnt);
+		} else {
+			printf("- [0:0]\n");
+		}
+	}
+
+	for (chain = cb->ops->first_chain(h);
+	     chain;
+	     chain = cb->ops->next_chain(h)) {
+		cb->dump_rules(chain, h);
+	}
+
+	now = time(NULL);
+	printf("COMMIT\n");
+	printf("# Completed on %s", ctime(&now));
+	cb->ops->free(h);
+
+	return 1;
+}
+
+/* Format:
+ * :Chain name POLICY packets bytes
+ * rule
+ */
+static int
+do_iptables_save(struct iptables_save_cb *cb, int argc, char *argv[])
+{
+	const char *tablename = NULL;
+	FILE *file = NULL;
+	int ret, c;
+
+	while ((c = getopt_long(argc, argv, "bcdt:M:f:V", options, NULL)) != -1) {
+		switch (c) {
+		case 'b':
+			fprintf(stderr, "-b/--binary option is not implemented\n");
+			break;
+		case 'c':
+			show_counters = 1;
+			break;
+
+		case 't':
+			/* Select specific table. */
+			tablename = optarg;
+			break;
+		case 'M':
+			xtables_modprobe_program = optarg;
+			break;
+		case 'f':
+			file = fopen(optarg, "w");
+			if (file == NULL) {
+				fprintf(stderr, "Failed to open file, error: %s\n",
+					strerror(errno));
+				exit(1);
+			}
+			ret = dup2(fileno(file), STDOUT_FILENO);
+			if (ret == -1) {
+				fprintf(stderr, "Failed to redirect stdout, error: %s\n",
+					strerror(errno));
+				exit(1);
+			}
+			fclose(file);
+			break;
+		case 'd':
+			do_output(cb, tablename);
+			exit(0);
+		case 'V':
+			printf("%s v%s (legacy)\n",
+			       xt_params->program_name,
+			       xt_params->program_version);
+			exit(0);
+		default:
+			fprintf(stderr,
+				"Look at manual page `%s.8' for more information.\n",
+				xt_params->program_name);
+			exit(1);
+		}
+	}
+
+	if (optind < argc) {
+		fprintf(stderr, "Unknown arguments found on commandline\n");
+		exit(1);
+	}
+
+	return !do_output(cb, tablename);
+}
+
+#ifdef ENABLE_IPV4
+static void iptables_dump_rules(const char *chain, struct xtc_handle *h)
+{
+	const struct ipt_entry *e;
+
+	/* Dump out rules */
+	e = iptc_first_rule(chain, h);
+	while(e) {
+		print_rule4(e, h, chain, show_counters);
+		e = iptc_next_rule(e, h);
+	}
+}
+
+struct iptables_save_cb ipt_save_cb = {
+	.ops		= &iptc_ops,
+	.dump_rules	= iptables_dump_rules,
+};
+
+/* Format:
+ * :Chain name POLICY packets bytes
+ * rule
+ */
+int
+iptables_save_main(int argc, char *argv[])
+{
+	int ret;
+
+	iptables_globals.program_name = "iptables-save";
+	if (xtables_init_all(&iptables_globals, NFPROTO_IPV4) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				iptables_globals.program_name,
+				iptables_globals.program_version);
+		exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+
+	ret = do_iptables_save(&ipt_save_cb, argc, argv);
+
+	xtables_fini();
+	return ret;
+}
+#endif /* ENABLE_IPV4 */
+
+#ifdef ENABLE_IPV6
+static void ip6tables_dump_rules(const char *chain, struct xtc_handle *h)
+{
+	const struct ip6t_entry *e;
+
+	/* Dump out rules */
+	e = ip6tc_first_rule(chain, h);
+	while(e) {
+		print_rule6(e, h, chain, show_counters);
+		e = ip6tc_next_rule(e, h);
+	}
+}
+
+struct iptables_save_cb ip6t_save_cb = {
+	.ops		= &ip6tc_ops,
+	.dump_rules	= ip6tables_dump_rules,
+};
+
+/* Format:
+ * :Chain name POLICY packets bytes
+ * rule
+ */
+int
+ip6tables_save_main(int argc, char *argv[])
+{
+	int ret;
+
+	ip6tables_globals.program_name = "ip6tables-save";
+	if (xtables_init_all(&ip6tables_globals, NFPROTO_IPV6) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				ip6tables_globals.program_name,
+				ip6tables_globals.program_version);
+		exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions6();
+#endif
+
+	ret = do_iptables_save(&ip6t_save_cb, argc, argv);
+
+	xtables_fini();
+	return ret;
+}
+#endif /* ENABLE_IPV6 */
diff --git a/iptables/iptables-standalone.c b/iptables/iptables-standalone.c
new file mode 100644
index 0000000..3c8af60
--- /dev/null
+++ b/iptables/iptables-standalone.c
@@ -0,0 +1,86 @@
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * Based on the ipchains code by Paul Russell and Michael Neuling
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	iptables -- IP firewall administration for kernels with
+ *	firewall table (aimed for the 2.3 kernels)
+ *
+ *	See the accompanying manual page iptables(8) for information
+ *	about proper usage of this program.
+ *
+ *	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 2 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, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <iptables.h>
+#include "iptables-multi.h"
+
+int
+iptables_main(int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct xtc_handle *handle = NULL;
+
+	signal(SIGPIPE, SIG_IGN);
+
+	iptables_globals.program_name = "iptables";
+	ret = xtables_init_all(&iptables_globals, NFPROTO_IPV4);
+	if (ret < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				iptables_globals.program_name,
+				iptables_globals.program_version);
+				exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+
+	ret = do_command4(argc, argv, &table, &handle, false);
+	if (ret) {
+		ret = iptc_commit(handle);
+		iptc_free(handle);
+	}
+
+	xtables_fini();
+
+	if (!ret) {
+		if (errno == EINVAL) {
+			fprintf(stderr, "iptables: %s. "
+					"Run `dmesg' for more information.\n",
+				iptc_strerror(errno));
+		} else {
+			fprintf(stderr, "iptables: %s.\n",
+				iptc_strerror(errno));
+		}
+		if (errno == EAGAIN)
+			exit(RESOURCE_PROBLEM);
+	}
+
+	exit(!ret);
+}
diff --git a/iptables/iptables-xml.1.in b/iptables/iptables-xml.1.in
new file mode 100644
index 0000000..7b7878f
--- /dev/null
+++ b/iptables/iptables-xml.1.in
@@ -0,0 +1,87 @@
+.TH IPTABLES-XML 1 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.\"
+.\" Man page written by Sam Liddicott <azez@ufomechanic.net>
+.\" It is based on the iptables-save man page.
+.\"
+.\"	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 2 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, write to the Free Software
+.\"	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+iptables-xml \(em Convert iptables-save format to XML
+.SH SYNOPSIS
+\fBiptables\-xml\fP [\fB\-c\fP] [\fB\-v\fP]
+.SH DESCRIPTION
+.PP
+.B iptables-xml
+is used to convert the output of iptables-save into an easily manipulatable
+XML format to STDOUT.  Use I/O-redirection provided by your shell to write to 
+a file.
+.TP
+\fB\-c\fR, \fB\-\-combine\fR
+combine consecutive rules with the same matches but different targets. iptables
+does not currently support more than one target per match, so this simulates 
+that by collecting the targets from consecutive iptables rules into one action
+tag, but only when the rule matches are identical. Terminating actions like
+RETURN, DROP, ACCEPT and QUEUE are not combined with subsequent targets.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Output xml comments containing the iptables line from which the XML is derived
+
+.PP
+iptables-xml does a mechanistic conversion to a very expressive xml
+format; the only semantic considerations are for \-g and \-j targets in
+order to discriminate between <call> <goto> and <nane-of-target> as it
+helps xml processing scripts if they can tell the difference between a
+target like SNAT and another chain.
+
+Some sample output is:
+
+<iptables-rules>
+  <table name="mangle">
+    <chain name="PREROUTING" policy="ACCEPT" packet-count="63436"
+byte-count="7137573">
+      <rule>
+       <conditions>
+        <match>
+          <p>tcp</p>
+        </match>
+        <tcp>
+          <sport>8443</sport>
+        </tcp>
+       </conditions>
+       <actions>
+        <call>
+          <check_ip/>
+        </call>
+        <ACCEPT/>
+       </actions>
+      </rule>
+    </chain>
+  </table>
+</iptables-rules>
+
+.PP
+Conversion from XML to iptables-save format may be done using the 
+iptables.xslt script and xsltproc, or a custom program using
+libxsltproc or similar; in this fashion:
+
+xsltproc iptables.xslt my-iptables.xml | iptables-restore
+
+.SH BUGS
+None known as of iptables-1.3.7 release
+.SH AUTHOR
+Sam Liddicott <azez@ufomechanic.net>
+.SH SEE ALSO
+\fBiptables\-save\fP(8), \fBiptables\-restore\fP(8), \fBiptables\fP(8)
diff --git a/iptables/iptables-xml.c b/iptables/iptables-xml.c
new file mode 100644
index 0000000..98d03dd
--- /dev/null
+++ b/iptables/iptables-xml.c
@@ -0,0 +1,695 @@
+/* Code to convert iptables-save format to xml format,
+ * (C) 2006 Ufo Mechanic <azez@ufomechanic.net>
+ * based on iptables-restore (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
+ * based on previous code from Rusty Russell <rusty@linuxcare.com.au>
+ *
+ * This code is distributed under the terms of GNU GPL v2
+ */
+#include "config.h"
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include "iptables.h"
+#include "libiptc/libiptc.h"
+#include "xtables-multi.h"
+#include <xtables.h>
+#include "xshared.h"
+
+struct xtables_globals iptables_xml_globals = {
+	.option_offset = 0,
+	.program_version = PACKAGE_VERSION,
+	.program_name = "iptables-xml",
+};
+#define prog_name iptables_xml_globals.program_name
+#define prog_vers iptables_xml_globals.program_version
+
+static void print_usage(const char *name, const char *version)
+	    __attribute__ ((noreturn));
+
+static int verbose;
+/* Whether to combine actions of sequential rules with identical conditions */
+static int combine;
+/* Keeping track of external matches and targets.  */
+static const struct option options[] = {
+	{"verbose", 0, NULL, 'v'},
+	{"combine", 0, NULL, 'c'},
+	{"help", 0, NULL, 'h'},
+	{ .name = NULL }
+};
+
+static void
+print_usage(const char *name, const char *version)
+{
+	fprintf(stderr, "Usage: %s [-c] [-v] [-h]\n"
+		"          [--combine ]\n"
+		"	   [ --verbose ]\n" "	   [ --help ]\n", name);
+
+	exit(1);
+}
+
+#define XT_CHAIN_MAXNAMELEN XT_TABLE_MAXNAMELEN
+static char closeActionTag[XT_TABLE_MAXNAMELEN + 1];
+static char closeRuleTag[XT_TABLE_MAXNAMELEN + 1];
+static char curTable[XT_TABLE_MAXNAMELEN + 1];
+static char curChain[XT_CHAIN_MAXNAMELEN + 1];
+
+struct chain {
+	char *chain;
+	char *policy;
+	struct xt_counters count;
+	int created;
+};
+
+#define maxChains 10240		/* max chains per table */
+static struct chain chains[maxChains];
+static int nextChain;
+
+/* like puts but with xml encoding */
+static void
+xmlEncode(char *text)
+{
+	while (text && *text) {
+		if ((unsigned char) (*text) >= 127)
+			printf("&#%d;", (unsigned char) (*text));
+		else if (*text == '&')
+			printf("&amp;");
+		else if (*text == '<')
+			printf("&lt;");
+		else if (*text == '>')
+			printf("&gt;");
+		else if (*text == '"')
+			printf("&quot;");
+		else
+			putchar(*text);
+		text++;
+	}
+}
+
+/* Output text as a comment, avoiding a double hyphen */
+static void
+xmlCommentEscape(char *comment)
+{
+	int h_count = 0;
+
+	while (comment && *comment) {
+		if (*comment == '-') {
+			h_count++;
+			if (h_count >= 2) {
+				h_count = 0;
+				putchar(' ');
+			}
+			putchar('*');
+		}
+		/* strip trailing newline */
+		if (*comment == '\n' && *(comment + 1) == 0);
+		else
+			putchar(*comment);
+		comment++;
+	}
+}
+
+static void
+xmlComment(char *comment)
+{
+	printf("<!-- ");
+	xmlCommentEscape(comment);
+	printf(" -->\n");
+}
+
+static void
+xmlAttrS(char *name, char *value)
+{
+	printf("%s=\"", name);
+	xmlEncode(value);
+	printf("\" ");
+}
+
+static void
+xmlAttrI(char *name, long long int num)
+{
+	printf("%s=\"%lld\" ", name, num);
+}
+
+static void
+closeChain(void)
+{
+	if (curChain[0] == 0)
+		return;
+
+	if (closeActionTag[0])
+		printf("%s\n", closeActionTag);
+	closeActionTag[0] = 0;
+	if (closeRuleTag[0])
+		printf("%s\n", closeRuleTag);
+	closeRuleTag[0] = 0;
+	if (curChain[0])
+		printf("    </chain>\n");
+	curChain[0] = 0;
+	//lastRule[0]=0;
+}
+
+static void
+openChain(char *chain, char *policy, struct xt_counters *ctr, char close)
+{
+	closeChain();
+
+	strncpy(curChain, chain, XT_CHAIN_MAXNAMELEN);
+	curChain[XT_CHAIN_MAXNAMELEN] = '\0';
+
+	printf("    <chain ");
+	xmlAttrS("name", curChain);
+	if (strcmp(policy, "-") != 0)
+		xmlAttrS("policy", policy);
+	xmlAttrI("packet-count", (unsigned long long) ctr->pcnt);
+	xmlAttrI("byte-count", (unsigned long long) ctr->bcnt);
+	if (close) {
+		printf("%c", close);
+		curChain[0] = 0;
+	}
+	printf(">\n");
+}
+
+static int
+existsChain(char *chain)
+{
+	/* open a saved chain */
+	int c = 0;
+
+	if (0 == strcmp(curChain, chain))
+		return 1;
+	for (c = 0; c < nextChain; c++)
+		if (chains[c].chain && strcmp(chains[c].chain, chain) == 0)
+			return 1;
+	return 0;
+}
+
+static void
+needChain(char *chain)
+{
+	/* open a saved chain */
+	int c = 0;
+
+	if (0 == strcmp(curChain, chain))
+		return;
+
+	for (c = 0; c < nextChain; c++)
+		if (chains[c].chain && strcmp(chains[c].chain, chain) == 0) {
+			openChain(chains[c].chain, chains[c].policy,
+				  &(chains[c].count), '\0');
+			/* And, mark it as done so we don't create 
+			   an empty chain at table-end time */
+			chains[c].created = 1;
+		}
+}
+
+static void
+saveChain(char *chain, char *policy, struct xt_counters *ctr)
+{
+	if (nextChain >= maxChains)
+		xtables_error(PARAMETER_PROBLEM,
+			   "%s: line %u chain name invalid\n",
+			   prog_name, line);
+
+	chains[nextChain].chain = strdup(chain);
+	chains[nextChain].policy = strdup(policy);
+	chains[nextChain].count = *ctr;
+	chains[nextChain].created = 0;
+	nextChain++;
+}
+
+static void
+finishChains(void)
+{
+	int c;
+
+	for (c = 0; c < nextChain; c++)
+		if (!chains[c].created) {
+			openChain(chains[c].chain, chains[c].policy,
+				  &(chains[c].count), '/');
+			free(chains[c].chain);
+			free(chains[c].policy);
+		}
+	nextChain = 0;
+}
+
+static void
+closeTable(void)
+{
+	closeChain();
+	finishChains();
+	if (curTable[0])
+		printf("  </table>\n");
+	curTable[0] = 0;
+}
+
+static void
+openTable(char *table)
+{
+	closeTable();
+
+	strncpy(curTable, table, XT_TABLE_MAXNAMELEN);
+	curTable[XT_TABLE_MAXNAMELEN] = '\0';
+
+	printf("  <table ");
+	xmlAttrS("name", curTable);
+	printf(">\n");
+}
+
+// is char* -j --jump -g or --goto
+static int
+isTarget(char *arg)
+{
+	return ((arg)
+		&& (strcmp((arg), "-j") == 0 || strcmp((arg), "--jump") == 0
+		    || strcmp((arg), "-g") == 0
+		    || strcmp((arg), "--goto") == 0));
+}
+
+// is it a terminating target like -j ACCEPT, etc
+// (or I guess -j SNAT in nat table, but we don't check for that yet
+static int
+isTerminatingTarget(char *arg)
+{
+	return ((arg)
+		&& (strcmp((arg), "ACCEPT") == 0
+		    || strcmp((arg), "DROP") == 0
+		    || strcmp((arg), "QUEUE") == 0
+		    || strcmp((arg), "RETURN") == 0));
+}
+
+// part=-1 means do conditions, part=1 means do rules, part=0 means do both
+static void
+do_rule_part(char *leveltag1, char *leveltag2, int part, int argc,
+	     char *argv[], int argvattr[])
+{
+	int i;
+	int arg = 2;		// ignore leading -A <chain>
+	char invert_next = 0;
+	char *spacer = "";	// space when needed to assemble arguments
+	char *level1 = NULL;
+	char *level2 = NULL;
+	char *leveli1 = "        ";
+	char *leveli2 = "          ";
+
+#define CLOSE_LEVEL(LEVEL) \
+	do { \
+		if (level ## LEVEL) printf("</%s>\n", \
+		(leveltag ## LEVEL)?(leveltag ## LEVEL):(level ## LEVEL)); \
+		level ## LEVEL=NULL;\
+	} while(0)
+
+#define OPEN_LEVEL(LEVEL,TAG) \
+	do {\
+		level ## LEVEL=TAG;\
+		if (leveltag ## LEVEL) {\
+			printf("%s<%s ", (leveli ## LEVEL), \
+				(leveltag ## LEVEL));\
+			xmlAttrS("type", (TAG)); \
+		} else printf("%s<%s ", (leveli ## LEVEL), (level ## LEVEL)); \
+	} while(0)
+
+	if (part == 1) {	/* skip */
+		/* use argvattr to tell which arguments were quoted 
+		   to avoid comparing quoted arguments, like comments, to -j, */
+		while (arg < argc && (argvattr[arg] || !isTarget(argv[arg])))
+			arg++;
+	}
+
+	/* Before we start, if the first arg is -[^-] and not -m or -j or -g
+	 * then start a dummy <match> tag for old style built-in matches.
+	 * We would do this in any case, but no need if it would be empty.
+	 * In the case of negation, we need to look at arg+1
+	 */
+	if (arg < argc && strcmp(argv[arg], "!") == 0)
+		i = arg + 1;
+	else
+		i = arg;
+	if (i < argc && argv[i][0] == '-' && !isTarget(argv[i])
+	    && strcmp(argv[i], "-m") != 0) {
+		OPEN_LEVEL(1, "match");
+		printf(">\n");
+	}
+	while (arg < argc) {
+		// If ! is followed by -* then apply to that else output as data
+		// Stop, if we need to
+		if (part == -1 && !argvattr[arg] && (isTarget(argv[arg]))) {
+			break;
+		} else if (!argvattr[arg] && strcmp(argv[arg], "!") == 0) {
+			if ((arg + 1) < argc && argv[arg + 1][0] == '-')
+				invert_next = '!';
+			else
+				printf("%s%s", spacer, argv[arg]);
+			spacer = " ";
+		} else if (!argvattr[arg] && isTarget(argv[arg]) &&
+			   (arg + 1 < argc) &&
+			   existsChain(argv[arg + 1])) {
+			CLOSE_LEVEL(2);
+			if (level1)
+				printf("%s", leveli1);
+			CLOSE_LEVEL(1);
+			spacer = "";
+			invert_next = 0;
+			if (strcmp(argv[arg], "-g") == 0
+			    || strcmp(argv[arg], "--goto") == 0) {
+				/* goto user chain */
+				OPEN_LEVEL(1, "goto");
+				printf(">\n");
+				arg++;
+				OPEN_LEVEL(2, argv[arg]);
+				printf("/>\n");
+				level2 = NULL;
+			} else {
+				/* call user chain */
+				OPEN_LEVEL(1, "call");
+				printf(">\n");
+				arg++;
+				OPEN_LEVEL(2, argv[arg]);
+				printf("/>\n");
+				level2 = NULL;
+			}
+		} else if (!argvattr[arg]
+			   && (isTarget(argv[arg])
+			       || strcmp(argv[arg], "-m") == 0
+			       || strcmp(argv[arg], "--module") == 0)) {
+			if (!((1 + arg) < argc))
+				// no args to -j, -m or -g, ignore & finish loop
+				break;
+			CLOSE_LEVEL(2);
+			if (level1)
+				printf("%s", leveli1);
+			CLOSE_LEVEL(1);
+			spacer = "";
+			invert_next = 0;
+			arg++;
+			OPEN_LEVEL(1, (argv[arg]));
+			// Optimize case, can we close this tag already?
+			if ((arg + 1) >= argc || (!argvattr[arg + 1]
+						  && (isTarget(argv[arg + 1])
+						      || strcmp(argv[arg + 1],
+								"-m") == 0
+						      || strcmp(argv[arg + 1],
+								"--module") ==
+						      0))) {
+				printf(" />\n");
+				level1 = NULL;
+			} else {
+				printf(">\n");
+			}
+		} else if (!argvattr[arg] && argv[arg][0] == '-') {
+			char *tag;
+			CLOSE_LEVEL(2);
+			// Skip past any -
+			tag = argv[arg];
+			while (*tag == '-' && *tag)
+				tag++;
+
+			spacer = "";
+			OPEN_LEVEL(2, tag);
+			if (invert_next)
+				printf(" invert=\"1\"");
+			invert_next = 0;
+
+			// Optimize case, can we close this tag already?
+			if (!((arg + 1) < argc)
+			    || (argv[arg + 1][0] == '-' /* NOT QUOTED */ )) {
+				printf(" />\n");
+				level2 = NULL;
+			} else {
+				printf(">");
+			}
+		} else {	// regular data
+			char *spaces = strchr(argv[arg], ' ');
+			printf("%s", spacer);
+			if (spaces || argvattr[arg])
+				printf("&quot;");
+			// if argv[arg] contains a space, enclose in quotes
+			xmlEncode(argv[arg]);
+			if (spaces || argvattr[arg])
+				printf("&quot;");
+			spacer = " ";
+		}
+		arg++;
+	}
+	CLOSE_LEVEL(2);
+	if (level1)
+		printf("%s", leveli1);
+	CLOSE_LEVEL(1);
+}
+
+static int
+compareRules(int newargc, char *newargv[], int oldargc, char *oldargv[])
+{
+	/* Compare arguments up to -j or -g for match.
+	 * NOTE: We don't want to combine actions if there were no criteria
+	 * in each rule, or rules didn't have an action.
+	 * NOTE: Depends on arguments being in some kind of "normal" order which
+	 * is the case when processing the ACTUAL output of actual iptables-save
+	 * rather than a file merely in a compatible format.
+	 */
+
+	unsigned int old = 0;
+	unsigned int new = 0;
+
+	int compare = 0;
+
+	while (new < newargc && old < oldargc) {
+		if (isTarget(oldargv[old]) && isTarget(newargv[new])) {
+			/* if oldarg was a terminating action then it makes no sense
+			 * to combine further actions into the same xml */
+			if (((strcmp((oldargv[old]), "-j") == 0 
+					|| strcmp((oldargv[old]), "--jump") == 0) 
+				&& old+1 < oldargc
+				&& isTerminatingTarget(oldargv[old+1]) )
+			    || strcmp((oldargv[old]), "-g") == 0 
+			    || strcmp((oldargv[old]), "--goto") == 0 ) {
+				/* Previous rule had terminating action */	
+				compare = 0;
+			} else {
+				compare = 1;
+			}
+			break;
+		}
+		// break when old!=new
+		if (strcmp(oldargv[old], newargv[new]) != 0) {
+			compare = 0;
+			break;
+		}
+
+		old++;
+		new++;
+	}
+	// We won't match unless both rules had a target. 
+	// This means we don't combine target-less rules, which is good
+
+	return compare == 1;
+}
+
+/* has a nice parsed rule starting with -A */
+static void
+do_rule(char *pcnt, char *bcnt, int argc, char *argv[], int argvattr[],
+	int oldargc, char *oldargv[])
+{
+	/* are these conditions the same as the previous rule?
+	 * If so, skip arg straight to -j or -g */
+	if (combine && argc > 2 && !isTarget(argv[2]) &&
+	    compareRules(argc, argv, oldargc, oldargv)) {
+		xmlComment("Combine action from next rule");
+	} else {
+
+		if (closeActionTag[0]) {
+			printf("%s\n", closeActionTag);
+			closeActionTag[0] = 0;
+		}
+		if (closeRuleTag[0]) {
+			printf("%s\n", closeRuleTag);
+			closeRuleTag[0] = 0;
+		}
+
+		printf("      <rule ");
+		//xmlAttrS("table",curTable); // not needed in full mode 
+		//xmlAttrS("chain",argv[1]); // not needed in full mode 
+		if (pcnt)
+			xmlAttrS("packet-count", pcnt);
+		if (bcnt)
+			xmlAttrS("byte-count", bcnt);
+		printf(">\n");
+
+		strncpy(closeRuleTag, "      </rule>\n", XT_TABLE_MAXNAMELEN);
+		closeRuleTag[XT_TABLE_MAXNAMELEN] = '\0';
+
+		/* no point in writing out condition if there isn't one */
+		if (argc >= 3 && !isTarget(argv[2])) {
+			printf("       <conditions>\n");
+			do_rule_part(NULL, NULL, -1, argc, argv, argvattr);
+			printf("       </conditions>\n");
+		}
+	}
+	/* Write out the action */
+	//do_rule_part("action","arg",1,argc,argv,argvattr);
+	if (!closeActionTag[0]) {
+		printf("       <actions>\n");
+		strncpy(closeActionTag, "       </actions>\n",
+			XT_TABLE_MAXNAMELEN);
+		closeActionTag[XT_TABLE_MAXNAMELEN] = '\0';
+	}
+	do_rule_part(NULL, NULL, 1, argc, argv, argvattr);
+}
+
+int
+iptables_xml_main(int argc, char *argv[])
+{
+	struct argv_store last_rule = {}, cur_rule = {};
+	char buffer[10240];
+	int c;
+	FILE *in;
+
+	line = 0;
+
+	xtables_set_params(&iptables_xml_globals);
+	while ((c = getopt_long(argc, argv, "cvh", options, NULL)) != -1) {
+		switch (c) {
+		case 'c':
+			combine = 1;
+			break;
+		case 'v':
+			printf("xptables-xml\n");
+			verbose = 1;
+			break;
+		case 'h':
+			print_usage("iptables-xml", PACKAGE_VERSION);
+			break;
+		}
+	}
+
+	if (optind == argc - 1) {
+		in = fopen(argv[optind], "re");
+		if (!in) {
+			fprintf(stderr, "Can't open %s: %s", argv[optind],
+				strerror(errno));
+			exit(1);
+		}
+	} else if (optind < argc) {
+		fprintf(stderr, "Unknown arguments found on commandline");
+		exit(1);
+	} else
+		in = stdin;
+
+	printf("<iptables-rules version=\"1.0\">\n");
+
+	/* Grab standard input. */
+	while (fgets(buffer, sizeof(buffer), in)) {
+		int ret = 0;
+
+		line++;
+
+		if (buffer[0] == '\n')
+			continue;
+		else if (buffer[0] == '#') {
+			xmlComment(buffer);
+			continue;
+		}
+
+		if (verbose) {
+			printf("<!-- line %d ", line);
+			xmlCommentEscape(buffer);
+			printf(" -->\n");
+		}
+
+		if ((strcmp(buffer, "COMMIT\n") == 0) && (curTable[0])) {
+			DEBUGP("Calling commit\n");
+			closeTable();
+			ret = 1;
+		} else if ((buffer[0] == '*')) {
+			/* New table */
+			char *table;
+
+			table = strtok(buffer + 1, " \t\n");
+			DEBUGP("line %u, table '%s'\n", line, table);
+			if (!table)
+				xtables_error(PARAMETER_PROBLEM,
+					   "%s: line %u table name invalid\n",
+					   prog_name, line);
+
+			openTable(table);
+
+			ret = 1;
+		} else if ((buffer[0] == ':') && (curTable[0])) {
+			/* New chain. */
+			char *policy, *chain;
+			struct xt_counters count;
+			char *ctrs;
+
+			chain = strtok(buffer + 1, " \t\n");
+			DEBUGP("line %u, chain '%s'\n", line, chain);
+			if (!chain)
+				xtables_error(PARAMETER_PROBLEM,
+					   "%s: line %u chain name invalid\n",
+					   prog_name, line);
+
+			DEBUGP("Creating new chain '%s'\n", chain);
+
+			policy = strtok(NULL, " \t\n");
+			DEBUGP("line %u, policy '%s'\n", line, policy);
+			if (!policy)
+				xtables_error(PARAMETER_PROBLEM,
+					   "%s: line %u policy invalid\n",
+					   prog_name, line);
+
+			ctrs = strtok(NULL, " \t\n");
+			parse_counters(ctrs, &count);
+			saveChain(chain, policy, &count);
+
+			ret = 1;
+		} else if (curTable[0]) {
+			unsigned int a;
+			char *pcnt = NULL;
+			char *bcnt = NULL;
+			char *parsestart = buffer;
+			char *chain = NULL;
+
+			tokenize_rule_counters(&parsestart, &pcnt, &bcnt, line);
+			add_param_to_argv(&cur_rule, parsestart, line);
+
+			DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
+			       cur_rule.argc, curTable);
+			debug_print_argv(&cur_rule);
+
+			for (a = 1; a < cur_rule.argc; a++) {
+				if (strcmp(cur_rule.argv[a - 1], "-A"))
+					continue;
+				chain = cur_rule.argv[a];
+				break;
+			}
+			if (!chain) {
+				fprintf(stderr, "%s: line %u failed - no chain found\n",
+					prog_name, line);
+				exit(1);
+			}
+			needChain(chain);// Should we explicitly look for -A
+			do_rule(pcnt, bcnt, cur_rule.argc, cur_rule.argv,
+				cur_rule.argvattr, last_rule.argc, last_rule.argv);
+
+			save_argv(&last_rule, &cur_rule);
+			ret = 1;
+		}
+		if (!ret) {
+			fprintf(stderr, "%s: line %u failed\n",
+				prog_name, line);
+			exit(1);
+		}
+	}
+	if (curTable[0]) {
+		fprintf(stderr, "%s: COMMIT expected at line %u\n",
+			prog_name, line + 1);
+		exit(1);
+	}
+
+	fclose(in);
+	printf("</iptables-rules>\n");
+	free_argv(&last_rule);
+
+	return 0;
+}
diff --git a/iptables/iptables.8.in b/iptables/iptables.8.in
new file mode 100644
index 0000000..999cf33
--- /dev/null
+++ b/iptables/iptables.8.in
@@ -0,0 +1,486 @@
+.TH IPTABLES 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.\"
+.\" Man page written by Herve Eychenne <rv@wallfire.org> (May 1999)
+.\" It is based on ipchains page.
+.\" TODO: add a word for protocol helpers (FTP, IRC, SNMP-ALG)
+.\"
+.\" ipchains page by Paul ``Rusty'' Russell March 1997
+.\" Based on the original ipfwadm man page by Jos Vos <jos@xos.nl>
+.\"
+.\"	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 2 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, write to the Free Software
+.\"	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+iptables/ip6tables \(em administration tool for IPv4/IPv6 packet filtering and NAT
+.SH SYNOPSIS
+\fBiptables\fP [\fB\-t\fP \fItable\fP] {\fB\-A\fP|\fB\-C\fP|\fB\-D\fP}
+\fIchain\fP \fIrule-specification\fP
+.P
+\fBip6tables\fP [\fB\-t\fP \fItable\fP] {\fB\-A\fP|\fB\-C\fP|\fB\-D\fP}
+\fIchain rule-specification\fP
+.PP
+\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-I\fP \fIchain\fP [\fIrulenum\fP] \fIrule-specification\fP
+.PP
+\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-R\fP \fIchain rulenum rule-specification\fP
+.PP
+\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-D\fP \fIchain rulenum\fP
+.PP
+\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-S\fP [\fIchain\fP [\fIrulenum\fP]]
+.PP
+\fBiptables\fP [\fB\-t\fP \fItable\fP] {\fB\-F\fP|\fB\-L\fP|\fB\-Z\fP} [\fIchain\fP [\fIrulenum\fP]] [\fIoptions...\fP]
+.PP
+\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-N\fP \fIchain\fP
+.PP
+\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-X\fP [\fIchain\fP]
+.PP
+\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-P\fP \fIchain target\fP
+.PP
+\fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-E\fP \fIold-chain-name new-chain-name\fP
+.PP
+rule-specification = [\fImatches...\fP] [\fItarget\fP]
+.PP
+match = \fB\-m\fP \fImatchname\fP [\fIper-match-options\fP]
+.PP
+target = \fB\-j\fP \fItargetname\fP [\fIper\-target\-options\fP]
+.SH DESCRIPTION
+\fBIptables\fP and \fBip6tables\fP are used to set up, maintain, and inspect the
+tables of IPv4 and IPv6 packet
+filter rules in the Linux kernel.  Several different tables
+may be defined.  Each table contains a number of built-in
+chains and may also contain user-defined chains.
+.PP
+Each chain is a list of rules which can match a set of packets.  Each
+rule specifies what to do with a packet that matches.  This is called
+a `target', which may be a jump to a user-defined chain in the same
+table.
+.SH TARGETS
+A firewall rule specifies criteria for a packet and a target.  If the
+packet does not match, the next rule in the chain is examined; if
+it does match, then the next rule is specified by the value of the
+target, which can be the name of a user-defined chain, one of the targets
+described in \fBiptables\-extensions\fP(8), or one of the
+special values \fBACCEPT\fP, \fBDROP\fP or \fBRETURN\fP.
+.PP
+\fBACCEPT\fP means to let the packet through.
+\fBDROP\fP means to drop the packet on the floor.
+\fBRETURN\fP means stop traversing this chain and resume at the next
+rule in the
+previous (calling) chain.  If the end of a built-in chain is reached
+or a rule in a built-in chain with target \fBRETURN\fP
+is matched, the target specified by the chain policy determines the
+fate of the packet.
+.SH TABLES
+There are currently five independent tables (which tables are present
+at any time depends on the kernel configuration options and which
+modules are present).
+.TP
+\fB\-t\fP, \fB\-\-table\fP \fItable\fP
+This option specifies the packet matching table which the command
+should operate on.  If the kernel is configured with automatic module
+loading, an attempt will be made to load the appropriate module for
+that table if it is not already there.
+
+The tables are as follows:
+.RS
+.TP .4i
+\fBfilter\fP:
+This is the default table (if no \-t option is passed). It contains
+the built-in chains \fBINPUT\fP (for packets destined to local sockets),
+\fBFORWARD\fP (for packets being routed through the box), and
+\fBOUTPUT\fP (for locally-generated packets).
+.TP
+\fBnat\fP:
+This table is consulted when a packet that creates a new
+connection is encountered.  It consists of four built-ins: \fBPREROUTING\fP
+(for altering packets as soon as they come in), \fBINPUT\fP (for altering
+packets destined for local sockets), \fBOUTPUT\fP
+(for altering locally-generated packets before routing), and \fBPOSTROUTING\fP
+(for altering packets as they are about to go out).
+IPv6 NAT support is available since kernel 3.7.
+.TP
+\fBmangle\fP:
+This table is used for specialized packet alteration.  Until kernel
+2.4.17 it had two built-in chains: \fBPREROUTING\fP
+(for altering incoming packets before routing) and \fBOUTPUT\fP
+(for altering locally-generated packets before routing).
+Since kernel 2.4.18, three other built-in chains are also supported:
+\fBINPUT\fP (for packets coming into the box itself), \fBFORWARD\fP
+(for altering packets being routed through the box), and \fBPOSTROUTING\fP
+(for altering packets as they are about to go out).
+.TP
+\fBraw\fP:
+This table is used mainly for configuring exemptions from connection
+tracking in combination with the NOTRACK target.  It registers at the netfilter
+hooks with higher priority and is thus called before ip_conntrack, or any other
+IP tables.  It provides the following built-in chains: \fBPREROUTING\fP
+(for packets arriving via any network interface) \fBOUTPUT\fP
+(for packets generated by local processes)
+.TP
+\fBsecurity\fP:
+This table is used for Mandatory Access Control (MAC) networking rules, such
+as those enabled by the \fBSECMARK\fP and \fBCONNSECMARK\fP targets.
+Mandatory Access Control is implemented by Linux Security Modules such as
+SELinux.  The security table is called after the filter table, allowing any
+Discretionary Access Control (DAC) rules in the filter table to take effect
+before MAC rules.  This table provides the following built-in chains:
+\fBINPUT\fP (for packets coming into the box itself),
+\fBOUTPUT\fP (for altering locally-generated packets before routing), and
+\fBFORWARD\fP (for altering packets being routed through the box).
+.RE
+.SH OPTIONS
+The options that are recognized by
+\fBiptables\fP and \fBip6tables\fP can be divided into several different groups.
+.SS COMMANDS
+These options specify the desired action to perform. Only one of them
+can be specified on the command line unless otherwise stated
+below. For long versions of the command and option names, you
+need to use only enough letters to ensure that
+\fBiptables\fP can differentiate it from all other options.
+.TP
+\fB\-A\fP, \fB\-\-append\fP \fIchain rule-specification\fP
+Append one or more rules to the end of the selected chain.
+When the source and/or destination names resolve to more than one
+address, a rule will be added for each possible address combination.
+.TP
+\fB\-C\fP, \fB\-\-check\fP \fIchain rule-specification\fP
+Check whether a rule matching the specification does exist in the
+selected chain. This command uses the same logic as \fB\-D\fP to
+find a matching entry, but does not alter the existing iptables
+configuration and uses its exit code to indicate success or failure.
+.TP
+\fB\-D\fP, \fB\-\-delete\fP \fIchain rule-specification\fP
+.ns
+.TP
+\fB\-D\fP, \fB\-\-delete\fP \fIchain rulenum\fP
+Delete one or more rules from the selected chain.  There are two
+versions of this command: the rule can be specified as a number in the
+chain (starting at 1 for the first rule) or a rule to match.
+.TP
+\fB\-I\fP, \fB\-\-insert\fP \fIchain\fP [\fIrulenum\fP] \fIrule-specification\fP
+Insert one or more rules in the selected chain as the given rule
+number.  So, if the rule number is 1, the rule or rules are inserted
+at the head of the chain.  This is also the default if no rule number
+is specified.
+.TP
+\fB\-R\fP, \fB\-\-replace\fP \fIchain rulenum rule-specification\fP
+Replace a rule in the selected chain.  If the source and/or
+destination names resolve to multiple addresses, the command will
+fail.  Rules are numbered starting at 1.
+.TP
+\fB\-L\fP, \fB\-\-list\fP [\fIchain\fP]
+List all rules in the selected chain.  If no chain is selected, all
+chains are listed. Like every other iptables command, it applies to the
+specified table (filter is the default), so NAT rules get listed by
+.nf
+ iptables \-t nat \-n \-L
+.fi
+Please note that it is often used with the \fB\-n\fP
+option, in order to avoid long reverse DNS lookups.
+It is legal to specify the \fB\-Z\fP
+(zero) option as well, in which case the chain(s) will be atomically
+listed and zeroed.  The exact output is affected by the other
+arguments given. The exact rules are suppressed until you use
+.nf
+ iptables \-L \-v
+.fi
+or
+\fBiptables\-save\fP(8).
+.TP
+\fB\-S\fP, \fB\-\-list\-rules\fP [\fIchain\fP]
+Print all rules in the selected chain.  If no chain is selected, all
+chains are printed like iptables-save. Like every other iptables command,
+it applies to the specified table (filter is the default).
+.TP
+\fB\-F\fP, \fB\-\-flush\fP [\fIchain\fP]
+Flush the selected chain (all the chains in the table if none is given).
+This is equivalent to deleting all the rules one by one.
+.TP
+\fB\-Z\fP, \fB\-\-zero\fP [\fIchain\fP [\fIrulenum\fP]]
+Zero the packet and byte counters in all chains, or only the given chain,
+or only the given rule in a chain. It is legal to
+specify the
+\fB\-L\fP, \fB\-\-list\fP
+(list) option as well, to see the counters immediately before they are
+cleared. (See above.)
+.TP
+\fB\-N\fP, \fB\-\-new\-chain\fP \fIchain\fP
+Create a new user-defined chain by the given name.  There must be no
+target of that name already.
+.TP
+\fB\-X\fP, \fB\-\-delete\-chain\fP [\fIchain\fP]
+Delete the optional user-defined chain specified.  There must be no references
+to the chain.  If there are, you must delete or replace the referring rules
+before the chain can be deleted.  The chain must be empty, i.e. not contain
+any rules.  If no argument is given, it will attempt to delete every
+non-builtin chain in the table.
+.TP
+\fB\-P\fP, \fB\-\-policy\fP \fIchain target\fP
+Set the policy for the built-in (non-user-defined) chain to the given target.
+The policy target must be either \fBACCEPT\fP or \fBDROP\fP.
+.TP
+\fB\-E\fP, \fB\-\-rename\-chain\fP \fIold\-chain new\-chain\fP
+Rename the user specified chain to the user supplied name.  This is
+cosmetic, and has no effect on the structure of the table.
+.TP
+\fB\-h\fP
+Help.
+Give a (currently very brief) description of the command syntax.
+.SS PARAMETERS
+The following parameters make up a rule specification (as used in the
+add, delete, insert, replace and append commands).
+.TP
+\fB\-4\fP, \fB\-\-ipv4\fP
+This option has no effect in iptables and iptables-restore.
+If a rule using the \fB\-4\fP option is inserted with (and only with)
+ip6tables-restore, it will be silently ignored. Any other uses will throw an
+error. This option allows IPv4 and IPv6 rules in a single rule file
+for use with both iptables-restore and ip6tables-restore.
+.TP
+\fB\-6\fP, \fB\-\-ipv6\fP
+If a rule using the \fB\-6\fP option is inserted with (and only with)
+iptables-restore, it will be silently ignored. Any other uses will throw an
+error. This option allows IPv4 and IPv6 rules in a single rule file
+for use with both iptables-restore and ip6tables-restore.
+This option has no effect in ip6tables and ip6tables-restore.
+.TP
+[\fB!\fP] \fB\-p\fP, \fB\-\-protocol\fP \fIprotocol\fP
+The protocol of the rule or of the packet to check.
+The specified protocol can be one of \fBtcp\fP, \fBudp\fP, \fBudplite\fP,
+\fBicmp\fP, \fBicmpv6\fP,\fBesp\fP, \fBah\fP, \fBsctp\fP, \fBmh\fP or the special keyword "\fBall\fP",
+or it can be a numeric value, representing one of these protocols or a
+different one.  A protocol name from /etc/protocols is also allowed.
+A "!" argument before the protocol inverts the
+test.  The number zero is equivalent to \fBall\fP. "\fBall\fP"
+will match with all protocols and is taken as default when this
+option is omitted.
+Note that, in ip6tables, IPv6 extension headers except \fBesp\fP are not allowed.
+\fBesp\fP and \fBipv6\-nonext\fP
+can be used with Kernel version 2.6.11 or later.
+The number zero is equivalent to \fBall\fP, which means that you cannot
+test the protocol field for the value 0 directly. To match on a HBH header,
+even if it were the last, you cannot use \fB\-p 0\fP, but always need
+\fB\-m hbh\fP.
+.TP
+[\fB!\fP] \fB\-s\fP, \fB\-\-source\fP \fIaddress\fP[\fB/\fP\fImask\fP][\fB,\fP\fI...\fP]
+Source specification. \fIAddress\fP
+can be either a network name, a hostname, a network IP address (with
+\fB/\fP\fImask\fP), or a plain IP address. Hostnames will
+be resolved once only, before the rule is submitted to the kernel.
+Please note that specifying any name to be resolved with a remote query such as
+DNS is a really bad idea.
+The \fImask\fP
+can be either an ipv4 network mask (for iptables) or a plain number,
+specifying the number of 1's at the left side of the network mask.
+Thus, an iptables mask of \fI24\fP is equivalent to \fI255.255.255.0\fP.
+A "!" argument before the address specification inverts the sense of
+the address. The flag \fB\-\-src\fP is an alias for this option.
+Multiple addresses can be specified, but this will \fBexpand to multiple
+rules\fP (when adding with \-A), or will cause multiple rules to be
+deleted (with \-D).
+.TP
+[\fB!\fP] \fB\-d\fP, \fB\-\-destination\fP \fIaddress\fP[\fB/\fP\fImask\fP][\fB,\fP\fI...\fP]
+Destination specification. 
+See the description of the \fB\-s\fP
+(source) flag for a detailed description of the syntax.  The flag
+\fB\-\-dst\fP is an alias for this option.
+.TP
+\fB\-m\fP, \fB\-\-match\fP \fImatch\fP
+Specifies a match to use, that is, an extension module that tests for a
+specific property. The set of matches make up the condition under which a
+target is invoked. Matches are evaluated first to last as specified on the
+command line and work in short-circuit fashion, i.e. if one extension yields
+false, evaluation will stop.
+.TP
+\fB\-j\fP, \fB\-\-jump\fP \fItarget\fP
+This specifies the target of the rule; i.e., what to do if the packet
+matches it.  The target can be a user-defined chain (other than the
+one this rule is in), one of the special builtin targets which decide
+the fate of the packet immediately, or an extension (see \fBEXTENSIONS\fP
+below).  If this
+option is omitted in a rule (and \fB\-g\fP
+is not used), then matching the rule will have no
+effect on the packet's fate, but the counters on the rule will be
+incremented.
+.TP
+\fB\-g\fP, \fB\-\-goto\fP \fIchain\fP
+This specifies that the processing should continue in a user
+specified chain. Unlike the \-\-jump option return will not continue
+processing in this chain but instead in the chain that called us via
+\-\-jump.
+.TP
+[\fB!\fP] \fB\-i\fP, \fB\-\-in\-interface\fP \fIname\fP
+Name of an interface via which a packet was received (only for
+packets entering the \fBINPUT\fP, \fBFORWARD\fP and \fBPREROUTING\fP
+chains).  When the "!" argument is used before the interface name, the
+sense is inverted.  If the interface name ends in a "+", then any
+interface which begins with this name will match.  If this option is
+omitted, any interface name will match.
+.TP
+[\fB!\fP] \fB\-o\fP, \fB\-\-out\-interface\fP \fIname\fP
+Name of an interface via which a packet is going to be sent (for packets
+entering the \fBFORWARD\fP, \fBOUTPUT\fP and \fBPOSTROUTING\fP
+chains).  When the "!" argument is used before the interface name, the
+sense is inverted.  If the interface name ends in a "+", then any
+interface which begins with this name will match.  If this option is
+omitted, any interface name will match.
+.TP
+[\fB!\fP] \fB\-f\fP, \fB\-\-fragment\fP
+This means that the rule only refers to second and further IPv4 fragments
+of fragmented packets.  Since there is no way to tell the source or
+destination ports of such a packet (or ICMP type), such a packet will
+not match any rules which specify them.  When the "!" argument
+precedes the "\-f" flag, the rule will only match head fragments, or
+unfragmented packets. This option is IPv4 specific, it is not available
+in ip6tables.
+.TP
+\fB\-c\fP, \fB\-\-set\-counters\fP \fIpackets bytes\fP
+This enables the administrator to initialize the packet and byte
+counters of a rule (during \fBINSERT\fP, \fBAPPEND\fP, \fBREPLACE\fP
+operations).
+.SS "OTHER OPTIONS"
+The following additional options can be specified:
+.TP
+\fB\-v\fP, \fB\-\-verbose\fP
+Verbose output.  This option makes the list command show the interface
+name, the rule options (if any), and the TOS masks.  The packet and
+byte counters are also listed, with the suffix 'K', 'M' or 'G' for
+1000, 1,000,000 and 1,000,000,000 multipliers respectively (but see
+the \fB\-x\fP flag to change this).
+For appending, insertion, deletion and replacement, this causes
+detailed information on the rule or rules to be printed. \fB\-v\fP may be
+specified multiple times to possibly emit more detailed debug statements.
+.TP
+\fB\-w\fP, \fB\-\-wait\fP [\fIseconds\fP]
+Wait for the xtables lock.
+To prevent multiple instances of the program from running concurrently,
+an attempt will be made to obtain an exclusive lock at launch.  By default,
+the program will exit if the lock cannot be obtained.  This option will
+make the program wait (indefinitely or for optional \fIseconds\fP) until
+the exclusive lock can be obtained.
+.TP
+\fB\-W\fP, \fB\-\-wait-interval\fP \fImicroseconds\fP
+Interval to wait per each iteration.
+When running latency sensitive applications, waiting for the xtables lock
+for extended durations may not be acceptable. This option will make each
+iteration take the amount of time specified. The default interval is
+1 second. This option only works with \fB\-w\fP.
+.TP
+\fB\-n\fP, \fB\-\-numeric\fP
+Numeric output.
+IP addresses and port numbers will be printed in numeric format.
+By default, the program will try to display them as host names,
+network names, or services (whenever applicable).
+.TP
+\fB\-x\fP, \fB\-\-exact\fP
+Expand numbers.
+Display the exact value of the packet and byte counters,
+instead of only the rounded number in K's (multiples of 1000)
+M's (multiples of 1000K) or G's (multiples of 1000M).  This option is
+only relevant for the \fB\-L\fP command.
+.TP
+\fB\-\-line\-numbers\fP
+When listing rules, add line numbers to the beginning of each rule,
+corresponding to that rule's position in the chain.
+.TP
+\fB\-\-modprobe=\fP\fIcommand\fP
+When adding or inserting rules into a chain, use \fIcommand\fP
+to load any necessary modules (targets, match extensions, etc).
+
+.SH LOCK FILE
+iptables uses the \fI@XT_LOCK_NAME@\fP file to take an exclusive lock at
+launch.
+
+The \fBXTABLES_LOCKFILE\fP environment variable can be used to override
+the default setting.
+
+.SH MATCH AND TARGET EXTENSIONS
+.PP
+iptables can use extended packet matching and target modules.
+A list of these is available in the \fBiptables\-extensions\fP(8) manpage.
+.SH DIAGNOSTICS
+Various error messages are printed to standard error.  The exit code
+is 0 for correct functioning.  Errors which appear to be caused by
+invalid or abused command line parameters cause an exit code of 2, and
+other errors cause an exit code of 1.
+.SH BUGS
+Bugs?  What's this? ;-)
+Well, you might want to have a look at http://bugzilla.netfilter.org/
+.SH COMPATIBILITY WITH IPCHAINS
+This \fBiptables\fP
+is very similar to ipchains by Rusty Russell.  The main difference is
+that the chains \fBINPUT\fP and \fBOUTPUT\fP
+are only traversed for packets coming into the local host and
+originating from the local host respectively.  Hence every packet only
+passes through one of the three chains (except loopback traffic, which
+involves both INPUT and OUTPUT chains); previously a forwarded packet
+would pass through all three.
+.PP
+The other main difference is that \fB\-i\fP refers to the input interface;
+\fB\-o\fP refers to the output interface, and both are available for packets
+entering the \fBFORWARD\fP chain.
+.PP
+The various forms of NAT have been separated out; \fBiptables\fP
+is a pure packet filter when using the default `filter' table, with
+optional extension modules.  This should simplify much of the previous
+confusion over the combination of IP masquerading and packet filtering
+seen previously.  So the following options are handled differently:
+.nf
+ \-j MASQ
+ \-M \-S
+ \-M \-L
+.fi
+There are several other changes in iptables.
+.SH SEE ALSO
+\fBiptables\-apply\fP(8),
+\fBiptables\-save\fP(8),
+\fBiptables\-restore\fP(8),
+\fBiptables\-extensions\fP(8),
+.PP
+The packet-filtering-HOWTO details iptables usage for
+packet filtering, the NAT-HOWTO details NAT,
+the netfilter-extensions-HOWTO details the extensions that are
+not in the standard distribution,
+and the netfilter-hacking-HOWTO details the netfilter internals.
+.br
+See
+.BR "http://www.netfilter.org/" .
+.SH AUTHORS
+Rusty Russell originally wrote iptables, in early consultation with Michael
+Neuling.
+.PP
+Marc Boucher made Rusty abandon ipnatctl by lobbying for a generic packet
+selection framework in iptables, then wrote the mangle table, the owner match,
+the mark stuff, and ran around doing cool stuff everywhere.
+.PP
+James Morris wrote the TOS target, and tos match.
+.PP
+Jozsef Kadlecsik wrote the REJECT target.
+.PP
+Harald Welte wrote the ULOG and NFQUEUE target, the new libiptc, as well as the TTL, DSCP, ECN matches and targets.
+.PP
+The Netfilter Core Team is: Jozsef Kadlecsik, Pablo Neira Ayuso,
+Eric Leblond, Florian Westphal and  Arturo Borrero Gonzalez.
+Emeritus Core Team members are: Marc
+Boucher, Martin Josefsson, Yasuyuki Kozakai, James Morris, Harald Welte and
+Rusty Russell.
+.PP
+Man page originally written by Herve Eychenne <rv@wallfire.org>.
+.\" .. and did I mention that we are incredibly cool people?
+.\" .. sexy, too ..
+.\" .. witty, charming, powerful ..
+.\" .. and most of all, modest ..
+.SH VERSION
+.PP
+This manual page applies to iptables/ip6tables @PACKAGE_VERSION@.
diff --git a/iptables/iptables.c b/iptables/iptables.c
new file mode 100644
index 0000000..7d61831
--- /dev/null
+++ b/iptables/iptables.c
@@ -0,0 +1,1703 @@
+/* Code to take an iptables-style command line and do it. */
+
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	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 2 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, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "config.h"
+#include <getopt.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <unistd.h>
+#include <iptables.h>
+#include <xtables.h>
+#include <fcntl.h>
+#include "xshared.h"
+
+static const char unsupported_rev[] = " [unsupported revision]";
+
+static struct option original_opts[] = {
+	{.name = "append",        .has_arg = 1, .val = 'A'},
+	{.name = "delete",        .has_arg = 1, .val = 'D'},
+	{.name = "check",         .has_arg = 1, .val = 'C'},
+	{.name = "insert",        .has_arg = 1, .val = 'I'},
+	{.name = "replace",       .has_arg = 1, .val = 'R'},
+	{.name = "list",          .has_arg = 2, .val = 'L'},
+	{.name = "list-rules",    .has_arg = 2, .val = 'S'},
+	{.name = "flush",         .has_arg = 2, .val = 'F'},
+	{.name = "zero",          .has_arg = 2, .val = 'Z'},
+	{.name = "new-chain",     .has_arg = 1, .val = 'N'},
+	{.name = "delete-chain",  .has_arg = 2, .val = 'X'},
+	{.name = "rename-chain",  .has_arg = 1, .val = 'E'},
+	{.name = "policy",        .has_arg = 1, .val = 'P'},
+	{.name = "source",        .has_arg = 1, .val = 's'},
+	{.name = "destination",   .has_arg = 1, .val = 'd'},
+	{.name = "src",           .has_arg = 1, .val = 's'}, /* synonym */
+	{.name = "dst",           .has_arg = 1, .val = 'd'}, /* synonym */
+	{.name = "protocol",      .has_arg = 1, .val = 'p'},
+	{.name = "in-interface",  .has_arg = 1, .val = 'i'},
+	{.name = "jump",          .has_arg = 1, .val = 'j'},
+	{.name = "table",         .has_arg = 1, .val = 't'},
+	{.name = "match",         .has_arg = 1, .val = 'm'},
+	{.name = "numeric",       .has_arg = 0, .val = 'n'},
+	{.name = "out-interface", .has_arg = 1, .val = 'o'},
+	{.name = "verbose",       .has_arg = 0, .val = 'v'},
+	{.name = "wait",          .has_arg = 2, .val = 'w'},
+	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
+	{.name = "exact",         .has_arg = 0, .val = 'x'},
+	{.name = "fragments",     .has_arg = 0, .val = 'f'},
+	{.name = "version",       .has_arg = 0, .val = 'V'},
+	{.name = "help",          .has_arg = 2, .val = 'h'},
+	{.name = "line-numbers",  .has_arg = 0, .val = '0'},
+	{.name = "modprobe",      .has_arg = 1, .val = 'M'},
+	{.name = "set-counters",  .has_arg = 1, .val = 'c'},
+	{.name = "goto",          .has_arg = 1, .val = 'g'},
+	{.name = "ipv4",          .has_arg = 0, .val = '4'},
+	{.name = "ipv6",          .has_arg = 0, .val = '6'},
+	{NULL},
+};
+
+void iptables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+
+struct xtables_globals iptables_globals = {
+	.option_offset = 0,
+	.program_version = PACKAGE_VERSION,
+	.orig_opts = original_opts,
+	.exit_err = iptables_exit_error,
+	.compat_rev = xtables_compatible_revision,
+};
+
+static const int inverse_for_options[NUMBER_OF_OPT] =
+{
+/* -n */ 0,
+/* -s */ IPT_INV_SRCIP,
+/* -d */ IPT_INV_DSTIP,
+/* -p */ XT_INV_PROTO,
+/* -j */ 0,
+/* -v */ 0,
+/* -x */ 0,
+/* -i */ IPT_INV_VIA_IN,
+/* -o */ IPT_INV_VIA_OUT,
+/*--line*/ 0,
+/* -c */ 0,
+/* -f */ IPT_INV_FRAG,
+};
+
+#define opts iptables_globals.opts
+#define prog_name iptables_globals.program_name
+#define prog_vers iptables_globals.program_version
+
+static void __attribute__((noreturn))
+exit_tryhelp(int status)
+{
+	if (line != -1)
+		fprintf(stderr, "Error occurred at line: %d\n", line);
+	fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
+			prog_name, prog_name);
+	xtables_free_opts(1);
+	exit(status);
+}
+
+static void
+exit_printhelp(const struct xtables_rule_match *matches)
+{
+	printf("%s v%s\n\n"
+"Usage: %s -[ACD] chain rule-specification [options]\n"
+"       %s -I chain [rulenum] rule-specification [options]\n"
+"       %s -R chain rulenum rule-specification [options]\n"
+"       %s -D chain rulenum [options]\n"
+"       %s -[LS] [chain [rulenum]] [options]\n"
+"       %s -[FZ] [chain] [options]\n"
+"       %s -[NX] chain\n"
+"       %s -E old-chain-name new-chain-name\n"
+"       %s -P chain target [options]\n"
+"       %s -h (print this help information)\n\n",
+	       prog_name, prog_vers, prog_name, prog_name,
+	       prog_name, prog_name, prog_name, prog_name,
+	       prog_name, prog_name, prog_name, prog_name);
+
+	printf(
+"Commands:\n"
+"Either long or short options are allowed.\n"
+"  --append  -A chain		Append to chain\n"
+"  --check   -C chain		Check for the existence of a rule\n"
+"  --delete  -D chain		Delete matching rule from chain\n"
+"  --delete  -D chain rulenum\n"
+"				Delete rule rulenum (1 = first) from chain\n"
+"  --insert  -I chain [rulenum]\n"
+"				Insert in chain as rulenum (default 1=first)\n"
+"  --replace -R chain rulenum\n"
+"				Replace rule rulenum (1 = first) in chain\n"
+"  --list    -L [chain [rulenum]]\n"
+"				List the rules in a chain or all chains\n"
+"  --list-rules -S [chain [rulenum]]\n"
+"				Print the rules in a chain or all chains\n"
+"  --flush   -F [chain]		Delete all rules in  chain or all chains\n"
+"  --zero    -Z [chain [rulenum]]\n"
+"				Zero counters in chain or all chains\n"
+"  --new     -N chain		Create a new user-defined chain\n"
+"  --delete-chain\n"
+"            -X [chain]		Delete a user-defined chain\n"
+"  --policy  -P chain target\n"
+"				Change policy on chain to target\n"
+"  --rename-chain\n"
+"            -E old-chain new-chain\n"
+"				Change chain name, (moving any references)\n"
+
+"Options:\n"
+"    --ipv4	-4		Nothing (line is ignored by ip6tables-restore)\n"
+"    --ipv6	-6		Error (line is ignored by iptables-restore)\n"
+"[!] --protocol	-p proto	protocol: by number or name, eg. `tcp'\n"
+"[!] --source	-s address[/mask][...]\n"
+"				source specification\n"
+"[!] --destination -d address[/mask][...]\n"
+"				destination specification\n"
+"[!] --in-interface -i input name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+" --jump	-j target\n"
+"				target for rule (may load target extension)\n"
+#ifdef IPT_F_GOTO
+"  --goto      -g chain\n"
+"                              jump to chain with no return\n"
+#endif
+"  --match	-m match\n"
+"				extended match (may load extension)\n"
+"  --numeric	-n		numeric output of addresses and ports\n"
+"[!] --out-interface -o output name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --table	-t table	table to manipulate (default: `filter')\n"
+"  --verbose	-v		verbose mode\n"
+"  --wait	-w [seconds]	maximum wait to acquire xtables lock before give up\n"
+"  --wait-interval -W [usecs]	wait time to try to acquire xtables lock\n"
+"				default is 1 second\n"
+"  --line-numbers		print line numbers when listing\n"
+"  --exact	-x		expand numbers (display exact values)\n"
+"[!] --fragment	-f		match second or further fragments only\n"
+"  --modprobe=<command>		try to insert modules using this command\n"
+"  --set-counters PKTS BYTES	set the counter during insert/append\n"
+"[!] --version	-V		print package version.\n");
+
+	print_extension_helps(xtables_targets, matches);
+	exit(0);
+}
+
+void
+iptables_exit_error(enum xtables_exittype status, const char *msg, ...)
+{
+	va_list args;
+
+	va_start(args, msg);
+	fprintf(stderr, "%s v%s (legacy): ", prog_name, prog_vers);
+	vfprintf(stderr, msg, args);
+	va_end(args);
+	fprintf(stderr, "\n");
+	if (status == PARAMETER_PROBLEM)
+		exit_tryhelp(status);
+	if (status == VERSION_PROBLEM)
+		fprintf(stderr,
+			"Perhaps iptables or your kernel needs to be upgraded.\n");
+	/* On error paths, make sure that we don't leak memory */
+	xtables_free_opts(1);
+	exit(status);
+}
+
+/*
+ *	All functions starting with "parse" should succeed, otherwise
+ *	the program fails.
+ *	Most routines return pointers to static data that may change
+ *	between calls to the same or other routines with a few exceptions:
+ *	"host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask"
+ *	return global static data.
+*/
+
+/* Christophe Burki wants `-p 6' to imply `-m tcp'.  */
+
+static void
+parse_chain(const char *chainname)
+{
+	const char *ptr;
+
+	if (strlen(chainname) >= XT_EXTENSION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name `%s' too long (must be under %u chars)",
+			   chainname, XT_EXTENSION_MAXNAMELEN);
+
+	if (*chainname == '-' || *chainname == '!')
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name not allowed to start "
+			   "with `%c'\n", *chainname);
+
+	if (xtables_find_target(chainname, XTF_TRY_LOAD))
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name may not clash "
+			   "with target name\n");
+
+	for (ptr = chainname; *ptr; ptr++)
+		if (isspace(*ptr))
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid chain name `%s'", chainname);
+}
+
+static void
+set_option(unsigned int *options, unsigned int option, uint8_t *invflg,
+	   int invert)
+{
+	if (*options & option)
+		xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed",
+			   opt2char(option));
+	*options |= option;
+
+	if (invert) {
+		unsigned int i;
+		for (i = 0; 1 << i != option; i++);
+
+		if (!inverse_for_options[i])
+			xtables_error(PARAMETER_PROBLEM,
+				   "cannot have ! before -%c",
+				   opt2char(option));
+		*invflg |= inverse_for_options[i];
+	}
+}
+
+static void
+print_header(unsigned int format, const char *chain, struct xtc_handle *handle)
+{
+	struct xt_counters counters;
+	const char *pol = iptc_get_policy(chain, &counters, handle);
+	printf("Chain %s", chain);
+	if (pol) {
+		printf(" (policy %s", pol);
+		if (!(format & FMT_NOCOUNTS)) {
+			fputc(' ', stdout);
+			xtables_print_num(counters.pcnt, (format|FMT_NOTABLE));
+			fputs("packets, ", stdout);
+			xtables_print_num(counters.bcnt, (format|FMT_NOTABLE));
+			fputs("bytes", stdout);
+		}
+		printf(")\n");
+	} else {
+		unsigned int refs;
+		if (!iptc_get_references(&refs, chain, handle))
+			printf(" (ERROR obtaining refs)\n");
+		else
+			printf(" (%u references)\n", refs);
+	}
+
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4s ", "%s "), "num");
+	if (!(format & FMT_NOCOUNTS)) {
+		if (format & FMT_KILOMEGAGIGA) {
+			printf(FMT("%5s ","%s "), "pkts");
+			printf(FMT("%5s ","%s "), "bytes");
+		} else {
+			printf(FMT("%8s ","%s "), "pkts");
+			printf(FMT("%10s ","%s "), "bytes");
+		}
+	}
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ","%s "), "target");
+	fputs(" prot ", stdout);
+	if (format & FMT_OPTIONS)
+		fputs("opt", stdout);
+	if (format & FMT_VIA) {
+		printf(FMT(" %-6s ","%s "), "in");
+		printf(FMT("%-6s ","%s "), "out");
+	}
+	printf(FMT(" %-19s ","%s "), "source");
+	printf(FMT(" %-19s "," %s "), "destination");
+	printf("\n");
+}
+
+
+static int
+print_match(const struct xt_entry_match *m,
+	    const struct ipt_ip *ip,
+	    int numeric)
+{
+	const char *name = m->u.user.name;
+	const int revision = m->u.user.revision;
+	struct xtables_match *match, *mt;
+
+	match = xtables_find_match(name, XTF_TRY_LOAD, NULL);
+	if (match) {
+		mt = xtables_find_match_revision(name, XTF_TRY_LOAD,
+						 match, revision);
+		if (mt && mt->print)
+			mt->print(ip, m, numeric);
+		else if (match->print)
+			printf("%s%s ", match->name, unsupported_rev);
+		else
+			printf("%s ", match->name);
+	} else {
+		if (name[0])
+			printf("UNKNOWN match `%s' ", name);
+	}
+	/* Don't stop iterating. */
+	return 0;
+}
+
+/* e is called `fw' here for historical reasons */
+static void
+print_firewall(const struct ipt_entry *fw,
+	       const char *targname,
+	       unsigned int num,
+	       unsigned int format,
+	       struct xtc_handle *const handle)
+{
+	struct xtables_target *target, *tg;
+	const struct xt_entry_target *t;
+	uint8_t flags;
+
+	if (!iptc_is_chain(targname, handle))
+		target = xtables_find_target(targname, XTF_TRY_LOAD);
+	else
+		target = xtables_find_target(XT_STANDARD_TARGET,
+		         XTF_LOAD_MUST_SUCCEED);
+
+	t = ipt_get_target((struct ipt_entry *)fw);
+	flags = fw->ip.flags;
+
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4u ", "%u "), num);
+
+	if (!(format & FMT_NOCOUNTS)) {
+		xtables_print_num(fw->counters.pcnt, format);
+		xtables_print_num(fw->counters.bcnt, format);
+	}
+
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ", "%s "), targname);
+
+	fputc(fw->ip.invflags & XT_INV_PROTO ? '!' : ' ', stdout);
+	{
+		const char *pname = proto_to_name(fw->ip.proto, format&FMT_NUMERIC);
+		if (pname)
+			printf(FMT("%-5s", "%s "), pname);
+		else
+			printf(FMT("%-5hu", "%hu "), fw->ip.proto);
+	}
+
+	if (format & FMT_OPTIONS) {
+		if (format & FMT_NOTABLE)
+			fputs("opt ", stdout);
+		fputc(fw->ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout);
+		fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout);
+		fputc(' ', stdout);
+	}
+
+	print_ifaces(fw->ip.iniface, fw->ip.outiface, fw->ip.invflags, format);
+
+	print_ipv4_addresses(fw, format);
+
+	if (format & FMT_NOTABLE)
+		fputs("  ", stdout);
+
+#ifdef IPT_F_GOTO
+	if(fw->ip.flags & IPT_F_GOTO)
+		printf("[goto] ");
+#endif
+
+	IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);
+
+	if (target) {
+		const int revision = t->u.user.revision;
+
+		tg = xtables_find_target_revision(targname, XTF_TRY_LOAD,
+						  target, revision);
+		if (tg && tg->print)
+			/* Print the target information. */
+			tg->print(&fw->ip, t, format & FMT_NUMERIC);
+		else if (target->print)
+			printf(" %s%s", target->name, unsupported_rev);
+	} else if (t->u.target_size != sizeof(*t))
+		printf("[%u bytes of unknown target data] ",
+		       (unsigned int)(t->u.target_size - sizeof(*t)));
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+}
+
+static void
+print_firewall_line(const struct ipt_entry *fw,
+		    struct xtc_handle *const h)
+{
+	struct xt_entry_target *t;
+
+	t = ipt_get_target((struct ipt_entry *)fw);
+	print_firewall(fw, t->u.user.name, 0, FMT_PRINT_RULE, h);
+}
+
+static int
+append_entry(const xt_chainlabel chain,
+	     struct ipt_entry *fw,
+	     unsigned int nsaddrs,
+	     const struct in_addr saddrs[],
+	     const struct in_addr smasks[],
+	     unsigned int ndaddrs,
+	     const struct in_addr daddrs[],
+	     const struct in_addr dmasks[],
+	     int verbose,
+	     struct xtc_handle *handle)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		fw->ip.src.s_addr = saddrs[i].s_addr;
+		fw->ip.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			fw->ip.dst.s_addr = daddrs[j].s_addr;
+			fw->ip.dmsk.s_addr = dmasks[j].s_addr;
+			if (verbose)
+				print_firewall_line(fw, handle);
+			ret &= iptc_append_entry(chain, fw, handle);
+		}
+	}
+
+	return ret;
+}
+
+static int
+replace_entry(const xt_chainlabel chain,
+	      struct ipt_entry *fw,
+	      unsigned int rulenum,
+	      const struct in_addr *saddr, const struct in_addr *smask,
+	      const struct in_addr *daddr, const struct in_addr *dmask,
+	      int verbose,
+	      struct xtc_handle *handle)
+{
+	fw->ip.src.s_addr = saddr->s_addr;
+	fw->ip.dst.s_addr = daddr->s_addr;
+	fw->ip.smsk.s_addr = smask->s_addr;
+	fw->ip.dmsk.s_addr = dmask->s_addr;
+
+	if (verbose)
+		print_firewall_line(fw, handle);
+	return iptc_replace_entry(chain, fw, rulenum, handle);
+}
+
+static int
+insert_entry(const xt_chainlabel chain,
+	     struct ipt_entry *fw,
+	     unsigned int rulenum,
+	     unsigned int nsaddrs,
+	     const struct in_addr saddrs[],
+	     const struct in_addr smasks[],
+	     unsigned int ndaddrs,
+	     const struct in_addr daddrs[],
+	     const struct in_addr dmasks[],
+	     int verbose,
+	     struct xtc_handle *handle)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		fw->ip.src.s_addr = saddrs[i].s_addr;
+		fw->ip.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			fw->ip.dst.s_addr = daddrs[j].s_addr;
+			fw->ip.dmsk.s_addr = dmasks[j].s_addr;
+			if (verbose)
+				print_firewall_line(fw, handle);
+			ret &= iptc_insert_entry(chain, fw, rulenum, handle);
+		}
+	}
+
+	return ret;
+}
+
+static unsigned char *
+make_delete_mask(const struct xtables_rule_match *matches,
+		 const struct xtables_target *target)
+{
+	/* Establish mask for comparison */
+	unsigned int size;
+	const struct xtables_rule_match *matchp;
+	unsigned char *mask, *mptr;
+
+	size = sizeof(struct ipt_entry);
+	for (matchp = matches; matchp; matchp = matchp->next)
+		size += XT_ALIGN(sizeof(struct xt_entry_match)) + matchp->match->size;
+
+	mask = xtables_calloc(1, size
+			 + XT_ALIGN(sizeof(struct xt_entry_target))
+			 + target->size);
+
+	memset(mask, 0xFF, sizeof(struct ipt_entry));
+	mptr = mask + sizeof(struct ipt_entry);
+
+	for (matchp = matches; matchp; matchp = matchp->next) {
+		memset(mptr, 0xFF,
+		       XT_ALIGN(sizeof(struct xt_entry_match))
+		       + matchp->match->userspacesize);
+		mptr += XT_ALIGN(sizeof(struct xt_entry_match)) + matchp->match->size;
+	}
+
+	memset(mptr, 0xFF,
+	       XT_ALIGN(sizeof(struct xt_entry_target))
+	       + target->userspacesize);
+
+	return mask;
+}
+
+static int
+delete_entry(const xt_chainlabel chain,
+	     struct ipt_entry *fw,
+	     unsigned int nsaddrs,
+	     const struct in_addr saddrs[],
+	     const struct in_addr smasks[],
+	     unsigned int ndaddrs,
+	     const struct in_addr daddrs[],
+	     const struct in_addr dmasks[],
+	     int verbose,
+	     struct xtc_handle *handle,
+	     struct xtables_rule_match *matches,
+	     const struct xtables_target *target)
+{
+	unsigned int i, j;
+	int ret = 1;
+	unsigned char *mask;
+
+	mask = make_delete_mask(matches, target);
+	for (i = 0; i < nsaddrs; i++) {
+		fw->ip.src.s_addr = saddrs[i].s_addr;
+		fw->ip.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			fw->ip.dst.s_addr = daddrs[j].s_addr;
+			fw->ip.dmsk.s_addr = dmasks[j].s_addr;
+			if (verbose)
+				print_firewall_line(fw, handle);
+			ret &= iptc_delete_entry(chain, fw, mask, handle);
+		}
+	}
+	free(mask);
+
+	return ret;
+}
+
+static int
+check_entry(const xt_chainlabel chain, struct ipt_entry *fw,
+	    unsigned int nsaddrs, const struct in_addr *saddrs,
+	    const struct in_addr *smasks, unsigned int ndaddrs,
+	    const struct in_addr *daddrs, const struct in_addr *dmasks,
+	    bool verbose, struct xtc_handle *handle,
+	    struct xtables_rule_match *matches,
+	    const struct xtables_target *target)
+{
+	unsigned int i, j;
+	int ret = 1;
+	unsigned char *mask;
+
+	mask = make_delete_mask(matches, target);
+	for (i = 0; i < nsaddrs; i++) {
+		fw->ip.src.s_addr = saddrs[i].s_addr;
+		fw->ip.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			fw->ip.dst.s_addr = daddrs[j].s_addr;
+			fw->ip.dmsk.s_addr = dmasks[j].s_addr;
+			if (verbose)
+				print_firewall_line(fw, handle);
+			ret &= iptc_check_entry(chain, fw, mask, handle);
+		}
+	}
+
+	free(mask);
+	return ret;
+}
+
+int
+for_each_chain4(int (*fn)(const xt_chainlabel, int, struct xtc_handle *),
+	       int verbose, int builtinstoo, struct xtc_handle *handle)
+{
+        int ret = 1;
+	const char *chain;
+	char *chains;
+	unsigned int i, chaincount = 0;
+
+	chain = iptc_first_chain(handle);
+	while (chain) {
+		chaincount++;
+		chain = iptc_next_chain(handle);
+        }
+
+	chains = xtables_malloc(sizeof(xt_chainlabel) * chaincount);
+	i = 0;
+	chain = iptc_first_chain(handle);
+	while (chain) {
+		strcpy(chains + i*sizeof(xt_chainlabel), chain);
+		i++;
+		chain = iptc_next_chain(handle);
+        }
+
+	for (i = 0; i < chaincount; i++) {
+		if (!builtinstoo
+		    && iptc_builtin(chains + i*sizeof(xt_chainlabel),
+				    handle) == 1)
+			continue;
+	        ret &= fn(chains + i*sizeof(xt_chainlabel), verbose, handle);
+	}
+
+	free(chains);
+        return ret;
+}
+
+int
+flush_entries4(const xt_chainlabel chain, int verbose,
+	      struct xtc_handle *handle)
+{
+	if (!chain)
+		return for_each_chain4(flush_entries4, verbose, 1, handle);
+
+	if (verbose)
+		fprintf(stdout, "Flushing chain `%s'\n", chain);
+	return iptc_flush_entries(chain, handle);
+}
+
+static int
+zero_entries(const xt_chainlabel chain, int verbose,
+	     struct xtc_handle *handle)
+{
+	if (!chain)
+		return for_each_chain4(zero_entries, verbose, 1, handle);
+
+	if (verbose)
+		fprintf(stdout, "Zeroing chain `%s'\n", chain);
+	return iptc_zero_entries(chain, handle);
+}
+
+int
+delete_chain4(const xt_chainlabel chain, int verbose,
+	     struct xtc_handle *handle)
+{
+	if (!chain)
+		return for_each_chain4(delete_chain4, verbose, 0, handle);
+
+	if (verbose)
+		fprintf(stdout, "Deleting chain `%s'\n", chain);
+	return iptc_delete_chain(chain, handle);
+}
+
+static int
+list_entries(const xt_chainlabel chain, int rulenum, int verbose, int numeric,
+	     int expanded, int linenumbers, struct xtc_handle *handle)
+{
+	int found = 0;
+	unsigned int format;
+	const char *this;
+
+	format = FMT_OPTIONS;
+	if (!verbose)
+		format |= FMT_NOCOUNTS;
+	else
+		format |= FMT_VIA;
+
+	if (numeric)
+		format |= FMT_NUMERIC;
+
+	if (!expanded)
+		format |= FMT_KILOMEGAGIGA;
+
+	if (linenumbers)
+		format |= FMT_LINENUMBERS;
+
+	for (this = iptc_first_chain(handle);
+	     this;
+	     this = iptc_next_chain(handle)) {
+		const struct ipt_entry *i;
+		unsigned int num;
+
+		if (chain && strcmp(chain, this) != 0)
+			continue;
+
+		if (found) printf("\n");
+
+		if (!rulenum)
+			print_header(format, this, handle);
+		i = iptc_first_rule(this, handle);
+
+		num = 0;
+		while (i) {
+			num++;
+			if (!rulenum || num == rulenum)
+				print_firewall(i,
+					       iptc_get_target(i, handle),
+					       num,
+					       format,
+					       handle);
+			i = iptc_next_rule(i, handle);
+		}
+		found = 1;
+	}
+
+	errno = ENOENT;
+	return found;
+}
+
+static void print_proto(uint16_t proto, int invert)
+{
+	if (proto) {
+		unsigned int i;
+		const char *invertstr = invert ? " !" : "";
+
+		const struct protoent *pent = getprotobynumber(proto);
+		if (pent) {
+			printf("%s -p %s", invertstr, pent->p_name);
+			return;
+		}
+
+		for (i = 0; xtables_chain_protos[i].name != NULL; ++i)
+			if (xtables_chain_protos[i].num == proto) {
+				printf("%s -p %s",
+				       invertstr, xtables_chain_protos[i].name);
+				return;
+			}
+
+		printf("%s -p %u", invertstr, proto);
+	}
+}
+
+#define IP_PARTS_NATIVE(n)			\
+(unsigned int)((n)>>24)&0xFF,			\
+(unsigned int)((n)>>16)&0xFF,			\
+(unsigned int)((n)>>8)&0xFF,			\
+(unsigned int)((n)&0xFF)
+
+#define IP_PARTS(n) IP_PARTS_NATIVE(ntohl(n))
+
+/* This assumes that mask is contiguous, and byte-bounded. */
+static void
+print_iface(char letter, const char *iface, const unsigned char *mask,
+	    int invert)
+{
+	unsigned int i;
+
+	if (mask[0] == 0)
+		return;
+
+	printf("%s -%c ", invert ? " !" : "", letter);
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (mask[i] != 0) {
+			if (iface[i] != '\0')
+				printf("%c", iface[i]);
+		} else {
+			/* we can access iface[i-1] here, because
+			 * a few lines above we make sure that mask[0] != 0 */
+			if (iface[i-1] != '\0')
+				printf("+");
+			break;
+		}
+	}
+}
+
+static int print_match_save(const struct xt_entry_match *e,
+			const struct ipt_ip *ip)
+{
+	const char *name = e->u.user.name;
+	const int revision = e->u.user.revision;
+	struct xtables_match *match, *mt, *mt2;
+
+	match = xtables_find_match(name, XTF_TRY_LOAD, NULL);
+	if (match) {
+		mt = mt2 = xtables_find_match_revision(name, XTF_TRY_LOAD,
+						       match, revision);
+		if (!mt2)
+			mt2 = match;
+		printf(" -m %s", mt2->alias ? mt2->alias(e) : name);
+
+		/* some matches don't provide a save function */
+		if (mt && mt->save)
+			mt->save(ip, e);
+		else if (match->save)
+			printf(unsupported_rev);
+	} else {
+		if (e->u.match_size) {
+			fprintf(stderr,
+				"Can't find library for match `%s'\n",
+				name);
+			exit(1);
+		}
+	}
+	return 0;
+}
+
+/* Print a given ip including mask if necessary. */
+static void print_ip(const char *prefix, uint32_t ip,
+		     uint32_t mask, int invert)
+{
+	uint32_t bits, hmask = ntohl(mask);
+	int i;
+
+	if (!mask && !ip && !invert)
+		return;
+
+	printf("%s %s %u.%u.%u.%u",
+		invert ? " !" : "",
+		prefix,
+		IP_PARTS(ip));
+
+	if (mask == 0xFFFFFFFFU) {
+		printf("/32");
+		return;
+	}
+
+	i    = 32;
+	bits = 0xFFFFFFFEU;
+	while (--i >= 0 && hmask != bits)
+		bits <<= 1;
+	if (i >= 0)
+		printf("/%u", i);
+	else
+		printf("/%u.%u.%u.%u", IP_PARTS(mask));
+}
+
+/* We want this to be readable, so only print out necessary fields.
+ * Because that's the kind of world I want to live in.
+ */
+void print_rule4(const struct ipt_entry *e,
+		struct xtc_handle *h, const char *chain, int counters)
+{
+	const struct xt_entry_target *t;
+	const char *target_name;
+
+	/* print counters for iptables-save */
+	if (counters > 0)
+		printf("[%llu:%llu] ", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);
+
+	/* print chain name */
+	printf("-A %s", chain);
+
+	/* Print IP part. */
+	print_ip("-s", e->ip.src.s_addr,e->ip.smsk.s_addr,
+			e->ip.invflags & IPT_INV_SRCIP);
+
+	print_ip("-d", e->ip.dst.s_addr, e->ip.dmsk.s_addr,
+			e->ip.invflags & IPT_INV_DSTIP);
+
+	print_iface('i', e->ip.iniface, e->ip.iniface_mask,
+		    e->ip.invflags & IPT_INV_VIA_IN);
+
+	print_iface('o', e->ip.outiface, e->ip.outiface_mask,
+		    e->ip.invflags & IPT_INV_VIA_OUT);
+
+	print_proto(e->ip.proto, e->ip.invflags & XT_INV_PROTO);
+
+	if (e->ip.flags & IPT_F_FRAG)
+		printf("%s -f",
+		       e->ip.invflags & IPT_INV_FRAG ? " !" : "");
+
+	/* Print matchinfo part */
+	if (e->target_offset)
+		IPT_MATCH_ITERATE(e, print_match_save, &e->ip);
+
+	/* print counters for iptables -R */
+	if (counters < 0)
+		printf(" -c %llu %llu", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);
+
+	/* Print target name and targinfo part */
+	target_name = iptc_get_target(e, h);
+	t = ipt_get_target((struct ipt_entry *)e);
+	if (t->u.user.name[0]) {
+		const char *name = t->u.user.name;
+		const int revision = t->u.user.revision;
+		struct xtables_target *target, *tg, *tg2;
+
+		target = xtables_find_target(name, XTF_TRY_LOAD);
+		if (!target) {
+			fprintf(stderr, "Can't find library for target `%s'\n",
+				name);
+			exit(1);
+		}
+
+		tg = tg2 = xtables_find_target_revision(name, XTF_TRY_LOAD,
+							target, revision);
+		if (!tg2)
+			tg2 = target;
+		printf(" -j %s", tg2->alias ? tg2->alias(t) : target_name);
+
+		if (tg && tg->save)
+			tg->save(&e->ip, t);
+		else if (target->save)
+			printf(unsupported_rev);
+		else {
+			/* If the target size is greater than xt_entry_target
+			 * there is something to be saved, we just don't know
+			 * how to print it */
+			if (t->u.target_size !=
+			    sizeof(struct xt_entry_target)) {
+				fprintf(stderr, "Target `%s' is missing "
+						"save function\n",
+					name);
+				exit(1);
+			}
+		}
+	} else if (target_name && (*target_name != '\0'))
+#ifdef IPT_F_GOTO
+		printf(" -%c %s", e->ip.flags & IPT_F_GOTO ? 'g' : 'j', target_name);
+#else
+		printf(" -j %s", target_name);
+#endif
+
+	printf("\n");
+}
+
+static int
+list_rules(const xt_chainlabel chain, int rulenum, int counters,
+	     struct xtc_handle *handle)
+{
+	const char *this = NULL;
+	int found = 0;
+
+	if (counters)
+	    counters = -1;		/* iptables -c format */
+
+	/* Dump out chain names first,
+	 * thereby preventing dependency conflicts */
+	if (!rulenum) for (this = iptc_first_chain(handle);
+	     this;
+	     this = iptc_next_chain(handle)) {
+		if (chain && strcmp(this, chain) != 0)
+			continue;
+
+		if (iptc_builtin(this, handle)) {
+			struct xt_counters count;
+			printf("-P %s %s", this, iptc_get_policy(this, &count, handle));
+			if (counters)
+			    printf(" -c %llu %llu", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt);
+			printf("\n");
+		} else {
+			printf("-N %s\n", this);
+		}
+	}
+
+	for (this = iptc_first_chain(handle);
+	     this;
+	     this = iptc_next_chain(handle)) {
+		const struct ipt_entry *e;
+		int num = 0;
+
+		if (chain && strcmp(this, chain) != 0)
+			continue;
+
+		/* Dump out rules */
+		e = iptc_first_rule(this, handle);
+		while(e) {
+			num++;
+			if (!rulenum || num == rulenum)
+			    print_rule4(e, handle, this, counters);
+			e = iptc_next_rule(e, handle);
+		}
+		found = 1;
+	}
+
+	errno = ENOENT;
+	return found;
+}
+
+static struct ipt_entry *
+generate_entry(const struct ipt_entry *fw,
+	       struct xtables_rule_match *matches,
+	       struct xt_entry_target *target)
+{
+	unsigned int size;
+	struct xtables_rule_match *matchp;
+	struct ipt_entry *e;
+
+	size = sizeof(struct ipt_entry);
+	for (matchp = matches; matchp; matchp = matchp->next)
+		size += matchp->match->m->u.match_size;
+
+	e = xtables_malloc(size + target->u.target_size);
+	*e = *fw;
+	e->target_offset = size;
+	e->next_offset = size + target->u.target_size;
+
+	size = 0;
+	for (matchp = matches; matchp; matchp = matchp->next) {
+		memcpy(e->elems + size, matchp->match->m, matchp->match->m->u.match_size);
+		size += matchp->match->m->u.match_size;
+	}
+	memcpy(e->elems + size, target, target->u.target_size);
+
+	return e;
+}
+
+int do_command4(int argc, char *argv[], char **table,
+		struct xtc_handle **handle, bool restore)
+{
+	struct iptables_command_state cs = {
+		.jumpto	= "",
+		.argv	= argv,
+	};
+	struct ipt_entry *e = NULL;
+	unsigned int nsaddrs = 0, ndaddrs = 0;
+	struct in_addr *saddrs = NULL, *smasks = NULL;
+	struct in_addr *daddrs = NULL, *dmasks = NULL;
+	struct timeval wait_interval = {
+		.tv_sec = 1,
+	};
+	bool wait_interval_set = false;
+	int verbose = 0;
+	int wait = 0;
+	const char *chain = NULL;
+	const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;
+	const char *policy = NULL, *newname = NULL;
+	unsigned int rulenum = 0, command = 0;
+	const char *pcnt = NULL, *bcnt = NULL;
+	int ret = 1;
+	struct xtables_match *m;
+	struct xtables_rule_match *matchp;
+	struct xtables_target *t;
+	unsigned long long cnt;
+	bool table_set = false;
+
+	/* re-set optind to 0 in case do_command4 gets called
+	 * a second time */
+	optind = 0;
+
+	/* clear mflags in case do_command4 gets called a second time
+	 * (we clear the global list of all matches for security)*/
+	for (m = xtables_matches; m; m = m->next)
+		m->mflags = 0;
+
+	for (t = xtables_targets; t; t = t->next) {
+		t->tflags = 0;
+		t->used = 0;
+	}
+
+	/* Suppress error messages: we may add new options if we
+           demand-load a protocol. */
+	opterr = 0;
+	opts = xt_params->orig_opts;
+	while ((cs.c = getopt_long(argc, argv,
+	   "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvw::W::nt:m:xc:g:46",
+					   opts, NULL)) != -1) {
+		switch (cs.c) {
+			/*
+			 * Command selection
+			 */
+		case 'A':
+			add_command(&command, CMD_APPEND, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			break;
+
+		case 'C':
+			add_command(&command, CMD_CHECK, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			break;
+
+		case 'D':
+			add_command(&command, CMD_DELETE, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv)) {
+				rulenum = parse_rulenumber(argv[optind++]);
+				command = CMD_DELETE_NUM;
+			}
+			break;
+
+		case 'R':
+			add_command(&command, CMD_REPLACE, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires a rule number",
+					   cmd2char(CMD_REPLACE));
+			break;
+
+		case 'I':
+			add_command(&command, CMD_INSERT, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			else rulenum = 1;
+			break;
+
+		case 'L':
+			add_command(&command, CMD_LIST,
+				    CMD_ZERO | CMD_ZERO_NUM, cs.invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			break;
+
+		case 'S':
+			add_command(&command, CMD_LIST_RULES,
+				    CMD_ZERO|CMD_ZERO_NUM, cs.invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			break;
+
+		case 'F':
+			add_command(&command, CMD_FLUSH, CMD_NONE,
+				    cs.invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'Z':
+			add_command(&command, CMD_ZERO, CMD_LIST|CMD_LIST_RULES,
+				    cs.invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			if (xs_has_arg(argc, argv)) {
+				rulenum = parse_rulenumber(argv[optind++]);
+				command = CMD_ZERO_NUM;
+			}
+			break;
+
+		case 'N':
+			parse_chain(optarg);
+			add_command(&command, CMD_NEW_CHAIN, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			break;
+
+		case 'X':
+			add_command(&command, CMD_DELETE_CHAIN, CMD_NONE,
+				    cs.invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'E':
+			add_command(&command, CMD_RENAME_CHAIN, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				newname = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires old-chain-name and "
+					   "new-chain-name",
+					    cmd2char(CMD_RENAME_CHAIN));
+			break;
+
+		case 'P':
+			add_command(&command, CMD_SET_POLICY, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				policy = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires a chain and a policy",
+					   cmd2char(CMD_SET_POLICY));
+			break;
+
+		case 'h':
+			if (!optarg)
+				optarg = argv[optind];
+
+			/* iptables -p icmp -h */
+			if (!cs.matches && cs.protocol)
+				xtables_find_match(cs.protocol,
+					XTF_TRY_LOAD, &cs.matches);
+
+			exit_printhelp(cs.matches);
+
+			/*
+			 * Option selection
+			 */
+		case 'p':
+			set_option(&cs.options, OPT_PROTOCOL, &cs.fw.ip.invflags,
+				   cs.invert);
+
+			/* Canonicalize into lower case */
+			for (cs.protocol = optarg; *cs.protocol; cs.protocol++)
+				*cs.protocol = tolower(*cs.protocol);
+
+			cs.protocol = optarg;
+			cs.fw.ip.proto = xtables_parse_protocol(cs.protocol);
+
+			if (cs.fw.ip.proto == 0
+			    && (cs.fw.ip.invflags & XT_INV_PROTO))
+				xtables_error(PARAMETER_PROBLEM,
+					   "rule would never match protocol");
+			break;
+
+		case 's':
+			set_option(&cs.options, OPT_SOURCE, &cs.fw.ip.invflags,
+				   cs.invert);
+			shostnetworkmask = optarg;
+			break;
+
+		case 'd':
+			set_option(&cs.options, OPT_DESTINATION, &cs.fw.ip.invflags,
+				   cs.invert);
+			dhostnetworkmask = optarg;
+			break;
+
+#ifdef IPT_F_GOTO
+		case 'g':
+			set_option(&cs.options, OPT_JUMP, &cs.fw.ip.invflags,
+				   cs.invert);
+			cs.fw.ip.flags |= IPT_F_GOTO;
+			cs.jumpto = xt_parse_target(optarg);
+			break;
+#endif
+
+		case 'j':
+			set_option(&cs.options, OPT_JUMP, &cs.fw.ip.invflags,
+				   cs.invert);
+			command_jump(&cs, optarg);
+			break;
+
+
+		case 'i':
+			if (*optarg == '\0')
+				xtables_error(PARAMETER_PROBLEM,
+					"Empty interface is likely to be "
+					"undesired");
+			set_option(&cs.options, OPT_VIANAMEIN, &cs.fw.ip.invflags,
+				   cs.invert);
+			xtables_parse_interface(optarg,
+					cs.fw.ip.iniface,
+					cs.fw.ip.iniface_mask);
+			break;
+
+		case 'o':
+			if (*optarg == '\0')
+				xtables_error(PARAMETER_PROBLEM,
+					"Empty interface is likely to be "
+					"undesired");
+			set_option(&cs.options, OPT_VIANAMEOUT, &cs.fw.ip.invflags,
+				   cs.invert);
+			xtables_parse_interface(optarg,
+					cs.fw.ip.outiface,
+					cs.fw.ip.outiface_mask);
+			break;
+
+		case 'f':
+			set_option(&cs.options, OPT_FRAGMENT, &cs.fw.ip.invflags,
+				   cs.invert);
+			cs.fw.ip.flags |= IPT_F_FRAG;
+			break;
+
+		case 'v':
+			if (!verbose)
+				set_option(&cs.options, OPT_VERBOSE,
+					   &cs.fw.ip.invflags, cs.invert);
+			verbose++;
+			break;
+
+		case 'w':
+			if (restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-w' from "
+					      "iptables-restore");
+			}
+			wait = parse_wait_time(argc, argv);
+			break;
+
+		case 'W':
+			if (restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-W' from "
+					      "iptables-restore");
+			}
+			parse_wait_interval(argc, argv, &wait_interval);
+			wait_interval_set = true;
+			break;
+
+		case 'm':
+			command_match(&cs);
+			break;
+
+		case 'n':
+			set_option(&cs.options, OPT_NUMERIC, &cs.fw.ip.invflags,
+				   cs.invert);
+			break;
+
+		case 't':
+			if (cs.invert)
+				xtables_error(PARAMETER_PROBLEM,
+					   "unexpected ! flag before --table");
+			if (restore && table_set)
+				xtables_error(PARAMETER_PROBLEM,
+					      "The -t option (seen in line %u) cannot be used in %s.\n",
+					      line, xt_params->program_name);
+			*table = optarg;
+			table_set = true;
+			break;
+
+		case 'x':
+			set_option(&cs.options, OPT_EXPANDED, &cs.fw.ip.invflags,
+				   cs.invert);
+			break;
+
+		case 'V':
+			if (cs.invert)
+				printf("Not %s ;-)\n", prog_vers);
+			else
+				printf("%s v%s (legacy)\n",
+				       prog_name, prog_vers);
+			exit(0);
+
+		case '0':
+			set_option(&cs.options, OPT_LINENUMBERS, &cs.fw.ip.invflags,
+				   cs.invert);
+			break;
+
+		case 'M':
+			xtables_modprobe_program = optarg;
+			break;
+
+		case 'c':
+
+			set_option(&cs.options, OPT_COUNTERS, &cs.fw.ip.invflags,
+				   cs.invert);
+			pcnt = optarg;
+			bcnt = strchr(pcnt + 1, ',');
+			if (bcnt)
+			    bcnt++;
+			if (!bcnt && xs_has_arg(argc, argv))
+				bcnt = argv[optind++];
+			if (!bcnt)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c requires packet and byte counter",
+					opt2char(OPT_COUNTERS));
+
+			if (sscanf(pcnt, "%llu", &cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c packet counter not numeric",
+					opt2char(OPT_COUNTERS));
+			cs.fw.counters.pcnt = cnt;
+
+			if (sscanf(bcnt, "%llu", &cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c byte counter not numeric",
+					opt2char(OPT_COUNTERS));
+			cs.fw.counters.bcnt = cnt;
+			break;
+
+		case '4':
+			/* This is indeed the IPv4 iptables */
+			break;
+
+		case '6':
+			/* This is not the IPv6 ip6tables */
+			if (line != -1)
+				return 1; /* success: line ignored */
+			fprintf(stderr, "This is the IPv4 version of iptables.\n");
+			exit_tryhelp(2);
+
+		case 1: /* non option */
+			if (optarg[0] == '!' && optarg[1] == '\0') {
+				if (cs.invert)
+					xtables_error(PARAMETER_PROBLEM,
+						   "multiple consecutive ! not"
+						   " allowed");
+				cs.invert = true;
+				optarg[0] = '\0';
+				continue;
+			}
+			fprintf(stderr, "Bad argument `%s'\n", optarg);
+			exit_tryhelp(2);
+
+		default:
+			if (command_default(&cs, &iptables_globals) == 1)
+				/* cf. ip6tables.c */
+				continue;
+			break;
+		}
+		cs.invert = false;
+	}
+
+	if (!wait && wait_interval_set)
+		xtables_error(PARAMETER_PROBLEM,
+			      "--wait-interval only makes sense with --wait\n");
+
+	if (strcmp(*table, "nat") == 0 &&
+	    ((policy != NULL && strcmp(policy, "DROP") == 0) ||
+	    (cs.jumpto != NULL && strcmp(cs.jumpto, "DROP") == 0)))
+		xtables_error(PARAMETER_PROBLEM,
+			"\nThe \"nat\" table is not intended for filtering, "
+		        "the use of DROP is therefore inhibited.\n\n");
+
+	for (matchp = cs.matches; matchp; matchp = matchp->next)
+		xtables_option_mfcall(matchp->match);
+	if (cs.target != NULL)
+		xtables_option_tfcall(cs.target);
+
+	/* Fix me: must put inverse options checking here --MN */
+
+	if (optind < argc)
+		xtables_error(PARAMETER_PROBLEM,
+			   "unknown arguments found on commandline");
+	if (!command)
+		xtables_error(PARAMETER_PROBLEM, "no command specified");
+	if (cs.invert)
+		xtables_error(PARAMETER_PROBLEM,
+			   "nothing appropriate following !");
+
+	if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+		if (!(cs.options & OPT_DESTINATION))
+			dhostnetworkmask = "0.0.0.0/0";
+		if (!(cs.options & OPT_SOURCE))
+			shostnetworkmask = "0.0.0.0/0";
+	}
+
+	if (shostnetworkmask)
+		xtables_ipparse_multiple(shostnetworkmask, &saddrs,
+					 &smasks, &nsaddrs);
+
+	if (dhostnetworkmask)
+		xtables_ipparse_multiple(dhostnetworkmask, &daddrs,
+					 &dmasks, &ndaddrs);
+
+	if ((nsaddrs > 1 || ndaddrs > 1) &&
+	    (cs.fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))
+		xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple"
+			   " source or destination IP addresses");
+
+	if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1))
+		xtables_error(PARAMETER_PROBLEM, "Replacement rule does not "
+			   "specify a unique address");
+
+	generic_opt_check(command, cs.options);
+
+	/* Attempt to acquire the xtables lock */
+	if (!restore)
+		xtables_lock_or_exit(wait, &wait_interval);
+
+	/* only allocate handle if we weren't called with a handle */
+	if (!*handle)
+		*handle = iptc_init(*table);
+
+	/* try to insmod the module if iptc_init failed */
+	if (!*handle && xtables_load_ko(xtables_modprobe_program, false) != -1)
+		*handle = iptc_init(*table);
+
+	if (!*handle)
+		xtables_error(VERSION_PROBLEM,
+			   "can't initialize iptables table `%s': %s",
+			   *table, iptc_strerror(errno));
+
+	if (command == CMD_APPEND
+	    || command == CMD_DELETE
+	    || command == CMD_CHECK
+	    || command == CMD_INSERT
+	    || command == CMD_REPLACE) {
+		if (strcmp(chain, "PREROUTING") == 0
+		    || strcmp(chain, "INPUT") == 0) {
+			/* -o not valid with incoming packets. */
+			if (cs.options & OPT_VIANAMEOUT)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Can't use -%c with %s\n",
+					   opt2char(OPT_VIANAMEOUT),
+					   chain);
+		}
+
+		if (strcmp(chain, "POSTROUTING") == 0
+		    || strcmp(chain, "OUTPUT") == 0) {
+			/* -i not valid with outgoing packets */
+			if (cs.options & OPT_VIANAMEIN)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Can't use -%c with %s\n",
+					   opt2char(OPT_VIANAMEIN),
+					   chain);
+		}
+
+		if (cs.target && iptc_is_chain(cs.jumpto, *handle)) {
+			fprintf(stderr,
+				"Warning: using chain %s, not extension\n",
+				cs.jumpto);
+
+			if (cs.target->t)
+				free(cs.target->t);
+
+			cs.target = NULL;
+		}
+
+		/* If they didn't specify a target, or it's a chain
+		   name, use standard. */
+		if (!cs.target
+		    && (strlen(cs.jumpto) == 0
+			|| iptc_is_chain(cs.jumpto, *handle))) {
+			size_t size;
+
+			cs.target = xtables_find_target(XT_STANDARD_TARGET,
+					 XTF_LOAD_MUST_SUCCEED);
+
+			size = sizeof(struct xt_entry_target)
+				+ cs.target->size;
+			cs.target->t = xtables_calloc(1, size);
+			cs.target->t->u.target_size = size;
+			strcpy(cs.target->t->u.user.name, cs.jumpto);
+			if (!iptc_is_chain(cs.jumpto, *handle))
+				cs.target->t->u.user.revision = cs.target->revision;
+			xs_init_target(cs.target);
+		}
+
+		if (!cs.target) {
+			/* It is no chain, and we can't load a plugin.
+			 * We cannot know if the plugin is corrupt, non
+			 * existent OR if the user just misspelled a
+			 * chain.
+			 */
+#ifdef IPT_F_GOTO
+			if (cs.fw.ip.flags & IPT_F_GOTO)
+				xtables_error(PARAMETER_PROBLEM,
+					   "goto '%s' is not a chain\n",
+					   cs.jumpto);
+#endif
+			xtables_find_target(cs.jumpto, XTF_LOAD_MUST_SUCCEED);
+		} else {
+			e = generate_entry(&cs.fw, cs.matches, cs.target->t);
+			free(cs.target->t);
+		}
+	}
+
+	switch (command) {
+	case CMD_APPEND:
+		ret = append_entry(chain, e,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   cs.options&OPT_VERBOSE,
+				   *handle);
+		break;
+	case CMD_DELETE:
+		ret = delete_entry(chain, e,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   cs.options&OPT_VERBOSE,
+				   *handle, cs.matches, cs.target);
+		break;
+	case CMD_DELETE_NUM:
+		ret = iptc_delete_num_entry(chain, rulenum - 1, *handle);
+		break;
+	case CMD_CHECK:
+		ret = check_entry(chain, e,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   cs.options&OPT_VERBOSE,
+				   *handle, cs.matches, cs.target);
+		break;
+	case CMD_REPLACE:
+		ret = replace_entry(chain, e, rulenum - 1,
+				    saddrs, smasks, daddrs, dmasks,
+				    cs.options&OPT_VERBOSE, *handle);
+		break;
+	case CMD_INSERT:
+		ret = insert_entry(chain, e, rulenum - 1,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   cs.options&OPT_VERBOSE,
+				   *handle);
+		break;
+	case CMD_FLUSH:
+		ret = flush_entries4(chain, cs.options&OPT_VERBOSE, *handle);
+		break;
+	case CMD_ZERO:
+		ret = zero_entries(chain, cs.options&OPT_VERBOSE, *handle);
+		break;
+	case CMD_ZERO_NUM:
+		ret = iptc_zero_counter(chain, rulenum, *handle);
+		break;
+	case CMD_LIST:
+	case CMD_LIST|CMD_ZERO:
+	case CMD_LIST|CMD_ZERO_NUM:
+		ret = list_entries(chain,
+				   rulenum,
+				   cs.options&OPT_VERBOSE,
+				   cs.options&OPT_NUMERIC,
+				   cs.options&OPT_EXPANDED,
+				   cs.options&OPT_LINENUMBERS,
+				   *handle);
+		if (ret && (command & CMD_ZERO))
+			ret = zero_entries(chain,
+					   cs.options&OPT_VERBOSE, *handle);
+		if (ret && (command & CMD_ZERO_NUM))
+			ret = iptc_zero_counter(chain, rulenum, *handle);
+		break;
+	case CMD_LIST_RULES:
+	case CMD_LIST_RULES|CMD_ZERO:
+	case CMD_LIST_RULES|CMD_ZERO_NUM:
+		ret = list_rules(chain,
+				   rulenum,
+				   cs.options&OPT_VERBOSE,
+				   *handle);
+		if (ret && (command & CMD_ZERO))
+			ret = zero_entries(chain,
+					   cs.options&OPT_VERBOSE, *handle);
+		if (ret && (command & CMD_ZERO_NUM))
+			ret = iptc_zero_counter(chain, rulenum, *handle);
+		break;
+	case CMD_NEW_CHAIN:
+		ret = iptc_create_chain(chain, *handle);
+		break;
+	case CMD_DELETE_CHAIN:
+		ret = delete_chain4(chain, cs.options&OPT_VERBOSE, *handle);
+		break;
+	case CMD_RENAME_CHAIN:
+		ret = iptc_rename_chain(chain, newname,	*handle);
+		break;
+	case CMD_SET_POLICY:
+		ret = iptc_set_policy(chain, policy, cs.options&OPT_COUNTERS ? &cs.fw.counters : NULL, *handle);
+		break;
+	default:
+		/* We should never reach this... */
+		exit_tryhelp(2);
+	}
+
+	if (verbose > 1)
+		dump_entries(*handle);
+
+	xtables_rule_matches_free(&cs.matches);
+
+	if (e != NULL) {
+		free(e);
+		e = NULL;
+	}
+
+	free(saddrs);
+	free(smasks);
+	free(daddrs);
+	free(dmasks);
+	xtables_free_opts(1);
+
+	return ret;
+}
diff --git a/iptables/iptables.xslt b/iptables/iptables.xslt
new file mode 100644
index 0000000..afe6d0d
--- /dev/null
+++ b/iptables/iptables.xslt
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!-- Converts from simple xml iptables format to iptables-save format  
+     Copyright 2006 UfoMechanic 
+     Author: azez@ufomechanic.net 
+     This code is distributed and licensed under the terms of GNU GPL v2
+     
+     This sample usage outputs roughly want goes in
+       iptables-save | iptables-xml -c | xsltproc iptables.xslt -
+     -->
+<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <xsl:output method = "text" />
+  <xsl:strip-space elements="*" />
+
+  <!-- output conditions of a rule but not an action -->
+  <xsl:template match="iptables-rules/table/chain/rule/conditions/*">
+    <!-- <match> is the pseudo module when a match module doesn't need to be loaded
+         and when -m does not need to be inserted -->
+    <xsl:if test="name() != 'match'">
+      <xsl:text> -m </xsl:text><xsl:value-of select="name()"/>
+    </xsl:if>
+    <xsl:apply-templates select="node()"/>
+  </xsl:template>
+
+  <!-- delete the actions or conditions containers, and process child nodes -->
+  <xsl:template match="iptables-rules/table/chain/rule/actions|table/chain/rule/conditions">
+    <xsl:apply-templates select="*"/>
+  </xsl:template>
+
+  <xsl:template match="iptables-rules/table/chain/rule/actions/goto">
+    <xsl:text> -g </xsl:text>
+    <xsl:apply-templates select="*"/>
+    <xsl:text>&#xA;</xsl:text>
+  </xsl:template>
+  <xsl:template match="iptables-rules/table/chain/rule/actions/call">
+    <xsl:text> -j </xsl:text>
+    <xsl:apply-templates select="*"/>
+    <xsl:text>&#xA;</xsl:text>
+  </xsl:template>
+  <!-- all other actions are module actions -->
+  <xsl:template match="iptables-rules/table/chain/rule/actions/*">
+    <xsl:text> -j </xsl:text><xsl:value-of select="name()"/>
+    <xsl:apply-templates select="*"/>
+    <xsl:text>&#xA;</xsl:text>
+  </xsl:template>
+  
+  <!-- all child action nodes -->
+  <xsl:template match="iptables-rules/table/chain/rule/actions//*|iptables-rules/table/chain/rule/conditions//*" priority="0">
+    <xsl:if test="@invert=1"><xsl:text> !</xsl:text></xsl:if>
+    <xsl:text> -</xsl:text>
+    <!-- if length of name is 1 character, then only do 1 - not 2 -->
+    <xsl:if test="string-length(name())&gt;1">
+      <xsl:text>-</xsl:text>
+    </xsl:if>
+    <xsl:value-of select="name()"/>
+    <xsl:text> </xsl:text>
+    <xsl:apply-templates select="node()"/>
+  </xsl:template>
+
+  <xsl:template match="iptables-rules/table/chain/rule/actions/call/*|iptables-rules/table/chain/rule/actions/goto/*">
+    <xsl:value-of select="name()"/>
+    <!-- I bet there are no child nodes, should we risk it? -->
+    <xsl:apply-templates select="node()"/>
+  </xsl:template>
+
+  <!-- output the head of the rule, and any conditions -->
+  <xsl:template name="rule-head">
+    <xsl:if test="string-length(@packet-count)+string-length(@byte-count)">
+      <xsl:call-template name="counters"><xsl:with-param name="node" select="."/></xsl:call-template>
+      <xsl:text> </xsl:text>
+    </xsl:if>
+    <xsl:text>-A </xsl:text><!-- a rule must be under a chain -->
+    <xsl:value-of select="../@name" />
+    <xsl:apply-templates select="conditions"/>
+  </xsl:template>
+
+  <!-- Output a single rule, perhaps as multiple rules if we have more than one action -->
+  <xsl:template match="iptables-rules/table/chain/rule">
+    <xsl:choose>
+      <xsl:when test="count(actions/*)&gt;0">
+        <xsl:for-each select="actions/*">
+          <!-- and a for-each to re-select the rule as the current node, to write the rule-head -->
+          <xsl:for-each select="../..">
+            <xsl:call-template name="rule-head"/>
+          </xsl:for-each>
+          <!-- now write the this action -->
+          <xsl:apply-templates select="."/>
+        </xsl:for-each>
+      </xsl:when>
+      <xsl:otherwise>
+        <!-- no need to loop if there are no actions, just output conditions -->
+        <xsl:call-template name="rule-head"/>
+        <xsl:text>&#xA;</xsl:text>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+  <xsl:template match="iptables-rules/table">
+    <xsl:text># Generated by iptables.xslt&#xA;</xsl:text>
+    <xsl:text>*</xsl:text><xsl:value-of select="@name"/><xsl:text>&#xA;</xsl:text>
+    <!-- Loop through each chain and output the chain header -->
+    <xsl:for-each select="chain">
+      <xsl:text>:</xsl:text>
+      <xsl:value-of select="@name"/>
+      <xsl:text> </xsl:text>
+      <xsl:choose>
+        <xsl:when test="not(string-length(@policy))"><xsl:text>-</xsl:text></xsl:when>
+        <xsl:otherwise><xsl:value-of select="@policy"/></xsl:otherwise>
+      </xsl:choose>
+      <xsl:text> </xsl:text>
+      <xsl:call-template name="counters"><xsl:with-param name="node" select="."/></xsl:call-template>
+      <xsl:text>&#xA;</xsl:text>
+    </xsl:for-each>
+    <!-- Loop through each chain and output the rules -->
+    <xsl:apply-templates select="node()"/>
+    <xsl:text>COMMIT&#xA;# Completed&#xA;</xsl:text>
+  </xsl:template>
+  
+  <xsl:template name="counters">
+    <xsl:param name="node"/>
+    <xsl:text>[</xsl:text>
+    <xsl:if test="string-length($node/@packet-count)"><xsl:value-of select="$node/@packet-count"/></xsl:if>
+    <xsl:if test="string-length($node/@packet-count)=0">0</xsl:if>
+    <xsl:text>:</xsl:text>
+    <xsl:if test="string-length($node/@byte-count)"><xsl:value-of select="$node/@byte-count"/></xsl:if>
+    <xsl:if test="string-length($node/@byte-count)=0">0</xsl:if>
+    <xsl:text>]</xsl:text>
+  </xsl:template>  
+  
+  <!-- the bit that automatically recurses for us, NOTE: we use * not node(), we don't want to copy every white space text -->
+  <xsl:template match="@*|node()">
+    <xsl:copy>
+      <!-- with libxslt xsltproc we can't do @*|node() or the nodes may get processed before the attributes -->
+      <xsl:apply-templates select="@*"/>
+      <xsl:apply-templates select="node()"/>
+    </xsl:copy>
+  </xsl:template>
+
+</xsl:transform>
diff --git a/iptables/nft-arp.c b/iptables/nft-arp.c
new file mode 100644
index 0000000..c82ffdc
--- /dev/null
+++ b/iptables/nft-arp.c
@@ -0,0 +1,647 @@
+/*
+ * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Giuseppe Longo <giuseppelng@gmail.com>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include <net/if_arp.h>
+
+#include <xtables.h>
+#include <libiptc/libxtc.h>
+#include <net/if_arp.h>
+#include <netinet/if_ether.h>
+
+#include <linux/netfilter_arp/arp_tables.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include "nft-shared.h"
+#include "nft-arp.h"
+#include "nft.h"
+
+/* a few names */
+char *arp_opcodes[] =
+{
+	"Request",
+	"Reply",
+	"Request_Reverse",
+	"Reply_Reverse",
+	"DRARP_Request",
+	"DRARP_Reply",
+	"DRARP_Error",
+	"InARP_Request",
+	"ARP_NAK",
+};
+
+static char *
+addr_to_dotted(const struct in_addr *addrp)
+{
+	static char buf[20];
+	const unsigned char *bytep;
+
+	bytep = (const unsigned char *) &(addrp->s_addr);
+	sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
+	return buf;
+}
+
+static char *
+addr_to_host(const struct in_addr *addr)
+{
+	struct hostent *host;
+
+	if ((host = gethostbyaddr((char *) addr,
+					sizeof(struct in_addr), AF_INET)) != NULL)
+		return (char *) host->h_name;
+
+	return (char *) NULL;
+}
+
+static char *
+addr_to_network(const struct in_addr *addr)
+{
+	struct netent *net;
+
+	if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL)
+		return (char *) net->n_name;
+
+	return (char *) NULL;
+}
+
+static char *
+addr_to_anyname(const struct in_addr *addr)
+{
+	char *name;
+
+	if ((name = addr_to_host(addr)) != NULL ||
+		(name = addr_to_network(addr)) != NULL)
+		return name;
+
+	return addr_to_dotted(addr);
+}
+
+static char *
+mask_to_dotted(const struct in_addr *mask)
+{
+	int i;
+	static char buf[22];
+	u_int32_t maskaddr, bits;
+
+	maskaddr = ntohl(mask->s_addr);
+
+	if (maskaddr == 0xFFFFFFFFL)
+		/* we don't want to see "/32" */
+		return "";
+
+	i = 32;
+	bits = 0xFFFFFFFEL;
+	while (--i >= 0 && maskaddr != bits)
+		bits <<= 1;
+	if (i >= 0)
+		sprintf(buf, "/%d", i);
+	else
+		/* mask was not a decent combination of 1's and 0's */
+		snprintf(buf, sizeof(buf), "/%s", addr_to_dotted(mask));
+
+	return buf;
+}
+
+static bool need_devaddr(struct arpt_devaddr_info *info)
+{
+	int i;
+
+	for (i = 0; i < ETH_ALEN; i++) {
+		if (info->addr[i] || info->mask[i])
+			return true;
+	}
+
+	return false;
+}
+
+static int nft_arp_add(struct nft_handle *h, struct nftnl_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct arpt_entry *fw = &cs->arp;
+	uint32_t op;
+	int ret = 0;
+
+	if (fw->arp.iniface[0] != '\0') {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_VIA_IN);
+		add_iniface(r, fw->arp.iniface, op);
+	}
+
+	if (fw->arp.outiface[0] != '\0') {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_VIA_OUT);
+		add_outiface(r, fw->arp.outiface, op);
+	}
+
+	if (fw->arp.arhrd != 0 ||
+	    fw->arp.invflags & IPT_INV_ARPHRD) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_ARPHRD);
+		add_payload(r, offsetof(struct arphdr, ar_hrd), 2,
+			    NFT_PAYLOAD_NETWORK_HEADER);
+		add_cmp_u16(r, fw->arp.arhrd, op);
+	}
+
+	if (fw->arp.arpro != 0 ||
+	    fw->arp.invflags & IPT_INV_PROTO) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_PROTO);
+	        add_payload(r, offsetof(struct arphdr, ar_pro), 2,
+			    NFT_PAYLOAD_NETWORK_HEADER);
+		add_cmp_u16(r, fw->arp.arpro, op);
+	}
+
+	if (fw->arp.arhln != 0 ||
+	    fw->arp.invflags & IPT_INV_ARPHLN) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_ARPHLN);
+		add_proto(r, offsetof(struct arphdr, ar_hln), 1,
+			  fw->arp.arhln, op);
+	}
+
+	add_proto(r, offsetof(struct arphdr, ar_pln), 1, 4, NFT_CMP_EQ);
+
+	if (fw->arp.arpop != 0 ||
+	    fw->arp.invflags & IPT_INV_ARPOP) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_ARPOP);
+		add_payload(r, offsetof(struct arphdr, ar_op), 2,
+			    NFT_PAYLOAD_NETWORK_HEADER);
+		add_cmp_u16(r, fw->arp.arpop, op);
+	}
+
+	if (need_devaddr(&fw->arp.src_devaddr)) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_SRCDEVADDR);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 sizeof(struct arphdr),
+			 &fw->arp.src_devaddr.addr,
+			 &fw->arp.src_devaddr.mask,
+			 fw->arp.arhln, op);
+
+	}
+
+	if (fw->arp.src.s_addr != 0 ||
+	    fw->arp.smsk.s_addr != 0 ||
+	    fw->arp.invflags & IPT_INV_SRCIP) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_SRCIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 sizeof(struct arphdr) + fw->arp.arhln,
+			 &fw->arp.src.s_addr, &fw->arp.smsk.s_addr,
+			 sizeof(struct in_addr), op);
+	}
+
+
+	if (need_devaddr(&fw->arp.tgt_devaddr)) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_TGTDEVADDR);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr),
+			 &fw->arp.tgt_devaddr.addr,
+			 &fw->arp.tgt_devaddr.mask,
+			 fw->arp.arhln, op);
+	}
+
+	if (fw->arp.tgt.s_addr != 0 ||
+	    fw->arp.tmsk.s_addr != 0 ||
+	    fw->arp.invflags & IPT_INV_DSTIP) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_DSTIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr) + fw->arp.arhln,
+			 &fw->arp.tgt.s_addr, &fw->arp.tmsk.s_addr,
+			 sizeof(struct in_addr), op);
+	}
+
+	/* Counters need to me added before the target, otherwise they are
+	 * increased for each rule because of the way nf_tables works.
+	 */
+	if (add_counters(r, fw->counters.pcnt, fw->counters.bcnt) < 0)
+		return -1;
+
+	if (cs->target != NULL) {
+		/* Standard target? */
+		if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
+			ret = add_verdict(r, NF_ACCEPT);
+		else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
+			ret = add_verdict(r, NF_DROP);
+		else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
+			ret = add_verdict(r, NFT_RETURN);
+		else
+			ret = add_target(r, cs->target->t);
+	} else if (strlen(cs->jumpto) > 0) {
+		/* No goto in arptables */
+		ret = add_jumpto(r, cs->jumpto, NFT_JUMP);
+	}
+
+	return ret;
+}
+
+static void nft_arp_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			       void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct arpt_entry *fw = &cs->arp;
+	uint8_t flags = 0;
+
+	parse_meta(e, ctx->meta.key, fw->arp.iniface, fw->arp.iniface_mask,
+		   fw->arp.outiface, fw->arp.outiface_mask,
+		   &flags);
+
+	fw->arp.invflags |= flags;
+}
+
+static void nft_arp_parse_immediate(const char *jumpto, bool nft_goto,
+				    void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->jumpto = jumpto;
+}
+
+static void parse_mask_ipv4(struct nft_xt_ctx *ctx, struct in_addr *mask)
+{
+	mask->s_addr = ctx->bitwise.mask[0];
+}
+
+static bool nft_arp_parse_devaddr(struct nft_xt_ctx *ctx,
+				  struct nftnl_expr *e,
+				  struct arpt_devaddr_info *info)
+{
+	uint32_t hlen;
+	bool inv;
+
+	nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &hlen);
+
+	if (hlen != ETH_ALEN)
+		return false;
+
+	get_cmp_data(e, info->addr, ETH_ALEN, &inv);
+
+	if (ctx->flags & NFT_XT_CTX_BITWISE) {
+		memcpy(info->mask, ctx->bitwise.mask, ETH_ALEN);
+		ctx->flags &= ~NFT_XT_CTX_BITWISE;
+	} else {
+		memset(info->mask, 0xff,
+		       min(ctx->payload.len, ETH_ALEN));
+	}
+
+	return inv;
+}
+
+static void nft_arp_parse_payload(struct nft_xt_ctx *ctx,
+				  struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct arpt_entry *fw = &cs->arp;
+	struct in_addr addr;
+	uint16_t ar_hrd, ar_pro, ar_op;
+	uint8_t ar_hln;
+	bool inv;
+
+	switch (ctx->payload.offset) {
+	case offsetof(struct arphdr, ar_hrd):
+		get_cmp_data(e, &ar_hrd, sizeof(ar_hrd), &inv);
+		fw->arp.arhrd = ar_hrd;
+		fw->arp.arhrd_mask = 0xffff;
+		if (inv)
+			fw->arp.invflags |= IPT_INV_ARPHRD;
+		break;
+	case offsetof(struct arphdr, ar_pro):
+		get_cmp_data(e, &ar_pro, sizeof(ar_pro), &inv);
+		fw->arp.arpro = ar_pro;
+		fw->arp.arpro_mask = 0xffff;
+		if (inv)
+			fw->arp.invflags |= IPT_INV_PROTO;
+		break;
+	case offsetof(struct arphdr, ar_op):
+		get_cmp_data(e, &ar_op, sizeof(ar_op), &inv);
+		fw->arp.arpop = ar_op;
+		fw->arp.arpop_mask = 0xffff;
+		if (inv)
+			fw->arp.invflags |= IPT_INV_ARPOP;
+		break;
+	case offsetof(struct arphdr, ar_hln):
+		get_cmp_data(e, &ar_hln, sizeof(ar_hln), &inv);
+		fw->arp.arhln = ar_hln;
+		fw->arp.arhln_mask = 0xff;
+		if (inv)
+			fw->arp.invflags |= IPT_INV_ARPOP;
+		break;
+	default:
+		if (ctx->payload.offset == sizeof(struct arphdr)) {
+			if (nft_arp_parse_devaddr(ctx, e, &fw->arp.src_devaddr))
+				fw->arp.invflags |= IPT_INV_SRCDEVADDR;
+		} else if (ctx->payload.offset == sizeof(struct arphdr) +
+					   fw->arp.arhln) {
+			get_cmp_data(e, &addr, sizeof(addr), &inv);
+			fw->arp.src.s_addr = addr.s_addr;
+			if (ctx->flags & NFT_XT_CTX_BITWISE) {
+				parse_mask_ipv4(ctx, &fw->arp.smsk);
+				ctx->flags &= ~NFT_XT_CTX_BITWISE;
+			} else {
+				memset(&fw->arp.smsk, 0xff,
+				       min(ctx->payload.len,
+					   sizeof(struct in_addr)));
+			}
+
+			if (inv)
+				fw->arp.invflags |= IPT_INV_SRCIP;
+		} else if (ctx->payload.offset == sizeof(struct arphdr) +
+						  fw->arp.arhln +
+						  sizeof(struct in_addr)) {
+			if (nft_arp_parse_devaddr(ctx, e, &fw->arp.tgt_devaddr))
+				fw->arp.invflags |= IPT_INV_TGTDEVADDR;
+		} else if (ctx->payload.offset == sizeof(struct arphdr) +
+						  fw->arp.arhln +
+						  sizeof(struct in_addr) +
+						  fw->arp.arhln) {
+			get_cmp_data(e, &addr, sizeof(addr), &inv);
+			fw->arp.tgt.s_addr = addr.s_addr;
+			if (ctx->flags & NFT_XT_CTX_BITWISE) {
+				parse_mask_ipv4(ctx, &fw->arp.tmsk);
+				ctx->flags &= ~NFT_XT_CTX_BITWISE;
+			} else {
+				memset(&fw->arp.tmsk, 0xff,
+				       min(ctx->payload.len,
+					   sizeof(struct in_addr)));
+			}
+
+			if (inv)
+				fw->arp.invflags |= IPT_INV_DSTIP;
+		}
+		break;
+	}
+}
+
+static void nft_arp_print_header(unsigned int format, const char *chain,
+				 const char *pol,
+				 const struct xt_counters *counters,
+				 bool basechain, uint32_t refs,
+				 uint32_t entries)
+{
+	printf("Chain %s", chain);
+	if (basechain && pol) {
+		printf(" (policy %s", pol);
+		if (!(format & FMT_NOCOUNTS)) {
+			fputc(' ', stdout);
+			xtables_print_num(counters->pcnt, (format|FMT_NOTABLE));
+			fputs("packets, ", stdout);
+			xtables_print_num(counters->bcnt, (format|FMT_NOTABLE));
+			fputs("bytes", stdout);
+		}
+		printf(")\n");
+	} else {
+		printf(" (%u references)\n", refs);
+	}
+}
+
+static void nft_arp_print_rule_details(const struct iptables_command_state *cs,
+				       unsigned int format)
+{
+	const struct arpt_entry *fw = &cs->arp;
+	char buf[BUFSIZ];
+	char iface[IFNAMSIZ+2];
+	const char *sep = "";
+	int print_iface = 0;
+	int i;
+
+	if (strlen(cs->jumpto)) {
+		printf("%s-j %s", sep, cs->jumpto);
+		sep = " ";
+	}
+
+	iface[0] = '\0';
+
+	if (fw->arp.iniface[0] != '\0') {
+		strcat(iface, fw->arp.iniface);
+		print_iface = 1;
+	}
+	else if (format & FMT_VIA) {
+		print_iface = 1;
+		if (format & FMT_NUMERIC) strcat(iface, "*");
+		else strcat(iface, "any");
+	}
+	if (print_iface) {
+		printf("%s%s-i %s", sep, fw->arp.invflags & IPT_INV_VIA_IN ?
+				   "! " : "", iface);
+		sep = " ";
+	}
+
+	print_iface = 0;
+	iface[0] = '\0';
+
+	if (fw->arp.outiface[0] != '\0') {
+		strcat(iface, fw->arp.outiface);
+		print_iface = 1;
+	}
+	else if (format & FMT_VIA) {
+		print_iface = 1;
+		if (format & FMT_NUMERIC) strcat(iface, "*");
+		else strcat(iface, "any");
+	}
+	if (print_iface) {
+		printf("%s%s-o %s", sep, fw->arp.invflags & IPT_INV_VIA_OUT ?
+				   "! " : "", iface);
+		sep = " ";
+	}
+
+	if (fw->arp.smsk.s_addr != 0L) {
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_SRCIP
+			? "! " : "");
+		if (format & FMT_NUMERIC)
+			sprintf(buf, "%s", addr_to_dotted(&(fw->arp.src)));
+		else
+			sprintf(buf, "%s", addr_to_anyname(&(fw->arp.src)));
+		strncat(buf, mask_to_dotted(&(fw->arp.smsk)),
+			sizeof(buf) - strlen(buf) - 1);
+		printf("-s %s", buf);
+		sep = " ";
+	}
+
+	for (i = 0; i < ARPT_DEV_ADDR_LEN_MAX; i++)
+		if (fw->arp.src_devaddr.mask[i] != 0)
+			break;
+	if (i == ARPT_DEV_ADDR_LEN_MAX)
+		goto after_devsrc;
+	printf("%s%s", sep, fw->arp.invflags & IPT_INV_SRCDEVADDR
+		? "! " : "");
+	printf("--src-mac ");
+	xtables_print_mac_and_mask((unsigned char *)fw->arp.src_devaddr.addr,
+				   (unsigned char *)fw->arp.src_devaddr.mask);
+	sep = " ";
+after_devsrc:
+
+	if (fw->arp.tmsk.s_addr != 0L) {
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_DSTIP
+			? "! " : "");
+		if (format & FMT_NUMERIC)
+			sprintf(buf, "%s", addr_to_dotted(&(fw->arp.tgt)));
+		else
+			sprintf(buf, "%s", addr_to_anyname(&(fw->arp.tgt)));
+		strncat(buf, mask_to_dotted(&(fw->arp.tmsk)),
+			sizeof(buf) - strlen(buf) - 1);
+		printf("-d %s", buf);
+		sep = " ";
+	}
+
+	for (i = 0; i <ARPT_DEV_ADDR_LEN_MAX; i++)
+		if (fw->arp.tgt_devaddr.mask[i] != 0)
+			break;
+	if (i == ARPT_DEV_ADDR_LEN_MAX)
+		goto after_devdst;
+	printf("%s%s", sep, fw->arp.invflags & IPT_INV_TGTDEVADDR
+		? "! " : "");
+	printf("--dst-mac ");
+	xtables_print_mac_and_mask((unsigned char *)fw->arp.tgt_devaddr.addr,
+				   (unsigned char *)fw->arp.tgt_devaddr.mask);
+	sep = " ";
+
+after_devdst:
+
+	if (fw->arp.arhln_mask != 255 || fw->arp.arhln != 6) {
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_ARPHLN
+			? "! " : "");
+		printf("--h-length %d", fw->arp.arhln);
+		if (fw->arp.arhln_mask != 255)
+			printf("/%d", fw->arp.arhln_mask);
+		sep = " ";
+	}
+
+	if (fw->arp.arpop_mask != 0) {
+		int tmp = ntohs(fw->arp.arpop);
+
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_ARPOP
+			? "! " : "");
+		if (tmp <= NUMOPCODES && !(format & FMT_NUMERIC))
+			printf("--opcode %s", arp_opcodes[tmp-1]);
+		else
+			printf("--opcode %d", tmp);
+
+		if (fw->arp.arpop_mask != 65535)
+			printf("/%d", ntohs(fw->arp.arpop_mask));
+		sep = " ";
+	}
+
+	if (fw->arp.arhrd_mask != 65535 || fw->arp.arhrd != htons(1)) {
+		uint16_t tmp = ntohs(fw->arp.arhrd);
+
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_ARPHRD
+			? "! " : "");
+		if (tmp == 1 && !(format & FMT_NUMERIC))
+			printf("--h-type %s", "Ethernet");
+		else
+			printf("--h-type %u", tmp);
+		if (fw->arp.arhrd_mask != 65535)
+			printf("/%d", ntohs(fw->arp.arhrd_mask));
+		sep = " ";
+	}
+
+	if (fw->arp.arpro_mask != 0) {
+		int tmp = ntohs(fw->arp.arpro);
+
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_PROTO
+			? "! " : "");
+		if (tmp == 0x0800 && !(format & FMT_NUMERIC))
+			printf("--proto-type %s", "IPv4");
+		else
+			printf("--proto-type 0x%x", tmp);
+		if (fw->arp.arpro_mask != 65535)
+			printf("/%x", ntohs(fw->arp.arpro_mask));
+		sep = " ";
+	}
+}
+
+static void
+nft_arp_save_rule(const void *data, unsigned int format)
+{
+	const struct iptables_command_state *cs = data;
+
+	format |= FMT_NUMERIC;
+
+	nft_arp_print_rule_details(cs, format);
+	if (cs->target && cs->target->save)
+		cs->target->save(&cs->fw, cs->target->t);
+	printf("\n");
+}
+
+static void
+nft_arp_print_rule(struct nft_handle *h, struct nftnl_rule *r,
+		   unsigned int num, unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	if (format & FMT_LINENUMBERS)
+		printf("%u ", num);
+
+	nft_rule_to_iptables_command_state(h, r, &cs);
+
+	nft_arp_print_rule_details(&cs, format);
+	print_matches_and_target(&cs, format);
+
+	if (!(format & FMT_NOCOUNTS)) {
+		printf(" , pcnt=");
+		xtables_print_num(cs.counters.pcnt, format | FMT_NOTABLE);
+		printf("-- bcnt=");
+		xtables_print_num(cs.counters.bcnt, format | FMT_NOTABLE);
+	}
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+
+	nft_clear_iptables_command_state(&cs);
+}
+
+static bool nft_arp_is_same(const void *data_a,
+			    const void *data_b)
+{
+	const struct arpt_entry *a = data_a;
+	const struct arpt_entry *b = data_b;
+
+	if (a->arp.src.s_addr != b->arp.src.s_addr
+	    || a->arp.tgt.s_addr != b->arp.tgt.s_addr
+	    || a->arp.smsk.s_addr != b->arp.smsk.s_addr
+	    || a->arp.tmsk.s_addr != b->arp.tmsk.s_addr
+	    || a->arp.arpro != b->arp.arpro
+	    || a->arp.flags != b->arp.flags
+	    || a->arp.invflags != b->arp.invflags) {
+		DEBUGP("different src/dst/proto/flags/invflags\n");
+		return false;
+	}
+
+	return is_same_interfaces(a->arp.iniface,
+				  a->arp.outiface,
+				  (unsigned char *)a->arp.iniface_mask,
+				  (unsigned char *)a->arp.outiface_mask,
+				  b->arp.iniface,
+				  b->arp.outiface,
+				  (unsigned char *)b->arp.iniface_mask,
+				  (unsigned char *)b->arp.outiface_mask);
+}
+
+static void nft_arp_save_chain(const struct nftnl_chain *c, const char *policy)
+{
+	const char *chain = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+
+	printf(":%s %s\n", chain, policy ?: "-");
+}
+
+struct nft_family_ops nft_family_ops_arp = {
+	.add			= nft_arp_add,
+	.is_same		= nft_arp_is_same,
+	.print_payload		= NULL,
+	.parse_meta		= nft_arp_parse_meta,
+	.parse_payload		= nft_arp_parse_payload,
+	.parse_immediate	= nft_arp_parse_immediate,
+	.print_header		= nft_arp_print_header,
+	.print_rule		= nft_arp_print_rule,
+	.save_rule		= nft_arp_save_rule,
+	.save_chain		= nft_arp_save_chain,
+	.post_parse		= NULL,
+	.rule_to_cs		= nft_rule_to_iptables_command_state,
+	.clear_cs		= nft_clear_iptables_command_state,
+	.parse_target		= nft_ipv46_parse_target,
+};
diff --git a/iptables/nft-arp.h b/iptables/nft-arp.h
new file mode 100644
index 0000000..0d93a31
--- /dev/null
+++ b/iptables/nft-arp.h
@@ -0,0 +1,14 @@
+#ifndef _NFT_ARP_H_
+#define _NFT_ARP_H_
+
+extern char *arp_opcodes[];
+#define NUMOPCODES 9
+
+/* define invflags which won't collide with IPT ones */
+#define IPT_INV_SRCDEVADDR	0x0080
+#define IPT_INV_TGTDEVADDR	0x0100
+#define IPT_INV_ARPHLN		0x0200
+#define IPT_INV_ARPOP		0x0400
+#define IPT_INV_ARPHRD		0x0800
+
+#endif
diff --git a/iptables/nft-bridge.c b/iptables/nft-bridge.c
new file mode 100644
index 0000000..d98fd52
--- /dev/null
+++ b/iptables/nft-bridge.c
@@ -0,0 +1,905 @@
+/*
+ * (C) 2014 by Giuseppe Longo <giuseppelng@gmail.com>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/ether.h>
+#include <inttypes.h>
+
+#include <xtables.h>
+#include <libiptc/libxtc.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include <libnftnl/set.h>
+
+#include "nft-shared.h"
+#include "nft-bridge.h"
+#include "nft-cache.h"
+#include "nft.h"
+
+void ebt_cs_clean(struct iptables_command_state *cs)
+{
+	struct ebt_match *m, *nm;
+
+	xtables_rule_matches_free(&cs->matches);
+
+	for (m = cs->match_list; m;) {
+		if (!m->ismatch) {
+			struct xtables_target *target = m->u.watcher;
+
+			if (target->t) {
+				free(target->t);
+				target->t = NULL;
+			}
+			if (target == target->next)
+				free(target);
+		}
+
+		nm = m->next;
+		free(m);
+		m = nm;
+	}
+
+	if (cs->target) {
+		free(cs->target->t);
+		cs->target->t = NULL;
+
+		if (cs->target == cs->target->next) {
+			free(cs->target);
+			cs->target = NULL;
+		}
+	}
+}
+
+/* Put the mac address into 6 (ETH_ALEN) bytes returns 0 on success. */
+static void ebt_print_mac_and_mask(const unsigned char *mac, const unsigned char *mask)
+{
+	if (xtables_print_well_known_mac_and_mask(mac, mask))
+		xtables_print_mac_and_mask(mac, mask);
+}
+
+static void add_logical_iniface(struct nftnl_rule *r, char *iface, uint32_t op)
+{
+	int iface_len;
+
+	iface_len = strlen(iface);
+
+	add_meta(r, NFT_META_BRI_IIFNAME);
+	if (iface[iface_len - 1] == '+')
+		add_cmp_ptr(r, op, iface, iface_len - 1);
+	else
+		add_cmp_ptr(r, op, iface, iface_len + 1);
+}
+
+static void add_logical_outiface(struct nftnl_rule *r, char *iface, uint32_t op)
+{
+	int iface_len;
+
+	iface_len = strlen(iface);
+
+	add_meta(r, NFT_META_BRI_OIFNAME);
+	if (iface[iface_len - 1] == '+')
+		add_cmp_ptr(r, op, iface, iface_len - 1);
+	else
+		add_cmp_ptr(r, op, iface, iface_len + 1);
+}
+
+static int _add_action(struct nftnl_rule *r, struct iptables_command_state *cs)
+{
+	return add_action(r, cs, false);
+}
+
+static int nft_bridge_add(struct nft_handle *h,
+			  struct nftnl_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct ebt_match *iter;
+	struct ebt_entry *fw = &cs->eb;
+	uint32_t op;
+
+	if (fw->in[0] != '\0') {
+		op = nft_invflags2cmp(fw->invflags, EBT_IIN);
+		add_iniface(r, fw->in, op);
+	}
+
+	if (fw->out[0] != '\0') {
+		op = nft_invflags2cmp(fw->invflags, EBT_IOUT);
+		add_outiface(r, fw->out, op);
+	}
+
+	if (fw->logical_in[0] != '\0') {
+		op = nft_invflags2cmp(fw->invflags, EBT_ILOGICALIN);
+		add_logical_iniface(r, fw->logical_in, op);
+	}
+
+	if (fw->logical_out[0] != '\0') {
+		op = nft_invflags2cmp(fw->invflags, EBT_ILOGICALOUT);
+		add_logical_outiface(r, fw->logical_out, op);
+	}
+
+	if (fw->bitmask & EBT_ISOURCE) {
+		op = nft_invflags2cmp(fw->invflags, EBT_ISOURCE);
+		add_addr(r, NFT_PAYLOAD_LL_HEADER,
+			 offsetof(struct ethhdr, h_source),
+			 fw->sourcemac, fw->sourcemsk, ETH_ALEN, op);
+	}
+
+	if (fw->bitmask & EBT_IDEST) {
+		op = nft_invflags2cmp(fw->invflags, EBT_IDEST);
+		add_addr(r, NFT_PAYLOAD_LL_HEADER,
+			 offsetof(struct ethhdr, h_dest),
+			 fw->destmac, fw->destmsk, ETH_ALEN, op);
+	}
+
+	if ((fw->bitmask & EBT_NOPROTO) == 0) {
+		op = nft_invflags2cmp(fw->invflags, EBT_IPROTO);
+		add_payload(r, offsetof(struct ethhdr, h_proto), 2,
+			    NFT_PAYLOAD_LL_HEADER);
+		add_cmp_u16(r, fw->ethproto, op);
+	}
+
+	add_compat(r, fw->ethproto, fw->invflags & EBT_IPROTO);
+
+	for (iter = cs->match_list; iter; iter = iter->next) {
+		if (iter->ismatch) {
+			if (add_match(h, r, iter->u.match->m))
+				break;
+		} else {
+			if (add_target(r, iter->u.watcher->t))
+				break;
+		}
+	}
+
+	if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
+		return -1;
+
+	return _add_action(r, cs);
+}
+
+static void nft_bridge_parse_meta(struct nft_xt_ctx *ctx,
+				  struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct ebt_entry *fw = &cs->eb;
+	uint8_t invflags = 0;
+	char iifname[IFNAMSIZ] = {}, oifname[IFNAMSIZ] = {};
+
+	parse_meta(e, ctx->meta.key, iifname, NULL, oifname, NULL, &invflags);
+
+	switch (ctx->meta.key) {
+	case NFT_META_BRI_IIFNAME:
+		if (invflags & IPT_INV_VIA_IN)
+			cs->eb.invflags |= EBT_ILOGICALIN;
+		snprintf(fw->logical_in, sizeof(fw->logical_in), "%s", iifname);
+		break;
+	case NFT_META_IIFNAME:
+		if (invflags & IPT_INV_VIA_IN)
+			cs->eb.invflags |= EBT_IIN;
+		snprintf(fw->in, sizeof(fw->in), "%s", iifname);
+		break;
+	case NFT_META_BRI_OIFNAME:
+		if (invflags & IPT_INV_VIA_OUT)
+			cs->eb.invflags |= EBT_ILOGICALOUT;
+		snprintf(fw->logical_out, sizeof(fw->logical_out), "%s", oifname);
+		break;
+	case NFT_META_OIFNAME:
+		if (invflags & IPT_INV_VIA_OUT)
+			cs->eb.invflags |= EBT_IOUT;
+		snprintf(fw->out, sizeof(fw->out), "%s", oifname);
+		break;
+	default:
+		break;
+	}
+}
+
+static void nft_bridge_parse_payload(struct nft_xt_ctx *ctx,
+				     struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct ebt_entry *fw = &cs->eb;
+	unsigned char addr[ETH_ALEN];
+	unsigned short int ethproto;
+	bool inv;
+	int i;
+
+	switch (ctx->payload.offset) {
+	case offsetof(struct ethhdr, h_dest):
+		get_cmp_data(e, addr, sizeof(addr), &inv);
+		for (i = 0; i < ETH_ALEN; i++)
+			fw->destmac[i] = addr[i];
+		if (inv)
+			fw->invflags |= EBT_IDEST;
+
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+                        memcpy(fw->destmsk, ctx->bitwise.mask, ETH_ALEN);
+                        ctx->flags &= ~NFT_XT_CTX_BITWISE;
+                } else {
+			memset(&fw->destmsk, 0xff,
+			       min(ctx->payload.len, ETH_ALEN));
+                }
+		fw->bitmask |= EBT_IDEST;
+		break;
+	case offsetof(struct ethhdr, h_source):
+		get_cmp_data(e, addr, sizeof(addr), &inv);
+		for (i = 0; i < ETH_ALEN; i++)
+			fw->sourcemac[i] = addr[i];
+		if (inv)
+			fw->invflags |= EBT_ISOURCE;
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+                        memcpy(fw->sourcemsk, ctx->bitwise.mask, ETH_ALEN);
+                        ctx->flags &= ~NFT_XT_CTX_BITWISE;
+                } else {
+			memset(&fw->sourcemsk, 0xff,
+			       min(ctx->payload.len, ETH_ALEN));
+                }
+		fw->bitmask |= EBT_ISOURCE;
+		break;
+	case offsetof(struct ethhdr, h_proto):
+		get_cmp_data(e, &ethproto, sizeof(ethproto), &inv);
+		fw->ethproto = ethproto;
+		if (inv)
+			fw->invflags |= EBT_IPROTO;
+		fw->bitmask &= ~EBT_NOPROTO;
+		break;
+	}
+}
+
+static void nft_bridge_parse_immediate(const char *jumpto, bool nft_goto,
+				       void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->jumpto = jumpto;
+}
+
+/* return 0 if saddr, 1 if daddr, -1 on error */
+static int
+lookup_check_ether_payload(uint32_t base, uint32_t offset, uint32_t len)
+{
+	if (base != 0 || len != ETH_ALEN)
+		return -1;
+
+	switch (offset) {
+	case offsetof(struct ether_header, ether_dhost):
+		return 1;
+	case offsetof(struct ether_header, ether_shost):
+		return 0;
+	default:
+		return -1;
+	}
+}
+
+/* return 0 if saddr, 1 if daddr, -1 on error */
+static int
+lookup_check_iphdr_payload(uint32_t base, uint32_t offset, uint32_t len)
+{
+	if (base != 1 || len != 4)
+		return -1;
+
+	switch (offset) {
+	case offsetof(struct iphdr, daddr):
+		return 1;
+	case offsetof(struct iphdr, saddr):
+		return 0;
+	default:
+		return -1;
+	}
+}
+
+/* Make sure previous payload expression(s) is/are consistent and extract if
+ * matching on source or destination address and if matching on MAC and IP or
+ * only MAC address. */
+static int lookup_analyze_payloads(const struct nft_xt_ctx *ctx,
+				   bool *dst, bool *ip)
+{
+	int val, val2 = -1;
+
+	if (ctx->flags & NFT_XT_CTX_PREV_PAYLOAD) {
+		val = lookup_check_ether_payload(ctx->prev_payload.base,
+						 ctx->prev_payload.offset,
+						 ctx->prev_payload.len);
+		if (val < 0) {
+			DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
+			       ctx->prev_payload.base, ctx->prev_payload.offset,
+			       ctx->prev_payload.len);
+			return -1;
+		}
+		if (!(ctx->flags & NFT_XT_CTX_PAYLOAD)) {
+			DEBUGP("Previous but no current payload?\n");
+			return -1;
+		}
+		val2 = lookup_check_iphdr_payload(ctx->payload.base,
+						  ctx->payload.offset,
+						  ctx->payload.len);
+		if (val2 < 0) {
+			DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
+			       ctx->payload.base, ctx->payload.offset,
+			       ctx->payload.len);
+			return -1;
+		} else if (val != val2) {
+			DEBUGP("mismatching payload match offsets\n");
+			return -1;
+		}
+	} else if (ctx->flags & NFT_XT_CTX_PAYLOAD) {
+		val = lookup_check_ether_payload(ctx->payload.base,
+						 ctx->payload.offset,
+						 ctx->payload.len);
+		if (val < 0) {
+			DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
+			       ctx->payload.base, ctx->payload.offset,
+			       ctx->payload.len);
+			return -1;
+		}
+	} else {
+		DEBUGP("unknown LHS of lookup expression\n");
+		return -1;
+	}
+
+	if (dst)
+		*dst = (val == 1);
+	if (ip)
+		*ip = (val2 != -1);
+	return 0;
+}
+
+static int set_elems_to_among_pairs(struct nft_among_pair *pairs,
+				    const struct nftnl_set *s, int cnt)
+{
+	struct nftnl_set_elems_iter *iter = nftnl_set_elems_iter_create(s);
+	struct nftnl_set_elem *elem;
+	size_t tmpcnt = 0;
+	const void *data;
+	uint32_t datalen;
+	int ret = -1;
+
+	if (!iter) {
+		fprintf(stderr, "BUG: set elems iter allocation failed\n");
+		return ret;
+	}
+
+	while ((elem = nftnl_set_elems_iter_next(iter))) {
+		data = nftnl_set_elem_get(elem, NFTNL_SET_ELEM_KEY, &datalen);
+		if (!data) {
+			fprintf(stderr, "BUG: set elem without key\n");
+			goto err;
+		}
+		if (datalen > sizeof(*pairs)) {
+			fprintf(stderr, "BUG: overlong set elem\n");
+			goto err;
+		}
+		nft_among_insert_pair(pairs, &tmpcnt, data);
+	}
+	ret = 0;
+err:
+	nftnl_set_elems_iter_destroy(iter);
+	return ret;
+}
+
+static struct nftnl_set *set_from_lookup_expr(struct nft_xt_ctx *ctx,
+					      const struct nftnl_expr *e)
+{
+	const char *set_name = nftnl_expr_get_str(e, NFTNL_EXPR_LOOKUP_SET);
+	uint32_t set_id = nftnl_expr_get_u32(e, NFTNL_EXPR_LOOKUP_SET_ID);
+	struct nftnl_set_list *slist;
+	struct nftnl_set *set;
+
+	slist = nft_set_list_get(ctx->h, ctx->table, set_name);
+	if (slist) {
+		set = nftnl_set_list_lookup_byname(slist, set_name);
+		if (set)
+			return set;
+
+		set = nft_set_batch_lookup_byid(ctx->h, set_id);
+		if (set)
+			return set;
+	}
+
+	return NULL;
+}
+
+static void nft_bridge_parse_lookup(struct nft_xt_ctx *ctx,
+				    struct nftnl_expr *e, void *data)
+{
+	struct xtables_match *match = NULL;
+	struct nft_among_data *among_data;
+	bool is_dst, have_ip, inv;
+	struct ebt_match *ematch;
+	struct nftnl_set *s;
+	size_t poff, size;
+	uint32_t cnt;
+
+	if (lookup_analyze_payloads(ctx, &is_dst, &have_ip))
+		return;
+
+	s = set_from_lookup_expr(ctx, e);
+	if (!s)
+		xtables_error(OTHER_PROBLEM,
+			      "BUG: lookup expression references unknown set");
+
+	cnt = nftnl_set_get_u32(s, NFTNL_SET_DESC_SIZE);
+
+	for (ematch = ctx->cs->match_list; ematch; ematch = ematch->next) {
+		if (!ematch->ismatch || strcmp(ematch->u.match->name, "among"))
+			continue;
+
+		match = ematch->u.match;
+		among_data = (struct nft_among_data *)match->m->data;
+
+		size = cnt + among_data->src.cnt + among_data->dst.cnt;
+		size *= sizeof(struct nft_among_pair);
+
+		size += XT_ALIGN(sizeof(struct xt_entry_match)) +
+			sizeof(struct nft_among_data);
+
+		match->m = xtables_realloc(match->m, size);
+		break;
+	}
+	if (!match) {
+		match = xtables_find_match("among", XTF_TRY_LOAD,
+					   &ctx->cs->matches);
+
+		size = cnt * sizeof(struct nft_among_pair);
+		size += XT_ALIGN(sizeof(struct xt_entry_match)) +
+			sizeof(struct nft_among_data);
+
+		match->m = xtables_calloc(1, size);
+		strcpy(match->m->u.user.name, match->name);
+		match->m->u.user.revision = match->revision;
+		xs_init_match(match);
+
+		if (ctx->h->ops->parse_match != NULL)
+			ctx->h->ops->parse_match(match, ctx->cs);
+	}
+	if (!match)
+		return;
+
+	match->m->u.match_size = size;
+
+	inv = !!(nftnl_expr_get_u32(e, NFTNL_EXPR_LOOKUP_FLAGS) &
+				    NFT_LOOKUP_F_INV);
+
+	among_data = (struct nft_among_data *)match->m->data;
+	poff = nft_among_prepare_data(among_data, is_dst, cnt, inv, have_ip);
+	if (set_elems_to_among_pairs(among_data->pairs + poff, s, cnt))
+		xtables_error(OTHER_PROBLEM,
+			      "ebtables among pair parsing failed");
+
+	ctx->flags &= ~(NFT_XT_CTX_PAYLOAD | NFT_XT_CTX_PREV_PAYLOAD);
+}
+
+static void parse_watcher(void *object, struct ebt_match **match_list,
+			  bool ismatch)
+{
+	struct ebt_match *m;
+
+	m = calloc(1, sizeof(struct ebt_match));
+	if (m == NULL)
+		xtables_error(OTHER_PROBLEM, "Can't allocate memory");
+
+	if (ismatch)
+		m->u.match = object;
+	else
+		m->u.watcher = object;
+
+	m->ismatch = ismatch;
+	if (*match_list == NULL)
+		*match_list = m;
+	else
+		(*match_list)->next = m;
+}
+
+static void nft_bridge_parse_match(struct xtables_match *m, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	parse_watcher(m, &cs->match_list, true);
+}
+
+static void nft_bridge_parse_target(struct xtables_target *t, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	/* harcoded names :-( */
+	if (strcmp(t->name, "log") == 0 ||
+	    strcmp(t->name, "nflog") == 0) {
+		parse_watcher(t, &cs->match_list, false);
+		return;
+	}
+
+	cs->target = t;
+}
+
+static void nft_rule_to_ebtables_command_state(struct nft_handle *h,
+					       const struct nftnl_rule *r,
+					       struct iptables_command_state *cs)
+{
+	cs->eb.bitmask = EBT_NOPROTO;
+	nft_rule_to_iptables_command_state(h, r, cs);
+}
+
+static void print_iface(const char *option, const char *name, bool invert)
+{
+	if (*name)
+		printf("%s%s %s ", option, invert ? " !" : "", name);
+}
+
+static void nft_bridge_print_table_header(const char *tablename)
+{
+	printf("Bridge table: %s\n\n", tablename);
+}
+
+static void nft_bridge_print_header(unsigned int format, const char *chain,
+				    const char *pol,
+				    const struct xt_counters *counters,
+				    bool basechain, uint32_t refs, uint32_t entries)
+{
+	printf("Bridge chain: %s, entries: %u, policy: %s\n",
+	       chain, entries, pol ?: "RETURN");
+}
+
+static void print_matches_and_watchers(const struct iptables_command_state *cs,
+				       unsigned int format)
+{
+	struct xtables_target *watcherp;
+	struct xtables_match *matchp;
+	struct ebt_match *m;
+
+	for (m = cs->match_list; m; m = m->next) {
+		if (m->ismatch) {
+			matchp = m->u.match;
+			if (matchp->print != NULL) {
+				matchp->print(&cs->eb, matchp->m,
+					      format & FMT_NUMERIC);
+			}
+		} else {
+			watcherp = m->u.watcher;
+			if (watcherp->print != NULL) {
+				watcherp->print(&cs->eb, watcherp->t,
+						format & FMT_NUMERIC);
+			}
+		}
+	}
+}
+
+static void print_mac(char option, const unsigned char *mac,
+		      const unsigned char *mask,
+		      bool invert)
+{
+	printf("-%c ", option);
+	if (invert)
+		printf("! ");
+	ebt_print_mac_and_mask(mac, mask);
+	printf(" ");
+}
+
+
+static void print_protocol(uint16_t ethproto, bool invert, unsigned int bitmask)
+{
+	struct xt_ethertypeent *ent;
+
+	/* Dont print anything about the protocol if no protocol was
+	 * specified, obviously this means any protocol will do. */
+	if (bitmask & EBT_NOPROTO)
+		return;
+
+	printf("-p ");
+	if (invert)
+		printf("! ");
+
+	if (bitmask & EBT_802_3) {
+		printf("length ");
+		return;
+	}
+
+	ent = xtables_getethertypebynumber(ntohs(ethproto));
+	if (!ent)
+		printf("0x%x ", ntohs(ethproto));
+	else
+		printf("%s ", ent->e_name);
+}
+
+static void nft_bridge_save_rule(const void *data, unsigned int format)
+{
+	const struct iptables_command_state *cs = data;
+
+	if (cs->eb.ethproto)
+		print_protocol(cs->eb.ethproto, cs->eb.invflags & EBT_IPROTO,
+			       cs->eb.bitmask);
+	if (cs->eb.bitmask & EBT_ISOURCE)
+		print_mac('s', cs->eb.sourcemac, cs->eb.sourcemsk,
+		          cs->eb.invflags & EBT_ISOURCE);
+	if (cs->eb.bitmask & EBT_IDEST)
+		print_mac('d', cs->eb.destmac, cs->eb.destmsk,
+		          cs->eb.invflags & EBT_IDEST);
+
+	print_iface("-i", cs->eb.in, cs->eb.invflags & EBT_IIN);
+	print_iface("--logical-in", cs->eb.logical_in,
+		    cs->eb.invflags & EBT_ILOGICALIN);
+	print_iface("-o", cs->eb.out, cs->eb.invflags & EBT_IOUT);
+	print_iface("--logical-out", cs->eb.logical_out,
+		    cs->eb.invflags & EBT_ILOGICALOUT);
+
+	print_matches_and_watchers(cs, format);
+
+	printf("-j ");
+
+	if (cs->jumpto != NULL) {
+		if (strcmp(cs->jumpto, "") != 0)
+			printf("%s", cs->jumpto);
+		else
+			printf("CONTINUE");
+	}
+	if (cs->target != NULL && cs->target->print != NULL) {
+		printf(" ");
+		cs->target->print(&cs->fw, cs->target->t, format & FMT_NUMERIC);
+	}
+
+	if ((format & (FMT_NOCOUNTS | FMT_C_COUNTS)) == FMT_C_COUNTS) {
+		if (format & FMT_EBT_SAVE)
+			printf(" -c %"PRIu64" %"PRIu64"",
+			       (uint64_t)cs->counters.pcnt,
+			       (uint64_t)cs->counters.bcnt);
+		else
+			printf(" , pcnt = %"PRIu64" -- bcnt = %"PRIu64"",
+			       (uint64_t)cs->counters.pcnt,
+			       (uint64_t)cs->counters.bcnt);
+	}
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+}
+
+static void nft_bridge_print_rule(struct nft_handle *h, struct nftnl_rule *r,
+				  unsigned int num, unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	if (format & FMT_LINENUMBERS)
+		printf("%d ", num);
+
+	nft_rule_to_ebtables_command_state(h, r, &cs);
+	nft_bridge_save_rule(&cs, format);
+	ebt_cs_clean(&cs);
+}
+
+static void nft_bridge_save_chain(const struct nftnl_chain *c,
+				  const char *policy)
+{
+	const char *chain = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+
+	printf(":%s %s\n", chain, policy ?: "ACCEPT");
+}
+
+static bool nft_bridge_is_same(const void *data_a, const void *data_b)
+{
+	const struct ebt_entry *a = data_a;
+	const struct ebt_entry *b = data_b;
+	int i;
+
+	if (a->ethproto != b->ethproto ||
+	    /* FIXME: a->flags != b->flags || */
+	    a->invflags != b->invflags) {
+		DEBUGP("different proto/flags/invflags\n");
+		return false;
+	}
+
+	for (i = 0; i < ETH_ALEN; i++) {
+		if (a->sourcemac[i] != b->sourcemac[i]) {
+			DEBUGP("different source mac %x, %x (%d)\n",
+			a->sourcemac[i] & 0xff, b->sourcemac[i] & 0xff, i);
+			return false;
+		}
+
+		if (a->destmac[i] != b->destmac[i]) {
+			DEBUGP("different destination mac %x, %x (%d)\n",
+			a->destmac[i] & 0xff, b->destmac[i] & 0xff, i);
+			return false;
+		}
+	}
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (a->logical_in[i] != b->logical_in[i]) {
+			DEBUGP("different logical iniface %x, %x (%d)\n",
+			a->logical_in[i] & 0xff, b->logical_in[i] & 0xff, i);
+			return false;
+		}
+
+		if (a->logical_out[i] != b->logical_out[i]) {
+			DEBUGP("different logical outiface %x, %x (%d)\n",
+			a->logical_out[i] & 0xff, b->logical_out[i] & 0xff, i);
+			return false;
+		}
+	}
+
+	return strcmp(a->in, b->in) == 0 && strcmp(a->out, b->out) == 0;
+}
+
+static int xlate_ebmatches(const struct iptables_command_state *cs, struct xt_xlate *xl)
+{
+	int ret = 1, numeric = cs->options & OPT_NUMERIC;
+	struct ebt_match *m;
+
+	for (m = cs->match_list; m; m = m->next) {
+		if (m->ismatch) {
+			struct xtables_match *matchp = m->u.match;
+			struct xt_xlate_mt_params mt_params = {
+				.ip		= (const void *)&cs->eb,
+				.numeric	= numeric,
+				.escape_quotes	= false,
+				.match		= matchp->m,
+			};
+
+			if (!matchp->xlate)
+				return 0;
+
+			ret = matchp->xlate(xl, &mt_params);
+		} else {
+			struct xtables_target *watcherp = m->u.watcher;
+			struct xt_xlate_tg_params wt_params = {
+				.ip		= (const void *)&cs->eb,
+				.numeric	= numeric,
+				.escape_quotes	= false,
+				.target		= watcherp->t,
+			};
+
+			if (!watcherp->xlate)
+				return 0;
+
+			ret = watcherp->xlate(xl, &wt_params);
+		}
+
+		if (!ret)
+			break;
+	}
+
+	return ret;
+}
+
+static int xlate_ebaction(const struct iptables_command_state *cs, struct xt_xlate *xl)
+{
+	int ret = 1, numeric = cs->options & OPT_NUMERIC;
+
+	/* If no target at all, add nothing (default to continue) */
+	if (cs->target != NULL) {
+		/* Standard target? */
+		if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
+			xt_xlate_add(xl, " accept");
+		else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
+			xt_xlate_add(xl, " drop");
+		else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
+			xt_xlate_add(xl, " return");
+		else if (cs->target->xlate) {
+			xt_xlate_add(xl, " ");
+			struct xt_xlate_tg_params params = {
+				.ip		= (const void *)&cs->eb,
+				.target		= cs->target->t,
+				.numeric	= numeric,
+			};
+			ret = cs->target->xlate(xl, &params);
+		}
+		else
+			return 0;
+	} else if (cs->jumpto == NULL) {
+	} else if (strlen(cs->jumpto) > 0)
+		xt_xlate_add(xl, " jump %s", cs->jumpto);
+
+	return ret;
+}
+
+static void xlate_mac(struct xt_xlate *xl, const unsigned char *mac)
+{
+	int i;
+
+	xt_xlate_add(xl, "%02x", mac[0]);
+
+	for (i=1; i < ETH_ALEN; i++)
+		xt_xlate_add(xl, ":%02x", mac[i]);
+}
+
+static void nft_bridge_xlate_mac(struct xt_xlate *xl, const char *type, bool invert,
+				 const unsigned char *mac, const unsigned char *mask)
+{
+	char one_msk[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+	xt_xlate_add(xl, "ether %s %s", type, invert ? "!= " : "");
+
+	xlate_mac(xl, mac);
+
+	if (memcmp(mask, one_msk, ETH_ALEN)) {
+		int i;
+		xt_xlate_add(xl, " and ");
+
+		xlate_mac(xl, mask);
+
+		xt_xlate_add(xl, " == %02x", mac[0] & mask[0]);
+		for (i=1; i < ETH_ALEN; i++)
+			xt_xlate_add(xl, ":%02x", mac[i] & mask[i]);
+	}
+
+	xt_xlate_add(xl, " ");
+}
+
+static int nft_bridge_xlate(const void *data, struct xt_xlate *xl)
+{
+	const struct iptables_command_state *cs = data;
+	int ret;
+
+	xlate_ifname(xl, "iifname", cs->eb.in,
+		     cs->eb.invflags & EBT_IIN);
+	xlate_ifname(xl, "meta ibrname", cs->eb.logical_in,
+		     cs->eb.invflags & EBT_ILOGICALIN);
+	xlate_ifname(xl, "oifname", cs->eb.out,
+		     cs->eb.invflags & EBT_IOUT);
+	xlate_ifname(xl, "meta obrname", cs->eb.logical_out,
+		     cs->eb.invflags & EBT_ILOGICALOUT);
+
+	if ((cs->eb.bitmask & EBT_NOPROTO) == 0) {
+		const char *implicit = NULL;
+
+		switch (ntohs(cs->eb.ethproto)) {
+		case ETH_P_IP:
+			implicit = "ip";
+			break;
+		case ETH_P_IPV6:
+			implicit = "ip6";
+			break;
+		case ETH_P_8021Q:
+			implicit = "vlan";
+			break;
+		default:
+			break;
+		}
+
+		if (!implicit || !xlate_find_match(cs, implicit))
+			xt_xlate_add(xl, "ether type %s0x%x ",
+				     cs->eb.invflags & EBT_IPROTO ? "!= " : "",
+				     ntohs(cs->eb.ethproto));
+	}
+
+	if (cs->eb.bitmask & EBT_802_3)
+		return 0;
+
+	if (cs->eb.bitmask & EBT_ISOURCE)
+		nft_bridge_xlate_mac(xl, "saddr", cs->eb.invflags & EBT_ISOURCE,
+				     cs->eb.sourcemac, cs->eb.sourcemsk);
+	if (cs->eb.bitmask & EBT_IDEST)
+		nft_bridge_xlate_mac(xl, "daddr", cs->eb.invflags & EBT_IDEST,
+				     cs->eb.destmac, cs->eb.destmsk);
+	ret = xlate_ebmatches(cs, xl);
+	if (ret == 0)
+		return ret;
+
+	/* Always add counters per rule, as in ebtables */
+	xt_xlate_add(xl, "counter");
+	ret = xlate_ebaction(cs, xl);
+
+	return ret;
+}
+
+struct nft_family_ops nft_family_ops_bridge = {
+	.add			= nft_bridge_add,
+	.is_same		= nft_bridge_is_same,
+	.print_payload		= NULL,
+	.parse_meta		= nft_bridge_parse_meta,
+	.parse_payload		= nft_bridge_parse_payload,
+	.parse_immediate	= nft_bridge_parse_immediate,
+	.parse_lookup		= nft_bridge_parse_lookup,
+	.parse_match		= nft_bridge_parse_match,
+	.parse_target		= nft_bridge_parse_target,
+	.print_table_header	= nft_bridge_print_table_header,
+	.print_header		= nft_bridge_print_header,
+	.print_rule		= nft_bridge_print_rule,
+	.save_rule		= nft_bridge_save_rule,
+	.save_chain		= nft_bridge_save_chain,
+	.post_parse		= NULL,
+	.rule_to_cs		= nft_rule_to_ebtables_command_state,
+	.clear_cs		= ebt_cs_clean,
+	.xlate			= nft_bridge_xlate,
+};
diff --git a/iptables/nft-bridge.h b/iptables/nft-bridge.h
new file mode 100644
index 0000000..eb1b392
--- /dev/null
+++ b/iptables/nft-bridge.h
@@ -0,0 +1,181 @@
+#ifndef _NFT_BRIDGE_H_
+#define _NFT_BRIDGE_H_
+
+#include <netinet/in.h>
+//#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/nf_tables.h>
+#include <net/ethernet.h>
+#include <libiptc/libxtc.h>
+
+/* We use replace->flags, so we can't use the following values:
+ * 0x01 == OPT_COMMAND, 0x02 == OPT_TABLE, 0x100 == OPT_ZERO */
+#define LIST_N	  0x04
+#define LIST_C	  0x08
+#define LIST_X	  0x10
+#define LIST_MAC2 0x20
+
+extern unsigned char eb_mac_type_unicast[ETH_ALEN];
+extern unsigned char eb_msk_type_unicast[ETH_ALEN];
+extern unsigned char eb_mac_type_multicast[ETH_ALEN];
+extern unsigned char eb_msk_type_multicast[ETH_ALEN];
+extern unsigned char eb_mac_type_broadcast[ETH_ALEN];
+extern unsigned char eb_msk_type_broadcast[ETH_ALEN];
+extern unsigned char eb_mac_type_bridge_group[ETH_ALEN];
+extern unsigned char eb_msk_type_bridge_group[ETH_ALEN];
+
+int ebt_get_mac_and_mask(const char *from, unsigned char *to, unsigned char *mask);
+
+/* From: include/linux/netfilter_bridge/ebtables.h
+ *
+ * Adapted for the need of the ebtables-compat.
+ */
+
+#define EBT_TABLE_MAXNAMELEN 32
+#define EBT_FUNCTION_MAXNAMELEN EBT_TABLE_MAXNAMELEN
+
+/* verdicts >0 are "branches" */
+#define EBT_ACCEPT   -1
+#define EBT_DROP     -2
+#define EBT_CONTINUE -3
+#define EBT_RETURN   -4
+#define NUM_STANDARD_TARGETS   4
+
+#define EBT_ENTRY_OR_ENTRIES 0x01
+/* these are the normal masks */
+#define EBT_NOPROTO 0x02
+#define EBT_802_3 0x04
+#define EBT_SOURCEMAC 0x08
+#define EBT_DESTMAC 0x10
+#define EBT_F_MASK (EBT_NOPROTO | EBT_802_3 | EBT_SOURCEMAC | EBT_DESTMAC \
+   | EBT_ENTRY_OR_ENTRIES)
+
+#define EBT_IPROTO 0x01
+#define EBT_IIN 0x02
+#define EBT_IOUT 0x04
+#define EBT_ISOURCE 0x8
+#define EBT_IDEST 0x10
+#define EBT_ILOGICALIN 0x20
+#define EBT_ILOGICALOUT 0x40
+#define EBT_INV_MASK (EBT_IPROTO | EBT_IIN | EBT_IOUT | EBT_ILOGICALIN \
+   | EBT_ILOGICALOUT | EBT_ISOURCE | EBT_IDEST)
+
+/* ebtables target modules store the verdict inside an int. We can
+ * reclaim a part of this int for backwards compatible extensions.
+ * The 4 lsb are more than enough to store the verdict.
+ */
+#define EBT_VERDICT_BITS 0x0000000F
+
+struct nftnl_rule;
+struct iptables_command_state;
+
+static const char *ebt_standard_targets[NUM_STANDARD_TARGETS] = {
+	"ACCEPT",
+	"DROP",
+	"CONTINUE",
+	"RETURN",
+};
+
+static inline const char *nft_ebt_standard_target(unsigned int num)
+{
+	if (num >= NUM_STANDARD_TARGETS)
+		return NULL;
+
+	return ebt_standard_targets[num];
+}
+
+static inline int ebt_fill_target(const char *str, unsigned int *verdict)
+{
+	int i, ret = 0;
+
+	for (i = 0; i < NUM_STANDARD_TARGETS; i++) {
+		if (!strcmp(str, nft_ebt_standard_target(i))) {
+			*verdict = -i - 1;
+			break;
+		}
+	}
+
+	if (i == NUM_STANDARD_TARGETS)
+		ret = 1;
+
+	return ret;
+}
+
+static inline const char *ebt_target_name(unsigned int verdict)
+{
+	return nft_ebt_standard_target(-verdict - 1);
+}
+
+#define EBT_CHECK_OPTION(flags, mask) ({			\
+	if (*flags & mask)					\
+		xtables_error(PARAMETER_PROBLEM,		\
+			      "Multiple use of same "		\
+			      "option not allowed");		\
+	*flags |= mask;						\
+})								\
+
+void ebt_cs_clean(struct iptables_command_state *cs);
+void ebt_load_match_extensions(void);
+void ebt_add_match(struct xtables_match *m,
+			  struct iptables_command_state *cs);
+void ebt_add_watcher(struct xtables_target *watcher,
+                     struct iptables_command_state *cs);
+int ebt_command_default(struct iptables_command_state *cs);
+
+struct nft_among_pair {
+	struct ether_addr ether;
+	struct in_addr in __attribute__((aligned (4)));
+};
+
+struct nft_among_data {
+	struct {
+		size_t cnt;
+		bool inv;
+		bool ip;
+	} src, dst;
+	/* first source, then dest pairs */
+	struct nft_among_pair pairs[0];
+};
+
+/* initialize fields, return offset into pairs array to write pairs to */
+static inline size_t
+nft_among_prepare_data(struct nft_among_data *data, bool dst,
+		       size_t cnt, bool inv, bool ip)
+{
+	size_t poff;
+
+	if (dst) {
+		data->dst.cnt = cnt;
+		data->dst.inv = inv;
+		data->dst.ip = ip;
+		poff = data->src.cnt;
+	} else {
+		data->src.cnt = cnt;
+		data->src.inv = inv;
+		data->src.ip = ip;
+		poff = 0;
+		memmove(data->pairs + cnt, data->pairs,
+			data->dst.cnt * sizeof(*data->pairs));
+	}
+	return poff;
+}
+
+static inline void
+nft_among_insert_pair(struct nft_among_pair *pairs,
+		      size_t *pcount, const struct nft_among_pair *new)
+{
+	int i;
+
+	/* nftables automatically sorts set elements from smallest to largest,
+	 * insert sorted so extension comparison works */
+
+	for (i = 0; i < *pcount; i++) {
+		if (memcmp(new, &pairs[i], sizeof(*new)) < 0)
+			break;
+	}
+	memmove(&pairs[i + 1], &pairs[i], sizeof(*pairs) * (*pcount - i));
+	memcpy(&pairs[i], new, sizeof(*new));
+	(*pcount)++;
+}
+
+#endif
diff --git a/iptables/nft-cache.c b/iptables/nft-cache.c
new file mode 100644
index 0000000..6b6e6da
--- /dev/null
+++ b/iptables/nft-cache.c
@@ -0,0 +1,772 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xtables.h>
+
+#include <linux/netfilter/nf_tables.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/gen.h>
+#include <libnftnl/set.h>
+#include <libnftnl/table.h>
+
+#include "nft.h"
+#include "nft-cache.h"
+#include "nft-chain.h"
+
+static void cache_chain_list_insert(struct list_head *list, const char *name)
+{
+	struct cache_chain *pos = NULL, *new;
+
+	list_for_each_entry(pos, list, head) {
+		int cmp = strcmp(pos->name, name);
+
+		if (!cmp)
+			return;
+		if (cmp > 0)
+			break;
+	}
+
+	new = xtables_malloc(sizeof(*new));
+	new->name = strdup(name);
+	list_add_tail(&new->head, pos ? &pos->head : list);
+}
+
+void nft_cache_level_set(struct nft_handle *h, int level,
+			 const struct nft_cmd *cmd)
+{
+	struct nft_cache_req *req = &h->cache_req;
+
+	if (level > req->level)
+		req->level = level;
+
+	if (!cmd || !cmd->table || req->all_chains)
+		return;
+
+	if (!req->table)
+		req->table = strdup(cmd->table);
+	else
+		assert(!strcmp(req->table, cmd->table));
+
+	if (!cmd->chain) {
+		req->all_chains = true;
+		return;
+	}
+
+	cache_chain_list_insert(&req->chain_list, cmd->chain);
+	if (cmd->rename)
+		cache_chain_list_insert(&req->chain_list, cmd->rename);
+	if (cmd->jumpto)
+		cache_chain_list_insert(&req->chain_list, cmd->jumpto);
+}
+
+static int genid_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t *genid = data;
+	struct nftnl_gen *gen;
+
+	gen = nftnl_gen_alloc();
+	if (!gen)
+		return MNL_CB_ERROR;
+
+	if (nftnl_gen_nlmsg_parse(nlh, gen) < 0)
+		goto out;
+
+	*genid = nftnl_gen_get_u32(gen, NFTNL_GEN_ID);
+
+	nftnl_gen_free(gen);
+	return MNL_CB_STOP;
+out:
+	nftnl_gen_free(gen);
+	return MNL_CB_ERROR;
+}
+
+static void mnl_genid_get(struct nft_handle *h, uint32_t *genid)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	int ret;
+
+	nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETGEN, 0, 0, h->seq);
+	ret = mnl_talk(h, nlh, genid_cb, genid);
+	if (ret == 0)
+		return;
+
+	xtables_error(RESOURCE_PROBLEM,
+		      "Could not fetch rule set generation id: %s\n", nft_strerror(errno));
+}
+
+static int nftnl_table_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nftnl_table *nftnl = nftnl_table_alloc();
+	const struct builtin_table *t;
+	struct nft_handle *h = data;
+	const char *name;
+
+	if (!nftnl)
+		return MNL_CB_OK;
+
+	if (nftnl_table_nlmsg_parse(nlh, nftnl) < 0)
+		goto out;
+
+	name = nftnl_table_get_str(nftnl, NFTNL_TABLE_NAME);
+	if (!name)
+		goto out;
+
+	t = nft_table_builtin_find(h, name);
+	if (!t)
+		goto out;
+
+	h->cache->table[t->type].exists = true;
+out:
+	nftnl_table_free(nftnl);
+	return MNL_CB_OK;
+}
+
+static int fetch_table_cache(struct nft_handle *h)
+{
+	struct nlmsghdr *nlh;
+	char buf[16536];
+	int i, ret;
+
+	nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, h->family,
+					NLM_F_DUMP, h->seq);
+
+	ret = mnl_talk(h, nlh, nftnl_table_list_cb, h);
+	if (ret < 0 && errno == EINTR)
+		assert(nft_restart(h) >= 0);
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+		enum nft_table_type type = h->tables[i].type;
+
+		if (!h->tables[i].name)
+			continue;
+
+		h->cache->table[type].chains = nft_chain_list_alloc();
+
+		h->cache->table[type].sets = nftnl_set_list_alloc();
+		if (!h->cache->table[type].sets)
+			return 0;
+	}
+
+	return 1;
+}
+
+static uint32_t djb_hash(const char *key)
+{
+	uint32_t i, hash = 5381;
+
+	for (i = 0; i < strlen(key); i++)
+		hash = ((hash << 5) + hash) + key[i];
+
+	return hash;
+}
+
+static struct hlist_head *chain_name_hlist(struct nft_handle *h,
+					   const struct builtin_table *t,
+					   const char *chain)
+{
+	int key = djb_hash(chain) % CHAIN_NAME_HSIZE;
+
+	return &h->cache->table[t->type].chains->names[key];
+}
+
+struct nft_chain *
+nft_chain_find(struct nft_handle *h, const char *table, const char *chain)
+{
+	const struct builtin_table *t;
+	struct hlist_node *node;
+	struct nft_chain *c;
+
+	t = nft_table_builtin_find(h, table);
+	if (!t)
+		return NULL;
+
+	hlist_for_each_entry(c, node, chain_name_hlist(h, t, chain), hnode) {
+		if (!strcmp(nftnl_chain_get_str(c->nftnl, NFTNL_CHAIN_NAME),
+			    chain))
+			return c;
+	}
+	return NULL;
+}
+
+int nft_cache_add_chain(struct nft_handle *h, const struct builtin_table *t,
+			struct nftnl_chain *c)
+{
+	const char *cname = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+	struct nft_chain *nc = nft_chain_alloc(c);
+
+	if (nftnl_chain_is_set(c, NFTNL_CHAIN_HOOKNUM)) {
+		uint32_t hooknum = nftnl_chain_get_u32(c, NFTNL_CHAIN_HOOKNUM);
+
+		if (hooknum >= NF_INET_NUMHOOKS) {
+			nft_chain_free(nc);
+			return -EINVAL;
+		}
+
+		if (h->cache->table[t->type].base_chains[hooknum]) {
+			nft_chain_free(nc);
+			return -EEXIST;
+		}
+
+		h->cache->table[t->type].base_chains[hooknum] = nc;
+	} else {
+		struct nft_chain_list *clist = h->cache->table[t->type].chains;
+		struct list_head *pos = &clist->list;
+		struct nft_chain *cur;
+		const char *n;
+
+		list_for_each_entry(cur, &clist->list, head) {
+			n = nftnl_chain_get_str(cur->nftnl, NFTNL_CHAIN_NAME);
+			if (strcmp(cname, n) <= 0) {
+				pos = &cur->head;
+				break;
+			}
+		}
+		list_add_tail(&nc->head, pos);
+	}
+	hlist_add_head(&nc->hnode, chain_name_hlist(h, t, cname));
+	return 0;
+}
+
+struct nftnl_chain_list_cb_data {
+	struct nft_handle *h;
+	const struct builtin_table *t;
+};
+
+static int nftnl_chain_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nftnl_chain_list_cb_data *d = data;
+	const struct builtin_table *t = d->t;
+	struct nft_handle *h = d->h;
+	struct nftnl_chain *c;
+	const char *tname;
+
+	c = nftnl_chain_alloc();
+	if (c == NULL)
+		goto err;
+
+	if (nftnl_chain_nlmsg_parse(nlh, c) < 0)
+		goto out;
+
+	tname = nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE);
+
+	if (!t) {
+		t = nft_table_builtin_find(h, tname);
+		if (!t)
+			goto out;
+	} else if (strcmp(t->name, tname)) {
+		goto out;
+	}
+
+	if (nft_cache_add_chain(h, t, c))
+		goto out;
+
+	return MNL_CB_OK;
+out:
+	nftnl_chain_free(c);
+err:
+	return MNL_CB_OK;
+}
+
+struct nftnl_set_list_cb_data {
+	struct nft_handle *h;
+	const struct builtin_table *t;
+};
+
+static int nftnl_set_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nftnl_set_list_cb_data *d = data;
+	const struct builtin_table *t = d->t;
+	struct nftnl_set_list *list;
+	struct nft_handle *h = d->h;
+	const char *tname, *sname;
+	struct nftnl_set *s;
+
+	s = nftnl_set_alloc();
+	if (s == NULL)
+		return MNL_CB_OK;
+
+	if (nftnl_set_nlmsg_parse(nlh, s) < 0)
+		goto out_free;
+
+	tname = nftnl_set_get_str(s, NFTNL_SET_TABLE);
+
+	if (!t)
+		t = nft_table_builtin_find(h, tname);
+	else if (strcmp(t->name, tname))
+		goto out_free;
+
+	if (!t)
+		goto out_free;
+
+	list = h->cache->table[t->type].sets;
+	sname = nftnl_set_get_str(s, NFTNL_SET_NAME);
+
+	if (nftnl_set_list_lookup_byname(list, sname))
+		goto out_free;
+
+	nftnl_set_list_add_tail(s, list);
+
+	return MNL_CB_OK;
+out_free:
+	nftnl_set_free(s);
+	return MNL_CB_OK;
+}
+
+static int set_elem_cb(const struct nlmsghdr *nlh, void *data)
+{
+	return nftnl_set_elems_nlmsg_parse(nlh, data) ? -1 : MNL_CB_OK;
+}
+
+static bool set_has_elements(struct nftnl_set *s)
+{
+	struct nftnl_set_elems_iter *iter;
+	bool ret = false;
+
+	iter = nftnl_set_elems_iter_create(s);
+	if (iter) {
+		ret = !!nftnl_set_elems_iter_cur(iter);
+		nftnl_set_elems_iter_destroy(iter);
+	}
+	return ret;
+}
+
+static int set_fetch_elem_cb(struct nftnl_set *s, void *data)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nft_handle *h = data;
+	struct nlmsghdr *nlh;
+
+	if (set_has_elements(s))
+		return 0;
+
+	nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM, h->family,
+				    NLM_F_DUMP, h->seq);
+	nftnl_set_elems_nlmsg_build_payload(nlh, s);
+
+	return mnl_talk(h, nlh, set_elem_cb, s);
+}
+
+static int fetch_set_cache(struct nft_handle *h,
+			   const struct builtin_table *t, const char *set)
+{
+	struct nftnl_set_list_cb_data d = {
+		.h = h,
+		.t = t,
+	};
+	uint16_t flags = NLM_F_DUMP;
+	struct nftnl_set *s = NULL;
+	struct nlmsghdr *nlh;
+	char buf[16536];
+	int i, ret;
+
+	if (t) {
+		s = nftnl_set_alloc();
+		if (!s)
+			return -1;
+
+		nftnl_set_set_str(s, NFTNL_SET_TABLE, t->name);
+
+		if (set) {
+			nftnl_set_set_str(s, NFTNL_SET_NAME, set);
+			flags = NLM_F_ACK;
+		}
+	}
+
+	nlh = nftnl_set_nlmsg_build_hdr(buf, NFT_MSG_GETSET,
+					h->family, flags, h->seq);
+
+	if (s) {
+		nftnl_set_nlmsg_build_payload(nlh, s);
+		nftnl_set_free(s);
+	}
+
+	ret = mnl_talk(h, nlh, nftnl_set_list_cb, &d);
+	if (ret < 0 && errno == EINTR) {
+		assert(nft_restart(h) >= 0);
+		return ret;
+	}
+
+	if (t) {
+		nftnl_set_list_foreach(h->cache->table[t->type].sets,
+				       set_fetch_elem_cb, h);
+	} else {
+		for (i = 0; i < NFT_TABLE_MAX; i++) {
+			enum nft_table_type type = h->tables[i].type;
+
+			if (!h->tables[i].name)
+				continue;
+
+			nftnl_set_list_foreach(h->cache->table[type].sets,
+					       set_fetch_elem_cb, h);
+		}
+	}
+	return ret;
+}
+
+static int __fetch_chain_cache(struct nft_handle *h,
+			       const struct builtin_table *t,
+			       const struct nftnl_chain *c)
+{
+	struct nftnl_chain_list_cb_data d = {
+		.h = h,
+		.t = t,
+	};
+	char buf[16536];
+	struct nlmsghdr *nlh;
+	int ret;
+
+	nlh = nftnl_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, h->family,
+					  c ? NLM_F_ACK : NLM_F_DUMP, h->seq);
+	if (c)
+		nftnl_chain_nlmsg_build_payload(nlh, c);
+
+	ret = mnl_talk(h, nlh, nftnl_chain_list_cb, &d);
+	if (ret < 0 && errno == EINTR)
+		assert(nft_restart(h) >= 0);
+
+	return ret;
+}
+
+static int fetch_chain_cache(struct nft_handle *h,
+			     const struct builtin_table *t,
+			     struct list_head *chains)
+{
+	struct cache_chain *cc;
+	struct nftnl_chain *c;
+	int rc, ret = 0;
+
+	if (!chains)
+		return __fetch_chain_cache(h, t, NULL);
+
+	assert(t);
+
+	c = nftnl_chain_alloc();
+	if (!c)
+		return -1;
+
+	nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, t->name);
+
+	list_for_each_entry(cc, chains, head) {
+		nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, cc->name);
+		rc = __fetch_chain_cache(h, t, c);
+		if (rc)
+			ret = rc;
+	}
+
+	nftnl_chain_free(c);
+	return ret;
+}
+
+static int nftnl_rule_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nftnl_chain *c = data;
+	struct nftnl_rule *r;
+
+	r = nftnl_rule_alloc();
+	if (r == NULL)
+		return MNL_CB_OK;
+
+	if (nftnl_rule_nlmsg_parse(nlh, r) < 0) {
+		nftnl_rule_free(r);
+		return MNL_CB_OK;
+	}
+
+	nftnl_chain_rule_add_tail(r, c);
+	return MNL_CB_OK;
+}
+
+static int nft_rule_list_update(struct nft_chain *nc, void *data)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	struct nft_handle *h = data;
+	char buf[16536];
+	struct nlmsghdr *nlh;
+	struct nftnl_rule *rule;
+	int ret;
+
+	if (nftnl_rule_lookup_byindex(c, 0))
+		return 0;
+
+	rule = nftnl_rule_alloc();
+	if (!rule)
+		return -1;
+
+	nftnl_rule_set_str(rule, NFTNL_RULE_TABLE,
+			   nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE));
+	nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN,
+			   nftnl_chain_get_str(c, NFTNL_CHAIN_NAME));
+
+	nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, h->family,
+					NLM_F_DUMP, h->seq);
+	nftnl_rule_nlmsg_build_payload(nlh, rule);
+
+	ret = mnl_talk(h, nlh, nftnl_rule_list_cb, c);
+	if (ret < 0 && errno == EINTR)
+		assert(nft_restart(h) >= 0);
+
+	nftnl_rule_free(rule);
+
+	if (h->family == NFPROTO_BRIDGE)
+		nft_bridge_chain_postprocess(h, c);
+
+	return 0;
+}
+
+static int fetch_rule_cache(struct nft_handle *h,
+			    const struct builtin_table *t)
+{
+	int i;
+
+	if (t)
+		return nft_chain_foreach(h, t->name, nft_rule_list_update, h);
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+
+		if (!h->tables[i].name)
+			continue;
+
+		if (nft_chain_foreach(h, h->tables[i].name,
+				      nft_rule_list_update, h))
+			return -1;
+	}
+	return 0;
+}
+
+static int flush_cache(struct nft_handle *h, struct nft_cache *c,
+		       const char *tablename);
+
+static void
+__nft_build_cache(struct nft_handle *h)
+{
+	struct nft_cache_req *req = &h->cache_req;
+	const struct builtin_table *t = NULL;
+	struct list_head *chains = NULL;
+	uint32_t genid_check;
+
+	if (h->cache_init)
+		return;
+
+	if (req->table) {
+		t = nft_table_builtin_find(h, req->table);
+		if (!req->all_chains)
+			chains = &req->chain_list;
+	}
+
+	h->cache_init = true;
+retry:
+	mnl_genid_get(h, &h->nft_genid);
+
+	if (req->level >= NFT_CL_TABLES)
+		fetch_table_cache(h);
+	if (req->level == NFT_CL_FAKE)
+		goto genid_check;
+	if (req->level >= NFT_CL_CHAINS)
+		fetch_chain_cache(h, t, chains);
+	if (req->level >= NFT_CL_SETS)
+		fetch_set_cache(h, t, NULL);
+	if (req->level >= NFT_CL_RULES)
+		fetch_rule_cache(h, t);
+genid_check:
+	mnl_genid_get(h, &genid_check);
+	if (h->nft_genid != genid_check) {
+		flush_cache(h, h->cache, NULL);
+		goto retry;
+	}
+}
+
+static void __nft_flush_cache(struct nft_handle *h)
+{
+	if (!h->cache_index) {
+		h->cache_index++;
+		h->cache = &h->__cache[h->cache_index];
+	} else {
+		flush_chain_cache(h, NULL);
+	}
+}
+
+static int ____flush_rule_cache(struct nftnl_rule *r, void *data)
+{
+	nftnl_rule_list_del(r);
+	nftnl_rule_free(r);
+
+	return 0;
+}
+
+static int __flush_rule_cache(struct nft_chain *c, void *data)
+{
+	return nftnl_rule_foreach(c->nftnl, ____flush_rule_cache, NULL);
+}
+
+int flush_rule_cache(struct nft_handle *h, const char *table,
+		     struct nft_chain *c)
+{
+	if (c)
+		return __flush_rule_cache(c, NULL);
+
+	nft_chain_foreach(h, table, __flush_rule_cache, NULL);
+	return 0;
+}
+
+static int __flush_chain_cache(struct nft_chain *c, void *data)
+{
+	nft_chain_list_del(c);
+	nft_chain_free(c);
+
+	return 0;
+}
+
+static int __flush_set_cache(struct nftnl_set *s, void *data)
+{
+	nftnl_set_list_del(s);
+	nftnl_set_free(s);
+
+	return 0;
+}
+
+static void flush_base_chain_cache(struct nft_chain **base_chains)
+{
+	int i;
+
+	for (i = 0; i < NF_INET_NUMHOOKS; i++) {
+		if (!base_chains[i])
+			continue;
+		hlist_del(&base_chains[i]->hnode);
+		nft_chain_free(base_chains[i]);
+		base_chains[i] = NULL;
+	}
+}
+
+static int flush_cache(struct nft_handle *h, struct nft_cache *c,
+		       const char *tablename)
+{
+	const struct builtin_table *table;
+	int i;
+
+	if (tablename) {
+		table = nft_table_builtin_find(h, tablename);
+		if (!table)
+			return 0;
+
+		flush_base_chain_cache(c->table[table->type].base_chains);
+		nft_chain_foreach(h, tablename, __flush_chain_cache, NULL);
+
+		if (c->table[table->type].sets)
+			nftnl_set_list_foreach(c->table[table->type].sets,
+					       __flush_set_cache, NULL);
+		return 0;
+	}
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+		if (h->tables[i].name == NULL)
+			continue;
+
+		flush_base_chain_cache(c->table[i].base_chains);
+		if (c->table[i].chains) {
+			nft_chain_list_free(c->table[i].chains);
+			c->table[i].chains = NULL;
+		}
+
+		if (c->table[i].sets) {
+			nftnl_set_list_free(c->table[i].sets);
+			c->table[i].sets = NULL;
+		}
+
+		c->table[i].exists = false;
+	}
+
+	return 1;
+}
+
+void flush_chain_cache(struct nft_handle *h, const char *tablename)
+{
+	if (!h->cache_init)
+		return;
+
+	if (flush_cache(h, h->cache, tablename))
+		h->cache_init = false;
+}
+
+void nft_rebuild_cache(struct nft_handle *h)
+{
+	if (h->cache_init) {
+		__nft_flush_cache(h);
+		h->cache_init = false;
+	}
+
+	__nft_build_cache(h);
+}
+
+void nft_cache_build(struct nft_handle *h)
+{
+	struct nft_cache_req *req = &h->cache_req;
+	const struct builtin_table *t = NULL;
+	int i;
+
+	if (req->table)
+		t = nft_table_builtin_find(h, req->table);
+
+	/* fetch builtin chains as well (if existing) so nft_xt_builtin_init()
+	 * doesn't override policies by accident */
+	if (t && !req->all_chains) {
+		for (i = 0; i < NF_INET_NUMHOOKS; i++) {
+			const char *cname = t->chains[i].name;
+
+			if (!cname)
+				break;
+			cache_chain_list_insert(&req->chain_list, cname);
+		}
+	}
+
+	__nft_build_cache(h);
+}
+
+void nft_release_cache(struct nft_handle *h)
+{
+	struct nft_cache_req *req = &h->cache_req;
+	struct cache_chain *cc, *cc_tmp;
+
+	while (h->cache_index)
+		flush_cache(h, &h->__cache[h->cache_index--], NULL);
+	flush_cache(h, &h->__cache[0], NULL);
+	h->cache = &h->__cache[0];
+	h->cache_init = false;
+
+	if (req->level != NFT_CL_FAKE)
+		req->level = NFT_CL_TABLES;
+	if (req->table) {
+		free(req->table);
+		req->table = NULL;
+	}
+	req->all_chains = false;
+	list_for_each_entry_safe(cc, cc_tmp, &req->chain_list, head) {
+		list_del(&cc->head);
+		free(cc->name);
+		free(cc);
+	}
+}
+
+struct nftnl_set_list *
+nft_set_list_get(struct nft_handle *h, const char *table, const char *set)
+{
+	const struct builtin_table *t;
+
+	t = nft_table_builtin_find(h, table);
+	if (!t)
+		return NULL;
+
+	return h->cache->table[t->type].sets;
+}
diff --git a/iptables/nft-cache.h b/iptables/nft-cache.h
new file mode 100644
index 0000000..20d96be
--- /dev/null
+++ b/iptables/nft-cache.h
@@ -0,0 +1,26 @@
+#ifndef _NFT_CACHE_H_
+#define _NFT_CACHE_H_
+
+struct nft_handle;
+struct nft_chain;
+struct nft_cmd;
+struct builtin_table;
+
+void nft_cache_level_set(struct nft_handle *h, int level,
+			 const struct nft_cmd *cmd);
+void nft_rebuild_cache(struct nft_handle *h);
+void nft_release_cache(struct nft_handle *h);
+void flush_chain_cache(struct nft_handle *h, const char *tablename);
+int flush_rule_cache(struct nft_handle *h, const char *table,
+		     struct nft_chain *c);
+void nft_cache_build(struct nft_handle *h);
+int nft_cache_add_chain(struct nft_handle *h, const struct builtin_table *t,
+			struct nftnl_chain *c);
+
+struct nft_chain *
+nft_chain_find(struct nft_handle *h, const char *table, const char *chain);
+
+struct nftnl_set_list *
+nft_set_list_get(struct nft_handle *h, const char *table, const char *set);
+
+#endif /* _NFT_CACHE_H_ */
diff --git a/iptables/nft-chain.c b/iptables/nft-chain.c
new file mode 100644
index 0000000..e954170
--- /dev/null
+++ b/iptables/nft-chain.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020  Red Hat GmbH.  Author: Phil Sutter <phil@nwl.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 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <stdlib.h>
+#include <xtables.h>
+
+#include "nft-chain.h"
+
+struct nft_chain *nft_chain_alloc(struct nftnl_chain *nftnl)
+{
+	struct nft_chain *c = xtables_malloc(sizeof(*c));
+
+	INIT_LIST_HEAD(&c->head);
+	c->nftnl = nftnl;
+
+	return c;
+}
+
+void nft_chain_free(struct nft_chain *c)
+{
+	if (c->nftnl)
+		nftnl_chain_free(c->nftnl);
+	free(c);
+}
+
+struct nft_chain_list *nft_chain_list_alloc(void)
+{
+	struct nft_chain_list *list = xtables_malloc(sizeof(*list));
+	int i;
+
+	INIT_LIST_HEAD(&list->list);
+	for (i = 0; i < CHAIN_NAME_HSIZE; i++)
+		INIT_HLIST_HEAD(&list->names[i]);
+
+	return list;
+}
+
+void nft_chain_list_del(struct nft_chain *c)
+{
+	list_del(&c->head);
+	hlist_del(&c->hnode);
+}
+
+void nft_chain_list_free(struct nft_chain_list *list)
+{
+	struct nft_chain *c, *c2;
+
+	list_for_each_entry_safe(c, c2, &list->list, head) {
+		nft_chain_list_del(c);
+		nft_chain_free(c);
+	}
+	free(list);
+}
diff --git a/iptables/nft-chain.h b/iptables/nft-chain.h
new file mode 100644
index 0000000..137f4b7
--- /dev/null
+++ b/iptables/nft-chain.h
@@ -0,0 +1,29 @@
+#ifndef _NFT_CHAIN_H_
+#define _NFT_CHAIN_H_
+
+#include <libnftnl/chain.h>
+#include <libiptc/linux_list.h>
+
+struct nft_handle;
+
+struct nft_chain {
+	struct list_head	head;
+	struct hlist_node	hnode;
+	struct nftnl_chain	*nftnl;
+};
+
+#define CHAIN_NAME_HSIZE	512
+
+struct nft_chain_list {
+	struct list_head	list;
+	struct hlist_head	names[CHAIN_NAME_HSIZE];
+};
+
+struct nft_chain *nft_chain_alloc(struct nftnl_chain *nftnl);
+void nft_chain_free(struct nft_chain *c);
+
+struct nft_chain_list *nft_chain_list_alloc(void);
+void nft_chain_list_free(struct nft_chain_list *list);
+void nft_chain_list_del(struct nft_chain *c);
+
+#endif /* _NFT_CHAIN_H_ */
diff --git a/iptables/nft-cmd.c b/iptables/nft-cmd.c
new file mode 100644
index 0000000..5d33f1f
--- /dev/null
+++ b/iptables/nft-cmd.c
@@ -0,0 +1,395 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "nft.h"
+#include "nft-cmd.h"
+
+struct nft_cmd *nft_cmd_new(struct nft_handle *h, int command,
+			    const char *table, const char *chain,
+			    struct iptables_command_state *state,
+			    int rulenum, bool verbose)
+{
+	struct nftnl_rule *rule;
+	struct nft_cmd *cmd;
+
+	cmd = calloc(1, sizeof(struct nft_cmd));
+	if (!cmd)
+		return NULL;
+
+	cmd->command = command;
+	cmd->table = strdup(table);
+	if (chain)
+		cmd->chain = strdup(chain);
+	cmd->rulenum = rulenum;
+	cmd->verbose = verbose;
+
+	if (state) {
+		rule = nft_rule_new(h, chain, table, state);
+		if (!rule)
+			return NULL;
+
+		cmd->obj.rule = rule;
+
+		if (!state->target && strlen(state->jumpto) > 0)
+			cmd->jumpto = strdup(state->jumpto);
+	}
+
+	list_add_tail(&cmd->head, &h->cmd_list);
+
+	return cmd;
+}
+
+void nft_cmd_free(struct nft_cmd *cmd)
+{
+	free((void *)cmd->table);
+	free((void *)cmd->chain);
+	free((void *)cmd->policy);
+	free((void *)cmd->rename);
+	free((void *)cmd->jumpto);
+
+	switch (cmd->command) {
+	case NFT_COMPAT_RULE_CHECK:
+	case NFT_COMPAT_RULE_DELETE:
+		if (cmd->obj.rule)
+			nftnl_rule_free(cmd->obj.rule);
+		break;
+	default:
+		break;
+	}
+
+	list_del(&cmd->head);
+	free(cmd);
+}
+
+static void nft_cmd_rule_bridge(struct nft_handle *h, const struct nft_cmd *cmd)
+{
+	const struct builtin_table *t;
+
+	t = nft_table_builtin_find(h, cmd->table);
+	if (!t)
+		return;
+
+	/* Since ebtables user-defined chain policies are implemented as last
+	 * rule in nftables, rule cache is required here to treat them right.
+	 */
+	if (h->family == NFPROTO_BRIDGE &&
+	    !nft_chain_builtin_find(t, cmd->chain))
+		nft_cache_level_set(h, NFT_CL_RULES, cmd);
+	else
+		nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+}
+
+int nft_cmd_rule_append(struct nft_handle *h, const char *chain,
+			const char *table, struct iptables_command_state *state,
+			void *ref, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_APPEND, table, chain, state, -1,
+			  verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cmd_rule_bridge(h, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_insert(struct nft_handle *h, const char *chain,
+			const char *table, struct iptables_command_state *state,
+			int rulenum, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_INSERT, table, chain, state,
+			  rulenum, verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cmd_rule_bridge(h, cmd);
+
+	if (cmd->rulenum > 0)
+		nft_cache_level_set(h, NFT_CL_RULES, cmd);
+	else
+		nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_delete(struct nft_handle *h, const char *chain,
+			const char *table, struct iptables_command_state *state,
+			bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_DELETE, table, chain, state,
+			  -1, verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_delete_num(struct nft_handle *h, const char *chain,
+			    const char *table, int rulenum, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_DELETE, table, chain, NULL,
+			  rulenum, verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_flush(struct nft_handle *h, const char *chain,
+		       const char *table, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_FLUSH, table, chain, NULL, -1,
+			  verbose);
+	if (!cmd)
+		return 0;
+
+	if (chain || verbose)
+		nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+	else
+		nft_cache_level_set(h, NFT_CL_TABLES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_zero_counters(struct nft_handle *h, const char *chain,
+				const char *table, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_ZERO, table, chain, NULL, -1,
+			  verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_user_add(struct nft_handle *h, const char *chain,
+			   const char *table)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_USER_ADD, table, chain, NULL, -1,
+			  false);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_user_del(struct nft_handle *h, const char *chain,
+			   const char *table, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_USER_DEL, table, chain, NULL, -1,
+			  verbose);
+	if (!cmd)
+		return 0;
+
+	/* This triggers nft_bridge_chain_postprocess() when fetching the
+	 * rule cache.
+	 */
+	if (h->family == NFPROTO_BRIDGE)
+		nft_cache_level_set(h, NFT_CL_RULES, cmd);
+	else
+		nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_user_rename(struct nft_handle *h,const char *chain,
+			      const char *table, const char *newname)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_RENAME, table, chain, NULL, -1,
+			  false);
+	if (!cmd)
+		return 0;
+
+	cmd->rename = strdup(newname);
+
+	nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_list(struct nft_handle *h, const char *chain,
+		      const char *table, int rulenum, unsigned int format)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_LIST, table, chain, NULL, rulenum,
+			  false);
+	if (!cmd)
+		return 0;
+
+	cmd->format = format;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_replace(struct nft_handle *h, const char *chain,
+			 const char *table, void *data, int rulenum,
+			 bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_REPLACE, table, chain, data,
+			  rulenum, verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_check(struct nft_handle *h, const char *chain,
+		       const char *table, void *data, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_CHECK, table, chain, data, -1,
+			  verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_set(struct nft_handle *h, const char *table,
+		      const char *chain, const char *policy,
+		      const struct xt_counters *counters)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_UPDATE, table, chain, NULL, -1,
+			  false);
+	if (!cmd)
+		return 0;
+
+	cmd->policy = strdup(policy);
+	if (counters)
+		cmd->counters = *counters;
+
+	nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_table_flush(struct nft_handle *h, const char *table, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	if (verbose) {
+		return nft_cmd_rule_flush(h, NULL, table, verbose) &&
+		       nft_cmd_chain_user_del(h, NULL, table, verbose);
+	}
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_TABLE_FLUSH, table, NULL, NULL, -1,
+			  false);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_TABLES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_restore(struct nft_handle *h, const char *chain,
+			  const char *table)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_RESTORE, table, chain, NULL, -1,
+			  false);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_zero_counters(struct nft_handle *h, const char *chain,
+			       const char *table, int rulenum)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_ZERO, table, chain, NULL, rulenum,
+			  false);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_list_save(struct nft_handle *h, const char *chain,
+			   const char *table, int rulenum, int counters)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_SAVE, table, chain, NULL, rulenum,
+			  false);
+	if (!cmd)
+		return 0;
+
+	cmd->counters_save = counters;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int ebt_cmd_user_chain_policy(struct nft_handle *h, const char *table,
+                              const char *chain, const char *policy)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE, table, chain,
+			  NULL, -1, false);
+	if (!cmd)
+		return 0;
+
+	cmd->policy = strdup(policy);
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
diff --git a/iptables/nft-cmd.h b/iptables/nft-cmd.h
new file mode 100644
index 0000000..ecf7655
--- /dev/null
+++ b/iptables/nft-cmd.h
@@ -0,0 +1,79 @@
+#ifndef _NFT_CMD_H_
+#define _NFT_CMD_H_
+
+#include <libiptc/linux_list.h>
+#include <stdbool.h>
+#include "nft.h"
+
+struct nftnl_rule;
+
+struct nft_cmd {
+	struct list_head		head;
+	int				command;
+	const char			*table;
+	const char			*chain;
+	const char			*jumpto;
+	int				rulenum;
+	bool				verbose;
+	unsigned int			format;
+	struct {
+		struct nftnl_rule	*rule;
+		struct nftnl_set	*set;
+	} obj;
+	const char			*policy;
+	struct xt_counters		counters;
+	const char			*rename;
+	int				counters_save;
+};
+
+struct nft_cmd *nft_cmd_new(struct nft_handle *h, int command,
+			    const char *table, const char *chain,
+			    struct iptables_command_state *state,
+			    int rulenum, bool verbose);
+void nft_cmd_free(struct nft_cmd *cmd);
+
+int nft_cmd_rule_append(struct nft_handle *h, const char *chain,
+			const char *table, struct iptables_command_state *state,
+                        void *ref, bool verbose);
+int nft_cmd_rule_insert(struct nft_handle *h, const char *chain,
+			const char *table, struct iptables_command_state *state,
+			int rulenum, bool verbose);
+int nft_cmd_rule_delete(struct nft_handle *h, const char *chain,
+                        const char *table, struct iptables_command_state *state,
+			bool verbose);
+int nft_cmd_rule_delete_num(struct nft_handle *h, const char *chain,
+			    const char *table, int rulenum, bool verbose);
+int nft_cmd_rule_flush(struct nft_handle *h, const char *chain,
+		       const char *table, bool verbose);
+int nft_cmd_zero_counters(struct nft_handle *h, const char *chain,
+			  const char *table, bool verbose);
+int nft_cmd_chain_user_add(struct nft_handle *h, const char *chain,
+			   const char *table);
+int nft_cmd_chain_user_del(struct nft_handle *h, const char *chain,
+			   const char *table, bool verbose);
+int nft_cmd_chain_zero_counters(struct nft_handle *h, const char *chain,
+				const char *table, bool verbose);
+int nft_cmd_rule_list(struct nft_handle *h, const char *chain,
+		      const char *table, int rulenum, unsigned int format);
+int nft_cmd_rule_check(struct nft_handle *h, const char *chain,
+                       const char *table, void *data, bool verbose);
+int nft_cmd_chain_set(struct nft_handle *h, const char *table,
+		      const char *chain, const char *policy,
+		      const struct xt_counters *counters);
+int nft_cmd_chain_user_rename(struct nft_handle *h,const char *chain,
+			      const char *table, const char *newname);
+int nft_cmd_rule_replace(struct nft_handle *h, const char *chain,
+			 const char *table, void *data, int rulenum,
+			 bool verbose);
+int nft_cmd_table_flush(struct nft_handle *h, const char *table, bool verbose);
+int nft_cmd_chain_restore(struct nft_handle *h, const char *chain,
+			  const char *table);
+int nft_cmd_rule_zero_counters(struct nft_handle *h, const char *chain,
+			       const char *table, int rulenum);
+int nft_cmd_rule_list_save(struct nft_handle *h, const char *chain,
+			   const char *table, int rulenum, int counters);
+int ebt_cmd_user_chain_policy(struct nft_handle *h, const char *table,
+			      const char *chain, const char *policy);
+void nft_cmd_table_new(struct nft_handle *h, const char *table);
+
+#endif /* _NFT_CMD_H_ */
diff --git a/iptables/nft-ipv4.c b/iptables/nft-ipv4.c
new file mode 100644
index 0000000..fdc15c6
--- /dev/null
+++ b/iptables/nft-ipv4.c
@@ -0,0 +1,464 @@
+/*
+ * (C) 2012-2014 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include <netdb.h>
+
+#include <xtables.h>
+
+#include <linux/netfilter/nf_tables.h>
+
+#include "nft.h"
+#include "nft-shared.h"
+
+static int nft_ipv4_add(struct nft_handle *h, struct nftnl_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct xtables_rule_match *matchp;
+	uint32_t op;
+	int ret;
+
+	if (cs->fw.ip.iniface[0] != '\0') {
+		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_VIA_IN);
+		add_iniface(r, cs->fw.ip.iniface, op);
+	}
+
+	if (cs->fw.ip.outiface[0] != '\0') {
+		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_VIA_OUT);
+		add_outiface(r, cs->fw.ip.outiface, op);
+	}
+
+	if (cs->fw.ip.proto != 0) {
+		op = nft_invflags2cmp(cs->fw.ip.invflags, XT_INV_PROTO);
+		add_l4proto(r, cs->fw.ip.proto, op);
+	}
+
+	if (cs->fw.ip.src.s_addr || cs->fw.ip.smsk.s_addr || cs->fw.ip.invflags & IPT_INV_SRCIP) {
+		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_SRCIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 offsetof(struct iphdr, saddr),
+			 &cs->fw.ip.src.s_addr, &cs->fw.ip.smsk.s_addr,
+			 sizeof(struct in_addr), op);
+	}
+	if (cs->fw.ip.dst.s_addr || cs->fw.ip.dmsk.s_addr || cs->fw.ip.invflags & IPT_INV_DSTIP) {
+		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_DSTIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 offsetof(struct iphdr, daddr),
+			 &cs->fw.ip.dst.s_addr, &cs->fw.ip.dmsk.s_addr,
+			 sizeof(struct in_addr), op);
+	}
+	if (cs->fw.ip.flags & IPT_F_FRAG) {
+		add_payload(r, offsetof(struct iphdr, frag_off), 2,
+			    NFT_PAYLOAD_NETWORK_HEADER);
+		/* get the 13 bits that contain the fragment offset */
+		add_bitwise_u16(r, htons(0x1fff), 0);
+
+		/* if offset is non-zero, this is a fragment */
+		op = NFT_CMP_NEQ;
+		if (cs->fw.ip.invflags & IPT_INV_FRAG)
+			op = NFT_CMP_EQ;
+
+		add_cmp_u16(r, 0, op);
+	}
+
+	add_compat(r, cs->fw.ip.proto, cs->fw.ip.invflags & XT_INV_PROTO);
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		ret = add_match(h, r, matchp->match->m);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Counters need to me added before the target, otherwise they are
+	 * increased for each rule because of the way nf_tables works.
+	 */
+	if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
+		return -1;
+
+	return add_action(r, cs, !!(cs->fw.ip.flags & IPT_F_GOTO));
+}
+
+static bool nft_ipv4_is_same(const void *data_a,
+			     const void *data_b)
+{
+	const struct iptables_command_state *a = data_a;
+	const struct iptables_command_state *b = data_b;
+
+	if (a->fw.ip.src.s_addr != b->fw.ip.src.s_addr
+	    || a->fw.ip.dst.s_addr != b->fw.ip.dst.s_addr
+	    || a->fw.ip.smsk.s_addr != b->fw.ip.smsk.s_addr
+	    || a->fw.ip.dmsk.s_addr != b->fw.ip.dmsk.s_addr
+	    || a->fw.ip.proto != b->fw.ip.proto
+	    || a->fw.ip.flags != b->fw.ip.flags
+	    || a->fw.ip.invflags != b->fw.ip.invflags) {
+		DEBUGP("different src/dst/proto/flags/invflags\n");
+		return false;
+	}
+
+	return is_same_interfaces(a->fw.ip.iniface, a->fw.ip.outiface,
+				  a->fw.ip.iniface_mask, a->fw.ip.outiface_mask,
+				  b->fw.ip.iniface, b->fw.ip.outiface,
+				  b->fw.ip.iniface_mask, b->fw.ip.outiface_mask);
+}
+
+static void get_frag(struct nft_xt_ctx *ctx, struct nftnl_expr *e, bool *inv)
+{
+	uint8_t op;
+
+	/* we assume correct mask and xor */
+	if (!(ctx->flags & NFT_XT_CTX_BITWISE))
+		return;
+
+	/* we assume correct data */
+	op = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP);
+	if (op == NFT_CMP_EQ)
+		*inv = true;
+	else
+		*inv = false;
+
+	ctx->flags &= ~NFT_XT_CTX_BITWISE;
+}
+
+static const char *mask_to_str(uint32_t mask)
+{
+	static char mask_str[sizeof("255.255.255.255")];
+	uint32_t bits, hmask = ntohl(mask);
+	struct in_addr mask_addr = {
+		.s_addr = mask,
+	};
+	int i;
+
+	if (mask == 0xFFFFFFFFU) {
+		sprintf(mask_str, "32");
+		return mask_str;
+	}
+
+	i    = 32;
+	bits = 0xFFFFFFFEU;
+	while (--i >= 0 && hmask != bits)
+		bits <<= 1;
+	if (i >= 0)
+		sprintf(mask_str, "%u", i);
+	else
+		sprintf(mask_str, "%s", inet_ntoa(mask_addr));
+
+	return mask_str;
+}
+
+static void nft_ipv4_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+				void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	switch (ctx->meta.key) {
+	case NFT_META_L4PROTO:
+		cs->fw.ip.proto = nftnl_expr_get_u8(e, NFTNL_EXPR_CMP_DATA);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			cs->fw.ip.invflags |= XT_INV_PROTO;
+		return;
+	default:
+		break;
+	}
+
+	parse_meta(e, ctx->meta.key, cs->fw.ip.iniface, cs->fw.ip.iniface_mask,
+		   cs->fw.ip.outiface, cs->fw.ip.outiface_mask,
+		   &cs->fw.ip.invflags);
+}
+
+static void parse_mask_ipv4(struct nft_xt_ctx *ctx, struct in_addr *mask)
+{
+	mask->s_addr = ctx->bitwise.mask[0];
+}
+
+static void nft_ipv4_parse_payload(struct nft_xt_ctx *ctx,
+				   struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct in_addr addr;
+	uint8_t proto;
+	bool inv;
+
+	switch(ctx->payload.offset) {
+	case offsetof(struct iphdr, saddr):
+		get_cmp_data(e, &addr, sizeof(addr), &inv);
+		cs->fw.ip.src.s_addr = addr.s_addr;
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+			parse_mask_ipv4(ctx, &cs->fw.ip.smsk);
+			ctx->flags &= ~NFT_XT_CTX_BITWISE;
+		} else {
+			memset(&cs->fw.ip.smsk, 0xff,
+			       min(ctx->payload.len, sizeof(struct in_addr)));
+		}
+
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_SRCIP;
+		break;
+	case offsetof(struct iphdr, daddr):
+		get_cmp_data(e, &addr, sizeof(addr), &inv);
+		cs->fw.ip.dst.s_addr = addr.s_addr;
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+			parse_mask_ipv4(ctx, &cs->fw.ip.dmsk);
+			ctx->flags &= ~NFT_XT_CTX_BITWISE;
+		} else {
+			memset(&cs->fw.ip.dmsk, 0xff,
+			       min(ctx->payload.len, sizeof(struct in_addr)));
+		}
+
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_DSTIP;
+		break;
+	case offsetof(struct iphdr, protocol):
+		get_cmp_data(e, &proto, sizeof(proto), &inv);
+		cs->fw.ip.proto = proto;
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_PROTO;
+		break;
+	case offsetof(struct iphdr, frag_off):
+		cs->fw.ip.flags |= IPT_F_FRAG;
+		inv = false;
+		get_frag(ctx, e, &inv);
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_FRAG;
+		break;
+	default:
+		DEBUGP("unknown payload offset %d\n", ctx->payload.offset);
+		break;
+	}
+}
+
+static void nft_ipv4_parse_immediate(const char *jumpto, bool nft_goto,
+				     void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->jumpto = jumpto;
+
+	if (nft_goto)
+		cs->fw.ip.flags |= IPT_F_GOTO;
+}
+
+static void print_fragment(unsigned int flags, unsigned int invflags,
+			   unsigned int format)
+{
+	if (!(format & FMT_OPTIONS))
+		return;
+
+	if (format & FMT_NOTABLE)
+		fputs("opt ", stdout);
+	fputc(invflags & IPT_INV_FRAG ? '!' : '-', stdout);
+	fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout);
+	fputc(' ', stdout);
+}
+
+static void nft_ipv4_print_rule(struct nft_handle *h, struct nftnl_rule *r,
+				unsigned int num, unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	nft_rule_to_iptables_command_state(h, r, &cs);
+
+	print_rule_details(&cs, cs.jumpto, cs.fw.ip.flags,
+			   cs.fw.ip.invflags, cs.fw.ip.proto, num, format);
+	print_fragment(cs.fw.ip.flags, cs.fw.ip.invflags, format);
+	print_ifaces(cs.fw.ip.iniface, cs.fw.ip.outiface, cs.fw.ip.invflags,
+		     format);
+	print_ipv4_addresses(&cs.fw, format);
+
+	if (format & FMT_NOTABLE)
+		fputs("  ", stdout);
+
+#ifdef IPT_F_GOTO
+	if (cs.fw.ip.flags & IPT_F_GOTO)
+		printf("[goto] ");
+#endif
+
+	print_matches_and_target(&cs, format);
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+
+	nft_clear_iptables_command_state(&cs);
+}
+
+static void save_ipv4_addr(char letter, const struct in_addr *addr,
+			   uint32_t mask, int invert)
+{
+	if (!mask && !invert && !addr->s_addr)
+		return;
+
+	printf("%s-%c %s/%s ", invert ? "! " : "", letter, inet_ntoa(*addr),
+	       mask_to_str(mask));
+}
+
+static void nft_ipv4_save_rule(const void *data, unsigned int format)
+{
+	const struct iptables_command_state *cs = data;
+
+	save_ipv4_addr('s', &cs->fw.ip.src, cs->fw.ip.smsk.s_addr,
+		       cs->fw.ip.invflags & IPT_INV_SRCIP);
+	save_ipv4_addr('d', &cs->fw.ip.dst, cs->fw.ip.dmsk.s_addr,
+		       cs->fw.ip.invflags & IPT_INV_DSTIP);
+
+	save_rule_details(cs, cs->fw.ip.invflags, cs->fw.ip.proto,
+			  cs->fw.ip.iniface, cs->fw.ip.iniface_mask,
+			  cs->fw.ip.outiface, cs->fw.ip.outiface_mask);
+
+	if (cs->fw.ip.flags & IPT_F_FRAG) {
+		if (cs->fw.ip.invflags & IPT_INV_FRAG)
+			printf("! ");
+		printf("-f ");
+	}
+
+	save_matches_and_target(cs, cs->fw.ip.flags & IPT_F_GOTO,
+				&cs->fw, format);
+}
+
+static void nft_ipv4_proto_parse(struct iptables_command_state *cs,
+				 struct xtables_args *args)
+{
+	cs->fw.ip.proto = args->proto;
+	cs->fw.ip.invflags = args->invflags;
+}
+
+static void nft_ipv4_post_parse(int command,
+				struct iptables_command_state *cs,
+				struct xtables_args *args)
+{
+	cs->fw.ip.flags = args->flags;
+	/* We already set invflags in proto_parse, but we need to refresh it
+	 * to include new parsed options.
+	 */
+	cs->fw.ip.invflags = args->invflags;
+
+	strncpy(cs->fw.ip.iniface, args->iniface, IFNAMSIZ);
+	memcpy(cs->fw.ip.iniface_mask,
+	       args->iniface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	strncpy(cs->fw.ip.outiface, args->outiface, IFNAMSIZ);
+	memcpy(cs->fw.ip.outiface_mask,
+	       args->outiface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	if (args->goto_set)
+		cs->fw.ip.flags |= IPT_F_GOTO;
+
+	cs->counters.pcnt = args->pcnt_cnt;
+	cs->counters.bcnt = args->bcnt_cnt;
+
+	if (command & (CMD_REPLACE | CMD_INSERT |
+			CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+		if (!(cs->options & OPT_DESTINATION))
+			args->dhostnetworkmask = "0.0.0.0/0";
+		if (!(cs->options & OPT_SOURCE))
+			args->shostnetworkmask = "0.0.0.0/0";
+	}
+
+	if (args->shostnetworkmask)
+		xtables_ipparse_multiple(args->shostnetworkmask,
+					 &args->s.addr.v4, &args->s.mask.v4,
+					 &args->s.naddrs);
+	if (args->dhostnetworkmask)
+		xtables_ipparse_multiple(args->dhostnetworkmask,
+					 &args->d.addr.v4, &args->d.mask.v4,
+					 &args->d.naddrs);
+
+	if ((args->s.naddrs > 1 || args->d.naddrs > 1) &&
+	    (cs->fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))
+		xtables_error(PARAMETER_PROBLEM,
+			      "! not allowed with multiple"
+			      " source or destination IP addresses");
+}
+
+static int nft_ipv4_xlate(const void *data, struct xt_xlate *xl)
+{
+	const struct iptables_command_state *cs = data;
+	const char *comment;
+	int ret;
+
+	xlate_ifname(xl, "iifname", cs->fw.ip.iniface,
+		     cs->fw.ip.invflags & IPT_INV_VIA_IN);
+	xlate_ifname(xl, "oifname", cs->fw.ip.outiface,
+		     cs->fw.ip.invflags & IPT_INV_VIA_OUT);
+
+	if (cs->fw.ip.flags & IPT_F_FRAG) {
+		xt_xlate_add(xl, "ip frag-off & 0x1fff %s%x ",
+			   cs->fw.ip.invflags & IPT_INV_FRAG? "" : "!= ", 0);
+	}
+
+	if (cs->fw.ip.proto != 0) {
+		const struct protoent *pent =
+			getprotobynumber(cs->fw.ip.proto);
+		char protonum[sizeof("65535")];
+		const char *name = protonum;
+
+		snprintf(protonum, sizeof(protonum), "%u",
+			 cs->fw.ip.proto);
+
+		if (!pent || !xlate_find_match(cs, pent->p_name)) {
+			if (pent)
+				name = pent->p_name;
+			xt_xlate_add(xl, "ip protocol %s%s ",
+				   cs->fw.ip.invflags & IPT_INV_PROTO ?
+					"!= " : "", name);
+		}
+	}
+
+	if (cs->fw.ip.src.s_addr != 0) {
+		xt_xlate_add(xl, "ip saddr %s%s%s ",
+			   cs->fw.ip.invflags & IPT_INV_SRCIP ? "!= " : "",
+			   inet_ntoa(cs->fw.ip.src),
+			   xtables_ipmask_to_numeric(&cs->fw.ip.smsk));
+	}
+	if (cs->fw.ip.dst.s_addr != 0) {
+		xt_xlate_add(xl, "ip daddr %s%s%s ",
+			   cs->fw.ip.invflags & IPT_INV_DSTIP ? "!= " : "",
+			   inet_ntoa(cs->fw.ip.dst),
+			   xtables_ipmask_to_numeric(&cs->fw.ip.dmsk));
+	}
+
+	ret = xlate_matches(cs, xl);
+	if (!ret)
+		return ret;
+
+	/* Always add counters per rule, as in iptables */
+	xt_xlate_add(xl, "counter");
+	ret = xlate_action(cs, !!(cs->fw.ip.flags & IPT_F_GOTO), xl);
+
+	comment = xt_xlate_get_comment(xl);
+	if (comment)
+		xt_xlate_add(xl, " comment %s", comment);
+
+	return ret;
+}
+
+struct nft_family_ops nft_family_ops_ipv4 = {
+	.add			= nft_ipv4_add,
+	.is_same		= nft_ipv4_is_same,
+	.parse_meta		= nft_ipv4_parse_meta,
+	.parse_payload		= nft_ipv4_parse_payload,
+	.parse_immediate	= nft_ipv4_parse_immediate,
+	.print_header		= print_header,
+	.print_rule		= nft_ipv4_print_rule,
+	.save_rule		= nft_ipv4_save_rule,
+	.save_chain		= nft_ipv46_save_chain,
+	.proto_parse		= nft_ipv4_proto_parse,
+	.post_parse		= nft_ipv4_post_parse,
+	.parse_target		= nft_ipv46_parse_target,
+	.rule_to_cs		= nft_rule_to_iptables_command_state,
+	.clear_cs		= nft_clear_iptables_command_state,
+	.xlate			= nft_ipv4_xlate,
+};
diff --git a/iptables/nft-ipv6.c b/iptables/nft-ipv6.c
new file mode 100644
index 0000000..130ad3e
--- /dev/null
+++ b/iptables/nft-ipv6.c
@@ -0,0 +1,416 @@
+/*
+ * (C) 2012-2014 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/ip6.h>
+#include <netdb.h>
+
+#include <xtables.h>
+
+#include <linux/netfilter/nf_tables.h>
+#include "nft.h"
+#include "nft-shared.h"
+
+static int nft_ipv6_add(struct nft_handle *h, struct nftnl_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct xtables_rule_match *matchp;
+	uint32_t op;
+	int ret;
+
+	if (cs->fw6.ipv6.iniface[0] != '\0') {
+		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_VIA_IN);
+		add_iniface(r, cs->fw6.ipv6.iniface, op);
+	}
+
+	if (cs->fw6.ipv6.outiface[0] != '\0') {
+		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_VIA_OUT);
+		add_outiface(r, cs->fw6.ipv6.outiface, op);
+	}
+
+	if (cs->fw6.ipv6.proto != 0) {
+		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, XT_INV_PROTO);
+		add_l4proto(r, cs->fw6.ipv6.proto, op);
+	}
+
+	if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.src) ||
+	    !IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.smsk) ||
+	    (cs->fw6.ipv6.invflags & IPT_INV_SRCIP)) {
+		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_SRCIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 offsetof(struct ip6_hdr, ip6_src),
+			 &cs->fw6.ipv6.src, &cs->fw6.ipv6.smsk,
+			 sizeof(struct in6_addr), op);
+	}
+	if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dst) ||
+	    !IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dmsk) ||
+	    (cs->fw6.ipv6.invflags & IPT_INV_DSTIP)) {
+		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_DSTIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 offsetof(struct ip6_hdr, ip6_dst),
+			 &cs->fw6.ipv6.dst, &cs->fw6.ipv6.dmsk,
+			 sizeof(struct in6_addr), op);
+	}
+	add_compat(r, cs->fw6.ipv6.proto, cs->fw6.ipv6.invflags & XT_INV_PROTO);
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		ret = add_match(h, r, matchp->match->m);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Counters need to me added before the target, otherwise they are
+	 * increased for each rule because of the way nf_tables works.
+	 */
+	if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
+		return -1;
+
+	return add_action(r, cs, !!(cs->fw6.ipv6.flags & IP6T_F_GOTO));
+}
+
+static bool nft_ipv6_is_same(const void *data_a,
+			     const void *data_b)
+{
+	const struct iptables_command_state *a = data_a;
+	const struct iptables_command_state *b = data_b;
+
+	if (memcmp(a->fw6.ipv6.src.s6_addr, b->fw6.ipv6.src.s6_addr,
+		   sizeof(struct in6_addr)) != 0
+	    || memcmp(a->fw6.ipv6.dst.s6_addr, b->fw6.ipv6.dst.s6_addr,
+		    sizeof(struct in6_addr)) != 0
+	    || a->fw6.ipv6.proto != b->fw6.ipv6.proto
+	    || a->fw6.ipv6.flags != b->fw6.ipv6.flags
+	    || a->fw6.ipv6.invflags != b->fw6.ipv6.invflags) {
+		DEBUGP("different src/dst/proto/flags/invflags\n");
+		return false;
+	}
+
+	return is_same_interfaces(a->fw6.ipv6.iniface, a->fw6.ipv6.outiface,
+				  a->fw6.ipv6.iniface_mask,
+				  a->fw6.ipv6.outiface_mask,
+				  b->fw6.ipv6.iniface, b->fw6.ipv6.outiface,
+				  b->fw6.ipv6.iniface_mask,
+				  b->fw6.ipv6.outiface_mask);
+}
+
+static void nft_ipv6_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+				void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	switch (ctx->meta.key) {
+	case NFT_META_L4PROTO:
+		cs->fw6.ipv6.proto = nftnl_expr_get_u8(e, NFTNL_EXPR_CMP_DATA);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			cs->fw6.ipv6.invflags |= XT_INV_PROTO;
+		return;
+	default:
+		break;
+	}
+
+	parse_meta(e, ctx->meta.key, cs->fw6.ipv6.iniface,
+		   cs->fw6.ipv6.iniface_mask, cs->fw6.ipv6.outiface,
+		   cs->fw6.ipv6.outiface_mask, &cs->fw6.ipv6.invflags);
+}
+
+static void parse_mask_ipv6(struct nft_xt_ctx *ctx, struct in6_addr *mask)
+{
+	memcpy(mask, ctx->bitwise.mask, sizeof(struct in6_addr));
+}
+
+static void nft_ipv6_parse_payload(struct nft_xt_ctx *ctx,
+				   struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct in6_addr addr;
+	uint8_t proto;
+	bool inv;
+
+	switch (ctx->payload.offset) {
+	case offsetof(struct ip6_hdr, ip6_src):
+		get_cmp_data(e, &addr, sizeof(addr), &inv);
+		memcpy(cs->fw6.ipv6.src.s6_addr, &addr, sizeof(addr));
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+			parse_mask_ipv6(ctx, &cs->fw6.ipv6.smsk);
+			ctx->flags &= ~NFT_XT_CTX_BITWISE;
+		} else {
+			memset(&cs->fw6.ipv6.smsk, 0xff,
+			       min(ctx->payload.len, sizeof(struct in6_addr)));
+		}
+
+		if (inv)
+			cs->fw6.ipv6.invflags |= IP6T_INV_SRCIP;
+		break;
+	case offsetof(struct ip6_hdr, ip6_dst):
+		get_cmp_data(e, &addr, sizeof(addr), &inv);
+		memcpy(cs->fw6.ipv6.dst.s6_addr, &addr, sizeof(addr));
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+			parse_mask_ipv6(ctx, &cs->fw6.ipv6.dmsk);
+			ctx->flags &= ~NFT_XT_CTX_BITWISE;
+		} else {
+			memset(&cs->fw6.ipv6.dmsk, 0xff,
+			       min(ctx->payload.len, sizeof(struct in6_addr)));
+		}
+
+		if (inv)
+			cs->fw6.ipv6.invflags |= IP6T_INV_DSTIP;
+		break;
+	case offsetof(struct ip6_hdr, ip6_nxt):
+		get_cmp_data(e, &proto, sizeof(proto), &inv);
+		cs->fw6.ipv6.proto = proto;
+		if (inv)
+			cs->fw6.ipv6.invflags |= IP6T_INV_PROTO;
+	default:
+		DEBUGP("unknown payload offset %d\n", ctx->payload.offset);
+		break;
+	}
+}
+
+static void nft_ipv6_parse_immediate(const char *jumpto, bool nft_goto,
+				     void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->jumpto = jumpto;
+
+	if (nft_goto)
+		cs->fw6.ipv6.flags |= IP6T_F_GOTO;
+}
+
+static void nft_ipv6_print_rule(struct nft_handle *h, struct nftnl_rule *r,
+				unsigned int num, unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	nft_rule_to_iptables_command_state(h, r, &cs);
+
+	print_rule_details(&cs, cs.jumpto, cs.fw6.ipv6.flags,
+			   cs.fw6.ipv6.invflags, cs.fw6.ipv6.proto,
+			   num, format);
+	if (format & FMT_OPTIONS) {
+		if (format & FMT_NOTABLE)
+			fputs("opt ", stdout);
+		fputs("   ", stdout);
+	}
+	print_ifaces(cs.fw6.ipv6.iniface, cs.fw6.ipv6.outiface,
+		     cs.fw6.ipv6.invflags, format);
+	print_ipv6_addresses(&cs.fw6, format);
+
+	if (format & FMT_NOTABLE)
+		fputs("  ", stdout);
+
+	if (cs.fw6.ipv6.flags & IP6T_F_GOTO)
+		printf("[goto] ");
+
+	print_matches_and_target(&cs, format);
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+
+	nft_clear_iptables_command_state(&cs);
+}
+
+static void save_ipv6_addr(char letter, const struct in6_addr *addr,
+			   const struct in6_addr *mask,
+			   int invert)
+{
+	char addr_str[INET6_ADDRSTRLEN];
+	int l = xtables_ip6mask_to_cidr(mask);
+
+	if (!invert && l == 0)
+		return;
+
+	printf("%s-%c %s",
+		invert ? "! " : "", letter,
+		inet_ntop(AF_INET6, addr, addr_str, sizeof(addr_str)));
+
+	if (l == -1)
+		printf("/%s ", inet_ntop(AF_INET6, mask, addr_str, sizeof(addr_str)));
+	else
+		printf("/%d ", l);
+}
+
+static void nft_ipv6_save_rule(const void *data, unsigned int format)
+{
+	const struct iptables_command_state *cs = data;
+
+	save_ipv6_addr('s', &cs->fw6.ipv6.src, &cs->fw6.ipv6.smsk,
+		       cs->fw6.ipv6.invflags & IP6T_INV_SRCIP);
+	save_ipv6_addr('d', &cs->fw6.ipv6.dst, &cs->fw6.ipv6.dmsk,
+		       cs->fw6.ipv6.invflags & IP6T_INV_DSTIP);
+
+	save_rule_details(cs, cs->fw6.ipv6.invflags, cs->fw6.ipv6.proto,
+			  cs->fw6.ipv6.iniface, cs->fw6.ipv6.iniface_mask,
+			  cs->fw6.ipv6.outiface, cs->fw6.ipv6.outiface_mask);
+
+	save_matches_and_target(cs, cs->fw6.ipv6.flags & IP6T_F_GOTO,
+				&cs->fw6, format);
+}
+
+/* These are invalid numbers as upper layer protocol */
+static int is_exthdr(uint16_t proto)
+{
+	return (proto == IPPROTO_ROUTING ||
+		proto == IPPROTO_FRAGMENT ||
+		proto == IPPROTO_AH ||
+		proto == IPPROTO_DSTOPTS);
+}
+
+static void nft_ipv6_proto_parse(struct iptables_command_state *cs,
+				 struct xtables_args *args)
+{
+	cs->fw6.ipv6.proto = args->proto;
+	cs->fw6.ipv6.invflags = args->invflags;
+
+	if (is_exthdr(cs->fw6.ipv6.proto)
+	    && (cs->fw6.ipv6.invflags & XT_INV_PROTO) == 0)
+		fprintf(stderr,
+			"Warning: never matched protocol: %s. "
+			"use extension match instead.\n",
+			cs->protocol);
+}
+
+static void nft_ipv6_post_parse(int command, struct iptables_command_state *cs,
+				struct xtables_args *args)
+{
+	cs->fw6.ipv6.flags = args->flags;
+	/* We already set invflags in proto_parse, but we need to refresh it
+	 * to include new parsed options.
+	 */
+	cs->fw6.ipv6.invflags = args->invflags;
+
+	strncpy(cs->fw6.ipv6.iniface, args->iniface, IFNAMSIZ);
+	memcpy(cs->fw6.ipv6.iniface_mask,
+	       args->iniface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	strncpy(cs->fw6.ipv6.outiface, args->outiface, IFNAMSIZ);
+	memcpy(cs->fw6.ipv6.outiface_mask,
+	       args->outiface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	if (args->goto_set)
+		cs->fw6.ipv6.flags |= IP6T_F_GOTO;
+
+	cs->fw6.counters.pcnt = args->pcnt_cnt;
+	cs->fw6.counters.bcnt = args->bcnt_cnt;
+
+	if (command & (CMD_REPLACE | CMD_INSERT |
+			CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+		if (!(cs->options & OPT_DESTINATION))
+			args->dhostnetworkmask = "::0/0";
+		if (!(cs->options & OPT_SOURCE))
+			args->shostnetworkmask = "::0/0";
+	}
+
+	if (args->shostnetworkmask)
+		xtables_ip6parse_multiple(args->shostnetworkmask,
+					  &args->s.addr.v6,
+					  &args->s.mask.v6,
+					  &args->s.naddrs);
+	if (args->dhostnetworkmask)
+		xtables_ip6parse_multiple(args->dhostnetworkmask,
+					  &args->d.addr.v6,
+					  &args->d.mask.v6,
+					  &args->d.naddrs);
+
+	if ((args->s.naddrs > 1 || args->d.naddrs > 1) &&
+	    (cs->fw6.ipv6.invflags & (IP6T_INV_SRCIP | IP6T_INV_DSTIP)))
+		xtables_error(PARAMETER_PROBLEM,
+			      "! not allowed with multiple"
+			      " source or destination IP addresses");
+}
+
+static void xlate_ipv6_addr(const char *selector, const struct in6_addr *addr,
+			    const struct in6_addr *mask,
+			    int invert, struct xt_xlate *xl)
+{
+	char addr_str[INET6_ADDRSTRLEN];
+
+	if (!invert && IN6_IS_ADDR_UNSPECIFIED(addr))
+		return;
+
+	inet_ntop(AF_INET6, addr, addr_str, INET6_ADDRSTRLEN);
+	xt_xlate_add(xl, "%s %s%s%s ", selector, invert ? "!= " : "", addr_str,
+			xtables_ip6mask_to_numeric(mask));
+}
+
+static int nft_ipv6_xlate(const void *data, struct xt_xlate *xl)
+{
+	const struct iptables_command_state *cs = data;
+	const char *comment;
+	int ret;
+
+	xlate_ifname(xl, "iifname", cs->fw6.ipv6.iniface,
+		     cs->fw6.ipv6.invflags & IP6T_INV_VIA_IN);
+	xlate_ifname(xl, "oifname", cs->fw6.ipv6.outiface,
+		     cs->fw6.ipv6.invflags & IP6T_INV_VIA_OUT);
+
+	if (cs->fw6.ipv6.proto != 0) {
+		const struct protoent *pent =
+			getprotobynumber(cs->fw6.ipv6.proto);
+		char protonum[sizeof("65535")];
+		const char *name = protonum;
+
+		snprintf(protonum, sizeof(protonum), "%u",
+			 cs->fw6.ipv6.proto);
+
+		if (!pent || !xlate_find_match(cs, pent->p_name)) {
+			if (pent)
+				name = pent->p_name;
+			xt_xlate_add(xl, "meta l4proto %s%s ",
+				   cs->fw6.ipv6.invflags & IP6T_INV_PROTO ?
+					"!= " : "", name);
+		}
+
+	}
+
+	xlate_ipv6_addr("ip6 saddr", &cs->fw6.ipv6.src, &cs->fw6.ipv6.smsk,
+			cs->fw6.ipv6.invflags & IP6T_INV_SRCIP, xl);
+	xlate_ipv6_addr("ip6 daddr", &cs->fw6.ipv6.dst, &cs->fw6.ipv6.dmsk,
+			cs->fw6.ipv6.invflags & IP6T_INV_DSTIP, xl);
+
+	ret = xlate_matches(cs, xl);
+	if (!ret)
+		return ret;
+
+	/* Always add counters per rule, as in iptables */
+	xt_xlate_add(xl, "counter");
+	ret = xlate_action(cs, !!(cs->fw6.ipv6.flags & IP6T_F_GOTO), xl);
+
+	comment = xt_xlate_get_comment(xl);
+	if (comment)
+		xt_xlate_add(xl, " comment %s", comment);
+
+	return ret;
+}
+
+struct nft_family_ops nft_family_ops_ipv6 = {
+	.add			= nft_ipv6_add,
+	.is_same		= nft_ipv6_is_same,
+	.parse_meta		= nft_ipv6_parse_meta,
+	.parse_payload		= nft_ipv6_parse_payload,
+	.parse_immediate	= nft_ipv6_parse_immediate,
+	.print_header		= print_header,
+	.print_rule		= nft_ipv6_print_rule,
+	.save_rule		= nft_ipv6_save_rule,
+	.save_chain		= nft_ipv46_save_chain,
+	.proto_parse		= nft_ipv6_proto_parse,
+	.post_parse		= nft_ipv6_post_parse,
+	.parse_target		= nft_ipv46_parse_target,
+	.rule_to_cs		= nft_rule_to_iptables_command_state,
+	.clear_cs		= nft_clear_iptables_command_state,
+	.xlate			= nft_ipv6_xlate,
+};
diff --git a/iptables/nft-shared.c b/iptables/nft-shared.c
new file mode 100644
index 0000000..10553ab
--- /dev/null
+++ b/iptables/nft-shared.c
@@ -0,0 +1,1016 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <netdb.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <xtables.h>
+
+#include <linux/netfilter/xt_comment.h>
+#include <linux/netfilter/xt_limit.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/rule.h>
+#include <libnftnl/expr.h>
+
+#include "nft-shared.h"
+#include "nft-bridge.h"
+#include "xshared.h"
+#include "nft.h"
+
+extern struct nft_family_ops nft_family_ops_ipv4;
+extern struct nft_family_ops nft_family_ops_ipv6;
+extern struct nft_family_ops nft_family_ops_arp;
+extern struct nft_family_ops nft_family_ops_bridge;
+
+void add_meta(struct nftnl_rule *r, uint32_t key)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("meta");
+	if (expr == NULL)
+		return;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_META_KEY, key);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_META_DREG, NFT_REG_1);
+
+	nftnl_rule_add_expr(r, expr);
+}
+
+void add_payload(struct nftnl_rule *r, int offset, int len, uint32_t base)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("payload");
+	if (expr == NULL)
+		return;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_BASE, base);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_DREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_OFFSET, offset);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_LEN, len);
+
+	nftnl_rule_add_expr(r, expr);
+}
+
+/* bitwise operation is = sreg & mask ^ xor */
+void add_bitwise_u16(struct nftnl_rule *r, uint16_t mask, uint16_t xor)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("bitwise");
+	if (expr == NULL)
+		return;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_SREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_DREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_LEN, sizeof(uint16_t));
+	nftnl_expr_set(expr, NFTNL_EXPR_BITWISE_MASK, &mask, sizeof(uint16_t));
+	nftnl_expr_set(expr, NFTNL_EXPR_BITWISE_XOR, &xor, sizeof(uint16_t));
+
+	nftnl_rule_add_expr(r, expr);
+}
+
+void add_bitwise(struct nftnl_rule *r, uint8_t *mask, size_t len)
+{
+	struct nftnl_expr *expr;
+	uint32_t xor[4] = { 0 };
+
+	expr = nftnl_expr_alloc("bitwise");
+	if (expr == NULL)
+		return;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_SREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_DREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_LEN, len);
+	nftnl_expr_set(expr, NFTNL_EXPR_BITWISE_MASK, mask, len);
+	nftnl_expr_set(expr, NFTNL_EXPR_BITWISE_XOR, &xor, len);
+
+	nftnl_rule_add_expr(r, expr);
+}
+
+void add_cmp_ptr(struct nftnl_rule *r, uint32_t op, void *data, size_t len)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("cmp");
+	if (expr == NULL)
+		return;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_CMP_SREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_CMP_OP, op);
+	nftnl_expr_set(expr, NFTNL_EXPR_CMP_DATA, data, len);
+
+	nftnl_rule_add_expr(r, expr);
+}
+
+void add_cmp_u8(struct nftnl_rule *r, uint8_t val, uint32_t op)
+{
+	add_cmp_ptr(r, op, &val, sizeof(val));
+}
+
+void add_cmp_u16(struct nftnl_rule *r, uint16_t val, uint32_t op)
+{
+	add_cmp_ptr(r, op, &val, sizeof(val));
+}
+
+void add_cmp_u32(struct nftnl_rule *r, uint32_t val, uint32_t op)
+{
+	add_cmp_ptr(r, op, &val, sizeof(val));
+}
+
+void add_iniface(struct nftnl_rule *r, char *iface, uint32_t op)
+{
+	int iface_len;
+
+	iface_len = strlen(iface);
+
+	add_meta(r, NFT_META_IIFNAME);
+	if (iface[iface_len - 1] == '+') {
+		if (iface_len > 1)
+			add_cmp_ptr(r, op, iface, iface_len - 1);
+	} else
+		add_cmp_ptr(r, op, iface, iface_len + 1);
+}
+
+void add_outiface(struct nftnl_rule *r, char *iface, uint32_t op)
+{
+	int iface_len;
+
+	iface_len = strlen(iface);
+
+	add_meta(r, NFT_META_OIFNAME);
+	if (iface[iface_len - 1] == '+') {
+		if (iface_len > 1)
+			add_cmp_ptr(r, op, iface, iface_len - 1);
+	} else
+		add_cmp_ptr(r, op, iface, iface_len + 1);
+}
+
+void add_addr(struct nftnl_rule *r, enum nft_payload_bases base, int offset,
+	      void *data, void *mask, size_t len, uint32_t op)
+{
+	const unsigned char *m = mask;
+	bool bitwise = false;
+	int i;
+
+	for (i = 0; i < len; i++) {
+		if (m[i] != 0xff) {
+			bitwise = m[i] != 0;
+			break;
+		}
+	}
+
+	if (!bitwise)
+		len = i;
+
+	add_payload(r, offset, len, base);
+
+	if (bitwise)
+		add_bitwise(r, mask, len);
+
+	add_cmp_ptr(r, op, data, len);
+}
+
+void add_proto(struct nftnl_rule *r, int offset, size_t len,
+	       uint8_t proto, uint32_t op)
+{
+	add_payload(r, offset, len, NFT_PAYLOAD_NETWORK_HEADER);
+	add_cmp_u8(r, proto, op);
+}
+
+void add_l4proto(struct nftnl_rule *r, uint8_t proto, uint32_t op)
+{
+	add_meta(r, NFT_META_L4PROTO);
+	add_cmp_u8(r, proto, op);
+}
+
+bool is_same_interfaces(const char *a_iniface, const char *a_outiface,
+			unsigned const char *a_iniface_mask,
+			unsigned const char *a_outiface_mask,
+			const char *b_iniface, const char *b_outiface,
+			unsigned const char *b_iniface_mask,
+			unsigned const char *b_outiface_mask)
+{
+	int i;
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (a_iniface_mask[i] != b_iniface_mask[i]) {
+			DEBUGP("different iniface mask %x, %x (%d)\n",
+			a_iniface_mask[i] & 0xff, b_iniface_mask[i] & 0xff, i);
+			return false;
+		}
+		if ((a_iniface[i] & a_iniface_mask[i])
+		    != (b_iniface[i] & b_iniface_mask[i])) {
+			DEBUGP("different iniface\n");
+			return false;
+		}
+		if (a_outiface_mask[i] != b_outiface_mask[i]) {
+			DEBUGP("different outiface mask\n");
+			return false;
+		}
+		if ((a_outiface[i] & a_outiface_mask[i])
+		    != (b_outiface[i] & b_outiface_mask[i])) {
+			DEBUGP("different outiface\n");
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static void parse_ifname(const char *name, unsigned int len, char *dst, unsigned char *mask)
+{
+	if (len == 0)
+		return;
+
+	memcpy(dst, name, len);
+	if (name[len - 1] == '\0') {
+		if (mask)
+			memset(mask, 0xff, len);
+		return;
+	}
+
+	if (len >= IFNAMSIZ)
+		return;
+
+	/* wildcard */
+	dst[len++] = '+';
+	if (len >= IFNAMSIZ)
+		return;
+	dst[len++] = 0;
+	if (mask)
+		memset(mask, 0xff, len - 2);
+}
+
+int parse_meta(struct nftnl_expr *e, uint8_t key, char *iniface,
+		unsigned char *iniface_mask, char *outiface,
+		unsigned char *outiface_mask, uint8_t *invflags)
+{
+	uint32_t value;
+	const void *ifname;
+	uint32_t len;
+
+	switch(key) {
+	case NFT_META_IIF:
+		value = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_DATA);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_IN;
+
+		if_indextoname(value, iniface);
+
+		memset(iniface_mask, 0xff, strlen(iniface)+1);
+		break;
+	case NFT_META_OIF:
+		value = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_DATA);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_OUT;
+
+		if_indextoname(value, outiface);
+
+		memset(outiface_mask, 0xff, strlen(outiface)+1);
+		break;
+	case NFT_META_BRI_IIFNAME:
+	case NFT_META_IIFNAME:
+		ifname = nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_IN;
+
+		parse_ifname(ifname, len, iniface, iniface_mask);
+		break;
+	case NFT_META_BRI_OIFNAME:
+	case NFT_META_OIFNAME:
+		ifname = nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_OUT;
+
+		parse_ifname(ifname, len, outiface, outiface_mask);
+		break;
+	default:
+		return -1;
+	}
+
+	return 0;
+}
+
+static void nft_parse_target(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	uint32_t tg_len;
+	const char *targname = nftnl_expr_get_str(e, NFTNL_EXPR_TG_NAME);
+	const void *targinfo = nftnl_expr_get(e, NFTNL_EXPR_TG_INFO, &tg_len);
+	struct xtables_target *target;
+	struct xt_entry_target *t;
+	size_t size;
+	void *data = ctx->cs;
+
+	target = xtables_find_target(targname, XTF_TRY_LOAD);
+	if (target == NULL)
+		return;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target)) + tg_len;
+
+	t = xtables_calloc(1, size);
+	memcpy(&t->data, targinfo, tg_len);
+	t->u.target_size = size;
+	t->u.user.revision = nftnl_expr_get_u32(e, NFTNL_EXPR_TG_REV);
+	strcpy(t->u.user.name, target->name);
+
+	target->t = t;
+
+	ctx->h->ops->parse_target(target, data);
+}
+
+static void nft_parse_match(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	uint32_t mt_len;
+	const char *mt_name = nftnl_expr_get_str(e, NFTNL_EXPR_MT_NAME);
+	const void *mt_info = nftnl_expr_get(e, NFTNL_EXPR_MT_INFO, &mt_len);
+	struct xtables_match *match;
+	struct xtables_rule_match **matches;
+	struct xt_entry_match *m;
+
+	switch (ctx->h->family) {
+	case NFPROTO_IPV4:
+	case NFPROTO_IPV6:
+	case NFPROTO_BRIDGE:
+		matches = &ctx->cs->matches;
+		break;
+	default:
+		fprintf(stderr, "BUG: nft_parse_match() unknown family %d\n",
+			ctx->h->family);
+		exit(EXIT_FAILURE);
+	}
+
+	match = xtables_find_match(mt_name, XTF_TRY_LOAD, matches);
+	if (match == NULL)
+		return;
+
+	m = xtables_calloc(1, sizeof(struct xt_entry_match) + mt_len);
+	memcpy(&m->data, mt_info, mt_len);
+	m->u.match_size = mt_len + XT_ALIGN(sizeof(struct xt_entry_match));
+	m->u.user.revision = nftnl_expr_get_u32(e, NFTNL_EXPR_TG_REV);
+	strcpy(m->u.user.name, match->name);
+
+	match->m = m;
+
+	if (ctx->h->ops->parse_match != NULL)
+		ctx->h->ops->parse_match(match, ctx->cs);
+}
+
+void print_proto(uint16_t proto, int invert)
+{
+	const struct protoent *pent = getprotobynumber(proto);
+
+	if (invert)
+		printf("! ");
+
+	if (pent) {
+		printf("-p %s ", pent->p_name);
+		return;
+	}
+
+	printf("-p %u ", proto);
+}
+
+void get_cmp_data(struct nftnl_expr *e, void *data, size_t dlen, bool *inv)
+{
+	uint32_t len;
+	uint8_t op;
+
+	memcpy(data, nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len), dlen);
+	op = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP);
+	if (op == NFT_CMP_NEQ)
+		*inv = true;
+	else
+		*inv = false;
+}
+
+static void nft_meta_set_to_target(struct nft_xt_ctx *ctx)
+{
+	struct xtables_target *target;
+	struct xt_entry_target *t;
+	unsigned int size;
+	const char *targname;
+
+	switch (ctx->meta.key) {
+	case NFT_META_NFTRACE:
+		if (ctx->immediate.data[0] == 0)
+			return;
+		targname = "TRACE";
+		break;
+	default:
+		return;
+	}
+
+	target = xtables_find_target(targname, XTF_TRY_LOAD);
+	if (target == NULL)
+		return;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target)) + target->size;
+
+	t = xtables_calloc(1, size);
+	t->u.target_size = size;
+	t->u.user.revision = target->revision;
+	strcpy(t->u.user.name, targname);
+
+	target->t = t;
+
+	ctx->h->ops->parse_target(target, ctx->cs);
+}
+
+static void nft_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	ctx->meta.key = nftnl_expr_get_u32(e, NFTNL_EXPR_META_KEY);
+
+	if (nftnl_expr_is_set(e, NFTNL_EXPR_META_SREG) &&
+	    (ctx->flags & NFT_XT_CTX_IMMEDIATE) &&
+	     nftnl_expr_get_u32(e, NFTNL_EXPR_META_SREG) == ctx->immediate.reg) {
+		ctx->flags &= ~NFT_XT_CTX_IMMEDIATE;
+		nft_meta_set_to_target(ctx);
+		return;
+	}
+
+	ctx->reg = nftnl_expr_get_u32(e, NFTNL_EXPR_META_DREG);
+	ctx->flags |= NFT_XT_CTX_META;
+}
+
+static void nft_parse_payload(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	if (ctx->flags & NFT_XT_CTX_PAYLOAD) {
+		memcpy(&ctx->prev_payload, &ctx->payload,
+		       sizeof(ctx->prev_payload));
+		ctx->flags |= NFT_XT_CTX_PREV_PAYLOAD;
+	}
+
+	ctx->reg = nftnl_expr_get_u32(e, NFTNL_EXPR_META_DREG);
+	ctx->payload.base = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_BASE);
+	ctx->payload.offset = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET);
+	ctx->payload.len = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_LEN);
+	ctx->flags |= NFT_XT_CTX_PAYLOAD;
+}
+
+static void nft_parse_bitwise(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	uint32_t reg, len;
+	const void *data;
+
+	reg = nftnl_expr_get_u32(e, NFTNL_EXPR_BITWISE_SREG);
+	if (ctx->reg && reg != ctx->reg)
+		return;
+
+	data = nftnl_expr_get(e, NFTNL_EXPR_BITWISE_XOR, &len);
+	memcpy(ctx->bitwise.xor, data, len);
+	data = nftnl_expr_get(e, NFTNL_EXPR_BITWISE_MASK, &len);
+	memcpy(ctx->bitwise.mask, data, len);
+	ctx->flags |= NFT_XT_CTX_BITWISE;
+}
+
+static void nft_parse_cmp(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	void *data = ctx->cs;
+	uint32_t reg;
+
+	reg = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_SREG);
+	if (ctx->reg && reg != ctx->reg)
+		return;
+
+	if (ctx->flags & NFT_XT_CTX_META) {
+		ctx->h->ops->parse_meta(ctx, e, data);
+		ctx->flags &= ~NFT_XT_CTX_META;
+	}
+	/* bitwise context is interpreted from payload */
+	if (ctx->flags & NFT_XT_CTX_PAYLOAD) {
+		ctx->h->ops->parse_payload(ctx, e, data);
+		ctx->flags &= ~NFT_XT_CTX_PAYLOAD;
+	}
+}
+
+static void nft_parse_counter(struct nftnl_expr *e, struct xt_counters *counters)
+{
+	counters->pcnt = nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_PACKETS);
+	counters->bcnt = nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_BYTES);
+}
+
+static void nft_parse_immediate(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	const char *chain = nftnl_expr_get_str(e, NFTNL_EXPR_IMM_CHAIN);
+	const char *jumpto = NULL;
+	bool nft_goto = false;
+	void *data = ctx->cs;
+	int verdict;
+
+	if (nftnl_expr_is_set(e, NFTNL_EXPR_IMM_DATA)) {
+		const void *imm_data;
+		uint32_t len;
+
+		imm_data = nftnl_expr_get_data(e, NFTNL_EXPR_IMM_DATA, &len);
+
+		if (len > sizeof(ctx->immediate.data))
+			return;
+
+		memcpy(ctx->immediate.data, imm_data, len);
+		ctx->immediate.len = len;
+		ctx->immediate.reg = nftnl_expr_get_u32(e, NFTNL_EXPR_IMM_DREG);
+		ctx->flags |= NFT_XT_CTX_IMMEDIATE;
+		return;
+	}
+
+	verdict = nftnl_expr_get_u32(e, NFTNL_EXPR_IMM_VERDICT);
+	/* Standard target? */
+	switch(verdict) {
+	case NF_ACCEPT:
+		jumpto = "ACCEPT";
+		break;
+	case NF_DROP:
+		jumpto = "DROP";
+		break;
+	case NFT_RETURN:
+		jumpto = "RETURN";
+		break;;
+	case NFT_GOTO:
+		nft_goto = true;
+		/* fall through */
+	case NFT_JUMP:
+		jumpto = chain;
+		break;
+	}
+
+	ctx->h->ops->parse_immediate(jumpto, nft_goto, data);
+}
+
+static void nft_parse_limit(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	__u32 burst = nftnl_expr_get_u32(e, NFTNL_EXPR_LIMIT_BURST);
+	__u64 unit = nftnl_expr_get_u64(e, NFTNL_EXPR_LIMIT_UNIT);
+	__u64 rate = nftnl_expr_get_u64(e, NFTNL_EXPR_LIMIT_RATE);
+	struct xtables_rule_match **matches;
+	struct xtables_match *match;
+	struct xt_rateinfo *rinfo;
+	size_t size;
+
+	switch (ctx->h->family) {
+	case NFPROTO_IPV4:
+	case NFPROTO_IPV6:
+	case NFPROTO_BRIDGE:
+		matches = &ctx->cs->matches;
+		break;
+	default:
+		fprintf(stderr, "BUG: nft_parse_limit() unknown family %d\n",
+			ctx->h->family);
+		exit(EXIT_FAILURE);
+	}
+
+	match = xtables_find_match("limit", XTF_TRY_LOAD, matches);
+	if (match == NULL)
+		return;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_match)) + match->size;
+	match->m = xtables_calloc(1, size);
+	match->m->u.match_size = size;
+	strcpy(match->m->u.user.name, match->name);
+	match->m->u.user.revision = match->revision;
+	xs_init_match(match);
+
+	rinfo = (void *)match->m->data;
+	rinfo->avg = XT_LIMIT_SCALE * unit / rate;
+	rinfo->burst = burst;
+
+	if (ctx->h->ops->parse_match != NULL)
+		ctx->h->ops->parse_match(match, ctx->cs);
+}
+
+static void nft_parse_lookup(struct nft_xt_ctx *ctx, struct nft_handle *h,
+			     struct nftnl_expr *e)
+{
+	if (ctx->h->ops->parse_lookup)
+		ctx->h->ops->parse_lookup(ctx, e, NULL);
+}
+
+void nft_rule_to_iptables_command_state(struct nft_handle *h,
+					const struct nftnl_rule *r,
+					struct iptables_command_state *cs)
+{
+	struct nftnl_expr_iter *iter;
+	struct nftnl_expr *expr;
+	struct nft_xt_ctx ctx = {
+		.cs = cs,
+		.h = h,
+		.table = nftnl_rule_get_str(r, NFTNL_RULE_TABLE),
+	};
+
+	iter = nftnl_expr_iter_create(r);
+	if (iter == NULL)
+		return;
+
+	ctx.iter = iter;
+	expr = nftnl_expr_iter_next(iter);
+	while (expr != NULL) {
+		const char *name =
+			nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
+
+		if (strcmp(name, "counter") == 0)
+			nft_parse_counter(expr, &ctx.cs->counters);
+		else if (strcmp(name, "payload") == 0)
+			nft_parse_payload(&ctx, expr);
+		else if (strcmp(name, "meta") == 0)
+			nft_parse_meta(&ctx, expr);
+		else if (strcmp(name, "bitwise") == 0)
+			nft_parse_bitwise(&ctx, expr);
+		else if (strcmp(name, "cmp") == 0)
+			nft_parse_cmp(&ctx, expr);
+		else if (strcmp(name, "immediate") == 0)
+			nft_parse_immediate(&ctx, expr);
+		else if (strcmp(name, "match") == 0)
+			nft_parse_match(&ctx, expr);
+		else if (strcmp(name, "target") == 0)
+			nft_parse_target(&ctx, expr);
+		else if (strcmp(name, "limit") == 0)
+			nft_parse_limit(&ctx, expr);
+		else if (strcmp(name, "lookup") == 0)
+			nft_parse_lookup(&ctx, h, expr);
+
+		expr = nftnl_expr_iter_next(iter);
+	}
+
+	nftnl_expr_iter_destroy(iter);
+
+	if (nftnl_rule_is_set(r, NFTNL_RULE_USERDATA)) {
+		const void *data;
+		uint32_t len, size;
+		const char *comment;
+
+		data = nftnl_rule_get_data(r, NFTNL_RULE_USERDATA, &len);
+		comment = get_comment(data, len);
+		if (comment) {
+			struct xtables_match *match;
+			struct xt_entry_match *m;
+
+			match = xtables_find_match("comment", XTF_TRY_LOAD,
+						   &cs->matches);
+			if (match == NULL)
+				return;
+
+			size = XT_ALIGN(sizeof(struct xt_entry_match))
+				+ match->size;
+			m = xtables_calloc(1, size);
+
+			strncpy((char *)m->data, comment, match->size - 1);
+			m->u.match_size = size;
+			m->u.user.revision = 0;
+			strcpy(m->u.user.name, match->name);
+
+			match->m = m;
+		}
+	}
+
+	if (cs->target != NULL) {
+		cs->jumpto = cs->target->name;
+	} else if (cs->jumpto != NULL) {
+		struct xt_entry_target *t;
+		uint32_t size;
+
+		cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD);
+		if (!cs->target)
+			return;
+
+		size = XT_ALIGN(sizeof(struct xt_entry_target)) + cs->target->size;
+		t = xtables_calloc(1, size);
+		t->u.target_size = size;
+		t->u.user.revision = cs->target->revision;
+		strcpy(t->u.user.name, cs->jumpto);
+		cs->target->t = t;
+	} else {
+		cs->jumpto = "";
+	}
+}
+
+void nft_clear_iptables_command_state(struct iptables_command_state *cs)
+{
+	xtables_rule_matches_free(&cs->matches);
+	if (cs->target) {
+		free(cs->target->t);
+		cs->target->t = NULL;
+
+		if (cs->target == cs->target->next) {
+			free(cs->target);
+			cs->target = NULL;
+		}
+	}
+}
+
+void print_header(unsigned int format, const char *chain, const char *pol,
+		  const struct xt_counters *counters, bool basechain,
+		  uint32_t refs, uint32_t entries)
+{
+	printf("Chain %s", chain);
+	if (basechain) {
+		printf(" (policy %s", pol);
+		if (!(format & FMT_NOCOUNTS)) {
+			fputc(' ', stdout);
+			xtables_print_num(counters->pcnt, (format|FMT_NOTABLE));
+			fputs("packets, ", stdout);
+			xtables_print_num(counters->bcnt, (format|FMT_NOTABLE));
+			fputs("bytes", stdout);
+		}
+		printf(")\n");
+	} else {
+		printf(" (%u references)\n", refs);
+	}
+
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4s ", "%s "), "num");
+	if (!(format & FMT_NOCOUNTS)) {
+		if (format & FMT_KILOMEGAGIGA) {
+			printf(FMT("%5s ","%s "), "pkts");
+			printf(FMT("%5s ","%s "), "bytes");
+		} else {
+			printf(FMT("%8s ","%s "), "pkts");
+			printf(FMT("%10s ","%s "), "bytes");
+		}
+	}
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ","%s "), "target");
+	fputs(" prot ", stdout);
+	if (format & FMT_OPTIONS)
+		fputs("opt", stdout);
+	if (format & FMT_VIA) {
+		printf(FMT(" %-6s ","%s "), "in");
+		printf(FMT("%-6s ","%s "), "out");
+	}
+	printf(FMT(" %-19s ","%s "), "source");
+	printf(FMT(" %-19s "," %s "), "destination");
+	printf("\n");
+}
+
+void print_rule_details(const struct iptables_command_state *cs,
+			const char *targname, uint8_t flags,
+			uint8_t invflags, uint8_t proto,
+			unsigned int num, unsigned int format)
+{
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4u ", "%u "), num);
+
+	if (!(format & FMT_NOCOUNTS)) {
+		xtables_print_num(cs->counters.pcnt, format);
+		xtables_print_num(cs->counters.bcnt, format);
+	}
+
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ", "%s "), targname ? targname : "");
+
+	fputc(invflags & XT_INV_PROTO ? '!' : ' ', stdout);
+	{
+		const char *pname =
+			proto_to_name(proto, format&FMT_NUMERIC);
+		if (pname)
+			printf(FMT("%-5s", "%s "), pname);
+		else
+			printf(FMT("%-5hu", "%hu "), proto);
+	}
+}
+
+static void
+print_iface(char letter, const char *iface, const unsigned char *mask, int inv)
+{
+	unsigned int i;
+
+	if (mask[0] == 0)
+		return;
+
+	printf("%s-%c ", inv ? "! " : "", letter);
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (mask[i] != 0) {
+			if (iface[i] != '\0')
+				printf("%c", iface[i]);
+			} else {
+				if (iface[i-1] != '\0')
+					printf("+");
+				break;
+		}
+	}
+
+	printf(" ");
+}
+
+void save_rule_details(const struct iptables_command_state *cs,
+		       uint8_t invflags, uint16_t proto,
+		       const char *iniface,
+		       unsigned const char *iniface_mask,
+		       const char *outiface,
+		       unsigned const char *outiface_mask)
+{
+	if (iniface != NULL) {
+		print_iface('i', iniface, iniface_mask,
+			    invflags & IPT_INV_VIA_IN);
+	}
+	if (outiface != NULL) {
+		print_iface('o', outiface, outiface_mask,
+			    invflags & IPT_INV_VIA_OUT);
+	}
+
+	if (proto > 0) {
+		const struct protoent *pent = getprotobynumber(proto);
+
+		if (invflags & XT_INV_PROTO)
+			printf("! ");
+
+		if (pent)
+			printf("-p %s ", pent->p_name);
+		else
+			printf("-p %u ", proto);
+	}
+}
+
+void nft_ipv46_save_chain(const struct nftnl_chain *c, const char *policy)
+{
+	const char *chain = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+	uint64_t pkts = nftnl_chain_get_u64(c, NFTNL_CHAIN_PACKETS);
+	uint64_t bytes = nftnl_chain_get_u64(c, NFTNL_CHAIN_BYTES);
+
+	printf(":%s %s [%"PRIu64":%"PRIu64"]\n",
+	       chain, policy ?: "-", pkts, bytes);
+}
+
+void save_matches_and_target(const struct iptables_command_state *cs,
+			     bool goto_flag, const void *fw,
+			     unsigned int format)
+{
+	struct xtables_rule_match *matchp;
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		if (matchp->match->alias) {
+			printf("-m %s",
+			       matchp->match->alias(matchp->match->m));
+		} else
+			printf("-m %s", matchp->match->name);
+
+		if (matchp->match->save != NULL) {
+			/* cs->fw union makes the trick */
+			matchp->match->save(fw, matchp->match->m);
+		}
+		printf(" ");
+	}
+
+	if ((format & (FMT_NOCOUNTS | FMT_C_COUNTS)) == FMT_C_COUNTS)
+		printf("-c %llu %llu ",
+		       (unsigned long long)cs->counters.pcnt,
+		       (unsigned long long)cs->counters.bcnt);
+
+	if (cs->target != NULL) {
+		if (cs->target->alias) {
+			printf("-j %s", cs->target->alias(cs->target->t));
+		} else
+			printf("-j %s", cs->jumpto);
+
+		if (cs->target->save != NULL)
+			cs->target->save(fw, cs->target->t);
+	} else if (strlen(cs->jumpto) > 0) {
+		printf("-%c %s", goto_flag ? 'g' : 'j', cs->jumpto);
+	}
+
+	printf("\n");
+}
+
+void print_matches_and_target(struct iptables_command_state *cs,
+			      unsigned int format)
+{
+	struct xtables_rule_match *matchp;
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		if (matchp->match->print != NULL) {
+			matchp->match->print(&cs->fw, matchp->match->m,
+					     format & FMT_NUMERIC);
+		}
+	}
+
+	if (cs->target != NULL) {
+		if (cs->target->print != NULL) {
+			cs->target->print(&cs->fw, cs->target->t,
+					  format & FMT_NUMERIC);
+		}
+	}
+}
+
+struct nft_family_ops *nft_family_ops_lookup(int family)
+{
+	switch (family) {
+	case AF_INET:
+		return &nft_family_ops_ipv4;
+	case AF_INET6:
+		return &nft_family_ops_ipv6;
+	case NFPROTO_ARP:
+		return &nft_family_ops_arp;
+	case NFPROTO_BRIDGE:
+		return &nft_family_ops_bridge;
+	default:
+		break;
+	}
+
+	return NULL;
+}
+
+bool compare_matches(struct xtables_rule_match *mt1,
+		     struct xtables_rule_match *mt2)
+{
+	struct xtables_rule_match *mp1;
+	struct xtables_rule_match *mp2;
+
+	for (mp1 = mt1, mp2 = mt2; mp1 && mp2; mp1 = mp1->next, mp2 = mp2->next) {
+		struct xt_entry_match *m1 = mp1->match->m;
+		struct xt_entry_match *m2 = mp2->match->m;
+
+		if (strcmp(m1->u.user.name, m2->u.user.name) != 0) {
+			DEBUGP("mismatching match name\n");
+			return false;
+		}
+
+		if (m1->u.user.match_size != m2->u.user.match_size) {
+			DEBUGP("mismatching match size\n");
+			return false;
+		}
+
+		if (memcmp(m1->data, m2->data,
+			   mp1->match->userspacesize) != 0) {
+			DEBUGP("mismatch match data\n");
+			return false;
+		}
+	}
+
+	/* Both cursors should be NULL */
+	if (mp1 != mp2) {
+		DEBUGP("mismatch matches amount\n");
+		return false;
+	}
+
+	return true;
+}
+
+bool compare_targets(struct xtables_target *tg1, struct xtables_target *tg2)
+{
+	if (tg1 == NULL && tg2 == NULL)
+		return true;
+
+	if (tg1 == NULL || tg2 == NULL)
+		return false;
+	if (tg1->userspacesize != tg2->userspacesize)
+		return false;
+
+	if (strcmp(tg1->t->u.user.name, tg2->t->u.user.name) != 0)
+		return false;
+
+	if (memcmp(tg1->t->data, tg2->t->data, tg1->userspacesize) != 0)
+		return false;
+
+	return true;
+}
+
+void nft_ipv46_parse_target(struct xtables_target *t, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->target = t;
+}
+
+void nft_check_xt_legacy(int family, bool is_ipt_save)
+{
+	static const char tables6[] = "/proc/net/ip6_tables_names";
+	static const char tables4[] = "/proc/net/ip_tables_names";
+	const char *prefix = "ip";
+	FILE *fp = NULL;
+	char buf[1024];
+
+	switch (family) {
+	case NFPROTO_IPV4:
+		fp = fopen(tables4, "r");
+		break;
+	case NFPROTO_IPV6:
+		fp = fopen(tables6, "r");
+		prefix = "ip6";
+		break;
+	default:
+		break;
+	}
+
+	if (!fp)
+		return;
+
+	if (fgets(buf, sizeof(buf), fp))
+		fprintf(stderr, "# Warning: %stables-legacy tables present, use %stables-legacy%s to see them\n",
+			prefix, prefix, is_ipt_save ? "-save" : "");
+	fclose(fp);
+}
diff --git a/iptables/nft-shared.h b/iptables/nft-shared.h
new file mode 100644
index 0000000..da4ba9d
--- /dev/null
+++ b/iptables/nft-shared.h
@@ -0,0 +1,255 @@
+#ifndef _NFT_SHARED_H_
+#define _NFT_SHARED_H_
+
+#include <stdbool.h>
+
+#include <libnftnl/rule.h>
+#include <libnftnl/expr.h>
+#include <libnftnl/chain.h>
+
+#include <linux/netfilter_arp/arp_tables.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include "xshared.h"
+
+#ifdef DEBUG
+#define NLDEBUG
+#define DEBUG_DEL
+#endif
+
+/*
+ * iptables print output emulation
+ */
+
+#define FMT_NUMERIC	0x0001
+#define FMT_NOCOUNTS	0x0002
+#define FMT_KILOMEGAGIGA 0x0004
+#define FMT_OPTIONS	0x0008
+#define FMT_NOTABLE	0x0010
+#define FMT_NOTARGET	0x0020
+#define FMT_VIA		0x0040
+#define FMT_NONEWLINE	0x0080
+#define FMT_LINENUMBERS 0x0100
+
+#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \
+			| FMT_NUMERIC | FMT_NOTABLE)
+#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab))
+
+struct xtables_args;
+struct nft_handle;
+struct xt_xlate;
+
+enum {
+	NFT_XT_CTX_PAYLOAD	= (1 << 0),
+	NFT_XT_CTX_META		= (1 << 1),
+	NFT_XT_CTX_BITWISE	= (1 << 2),
+	NFT_XT_CTX_IMMEDIATE	= (1 << 3),
+	NFT_XT_CTX_PREV_PAYLOAD	= (1 << 4),
+};
+
+struct nft_xt_ctx {
+	struct iptables_command_state *cs;
+	struct nftnl_expr_iter *iter;
+	struct nft_handle *h;
+	uint32_t flags;
+	const char *table;
+
+	uint32_t reg;
+	struct {
+		uint32_t base;
+		uint32_t offset;
+		uint32_t len;
+	} payload, prev_payload;
+	struct {
+		uint32_t key;
+	} meta;
+	struct {
+		uint32_t data[4];
+		uint32_t len, reg;
+	} immediate;
+	struct {
+		uint32_t mask[4];
+		uint32_t xor[4];
+	} bitwise;
+};
+
+struct nft_family_ops {
+	int (*add)(struct nft_handle *h, struct nftnl_rule *r, void *data);
+	bool (*is_same)(const void *data_a,
+			const void *data_b);
+	void (*print_payload)(struct nftnl_expr *e,
+			      struct nftnl_expr_iter *iter);
+	void (*parse_meta)(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			   void *data);
+	void (*parse_payload)(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			      void *data);
+	void (*parse_bitwise)(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			      void *data);
+	void (*parse_cmp)(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			  void *data);
+	void (*parse_lookup)(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			     void *data);
+	void (*parse_immediate)(const char *jumpto, bool nft_goto, void *data);
+
+	void (*print_table_header)(const char *tablename);
+	void (*print_header)(unsigned int format, const char *chain,
+			     const char *pol,
+			     const struct xt_counters *counters, bool basechain,
+			     uint32_t refs, uint32_t entries);
+	void (*print_rule)(struct nft_handle *h, struct nftnl_rule *r,
+			   unsigned int num, unsigned int format);
+	void (*save_rule)(const void *data, unsigned int format);
+	void (*save_chain)(const struct nftnl_chain *c, const char *policy);
+	void (*proto_parse)(struct iptables_command_state *cs,
+			    struct xtables_args *args);
+	void (*post_parse)(int command, struct iptables_command_state *cs,
+			   struct xtables_args *args);
+	void (*parse_match)(struct xtables_match *m, void *data);
+	void (*parse_target)(struct xtables_target *t, void *data);
+	void (*rule_to_cs)(struct nft_handle *h, const struct nftnl_rule *r,
+			   struct iptables_command_state *cs);
+	void (*clear_cs)(struct iptables_command_state *cs);
+	int (*xlate)(const void *data, struct xt_xlate *xl);
+};
+
+void add_meta(struct nftnl_rule *r, uint32_t key);
+void add_payload(struct nftnl_rule *r, int offset, int len, uint32_t base);
+void add_bitwise(struct nftnl_rule *r, uint8_t *mask, size_t len);
+void add_bitwise_u16(struct nftnl_rule *r, uint16_t mask, uint16_t xor);
+void add_cmp_ptr(struct nftnl_rule *r, uint32_t op, void *data, size_t len);
+void add_cmp_u8(struct nftnl_rule *r, uint8_t val, uint32_t op);
+void add_cmp_u16(struct nftnl_rule *r, uint16_t val, uint32_t op);
+void add_cmp_u32(struct nftnl_rule *r, uint32_t val, uint32_t op);
+void add_iniface(struct nftnl_rule *r, char *iface, uint32_t op);
+void add_outiface(struct nftnl_rule *r, char *iface, uint32_t op);
+void add_addr(struct nftnl_rule *r, enum nft_payload_bases base, int offset,
+	      void *data, void *mask, size_t len, uint32_t op);
+void add_proto(struct nftnl_rule *r, int offset, size_t len,
+	       uint8_t proto, uint32_t op);
+void add_l4proto(struct nftnl_rule *r, uint8_t proto, uint32_t op);
+void add_compat(struct nftnl_rule *r, uint32_t proto, bool inv);
+
+bool is_same_interfaces(const char *a_iniface, const char *a_outiface,
+			unsigned const char *a_iniface_mask,
+			unsigned const char *a_outiface_mask,
+			const char *b_iniface, const char *b_outiface,
+			unsigned const char *b_iniface_mask,
+			unsigned const char *b_outiface_mask);
+
+int parse_meta(struct nftnl_expr *e, uint8_t key, char *iniface,
+		unsigned char *iniface_mask, char *outiface,
+		unsigned char *outiface_mask, uint8_t *invflags);
+void print_proto(uint16_t proto, int invert);
+void get_cmp_data(struct nftnl_expr *e, void *data, size_t dlen, bool *inv);
+void nft_rule_to_iptables_command_state(struct nft_handle *h,
+					const struct nftnl_rule *r,
+					struct iptables_command_state *cs);
+void nft_clear_iptables_command_state(struct iptables_command_state *cs);
+void print_header(unsigned int format, const char *chain, const char *pol,
+		  const struct xt_counters *counters, bool basechain,
+		  uint32_t refs, uint32_t entries);
+void print_rule_details(const struct iptables_command_state *cs,
+			const char *targname, uint8_t flags,
+			uint8_t invflags, uint8_t proto,
+			unsigned int num, unsigned int format);
+void print_matches_and_target(struct iptables_command_state *cs,
+			      unsigned int format);
+void save_rule_details(const struct iptables_command_state *cs,
+		       uint8_t invflags, uint16_t proto,
+		       const char *iniface,
+		       unsigned const char *iniface_mask,
+		       const char *outiface,
+		       unsigned const char *outiface_mask);
+void nft_ipv46_save_chain(const struct nftnl_chain *c, const char *policy);
+void save_matches_and_target(const struct iptables_command_state *cs,
+			     bool goto_flag, const void *fw,
+			     unsigned int format);
+
+struct nft_family_ops *nft_family_ops_lookup(int family);
+
+void nft_ipv46_parse_target(struct xtables_target *t, void *data);
+
+bool compare_matches(struct xtables_rule_match *mt1, struct xtables_rule_match *mt2);
+bool compare_targets(struct xtables_target *tg1, struct xtables_target *tg2);
+
+struct addr_mask {
+	union {
+		struct in_addr	*v4;
+		struct in6_addr *v6;
+	} addr;
+
+	unsigned int naddrs;
+
+	union {
+		struct in_addr	*v4;
+		struct in6_addr *v6;
+	} mask;
+};
+
+struct xtables_args {
+	int		family;
+	uint16_t	proto;
+	uint8_t		flags;
+	uint8_t		invflags;
+	char		iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char	iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+	bool		goto_set;
+	const char	*shostnetworkmask, *dhostnetworkmask;
+	const char	*pcnt, *bcnt;
+	struct addr_mask s, d;
+	unsigned long long pcnt_cnt, bcnt_cnt;
+};
+
+struct nft_xt_cmd_parse {
+	unsigned int			command;
+	unsigned int			rulenum;
+	char				*table;
+	const char			*chain;
+	const char			*newname;
+	const char			*policy;
+	bool				restore;
+	int				verbose;
+	bool				xlate;
+};
+
+void do_parse(struct nft_handle *h, int argc, char *argv[],
+	      struct nft_xt_cmd_parse *p, struct iptables_command_state *cs,
+	      struct xtables_args *args);
+
+struct nftnl_chain_list;
+
+struct nft_xt_restore_cb {
+	void (*table_new)(struct nft_handle *h, const char *table);
+	int (*chain_set)(struct nft_handle *h, const char *table,
+			 const char *chain, const char *policy,
+			 const struct xt_counters *counters);
+	int (*chain_restore)(struct nft_handle *h, const char *chain,
+			     const char *table);
+
+	int (*table_flush)(struct nft_handle *h, const char *table,
+			   bool verbose);
+
+	int (*do_command)(struct nft_handle *h, int argc, char *argv[],
+			  char **table, bool restore);
+
+	int (*commit)(struct nft_handle *h);
+	int (*abort)(struct nft_handle *h);
+};
+
+struct nft_xt_restore_parse {
+	FILE				*in;
+	int				testing;
+	const char			*tablename;
+	bool				commit;
+	const struct nft_xt_restore_cb	*cb;
+};
+
+void xtables_restore_parse(struct nft_handle *h,
+			   const struct nft_xt_restore_parse *p);
+
+void nft_check_xt_legacy(int family, bool is_ipt_save);
+
+#define min(x, y) ((x) < (y) ? (x) : (y))
+#define max(x, y) ((x) > (y) ? (x) : (y))
+
+#endif
diff --git a/iptables/nft.c b/iptables/nft.c
new file mode 100644
index 0000000..bde4ca7
--- /dev/null
+++ b/iptables/nft.c
@@ -0,0 +1,3550 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <netdb.h>	/* getprotobynumber */
+#include <time.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include <xtables.h>
+#include <libiptc/libxtc.h>
+#include <libiptc/xtcshared.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <netinet/ip6.h>
+
+#include <linux/netlink.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter/nf_tables_compat.h>
+
+#include <linux/netfilter/xt_limit.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/gen.h>
+#include <libnftnl/table.h>
+#include <libnftnl/chain.h>
+#include <libnftnl/rule.h>
+#include <libnftnl/expr.h>
+#include <libnftnl/set.h>
+#include <libnftnl/udata.h>
+#include <libnftnl/batch.h>
+
+#include <netinet/in.h>	/* inet_ntoa */
+#include <arpa/inet.h>
+
+#include "nft.h"
+#include "xshared.h" /* proto_to_name */
+#include "nft-cache.h"
+#include "nft-shared.h"
+#include "nft-bridge.h" /* EBT_NOPROTO */
+
+static void *nft_fn;
+
+int mnl_talk(struct nft_handle *h, struct nlmsghdr *nlh,
+	     int (*cb)(const struct nlmsghdr *nlh, void *data),
+	     void *data)
+{
+	int ret;
+	char buf[32768];
+
+	if (mnl_socket_sendto(h->nl, nlh, nlh->nlmsg_len) < 0)
+		return -1;
+
+	ret = mnl_socket_recvfrom(h->nl, buf, sizeof(buf));
+	while (ret > 0) {
+		ret = mnl_cb_run(buf, ret, h->seq, h->portid, cb, data);
+		if (ret <= 0)
+			break;
+
+		ret = mnl_socket_recvfrom(h->nl, buf, sizeof(buf));
+	}
+	if (ret == -1) {
+		return -1;
+	}
+
+	return 0;
+}
+
+#define NFT_NLMSG_MAXSIZE (UINT16_MAX + getpagesize())
+
+/* selected batch page is 256 Kbytes long to load ruleset of
+ * half a million rules without hitting -EMSGSIZE due to large
+ * iovec.
+ */
+#define BATCH_PAGE_SIZE getpagesize() * 32
+
+static struct nftnl_batch *mnl_batch_init(void)
+{
+	struct nftnl_batch *batch;
+
+	batch = nftnl_batch_alloc(BATCH_PAGE_SIZE, NFT_NLMSG_MAXSIZE);
+	if (batch == NULL)
+		return NULL;
+
+	return batch;
+}
+
+static void mnl_nft_batch_continue(struct nftnl_batch *batch)
+{
+	assert(nftnl_batch_update(batch) >= 0);
+}
+
+static uint32_t mnl_batch_begin(struct nftnl_batch *batch, uint32_t genid, uint32_t seqnum)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nftnl_batch_begin(nftnl_batch_buffer(batch), seqnum);
+
+	mnl_attr_put_u32(nlh, NFTA_GEN_ID, htonl(genid));
+
+	mnl_nft_batch_continue(batch);
+
+	return seqnum;
+}
+
+static void mnl_batch_end(struct nftnl_batch *batch, uint32_t seqnum)
+{
+	nftnl_batch_end(nftnl_batch_buffer(batch), seqnum);
+	mnl_nft_batch_continue(batch);
+}
+
+static void mnl_batch_reset(struct nftnl_batch *batch)
+{
+	nftnl_batch_free(batch);
+}
+
+struct mnl_err {
+	struct list_head	head;
+	int			err;
+	uint32_t		seqnum;
+};
+
+static void mnl_err_list_node_add(struct list_head *err_list, int error,
+				  int seqnum)
+{
+	struct mnl_err *err = malloc(sizeof(struct mnl_err));
+
+	err->seqnum = seqnum;
+	err->err = error;
+	list_add_tail(&err->head, err_list);
+}
+
+static void mnl_err_list_free(struct mnl_err *err)
+{
+	list_del(&err->head);
+	free(err);
+}
+
+static void mnl_set_sndbuffer(struct nft_handle *h)
+{
+	int newbuffsiz = nftnl_batch_iovec_len(h->batch) * BATCH_PAGE_SIZE;
+
+	if (newbuffsiz <= h->nlsndbuffsiz)
+		return;
+
+	/* Rise sender buffer length to avoid hitting -EMSGSIZE */
+	if (setsockopt(mnl_socket_get_fd(h->nl), SOL_SOCKET, SO_SNDBUFFORCE,
+		       &newbuffsiz, sizeof(socklen_t)) < 0)
+		return;
+
+	h->nlsndbuffsiz = newbuffsiz;
+}
+
+static void mnl_set_rcvbuffer(struct nft_handle *h, int numcmds)
+{
+	int newbuffsiz = getpagesize() * numcmds;
+
+	if (newbuffsiz <= h->nlrcvbuffsiz)
+		return;
+
+	/* Rise receiver buffer length to avoid hitting -ENOBUFS */
+	if (setsockopt(mnl_socket_get_fd(h->nl), SOL_SOCKET, SO_RCVBUFFORCE,
+		       &newbuffsiz, sizeof(socklen_t)) < 0)
+		return;
+
+	h->nlrcvbuffsiz = newbuffsiz;
+}
+
+static ssize_t mnl_nft_socket_sendmsg(struct nft_handle *h, int numcmds)
+{
+	static const struct sockaddr_nl snl = {
+		.nl_family = AF_NETLINK
+	};
+	uint32_t iov_len = nftnl_batch_iovec_len(h->batch);
+	struct iovec iov[iov_len];
+	struct msghdr msg = {
+		.msg_name	= (struct sockaddr *) &snl,
+		.msg_namelen	= sizeof(snl),
+		.msg_iov	= iov,
+		.msg_iovlen	= iov_len,
+	};
+
+	mnl_set_sndbuffer(h);
+	mnl_set_rcvbuffer(h, numcmds);
+	nftnl_batch_iovec(h->batch, iov, iov_len);
+
+	return sendmsg(mnl_socket_get_fd(h->nl), &msg, 0);
+}
+
+static int mnl_batch_talk(struct nft_handle *h, int numcmds)
+{
+	const struct mnl_socket *nl = h->nl;
+	int ret, fd = mnl_socket_get_fd(nl), portid = mnl_socket_get_portid(nl);
+	char rcv_buf[MNL_SOCKET_BUFFER_SIZE];
+	fd_set readfds;
+	struct timeval tv = {
+		.tv_sec		= 0,
+		.tv_usec	= 0
+	};
+	int err = 0;
+
+	ret = mnl_nft_socket_sendmsg(h, numcmds);
+	if (ret == -1)
+		return -1;
+
+	FD_ZERO(&readfds);
+	FD_SET(fd, &readfds);
+
+	/* receive and digest all the acknowledgments from the kernel. */
+	ret = select(fd+1, &readfds, NULL, NULL, &tv);
+	if (ret == -1)
+		return -1;
+
+	while (ret > 0 && FD_ISSET(fd, &readfds)) {
+		struct nlmsghdr *nlh = (struct nlmsghdr *)rcv_buf;
+
+		ret = mnl_socket_recvfrom(nl, rcv_buf, sizeof(rcv_buf));
+		if (ret == -1)
+			return -1;
+
+		ret = mnl_cb_run(rcv_buf, ret, 0, portid, NULL, NULL);
+		/* Continue on error, make sure we get all acknowledgments */
+		if (ret == -1) {
+			mnl_err_list_node_add(&h->err_list, errno,
+					      nlh->nlmsg_seq);
+			err = -1;
+		}
+
+		ret = select(fd+1, &readfds, NULL, NULL, &tv);
+		if (ret == -1)
+			return -1;
+
+		FD_ZERO(&readfds);
+		FD_SET(fd, &readfds);
+	}
+	return err;
+}
+
+enum obj_action {
+	NFT_COMPAT_COMMIT,
+	NFT_COMPAT_ABORT,
+};
+
+struct obj_update {
+	struct list_head	head;
+	enum obj_update_type	type:8;
+	uint8_t			skip:1;
+	unsigned int		seq;
+	union {
+		struct nftnl_table	*table;
+		struct nftnl_chain	*chain;
+		struct nftnl_rule	*rule;
+		struct nftnl_set	*set;
+		void			*ptr;
+	};
+	struct {
+		unsigned int		lineno;
+	} error;
+};
+
+static int mnl_append_error(const struct nft_handle *h,
+			    const struct obj_update *o,
+			    const struct mnl_err *err,
+			    char *buf, unsigned int len)
+{
+	static const char *type_name[] = {
+		[NFT_COMPAT_TABLE_ADD] = "TABLE_ADD",
+		[NFT_COMPAT_TABLE_FLUSH] = "TABLE_FLUSH",
+		[NFT_COMPAT_CHAIN_ADD] = "CHAIN_ADD",
+		[NFT_COMPAT_CHAIN_USER_ADD] = "CHAIN_USER_ADD",
+		[NFT_COMPAT_CHAIN_USER_DEL] = "CHAIN_USER_DEL",
+		[NFT_COMPAT_CHAIN_USER_FLUSH] = "CHAIN_USER_FLUSH",
+		[NFT_COMPAT_CHAIN_UPDATE] = "CHAIN_UPDATE",
+		[NFT_COMPAT_CHAIN_RENAME] = "CHAIN_RENAME",
+		[NFT_COMPAT_CHAIN_ZERO] = "CHAIN_ZERO",
+		[NFT_COMPAT_RULE_APPEND] = "RULE_APPEND",
+		[NFT_COMPAT_RULE_INSERT] = "RULE_INSERT",
+		[NFT_COMPAT_RULE_REPLACE] = "RULE_REPLACE",
+		[NFT_COMPAT_RULE_DELETE] = "RULE_DELETE",
+		[NFT_COMPAT_RULE_FLUSH] = "RULE_FLUSH",
+		[NFT_COMPAT_SET_ADD] = "SET_ADD",
+	};
+	char errmsg[256];
+	char tcr[128];
+
+	if (o->error.lineno)
+		snprintf(errmsg, sizeof(errmsg), "\nline %u: %s failed (%s)",
+			 o->error.lineno, type_name[o->type], strerror(err->err));
+	else
+		snprintf(errmsg, sizeof(errmsg), " %s failed (%s)",
+			 type_name[o->type], strerror(err->err));
+
+	switch (o->type) {
+	case NFT_COMPAT_TABLE_ADD:
+	case NFT_COMPAT_TABLE_FLUSH:
+		snprintf(tcr, sizeof(tcr), "table %s",
+			 nftnl_table_get_str(o->table, NFTNL_TABLE_NAME));
+		break;
+	case NFT_COMPAT_CHAIN_ADD:
+	case NFT_COMPAT_CHAIN_ZERO:
+	case NFT_COMPAT_CHAIN_USER_ADD:
+	case NFT_COMPAT_CHAIN_USER_DEL:
+	case NFT_COMPAT_CHAIN_USER_FLUSH:
+	case NFT_COMPAT_CHAIN_UPDATE:
+	case NFT_COMPAT_CHAIN_RENAME:
+		snprintf(tcr, sizeof(tcr), "chain %s",
+			 nftnl_chain_get_str(o->chain, NFTNL_CHAIN_NAME));
+		break;
+	case NFT_COMPAT_RULE_APPEND:
+	case NFT_COMPAT_RULE_INSERT:
+	case NFT_COMPAT_RULE_REPLACE:
+	case NFT_COMPAT_RULE_DELETE:
+	case NFT_COMPAT_RULE_FLUSH:
+		snprintf(tcr, sizeof(tcr), "rule in chain %s",
+			 nftnl_rule_get_str(o->rule, NFTNL_RULE_CHAIN));
+#if 0
+		{
+			nft_rule_print_save(h, o->rule, NFT_RULE_APPEND, FMT_NOCOUNTS);
+		}
+#endif
+		break;
+	case NFT_COMPAT_SET_ADD:
+		snprintf(tcr, sizeof(tcr), "set %s",
+			 nftnl_set_get_str(o->set, NFTNL_SET_NAME));
+		break;
+	case NFT_COMPAT_RULE_LIST:
+	case NFT_COMPAT_RULE_CHECK:
+	case NFT_COMPAT_CHAIN_RESTORE:
+	case NFT_COMPAT_RULE_SAVE:
+	case NFT_COMPAT_RULE_ZERO:
+	case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
+		assert(0);
+		break;
+	}
+
+	return snprintf(buf, len, "%s: %s", errmsg, tcr);
+}
+
+static struct obj_update *batch_add(struct nft_handle *h, enum obj_update_type type, void *ptr)
+{
+	struct obj_update *obj;
+
+	obj = calloc(1, sizeof(struct obj_update));
+	if (obj == NULL)
+		return NULL;
+
+	obj->ptr = ptr;
+	obj->error.lineno = h->error.lineno;
+	obj->type = type;
+	list_add_tail(&obj->head, &h->obj_list);
+	h->obj_list_num++;
+
+	return obj;
+}
+
+static struct obj_update *
+batch_table_add(struct nft_handle *h, enum obj_update_type type,
+		struct nftnl_table *t)
+{
+	return batch_add(h, type, t);
+}
+
+static struct obj_update *
+batch_set_add(struct nft_handle *h, enum obj_update_type type,
+	      struct nftnl_set *s)
+{
+	return batch_add(h, type, s);
+}
+
+static struct obj_update *
+batch_chain_add(struct nft_handle *h, enum obj_update_type type,
+			   struct nftnl_chain *c)
+{
+	return batch_add(h, type, c);
+}
+
+static struct obj_update *
+batch_rule_add(struct nft_handle *h, enum obj_update_type type,
+			  struct nftnl_rule *r)
+{
+	return batch_add(h, type, r);
+}
+
+static void batch_obj_del(struct nft_handle *h, struct obj_update *o);
+
+static void batch_chain_flush(struct nft_handle *h,
+			      const char *table, const char *chain)
+{
+	struct obj_update *obj, *tmp;
+
+	list_for_each_entry_safe(obj, tmp, &h->obj_list, head) {
+		struct nftnl_rule *r = obj->ptr;
+
+		switch (obj->type) {
+		case NFT_COMPAT_RULE_APPEND:
+		case NFT_COMPAT_RULE_INSERT:
+		case NFT_COMPAT_RULE_REPLACE:
+		case NFT_COMPAT_RULE_DELETE:
+			break;
+		default:
+			continue;
+		}
+
+		if (table &&
+		    strcmp(table, nftnl_rule_get_str(r, NFTNL_RULE_TABLE)))
+			continue;
+
+		if (chain &&
+		    strcmp(chain, nftnl_rule_get_str(r, NFTNL_RULE_CHAIN)))
+			continue;
+
+		batch_obj_del(h, obj);
+	}
+}
+
+const struct builtin_table xtables_ipv4[NFT_TABLE_MAX] = {
+	[NFT_TABLE_RAW] = {
+		.name	= "raw",
+		.type	= NFT_TABLE_RAW,
+		.chains = {
+			{
+				.name	= "PREROUTING",
+				.type	= "filter",
+				.prio	= -300,	/* NF_IP_PRI_RAW */
+				.hook	= NF_INET_PRE_ROUTING,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "filter",
+				.prio	= -300,	/* NF_IP_PRI_RAW */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+	[NFT_TABLE_MANGLE] = {
+		.name	= "mangle",
+		.type	= NFT_TABLE_MANGLE,
+		.chains = {
+			{
+				.name	= "PREROUTING",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_PRE_ROUTING,
+			},
+			{
+				.name	= "INPUT",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "FORWARD",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_FORWARD,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "route",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+			{
+				.name	= "POSTROUTING",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_POST_ROUTING,
+			},
+		},
+	},
+	[NFT_TABLE_FILTER] = {
+		.name	= "filter",
+		.type	= NFT_TABLE_FILTER,
+		.chains = {
+			{
+				.name	= "INPUT",
+				.type	= "filter",
+				.prio	= 0,	/* NF_IP_PRI_FILTER */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "FORWARD",
+				.type	= "filter",
+				.prio	= 0,	/* NF_IP_PRI_FILTER */
+				.hook	= NF_INET_FORWARD,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "filter",
+				.prio	= 0,	/* NF_IP_PRI_FILTER */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+	[NFT_TABLE_SECURITY] = {
+		.name	= "security",
+		.type	= NFT_TABLE_SECURITY,
+		.chains = {
+			{
+				.name	= "INPUT",
+				.type	= "filter",
+				.prio	= 150,	/* NF_IP_PRI_SECURITY */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "FORWARD",
+				.type	= "filter",
+				.prio	= 150,	/* NF_IP_PRI_SECURITY */
+				.hook	= NF_INET_FORWARD,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "filter",
+				.prio	= 150,	/* NF_IP_PRI_SECURITY */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+	[NFT_TABLE_NAT] = {
+		.name	= "nat",
+		.type	= NFT_TABLE_NAT,
+		.chains = {
+			{
+				.name	= "PREROUTING",
+				.type	= "nat",
+				.prio	= -100, /* NF_IP_PRI_NAT_DST */
+				.hook	= NF_INET_PRE_ROUTING,
+			},
+			{
+				.name	= "INPUT",
+				.type	= "nat",
+				.prio	= 100, /* NF_IP_PRI_NAT_SRC */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "POSTROUTING",
+				.type	= "nat",
+				.prio	= 100, /* NF_IP_PRI_NAT_SRC */
+				.hook	= NF_INET_POST_ROUTING,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "nat",
+				.prio	= -100, /* NF_IP_PRI_NAT_DST */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+};
+
+#include <linux/netfilter_arp.h>
+
+const struct builtin_table xtables_arp[NFT_TABLE_MAX] = {
+	[NFT_TABLE_FILTER] = {
+	.name   = "filter",
+	.type	= NFT_TABLE_FILTER,
+	.chains = {
+			{
+				.name   = "INPUT",
+				.type   = "filter",
+				.prio   = NF_IP_PRI_FILTER,
+				.hook   = NF_ARP_IN,
+			},
+			{
+				.name   = "OUTPUT",
+				.type   = "filter",
+				.prio   = NF_IP_PRI_FILTER,
+				.hook   = NF_ARP_OUT,
+			},
+		},
+	},
+};
+
+#include <linux/netfilter_bridge.h>
+
+const struct builtin_table xtables_bridge[NFT_TABLE_MAX] = {
+	[NFT_TABLE_FILTER] = {
+		.name = "filter",
+		.type	= NFT_TABLE_FILTER,
+		.chains = {
+			{
+				.name   = "INPUT",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_FILTER_BRIDGED,
+				.hook   = NF_BR_LOCAL_IN,
+			},
+			{
+				.name   = "FORWARD",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_FILTER_BRIDGED,
+				.hook   = NF_BR_FORWARD,
+			},
+			{
+				.name   = "OUTPUT",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_FILTER_BRIDGED,
+				.hook   = NF_BR_LOCAL_OUT,
+			},
+		},
+	},
+	[NFT_TABLE_NAT] = {
+		.name = "nat",
+		.type	= NFT_TABLE_NAT,
+		.chains = {
+			{
+				.name   = "PREROUTING",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_NAT_DST_BRIDGED,
+				.hook   = NF_BR_PRE_ROUTING,
+			},
+			{
+				.name   = "OUTPUT",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_NAT_DST_OTHER,
+				.hook   = NF_BR_LOCAL_OUT,
+			},
+			{
+				.name   = "POSTROUTING",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_NAT_SRC,
+				.hook   = NF_BR_POST_ROUTING,
+			},
+		},
+	},
+};
+
+static int nft_table_builtin_add(struct nft_handle *h,
+				 const struct builtin_table *_t)
+{
+	struct nftnl_table *t;
+	int ret;
+
+	if (h->cache->table[_t->type].exists)
+		return 0;
+
+	t = nftnl_table_alloc();
+	if (t == NULL)
+		return -1;
+
+	nftnl_table_set_str(t, NFTNL_TABLE_NAME, _t->name);
+
+	ret = batch_table_add(h, NFT_COMPAT_TABLE_ADD, t) ? 0 : - 1;
+
+	return ret;
+}
+
+static struct nftnl_chain *
+nft_chain_builtin_alloc(const struct builtin_table *table,
+			const struct builtin_chain *chain, int policy)
+{
+	struct nftnl_chain *c;
+
+	c = nftnl_chain_alloc();
+	if (c == NULL)
+		return NULL;
+
+	nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table->name);
+	nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain->name);
+	nftnl_chain_set_u32(c, NFTNL_CHAIN_HOOKNUM, chain->hook);
+	nftnl_chain_set_u32(c, NFTNL_CHAIN_PRIO, chain->prio);
+	if (policy >= 0)
+		nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, policy);
+
+	nftnl_chain_set_str(c, NFTNL_CHAIN_TYPE, chain->type);
+
+	return c;
+}
+
+static void nft_chain_builtin_add(struct nft_handle *h,
+				  const struct builtin_table *table,
+				  const struct builtin_chain *chain,
+				  bool fake)
+{
+	struct nftnl_chain *c;
+
+	c = nft_chain_builtin_alloc(table, chain, NF_ACCEPT);
+	if (c == NULL)
+		return;
+
+	if (!fake)
+		batch_chain_add(h, NFT_COMPAT_CHAIN_ADD, c);
+	nft_cache_add_chain(h, table, c);
+}
+
+/* find if built-in table already exists */
+const struct builtin_table *
+nft_table_builtin_find(struct nft_handle *h, const char *table)
+{
+	int i;
+	bool found = false;
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+		if (h->tables[i].name == NULL)
+			continue;
+
+		if (strcmp(h->tables[i].name, table) != 0)
+			continue;
+
+		found = true;
+		break;
+	}
+
+	return found ? &h->tables[i] : NULL;
+}
+
+/* find if built-in chain already exists */
+const struct builtin_chain *
+nft_chain_builtin_find(const struct builtin_table *t, const char *chain)
+{
+	int i;
+	bool found = false;
+
+	for (i=0; i<NF_IP_NUMHOOKS && t->chains[i].name != NULL; i++) {
+		if (strcmp(t->chains[i].name, chain) != 0)
+			continue;
+
+		found = true;
+		break;
+	}
+	return found ? &t->chains[i] : NULL;
+}
+
+static void nft_chain_builtin_init(struct nft_handle *h,
+				   const struct builtin_table *table)
+{
+	int i;
+
+	/* Initialize built-in chains if they don't exist yet */
+	for (i=0; i < NF_INET_NUMHOOKS && table->chains[i].name != NULL; i++) {
+		if (nft_chain_find(h, table->name, table->chains[i].name))
+			continue;
+
+		nft_chain_builtin_add(h, table, &table->chains[i], false);
+	}
+}
+
+static const struct builtin_table *
+nft_xt_builtin_table_init(struct nft_handle *h, const char *table)
+{
+	const struct builtin_table *t;
+
+	if (!h->cache_init)
+		return NULL;
+
+	t = nft_table_builtin_find(h, table);
+	if (t == NULL)
+		return NULL;
+
+	if (nft_table_builtin_add(h, t) < 0)
+		return NULL;
+
+	return t;
+}
+
+static int nft_xt_builtin_init(struct nft_handle *h, const char *table,
+			       const char *chain)
+{
+	const struct builtin_table *t;
+	const struct builtin_chain *c;
+
+	if (!h->cache_init)
+		return 0;
+
+	t = nft_xt_builtin_table_init(h, table);
+	if (!t)
+		return -1;
+
+	if (h->cache_req.level < NFT_CL_CHAINS)
+		return 0;
+
+	if (!chain) {
+		nft_chain_builtin_init(h, t);
+		return 0;
+	}
+
+	c = nft_chain_builtin_find(t, chain);
+	if (!c)
+		return -1;
+
+	if (h->cache->table[t->type].base_chains[c->hook])
+		return 0;
+
+	nft_chain_builtin_add(h, t, c, false);
+	return 0;
+}
+
+static bool nft_chain_builtin(struct nftnl_chain *c)
+{
+	/* Check if this chain has hook number, in that case is built-in.
+	 * Should we better export the flags to user-space via nf_tables?
+	 */
+	return nftnl_chain_get(c, NFTNL_CHAIN_HOOKNUM) != NULL;
+}
+
+static int __nft_xt_fake_builtin_chains(struct nft_handle *h,
+				        const char *table, void *data)
+{
+	const char *chain = data ? *(const char **)data : NULL;
+	const struct builtin_table *t;
+	struct nft_chain **bcp;
+	int i;
+
+	t = nft_table_builtin_find(h, table);
+	if (!t)
+		return -1;
+
+	bcp = h->cache->table[t->type].base_chains;
+	for (i = 0; i < NF_INET_NUMHOOKS && t->chains[i].name; i++) {
+		if (bcp[t->chains[i].hook])
+			continue;
+
+		if (chain && strcmp(chain, t->chains[i].name))
+			continue;
+
+		nft_chain_builtin_add(h, t, &t->chains[i], true);
+	}
+	return 0;
+}
+
+int nft_xt_fake_builtin_chains(struct nft_handle *h,
+			       const char *table, const char *chain)
+{
+	if (table)
+		return __nft_xt_fake_builtin_chains(h, table, &chain);
+
+	return nft_for_each_table(h, __nft_xt_fake_builtin_chains, &chain);
+}
+
+int nft_restart(struct nft_handle *h)
+{
+	mnl_socket_close(h->nl);
+
+	h->nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (h->nl == NULL)
+		return -1;
+
+	if (mnl_socket_bind(h->nl, 0, MNL_SOCKET_AUTOPID) < 0)
+		return -1;
+
+	h->portid = mnl_socket_get_portid(h->nl);
+	h->nlsndbuffsiz = 0;
+	h->nlrcvbuffsiz = 0;
+
+	return 0;
+}
+
+int nft_init(struct nft_handle *h, int family, const struct builtin_table *t)
+{
+	memset(h, 0, sizeof(*h));
+
+	h->nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (h->nl == NULL)
+		return -1;
+
+	if (mnl_socket_bind(h->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		mnl_socket_close(h->nl);
+		return -1;
+	}
+
+	h->ops = nft_family_ops_lookup(family);
+	if (!h->ops)
+		xtables_error(PARAMETER_PROBLEM, "Unknown family");
+
+	h->portid = mnl_socket_get_portid(h->nl);
+	h->tables = t;
+	h->cache = &h->__cache[0];
+	h->family = family;
+
+	INIT_LIST_HEAD(&h->obj_list);
+	INIT_LIST_HEAD(&h->err_list);
+	INIT_LIST_HEAD(&h->cmd_list);
+	INIT_LIST_HEAD(&h->cache_req.chain_list);
+
+	return 0;
+}
+
+void nft_fini(struct nft_handle *h)
+{
+	struct list_head *pos, *n;
+
+	list_for_each_safe(pos, n, &h->cmd_list)
+		nft_cmd_free(list_entry(pos, struct nft_cmd, head));
+
+	list_for_each_safe(pos, n, &h->obj_list)
+		batch_obj_del(h, list_entry(pos, struct obj_update, head));
+
+	list_for_each_safe(pos, n, &h->err_list)
+		mnl_err_list_free(list_entry(pos, struct mnl_err, head));
+
+	nft_release_cache(h);
+	mnl_socket_close(h->nl);
+}
+
+static void nft_chain_print_debug(struct nftnl_chain *c, struct nlmsghdr *nlh)
+{
+#ifdef NLDEBUG
+	char tmp[1024];
+
+	nftnl_chain_snprintf(tmp, sizeof(tmp), c, 0, 0);
+	printf("DEBUG: chain: %s\n", tmp);
+	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg));
+#endif
+}
+
+static struct nftnl_chain *nft_chain_new(struct nft_handle *h,
+				       const char *table, const char *chain,
+				       int policy,
+				       const struct xt_counters *counters)
+{
+	struct nftnl_chain *c;
+	const struct builtin_table *_t;
+	const struct builtin_chain *_c;
+
+	_t = nft_table_builtin_find(h, table);
+	if (!_t) {
+		errno = ENXIO;
+		return NULL;
+	}
+
+	/* if this built-in table does not exists, create it */
+	nft_xt_builtin_init(h, table, chain);
+
+	_c = nft_chain_builtin_find(_t, chain);
+	if (_c != NULL) {
+		/* This is a built-in chain */
+		c = nft_chain_builtin_alloc(_t, _c, policy);
+		if (c == NULL)
+			return NULL;
+	} else {
+		errno = ENOENT;
+		return NULL;
+	}
+
+	if (counters) {
+		nftnl_chain_set_u64(c, NFTNL_CHAIN_BYTES,
+					counters->bcnt);
+		nftnl_chain_set_u64(c, NFTNL_CHAIN_PACKETS,
+					counters->pcnt);
+	}
+
+	return c;
+}
+
+int nft_chain_set(struct nft_handle *h, const char *table,
+		  const char *chain, const char *policy,
+		  const struct xt_counters *counters)
+{
+	struct nftnl_chain *c = NULL;
+
+	nft_fn = nft_chain_set;
+
+	if (strcmp(policy, "DROP") == 0)
+		c = nft_chain_new(h, table, chain, NF_DROP, counters);
+	else if (strcmp(policy, "ACCEPT") == 0)
+		c = nft_chain_new(h, table, chain, NF_ACCEPT, counters);
+	else if (strcmp(policy, "-") == 0)
+		c = nft_chain_new(h, table, chain, -1, counters);
+	else
+		errno = EINVAL;
+
+	if (c == NULL)
+		return 0;
+
+	if (!batch_chain_add(h, NFT_COMPAT_CHAIN_UPDATE, c))
+		return 0;
+
+	/* the core expects 1 for success and 0 for error */
+	return 1;
+}
+
+static int __add_match(struct nftnl_expr *e, struct xt_entry_match *m)
+{
+	void *info;
+
+	nftnl_expr_set(e, NFTNL_EXPR_MT_NAME, m->u.user.name, strlen(m->u.user.name));
+	nftnl_expr_set_u32(e, NFTNL_EXPR_MT_REV, m->u.user.revision);
+
+	info = calloc(1, m->u.match_size);
+	if (info == NULL)
+		return -ENOMEM;
+
+	memcpy(info, m->data, m->u.match_size - sizeof(*m));
+	nftnl_expr_set(e, NFTNL_EXPR_MT_INFO, info, m->u.match_size - sizeof(*m));
+
+	return 0;
+}
+
+static int add_nft_limit(struct nftnl_rule *r, struct xt_entry_match *m)
+{
+	struct xt_rateinfo *rinfo = (void *)m->data;
+	static const uint32_t mult[] = {
+		XT_LIMIT_SCALE*24*60*60,	/* day */
+		XT_LIMIT_SCALE*60*60,		/* hour */
+		XT_LIMIT_SCALE*60,		/* min */
+		XT_LIMIT_SCALE,			/* sec */
+	};
+	struct nftnl_expr *expr;
+	int i;
+
+	expr = nftnl_expr_alloc("limit");
+	if (!expr)
+		return -ENOMEM;
+
+	for (i = 1; i < ARRAY_SIZE(mult); i++) {
+		if (rinfo->avg > mult[i] ||
+		    mult[i] / rinfo->avg < mult[i] % rinfo->avg)
+			break;
+	}
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_TYPE, NFT_LIMIT_PKTS);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_FLAGS, 0);
+
+	nftnl_expr_set_u64(expr, NFTNL_EXPR_LIMIT_RATE,
+			   mult[i - 1] / rinfo->avg);
+        nftnl_expr_set_u64(expr, NFTNL_EXPR_LIMIT_UNIT,
+			   mult[i - 1] / XT_LIMIT_SCALE);
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_BURST, rinfo->burst);
+
+	nftnl_rule_add_expr(r, expr);
+	return 0;
+}
+
+static struct nftnl_set *add_anon_set(struct nft_handle *h, const char *table,
+				      uint32_t flags, uint32_t key_type,
+				      uint32_t key_len, uint32_t size)
+{
+	static uint32_t set_id = 0;
+	struct nftnl_set *s;
+	struct nft_cmd *cmd;
+
+	s = nftnl_set_alloc();
+	if (!s)
+		return NULL;
+
+	nftnl_set_set_u32(s, NFTNL_SET_FAMILY, h->family);
+	nftnl_set_set_str(s, NFTNL_SET_TABLE, table);
+	nftnl_set_set_str(s, NFTNL_SET_NAME, "__set%d");
+	nftnl_set_set_u32(s, NFTNL_SET_ID, ++set_id);
+	nftnl_set_set_u32(s, NFTNL_SET_FLAGS,
+			  NFT_SET_ANONYMOUS | NFT_SET_CONSTANT | flags);
+	nftnl_set_set_u32(s, NFTNL_SET_KEY_TYPE, key_type);
+	nftnl_set_set_u32(s, NFTNL_SET_KEY_LEN, key_len);
+	nftnl_set_set_u32(s, NFTNL_SET_DESC_SIZE, size);
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_SET_ADD, table, NULL, NULL, -1, false);
+	if (!cmd) {
+		nftnl_set_free(s);
+		return NULL;
+	}
+	cmd->obj.set = s;
+
+	return s;
+}
+
+static struct nftnl_expr *
+gen_payload(uint32_t base, uint32_t offset, uint32_t len, uint32_t dreg)
+{
+	struct nftnl_expr *e = nftnl_expr_alloc("payload");
+
+	if (!e)
+		return NULL;
+	nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_BASE, base);
+	nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET, offset);
+	nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_LEN, len);
+	nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_DREG, dreg);
+	return e;
+}
+
+static struct nftnl_expr *
+gen_lookup(uint32_t sreg, const char *set_name, uint32_t set_id, uint32_t flags)
+{
+	struct nftnl_expr *e = nftnl_expr_alloc("lookup");
+
+	if (!e)
+		return NULL;
+	nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_SREG, sreg);
+	nftnl_expr_set_str(e, NFTNL_EXPR_LOOKUP_SET, set_name);
+	nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_SET_ID, set_id);
+	nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_FLAGS, flags);
+	return e;
+}
+
+/* simplified nftables:include/netlink.h, netlink_padded_len() */
+#define NETLINK_ALIGN		4
+
+/* from nftables:include/datatype.h, TYPE_BITS */
+#define CONCAT_TYPE_BITS	6
+
+/* from nftables:include/datatype.h, enum datatypes */
+#define NFT_DATATYPE_IPADDR	7
+#define NFT_DATATYPE_ETHERADDR	9
+
+static int __add_nft_among(struct nft_handle *h, const char *table,
+			   struct nftnl_rule *r, struct nft_among_pair *pairs,
+			   int cnt, bool dst, bool inv, bool ip)
+{
+	uint32_t set_id, type = NFT_DATATYPE_ETHERADDR, len = ETH_ALEN;
+	/* { !dst, dst } */
+	static const int eth_addr_off[] = {
+		offsetof(struct ether_header, ether_shost),
+		offsetof(struct ether_header, ether_dhost)
+	};
+	static const int ip_addr_off[] = {
+		offsetof(struct iphdr, saddr),
+		offsetof(struct iphdr, daddr)
+	};
+	struct nftnl_expr *e;
+	struct nftnl_set *s;
+	uint32_t flags = 0;
+	int idx = 0;
+
+	if (ip) {
+		type = type << CONCAT_TYPE_BITS | NFT_DATATYPE_IPADDR;
+		len += sizeof(struct in_addr) + NETLINK_ALIGN - 1;
+		len &= ~(NETLINK_ALIGN - 1);
+		flags = NFT_SET_INTERVAL;
+	}
+
+	s = add_anon_set(h, table, flags, type, len, cnt);
+	if (!s)
+		return -ENOMEM;
+	set_id = nftnl_set_get_u32(s, NFTNL_SET_ID);
+
+	if (ip) {
+		uint8_t field_len[2] = { ETH_ALEN, sizeof(struct in_addr) };
+
+		nftnl_set_set_data(s, NFTNL_SET_DESC_CONCAT,
+				   field_len, sizeof(field_len));
+	}
+
+	for (idx = 0; idx < cnt; idx++) {
+		struct nftnl_set_elem *elem = nftnl_set_elem_alloc();
+
+		if (!elem)
+			return -ENOMEM;
+		nftnl_set_elem_set(elem, NFTNL_SET_ELEM_KEY,
+				   &pairs[idx], len);
+		if (ip) {
+			struct in_addr tmp = pairs[idx].in;
+
+			if (tmp.s_addr == INADDR_ANY)
+				pairs[idx].in.s_addr = INADDR_BROADCAST;
+			nftnl_set_elem_set(elem, NFTNL_SET_ELEM_KEY_END,
+					   &pairs[idx], len);
+			pairs[idx].in = tmp;
+		}
+		nftnl_set_elem_add(s, elem);
+	}
+
+	e = gen_payload(NFT_PAYLOAD_LL_HEADER,
+			eth_addr_off[dst], ETH_ALEN, NFT_REG_1);
+	if (!e)
+		return -ENOMEM;
+	nftnl_rule_add_expr(r, e);
+
+	if (ip) {
+		e = gen_payload(NFT_PAYLOAD_NETWORK_HEADER, ip_addr_off[dst],
+				sizeof(struct in_addr), NFT_REG32_02);
+		if (!e)
+			return -ENOMEM;
+		nftnl_rule_add_expr(r, e);
+	}
+
+	e = gen_lookup(NFT_REG_1, "__set%d", set_id, inv);
+	if (!e)
+		return -ENOMEM;
+	nftnl_rule_add_expr(r, e);
+
+	return 0;
+}
+
+static int add_nft_among(struct nft_handle *h,
+			 struct nftnl_rule *r, struct xt_entry_match *m)
+{
+	struct nft_among_data *data = (struct nft_among_data *)m->data;
+	const char *table = nftnl_rule_get(r, NFTNL_RULE_TABLE);
+
+	if ((data->src.cnt && data->src.ip) ||
+	    (data->dst.cnt && data->dst.ip)) {
+		uint16_t eth_p_ip = htons(ETH_P_IP);
+
+		add_meta(r, NFT_META_PROTOCOL);
+		add_cmp_ptr(r, NFT_CMP_EQ, &eth_p_ip, 2);
+	}
+
+	if (data->src.cnt)
+		__add_nft_among(h, table, r, data->pairs, data->src.cnt,
+				false, data->src.inv, data->src.ip);
+	if (data->dst.cnt)
+		__add_nft_among(h, table, r, data->pairs + data->src.cnt,
+				data->dst.cnt, true, data->dst.inv,
+				data->dst.ip);
+	return 0;
+}
+
+int add_match(struct nft_handle *h,
+	      struct nftnl_rule *r, struct xt_entry_match *m)
+{
+	struct nftnl_expr *expr;
+	int ret;
+
+	if (!strcmp(m->u.user.name, "limit"))
+		return add_nft_limit(r, m);
+	else if (!strcmp(m->u.user.name, "among"))
+		return add_nft_among(h, r, m);
+
+	expr = nftnl_expr_alloc("match");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	ret = __add_match(expr, m);
+	nftnl_rule_add_expr(r, expr);
+
+	return ret;
+}
+
+static int __add_target(struct nftnl_expr *e, struct xt_entry_target *t)
+{
+	void *info;
+
+	nftnl_expr_set(e, NFTNL_EXPR_TG_NAME, t->u.user.name,
+			  strlen(t->u.user.name));
+	nftnl_expr_set_u32(e, NFTNL_EXPR_TG_REV, t->u.user.revision);
+
+	info = calloc(1, t->u.target_size);
+	if (info == NULL)
+		return -ENOMEM;
+
+	memcpy(info, t->data, t->u.target_size - sizeof(*t));
+	nftnl_expr_set(e, NFTNL_EXPR_TG_INFO, info, t->u.target_size - sizeof(*t));
+
+	return 0;
+}
+
+static int add_meta_nftrace(struct nftnl_rule *r)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("immediate");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG32_01);
+	nftnl_expr_set_u8(expr, NFTNL_EXPR_IMM_DATA, 1);
+	nftnl_rule_add_expr(r, expr);
+
+	expr = nftnl_expr_alloc("meta");
+	if (expr == NULL)
+		return -ENOMEM;
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_META_KEY, NFT_META_NFTRACE);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_META_SREG, NFT_REG32_01);
+
+	nftnl_rule_add_expr(r, expr);
+	return 0;
+}
+
+int add_target(struct nftnl_rule *r, struct xt_entry_target *t)
+{
+	struct nftnl_expr *expr;
+	int ret;
+
+	if (strcmp(t->u.user.name, "TRACE") == 0)
+		return add_meta_nftrace(r);
+
+	expr = nftnl_expr_alloc("target");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	ret = __add_target(expr, t);
+	nftnl_rule_add_expr(r, expr);
+
+	return ret;
+}
+
+int add_jumpto(struct nftnl_rule *r, const char *name, int verdict)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("immediate");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_VERDICT, verdict);
+	nftnl_expr_set_str(expr, NFTNL_EXPR_IMM_CHAIN, (char *)name);
+	nftnl_rule_add_expr(r, expr);
+
+	return 0;
+}
+
+int add_verdict(struct nftnl_rule *r, int verdict)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("immediate");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_VERDICT, verdict);
+	nftnl_rule_add_expr(r, expr);
+
+	return 0;
+}
+
+int add_action(struct nftnl_rule *r, struct iptables_command_state *cs,
+	       bool goto_set)
+{
+       int ret = 0;
+
+       /* If no target at all, add nothing (default to continue) */
+       if (cs->target != NULL) {
+	       /* Standard target? */
+	       if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
+		       ret = add_verdict(r, NF_ACCEPT);
+	       else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
+		       ret = add_verdict(r, NF_DROP);
+	       else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
+		       ret = add_verdict(r, NFT_RETURN);
+	       else
+		       ret = add_target(r, cs->target->t);
+       } else if (strlen(cs->jumpto) > 0) {
+	       /* Not standard, then it's a go / jump to chain */
+	       if (goto_set)
+		       ret = add_jumpto(r, cs->jumpto, NFT_GOTO);
+	       else
+		       ret = add_jumpto(r, cs->jumpto, NFT_JUMP);
+       }
+       return ret;
+}
+
+static void nft_rule_print_debug(struct nftnl_rule *r, struct nlmsghdr *nlh)
+{
+#ifdef NLDEBUG
+	char tmp[1024];
+
+	nftnl_rule_snprintf(tmp, sizeof(tmp), r, 0, 0);
+	printf("DEBUG: rule: %s\n", tmp);
+	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg));
+#endif
+}
+
+int add_counters(struct nftnl_rule *r, uint64_t packets, uint64_t bytes)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("counter");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nftnl_expr_set_u64(expr, NFTNL_EXPR_CTR_PACKETS, packets);
+	nftnl_expr_set_u64(expr, NFTNL_EXPR_CTR_BYTES, bytes);
+
+	nftnl_rule_add_expr(r, expr);
+
+	return 0;
+}
+
+enum udata_type {
+	UDATA_TYPE_COMMENT,
+	UDATA_TYPE_EBTABLES_POLICY,
+	__UDATA_TYPE_MAX,
+};
+#define UDATA_TYPE_MAX (__UDATA_TYPE_MAX - 1)
+
+static int parse_udata_cb(const struct nftnl_udata *attr, void *data)
+{
+	unsigned char *value = nftnl_udata_get(attr);
+	uint8_t type = nftnl_udata_type(attr);
+	uint8_t len = nftnl_udata_len(attr);
+	const struct nftnl_udata **tb = data;
+
+	switch (type) {
+	case UDATA_TYPE_COMMENT:
+		if (value[len - 1] != '\0')
+			return -1;
+		break;
+	case UDATA_TYPE_EBTABLES_POLICY:
+		break;
+	default:
+		return 0;
+	}
+	tb[type] = attr;
+	return 0;
+}
+
+char *get_comment(const void *data, uint32_t data_len)
+{
+	const struct nftnl_udata *tb[UDATA_TYPE_MAX + 1] = {};
+
+	if (nftnl_udata_parse(data, data_len, parse_udata_cb, tb) < 0)
+		return NULL;
+
+	if (!tb[UDATA_TYPE_COMMENT])
+		return NULL;
+
+	return nftnl_udata_get(tb[UDATA_TYPE_COMMENT]);
+}
+
+void add_compat(struct nftnl_rule *r, uint32_t proto, bool inv)
+{
+	nftnl_rule_set_u32(r, NFTNL_RULE_COMPAT_PROTO, proto);
+	nftnl_rule_set_u32(r, NFTNL_RULE_COMPAT_FLAGS,
+			      inv ? NFT_RULE_COMPAT_F_INV : 0);
+}
+
+struct nftnl_rule *
+nft_rule_new(struct nft_handle *h, const char *chain, const char *table,
+	     void *data)
+{
+	struct nftnl_rule *r;
+
+	r = nftnl_rule_alloc();
+	if (r == NULL)
+		return NULL;
+
+	nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, h->family);
+	nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
+	nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);
+
+	if (h->ops->add(h, r, data) < 0)
+		goto err;
+
+	return r;
+err:
+	nftnl_rule_free(r);
+	return NULL;
+}
+
+int
+nft_rule_append(struct nft_handle *h, const char *chain, const char *table,
+		struct nftnl_rule *r, struct nftnl_rule *ref, bool verbose)
+{
+	struct nft_chain *c;
+	int type;
+
+	nft_xt_builtin_init(h, table, chain);
+
+	nft_fn = nft_rule_append;
+
+	if (ref) {
+		nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE,
+				   nftnl_rule_get_u64(ref, NFTNL_RULE_HANDLE));
+		type = NFT_COMPAT_RULE_REPLACE;
+	} else
+		type = NFT_COMPAT_RULE_APPEND;
+
+	if (batch_rule_add(h, type, r) == NULL)
+		return 0;
+
+	if (verbose)
+		h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
+
+	if (ref) {
+		nftnl_chain_rule_insert_at(r, ref);
+		nftnl_chain_rule_del(ref);
+		nftnl_rule_free(ref);
+	} else {
+		c = nft_chain_find(h, table, chain);
+		if (!c) {
+			errno = ENOENT;
+			return 0;
+		}
+		nftnl_chain_rule_add_tail(r, c->nftnl);
+	}
+
+	return 1;
+}
+
+void
+nft_rule_print_save(struct nft_handle *h, const struct nftnl_rule *r,
+		    enum nft_rule_print type, unsigned int format)
+{
+	const char *chain = nftnl_rule_get_str(r, NFTNL_RULE_CHAIN);
+	struct iptables_command_state cs = {};
+	struct nft_family_ops *ops = h->ops;
+
+	ops->rule_to_cs(h, r, &cs);
+
+	if (!(format & (FMT_NOCOUNTS | FMT_C_COUNTS)))
+		printf("[%llu:%llu] ", (unsigned long long)cs.counters.pcnt,
+				       (unsigned long long)cs.counters.bcnt);
+
+	/* print chain name */
+	switch(type) {
+	case NFT_RULE_APPEND:
+		printf("-A %s ", chain);
+		break;
+	case NFT_RULE_DEL:
+		printf("-D %s ", chain);
+		break;
+	}
+
+	if (ops->save_rule)
+		ops->save_rule(&cs, format);
+
+	if (ops->clear_cs)
+		ops->clear_cs(&cs);
+}
+
+static bool nft_rule_is_policy_rule(struct nftnl_rule *r)
+{
+	const struct nftnl_udata *tb[UDATA_TYPE_MAX + 1] = {};
+	const void *data;
+	uint32_t len;
+
+	if (!nftnl_rule_is_set(r, NFTNL_RULE_USERDATA))
+		return false;
+
+	data = nftnl_rule_get_data(r, NFTNL_RULE_USERDATA, &len);
+	if (nftnl_udata_parse(data, len, parse_udata_cb, tb) < 0)
+		return NULL;
+
+	if (!tb[UDATA_TYPE_EBTABLES_POLICY] ||
+	    nftnl_udata_get_u32(tb[UDATA_TYPE_EBTABLES_POLICY]) != 1)
+		return false;
+
+	return true;
+}
+
+static struct nftnl_rule *nft_chain_last_rule(struct nftnl_chain *c)
+{
+	struct nftnl_rule *r = NULL, *last;
+	struct nftnl_rule_iter *iter;
+
+	iter = nftnl_rule_iter_create(c);
+	if (!iter)
+		return NULL;
+
+	do {
+		last = r;
+		r = nftnl_rule_iter_next(iter);
+	} while (r);
+	nftnl_rule_iter_destroy(iter);
+
+	return last;
+}
+
+void nft_bridge_chain_postprocess(struct nft_handle *h,
+				  struct nftnl_chain *c)
+{
+	struct nftnl_rule *last = nft_chain_last_rule(c);
+	struct nftnl_expr_iter *iter;
+	struct nftnl_expr *expr;
+	int verdict;
+
+	if (!last || !nft_rule_is_policy_rule(last))
+		return;
+
+	iter = nftnl_expr_iter_create(last);
+	if (!iter)
+		return;
+
+	expr = nftnl_expr_iter_next(iter);
+	if (!expr ||
+	    strcmp("counter", nftnl_expr_get_str(expr, NFTNL_EXPR_NAME)))
+		goto out_iter;
+
+	expr = nftnl_expr_iter_next(iter);
+	if (!expr ||
+	    strcmp("immediate", nftnl_expr_get_str(expr, NFTNL_EXPR_NAME)) ||
+	    !nftnl_expr_is_set(expr, NFTNL_EXPR_IMM_VERDICT))
+		goto out_iter;
+
+	verdict = nftnl_expr_get_u32(expr, NFTNL_EXPR_IMM_VERDICT);
+	switch (verdict) {
+	case NF_ACCEPT:
+	case NF_DROP:
+		break;
+	default:
+		goto out_iter;
+	}
+
+	nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, verdict);
+	if (batch_rule_add(h, NFT_COMPAT_RULE_DELETE, last) == NULL)
+		fprintf(stderr, "Failed to delete old policy rule\n");
+	nftnl_chain_rule_del(last);
+out_iter:
+	nftnl_expr_iter_destroy(iter);
+}
+static const char *policy_name[NF_ACCEPT+1] = {
+	[NF_DROP] = "DROP",
+	[NF_ACCEPT] = "ACCEPT",
+};
+
+int nft_chain_save(struct nft_chain *nc, void *data)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	struct nft_handle *h = data;
+	const char *policy = NULL;
+
+	if (nftnl_chain_is_set(c, NFTNL_CHAIN_POLICY)) {
+		policy = policy_name[nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY)];
+	} else if (nft_chain_builtin(c)) {
+		policy = "ACCEPT";
+	} else if (h->family == NFPROTO_BRIDGE) {
+		policy = "RETURN";
+	}
+
+	if (h->ops->save_chain)
+		h->ops->save_chain(c, policy);
+
+	return 0;
+}
+
+struct nft_rule_save_data {
+	struct nft_handle *h;
+	unsigned int format;
+};
+
+static int nft_rule_save_cb(struct nft_chain *c, void *data)
+{
+	struct nft_rule_save_data *d = data;
+	struct nftnl_rule_iter *iter;
+	struct nftnl_rule *r;
+
+	iter = nftnl_rule_iter_create(c->nftnl);
+	if (iter == NULL)
+		return 1;
+
+	r = nftnl_rule_iter_next(iter);
+	while (r != NULL) {
+		nft_rule_print_save(d->h, r, NFT_RULE_APPEND, d->format);
+		r = nftnl_rule_iter_next(iter);
+	}
+
+	nftnl_rule_iter_destroy(iter);
+	return 0;
+}
+
+int nft_rule_save(struct nft_handle *h, const char *table, unsigned int format)
+{
+	struct nft_rule_save_data d = {
+		.h = h,
+		.format = format,
+	};
+	int ret;
+
+	ret = nft_chain_foreach(h, table, nft_rule_save_cb, &d);
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+struct nftnl_set *nft_set_batch_lookup_byid(struct nft_handle *h,
+					    uint32_t set_id)
+{
+	struct obj_update *n;
+
+	list_for_each_entry(n, &h->obj_list, head) {
+		if (n->type == NFT_COMPAT_SET_ADD &&
+		    nftnl_set_get_u32(n->set, NFTNL_SET_ID) == set_id)
+			return n->set;
+	}
+
+	return NULL;
+}
+
+static void
+__nft_rule_flush(struct nft_handle *h, const char *table,
+		 const char *chain, bool verbose, bool skip)
+{
+	struct obj_update *obj;
+	struct nftnl_rule *r;
+
+	if (verbose && chain)
+		fprintf(stdout, "Flushing chain `%s'\n", chain);
+
+	r = nftnl_rule_alloc();
+	if (r == NULL)
+		return;
+
+	nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
+	if (chain)
+		nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);
+
+	obj = batch_rule_add(h, NFT_COMPAT_RULE_FLUSH, r);
+	if (!obj) {
+		nftnl_rule_free(r);
+		return;
+	}
+
+	obj->skip = skip;
+}
+
+struct nft_rule_flush_data {
+	struct nft_handle *h;
+	const char *table;
+	bool verbose;
+};
+
+static int nft_rule_flush_cb(struct nft_chain *c, void *data)
+{
+	const char *chain = nftnl_chain_get_str(c->nftnl, NFTNL_CHAIN_NAME);
+	struct nft_rule_flush_data *d = data;
+
+	batch_chain_flush(d->h, d->table, chain);
+	__nft_rule_flush(d->h, d->table, chain, d->verbose, false);
+	flush_rule_cache(d->h, d->table, c);
+	return 0;
+}
+
+int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table,
+		   bool verbose)
+{
+	struct nft_rule_flush_data d = {
+		.h = h,
+		.table = table,
+		.verbose = verbose,
+	};
+	struct nft_chain *c = NULL;
+	int ret = 0;
+
+	nft_fn = nft_rule_flush;
+
+	if (chain || verbose)
+		nft_xt_builtin_init(h, table, chain);
+	else if (!nft_table_find(h, table))
+		return 1;
+
+	if (chain) {
+		c = nft_chain_find(h, table, chain);
+		if (!c) {
+			errno = ENOENT;
+			return 0;
+		}
+	}
+
+	if (chain || !verbose) {
+		batch_chain_flush(h, table, chain);
+		__nft_rule_flush(h, table, chain, verbose, false);
+		flush_rule_cache(h, table, c);
+		return 1;
+	}
+
+	ret = nft_chain_foreach(h, table, nft_rule_flush_cb, &d);
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *table)
+{
+	const struct builtin_table *t;
+	struct nftnl_chain *c;
+
+	nft_fn = nft_chain_user_add;
+
+	t = nft_xt_builtin_table_init(h, table);
+
+	if (nft_chain_exists(h, table, chain)) {
+		errno = EEXIST;
+		return 0;
+	}
+
+	c = nftnl_chain_alloc();
+	if (c == NULL)
+		return 0;
+
+	nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
+	nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain);
+	if (h->family == NFPROTO_BRIDGE)
+		nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, NF_ACCEPT);
+
+	if (!batch_chain_add(h, NFT_COMPAT_CHAIN_USER_ADD, c))
+		return 0;
+
+	nft_cache_add_chain(h, t, c);
+
+	/* the core expects 1 for success and 0 for error */
+	return 1;
+}
+
+int nft_chain_restore(struct nft_handle *h, const char *chain, const char *table)
+{
+	const struct builtin_table *t;
+	struct obj_update *obj;
+	struct nftnl_chain *c;
+	struct nft_chain *nc;
+	bool created = false;
+
+	t = nft_xt_builtin_table_init(h, table);
+
+	nc = nft_chain_find(h, table, chain);
+	if (!nc) {
+		c = nftnl_chain_alloc();
+		if (!c)
+			return 0;
+
+		nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
+		nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain);
+		created = true;
+
+		nft_cache_add_chain(h, t, c);
+	} else {
+		c = nc->nftnl;
+
+		/* If the chain should vanish meanwhile, kernel genid changes
+		 * and the transaction is refreshed enabling the chain add
+		 * object. With the handle still set, kernel interprets it as a
+		 * chain replace job and errors since it is not found anymore.
+		 */
+		nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE);
+	}
+
+	__nft_rule_flush(h, table, chain, false, created);
+
+	obj = batch_chain_add(h, NFT_COMPAT_CHAIN_USER_ADD, c);
+	if (!obj)
+		return 0;
+
+	obj->skip = !created;
+
+	/* the core expects 1 for success and 0 for error */
+	return 1;
+}
+
+/* From linux/netlink.h */
+#ifndef NLM_F_NONREC
+#define NLM_F_NONREC	0x100	/* Do not delete recursively    */
+#endif
+
+struct chain_user_del_data {
+	struct nft_handle	*handle;
+	bool			verbose;
+	int			builtin_err;
+};
+
+static int __nft_chain_user_del(struct nft_chain *nc, void *data)
+{
+	struct chain_user_del_data *d = data;
+	struct nftnl_chain *c = nc->nftnl;
+	struct nft_handle *h = d->handle;
+
+	/* don't delete built-in chain */
+	if (nft_chain_builtin(c))
+		return d->builtin_err;
+
+	if (d->verbose)
+		fprintf(stdout, "Deleting chain `%s'\n",
+			nftnl_chain_get_str(c, NFTNL_CHAIN_NAME));
+
+
+	/* XXX This triggers a fast lookup from the kernel. */
+	nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE);
+	if (!batch_chain_add(h, NFT_COMPAT_CHAIN_USER_DEL, c))
+		return -1;
+
+	/* nftnl_chain is freed when deleting the batch object */
+	nc->nftnl = NULL;
+
+	nft_chain_list_del(nc);
+	nft_chain_free(nc);
+	return 0;
+}
+
+int nft_chain_user_del(struct nft_handle *h, const char *chain,
+		       const char *table, bool verbose)
+{
+	struct chain_user_del_data d = {
+		.handle = h,
+		.verbose = verbose,
+	};
+	struct nft_chain *c;
+	int ret = 0;
+
+	nft_fn = nft_chain_user_del;
+
+	if (chain) {
+		c = nft_chain_find(h, table, chain);
+		if (!c) {
+			errno = ENOENT;
+			return 0;
+		}
+		d.builtin_err = -2;
+		ret = __nft_chain_user_del(c, &d);
+		if (ret == -2)
+			errno = EINVAL;
+		goto out;
+	}
+
+	ret = nft_chain_foreach(h, table, __nft_chain_user_del, &d);
+out:
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+bool nft_chain_exists(struct nft_handle *h,
+		      const char *table, const char *chain)
+{
+	const struct builtin_table *t = nft_table_builtin_find(h, table);
+
+	/* xtables does not support custom tables */
+	if (!t)
+		return false;
+
+	if (nft_chain_builtin_find(t, chain))
+		return true;
+
+	return !!nft_chain_find(h, table, chain);
+}
+
+int nft_chain_user_rename(struct nft_handle *h,const char *chain,
+			  const char *table, const char *newname)
+{
+	struct nftnl_chain *c;
+	struct nft_chain *nc;
+	uint64_t handle;
+
+	nft_fn = nft_chain_user_rename;
+
+	if (nft_chain_exists(h, table, newname)) {
+		errno = EEXIST;
+		return 0;
+	}
+
+	/* Find the old chain to be renamed */
+	nc = nft_chain_find(h, table, chain);
+	if (nc == NULL) {
+		errno = ENOENT;
+		return 0;
+	}
+	handle = nftnl_chain_get_u64(nc->nftnl, NFTNL_CHAIN_HANDLE);
+
+	/* Now prepare the new name for the chain */
+	c = nftnl_chain_alloc();
+	if (c == NULL)
+		return 0;
+
+	nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
+	nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, newname);
+	nftnl_chain_set_u64(c, NFTNL_CHAIN_HANDLE, handle);
+
+	if (!batch_chain_add(h, NFT_COMPAT_CHAIN_RENAME, c))
+		return 0;
+
+	/* the core expects 1 for success and 0 for error */
+	return 1;
+}
+
+bool nft_table_find(struct nft_handle *h, const char *tablename)
+{
+	const struct builtin_table *t;
+
+	t = nft_table_builtin_find(h, tablename);
+	return t ? h->cache->table[t->type].exists : false;
+}
+
+int nft_for_each_table(struct nft_handle *h,
+		       int (*func)(struct nft_handle *h, const char *tablename, void *data),
+		       void *data)
+{
+	int i;
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+		if (h->tables[i].name == NULL)
+			continue;
+
+		if (!h->cache->table[h->tables[i].type].exists)
+			continue;
+
+		func(h, h->tables[i].name, data);
+	}
+
+	return 0;
+}
+
+static int __nft_table_flush(struct nft_handle *h, const char *table, bool exists)
+{
+	const struct builtin_table *_t;
+	struct obj_update *obj;
+	struct nftnl_table *t;
+
+	t = nftnl_table_alloc();
+	if (t == NULL)
+		return -1;
+
+	nftnl_table_set_str(t, NFTNL_TABLE_NAME, table);
+
+	obj = batch_table_add(h, NFT_COMPAT_TABLE_FLUSH, t);
+	if (!obj) {
+		nftnl_table_free(t);
+		return -1;
+	}
+
+	if (!exists)
+		obj->skip = 1;
+
+	_t = nft_table_builtin_find(h, table);
+	assert(_t);
+	h->cache->table[_t->type].exists = false;
+
+	flush_chain_cache(h, table);
+
+	return 0;
+}
+
+int nft_table_flush(struct nft_handle *h, const char *table)
+{
+	const struct builtin_table *t;
+	int ret = 0;
+
+	nft_fn = nft_table_flush;
+
+	t = nft_table_builtin_find(h, table);
+	if (!t)
+		return 0;
+
+	ret = __nft_table_flush(h, table, h->cache->table[t->type].exists);
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+static int __nft_rule_del(struct nft_handle *h, struct nftnl_rule *r)
+{
+	struct obj_update *obj;
+
+	nftnl_rule_list_del(r);
+
+	if (!nftnl_rule_get_u64(r, NFTNL_RULE_HANDLE))
+		nftnl_rule_set_u32(r, NFTNL_RULE_ID, ++h->rule_id);
+
+	obj = batch_rule_add(h, NFT_COMPAT_RULE_DELETE, r);
+	if (!obj) {
+		nftnl_rule_free(r);
+		return -1;
+	}
+	return 1;
+}
+
+static bool nft_rule_cmp(struct nft_handle *h, struct nftnl_rule *r,
+			 struct nftnl_rule *rule)
+{
+	struct iptables_command_state _cs = {}, this = {}, *cs = &_cs;
+	bool ret = false;
+
+	h->ops->rule_to_cs(h, r, &this);
+	h->ops->rule_to_cs(h, rule, cs);
+
+	DEBUGP("comparing with... ");
+#ifdef DEBUG_DEL
+	nft_rule_print_save(h, r, NFT_RULE_APPEND, 0);
+#endif
+	if (!h->ops->is_same(cs, &this))
+		goto out;
+
+	if (!compare_matches(cs->matches, this.matches)) {
+		DEBUGP("Different matches\n");
+		goto out;
+	}
+
+	if (!compare_targets(cs->target, this.target)) {
+		DEBUGP("Different target\n");
+		goto out;
+	}
+
+	if ((!cs->target || !this.target) &&
+	    strcmp(cs->jumpto, this.jumpto) != 0) {
+		DEBUGP("Different verdict\n");
+		goto out;
+	}
+
+	ret = true;
+out:
+	h->ops->clear_cs(&this);
+	h->ops->clear_cs(cs);
+	return ret;
+}
+
+static struct nftnl_rule *
+nft_rule_find(struct nft_handle *h, struct nft_chain *nc,
+	      struct nftnl_rule *rule, int rulenum)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	struct nftnl_rule *r;
+	struct nftnl_rule_iter *iter;
+	bool found = false;
+
+	if (rulenum >= 0)
+		/* Delete by rule number case */
+		return nftnl_rule_lookup_byindex(c, rulenum);
+
+	iter = nftnl_rule_iter_create(c);
+	if (iter == NULL)
+		return 0;
+
+	r = nftnl_rule_iter_next(iter);
+	while (r != NULL) {
+		found = nft_rule_cmp(h, r, rule);
+		if (found)
+			break;
+		r = nftnl_rule_iter_next(iter);
+	}
+
+	nftnl_rule_iter_destroy(iter);
+
+	return found ? r : NULL;
+}
+
+int nft_rule_check(struct nft_handle *h, const char *chain,
+		   const char *table, struct nftnl_rule *rule, bool verbose)
+{
+	struct nftnl_rule *r;
+	struct nft_chain *c;
+
+	nft_fn = nft_rule_check;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c)
+		goto fail_enoent;
+
+	r = nft_rule_find(h, c, rule, -1);
+	if (r == NULL)
+		goto fail_enoent;
+
+	if (verbose)
+		h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
+
+	return 1;
+fail_enoent:
+	errno = ENOENT;
+	return 0;
+}
+
+int nft_rule_delete(struct nft_handle *h, const char *chain,
+		    const char *table, struct nftnl_rule *rule, bool verbose)
+{
+	int ret = 0;
+	struct nftnl_rule *r;
+	struct nft_chain *c;
+
+	nft_fn = nft_rule_delete;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	r = nft_rule_find(h, c, rule, -1);
+	if (r != NULL) {
+		ret =__nft_rule_del(h, r);
+		if (ret < 0)
+			errno = ENOMEM;
+		if (verbose)
+			h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
+	} else
+		errno = ENOENT;
+
+	return ret;
+}
+
+static struct nftnl_rule *
+nft_rule_add(struct nft_handle *h, const char *chain,
+	     const char *table, struct nftnl_rule *r,
+	     struct nftnl_rule *ref, bool verbose)
+{
+	uint64_t ref_id;
+
+	if (ref) {
+		ref_id = nftnl_rule_get_u64(ref, NFTNL_RULE_HANDLE);
+		if (ref_id > 0) {
+			nftnl_rule_set_u64(r, NFTNL_RULE_POSITION, ref_id);
+			DEBUGP("adding after rule handle %"PRIu64"\n", ref_id);
+		} else {
+			ref_id = nftnl_rule_get_u32(ref, NFTNL_RULE_ID);
+			if (!ref_id) {
+				ref_id = ++h->rule_id;
+				nftnl_rule_set_u32(ref, NFTNL_RULE_ID, ref_id);
+			}
+			nftnl_rule_set_u32(r, NFTNL_RULE_POSITION_ID, ref_id);
+			DEBUGP("adding after rule ID %"PRIu64"\n", ref_id);
+		}
+	}
+
+	if (!batch_rule_add(h, NFT_COMPAT_RULE_INSERT, r))
+		return NULL;
+
+	if (verbose)
+		h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
+
+	return r;
+}
+
+int nft_rule_insert(struct nft_handle *h, const char *chain,
+		    const char *table, struct nftnl_rule *new_rule, int rulenum,
+		    bool verbose)
+{
+	struct nftnl_rule *r = NULL;
+	struct nft_chain *c;
+
+	nft_xt_builtin_init(h, table, chain);
+
+	nft_fn = nft_rule_insert;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c) {
+		errno = ENOENT;
+		goto err;
+	}
+
+	if (rulenum > 0) {
+		r = nft_rule_find(h, c, new_rule, rulenum);
+		if (r == NULL) {
+			/* special case: iptables allows to insert into
+			 * rule_count + 1 position.
+			 */
+			r = nft_rule_find(h, c, new_rule, rulenum - 1);
+			if (r != NULL)
+				return nft_rule_append(h, chain, table,
+						       new_rule, NULL, verbose);
+
+			errno = E2BIG;
+			goto err;
+		}
+	}
+
+	new_rule = nft_rule_add(h, chain, table, new_rule, r, verbose);
+	if (!new_rule)
+		goto err;
+
+	if (r)
+		nftnl_chain_rule_insert_at(new_rule, r);
+	else
+		nftnl_chain_rule_add(new_rule, c->nftnl);
+
+	return 1;
+err:
+	return 0;
+}
+
+int nft_rule_delete_num(struct nft_handle *h, const char *chain,
+			const char *table, int rulenum, bool verbose)
+{
+	int ret = 0;
+	struct nftnl_rule *r;
+	struct nft_chain *c;
+
+	nft_fn = nft_rule_delete_num;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	r = nft_rule_find(h, c, NULL, rulenum);
+	if (r != NULL) {
+		DEBUGP("deleting rule by number %d\n", rulenum);
+		ret = __nft_rule_del(h, r);
+		if (ret < 0)
+			errno = ENOMEM;
+	} else
+		errno = E2BIG;
+
+	return ret;
+}
+
+int nft_rule_replace(struct nft_handle *h, const char *chain,
+		     const char *table, struct nftnl_rule *rule,
+		     int rulenum, bool verbose)
+{
+	int ret = 0;
+	struct nftnl_rule *r;
+	struct nft_chain *c;
+
+	nft_fn = nft_rule_replace;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	r = nft_rule_find(h, c, rule, rulenum);
+	if (r != NULL) {
+		DEBUGP("replacing rule with handle=%llu\n",
+			(unsigned long long)
+			nftnl_rule_get_u64(r, NFTNL_RULE_HANDLE));
+
+		ret = nft_rule_append(h, chain, table, rule, r, verbose);
+	} else
+		errno = E2BIG;
+
+	return ret;
+}
+
+static int
+__nft_rule_list(struct nft_handle *h, struct nftnl_chain *c,
+		int rulenum, unsigned int format,
+		void (*cb)(struct nft_handle *h, struct nftnl_rule *r,
+			   unsigned int num, unsigned int format))
+{
+	struct nftnl_rule_iter *iter;
+	struct nftnl_rule *r;
+	int rule_ctr = 0;
+
+	if (rulenum > 0) {
+		r = nftnl_rule_lookup_byindex(c, rulenum - 1);
+		if (!r)
+			/* iptables-legacy returns 0 when listing for
+			 * valid chain but invalid rule number
+			 */
+			return 1;
+		cb(h, r, rulenum, format);
+		return 1;
+	}
+
+	iter = nftnl_rule_iter_create(c);
+	if (iter == NULL)
+		return 0;
+
+	r = nftnl_rule_iter_next(iter);
+	while (r != NULL) {
+		cb(h, r, ++rule_ctr, format);
+		r = nftnl_rule_iter_next(iter);
+	}
+
+	nftnl_rule_iter_destroy(iter);
+	return 1;
+}
+
+static int nft_rule_count(struct nft_handle *h, struct nftnl_chain *c)
+{
+	struct nftnl_rule_iter *iter;
+	struct nftnl_rule *r;
+	int rule_ctr = 0;
+
+	iter = nftnl_rule_iter_create(c);
+	if (iter == NULL)
+		return 0;
+
+	r = nftnl_rule_iter_next(iter);
+	while (r != NULL) {
+		rule_ctr++;
+		r = nftnl_rule_iter_next(iter);
+	}
+
+	nftnl_rule_iter_destroy(iter);
+	return rule_ctr;
+}
+
+static void __nft_print_header(struct nft_handle *h,
+			       struct nft_chain *nc, unsigned int format)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	const char *chain_name = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+	bool basechain = !!nftnl_chain_get(c, NFTNL_CHAIN_HOOKNUM);
+	uint32_t refs = nftnl_chain_get_u32(c, NFTNL_CHAIN_USE);
+	uint32_t entries = nft_rule_count(h, c);
+	struct xt_counters ctrs = {
+		.pcnt = nftnl_chain_get_u64(c, NFTNL_CHAIN_PACKETS),
+		.bcnt = nftnl_chain_get_u64(c, NFTNL_CHAIN_BYTES),
+	};
+	const char *pname = NULL;
+
+	if (nftnl_chain_is_set(c, NFTNL_CHAIN_POLICY))
+		pname = policy_name[nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY)];
+
+	h->ops->print_header(format, chain_name, pname,
+			&ctrs, basechain, refs - entries, entries);
+}
+
+struct nft_rule_list_cb_data {
+	struct nft_handle *h;
+	unsigned int format;
+	int rulenum;
+	bool found;
+	bool save_fmt;
+	void (*cb)(struct nft_handle *h, struct nftnl_rule *r,
+		   unsigned int num, unsigned int format);
+};
+
+static int nft_rule_list_cb(struct nft_chain *c, void *data)
+{
+	struct nft_rule_list_cb_data *d = data;
+
+	if (!d->save_fmt) {
+		if (d->found)
+			printf("\n");
+		d->found = true;
+
+		__nft_print_header(d->h, c, d->format);
+	}
+
+	return __nft_rule_list(d->h, c->nftnl, d->rulenum, d->format, d->cb);
+}
+
+int nft_rule_list(struct nft_handle *h, const char *chain, const char *table,
+		  int rulenum, unsigned int format)
+{
+	const struct nft_family_ops *ops = h->ops;
+	struct nft_rule_list_cb_data d = {
+		.h = h,
+		.format = format,
+		.rulenum = rulenum,
+		.cb = ops->print_rule,
+	};
+	struct nft_chain *c;
+
+	nft_xt_fake_builtin_chains(h, table, chain);
+	nft_assert_table_compatible(h, table, chain);
+
+	if (chain) {
+		c = nft_chain_find(h, table, chain);
+		if (!c)
+			return 0;
+
+		if (rulenum)
+			d.save_fmt = true;	/* skip header printing */
+		else if (ops->print_table_header)
+			ops->print_table_header(table);
+
+		nft_rule_list_cb(c, &d);
+		return 1;
+	}
+
+	if (ops->print_table_header)
+		ops->print_table_header(table);
+
+	nft_chain_foreach(h, table, nft_rule_list_cb, &d);
+	return 1;
+}
+
+static void
+list_save(struct nft_handle *h, struct nftnl_rule *r,
+	  unsigned int num, unsigned int format)
+{
+	nft_rule_print_save(h, r, NFT_RULE_APPEND, format);
+}
+
+int nft_chain_foreach(struct nft_handle *h, const char *table,
+		      int (*cb)(struct nft_chain *c, void *data),
+		      void *data)
+{
+	const struct builtin_table *t;
+	struct nft_chain_list *list;
+	struct nft_chain *c, *c_bak;
+	int i, ret;
+
+	t = nft_table_builtin_find(h, table);
+	if (!t)
+		return -1;
+
+	for (i = 0; i < NF_INET_NUMHOOKS; i++) {
+		c = h->cache->table[t->type].base_chains[i];
+		if (!c)
+			continue;
+
+		ret = cb(c, data);
+		if (ret < 0)
+			return ret;
+	}
+
+	list = h->cache->table[t->type].chains;
+	if (!list)
+		return -1;
+
+	list_for_each_entry_safe(c, c_bak, &list->list, head) {
+		ret = cb(c, data);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+static int nft_rule_list_chain_save(struct nft_chain *nc, void *data)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	const char *chain_name = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+	uint32_t policy = nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY);
+	int *counters = data;
+
+	if (!nft_chain_builtin(c)) {
+		printf("-N %s\n", chain_name);
+		return 0;
+	}
+
+	/* this is a base chain */
+
+	printf("-P %s %s", chain_name, policy_name[policy]);
+	if (*counters)
+		printf(" -c %"PRIu64" %"PRIu64,
+		       nftnl_chain_get_u64(c, NFTNL_CHAIN_PACKETS),
+		       nftnl_chain_get_u64(c, NFTNL_CHAIN_BYTES));
+	printf("\n");
+	return 0;
+}
+
+int nft_rule_list_save(struct nft_handle *h, const char *chain,
+		       const char *table, int rulenum, int counters)
+{
+	struct nft_rule_list_cb_data d = {
+		.h = h,
+		.rulenum = rulenum,
+		.save_fmt = true,
+		.cb = list_save,
+	};
+	struct nft_chain *c;
+	int ret = 0;
+
+	nft_xt_fake_builtin_chains(h, table, chain);
+	nft_assert_table_compatible(h, table, chain);
+
+	if (counters < 0)
+		d.format = FMT_C_COUNTS;
+	else if (counters == 0)
+		d.format = FMT_NOCOUNTS;
+
+	if (chain) {
+		c = nft_chain_find(h, table, chain);
+		if (!c)
+			return 0;
+
+		if (!rulenum)
+			nft_rule_list_chain_save(c, &counters);
+
+		return nft_rule_list_cb(c, &d);
+	}
+
+	/* Dump policies and custom chains first */
+	nft_chain_foreach(h, table, nft_rule_list_chain_save, &counters);
+
+	/* Now dump out rules in this table */
+	ret = nft_chain_foreach(h, table, nft_rule_list_cb, &d);
+	return ret == 0 ? 1 : 0;
+}
+
+int nft_rule_zero_counters(struct nft_handle *h, const char *chain,
+			   const char *table, int rulenum)
+{
+	struct iptables_command_state cs = {};
+	struct nftnl_rule *r, *new_rule;
+	struct nft_chain *c;
+	int ret = 0;
+
+	nft_fn = nft_rule_delete;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c)
+		return 0;
+
+	r = nft_rule_find(h, c, NULL, rulenum);
+	if (r == NULL) {
+		errno = ENOENT;
+		ret = 1;
+		goto error;
+	}
+
+	nft_rule_to_iptables_command_state(h, r, &cs);
+
+	cs.counters.pcnt = cs.counters.bcnt = 0;
+	new_rule = nft_rule_new(h, chain, table, &cs);
+	if (!new_rule)
+		return 1;
+
+	ret = nft_rule_append(h, chain, table, new_rule, r, false);
+
+error:
+	return ret;
+}
+
+static void nft_compat_table_batch_add(struct nft_handle *h, uint16_t type,
+				       uint16_t flags, uint32_t seq,
+				       struct nftnl_table *table)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nftnl_table_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
+					type, h->family, flags, seq);
+	nftnl_table_nlmsg_build_payload(nlh, table);
+}
+
+static void nft_compat_set_batch_add(struct nft_handle *h, uint16_t type,
+				     uint16_t flags, uint32_t seq,
+				     struct nftnl_set *set)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
+					type, h->family, flags, seq);
+	nftnl_set_nlmsg_build_payload(nlh, set);
+}
+
+static void nft_compat_setelem_batch_add(struct nft_handle *h, uint16_t type,
+					 uint16_t flags, uint32_t *seq,
+					 struct nftnl_set *set)
+{
+	struct nftnl_set_elems_iter *iter;
+	struct nlmsghdr *nlh;
+
+	iter = nftnl_set_elems_iter_create(set);
+	if (!iter)
+		return;
+
+	while (nftnl_set_elems_iter_cur(iter)) {
+		(*seq)++;
+		mnl_nft_batch_continue(h->batch);
+		nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
+					    type, h->family, flags, *seq);
+		if (nftnl_set_elems_nlmsg_build_payload_iter(nlh, iter) <= 0)
+			break;
+	}
+	nftnl_set_elems_iter_destroy(iter);
+}
+
+static void nft_compat_chain_batch_add(struct nft_handle *h, uint16_t type,
+				       uint16_t flags, uint32_t seq,
+				       struct nftnl_chain *chain)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nftnl_chain_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
+					type, h->family, flags, seq);
+	nftnl_chain_nlmsg_build_payload(nlh, chain);
+	nft_chain_print_debug(chain, nlh);
+}
+
+static void nft_compat_rule_batch_add(struct nft_handle *h, uint16_t type,
+				      uint16_t flags, uint32_t seq,
+				      struct nftnl_rule *rule)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nftnl_rule_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
+				       type, h->family, flags, seq);
+	nftnl_rule_nlmsg_build_payload(nlh, rule);
+	nft_rule_print_debug(rule, nlh);
+}
+
+static void batch_obj_del(struct nft_handle *h, struct obj_update *o)
+{
+	switch (o->type) {
+	case NFT_COMPAT_TABLE_ADD:
+	case NFT_COMPAT_TABLE_FLUSH:
+		nftnl_table_free(o->table);
+		break;
+	case NFT_COMPAT_CHAIN_ZERO:
+	case NFT_COMPAT_CHAIN_USER_ADD:
+	case NFT_COMPAT_CHAIN_ADD:
+		break;
+	case NFT_COMPAT_CHAIN_USER_DEL:
+	case NFT_COMPAT_CHAIN_USER_FLUSH:
+	case NFT_COMPAT_CHAIN_UPDATE:
+	case NFT_COMPAT_CHAIN_RENAME:
+		nftnl_chain_free(o->chain);
+		break;
+	case NFT_COMPAT_RULE_APPEND:
+	case NFT_COMPAT_RULE_INSERT:
+	case NFT_COMPAT_RULE_REPLACE:
+		break;
+	case NFT_COMPAT_RULE_DELETE:
+	case NFT_COMPAT_RULE_FLUSH:
+		nftnl_rule_free(o->rule);
+		break;
+	case NFT_COMPAT_SET_ADD:
+		nftnl_set_free(o->set);
+		break;
+	case NFT_COMPAT_RULE_LIST:
+	case NFT_COMPAT_RULE_CHECK:
+	case NFT_COMPAT_CHAIN_RESTORE:
+	case NFT_COMPAT_RULE_SAVE:
+	case NFT_COMPAT_RULE_ZERO:
+	case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
+		assert(0);
+		break;
+	}
+	h->obj_list_num--;
+	list_del(&o->head);
+	free(o);
+}
+
+static void nft_refresh_transaction(struct nft_handle *h)
+{
+	const char *tablename, *chainname;
+	const struct nft_chain *c;
+	struct obj_update *n, *tmp;
+	bool exists;
+
+	h->error.lineno = 0;
+
+	list_for_each_entry_safe(n, tmp, &h->obj_list, head) {
+		switch (n->type) {
+		case NFT_COMPAT_TABLE_FLUSH:
+			tablename = nftnl_table_get_str(n->table, NFTNL_TABLE_NAME);
+			if (!tablename)
+				continue;
+			exists = nft_table_find(h, tablename);
+			if (exists)
+				n->skip = 0;
+			else
+				n->skip = 1;
+			break;
+		case NFT_COMPAT_CHAIN_USER_ADD:
+			tablename = nftnl_chain_get_str(n->chain, NFTNL_CHAIN_TABLE);
+			if (!tablename)
+				continue;
+
+			chainname = nftnl_chain_get_str(n->chain, NFTNL_CHAIN_NAME);
+			if (!chainname)
+				continue;
+
+			if (!h->noflush)
+				break;
+
+			c = nft_chain_find(h, tablename, chainname);
+			if (c) {
+				n->skip = 1;
+			} else if (!c) {
+				n->skip = 0;
+			}
+			break;
+		case NFT_COMPAT_RULE_FLUSH:
+			tablename = nftnl_rule_get_str(n->rule, NFTNL_RULE_TABLE);
+			if (!tablename)
+				continue;
+
+			chainname = nftnl_rule_get_str(n->rule, NFTNL_RULE_CHAIN);
+			if (!chainname)
+				continue;
+
+			n->skip = !nft_chain_find(h, tablename, chainname);
+			break;
+		case NFT_COMPAT_TABLE_ADD:
+		case NFT_COMPAT_CHAIN_ADD:
+		case NFT_COMPAT_CHAIN_ZERO:
+		case NFT_COMPAT_CHAIN_USER_DEL:
+		case NFT_COMPAT_CHAIN_USER_FLUSH:
+		case NFT_COMPAT_CHAIN_UPDATE:
+		case NFT_COMPAT_CHAIN_RENAME:
+		case NFT_COMPAT_RULE_APPEND:
+		case NFT_COMPAT_RULE_INSERT:
+		case NFT_COMPAT_RULE_REPLACE:
+		case NFT_COMPAT_RULE_DELETE:
+		case NFT_COMPAT_SET_ADD:
+		case NFT_COMPAT_RULE_LIST:
+		case NFT_COMPAT_RULE_CHECK:
+		case NFT_COMPAT_CHAIN_RESTORE:
+		case NFT_COMPAT_RULE_SAVE:
+		case NFT_COMPAT_RULE_ZERO:
+		case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
+			break;
+		}
+	}
+}
+
+static int nft_action(struct nft_handle *h, int action)
+{
+	struct obj_update *n, *tmp;
+	struct mnl_err *err, *ne;
+	unsigned int buflen, i, len;
+	bool show_errors = true;
+	char errmsg[1024];
+	uint32_t seq;
+	int ret = 0;
+
+retry:
+	seq = 1;
+	h->batch = mnl_batch_init();
+
+	mnl_batch_begin(h->batch, h->nft_genid, seq++);
+	h->nft_genid++;
+
+	list_for_each_entry(n, &h->obj_list, head) {
+		if (n->skip) {
+			n->seq = 0;
+			continue;
+		}
+
+		n->seq = seq++;
+		switch (n->type) {
+		case NFT_COMPAT_TABLE_ADD:
+			nft_compat_table_batch_add(h, NFT_MSG_NEWTABLE,
+						   NLM_F_CREATE, n->seq,
+						   n->table);
+			break;
+		case NFT_COMPAT_TABLE_FLUSH:
+			nft_compat_table_batch_add(h, NFT_MSG_DELTABLE,
+						   0,
+						   n->seq, n->table);
+			break;
+		case NFT_COMPAT_CHAIN_ADD:
+		case NFT_COMPAT_CHAIN_ZERO:
+			nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN,
+						   NLM_F_CREATE, n->seq,
+						   n->chain);
+			break;
+		case NFT_COMPAT_CHAIN_USER_ADD:
+			nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN,
+						   NLM_F_EXCL, n->seq,
+						   n->chain);
+			break;
+		case NFT_COMPAT_CHAIN_USER_DEL:
+			nft_compat_chain_batch_add(h, NFT_MSG_DELCHAIN,
+						   NLM_F_NONREC, n->seq,
+						   n->chain);
+			break;
+		case NFT_COMPAT_CHAIN_USER_FLUSH:
+			nft_compat_chain_batch_add(h, NFT_MSG_DELCHAIN,
+						   0, n->seq,
+						   n->chain);
+			break;
+		case NFT_COMPAT_CHAIN_UPDATE:
+			nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN,
+						   h->restore ?
+						     NLM_F_CREATE : 0,
+						   n->seq, n->chain);
+			break;
+		case NFT_COMPAT_CHAIN_RENAME:
+			nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN, 0,
+						   n->seq, n->chain);
+			break;
+		case NFT_COMPAT_RULE_APPEND:
+			nft_compat_rule_batch_add(h, NFT_MSG_NEWRULE,
+						  NLM_F_CREATE | NLM_F_APPEND,
+						  n->seq, n->rule);
+			break;
+		case NFT_COMPAT_RULE_INSERT:
+			nft_compat_rule_batch_add(h, NFT_MSG_NEWRULE,
+						  NLM_F_CREATE, n->seq,
+						  n->rule);
+			break;
+		case NFT_COMPAT_RULE_REPLACE:
+			nft_compat_rule_batch_add(h, NFT_MSG_NEWRULE,
+						  NLM_F_CREATE | NLM_F_REPLACE,
+						  n->seq, n->rule);
+			break;
+		case NFT_COMPAT_RULE_DELETE:
+		case NFT_COMPAT_RULE_FLUSH:
+			nft_compat_rule_batch_add(h, NFT_MSG_DELRULE, 0,
+						  n->seq, n->rule);
+			break;
+		case NFT_COMPAT_SET_ADD:
+			nft_compat_set_batch_add(h, NFT_MSG_NEWSET,
+						 NLM_F_CREATE, n->seq, n->set);
+			nft_compat_setelem_batch_add(h, NFT_MSG_NEWSETELEM,
+						     NLM_F_CREATE, &n->seq, n->set);
+			seq = n->seq;
+			break;
+		case NFT_COMPAT_RULE_LIST:
+		case NFT_COMPAT_RULE_CHECK:
+		case NFT_COMPAT_CHAIN_RESTORE:
+		case NFT_COMPAT_RULE_SAVE:
+		case NFT_COMPAT_RULE_ZERO:
+		case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
+			assert(0);
+		}
+
+		mnl_nft_batch_continue(h->batch);
+	}
+
+	switch (action) {
+	case NFT_COMPAT_COMMIT:
+		mnl_batch_end(h->batch, seq++);
+		break;
+	case NFT_COMPAT_ABORT:
+		break;
+	}
+
+	errno = 0;
+	ret = mnl_batch_talk(h, seq);
+	if (ret && errno == ERESTART) {
+		nft_rebuild_cache(h);
+
+		nft_refresh_transaction(h);
+
+		list_for_each_entry_safe(err, ne, &h->err_list, head)
+			mnl_err_list_free(err);
+
+		mnl_batch_reset(h->batch);
+		goto retry;
+	}
+
+	i = 0;
+	buflen = sizeof(errmsg);
+
+	list_for_each_entry_safe(n, tmp, &h->obj_list, head) {
+		list_for_each_entry_safe(err, ne, &h->err_list, head) {
+			if (err->seqnum > n->seq)
+				break;
+
+			if (err->seqnum == n->seq && show_errors) {
+				if (n->error.lineno == 0)
+					show_errors = false;
+				len = mnl_append_error(h, n, err, errmsg + i, buflen);
+				if (len > 0 && len <= buflen) {
+					buflen -= len;
+					i += len;
+				}
+			}
+			mnl_err_list_free(err);
+		}
+		batch_obj_del(h, n);
+	}
+
+	nft_release_cache(h);
+	mnl_batch_reset(h->batch);
+
+	if (i)
+		xtables_error(RESOURCE_PROBLEM, "%s", errmsg);
+
+	return ret == 0 ? 1 : 0;
+}
+
+static int ebt_add_policy_rule(struct nftnl_chain *c, void *data)
+{
+	uint32_t policy = nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY);
+	struct iptables_command_state cs = {
+		.eb.bitmask = EBT_NOPROTO,
+	};
+	struct nftnl_udata_buf *udata;
+	struct nft_handle *h = data;
+	struct nftnl_rule *r;
+	const char *pname;
+
+	if (nftnl_chain_get(c, NFTNL_CHAIN_HOOKNUM))
+		return 0; /* ignore base chains */
+
+	if (!nftnl_chain_is_set(c, NFTNL_CHAIN_POLICY))
+		return 0;
+
+	nftnl_chain_unset(c, NFTNL_CHAIN_POLICY);
+
+	switch (policy) {
+	case NFT_RETURN:
+		return 0; /* return policy is default for nft chains */
+	case NF_ACCEPT:
+		pname = "ACCEPT";
+		break;
+	case NF_DROP:
+		pname = "DROP";
+		break;
+	default:
+		return -1;
+	}
+
+	command_jump(&cs, pname);
+
+	r = nft_rule_new(h, nftnl_chain_get_str(c, NFTNL_CHAIN_NAME),
+			 nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE), &cs);
+	ebt_cs_clean(&cs);
+
+	if (!r)
+		return -1;
+
+	udata = nftnl_udata_buf_alloc(NFT_USERDATA_MAXLEN);
+	if (!udata)
+		goto err_free_rule;
+
+	if (!nftnl_udata_put_u32(udata, UDATA_TYPE_EBTABLES_POLICY, 1))
+		goto err_free_rule;
+
+	nftnl_rule_set_data(r, NFTNL_RULE_USERDATA,
+			    nftnl_udata_buf_data(udata),
+			    nftnl_udata_buf_len(udata));
+	nftnl_udata_buf_free(udata);
+
+	if (!batch_rule_add(h, NFT_COMPAT_RULE_APPEND, r))
+		goto err_free_rule;
+
+	/* add the rule to chain so it is freed later */
+	nftnl_chain_rule_add_tail(r, c);
+
+	return 0;
+err_free_rule:
+	nftnl_rule_free(r);
+	return -1;
+}
+
+int ebt_set_user_chain_policy(struct nft_handle *h, const char *table,
+			      const char *chain, const char *policy)
+{
+	struct nft_chain *c = nft_chain_find(h, table, chain);
+	int pval;
+
+	if (!c)
+		return 0;
+
+	if (!strcmp(policy, "DROP"))
+		pval = NF_DROP;
+	else if (!strcmp(policy, "ACCEPT"))
+		pval = NF_ACCEPT;
+	else if (!strcmp(policy, "RETURN"))
+		pval = NFT_RETURN;
+	else
+		return 0;
+
+	nftnl_chain_set_u32(c->nftnl, NFTNL_CHAIN_POLICY, pval);
+	return 1;
+}
+
+static void nft_bridge_commit_prepare(struct nft_handle *h)
+{
+	const struct builtin_table *t;
+	struct nft_chain_list *list;
+	struct nft_chain *c;
+	int i;
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+		t = &h->tables[i];
+
+		if (!t->name)
+			continue;
+
+		list = h->cache->table[t->type].chains;
+		if (!list)
+			continue;
+
+		list_for_each_entry(c, &list->list, head) {
+			ebt_add_policy_rule(c->nftnl, h);
+		}
+	}
+}
+
+static void assert_chain_exists(struct nft_handle *h,
+				const char *table, const char *chain)
+{
+	if (chain && !nft_chain_exists(h, table, chain))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Chain '%s' does not exist", chain);
+}
+
+static int nft_prepare(struct nft_handle *h)
+{
+	struct nft_cmd *cmd, *next;
+	int ret = 1;
+
+	nft_cache_build(h);
+
+	list_for_each_entry_safe(cmd, next, &h->cmd_list, head) {
+		switch (cmd->command) {
+		case NFT_COMPAT_TABLE_FLUSH:
+			ret = nft_table_flush(h, cmd->table);
+			break;
+		case NFT_COMPAT_CHAIN_USER_ADD:
+			ret = nft_chain_user_add(h, cmd->chain, cmd->table);
+			break;
+		case NFT_COMPAT_CHAIN_USER_DEL:
+			ret = nft_chain_user_del(h, cmd->chain, cmd->table,
+						 cmd->verbose);
+			break;
+		case NFT_COMPAT_CHAIN_RESTORE:
+			ret = nft_chain_restore(h, cmd->chain, cmd->table);
+			break;
+		case NFT_COMPAT_CHAIN_UPDATE:
+			ret = nft_chain_set(h, cmd->table, cmd->chain,
+					    cmd->policy, &cmd->counters);
+			break;
+		case NFT_COMPAT_CHAIN_RENAME:
+			ret = nft_chain_user_rename(h, cmd->chain, cmd->table,
+						    cmd->rename);
+			break;
+		case NFT_COMPAT_CHAIN_ZERO:
+			ret = nft_chain_zero_counters(h, cmd->chain, cmd->table,
+						      cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_APPEND:
+			assert_chain_exists(h, cmd->table, cmd->jumpto);
+			ret = nft_rule_append(h, cmd->chain, cmd->table,
+					      cmd->obj.rule, NULL, cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_INSERT:
+			assert_chain_exists(h, cmd->table, cmd->jumpto);
+			ret = nft_rule_insert(h, cmd->chain, cmd->table,
+					      cmd->obj.rule, cmd->rulenum,
+					      cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_REPLACE:
+			assert_chain_exists(h, cmd->table, cmd->jumpto);
+			ret = nft_rule_replace(h, cmd->chain, cmd->table,
+					      cmd->obj.rule, cmd->rulenum,
+					      cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_DELETE:
+			assert_chain_exists(h, cmd->table, cmd->jumpto);
+			if (cmd->rulenum >= 0)
+				ret = nft_rule_delete_num(h, cmd->chain,
+							  cmd->table,
+							  cmd->rulenum,
+							  cmd->verbose);
+			else
+				ret = nft_rule_delete(h, cmd->chain, cmd->table,
+						      cmd->obj.rule, cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_FLUSH:
+			ret = nft_rule_flush(h, cmd->chain, cmd->table,
+					     cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_LIST:
+			ret = nft_rule_list(h, cmd->chain, cmd->table,
+					    cmd->rulenum, cmd->format);
+			break;
+		case NFT_COMPAT_RULE_CHECK:
+			assert_chain_exists(h, cmd->table, cmd->jumpto);
+			ret = nft_rule_check(h, cmd->chain, cmd->table,
+					     cmd->obj.rule, cmd->rulenum);
+			break;
+		case NFT_COMPAT_RULE_ZERO:
+			ret = nft_rule_zero_counters(h, cmd->chain, cmd->table,
+                                                     cmd->rulenum);
+			break;
+		case NFT_COMPAT_RULE_SAVE:
+			ret = nft_rule_list_save(h, cmd->chain, cmd->table,
+						 cmd->rulenum,
+						 cmd->counters_save);
+			break;
+		case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
+			ret = ebt_set_user_chain_policy(h, cmd->table,
+							cmd->chain, cmd->policy);
+			break;
+		case NFT_COMPAT_SET_ADD:
+			nft_xt_builtin_table_init(h, cmd->table);
+			batch_set_add(h, NFT_COMPAT_SET_ADD, cmd->obj.set);
+			ret = 1;
+			break;
+		case NFT_COMPAT_TABLE_ADD:
+		case NFT_COMPAT_CHAIN_ADD:
+			assert(0);
+			break;
+		}
+
+		nft_cmd_free(cmd);
+
+		if (ret == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
+int nft_commit(struct nft_handle *h)
+{
+	if (!nft_prepare(h))
+		return 0;
+
+	return nft_action(h, NFT_COMPAT_COMMIT);
+}
+
+int nft_bridge_commit(struct nft_handle *h)
+{
+	if (!nft_prepare(h))
+		return 0;
+
+	nft_bridge_commit_prepare(h);
+
+	return nft_action(h, NFT_COMPAT_COMMIT);
+}
+
+int nft_abort(struct nft_handle *h)
+{
+	struct nft_cmd *cmd, *next;
+
+	list_for_each_entry_safe(cmd, next, &h->cmd_list, head)
+		nft_cmd_free(cmd);
+
+	return nft_action(h, NFT_COMPAT_ABORT);
+}
+
+int nft_compatible_revision(const char *name, uint8_t rev, int opt)
+{
+	struct mnl_socket *nl;
+	char buf[16536];
+	struct nlmsghdr *nlh;
+	uint32_t portid, seq, type = 0;
+	uint32_t pf = AF_INET;
+	int ret = 0;
+
+	switch (opt) {
+	case IPT_SO_GET_REVISION_MATCH:
+		break;
+	case IP6T_SO_GET_REVISION_MATCH:
+		pf = AF_INET6;
+		break;
+	case IPT_SO_GET_REVISION_TARGET:
+		type = 1;
+		break;
+	case IP6T_SO_GET_REVISION_TARGET:
+		type = 1;
+		pf = AF_INET6;
+		break;
+	default:
+		/* No revision support (arp, ebtables), assume latest version ok */
+		return 1;
+	}
+
+	nlh = mnl_nlmsg_put_header(buf);
+	nlh->nlmsg_type = (NFNL_SUBSYS_NFT_COMPAT << 8) | NFNL_MSG_COMPAT_GET;
+	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	nlh->nlmsg_seq = seq = time(NULL);
+
+	struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
+	nfg->nfgen_family = pf;
+	nfg->version = NFNETLINK_V0;
+	nfg->res_id = 0;
+
+	mnl_attr_put_strz(nlh, NFTA_COMPAT_NAME, name);
+	mnl_attr_put_u32(nlh, NFTA_COMPAT_REV, htonl(rev));
+	mnl_attr_put_u32(nlh, NFTA_COMPAT_TYPE, htonl(type));
+
+	DEBUGP("requesting `%s' rev=%d type=%d via nft_compat\n",
+		name, rev, type);
+
+	nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (nl == NULL)
+		return 0;
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
+		goto err;
+
+	portid = mnl_socket_get_portid(nl);
+
+	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
+		goto err;
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	if (ret == -1)
+		goto err;
+
+	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
+	if (ret == -1)
+		goto err;
+
+err:
+	mnl_socket_close(nl);
+
+	return ret < 0 ? 0 : 1;
+}
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *nft_strerror(int err)
+{
+	unsigned int i;
+	static struct table_struct {
+		void *fn;
+		int err;
+		const char *message;
+	} table[] =
+	  {
+	    { nft_chain_user_del, ENOTEMPTY, "Chain is not empty" },
+	    { nft_chain_user_del, EINVAL, "Can't delete built-in chain" },
+	    { nft_chain_user_del, EBUSY, "Directory not empty" },
+	    { nft_chain_user_del, EMLINK,
+	      "Can't delete chain with references left" },
+	    { nft_chain_user_add, EEXIST, "Chain already exists" },
+	    { nft_chain_user_rename, EEXIST, "File exists" },
+	    { nft_rule_insert, E2BIG, "Index of insertion too big" },
+	    { nft_rule_check, ENOENT, "Bad rule (does a matching rule exist in that chain?)" },
+	    { nft_rule_replace, E2BIG, "Index of replacement too big" },
+	    { nft_rule_delete_num, E2BIG, "Index of deletion too big" },
+/*	    { TC_READ_COUNTER, E2BIG, "Index of counter too big" },
+	    { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, */
+	    /* ENOENT for DELETE probably means no matching rule */
+	    { nft_rule_delete, ENOENT,
+	      "Bad rule (does a matching rule exist in that chain?)" },
+	    { nft_chain_set, ENOENT, "Bad built-in chain name" },
+	    { nft_chain_set, EINVAL, "Bad policy name" },
+	    { nft_chain_set, ENXIO, "Bad table name" },
+	    { NULL, ELOOP, "Loop found in table" },
+	    { NULL, EPERM, "Permission denied (you must be root)" },
+	    { NULL, 0, "Incompatible with this kernel" },
+	    { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" },
+	    { NULL, ENOSYS, "Will be implemented real soon.  I promise ;)" },
+	    { NULL, ENOMEM, "Memory allocation problem" },
+	    { NULL, ENOENT, "No chain/target/match by that name" },
+	  };
+
+	for (i = 0; i < ARRAY_SIZE(table); i++) {
+		if ((!table[i].fn || table[i].fn == nft_fn)
+		    && table[i].err == err)
+			return table[i].message;
+	}
+
+	return strerror(err);
+}
+
+static int recover_rule_compat(struct nftnl_rule *r)
+{
+	struct nftnl_expr_iter *iter;
+	struct nftnl_expr *e;
+	uint32_t reg;
+	int ret = -1;
+
+	iter = nftnl_expr_iter_create(r);
+	if (!iter)
+		return -1;
+
+next_expr:
+	e = nftnl_expr_iter_next(iter);
+	if (!e)
+		goto out;
+
+	if (strcmp("meta", nftnl_expr_get_str(e, NFTNL_EXPR_NAME)) ||
+	    nftnl_expr_get_u32(e, NFTNL_EXPR_META_KEY) != NFT_META_L4PROTO)
+		goto next_expr;
+
+	reg = nftnl_expr_get_u32(e, NFTNL_EXPR_META_DREG);
+
+	e = nftnl_expr_iter_next(iter);
+	if (!e)
+		goto out;
+
+	if (strcmp("cmp", nftnl_expr_get_str(e, NFTNL_EXPR_NAME)) ||
+	    reg != nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_SREG))
+		goto next_expr;
+
+	add_compat(r, nftnl_expr_get_u8(e, NFTNL_EXPR_CMP_DATA),
+		   nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ);
+	ret = 0;
+out:
+	nftnl_expr_iter_destroy(iter);
+	return ret;
+}
+
+struct chain_zero_data {
+	struct nft_handle	*handle;
+	bool			verbose;
+};
+
+static int __nft_chain_zero_counters(struct nft_chain *nc, void *data)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	struct chain_zero_data *d = data;
+	struct nft_handle *h = d->handle;
+	struct nftnl_rule_iter *iter;
+	struct nftnl_rule *r;
+
+	if (d->verbose)
+		fprintf(stdout, "Zeroing chain `%s'\n",
+		        nftnl_chain_get_str(c, NFTNL_CHAIN_NAME));
+
+	if (nftnl_chain_is_set(c, NFTNL_CHAIN_HOOKNUM)) {
+		/* zero base chain counters. */
+		nftnl_chain_set_u64(c, NFTNL_CHAIN_PACKETS, 0);
+		nftnl_chain_set_u64(c, NFTNL_CHAIN_BYTES, 0);
+		nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE);
+		if (!batch_chain_add(h, NFT_COMPAT_CHAIN_ZERO, c))
+			return -1;
+	}
+
+	iter = nftnl_rule_iter_create(c);
+	if (iter == NULL)
+		return -1;
+
+	r = nftnl_rule_iter_next(iter);
+	while (r != NULL) {
+		struct nftnl_expr_iter *ei;
+		struct nftnl_expr *e;
+		bool zero_needed;
+
+		ei = nftnl_expr_iter_create(r);
+		if (!ei)
+			break;
+
+		e = nftnl_expr_iter_next(ei);
+	        zero_needed = false;
+		while (e != NULL) {
+			const char *en = nftnl_expr_get_str(e, NFTNL_EXPR_NAME);
+
+			if (strcmp(en, "counter") == 0 && (
+			    nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_PACKETS) ||
+			    nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_BYTES))) {
+				nftnl_expr_set_u64(e, NFTNL_EXPR_CTR_PACKETS, 0);
+				nftnl_expr_set_u64(e, NFTNL_EXPR_CTR_BYTES, 0);
+				zero_needed = true;
+			}
+
+			e = nftnl_expr_iter_next(ei);
+		}
+
+		nftnl_expr_iter_destroy(ei);
+
+		if (zero_needed) {
+			/*
+			 * Unset RULE_POSITION for older kernels, we want to replace
+			 * rule based on its handle only.
+			 */
+			recover_rule_compat(r);
+			nftnl_rule_unset(r, NFTNL_RULE_POSITION);
+			if (!batch_rule_add(h, NFT_COMPAT_RULE_REPLACE, r)) {
+				nftnl_rule_iter_destroy(iter);
+				return -1;
+			}
+		}
+		r = nftnl_rule_iter_next(iter);
+	}
+
+	nftnl_rule_iter_destroy(iter);
+	return 0;
+}
+
+int nft_chain_zero_counters(struct nft_handle *h, const char *chain,
+			    const char *table, bool verbose)
+{
+	struct chain_zero_data d = {
+		.handle = h,
+		.verbose = verbose,
+	};
+	struct nft_chain *c;
+	int ret = 0;
+
+	if (chain) {
+		c = nft_chain_find(h, table, chain);
+		if (!c) {
+			errno = ENOENT;
+			return 0;
+		}
+
+		ret = __nft_chain_zero_counters(c, &d);
+		goto err;
+	}
+
+	ret = nft_chain_foreach(h, table, __nft_chain_zero_counters, &d);
+err:
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+uint32_t nft_invflags2cmp(uint32_t invflags, uint32_t flag)
+{
+	if (invflags & flag)
+		return NFT_CMP_NEQ;
+
+	return NFT_CMP_EQ;
+}
+
+static const char *supported_exprs[] = {
+	"match",
+	"target",
+	"payload",
+	"meta",
+	"cmp",
+	"bitwise",
+	"counter",
+	"immediate",
+	"lookup",
+};
+
+
+static int nft_is_expr_compatible(struct nftnl_expr *expr, void *data)
+{
+	const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(supported_exprs); i++) {
+		if (strcmp(supported_exprs[i], name) == 0)
+			return 0;
+	}
+
+	if (!strcmp(name, "limit") &&
+	    nftnl_expr_get_u32(expr, NFTNL_EXPR_LIMIT_TYPE) == NFT_LIMIT_PKTS &&
+	    nftnl_expr_get_u32(expr, NFTNL_EXPR_LIMIT_FLAGS) == 0)
+		return 0;
+
+	return -1;
+}
+
+static int nft_is_rule_compatible(struct nftnl_rule *rule, void *data)
+{
+	return nftnl_expr_foreach(rule, nft_is_expr_compatible, NULL);
+}
+
+static int nft_is_chain_compatible(struct nft_chain *nc, void *data)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	const struct builtin_table *table;
+	const struct builtin_chain *chain;
+	const char *tname, *cname, *type;
+	struct nft_handle *h = data;
+	enum nf_inet_hooks hook;
+	int prio;
+
+	if (nftnl_rule_foreach(c, nft_is_rule_compatible, NULL))
+		return -1;
+
+	if (!nft_chain_builtin(c))
+		return 0;
+
+	tname = nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE);
+	table = nft_table_builtin_find(h, tname);
+	if (!table)
+		return -1;
+
+	cname = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+	chain = nft_chain_builtin_find(table, cname);
+	if (!chain)
+		return -1;
+
+	type = nftnl_chain_get_str(c, NFTNL_CHAIN_TYPE);
+	prio = nftnl_chain_get_u32(c, NFTNL_CHAIN_PRIO);
+	hook = nftnl_chain_get_u32(c, NFTNL_CHAIN_HOOKNUM);
+	if (strcmp(type, chain->type) ||
+	    prio != chain->prio ||
+	    hook != chain->hook)
+		return -1;
+
+	return 0;
+}
+
+bool nft_is_table_compatible(struct nft_handle *h,
+			     const char *table, const char *chain)
+{
+	if (chain) {
+		struct nft_chain *c = nft_chain_find(h, table, chain);
+
+		return c && !nft_is_chain_compatible(c, h);
+	}
+
+	return !nft_chain_foreach(h, table, nft_is_chain_compatible, h);
+}
+
+void nft_assert_table_compatible(struct nft_handle *h,
+				 const char *table, const char *chain)
+{
+	const char *pfx = "", *sfx = "";
+
+	if (nft_is_table_compatible(h, table, chain))
+		return;
+
+	if (chain) {
+		pfx = "chain `";
+		sfx = "' in ";
+	} else {
+		chain = "";
+	}
+	xtables_error(OTHER_PROBLEM,
+		      "%s%s%stable `%s' is incompatible, use 'nft' tool.\n",
+		      pfx, chain, sfx, table);
+}
diff --git a/iptables/nft.h b/iptables/nft.h
new file mode 100644
index 0000000..0910f82
--- /dev/null
+++ b/iptables/nft.h
@@ -0,0 +1,270 @@
+#ifndef _NFT_H_
+#define _NFT_H_
+
+#include "xshared.h"
+#include "nft-shared.h"
+#include "nft-cache.h"
+#include "nft-chain.h"
+#include "nft-cmd.h"
+#include <libiptc/linux_list.h>
+
+enum nft_table_type {
+	NFT_TABLE_MANGLE	= 0,
+	NFT_TABLE_SECURITY,
+	NFT_TABLE_RAW,
+	NFT_TABLE_FILTER,
+	NFT_TABLE_NAT,
+};
+#define NFT_TABLE_MAX	(NFT_TABLE_NAT + 1)
+
+struct builtin_chain {
+	const char *name;
+	const char *type;
+	uint32_t prio;
+	uint32_t hook;
+};
+
+struct builtin_table {
+	const char *name;
+	enum nft_table_type type;
+	struct builtin_chain chains[NF_INET_NUMHOOKS];
+};
+
+enum nft_cache_level {
+	NFT_CL_TABLES,
+	NFT_CL_CHAINS,
+	NFT_CL_SETS,
+	NFT_CL_RULES,
+	NFT_CL_FAKE	/* must be last entry */
+};
+
+struct nft_cache {
+	struct {
+		struct nft_chain	*base_chains[NF_INET_NUMHOOKS];
+		struct nft_chain_list	*chains;
+		struct nftnl_set_list	*sets;
+		bool			exists;
+	} table[NFT_TABLE_MAX];
+};
+
+enum obj_update_type {
+	NFT_COMPAT_TABLE_ADD,
+	NFT_COMPAT_TABLE_FLUSH,
+	NFT_COMPAT_CHAIN_ADD,
+	NFT_COMPAT_CHAIN_USER_ADD,
+	NFT_COMPAT_CHAIN_USER_DEL,
+	NFT_COMPAT_CHAIN_USER_FLUSH,
+	NFT_COMPAT_CHAIN_UPDATE,
+	NFT_COMPAT_CHAIN_RENAME,
+	NFT_COMPAT_CHAIN_ZERO,
+	NFT_COMPAT_RULE_APPEND,
+	NFT_COMPAT_RULE_INSERT,
+	NFT_COMPAT_RULE_REPLACE,
+	NFT_COMPAT_RULE_DELETE,
+	NFT_COMPAT_RULE_FLUSH,
+	NFT_COMPAT_SET_ADD,
+	NFT_COMPAT_RULE_LIST,
+	NFT_COMPAT_RULE_CHECK,
+	NFT_COMPAT_CHAIN_RESTORE,
+	NFT_COMPAT_RULE_SAVE,
+	NFT_COMPAT_RULE_ZERO,
+	NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE,
+};
+
+struct cache_chain {
+	struct list_head head;
+	char *name;
+};
+
+struct nft_cache_req {
+	enum nft_cache_level	level;
+	char			*table;
+	bool			all_chains;
+	struct list_head	chain_list;
+};
+
+struct nft_handle {
+	int			family;
+	struct mnl_socket	*nl;
+	int			nlsndbuffsiz;
+	int			nlrcvbuffsiz;
+	uint32_t		portid;
+	uint32_t		seq;
+	uint32_t		nft_genid;
+	uint32_t		rule_id;
+	struct list_head	obj_list;
+	int			obj_list_num;
+	struct nftnl_batch	*batch;
+	struct list_head	err_list;
+	struct nft_family_ops	*ops;
+	const struct builtin_table *tables;
+	unsigned int		cache_index;
+	struct nft_cache	__cache[2];
+	struct nft_cache	*cache;
+	struct nft_cache_req	cache_req;
+	bool			restore;
+	bool			noflush;
+	int8_t			config_done;
+	struct list_head	cmd_list;
+	bool			cache_init;
+
+	/* meta data, for error reporting */
+	struct {
+		unsigned int	lineno;
+	} error;
+};
+
+extern const struct builtin_table xtables_ipv4[NFT_TABLE_MAX];
+extern const struct builtin_table xtables_arp[NFT_TABLE_MAX];
+extern const struct builtin_table xtables_bridge[NFT_TABLE_MAX];
+
+int mnl_talk(struct nft_handle *h, struct nlmsghdr *nlh,
+	     int (*cb)(const struct nlmsghdr *nlh, void *data),
+	     void *data);
+int nft_init(struct nft_handle *h, int family, const struct builtin_table *t);
+void nft_fini(struct nft_handle *h);
+int nft_restart(struct nft_handle *h);
+
+/*
+ * Operations with tables.
+ */
+struct nftnl_table;
+struct nftnl_chain_list;
+
+int nft_for_each_table(struct nft_handle *h, int (*func)(struct nft_handle *h, const char *tablename, void *data), void *data);
+bool nft_table_find(struct nft_handle *h, const char *tablename);
+int nft_table_purge_chains(struct nft_handle *h, const char *table, struct nftnl_chain_list *list);
+int nft_table_flush(struct nft_handle *h, const char *table);
+const struct builtin_table *nft_table_builtin_find(struct nft_handle *h, const char *table);
+int nft_xt_fake_builtin_chains(struct nft_handle *h, const char *table, const char *chain);
+
+/*
+ * Operations with chains.
+ */
+struct nftnl_chain;
+
+int nft_chain_set(struct nft_handle *h, const char *table, const char *chain, const char *policy, const struct xt_counters *counters);
+int nft_chain_save(struct nft_chain *c, void *data);
+int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *table);
+int nft_chain_user_del(struct nft_handle *h, const char *chain, const char *table, bool verbose);
+int nft_chain_restore(struct nft_handle *h, const char *chain, const char *table);
+int nft_chain_user_rename(struct nft_handle *h, const char *chain, const char *table, const char *newname);
+int nft_chain_zero_counters(struct nft_handle *h, const char *chain, const char *table, bool verbose);
+const struct builtin_chain *nft_chain_builtin_find(const struct builtin_table *t, const char *chain);
+bool nft_chain_exists(struct nft_handle *h, const char *table, const char *chain);
+void nft_bridge_chain_postprocess(struct nft_handle *h,
+				  struct nftnl_chain *c);
+int nft_chain_foreach(struct nft_handle *h, const char *table,
+		      int (*cb)(struct nft_chain *c, void *data),
+		      void *data);
+
+
+/*
+ * Operations with sets.
+ */
+struct nftnl_set *nft_set_batch_lookup_byid(struct nft_handle *h,
+					    uint32_t set_id);
+
+/*
+ * Operations with rule-set.
+ */
+struct nftnl_rule;
+
+struct nftnl_rule *nft_rule_new(struct nft_handle *h, const char *chain, const char *table, void *data);
+int nft_rule_append(struct nft_handle *h, const char *chain, const char *table, struct nftnl_rule *r, struct nftnl_rule *ref, bool verbose);
+int nft_rule_insert(struct nft_handle *h, const char *chain, const char *table, struct nftnl_rule *r, int rulenum, bool verbose);
+int nft_rule_check(struct nft_handle *h, const char *chain, const char *table, struct nftnl_rule *r, bool verbose);
+int nft_rule_delete(struct nft_handle *h, const char *chain, const char *table, struct nftnl_rule *r, bool verbose);
+int nft_rule_delete_num(struct nft_handle *h, const char *chain, const char *table, int rulenum, bool verbose);
+int nft_rule_replace(struct nft_handle *h, const char *chain, const char *table, struct nftnl_rule *r, int rulenum, bool verbose);
+int nft_rule_list(struct nft_handle *h, const char *chain, const char *table, int rulenum, unsigned int format);
+int nft_rule_list_save(struct nft_handle *h, const char *chain, const char *table, int rulenum, int counters);
+int nft_rule_save(struct nft_handle *h, const char *table, unsigned int format);
+int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table, bool verbose);
+int nft_rule_zero_counters(struct nft_handle *h, const char *chain, const char *table, int rulenum);
+
+/*
+ * Operations used in userspace tools
+ */
+int add_counters(struct nftnl_rule *r, uint64_t packets, uint64_t bytes);
+int add_verdict(struct nftnl_rule *r, int verdict);
+int add_match(struct nft_handle *h, struct nftnl_rule *r, struct xt_entry_match *m);
+int add_target(struct nftnl_rule *r, struct xt_entry_target *t);
+int add_jumpto(struct nftnl_rule *r, const char *name, int verdict);
+int add_action(struct nftnl_rule *r, struct iptables_command_state *cs, bool goto_set);
+char *get_comment(const void *data, uint32_t data_len);
+
+enum nft_rule_print {
+	NFT_RULE_APPEND,
+	NFT_RULE_DEL,
+};
+
+void nft_rule_print_save(struct nft_handle *h, const struct nftnl_rule *r,
+			 enum nft_rule_print type, unsigned int format);
+
+uint32_t nft_invflags2cmp(uint32_t invflags, uint32_t flag);
+
+/*
+ * global commit and abort
+ */
+int nft_commit(struct nft_handle *h);
+int nft_bridge_commit(struct nft_handle *h);
+int nft_abort(struct nft_handle *h);
+
+/*
+ * revision compatibility.
+ */
+int nft_compatible_revision(const char *name, uint8_t rev, int opt);
+
+/*
+ * Error reporting.
+ */
+const char *nft_strerror(int err);
+
+/* For xtables.c */
+int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table, bool restore);
+/* For xtables-arptables.c */
+int nft_init_arp(struct nft_handle *h, const char *pname);
+int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table, bool restore);
+/* For xtables-eb.c */
+int nft_init_eb(struct nft_handle *h, const char *pname);
+void nft_fini_eb(struct nft_handle *h);
+int ebt_get_current_chain(const char *chain);
+int do_commandeb(struct nft_handle *h, int argc, char *argv[], char **table, bool restore);
+
+/*
+ * Translation from iptables to nft
+ */
+struct xt_buf;
+
+bool xlate_find_match(const struct iptables_command_state *cs, const char *p_name);
+int xlate_matches(const struct iptables_command_state *cs, struct xt_xlate *xl);
+int xlate_action(const struct iptables_command_state *cs, bool goto_set,
+		 struct xt_xlate *xl);
+void xlate_ifname(struct xt_xlate *xl, const char *nftmeta, const char *ifname,
+		  bool invert);
+
+/*
+ * ARP
+ */
+
+struct arpt_entry;
+
+int nft_arp_rule_append(struct nft_handle *h, const char *chain,
+			const char *table, struct arpt_entry *fw,
+			bool verbose);
+int nft_arp_rule_insert(struct nft_handle *h, const char *chain,
+			const char *table, struct arpt_entry *fw,
+			int rulenum, bool verbose);
+
+void nft_rule_to_arpt_entry(struct nftnl_rule *r, struct arpt_entry *fw);
+
+bool nft_is_table_compatible(struct nft_handle *h,
+			     const char *table, const char *chain);
+void nft_assert_table_compatible(struct nft_handle *h,
+				 const char *table, const char *chain);
+
+int ebt_set_user_chain_policy(struct nft_handle *h, const char *table,
+			      const char *chain, const char *policy);
+
+#endif
diff --git a/iptables/tests/shell/README b/iptables/tests/shell/README
new file mode 100644
index 0000000..08da486
--- /dev/null
+++ b/iptables/tests/shell/README
@@ -0,0 +1,17 @@
+To run the test suite (as root):
+ $ cd iptables/tests/shell
+ # ./run-tests.sh
+
+Test files are executable files with the pattern <<name_N>> , where N is the
+expected return code of the executable. Since they are located with `find',
+test-files can be spreaded in any sub-directories.
+
+You can turn on a verbose execution by calling:
+ # ./run-tests.sh -v
+
+And to run test suite for pariticular test files:
+ # ./run-tests.sh <PATH_OF_TESTFILES>
+
+Also, test-files will receive the environment variable $XT_MULTI which contains
+the path to the old iptables (xtables-legacy-multi) or new iptables (xtables-nft-multi)
+binary being tested.
diff --git a/iptables/tests/shell/run-tests.sh b/iptables/tests/shell/run-tests.sh
new file mode 100755
index 0000000..65c37ad
--- /dev/null
+++ b/iptables/tests/shell/run-tests.sh
@@ -0,0 +1,198 @@
+#!/bin/bash
+
+#configuration
+TESTDIR="./$(dirname $0)/"
+RETURNCODE_SEPARATOR="_"
+
+usage() {
+	cat <<EOF
+Usage: $(basename $0) [-v|--verbose] [-H|--host] [-V|--valgrind]
+		      [[-l|--legacy]|[-n|--nft]] [testscript ...]
+
+-v | --verbose		Enable verbose mode (do not drop testscript output).
+-H | --host		Run tests against installed binaries in \$PATH,
+			not those built in this source tree.
+-V | --valgrind		Enable leak checking via valgrind.
+-l | --legacy		Test legacy variant only. Conflicts with --nft.
+-n | --nft		Test nft variant only. Conflicts with --legacy.
+testscript		Run only specific test(s). Implies --verbose.
+EOF
+}
+
+msg_error() {
+        echo "E: $1 ..." >&2
+        exit 1
+}
+
+msg_warn() {
+        echo "W: $1" >&2
+}
+
+msg_info() {
+        echo "I: $1"
+}
+
+if [ "$(id -u)" != "0" ] ; then
+        msg_error "this requires root!"
+fi
+
+if [ ! -d "$TESTDIR" ] ; then
+        msg_error "missing testdir $TESTDIR"
+fi
+
+# support matching repeated pattern in SINGLE check below
+shopt -s extglob
+
+while [ -n "$1" ]; do
+	case "$1" in
+	-v|--verbose)
+		VERBOSE=y
+		shift
+		;;
+	-H|--host)
+		HOST=y
+		shift
+		;;
+	-l|--legacy)
+		LEGACY_ONLY=y
+		shift
+		;;
+	-n|--nft)
+		NFT_ONLY=y
+		shift
+		;;
+	-V|--valgrind)
+		VALGRIND=y
+		shift
+		;;
+	-h|--help)
+		usage
+		exit 0
+		;;
+	*${RETURNCODE_SEPARATOR}+([0-9]))
+		SINGLE+=" $1"
+		VERBOSE=y
+		shift
+		;;
+	*)
+		msg_error "unknown parameter '$1'"
+		;;
+	esac
+done
+
+if [ "$HOST" != "y" ]; then
+	XTABLES_NFT_MULTI="$(dirname $0)/../../xtables-nft-multi"
+	XTABLES_LEGACY_MULTI="$(dirname $0)/../../xtables-legacy-multi"
+
+	export XTABLES_LIBDIR=${TESTDIR}/../../../extensions
+else
+	XTABLES_NFT_MULTI="xtables-nft-multi"
+	XTABLES_LEGACY_MULTI="xtables-legacy-multi"
+fi
+
+printscript() { # (cmd, tmpd)
+	cat <<EOF
+#!/bin/bash
+
+CMD="$1"
+
+# note: valgrind man page warns about --log-file with --trace-children, the
+# last child executed overwrites previous reports unless %p or %q is used.
+# Since libtool wrapper calls exec but none of the iptables tools do, this is
+# perfect for us as it effectively hides bash-related errors
+
+valgrind --log-file=$2/valgrind.log --trace-children=yes \
+	 --leak-check=full --show-leak-kinds=all \$CMD "\$@"
+RC=\$?
+
+# don't keep uninteresting logs
+if grep -q 'no leaks are possible' $2/valgrind.log; then
+	rm $2/valgrind.log
+else
+	mv $2/valgrind.log $2/valgrind_\$\$.log
+fi
+
+# drop logs for failing commands for now
+[ \$RC -eq 0 ] || rm $2/valgrind_\$\$.log
+
+exit \$RC
+EOF
+}
+
+if [ "$VALGRIND" == "y" ]; then
+	tmpd=$(mktemp -d)
+	msg_info "writing valgrind logs to $tmpd"
+	chmod a+rx $tmpd
+	printscript "$XTABLES_NFT_MULTI" "$tmpd" >${tmpd}/xtables-nft-multi
+	printscript "$XTABLES_LEGACY_MULTI" "$tmpd" >${tmpd}/xtables-legacy-multi
+	trap "rm ${tmpd}/xtables-*-multi" EXIT
+	chmod a+x ${tmpd}/xtables-nft-multi ${tmpd}/xtables-legacy-multi
+
+	XTABLES_NFT_MULTI="${tmpd}/xtables-nft-multi"
+	XTABLES_LEGACY_MULTI="${tmpd}/xtables-legacy-multi"
+
+fi
+
+find_tests() {
+        if [ ! -z "$SINGLE" ] ; then
+                echo $SINGLE
+                return
+        fi
+        find ${TESTDIR} -executable -regex \
+                .*${RETURNCODE_SEPARATOR}[0-9]+ | sort
+}
+
+ok=0
+failed=0
+
+do_test() {
+	testfile="$1"
+	xtables_multi="$2"
+
+	rc_spec=`echo $(basename ${testfile}) | cut -d _ -f2-`
+
+	msg_info "[EXECUTING]   $testfile"
+
+	if [ "$VERBOSE" = "y" ]; then
+		XT_MULTI=$xtables_multi unshare -n ${testfile}
+		rc_got=$?
+	else
+		XT_MULTI=$xtables_multi unshare -n ${testfile} > /dev/null 2>&1
+		rc_got=$?
+		echo -en "\033[1A\033[K" # clean the [EXECUTING] foobar line
+	fi
+
+	if [ "$rc_got" == "$rc_spec" ] ; then
+		msg_info "[OK]          $testfile"
+		((ok++))
+	else
+		((failed++))
+		msg_warn "[FAILED]      $testfile: expected $rc_spec but got $rc_got"
+	fi
+}
+
+echo ""
+if [ "$NFT_ONLY" != "y" ]; then
+	for testfile in $(find_tests);do
+		do_test "$testfile" "$XTABLES_LEGACY_MULTI"
+	done
+	msg_info "legacy results: [OK] $ok [FAILED] $failed [TOTAL] $((ok+failed))"
+
+fi
+legacy_ok=$ok
+legacy_fail=$failed
+ok=0
+failed=0
+if [ "$LEGACY_ONLY" != "y" ]; then
+	for testfile in $(find_tests);do
+		do_test "$testfile" "$XTABLES_NFT_MULTI"
+	done
+	msg_info "nft results: [OK] $ok [FAILED] $failed [TOTAL] $((ok+failed))"
+fi
+
+ok=$((legacy_ok+ok))
+failed=$((legacy_fail+failed))
+
+msg_info "combined results: [OK] $ok [FAILED] $failed [TOTAL] $((ok+failed))"
+
+exit 0
diff --git a/iptables/tests/shell/testcases/arptables/0001-arptables-save-restore_0 b/iptables/tests/shell/testcases/arptables/0001-arptables-save-restore_0
new file mode 100755
index 0000000..e64e914
--- /dev/null
+++ b/iptables/tests/shell/testcases/arptables/0001-arptables-save-restore_0
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+set -e
+#set -x
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+# fill arptables manually
+
+$XT_MULTI arptables -F
+$XT_MULTI arptables -A INPUT -s 10.0.0.0/8 -j ACCEPT
+$XT_MULTI arptables -A INPUT -d 192.168.123.1 -j ACCEPT
+$XT_MULTI arptables -A INPUT --source-mac fe:ed:ba:be:00:01 -j ACCEPT
+$XT_MULTI arptables -A INPUT --destination-mac fe:ed:ba:be:00:01 -j ACCEPT
+$XT_MULTI arptables -N foo
+$XT_MULTI arptables -A foo -i lo -j ACCEPT
+$XT_MULTI arptables -A foo -l 6 -j ACCEPT
+$XT_MULTI arptables -A foo -j MARK --set-mark 12345
+$XT_MULTI arptables -A foo --opcode Request -j ACCEPT
+$XT_MULTI arptables -A foo --h-type 1 --proto-type 0x800 -j ACCEPT
+$XT_MULTI arptables -A foo -l 6 --h-type 1 --proto-type 0x800 -i lo --opcode Request -j ACCEPT
+$XT_MULTI arptables -A INPUT -j foo
+$XT_MULTI arptables -A INPUT
+
+$XT_MULTI arptables -A OUTPUT -o lo -j ACCEPT
+$XT_MULTI arptables -A OUTPUT -o eth134 -j mangle --mangle-ip-s 10.0.0.1
+$XT_MULTI arptables -A OUTPUT -o eth432 -j CLASSIFY --set-class feed:babe
+$XT_MULTI arptables -A OUTPUT -o eth432 --opcode Request -j CLASSIFY --set-class feed:babe
+$XT_MULTI arptables -P OUTPUT DROP
+
+# compare against stored arptables dump
+
+DUMP='*filter
+:INPUT ACCEPT
+:OUTPUT DROP
+:foo -
+-A INPUT -j ACCEPT -s 10.0.0.0/8
+-A INPUT -j ACCEPT -d 192.168.123.1
+-A INPUT -j ACCEPT --src-mac fe:ed:ba:be:00:01
+-A INPUT -j ACCEPT --dst-mac fe:ed:ba:be:00:01
+-A INPUT -j foo
+-A INPUT 
+-A OUTPUT -j ACCEPT -o lo
+-A OUTPUT -j mangle -o eth134 --mangle-ip-s 10.0.0.1
+-A OUTPUT -j CLASSIFY -o eth432 --set-class feed:babe
+-A OUTPUT -j CLASSIFY -o eth432 --opcode 1 --set-class feed:babe
+-A foo -j ACCEPT -i lo
+-A foo -j ACCEPT
+-A foo -j MARK --set-mark 12345
+-A foo -j ACCEPT --opcode 1
+-A foo -j ACCEPT --proto-type 0x800
+-A foo -j ACCEPT -i lo --opcode 1 --proto-type 0x800'
+
+diff -u <(echo -e "$DUMP") <($XT_MULTI arptables-save | grep -v "^#")
+
+# make sure dump can be restored and check it didn't change
+
+$XT_MULTI arptables -F
+$XT_MULTI arptables-restore <<<$DUMP
+diff -u <(echo -e "$DUMP") <($XT_MULTI arptables-save | grep -v "^#")
diff --git a/iptables/tests/shell/testcases/arptables/0002-arptables-restore-defaults_0 b/iptables/tests/shell/testcases/arptables/0002-arptables-restore-defaults_0
new file mode 100755
index 0000000..afd0fcb
--- /dev/null
+++ b/iptables/tests/shell/testcases/arptables/0002-arptables-restore-defaults_0
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+set -e
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+# arptables-restore reuses preloaded targets and matches, make sure defaults
+# apply to consecutive rules using the same target/match as a previous one
+
+DUMP='*filter
+:OUTPUT ACCEPT
+-A OUTPUT -j mangle --mangle-ip-s 10.0.0.1
+-A OUTPUT -j mangle --mangle-ip-d 10.0.0.2'
+
+# note how mangle-ip-s is unset in second rule
+
+EXPECT='*filter
+:INPUT ACCEPT
+:OUTPUT ACCEPT
+-A OUTPUT -j mangle --mangle-ip-s 10.0.0.1
+-A OUTPUT -j mangle --mangle-ip-d 10.0.0.2'
+
+$XT_MULTI arptables -F
+$XT_MULTI arptables-restore <<<$DUMP
+diff -u <(echo -e "$EXPECT") <($XT_MULTI arptables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/arptables/0003-arptables-verbose-output_0 b/iptables/tests/shell/testcases/arptables/0003-arptables-verbose-output_0
new file mode 100755
index 0000000..952cfa7
--- /dev/null
+++ b/iptables/tests/shell/testcases/arptables/0003-arptables-verbose-output_0
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+set -e
+set -x
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+$XT_MULTI arptables -N foo
+
+# check verbose output matches expectations
+
+RULE1='-i eth23 -j ACCEPT'
+VOUT1='-j ACCEPT -i eth23 -o *'
+
+RULE2='-i eth23'
+VOUT2='-i eth23 -o *'
+
+RULE3='-i eth23 -j MARK --set-mark 42'
+VOUT3='-j MARK -i eth23 -o * --set-mark 42'
+
+RULE4='-o eth23 -j CLASSIFY --set-class 23:42'
+VOUT4='-j CLASSIFY -i * -o eth23 --set-class 23:42'
+
+RULE5='-o eth23 -j foo'
+VOUT5='-j foo -i * -o eth23'
+
+RULE6='-o eth23 -j mangle --mangle-ip-s 10.0.0.1'
+VOUT6='-j mangle -i * -o eth23 --mangle-ip-s 10.0.0.1'
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI arptables -v -A INPUT $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI arptables -v -A INPUT $RULE2)
+diff -u -Z <(echo -e "$VOUT3") <($XT_MULTI arptables -v -A INPUT $RULE3)
+diff -u -Z <(echo -e "$VOUT4") <($XT_MULTI arptables -v -A OUTPUT $RULE4)
+diff -u -Z <(echo -e "$VOUT5") <($XT_MULTI arptables -v -A OUTPUT $RULE5)
+diff -u -Z <(echo -e "$VOUT6") <($XT_MULTI arptables -v -A foo $RULE6)
+
+EXPECT='Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
+-j ACCEPT -i eth23 -o * , pcnt=0 -- bcnt=0
+-i eth23 -o * , pcnt=0 -- bcnt=0
+-j MARK -i eth23 -o * --set-mark 42 , pcnt=0 -- bcnt=0
+
+Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
+-j CLASSIFY -i * -o eth23 --set-class 23:42 , pcnt=0 -- bcnt=0
+-j foo -i * -o eth23 , pcnt=0 -- bcnt=0
+
+Chain foo (1 references)
+-j mangle -i * -o eth23 --mangle-ip-s 10.0.0.1 , pcnt=0 -- bcnt=0'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI arptables -v -n -L)
+
+EXPECT='*filter
+:INPUT ACCEPT
+:OUTPUT ACCEPT
+:foo -
+-A INPUT -j ACCEPT -i eth23
+-A INPUT -i eth23
+-A INPUT -j MARK -i eth23 --set-mark 42
+-A OUTPUT -j CLASSIFY -o eth23 --set-class 23:42
+-A OUTPUT -j foo -o eth23
+-A foo -j mangle -o eth23 --mangle-ip-s 10.0.0.1'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI arptables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/chain/0001duplicate_1 b/iptables/tests/shell/testcases/chain/0001duplicate_1
new file mode 100755
index 0000000..80ebb11
--- /dev/null
+++ b/iptables/tests/shell/testcases/chain/0001duplicate_1
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -x
+
+$XT_MULTI iptables -t filter -N c1 || exit 0
+$XT_MULTI iptables -t filter -N c1 || exit 1
+
+$XT_MULTI ip6tables -t filter -N c1 || exit 0
+$XT_MULTI ip6tables -t filter -N c1 || exit 1
+
+echo "E: Duplicate chains" >&2
+exit 0
diff --git a/iptables/tests/shell/testcases/chain/0002newchain_0 b/iptables/tests/shell/testcases/chain/0002newchain_0
new file mode 100755
index 0000000..53f8a3a
--- /dev/null
+++ b/iptables/tests/shell/testcases/chain/0002newchain_0
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+set -e
+
+$XT_MULTI iptables -N c1
+$XT_MULTI ip6tables -N c1
+
+$XT_MULTI iptables -N c2
+$XT_MULTI ip6tables -N c2
diff --git a/iptables/tests/shell/testcases/chain/0003rename_1 b/iptables/tests/shell/testcases/chain/0003rename_1
new file mode 100755
index 0000000..975c8e1
--- /dev/null
+++ b/iptables/tests/shell/testcases/chain/0003rename_1
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+$XT_MULTI iptables -N c1 || exit 0
+$XT_MULTI iptables -N c2 || exit 0
+$XT_MULTI iptables -E c1 c2 || exit 1
+
+$XT_MULTI ip6tables -N c1 || exit 0
+$XT_MULTI ip6tables -N c2 || exit 0
+$XT_MULTI ip6tables -E c1 c2 || exit 1
+
+echo "E: Renamed with existing chain" >&2
+exit 0
diff --git a/iptables/tests/shell/testcases/ebtables/0001-ebtables-basic_0 b/iptables/tests/shell/testcases/ebtables/0001-ebtables-basic_0
new file mode 100755
index 0000000..6f11bd1
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0001-ebtables-basic_0
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+case "$XT_MULTI" in
+*xtables-nft-multi)
+	;;
+*)
+	echo "skip $XT_MULTI"
+	exit 0
+	;;
+esac
+
+get_entries_count() { # (chain)
+	$XT_MULTI ebtables -L $1 | sed -n 's/.*entries: \([0-9]*\).*/\1/p'
+}
+
+set -x
+
+for t in filter nat;do
+	$XT_MULTI ebtables -t $t -L || exit 1
+	$XT_MULTI ebtables -t $t -X || exit 1
+	$XT_MULTI ebtables -t $t -F || exit 1
+done
+
+for t in broute foobar ;do
+	$XT_MULTI ebtables -t $t -L &&
+	$XT_MULTI ebtables -t $t -X &&
+	$XT_MULTI ebtables -t $t -F
+	if [ $? -eq 0 ]; then
+		echo "Expect nonzero return for unsupported table"
+		exit 1
+	fi
+done
+
+
+$XT_MULTI ebtables -t filter -N FOO || exit 1
+$XT_MULTI ebtables -t filter -N FOO
+if [ $? -eq 0 ]; then
+	echo "Duplicate chain FOO"
+	$XT_MULTI ebtables -t filter -L
+	exit 1
+fi
+
+entries=$(get_entries_count FOO)
+if [ $entries -ne 0 ]; then
+	echo "Unexpected entries count in empty unreferenced chain (expected 0, have $entries)"
+	$XT_MULTI ebtables -L
+	exit 1
+fi
+
+$XT_MULTI ebtables -A FORWARD -j FOO
+entries=$(get_entries_count FORWARD)
+if [ $entries -ne 1 ]; then
+	echo "Unexpected entries count in FORWARD chain (expected 1, have $entries)"
+	$XT_MULTI ebtables -L
+	exit 1
+fi
+
+entries=$(get_entries_count FOO)
+if [ $entries -ne 0 ]; then
+	echo "Unexpected entries count in empty referenced chain (expected 0, have $entries)"
+	$XT_MULTI ebtables -L
+	exit 1
+fi
+
+$XT_MULTI ebtables -A FOO -j ACCEPT
+entries=$(get_entries_count FOO)
+if [ $entries -ne 1 ]; then
+	echo "Unexpected entries count in non-empty referenced chain (expected 1, have $entries)"
+	$XT_MULTI ebtables -L
+	exit 1
+fi
+
+$XT_MULTI ebtables -t filter -N BAR || exit 1
+$XT_MULTI ebtables -t filter -N BAZ || exit 1
+
+$XT_MULTI ebtables -t filter -L | grep -q FOO || exit 1
+$XT_MULTI ebtables -t filter -L | grep -q BAR || exit 1
+$XT_MULTI ebtables -t filter -L | grep -q BAZ || exit 1
+
+$XT_MULTI ebtables -t filter -L BAZ || exit 1
+$XT_MULTI ebtables -t filter -X BAZ || exit 1
+$XT_MULTI ebtables -t filter -L BAZ | grep -q BAZ
+if [ $? -eq 0 ]; then
+	echo "Deleted chain -L BAZ ok, expected failure"
+	$XT_MULTI ebtables -t filter -L
+	exit 1
+fi
+
+$XT_MULTI ebtables -t filter -E FOO BAZ || exit 1
+$XT_MULTI ebtables -t filter -L | grep -q FOO && exit 1
+$XT_MULTI ebtables -t filter -L | grep -q BAZ || exit 1
+
+$XT_MULTI ebtables -t $t -F || exit 0
diff --git a/iptables/tests/shell/testcases/ebtables/0002-ebtables-save-restore_0 b/iptables/tests/shell/testcases/ebtables/0002-ebtables-save-restore_0
new file mode 100755
index 0000000..ccdef19
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0002-ebtables-save-restore_0
@@ -0,0 +1,117 @@
+#!/bin/bash
+
+set -e
+#set -x
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+# fill ebtables manually
+
+$XT_MULTI ebtables --init-table
+$XT_MULTI ebtables -A INPUT -p IPv4 -i lo -j ACCEPT
+$XT_MULTI ebtables -P FORWARD DROP
+$XT_MULTI ebtables -A OUTPUT -s ff:ff:ff:ff:ff:ff/ff:ff:ff:ff:ff:ff -j DROP
+$XT_MULTI ebtables -N foo
+$XT_MULTI ebtables -A foo --802_3-sap 0x23 -j ACCEPT
+$XT_MULTI ebtables -A foo --802_3-sap 0xaa --802_3-type 0x1337 -j ACCEPT
+#$XT_MULTI ebtables -A foo --among-dst fe:ed:ba:be:00:01,fe:ed:ba:be:00:02,fe:ed:ba:be:00:03 -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-gratuitous -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-opcode Request -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-ip-src 10.0.0.1 -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-ip-dst 10.0.0.0/8 -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-mac-src fe:ed:ba:be:00:01 -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-mac-dst fe:ed:ba:be:00:01/ff:ff:ff:00:00:00 -j ACCEPT
+
+$XT_MULTI ebtables -A foo -p IPv4 --ip-src 10.0.0.1 -j ACCEPT
+$XT_MULTI ebtables -A foo -p IPv4 --ip-dst 10.0.0.0/8 -j ACCEPT
+$XT_MULTI ebtables -A foo -p IPv4 --ip-tos 0x10 -j ACCEPT
+$XT_MULTI ebtables -A foo -p IPv4 --ip-protocol tcp -j ACCEPT
+#$XT_MULTI ebtables -A foo -p IPv4 --ip-sport 23 -j ACCEPT
+#$XT_MULTI ebtables -A foo -p IPv4 --ip-dport 1024:4096 -j ACCEPT
+
+$XT_MULTI ebtables -A foo -p IPv6 --ip6-src feed:babe::1 -j ACCEPT
+$XT_MULTI ebtables -A foo -p IPv6 --ip6-dst feed:babe::/64 -j ACCEPT
+$XT_MULTI ebtables -A foo -p IPv6 --ip6-proto tcp -j ACCEPT
+#$XT_MULTI ebtables -A foo -p IPv6 --ip6-sport 23 -j ACCEPT
+#$XT_MULTI ebtables -A foo -p IPv6 --ip6-dport 1024:4096 -j ACCEPT
+
+$XT_MULTI ebtables -A foo --limit 100 --limit-burst 42 -j ACCEPT
+$XT_MULTI ebtables -A foo --log
+$XT_MULTI ebtables -A foo --mark-set 0x23 --mark-target ACCEPT
+$XT_MULTI ebtables -A foo --nflog
+$XT_MULTI ebtables -A foo --pkttype-type multicast -j ACCEPT
+$XT_MULTI ebtables -A foo --stp-type config -j ACCEPT
+#$XT_MULTI ebtables -A foo --vlan-id 42 -j ACCEPT
+
+$XT_MULTI ebtables -A foo --802_3-sap 0x23 --limit 100 -j ACCEPT
+$XT_MULTI ebtables -A foo --pkttype-type multicast --log
+$XT_MULTI ebtables -A foo --pkttype-type multicast --limit 100 -j ACCEPT
+
+$XT_MULTI ebtables -A FORWARD -j foo
+
+$XT_MULTI ebtables -N bar
+$XT_MULTI ebtables -P bar RETURN
+
+$XT_MULTI ebtables -t nat -A PREROUTING --redirect-target ACCEPT
+#$XT_MULTI ebtables -t nat -A PREROUTING --to-src fe:ed:ba:be:00:01
+
+$XT_MULTI ebtables -t nat -A OUTPUT -j ACCEPT
+$XT_MULTI ebtables -t nat -P OUTPUT DROP
+
+$XT_MULTI ebtables -t nat -A POSTROUTING -j ACCEPT
+#$XT_MULTI ebtables -t nat -A POSTROUTING --to-dst fe:ed:ba:be:00:01 --dnat-target ACCEPT
+
+$XT_MULTI ebtables -t nat -N nat_foo -P DROP
+
+# compare against stored ebtables dump
+
+DUMP='*filter
+:INPUT ACCEPT
+:FORWARD DROP
+:OUTPUT ACCEPT
+:bar RETURN
+:foo ACCEPT
+-A INPUT -p IPv4 -i lo -j ACCEPT
+-A FORWARD -j foo
+-A OUTPUT -s Broadcast -j DROP
+-A foo --802_3-sap 0x23 -j ACCEPT
+-A foo --802_3-sap 0xaa --802_3-type 0x1337 -j ACCEPT
+-A foo -p ARP --arp-gratuitous -j ACCEPT
+-A foo -p ARP --arp-op Request -j ACCEPT
+-A foo -p ARP --arp-ip-src 10.0.0.1 -j ACCEPT
+-A foo -p ARP --arp-ip-dst 10.0.0.0/8 -j ACCEPT
+-A foo -p ARP --arp-mac-src fe:ed:ba:be:00:01 -j ACCEPT
+-A foo -p ARP --arp-mac-dst fe:ed:ba:00:00:00/ff:ff:ff:00:00:00 -j ACCEPT
+-A foo -p IPv4 --ip-src 10.0.0.1 -j ACCEPT
+-A foo -p IPv4 --ip-dst 10.0.0.0/8 -j ACCEPT
+-A foo -p IPv4 --ip-tos 0x10 -j ACCEPT
+-A foo -p IPv4 --ip-proto tcp -j ACCEPT
+-A foo -p IPv6 --ip6-src feed:babe::1 -j ACCEPT
+-A foo -p IPv6 --ip6-dst feed:babe::/64 -j ACCEPT
+-A foo -p IPv6 --ip6-proto tcp -j ACCEPT
+-A foo --limit 100/sec --limit-burst 42 -j ACCEPT
+-A foo --log-level notice --log-prefix "" -j CONTINUE
+-A foo -j mark --mark-set 0x23 --mark-target ACCEPT
+-A foo --nflog-group 1 -j CONTINUE
+-A foo --pkttype-type multicast -j ACCEPT
+-A foo --stp-type config -j ACCEPT
+-A foo --802_3-sap 0x23 --limit 100/sec --limit-burst 5 -j ACCEPT
+-A foo --pkttype-type multicast --log-level notice --log-prefix "" -j CONTINUE
+-A foo --pkttype-type multicast --limit 100/sec --limit-burst 5 -j ACCEPT
+*nat
+:PREROUTING ACCEPT
+:OUTPUT DROP
+:POSTROUTING ACCEPT
+:nat_foo DROP
+-A PREROUTING -j redirect 
+-A OUTPUT -j ACCEPT
+-A POSTROUTING -j ACCEPT'
+
+diff -u <(echo -e "$DUMP") <($XT_MULTI ebtables-save | grep -v '^#')
+
+# make sure dump can be restored and check it didn't change
+
+$XT_MULTI ebtables --init-table
+$XT_MULTI ebtables-restore <<<$DUMP
+diff -u <(echo -e "$DUMP") <($XT_MULTI ebtables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/ebtables/0003-ebtables-restore-defaults_0 b/iptables/tests/shell/testcases/ebtables/0003-ebtables-restore-defaults_0
new file mode 100755
index 0000000..63891c1
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0003-ebtables-restore-defaults_0
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+set -e
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+# ebtables-restore reuses preloaded targets and matches, make sure defaults
+# apply to consecutive rules using the same target/match as a previous one
+
+DUMP='*filter
+:FORWARD ACCEPT
+-A FORWARD --limit 100 --limit-burst 42 -j ACCEPT
+-A FORWARD --limit 1000 -j ACCEPT
+-A FORWARD --log --log-prefix "foobar"
+-A FORWARD --log'
+
+# note how limit-burst is 5 in second rule and log-prefix empty in fourth one
+
+EXPECT='*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+-A FORWARD --limit 100/sec --limit-burst 42 -j ACCEPT
+-A FORWARD --limit 1000/sec --limit-burst 5 -j ACCEPT
+-A FORWARD --log-level notice --log-prefix "foobar" -j CONTINUE
+-A FORWARD --log-level notice --log-prefix "" -j CONTINUE'
+
+$XT_MULTI ebtables --init-table
+$XT_MULTI ebtables-restore <<<$DUMP
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/ebtables/0004-save-counters_0 b/iptables/tests/shell/testcases/ebtables/0004-save-counters_0
new file mode 100755
index 0000000..d52db90
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0004-save-counters_0
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+set -e
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+$XT_MULTI ebtables --init-table
+$XT_MULTI ebtables -A FORWARD -i nodev123 -o nodev432 -j ACCEPT
+$XT_MULTI ebtables -A FORWARD -i nodev432 -o nodev123 -j ACCEPT
+
+EXPECT='Bridge table: filter
+
+Bridge chain: FORWARD, entries: 2, policy: ACCEPT
+-i nodev123 -o nodev432 -j ACCEPT
+-i nodev432 -o nodev123 -j ACCEPT'
+
+echo "ebtables -L FORWARD"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables -L FORWARD)
+
+EXPECT='Bridge table: filter
+
+Bridge chain: FORWARD, entries: 2, policy: ACCEPT
+-i nodev123 -o nodev432 -j ACCEPT , pcnt = 0 -- bcnt = 0
+-i nodev432 -o nodev123 -j ACCEPT , pcnt = 0 -- bcnt = 0'
+
+echo "ebtables -L FORWARD --Lc"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables -L FORWARD --Lc)
+
+EXPECT='*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+-A FORWARD -i nodev123 -o nodev432 -j ACCEPT
+-A FORWARD -i nodev432 -o nodev123 -j ACCEPT'
+
+echo "ebtables-save"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save | grep -v '^#')
+
+EXPECT='*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+[0:0] -A FORWARD -i nodev123 -o nodev432 -j ACCEPT
+[0:0] -A FORWARD -i nodev432 -o nodev123 -j ACCEPT'
+
+echo "ebtables-save -c"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save -c | grep -v '^#')
+
+export EBTABLES_SAVE_COUNTER=yes
+
+# -c flag overrides EBTABLES_SAVE_COUNTER variable
+echo "EBTABLES_SAVE_COUNTER=yes ebtables-save -c"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save -c | grep -v '^#')
+
+EXPECT='*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+-A FORWARD -i nodev123 -o nodev432 -j ACCEPT -c 0 0
+-A FORWARD -i nodev432 -o nodev123 -j ACCEPT -c 0 0'
+
+echo "EBTABLES_SAVE_COUNTER=yes ebtables-save"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/ebtables/0005-ifnamechecks_0 b/iptables/tests/shell/testcases/ebtables/0005-ifnamechecks_0
new file mode 100755
index 0000000..0b3acfd
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0005-ifnamechecks_0
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+EXPECT='*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+:PVEFW-FORWARD ACCEPT
+:PVEFW-FWBR-OUT ACCEPT
+-A FORWARD -j PVEFW-FORWARD
+-A PVEFW-FORWARD -p IPv4 -j ACCEPT
+-A PVEFW-FORWARD -p IPv6 -j ACCEPT
+-A PVEFW-FORWARD -i fwln+ -j ACCEPT
+-A PVEFW-FORWARD -o fwln+ -j PVEFW-FWBR-OUT'
+
+$XT_MULTI ebtables-restore <<<$EXPECT
+exec diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/firewalld-restore/0001-firewalld_0 b/iptables/tests/shell/testcases/firewalld-restore/0001-firewalld_0
new file mode 100755
index 0000000..4900554
--- /dev/null
+++ b/iptables/tests/shell/testcases/firewalld-restore/0001-firewalld_0
@@ -0,0 +1,238 @@
+#!/bin/sh
+
+$XT_MULTI iptables -w -L -n > /dev/null || exit 1
+$XT_MULTI iptables -w2 -L -n > /dev/null || exit 1
+
+echo -n '#foo' | $XT_MULTI iptables-restore -w || exit 1
+
+# table probing
+for table in security raw mangle nat filter;do
+	$XT_MULTI iptables -w2 -t $table -L -n > /dev/null
+done
+
+$XT_MULTI iptables -w2 -p icmp --help | grep -q 'Valid ICMP Types' || exit 1
+
+cat <<EOF | $XT_MULTI iptables-restore -w -n
+*nat
+-F
+-X
+-Z
+-N PREROUTING_direct
+-I PREROUTING 1 -j PREROUTING_direct
+-N PREROUTING_ZONES_SOURCE
+-N PREROUTING_ZONES
+-I PREROUTING 2 -j PREROUTING_ZONES_SOURCE
+-I PREROUTING 3 -j PREROUTING_ZONES
+-N POSTROUTING_direct
+-I POSTROUTING 1 -j POSTROUTING_direct
+-N POSTROUTING_ZONES_SOURCE
+-N POSTROUTING_ZONES
+-I POSTROUTING 2 -j POSTROUTING_ZONES_SOURCE
+-I POSTROUTING 3 -j POSTROUTING_ZONES
+-N OUTPUT_direct
+-I OUTPUT 1 -j OUTPUT_direct
+COMMIT
+*mangle
+-F
+-X
+-Z
+-N PREROUTING_direct
+-I PREROUTING 1 -j PREROUTING_direct
+-N PREROUTING_ZONES_SOURCE
+-N PREROUTING_ZONES
+-I PREROUTING 2 -j PREROUTING_ZONES_SOURCE
+-I PREROUTING 3 -j PREROUTING_ZONES
+-N POSTROUTING_direct
+-I POSTROUTING 1 -j POSTROUTING_direct
+-N INPUT_direct
+-I INPUT 1 -j INPUT_direct
+-N OUTPUT_direct
+-I OUTPUT 1 -j OUTPUT_direct
+-N FORWARD_direct
+-I FORWARD 1 -j FORWARD_direct
+COMMIT
+*raw
+-F
+-X
+-Z
+-N PREROUTING_direct
+-I PREROUTING 1 -j PREROUTING_direct
+-N PREROUTING_ZONES_SOURCE
+-N PREROUTING_ZONES
+-I PREROUTING 2 -j PREROUTING_ZONES_SOURCE
+-I PREROUTING 3 -j PREROUTING_ZONES
+-N OUTPUT_direct
+-I OUTPUT 1 -j OUTPUT_direct
+COMMIT
+*filter
+-F
+-X
+-Z
+-N INPUT_direct
+-N INPUT_ZONES_SOURCE
+-N INPUT_ZONES
+-I INPUT 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-I INPUT 2 -i lo -j ACCEPT
+-I INPUT 3 -j INPUT_direct
+-I INPUT 4 -j INPUT_ZONES_SOURCE
+-I INPUT 5 -j INPUT_ZONES
+-I INPUT 6 -m conntrack --ctstate INVALID -j DROP
+-I INPUT 7 -j REJECT --reject-with icmp-host-prohibited
+-N FORWARD_direct
+-N FORWARD_IN_ZONES_SOURCE
+-N FORWARD_IN_ZONES
+-N FORWARD_OUT_ZONES_SOURCE
+-N FORWARD_OUT_ZONES
+-I FORWARD 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-I FORWARD 2 -i lo -j ACCEPT
+-I FORWARD 3 -j FORWARD_direct
+-I FORWARD 4 -j FORWARD_IN_ZONES_SOURCE
+-I FORWARD 5 -j FORWARD_IN_ZONES
+-I FORWARD 6 -j FORWARD_OUT_ZONES_SOURCE
+-I FORWARD 7 -j FORWARD_OUT_ZONES
+-I FORWARD 8 -m conntrack --ctstate INVALID -j DROP
+-I FORWARD 9 -j REJECT --reject-with icmp-host-prohibited
+-N OUTPUT_direct
+-I OUTPUT 1 -j OUTPUT_direct
+COMMIT
+EOF
+
+if [ $? -ne 0 ]; then
+	echo "Error during first iptables-restore"
+	exit 1
+fi
+
+cat <<EOF | $XT_MULTI iptables-restore -w -n
+*raw
+-N PRE_public
+-N PRE_public_log
+-N PRE_public_deny
+-N PRE_public_allow
+-I PRE_public 1 -j PRE_public_log
+-I PRE_public 2 -j PRE_public_deny
+-I PRE_public 3 -j PRE_public_allow
+-A PREROUTING_ZONES -i + -g PRE_public
+COMMIT
+*filter
+-N IN_public
+-N IN_public_log
+-N IN_public_deny
+-N IN_public_allow
+-I IN_public 1 -j IN_public_log
+-I IN_public 2 -j IN_public_deny
+-I IN_public 3 -j IN_public_allow
+-A IN_public_allow -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_public_allow -p udp --dport 5353 -d 224.0.0.251 -m conntrack --ctstate NEW -j ACCEPT
+-N FWDI_public
+-N FWDI_public_log
+-N FWDI_public_deny
+-N FWDI_public_allow
+-I FWDI_public 1 -j FWDI_public_log
+-I FWDI_public 2 -j FWDI_public_deny
+-I FWDI_public 3 -j FWDI_public_allow
+-I IN_public 4 -p icmp -j ACCEPT
+-I FWDI_public 4 -p icmp -j ACCEPT
+-A INPUT_ZONES -i + -g IN_public
+-A FORWARD_IN_ZONES -i + -g FWDI_public
+-N FWDO_public
+-N FWDO_public_log
+-N FWDO_public_deny
+-N FWDO_public_allow
+-I FWDO_public 1 -j FWDO_public_log
+-I FWDO_public 2 -j FWDO_public_deny
+-I FWDO_public 3 -j FWDO_public_allow
+-A FORWARD_OUT_ZONES -o + -g FWDO_public
+COMMIT
+*nat
+-N PRE_public
+-N PRE_public_log
+-N PRE_public_deny
+-N PRE_public_allow
+-I PRE_public 1 -j PRE_public_log
+-I PRE_public 2 -j PRE_public_deny
+-I PRE_public 3 -j PRE_public_allow
+-A PREROUTING_ZONES -i + -g PRE_public
+-N POST_public
+-N POST_public_log
+-N POST_public_deny
+-N POST_public_allow
+-I POST_public 1 -j POST_public_log
+-I POST_public 2 -j POST_public_deny
+-I POST_public 3 -j POST_public_allow
+-A POSTROUTING_ZONES -o + -g POST_public
+COMMIT
+*mangle
+-N PRE_public
+-N PRE_public_log
+-N PRE_public_deny
+-N PRE_public_allow
+-I PRE_public 1 -j PRE_public_log
+-I PRE_public 2 -j PRE_public_deny
+-I PRE_public 3 -j PRE_public_allow
+-A PREROUTING_ZONES -i + -g PRE_public
+COMMIT
+EOF
+
+if [ $? -ne 0 ]; then
+	echo "Error during 2nd iptables-restore"
+	exit 1
+fi
+
+cat <<EOF | $XT_MULTI iptables-restore -w -n
+*mangle
+-P PREROUTING ACCEPT
+-P POSTROUTING ACCEPT
+-P INPUT ACCEPT
+-P OUTPUT ACCEPT
+-P FORWARD ACCEPT
+COMMIT
+*raw
+-P PREROUTING ACCEPT
+-P OUTPUT ACCEPT
+COMMIT
+*filter
+-P INPUT ACCEPT
+-P OUTPUT ACCEPT
+-P FORWARD ACCEPT
+COMMIT
+EOF
+
+if [ $? -ne 0 ]; then
+	echo "Error during 3rd iptables-restore"
+	exit 1
+fi
+
+cat <<EOF | $XT_MULTI iptables-restore -w -n
+*filter
+-I INPUT_ZONES 1 -i enp3s0 -g IN_public
+-I FORWARD_IN_ZONES 1 -i enp3s0 -g FWDI_public
+-I FORWARD_OUT_ZONES 1 -o enp3s0 -g FWDO_public
+COMMIT
+*nat
+-I PREROUTING_ZONES 1 -i enp3s0 -g PRE_public
+-I POSTROUTING_ZONES 1 -o enp3s0 -g POST_public
+COMMIT
+*mangle
+-I PREROUTING_ZONES 1 -i enp3s0 -g PRE_public
+COMMIT
+*raw
+-I PREROUTING_ZONES 1 -i enp3s0 -g PRE_public
+COMMIT
+EOF
+
+if [ $? -ne 0 ]; then
+	echo "Error during 4th iptables-restore"
+	exit 1
+fi
+
+tmpfile=$(mktemp) || exit 1
+for table in nat mangle raw filter;do
+	$XT_MULTI iptables-save -t $table | grep -v '^#' >> "$tmpfile"
+done
+
+diff -u $tmpfile  $(dirname "$0")/dumps/ipt-save-completed.txt
+RET=$?
+
+rm -f "$tmpfile"
+
+exit $RET
diff --git a/iptables/tests/shell/testcases/firewalld-restore/0002-firewalld-restart_0 b/iptables/tests/shell/testcases/firewalld-restore/0002-firewalld-restart_0
new file mode 100755
index 0000000..50e9492
--- /dev/null
+++ b/iptables/tests/shell/testcases/firewalld-restore/0002-firewalld-restart_0
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# simulate restart after it went down, so first restore
+# the complete ruleset
+
+$XT_MULTI iptables-restore < $(dirname "$0")/dumps/ipt-save-completed.txt
+
+# add dummy rules to see if they get cleared or not.
+for table in raw mangle nat filter;do
+	$XT_MULTI iptables -t $table -N FOO$table || exit 1
+	$XT_MULTI iptables -t $table -A OUTPUT -m comment --comment '"dummy rule in table $table OUTPUT"' || exit 1
+	$XT_MULTI iptables -t $table -A FOO$table -m comment --comment '"dummy rule in table $table FOO$table"' || exit 1
+done
+
+# then run the other test script so it finds already-existing ruleset.
+
+exec $(dirname "$0")/0001-firewalld_0
diff --git a/iptables/tests/shell/testcases/firewalld-restore/dumps/ipt-save-completed.txt b/iptables/tests/shell/testcases/firewalld-restore/dumps/ipt-save-completed.txt
new file mode 100644
index 0000000..03704ec
--- /dev/null
+++ b/iptables/tests/shell/testcases/firewalld-restore/dumps/ipt-save-completed.txt
@@ -0,0 +1,151 @@
+*nat
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+:OUTPUT_direct - [0:0]
+:POSTROUTING_ZONES - [0:0]
+:POSTROUTING_ZONES_SOURCE - [0:0]
+:POSTROUTING_direct - [0:0]
+:POST_public - [0:0]
+:POST_public_allow - [0:0]
+:POST_public_deny - [0:0]
+:POST_public_log - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_public - [0:0]
+:PRE_public_allow - [0:0]
+:PRE_public_deny - [0:0]
+:PRE_public_log - [0:0]
+-A PREROUTING -j PREROUTING_direct
+-A PREROUTING -j PREROUTING_ZONES_SOURCE
+-A PREROUTING -j PREROUTING_ZONES
+-A OUTPUT -j OUTPUT_direct
+-A POSTROUTING -j POSTROUTING_direct
+-A POSTROUTING -j POSTROUTING_ZONES_SOURCE
+-A POSTROUTING -j POSTROUTING_ZONES
+-A POSTROUTING_ZONES -o enp3s0 -g POST_public
+-A POSTROUTING_ZONES -g POST_public
+-A POST_public -j POST_public_log
+-A POST_public -j POST_public_deny
+-A POST_public -j POST_public_allow
+-A PREROUTING_ZONES -i enp3s0 -g PRE_public
+-A PREROUTING_ZONES -g PRE_public
+-A PRE_public -j PRE_public_log
+-A PRE_public -j PRE_public_deny
+-A PRE_public -j PRE_public_allow
+COMMIT
+*mangle
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+:FORWARD_direct - [0:0]
+:INPUT_direct - [0:0]
+:OUTPUT_direct - [0:0]
+:POSTROUTING_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_public - [0:0]
+:PRE_public_allow - [0:0]
+:PRE_public_deny - [0:0]
+:PRE_public_log - [0:0]
+-A PREROUTING -j PREROUTING_direct
+-A PREROUTING -j PREROUTING_ZONES_SOURCE
+-A PREROUTING -j PREROUTING_ZONES
+-A INPUT -j INPUT_direct
+-A FORWARD -j FORWARD_direct
+-A OUTPUT -j OUTPUT_direct
+-A POSTROUTING -j POSTROUTING_direct
+-A PREROUTING_ZONES -i enp3s0 -g PRE_public
+-A PREROUTING_ZONES -g PRE_public
+-A PRE_public -j PRE_public_log
+-A PRE_public -j PRE_public_deny
+-A PRE_public -j PRE_public_allow
+COMMIT
+*raw
+:PREROUTING ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:OUTPUT_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_public - [0:0]
+:PRE_public_allow - [0:0]
+:PRE_public_deny - [0:0]
+:PRE_public_log - [0:0]
+-A PREROUTING -j PREROUTING_direct
+-A PREROUTING -j PREROUTING_ZONES_SOURCE
+-A PREROUTING -j PREROUTING_ZONES
+-A OUTPUT -j OUTPUT_direct
+-A PREROUTING_ZONES -i enp3s0 -g PRE_public
+-A PREROUTING_ZONES -g PRE_public
+-A PRE_public -j PRE_public_log
+-A PRE_public -j PRE_public_deny
+-A PRE_public -j PRE_public_allow
+COMMIT
+*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:FORWARD_IN_ZONES - [0:0]
+:FORWARD_IN_ZONES_SOURCE - [0:0]
+:FORWARD_OUT_ZONES - [0:0]
+:FORWARD_OUT_ZONES_SOURCE - [0:0]
+:FORWARD_direct - [0:0]
+:FWDI_public - [0:0]
+:FWDI_public_allow - [0:0]
+:FWDI_public_deny - [0:0]
+:FWDI_public_log - [0:0]
+:FWDO_public - [0:0]
+:FWDO_public_allow - [0:0]
+:FWDO_public_deny - [0:0]
+:FWDO_public_log - [0:0]
+:INPUT_ZONES - [0:0]
+:INPUT_ZONES_SOURCE - [0:0]
+:INPUT_direct - [0:0]
+:IN_public - [0:0]
+:IN_public_allow - [0:0]
+:IN_public_deny - [0:0]
+:IN_public_log - [0:0]
+:OUTPUT_direct - [0:0]
+-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -i lo -j ACCEPT
+-A INPUT -j INPUT_direct
+-A INPUT -j INPUT_ZONES_SOURCE
+-A INPUT -j INPUT_ZONES
+-A INPUT -m conntrack --ctstate INVALID -j DROP
+-A INPUT -j REJECT --reject-with icmp-host-prohibited
+-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A FORWARD -i lo -j ACCEPT
+-A FORWARD -j FORWARD_direct
+-A FORWARD -j FORWARD_IN_ZONES_SOURCE
+-A FORWARD -j FORWARD_IN_ZONES
+-A FORWARD -j FORWARD_OUT_ZONES_SOURCE
+-A FORWARD -j FORWARD_OUT_ZONES
+-A FORWARD -m conntrack --ctstate INVALID -j DROP
+-A FORWARD -j REJECT --reject-with icmp-host-prohibited
+-A OUTPUT -j OUTPUT_direct
+-A FORWARD_IN_ZONES -i enp3s0 -g FWDI_public
+-A FORWARD_IN_ZONES -g FWDI_public
+-A FORWARD_OUT_ZONES -o enp3s0 -g FWDO_public
+-A FORWARD_OUT_ZONES -g FWDO_public
+-A FWDI_public -j FWDI_public_log
+-A FWDI_public -j FWDI_public_deny
+-A FWDI_public -j FWDI_public_allow
+-A FWDI_public -p icmp -j ACCEPT
+-A FWDO_public -j FWDO_public_log
+-A FWDO_public -j FWDO_public_deny
+-A FWDO_public -j FWDO_public_allow
+-A INPUT_ZONES -i enp3s0 -g IN_public
+-A INPUT_ZONES -g IN_public
+-A IN_public -j IN_public_log
+-A IN_public -j IN_public_deny
+-A IN_public -j IN_public_allow
+-A IN_public -p icmp -j ACCEPT
+-A IN_public_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_public_allow -d 224.0.0.251/32 -p udp -m udp --dport 5353 -m conntrack --ctstate NEW -j ACCEPT
+COMMIT
diff --git a/iptables/tests/shell/testcases/ip6tables/0002-verbose-output_0 b/iptables/tests/shell/testcases/ip6tables/0002-verbose-output_0
new file mode 100755
index 0000000..7b0e646
--- /dev/null
+++ b/iptables/tests/shell/testcases/ip6tables/0002-verbose-output_0
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+set -e
+#set -x
+
+# ensure verbose output is identical between legacy and nft tools
+
+RULE1='-i eth2 -o eth3 -s feed:babe::1 -d feed:babe::2 -j ACCEPT'
+VOUT1='ACCEPT  all opt    in eth2 out eth3  feed:babe::1  -> feed:babe::2'
+RULE2='-i eth2 -o eth3 -s feed:babe::4 -d feed:babe::5 -j ACCEPT'
+VOUT2='ACCEPT  all opt    in eth2 out eth3  feed:babe::4  -> feed:babe::5'
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI ip6tables -v -A FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI ip6tables -v -I FORWARD 2 $RULE2)
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI ip6tables -v -C FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI ip6tables -v -C FORWARD $RULE2)
+
+EXPECT='Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination
+
+Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination
+    0     0 ACCEPT     all      eth2   eth3    feed:babe::1         feed:babe::2
+    0     0 ACCEPT     all      eth2   eth3    feed:babe::4         feed:babe::5
+
+Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -n -L)
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI ip6tables -v -D FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI ip6tables -v -D FORWARD $RULE2)
+
+EXPECT="Flushing chain \`INPUT'
+Flushing chain \`FORWARD'
+Flushing chain \`OUTPUT'"
+
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -F)
+
+EXPECT="Zeroing chain \`INPUT'
+Zeroing chain \`FORWARD'
+Zeroing chain \`OUTPUT'"
+
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -Z)
+
+diff -u <(echo "Flushing chain \`OUTPUT'") <($XT_MULTI ip6tables -v -F OUTPUT)
+diff -u <(echo "Zeroing chain \`OUTPUT'") <($XT_MULTI ip6tables -v -Z OUTPUT)
+
+$XT_MULTI ip6tables -N foo
+diff -u <(echo "Deleting chain \`foo'") <($XT_MULTI ip6tables -v -X foo)
diff --git a/iptables/tests/shell/testcases/ip6tables/0003-list-rules_0 b/iptables/tests/shell/testcases/ip6tables/0003-list-rules_0
new file mode 100755
index 0000000..c98bdd6
--- /dev/null
+++ b/iptables/tests/shell/testcases/ip6tables/0003-list-rules_0
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+set -e
+
+$XT_MULTI ip6tables -N foo
+$XT_MULTI ip6tables -A FORWARD -i eth23 -o eth42 -j ACCEPT
+$XT_MULTI ip6tables -A FORWARD -i eth42 -o eth23 -g foo
+$XT_MULTI ip6tables -t nat -A OUTPUT -o eth123 -m mark --mark 0x42 -j ACCEPT
+
+EXPECT='-P INPUT ACCEPT
+-P FORWARD ACCEPT
+-P OUTPUT ACCEPT
+-N foo
+-A FORWARD -i eth23 -o eth42 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -S)
+
+EXPECT='-P INPUT ACCEPT -c 0 0
+-P FORWARD ACCEPT -c 0 0
+-P OUTPUT ACCEPT -c 0 0
+-N foo
+-A FORWARD -i eth23 -o eth42 -c 0 0 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -c 0 0 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -S)
+
+EXPECT='-P FORWARD ACCEPT
+-A FORWARD -i eth23 -o eth42 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -S FORWARD)
+
+EXPECT='-P FORWARD ACCEPT -c 0 0
+-A FORWARD -i eth23 -o eth42 -c 0 0 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -c 0 0 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -S FORWARD)
+
+EXPECT='-P OUTPUT ACCEPT
+-A OUTPUT -o eth123 -m mark --mark 0x42 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -t nat -S OUTPUT)
+
+EXPECT='-P OUTPUT ACCEPT -c 0 0
+-A OUTPUT -o eth123 -m mark --mark 0x42 -c 0 0 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -t nat -S OUTPUT)
+
+# some of the following commands are supposed to fail
+set +e
+
+$XT_MULTI ip6tables -S nonexistent && {
+	echo "list-rules in non-existent chain should fail"
+	exit 1
+}
+$XT_MULTI ip6tables -S nonexistent 23 && {
+	echo "list-rules in non-existent chain with given rule number should fail"
+	exit 1
+}
+$XT_MULTI ip6tables -S FORWARD 234 || {
+	echo "list-rules in existent chain with invalid rule number should succeed"
+	exit 1
+}
diff --git a/iptables/tests/shell/testcases/ipt-restore/0001load-specific-table_0 b/iptables/tests/shell/testcases/ipt-restore/0001load-specific-table_0
new file mode 100755
index 0000000..3f443a9
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0001load-specific-table_0
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+RET=0
+tmpfile=""
+
+set -x
+
+clean_tempfile()
+{
+	if [ -n "${tmpfile}" ]; then
+		rm -f "${tmpfile}"
+	fi
+}
+
+trap clean_tempfile EXIT
+
+tmpfile=$(mktemp) || exit 1
+
+do_simple()
+{
+	iptables="${1}"
+	table="${2}"
+	dumpfile="$(dirname "${0}")/dumps/${iptables}.dump"
+
+	"$XT_MULTI" "${iptables}-restore" --table="${table}" "${dumpfile}"; rv=$?
+
+	if [ "${rv}" -ne 0 ]; then
+		RET=1
+	fi
+}
+
+do_simple "iptables" "filter"
+do_simple "iptables" "mangle"
+do_simple "iptables" "raw"
+do_simple "iptables" "nat"
+do_simple "ip6tables" "filter"
+do_simple "ip6tables" "mangle"
+do_simple "ip6tables" "raw"
+do_simple "ip6tables" "nat"
+
+exit "${RET}"
diff --git a/iptables/tests/shell/testcases/ipt-restore/0002-parameters_0 b/iptables/tests/shell/testcases/ipt-restore/0002-parameters_0
new file mode 100755
index 0000000..5c8748e
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0002-parameters_0
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -e
+
+# make sure wait and wait-interval options are accepted
+
+clean_tempfile()
+{
+	if [ -n "${tmpfile}" ]; then
+		rm -f "${tmpfile}"
+	fi
+}
+
+trap clean_tempfile EXIT
+
+tmpfile=$(mktemp) || exit 1
+
+$XT_MULTI iptables-save -f $tmpfile
+$XT_MULTI iptables-restore $tmpfile
+$XT_MULTI iptables-restore -w 5 $tmpfile
+$XT_MULTI iptables-restore -w 5 -W 1 $tmpfile
diff --git a/iptables/tests/shell/testcases/ipt-restore/0003-restore-ordering_0 b/iptables/tests/shell/testcases/ipt-restore/0003-restore-ordering_0
new file mode 100755
index 0000000..3f1d229
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0003-restore-ordering_0
@@ -0,0 +1,125 @@
+#!/bin/bash
+
+# Make sure iptables-restore does the right thing
+# when encountering INSERT rules with index.
+
+set -e
+
+# show rules, drop uninteresting policy settings
+ipt_show() {
+	$XT_MULTI iptables -S | grep -v '^-P'
+}
+
+# basic issue reproducer
+
+$XT_MULTI iptables-restore <<EOF
+*filter
+-A FORWARD -m comment --comment "rule 4" -j ACCEPT
+-I FORWARD 1 -m comment --comment "rule 1" -j ACCEPT
+-I FORWARD 2 -m comment --comment "rule 2" -j ACCEPT
+-I FORWARD 3 -m comment --comment "rule 3" -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT
+-A FORWARD -m comment --comment "rule 4" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+# insert rules into existing ruleset
+
+$XT_MULTI iptables-restore --noflush <<EOF
+*filter
+-A FORWARD -m comment --comment "rule 5" -j ACCEPT
+-I FORWARD 1 -m comment --comment "rule 0.5" -j ACCEPT
+-I FORWARD 3 -m comment --comment "rule 1.5" -j ACCEPT
+-I FORWARD 5 -m comment --comment "rule 2.5" -j ACCEPT
+-I FORWARD 7 -m comment --comment "rule 3.5" -j ACCEPT
+-I FORWARD 9 -m comment --comment "rule 4.5" -j ACCEPT
+-I FORWARD 11 -m comment --comment "rule 5.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 6" -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment "rule 0.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "rule 1.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 2.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 4" -j ACCEPT
+-A FORWARD -m comment --comment "rule 4.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 5.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 6" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+# insert rules in between added ones
+
+$XT_MULTI iptables-restore <<EOF
+*filter
+-A FORWARD -m comment --comment "appended rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 3" -j ACCEPT
+-I FORWARD 1 -m comment --comment "rule 1" -j ACCEPT
+-I FORWARD 3 -m comment --comment "rule 2" -j ACCEPT
+-I FORWARD 5 -m comment --comment "rule 3" -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+# test rule deletion in dump files
+
+$XT_MULTI iptables-restore --noflush <<EOF
+*filter
+-A FORWARD -m comment --comment "appended rule 4" -j ACCEPT
+-D FORWARD 7
+-D FORWARD -m comment --comment "appended rule 1" -j ACCEPT
+-D FORWARD 3
+-I FORWARD 3 -m comment --comment "manually replaced rule 2" -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "manually replaced rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+# test rule replacement in dump files
+
+$XT_MULTI iptables-restore <<EOF
+*filter
+-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "rule to be replaced" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT
+COMMIT
+EOF
+
+$XT_MULTI iptables-restore --noflush <<EOF
+*filter
+-R FORWARD 2 -m comment --comment "replacement" -j ACCEPT
+-I FORWARD 2 -m comment --comment "insert referencing replaced rule" -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "insert referencing replaced rule" -j ACCEPT
+-A FORWARD -m comment --comment replacement -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
diff --git a/iptables/tests/shell/testcases/ipt-restore/0004-restore-race_0 b/iptables/tests/shell/testcases/ipt-restore/0004-restore-race_0
new file mode 100755
index 0000000..a7fae41
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0004-restore-race_0
@@ -0,0 +1,117 @@
+#!/bin/bash
+
+have_nft=false
+nft -v > /dev/null && have_nft=true
+
+dumpfile=""
+tmpfile=""
+
+set -e
+
+clean()
+{
+	$XT_MULTI iptables -t filter -F
+	$XT_MULTI iptables -t filter -X
+	$have_nft && nft flush ruleset
+}
+
+clean_tempfile()
+{
+	[ -n "${tmpfile}" ] && rm -f "${tmpfile}"
+	[ -n "${dumpfile}" ] && rm -f "${dumpfile}"
+	clean
+}
+
+trap clean_tempfile EXIT
+
+ENTRY_NUM=$((RANDOM%10))
+UCHAIN_NUM=$((RANDOM%10))
+
+get_target()
+{
+	if [ $UCHAIN_NUM -eq 0 ]; then
+		echo -n "ACCEPT"
+		return
+	fi
+
+
+	x=$((RANDOM%2))
+	if [ $x -eq 0 ];then
+		echo -n "ACCEPT"
+	else
+		printf -- "UC-%x" $((RANDOM%UCHAIN_NUM))
+	fi
+}
+
+make_dummy_rules()
+{
+	echo "*${1:-filter}"
+	echo ":INPUT ACCEPT [0:0]"
+	echo ":FORWARD ACCEPT [0:0]"
+	echo ":OUTPUT ACCEPT [0:0]"
+
+	if [ $UCHAIN_NUM -gt 0 ]; then
+		for i in $(seq 0 $UCHAIN_NUM); do
+			printf -- ":UC-%x - [0:0]\n" $i
+		done
+	fi
+
+	for proto in tcp udp sctp; do
+		for i in $(seq 0 $ENTRY_NUM); do
+			t=$(get_target)
+			printf -- "-A INPUT -i lo -p $proto --dport %d -j %s\n" $((61000-i)) $t
+			t=$(get_target)
+			printf -- "-A FORWARD -i lo -o lo -p $proto --dport %d -j %s\n" $((61000-i)) $t
+			t=$(get_target)
+			printf -- "-A OUTPUT -o lo -p $proto --dport %d -j %s\n" $((61000-i)) $t
+			[ $UCHAIN_NUM -gt 0 ] && printf -- "-A UC-%x -j ACCEPT\n" $((RANDOM%UCHAIN_NUM))
+		done
+	done
+	echo COMMIT
+}
+
+tmpfile=$(mktemp) || exit 1
+dumpfile=$(mktemp) || exit 1
+
+(make_dummy_rules; make_dummy_rules security) > $dumpfile
+$XT_MULTI iptables-restore -w < $dumpfile
+LINES1=$(wc -l < $dumpfile)
+$XT_MULTI iptables-save | grep -v '^#' > $dumpfile
+LINES2=$(wc -l < $dumpfile)
+
+if [ $LINES1 -ne $LINES2 ]; then
+	echo "Original dump has $LINES1, not $LINES2" 1>&2
+	exit 111
+fi
+
+case "$XT_MULTI" in
+*xtables-nft-multi)
+	attempts=$((RANDOM%10))
+	attempts=$((attempts+1))
+	;;
+*)
+	attempts=1
+	;;
+esac
+
+while [ $attempts -gt 0 ]; do
+	attempts=$((attempts-1))
+
+	clean
+
+	for i in $(seq 1 10); do
+		$XT_MULTI iptables-restore -w 15 < $dumpfile &
+	done
+
+	for i in $(seq 1 10); do
+		# causes exit in case ipt-restore failed (runs with set -e)
+		wait %$i
+	done
+
+	$XT_MULTI iptables-save | grep -v '^#' > $tmpfile
+
+	clean
+	cmp $tmpfile $dumpfile
+done
+
+exit 0
diff --git a/iptables/tests/shell/testcases/ipt-restore/0005-ipt-6_0 b/iptables/tests/shell/testcases/ipt-restore/0005-ipt-6_0
new file mode 100755
index 0000000..dd06977
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0005-ipt-6_0
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Make sure iptables-restore simply ignores
+# rules starting with -6
+
+set -e
+
+# show rules, drop uninteresting policy settings
+ipt_show() {
+	$XT_MULTI iptables -S | grep -v '^-P'
+}
+
+# issue reproducer for iptables-restore
+
+$XT_MULTI iptables-restore <<EOF
+*filter
+-A FORWARD -m comment --comment any -j ACCEPT
+-4 -A FORWARD -m comment --comment ipv4 -j ACCEPT
+-6 -A FORWARD -m comment --comment ipv6 -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment any -j ACCEPT
+-A FORWARD -m comment --comment ipv4 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
diff --git a/iptables/tests/shell/testcases/ipt-restore/0006-ip6t-4_0 b/iptables/tests/shell/testcases/ipt-restore/0006-ip6t-4_0
new file mode 100755
index 0000000..a37253a
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0006-ip6t-4_0
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Make sure ip6tables-restore simply ignores
+# rules starting with -4
+
+set -e
+
+# show rules, drop uninteresting policy settings
+ipt_show() {
+	$XT_MULTI ip6tables -S | grep -v '^-P'
+}
+
+# issue reproducer for ip6tables-restore
+
+$XT_MULTI ip6tables-restore <<EOF
+*filter
+-A FORWARD -m comment --comment any -j ACCEPT
+-4 -A FORWARD -m comment --comment ipv4 -j ACCEPT
+-6 -A FORWARD -m comment --comment ipv6 -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment any -j ACCEPT
+-A FORWARD -m comment --comment ipv6 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
diff --git a/iptables/tests/shell/testcases/ipt-restore/0007-flush-noflush_0 b/iptables/tests/shell/testcases/ipt-restore/0007-flush-noflush_0
new file mode 100755
index 0000000..e705b28
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0007-flush-noflush_0
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# Make sure iptables-restore without --noflush does not flush tables other than
+# those contained in the dump it's reading from
+
+set -e
+
+$XT_MULTI iptables-restore <<EOF
+*nat
+-A POSTROUTING -j ACCEPT
+COMMIT
+EOF
+
+EXPECT="*nat
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+-A POSTROUTING -j ACCEPT
+COMMIT"
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables-save | grep -v '^#')
+
+$XT_MULTI iptables-restore <<EOF
+*filter
+-A FORWARD -j ACCEPT
+COMMIT
+EOF
+
+EXPECT="*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+-A FORWARD -j ACCEPT
+COMMIT
+*nat
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+-A POSTROUTING -j ACCEPT
+COMMIT"
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/ipt-restore/0008-restore-counters_0 b/iptables/tests/shell/testcases/ipt-restore/0008-restore-counters_0
new file mode 100755
index 0000000..5ac7068
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0008-restore-counters_0
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+set -e
+
+DUMP="*filter
+:foo - [23:42]
+[13:37] -A foo -j ACCEPT
+COMMIT
+"
+
+EXPECT=":foo - [0:0]
+[0:0] -A foo -j ACCEPT"
+
+$XT_MULTI iptables-restore <<< "$DUMP"
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables-save --counters | grep foo)
+
+# iptables-*-restore ignores custom chain counters :(
+EXPECT=":foo - [0:0]
+[13:37] -A foo -j ACCEPT"
+
+$XT_MULTI iptables-restore --counters <<< "$DUMP"
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables-save --counters | grep foo)
diff --git a/iptables/tests/shell/testcases/ipt-restore/0009-table-name-comment_0 b/iptables/tests/shell/testcases/ipt-restore/0009-table-name-comment_0
new file mode 100755
index 0000000..e961407
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0009-table-name-comment_0
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# when restoring a ruleset, *tables-restore prefixes each rule with
+# '-t <tablename>' so standard rule parsing routines may be used. This means
+# that it has to detect and reject rules which already contain a table option.
+
+families="ip ip6"
+[[ $(basename $XT_MULTI) == xtables-nft-multi ]] && families+=" eb"
+
+for fam in $families; do
+	$XT_MULTI ${fam}tables-restore <<EOF
+*filter
+-t nat -A FORWARD -j ACCEPT
+COMMIT
+EOF
+	[[ $? != 0 ]] || {
+		echo "${fam}tables-restore did not fail when it should have"
+		exit 1
+	}
+
+	$XT_MULTI ${fam}tables-restore <<EOF
+*filter
+-A FORWARD -j ACCEPT
+COMMIT
+EOF
+	[[ $? == 0 ]] || {
+		echo "${fam}tables-restore failed when it should not have"
+		exit 1
+	}
+done
diff --git a/iptables/tests/shell/testcases/ipt-restore/0010-noflush-new-chain_0 b/iptables/tests/shell/testcases/ipt-restore/0010-noflush-new-chain_0
new file mode 100755
index 0000000..2817376
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0010-noflush-new-chain_0
@@ -0,0 +1,11 @@
+#!/bin/sh -e
+
+# assert input feed from buffer doesn't trip over
+# added nul-chars from parsing chain line.
+
+$XT_MULTI iptables-restore --noflush <<EOF
+*filter
+:foobar - [0:0]
+-A foobar -j ACCEPT
+COMMIT
+EOF
diff --git a/iptables/tests/shell/testcases/ipt-restore/0011-noflush-empty-line_0 b/iptables/tests/shell/testcases/ipt-restore/0011-noflush-empty-line_0
new file mode 100755
index 0000000..bea1a69
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0011-noflush-empty-line_0
@@ -0,0 +1,16 @@
+#!/bin/bash -e
+
+# make sure empty lines won't break --noflush
+
+cat <<EOF | $XT_MULTI iptables-restore --noflush
+# just a comment followed by innocent empty line
+
+*filter
+-A FORWARD -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='Chain FORWARD (policy ACCEPT)
+target     prot opt source               destination         
+ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           '
+diff -u <(echo "$EXPECT") <($XT_MULTI iptables -n -L FORWARD)
diff --git a/iptables/tests/shell/testcases/ipt-restore/0012-dash-F_0 b/iptables/tests/shell/testcases/ipt-restore/0012-dash-F_0
new file mode 100755
index 0000000..fd82afa
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0012-dash-F_0
@@ -0,0 +1,12 @@
+#!/bin/bash -e
+
+# make sure -F lines don't cause segfaults
+
+RULESET='*nat
+-F PREROUTING
+-A PREROUTING -j ACCEPT
+-F PREROUTING
+COMMIT'
+
+echo -e "$RULESET" | $XT_MULTI iptables-restore
+echo -e "$RULESET" | $XT_MULTI iptables-restore -n
diff --git a/iptables/tests/shell/testcases/ipt-restore/0013-test-mode_0 b/iptables/tests/shell/testcases/ipt-restore/0013-test-mode_0
new file mode 100755
index 0000000..65c3b9a
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0013-test-mode_0
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -e
+
+# segfault with --test reported in nfbz#1391
+
+printf '%s\nCOMMIT\n' '*nat' '*raw' '*filter' | $XT_MULTI iptables-restore --test
diff --git a/iptables/tests/shell/testcases/ipt-restore/0014-verbose-restore_0 b/iptables/tests/shell/testcases/ipt-restore/0014-verbose-restore_0
new file mode 100755
index 0000000..fc8559c
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0014-verbose-restore_0
@@ -0,0 +1,76 @@
+#!/bin/bash
+
+set -e
+
+DUMP="*filter
+:foo - [0:0]
+:bar - [0:0]
+-A foo -j ACCEPT
+COMMIT
+*nat
+:natfoo - [0:0]
+:natbar - [0:0]
+-A natfoo -j ACCEPT
+COMMIT
+*raw
+:rawfoo - [0:0]
+COMMIT
+*mangle
+:manglefoo - [0:0]
+COMMIT
+*security
+:secfoo - [0:0]
+COMMIT
+"
+
+$XT_MULTI iptables-restore <<< "$DUMP"
+$XT_MULTI ip6tables-restore <<< "$DUMP"
+
+EXPECT="Flushing chain \`INPUT'
+Flushing chain \`FORWARD'
+Flushing chain \`OUTPUT'
+Flushing chain \`bar'
+Flushing chain \`foo'
+Deleting chain \`bar'
+Deleting chain \`foo'
+Flushing chain \`PREROUTING'
+Flushing chain \`INPUT'
+Flushing chain \`OUTPUT'
+Flushing chain \`POSTROUTING'
+Flushing chain \`natbar'
+Flushing chain \`natfoo'
+Deleting chain \`natbar'
+Deleting chain \`natfoo'
+Flushing chain \`PREROUTING'
+Flushing chain \`OUTPUT'
+Flushing chain \`rawfoo'
+Deleting chain \`rawfoo'
+Flushing chain \`PREROUTING'
+Flushing chain \`INPUT'
+Flushing chain \`FORWARD'
+Flushing chain \`OUTPUT'
+Flushing chain \`POSTROUTING'
+Flushing chain \`manglefoo'
+Deleting chain \`manglefoo'
+Flushing chain \`INPUT'
+Flushing chain \`FORWARD'
+Flushing chain \`OUTPUT'
+Flushing chain \`secfoo'
+Deleting chain \`secfoo'"
+
+for ipt in iptables-restore ip6tables-restore; do
+	diff -u -Z <(echo "$EXPECT") <($XT_MULTI $ipt -v <<< "$DUMP")
+done
+
+DUMP="*filter
+:baz - [0:0]
+-F foo
+-X bar
+-A foo -j ACCEPT
+COMMIT
+"
+
+EXPECT=""
+for ipt in iptables-restore ip6tables-restore; do
+	diff -u -Z <(echo -ne "$EXPECT") <($XT_MULTI $ipt -v --noflush <<< "$DUMP")
+done
diff --git a/iptables/tests/shell/testcases/ipt-restore/0016-concurrent-restores_0 b/iptables/tests/shell/testcases/ipt-restore/0016-concurrent-restores_0
new file mode 100755
index 0000000..aa746ab
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0016-concurrent-restores_0
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+# test for iptables-restore --noflush skipping an explicitly requested chain
+# flush because the chain did not exist when cache was fetched. In order to
+# expect for that chain to appear when refreshing the transaction (due to a
+# concurrent ruleset change), the chain flush job has to be present in batch
+# job list (although disabled at first).
+# The input line requesting chain flush is ':FOO - [0:0]'. RS1 and RS2 contents
+# are crafted to cause EBUSY when deleting the BAR* chains if FOO is not
+# flushed in the same transaction.
+
+set -e
+
+RS="*filter
+:INPUT ACCEPT [12024:3123388]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [12840:2144421]
+:FOO - [0:0]
+:BAR0 - [0:0]
+:BAR1 - [0:0]
+:BAR2 - [0:0]
+:BAR3 - [0:0]
+:BAR4 - [0:0]
+:BAR5 - [0:0]
+:BAR6 - [0:0]
+:BAR7 - [0:0]
+:BAR8 - [0:0]
+:BAR9 - [0:0]
+"
+
+RS1="$RS
+-X BAR3
+-X BAR6
+-X BAR9
+-A FOO -s 9.9.0.1/32 -j BAR1
+-A FOO -s 9.9.0.2/32 -j BAR2
+-A FOO -s 9.9.0.4/32 -j BAR4
+-A FOO -s 9.9.0.5/32 -j BAR5
+-A FOO -s 9.9.0.7/32 -j BAR7
+-A FOO -s 9.9.0.8/32 -j BAR8
+COMMIT
+"
+
+RS2="$RS
+-X BAR2
+-X BAR5
+-X BAR7
+-A FOO -s 9.9.0.1/32 -j BAR1
+-A FOO -s 9.9.0.3/32 -j BAR3
+-A FOO -s 9.9.0.4/32 -j BAR4
+-A FOO -s 9.9.0.6/32 -j BAR6
+-A FOO -s 9.9.0.8/32 -j BAR8
+-A FOO -s 9.9.0.9/32 -j BAR9
+COMMIT
+"
+
+NORS="*filter
+COMMIT
+"
+
+for n in $(seq 1 10); do
+	$XT_MULTI iptables-restore <<< "$NORS"
+	$XT_MULTI iptables-restore --noflush -w <<< "$RS1" &
+	$XT_MULTI iptables-restore --noflush -w <<< "$RS2" &
+	wait -n
+	wait -n
+done
diff --git a/iptables/tests/shell/testcases/ipt-restore/0017-pointless-compat-checks_0 b/iptables/tests/shell/testcases/ipt-restore/0017-pointless-compat-checks_0
new file mode 100755
index 0000000..cf73de3
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0017-pointless-compat-checks_0
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# A bug in extension registration would leave unsupported older extension
+# revisions in pending list and get compatibility checked again for each rule
+# using them. With SELinux enabled, the resulting socket() call per rule leads
+# to significant slowdown (~50% performance in worst cases).
+
+set -e
+
+strace --version >/dev/null || { echo "skip for missing strace"; exit 0; }
+
+RULESET="$(
+	echo "*filter"
+	for ((i = 0; i < 100; i++)); do
+		echo "-A FORWARD -m conntrack --ctstate NEW"
+	done
+	echo "COMMIT"
+)"
+
+cmd="$XT_MULTI iptables-restore"
+socketcount=$(strace -esocket $cmd <<< "$RULESET" 2>&1 | wc -l)
+
+# unpatched iptables-restore would open 111 sockets,
+# patched only 12 but keep a certain margin for future changes
+[[ $socketcount -lt 20 ]]
diff --git a/iptables/tests/shell/testcases/ipt-restore/dumps/ip6tables.dump b/iptables/tests/shell/testcases/ipt-restore/dumps/ip6tables.dump
new file mode 100644
index 0000000..4ac4f88
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/dumps/ip6tables.dump
@@ -0,0 +1,30 @@
+*nat
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:OUTPUT ACCEPT [8:656]
+:POSTROUTING ACCEPT [8:656]
+COMMIT
+
+*mangle
+:PREROUTING ACCEPT [794:190738]
+:INPUT ACCEPT [794:190738]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [991:170303]
+:POSTROUTING ACCEPT [991:170303]
+COMMIT
+
+*raw
+:PREROUTING ACCEPT [794:190738]
+:OUTPUT ACCEPT [991:170303]
+COMMIT
+
+*filter
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT ACCEPT [991:170303]
+-A INPUT -i lo -j ACCEPT
+-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -p ipv6-icmp -j ACCEPT
+-A OUTPUT -p tcp -m tcp --dport 137 -j REJECT --reject-with icmp6-port-unreachable
+-A OUTPUT -p udp -m udp --dport 137 -j REJECT --reject-with icmp6-port-unreachable
+COMMIT
diff --git a/iptables/tests/shell/testcases/ipt-restore/dumps/iptables.dump b/iptables/tests/shell/testcases/ipt-restore/dumps/iptables.dump
new file mode 100644
index 0000000..6e4e42d
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/dumps/iptables.dump
@@ -0,0 +1,30 @@
+*nat
+:PREROUTING ACCEPT [1:89]
+:INPUT ACCEPT [0:0]
+:OUTPUT ACCEPT [351:24945]
+:POSTROUTING ACCEPT [351:24945]
+COMMIT
+
+*mangle
+:PREROUTING ACCEPT [3270:1513114]
+:INPUT ACCEPT [3270:1513114]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [3528:1087907]
+:POSTROUTING ACCEPT [3546:1090751]
+COMMIT
+
+*raw
+:PREROUTING ACCEPT [3270:1513114]
+:OUTPUT ACCEPT [3528:1087907]
+COMMIT
+
+*filter
+:INPUT DROP [37:4057]
+:FORWARD DROP [0:0]
+:OUTPUT ACCEPT [3528:1087907]
+-A INPUT -i lo -j ACCEPT
+-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -p icmp -j ACCEPT
+-A OUTPUT -p tcp -m tcp --dport 137 -j REJECT --reject-with icmp-port-unreachable
+-A OUTPUT -p udp -m udp --dport 137 -j REJECT --reject-with icmp-port-unreachable
+COMMIT
diff --git a/iptables/tests/shell/testcases/ipt-save/0001load-dumps_0 b/iptables/tests/shell/testcases/ipt-save/0001load-dumps_0
new file mode 100755
index 0000000..4e0be51
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/0001load-dumps_0
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+RET=0
+tmpfile=""
+set -x
+
+
+clean_tmpfile()
+{
+        if [ ! -z "$tmpfile" ];then
+                rm -f "$tmpfile"
+        fi
+}
+
+trap clean_tmpfile EXIT
+
+do_diff()
+{
+	A="$1"
+	B="$2"
+
+	AT=$(mktemp)
+	grep -v "^#" "$A" > "$AT"
+
+	diff -u "$AT" "$B"
+
+	x=$?
+	rm -f "$AT"
+	echo "Return $x for $XT_MULTI $A"
+
+	return $x
+}
+
+tmpfile=$(mktemp) || exit 1
+do_simple()
+{
+	iptables="$1"
+	dumpfile="$2"
+
+	$XT_MULTI ${iptables}-restore < "$dumpfile"
+	$XT_MULTI ${iptables}-save | grep -v "^#" > "$tmpfile"
+	do_diff $dumpfile "$tmpfile"
+	if [ $? -ne 0 ]; then
+		# cp "$tmpfile" "$dumpfile.got"
+		RET=1
+	fi
+}
+
+do_simple "iptables" $(dirname "$0")/dumps/ipt-save-filter.txt
+do_simple "iptables" $(dirname "$0")/dumps/policy-drop.txt
+do_simple "iptables" $(dirname "$0")/dumps/wireless.txt
+
+exit $RET
diff --git a/iptables/tests/shell/testcases/ipt-save/0002load-fedora27-firewalld_0 b/iptables/tests/shell/testcases/ipt-save/0002load-fedora27-firewalld_0
new file mode 100755
index 0000000..2ab08b7
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/0002load-fedora27-firewalld_0
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+RET=0
+tmpfile=""
+
+clean_tmpfile()
+{
+        if [ ! -z "$tmpfile" ];then
+                rm -f "$tmpfile"
+        fi
+}
+
+trap clean_tmpfile EXIT
+
+do_diff()
+{
+	A="$1"
+	B="$2"
+
+	AT=$(mktemp)
+	grep -v "^#" "$A" > "$AT"
+
+	diff -u "$AT" "$B"
+	x=$?
+	rm -f "$AT"
+
+	return $x
+}
+
+tmpfile=$(mktemp) || exit 1
+do_simple()
+{
+	iptables="$1"
+	dumpfile="$2"
+	opt="$3"
+
+	$XT_MULTI ${iptables}-restore $opt < "$dumpfile"
+	if [ $? -ne 0 ]; then
+		echo "$XT_MULTI ${iptables}-restore $opt $dumpfile failed" 1>&2
+		exit 1
+	fi
+
+	:> "$tmpfile"
+
+	for table in mangle raw filter; do
+		$XT_MULTI ${iptables}-save -t $table $opt | grep -v "^#" >> "$tmpfile"
+	done
+
+	do_diff $dumpfile "$tmpfile"
+
+	if [ $? -ne 0 ]; then
+		RET=1
+	fi
+}
+# fedora27-iptables dump contains chain counters to test counter restore/save
+do_simple "iptables" $(dirname "$0")/dumps/fedora27-iptables "-c"
+do_simple "ip6tables" $(dirname "$0")/dumps/fedora27-ip6tables
+
+exit $RET
diff --git a/iptables/tests/shell/testcases/ipt-save/0003save-restore_0 b/iptables/tests/shell/testcases/ipt-save/0003save-restore_0
new file mode 100644
index 0000000..6b41ede
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/0003save-restore_0
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+tmpfile=""
+tmpfile1=""
+set -x
+
+clean_tmpfile()
+{
+	if [ ! -z "$tmpfile" ];then
+		rm -f "$tmpfile"
+	fi
+	if [ ! -z "$tmpfile1" ];then
+                rm -f "$tmpfile1"
+	fi
+}
+
+trap clean_tmpfile EXIT
+
+tmpfile=$(mktemp) || exit 1
+tmpfile1=$(mktemp) || exit 1
+
+do_diff()
+{
+	diff -u "$1" "$2"
+	if [ $? -ne 0 ]; then
+		echo "iptables configuration is not restored" 1>&2
+		exit 1
+	else
+		exit 0
+	fi
+}
+
+$XT_MULTI iptables -N FOO || exit 1
+$XT_MULTI iptables -I INPUT || exit 1
+$XT_MULTI iptables -I FOO || exit 1
+$XT_MULTI iptables -I FOO || exit 1
+
+$XT_MULTI iptables-save | grep -v "^#" > "$tmpfile" || exit 1
+$XT_MULTI iptables-restore < "$tmpfile" || exit 1
+
+$XT_MULTI iptables -N BAR || exit 1
+$XT_MULTI iptables -A BAR || exit 1
+
+$XT_MULTI iptables-restore  < "$tmpfile" || exit 1
+$XT_MULTI iptables-save | grep -v "^#" > "$tmpfile1" || exit 1
+
+do_diff $tmpfile1 "$tmpfile"
diff --git a/iptables/tests/shell/testcases/ipt-save/0005iptables_0 b/iptables/tests/shell/testcases/ipt-save/0005iptables_0
new file mode 100755
index 0000000..d5eb76a
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/0005iptables_0
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -e
+
+tmpfile1=$(mktemp)
+tmpfile2=$(mktemp)
+
+clean_tmpfile()
+{
+	rm -f "$tmpfile1" "$tmpfile2"
+}
+
+trap clean_tmpfile EXIT
+
+
+cat > $tmpfile1<<EOF
+-P INPUT ACCEPT
+-P FORWARD ACCEPT
+-P OUTPUT ACCEPT
+-N FOO
+-A FOO -j DROP
+EOF
+
+$XT_MULTI iptables -N FOO
+$XT_MULTI iptables -A FOO -j DROP
+$XT_MULTI iptables -S > $tmpfile2
+
+diff -u $tmpfile1 $tmpfile2
+
+rm -f $tmpfile1 $tmpfile2
diff --git a/iptables/tests/shell/testcases/ipt-save/0006iptables-xml_0 b/iptables/tests/shell/testcases/ipt-save/0006iptables-xml_0
new file mode 100755
index 0000000..50c0cae
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/0006iptables-xml_0
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+case "$(basename $XT_MULTI)" in
+	xtables-legacy-multi)
+		;;
+	*)
+		echo "skip $XT_MULTI"
+		exit 0
+		;;
+esac
+
+dump=$(dirname $0)/dumps/fedora27-iptables
+diff -u -Z <(cat ${dump}.xml) <($XT_MULTI iptables-xml <$dump)
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-ip6tables b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-ip6tables
new file mode 100644
index 0000000..6c426a7
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-ip6tables
@@ -0,0 +1,125 @@
+# Generated by ip6tables-save v1.6.1 on Sat Feb 17 10:51:39 2018
+*mangle
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+:FORWARD_direct - [0:0]
+:INPUT_direct - [0:0]
+:OUTPUT_direct - [0:0]
+:POSTROUTING_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_FedoraWorkstation - [0:0]
+:PRE_FedoraWorkstation_allow - [0:0]
+:PRE_FedoraWorkstation_deny - [0:0]
+:PRE_FedoraWorkstation_log - [0:0]
+-A PREROUTING -j PREROUTING_direct
+-A PREROUTING -j PREROUTING_ZONES_SOURCE
+-A PREROUTING -j PREROUTING_ZONES
+-A INPUT -j INPUT_direct
+-A FORWARD -j FORWARD_direct
+-A OUTPUT -j OUTPUT_direct
+-A POSTROUTING -j POSTROUTING_direct
+-A PREROUTING_ZONES -i wlp58s0 -g PRE_FedoraWorkstation
+-A PREROUTING_ZONES -g PRE_FedoraWorkstation
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_log
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_deny
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_allow
+COMMIT
+# Completed on Sat Feb 17 10:51:39 2018
+# Generated by ip6tables-save v1.6.1 on Sat Feb 17 10:51:39 2018
+*raw
+:PREROUTING ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:OUTPUT_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_FedoraWorkstation - [0:0]
+:PRE_FedoraWorkstation_allow - [0:0]
+:PRE_FedoraWorkstation_deny - [0:0]
+:PRE_FedoraWorkstation_log - [0:0]
+-A PREROUTING -p ipv6-icmp -m icmp6 --icmpv6-type 134 -j ACCEPT
+-A PREROUTING -j PREROUTING_direct
+-A PREROUTING -j PREROUTING_ZONES_SOURCE
+-A PREROUTING -j PREROUTING_ZONES
+-A OUTPUT -j OUTPUT_direct
+-A PREROUTING_ZONES -i wlp58s0 -g PRE_FedoraWorkstation
+-A PREROUTING_ZONES -g PRE_FedoraWorkstation
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_log
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_deny
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_allow
+COMMIT
+# Completed on Sat Feb 17 10:51:39 2018
+# Generated by ip6tables-save v1.6.1 on Sat Feb 17 10:51:39 2018
+*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:FORWARD_IN_ZONES - [0:0]
+:FORWARD_IN_ZONES_SOURCE - [0:0]
+:FORWARD_OUT_ZONES - [0:0]
+:FORWARD_OUT_ZONES_SOURCE - [0:0]
+:FORWARD_direct - [0:0]
+:FWDI_FedoraWorkstation - [0:0]
+:FWDI_FedoraWorkstation_allow - [0:0]
+:FWDI_FedoraWorkstation_deny - [0:0]
+:FWDI_FedoraWorkstation_log - [0:0]
+:FWDO_FedoraWorkstation - [0:0]
+:FWDO_FedoraWorkstation_allow - [0:0]
+:FWDO_FedoraWorkstation_deny - [0:0]
+:FWDO_FedoraWorkstation_log - [0:0]
+:INPUT_ZONES - [0:0]
+:INPUT_ZONES_SOURCE - [0:0]
+:INPUT_direct - [0:0]
+:IN_FedoraWorkstation - [0:0]
+:IN_FedoraWorkstation_allow - [0:0]
+:IN_FedoraWorkstation_deny - [0:0]
+:IN_FedoraWorkstation_log - [0:0]
+:OUTPUT_direct - [0:0]
+-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -i lo -j ACCEPT
+-A INPUT -j INPUT_direct
+-A INPUT -j INPUT_ZONES_SOURCE
+-A INPUT -j INPUT_ZONES
+-A INPUT -m conntrack --ctstate INVALID -j DROP
+-A INPUT -j REJECT --reject-with icmp6-adm-prohibited
+-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A FORWARD -i lo -j ACCEPT
+-A FORWARD -j FORWARD_direct
+-A FORWARD -j FORWARD_IN_ZONES_SOURCE
+-A FORWARD -j FORWARD_IN_ZONES
+-A FORWARD -j FORWARD_OUT_ZONES_SOURCE
+-A FORWARD -j FORWARD_OUT_ZONES
+-A FORWARD -m conntrack --ctstate INVALID -j DROP
+-A FORWARD -j REJECT --reject-with icmp6-adm-prohibited
+-A OUTPUT -j OUTPUT_direct
+-A FORWARD_IN_ZONES -i wlp58s0 -g FWDI_FedoraWorkstation
+-A FORWARD_IN_ZONES -g FWDI_FedoraWorkstation
+-A FORWARD_OUT_ZONES -o wlp58s0 -g FWDO_FedoraWorkstation
+-A FORWARD_OUT_ZONES -g FWDO_FedoraWorkstation
+-A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_log
+-A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_deny
+-A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_allow
+-A FWDI_FedoraWorkstation -p ipv6-icmp -j ACCEPT
+-A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_log
+-A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_deny
+-A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_allow
+-A INPUT_ZONES -i wlp58s0 -g IN_FedoraWorkstation
+-A INPUT_ZONES -g IN_FedoraWorkstation
+-A IN_FedoraWorkstation -j IN_FedoraWorkstation_log
+-A IN_FedoraWorkstation -j IN_FedoraWorkstation_deny
+-A IN_FedoraWorkstation -j IN_FedoraWorkstation_allow
+-A IN_FedoraWorkstation -p ipv6-icmp -j ACCEPT
+-A IN_FedoraWorkstation_allow -p udp -m udp --dport 137 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -p udp -m udp --dport 138 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -d ff02::fb/128 -p udp -m udp --dport 5353 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -d fe80::/64 -p udp -m udp --dport 546 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -p udp -m udp --dport 1025:65535 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -p tcp -m tcp --dport 1025:65535 -m conntrack --ctstate NEW -j ACCEPT
+COMMIT
+# Completed on Sat Feb 17 10:51:39 2018
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables
new file mode 100644
index 0000000..89a05fc
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables
@@ -0,0 +1,136 @@
+# Completed on Sat Feb 17 10:50:33 2018
+# Generated by iptables-save v1.6.1 on Sat Feb 17 10:50:33 2018
+*mangle
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+:FORWARD_direct - [0:0]
+:INPUT_direct - [0:0]
+:OUTPUT_direct - [0:0]
+:POSTROUTING_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_FedoraWorkstation - [0:0]
+:PRE_FedoraWorkstation_allow - [0:0]
+:PRE_FedoraWorkstation_deny - [0:0]
+:PRE_FedoraWorkstation_log - [0:0]
+[1:2] -A PREROUTING -j PREROUTING_direct
+[3:4] -A PREROUTING -j PREROUTING_ZONES_SOURCE
+[0:0] -A PREROUTING -j PREROUTING_ZONES
+[0:0] -A INPUT -j INPUT_direct
+[0:0] -A FORWARD -j FORWARD_direct
+[0:0] -A OUTPUT -j OUTPUT_direct
+[0:0] -A POSTROUTING -o virbr0 -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill
+[0:0] -A POSTROUTING -j POSTROUTING_direct
+[0:0] -A PREROUTING_ZONES -i wlp58s0 -g PRE_FedoraWorkstation
+[0:0] -A PREROUTING_ZONES -g PRE_FedoraWorkstation
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_log
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_deny
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_allow
+COMMIT
+# Completed on Sat Feb 17 10:50:33 2018
+# Generated by iptables-save v1.6.1 on Sat Feb 17 10:50:33 2018
+*raw
+:PREROUTING ACCEPT [1681:2620433]
+:OUTPUT ACCEPT [1619:171281]
+:OUTPUT_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_FedoraWorkstation - [0:0]
+:PRE_FedoraWorkstation_allow - [0:0]
+:PRE_FedoraWorkstation_deny - [0:0]
+:PRE_FedoraWorkstation_log - [0:0]
+[0:0] -A PREROUTING -j PREROUTING_direct
+[0:0] -A PREROUTING -j PREROUTING_ZONES_SOURCE
+[0:0] -A PREROUTING -j PREROUTING_ZONES
+[0:0] -A OUTPUT -j OUTPUT_direct
+[0:0] -A PREROUTING_ZONES -i wlp58s0 -g PRE_FedoraWorkstation
+[0:0] -A PREROUTING_ZONES -g PRE_FedoraWorkstation
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_log
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_deny
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_allow
+[0:0] -A PRE_FedoraWorkstation_allow -p udp -m udp --dport 137 -j CT --helper netbios-ns
+COMMIT
+# Completed on Sat Feb 17 10:50:33 2018
+# Generated by iptables-save v1.6.1 on Sat Feb 17 10:50:33 2018
+*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [1619:171281]
+:FORWARD_IN_ZONES - [0:0]
+:FORWARD_IN_ZONES_SOURCE - [0:0]
+:FORWARD_OUT_ZONES - [0:0]
+:FORWARD_OUT_ZONES_SOURCE - [0:0]
+:FORWARD_direct - [0:0]
+:FWDI_FedoraWorkstation - [0:0]
+:FWDI_FedoraWorkstation_allow - [0:0]
+:FWDI_FedoraWorkstation_deny - [0:0]
+:FWDI_FedoraWorkstation_log - [0:0]
+:FWDO_FedoraWorkstation - [0:0]
+:FWDO_FedoraWorkstation_allow - [0:0]
+:FWDO_FedoraWorkstation_deny - [0:0]
+:FWDO_FedoraWorkstation_log - [0:0]
+:INPUT_ZONES - [0:0]
+:INPUT_ZONES_SOURCE - [0:0]
+:INPUT_direct - [0:0]
+:IN_FedoraWorkstation - [0:0]
+:IN_FedoraWorkstation_allow - [0:0]
+:IN_FedoraWorkstation_deny - [0:0]
+:IN_FedoraWorkstation_log - [0:0]
+:OUTPUT_direct - [0:0]
+[5:6] -A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT
+[0:123456789] -A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT
+[0:0] -A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT
+[0:0] -A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT
+[0:0] -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+[0:0] -A INPUT -i lo -j ACCEPT
+[0:0] -A INPUT -j INPUT_direct
+[0:0] -A INPUT -j INPUT_ZONES_SOURCE
+[0:0] -A INPUT -j INPUT_ZONES
+[0:0] -A INPUT -m conntrack --ctstate INVALID -j DROP
+[0:0] -A INPUT -j REJECT --reject-with icmp-host-prohibited
+[0:0] -A FORWARD -d 192.168.122.0/24 -o virbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+[0:0] -A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT
+[0:0] -A FORWARD -i virbr0 -o virbr0 -j ACCEPT
+[0:0] -A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable
+[0:0] -A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable
+[0:0] -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+[0:0] -A FORWARD -i lo -j ACCEPT
+[0:0] -A FORWARD -j FORWARD_direct
+[0:0] -A FORWARD -j FORWARD_IN_ZONES_SOURCE
+[0:0] -A FORWARD -j FORWARD_IN_ZONES
+[0:0] -A FORWARD -j FORWARD_OUT_ZONES_SOURCE
+[0:0] -A FORWARD -j FORWARD_OUT_ZONES
+[0:0] -A FORWARD -m conntrack --ctstate INVALID -j DROP
+[0:0] -A FORWARD -j REJECT --reject-with icmp-host-prohibited
+[0:0] -A OUTPUT -o virbr0 -p udp -m udp --dport 68 -j ACCEPT
+[0:0] -A OUTPUT -j OUTPUT_direct
+[0:0] -A FORWARD_IN_ZONES -i wlp58s0 -g FWDI_FedoraWorkstation
+[0:0] -A FORWARD_IN_ZONES -g FWDI_FedoraWorkstation
+[0:0] -A FORWARD_OUT_ZONES -o wlp58s0 -g FWDO_FedoraWorkstation
+[0:0] -A FORWARD_OUT_ZONES -g FWDO_FedoraWorkstation
+[0:0] -A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_log
+[0:0] -A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_deny
+[0:0] -A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_allow
+[0:0] -A FWDI_FedoraWorkstation -p icmp -j ACCEPT
+[0:0] -A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_log
+[0:0] -A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_deny
+[0:0] -A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_allow
+[0:0] -A INPUT_ZONES -i wlp58s0 -g IN_FedoraWorkstation
+[0:0] -A INPUT_ZONES -g IN_FedoraWorkstation
+[0:0] -A IN_FedoraWorkstation -j IN_FedoraWorkstation_log
+[0:0] -A IN_FedoraWorkstation -j IN_FedoraWorkstation_deny
+[0:0] -A IN_FedoraWorkstation -j IN_FedoraWorkstation_allow
+[0:0] -A IN_FedoraWorkstation -p icmp -j ACCEPT
+[0:0] -A IN_FedoraWorkstation_allow -p udp -m udp --dport 137 -m conntrack --ctstate NEW -j ACCEPT
+[0:0] -A IN_FedoraWorkstation_allow -p udp -m udp --dport 138 -m conntrack --ctstate NEW -j ACCEPT
+[0:0] -A IN_FedoraWorkstation_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+[0:0] -A IN_FedoraWorkstation_allow -d 224.0.0.251/32 -p udp -m udp --dport 5353 -m conntrack --ctstate NEW -j ACCEPT
+[0:0] -A IN_FedoraWorkstation_allow -p udp -m udp --dport 1025:65535 -m conntrack --ctstate NEW -j ACCEPT
+[7:8] -A IN_FedoraWorkstation_allow -p tcp -m tcp --dport 1025:65535 -m conntrack --ctstate NEW -j ACCEPT
+COMMIT
+# Completed on Sat Feb 17 10:50:33 2018
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables.xml b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables.xml
new file mode 100644
index 0000000..400be03
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables.xml
@@ -0,0 +1,925 @@
+<iptables-rules version="1.0">
+<!-- # Completed on Sat Feb 17 10:50:33 2018 -->
+<!-- # Generated by iptables*-save v1.6.1 on Sat Feb 17 10:50:33 2018 -->
+  <table name="mangle" >
+    <chain name="PREROUTING" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="1" byte-count="2" >
+       <actions>
+        <call >
+          <PREROUTING_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="3" byte-count="4" >
+       <actions>
+        <call >
+          <PREROUTING_ZONES_SOURCE />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PREROUTING_ZONES />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="INPUT" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <INPUT_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="OUTPUT" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <OUTPUT_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="POSTROUTING" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <o >virbr0</o>
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >68</dport>
+        </udp>
+       </conditions>
+       <actions>
+        <CHECKSUM >
+          <checksum-fill  />
+        </CHECKSUM>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <POSTROUTING_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="PREROUTING_ZONES" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >wlp58s0</i>
+        </match>
+       </conditions>
+       <actions>
+        <goto >
+          <PRE_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <goto >
+          <PRE_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="PRE_FedoraWorkstation" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_log />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_deny />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_allow />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD_direct" packet-count="0" byte-count="0" />
+    <chain name="INPUT_direct" packet-count="0" byte-count="0" />
+    <chain name="OUTPUT_direct" packet-count="0" byte-count="0" />
+    <chain name="POSTROUTING_direct" packet-count="0" byte-count="0" />
+    <chain name="PREROUTING_ZONES_SOURCE" packet-count="0" byte-count="0" />
+    <chain name="PREROUTING_direct" packet-count="0" byte-count="0" />
+    <chain name="PRE_FedoraWorkstation_allow" packet-count="0" byte-count="0" />
+    <chain name="PRE_FedoraWorkstation_deny" packet-count="0" byte-count="0" />
+    <chain name="PRE_FedoraWorkstation_log" packet-count="0" byte-count="0" />
+  </table>
+<!-- # Completed on Sat Feb 17 10:50:33 2018 -->
+<!-- # Generated by iptables*-save v1.6.1 on Sat Feb 17 10:50:33 2018 -->
+  <table name="raw" >
+    <chain name="PREROUTING" policy="ACCEPT" packet-count="1681" byte-count="2620433" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PREROUTING_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PREROUTING_ZONES_SOURCE />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PREROUTING_ZONES />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="OUTPUT" policy="ACCEPT" packet-count="1619" byte-count="171281" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <OUTPUT_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="PREROUTING_ZONES" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >wlp58s0</i>
+        </match>
+       </conditions>
+       <actions>
+        <goto >
+          <PRE_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <goto >
+          <PRE_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="PRE_FedoraWorkstation" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_log />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_deny />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_allow />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="PRE_FedoraWorkstation_allow" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >137</dport>
+        </udp>
+       </conditions>
+       <actions>
+        <CT >
+          <helper >netbios-ns</helper>
+        </CT>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="OUTPUT_direct" packet-count="0" byte-count="0" />
+    <chain name="PREROUTING_ZONES_SOURCE" packet-count="0" byte-count="0" />
+    <chain name="PREROUTING_direct" packet-count="0" byte-count="0" />
+    <chain name="PRE_FedoraWorkstation_deny" packet-count="0" byte-count="0" />
+    <chain name="PRE_FedoraWorkstation_log" packet-count="0" byte-count="0" />
+  </table>
+<!-- # Completed on Sat Feb 17 10:50:33 2018 -->
+<!-- # Generated by iptables*-save v1.6.1 on Sat Feb 17 10:50:33 2018 -->
+  <table name="filter" >
+    <chain name="INPUT" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="5" byte-count="6" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >53</dport>
+        </udp>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="123456789" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+          <p >tcp</p>
+        </match>
+        <tcp >
+          <dport >53</dport>
+        </tcp>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >67</dport>
+        </udp>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+          <p >tcp</p>
+        </match>
+        <tcp >
+          <dport >67</dport>
+        </tcp>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <conntrack >
+          <ctstate >RELATED,ESTABLISHED</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >lo</i>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <INPUT_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <INPUT_ZONES_SOURCE />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <INPUT_ZONES />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <conntrack >
+          <ctstate >INVALID</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <DROP  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <REJECT >
+          <reject-with >icmp-host-prohibited</reject-with>
+        </REJECT>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <d >192.168.122.0/24</d>
+          <o >virbr0</o>
+        </match>
+        <conntrack >
+          <ctstate >RELATED,ESTABLISHED</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <s >192.168.122.0/24</s>
+          <i >virbr0</i>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+          <o >virbr0</o>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <o >virbr0</o>
+        </match>
+       </conditions>
+       <actions>
+        <REJECT >
+          <reject-with >icmp-port-unreachable</reject-with>
+        </REJECT>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+        </match>
+       </conditions>
+       <actions>
+        <REJECT >
+          <reject-with >icmp-port-unreachable</reject-with>
+        </REJECT>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <conntrack >
+          <ctstate >RELATED,ESTABLISHED</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >lo</i>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_IN_ZONES_SOURCE />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_IN_ZONES />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_OUT_ZONES_SOURCE />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_OUT_ZONES />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <conntrack >
+          <ctstate >INVALID</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <DROP  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <REJECT >
+          <reject-with >icmp-host-prohibited</reject-with>
+        </REJECT>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="OUTPUT" policy="ACCEPT" packet-count="1619" byte-count="171281" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <o >virbr0</o>
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >68</dport>
+        </udp>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <OUTPUT_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD_IN_ZONES" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >wlp58s0</i>
+        </match>
+       </conditions>
+       <actions>
+        <goto >
+          <FWDI_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <goto >
+          <FWDI_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD_OUT_ZONES" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <o >wlp58s0</o>
+        </match>
+       </conditions>
+       <actions>
+        <goto >
+          <FWDO_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <goto >
+          <FWDO_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FWDI_FedoraWorkstation" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDI_FedoraWorkstation_log />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDI_FedoraWorkstation_deny />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDI_FedoraWorkstation_allow />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >icmp</p>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FWDO_FedoraWorkstation" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDO_FedoraWorkstation_log />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDO_FedoraWorkstation_deny />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDO_FedoraWorkstation_allow />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="INPUT_ZONES" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >wlp58s0</i>
+        </match>
+       </conditions>
+       <actions>
+        <goto >
+          <IN_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <goto >
+          <IN_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="IN_FedoraWorkstation" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <IN_FedoraWorkstation_log />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <IN_FedoraWorkstation_deny />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <IN_FedoraWorkstation_allow />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >icmp</p>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="IN_FedoraWorkstation_allow" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >137</dport>
+        </udp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >138</dport>
+        </udp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >tcp</p>
+        </match>
+        <tcp >
+          <dport >22</dport>
+        </tcp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <d >224.0.0.251/32</d>
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >5353</dport>
+        </udp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >1025:65535</dport>
+        </udp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="7" byte-count="8" >
+       <conditions>
+        <match >
+          <p >tcp</p>
+        </match>
+        <tcp >
+          <dport >1025:65535</dport>
+        </tcp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD_IN_ZONES_SOURCE" packet-count="0" byte-count="0" />
+    <chain name="FORWARD_OUT_ZONES_SOURCE" packet-count="0" byte-count="0" />
+    <chain name="FORWARD_direct" packet-count="0" byte-count="0" />
+    <chain name="FWDI_FedoraWorkstation_allow" packet-count="0" byte-count="0" />
+    <chain name="FWDI_FedoraWorkstation_deny" packet-count="0" byte-count="0" />
+    <chain name="FWDI_FedoraWorkstation_log" packet-count="0" byte-count="0" />
+    <chain name="FWDO_FedoraWorkstation_allow" packet-count="0" byte-count="0" />
+    <chain name="FWDO_FedoraWorkstation_deny" packet-count="0" byte-count="0" />
+    <chain name="FWDO_FedoraWorkstation_log" packet-count="0" byte-count="0" />
+    <chain name="INPUT_ZONES_SOURCE" packet-count="0" byte-count="0" />
+    <chain name="INPUT_direct" packet-count="0" byte-count="0" />
+    <chain name="IN_FedoraWorkstation_deny" packet-count="0" byte-count="0" />
+    <chain name="IN_FedoraWorkstation_log" packet-count="0" byte-count="0" />
+    <chain name="OUTPUT_direct" packet-count="0" byte-count="0" />
+  </table>
+<!-- # Completed on Sat Feb 17 10:50:33 2018 -->
+</iptables-rules>
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/ipt-save-filter.txt b/iptables/tests/shell/testcases/ipt-save/dumps/ipt-save-filter.txt
new file mode 100644
index 0000000..6e42de7
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/ipt-save-filter.txt
@@ -0,0 +1,69 @@
+# Generated by iptables-save v1.2.4 on Mon Mar 17 19:59:10 2003
+*filter
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT DROP [0:0]
+:WLAN - [0:0]
+:accept_log - [0:0]
+:block - [0:0]
+:in_icmp - [0:0]
+:in_trusted - [0:0]
+:reject_log - [0:0]
+:wlanout - [0:0]
+-A INPUT -i wlan0 -j WLAN
+-A INPUT -s 127.0.0.1/32 -d 127.0.0.1/32 -i lo -j ACCEPT
+-A INPUT -i ppp0 -p icmp -m limit --limit 1/sec -j in_icmp
+-A INPUT -i ppp0 -p tcp -m tcp --dport 22 -j in_trusted
+-A INPUT -j block
+-A FORWARD -d 192.168.100.77/32 -i ppp0 -p udp -m udp --dport 4166 -j ACCEPT
+-A FORWARD -d 192.168.100.77/32 -i ppp0 -p tcp -m tcp --dport 4180 -j ACCEPT
+-A FORWARD -d 192.168.100.77/32 -i ppp0 -p tcp -m tcp --dport 4162 -j ACCEPT
+-A FORWARD -d 192.168.100.77/32 -i ppp0 -p tcp -m tcp --dport 20376 -j ACCEPT
+-A FORWARD -d 192.168.100.2/32 -i ppp0 -p tcp -m tcp --dport 10209 -j ACCEPT
+-A FORWARD -d 192.168.100.2/32 -i ppp0 -p tcp -m tcp --dport 881 -j ACCEPT
+-A FORWARD ! -s 192.168.0.0/24 -i eth2 -p icmp -j DROP
+-A FORWARD ! -s 192.168.0.0/24 -i eth2 -p udp -j DROP
+-A FORWARD ! -s 192.168.0.0/24 -i eth2 -p tcp -j DROP
+-A FORWARD ! -s 192.168.100.0/24 -i eth1 -p icmp -j DROP
+-A FORWARD ! -s 192.168.100.0/24 -i eth1 -p udp -j DROP
+-A FORWARD ! -s 192.168.100.0/24 -i eth1 -p tcp -j DROP
+-A FORWARD -o ppp0 -p udp -m udp --sport 137:139 -j DROP
+-A FORWARD -o ppp0 -p udp -m udp --sport 445 -j DROP
+-A FORWARD -o ppp0 -p tcp -m tcp --sport 137:139 -j DROP
+-A FORWARD -o ppp0 -p tcp -m tcp --sport 445 -j DROP
+-A FORWARD -i ppp0 -p udp -m udp --dport 137:139 -j DROP
+-A FORWARD -i ppp0 -p udp -m udp --dport 445 -j DROP
+-A FORWARD -i ppp0 -p tcp -m tcp --dport 137:139 -j DROP
+-A FORWARD -i ppp0 -p tcp -m tcp --dport 445 -j DROP
+-A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
+-A FORWARD -j block
+-A OUTPUT -s 127.0.0.1/32 -d 127.0.0.1/32 -o lo -j ACCEPT
+-A OUTPUT -o wlan0 -j wlanout
+-A OUTPUT -j block
+-A WLAN -s 192.168.200.4/32 -m mac --mac-source 00:00:f1:05:a0:e0 -j RETURN
+-A WLAN -s 192.168.200.9/32 -m mac --mac-source 00:00:f1:05:99:85 -j RETURN
+-A WLAN -m limit --limit 12/min -j LOG --log-prefix "UNKNOWN WLAN dropped:"
+-A WLAN -j DROP
+-A accept_log -i ppp0 -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -m limit --limit 1/sec -j LOG --log-prefix "TCPConnect on ppp0:"
+-A accept_log -i ppp0 ! -p tcp -m limit --limit 1/sec -j LOG --log-prefix "Accepted Datagram on ppp0:"
+-A accept_log -j ACCEPT
+-A block -m state --state RELATED,ESTABLISHED -j ACCEPT
+-A block ! -i ppp0 -m state --state NEW -j ACCEPT
+-A block -p tcp -j reject_log
+-A block -p udp -j reject_log
+-A in_icmp -p icmp -m icmp --icmp-type 8 -j ACCEPT
+-A in_icmp -p icmp -m icmp --icmp-type 4 -j ACCEPT
+-A in_icmp -p icmp -m icmp --icmp-type 1 -j ACCEPT
+-A in_icmp -p icmp -m icmp --icmp-type 3 -j ACCEPT
+-A in_icmp -p icmp -m icmp --icmp-type 11 -j ACCEPT
+-A in_icmp -p icmp -m icmp --icmp-type 12 -j ACCEPT
+-A in_trusted -s 10.230.173.148/32 -j ACCEPT
+-A in_trusted -s 10.230.173.151/32 -j ACCEPT
+-A reject_log -i ppp0 -p tcp -m tcp --dport 22:80 --tcp-flags SYN,RST,ACK SYN -m limit --limit 1/sec -j LOG --log-prefix "RejectTCPConnectReq on ppp0:"
+-A reject_log -p tcp -j REJECT --reject-with tcp-reset
+-A reject_log -p udp -j REJECT --reject-with icmp-port-unreachable
+-A wlanout -d 192.168.200.4/32 -j RETURN
+-A wlanout -d 192.168.200.9/32 -j RETURN
+-A wlanout -j DROP
+COMMIT
+# Completed on Mon Mar 17 19:59:10 2003
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/policy-drop.txt b/iptables/tests/shell/testcases/ipt-save/dumps/policy-drop.txt
new file mode 100644
index 0000000..7522231
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/policy-drop.txt
@@ -0,0 +1,8 @@
+# Generated by xtables-save v1.6.2 on Tue Jun 26 22:28:41 2018
+*filter
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT DROP [0:0]
+-A OUTPUT -j ACCEPT
+COMMIT
+# Completed on Tue Jun 26 22:28:41 2018
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/wireless.txt b/iptables/tests/shell/testcases/ipt-save/dumps/wireless.txt
new file mode 100644
index 0000000..2bd3832
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/wireless.txt
@@ -0,0 +1,81 @@
+# Generated by iptables-save v1.4.21 on Thu Jun 29 18:03:06 2017
+*raw
+:PREROUTING ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:port_assignment - [0:0]
+-A PREROUTING -j port_assignment
+-A OUTPUT -j port_assignment
+-A port_assignment -p tcp -m tcp --dport 1723 -j CT --helper pptp
+COMMIT
+# Completed on Thu Jun 29 18:03:06 2017
+# Generated by iptables-save v1.4.21 on Thu Jun 29 18:03:06 2017
+*filter
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT ACCEPT [0:0]
+:CUST_I15_IN - [0:0]
+:CUST_I15_OUT - [0:0]
+:CUST_I16_IN - [0:0]
+:CUST_I16_OUT - [0:0]
+:L_ACCEPT - [0:0]
+:L_DROP - [0:0]
+:L_REJECT - [0:0]
+:VPN_USERS_IN - [0:0]
+:VPN_USERS_OUT - [0:0]
+-A INPUT -m conntrack --ctstate INVALID -j L_DROP
+-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j L_ACCEPT
+-A INPUT -i lo -j L_ACCEPT
+-A INPUT -s 10.78.129.130/32 -p tcp -m tcp --dport 5666 -j L_ACCEPT
+-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m multiport --dports 22,80,443,873,1723 -j L_ACCEPT
+-A INPUT -p udp -m udp -m multiport --dports 500,1701,4500 -j L_ACCEPT
+-A INPUT -p icmp -m icmp --icmp-type 8 -j L_ACCEPT
+-A INPUT -s 10.31.70.8/29 -i bond0.208 -p tcp -m tcp --dport 179 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A INPUT -s 10.44.224.8/29 -i bond0.686 -p tcp -m tcp --dport 179 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A INPUT -p esp -j L_ACCEPT
+-A INPUT -s 168.209.255.75/32 -p gre -j L_ACCEPT
+-A INPUT -s 168.209.255.106/32 -p gre -j L_ACCEPT
+-A INPUT -s 10.35.167.46/32 -p gre -j L_ACCEPT
+-A INPUT -s 10.35.167.45/32 -p gre -j L_ACCEPT
+-A INPUT -i gre-wbcore -j L_ACCEPT
+-A INPUT -i gre-davo-+ -j L_ACCEPT
+-A INPUT -i bond0.208 -j L_DROP
+-A INPUT -i bond0.686 -j L_DROP
+-A INPUT -j L_ACCEPT
+-A FORWARD -i bond0.10 -j ACCEPT
+-A FORWARD -m conntrack --ctstate INVALID -j L_DROP
+-A FORWARD -p tcp -m tcp --tcp-flags FIN,SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
+-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j L_ACCEPT
+-A FORWARD -d 10.31.63.80/30 -o bond0.10 -j L_ACCEPT
+-A FORWARD -o bond0.11 -j CUST_I16_IN
+-A FORWARD -i bond0.11 -j CUST_I16_OUT
+-A FORWARD -o bond0.12 -j CUST_I15_IN
+-A FORWARD -i bond0.12 -j CUST_I15_OUT
+-A FORWARD -s 192.168.255.0/24 -i ppp+ -o bond0.208 -j L_DROP
+-A FORWARD -s 192.168.255.0/24 -i ppp+ -o bond0.686 -j L_DROP
+-A FORWARD -j L_ACCEPT
+-A CUST_I15_IN -p tcp -m tcp --dport 22 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I15_IN -p tcp -m tcp --dport 80 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I15_IN -p tcp -m tcp --dport 433 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I15_IN -p tcp -m tcp --dport 3306 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I15_IN -p tcp -m tcp --dport 3390 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I15_IN -j L_DROP
+-A CUST_I15_OUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m multiport --dports 80,443 -j L_ACCEPT
+-A CUST_I15_OUT -j L_DROP
+-A CUST_I16_IN -p tcp -m tcp --dport 3390 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I16_IN -p tcp -m tcp --dport 21 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I16_IN -p icmp -m icmp --icmp-type 8 -j L_ACCEPT
+-A CUST_I16_IN -j L_DROP
+-A CUST_I16_OUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m multiport --dports 80,443 -j L_ACCEPT
+-A CUST_I16_OUT -d 154.73.34.12/32 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m multiport --dports 25 -j L_ACCEPT
+-A CUST_I16_OUT -j L_DROP
+-A L_ACCEPT -j NFLOG --nflog-group 1 --nflog-threshold 5
+-A L_ACCEPT -j ACCEPT
+-A L_DROP -j LOG --log-prefix "L_DROP: "
+-A L_DROP -j NFLOG --nflog-group 2 --nflog-threshold 5
+-A L_DROP -j DROP
+-A L_REJECT -j NFLOG --nflog-group 3 --nflog-threshold 5
+-A L_REJECT -j REJECT --reject-with icmp-port-unreachable
+-A VPN_USERS_IN -i ppp0 -m comment --comment "User: " -j ACCEPT
+-A VPN_USERS_OUT -o ppp0 -m comment --comment "User: " -j ACCEPT
+COMMIT
+# Completed on Thu Jun 29 18:03:06 2017
diff --git a/iptables/tests/shell/testcases/iptables/0001-chain-refs_0 b/iptables/tests/shell/testcases/iptables/0001-chain-refs_0
new file mode 100755
index 0000000..e55506e
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0001-chain-refs_0
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# make sure rules are not counted in references of iptables output
+
+set -e
+
+$XT_MULTI iptables -N foo
+$XT_MULTI iptables -L | grep 'Chain foo (0 references)'
+
+$XT_MULTI iptables -A foo -j ACCEPT
+$XT_MULTI iptables -L | grep 'Chain foo (0 references)'
+
+$XT_MULTI iptables -A FORWARD -j foo
+$XT_MULTI iptables -L | grep 'Chain foo (1 references)'
diff --git a/iptables/tests/shell/testcases/iptables/0002-verbose-output_0 b/iptables/tests/shell/testcases/iptables/0002-verbose-output_0
new file mode 100755
index 0000000..b1ef91f
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0002-verbose-output_0
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+set -e
+#set -x
+
+# ensure verbose output is identical between legacy and nft tools
+
+RULE1='-i eth2 -o eth3 -s 10.0.0.1 -d 10.0.0.2 -j ACCEPT'
+VOUT1='ACCEPT  all opt -- in eth2 out eth3  10.0.0.1  -> 10.0.0.2'
+RULE2='-i eth2 -o eth3 -s 10.0.0.4 -d 10.0.0.5 -j ACCEPT'
+VOUT2='ACCEPT  all opt -- in eth2 out eth3  10.0.0.4  -> 10.0.0.5'
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI iptables -v -A FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI iptables -v -I FORWARD 2 $RULE2)
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI iptables -v -C FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI iptables -v -C FORWARD $RULE2)
+
+EXPECT='Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination
+
+Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination
+    0     0 ACCEPT     all  --  eth2   eth3    10.0.0.1             10.0.0.2
+    0     0 ACCEPT     all  --  eth2   eth3    10.0.0.4             10.0.0.5
+
+Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -v -n -L)
+
+[[ -z $($XT_MULTI iptables -v -N foobar) ]] || exit 1
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI iptables -v -D FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI iptables -v -D FORWARD $RULE2)
+
+EXPECT="Flushing chain \`INPUT'
+Flushing chain \`FORWARD'
+Flushing chain \`OUTPUT'
+Flushing chain \`foobar'"
+
+diff -u <(echo -e "$EXPECT") <($XT_MULTI iptables -v -F)
+
+EXPECT="Zeroing chain \`INPUT'
+Zeroing chain \`FORWARD'
+Zeroing chain \`OUTPUT'
+Zeroing chain \`foobar'"
+
+diff -u <(echo -e "$EXPECT") <($XT_MULTI iptables -v -Z)
+
+diff -u <(echo "Flushing chain \`OUTPUT'") <($XT_MULTI iptables -v -F OUTPUT)
+diff -u <(echo "Zeroing chain \`OUTPUT'") <($XT_MULTI iptables -v -Z OUTPUT)
+diff -u <(echo "Flushing chain \`foobar'") <($XT_MULTI iptables -v -F foobar)
+diff -u <(echo "Zeroing chain \`foobar'") <($XT_MULTI iptables -v -Z foobar)
+
+diff -u <(echo "Deleting chain \`foobar'") <($XT_MULTI iptables -v -X foobar)
diff --git a/iptables/tests/shell/testcases/iptables/0003-list-rules_0 b/iptables/tests/shell/testcases/iptables/0003-list-rules_0
new file mode 100755
index 0000000..d335d44
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0003-list-rules_0
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+set -e
+
+$XT_MULTI iptables -N foo
+$XT_MULTI iptables -A FORWARD -i eth23 -o eth42 -j ACCEPT
+$XT_MULTI iptables -A FORWARD -i eth42 -o eth23 -g foo
+$XT_MULTI iptables -t nat -A OUTPUT -o eth123 -m mark --mark 0x42 -j ACCEPT
+
+EXPECT='-P INPUT ACCEPT
+-P FORWARD ACCEPT
+-P OUTPUT ACCEPT
+-N foo
+-A FORWARD -i eth23 -o eth42 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -S)
+
+EXPECT='-P INPUT ACCEPT -c 0 0
+-P FORWARD ACCEPT -c 0 0
+-P OUTPUT ACCEPT -c 0 0
+-N foo
+-A FORWARD -i eth23 -o eth42 -c 0 0 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -c 0 0 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -v -S)
+
+EXPECT='-P FORWARD ACCEPT
+-A FORWARD -i eth23 -o eth42 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -S FORWARD)
+
+EXPECT='-P FORWARD ACCEPT -c 0 0
+-A FORWARD -i eth23 -o eth42 -c 0 0 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -c 0 0 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -v -S FORWARD)
+
+EXPECT='-P OUTPUT ACCEPT
+-A OUTPUT -o eth123 -m mark --mark 0x42 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -t nat -S OUTPUT)
+
+EXPECT='-P OUTPUT ACCEPT -c 0 0
+-A OUTPUT -o eth123 -m mark --mark 0x42 -c 0 0 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -v -t nat -S OUTPUT)
+
+# some of the following commands are supposed to fail
+set +e
+
+$XT_MULTI iptables -S nonexistent && {
+	echo "list-rules in non-existent chain should fail"
+	exit 1
+}
+$XT_MULTI iptables -S nonexistent 23 && {
+	echo "list-rules in non-existent chain with given rule number should fail"
+	exit 1
+}
+$XT_MULTI iptables -S FORWARD 234 || {
+	echo "list-rules in existent chain with invalid rule number should succeed"
+	exit 1
+}
diff --git a/iptables/tests/shell/testcases/iptables/0004-return-codes_0 b/iptables/tests/shell/testcases/iptables/0004-return-codes_0
new file mode 100755
index 0000000..dcd9dfd
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0004-return-codes_0
@@ -0,0 +1,96 @@
+#!/bin/bash
+
+# make sure error return codes are as expected useful cases
+# (e.g. commands to check ruleset state)
+
+global_rc=0
+
+cmd() { # (rc, msg, cmd, [args ...])
+	rc_exp=$1; shift
+
+	msg_exp=""
+	[ $rc_exp != 0 ] && {
+		msg_exp="$1"; shift
+	}
+
+	for ipt in iptables ip6tables; do
+		msg="$($XT_MULTI $ipt "$@" 2>&1 >/dev/null)"
+		rc=$?
+
+		[ $rc -eq $rc_exp ] || {
+			echo "---> expected return code $rc_exp, got $rc for command '$ipt $@'"
+			global_rc=1
+		}
+
+		[ -n "$msg_exp" ] || continue
+		msg_exp_full="${ipt}$msg_exp"
+		grep -q "$msg_exp_full" <<< $msg || {
+			echo "---> expected error message '$msg_exp_full', got '$msg' for command '$ipt $@'"
+			global_rc=1
+		}
+	done
+}
+
+EEXIST_F=": File exists."
+EEXIST=": Chain already exists."
+ENOENT=": No chain/target/match by that name."
+E2BIG_I=": Index of insertion too big."
+E2BIG_D=": Index of deletion too big."
+E2BIG_R=": Index of replacement too big."
+EBADRULE=": Bad rule (does a matching rule exist in that chain?)."
+#ENOTGT=" v[0-9\.]* [^ ]*: Couldn't load target \`foobar':No such file or directory"
+ENOMTH=" v[0-9\.]* [^ ]*: Couldn't load match \`foobar':No such file or directory"
+ENOTBL=": can't initialize iptables table \`foobar': Table does not exist"
+
+# test chain creation
+cmd 0 -N foo
+cmd 1 "$EEXIST" -N foo
+# iptables-nft allows this - bug or feature?
+#cmd 2 -N "invalid name"
+
+# test chain flushing/zeroing
+cmd 0 -F foo
+cmd 0 -Z foo
+cmd 1 "$ENOENT" -F bar
+cmd 1 "$ENOENT" -Z bar
+
+# test chain rename
+cmd 0 -E foo bar
+cmd 1 "$EEXIST_F" -E foo bar
+cmd 1 "$ENOENT" -E foo bar2
+cmd 0 -N foo2
+cmd 1 "$EEXIST_F" -E foo2 bar
+
+# test rule adding
+cmd 0 -A INPUT -j ACCEPT
+cmd 1 "$ENOENT" -A noexist -j ACCEPT
+# next three differ:
+# legacy: Couldn't load target `foobar':No such file or directory
+# nft:    Chain 'foobar' does not exist
+cmd 2 "" -I INPUT -j foobar
+cmd 2 "" -R INPUT 1 -j foobar
+cmd 2 "" -D INPUT -j foobar
+cmd 1 "$EBADRULE" -D INPUT -p tcp --dport 22 -j ACCEPT
+
+# test rulenum commands
+cmd 1 "$E2BIG_I" -I INPUT 23 -j ACCEPT
+cmd 1 "$E2BIG_D" -D INPUT 23
+cmd 1 "$E2BIG_R" -R INPUT 23 -j ACCEPT
+cmd 1 "$ENOENT" -I nonexist 23 -j ACCEPT
+cmd 1 "$ENOENT" -D nonexist 23
+cmd 1 "$ENOENT" -R nonexist 23 -j ACCEPT
+
+# test rule checking
+cmd 0 -C INPUT -j ACCEPT
+cmd 1 "$EBADRULE" -C FORWARD -j ACCEPT
+cmd 1 "$BADRULE" -C nonexist -j ACCEPT
+cmd 2 "$ENOMTH" -C INPUT -m foobar -j ACCEPT
+# messages of those don't match, but iptables-nft ones are actually nicer.
+# legacy: Couldn't load target `foobar':No such file or directory
+# nft:    Chain 'foobar' does not exist
+cmd 2 "" -C INPUT -j foobar
+# legacy: can't initialize ip6tables table `foobar': Table does not exist (do you need to insmod?)
+# nft:    table 'foobar' does not exist
+cmd 3 "" -t foobar -C INPUT -j ACCEPT
+
+exit $global_rc
diff --git a/iptables/tests/shell/testcases/iptables/0005-delete-rules_0 b/iptables/tests/shell/testcases/iptables/0005-delete-rules_0
new file mode 100755
index 0000000..5038cbc
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0005-delete-rules_0
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# test for crash when comparing rules with standard target
+
+$XT_MULTI iptables -A FORWARD -i eth23 -o eth42 -j DROP
+$XT_MULTI iptables -D FORWARD -i eth23 -o eth42 -j REJECT
+[[ $? -eq 1 ]] || exit 1
+
+# test incorrect deletion of rules with deviating payload
+# in non-standard target
+
+$XT_MULTI iptables -A FORWARD -i eth23 -o eth42 -j MARK --set-mark 23
+$XT_MULTI iptables -D FORWARD -i eth23 -o eth42 -j MARK --set-mark 42
+[[ $? -eq 1 ]] || exit 1
diff --git a/iptables/tests/shell/testcases/iptables/0005-rule-replace_0 b/iptables/tests/shell/testcases/iptables/0005-rule-replace_0
new file mode 100755
index 0000000..5a3e922
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0005-rule-replace_0
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+# test rule replacement
+
+set -e
+
+# show rules, drop uninteresting policy settings
+ipt_show() {
+	$XT_MULTI iptables -S | grep -v '^-P'
+}
+
+$XT_MULTI iptables -A FORWARD -m comment --comment "rule 1" -j ACCEPT
+$XT_MULTI iptables -A FORWARD -m comment --comment "rule 2" -j ACCEPT
+$XT_MULTI iptables -A FORWARD -m comment --comment "rule 3" -j ACCEPT
+
+$XT_MULTI iptables -R FORWARD 2 -m comment --comment "replaced 2" -j ACCEPT
+
+EXPECT='-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "replaced 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+$XT_MULTI iptables -R FORWARD 1 -m comment --comment "replaced 1" -j ACCEPT
+
+EXPECT='-A FORWARD -m comment --comment "replaced 1" -j ACCEPT
+-A FORWARD -m comment --comment "replaced 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+$XT_MULTI iptables -R FORWARD 3 -m comment --comment "replaced 3" -j ACCEPT
+
+EXPECT='-A FORWARD -m comment --comment "replaced 1" -j ACCEPT
+-A FORWARD -m comment --comment "replaced 2" -j ACCEPT
+-A FORWARD -m comment --comment "replaced 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
diff --git a/iptables/tests/shell/testcases/iptables/0006-46-args_0 b/iptables/tests/shell/testcases/iptables/0006-46-args_0
new file mode 100755
index 0000000..17a0a01
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0006-46-args_0
@@ -0,0 +1,88 @@
+#!/bin/bash
+
+RC=0
+
+$XT_MULTI iptables -6 -A FORWARD -j ACCEPT
+rc=$?
+if [[ $rc -ne 2 ]]; then
+	echo "'iptables -6' returned $rc instead of 2"
+	RC=1
+fi
+
+$XT_MULTI ip6tables -4 -A FORWARD -j ACCEPT
+rc=$?
+if [[ $rc -ne 2 ]]; then
+	echo "'ip6tables -4' returned $rc instead of 2"
+	RC=1
+fi
+
+RULESET='*filter
+-4 -A FORWARD -d 10.0.0.1 -j ACCEPT
+-6 -A FORWARD -d fec0:10::1 -j ACCEPT
+COMMIT
+'
+EXPECT4='-P FORWARD ACCEPT
+-A FORWARD -d 10.0.0.1/32 -j ACCEPT'
+EXPECT6='-P FORWARD ACCEPT
+-A FORWARD -d fec0:10::1/128 -j ACCEPT'
+EXPECT_EMPTY='-P FORWARD ACCEPT'
+
+echo "$RULESET" | $XT_MULTI iptables-restore || {
+	echo "iptables-restore failed!"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT4") <($XT_MULTI iptables -S FORWARD) || {
+	echo "unexpected iptables ruleset"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT_EMPTY") <($XT_MULTI ip6tables -S FORWARD) || {
+	echo "unexpected non-empty ip6tables ruleset"
+	RC=1
+}
+
+$XT_MULTI iptables -F FORWARD
+
+echo "$RULESET" | $XT_MULTI ip6tables-restore || {
+	echo "ip6tables-restore failed!"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT6") <($XT_MULTI ip6tables -S FORWARD) || {
+	echo "unexpected ip6tables ruleset"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT_EMPTY") <($XT_MULTI iptables -S FORWARD) || {
+	echo "unexpected non-empty iptables ruleset"
+	RC=1
+}
+
+$XT_MULTI ip6tables -F FORWARD
+
+$XT_MULTI iptables -4 -A FORWARD -d 10.0.0.1 -j ACCEPT || {
+	echo "iptables failed!"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT4") <($XT_MULTI iptables -S FORWARD) || {
+	echo "unexpected iptables ruleset"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT_EMPTY") <($XT_MULTI ip6tables -S FORWARD) || {
+	echo "unexpected non-empty ip6tables ruleset"
+	RC=1
+}
+
+$XT_MULTI iptables -F FORWARD
+
+$XT_MULTI ip6tables -6 -A FORWARD -d fec0:10::1 -j ACCEPT || {
+	echo "ip6tables failed!"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT6") <($XT_MULTI ip6tables -S FORWARD) || {
+	echo "unexpected ip6tables ruleset"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT_EMPTY") <($XT_MULTI iptables -S FORWARD) || {
+	echo "unexpected non-empty iptables ruleset"
+	RC=1
+}
+
+exit $RC
diff --git a/iptables/tests/shell/testcases/nft-only/0001compat_0 b/iptables/tests/shell/testcases/nft-only/0001compat_0
new file mode 100755
index 0000000..a617c52
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0001compat_0
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# test case for bug fixed in
+# commit 873c5d5d293991ee3c06aed2b1dfc5764872582f (HEAD -> master)
+# xtables: avoid bogus 'is incompatible' warning
+
+case "$XT_MULTI" in
+*xtables-nft-multi)
+	;;
+*)
+	echo skip $XT_MULTI
+	exit 0
+	;;
+esac
+
+nft -v >/dev/null || exit 0
+nft 'add table ip nft-test; add chain ip nft-test foobar { type filter hook forward priority 42;  }' || exit 1
+nft 'add table ip6 nft-test; add chain ip6 nft-test foobar { type filter hook forward priority 42;  }' || exit 1
+
+$XT_MULTI iptables -L -t filter || exit 1
+$XT_MULTI ip6tables -L -t filter || exit 1
+exit 0
diff --git a/iptables/tests/shell/testcases/nft-only/0002invflags_0 b/iptables/tests/shell/testcases/nft-only/0002invflags_0
new file mode 100755
index 0000000..fe33874
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0002invflags_0
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+set -e
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+$XT_MULTI iptables -A INPUT -p tcp --dport 53 ! -s 192.168.0.1 -j ACCEPT
+$XT_MULTI ip6tables -A INPUT -p tcp --dport 53 ! -s feed:babe::1 -j ACCEPT
+$XT_MULTI ebtables -A INPUT -p IPv4 --ip-src 10.0.0.1 ! -i lo -j ACCEPT
+
diff --git a/iptables/tests/shell/testcases/nft-only/0003delete-with-comment_0 b/iptables/tests/shell/testcases/nft-only/0003delete-with-comment_0
new file mode 100755
index 0000000..ccb009e
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0003delete-with-comment_0
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+set -e
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+comment1="foo bar"
+comment2="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+
+for ipt in iptables ip6tables; do
+	for comment in "$comment1" "$comment2"; do
+		$XT_MULTI $ipt -A INPUT -m comment --comment "$comment" -j ACCEPT
+		$XT_MULTI $ipt -D INPUT -m comment --comment "$comment" -j ACCEPT
+	done
+done
diff --git a/iptables/tests/shell/testcases/nft-only/0006-policy-override_0 b/iptables/tests/shell/testcases/nft-only/0006-policy-override_0
new file mode 100755
index 0000000..68e2019
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0006-policy-override_0
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+# make sure none of the commands invoking nft_xt_builtin_init() override
+# non-default chain policies via needless chain add.
+
+RC=0
+
+do_test() {
+	$XT_MULTI $@
+	$XT_MULTI iptables -S | grep -q -- '-P FORWARD DROP' && return
+
+	echo "command '$@' kills chain policies"
+	$XT_MULTI iptables -P FORWARD DROP
+	RC=1
+}
+
+$XT_MULTI iptables -P FORWARD DROP
+
+do_test iptables -A OUTPUT -j ACCEPT
+do_test iptables -F
+do_test iptables -N foo
+do_test iptables -E foo foo2
+do_test iptables -I OUTPUT -j ACCEPT
+do_test iptables -nL
+do_test iptables -S
+
+exit $RC
diff --git a/iptables/tests/shell/testcases/nft-only/0007-mid-restore-flush_0 b/iptables/tests/shell/testcases/nft-only/0007-mid-restore-flush_0
new file mode 100755
index 0000000..43880ff
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0007-mid-restore-flush_0
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+nft -v >/dev/null || { echo "skip $XT_MULTI (no nft)"; exit 0; }
+
+coproc $XT_MULTI iptables-restore --noflush
+
+cat >&"${COPROC[1]}" <<EOF
+*filter
+:foo [0:0]
+COMMIT
+*filter
+:foo [0:0]
+EOF
+
+$XT_MULTI iptables-save | grep -q ':foo'
+nft flush ruleset
+
+echo "COMMIT" >&"${COPROC[1]}"
+sleep 1
+
+[[ -n $COPROC_PID ]] && kill $COPROC_PID
+wait
diff --git a/iptables/tests/shell/testcases/nft-only/0008-basechain-policy_0 b/iptables/tests/shell/testcases/nft-only/0008-basechain-policy_0
new file mode 100755
index 0000000..a81e9ba
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0008-basechain-policy_0
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+set -e
+
+$XT_MULTI iptables -t raw -P OUTPUT DROP
+
+# make sure iptables-nft-restore can correctly handle basechain policies when
+# they aren't set with --noflush
+#
+$XT_MULTI iptables-restore --noflush <<EOF
+*raw
+:OUTPUT - [0:0]
+:PREROUTING - [0:0]
+:neutron-linuxbri-OUTPUT - [0:0]
+:neutron-linuxbri-PREROUTING - [0:0]
+-I OUTPUT 1 -j neutron-linuxbri-OUTPUT
+-I PREROUTING 1 -j neutron-linuxbri-PREROUTING
+-I neutron-linuxbri-PREROUTING 1 -m physdev --physdev-in brq7425e328-56 -j CT --zone 4097
+-I neutron-linuxbri-PREROUTING 2 -i brq7425e328-56 -j CT --zone 4097
+-I neutron-linuxbri-PREROUTING 3 -m physdev --physdev-in tap7f101a28-1d -j CT --zone 4097
+
+COMMIT
+EOF
+
+$XT_MULTI iptables-save | grep -C2 raw | grep OUTPUT | grep DROP
+if [ $? -ne 0 ]; then
+	exit 1
+fi
diff --git a/iptables/tests/shell/testcases/nft-only/0009-needless-bitwise_0 b/iptables/tests/shell/testcases/nft-only/0009-needless-bitwise_0
new file mode 100755
index 0000000..41588a1
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0009-needless-bitwise_0
@@ -0,0 +1,346 @@
+#!/bin/bash -x
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+set -e
+
+nft flush ruleset
+
+(
+	echo "*filter"
+	for plen in "" 32 30 24 16 8 0; do
+		addr="10.1.2.3${plen:+/}$plen"
+		echo "-A OUTPUT -d $addr"
+	done
+	echo "COMMIT"
+) | $XT_MULTI iptables-restore
+
+(
+	echo "*filter"
+	for plen in "" 128 124 120 112 88 80 64 48 16 8 0; do
+		addr="feed:c0ff:ee00:0102:0304:0506:0708:090A${plen:+/}$plen"
+		echo "-A OUTPUT -d $addr"
+	done
+	echo "COMMIT"
+) | $XT_MULTI ip6tables-restore
+
+masks="
+ff:ff:ff:ff:ff:ff
+ff:ff:ff:ff:ff:f0
+ff:ff:ff:ff:ff:00
+ff:ff:ff:ff:00:00
+ff:ff:ff:00:00:00
+ff:ff:00:00:00:00
+ff:00:00:00:00:00
+"
+(
+	echo "*filter"
+	for plen in "" 32 30 24 16 8 0; do
+		addr="10.1.2.3${plen:+/}$plen"
+		echo "-A OUTPUT -d $addr"
+	done
+	for mask in $masks; do
+		echo "-A OUTPUT --destination-mac fe:ed:00:c0:ff:ee/$mask"
+	done
+	echo "COMMIT"
+) | $XT_MULTI arptables-restore
+
+(
+	echo "*filter"
+	for mask in $masks; do
+		echo "-A OUTPUT -d fe:ed:00:c0:ff:ee/$mask"
+	done
+	echo "COMMIT"
+) | $XT_MULTI ebtables-restore
+
+EXPECT="ip filter OUTPUT 4
+  [ payload load 4b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x0302010a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 5 4
+  [ payload load 4b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x0302010a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 6 5
+  [ payload load 4b @ network header + 16 => reg 1 ]
+  [ bitwise reg 1 = ( reg 1 & 0xfcffffff ) ^ 0x00000000 ]
+  [ cmp eq reg 1 0x0002010a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 7 6
+  [ payload load 3b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x0002010a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 8 7
+  [ payload load 2b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x0000010a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 9 8
+  [ payload load 1b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x0000000a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 10 9
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 4
+  [ payload load 16b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x06050403 0x0a090807 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 5 4
+  [ payload load 16b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x06050403 0x0a090807 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 6 5
+  [ payload load 16b @ network header + 24 => reg 1 ]
+  [ bitwise reg 1 = ( reg 1 & 0xffffffff 0xffffffff 0xffffffff 0xf0ffffff ) ^ 0x00000000 0x00000000 0x00000000 0x00000000 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x06050403 0x00090807 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 7 6
+  [ payload load 15b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x06050403 0x00090807 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 8 7
+  [ payload load 14b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x06050403 0x00000807 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 9 8
+  [ payload load 11b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x00050403 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 10 9
+  [ payload load 10b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x00000403 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 11 10
+  [ payload load 8b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 12 11
+  [ payload load 6b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x000000ee ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 13 12
+  [ payload load 2b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 14 13
+  [ payload load 1b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x000000fe ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 15 14
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 3
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 4b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0302010a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 4 3
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 4b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0302010a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 5 4
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 4b @ network header + 24 => reg 1 ]
+  [ bitwise reg 1 = ( reg 1 & 0xfcffffff ) ^ 0x00000000 ]
+  [ cmp eq reg 1 0x0002010a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 6 5
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 3b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0002010a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 7 6
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 2b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0000010a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 8 7
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 1b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0000000a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 9 8
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 10 9
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 6b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe 0x0000eeff ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 11 10
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 6b @ network header + 18 => reg 1 ]
+  [ bitwise reg 1 = ( reg 1 & 0xffffffff 0x0000f0ff ) ^ 0x00000000 0x00000000 ]
+  [ cmp eq reg 1 0xc000edfe 0x0000e0ff ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 12 11
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 5b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe 0x000000ff ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 13 12
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 4b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 14 13
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 3b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0x0000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 15 14
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 2b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0x0000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 16 15
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 1b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0x000000fe ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 4
+  [ payload load 6b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe 0x0000eeff ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 5 4
+  [ payload load 6b @ link header + 0 => reg 1 ]
+  [ bitwise reg 1 = ( reg 1 & 0xffffffff 0x0000f0ff ) ^ 0x00000000 0x00000000 ]
+  [ cmp eq reg 1 0xc000edfe 0x0000e0ff ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 6 5
+  [ payload load 5b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe 0x000000ff ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 7 6
+  [ payload load 4b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 8 7
+  [ payload load 3b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x0000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 9 8
+  [ payload load 2b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x0000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 10 9
+  [ payload load 1b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x000000fe ]
+  [ counter pkts 0 bytes 0 ]
+"
+
+# print nothing but:
+# - lines with bytecode (starting with '  [')
+# - empty lines (so printed diff is not a complete mess)
+filter() {
+	awk '/^(  \[|$)/{print}'
+}
+
+diff -u -Z <(filter <<< "$EXPECT") <(nft --debug=netlink list ruleset | filter)
diff --git a/iptables/xshared.c b/iptables/xshared.c
new file mode 100644
index 0000000..71f6899
--- /dev/null
+++ b/iptables/xshared.c
@@ -0,0 +1,855 @@
+#include <config.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <errno.h>
+#include <libgen.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <xtables.h>
+#include <math.h>
+#include "xshared.h"
+
+/*
+ * Print out any special helps. A user might like to be able to add a --help
+ * to the commandline, and see expected results. So we call help for all
+ * specified matches and targets.
+ */
+void print_extension_helps(const struct xtables_target *t,
+    const struct xtables_rule_match *m)
+{
+	for (; t != NULL; t = t->next) {
+		if (t->used) {
+			printf("\n");
+			if (t->help == NULL)
+				printf("%s does not take any options\n",
+				       t->name);
+			else
+				t->help();
+		}
+	}
+	for (; m != NULL; m = m->next) {
+		printf("\n");
+		if (m->match->help == NULL)
+			printf("%s does not take any options\n",
+			       m->match->name);
+		else
+			m->match->help();
+	}
+}
+
+const char *
+proto_to_name(uint8_t proto, int nolookup)
+{
+	unsigned int i;
+
+	if (proto && !nolookup) {
+		struct protoent *pent = getprotobynumber(proto);
+		if (pent)
+			return pent->p_name;
+	}
+
+	for (i = 0; xtables_chain_protos[i].name != NULL; ++i)
+		if (xtables_chain_protos[i].num == proto)
+			return xtables_chain_protos[i].name;
+
+	return NULL;
+}
+
+static struct xtables_match *
+find_proto(const char *pname, enum xtables_tryload tryload,
+	   int nolookup, struct xtables_rule_match **matches)
+{
+	unsigned int proto;
+
+	if (xtables_strtoui(pname, NULL, &proto, 0, UINT8_MAX)) {
+		const char *protoname = proto_to_name(proto, nolookup);
+
+		if (protoname)
+			return xtables_find_match(protoname, tryload, matches);
+	} else
+		return xtables_find_match(pname, tryload, matches);
+
+	return NULL;
+}
+
+/*
+ * Some explanations (after four different bugs in 3 different releases): If
+ * we encounter a parameter, that has not been parsed yet, it's not an option
+ * of an explicitly loaded match or a target. However, we support implicit
+ * loading of the protocol match extension. '-p tcp' means 'l4 proto 6' and at
+ * the same time 'load tcp protocol match on demand if we specify --dport'.
+ *
+ * To make this work, we need to make sure:
+ * - the parameter has not been parsed by a match (m above)
+ * - a protocol has been specified
+ * - the protocol extension has not been loaded yet, or is loaded and unused
+ *   [think of ip6tables-restore!]
+ * - the protocol extension can be successively loaded
+ */
+static bool should_load_proto(struct iptables_command_state *cs)
+{
+	if (cs->protocol == NULL)
+		return false;
+	if (find_proto(cs->protocol, XTF_DONT_LOAD,
+	    cs->options & OPT_NUMERIC, NULL) == NULL)
+		return true;
+	return !cs->proto_used;
+}
+
+struct xtables_match *load_proto(struct iptables_command_state *cs)
+{
+	if (!should_load_proto(cs))
+		return NULL;
+	return find_proto(cs->protocol, XTF_TRY_LOAD,
+			  cs->options & OPT_NUMERIC, &cs->matches);
+}
+
+int command_default(struct iptables_command_state *cs,
+		    struct xtables_globals *gl)
+{
+	struct xtables_rule_match *matchp;
+	struct xtables_match *m;
+
+	if (cs->target != NULL &&
+	    (cs->target->parse != NULL || cs->target->x6_parse != NULL) &&
+	    cs->c >= cs->target->option_offset &&
+	    cs->c < cs->target->option_offset + XT_OPTION_OFFSET_SCALE) {
+		xtables_option_tpcall(cs->c, cs->argv, cs->invert,
+				      cs->target, &cs->fw);
+		return 0;
+	}
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		m = matchp->match;
+
+		if (matchp->completed ||
+		    (m->x6_parse == NULL && m->parse == NULL))
+			continue;
+		if (cs->c < matchp->match->option_offset ||
+		    cs->c >= matchp->match->option_offset + XT_OPTION_OFFSET_SCALE)
+			continue;
+		xtables_option_mpcall(cs->c, cs->argv, cs->invert, m, &cs->fw);
+		return 0;
+	}
+
+	/* Try loading protocol */
+	m = load_proto(cs);
+	if (m != NULL) {
+		size_t size;
+
+		cs->proto_used = 1;
+
+		size = XT_ALIGN(sizeof(struct xt_entry_match)) + m->size;
+
+		m->m = xtables_calloc(1, size);
+		m->m->u.match_size = size;
+		strcpy(m->m->u.user.name, m->name);
+		m->m->u.user.revision = m->revision;
+		xs_init_match(m);
+
+		if (m->x6_options != NULL)
+			gl->opts = xtables_options_xfrm(gl->orig_opts,
+							gl->opts,
+							m->x6_options,
+							&m->option_offset);
+		else
+			gl->opts = xtables_merge_options(gl->orig_opts,
+							 gl->opts,
+							 m->extra_opts,
+							 &m->option_offset);
+		if (gl->opts == NULL)
+			xtables_error(OTHER_PROBLEM, "can't alloc memory!");
+		optind--;
+		/* Indicate to rerun getopt *immediately* */
+ 		return 1;
+	}
+
+	if (cs->c == ':')
+		xtables_error(PARAMETER_PROBLEM, "option \"%s\" "
+		              "requires an argument", cs->argv[optind-1]);
+	if (cs->c == '?')
+		xtables_error(PARAMETER_PROBLEM, "unknown option "
+			      "\"%s\"", cs->argv[optind-1]);
+	xtables_error(PARAMETER_PROBLEM, "Unknown arg \"%s\"", optarg);
+}
+
+static mainfunc_t subcmd_get(const char *cmd, const struct subcommand *cb)
+{
+	for (; cb->name != NULL; ++cb)
+		if (strcmp(cb->name, cmd) == 0)
+			return cb->main;
+	return NULL;
+}
+
+int subcmd_main(int argc, char **argv, const struct subcommand *cb)
+{
+	const char *cmd = basename(*argv);
+	mainfunc_t f = subcmd_get(cmd, cb);
+
+	if (f == NULL && argc > 1) {
+		/*
+		 * Unable to find a main method for our command name?
+		 * Let's try again with the first argument!
+		 */
+		++argv;
+		--argc;
+		f = subcmd_get(*argv, cb);
+	}
+
+	/* now we should have a valid function pointer */
+	if (f != NULL)
+		return f(argc, argv);
+
+	fprintf(stderr, "ERROR: No valid subcommand given.\nValid subcommands:\n");
+	for (; cb->name != NULL; ++cb)
+		fprintf(stderr, " * %s\n", cb->name);
+	exit(EXIT_FAILURE);
+}
+
+void xs_init_target(struct xtables_target *target)
+{
+	if (target->udata_size != 0) {
+		free(target->udata);
+		target->udata = calloc(1, target->udata_size);
+		if (target->udata == NULL)
+			xtables_error(RESOURCE_PROBLEM, "malloc");
+	}
+	if (target->init != NULL)
+		target->init(target->t);
+}
+
+void xs_init_match(struct xtables_match *match)
+{
+	if (match->udata_size != 0) {
+		/*
+		 * As soon as a subsequent instance of the same match
+		 * is used, e.g. "-m time -m time", the first instance
+		 * is no longer reachable anyway, so we can free udata.
+		 * Same goes for target.
+		 */
+		free(match->udata);
+		match->udata = calloc(1, match->udata_size);
+		if (match->udata == NULL)
+			xtables_error(RESOURCE_PROBLEM, "malloc");
+	}
+	if (match->init != NULL)
+		match->init(match->m);
+}
+
+static int xtables_lock(int wait, struct timeval *wait_interval)
+{
+	struct timeval time_left, wait_time;
+	const char *lock_file;
+	int fd, i = 0;
+
+	time_left.tv_sec = wait;
+	time_left.tv_usec = 0;
+
+	lock_file = getenv("XTABLES_LOCKFILE");
+	if (lock_file == NULL || lock_file[0] == '\0')
+		lock_file = XT_LOCK_NAME;
+
+	fd = open(lock_file, O_CREAT, 0600);
+	if (fd < 0) {
+		fprintf(stderr, "Fatal: can't open lock file %s: %s\n",
+			lock_file, strerror(errno));
+		return XT_LOCK_FAILED;
+	}
+
+	if (wait == -1) {
+		if (flock(fd, LOCK_EX) == 0)
+			return fd;
+
+		fprintf(stderr, "Can't lock %s: %s\n", lock_file,
+			strerror(errno));
+		return XT_LOCK_BUSY;
+	}
+
+	while (1) {
+		if (flock(fd, LOCK_EX | LOCK_NB) == 0)
+			return fd;
+		else if (timercmp(&time_left, wait_interval, <))
+			return XT_LOCK_BUSY;
+
+		if (++i % 10 == 0) {
+			fprintf(stderr, "Another app is currently holding the xtables lock; "
+				"still %lds %ldus time ahead to have a chance to grab the lock...\n",
+				time_left.tv_sec, time_left.tv_usec);
+		}
+
+		wait_time = *wait_interval;
+		select(0, NULL, NULL, NULL, &wait_time);
+		timersub(&time_left, wait_interval, &time_left);
+	}
+}
+
+void xtables_unlock(int lock)
+{
+	if (lock >= 0)
+		close(lock);
+}
+
+int xtables_lock_or_exit(int wait, struct timeval *wait_interval)
+{
+	int lock = xtables_lock(wait, wait_interval);
+
+	if (lock == XT_LOCK_FAILED) {
+		xtables_free_opts(1);
+		exit(RESOURCE_PROBLEM);
+	}
+
+	if (lock == XT_LOCK_BUSY) {
+		fprintf(stderr, "Another app is currently holding the xtables lock. ");
+		if (wait == 0)
+			fprintf(stderr, "Perhaps you want to use the -w option?\n");
+		else
+			fprintf(stderr, "Stopped waiting after %ds.\n", wait);
+		xtables_free_opts(1);
+		exit(RESOURCE_PROBLEM);
+	}
+
+	return lock;
+}
+
+int parse_wait_time(int argc, char *argv[])
+{
+	int wait = -1;
+
+	if (optarg) {
+		if (sscanf(optarg, "%i", &wait) != 1)
+			xtables_error(PARAMETER_PROBLEM,
+				"wait seconds not numeric");
+	} else if (xs_has_arg(argc, argv))
+		if (sscanf(argv[optind++], "%i", &wait) != 1)
+			xtables_error(PARAMETER_PROBLEM,
+				"wait seconds not numeric");
+
+	return wait;
+}
+
+void parse_wait_interval(int argc, char *argv[], struct timeval *wait_interval)
+{
+	const char *arg;
+	unsigned int usec;
+	int ret;
+
+	if (optarg)
+		arg = optarg;
+	else if (xs_has_arg(argc, argv))
+		arg = argv[optind++];
+	else
+		xtables_error(PARAMETER_PROBLEM, "wait interval value required");
+
+	ret = sscanf(arg, "%u", &usec);
+	if (ret == 1) {
+		if (usec > 999999)
+			xtables_error(PARAMETER_PROBLEM,
+				      "too long usec wait %u > 999999 usec",
+				      usec);
+
+		wait_interval->tv_sec = 0;
+		wait_interval->tv_usec = usec;
+		return;
+	}
+	xtables_error(PARAMETER_PROBLEM, "wait interval not numeric");
+}
+
+int parse_counters(const char *string, struct xt_counters *ctr)
+{
+	int ret;
+
+	if (!string)
+		return 0;
+
+	ret = sscanf(string, "[%llu:%llu]",
+		     (unsigned long long *)&ctr->pcnt,
+		     (unsigned long long *)&ctr->bcnt);
+
+	return ret == 2;
+}
+
+/* Tokenize counters argument of typical iptables-restore format rule.
+ *
+ * If *bufferp contains counters, update *pcntp and *bcntp to point at them,
+ * change bytes after counters in *bufferp to nul-bytes, update *bufferp to
+ * point to after the counters and return true.
+ * If *bufferp does not contain counters, return false.
+ * If syntax is wrong in *bufferp, call xtables_error() and hence exit().
+ * */
+bool tokenize_rule_counters(char **bufferp, char **pcntp, char **bcntp, int line)
+{
+	char *ptr, *buffer = *bufferp, *pcnt, *bcnt;
+
+	if (buffer[0] != '[')
+		return false;
+
+	/* we have counters in our input */
+
+	ptr = strchr(buffer, ']');
+	if (!ptr)
+		xtables_error(PARAMETER_PROBLEM, "Bad line %u: need ]\n", line);
+
+	pcnt = strtok(buffer+1, ":");
+	if (!pcnt)
+		xtables_error(PARAMETER_PROBLEM, "Bad line %u: need :\n", line);
+
+	bcnt = strtok(NULL, "]");
+	if (!bcnt)
+		xtables_error(PARAMETER_PROBLEM, "Bad line %u: need ]\n", line);
+
+	*pcntp = pcnt;
+	*bcntp = bcnt;
+	/* start command parsing after counter */
+	*bufferp = ptr + 1;
+
+	return true;
+}
+
+inline bool xs_has_arg(int argc, char *argv[])
+{
+	return optind < argc &&
+	       argv[optind][0] != '-' &&
+	       argv[optind][0] != '!';
+}
+
+/* function adding one argument to store, updating argc
+ * returns if argument added, does not return otherwise */
+void add_argv(struct argv_store *store, const char *what, int quoted)
+{
+	DEBUGP("add_argv: %s\n", what);
+
+	if (store->argc + 1 >= MAX_ARGC)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Parser cannot handle more arguments\n");
+	if (!what)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Trying to store NULL argument\n");
+
+	store->argv[store->argc] = strdup(what);
+	store->argvattr[store->argc] = quoted;
+	store->argv[++store->argc] = NULL;
+}
+
+void free_argv(struct argv_store *store)
+{
+	while (store->argc) {
+		store->argc--;
+		free(store->argv[store->argc]);
+		store->argvattr[store->argc] = 0;
+	}
+}
+
+/* Save parsed rule for comparison with next rule to perform action aggregation
+ * on duplicate conditions.
+ */
+void save_argv(struct argv_store *dst, struct argv_store *src)
+{
+	int i;
+
+	free_argv(dst);
+	for (i = 0; i < src->argc; i++) {
+		dst->argvattr[i] = src->argvattr[i];
+		dst->argv[i] = src->argv[i];
+		src->argv[i] = NULL;
+	}
+	dst->argc = src->argc;
+	src->argc = 0;
+}
+
+struct xt_param_buf {
+	char	buffer[1024];
+	int 	len;
+};
+
+static void add_param(struct xt_param_buf *param, const char *curchar)
+{
+	param->buffer[param->len++] = *curchar;
+	if (param->len >= sizeof(param->buffer))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Parameter too long!");
+}
+
+void add_param_to_argv(struct argv_store *store, char *parsestart, int line)
+{
+	int quote_open = 0, escaped = 0, quoted = 0;
+	struct xt_param_buf param = {};
+	char *curchar;
+
+	/* After fighting with strtok enough, here's now
+	 * a 'real' parser. According to Rusty I'm now no
+	 * longer a real hacker, but I can live with that */
+
+	for (curchar = parsestart; *curchar; curchar++) {
+		if (quote_open) {
+			if (escaped) {
+				add_param(&param, curchar);
+				escaped = 0;
+				continue;
+			} else if (*curchar == '\\') {
+				escaped = 1;
+				continue;
+			} else if (*curchar == '"') {
+				quote_open = 0;
+			} else {
+				add_param(&param, curchar);
+				continue;
+			}
+		} else {
+			if (*curchar == '"') {
+				quote_open = 1;
+				quoted = 1;
+				continue;
+			}
+		}
+
+		switch (*curchar) {
+		case '"':
+			break;
+		case ' ':
+		case '\t':
+		case '\n':
+			if (!param.len) {
+				/* two spaces? */
+				continue;
+			}
+			break;
+		default:
+			/* regular character, copy to buffer */
+			add_param(&param, curchar);
+			continue;
+		}
+
+		param.buffer[param.len] = '\0';
+		add_argv(store, param.buffer, quoted);
+		param.len = 0;
+		quoted = 0;
+	}
+	if (param.len) {
+		param.buffer[param.len] = '\0';
+		add_argv(store, param.buffer, 0);
+	}
+}
+
+#ifdef DEBUG
+void debug_print_argv(struct argv_store *store)
+{
+	int i;
+
+	for (i = 0; i < store->argc; i++)
+		fprintf(stderr, "argv[%d]: %s\n", i, store->argv[i]);
+}
+#endif
+
+static const char *ipv4_addr_to_string(const struct in_addr *addr,
+				       const struct in_addr *mask,
+				       unsigned int format)
+{
+	static char buf[BUFSIZ];
+
+	if (!mask->s_addr && !(format & FMT_NUMERIC))
+		return "anywhere";
+
+	if (format & FMT_NUMERIC)
+		strncpy(buf, xtables_ipaddr_to_numeric(addr), BUFSIZ - 1);
+	else
+		strncpy(buf, xtables_ipaddr_to_anyname(addr), BUFSIZ - 1);
+	buf[BUFSIZ - 1] = '\0';
+
+	strncat(buf, xtables_ipmask_to_numeric(mask),
+		BUFSIZ - strlen(buf) - 1);
+
+	return buf;
+}
+
+void print_ipv4_addresses(const struct ipt_entry *fw, unsigned int format)
+{
+	fputc(fw->ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout);
+	printf(FMT("%-19s ", "%s "),
+	       ipv4_addr_to_string(&fw->ip.src, &fw->ip.smsk, format));
+
+	fputc(fw->ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);
+	printf(FMT("%-19s ", "-> %s"),
+	       ipv4_addr_to_string(&fw->ip.dst, &fw->ip.dmsk, format));
+}
+
+static const char *ipv6_addr_to_string(const struct in6_addr *addr,
+				       const struct in6_addr *mask,
+				       unsigned int format)
+{
+	static char buf[BUFSIZ];
+
+	if (IN6_IS_ADDR_UNSPECIFIED(addr) && !(format & FMT_NUMERIC))
+		return "anywhere";
+
+	if (format & FMT_NUMERIC)
+		strncpy(buf, xtables_ip6addr_to_numeric(addr), BUFSIZ - 1);
+	else
+		strncpy(buf, xtables_ip6addr_to_anyname(addr), BUFSIZ - 1);
+	buf[BUFSIZ - 1] = '\0';
+
+	strncat(buf, xtables_ip6mask_to_numeric(mask),
+		BUFSIZ - strlen(buf) - 1);
+
+	return buf;
+}
+
+void print_ipv6_addresses(const struct ip6t_entry *fw6, unsigned int format)
+{
+	fputc(fw6->ipv6.invflags & IP6T_INV_SRCIP ? '!' : ' ', stdout);
+	printf(FMT("%-19s ", "%s "),
+	       ipv6_addr_to_string(&fw6->ipv6.src,
+				   &fw6->ipv6.smsk, format));
+
+	fputc(fw6->ipv6.invflags & IP6T_INV_DSTIP ? '!' : ' ', stdout);
+	printf(FMT("%-19s ", "-> %s"),
+	       ipv6_addr_to_string(&fw6->ipv6.dst,
+				   &fw6->ipv6.dmsk, format));
+}
+
+/* Luckily, IPT_INV_VIA_IN and IPT_INV_VIA_OUT
+ * have the same values as IP6T_INV_VIA_IN and IP6T_INV_VIA_OUT
+ * so this function serves for both iptables and ip6tables */
+void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags,
+		  unsigned int format)
+{
+	const char *anyname = format & FMT_NUMERIC ? "*" : "any";
+	char iface[IFNAMSIZ + 2];
+
+	if (!(format & FMT_VIA))
+		return;
+
+	snprintf(iface, IFNAMSIZ + 2, "%s%s",
+		 invflags & IPT_INV_VIA_IN ? "!" : "",
+		 iniface[0] != '\0' ? iniface : anyname);
+
+	printf(FMT(" %-6s ", "in %s "), iface);
+
+	snprintf(iface, IFNAMSIZ + 2, "%s%s",
+		 invflags & IPT_INV_VIA_OUT ? "!" : "",
+		 outiface[0] != '\0' ? outiface : anyname);
+
+	printf(FMT("%-6s ", "out %s "), iface);
+}
+
+void command_match(struct iptables_command_state *cs)
+{
+	struct option *opts = xt_params->opts;
+	struct xtables_match *m;
+	size_t size;
+
+	if (cs->invert)
+		xtables_error(PARAMETER_PROBLEM,
+			   "unexpected ! flag before --match");
+
+	m = xtables_find_match(optarg, XTF_LOAD_MUST_SUCCEED, &cs->matches);
+	size = XT_ALIGN(sizeof(struct xt_entry_match)) + m->size;
+	m->m = xtables_calloc(1, size);
+	m->m->u.match_size = size;
+	if (m->real_name == NULL) {
+		strcpy(m->m->u.user.name, m->name);
+	} else {
+		strcpy(m->m->u.user.name, m->real_name);
+		if (!(m->ext_flags & XTABLES_EXT_ALIAS))
+			fprintf(stderr, "Notice: the %s match is converted into %s match "
+				"in rule listing and saving.\n", m->name, m->real_name);
+	}
+	m->m->u.user.revision = m->revision;
+	xs_init_match(m);
+	if (m == m->next)
+		return;
+	/* Merge options for non-cloned matches */
+	if (m->x6_options != NULL)
+		opts = xtables_options_xfrm(xt_params->orig_opts, opts,
+					    m->x6_options, &m->option_offset);
+	else if (m->extra_opts != NULL)
+		opts = xtables_merge_options(xt_params->orig_opts, opts,
+					     m->extra_opts, &m->option_offset);
+	if (opts == NULL)
+		xtables_error(OTHER_PROBLEM, "can't alloc memory!");
+	xt_params->opts = opts;
+}
+
+const char *xt_parse_target(const char *targetname)
+{
+	const char *ptr;
+
+	if (strlen(targetname) < 1)
+		xtables_error(PARAMETER_PROBLEM,
+			   "Invalid target name (too short)");
+
+	if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+			   "Invalid target name `%s' (%u chars max)",
+			   targetname, XT_EXTENSION_MAXNAMELEN - 1);
+
+	for (ptr = targetname; *ptr; ptr++)
+		if (isspace(*ptr))
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid target name `%s'", targetname);
+	return targetname;
+}
+
+void command_jump(struct iptables_command_state *cs, const char *jumpto)
+{
+	struct option *opts = xt_params->opts;
+	size_t size;
+
+	cs->jumpto = xt_parse_target(jumpto);
+	/* TRY_LOAD (may be chain name) */
+	cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD);
+
+	if (cs->target == NULL)
+		return;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target)) + cs->target->size;
+
+	cs->target->t = xtables_calloc(1, size);
+	cs->target->t->u.target_size = size;
+	if (cs->target->real_name == NULL) {
+		strcpy(cs->target->t->u.user.name, cs->jumpto);
+	} else {
+		/* Alias support for userspace side */
+		strcpy(cs->target->t->u.user.name, cs->target->real_name);
+		if (!(cs->target->ext_flags & XTABLES_EXT_ALIAS))
+			fprintf(stderr, "Notice: The %s target is converted into %s target "
+				"in rule listing and saving.\n",
+				cs->jumpto, cs->target->real_name);
+	}
+	cs->target->t->u.user.revision = cs->target->revision;
+	xs_init_target(cs->target);
+
+	if (cs->target->x6_options != NULL)
+		opts = xtables_options_xfrm(xt_params->orig_opts, opts,
+					    cs->target->x6_options,
+					    &cs->target->option_offset);
+	else
+		opts = xtables_merge_options(xt_params->orig_opts, opts,
+					     cs->target->extra_opts,
+					     &cs->target->option_offset);
+	if (opts == NULL)
+		xtables_error(OTHER_PROBLEM, "can't alloc memory!");
+	xt_params->opts = opts;
+}
+
+char cmd2char(int option)
+{
+	/* cmdflags index corresponds with position of bit in CMD_* values */
+	static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z',
+					 'N', 'X', 'P', 'E', 'S', 'Z', 'C' };
+	int i;
+
+	for (i = 0; option > 1; option >>= 1, i++)
+		;
+	if (i >= ARRAY_SIZE(cmdflags))
+		xtables_error(OTHER_PROBLEM,
+			      "cmd2char(): Invalid command number %u.\n",
+			      1 << i);
+	return cmdflags[i];
+}
+
+void add_command(unsigned int *cmd, const int newcmd,
+		 const int othercmds, int invert)
+{
+	if (invert)
+		xtables_error(PARAMETER_PROBLEM, "unexpected '!' flag");
+	if (*cmd & (~othercmds))
+		xtables_error(PARAMETER_PROBLEM, "Cannot use -%c with -%c\n",
+			   cmd2char(newcmd), cmd2char(*cmd & (~othercmds)));
+	*cmd |= newcmd;
+}
+
+/* Can't be zero. */
+int parse_rulenumber(const char *rule)
+{
+	unsigned int rulenum;
+
+	if (!xtables_strtoui(rule, NULL, &rulenum, 1, INT_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			   "Invalid rule number `%s'", rule);
+
+	return rulenum;
+}
+
+/* Table of legal combinations of commands and options.  If any of the
+ * given commands make an option legal, that option is legal (applies to
+ * CMD_LIST and CMD_ZERO only).
+ * Key:
+ *  +  compulsory
+ *  x  illegal
+ *     optional
+ */
+static const char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] =
+/* Well, it's better than "Re: Linux vs FreeBSD" */
+{
+	/*     -n  -s  -d  -p  -j  -v  -x  -i  -o --line -c -f 2 3 l 4 5 6 */
+/*INSERT*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' ',' ',' ',' ',' ',' ',' '},
+/*DELETE*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' ',' ',' ',' ',' ',' ',' '},
+/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*REPLACE*/   {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' ',' ',' ',' ',' ',' ',' '},
+/*APPEND*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' ',' ',' ',' ',' ',' ',' '},
+/*LIST*/      {' ','x','x','x','x',' ',' ','x','x',' ','x','x','x','x','x','x','x','x'},
+/*FLUSH*/     {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*ZERO*/      {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x',' ','x','x','x','x','x','x','x'},
+/*RENAME*/    {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*LIST_RULES*/{'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*ZERO_NUM*/  {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*CHECK*/     {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' ',' ',' ',' ',' ',' ',' '},
+};
+
+void generic_opt_check(int command, int options)
+{
+	int i, j, legal = 0;
+
+	/* Check that commands are valid with options. Complicated by the
+	 * fact that if an option is legal with *any* command given, it is
+	 * legal overall (ie. -z and -l).
+	 */
+	for (i = 0; i < NUMBER_OF_OPT; i++) {
+		legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */
+
+		for (j = 0; j < NUMBER_OF_CMD; j++) {
+			if (!(command & (1<<j)))
+				continue;
+
+			if (!(options & (1<<i))) {
+				if (commands_v_options[j][i] == '+')
+					xtables_error(PARAMETER_PROBLEM,
+						   "You need to supply the `-%c' "
+						   "option for this command\n",
+						   optflags[i]);
+			} else {
+				if (commands_v_options[j][i] != 'x')
+					legal = 1;
+				else if (legal == 0)
+					legal = -1;
+			}
+		}
+		if (legal == -1)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Illegal option `-%c' with this command\n",
+				   optflags[i]);
+	}
+}
+
+char opt2char(int option)
+{
+	const char *ptr;
+
+	for (ptr = optflags; option > 1; option >>= 1, ptr++)
+		;
+
+	return *ptr;
+}
diff --git a/iptables/xshared.h b/iptables/xshared.h
new file mode 100644
index 0000000..9159b2b
--- /dev/null
+++ b/iptables/xshared.h
@@ -0,0 +1,227 @@
+#ifndef IPTABLES_XSHARED_H
+#define IPTABLES_XSHARED_H 1
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <sys/time.h>
+#include <linux/netfilter_arp/arp_tables.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+#ifdef DEBUG
+#define DEBUGP(x, args...) fprintf(stderr, x, ## args)
+#else
+#define DEBUGP(x, args...)
+#endif
+
+enum {
+	OPT_NONE        = 0,
+	OPT_NUMERIC     = 1 << 0,
+	OPT_SOURCE      = 1 << 1,
+	OPT_DESTINATION = 1 << 2,
+	OPT_PROTOCOL    = 1 << 3,
+	OPT_JUMP        = 1 << 4,
+	OPT_VERBOSE     = 1 << 5,
+	OPT_EXPANDED    = 1 << 6,
+	OPT_VIANAMEIN   = 1 << 7,
+	OPT_VIANAMEOUT  = 1 << 8,
+	OPT_LINENUMBERS = 1 << 9,
+	OPT_COUNTERS    = 1 << 10,
+	OPT_FRAGMENT	= 1 << 11,
+	/* below are for arptables only */
+	OPT_S_MAC	= 1 << 12,
+	OPT_D_MAC	= 1 << 13,
+	OPT_H_LENGTH	= 1 << 14,
+	OPT_OPCODE	= 1 << 15,
+	OPT_H_TYPE	= 1 << 16,
+	OPT_P_TYPE	= 1 << 17,
+};
+
+#define NUMBER_OF_OPT	ARRAY_SIZE(optflags)
+static const char optflags[]
+= { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c', 'f', 2, 3, 'l', 4, 5, 6 };
+
+enum {
+	CMD_NONE		= 0,
+	CMD_INSERT		= 1 << 0,
+	CMD_DELETE		= 1 << 1,
+	CMD_DELETE_NUM		= 1 << 2,
+	CMD_REPLACE		= 1 << 3,
+	CMD_APPEND		= 1 << 4,
+	CMD_LIST		= 1 << 5,
+	CMD_FLUSH		= 1 << 6,
+	CMD_ZERO		= 1 << 7,
+	CMD_NEW_CHAIN		= 1 << 8,
+	CMD_DELETE_CHAIN	= 1 << 9,
+	CMD_SET_POLICY		= 1 << 10,
+	CMD_RENAME_CHAIN	= 1 << 11,
+	CMD_LIST_RULES		= 1 << 12,
+	CMD_ZERO_NUM		= 1 << 13,
+	CMD_CHECK		= 1 << 14,
+};
+#define NUMBER_OF_CMD		16
+
+struct xtables_globals;
+struct xtables_rule_match;
+struct xtables_target;
+
+/**
+ * xtables_afinfo - protocol family dependent information
+ * @kmod:		kernel module basename (e.g. "ip_tables")
+ * @proc_exists:	file which exists in procfs when module already loaded
+ * @libprefix:		prefix of .so library name (e.g. "libipt_")
+ * @family:		nfproto family
+ * @ipproto:		used by setsockopt (e.g. IPPROTO_IP)
+ * @so_rev_match:	optname to check revision support of match
+ * @so_rev_target:	optname to check revision support of target
+ */
+struct xtables_afinfo {
+	const char *kmod;
+	const char *proc_exists;
+	const char *libprefix;
+	uint8_t family;
+	uint8_t ipproto;
+	int so_rev_match;
+	int so_rev_target;
+};
+
+/* trick for ebtables-compat, since watchers are targets */
+struct ebt_match {
+	struct ebt_match			*next;
+	union {
+		struct xtables_match		*match;
+		struct xtables_target		*watcher;
+	} u;
+	bool					ismatch;
+};
+
+/* Fake ebt_entry */
+struct ebt_entry {
+	/* this needs to be the first field */
+	unsigned int bitmask;
+	unsigned int invflags;
+	uint16_t ethproto;
+	/* the physical in-dev */
+	char in[IFNAMSIZ];
+	/* the logical in-dev */
+	char logical_in[IFNAMSIZ];
+	/* the physical out-dev */
+	char out[IFNAMSIZ];
+	/* the logical out-dev */
+	char logical_out[IFNAMSIZ];
+	unsigned char sourcemac[6];
+	unsigned char sourcemsk[6];
+	unsigned char destmac[6];
+	unsigned char destmsk[6];
+};
+
+struct iptables_command_state {
+	union {
+		struct ebt_entry eb;
+		struct ipt_entry fw;
+		struct ip6t_entry fw6;
+		struct arpt_entry arp;
+	};
+	int invert;
+	int c;
+	unsigned int options;
+	struct xtables_rule_match *matches;
+	struct ebt_match *match_list;
+	struct xtables_target *target;
+	struct xt_counters counters;
+	char *protocol;
+	int proto_used;
+	const char *jumpto;
+	char **argv;
+	bool restore;
+};
+
+typedef int (*mainfunc_t)(int, char **);
+
+struct subcommand {
+	const char *name;
+	mainfunc_t main;
+};
+
+enum {
+	XT_OPTION_OFFSET_SCALE = 256,
+};
+
+extern void print_extension_helps(const struct xtables_target *,
+	const struct xtables_rule_match *);
+extern const char *proto_to_name(uint8_t, int);
+extern int command_default(struct iptables_command_state *,
+	struct xtables_globals *);
+extern struct xtables_match *load_proto(struct iptables_command_state *);
+extern int subcmd_main(int, char **, const struct subcommand *);
+extern void xs_init_target(struct xtables_target *);
+extern void xs_init_match(struct xtables_match *);
+
+/**
+ * Values for the iptables lock.
+ *
+ * A value >= 0 indicates the lock filedescriptor. Other values are:
+ *
+ * XT_LOCK_FAILED : The lock could not be acquired.
+ *
+ * XT_LOCK_BUSY : The lock was held by another process. xtables_lock only
+ * returns this value when |wait| == false. If |wait| == true, xtables_lock
+ * will not return unless the lock has been acquired.
+ *
+ * XT_LOCK_NOT_ACQUIRED : We have not yet attempted to acquire the lock.
+ */
+enum {
+	XT_LOCK_BUSY = -1,
+	XT_LOCK_FAILED = -2,
+	XT_LOCK_NOT_ACQUIRED  = -3,
+};
+extern void xtables_unlock(int lock);
+extern int xtables_lock_or_exit(int wait, struct timeval *tv);
+
+int parse_wait_time(int argc, char *argv[]);
+void parse_wait_interval(int argc, char *argv[], struct timeval *wait_interval);
+int parse_counters(const char *string, struct xt_counters *ctr);
+bool tokenize_rule_counters(char **bufferp, char **pcnt, char **bcnt, int line);
+bool xs_has_arg(int argc, char *argv[]);
+
+extern const struct xtables_afinfo *afinfo;
+
+#define MAX_ARGC	255
+struct argv_store {
+	int argc;
+	char *argv[MAX_ARGC];
+	int argvattr[MAX_ARGC];
+};
+
+void add_argv(struct argv_store *store, const char *what, int quoted);
+void free_argv(struct argv_store *store);
+void save_argv(struct argv_store *dst, struct argv_store *src);
+void add_param_to_argv(struct argv_store *store, char *parsestart, int line);
+#ifdef DEBUG
+void debug_print_argv(struct argv_store *store);
+#else
+#  define debug_print_argv(...) /* nothing */
+#endif
+
+void print_ipv4_addresses(const struct ipt_entry *fw, unsigned int format);
+void print_ipv6_addresses(const struct ip6t_entry *fw6, unsigned int format);
+
+void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags,
+		  unsigned int format);
+
+void command_match(struct iptables_command_state *cs);
+const char *xt_parse_target(const char *targetname);
+void command_jump(struct iptables_command_state *cs, const char *jumpto);
+
+char cmd2char(int option);
+void add_command(unsigned int *cmd, const int newcmd,
+		 const int othercmds, int invert);
+int parse_rulenumber(const char *rule);
+
+void generic_opt_check(int command, int options);
+char opt2char(int option);
+
+#endif /* IPTABLES_XSHARED_H */
diff --git a/iptables/xtables-arp-standalone.c b/iptables/xtables-arp-standalone.c
new file mode 100644
index 0000000..04cf7dc
--- /dev/null
+++ b/iptables/xtables-arp-standalone.c
@@ -0,0 +1,65 @@
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * Based on the ipchains code by Paul Russell and Michael Neuling
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	arptables -- IP firewall administration for kernels with
+ *	firewall table (aimed for the 2.3 kernels)
+ *
+ *	See the accompanying manual page arptables(8) for information
+ *	about proper usage of this program.
+ *
+ *	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 2 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, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <xtables.h>
+#include "nft.h"
+#include <linux/netfilter_arp/arp_tables.h>
+
+#include "xtables-multi.h"
+
+extern struct xtables_globals arptables_globals;
+
+int xtables_arp_main(int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h;
+
+	nft_init_arp(&h, "arptables");
+
+	ret = do_commandarp(&h, argc, argv, &table, false);
+	if (ret)
+		ret = nft_commit(&h);
+
+	nft_fini(&h);
+	xtables_fini();
+
+	if (!ret)
+		fprintf(stderr, "arptables: %s\n", nft_strerror(errno));
+
+	exit(!ret);
+}
diff --git a/iptables/xtables-arp.c b/iptables/xtables-arp.c
new file mode 100644
index 0000000..4a89ae9
--- /dev/null
+++ b/iptables/xtables-arp.c
@@ -0,0 +1,971 @@
+/* Code to take an arptables-style command line and do it. */
+
+/*
+ * arptables:
+ * Author: Bart De Schuymer <bdschuym@pandora.be>, but
+ * almost all code is from the iptables userspace program, which has main
+ * authors: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ *	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 2 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, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+  Currently, only support for specifying hardware addresses for Ethernet
+  is available.
+  This tool is not luser-proof: you can specify an Ethernet source address
+  and set hardware length to something different than 6, f.e.
+*/
+#include "config.h"
+#include <getopt.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <dlfcn.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <iptables.h>
+#include <xtables.h>
+
+#include "xshared.h"
+
+#include "nft.h"
+#include "nft-arp.h"
+#include <linux/netfilter_arp/arp_tables.h>
+
+static struct option original_opts[] = {
+	{ "append", 1, 0, 'A' },
+	{ "delete", 1, 0,  'D' },
+	{ "insert", 1, 0,  'I' },
+	{ "replace", 1, 0,  'R' },
+	{ "list", 2, 0,  'L' },
+	{ "flush", 2, 0,  'F' },
+	{ "zero", 2, 0,  'Z' },
+	{ "new-chain", 1, 0,  'N' },
+	{ "delete-chain", 2, 0,  'X' },
+	{ "rename-chain", 1, 0,  'E' },
+	{ "policy", 1, 0,  'P' },
+	{ "source-ip", 1, 0, 's' },
+	{ "destination-ip", 1, 0,  'd' },
+	{ "src-ip", 1, 0,  's' },
+	{ "dst-ip", 1, 0,  'd' },
+	{ "source-mac", 1, 0, 2},
+	{ "destination-mac", 1, 0, 3},
+	{ "src-mac", 1, 0, 2},
+	{ "dst-mac", 1, 0, 3},
+	{ "h-length", 1, 0,  'l' },
+	{ "p-length", 1, 0,  8 },
+	{ "opcode", 1, 0,  4 },
+	{ "h-type", 1, 0,  5 },
+	{ "proto-type", 1, 0,  6 },
+	{ "in-interface", 1, 0, 'i' },
+	{ "jump", 1, 0, 'j' },
+	{ "table", 1, 0, 't' },
+	{ "match", 1, 0, 'm' },
+	{ "numeric", 0, 0, 'n' },
+	{ "out-interface", 1, 0, 'o' },
+	{ "verbose", 0, 0, 'v' },
+	{ "exact", 0, 0, 'x' },
+	{ "version", 0, 0, 'V' },
+	{ "help", 2, 0, 'h' },
+	{ "line-numbers", 0, 0, '0' },
+	{ "modprobe", 1, 0, 'M' },
+	{ "set-counters", 1, 0, 'c' },
+	{ 0 }
+};
+
+#define opts xt_params->opts
+
+extern void xtables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+struct xtables_globals arptables_globals = {
+	.option_offset		= 0,
+	.program_version	= PACKAGE_VERSION,
+	.orig_opts		= original_opts,
+	.exit_err		= xtables_exit_error,
+	.compat_rev		= nft_compatible_revision,
+};
+
+/* index relates to bit of each OPT_* value */
+static int inverse_for_options[] =
+{
+/* -n */ 0,
+/* -s */ IPT_INV_SRCIP,
+/* -d */ IPT_INV_DSTIP,
+/* -p */ 0,
+/* -j */ 0,
+/* -v */ 0,
+/* -x */ 0,
+/* -i */ IPT_INV_VIA_IN,
+/* -o */ IPT_INV_VIA_OUT,
+/*--line*/ 0,
+/* -c */ 0,
+/* -f */ 0,
+/* 2 */ IPT_INV_SRCDEVADDR,
+/* 3 */ IPT_INV_TGTDEVADDR,
+/* -l */ IPT_INV_ARPHLN,
+/* 4 */ IPT_INV_ARPOP,
+/* 5 */ IPT_INV_ARPHRD,
+/* 6 */ IPT_INV_PROTO,
+};
+
+/***********************************************/
+/* ARPTABLES SPECIFIC NEW FUNCTIONS ADDED HERE */
+/***********************************************/
+
+static int getlength_and_mask(char *from, uint8_t *to, uint8_t *mask)
+{
+	char *p, *buffer;
+	int i;
+
+	if ( (p = strrchr(from, '/')) != NULL) {
+		*p = '\0';
+		i = strtol(p+1, &buffer, 10);
+		if (*buffer != '\0' || i < 0 || i > 255)
+			return -1;
+		*mask = (uint8_t)i;
+	} else
+		*mask = 255;
+	i = strtol(from, &buffer, 10);
+	if (*buffer != '\0' || i < 0 || i > 255)
+		return -1;
+	*to = (uint8_t)i;
+	return 0;
+}
+
+static int get16_and_mask(char *from, uint16_t *to, uint16_t *mask, int base)
+{
+	char *p, *buffer;
+	int i;
+
+	if ( (p = strrchr(from, '/')) != NULL) {
+		*p = '\0';
+		i = strtol(p+1, &buffer, base);
+		if (*buffer != '\0' || i < 0 || i > 65535)
+			return -1;
+		*mask = htons((uint16_t)i);
+	} else
+		*mask = 65535;
+	i = strtol(from, &buffer, base);
+	if (*buffer != '\0' || i < 0 || i > 65535)
+		return -1;
+	*to = htons((uint16_t)i);
+	return 0;
+}
+
+/*********************************************/
+/* ARPTABLES SPECIFIC NEW FUNCTIONS END HERE */
+/*********************************************/
+
+static void
+exit_tryhelp(int status)
+{
+	fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
+		arptables_globals.program_name,
+		arptables_globals.program_version);
+	exit(status);
+}
+
+static void
+printhelp(void)
+{
+	struct xtables_target *t = NULL;
+	int i;
+
+	printf("%s v%s\n\n"
+"Usage: %s -[AD] chain rule-specification [options]\n"
+"       %s -[RI] chain rulenum rule-specification [options]\n"
+"       %s -D chain rulenum [options]\n"
+"       %s -[LFZ] [chain] [options]\n"
+"       %s -[NX] chain\n"
+"       %s -E old-chain-name new-chain-name\n"
+"       %s -P chain target [options]\n"
+"       %s -h (print this help information)\n\n",
+	       arptables_globals.program_name,
+	       arptables_globals.program_version,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name);
+	printf(
+"Commands:\n"
+"Either long or short options are allowed.\n"
+"  --append  -A chain		Append to chain\n"
+"  --delete  -D chain		Delete matching rule from chain\n"
+"  --delete  -D chain rulenum\n"
+"				Delete rule rulenum (1 = first) from chain\n"
+"  --insert  -I chain [rulenum]\n"
+"				Insert in chain as rulenum (default 1=first)\n"
+"  --replace -R chain rulenum\n"
+"				Replace rule rulenum (1 = first) in chain\n"
+"  --list    -L [chain]		List the rules in a chain or all chains\n"
+"  --flush   -F [chain]		Delete all rules in  chain or all chains\n"
+"  --zero    -Z [chain]		Zero counters in chain or all chains\n"
+"  --new     -N chain		Create a new user-defined chain\n"
+"  --delete-chain\n"
+"            -X [chain]		Delete a user-defined chain\n"
+"  --policy  -P chain target\n"
+"				Change policy on chain to target\n"
+"  --rename-chain\n"
+"            -E old-chain new-chain\n"
+"				Change chain name, (moving any references)\n"
+
+"Options:\n"
+"  --source-ip	-s [!] address[/mask]\n"
+"				source specification\n"
+"  --destination-ip -d [!] address[/mask]\n"
+"				destination specification\n"
+"  --source-mac [!] address[/mask]\n"
+"  --destination-mac [!] address[/mask]\n"
+"  --h-length   -l   length[/mask] hardware length (nr of bytes)\n"
+"  --opcode code[/mask] operation code (2 bytes)\n"
+"  --h-type   type[/mask]  hardware type (2 bytes, hexadecimal)\n"
+"  --proto-type   type[/mask]  protocol type (2 bytes)\n"
+"  --in-interface -i [!] input name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --out-interface -o [!] output name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --jump	-j target\n"
+"				target for rule (may load target extension)\n"
+"  --match	-m match\n"
+"				extended match (may load extension)\n"
+"  --numeric	-n		numeric output of addresses and ports\n"
+"  --table	-t table	table to manipulate (default: `filter')\n"
+"  --verbose	-v		verbose mode\n"
+"  --line-numbers		print line numbers when listing\n"
+"  --exact	-x		expand numbers (display exact values)\n"
+"  --modprobe=<command>		try to insert modules using this command\n"
+"  --set-counters -c PKTS BYTES	set the counter during insert/append\n"
+"[!] --version	-V		print package version.\n");
+	printf(" opcode strings: \n");
+        for (i = 0; i < NUMOPCODES; i++)
+                printf(" %d = %s\n", i + 1, arp_opcodes[i]);
+        printf(
+" hardware type string: 1 = Ethernet\n"
+" protocol type string: 0x800 = IPv4\n");
+
+	/* Print out any special helps. A user might like to be able
+		to add a --help to the commandline, and see expected
+		results. So we call help for all matches & targets */
+	for (t = xtables_targets; t; t = t->next) {
+		if (strcmp(t->name, "CLASSIFY") && strcmp(t->name, "mangle"))
+			continue;
+		printf("\n");
+		t->help();
+	}
+}
+
+static int
+check_inverse(const char option[], int *invert, int *optidx, int argc)
+{
+	if (option && strcmp(option, "!") == 0) {
+		if (*invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Multiple `!' flags not allowed");
+		*invert = true;
+		if (optidx) {
+			*optidx = *optidx+1;
+			if (argc && *optidx > argc)
+				xtables_error(PARAMETER_PROBLEM,
+					      "no argument following `!'");
+		}
+
+		return true;
+	}
+	return false;
+}
+
+static void
+set_option(unsigned int *options, unsigned int option, u_int16_t *invflg,
+	   int invert)
+{
+	if (*options & option)
+		xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed",
+			      opt2char(option));
+	*options |= option;
+
+	if (invert) {
+		unsigned int i;
+		for (i = 0; 1 << i != option; i++);
+
+		if (!inverse_for_options[i])
+			xtables_error(PARAMETER_PROBLEM,
+				      "cannot have ! before -%c",
+				      opt2char(option));
+		*invflg |= inverse_for_options[i];
+	}
+}
+
+static int
+list_entries(struct nft_handle *h, const char *chain, const char *table,
+	     int rulenum, int verbose, int numeric, int expanded,
+	     int linenumbers)
+{
+	unsigned int format;
+
+	format = FMT_OPTIONS;
+	if (!verbose)
+		format |= FMT_NOCOUNTS;
+	else
+		format |= FMT_VIA;
+
+	if (numeric)
+		format |= FMT_NUMERIC;
+
+	if (!expanded)
+		format |= FMT_KILOMEGAGIGA;
+
+	if (linenumbers)
+		format |= FMT_LINENUMBERS;
+
+	return nft_cmd_rule_list(h, chain, table, rulenum, format);
+}
+
+static int
+append_entry(struct nft_handle *h,
+	     const char *chain,
+	     const char *table,
+	     struct iptables_command_state *cs,
+	     int rulenum,
+	     unsigned int nsaddrs,
+	     const struct in_addr saddrs[],
+	     const struct in_addr smasks[],
+	     unsigned int ndaddrs,
+	     const struct in_addr daddrs[],
+	     const struct in_addr dmasks[],
+	     bool verbose, bool append)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		cs->arp.arp.src.s_addr = saddrs[i].s_addr;
+		cs->arp.arp.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			cs->arp.arp.tgt.s_addr = daddrs[j].s_addr;
+			cs->arp.arp.tmsk.s_addr = dmasks[j].s_addr;
+			if (append) {
+				ret = nft_cmd_rule_append(h, chain, table, cs, NULL,
+						      verbose);
+			} else {
+				ret = nft_cmd_rule_insert(h, chain, table, cs,
+						      rulenum, verbose);
+			}
+		}
+	}
+
+	return ret;
+}
+
+static int
+replace_entry(const char *chain,
+	      const char *table,
+	      struct iptables_command_state *cs,
+	      unsigned int rulenum,
+	      const struct in_addr *saddr,
+	      const struct in_addr *smask,
+	      const struct in_addr *daddr,
+	      const struct in_addr *dmask,
+	      bool verbose, struct nft_handle *h)
+{
+	cs->arp.arp.src.s_addr = saddr->s_addr;
+	cs->arp.arp.tgt.s_addr = daddr->s_addr;
+	cs->arp.arp.smsk.s_addr = smask->s_addr;
+	cs->arp.arp.tmsk.s_addr = dmask->s_addr;
+
+	return nft_cmd_rule_replace(h, chain, table, cs, rulenum, verbose);
+}
+
+static int
+delete_entry(const char *chain,
+	     const char *table,
+	     struct iptables_command_state *cs,
+	     unsigned int nsaddrs,
+	     const struct in_addr saddrs[],
+	     const struct in_addr smasks[],
+	     unsigned int ndaddrs,
+	     const struct in_addr daddrs[],
+	     const struct in_addr dmasks[],
+	     bool verbose, struct nft_handle *h)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		cs->arp.arp.src.s_addr = saddrs[i].s_addr;
+		cs->arp.arp.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			cs->arp.arp.tgt.s_addr = daddrs[j].s_addr;
+			cs->arp.arp.tmsk.s_addr = dmasks[j].s_addr;
+			ret = nft_cmd_rule_delete(h, chain, table, cs, verbose);
+		}
+	}
+
+	return ret;
+}
+
+int nft_init_arp(struct nft_handle *h, const char *pname)
+{
+	arptables_globals.program_name = pname;
+	if (xtables_init_all(&arptables_globals, NFPROTO_ARP) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize arptables-compat\n",
+			arptables_globals.program_name,
+			arptables_globals.program_version);
+		exit(1);
+	}
+
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensionsa();
+#endif
+
+	if (nft_init(h, NFPROTO_ARP, xtables_arp) < 0)
+		xtables_error(OTHER_PROBLEM,
+			      "Could not initialize nftables layer.");
+
+	return 0;
+}
+
+int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table,
+		  bool restore)
+{
+	struct iptables_command_state cs = {
+		.jumpto = "",
+		.arp.arp = {
+			.arhln = 6,
+			.arhln_mask = 255,
+			.arhrd = htons(ARPHRD_ETHER),
+			.arhrd_mask = 65535,
+		},
+	};
+	int invert = 0;
+	unsigned int nsaddrs = 0, ndaddrs = 0;
+	struct in_addr *saddrs = NULL, *smasks = NULL;
+	struct in_addr *daddrs = NULL, *dmasks = NULL;
+
+	int c, verbose = 0;
+	const char *chain = NULL;
+	const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;
+	const char *policy = NULL, *newname = NULL;
+	unsigned int rulenum = 0, options = 0, command = 0;
+	const char *pcnt = NULL, *bcnt = NULL;
+	int ret = 1;
+	struct xtables_target *t;
+
+	/* re-set optind to 0 in case do_command gets called
+	 * a second time */
+	optind = 0;
+
+	for (t = xtables_targets; t; t = t->next) {
+		t->tflags = 0;
+		t->used = 0;
+	}
+
+	/* Suppress error messages: we may add new options if we
+	    demand-load a protocol. */
+	opterr = 0;
+
+	opts = xt_params->orig_opts;
+	while ((c = getopt_long(argc, argv,
+	   "-A:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:l:i:vnt:m:c:",
+					   opts, NULL)) != -1) {
+		switch (c) {
+			/*
+			 * Command selection
+			 */
+		case 'A':
+			add_command(&command, CMD_APPEND, CMD_NONE,
+				    invert);
+			chain = optarg;
+			break;
+
+		case 'D':
+			add_command(&command, CMD_DELETE, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv)) {
+				rulenum = parse_rulenumber(argv[optind++]);
+				command = CMD_DELETE_NUM;
+			}
+			break;
+
+		case 'R':
+			add_command(&command, CMD_REPLACE, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires a rule number",
+					      cmd2char(CMD_REPLACE));
+			break;
+
+		case 'I':
+			add_command(&command, CMD_INSERT, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			else rulenum = 1;
+			break;
+
+		case 'L':
+			add_command(&command, CMD_LIST, CMD_ZERO,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'F':
+			add_command(&command, CMD_FLUSH, CMD_NONE,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'Z':
+			add_command(&command, CMD_ZERO, CMD_LIST,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'N':
+			if (optarg && *optarg == '-')
+				xtables_error(PARAMETER_PROBLEM,
+					      "chain name not allowed to start "
+					      "with `-'\n");
+			if (xtables_find_target(optarg, XTF_TRY_LOAD))
+				xtables_error(PARAMETER_PROBLEM,
+						"chain name may not clash "
+						"with target name\n");
+			add_command(&command, CMD_NEW_CHAIN, CMD_NONE,
+				    invert);
+			chain = optarg;
+			break;
+
+		case 'X':
+			add_command(&command, CMD_DELETE_CHAIN, CMD_NONE,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'E':
+			add_command(&command, CMD_RENAME_CHAIN, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				newname = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires old-chain-name and "
+					      "new-chain-name",
+					      cmd2char(CMD_RENAME_CHAIN));
+			break;
+
+		case 'P':
+			add_command(&command, CMD_SET_POLICY, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				policy = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires a chain and a policy",
+					      cmd2char(CMD_SET_POLICY));
+			break;
+
+		case 'h':
+			if (!optarg)
+				optarg = argv[optind];
+
+			printhelp();
+			command = CMD_NONE;
+			break;
+		case 's':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_SOURCE, &cs.arp.arp.invflags,
+				   invert);
+			shostnetworkmask = argv[optind-1];
+			break;
+
+		case 'd':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_DESTINATION, &cs.arp.arp.invflags,
+				   invert);
+			dhostnetworkmask = argv[optind-1];
+			break;
+
+		case 2:/* src-mac */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_S_MAC, &cs.arp.arp.invflags,
+				   invert);
+			if (xtables_parse_mac_and_mask(argv[optind - 1],
+			    cs.arp.arp.src_devaddr.addr, cs.arp.arp.src_devaddr.mask))
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified "
+						"source mac");
+			break;
+
+		case 3:/* dst-mac */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_D_MAC, &cs.arp.arp.invflags,
+				   invert);
+
+			if (xtables_parse_mac_and_mask(argv[optind - 1],
+			    cs.arp.arp.tgt_devaddr.addr, cs.arp.arp.tgt_devaddr.mask))
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified "
+						"destination mac");
+			break;
+
+		case 'l':/* hardware length */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_H_LENGTH, &cs.arp.arp.invflags,
+				   invert);
+			getlength_and_mask(argv[optind - 1], &cs.arp.arp.arhln,
+					   &cs.arp.arp.arhln_mask);
+
+			if (cs.arp.arp.arhln != 6) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "Only harware address length of"
+					      " 6 is supported currently.");
+			}
+
+			break;
+
+		case 8: /* was never supported, not even in arptables-legacy */
+			xtables_error(PARAMETER_PROBLEM, "not supported");
+		case 4:/* opcode */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_OPCODE, &cs.arp.arp.invflags,
+				   invert);
+			if (get16_and_mask(argv[optind - 1], &cs.arp.arp.arpop,
+					   &cs.arp.arp.arpop_mask, 10)) {
+				int i;
+
+				for (i = 0; i < NUMOPCODES; i++)
+					if (!strcasecmp(arp_opcodes[i], optarg))
+						break;
+				if (i == NUMOPCODES)
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified opcode");
+				cs.arp.arp.arpop = htons(i+1);
+			}
+			break;
+
+		case 5:/* h-type */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_H_TYPE, &cs.arp.arp.invflags,
+				   invert);
+			if (get16_and_mask(argv[optind - 1], &cs.arp.arp.arhrd,
+					   &cs.arp.arp.arhrd_mask, 16)) {
+				if (strcasecmp(argv[optind-1], "Ethernet"))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified hardware type");
+				cs.arp.arp.arhrd = htons(1);
+			}
+			break;
+
+		case 6:/* proto-type */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_P_TYPE, &cs.arp.arp.invflags,
+				   invert);
+			if (get16_and_mask(argv[optind - 1], &cs.arp.arp.arpro,
+					   &cs.arp.arp.arpro_mask, 0)) {
+				if (strcasecmp(argv[optind-1], "ipv4"))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified protocol type");
+				cs.arp.arp.arpro = htons(0x800);
+			}
+			break;
+
+		case 'j':
+			set_option(&options, OPT_JUMP, &cs.arp.arp.invflags,
+				   invert);
+			command_jump(&cs, optarg);
+			break;
+
+		case 'i':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_VIANAMEIN, &cs.arp.arp.invflags,
+				   invert);
+			xtables_parse_interface(argv[optind-1],
+						cs.arp.arp.iniface,
+						cs.arp.arp.iniface_mask);
+			break;
+
+		case 'o':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_VIANAMEOUT, &cs.arp.arp.invflags,
+				   invert);
+			xtables_parse_interface(argv[optind-1],
+						cs.arp.arp.outiface,
+						cs.arp.arp.outiface_mask);
+			break;
+
+		case 'v':
+			if (!verbose)
+				set_option(&options, OPT_VERBOSE,
+					   &cs.arp.arp.invflags, invert);
+			verbose++;
+			break;
+
+		case 'm': /* ignored by arptables-legacy */
+			break;
+		case 'n':
+			set_option(&options, OPT_NUMERIC, &cs.arp.arp.invflags,
+				   invert);
+			break;
+
+		case 't':
+			if (invert)
+				xtables_error(PARAMETER_PROBLEM,
+					      "unexpected ! flag before --table");
+			/* ignore this option.
+			 * arptables-legacy parses it, but libarptc doesn't use it.
+			 * arptables only has a 'filter' table anyway.
+			 */
+			break;
+
+		case 'V':
+			if (invert)
+				printf("Not %s ;-)\n", arptables_globals.program_version);
+			else
+				printf("%s v%s (nf_tables)\n",
+				       arptables_globals.program_name,
+				       arptables_globals.program_version);
+			exit(0);
+
+		case '0':
+			set_option(&options, OPT_LINENUMBERS, &cs.arp.arp.invflags,
+				   invert);
+			break;
+
+		case 'M':
+			//modprobe = optarg;
+			break;
+
+		case 'c':
+
+			set_option(&options, OPT_COUNTERS, &cs.arp.arp.invflags,
+				   invert);
+			pcnt = optarg;
+			if (xs_has_arg(argc, argv))
+				bcnt = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires packet and byte counter",
+					      opt2char(OPT_COUNTERS));
+
+			if (sscanf(pcnt, "%llu", &cs.arp.counters.pcnt) != 1)
+			xtables_error(PARAMETER_PROBLEM,
+				"-%c packet counter not numeric",
+				opt2char(OPT_COUNTERS));
+
+			if (sscanf(bcnt, "%llu", &cs.arp.counters.bcnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c byte counter not numeric",
+					      opt2char(OPT_COUNTERS));
+
+			break;
+
+
+		case 1: /* non option */
+			if (optarg[0] == '!' && optarg[1] == '\0') {
+				if (invert)
+					xtables_error(PARAMETER_PROBLEM,
+						      "multiple consecutive ! not"
+						      " allowed");
+				invert = true;
+				optarg[0] = '\0';
+				continue;
+			}
+			printf("Bad argument `%s'\n", optarg);
+			exit_tryhelp(2);
+
+		default:
+			if (cs.target) {
+				xtables_option_tpcall(c, argv,
+						      invert, cs.target, &cs.arp);
+			}
+			break;
+		}
+		invert = false;
+	}
+
+	if (cs.target)
+		xtables_option_tfcall(cs.target);
+
+	if (optind < argc)
+		xtables_error(PARAMETER_PROBLEM,
+			      "unknown arguments found on commandline");
+	if (invert)
+		xtables_error(PARAMETER_PROBLEM,
+			      "nothing appropriate following !");
+
+	if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) {
+		if (!(options & OPT_DESTINATION))
+			dhostnetworkmask = "0.0.0.0/0";
+		if (!(options & OPT_SOURCE))
+			shostnetworkmask = "0.0.0.0/0";
+	}
+
+	if (shostnetworkmask)
+		xtables_ipparse_multiple(shostnetworkmask, &saddrs,
+					 &smasks, &nsaddrs);
+
+	if (dhostnetworkmask)
+		xtables_ipparse_multiple(dhostnetworkmask, &daddrs,
+					 &dmasks, &ndaddrs);
+
+	if ((nsaddrs > 1 || ndaddrs > 1) &&
+	    (cs.arp.arp.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))
+		xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple"
+				" source or destination IP addresses");
+
+	if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1))
+		xtables_error(PARAMETER_PROBLEM, "Replacement rule does not "
+						 "specify a unique address");
+
+	if (chain && strlen(chain) > ARPT_FUNCTION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+				"chain name `%s' too long (must be under %i chars)",
+				chain, ARPT_FUNCTION_MAXNAMELEN);
+
+	if (command == CMD_APPEND
+	    || command == CMD_DELETE
+	    || command == CMD_INSERT
+	    || command == CMD_REPLACE) {
+		if (strcmp(chain, "PREROUTING") == 0
+		    || strcmp(chain, "INPUT") == 0) {
+			/* -o not valid with incoming packets. */
+			if (options & OPT_VIANAMEOUT)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Can't use -%c with %s\n",
+					      opt2char(OPT_VIANAMEOUT),
+					      chain);
+		}
+
+		if (strcmp(chain, "POSTROUTING") == 0
+		    || strcmp(chain, "OUTPUT") == 0) {
+			/* -i not valid with outgoing packets */
+			if (options & OPT_VIANAMEIN)
+				xtables_error(PARAMETER_PROBLEM,
+						"Can't use -%c with %s\n",
+						opt2char(OPT_VIANAMEIN),
+						chain);
+		}
+	}
+
+	switch (command) {
+	case CMD_APPEND:
+		ret = append_entry(h, chain, *table, &cs, 0,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   options&OPT_VERBOSE, true);
+		break;
+	case CMD_DELETE:
+		ret = delete_entry(chain, *table, &cs,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   options&OPT_VERBOSE, h);
+		break;
+	case CMD_DELETE_NUM:
+		ret = nft_cmd_rule_delete_num(h, chain, *table, rulenum - 1, verbose);
+		break;
+	case CMD_REPLACE:
+		ret = replace_entry(chain, *table, &cs, rulenum - 1,
+				    saddrs, smasks, daddrs, dmasks,
+				    options&OPT_VERBOSE, h);
+		break;
+	case CMD_INSERT:
+		ret = append_entry(h, chain, *table, &cs, rulenum - 1,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   options&OPT_VERBOSE, false);
+		break;
+	case CMD_LIST:
+		ret = list_entries(h, chain, *table,
+				   rulenum,
+				   options&OPT_VERBOSE,
+				   options&OPT_NUMERIC,
+				   /*options&OPT_EXPANDED*/0,
+				   options&OPT_LINENUMBERS);
+		break;
+	case CMD_FLUSH:
+		ret = nft_cmd_rule_flush(h, chain, *table, options & OPT_VERBOSE);
+		break;
+	case CMD_ZERO:
+		ret = nft_cmd_chain_zero_counters(h, chain, *table,
+					      options & OPT_VERBOSE);
+		break;
+	case CMD_LIST|CMD_ZERO:
+		ret = list_entries(h, chain, *table, rulenum,
+				   options&OPT_VERBOSE,
+				   options&OPT_NUMERIC,
+				   /*options&OPT_EXPANDED*/0,
+				   options&OPT_LINENUMBERS);
+		if (ret)
+			ret = nft_cmd_chain_zero_counters(h, chain, *table,
+						      options & OPT_VERBOSE);
+		break;
+	case CMD_NEW_CHAIN:
+		ret = nft_cmd_chain_user_add(h, chain, *table);
+		break;
+	case CMD_DELETE_CHAIN:
+		ret = nft_cmd_chain_user_del(h, chain, *table,
+					 options & OPT_VERBOSE);
+		break;
+	case CMD_RENAME_CHAIN:
+		ret = nft_cmd_chain_user_rename(h, chain, *table, newname);
+		break;
+	case CMD_SET_POLICY:
+		ret = nft_cmd_chain_set(h, *table, chain, policy, NULL);
+		if (ret < 0)
+			xtables_error(PARAMETER_PROBLEM, "Wrong policy `%s'\n",
+				      policy);
+		break;
+	case CMD_NONE:
+		break;
+	default:
+		/* We should never reach this... */
+		exit_tryhelp(2);
+	}
+
+	free(saddrs);
+	free(smasks);
+	free(daddrs);
+	free(dmasks);
+
+	nft_clear_iptables_command_state(&cs);
+	xtables_free_opts(1);
+
+/*	if (verbose > 1)
+		dump_entries(*handle);*/
+
+	return ret;
+}
diff --git a/iptables/xtables-eb-standalone.c b/iptables/xtables-eb-standalone.c
new file mode 100644
index 0000000..181cf2d
--- /dev/null
+++ b/iptables/xtables-eb-standalone.c
@@ -0,0 +1,62 @@
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * Based on the ipchains code by Paul Russell and Michael Neuling
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	arptables -- IP firewall administration for kernels with
+ *	firewall table (aimed for the 2.3 kernels)
+ *
+ *	See the accompanying manual page arptables(8) for information
+ *	about proper usage of this program.
+ *
+ *	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 2 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, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <xtables.h>
+#include <iptables.h>
+#include "nft.h"
+
+#include "xtables-multi.h"
+
+int xtables_eb_main(int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h;
+
+	nft_init_eb(&h, "ebtables");
+
+	ret = do_commandeb(&h, argc, argv, &table, false);
+	if (ret)
+		ret = nft_bridge_commit(&h);
+
+	nft_fini_eb(&h);
+
+	if (!ret)
+		fprintf(stderr, "ebtables: %s\n", nft_strerror(errno));
+
+	exit(!ret);
+}
diff --git a/iptables/xtables-eb-translate.c b/iptables/xtables-eb-translate.c
new file mode 100644
index 0000000..83ae77c
--- /dev/null
+++ b/iptables/xtables-eb-translate.c
@@ -0,0 +1,580 @@
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <string.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <iptables.h>
+#include <xtables.h>
+
+#include <netinet/ether.h>
+
+#include <linux/netfilter_bridge.h>
+#include <linux/netfilter/nf_tables.h>
+#include <libiptc/libxtc.h>
+
+#include "xshared.h"
+#include "xtables-multi.h"
+#include "nft-bridge.h"
+#include "nft.h"
+#include "nft-shared.h"
+/*
+ * From include/ebtables_u.h
+ */
+#define EXEC_STYLE_PRG    0
+#define EXEC_STYLE_DAEMON 1
+
+#define ebt_check_option2(flags, mask) EBT_CHECK_OPTION(flags, mask)
+
+extern int ebt_invert;
+
+static int ebt_check_inverse2(const char option[], int argc, char **argv)
+{
+	if (!option)
+		return ebt_invert;
+	if (strcmp(option, "!") == 0) {
+		if (ebt_invert == 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Double use of '!' not allowed");
+		if (optind >= argc)
+			optarg = NULL;
+		else
+			optarg = argv[optind];
+		optind++;
+		ebt_invert = 1;
+		return 1;
+	}
+	return ebt_invert;
+}
+
+/*
+ * Glue code to use libxtables
+ */
+static int parse_rule_number(const char *rule)
+{
+	unsigned int rule_nr;
+
+	if (!xtables_strtoui(rule, NULL, &rule_nr, 1, INT_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Invalid rule number `%s'", rule);
+
+	return rule_nr;
+}
+
+static int get_current_chain(const char *chain)
+{
+	if (strcmp(chain, "PREROUTING") == 0)
+		return NF_BR_PRE_ROUTING;
+	else if (strcmp(chain, "INPUT") == 0)
+		return NF_BR_LOCAL_IN;
+	else if (strcmp(chain, "FORWARD") == 0)
+		return NF_BR_FORWARD;
+	else if (strcmp(chain, "OUTPUT") == 0)
+		return NF_BR_LOCAL_OUT;
+	else if (strcmp(chain, "POSTROUTING") == 0)
+		return NF_BR_POST_ROUTING;
+
+	return -1;
+}
+
+/*
+ * The original ebtables parser
+ */
+
+/* Checks whether a command has already been specified */
+#define OPT_COMMANDS (flags & OPT_COMMAND || flags & OPT_ZERO)
+
+#define OPT_COMMAND	0x01
+#define OPT_TABLE	0x02
+#define OPT_IN		0x04
+#define OPT_OUT		0x08
+#define OPT_JUMP	0x10
+#define OPT_PROTOCOL	0x20
+#define OPT_SOURCE	0x40
+#define OPT_DEST	0x80
+#define OPT_ZERO	0x100
+#define OPT_LOGICALIN	0x200
+#define OPT_LOGICALOUT	0x400
+#define OPT_COUNT	0x1000 /* This value is also defined in libebtc.c */
+
+/* Default command line options. Do not mess around with the already
+ * assigned numbers unless you know what you are doing */
+extern struct option ebt_original_options[];
+extern struct xtables_globals ebtables_globals;
+#define opts ebtables_globals.opts
+#define prog_name ebtables_globals.program_name
+#define prog_vers ebtables_globals.program_version
+
+static void print_help(void)
+{
+	fprintf(stderr, "%s: Translate ebtables command to nft syntax\n"
+			"no side effects occur, the translated command is written "
+			"to standard output.\n"
+			"A '#' followed by input means no translation "
+			"is available.\n", prog_name);
+	exit(0);
+}
+
+static int parse_rule_range(const char *argv, int *rule_nr, int *rule_nr_end)
+{
+	char *colon = strchr(argv, ':'), *buffer;
+
+	if (colon) {
+		*colon = '\0';
+		if (*(colon + 1) == '\0')
+			*rule_nr_end = -1; /* Until the last rule */
+		else {
+			*rule_nr_end = strtol(colon + 1, &buffer, 10);
+			if (*buffer != '\0' || *rule_nr_end == 0)
+				return -1;
+		}
+	}
+	if (colon == argv)
+		*rule_nr = 1; /* Beginning with the first rule */
+	else {
+		*rule_nr = strtol(argv, &buffer, 10);
+		if (*buffer != '\0' || *rule_nr == 0)
+			return -1;
+	}
+	if (!colon)
+		*rule_nr_end = *rule_nr;
+	return 0;
+}
+
+static void ebtables_parse_interface(const char *arg, char *vianame)
+{
+	unsigned char mask[IFNAMSIZ];
+	char *c;
+
+	xtables_parse_interface(arg, vianame, mask);
+
+	if ((c = strchr(vianame, '+'))) {
+		if (*(c + 1) != '\0')
+			xtables_error(PARAMETER_PROBLEM,
+				      "Spurious characters after '+' wildcard");
+	}
+}
+
+static void print_ebt_cmd(int argc, char *argv[])
+{
+	int i;
+
+	printf("# ");
+	for (i = 1; i < argc; i++)
+		printf("%s ", argv[i]);
+
+	printf("\n");
+}
+
+static int nft_rule_eb_xlate_add(struct nft_handle *h, const struct nft_xt_cmd_parse *p,
+				 const struct iptables_command_state *cs, bool append)
+{
+	struct xt_xlate *xl = xt_xlate_alloc(10240);
+	int ret;
+
+	if (append) {
+		xt_xlate_add(xl, "add rule bridge %s %s ", p->table, p->chain);
+	} else {
+		xt_xlate_add(xl, "insert rule bridge %s %s ", p->table, p->chain);
+	}
+
+	ret = h->ops->xlate(cs, xl);
+	if (ret)
+		printf("%s\n", xt_xlate_get(xl));
+
+	xt_xlate_free(xl);
+	return ret;
+}
+
+/* We use exec_style instead of #ifdef's because ebtables.so is a shared object. */
+static int do_commandeb_xlate(struct nft_handle *h, int argc, char *argv[], char **table)
+{
+	char *buffer;
+	int c, i;
+	int rule_nr = 0;
+	int rule_nr_end = 0;
+	int ret = 0;
+	unsigned int flags = 0;
+	struct iptables_command_state cs = {
+		.argv		= argv,
+		.eb.bitmask	= EBT_NOPROTO,
+	};
+	char command = 'h';
+	const char *chain = NULL;
+	int exec_style = EXEC_STYLE_PRG;
+	int selected_chain = -1;
+	struct xtables_rule_match *xtrm_i;
+	struct ebt_match *match;
+	struct nft_xt_cmd_parse p = {
+		.table          = *table,
+        };
+
+	/* prevent getopt to spoil our error reporting */
+	opterr = false;
+
+	printf("nft ");
+	/* Getopt saves the day */
+	while ((c = getopt_long(argc, argv,
+	   "-A:D:I:N:E:X::L::Z::F::P:Vhi:o:j:c:p:s:d:t:M:", opts, NULL)) != -1) {
+		cs.c = c;
+		cs.invert = ebt_invert;
+		switch (c) {
+		case 'A': /* Add a rule */
+		case 'D': /* Delete a rule */
+		case 'P': /* Define policy */
+		case 'I': /* Insert a rule */
+		case 'N': /* Make a user defined chain */
+		case 'E': /* Rename chain */
+		case 'X': /* Delete chain */
+			/* We allow -N chainname -P policy */
+			/* XXX: Not in ebtables-compat */
+			if (command == 'N' && c == 'P') {
+				command = c;
+				optind--; /* No table specified */
+				break;
+			}
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+			command = c;
+			chain = optarg;
+			selected_chain = get_current_chain(chain);
+			p.chain = chain;
+			flags |= OPT_COMMAND;
+
+			if (c == 'N') {
+				printf("add chain bridge %s %s\n", p.table, p.chain);
+				ret = 1;
+				break;
+			} else if (c == 'X') {
+				printf("delete chain bridge %s %s\n", p.table, p.chain);
+				ret = 1;
+				break;
+			}
+
+			if (c == 'E') {
+				break;
+			} else if (c == 'D' && optind < argc && (argv[optind][0] != '-' || (argv[optind][1] >= '0' && argv[optind][1] <= '9'))) {
+				if (optind != argc - 1)
+					xtables_error(PARAMETER_PROBLEM,
+							 "No extra options allowed with -D start_nr[:end_nr]");
+				if (parse_rule_range(argv[optind], &rule_nr, &rule_nr_end))
+					xtables_error(PARAMETER_PROBLEM,
+							 "Problem with the specified rule number(s) '%s'", argv[optind]);
+				optind++;
+			} else if (c == 'I') {
+				if (optind >= argc || (argv[optind][0] == '-' && (argv[optind][1] < '0' || argv[optind][1] > '9')))
+					rule_nr = 1;
+				else {
+					rule_nr = parse_rule_number(argv[optind]);
+					optind++;
+				}
+				p.rulenum = rule_nr;
+			} else if (c == 'P') {
+				break;
+			}
+			break;
+		case 'L': /* List */
+			printf("list table bridge %s\n", p.table);
+			ret = 1;
+			break;
+		case 'F': /* Flush */
+			if (p.chain) {
+				printf("flush chain bridge %s %s\n", p.table, p.chain);
+			} else {
+				printf("flush table bridge %s\n", p.table);
+			}
+			ret = 1;
+			break;
+		case 'Z': /* Zero counters */
+			if (c == 'Z') {
+				if ((flags & OPT_ZERO) || (flags & OPT_COMMAND && command != 'L'))
+print_zero:
+					xtables_error(PARAMETER_PROBLEM,
+						      "Command -Z only allowed together with command -L");
+				flags |= OPT_ZERO;
+			} else {
+				if (flags & OPT_COMMAND)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Multiple commands are not allowed");
+				command = c;
+				flags |= OPT_COMMAND;
+				if (flags & OPT_ZERO && c != 'L')
+					goto print_zero;
+			}
+			break;
+		case 'V': /* Version */
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+			if (exec_style == EXEC_STYLE_DAEMON)
+				xtables_error(PARAMETER_PROBLEM,
+					      "%s %s\n", prog_name, prog_vers);
+			printf("%s %s\n", prog_name, prog_vers);
+			exit(0);
+		case 'h':
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+			print_help();
+			break;
+		case 't': /* Table */
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Please put the -t option first");
+			ebt_check_option2(&flags, OPT_TABLE);
+			if (strlen(optarg) > EBT_TABLE_MAXNAMELEN - 1)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Table name length cannot exceed %d characters",
+					      EBT_TABLE_MAXNAMELEN - 1);
+			*table = optarg;
+			p.table = optarg;
+			break;
+		case 'i': /* Input interface */
+		case 2  : /* Logical input interface */
+		case 'o': /* Output interface */
+		case 3  : /* Logical output interface */
+		case 'j': /* Target */
+		case 'p': /* Net family protocol */
+		case 's': /* Source mac */
+		case 'd': /* Destination mac */
+		case 'c': /* Set counters */
+			if (!OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "No command specified");
+			if (command != 'A' && command != 'D' && command != 'I')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Command and option do not match");
+			if (c == 'i') {
+				ebt_check_option2(&flags, OPT_IN);
+				if (selected_chain > 2 && selected_chain < NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use -i only in INPUT, FORWARD, PREROUTING and BROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IIN;
+
+				ebtables_parse_interface(optarg, cs.eb.in);
+				break;
+			} else if (c == 2) {
+				ebt_check_option2(&flags, OPT_LOGICALIN);
+				if (selected_chain > 2 && selected_chain < NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use --logical-in only in INPUT, FORWARD, PREROUTING and BROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ILOGICALIN;
+
+				ebtables_parse_interface(optarg, cs.eb.logical_in);
+				break;
+			} else if (c == 'o') {
+				ebt_check_option2(&flags, OPT_OUT);
+				if (selected_chain < 2 || selected_chain == NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use -o only in OUTPUT, FORWARD and POSTROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IOUT;
+
+				ebtables_parse_interface(optarg, cs.eb.out);
+				break;
+			} else if (c == 3) {
+				ebt_check_option2(&flags, OPT_LOGICALOUT);
+				if (selected_chain < 2 || selected_chain == NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use --logical-out only in OUTPUT, FORWARD and POSTROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ILOGICALOUT;
+
+				ebtables_parse_interface(optarg, cs.eb.logical_out);
+				break;
+			} else if (c == 'j') {
+				ebt_check_option2(&flags, OPT_JUMP);
+				command_jump(&cs, optarg);
+				break;
+			} else if (c == 's') {
+				ebt_check_option2(&flags, OPT_SOURCE);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ISOURCE;
+
+				if (xtables_parse_mac_and_mask(optarg,
+							       cs.eb.sourcemac,
+							       cs.eb.sourcemsk))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified source mac '%s'", optarg);
+				cs.eb.bitmask |= EBT_SOURCEMAC;
+				break;
+			} else if (c == 'd') {
+				ebt_check_option2(&flags, OPT_DEST);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IDEST;
+
+				if (xtables_parse_mac_and_mask(optarg,
+							       cs.eb.destmac,
+							       cs.eb.destmsk))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified destination mac '%s'", optarg);
+				cs.eb.bitmask |= EBT_DESTMAC;
+				break;
+			} else if (c == 'c') {
+				ebt_check_option2(&flags, OPT_COUNT);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					xtables_error(PARAMETER_PROBLEM,
+						      "Unexpected '!' after -c");
+				if (optind >= argc || optarg[0] == '-' || argv[optind][0] == '-')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Option -c needs 2 arguments");
+
+				cs.counters.pcnt = strtoull(optarg, &buffer, 10);
+				if (*buffer != '\0')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Packet counter '%s' invalid",
+						      optarg);
+				cs.counters.bcnt = strtoull(argv[optind], &buffer, 10);
+				if (*buffer != '\0')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Packet counter '%s' invalid",
+						      argv[optind]);
+				optind++;
+				break;
+			}
+			ebt_check_option2(&flags, OPT_PROTOCOL);
+			if (ebt_check_inverse2(optarg, argc, argv))
+				cs.eb.invflags |= EBT_IPROTO;
+
+			cs.eb.bitmask &= ~((unsigned int)EBT_NOPROTO);
+			i = strtol(optarg, &buffer, 16);
+			if (*buffer == '\0' && (i < 0 || i > 0xFFFF))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Problem with the specified protocol");
+			if (*buffer != '\0') {
+				struct xt_ethertypeent *ent;
+
+				if (!strcasecmp(optarg, "LENGTH")) {
+					cs.eb.bitmask |= EBT_802_3;
+					break;
+				}
+				ent = xtables_getethertypebyname(optarg);
+				if (!ent)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Problem with the specified Ethernet protocol '%s', perhaps "XT_PATH_ETHERTYPES " is missing", optarg);
+				cs.eb.ethproto = ent->e_ethertype;
+			} else
+				cs.eb.ethproto = i;
+
+			if (cs.eb.ethproto < 0x0600)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Sorry, protocols have values above or equal to 0x0600");
+			break;
+		case 4  : /* Lc */
+			ebt_check_option2(&flags, LIST_C);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Lc with -L");
+			flags |= LIST_C;
+			break;
+		case 5  : /* Ln */
+			ebt_check_option2(&flags, LIST_N);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Ln with -L");
+			if (flags & LIST_X)
+				xtables_error(PARAMETER_PROBLEM,
+					      "--Lx is not compatible with --Ln");
+			flags |= LIST_N;
+			break;
+		case 6  : /* Lx */
+			ebt_check_option2(&flags, LIST_X);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Lx with -L");
+			if (flags & LIST_N)
+				xtables_error(PARAMETER_PROBLEM,
+					      "--Lx is not compatible with --Ln");
+			flags |= LIST_X;
+			break;
+		case 12 : /* Lmac2 */
+			ebt_check_option2(&flags, LIST_MAC2);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					       "Use --Lmac2 with -L");
+			flags |= LIST_MAC2;
+			break;
+		case 1 :
+			if (!strcmp(optarg, "!"))
+				ebt_check_inverse2(optarg, argc, argv);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "Bad argument : '%s'", optarg);
+			/* ebt_ebt_check_inverse2() did optind++ */
+			optind--;
+			continue;
+		default:
+			ebt_check_inverse2(optarg, argc, argv);
+
+			if (ebt_command_default(&cs))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Unknown argument: '%s'",
+					      argv[optind - 1]);
+
+			if (command != 'A' && command != 'I' &&
+			    command != 'D')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Extensions only for -A, -I, -D");
+		}
+		ebt_invert = 0;
+	}
+
+	/* Do the final checks */
+	if (command == 'A' || command == 'I' || command == 'D') {
+		for (xtrm_i = cs.matches; xtrm_i; xtrm_i = xtrm_i->next)
+			xtables_option_mfcall(xtrm_i->match);
+
+		for (match = cs.match_list; match; match = match->next) {
+			if (match->ismatch)
+				continue;
+
+			xtables_option_tfcall(match->u.watcher);
+		}
+
+		if (cs.target != NULL)
+			xtables_option_tfcall(cs.target);
+	}
+
+	cs.eb.ethproto = htons(cs.eb.ethproto);
+
+	if (command == 'P') {
+		return 0;
+	} else if (command == 'A') {
+		ret = nft_rule_eb_xlate_add(h, &p, &cs, true);
+		if (!ret)
+			print_ebt_cmd(argc, argv);
+	} else if (command == 'I') {
+		ret = nft_rule_eb_xlate_add(h, &p, &cs, false);
+		if (!ret)
+			print_ebt_cmd(argc, argv);
+	}
+
+	ebt_cs_clean(&cs);
+	return ret;
+}
+
+static int dummy_compat_rev(const char *name, uint8_t rev, int opt)
+{
+	return 1;
+}
+
+int xtables_eb_xlate_main(int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h;
+
+	nft_init_eb(&h, argv[0]);
+	ebtables_globals.compat_rev = dummy_compat_rev;
+
+	ret = do_commandeb_xlate(&h, argc, argv, &table);
+	if (!ret)
+		fprintf(stderr, "Translation not implemented\n");
+
+	exit(!ret);
+}
+
diff --git a/iptables/xtables-eb.c b/iptables/xtables-eb.c
new file mode 100644
index 0000000..cfa9317
--- /dev/null
+++ b/iptables/xtables-eb.c
@@ -0,0 +1,1238 @@
+/*
+ * ebtables.c, v2.0 July 2002
+ *
+ * Author: Bart De Schuymer
+ *
+ *  This code was stongly inspired on the iptables code which is
+ *  Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "config.h"
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <iptables.h>
+#include <xtables.h>
+
+#include <linux/netfilter_bridge.h>
+#include <linux/netfilter/nf_tables.h>
+#include <libiptc/libxtc.h>
+#include "xshared.h"
+#include "nft.h"
+#include "nft-bridge.h"
+
+/*
+ * From include/ebtables_u.h
+ */
+#define ebt_check_option2(flags, mask) EBT_CHECK_OPTION(flags, mask)
+
+/*
+ * From useful_functions.c
+ */
+
+/* 0: default
+ * 1: the inverse '!' of the option has already been specified */
+int ebt_invert = 0;
+
+static int ebt_check_inverse2(const char option[], int argc, char **argv)
+{
+	if (!option)
+		return ebt_invert;
+	if (strcmp(option, "!") == 0) {
+		if (ebt_invert == 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Double use of '!' not allowed");
+		if (optind >= argc)
+			optarg = NULL;
+		else
+			optarg = argv[optind];
+		optind++;
+		ebt_invert = 1;
+		return 1;
+	}
+	return ebt_invert;
+}
+
+/*
+ * Glue code to use libxtables
+ */
+static int parse_rule_number(const char *rule)
+{
+	unsigned int rule_nr;
+
+	if (!xtables_strtoui(rule, NULL, &rule_nr, 1, INT_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Invalid rule number `%s'", rule);
+
+	return rule_nr;
+}
+
+static int
+append_entry(struct nft_handle *h,
+	     const char *chain,
+	     const char *table,
+	     struct iptables_command_state *cs,
+	     int rule_nr,
+	     bool verbose, bool append)
+{
+	int ret = 1;
+
+	if (append)
+		ret = nft_cmd_rule_append(h, chain, table, cs, NULL, verbose);
+	else
+		ret = nft_cmd_rule_insert(h, chain, table, cs, rule_nr, verbose);
+
+	return ret;
+}
+
+static int
+delete_entry(struct nft_handle *h,
+	     const char *chain,
+	     const char *table,
+	     struct iptables_command_state *cs,
+	     int rule_nr,
+	     int rule_nr_end,
+	     bool verbose)
+{
+	int ret = 1;
+
+	if (rule_nr == -1)
+		ret = nft_cmd_rule_delete(h, chain, table, cs, verbose);
+	else {
+		do {
+			ret = nft_cmd_rule_delete_num(h, chain, table,
+						  rule_nr, verbose);
+			rule_nr++;
+		} while (rule_nr < rule_nr_end);
+	}
+
+	return ret;
+}
+
+int ebt_get_current_chain(const char *chain)
+{
+	if (!chain)
+		return -1;
+
+	if (strcmp(chain, "PREROUTING") == 0)
+		return NF_BR_PRE_ROUTING;
+	else if (strcmp(chain, "INPUT") == 0)
+		return NF_BR_LOCAL_IN;
+	else if (strcmp(chain, "FORWARD") == 0)
+		return NF_BR_FORWARD;
+	else if (strcmp(chain, "OUTPUT") == 0)
+		return NF_BR_LOCAL_OUT;
+	else if (strcmp(chain, "POSTROUTING") == 0)
+		return NF_BR_POST_ROUTING;
+
+	/* placeholder for user defined chain */
+	return NF_BR_NUMHOOKS;
+}
+
+/*
+ * The original ebtables parser
+ */
+
+/* Checks whether a command has already been specified */
+#define OPT_COMMANDS (flags & OPT_COMMAND || flags & OPT_ZERO)
+
+#define OPT_COMMAND	0x01
+#define OPT_TABLE	0x02
+#define OPT_IN		0x04
+#define OPT_OUT		0x08
+#define OPT_JUMP	0x10
+#define OPT_PROTOCOL	0x20
+#define OPT_SOURCE	0x40
+#define OPT_DEST	0x80
+#define OPT_ZERO	0x100
+#define OPT_LOGICALIN	0x200
+#define OPT_LOGICALOUT	0x400
+#define OPT_KERNELDATA	0x800 /* This value is also defined in ebtablesd.c */
+#define OPT_COUNT	0x1000 /* This value is also defined in libebtc.c */
+#define OPT_CNT_INCR	0x2000 /* This value is also defined in libebtc.c */
+#define OPT_CNT_DECR	0x4000 /* This value is also defined in libebtc.c */
+
+/* Default command line options. Do not mess around with the already
+ * assigned numbers unless you know what you are doing */
+struct option ebt_original_options[] =
+{
+	{ "append"         , required_argument, 0, 'A' },
+	{ "insert"         , required_argument, 0, 'I' },
+	{ "delete"         , required_argument, 0, 'D' },
+	{ "list"           , optional_argument, 0, 'L' },
+	{ "Lc"             , no_argument      , 0, 4   },
+	{ "Ln"             , no_argument      , 0, 5   },
+	{ "Lx"             , no_argument      , 0, 6   },
+	{ "Lmac2"          , no_argument      , 0, 12  },
+	{ "zero"           , optional_argument, 0, 'Z' },
+	{ "flush"          , optional_argument, 0, 'F' },
+	{ "policy"         , required_argument, 0, 'P' },
+	{ "in-interface"   , required_argument, 0, 'i' },
+	{ "in-if"          , required_argument, 0, 'i' },
+	{ "logical-in"     , required_argument, 0, 2   },
+	{ "logical-out"    , required_argument, 0, 3   },
+	{ "out-interface"  , required_argument, 0, 'o' },
+	{ "out-if"         , required_argument, 0, 'o' },
+	{ "version"        , no_argument      , 0, 'V' },
+	{ "help"           , no_argument      , 0, 'h' },
+	{ "jump"           , required_argument, 0, 'j' },
+	{ "set-counters"   , required_argument, 0, 'c' },
+	{ "change-counters", required_argument, 0, 'C' },
+	{ "proto"          , required_argument, 0, 'p' },
+	{ "protocol"       , required_argument, 0, 'p' },
+	{ "db"             , required_argument, 0, 'b' },
+	{ "source"         , required_argument, 0, 's' },
+	{ "src"            , required_argument, 0, 's' },
+	{ "destination"    , required_argument, 0, 'd' },
+	{ "dst"            , required_argument, 0, 'd' },
+	{ "table"          , required_argument, 0, 't' },
+	{ "modprobe"       , required_argument, 0, 'M' },
+	{ "new-chain"      , required_argument, 0, 'N' },
+	{ "rename-chain"   , required_argument, 0, 'E' },
+	{ "delete-chain"   , optional_argument, 0, 'X' },
+	{ "atomic-init"    , no_argument      , 0, 7   },
+	{ "atomic-commit"  , no_argument      , 0, 8   },
+	{ "atomic-file"    , required_argument, 0, 9   },
+	{ "atomic-save"    , no_argument      , 0, 10  },
+	{ "init-table"     , no_argument      , 0, 11  },
+	{ "concurrent"     , no_argument      , 0, 13  },
+	{ 0 }
+};
+
+extern void xtables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+struct xtables_globals ebtables_globals = {
+	.option_offset 		= 0,
+	.program_version	= PACKAGE_VERSION,
+	.orig_opts		= ebt_original_options,
+	.exit_err		= xtables_exit_error,
+	.compat_rev		= nft_compatible_revision,
+};
+
+#define opts ebtables_globals.opts
+#define prog_name ebtables_globals.program_name
+#define prog_vers ebtables_globals.program_version
+
+/*
+ * From libebtc.c
+ */
+
+/* Prints all registered extensions */
+static void ebt_list_extensions(const struct xtables_target *t,
+				const struct xtables_rule_match *m)
+{
+	printf("%s v%s\n", prog_name, prog_vers);
+	printf("Loaded userspace extensions:\n");
+	/*printf("\nLoaded tables:\n");
+        while (tbl) {
+		printf("%s\n", tbl->name);
+                tbl = tbl->next;
+	}*/
+	printf("\nLoaded targets:\n");
+        for (t = xtables_targets; t; t = t->next) {
+		printf("%s\n", t->name);
+	}
+	printf("\nLoaded matches:\n");
+        for (; m != NULL; m = m->next)
+		printf("%s\n", m->match->name);
+	/*printf("\nLoaded watchers:\n");
+        while (w) {
+		printf("%s\n", w->name);
+                w = w->next;
+	}*/
+}
+
+#define OPTION_OFFSET 256
+static struct option *merge_options(struct option *oldopts,
+				    const struct option *newopts,
+				    unsigned int *options_offset)
+{
+	unsigned int num_old, num_new, i;
+	struct option *merge;
+
+	if (!newopts || !oldopts || !options_offset)
+		return oldopts;
+	for (num_old = 0; oldopts[num_old].name; num_old++);
+	for (num_new = 0; newopts[num_new].name; num_new++);
+
+	ebtables_globals.option_offset += OPTION_OFFSET;
+	*options_offset = ebtables_globals.option_offset;
+
+	merge = malloc(sizeof(struct option) * (num_new + num_old + 1));
+	if (!merge)
+		return NULL;
+	memcpy(merge, oldopts, num_old * sizeof(struct option));
+	for (i = 0; i < num_new; i++) {
+		merge[num_old + i] = newopts[i];
+		merge[num_old + i].val += *options_offset;
+	}
+	memset(merge + num_old + num_new, 0, sizeof(struct option));
+	/* Only free dynamically allocated stuff */
+	if (oldopts != ebt_original_options)
+		free(oldopts);
+
+	return merge;
+}
+
+static void print_help(const struct xtables_target *t,
+		       const struct xtables_rule_match *m, const char *table)
+{
+	printf("%s %s\n", prog_name, prog_vers);
+	printf(
+"Usage:\n"
+"ebtables -[ADI] chain rule-specification [options]\n"
+"ebtables -P chain target\n"
+"ebtables -[LFZ] [chain]\n"
+"ebtables -[NX] [chain]\n"
+"ebtables -E old-chain-name new-chain-name\n\n"
+"Commands:\n"
+"--append -A chain             : append to chain\n"
+"--delete -D chain             : delete matching rule from chain\n"
+"--delete -D chain rulenum     : delete rule at position rulenum from chain\n"
+"--change-counters -C chain\n"
+"          [rulenum] pcnt bcnt : change counters of existing rule\n"
+"--insert -I chain rulenum     : insert rule at position rulenum in chain\n"
+"--list   -L [chain]           : list the rules in a chain or in all chains\n"
+"--flush  -F [chain]           : delete all rules in chain or in all chains\n"
+"--init-table                  : replace the kernel table with the initial table\n"
+"--zero   -Z [chain]           : put counters on zero in chain or in all chains\n"
+"--policy -P chain target      : change policy on chain to target\n"
+"--new-chain -N chain          : create a user defined chain\n"
+"--rename-chain -E old new     : rename a chain\n"
+"--delete-chain -X [chain]     : delete a user defined chain\n"
+"--atomic-commit               : update the kernel w/t table contained in <FILE>\n"
+"--atomic-init                 : put the initial kernel table into <FILE>\n"
+"--atomic-save                 : put the current kernel table into <FILE>\n"
+"--atomic-file file            : set <FILE> to file\n\n"
+"Options:\n"
+"--proto  -p [!] proto         : protocol hexadecimal, by name or LENGTH\n"
+"--src    -s [!] address[/mask]: source mac address\n"
+"--dst    -d [!] address[/mask]: destination mac address\n"
+"--in-if  -i [!] name[+]       : network input interface name\n"
+"--out-if -o [!] name[+]       : network output interface name\n"
+"--logical-in  [!] name[+]     : logical bridge input interface name\n"
+"--logical-out [!] name[+]     : logical bridge output interface name\n"
+"--set-counters -c chain\n"
+"          pcnt bcnt           : set the counters of the to be added rule\n"
+"--modprobe -M program         : try to insert modules using this program\n"
+"--concurrent                  : use a file lock to support concurrent scripts\n"
+"--version -V                  : print package version\n\n"
+"Environment variable:\n"
+/*ATOMIC_ENV_VARIABLE "          : if set <FILE> (see above) will equal its value"*/
+"\n\n");
+	for (; m != NULL; m = m->next) {
+		printf("\n");
+		m->match->help();
+	}
+	if (t != NULL) {
+		printf("\n");
+		t->help();
+	}
+
+//	if (table->help)
+//		table->help(ebt_hooknames);
+}
+
+/* Execute command L */
+static int list_rules(struct nft_handle *h, const char *chain, const char *table,
+		      int rule_nr, int verbose, int numeric, int expanded,
+		      int linenumbers, int counters)
+{
+	unsigned int format;
+
+	format = FMT_OPTIONS | FMT_C_COUNTS;
+	if (verbose)
+		format |= FMT_VIA;
+
+	if (numeric)
+		format |= FMT_NUMERIC;
+
+	if (!expanded)
+		format |= FMT_KILOMEGAGIGA;
+
+	if (linenumbers)
+		format |= FMT_LINENUMBERS;
+
+	if (!counters)
+		format |= FMT_NOCOUNTS;
+
+	return nft_cmd_rule_list(h, chain, table, rule_nr, format);
+}
+
+static int parse_rule_range(const char *argv, int *rule_nr, int *rule_nr_end)
+{
+	char *colon = strchr(argv, ':'), *buffer;
+
+	if (colon) {
+		*colon = '\0';
+		if (*(colon + 1) == '\0')
+			*rule_nr_end = -1; /* Until the last rule */
+		else {
+			*rule_nr_end = strtol(colon + 1, &buffer, 10);
+			if (*buffer != '\0' || *rule_nr_end == 0)
+				return -1;
+		}
+	}
+	if (colon == argv)
+		*rule_nr = 1; /* Beginning with the first rule */
+	else {
+		*rule_nr = strtol(argv, &buffer, 10);
+		if (*buffer != '\0' || *rule_nr == 0)
+			return -1;
+	}
+	if (!colon)
+		*rule_nr_end = *rule_nr;
+	return 0;
+}
+
+/* Incrementing or decrementing rules in daemon mode is not supported as the
+ * involved code overload is not worth it (too annoying to take the increased
+ * counters in the kernel into account). */
+static int parse_change_counters_rule(int argc, char **argv, int *rule_nr, int *rule_nr_end, struct iptables_command_state *cs)
+{
+	char *buffer;
+	int ret = 0;
+
+	if (optind + 1 >= argc || argv[optind][0] == '-' || argv[optind + 1][0] == '-')
+		xtables_error(PARAMETER_PROBLEM,
+			      "The command -C needs at least 2 arguments");
+	if (optind + 2 < argc && (argv[optind + 2][0] != '-' || (argv[optind + 2][1] >= '0' && argv[optind + 2][1] <= '9'))) {
+		if (optind + 3 != argc)
+			xtables_error(PARAMETER_PROBLEM,
+				      "No extra options allowed with -C start_nr[:end_nr] pcnt bcnt");
+		if (parse_rule_range(argv[optind], rule_nr, rule_nr_end))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Something is wrong with the rule number specification '%s'", argv[optind]);
+		optind++;
+	}
+
+	if (argv[optind][0] == '+') {
+		ret += 1;
+		cs->counters.pcnt = strtoull(argv[optind] + 1, &buffer, 10);
+	} else if (argv[optind][0] == '-') {
+		ret += 2;
+		cs->counters.pcnt = strtoull(argv[optind] + 1, &buffer, 10);
+	} else
+		cs->counters.pcnt = strtoull(argv[optind], &buffer, 10);
+
+	if (*buffer != '\0')
+		goto invalid;
+	optind++;
+	if (argv[optind][0] == '+') {
+		ret += 3;
+		cs->counters.bcnt = strtoull(argv[optind] + 1, &buffer, 10);
+	} else if (argv[optind][0] == '-') {
+		ret += 6;
+		cs->counters.bcnt = strtoull(argv[optind] + 1, &buffer, 10);
+	} else
+		cs->counters.bcnt = strtoull(argv[optind], &buffer, 10);
+
+	if (*buffer != '\0')
+		goto invalid;
+	optind++;
+	return ret;
+invalid:
+	xtables_error(PARAMETER_PROBLEM,"Packet counter '%s' invalid", argv[optind]);
+}
+
+static void ebtables_parse_interface(const char *arg, char *vianame)
+{
+	unsigned char mask[IFNAMSIZ];
+	char *c;
+
+	xtables_parse_interface(arg, vianame, mask);
+
+	if ((c = strchr(vianame, '+'))) {
+		if (*(c + 1) != '\0')
+			xtables_error(PARAMETER_PROBLEM,
+				      "Spurious characters after '+' wildcard");
+	}
+}
+
+/* This code is very similar to iptables/xtables.c:command_match() */
+static void ebt_load_match(const char *name)
+{
+	struct xtables_match *m;
+	size_t size;
+
+	m = xtables_find_match(name, XTF_TRY_LOAD, NULL);
+	if (m == NULL) {
+		fprintf(stderr, "Unable to load %s match\n", name);
+		return;
+	}
+
+	size = XT_ALIGN(sizeof(struct xt_entry_match)) + m->size;
+	m->m = xtables_calloc(1, size);
+	m->m->u.match_size = size;
+	strcpy(m->m->u.user.name, m->name);
+	m->m->u.user.revision = m->revision;
+	xs_init_match(m);
+
+	opts = merge_options(opts, m->extra_opts, &m->option_offset);
+	if (opts == NULL)
+		xtables_error(OTHER_PROBLEM, "Can't alloc memory");
+}
+
+static void __ebt_load_watcher(const char *name, const char *typename)
+{
+	struct xtables_target *watcher;
+	size_t size;
+
+	watcher = xtables_find_target(name, XTF_TRY_LOAD);
+	if (!watcher) {
+		fprintf(stderr, "Unable to load %s %s\n", name, typename);
+		return;
+	}
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target)) + watcher->size;
+
+	watcher->t = xtables_calloc(1, size);
+	watcher->t->u.target_size = size;
+	snprintf(watcher->t->u.user.name,
+		sizeof(watcher->t->u.user.name), "%s", name);
+	watcher->t->u.user.name[sizeof(watcher->t->u.user.name)-1] = '\0';
+	watcher->t->u.user.revision = watcher->revision;
+
+	xs_init_target(watcher);
+
+	opts = merge_options(opts, watcher->extra_opts,
+			     &watcher->option_offset);
+	if (opts == NULL)
+		xtables_error(OTHER_PROBLEM, "Can't alloc memory");
+}
+
+static void ebt_load_watcher(const char *name)
+{
+	return __ebt_load_watcher(name, "watcher");
+}
+
+static void ebt_load_target(const char *name)
+{
+	return __ebt_load_watcher(name, "target");
+}
+
+void ebt_load_match_extensions(void)
+{
+	opts = ebt_original_options;
+	ebt_load_match("802_3");
+	ebt_load_match("arp");
+	ebt_load_match("ip");
+	ebt_load_match("ip6");
+	ebt_load_match("mark_m");
+	ebt_load_match("limit");
+	ebt_load_match("pkttype");
+	ebt_load_match("vlan");
+	ebt_load_match("stp");
+	ebt_load_match("among");
+
+	ebt_load_watcher("log");
+	ebt_load_watcher("nflog");
+
+	ebt_load_target("mark");
+	ebt_load_target("dnat");
+	ebt_load_target("snat");
+	ebt_load_target("arpreply");
+	ebt_load_target("redirect");
+	ebt_load_target("standard");
+}
+
+void ebt_add_match(struct xtables_match *m,
+		   struct iptables_command_state *cs)
+{
+	struct xtables_rule_match **rule_matches = &cs->matches;
+	struct xtables_match *newm;
+	struct ebt_match *newnode, **matchp;
+	struct xt_entry_match *m2;
+
+	newm = xtables_find_match(m->name, XTF_LOAD_MUST_SUCCEED, rule_matches);
+	if (newm == NULL)
+		xtables_error(OTHER_PROBLEM,
+			      "Unable to add match %s", m->name);
+
+	m2 = xtables_calloc(1, newm->m->u.match_size);
+	memcpy(m2, newm->m, newm->m->u.match_size);
+	memset(newm->m->data, 0, newm->size);
+	xs_init_match(newm);
+	newm->m = m2;
+
+	newm->mflags = m->mflags;
+	m->mflags = 0;
+
+	/* glue code for watchers */
+	newnode = calloc(1, sizeof(struct ebt_match));
+	if (newnode == NULL)
+		xtables_error(OTHER_PROBLEM, "Unable to alloc memory");
+
+	newnode->ismatch = true;
+	newnode->u.match = newm;
+
+	for (matchp = &cs->match_list; *matchp; matchp = &(*matchp)->next)
+		;
+	*matchp = newnode;
+}
+
+void ebt_add_watcher(struct xtables_target *watcher,
+		     struct iptables_command_state *cs)
+{
+	struct ebt_match *newnode, **matchp;
+	struct xtables_target *clone;
+
+	clone = xtables_malloc(sizeof(struct xtables_target));
+	memcpy(clone, watcher, sizeof(struct xtables_target));
+	clone->udata = NULL;
+	clone->tflags = watcher->tflags;
+	clone->next = clone;
+
+	clone->t = xtables_calloc(1, watcher->t->u.target_size);
+	memcpy(clone->t, watcher->t, watcher->t->u.target_size);
+
+	memset(watcher->t->data, 0, watcher->size);
+	xs_init_target(watcher);
+	watcher->tflags = 0;
+
+
+	newnode = calloc(1, sizeof(struct ebt_match));
+	if (newnode == NULL)
+		xtables_error(OTHER_PROBLEM, "Unable to alloc memory");
+
+	newnode->u.watcher = clone;
+
+	for (matchp = &cs->match_list; *matchp; matchp = &(*matchp)->next)
+		;
+	*matchp = newnode;
+}
+
+int ebt_command_default(struct iptables_command_state *cs)
+{
+	struct xtables_target *t = cs->target;
+	struct xtables_match *m;
+	struct ebt_match *matchp;
+
+	/* Is it a target option? */
+	if (t && t->parse) {
+		if (t->parse(cs->c - t->option_offset, cs->argv,
+			     ebt_invert, &t->tflags, NULL, &t->t))
+			return 0;
+	}
+
+	/* check previously added matches/watchers to this rule first */
+	for (matchp = cs->match_list; matchp; matchp = matchp->next) {
+		if (matchp->ismatch) {
+			m = matchp->u.match;
+			if (m->parse &&
+			    m->parse(cs->c - m->option_offset, cs->argv,
+				     ebt_invert, &m->mflags, NULL, &m->m))
+				return 0;
+		} else {
+			t = matchp->u.watcher;
+			if (t->parse &&
+			    t->parse(cs->c - t->option_offset, cs->argv,
+				     ebt_invert, &t->tflags, NULL, &t->t))
+				return 0;
+		}
+	}
+
+	/* Is it a match_option? */
+	for (m = xtables_matches; m; m = m->next) {
+		if (m->parse &&
+		    m->parse(cs->c - m->option_offset, cs->argv,
+			     ebt_invert, &m->mflags, NULL, &m->m)) {
+			ebt_add_match(m, cs);
+			return 0;
+		}
+	}
+
+	/* Is it a watcher option? */
+	for (t = xtables_targets; t; t = t->next) {
+		if (t->parse &&
+		    t->parse(cs->c - t->option_offset, cs->argv,
+			     ebt_invert, &t->tflags, NULL, &t->t)) {
+			ebt_add_watcher(t, cs);
+			return 0;
+		}
+	}
+	return 1;
+}
+
+int nft_init_eb(struct nft_handle *h, const char *pname)
+{
+	ebtables_globals.program_name = pname;
+	if (xtables_init_all(&ebtables_globals, NFPROTO_BRIDGE) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize ebtables-compat\n",
+			ebtables_globals.program_name,
+			ebtables_globals.program_version);
+		exit(1);
+	}
+
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensionsb();
+#endif
+
+	if (nft_init(h, NFPROTO_BRIDGE, xtables_bridge) < 0)
+		xtables_error(OTHER_PROBLEM,
+			      "Could not initialize nftables layer.");
+
+	/* manually registering ebt matches, given the original ebtables parser
+	 * don't use '-m matchname' and the match can't be loaded dynamically when
+	 * the user calls it.
+	 */
+	ebt_load_match_extensions();
+
+	return 0;
+}
+
+void nft_fini_eb(struct nft_handle *h)
+{
+	struct xtables_match *match;
+	struct xtables_target *target;
+
+	for (match = xtables_matches; match; match = match->next) {
+		free(match->m);
+	}
+	for (target = xtables_targets; target; target = target->next) {
+		free(target->t);
+	}
+
+	free(opts);
+
+	nft_fini(h);
+	xtables_fini();
+}
+
+int do_commandeb(struct nft_handle *h, int argc, char *argv[], char **table,
+		 bool restore)
+{
+	char *buffer;
+	int c, i;
+	int chcounter = 0; /* Needed for -C */
+	int rule_nr = 0;
+	int rule_nr_end = 0;
+	int ret = 0;
+	unsigned int flags = 0;
+	struct xtables_target *t;
+	struct iptables_command_state cs = {
+		.argv = argv,
+		.jumpto	= "",
+		.eb.bitmask = EBT_NOPROTO,
+	};
+	char command = 'h';
+	const char *chain = NULL;
+	const char *policy = NULL;
+	int selected_chain = -1;
+	struct xtables_rule_match *xtrm_i;
+	struct ebt_match *match;
+	bool table_set = false;
+
+	/* prevent getopt to spoil our error reporting */
+	optind = 0;
+	opterr = false;
+
+	/* Getopt saves the day */
+	while ((c = getopt_long(argc, argv,
+	   "-A:D:C:I:N:E:X::L::Z::F::P:Vhi:o:j:c:p:s:d:t:M:", opts, NULL)) != -1) {
+		cs.c = c;
+		cs.invert = ebt_invert;
+		switch (c) {
+
+		case 'A': /* Add a rule */
+		case 'D': /* Delete a rule */
+		case 'C': /* Change counters */
+		case 'P': /* Define policy */
+		case 'I': /* Insert a rule */
+		case 'N': /* Make a user defined chain */
+		case 'E': /* Rename chain */
+		case 'X': /* Delete chain */
+			/* We allow -N chainname -P policy */
+			if (command == 'N' && c == 'P') {
+				command = c;
+				optind--; /* No table specified */
+				goto handle_P;
+			}
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+
+			command = c;
+			if (optarg && (optarg[0] == '-' || !strcmp(optarg, "!")))
+				xtables_error(PARAMETER_PROBLEM, "No chain name specified");
+			chain = optarg;
+			selected_chain = ebt_get_current_chain(chain);
+			flags |= OPT_COMMAND;
+
+			if (c == 'N') {
+				ret = nft_cmd_chain_user_add(h, chain, *table);
+				break;
+			} else if (c == 'X') {
+				/* X arg is optional, optarg is NULL */
+				if (!chain && optind < argc && argv[optind][0] != '-') {
+					chain = argv[optind];
+					optind++;
+				}
+				ret = nft_cmd_chain_user_del(h, chain, *table, 0);
+				break;
+			}
+
+			if (c == 'E') {
+				if (optind >= argc)
+					xtables_error(PARAMETER_PROBLEM, "No new chain name specified");
+				else if (optind < argc - 1)
+					xtables_error(PARAMETER_PROBLEM, "No extra options allowed with -E");
+				else if (strlen(argv[optind]) >= NFT_CHAIN_MAXNAMELEN)
+					xtables_error(PARAMETER_PROBLEM, "Chain name length can't exceed %d"" characters", NFT_CHAIN_MAXNAMELEN - 1);
+				else if (strchr(argv[optind], ' ') != NULL)
+					xtables_error(PARAMETER_PROBLEM, "Use of ' ' not allowed in chain names");
+
+				errno = 0;
+				ret = nft_cmd_chain_user_rename(h, chain, *table,
+							    argv[optind]);
+				if (ret != 0 && errno == ENOENT)
+					xtables_error(PARAMETER_PROBLEM, "Chain '%s' doesn't exists", chain);
+
+				optind++;
+				break;
+			} else if (c == 'D' && optind < argc && (argv[optind][0] != '-' || (argv[optind][1] >= '0' && argv[optind][1] <= '9'))) {
+				if (optind != argc - 1)
+					xtables_error(PARAMETER_PROBLEM,
+							 "No extra options allowed with -D start_nr[:end_nr]");
+				if (parse_rule_range(argv[optind], &rule_nr, &rule_nr_end))
+					xtables_error(PARAMETER_PROBLEM,
+							 "Problem with the specified rule number(s) '%s'", argv[optind]);
+				optind++;
+			} else if (c == 'C') {
+				if ((chcounter = parse_change_counters_rule(argc, argv, &rule_nr, &rule_nr_end, &cs)) == -1)
+					return -1;
+			} else if (c == 'I') {
+				if (optind >= argc || (argv[optind][0] == '-' && (argv[optind][1] < '0' || argv[optind][1] > '9')))
+					rule_nr = 1;
+				else {
+					rule_nr = parse_rule_number(argv[optind]);
+					optind++;
+				}
+			} else if (c == 'P') {
+handle_P:
+				if (optind >= argc)
+					xtables_error(PARAMETER_PROBLEM,
+						      "No policy specified");
+				for (i = 0; i < NUM_STANDARD_TARGETS; i++)
+					if (!strcmp(argv[optind], nft_ebt_standard_target(i))) {
+						policy = argv[optind];
+						if (-i-1 == EBT_CONTINUE)
+							xtables_error(PARAMETER_PROBLEM,
+								      "Wrong policy '%s'",
+								      argv[optind]);
+						break;
+					}
+				if (i == NUM_STANDARD_TARGETS)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Unknown policy '%s'", argv[optind]);
+				optind++;
+			}
+			break;
+		case 'L': /* List */
+		case 'F': /* Flush */
+		case 'Z': /* Zero counters */
+			if (c == 'Z') {
+				if ((flags & OPT_ZERO) || (flags & OPT_COMMAND && command != 'L'))
+print_zero:
+					xtables_error(PARAMETER_PROBLEM,
+						      "Command -Z only allowed together with command -L");
+				flags |= OPT_ZERO;
+			} else {
+				if (flags & OPT_COMMAND)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Multiple commands are not allowed");
+				command = c;
+				flags |= OPT_COMMAND;
+				if (flags & OPT_ZERO && c != 'L')
+					goto print_zero;
+			}
+
+			if (optind < argc && argv[optind][0] != '-') {
+				chain = argv[optind];
+				optind++;
+			}
+			break;
+		case 'V': /* Version */
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+			printf("%s %s (nf_tables)\n", prog_name, prog_vers);
+			exit(0);
+		case 'h': /* Help */
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+			command = 'h';
+
+			/* All other arguments should be extension names */
+			while (optind < argc) {
+				/*struct ebt_u_match *m;
+				struct ebt_u_watcher *w;*/
+
+				if (!strcasecmp("list_extensions", argv[optind])) {
+					ebt_list_extensions(xtables_targets, cs.matches);
+					exit(0);
+				}
+				/*if ((m = ebt_find_match(argv[optind])))
+					ebt_add_match(new_entry, m);
+				else if ((w = ebt_find_watcher(argv[optind])))
+					ebt_add_watcher(new_entry, w);
+				else {*/
+					if (!(t = xtables_find_target(argv[optind], XTF_TRY_LOAD)))
+						xtables_error(PARAMETER_PROBLEM,"Extension '%s' not found", argv[optind]);
+					if (flags & OPT_JUMP)
+						xtables_error(PARAMETER_PROBLEM,"Sorry, you can only see help for one target extension at a time");
+					flags |= OPT_JUMP;
+					cs.target = t;
+				//}
+				optind++;
+			}
+			break;
+		case 't': /* Table */
+			ebt_check_option2(&flags, OPT_TABLE);
+			if (restore && table_set)
+				xtables_error(PARAMETER_PROBLEM,
+					      "The -t option (seen in line %u) cannot be used in %s.\n",
+					      line, xt_params->program_name);
+			if (strlen(optarg) > EBT_TABLE_MAXNAMELEN - 1)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Table name length cannot exceed %d characters",
+					      EBT_TABLE_MAXNAMELEN - 1);
+			*table = optarg;
+			table_set = true;
+			break;
+		case 'i': /* Input interface */
+		case 2  : /* Logical input interface */
+		case 'o': /* Output interface */
+		case 3  : /* Logical output interface */
+		case 'j': /* Target */
+		case 'p': /* Net family protocol */
+		case 's': /* Source mac */
+		case 'd': /* Destination mac */
+		case 'c': /* Set counters */
+			if (!OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "No command specified");
+			if (command != 'A' && command != 'D' && command != 'I' && command != 'C')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Command and option do not match");
+			if (c == 'i') {
+				ebt_check_option2(&flags, OPT_IN);
+				if (selected_chain > 2 && selected_chain < NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use -i only in INPUT, FORWARD, PREROUTING and BROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IIN;
+
+				ebtables_parse_interface(optarg, cs.eb.in);
+				break;
+			} else if (c == 2) {
+				ebt_check_option2(&flags, OPT_LOGICALIN);
+				if (selected_chain > 2 && selected_chain < NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use --logical-in only in INPUT, FORWARD, PREROUTING and BROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ILOGICALIN;
+
+				ebtables_parse_interface(optarg, cs.eb.logical_in);
+				break;
+			} else if (c == 'o') {
+				ebt_check_option2(&flags, OPT_OUT);
+				if (selected_chain < 2 || selected_chain == NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use -o only in OUTPUT, FORWARD and POSTROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IOUT;
+
+				ebtables_parse_interface(optarg, cs.eb.out);
+				break;
+			} else if (c == 3) {
+				ebt_check_option2(&flags, OPT_LOGICALOUT);
+				if (selected_chain < 2 || selected_chain == NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use --logical-out only in OUTPUT, FORWARD and POSTROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ILOGICALOUT;
+
+				ebtables_parse_interface(optarg, cs.eb.logical_out);
+				break;
+			} else if (c == 'j') {
+				ebt_check_option2(&flags, OPT_JUMP);
+				if (strcmp(optarg, "CONTINUE") != 0) {
+					command_jump(&cs, optarg);
+				}
+				break;
+			} else if (c == 's') {
+				ebt_check_option2(&flags, OPT_SOURCE);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ISOURCE;
+
+				if (xtables_parse_mac_and_mask(optarg,
+							       cs.eb.sourcemac,
+							       cs.eb.sourcemsk))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified source mac '%s'", optarg);
+				cs.eb.bitmask |= EBT_SOURCEMAC;
+				break;
+			} else if (c == 'd') {
+				ebt_check_option2(&flags, OPT_DEST);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IDEST;
+
+				if (xtables_parse_mac_and_mask(optarg,
+							       cs.eb.destmac,
+							       cs.eb.destmsk))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified destination mac '%s'", optarg);
+				cs.eb.bitmask |= EBT_DESTMAC;
+				break;
+			} else if (c == 'c') {
+				ebt_check_option2(&flags, OPT_COUNT);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					xtables_error(PARAMETER_PROBLEM,
+						      "Unexpected '!' after -c");
+				if (optind >= argc || optarg[0] == '-' || argv[optind][0] == '-')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Option -c needs 2 arguments");
+
+				cs.counters.pcnt = strtoull(optarg, &buffer, 10);
+				if (*buffer != '\0')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Packet counter '%s' invalid",
+						      optarg);
+				cs.counters.bcnt = strtoull(argv[optind], &buffer, 10);
+				if (*buffer != '\0')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Packet counter '%s' invalid",
+						      argv[optind]);
+				optind++;
+				break;
+			}
+			ebt_check_option2(&flags, OPT_PROTOCOL);
+			if (ebt_check_inverse2(optarg, argc, argv))
+				cs.eb.invflags |= EBT_IPROTO;
+
+			cs.eb.bitmask &= ~((unsigned int)EBT_NOPROTO);
+			i = strtol(optarg, &buffer, 16);
+			if (*buffer == '\0' && (i < 0 || i > 0xFFFF))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Problem with the specified protocol");
+			if (*buffer != '\0') {
+				struct xt_ethertypeent *ent;
+
+				if (!strcasecmp(optarg, "LENGTH")) {
+					cs.eb.bitmask |= EBT_802_3;
+					break;
+				}
+				ent = xtables_getethertypebyname(optarg);
+				if (!ent)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Problem with the specified Ethernet protocol '%s', perhaps "XT_PATH_ETHERTYPES " is missing", optarg);
+				cs.eb.ethproto = ent->e_ethertype;
+			} else
+				cs.eb.ethproto = i;
+
+			if (cs.eb.ethproto < 0x0600)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Sorry, protocols have values above or equal to 0x0600");
+			break;
+		case 4  : /* Lc */
+			ebt_check_option2(&flags, LIST_C);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Lc with -L");
+			flags |= LIST_C;
+			break;
+		case 5  : /* Ln */
+			ebt_check_option2(&flags, LIST_N);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Ln with -L");
+			if (flags & LIST_X)
+				xtables_error(PARAMETER_PROBLEM,
+					      "--Lx is not compatible with --Ln");
+			flags |= LIST_N;
+			break;
+		case 6  : /* Lx */
+			ebt_check_option2(&flags, LIST_X);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Lx with -L");
+			if (flags & LIST_N)
+				xtables_error(PARAMETER_PROBLEM,
+					      "--Lx is not compatible with --Ln");
+			flags |= LIST_X;
+			break;
+		case 12 : /* Lmac2 */
+			ebt_check_option2(&flags, LIST_MAC2);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					       "Use --Lmac2 with -L");
+			flags |= LIST_MAC2;
+			break;
+		case 8 : /* atomic-commit */
+/*
+			replace->command = c;
+			if (OPT_COMMANDS)
+				ebt_print_error2("Multiple commands are not allowed");
+			replace->flags |= OPT_COMMAND;
+			if (!replace->filename)
+				ebt_print_error2("No atomic file specified");*/
+			/* Get the information from the file */
+			/*ebt_get_table(replace, 0);*/
+			/* We don't want the kernel giving us its counters,
+			 * they would overwrite the counters extracted from
+			 * the file */
+			/*replace->num_counters = 0;*/
+			/* Make sure the table will be written to the kernel */
+			/*free(replace->filename);
+			replace->filename = NULL;
+			break;*/
+		/*case 7 :*/ /* atomic-init */
+		/*case 10:*/ /* atomic-save */
+		case 11: /* init-table */
+			nft_cmd_table_flush(h, *table, false);
+			return 1;
+		/*
+			replace->command = c;
+			if (OPT_COMMANDS)
+				ebt_print_error2("Multiple commands are not allowed");
+			if (c != 11 && !replace->filename)
+				ebt_print_error2("No atomic file specified");
+			replace->flags |= OPT_COMMAND;
+			{
+				char *tmp = replace->filename;*/
+
+				/* Get the kernel table */
+				/*replace->filename = NULL;
+				ebt_get_kernel_table(replace, c == 10 ? 0 : 1);
+				replace->filename = tmp;
+			}
+			break;
+		case 9 :*/ /* atomic */
+			/*
+			if (OPT_COMMANDS)
+				ebt_print_error2("--atomic has to come before the command");*/
+			/* A possible memory leak here, but this is not
+			 * executed in daemon mode */
+			/*replace->filename = (char *)malloc(strlen(optarg) + 1);
+			strcpy(replace->filename, optarg);
+			break; */
+		case 13 :
+			break;
+		case 1 :
+			if (!strcmp(optarg, "!"))
+				ebt_check_inverse2(optarg, argc, argv);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "Bad argument : '%s'", optarg);
+			/* ebt_ebt_check_inverse2() did optind++ */
+			optind--;
+			continue;
+		default:
+			ebt_check_inverse2(optarg, argc, argv);
+
+			if (ebt_command_default(&cs))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Unknown argument: '%s'",
+					      argv[optind]);
+
+			if (command != 'A' && command != 'I' &&
+			    command != 'D' && command != 'C')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Extensions only for -A, -I, -D and -C");
+		}
+		ebt_invert = 0;
+	}
+
+	/* Just in case we didn't catch an error */
+	/*if (ebt_errormsg[0] != '\0')
+		return -1;
+
+	if (!(table = ebt_find_table(replace->name)))
+		ebt_print_error2("Bad table name");*/
+
+	if (command == 'h' && !(flags & OPT_ZERO)) {
+		print_help(cs.target, cs.matches, *table);
+		ret = 1;
+	}
+
+	/* Do the final checks */
+	if (command == 'A' || command == 'I' ||
+	    command == 'D' || command == 'C') {
+		for (xtrm_i = cs.matches; xtrm_i; xtrm_i = xtrm_i->next)
+			xtables_option_mfcall(xtrm_i->match);
+
+		for (match = cs.match_list; match; match = match->next) {
+			if (match->ismatch)
+				continue;
+
+			xtables_option_tfcall(match->u.watcher);
+		}
+
+		if (cs.target != NULL)
+			xtables_option_tfcall(cs.target);
+	}
+	/* So, the extensions can work with the host endian.
+	 * The kernel does not have to do this of course */
+	cs.eb.ethproto = htons(cs.eb.ethproto);
+
+	if (command == 'P') {
+		if (selected_chain >= NF_BR_NUMHOOKS) {
+			ret = ebt_cmd_user_chain_policy(h, *table, chain, policy);
+		} else {
+			if (strcmp(policy, "RETURN") == 0) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "Policy RETURN only allowed for user defined chains");
+			}
+			ret = nft_cmd_chain_set(h, *table, chain, policy, NULL);
+			if (ret < 0)
+				xtables_error(PARAMETER_PROBLEM, "Wrong policy");
+		}
+	} else if (command == 'L') {
+		ret = list_rules(h, chain, *table, rule_nr,
+				 0,
+				 0,
+				 /*flags&OPT_EXPANDED*/0,
+				 flags&LIST_N,
+				 flags&LIST_C);
+	}
+	if (flags & OPT_ZERO) {
+		ret = nft_cmd_chain_zero_counters(h, chain, *table, 0);
+	} else if (command == 'F') {
+		ret = nft_cmd_rule_flush(h, chain, *table, 0);
+	} else if (command == 'A') {
+		ret = append_entry(h, chain, *table, &cs, 0, 0, true);
+	} else if (command == 'I') {
+		ret = append_entry(h, chain, *table, &cs, rule_nr - 1,
+				   0, false);
+	} else if (command == 'D') {
+		ret = delete_entry(h, chain, *table, &cs, rule_nr - 1,
+				   rule_nr_end, 0);
+	} /*else if (replace->command == 'C') {
+		ebt_change_counters(replace, new_entry, rule_nr, rule_nr_end, &(new_entry->cnt_surplus), chcounter);
+		if (ebt_errormsg[0] != '\0')
+			return -1;
+	}*/
+
+	ebt_cs_clean(&cs);
+	return ret;
+}
diff --git a/iptables/xtables-legacy-multi.c b/iptables/xtables-legacy-multi.c
new file mode 100644
index 0000000..3b7905f
--- /dev/null
+++ b/iptables/xtables-legacy-multi.c
@@ -0,0 +1,54 @@
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "xshared.h"
+
+#include "xtables-multi.h"
+
+#ifdef ENABLE_IPV4
+#include "iptables-multi.h"
+#endif
+
+#ifdef ENABLE_IPV6
+#include "ip6tables-multi.h"
+#endif
+
+#ifdef ENABLE_NFTABLES
+#include "xtables-multi.h"
+#endif
+
+static const struct subcommand multi_subcommands[] = {
+#ifdef ENABLE_IPV4
+	{"iptables",            iptables_main},
+	{"main4",               iptables_main},
+	{"iptables-save",       iptables_save_main},
+	{"save4",               iptables_save_main},
+	{"iptables-restore",    iptables_restore_main},
+	{"restore4",            iptables_restore_main},
+	{"iptables-legacy",     iptables_main},
+	{"iptables-legacy-save",iptables_save_main},
+	{"iptables-legacy-restore",iptables_restore_main},
+
+
+#endif
+	{"iptables-xml",        iptables_xml_main},
+	{"xml",                 iptables_xml_main},
+#ifdef ENABLE_IPV6
+	{"ip6tables",           ip6tables_main},
+	{"main6",               ip6tables_main},
+	{"ip6tables-save",      ip6tables_save_main},
+	{"save6",               ip6tables_save_main},
+	{"ip6tables-restore",   ip6tables_restore_main},
+	{"restore6",            ip6tables_restore_main},
+	{"ip6tables-legacy",    ip6tables_main},
+	{"ip6tables-legacy-save",ip6tables_save_main},
+	{"ip6tables-legacy-restore",ip6tables_restore_main},
+#endif
+	{NULL},
+};
+
+int main(int argc, char **argv)
+{
+	return subcmd_main(argc, argv, multi_subcommands);
+}
diff --git a/iptables/xtables-legacy.8 b/iptables/xtables-legacy.8
new file mode 100644
index 0000000..6db7d2c
--- /dev/null
+++ b/iptables/xtables-legacy.8
@@ -0,0 +1,78 @@
+.\"
+.\" (C) Copyright 2016-2017, Arturo Borrero Gonzalez <arturo@netfilter.org>
+.\"
+.\" %%%LICENSE_START(GPLv2+_DOC_FULL)
+.\" This is free documentation; 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 2 of
+.\" the License, or (at your option) any later version.
+.\"
+.\" The GNU General Public License's references to "object code"
+.\" and "executables" are to be interpreted as the output of any
+.\" document formatting or typesetting system, including
+.\" intermediate and printed output.
+.\"
+.\" This manual 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 manual; if not, see
+.\" <http://www.gnu.org/licenses/>.
+.\" %%%LICENSE_END
+.\"
+.TH XTABLES-LEGACY 8 "June 2018"
+
+.SH NAME
+xtables-legacy \(em iptables using old getsockopt/setsockopt-based kernel api
+
+.SH DESCRIPTION
+\fBxtables-legacy\fP are the original versions of iptables that use
+old getsockopt/setsockopt-based kernel interface.
+This kernel interface has some limitations, therefore iptables can also
+be used with the newer nf_tables based API.
+See
+.B xtables\-nft(8)
+for information about the xtables-nft variants of iptables.
+
+.SH USAGE
+The xtables-legacy-multi binary can be linked to the traditional names:
+
+.nf
+	/sbin/iptables -> /sbin/iptables\-legacy\-multi
+	/sbin/ip6tables -> /sbin/ip6tables\-legacy\-multi
+	/sbin/iptables\-save -> /sbin/ip6tables\-legacy\-multi
+	/sbin/iptables\-restore -> /sbin/ip6tables\-legacy\-multi
+.fi
+
+The iptables version string will indicate whether the legacy API (get/setsockopt) or
+the new nf_tables API is used:
+.nf
+	iptables \-V
+	iptables v1.7 (legacy)
+.fi
+
+.SH LIMITATIONS
+
+When inserting a rule using
+iptables \-A or iptables \-I, iptables first needs to retrieve the current active
+ruleset, change it to include the new rule, and then commit back the result.
+This means that if two instances of iptables are running concurrently, one of the
+updates might be lost.  This can be worked around partially with the \-\-wait option.
+
+There is also no method to monitor changes to the ruleset, except periodically calling
+iptables-legacy-save and checking for any differences in output.
+
+.B xtables\-monitor(8)
+will need the
+.B xtables\-nft(8)
+versions to work, it cannot display changes made using the
+.B iptables-legacy
+tools.
+
+.SH SEE ALSO
+\fBxtables\-nft(8)\fP, \fBxtables\-translate(8)\fP
+
+.SH AUTHORS
+Rusty Russell originally wrote iptables, in early consultation with Michael Neuling.
diff --git a/iptables/xtables-monitor.8.in b/iptables/xtables-monitor.8.in
new file mode 100644
index 0000000..b647a79
--- /dev/null
+++ b/iptables/xtables-monitor.8.in
@@ -0,0 +1,93 @@
+.TH XTABLES\-MONITOR 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.SH NAME
+xtables-monitor \(em show changes to rule set and trace-events
+.SH SYNOPSIS
+\fBxtables\-monitor\fP [\fB\-t\fP] [\fB\-e\fP] [\fB\-4\fP|\fB|\-6\fB]
+.PP
+\
+.SH DESCRIPTION
+.PP
+.B xtables-monitor
+is used to monitor changes to the ruleset or to show rule evaluation events
+for packets tagged using the TRACE target.
+.B xtables-monitor
+will run until the user aborts execution, typically by using CTRL-C.
+.RE
+.SH OPTIONS
+\fB\-e\fP, \fB\-\-event\fP
+.TP
+Watch for updates to the rule set.
+Updates include creation of new tables, chains and rules and
+the name of the program that caused the rule update.
+.TP
+\fB\-t\fP, \fB\-\-trace\fP
+Watch for trace events generated by packets that have been tagged
+using the TRACE target.
+.TP
+\fB\-4\fP
+Restrict output to IPv4.
+.TP
+\fB\-6\fP
+Restrict output to IPv6.
+.SH EXAMPLE OUTPUT
+.TP
+.B xtables-monitor \-\-trace
+
+ 1 TRACE: 2 fc475095 raw:PREROUTING:rule:0x3:CONTINUE \-4 \-t raw \-A PREROUTING \-p icmp \-j TRACE
+ 2 PACKET: 0 fc475095 IN=lo LL=0x304 0000000000000000000000000800 SRC=127.0.0.1 DST=127.0.0.1 LEN=84 TOS=0x0 TTL=64 ID=38349DF
+ 3 TRACE: 2 fc475095 raw:PREROUTING:return:
+ 4 TRACE: 2 fc475095 raw:PREROUTING:policy:ACCEPT
+ 5 TRACE: 2 fc475095 filter:INPUT:return:
+ 6 TRACE: 2 fc475095 filter:INPUT:policy:DROP
+ 7 TRACE: 2 0df9d3d8 raw:PREROUTING:rule:0x3:CONTINUE \-4 \-t raw \-A PREROUTING \-p icmp \-j TRACE
+.PP
+The first line shows a packet entering rule set evaluation.
+The protocol number is shown (AF_INET in this case), then a packet
+identifier number that allows to correlate messages coming from rule set evaluation of
+this packet.  After this, the rule that was matched by the packet is shown.
+This is the TRACE rule that turns on tracing events for this packet.
+
+The second line dumps information about the packet. Incoming interface
+and packet headers such as source and destination addresses are shown.
+
+The third line shows that the packet completed traversal of the raw table
+PREROUTING chain, and is returning, followed by use the chain policy to make accept/drop
+decision (the example shows accept being applied).
+The fifth line shows that the packet leaves the filter INPUT chain, i.e., no rules in the filter tables
+INPUT chain matched the packet.
+It then got DROPPED by the policy of the INPUT table, as shown by line six.
+The last line shows another packet arriving \-\- the packet id is different.
+
+When using the TRACE target, it is usually a good idea to only select packets
+that are relevant, for example via
+.nf
+iptables \-t raw \-A PREROUTING \-p tcp \-\-dport 80 \-\-syn \-m limit \-\-limit 1/s \-j TRACE
+.fi
+.TP
+.B xtables-monitor \-\-event
+  1 EVENT: nft: NEW table: table filter ip flags 0 use 4 handle 444
+  2 EVENT: # nft: ip filter INPUT use 2 type filter hook input prio 0 policy drop packets 0 bytes 0
+  3 EVENT: # nft: ip filter FORWARD use 0 type filter hook forward prio 0 policy accept packets 0 bytes 0
+  4 EVENT: # nft: ip filter OUTPUT use 0 type filter hook output prio 0 policy accept packets 0 bytes 0
+  5 EVENT: \-4 \-t filter \-N TCP
+  6 EVENT: \-4 \-t filter \-A TCP \-s 192.168.0.0/16 \-p tcp \-m tcp \-\-dport 22 \-j ACCEPT
+  7 EVENT: \-4 \-t filter \-A TCP \-p tcp \-m multiport \-\-dports 80,443 \-j ACCEPT
+  8 EVENT: \-4 \-t filter \-A INPUT \-p tcp \-j TCP
+  9 EVENT: \-4 \-t filter \-A INPUT \-m conntrack \-\-ctstate RELATED,ESTABLISHED \-j ACCEPT
+ 10 NEWGEN: GENID=13904 PID=25167 NAME=iptables-nftables-restore
+.PP
+This example shows event monitoring.  Line one shows creation of a table (filter in this case), followed
+by three base hooks INPUT, FORWARD and OUTPUT.  The iptables-nftables tools all create tables and base
+chains automatically when needed, so this is expected when a table was not yet initialized or when it is
+re-created from scratch by iptables-nftables-restore.  Line five shows a new user-defined chain (TCP)
+being added, followed by addition a few rules. the last line shows that a new ruleset generation has
+become active, i.e., the rule set changes are now active.  This also lists the process id and the programs name.
+.SH LIMITATIONS
+.B xtables-monitor
+only works with rules added using iptables-nftables, rules added using
+iptables-legacy cannot be monitored.
+.SH BUGS
+Should be reported or by sending email to netfilter-devel@vger.kernel.org or
+by filing a report on https://bugzilla.netfilter.org/.
+.SH SEE ALSO
+\fBiptables\fP(8), \fBxtables\fP(8), \fBnft\fP(8)
diff --git a/iptables/xtables-monitor.c b/iptables/xtables-monitor.c
new file mode 100644
index 0000000..4b98098
--- /dev/null
+++ b/iptables/xtables-monitor.c
@@ -0,0 +1,710 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#define _GNU_SOURCE
+#include "config.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <netinet/ether.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <net/if_arp.h>
+#include <getopt.h>
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/table.h>
+#include <libnftnl/trace.h>
+#include <libnftnl/chain.h>
+#include <libnftnl/rule.h>
+
+#include <include/xtables.h>
+#include "iptables.h" /* for xtables_globals */
+#include "xtables-multi.h"
+#include "nft.h"
+#include "nft-arp.h"
+
+struct cb_arg {
+	uint32_t nfproto;
+	bool is_event;
+	struct nft_handle *h;
+};
+
+static int table_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t type = nlh->nlmsg_type & 0xFF;
+	const struct cb_arg *arg = data;
+	struct nftnl_table *t;
+	char buf[4096];
+
+	t = nftnl_table_alloc();
+	if (t == NULL)
+		goto err;
+
+	if (nftnl_table_nlmsg_parse(nlh, t) < 0)
+		goto err_free;
+
+	if (arg->nfproto && arg->nfproto != nftnl_table_get_u32(t, NFTNL_TABLE_FAMILY))
+		goto err_free;
+	nftnl_table_snprintf(buf, sizeof(buf), t, NFTNL_OUTPUT_DEFAULT, 0);
+	printf(" EVENT: ");
+	printf("nft: %s table: %s\n", type == NFT_MSG_NEWTABLE ? "NEW" : "DEL", buf);
+
+err_free:
+	nftnl_table_free(t);
+err:
+	return MNL_CB_OK;
+}
+
+static bool counters;
+static bool trace;
+static bool events;
+
+static int rule_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t type = nlh->nlmsg_type & 0xFF;
+	const struct cb_arg *arg = data;
+	struct nftnl_rule *r;
+	uint8_t family;
+
+	r = nftnl_rule_alloc();
+	if (r == NULL)
+		goto err;
+
+	if (nftnl_rule_nlmsg_parse(nlh, r) < 0)
+		goto err_free;
+
+	family = nftnl_rule_get_u32(r, NFTNL_RULE_FAMILY);
+	if (arg->nfproto && arg->nfproto != family)
+		goto err_free;
+
+	arg->h->ops = nft_family_ops_lookup(family);
+
+	if (arg->is_event)
+		printf(" EVENT: ");
+	switch (family) {
+	case AF_INET:
+	case AF_INET6:
+		printf("-%c ", family == AF_INET ? '4' : '6');
+		break;
+	case NFPROTO_ARP:
+		printf("-0 ");
+		break;
+	default:
+		puts("");
+		goto err_free;
+	}
+
+	printf("-t %s ", nftnl_rule_get_str(r, NFTNL_RULE_TABLE));
+	nft_rule_print_save(arg->h, r, type == NFT_MSG_NEWRULE ? NFT_RULE_APPEND :
+							   NFT_RULE_DEL,
+			    counters ? 0 : FMT_NOCOUNTS);
+err_free:
+	nftnl_rule_free(r);
+err:
+	return MNL_CB_OK;
+}
+
+static int chain_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t type = nlh->nlmsg_type & 0xFF;
+	const struct cb_arg *arg = data;
+	struct nftnl_chain *c;
+	char buf[4096];
+	int family;
+
+	c = nftnl_chain_alloc();
+	if (c == NULL)
+		goto err;
+
+	if (nftnl_chain_nlmsg_parse(nlh, c) < 0)
+		goto err_free;
+
+	family = nftnl_chain_get_u32(c, NFTNL_CHAIN_FAMILY);
+	if (arg->nfproto && arg->nfproto != family)
+		goto err_free;
+
+	if (nftnl_chain_is_set(c, NFTNL_CHAIN_PRIO))
+		family = -1;
+
+	printf(" EVENT: ");
+	switch (family) {
+	case NFPROTO_IPV4:
+		family = 4;
+		break;
+	case NFPROTO_IPV6:
+		family = 6;
+		break;
+	default:
+		nftnl_chain_snprintf(buf, sizeof(buf), c, NFTNL_OUTPUT_DEFAULT, 0);
+		printf("# nft: %s\n", buf);
+		goto err_free;
+	}
+
+	printf("-%d -t %s -%c %s\n",
+			family,
+			nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE),
+			type == NFT_MSG_NEWCHAIN ? 'N' : 'X',
+			nftnl_chain_get_str(c, NFTNL_CHAIN_NAME));
+err_free:
+	nftnl_chain_free(c);
+err:
+	return MNL_CB_OK;
+}
+
+static int newgen_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t genid = 0, pid = 0;
+	const struct nlattr *attr;
+	const char *name = NULL;
+
+	mnl_attr_for_each(attr, nlh, sizeof(struct nfgenmsg)) {
+		switch (mnl_attr_get_type(attr)) {
+		case NFTA_GEN_ID:
+			if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+				break;
+		        genid = ntohl(mnl_attr_get_u32(attr));
+			break;
+		case NFTA_GEN_PROC_NAME:
+			if (mnl_attr_validate(attr, MNL_TYPE_NUL_STRING) < 0)
+				break;
+			name = mnl_attr_get_str(attr);
+			break;
+		case NFTA_GEN_PROC_PID:
+			if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+				break;
+			pid = ntohl(mnl_attr_get_u32(attr));
+			break;
+		}
+	}
+
+	if (name)
+		printf("NEWGEN: GENID=%u PID=%u NAME=%s\n", genid, pid, name);
+
+	return MNL_CB_OK;
+}
+
+static void trace_print_return(const struct nftnl_trace *nlt)
+{
+	const char *chain = NULL;
+
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_JUMP_TARGET)) {
+		chain = nftnl_trace_get_str(nlt, NFTNL_TRACE_JUMP_TARGET);
+		printf("%s", chain);
+	}
+}
+
+static void trace_print_rule(const struct nftnl_trace *nlt, struct cb_arg *args)
+{
+	uint64_t handle = nftnl_trace_get_u64(nlt, NFTNL_TRACE_RULE_HANDLE);
+	uint32_t family = nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY);
+	const char *table = nftnl_trace_get_str(nlt, NFTNL_TRACE_TABLE);
+	const char *chain = nftnl_trace_get_str(nlt, NFTNL_TRACE_CHAIN);
+        struct nftnl_rule *r;
+	struct mnl_socket *nl;
+	struct nlmsghdr *nlh;
+	uint32_t portid;
+	char buf[16536];
+	int ret;
+
+        r = nftnl_rule_alloc();
+	if (r == NULL) {
+		perror("OOM");
+		exit(EXIT_FAILURE);
+	}
+
+	nlh = nftnl_chain_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, family, 0, 0);
+
+        nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family);
+	nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);
+	nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
+	nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE, handle);
+	nftnl_rule_nlmsg_build_payload(nlh, r);
+	nftnl_rule_free(r);
+
+	nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (nl == NULL) {
+		perror("mnl_socket_open");
+		exit(EXIT_FAILURE);
+	}
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		perror("mnl_socket_bind");
+		exit(EXIT_FAILURE);
+	}
+
+	portid = mnl_socket_get_portid(nl);
+	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
+		perror("mnl_socket_send");
+		exit(EXIT_FAILURE);
+	}
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	if (ret > 0) {
+		args->is_event = false;
+		ret = mnl_cb_run(buf, ret, 0, portid, rule_cb, args);
+	}
+	if (ret == -1) {
+		perror("error");
+		exit(EXIT_FAILURE);
+	}
+	mnl_socket_close(nl);
+}
+
+static void trace_print_packet(const struct nftnl_trace *nlt, struct cb_arg *args)
+{
+	struct list_head stmts = LIST_HEAD_INIT(stmts);
+	uint32_t nfproto, family;
+	uint16_t l4proto = 0;
+	uint32_t mark;
+	char name[IFNAMSIZ];
+
+	family = nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY);
+	printf("PACKET: %d %08x ", family, nftnl_trace_get_u32(nlt, NFTNL_TRACE_ID));
+
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_IIF))
+		printf("IN=%s ", if_indextoname(nftnl_trace_get_u32(nlt, NFTNL_TRACE_IIF), name));
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_OIF))
+		printf("OUT=%s ", if_indextoname(nftnl_trace_get_u32(nlt, NFTNL_TRACE_OIF), name));
+
+	nfproto = family;
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_NFPROTO)) {
+		nfproto = nftnl_trace_get_u32(nlt, NFTNL_TRACE_NFPROTO);
+
+		if (family != nfproto)
+			printf("NFPROTO=%d ", nfproto);
+	}
+
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_LL_HEADER)) {
+		const struct ethhdr *eh;
+		const char *linklayer;
+		uint32_t i, len;
+		uint16_t type = nftnl_trace_get_u16(nlt, NFTNL_TRACE_IIFTYPE);
+
+		linklayer = nftnl_trace_get_data(nlt, NFTNL_TRACE_LL_HEADER, &len);
+		switch (type) {
+		case ARPHRD_ETHER:
+			if (len < sizeof(*eh))
+			       break;
+			eh = (const void *)linklayer;
+			printf("MACSRC=%s ", ether_ntoa((const void *)eh->h_source));
+			printf("MACDST=%s ", ether_ntoa((const void *)eh->h_dest));
+			printf("MACPROTO=%04x ", ntohs(eh->h_proto));
+			break;
+		case ARPHRD_LOOPBACK:
+			printf("LOOPBACK ");
+			break;
+		default:
+			printf("LL=0x%x ", type);
+			for (i = 0 ; i < len; i++)
+				printf("%02x", linklayer[i]);
+			printf(" ");
+			break;
+		}
+	}
+
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_NETWORK_HEADER)) {
+		const struct ip6_hdr *ip6h;
+		const struct iphdr *iph;
+		uint32_t i, len;
+		const char *nh;
+
+		ip6h = nftnl_trace_get_data(nlt, NFTNL_TRACE_NETWORK_HEADER, &len);
+
+		switch (nfproto) {
+		case NFPROTO_IPV4: {
+			char addrbuf[INET_ADDRSTRLEN];
+
+			if (len < sizeof(*iph))
+				break;
+			iph = (const void *)ip6h;
+
+
+			inet_ntop(AF_INET, &iph->saddr, addrbuf, sizeof(addrbuf));
+			printf("SRC=%s ", addrbuf);
+			inet_ntop(AF_INET, &iph->daddr, addrbuf, sizeof(addrbuf));
+			printf("DST=%s ", addrbuf);
+
+			printf("LEN=%d TOS=0x%x TTL=%d ID=%d", ntohs(iph->tot_len), iph->tos, iph->ttl, ntohs(iph->id));
+			if (iph->frag_off & htons(0x8000))
+				printf("CE ");
+			if (iph->frag_off & htons(IP_DF))
+				printf("DF ");
+			if (iph->frag_off & htons(IP_MF))
+				printf("MF ");
+
+			if (ntohs(iph->frag_off) & 0x1fff)
+				printf("FRAG:%u ", ntohs(iph->frag_off) & 0x1fff);
+
+			l4proto = iph->protocol;
+			if (iph->ihl * 4 > sizeof(*iph)) {
+				unsigned int optsize;
+				const char *op;
+
+				optsize = iph->ihl * 4 - sizeof(*iph);
+				op = (const char *)iph;
+				op += sizeof(*iph);
+
+				printf("OPT (");
+				for (i = 0; i < optsize; i++)
+					printf("%02X", op[i]);
+				printf(")");
+			}
+			break;
+		}
+		case NFPROTO_IPV6: {
+			uint32_t flowlabel = ntohl(*(uint32_t *)ip6h);
+			char addrbuf[INET6_ADDRSTRLEN];
+
+			if (len < sizeof(*ip6h))
+				break;
+
+			inet_ntop(AF_INET6, &ip6h->ip6_src, addrbuf, sizeof(addrbuf));
+			printf("SRC=%s ", addrbuf);
+			inet_ntop(AF_INET6, &ip6h->ip6_dst, addrbuf, sizeof(addrbuf));
+			printf("DST=%s ", addrbuf);
+
+			printf("LEN=%zu TC=%u HOPLIMIT=%u FLOWLBL=%u ",
+				ntohs(ip6h->ip6_plen) + sizeof(*iph),
+				(flowlabel & 0x0ff00000) >> 20,
+				ip6h->ip6_hops,
+				flowlabel & 0x000fffff);
+
+			l4proto = ip6h->ip6_nxt;
+			break;
+		}
+		default:
+			nh = (const char *)ip6h;
+			printf("NH=");
+			for (i = 0 ; i < len; i++)
+				printf("%02x", nh[i]);
+			printf(" ");
+		}
+	}
+
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_TRANSPORT_HEADER)) {
+		const struct tcphdr *tcph;
+		uint32_t len;
+
+		tcph = nftnl_trace_get_data(nlt, NFTNL_TRACE_TRANSPORT_HEADER, &len);
+
+		switch (l4proto) {
+		case IPPROTO_DCCP:
+		case IPPROTO_SCTP:
+		case IPPROTO_UDPLITE:
+		case IPPROTO_UDP:
+			if (len < 4)
+				break;
+			printf("SPORT=%d DPORT=%d ", ntohs(tcph->source), ntohs(tcph->dest));
+			break;
+		case IPPROTO_TCP:
+			if (len < sizeof(*tcph))
+				break;
+			printf("SPORT=%d DPORT=%d ", ntohs(tcph->source), ntohs(tcph->dest));
+			if (tcph->syn)
+				printf("SYN ");
+			if (tcph->ack)
+				printf("ACK ");
+			if (tcph->fin)
+				printf("FIN ");
+			if (tcph->rst)
+				printf("RST ");
+			if (tcph->psh)
+				printf("PSH ");
+			if (tcph->urg)
+				printf("URG ");
+			break;
+		default:
+			break;
+		}
+	}
+
+	mark = nftnl_trace_get_u32(nlt, NFTNL_TRACE_MARK);
+	if (mark)
+		printf("MARK=0x%x ", mark);
+	puts("");
+}
+
+static void trace_print_hdr(const struct nftnl_trace *nlt)
+{
+	printf(" TRACE: %d %08x %s:%s", nftnl_trace_get_u32(nlt, NFTNL_TABLE_FAMILY),
+					nftnl_trace_get_u32(nlt, NFTNL_TRACE_ID),
+					nftnl_trace_get_str(nlt, NFTNL_TRACE_TABLE),
+					nftnl_trace_get_str(nlt, NFTNL_TRACE_CHAIN));
+}
+
+static void print_verdict(const struct nftnl_trace *nlt, uint32_t verdict)
+{
+	const char *chain;
+
+	switch (verdict) {
+	case NF_ACCEPT:
+		printf("ACCEPT");
+		break;
+	case NF_DROP:
+		printf("DROP");
+		break;
+	case NF_QUEUE:
+		printf("QUEUE");
+		break;
+	case NF_STOLEN:
+		printf("STOLEN");
+		break;
+	case NFT_BREAK:
+		printf("BREAK");
+		break;
+	case NFT_CONTINUE:
+		printf("CONTINUE");
+		break;
+	case NFT_GOTO:
+		printf("GOTO");
+		if (nftnl_trace_is_set(nlt, NFTNL_TRACE_JUMP_TARGET)) {
+			chain = nftnl_trace_get_str(nlt, NFTNL_TRACE_JUMP_TARGET);
+			printf(":%s", chain);
+		}
+		break;
+	case NFT_JUMP:
+		printf("JUMP");
+		if (nftnl_trace_is_set(nlt, NFTNL_TRACE_JUMP_TARGET)) {
+			chain = nftnl_trace_get_str(nlt, NFTNL_TRACE_JUMP_TARGET);
+			printf(":%s", chain);
+		}
+		break;
+	default:
+		printf("0x%x", verdict);
+		break;
+	}
+
+	printf(" ");
+}
+
+static int trace_cb(const struct nlmsghdr *nlh, struct cb_arg *arg)
+{
+	struct nftnl_trace *nlt;
+	uint32_t verdict;
+
+	nlt = nftnl_trace_alloc();
+	if (nlt == NULL)
+		goto err;
+
+	if (nftnl_trace_nlmsg_parse(nlh, nlt) < 0)
+		goto err_free;
+
+	if (arg->nfproto &&
+	    arg->nfproto != nftnl_trace_get_u32(nlt, NFTNL_TABLE_FAMILY))
+		goto err_free;
+
+	switch (nftnl_trace_get_u32(nlt, NFTNL_TRACE_TYPE)) {
+	case NFT_TRACETYPE_RULE:
+		verdict = nftnl_trace_get_u32(nlt, NFTNL_TRACE_VERDICT);
+
+		if (nftnl_trace_is_set(nlt, NFTNL_TRACE_LL_HEADER) ||
+		    nftnl_trace_is_set(nlt, NFTNL_TRACE_NETWORK_HEADER))
+			trace_print_packet(nlt, arg);
+
+		if (nftnl_trace_is_set(nlt, NFTNL_TRACE_RULE_HANDLE)) {
+			trace_print_hdr(nlt);
+			printf(":rule:0x%" PRIx64":", nftnl_trace_get_u64(nlt, NFTNL_TRACE_RULE_HANDLE));
+			print_verdict(nlt, verdict);
+			printf(" ");
+			trace_print_rule(nlt, arg);
+		}
+		break;
+	case NFT_TRACETYPE_POLICY:
+		trace_print_hdr(nlt);
+		printf(":policy:");
+		verdict = nftnl_trace_get_u32(nlt, NFTNL_TRACE_POLICY);
+
+		print_verdict(nlt, verdict);
+		puts("");
+		break;
+	case NFT_TRACETYPE_RETURN:
+		trace_print_hdr(nlt);
+		printf(":return:");
+		trace_print_return(nlt);
+		puts("");
+		break;
+	}
+err_free:
+	nftnl_trace_free(nlt);
+err:
+	fflush(stdout);
+	return MNL_CB_OK;
+}
+
+static int monitor_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t type = nlh->nlmsg_type & 0xFF;
+	struct cb_arg *arg = data;
+	int ret = MNL_CB_OK;
+
+	switch(type) {
+	case NFT_MSG_NEWTABLE:
+	case NFT_MSG_DELTABLE:
+		ret = table_cb(nlh, data);
+		break;
+	case NFT_MSG_NEWCHAIN:
+	case NFT_MSG_DELCHAIN:
+		ret = chain_cb(nlh, data);
+		break;
+	case NFT_MSG_NEWRULE:
+	case NFT_MSG_DELRULE:
+		arg->is_event = true;
+		ret = rule_cb(nlh, data);
+		break;
+	case NFT_MSG_NEWGEN:
+		ret = newgen_cb(nlh, data);
+		break;
+	case NFT_MSG_TRACE:
+		ret = trace_cb(nlh, data);
+		break;
+	}
+
+	return ret;
+}
+
+static const struct option options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "trace", .has_arg = false, .val = 't'},
+	{.name = "event", .has_arg = false, .val = 'e'},
+	{.name = "ipv4", .has_arg = false, .val = '4'},
+	{.name = "ipv6", .has_arg = false, .val = '6'},
+	{.name = "version", .has_arg = false, .val = 'V'},
+	{.name = "help", .has_arg = false, .val = 'h'},
+	{NULL},
+};
+
+static void print_usage(void)
+{
+	printf("%s %s\n", xtables_globals.program_name,
+			  xtables_globals.program_version);
+	printf("Usage: %s [ -t | -e ]\n"
+	       "        --trace    -t    trace ruleset traversal of packets tagged via -j TRACE rule\n"
+	       "        --event    -e    show events that modify the ruleset\n"
+	       "Optional arguments:\n"
+	       "        --ipv4     -4    only monitor IPv4\n"
+	       "        --ipv6     -6    only monitor IPv6\n"
+	       "	--counters -c    show counters in rules\n"
+
+	       , xtables_globals.program_name);
+	exit(EXIT_FAILURE);
+}
+
+int xtables_monitor_main(int argc, char *argv[])
+{
+	struct mnl_socket *nl;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	uint32_t nfgroup = 0;
+	struct nft_handle h = {};
+	struct cb_arg cb_arg = {
+		.h = &h,
+	};
+	int ret, c;
+
+	xtables_globals.program_name = "xtables-monitor";
+	/* XXX xtables_init_all does several things we don't want */
+	c = xtables_init_all(&xtables_globals, NFPROTO_IPV4);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+
+	if (nft_init(&h, AF_INET, xtables_ipv4)) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+			xtables_globals.program_name,
+			xtables_globals.program_version,
+			strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	opterr = 0;
+	while ((c = getopt_long(argc, argv, "ceht46V", options, NULL)) != -1) {
+		switch (c) {
+	        case 'c':
+			counters = true;
+			break;
+	        case 't':
+			trace = true;
+			break;
+	        case 'e':
+			events = true;
+			break;
+	        case 'h':
+			print_usage();
+			exit(0);
+		case '4':
+			cb_arg.nfproto = NFPROTO_IPV4;
+			break;
+		case '6':
+			cb_arg.nfproto = NFPROTO_IPV6;
+			break;
+		case 'V':
+			printf("xtables-monitor %s\n", PACKAGE_VERSION);
+			exit(0);
+		default:
+			fprintf(stderr, "xtables-monitor %s: Bad argument.\n", PACKAGE_VERSION);
+			fprintf(stderr, "Try `xtables-monitor -h' for more information.\n");
+			exit(PARAMETER_PROBLEM);
+		}
+	}
+
+	if (trace)
+		nfgroup |= 1 << (NFNLGRP_NFTRACE - 1);
+	if (events)
+		nfgroup |= 1 << (NFNLGRP_NFTABLES - 1);
+
+	if (nfgroup == 0) {
+		print_usage();
+		exit(EXIT_FAILURE);
+	}
+
+	nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (nl == NULL) {
+		perror("cannot open nfnetlink socket");
+		exit(EXIT_FAILURE);
+	}
+
+	if (mnl_socket_bind(nl, nfgroup, MNL_SOCKET_AUTOPID) < 0) {
+		perror("cannot bind to nfnetlink socket");
+		exit(EXIT_FAILURE);
+	}
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	while (ret > 0) {
+		ret = mnl_cb_run(buf, ret, 0, 0, monitor_cb, &cb_arg);
+		if (ret <= 0)
+			break;
+		ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	}
+	if (ret == -1) {
+		perror("cannot receive from nfnetlink socket");
+		exit(EXIT_FAILURE);
+	}
+	mnl_socket_close(nl);
+
+	xtables_fini();
+
+	return EXIT_SUCCESS;
+}
+
diff --git a/iptables/xtables-multi.h b/iptables/xtables-multi.h
new file mode 100644
index 0000000..0fedb43
--- /dev/null
+++ b/iptables/xtables-multi.h
@@ -0,0 +1,27 @@
+#ifndef _XTABLES_MULTI_H
+#define _XTABLES_MULTI_H 1
+
+extern int iptables_xml_main(int, char **);
+#ifdef ENABLE_NFTABLES
+extern int xtables_ip4_main(int, char **);
+extern int xtables_ip4_save_main(int, char **);
+extern int xtables_ip4_restore_main(int, char **);
+extern int xtables_ip6_main(int, char **);
+extern int xtables_ip6_save_main(int, char **);
+extern int xtables_ip6_restore_main(int, char **);
+extern int xtables_ip4_xlate_main(int, char **);
+extern int xtables_ip6_xlate_main(int, char **);
+extern int xtables_eb_xlate_main(int, char **);
+extern int xtables_ip4_xlate_restore_main(int, char **);
+extern int xtables_ip6_xlate_restore_main(int, char **);
+extern int xtables_arp_main(int, char **);
+extern int xtables_arp_restore_main(int, char **);
+extern int xtables_arp_save_main(int, char **);
+extern int xtables_eb_main(int, char **);
+extern int xtables_eb_restore_main(int, char **);
+extern int xtables_eb_save_main(int, char **);
+extern int xtables_config_main(int, char **);
+extern int xtables_monitor_main(int, char **);
+#endif
+
+#endif /* _XTABLES_MULTI_H */
diff --git a/iptables/xtables-nft-multi.c b/iptables/xtables-nft-multi.c
new file mode 100644
index 0000000..e2b7c64
--- /dev/null
+++ b/iptables/xtables-nft-multi.c
@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "xshared.h"
+
+#include "xtables-multi.h"
+
+static const struct subcommand multi_subcommands[] = {
+	{"iptables-xml",		iptables_xml_main},
+	{"xml",				iptables_xml_main},
+	{"iptables",			xtables_ip4_main},
+	{"iptables-nft",		xtables_ip4_main},
+	{"main4",			xtables_ip4_main},
+	{"save4",			xtables_ip4_save_main},
+	{"restore4",			xtables_ip4_restore_main},
+	{"iptables-save",		xtables_ip4_save_main},
+	{"iptables-restore",		xtables_ip4_restore_main},
+	{"iptables-nft-save",	xtables_ip4_save_main},
+	{"iptables-nft-restore",	xtables_ip4_restore_main},
+	{"ip6tables",			xtables_ip6_main},
+	{"ip6tables-nft",		xtables_ip6_main},
+	{"main6",			xtables_ip6_main},
+	{"save6",			xtables_ip6_save_main},
+	{"restore6",			xtables_ip6_restore_main},
+	{"ip6tables-save",		xtables_ip6_save_main},
+	{"ip6tables-restore",		xtables_ip6_restore_main},
+	{"ip6tables-nft-save",	xtables_ip6_save_main},
+	{"ip6tables-nft-restore",	xtables_ip6_restore_main},
+	{"iptables-translate",		xtables_ip4_xlate_main},
+	{"ip6tables-translate",		xtables_ip6_xlate_main},
+	{"iptables-restore-translate",	xtables_ip4_xlate_restore_main},
+	{"ip6tables-restore-translate",	xtables_ip6_xlate_restore_main},
+	{"arptables",			xtables_arp_main},
+	{"arptables-nft",		xtables_arp_main},
+	{"arptables-restore",		xtables_arp_restore_main},
+	{"arptables-nft-restore",	xtables_arp_restore_main},
+	{"arptables-save",		xtables_arp_save_main},
+	{"arptables-nft-save",		xtables_arp_save_main},
+	{"ebtables-translate",		xtables_eb_xlate_main},
+	{"ebtables",			xtables_eb_main},
+	{"ebtables-restore",		xtables_eb_restore_main},
+	{"ebtables-save",		xtables_eb_save_main},
+	{"ebtables-nft",		xtables_eb_main},
+	{"ebtables-nft-restore",	xtables_eb_restore_main},
+	{"ebtables-nft-save",		xtables_eb_save_main},
+	{"xtables-monitor",		xtables_monitor_main},
+	{NULL},
+};
+
+int main(int argc, char **argv)
+{
+	return subcmd_main(argc, argv, multi_subcommands);
+}
diff --git a/iptables/xtables-nft.8 b/iptables/xtables-nft.8
new file mode 100644
index 0000000..702bf95
--- /dev/null
+++ b/iptables/xtables-nft.8
@@ -0,0 +1,208 @@
+.\"
+.\" (C) Copyright 2016-2017, Arturo Borrero Gonzalez <arturo@netfilter.org>
+.\"
+.\" %%%LICENSE_START(GPLv2+_DOC_FULL)
+.\" This is free documentation; 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 2 of
+.\" the License, or (at your option) any later version.
+.\"
+.\" The GNU General Public License's references to "object code"
+.\" and "executables" are to be interpreted as the output of any
+.\" document formatting or typesetting system, including
+.\" intermediate and printed output.
+.\"
+.\" This manual 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 manual; if not, see
+.\" <http://www.gnu.org/licenses/>.
+.\" %%%LICENSE_END
+.\"
+.TH XTABLES-NFT 8 "June 2018"
+
+.SH NAME
+xtables-nft \(em iptables using nftables kernel api
+
+.SH DESCRIPTION
+\fBxtables-nft\fP are versions of iptables that use the nftables API.
+This is a set of tools to help the system administrator migrate the
+ruleset from \fBiptables(8)\fP, \fBip6tables(8)\fP, \fBarptables(8)\fP, and
+\fBebtables(8)\fP to \fBnftables(8)\fP.
+
+The \fBxtables-nft\fP set is composed of several commands:
+.IP \[bu] 2
+iptables\-nft
+.IP \[bu]
+iptables\-nft\-save
+.IP \[bu]
+iptables\-nft\-restore
+.IP \[bu]
+ip6tables\-nft
+.IP \[bu]
+ip6tables\-nft\-save
+.IP \[bu]
+ip6tables\-nft\-restore
+.IP \[bu]
+arptables\-nft
+.IP \[bu]
+ebtables\-nft
+
+These tools use the libxtables framework extensions and hook to the nf_tables
+kernel subsystem using the \fBnft_compat\fP module.
+
+.SH USAGE
+The xtables-nft tools allow you to manage the nf_tables backend using the
+native syntax of \fBiptables(8)\fP, \fBip6tables(8)\fP, \fBarptables(8)\fP, and
+\fBebtables(8)\fP.
+
+You should use the xtables-nft tools exactly the same way as you would use the
+corresponding original tools.
+
+Adding a rule will result in that rule being added to the nf_tables kernel
+subsystem instead.
+Listing the ruleset will use the nf_tables backend as well.
+
+When these tools were designed, the main idea was to replace each legacy binary
+with a symlink to the xtables-nft program, for example:
+
+.nf
+	/sbin/iptables -> /usr/sbin/iptables\-nft\-multi
+	/sbin/ip6tables -> /usr/sbin/ip6tables\-nft\-multi
+	/sbin/arptables -> /usr/sbin/arptables\-nft\-multi
+	/sbin/ebtables -> /usr/sbin/ebtables\-nft\-multi
+.fi
+
+The iptables version string will indicate whether the legacy API (get/setsockopt) or
+the new nf_tables api is used:
+.nf
+	iptables \-V
+	iptables v1.7 (nf_tables)
+.fi
+
+.SH DIFFERENCES TO LEGACY IPTABLES
+
+Because the xtables-nft tools use the nf_tables kernel API, rule additions
+and deletions are always atomic.  Unlike iptables-legacy, iptables-nft \-A ..
+will NOT need to retrieve the current ruleset from the kernel, change it, and
+re-load the altered ruleset.  Instead, iptables-nft will tell the kernel to add
+one rule.  For this reason, the iptables-legacy \-\-wait option is a no-op in
+iptables-nft.
+
+Use of the xtables-nft tools allow monitoring ruleset changes using the
+.B xtables\-monitor(8)
+command.
+
+When using \-j TRACE to debug packet traversal to the ruleset, note that you will need to use
+.B xtables\-monitor(8)
+in \-\-trace mode to obtain monitoring trace events.
+
+.SH EXAMPLES
+One basic example is creating the skeleton ruleset in nf_tables from the
+xtables-nft tools, in a fresh machine:
+
+.nf
+	root@machine:~# iptables\-nft \-L
+	[...]
+	root@machine:~# ip6tables\-nft \-L
+	[...]
+	root@machine:~# arptables\-nft \-L
+	[...]
+	root@machine:~# ebtables\-nft \-L
+	[...]
+	root@machine:~# nft list ruleset
+	table ip filter {
+		chain INPUT {
+			type filter hook input priority 0; policy accept;
+		}
+
+		chain FORWARD {
+			type filter hook forward priority 0; policy accept;
+		}
+
+		chain OUTPUT {
+			type filter hook output priority 0; policy accept;
+		}
+	}
+	table ip6 filter {
+		chain INPUT {
+			type filter hook input priority 0; policy accept;
+		}
+
+		chain FORWARD {
+			type filter hook forward priority 0; policy accept;
+		}
+
+		chain OUTPUT {
+			type filter hook output priority 0; policy accept;
+		}
+	}
+	table bridge filter {
+		chain INPUT {
+			type filter hook input priority \-200; policy accept;
+		}
+
+		chain FORWARD {
+			type filter hook forward priority \-200; policy accept;
+		}
+
+		chain OUTPUT {
+			type filter hook output priority \-200; policy accept;
+		}
+	}
+	table arp filter {
+		chain INPUT {
+			type filter hook input priority 0; policy accept;
+		}
+
+		chain FORWARD {
+			type filter hook forward priority 0; policy accept;
+		}
+
+		chain OUTPUT {
+			type filter hook output priority 0; policy accept;
+		}
+	}
+.fi
+
+(please note that in fresh machines, listing the ruleset for the first time
+results in all tables an chain being created).
+
+To migrate your complete filter ruleset, in the case of \fBiptables(8)\fP,
+you would use:
+
+.nf
+	root@machine:~# iptables\-legacy\-save > myruleset # reads from x_tables
+	root@machine:~# iptables\-nft\-restore myruleset   # writes to nf_tables
+.fi
+or
+.nf
+	root@machine:~# iptables\-legacy\-save | iptables-translate-restore | less
+.fi
+
+to see how rules would look like in the nft
+\fBnft(8)\fP
+syntax.
+
+.SH LIMITATIONS
+You should use \fBLinux kernel >= 4.17\fP.
+
+The CLUSTERIP target is not supported.
+
+To get up-to-date information about this, please head to
+\fBhttp://wiki.nftables.org/\fP.
+
+.SH SEE ALSO
+\fBnft(8)\fP, \fBxtables\-translate(8)\fP, \fBxtables\-monitor(8)\fP
+
+.SH AUTHORS
+The nftables framework is written by the Netfilter project
+(https://www.netfilter.org).
+
+This manual page was written by Arturo Borrero Gonzalez
+<arturo@debian.org> for the Debian project, but may be used by others.
+
+This documentation is free/libre under the terms of the GPLv2+.
diff --git a/iptables/xtables-restore.c b/iptables/xtables-restore.c
new file mode 100644
index 0000000..d273949
--- /dev/null
+++ b/iptables/xtables-restore.c
@@ -0,0 +1,477 @@
+/* Code to restore the iptables state, from file by iptables-save.
+ * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
+ * based on previous code from Rusty Russell <rusty@linuxcare.com.au>
+ *
+ * This code is distributed under the terms of GNU GPL v2
+ */
+#include "config.h"
+#include <getopt.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "iptables.h"
+#include "xtables.h"
+#include "libiptc/libiptc.h"
+#include "xtables-multi.h"
+#include "nft.h"
+#include "nft-bridge.h"
+#include "nft-cache.h"
+#include <libnftnl/chain.h>
+
+static int counters, verbose;
+
+/* Keeping track of external matches and targets.  */
+static const struct option options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "verbose",  .has_arg = false, .val = 'v'},
+	{.name = "version",       .has_arg = 0, .val = 'V'},
+	{.name = "test",     .has_arg = false, .val = 't'},
+	{.name = "help",     .has_arg = false, .val = 'h'},
+	{.name = "noflush",  .has_arg = false, .val = 'n'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{.name = "table",    .has_arg = true,  .val = 'T'},
+	{.name = "ipv4",     .has_arg = false, .val = '4'},
+	{.name = "ipv6",     .has_arg = false, .val = '6'},
+	{.name = "wait",          .has_arg = 2, .val = 'w'},
+	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
+	{NULL},
+};
+
+#define prog_name xtables_globals.program_name
+#define prog_vers xtables_globals.program_version
+
+static void print_usage(const char *name, const char *version)
+{
+	fprintf(stderr, "Usage: %s [-c] [-v] [-V] [-t] [-h] [-n] [-T table] [-M command] [-4] [-6] [file]\n"
+			"	   [ --counters ]\n"
+			"	   [ --verbose ]\n"
+			"	   [ --version]\n"
+			"	   [ --test ]\n"
+			"	   [ --help ]\n"
+			"	   [ --noflush ]\n"
+			"	   [ --table=<TABLE> ]\n"
+			"	   [ --modprobe=<command> ]\n"
+			"	   [ --ipv4 ]\n"
+			"	   [ --ipv6 ]\n", name);
+}
+
+static const struct nft_xt_restore_cb restore_cb = {
+	.commit		= nft_commit,
+	.abort		= nft_abort,
+	.table_flush	= nft_cmd_table_flush,
+	.do_command	= do_commandx,
+	.chain_set	= nft_cmd_chain_set,
+	.chain_restore  = nft_cmd_chain_restore,
+};
+
+struct nft_xt_restore_state {
+	const struct builtin_table *curtable;
+	struct argv_store av_store;
+	bool in_table;
+};
+
+static void xtables_restore_parse_line(struct nft_handle *h,
+				       const struct nft_xt_restore_parse *p,
+				       struct nft_xt_restore_state *state,
+				       char *buffer)
+{
+	const struct nft_xt_restore_cb *cb = p->cb;
+	int ret = 0;
+
+	if (buffer[0] == '\n')
+		return;
+	else if (buffer[0] == '#') {
+		if (verbose) {
+			fputs(buffer, stdout);
+			fflush(stdout);
+		}
+		return;
+	} else if (state->in_table &&
+		   (strncmp(buffer, "COMMIT", 6) == 0) &&
+		   (buffer[6] == '\0' || buffer[6] == '\n')) {
+		if (!p->testing) {
+			/* Commit per table, although we support
+			 * global commit at once, stick by now to
+			 * the existing behaviour.
+			 */
+			DEBUGP("Calling commit\n");
+			if (cb->commit)
+				ret = cb->commit(h);
+		} else {
+			DEBUGP("Not calling commit, testing\n");
+			if (cb->abort)
+				ret = cb->abort(h);
+		}
+		state->in_table = false;
+
+	} else if ((buffer[0] == '*') && (!state->in_table || !p->commit)) {
+		/* New table */
+		char *table;
+
+		table = strtok(buffer+1, " \t\n");
+		DEBUGP("line %u, table '%s'\n", line, table);
+		if (!table)
+			xtables_error(PARAMETER_PROBLEM,
+				"%s: line %u table name invalid\n",
+				xt_params->program_name, line);
+
+		state->curtable = nft_table_builtin_find(h, table);
+		if (!state->curtable)
+			xtables_error(PARAMETER_PROBLEM,
+				"%s: line %u table name '%s' invalid\n",
+				xt_params->program_name, line, table);
+
+		if (p->tablename && (strcmp(p->tablename, table) != 0))
+			return;
+
+		/* implicit commit if no explicit COMMIT supported */
+		if (!p->commit)
+			cb->commit(h);
+
+		if (h->noflush == 0) {
+			DEBUGP("Cleaning all chains of table '%s'\n", table);
+			if (cb->table_flush)
+				cb->table_flush(h, table, verbose);
+		}
+
+		ret = 1;
+		state->in_table = true;
+
+		if (cb->table_new)
+			cb->table_new(h, table);
+
+	} else if ((buffer[0] == ':') && state->in_table) {
+		/* New chain. */
+		char *policy, *chain = NULL;
+		struct xt_counters count = {};
+
+		chain = strtok(buffer+1, " \t\n");
+		DEBUGP("line %u, chain '%s'\n", line, chain);
+		if (!chain)
+			xtables_error(PARAMETER_PROBLEM,
+				   "%s: line %u chain name invalid\n",
+				   xt_params->program_name, line);
+
+		if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid chain name `%s' (%u chars max)",
+				   chain, XT_EXTENSION_MAXNAMELEN - 1);
+
+		policy = strtok(NULL, " \t\n");
+		DEBUGP("line %u, policy '%s'\n", line, policy);
+		if (!policy)
+			xtables_error(PARAMETER_PROBLEM,
+				   "%s: line %u policy invalid\n",
+				   xt_params->program_name, line);
+
+		if (nft_chain_builtin_find(state->curtable, chain)) {
+			if (counters) {
+				char *ctrs;
+				ctrs = strtok(NULL, " \t\n");
+
+				if (!ctrs || !parse_counters(ctrs, &count))
+					xtables_error(PARAMETER_PROBLEM,
+						   "invalid policy counters for chain '%s'\n",
+						   chain);
+
+			}
+			if (cb->chain_set &&
+			    cb->chain_set(h, state->curtable->name,
+					  chain, policy, &count) < 0) {
+				xtables_error(OTHER_PROBLEM,
+					      "Can't set policy `%s' on `%s' line %u: %s\n",
+					      policy, chain, line,
+					      strerror(errno));
+			}
+			DEBUGP("Setting policy of chain %s to %s\n",
+			       chain, policy);
+		} else if (cb->chain_restore(h, chain, state->curtable->name) < 0 &&
+			   errno != EEXIST) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "cannot create chain '%s' (%s)\n",
+				      chain, strerror(errno));
+		} else if (h->family == NFPROTO_BRIDGE &&
+			   !ebt_cmd_user_chain_policy(h, state->curtable->name,
+						      chain, policy)) {
+			xtables_error(OTHER_PROBLEM,
+				      "Can't set policy `%s' on `%s' line %u: %s\n",
+				      policy, chain, line,
+				      strerror(errno));
+		}
+		ret = 1;
+	} else if (state->in_table) {
+		char *pcnt = NULL;
+		char *bcnt = NULL;
+		char *parsestart = buffer;
+
+		add_argv(&state->av_store, xt_params->program_name, 0);
+		add_argv(&state->av_store, "-t", 0);
+		add_argv(&state->av_store, state->curtable->name, 0);
+
+		tokenize_rule_counters(&parsestart, &pcnt, &bcnt, line);
+		if (counters && pcnt && bcnt) {
+			add_argv(&state->av_store, "--set-counters", 0);
+			add_argv(&state->av_store, pcnt, 0);
+			add_argv(&state->av_store, bcnt, 0);
+		}
+
+		add_param_to_argv(&state->av_store, parsestart, line);
+
+		DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
+		       state->av_store.argc, state->curtable->name);
+		debug_print_argv(&state->av_store);
+
+		ret = cb->do_command(h, state->av_store.argc,
+				     state->av_store.argv,
+				     &state->av_store.argv[2], true);
+		if (ret < 0) {
+			if (cb->abort)
+				ret = cb->abort(h);
+			else
+				ret = 0;
+
+			if (ret < 0) {
+				fprintf(stderr,
+					"failed to abort commit operation\n");
+			}
+			exit(1);
+		}
+
+		free_argv(&state->av_store);
+		fflush(stdout);
+	}
+	if (p->tablename && state->curtable &&
+	    (strcmp(p->tablename, state->curtable->name) != 0))
+		return;
+	if (!ret) {
+		fprintf(stderr, "%s: line %u failed\n",
+				xt_params->program_name, line);
+		exit(1);
+	}
+}
+
+void xtables_restore_parse(struct nft_handle *h,
+			   const struct nft_xt_restore_parse *p)
+{
+	struct nft_xt_restore_state state = {};
+	char buffer[10240] = {};
+
+	if (!verbose && !h->noflush)
+		nft_cache_level_set(h, NFT_CL_FAKE, NULL);
+
+	line = 0;
+	while (fgets(buffer, sizeof(buffer), p->in)) {
+		h->error.lineno = ++line;
+		DEBUGP("%s: input line %d: '%s'\n", __func__, line, buffer);
+		xtables_restore_parse_line(h, p, &state, buffer);
+	}
+	if (state.in_table && p->commit) {
+		fprintf(stderr, "%s: COMMIT expected at line %u\n",
+				xt_params->program_name, line + 1);
+		exit(1);
+	} else if (state.in_table && p->cb->commit && !p->cb->commit(h)) {
+		xtables_error(OTHER_PROBLEM, "%s: final implicit COMMIT failed",
+			      xt_params->program_name);
+	}
+}
+
+static int
+xtables_restore_main(int family, const char *progname, int argc, char *argv[])
+{
+	const struct builtin_table *tables;
+	struct nft_xt_restore_parse p = {
+		.commit = true,
+		.cb = &restore_cb,
+	};
+	bool noflush = false;
+	struct nft_handle h;
+	int c;
+
+	line = 0;
+
+	xtables_globals.program_name = progname;
+	c = xtables_init_all(&xtables_globals, family);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+
+	while ((c = getopt_long(argc, argv, "bcvVthnM:T:wW", options, NULL)) != -1) {
+		switch (c) {
+			case 'b':
+				fprintf(stderr, "-b/--binary option is not implemented\n");
+				break;
+			case 'c':
+				counters = 1;
+				break;
+			case 'v':
+				verbose = 1;
+				break;
+			case 'V':
+				printf("%s v%s (nf_tables)\n", prog_name, prog_vers);
+				exit(0);
+			case 't':
+				p.testing = 1;
+				break;
+			case 'h':
+				print_usage(prog_name, PACKAGE_VERSION);
+				exit(0);
+			case 'n':
+				noflush = true;
+				break;
+			case 'M':
+				xtables_modprobe_program = optarg;
+				break;
+			case 'T':
+				p.tablename = optarg;
+				break;
+			case 'w': /* fallthrough.  Ignored by xt-restore */
+			case 'W':
+				if (!optarg && xs_has_arg(argc, argv))
+					optind++;
+				break;
+			default:
+				fprintf(stderr,
+					"Try `%s -h' for more information.\n",
+					prog_name);
+				exit(1);
+		}
+	}
+
+	if (optind == argc - 1) {
+		p.in = fopen(argv[optind], "re");
+		if (!p.in) {
+			fprintf(stderr, "Can't open %s: %s\n", argv[optind],
+				strerror(errno));
+			exit(1);
+		}
+	} else if (optind < argc) {
+		fprintf(stderr, "Unknown arguments found on commandline\n");
+		exit(1);
+	} else {
+		p.in = stdin;
+	}
+
+	switch (family) {
+	case NFPROTO_IPV4:
+	case NFPROTO_IPV6: /* fallthough, same table */
+		tables = xtables_ipv4;
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+		init_extensions();
+		init_extensions4();
+#endif
+		break;
+	case NFPROTO_ARP:
+		tables = xtables_arp;
+		break;
+	case NFPROTO_BRIDGE:
+		tables = xtables_bridge;
+		break;
+	default:
+		fprintf(stderr, "Unknown family %d\n", family);
+		return 1;
+	}
+
+	if (nft_init(&h, family, tables) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	h.noflush = noflush;
+	h.restore = true;
+
+	xtables_restore_parse(&h, &p);
+
+	nft_fini(&h);
+	xtables_fini();
+	fclose(p.in);
+	return 0;
+}
+
+int xtables_ip4_restore_main(int argc, char *argv[])
+{
+	return xtables_restore_main(NFPROTO_IPV4, basename(*argv),
+				    argc, argv);
+}
+
+int xtables_ip6_restore_main(int argc, char *argv[])
+{
+	return xtables_restore_main(NFPROTO_IPV6, basename(*argv),
+				    argc, argv);
+}
+
+static const struct nft_xt_restore_cb ebt_restore_cb = {
+	.commit		= nft_bridge_commit,
+	.table_flush	= nft_cmd_table_flush,
+	.do_command	= do_commandeb,
+	.chain_set	= nft_cmd_chain_set,
+	.chain_restore  = nft_cmd_chain_restore,
+};
+
+static const struct option ebt_restore_options[] = {
+	{.name = "noflush", .has_arg = 0, .val = 'n'},
+	{ 0 }
+};
+
+int xtables_eb_restore_main(int argc, char *argv[])
+{
+	struct nft_xt_restore_parse p = {
+		.in = stdin,
+		.cb = &ebt_restore_cb,
+	};
+	bool noflush = false;
+	struct nft_handle h;
+	int c;
+
+	while ((c = getopt_long(argc, argv, "n",
+				ebt_restore_options, NULL)) != -1) {
+		switch(c) {
+		case 'n':
+			noflush = 1;
+			break;
+		default:
+			fprintf(stderr,
+				"Usage: ebtables-restore [ --noflush ]\n");
+			exit(1);
+			break;
+		}
+	}
+
+	nft_init_eb(&h, "ebtables-restore");
+	h.noflush = noflush;
+	xtables_restore_parse(&h, &p);
+	nft_fini_eb(&h);
+
+	return 0;
+}
+
+static const struct nft_xt_restore_cb arp_restore_cb = {
+	.commit		= nft_commit,
+	.table_flush	= nft_cmd_table_flush,
+	.do_command	= do_commandarp,
+	.chain_set	= nft_cmd_chain_set,
+	.chain_restore  = nft_cmd_chain_restore,
+};
+
+int xtables_arp_restore_main(int argc, char *argv[])
+{
+	struct nft_xt_restore_parse p = {
+		.in = stdin,
+		.cb = &arp_restore_cb,
+	};
+	struct nft_handle h;
+
+	nft_init_arp(&h, "arptables-restore");
+	xtables_restore_parse(&h, &p);
+	nft_fini(&h);
+	xtables_fini();
+
+	return 0;
+}
diff --git a/iptables/xtables-save.c b/iptables/xtables-save.c
new file mode 100644
index 0000000..d7901c6
--- /dev/null
+++ b/iptables/xtables-save.c
@@ -0,0 +1,272 @@
+/* Code to save the xtables state, in human readable-form. */
+/* (C) 1999 by Paul 'Rusty' Russell <rusty@rustcorp.com.au> and
+ * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This code is distributed under the terms of GNU GPL v2
+ *
+ */
+#include "config.h"
+#include <getopt.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <netdb.h>
+#include <unistd.h>
+#include "libiptc/libiptc.h"
+#include "iptables.h"
+#include "xtables-multi.h"
+#include "nft.h"
+#include "nft-cache.h"
+
+#include <libnftnl/chain.h>
+
+#ifndef NO_SHARED_LIBS
+#include <dlfcn.h>
+#endif
+
+#define prog_name xtables_globals.program_name
+#define prog_vers xtables_globals.program_version
+
+static const char *ipt_save_optstring = "bcdt:M:f:V";
+static const struct option ipt_save_options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "version",  .has_arg = false, .val = 'V'},
+	{.name = "dump",     .has_arg = false, .val = 'd'},
+	{.name = "table",    .has_arg = true,  .val = 't'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{.name = "file",     .has_arg = true,  .val = 'f'},
+	{NULL},
+};
+
+static const char *arp_save_optstring = "cM:V";
+static const struct option arp_save_options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "version",  .has_arg = false, .val = 'V'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{NULL},
+};
+
+static const char *ebt_save_optstring = "ct:M:V";
+static const struct option ebt_save_options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "version",  .has_arg = false, .val = 'V'},
+	{.name = "table",    .has_arg = true,  .val = 't'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{NULL},
+};
+
+struct do_output_data {
+	unsigned int format;
+	bool commit;
+};
+
+static int
+__do_output(struct nft_handle *h, const char *tablename, void *data)
+{
+	struct do_output_data *d = data;
+	time_t now;
+
+	if (!nft_table_builtin_find(h, tablename))
+		return 0;
+
+	if (!nft_is_table_compatible(h, tablename, NULL)) {
+		printf("# Table `%s' is incompatible, use 'nft' tool.\n",
+		       tablename);
+		return 0;
+	}
+
+	now = time(NULL);
+	printf("# Generated by %s v%s on %s", prog_name,
+	       prog_vers, ctime(&now));
+
+	printf("*%s\n", tablename);
+	/* Dump out chain names first,
+	 * thereby preventing dependency conflicts */
+	nft_chain_foreach(h, tablename, nft_chain_save, h);
+	nft_rule_save(h, tablename, d->format);
+	if (d->commit)
+		printf("COMMIT\n");
+
+	now = time(NULL);
+	printf("# Completed on %s", ctime(&now));
+	return 0;
+}
+
+static int
+do_output(struct nft_handle *h, const char *tablename, struct do_output_data *d)
+{
+	int ret;
+
+	if (!tablename) {
+		ret = nft_for_each_table(h, __do_output, d);
+		nft_check_xt_legacy(h->family, true);
+		return !!ret;
+	}
+
+	if (!nft_table_find(h, tablename) &&
+	    !nft_table_builtin_find(h, tablename)) {
+		fprintf(stderr, "Table `%s' does not exist\n", tablename);
+		return 1;
+	}
+
+	ret = __do_output(h, tablename, d);
+	nft_check_xt_legacy(h->family, true);
+	return ret;
+}
+
+/* Format:
+ * :Chain name POLICY packets bytes
+ * rule
+ */
+static int
+xtables_save_main(int family, int argc, char *argv[],
+		  const char *optstring, const struct option *longopts)
+{
+	const struct builtin_table *tables;
+	const char *tablename = NULL;
+	struct do_output_data d = {
+		.format = FMT_NOCOUNTS,
+	};
+	struct nft_handle h;
+	bool dump = false;
+	FILE *file = NULL;
+	int ret, c;
+
+	xtables_globals.program_name = basename(*argv);;
+	c = xtables_init_all(&xtables_globals, family);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+
+	while ((c = getopt_long(argc, argv, optstring, longopts, NULL)) != -1) {
+		switch (c) {
+		case 'b':
+			fprintf(stderr, "-b/--binary option is not implemented\n");
+			break;
+		case 'c':
+			d.format &= ~FMT_NOCOUNTS;
+			break;
+
+		case 't':
+			/* Select specific table. */
+			tablename = optarg;
+			break;
+		case 'M':
+			xtables_modprobe_program = optarg;
+			break;
+		case 'f':
+			file = fopen(optarg, "w");
+			if (file == NULL) {
+				fprintf(stderr, "Failed to open file, error: %s\n",
+					strerror(errno));
+				exit(1);
+			}
+			ret = dup2(fileno(file), STDOUT_FILENO);
+			if (ret == -1) {
+				fprintf(stderr, "Failed to redirect stdout, error: %s\n",
+					strerror(errno));
+				exit(1);
+			}
+			fclose(file);
+			break;
+		case 'd':
+			dump = true;
+			break;
+		case 'V':
+			printf("%s v%s (nf_tables)\n", prog_name, prog_vers);
+			exit(0);
+		default:
+			fprintf(stderr,
+				"Look at manual page `%s.8' for more information.\n",
+				prog_name);
+			exit(1);
+		}
+	}
+
+	if (optind < argc) {
+		fprintf(stderr, "Unknown arguments found on commandline\n");
+		exit(1);
+	}
+
+	switch (family) {
+	case NFPROTO_IPV4:
+	case NFPROTO_IPV6: /* fallthough, same table */
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+		init_extensions();
+		init_extensions4();
+#endif
+		tables = xtables_ipv4;
+		d.commit = true;
+		break;
+	case NFPROTO_ARP:
+		tables = xtables_arp;
+		break;
+	case NFPROTO_BRIDGE: {
+		const char *ctr = getenv("EBTABLES_SAVE_COUNTER");
+
+		if (!(d.format & FMT_NOCOUNTS)) {
+			d.format |= FMT_EBT_SAVE;
+		} else if (ctr && !strcmp(ctr, "yes")) {
+			d.format &= ~FMT_NOCOUNTS;
+			d.format |= FMT_C_COUNTS | FMT_EBT_SAVE;
+		}
+		tables = xtables_bridge;
+		break;
+	}
+	default:
+		fprintf(stderr, "Unknown family %d\n", family);
+		return 1;
+	}
+
+	if (nft_init(&h, family, tables) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	nft_cache_level_set(&h, NFT_CL_RULES, NULL);
+	nft_cache_build(&h);
+	nft_xt_fake_builtin_chains(&h, tablename, NULL);
+
+	ret = do_output(&h, tablename, &d);
+	nft_fini(&h);
+	xtables_fini();
+	if (dump)
+		exit(0);
+
+	return ret;
+}
+
+int xtables_ip4_save_main(int argc, char *argv[])
+{
+	return xtables_save_main(NFPROTO_IPV4, argc, argv,
+				 ipt_save_optstring, ipt_save_options);
+}
+
+int xtables_ip6_save_main(int argc, char *argv[])
+{
+	return xtables_save_main(NFPROTO_IPV6, argc, argv,
+				 ipt_save_optstring, ipt_save_options);
+}
+
+int xtables_eb_save_main(int argc, char *argv[])
+{
+	return xtables_save_main(NFPROTO_BRIDGE, argc, argv,
+				 ebt_save_optstring, ebt_save_options);
+}
+
+int xtables_arp_save_main(int argc, char *argv[])
+{
+	return xtables_save_main(NFPROTO_ARP, argc, argv,
+				 arp_save_optstring, arp_save_options);
+}
diff --git a/iptables/xtables-standalone.c b/iptables/xtables-standalone.c
new file mode 100644
index 0000000..7b71db6
--- /dev/null
+++ b/iptables/xtables-standalone.c
@@ -0,0 +1,97 @@
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * Based on the ipchains code by Paul Russell and Michael Neuling
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	iptables -- IP firewall administration for kernels with
+ *	firewall table (aimed for the 2.3 kernels)
+ *
+ *	See the accompanying manual page iptables(8) for information
+ *	about proper usage of this program.
+ *
+ *	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 2 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, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <iptables.h>
+#include "xtables-multi.h"
+#include "nft.h"
+
+static int
+xtables_main(int family, const char *progname, int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h;
+
+	xtables_globals.program_name = progname;
+	ret = xtables_init_all(&xtables_globals, family);
+	if (ret < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+				exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+
+	if (nft_init(&h, family, xtables_ipv4) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	ret = do_commandx(&h, argc, argv, &table, false);
+	if (ret)
+		ret = nft_commit(&h);
+
+	nft_fini(&h);
+	xtables_fini();
+
+	if (!ret) {
+		fprintf(stderr, "%s: %s.%s\n", progname, nft_strerror(errno),
+			(errno == EINVAL ?
+			 " Run `dmesg' for more information." : ""));
+
+		if (errno == EAGAIN)
+			exit(RESOURCE_PROBLEM);
+	}
+
+	exit(!ret);
+}
+
+int xtables_ip4_main(int argc, char *argv[])
+{
+	return xtables_main(NFPROTO_IPV4, "iptables", argc, argv);
+}
+
+int xtables_ip6_main(int argc, char *argv[])
+{
+	return xtables_main(NFPROTO_IPV6, "ip6tables", argc, argv);
+}
diff --git a/iptables/xtables-translate.8 b/iptables/xtables-translate.8
new file mode 100644
index 0000000..3dc7276
--- /dev/null
+++ b/iptables/xtables-translate.8
@@ -0,0 +1,136 @@
+.\"
+.\" (C) Copyright 2018, Arturo Borrero Gonzalez <arturo@netfilter.org>
+.\"
+.\" %%%LICENSE_START(GPLv2+_DOC_FULL)
+.\" This is free documentation; 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 2 of
+.\" the License, or (at your option) any later version.
+.\"
+.\" The GNU General Public License's references to "object code"
+.\" and "executables" are to be interpreted as the output of any
+.\" document formatting or typesetting system, including
+.\" intermediate and printed output.
+.\"
+.\" This manual 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 manual; if not, see
+.\" <http://www.gnu.org/licenses/>.
+.\" %%%LICENSE_END
+.\"
+.TH IPTABLES-TRANSLATE 8 "May 14, 2019"
+
+.SH NAME
+iptables-translate \(em translation tool to migrate from iptables to nftables
+.P
+ip6tables-translate \(em translation tool to migrate from ip6tables to nftables
+.SH DESCRIPTION
+There is a set of tools to help the system administrator translate a given
+ruleset from \fBiptables(8)\fP and \fBip6tables(8)\fP to \fBnftables(8)\fP.
+
+The available commands are:
+
+.IP \[bu] 2
+iptables-translate
+.IP \[bu]
+iptables-restore-translate
+.IP \[bu] 2
+ip6tables-translate
+.IP \[bu]
+ip6tables-restore-translate
+
+.SH USAGE
+They take as input the original \fBiptables(8)\fP/\fBip6tables(8)\fP syntax and
+output the native \fBnftables(8)\fP syntax.
+
+The \fBiptables-restore-translate\fP tool reads a ruleset in the syntax
+produced by \fBiptables-save(8)\fP. Likewise, the
+\fBip6tables-restore-translate\fP tool reads one produced by
+\fBip6tables-save(8)\fP.  No ruleset modifications occur, these tools are
+text converters only.
+
+The \fBiptables-translate\fP reads a command line as if it was entered to
+\fBiptables(8)\fP, and \fBip6tables-translate\fP reads a command like as if it
+was entered to \fBip6tables(8)\fP.
+
+.SH EXAMPLES
+Basic operation examples.
+
+Single command translation:
+
+.nf
+root@machine:~# iptables-translate -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+nft add rule ip filter INPUT tcp dport 22 ct state new counter accept
+
+root@machine:~# ip6tables-translate -A FORWARD -i eth0 -o eth3 -p udp -m multiport --dports 111,222 -j ACCEPT
+nft add rule ip6 filter FORWARD iifname eth0 oifname eth3 meta l4proto udp udp dport { 111,222} counter accept
+.fi
+
+Whole ruleset translation:
+
+.nf
+root@machine:~# iptables-save > save.txt
+root@machine:~# cat save.txt
+# Generated by iptables-save v1.6.0 on Sat Dec 24 14:26:40 2016
+*filter
+:INPUT ACCEPT [5166:1752111]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [5058:628693]
+-A FORWARD -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+COMMIT
+# Completed on Sat Dec 24 14:26:40 2016
+
+root@machine:~# iptables-restore-translate -f save.txt
+# Translated by iptables-restore-translate v1.6.0 on Sat Dec 24 14:26:59 2016
+add table ip filter
+add chain ip filter INPUT { type filter hook input priority 0; }
+add chain ip filter FORWARD { type filter hook forward priority 0; }
+add chain ip filter OUTPUT { type filter hook output priority 0; }
+add rule ip filter FORWARD tcp dport 22 ct state new counter accept
+
+root@machine:~# iptables-restore-translate -f save.txt > ruleset.nft
+root@machine:~# nft -f ruleset.nft
+root@machine:~# nft list ruleset
+table ip filter {
+	chain INPUT {
+		type filter hook input priority 0; policy accept;
+	}
+
+	chain FORWARD {
+		type filter hook forward priority 0; policy accept;
+		tcp dport ssh ct state new counter packets 0 bytes 0 accept
+	}
+
+	chain OUTPUT {
+		type filter hook output priority 0; policy accept;
+	}
+}
+.fi
+
+
+.SH LIMITATIONS
+Some (few) extensions may be not supported (or fully-supported) for whatever
+reason (for example, they were considered obsolete, or we didn't have the time
+to work on them).
+
+There are no translations available for \fBebtables(8)\fP and
+\fBarptables(8)\fP.
+
+To get up-to-date information about this, please head to
+\fBhttps://wiki.nftables.org/\fP.
+
+.SH SEE ALSO
+\fBnft(8)\fP, \fBiptables(8)\fP
+
+.SH AUTHORS
+The nftables framework is written by the Netfilter project
+(https://www.netfilter.org).
+
+This manual page was written by Arturo Borrero Gonzalez
+<arturo@netfilter.org>.
+
+This documentation is free/libre under the terms of the GPLv2+.
diff --git a/iptables/xtables-translate.c b/iptables/xtables-translate.c
new file mode 100644
index 0000000..575fb32
--- /dev/null
+++ b/iptables/xtables-translate.c
@@ -0,0 +1,599 @@
+/*
+ * (C) 2014 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ */
+#include "config.h"
+#include <time.h>
+#include "xtables-multi.h"
+#include "nft.h"
+
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <unistd.h>
+#include <iptables.h>
+#include <xtables.h>
+#include <libiptc/libxtc.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include "xshared.h"
+#include "nft-shared.h"
+
+void xlate_ifname(struct xt_xlate *xl, const char *nftmeta, const char *ifname,
+		  bool invert)
+{
+	int ifaclen = strlen(ifname), i, j;
+	char iface[IFNAMSIZ * 2];
+
+	if (ifaclen < 1 || ifaclen >= IFNAMSIZ)
+		return;
+
+	for (i = 0, j = 0; i < ifaclen + 1; i++, j++) {
+		switch (ifname[i]) {
+		case '*':
+			iface[j++] = '\\';
+			/* fall through */
+		default:
+			iface[j] = ifname[i];
+			break;
+		}
+	}
+
+	if (ifaclen == 1 && ifname[0] == '+') {
+		/* Nftables does not support wildcard only string. Workaround
+		 * is easy, given that this will match always or never
+		 * depending on 'invert' value. To match always, simply don't
+		 * generate an expression. To match never, use an invalid
+		 * interface name (kernel doesn't accept '/' in names) to match
+		 * against. */
+		if (!invert)
+			return;
+		strcpy(iface, "INVAL/D");
+		invert = false;
+	}
+
+	if (iface[j - 2] == '+')
+		iface[j - 2] = '*';
+
+	xt_xlate_add(xl, "%s %s\"%s\" ", nftmeta, invert ? "!= " : "", iface);
+}
+
+int xlate_action(const struct iptables_command_state *cs, bool goto_set,
+		 struct xt_xlate *xl)
+{
+	int ret = 1, numeric = cs->options & OPT_NUMERIC;
+
+	/* If no target at all, add nothing (default to continue) */
+	if (cs->target != NULL) {
+		/* Standard target? */
+		if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
+			xt_xlate_add(xl, " accept");
+		else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
+			xt_xlate_add(xl, " drop");
+		else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
+			xt_xlate_add(xl, " return");
+		else if (cs->target->xlate) {
+			xt_xlate_add(xl, " ");
+			struct xt_xlate_tg_params params = {
+				.ip		= (const void *)&cs->fw,
+				.target		= cs->target->t,
+				.numeric	= numeric,
+				.escape_quotes	= !cs->restore,
+			};
+			ret = cs->target->xlate(xl, &params);
+		}
+		else
+			return 0;
+	} else if (strlen(cs->jumpto) > 0) {
+		/* Not standard, then it's a go / jump to chain */
+		if (goto_set)
+			xt_xlate_add(xl, " goto %s", cs->jumpto);
+		else
+			xt_xlate_add(xl, " jump %s", cs->jumpto);
+	}
+
+	return ret;
+}
+
+int xlate_matches(const struct iptables_command_state *cs, struct xt_xlate *xl)
+{
+	struct xtables_rule_match *matchp;
+	int ret = 1, numeric = cs->options & OPT_NUMERIC;
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		struct xt_xlate_mt_params params = {
+			.ip		= (const void *)&cs->fw,
+			.match		= matchp->match->m,
+			.numeric	= numeric,
+			.escape_quotes	= !cs->restore,
+		};
+
+		if (!matchp->match->xlate)
+			return 0;
+
+		ret = matchp->match->xlate(xl, &params);
+
+		if (strcmp(matchp->match->name, "comment") != 0)
+			xt_xlate_add(xl, " ");
+
+		if (!ret)
+			break;
+	}
+	return ret;
+}
+
+bool xlate_find_match(const struct iptables_command_state *cs, const char *p_name)
+{
+	struct xtables_rule_match *matchp;
+
+	/* Skip redundant protocol, eg. ip protocol tcp tcp dport */
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		if (strcmp(matchp->match->name, p_name) == 0)
+			return true;
+	}
+	return false;
+}
+
+const char *family2str[] = {
+	[NFPROTO_IPV4]	= "ip",
+	[NFPROTO_IPV6]	= "ip6",
+};
+
+static int nft_rule_xlate_add(struct nft_handle *h,
+			      const struct nft_xt_cmd_parse *p,
+			      const struct iptables_command_state *cs,
+			      bool append)
+{
+	struct xt_xlate *xl = xt_xlate_alloc(10240);
+	int ret;
+
+	if (append) {
+		xt_xlate_add(xl, "add rule %s %s %s ",
+			   family2str[h->family], p->table, p->chain);
+	} else {
+		xt_xlate_add(xl, "insert rule %s %s %s ",
+			   family2str[h->family], p->table, p->chain);
+	}
+
+	ret = h->ops->xlate(cs, xl);
+	if (ret)
+		printf("%s\n", xt_xlate_get(xl));
+
+	xt_xlate_free(xl);
+	return ret;
+}
+
+static int xlate(struct nft_handle *h, struct nft_xt_cmd_parse *p,
+		 struct iptables_command_state *cs,
+		 struct xtables_args *args, bool append,
+		 int (*cb)(struct nft_handle *h,
+			   const struct nft_xt_cmd_parse *p,
+			   const struct iptables_command_state *cs,
+			   bool append))
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < args->s.naddrs; i++) {
+		switch (h->family) {
+		case AF_INET:
+			cs->fw.ip.src.s_addr = args->s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = args->s.mask.v4[i].s_addr;
+			for (j = 0; j < args->d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr =
+					args->d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr =
+					args->d.mask.v4[j].s_addr;
+				ret = cb(h, p, cs, append);
+			}
+			break;
+		case AF_INET6:
+			memcpy(&cs->fw6.ipv6.src,
+			       &args->s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &args->s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < args->d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &args->d.addr.v6[j],
+				       sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &args->d.mask.v6[j],
+				       sizeof(struct in6_addr));
+				ret = cb(h, p, cs, append);
+			}
+			break;
+		}
+		if (!cs->restore && i < args->s.naddrs - 1)
+			printf("nft ");
+	}
+
+	return ret;
+}
+
+static void print_ipt_cmd(int argc, char *argv[])
+{
+	int i;
+
+	printf("# ");
+	for (i = 1; i < argc; i++)
+		printf("%s ", argv[i]);
+
+	printf("\n");
+}
+
+static int do_command_xlate(struct nft_handle *h, int argc, char *argv[],
+			    char **table, bool restore)
+{
+	int ret = 0;
+	struct nft_xt_cmd_parse p = {
+		.table		= *table,
+		.restore	= restore,
+		.xlate		= true,
+	};
+	struct iptables_command_state cs;
+	struct xtables_args args = {
+		.family = h->family,
+	};
+
+	do_parse(h, argc, argv, &p, &cs, &args);
+
+	cs.restore = restore;
+
+	if (!restore && p.command != CMD_NONE)
+		printf("nft ");
+
+	switch (p.command) {
+	case CMD_APPEND:
+		ret = 1;
+		if (!xlate(h, &p, &cs, &args, true, nft_rule_xlate_add))
+			print_ipt_cmd(argc, argv);
+		break;
+	case CMD_DELETE:
+		break;
+	case CMD_DELETE_NUM:
+		break;
+	case CMD_CHECK:
+		break;
+	case CMD_REPLACE:
+		break;
+	case CMD_INSERT:
+		ret = 1;
+		if (!xlate(h, &p, &cs, &args, false, nft_rule_xlate_add))
+			print_ipt_cmd(argc, argv);
+		break;
+	case CMD_FLUSH:
+		if (p.chain) {
+			printf("flush chain %s %s %s\n",
+				family2str[h->family], p.table, p.chain);
+		} else {
+			printf("flush table %s %s\n",
+				family2str[h->family], p.table);
+		}
+		ret = 1;
+		break;
+	case CMD_ZERO:
+		break;
+	case CMD_ZERO_NUM:
+		break;
+	case CMD_LIST:
+	case CMD_LIST|CMD_ZERO:
+	case CMD_LIST|CMD_ZERO_NUM:
+		printf("list table %s %s\n",
+		       family2str[h->family], p.table);
+		ret = 1;
+		break;
+	case CMD_LIST_RULES:
+	case CMD_LIST_RULES|CMD_ZERO:
+	case CMD_LIST_RULES|CMD_ZERO_NUM:
+		break;
+	case CMD_NEW_CHAIN:
+		printf("add chain %s %s %s\n",
+		       family2str[h->family], p.table, p.chain);
+		ret = 1;
+		break;
+	case CMD_DELETE_CHAIN:
+		printf("delete chain %s %s %s\n",
+		       family2str[h->family], p.table, p.chain);
+		ret = 1;
+		break;
+	case CMD_RENAME_CHAIN:
+		break;
+	case CMD_SET_POLICY:
+		break;
+	case CMD_NONE:
+		ret = 1;
+		break;
+	default:
+		/* We should never reach this... */
+		printf("Unsupported command?\n");
+		exit(1);
+	}
+
+	nft_clear_iptables_command_state(&cs);
+
+	if (h->family == AF_INET) {
+		free(args.s.addr.v4);
+		free(args.s.mask.v4);
+		free(args.d.addr.v4);
+		free(args.d.mask.v4);
+	} else if (h->family == AF_INET6) {
+		free(args.s.addr.v6);
+		free(args.s.mask.v6);
+		free(args.d.addr.v6);
+		free(args.d.mask.v6);
+	}
+	xtables_free_opts(1);
+
+	return ret;
+}
+
+static void print_usage(const char *name, const char *version)
+{
+	fprintf(stderr, "%s %s "
+			"(c) 2014 by Pablo Neira Ayuso <pablo@netfilter.org>\n"
+			"Usage: %s [-h] [-f]\n"
+                        "	[ --help ]\n"
+                        "	[ --file=<FILE> ]\n", name, version, name);
+        exit(1);
+}
+
+static const struct option options[] = {
+	{ .name = "help",	.has_arg = false,	.val = 'h' },
+	{ .name = "file",	.has_arg = true,	.val = 'f' },
+	{ .name = "version",	.has_arg = false,	.val = 'V' },
+	{ NULL },
+};
+
+static int xlate_chain_user_restore(struct nft_handle *h, const char *chain,
+				    const char *table)
+{
+	printf("add chain %s %s %s\n", family2str[h->family], table, chain);
+	return 0;
+}
+
+static int commit(struct nft_handle *h)
+{
+	return 1;
+}
+
+static void xlate_table_new(struct nft_handle *h, const char *table)
+{
+	printf("add table %s %s\n", family2str[h->family], table);
+}
+
+static int get_hook_prio(const char *table, const char *chain)
+{
+	int prio = 0;
+
+	if (strcmp("nat", table) == 0) {
+		if (strcmp(chain, "PREROUTING") == 0)
+			prio = NF_IP_PRI_NAT_DST;
+		if (strcmp(chain, "INPUT") == 0)
+			prio = NF_IP_PRI_NAT_SRC;
+		if (strcmp(chain, "OUTPUT") == 0)
+			prio = NF_IP_PRI_NAT_DST;
+		if (strcmp(chain, "POSTROUTING") == 0)
+			prio = NF_IP_PRI_NAT_SRC;
+	} else if (strcmp("mangle", table) == 0) {
+		prio = NF_IP_PRI_MANGLE;
+	} else if (strcmp("raw", table) == 0) {
+		prio = NF_IP_PRI_RAW;
+	} else if (strcmp(chain, "security") == 0) {
+		prio = NF_IP_PRI_SECURITY;
+	}
+
+	return prio;
+}
+
+static int xlate_chain_set(struct nft_handle *h, const char *table,
+			   const char *chain, const char *policy,
+			   const struct xt_counters *counters)
+{
+	const char *type = "filter";
+	int prio;
+
+	if (strcmp(table, "nat") == 0)
+		type = "nat";
+	else if (strcmp(table, "mangle") == 0 && strcmp(chain, "OUTPUT") == 0)
+		type = "route";
+
+	printf("add chain %s %s %s { type %s ",
+	       family2str[h->family], table, chain, type);
+	prio = get_hook_prio(table, chain);
+	if (strcmp(chain, "PREROUTING") == 0)
+		printf("hook prerouting priority %d; ", prio);
+	else if (strcmp(chain, "INPUT") == 0)
+		printf("hook input priority %d; ", prio);
+	else if (strcmp(chain, "FORWARD") == 0)
+		printf("hook forward priority %d; ", prio);
+	else if (strcmp(chain, "OUTPUT") == 0)
+		printf("hook output priority %d; ", prio);
+	else if (strcmp(chain, "POSTROUTING") == 0)
+		printf("hook postrouting priority %d; ", prio);
+
+	if (strcmp(policy, "ACCEPT") == 0)
+		printf("policy accept; ");
+	else if (strcmp(policy, "DROP") == 0)
+		printf("policy drop; ");
+
+	printf("}\n");
+	return 1;
+}
+
+static int dummy_compat_rev(const char *name, uint8_t rev, int opt)
+{
+	/* Avoid querying the kernel - it's not needed when just translating
+	 * rules and not even possible when running as unprivileged user.
+	 */
+	return 1;
+}
+
+static const struct nft_xt_restore_cb cb_xlate = {
+	.table_new	= xlate_table_new,
+	.chain_set	= xlate_chain_set,
+	.chain_restore	= xlate_chain_user_restore,
+	.do_command	= do_command_xlate,
+	.commit		= commit,
+	.abort		= commit,
+};
+
+static int xtables_xlate_main_common(struct nft_handle *h,
+				     int family,
+				     const char *progname)
+{
+	const struct builtin_table *tables;
+	int ret;
+
+	xtables_globals.program_name = progname;
+	xtables_globals.compat_rev = dummy_compat_rev;
+	ret = xtables_init_all(&xtables_globals, family);
+	if (ret < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+			xtables_globals.program_name,
+			xtables_globals.program_version);
+		return 1;
+	}
+	switch (family) {
+	case NFPROTO_IPV4:
+	case NFPROTO_IPV6: /* fallthrough: same table */
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+		tables = xtables_ipv4;
+		break;
+	case NFPROTO_ARP:
+		tables = xtables_arp;
+		break;
+	case NFPROTO_BRIDGE:
+		tables = xtables_bridge;
+		break;
+	default:
+		fprintf(stderr, "Unknown family %d\n", family);
+		return 1;
+	}
+
+	if (nft_init(h, family, tables) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		return 1;
+	}
+
+	return 0;
+}
+
+static int xtables_xlate_main(int family, const char *progname, int argc,
+			      char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h = {
+		.family = family,
+	};
+
+	ret = xtables_xlate_main_common(&h, family, progname);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	ret = do_command_xlate(&h, argc, argv, &table, false);
+	if (!ret)
+		fprintf(stderr, "Translation not implemented\n");
+
+	nft_fini(&h);
+	xtables_fini();
+	exit(!ret);
+}
+
+static int xtables_restore_xlate_main(int family, const char *progname,
+				      int argc, char *argv[])
+{
+	int ret;
+	struct nft_handle h = {
+		.family = family,
+	};
+	const char *file = NULL;
+	struct nft_xt_restore_parse p = {
+		.cb = &cb_xlate,
+	};
+	time_t now = time(NULL);
+	int c;
+
+	ret = xtables_xlate_main_common(&h, family, progname);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	opterr = 0;
+	while ((c = getopt_long(argc, argv, "hf:V", options, NULL)) != -1) {
+		switch (c) {
+		case 'h':
+			print_usage(argv[0], PACKAGE_VERSION);
+			exit(0);
+		case 'f':
+			file = optarg;
+			break;
+		case 'V':
+			printf("%s v%s\n", argv[0], PACKAGE_VERSION);
+			exit(0);
+		}
+	}
+
+	if (file == NULL) {
+		fprintf(stderr, "ERROR: missing file name\n");
+		print_usage(argv[0], PACKAGE_VERSION);
+		exit(0);
+	}
+
+	p.in = fopen(file, "r");
+	if (p.in == NULL) {
+		fprintf(stderr, "Cannot open file %s\n", file);
+		exit(1);
+	}
+
+	printf("# Translated by %s v%s on %s",
+	       argv[0], PACKAGE_VERSION, ctime(&now));
+	xtables_restore_parse(&h, &p);
+	printf("# Completed on %s", ctime(&now));
+
+	nft_fini(&h);
+	xtables_fini();
+	fclose(p.in);
+	exit(0);
+}
+
+int xtables_ip4_xlate_main(int argc, char *argv[])
+{
+	return xtables_xlate_main(NFPROTO_IPV4, "iptables-translate",
+				  argc, argv);
+}
+
+int xtables_ip6_xlate_main(int argc, char *argv[])
+{
+	return xtables_xlate_main(NFPROTO_IPV6, "ip6tables-translate",
+				  argc, argv);
+}
+
+int xtables_ip4_xlate_restore_main(int argc, char *argv[])
+{
+	return xtables_restore_xlate_main(NFPROTO_IPV4,
+					  "iptables-translate-restore",
+					  argc, argv);
+}
+
+int xtables_ip6_xlate_restore_main(int argc, char *argv[])
+{
+	return xtables_restore_xlate_main(NFPROTO_IPV6,
+					  "ip6tables-translate-restore",
+					  argc, argv);
+}
diff --git a/iptables/xtables.c b/iptables/xtables.c
new file mode 100644
index 0000000..9779bd8
--- /dev/null
+++ b/iptables/xtables.c
@@ -0,0 +1,1078 @@
+/* Code to take an iptables-style command line and do it. */
+
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ *		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ *		    Marc Boucher <marc+nf@mbsi.ca>
+ *		    James Morris <jmorris@intercode.com.au>
+ *		    Harald Welte <laforge@gnumonks.org>
+ *		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	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 2 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, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "config.h"
+#include <getopt.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <unistd.h>
+#include <iptables.h>
+#include <xtables.h>
+#include <fcntl.h>
+#include "xshared.h"
+#include "nft-shared.h"
+#include "nft.h"
+
+static struct option original_opts[] = {
+	{.name = "append",	  .has_arg = 1, .val = 'A'},
+	{.name = "delete",	  .has_arg = 1, .val = 'D'},
+	{.name = "check",	  .has_arg = 1, .val = 'C'},
+	{.name = "insert",	  .has_arg = 1, .val = 'I'},
+	{.name = "replace",	  .has_arg = 1, .val = 'R'},
+	{.name = "list",	  .has_arg = 2, .val = 'L'},
+	{.name = "list-rules",	  .has_arg = 2, .val = 'S'},
+	{.name = "flush",	  .has_arg = 2, .val = 'F'},
+	{.name = "zero",	  .has_arg = 2, .val = 'Z'},
+	{.name = "new-chain",	  .has_arg = 1, .val = 'N'},
+	{.name = "delete-chain",  .has_arg = 2, .val = 'X'},
+	{.name = "rename-chain",  .has_arg = 1, .val = 'E'},
+	{.name = "policy",	  .has_arg = 1, .val = 'P'},
+	{.name = "source",	  .has_arg = 1, .val = 's'},
+	{.name = "destination",   .has_arg = 1, .val = 'd'},
+	{.name = "src",		  .has_arg = 1, .val = 's'}, /* synonym */
+	{.name = "dst",		  .has_arg = 1, .val = 'd'}, /* synonym */
+	{.name = "protocol",	  .has_arg = 1, .val = 'p'},
+	{.name = "in-interface",  .has_arg = 1, .val = 'i'},
+	{.name = "jump",	  .has_arg = 1, .val = 'j'},
+	{.name = "table",	  .has_arg = 1, .val = 't'},
+	{.name = "match",	  .has_arg = 1, .val = 'm'},
+	{.name = "numeric",	  .has_arg = 0, .val = 'n'},
+	{.name = "out-interface", .has_arg = 1, .val = 'o'},
+	{.name = "verbose",	  .has_arg = 0, .val = 'v'},
+	{.name = "wait",	  .has_arg = 2, .val = 'w'},
+	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
+	{.name = "exact",	  .has_arg = 0, .val = 'x'},
+	{.name = "fragments",	  .has_arg = 0, .val = 'f'},
+	{.name = "version",	  .has_arg = 0, .val = 'V'},
+	{.name = "help",	  .has_arg = 2, .val = 'h'},
+	{.name = "line-numbers",  .has_arg = 0, .val = '0'},
+	{.name = "modprobe",	  .has_arg = 1, .val = 'M'},
+	{.name = "set-counters",  .has_arg = 1, .val = 'c'},
+	{.name = "goto",	  .has_arg = 1, .val = 'g'},
+	{.name = "ipv4",	  .has_arg = 0, .val = '4'},
+	{.name = "ipv6",	  .has_arg = 0, .val = '6'},
+	{NULL},
+};
+
+void xtables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+
+struct xtables_globals xtables_globals = {
+	.option_offset = 0,
+	.program_version = PACKAGE_VERSION,
+	.orig_opts = original_opts,
+	.exit_err = xtables_exit_error,
+	.compat_rev = nft_compatible_revision,
+};
+
+static const int inverse_for_options[NUMBER_OF_OPT] =
+{
+/* -n */ 0,
+/* -s */ IPT_INV_SRCIP,
+/* -d */ IPT_INV_DSTIP,
+/* -p */ XT_INV_PROTO,
+/* -j */ 0,
+/* -v */ 0,
+/* -x */ 0,
+/* -i */ IPT_INV_VIA_IN,
+/* -o */ IPT_INV_VIA_OUT,
+/*--line*/ 0,
+/* -c */ 0,
+/* -f */ IPT_INV_FRAG,
+};
+
+#define opts xt_params->opts
+#define prog_name xt_params->program_name
+#define prog_vers xt_params->program_version
+
+static void __attribute__((noreturn))
+exit_tryhelp(int status)
+{
+	if (line != -1)
+		fprintf(stderr, "Error occurred at line: %d\n", line);
+	fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
+			prog_name, prog_name);
+	xtables_free_opts(1);
+	exit(status);
+}
+
+static void
+printhelp(const struct xtables_rule_match *matches)
+{
+	printf("%s v%s\n\n"
+"Usage: %s -[ACD] chain rule-specification [options]\n"
+"	%s -I chain [rulenum] rule-specification [options]\n"
+"	%s -R chain rulenum rule-specification [options]\n"
+"	%s -D chain rulenum [options]\n"
+"	%s -[LS] [chain [rulenum]] [options]\n"
+"	%s -[FZ] [chain] [options]\n"
+"	%s -[NX] chain\n"
+"	%s -E old-chain-name new-chain-name\n"
+"	%s -P chain target [options]\n"
+"	%s -h (print this help information)\n\n",
+	       prog_name, prog_vers, prog_name, prog_name,
+	       prog_name, prog_name, prog_name, prog_name,
+	       prog_name, prog_name, prog_name, prog_name);
+
+	printf(
+"Commands:\n"
+"Either long or short options are allowed.\n"
+"  --append  -A chain		Append to chain\n"
+"  --check   -C chain		Check for the existence of a rule\n"
+"  --delete  -D chain		Delete matching rule from chain\n"
+"  --delete  -D chain rulenum\n"
+"				Delete rule rulenum (1 = first) from chain\n"
+"  --insert  -I chain [rulenum]\n"
+"				Insert in chain as rulenum (default 1=first)\n"
+"  --replace -R chain rulenum\n"
+"				Replace rule rulenum (1 = first) in chain\n"
+"  --list    -L [chain [rulenum]]\n"
+"				List the rules in a chain or all chains\n"
+"  --list-rules -S [chain [rulenum]]\n"
+"				Print the rules in a chain or all chains\n"
+"  --flush   -F [chain]		Delete all rules in  chain or all chains\n"
+"  --zero    -Z [chain [rulenum]]\n"
+"				Zero counters in chain or all chains\n"
+"  --new     -N chain		Create a new user-defined chain\n"
+"  --delete-chain\n"
+"	     -X [chain]		Delete a user-defined chain\n"
+"  --policy  -P chain target\n"
+"				Change policy on chain to target\n"
+"  --rename-chain\n"
+"	     -E old-chain new-chain\n"
+"				Change chain name, (moving any references)\n"
+
+"Options:\n"
+"    --ipv4	-4		Nothing (line is ignored by ip6tables-restore)\n"
+"    --ipv6	-6		Error (line is ignored by iptables-restore)\n"
+"[!] --proto	-p proto	protocol: by number or name, eg. `tcp'\n"
+"[!] --source	-s address[/mask][...]\n"
+"				source specification\n"
+"[!] --destination -d address[/mask][...]\n"
+"				destination specification\n"
+"[!] --in-interface -i input name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+" --jump	-j target\n"
+"				target for rule (may load target extension)\n"
+#ifdef IPT_F_GOTO
+"  --goto      -g chain\n"
+"			       jump to chain with no return\n"
+#endif
+"  --match	-m match\n"
+"				extended match (may load extension)\n"
+"  --numeric	-n		numeric output of addresses and ports\n"
+"[!] --out-interface -o output name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --table	-t table	table to manipulate (default: `filter')\n"
+"  --verbose	-v		verbose mode\n"
+"  --wait	-w [seconds]	maximum wait to acquire xtables lock before give up\n"
+"  --wait-interval -W [usecs]	wait time to try to acquire xtables lock\n"
+"				default is 1 second\n"
+"  --line-numbers		print line numbers when listing\n"
+"  --exact	-x		expand numbers (display exact values)\n"
+"[!] --fragment	-f		match second or further fragments only\n"
+"  --modprobe=<command>		try to insert modules using this command\n"
+"  --set-counters PKTS BYTES	set the counter during insert/append\n"
+"[!] --version	-V		print package version.\n");
+
+	print_extension_helps(xtables_targets, matches);
+}
+
+void
+xtables_exit_error(enum xtables_exittype status, const char *msg, ...)
+{
+	va_list args;
+
+	va_start(args, msg);
+	fprintf(stderr, "%s v%s (nf_tables): ", prog_name, prog_vers);
+	vfprintf(stderr, msg, args);
+	va_end(args);
+	fprintf(stderr, "\n");
+	if (status == PARAMETER_PROBLEM)
+		exit_tryhelp(status);
+	if (status == VERSION_PROBLEM)
+		fprintf(stderr,
+			"Perhaps iptables or your kernel needs to be upgraded.\n");
+	/* On error paths, make sure that we don't leak memory */
+	xtables_free_opts(1);
+	exit(status);
+}
+
+/*
+ *	All functions starting with "parse" should succeed, otherwise
+ *	the program fails.
+ *	Most routines return pointers to static data that may change
+ *	between calls to the same or other routines with a few exceptions:
+ *	"host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask"
+ *	return global static data.
+*/
+
+/* Christophe Burki wants `-p 6' to imply `-m tcp'.  */
+
+static void
+set_option(unsigned int *options, unsigned int option, uint8_t *invflg,
+	   int invert)
+{
+	if (*options & option)
+		xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed",
+			   opt2char(option));
+	*options |= option;
+
+	if (invert) {
+		unsigned int i;
+		for (i = 0; 1 << i != option; i++);
+
+		if (!inverse_for_options[i])
+			xtables_error(PARAMETER_PROBLEM,
+				   "cannot have ! before -%c",
+				   opt2char(option));
+		*invflg |= inverse_for_options[i];
+	}
+}
+
+static int
+add_entry(const char *chain,
+	  const char *table,
+	  struct iptables_command_state *cs,
+	  int rulenum, int family,
+	  const struct addr_mask s,
+	  const struct addr_mask d,
+	  bool verbose, struct nft_handle *h, bool append)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < s.naddrs; i++) {
+		if (family == AF_INET) {
+			cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr;
+			for (j = 0; j < d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr;
+
+				if (append) {
+					ret = nft_cmd_rule_append(h, chain, table,
+							      cs, NULL,
+							      verbose);
+				} else {
+					ret = nft_cmd_rule_insert(h, chain, table,
+							      cs, rulenum,
+							      verbose);
+				}
+			}
+		} else if (family == AF_INET6) {
+			memcpy(&cs->fw6.ipv6.src,
+			       &s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &d.addr.v6[j], sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &d.mask.v6[j], sizeof(struct in6_addr));
+				if (append) {
+					ret = nft_cmd_rule_append(h, chain, table,
+							      cs, NULL,
+							      verbose);
+				} else {
+					ret = nft_cmd_rule_insert(h, chain, table,
+							      cs, rulenum,
+							      verbose);
+				}
+			}
+		}
+	}
+
+	return ret;
+}
+
+static int
+replace_entry(const char *chain, const char *table,
+	      struct iptables_command_state *cs,
+	      unsigned int rulenum,
+	      int family,
+	      const struct addr_mask s,
+	      const struct addr_mask d,
+	      bool verbose, struct nft_handle *h)
+{
+	if (family == AF_INET) {
+		cs->fw.ip.src.s_addr = s.addr.v4->s_addr;
+		cs->fw.ip.dst.s_addr = d.addr.v4->s_addr;
+		cs->fw.ip.smsk.s_addr = s.mask.v4->s_addr;
+		cs->fw.ip.dmsk.s_addr = d.mask.v4->s_addr;
+	} else if (family == AF_INET6) {
+		memcpy(&cs->fw6.ipv6.src, s.addr.v6, sizeof(struct in6_addr));
+		memcpy(&cs->fw6.ipv6.dst, d.addr.v6, sizeof(struct in6_addr));
+		memcpy(&cs->fw6.ipv6.smsk, s.mask.v6, sizeof(struct in6_addr));
+		memcpy(&cs->fw6.ipv6.dmsk, d.mask.v6, sizeof(struct in6_addr));
+	} else
+		return 1;
+
+	return nft_cmd_rule_replace(h, chain, table, cs, rulenum, verbose);
+}
+
+static int
+delete_entry(const char *chain, const char *table,
+	     struct iptables_command_state *cs,
+	     int family,
+	     const struct addr_mask s,
+	     const struct addr_mask d,
+	     bool verbose,
+	     struct nft_handle *h)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < s.naddrs; i++) {
+		if (family == AF_INET) {
+			cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr;
+			for (j = 0; j < d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr;
+				ret = nft_cmd_rule_delete(h, chain,
+						      table, cs, verbose);
+			}
+		} else if (family == AF_INET6) {
+			memcpy(&cs->fw6.ipv6.src,
+			       &s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &d.addr.v6[j], sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &d.mask.v6[j], sizeof(struct in6_addr));
+				ret = nft_cmd_rule_delete(h, chain,
+						      table, cs, verbose);
+			}
+		}
+	}
+
+	return ret;
+}
+
+static int
+check_entry(const char *chain, const char *table,
+	    struct iptables_command_state *cs,
+	    int family,
+	    const struct addr_mask s,
+	    const struct addr_mask d,
+	    bool verbose, struct nft_handle *h)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < s.naddrs; i++) {
+		if (family == AF_INET) {
+			cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr;
+			for (j = 0; j < d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr;
+				ret = nft_cmd_rule_check(h, chain,
+						     table, cs, verbose);
+			}
+		} else if (family == AF_INET6) {
+			memcpy(&cs->fw6.ipv6.src,
+			       &s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &d.addr.v6[j], sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &d.mask.v6[j], sizeof(struct in6_addr));
+				ret = nft_cmd_rule_check(h, chain,
+						     table, cs, verbose);
+			}
+		}
+	}
+
+	return ret;
+}
+
+static int
+list_entries(struct nft_handle *h, const char *chain, const char *table,
+	     int rulenum, int verbose, int numeric, int expanded,
+	     int linenumbers)
+{
+	unsigned int format;
+
+	format = FMT_OPTIONS;
+	if (!verbose)
+		format |= FMT_NOCOUNTS;
+	else
+		format |= FMT_VIA;
+
+	if (numeric)
+		format |= FMT_NUMERIC;
+
+	if (!expanded)
+		format |= FMT_KILOMEGAGIGA;
+
+	if (linenumbers)
+		format |= FMT_LINENUMBERS;
+
+	return nft_cmd_rule_list(h, chain, table, rulenum, format);
+}
+
+static int
+list_rules(struct nft_handle *h, const char *chain, const char *table,
+	   int rulenum, int counters)
+{
+	if (counters)
+	    counters = -1;		/* iptables -c format */
+
+	return nft_cmd_rule_list_save(h, chain, table, rulenum, counters);
+}
+
+void do_parse(struct nft_handle *h, int argc, char *argv[],
+	      struct nft_xt_cmd_parse *p, struct iptables_command_state *cs,
+	      struct xtables_args *args)
+{
+	struct xtables_match *m;
+	struct xtables_rule_match *matchp;
+	bool wait_interval_set = false;
+	struct timeval wait_interval;
+	struct xtables_target *t;
+	bool table_set = false;
+	int wait = 0;
+
+	memset(cs, 0, sizeof(*cs));
+	cs->jumpto = "";
+	cs->argv = argv;
+
+	/* re-set optind to 0 in case do_command4 gets called
+	 * a second time */
+	optind = 0;
+
+	/* clear mflags in case do_command4 gets called a second time
+	 * (we clear the global list of all matches for security)*/
+	for (m = xtables_matches; m; m = m->next)
+		m->mflags = 0;
+
+	for (t = xtables_targets; t; t = t->next) {
+		t->tflags = 0;
+		t->used = 0;
+	}
+
+	/* Suppress error messages: we may add new options if we
+	   demand-load a protocol. */
+	opterr = 0;
+
+	opts = xt_params->orig_opts;
+	while ((cs->c = getopt_long(argc, argv,
+	   "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvw::W::nt:m:xc:g:46",
+					   opts, NULL)) != -1) {
+		switch (cs->c) {
+			/*
+			 * Command selection
+			 */
+		case 'A':
+			add_command(&p->command, CMD_APPEND, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			break;
+
+		case 'C':
+			add_command(&p->command, CMD_CHECK, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			break;
+
+		case 'D':
+			add_command(&p->command, CMD_DELETE, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			if (xs_has_arg(argc, argv)) {
+				p->rulenum = parse_rulenumber(argv[optind++]);
+				p->command = CMD_DELETE_NUM;
+			}
+			break;
+
+		case 'R':
+			add_command(&p->command, CMD_REPLACE, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			if (xs_has_arg(argc, argv))
+				p->rulenum = parse_rulenumber(argv[optind++]);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires a rule number",
+					   cmd2char(CMD_REPLACE));
+			break;
+
+		case 'I':
+			add_command(&p->command, CMD_INSERT, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			if (xs_has_arg(argc, argv))
+				p->rulenum = parse_rulenumber(argv[optind++]);
+			else
+				p->rulenum = 1;
+			break;
+
+		case 'L':
+			add_command(&p->command, CMD_LIST,
+				    CMD_ZERO | CMD_ZERO_NUM, cs->invert);
+			if (optarg)
+				p->chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				p->chain = argv[optind++];
+			if (xs_has_arg(argc, argv))
+				p->rulenum = parse_rulenumber(argv[optind++]);
+			break;
+
+		case 'S':
+			add_command(&p->command, CMD_LIST_RULES,
+				    CMD_ZERO|CMD_ZERO_NUM, cs->invert);
+			if (optarg)
+				p->chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				p->chain = argv[optind++];
+			if (xs_has_arg(argc, argv))
+				p->rulenum = parse_rulenumber(argv[optind++]);
+			break;
+
+		case 'F':
+			add_command(&p->command, CMD_FLUSH, CMD_NONE,
+				    cs->invert);
+			if (optarg)
+				p->chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				p->chain = argv[optind++];
+			break;
+
+		case 'Z':
+			add_command(&p->command, CMD_ZERO,
+				    CMD_LIST|CMD_LIST_RULES, cs->invert);
+			if (optarg)
+				p->chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				p->chain = argv[optind++];
+			if (xs_has_arg(argc, argv)) {
+				p->rulenum = parse_rulenumber(argv[optind++]);
+				p->command = CMD_ZERO_NUM;
+			}
+			break;
+
+		case 'N':
+			if (optarg && (*optarg == '-' || *optarg == '!'))
+				xtables_error(PARAMETER_PROBLEM,
+					   "chain name not allowed to start "
+					   "with `%c'\n", *optarg);
+			if (xtables_find_target(optarg, XTF_TRY_LOAD))
+				xtables_error(PARAMETER_PROBLEM,
+					   "chain name may not clash "
+					   "with target name\n");
+			add_command(&p->command, CMD_NEW_CHAIN, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			break;
+
+		case 'X':
+			add_command(&p->command, CMD_DELETE_CHAIN, CMD_NONE,
+				    cs->invert);
+			if (optarg)
+				p->chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				p->chain = argv[optind++];
+			break;
+
+		case 'E':
+			add_command(&p->command, CMD_RENAME_CHAIN, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			if (xs_has_arg(argc, argv))
+				p->newname = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires old-chain-name and "
+					   "new-chain-name",
+					    cmd2char(CMD_RENAME_CHAIN));
+			break;
+
+		case 'P':
+			add_command(&p->command, CMD_SET_POLICY, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			if (xs_has_arg(argc, argv))
+				p->policy = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires a chain and a policy",
+					   cmd2char(CMD_SET_POLICY));
+			break;
+
+		case 'h':
+			if (!optarg)
+				optarg = argv[optind];
+
+			/* iptables -p icmp -h */
+			if (!cs->matches && cs->protocol)
+				xtables_find_match(cs->protocol,
+					XTF_TRY_LOAD, &cs->matches);
+
+			printhelp(cs->matches);
+			p->command = CMD_NONE;
+			return;
+
+			/*
+			 * Option selection
+			 */
+		case 'p':
+			set_option(&cs->options, OPT_PROTOCOL,
+				   &args->invflags, cs->invert);
+
+			/* Canonicalize into lower case */
+			for (cs->protocol = optarg; *cs->protocol; cs->protocol++)
+				*cs->protocol = tolower(*cs->protocol);
+
+			cs->protocol = optarg;
+			args->proto = xtables_parse_protocol(cs->protocol);
+
+			if (args->proto == 0 &&
+			    (args->invflags & XT_INV_PROTO))
+				xtables_error(PARAMETER_PROBLEM,
+					   "rule would never match protocol");
+
+			/* This needs to happen here to parse extensions */
+			h->ops->proto_parse(cs, args);
+			break;
+
+		case 's':
+			set_option(&cs->options, OPT_SOURCE,
+				   &args->invflags, cs->invert);
+			args->shostnetworkmask = optarg;
+			break;
+
+		case 'd':
+			set_option(&cs->options, OPT_DESTINATION,
+				   &args->invflags, cs->invert);
+			args->dhostnetworkmask = optarg;
+			break;
+
+#ifdef IPT_F_GOTO
+		case 'g':
+			set_option(&cs->options, OPT_JUMP, &args->invflags,
+				   cs->invert);
+			args->goto_set = true;
+			cs->jumpto = xt_parse_target(optarg);
+			break;
+#endif
+
+		case 'j':
+			set_option(&cs->options, OPT_JUMP, &cs->fw.ip.invflags,
+				   cs->invert);
+			command_jump(cs, optarg);
+			break;
+
+
+		case 'i':
+			if (*optarg == '\0')
+				xtables_error(PARAMETER_PROBLEM,
+					"Empty interface is likely to be "
+					"undesired");
+			set_option(&cs->options, OPT_VIANAMEIN,
+				   &args->invflags, cs->invert);
+			xtables_parse_interface(optarg,
+						args->iniface,
+						args->iniface_mask);
+			break;
+
+		case 'o':
+			if (*optarg == '\0')
+				xtables_error(PARAMETER_PROBLEM,
+					"Empty interface is likely to be "
+					"undesired");
+			set_option(&cs->options, OPT_VIANAMEOUT,
+				   &args->invflags, cs->invert);
+			xtables_parse_interface(optarg,
+						args->outiface,
+						args->outiface_mask);
+			break;
+
+		case 'f':
+			if (args->family == AF_INET6) {
+				xtables_error(PARAMETER_PROBLEM,
+					"`-f' is not supported in IPv6, "
+					"use -m frag instead");
+			}
+			set_option(&cs->options, OPT_FRAGMENT, &args->invflags,
+				   cs->invert);
+			args->flags |= IPT_F_FRAG;
+			break;
+
+		case 'v':
+			if (!p->verbose)
+				set_option(&cs->options, OPT_VERBOSE,
+					   &args->invflags, cs->invert);
+			p->verbose++;
+			break;
+
+		case 'm':
+			command_match(cs);
+			break;
+
+		case 'n':
+			set_option(&cs->options, OPT_NUMERIC, &args->invflags,
+				   cs->invert);
+			break;
+
+		case 't':
+			if (cs->invert)
+				xtables_error(PARAMETER_PROBLEM,
+					   "unexpected ! flag before --table");
+			if (p->restore && table_set)
+				xtables_error(PARAMETER_PROBLEM,
+					      "The -t option (seen in line %u) cannot be used in %s.\n",
+					      line, xt_params->program_name);
+			if (!nft_table_builtin_find(h, optarg))
+				xtables_error(VERSION_PROBLEM,
+					      "table '%s' does not exist",
+					      optarg);
+			p->table = optarg;
+			table_set = true;
+			break;
+
+		case 'x':
+			set_option(&cs->options, OPT_EXPANDED, &args->invflags,
+				   cs->invert);
+			break;
+
+		case 'V':
+			if (cs->invert)
+				printf("Not %s ;-)\n", prog_vers);
+			else
+				printf("%s v%s (nf_tables)\n",
+				       prog_name, prog_vers);
+			exit(0);
+
+		case 'w':
+			if (p->restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-w' from "
+					      "iptables-restore");
+			}
+
+			wait = parse_wait_time(argc, argv);
+			break;
+
+		case 'W':
+			if (p->restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-W' from "
+					      "iptables-restore");
+			}
+
+			parse_wait_interval(argc, argv, &wait_interval);
+			wait_interval_set = true;
+			break;
+
+		case '0':
+			set_option(&cs->options, OPT_LINENUMBERS,
+				   &args->invflags, cs->invert);
+			break;
+
+		case 'M':
+			xtables_modprobe_program = optarg;
+			break;
+
+		case 'c':
+			set_option(&cs->options, OPT_COUNTERS, &args->invflags,
+				   cs->invert);
+			args->pcnt = optarg;
+			args->bcnt = strchr(args->pcnt + 1, ',');
+			if (args->bcnt)
+			    args->bcnt++;
+			if (!args->bcnt && xs_has_arg(argc, argv))
+				args->bcnt = argv[optind++];
+			if (!args->bcnt)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c requires packet and byte counter",
+					opt2char(OPT_COUNTERS));
+
+			if (sscanf(args->pcnt, "%llu", &args->pcnt_cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c packet counter not numeric",
+					opt2char(OPT_COUNTERS));
+
+			if (sscanf(args->bcnt, "%llu", &args->bcnt_cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c byte counter not numeric",
+					opt2char(OPT_COUNTERS));
+			break;
+
+		case '4':
+			if (args->family == AF_INET)
+				break;
+
+			if (p->restore && args->family == AF_INET6)
+				return;
+
+			exit_tryhelp(2);
+
+		case '6':
+			if (args->family == AF_INET6)
+				break;
+
+			if (p->restore && args->family == AF_INET)
+				return;
+
+			exit_tryhelp(2);
+
+		case 1: /* non option */
+			if (optarg[0] == '!' && optarg[1] == '\0') {
+				if (cs->invert)
+					xtables_error(PARAMETER_PROBLEM,
+						   "multiple consecutive ! not"
+						   " allowed");
+				cs->invert = true;
+				optarg[0] = '\0';
+				continue;
+			}
+			fprintf(stderr, "Bad argument `%s'\n", optarg);
+			exit_tryhelp(2);
+
+		default:
+			if (command_default(cs, &xtables_globals) == 1)
+				/* cf. ip6tables.c */
+				continue;
+			break;
+		}
+		cs->invert = false;
+	}
+
+	if (strcmp(p->table, "nat") == 0 &&
+	    ((p->policy != NULL && strcmp(p->policy, "DROP") == 0) ||
+	    (cs->jumpto != NULL && strcmp(cs->jumpto, "DROP") == 0)))
+		xtables_error(PARAMETER_PROBLEM,
+			"\nThe \"nat\" table is not intended for filtering, "
+			"the use of DROP is therefore inhibited.\n\n");
+
+	if (!wait && wait_interval_set)
+		xtables_error(PARAMETER_PROBLEM,
+			      "--wait-interval only makes sense with --wait\n");
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next)
+		xtables_option_mfcall(matchp->match);
+	if (cs->target != NULL)
+		xtables_option_tfcall(cs->target);
+
+	/* Fix me: must put inverse options checking here --MN */
+
+	if (optind < argc)
+		xtables_error(PARAMETER_PROBLEM,
+			   "unknown arguments found on commandline");
+	if (!p->command)
+		xtables_error(PARAMETER_PROBLEM, "no command specified");
+	if (cs->invert)
+		xtables_error(PARAMETER_PROBLEM,
+			   "nothing appropriate following !");
+
+	/* Set only if required, needed by xtables-restore */
+	if (h->family == AF_UNSPEC)
+		h->family = args->family;
+
+	h->ops->post_parse(p->command, cs, args);
+
+	if (p->command == CMD_REPLACE &&
+	    (args->s.naddrs != 1 || args->d.naddrs != 1))
+		xtables_error(PARAMETER_PROBLEM, "Replacement rule does not "
+			   "specify a unique address");
+
+	generic_opt_check(p->command, cs->options);
+
+	if (p->chain != NULL && strlen(p->chain) >= XT_EXTENSION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name `%s' too long (must be under %u chars)",
+			   p->chain, XT_EXTENSION_MAXNAMELEN);
+
+	if (p->command == CMD_APPEND ||
+	    p->command == CMD_DELETE ||
+	    p->command == CMD_DELETE_NUM ||
+	    p->command == CMD_CHECK ||
+	    p->command == CMD_INSERT ||
+	    p->command == CMD_REPLACE) {
+		if (strcmp(p->chain, "PREROUTING") == 0
+		    || strcmp(p->chain, "INPUT") == 0) {
+			/* -o not valid with incoming packets. */
+			if (cs->options & OPT_VIANAMEOUT)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Can't use -%c with %s\n",
+					   opt2char(OPT_VIANAMEOUT),
+					   p->chain);
+		}
+
+		if (strcmp(p->chain, "POSTROUTING") == 0
+		    || strcmp(p->chain, "OUTPUT") == 0) {
+			/* -i not valid with outgoing packets */
+			if (cs->options & OPT_VIANAMEIN)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Can't use -%c with %s\n",
+					   opt2char(OPT_VIANAMEIN),
+					   p->chain);
+		}
+	}
+}
+
+int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table,
+		bool restore)
+{
+	int ret = 1;
+	struct nft_xt_cmd_parse p = {
+		.table		= *table,
+		.restore	= restore,
+	};
+	struct iptables_command_state cs;
+	struct xtables_args args = {
+		.family = h->family,
+	};
+
+	do_parse(h, argc, argv, &p, &cs, &args);
+
+	switch (p.command) {
+	case CMD_APPEND:
+		ret = add_entry(p.chain, p.table, &cs, 0, h->family,
+				args.s, args.d,
+				cs.options & OPT_VERBOSE, h, true);
+		break;
+	case CMD_DELETE:
+		ret = delete_entry(p.chain, p.table, &cs, h->family,
+				   args.s, args.d,
+				   cs.options & OPT_VERBOSE, h);
+		break;
+	case CMD_DELETE_NUM:
+		ret = nft_cmd_rule_delete_num(h, p.chain, p.table,
+					      p.rulenum - 1, p.verbose);
+		break;
+	case CMD_CHECK:
+		ret = check_entry(p.chain, p.table, &cs, h->family,
+				  args.s, args.d,
+				  cs.options & OPT_VERBOSE, h);
+		break;
+	case CMD_REPLACE:
+		ret = replace_entry(p.chain, p.table, &cs, p.rulenum - 1,
+				    h->family, args.s, args.d,
+				    cs.options & OPT_VERBOSE, h);
+		break;
+	case CMD_INSERT:
+		ret = add_entry(p.chain, p.table, &cs, p.rulenum - 1,
+				h->family, args.s, args.d,
+				cs.options&OPT_VERBOSE, h, false);
+		break;
+	case CMD_FLUSH:
+		ret = nft_cmd_rule_flush(h, p.chain, p.table,
+					 cs.options & OPT_VERBOSE);
+		break;
+	case CMD_ZERO:
+		ret = nft_cmd_chain_zero_counters(h, p.chain, p.table,
+						  cs.options & OPT_VERBOSE);
+		break;
+	case CMD_ZERO_NUM:
+		ret = nft_cmd_rule_zero_counters(h, p.chain, p.table,
+					     p.rulenum - 1);
+		break;
+	case CMD_LIST:
+	case CMD_LIST|CMD_ZERO:
+	case CMD_LIST|CMD_ZERO_NUM:
+		ret = list_entries(h, p.chain, p.table, p.rulenum,
+				   cs.options & OPT_VERBOSE,
+				   cs.options & OPT_NUMERIC,
+				   cs.options & OPT_EXPANDED,
+				   cs.options & OPT_LINENUMBERS);
+		if (ret && (p.command & CMD_ZERO)) {
+			ret = nft_cmd_chain_zero_counters(h, p.chain, p.table,
+						      cs.options & OPT_VERBOSE);
+		}
+		if (ret && (p.command & CMD_ZERO_NUM)) {
+			ret = nft_cmd_rule_zero_counters(h, p.chain, p.table,
+						     p.rulenum - 1);
+		}
+		nft_check_xt_legacy(h->family, false);
+		break;
+	case CMD_LIST_RULES:
+	case CMD_LIST_RULES|CMD_ZERO:
+	case CMD_LIST_RULES|CMD_ZERO_NUM:
+		ret = list_rules(h, p.chain, p.table, p.rulenum,
+				 cs.options & OPT_VERBOSE);
+		if (ret && (p.command & CMD_ZERO)) {
+			ret = nft_cmd_chain_zero_counters(h, p.chain, p.table,
+						      cs.options & OPT_VERBOSE);
+		}
+		if (ret && (p.command & CMD_ZERO_NUM)) {
+			ret = nft_cmd_rule_zero_counters(h, p.chain, p.table,
+						     p.rulenum - 1);
+		}
+		nft_check_xt_legacy(h->family, false);
+		break;
+	case CMD_NEW_CHAIN:
+		ret = nft_cmd_chain_user_add(h, p.chain, p.table);
+		break;
+	case CMD_DELETE_CHAIN:
+		ret = nft_cmd_chain_user_del(h, p.chain, p.table,
+					 cs.options & OPT_VERBOSE);
+		break;
+	case CMD_RENAME_CHAIN:
+		ret = nft_cmd_chain_user_rename(h, p.chain, p.table, p.newname);
+		break;
+	case CMD_SET_POLICY:
+		ret = nft_cmd_chain_set(h, p.table, p.chain, p.policy, NULL);
+		break;
+	case CMD_NONE:
+	/* do_parse ignored the line (eg: -4 with ip6tables-restore) */
+		break;
+	default:
+		/* We should never reach this... */
+		exit_tryhelp(2);
+	}
+
+	*table = p.table;
+
+	nft_clear_iptables_command_state(&cs);
+
+	if (h->family == AF_INET) {
+		free(args.s.addr.v4);
+		free(args.s.mask.v4);
+		free(args.d.addr.v4);
+		free(args.d.mask.v4);
+	} else if (h->family == AF_INET6) {
+		free(args.s.addr.v6);
+		free(args.s.mask.v6);
+		free(args.d.addr.v6);
+		free(args.d.mask.v6);
+	}
+	xtables_free_opts(1);
+
+	return ret;
+}
diff --git a/iptables/xtables.lock b/iptables/xtables.lock
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/iptables/xtables.lock
diff --git a/iptables/xtables.pc.in b/iptables/xtables.pc.in
new file mode 100644
index 0000000..43f35d5
--- /dev/null
+++ b/iptables/xtables.pc.in
@@ -0,0 +1,13 @@
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+xtlibdir=@xtlibdir@
+includedir=@includedir@
+
+Name:		xtables
+Description:	Shared Xtables code for extensions and iproute2
+Version:	@PACKAGE_VERSION@
+Cflags:		-I${includedir}
+Libs:		-L${libdir} -lxtables
+Libs.private:	-ldl
diff --git a/libipq/.gitignore b/libipq/.gitignore
new file mode 100644
index 0000000..6cb21a3
--- /dev/null
+++ b/libipq/.gitignore
@@ -0,0 +1 @@
+/libipq.pc
diff --git a/libipq/Makefile.am b/libipq/Makefile.am
new file mode 100644
index 0000000..9e3a2ca
--- /dev/null
+++ b/libipq/Makefile.am
@@ -0,0 +1,13 @@
+# -*- Makefile -*-
+
+AM_CFLAGS = ${regular_CFLAGS}
+AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include
+
+libipq_la_SOURCES = libipq.c
+lib_LTLIBRARIES   = libipq.la
+man_MANS         = ipq_create_handle.3 ipq_destroy_handle.3 ipq_errstr.3 \
+                   ipq_get_msgerr.3 ipq_get_packet.3 ipq_message_type.3 \
+                   ipq_perror.3 ipq_read.3 ipq_set_mode.3 ipq_set_verdict.3 \
+                   libipq.3
+
+pkgconfig_DATA = libipq.pc
diff --git a/libipq/ipq_create_handle.3 b/libipq/ipq_create_handle.3
new file mode 100644
index 0000000..11ef95c
--- /dev/null
+++ b/libipq/ipq_create_handle.3
@@ -0,0 +1,82 @@
+.TH IPQ_CREATE_HANDLE 3 "16 October 2001" "Linux iptables 1.2" "Linux Programmer's Manual" 
+.\"
+.\"     Copyright (c) 2000-2001 Netfilter Core Team
+.\"
+.\"     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 2 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, write to the Free Software
+.\"     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+ipq_create_handle, ipq_destroy_handle \(em create and destroy libipq handles.
+.SH SYNOPSIS
+.B #include <linux/netfilter.h>
+.br
+.B #include <libipq.h>
+.sp
+.BI "struct ipq_handle *ipq_create_handle(u_int32_t " flags ", u_int32_t " protocol ");"
+.br
+.BI "int ipq_destroy_handle(struct ipq_handle *" h );
+.SH DESCRIPTION
+The
+.B ipq_create_handle
+function initialises libipq for an application, attempts to bind to the
+Netlink socket used by ip_queue, and returns an opaque context handle.  It
+should be the first libipq function to be called by an application.  The
+handle returned should be used in all subsequent library calls which 
+require a handle parameter.
+.PP
+The
+.I flags
+parameter is not currently used and should be set to zero by the application
+for forward compatibility.
+.PP
+The
+.I protocol
+parameter is used to specify the protocol of the packets to be queued.
+Valid values are NFPROTO_IPV4 for IPv4 and NFPROTO_IPV6 for IPv6. Currently,
+only one protocol may be queued at a time for a handle.
+.PP
+The
+.B ipq_destroy_handle
+function frees up resources allocated by
+.BR ipq_create_handle ,
+and should be used when the handle is no longer required by the application.
+.SH RETURN VALUES
+On success,
+.B ipq_create_handle
+returns a pointer to a context handle.
+.br
+On failure, NULL is returned.
+.PP
+On success,
+.B ipq_destroy_handle
+returns zero.
+.br
+On failure, \-1 is returned.
+.SH ERRORS
+On failure, a descriptive error message will be available
+via the
+.B ipq_errstr
+function.
+.SH BUGS
+None known.
+.SH AUTHOR
+James Morris <jmorris@intercode.com.au>
+.SH COPYRIGHT
+Copyright (c) 2000-2001 Netfilter Core Team.
+.PP
+Distributed under the GNU General Public License.
+.SH SEE ALSO
+.BR iptables (8),
+.BR libipq (3).
diff --git a/libipq/ipq_destroy_handle.3 b/libipq/ipq_destroy_handle.3
new file mode 100644
index 0000000..29dcd98
--- /dev/null
+++ b/libipq/ipq_destroy_handle.3
@@ -0,0 +1 @@
+.so man3/ipq_create_handle.3
diff --git a/libipq/ipq_errstr.3 b/libipq/ipq_errstr.3
new file mode 100644
index 0000000..c8d67ce
--- /dev/null
+++ b/libipq/ipq_errstr.3
@@ -0,0 +1,64 @@
+.TH IPQ_ERRSTR 3 "16 October 2001" "Linux iptables 1.2" "Linux Programmer's Manual" 
+.\"
+.\"     Copyright (c) 2000 Netfilter Core Team
+.\"
+.\"     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 2 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, write to the Free Software
+.\"     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+ipq_errstr, ipq_perror \(em libipq error handling routines
+.SH SYNOPSIS
+.B #include <linux/netfilter.h>
+.br
+.B #include <libipq.h>
+.sp
+.BI "char *ipq_errstr(" void );
+.br
+.BI "void ipq_perror(const char *" s );
+.SH DESCRIPTION
+The
+.B ipq_errstr
+function returns a descriptive error message based on the current
+value of the internal
+.B ipq_errno
+variable.  All libipq API functions set this internal variable
+upon failure.
+.PP
+The
+.B ipq_perror
+function prints an error message to stderr corresponding to the
+current value of the internal
+.B ipq_error
+variable, and the global
+.B errno
+variable (if set).  The error message is prefixed with the string
+.I s
+as supplied by the application. If
+.I s
+is NULL, the error message is prefixed with the string "ERROR".
+.SH RETURN VALUE
+.B ipq_errstr
+returns an error message as outlined above.
+.SH BUGS
+None known.
+.SH AUTHOR
+James Morris <jmorris@intercode.com.au>
+.SH COPYRIGHT
+Copyright (c) 2000-2001 Netfilter Core Team.
+.PP
+Distributed under the GNU General Public License.
+.SH SEE ALSO
+.BR iptables (8),
+.BR libipq (3).
diff --git a/libipq/ipq_get_msgerr.3 b/libipq/ipq_get_msgerr.3
new file mode 100644
index 0000000..8a28be3
--- /dev/null
+++ b/libipq/ipq_get_msgerr.3
@@ -0,0 +1 @@
+.so man3/ipq_message_type.3
diff --git a/libipq/ipq_get_packet.3 b/libipq/ipq_get_packet.3
new file mode 100644
index 0000000..8a28be3
--- /dev/null
+++ b/libipq/ipq_get_packet.3
@@ -0,0 +1 @@
+.so man3/ipq_message_type.3
diff --git a/libipq/ipq_message_type.3 b/libipq/ipq_message_type.3
new file mode 100644
index 0000000..89d8817
--- /dev/null
+++ b/libipq/ipq_message_type.3
@@ -0,0 +1,134 @@
+.TH IPQ_MESSAGE_TYPE 3 "16 October 2001" "Linux iptables 1.2" "Linux Programmer's Manual" 
+.\"
+.\"     Copyright (c) 2000-2001 Netfilter Core Team
+.\"
+.\"     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 2 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, write to the Free Software
+.\"     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+ipq_message_type, ipq_get_packet, ipq_getmsgerr \(em query queue messages
+.SH SYNOPSIS
+.B #include <linux/netfilter.h>
+.br
+.B #include <libipq.h>
+.sp
+.BI "int ipq_message_type(const unsigned char *" buf ");"
+.br
+.BI "ipq_packet_msg_t *ipq_get_packet(const unsigned char *" buf ");"
+.br
+.BI "int ipq_get_msgerr(const unsigned char *" buf ");"
+.SH DESCRIPTION
+The
+.B ipq_message_type
+function returns the type of queue message returned to userspace
+via
+.BR ipq_read .
+.PP
+.B ipq_message_type
+should always be called following a successful call to
+.B ipq_read
+to determine whether the message is a packet message or an
+error message. The
+.I buf
+parameter should be the same data obtained from
+the previous call to
+.BR ipq_read .
+.PP
+.B ipq_message_type
+will return one of the following values:
+.TP
+.B NLMSG_ERROR
+An error message generated by the Netlink transport.
+.PP
+.TP
+.B IPQM_PACKET
+A packet message containing packet metadata and optional packet payload data.
+.PP
+The
+.B ipq_get_packet
+function should be called if
+.B ipq_message_type
+returns
+.BR IPQM_PACKET .
+The
+.I buf
+parameter should point to the same data used for the call to
+.BR ipq_message_type .
+The pointer returned by
+.B ipq_get_packet
+points to a packet message, which is declared as follows:
+.PP
+.RS
+.nf
+typedef struct ipq_packet_msg {
+	unsigned long packet_id;        /* ID of queued packet */
+	unsigned long mark;             /* Netfilter mark value */
+	long timestamp_sec;             /* Packet arrival time (seconds) */
+	long timestamp_usec;            /* Packet arrvial time (+useconds) */
+	unsigned int hook;              /* Netfilter hook we rode in on */
+	char indev_name[IFNAMSIZ];      /* Name of incoming interface */
+	char outdev_name[IFNAMSIZ];     /* Name of outgoing interface */
+	unsigned short hw_protocol;     /* Hardware protocol (network order) */
+	unsigned short hw_type;         /* Hardware type */
+	unsigned char hw_addrlen;       /* Hardware address length */
+	unsigned char hw_addr[8];       /* Hardware address */
+	size_t data_len;                /* Length of packet data */
+	unsigned char payload[0];       /* Optional packet data */
+} ipq_packet_msg_t;
+.fi
+.RE
+.PP
+Each of these fields may be read by the application.  If the queue mode
+is
+.B IPQ_COPY_PACKET
+and the
+.I data_len
+value is greater than zero, the packet payload contents may be accessed
+in the memory following the
+.B ipq_packet_msg_t
+structure to a range of
+.I data_len.
+.PP
+The
+.I packet_id
+field contains a packet identifier to be used when calling
+.BR ipq_set_verdict .
+.PP
+The
+.B ipq_get_msgerr
+function should be called if
+.B ipq_message_type
+returns
+.BR NLMSG_ERROR.
+The
+.I buf
+parameter should point to the same data used for the call to
+.BR ipq_message_type .
+The value returned by
+.B ipq_get_msgerr
+is set by higher level kernel code and corresponds to standard
+.B errno
+values.
+.SH BUGS
+None known.
+.SH AUTHOR
+James Morris <jmorris@intercode.com.au>
+.SH COPYRIGHT
+Copyright (c) 2000-2001 Netfilter Core Team.
+.PP
+Distributed under the GNU General Public License.
+.SH SEE ALSO
+.BR iptables (8),
+.BR libipq (3).
diff --git a/libipq/ipq_perror.3 b/libipq/ipq_perror.3
new file mode 100644
index 0000000..6efd53d
--- /dev/null
+++ b/libipq/ipq_perror.3
@@ -0,0 +1 @@
+.so man3/ipq_errstr.3
diff --git a/libipq/ipq_read.3 b/libipq/ipq_read.3
new file mode 100644
index 0000000..26ab9f9
--- /dev/null
+++ b/libipq/ipq_read.3
@@ -0,0 +1,104 @@
+.TH IPQ_READ 3 "16 October 2001" "Linux iptables 1.2" "Linux Programmer's Manual" 
+.\"
+.\"     Copyright (c) 2000-2001 Netfilter Core Team
+.\"
+.\"     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 2 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, write to the Free Software
+.\"     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+ipq_read \(em read queue messages from ip_queue and read into supplied buffer
+.SH SYNOPSIS
+.B #include <linux/netfilter.h>
+.br
+.B #include <libipq.h>
+.sp
+.BI "ssize_t ipq_read(const struct ipq_handle *" h ", unsigned char *" buf ", size_t " len ", int " timeout ");"
+.SH DESCRIPTION
+The
+.B ipq_read
+function reads a queue message from the kernel and copies it to
+the memory pointed to by 
+.I buf
+to a maximum length of
+. IR len .
+.PP
+The
+.I h
+parameter is a context handle which must previously have been returned 
+successfully from a call to
+.BR ipq_create_handle .
+.PP
+The caller is responsible for ensuring that the memory pointed to by
+.I buf
+is large enough to contain
+.I len
+bytes.
+.PP
+The
+.I timeout
+parameter may be used to set a timeout for the operation, specified in microseconds.
+This is implemented internally by the library via the
+.BR select
+system call.  A value of zero provides normal, backwards-compatible blocking behaviour
+with no timeout.  A negative value causes the function to return immediately.
+.PP
+Data returned via
+.I buf
+should not be accessed directly.  Use the 
+.BR ipq_message_type ,
+.BR ipq_get_packet ", and"
+.BR ipq_get_msgerr
+functions to access the queue message in the buffer.
+.SH RETURN VALUE
+On failure, \-1 is returned.
+.br
+On success, a non-zero positive value is returned when no timeout
+value is specified.
+.br
+On success with a timeout value specified, zero is returned if no data
+was available to read, or if a non-blocked signal was caught.  In the
+latter case, the global
+.B errno
+value will be set to 
+.BR EINTR .
+.SH ERRORS
+On error, a descriptive error message will be available
+via the
+.B ipq_errstr
+function.
+.SH DIAGNOSTICS
+While the
+.B ipq_read
+function may return successfully, the queue message copied to the buffer
+may itself be an error message from a higher level kernel component.  Use
+.B ipq_message_type
+to determine if it is an error message, and
+.B ipq_get_msgerr
+to access the value of the message.
+.SH BUGS
+None known.
+.SH AUTHOR
+James Morris <jmorris@intercode.com.au>
+.SH COPYRIGHT
+Copyright (c) 2000-2001 Netfilter Core Team.
+.PP
+Distributed under the GNU General Public License.
+.SH CREDITS
+Joost Remijn implemented the timeout feature, which appeared in the 1.2.4 release of iptables.
+.SH SEE ALSO
+.BR iptables (8),
+.BR libipq (3),
+.BR select (2).
+
diff --git a/libipq/ipq_set_mode.3 b/libipq/ipq_set_mode.3
new file mode 100644
index 0000000..0edd3c0
--- /dev/null
+++ b/libipq/ipq_set_mode.3
@@ -0,0 +1,105 @@
+.TH IPQ_SET_MODE 3 "16 October 2001" "Linux iptables 1.2" "Linux Programmer's Manual" 
+.\"
+.\"     Copyright (c) 2000-2001 Netfilter Core Team
+.\"
+.\"     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 2 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, write to the Free Software
+.\"     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+ipq_set_mode \(em set the ip_queue queuing mode
+.SH SYNOPSIS
+.B #include <linux/netfilter.h>
+.br
+.B #include <libipq.h>
+.sp
+.BI "int ipq_set_mode(const struct ipq_handle *" h ", u_int8_t " mode ", size_t " range );
+.SH DESCRIPTION
+The
+.B ipq_set_mode
+function sends a message to the kernel ip_queue module, specifying whether
+packet metadata only, or packet payloads as well as metadata should be copied to
+userspace.
+.PP
+The
+.I h
+parameter is a context handle which must previously have been returned 
+successfully from a call to
+.BR ipq_create_handle .
+.PP
+The
+.I mode
+parameter must be one of:
+.TP
+.B IPQ_COPY_META
+Copy only packet metadata to userspace.
+.TP
+.B IPQ_COPY_PACKET
+Copy packet metadata and packet payloads to userspace.
+.PP
+The
+.I range
+parameter is used to specify how many bytes of the payload to copy
+to userspace.  It is only valid for
+.B IPQ_COPY_PACKET
+mode and is otherwise ignored.  The maximum useful value for
+.I range
+is 65535 (greater values will be clamped to this by ip_queue).
+.PP
+.B ipq_set_mode
+is usually used immediately following
+.B ipq_create_handle
+to enable the flow of packets to userspace.
+.PP
+Note that as the underlying Netlink messaging transport is connectionless,
+the ip_queue module does not know that a userspace application is ready to
+communicate until it receives a message such as this.
+.SH RETURN VALUE
+On failure, \-1 is returned.
+.br
+On success, a non-zero positive value is returned.
+.SH ERRORS
+On failure, a descriptive error message will be available
+via the
+.B ipq_errstr
+function.
+.SH DIAGNOSTICS
+A relatively common failure may occur if the ip_queue module is not loaded.
+In this case, the following code excerpt:
+.PP
+.RS
+.nf
+status = ipq_set_mode(h, IPQ_COPY_META, 0);
+if (status < 0) {
+	ipq_perror("myapp");
+	ipq_destroy_handle(h);
+	exit(1);
+}
+.RE
+.fi
+.PP
+would generate the following output:
+.PP
+.I myapp: Failed to send netlink message: Connection refused
+.SH BUGS
+None known.
+.SH AUTHOR
+James Morris <jmorris@intercode.com.au>
+.SH COPYRIGHT
+Copyright (c) 2000-2001 Netfilter Core Team.
+.PP
+Distributed under the GNU General Public License.
+.SH SEE ALSO
+.BR libipq (3),
+.BR iptables (8).
diff --git a/libipq/ipq_set_verdict.3 b/libipq/ipq_set_verdict.3
new file mode 100644
index 0000000..a6172b3
--- /dev/null
+++ b/libipq/ipq_set_verdict.3
@@ -0,0 +1,100 @@
+.TH IPQ_SET_VERDICT 3 "16 October 2001" "Linux iptables 1.2" "Linux Programmer's Manual" 
+.\"
+.\"     Copyright (c) 2000-2001 Netfilter Core Team
+.\"
+.\"     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 2 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, write to the Free Software
+.\"     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+ipq_set_verdict \(em issue verdict and optionally modified packet to kernel
+.SH SYNOPSIS
+.B #include <linux/netfilter.h>
+.br
+.B #include <libipq.h>
+.sp
+.BI "int ipq_set_verdict(const struct ipq_handle *" h ", ipq_id_t " id ", unsigned int " verdict ", size_t " data_len ", unsigned char *" buf ");"
+.SH DESCRIPTION
+The
+.B ipq_set_verdict
+function issues a verdict on a packet previously obtained with
+.BR ipq_read ,
+specifying the intended disposition of the packet, and optionally
+supplying a modified version of the payload data.
+.PP
+The
+.I h
+parameter is a context handle which must previously have been returned 
+successfully from a call to
+.BR ipq_create_handle .
+.PP
+The
+.I id
+parameter is the packet identifier obtained via
+.BR ipq_get_packet .
+.PP
+The
+.I verdict
+parameter must be one of:
+.TP
+.B NF_ACCEPT
+Accept the packet and continue traversal within the kernel.
+.br
+.TP
+.B NF_DROP
+Drop the packet.
+.TP
+\fBNF_QUEUE\fP
+Requeue the packet.
+.PP
+\fBNF_STOLEN\fP and \fBNF_REPEAT\fP are kernel-internal constants and should
+not be used from userspace as their exact side effects have not been
+investigated.
+.PP
+The
+.I data_len
+parameter is the length of the data pointed to
+by
+.IR buf ,
+the optional replacement payload data.
+.PP
+If simply setting a verdict without modifying the payload data, use zero
+for
+.I data_len
+and NULL for
+.IR buf .
+.PP
+The application is responsible for recalculating any packet checksums
+when modifying packets.
+.SH RETURN VALUE
+On failure, \-1 is returned.
+.br
+On success, a non-zero positive value is returned.
+.SH ERRORS
+On error, a descriptive error message will be available
+via the
+.B ipq_errstr
+function.
+.SH BUGS
+None known.
+.SH AUTHOR
+James Morris <jmorris@intercode.com.au>
+.SH COPYRIGHT
+Copyright (c) 2000-2001 Netfilter Core Team.
+.PP
+Distributed under the GNU General Public License.
+.SH SEE ALSO
+.BR iptables (8),
+.BR libipq (3).
+
diff --git a/libipq/libipq.3 b/libipq/libipq.3
new file mode 100644
index 0000000..611fcdf
--- /dev/null
+++ b/libipq/libipq.3
@@ -0,0 +1,277 @@
+.TH LIBIPQ 3 "16 October 2001" "Linux iptables 1.2" "Linux Programmer's Manual" 
+.\"
+.\"     Copyright (c) 2000-2001 Netfilter Core Team
+.\"
+.\"     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 2 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, write to the Free Software
+.\"     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\"
+.SH NAME
+libipq \(em iptables userspace packet queuing library.
+.SH SYNOPSIS
+.B #include <linux/netfilter.h>
+.br
+.B #include <libipq.h>
+.SH DESCRIPTION
+libipq is a development library for iptables userspace packet queuing.
+.SS Userspace Packet Queuing
+Netfilter provides a mechanism for passing packets out of the stack for
+queueing to userspace, then receiving these packets back into the kernel
+with a verdict specifying what to do with the packets (such as ACCEPT
+or DROP).  These packets may also be modified in userspace prior to
+reinjection back into the kernel.
+.PP
+For each supported protocol, a kernel module called a
+.I queue handler
+may register with Netfilter to perform the mechanics of passing
+packets to and from userspace.
+.PP
+The standard queue handler for IPv4 is ip_queue.  It is provided as an
+experimental module with 2.4 kernels, and uses a Netlink socket for
+kernel/userspace communication.
+.PP
+Once ip_queue is loaded, IP packets may be selected with iptables
+and queued for userspace processing via the QUEUE target.  For example,
+running the following commands:
+.PP
+	# modprobe iptable_filter
+.br	
+	# modprobe ip_queue
+.br	
+	# iptables \-A OUTPUT \-p icmp \-j QUEUE
+.PP
+will cause any locally generated ICMP packets (e.g. ping output) to
+be sent to the ip_queue module, which will then attempt to deliver the
+packets to a userspace application.  If no userspace application is waiting,
+the packets will be dropped
+.PP
+An application may receive and process these packets via libipq.
+.PP
+.PP
+.SS Libipq Overview
+Libipq provides an API for communicating with ip_queue.  The following is
+an overview of API usage, refer to individual man pages for more details
+on each function.
+.PP
+.B Initialisation
+.br
+To initialise the library, call
+.BR ipq_create_handle (3).
+This will attempt to bind to the Netlink socket used by ip_queue and
+return an opaque context handle for subsequent library calls.
+.PP
+.B Setting the Queue Mode
+.br
+.BR ipq_set_mode (3)
+allows the application to specify whether packet metadata, or packet
+payloads as well as metadata are copied to userspace.  It is also used to
+initially notify ip_queue that an application is ready to receive queue
+messages.
+.PP
+.B Receiving Packets from the Queue
+.br
+.BR ipq_read (3)
+waits for queue messages to arrive from ip_queue and copies
+them into a supplied buffer.
+Queue messages may be
+.I packet messages
+or
+.I error messages.
+.PP
+The type of packet may be determined with
+.BR ipq_message_type (3).
+.PP
+If it's a packet message, the metadata and optional payload may be retrieved with
+.BR ipq_get_packet (3).
+.PP
+To retrieve the value of an error message, use
+.BR ipq_get_msgerr (3).
+.PP
+.B Issuing Verdicts on Packets
+.br
+To issue a verdict on a packet, and optionally return a modified version
+of the packet to the kernel, call
+.BR ipq_set_verdict (3).
+.PP
+.B Error Handling
+.br
+An error string corresponding to the current value of the internal error
+variable
+.B ipq_errno
+may be obtained with
+.BR ipq_errstr (3).
+.PP
+For simple applications, calling
+.BR ipq_perror (3)
+will print the same message as
+.BR ipq_errstr (3),
+as well as the string corresponding to the global
+.B errno
+value (if set) to stderr.
+.PP
+.B Cleaning Up
+.br
+To free up the Netlink socket and destroy resources associated with
+the context handle, call
+.BR ipq_destroy_handle (3).
+.SH SUMMARY
+.TP 4
+.BR ipq_create_handle (3)
+Initialise library, return context handle.
+.TP
+.BR ipq_set_mode (3)
+Set the queue mode, to copy either packet metadata, or payloads
+as well as metadata to userspace.
+.TP
+.BR ipq_read (3)
+Wait for a queue message to arrive from ip_queue and read it into
+a buffer.
+.TP
+.BR ipq_message_type (3)
+Determine message type in the buffer.
+.TP
+.BR ipq_get_packet (3)
+Retrieve a packet message from the buffer.
+.TP
+.BR ipq_get_msgerr (3)
+Retrieve an error message from the buffer.
+.TP
+.BR ipq_set_verdict (3)
+Set a verdict on a packet, optionally replacing its contents.
+.TP
+.BR ipq_errstr (3)
+Return an error message corresponding to the internal ipq_errno variable.
+.TP
+.BR ipq_perror (3)
+Helper function to print error messages to stderr.
+.TP
+.BR ipq_destroy_handle (3)
+Destroy context handle and associated resources.
+.SH EXAMPLE
+The following is an example of a simple application which receives
+packets and issues NF_ACCEPT verdicts on each packet.
+.RS
+.nf
+/*
+ * This code is GPL.
+ */
+#include <linux/netfilter.h>
+#include <libipq.h>
+#include <stdio.h>
+
+#define BUFSIZE 2048 
+
+static void die(struct ipq_handle *h)
+{
+	ipq_perror("passer");
+	ipq_destroy_handle(h);
+	exit(1);
+}
+
+int main(int argc, char **argv)
+{
+	int status;
+	unsigned char buf[BUFSIZE];
+	struct ipq_handle *h;
+	
+	h = ipq_create_handle(0, NFPROTO_IPV4);
+	if (!h)
+		die(h);
+		
+	status = ipq_set_mode(h, IPQ_COPY_PACKET, BUFSIZE);
+	if (status < 0)
+		die(h);
+		
+	do{
+		status = ipq_read(h, buf, BUFSIZE, 0);
+		if (status < 0)
+			die(h);
+			
+		switch (ipq_message_type(buf)) {
+			case NLMSG_ERROR:
+				fprintf(stderr, "Received error message %d\\n",
+				        ipq_get_msgerr(buf));
+				break;
+				
+			case IPQM_PACKET: {
+				ipq_packet_msg_t *m = ipq_get_packet(buf);
+				
+				status = ipq_set_verdict(h, m->packet_id,
+				                         NF_ACCEPT, 0, NULL);
+				if (status < 0)
+					die(h);
+				break;
+			}
+			
+			default:
+				fprintf(stderr, "Unknown message type!\\n");
+				break;
+		}
+	} while (1);
+	
+	ipq_destroy_handle(h);
+	return 0;
+}
+.RE
+.fi
+.PP
+Pointers to more libipq application examples may be found in The
+Netfilter FAQ.
+.SH DIAGNOSTICS
+For information about monitoring and tuning ip_queue, refer to the
+Linux 2.4 Packet Filtering HOWTO.
+.PP
+If an application modifies a packet, it needs to also update any
+checksums for the packet.  Typically, the kernel will silently discard
+modified packets with invalid checksums. 
+.SH SECURITY
+Processes require CAP_NET_ADMIN capabilty to access the kernel ip_queue
+module.  Such processes can potentially access and modify any IP packets
+received, generated or forwarded by the kernel.
+.SH TODO
+Per-handle
+.B ipq_errno
+values.
+.SH BUGS
+Probably.
+.SH AUTHOR
+James Morris <jmorris@intercode.com.au>
+.SH COPYRIGHT
+Copyright (c) 2000-2001 Netfilter Core Team.
+.PP
+Distributed under the GNU General Public License.
+.SH CREDITS
+Joost Remijn implemented the
+.B ipq_read
+timeout feature, which appeared in the 1.2.4 release of iptables.
+.PP
+Fernando Anton added support for IPv6.
+.SH SEE ALSO
+.BR iptables (8),
+.BR ipq_create_handle (3),
+.BR ipq_destroy_handle (3),
+.BR ipq_errstr (3),
+.BR ipq_get_msgerr (3),
+.BR ipq_get_packet (3),
+.BR ipq_message_type (3),
+.BR ipq_perror (3),
+.BR ipq_read (3),
+.BR ipq_set_mode (3),
+.BR ipq_set_verdict (3).
+.PP
+The Netfilter home page at http://netfilter.samba.org/
+which has links to The Networking Concepts HOWTO, The Linux 2.4 Packet
+Filtering HOWTO, The Linux 2.4 NAT HOWTO, The Netfilter Hacking HOWTO,
+The Netfilter FAQ and many other useful resources.
+
diff --git a/libipq/libipq.c b/libipq/libipq.c
new file mode 100644
index 0000000..fb65971
--- /dev/null
+++ b/libipq/libipq.c
@@ -0,0 +1,379 @@
+/*
+ * libipq.c
+ *
+ * IPQ userspace library.
+ *
+ * Please note that this library is still developmental, and there may
+ * be some API changes.
+ *
+ * Author: James Morris <jmorris@intercode.com.au>
+ *
+ * 07-11-2001 Modified by Fernando Anton to add support for IPv6.
+ *
+ * Copyright (c) 2000-2001 Netfilter Core Team
+ *
+ * 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 2 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.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <libipq/libipq.h>
+#include <netinet/in.h>
+#include <linux/netfilter.h>
+
+/****************************************************************************
+ *
+ * Private interface
+ *
+ ****************************************************************************/
+
+enum {
+	IPQ_ERR_NONE = 0,
+	IPQ_ERR_IMPL,
+	IPQ_ERR_HANDLE,
+	IPQ_ERR_SOCKET,
+	IPQ_ERR_BIND,
+	IPQ_ERR_BUFFER,
+	IPQ_ERR_RECV,
+	IPQ_ERR_NLEOF,
+	IPQ_ERR_ADDRLEN,
+	IPQ_ERR_STRUNC,
+	IPQ_ERR_RTRUNC,
+	IPQ_ERR_NLRECV,
+	IPQ_ERR_SEND,
+	IPQ_ERR_SUPP,
+	IPQ_ERR_RECVBUF,
+	IPQ_ERR_TIMEOUT,
+        IPQ_ERR_PROTOCOL
+};
+#define IPQ_MAXERR IPQ_ERR_PROTOCOL
+
+struct ipq_errmap_t {
+	int errcode;
+	char *message;
+} ipq_errmap[] = {
+	{ IPQ_ERR_NONE, "Unknown error" },
+	{ IPQ_ERR_IMPL, "Implementation error" },
+	{ IPQ_ERR_HANDLE, "Unable to create netlink handle" },
+	{ IPQ_ERR_SOCKET, "Unable to create netlink socket" },
+	{ IPQ_ERR_BIND, "Unable to bind netlink socket" },
+	{ IPQ_ERR_BUFFER, "Unable to allocate buffer" },
+	{ IPQ_ERR_RECV, "Failed to receive netlink message" },
+	{ IPQ_ERR_NLEOF, "Received EOF on netlink socket" },
+	{ IPQ_ERR_ADDRLEN, "Invalid peer address length" },
+	{ IPQ_ERR_STRUNC, "Sent message truncated" },
+	{ IPQ_ERR_RTRUNC, "Received message truncated" },
+	{ IPQ_ERR_NLRECV, "Received error from netlink" },
+	{ IPQ_ERR_SEND, "Failed to send netlink message" },
+	{ IPQ_ERR_SUPP, "Operation not supported" },
+	{ IPQ_ERR_RECVBUF, "Receive buffer size invalid" },
+	{ IPQ_ERR_TIMEOUT, "Timeout"},
+	{ IPQ_ERR_PROTOCOL, "Invalid protocol specified" }
+};
+
+static int ipq_errno = IPQ_ERR_NONE;
+
+static ssize_t ipq_netlink_sendto(const struct ipq_handle *h,
+                                  const void *msg, size_t len);
+
+static ssize_t ipq_netlink_recvfrom(const struct ipq_handle *h,
+                                    unsigned char *buf, size_t len,
+                                    int timeout);
+
+static ssize_t ipq_netlink_sendmsg(const struct ipq_handle *h,
+                                   const struct msghdr *msg,
+                                   unsigned int flags);
+
+static char *ipq_strerror(int errcode);
+
+static ssize_t ipq_netlink_sendto(const struct ipq_handle *h,
+                                  const void *msg, size_t len)
+{
+	int status = sendto(h->fd, msg, len, 0,
+	                    (struct sockaddr *)&h->peer, sizeof(h->peer));
+	if (status < 0)
+		ipq_errno = IPQ_ERR_SEND;
+	return status;
+}
+
+static ssize_t ipq_netlink_sendmsg(const struct ipq_handle *h,
+                                   const struct msghdr *msg,
+                                   unsigned int flags)
+{
+	int status = sendmsg(h->fd, msg, flags);
+	if (status < 0)
+		ipq_errno = IPQ_ERR_SEND;
+	return status;
+}
+
+static ssize_t ipq_netlink_recvfrom(const struct ipq_handle *h,
+                                    unsigned char *buf, size_t len,
+                                    int timeout)
+{
+	unsigned int addrlen;
+	int status;
+	struct nlmsghdr *nlh;
+
+	if (len < sizeof(struct nlmsgerr)) {
+		ipq_errno = IPQ_ERR_RECVBUF;
+		return -1;
+	}
+	addrlen = sizeof(h->peer);
+
+	if (timeout != 0) {
+		int ret;
+		struct timeval tv;
+		fd_set read_fds;
+		
+		if (timeout < 0) {
+			/* non-block non-timeout */
+			tv.tv_sec = 0;
+			tv.tv_usec = 0;
+		} else {
+			tv.tv_sec = timeout / 1000000;
+			tv.tv_usec = timeout % 1000000;
+		}
+
+		FD_ZERO(&read_fds);
+		FD_SET(h->fd, &read_fds);
+		ret = select(h->fd+1, &read_fds, NULL, NULL, &tv);
+		if (ret < 0) {
+			if (errno == EINTR) {
+				return 0;
+			} else {
+				ipq_errno = IPQ_ERR_RECV;
+				return -1;
+			}
+		}
+		if (!FD_ISSET(h->fd, &read_fds)) {
+			ipq_errno = IPQ_ERR_TIMEOUT;
+			return 0;
+		}
+	}
+	status = recvfrom(h->fd, buf, len, 0,
+	                      (struct sockaddr *)&h->peer, &addrlen);
+	if (status < 0) {
+		ipq_errno = IPQ_ERR_RECV;
+		return status;
+	}
+	if (addrlen != sizeof(h->peer)) {
+		ipq_errno = IPQ_ERR_RECV;
+		return -1;
+	}
+	if (h->peer.nl_pid != 0) {
+		ipq_errno = IPQ_ERR_RECV;
+		return -1;
+	}
+	if (status == 0) {
+		ipq_errno = IPQ_ERR_NLEOF;
+		return -1;
+	}
+	nlh = (struct nlmsghdr *)buf;
+	if (nlh->nlmsg_flags & MSG_TRUNC || nlh->nlmsg_len > status) {
+		ipq_errno = IPQ_ERR_RTRUNC;
+		return -1;
+	}
+	return status;
+}
+
+static char *ipq_strerror(int errcode)
+{
+	if (errcode < 0 || errcode > IPQ_MAXERR)
+		errcode = IPQ_ERR_IMPL;
+	return ipq_errmap[errcode].message;
+}
+
+/****************************************************************************
+ *
+ * Public interface
+ *
+ ****************************************************************************/
+
+/*
+ * Create and initialise an ipq handle.
+ */
+struct ipq_handle *ipq_create_handle(uint32_t flags, uint32_t protocol)
+{
+	int status;
+	struct ipq_handle *h;
+
+	h = (struct ipq_handle *)malloc(sizeof(struct ipq_handle));
+	if (h == NULL) {
+		ipq_errno = IPQ_ERR_HANDLE;
+		return NULL;
+	}
+	
+	memset(h, 0, sizeof(struct ipq_handle));
+	
+        if (protocol == NFPROTO_IPV4)
+                h->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_FIREWALL);
+        else if (protocol == NFPROTO_IPV6)
+                h->fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_IP6_FW);
+        else {
+		ipq_errno = IPQ_ERR_PROTOCOL;
+		free(h);
+		return NULL;
+        }
+        
+	if (h->fd == -1) {
+		ipq_errno = IPQ_ERR_SOCKET;
+		free(h);
+		return NULL;
+	}
+	memset(&h->local, 0, sizeof(struct sockaddr_nl));
+	h->local.nl_family = AF_NETLINK;
+	h->local.nl_pid = getpid();
+	h->local.nl_groups = 0;
+	status = bind(h->fd, (struct sockaddr *)&h->local, sizeof(h->local));
+	if (status == -1) {
+		ipq_errno = IPQ_ERR_BIND;
+		close(h->fd);
+		free(h);
+		return NULL;
+	}
+	memset(&h->peer, 0, sizeof(struct sockaddr_nl));
+	h->peer.nl_family = AF_NETLINK;
+	h->peer.nl_pid = 0;
+	h->peer.nl_groups = 0;
+	return h;
+}
+
+/*
+ * No error condition is checked here at this stage, but it may happen
+ * if/when reliable messaging is implemented.
+ */
+int ipq_destroy_handle(struct ipq_handle *h)
+{
+	if (h) {
+		close(h->fd);
+		free(h);
+	}
+	return 0;
+}
+
+int ipq_set_mode(const struct ipq_handle *h,
+                 uint8_t mode, size_t range)
+{
+	struct {
+		struct nlmsghdr nlh;
+		ipq_peer_msg_t pm;
+	} req;
+
+	memset(&req, 0, sizeof(req));
+	req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req));
+	req.nlh.nlmsg_flags = NLM_F_REQUEST;
+	req.nlh.nlmsg_type = IPQM_MODE;
+	req.nlh.nlmsg_pid = h->local.nl_pid;
+	req.pm.msg.mode.value = mode;
+	req.pm.msg.mode.range = range;
+	return ipq_netlink_sendto(h, (void *)&req, req.nlh.nlmsg_len);
+}
+
+/*
+ * timeout is in microseconds (1 second is 1000000 (1 million) microseconds)
+ *
+ */
+ssize_t ipq_read(const struct ipq_handle *h,
+                 unsigned char *buf, size_t len, int timeout)
+{
+	return ipq_netlink_recvfrom(h, buf, len, timeout);
+}
+
+int ipq_message_type(const unsigned char *buf)
+{
+	return ((struct nlmsghdr*)buf)->nlmsg_type;
+}
+
+int ipq_get_msgerr(const unsigned char *buf)
+{
+	struct nlmsghdr *h = (struct nlmsghdr *)buf;
+	struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h);
+	return -err->error;
+}
+
+ipq_packet_msg_t *ipq_get_packet(const unsigned char *buf)
+{
+	return NLMSG_DATA((struct nlmsghdr *)(buf));
+}
+
+int ipq_set_verdict(const struct ipq_handle *h,
+                    ipq_id_t id,
+                    unsigned int verdict,
+                    size_t data_len,
+                    unsigned char *buf)
+{
+	unsigned char nvecs;
+	size_t tlen;
+	struct nlmsghdr nlh;
+	ipq_peer_msg_t pm;
+	struct iovec iov[3];
+	struct msghdr msg;
+
+	memset(&nlh, 0, sizeof(nlh));
+	nlh.nlmsg_flags = NLM_F_REQUEST;
+	nlh.nlmsg_type = IPQM_VERDICT;
+	nlh.nlmsg_pid = h->local.nl_pid;
+	memset(&pm, 0, sizeof(pm));
+	pm.msg.verdict.value = verdict;
+	pm.msg.verdict.id = id;
+	pm.msg.verdict.data_len = data_len;
+	iov[0].iov_base = &nlh;
+	iov[0].iov_len = sizeof(nlh);
+	iov[1].iov_base = &pm;
+	iov[1].iov_len = sizeof(pm);
+	tlen = sizeof(nlh) + sizeof(pm);
+	nvecs = 2;
+	if (data_len && buf) {
+		iov[2].iov_base = buf;
+		iov[2].iov_len = data_len;
+		tlen += data_len;
+		nvecs++;
+	}
+	msg.msg_name = (void *)&h->peer;
+	msg.msg_namelen = sizeof(h->peer);
+	msg.msg_iov = iov;
+	msg.msg_iovlen = nvecs;
+	msg.msg_control = NULL;
+	msg.msg_controllen = 0;
+	msg.msg_flags = 0;
+	nlh.nlmsg_len = tlen;
+	return ipq_netlink_sendmsg(h, &msg, 0);
+}
+
+/* Not implemented yet */
+int ipq_ctl(const struct ipq_handle *h, int request, ...)
+{
+	return 1;
+}
+
+char *ipq_errstr(void)
+{
+	return ipq_strerror(ipq_errno);
+}
+
+void ipq_perror(const char *s)
+{
+	if (s)
+		fputs(s, stderr);
+	else
+		fputs("ERROR", stderr);
+	if (ipq_errno)
+		fprintf(stderr, ": %s", ipq_errstr());
+	if (errno)
+		fprintf(stderr, ": %s", strerror(errno));
+	fputc('\n', stderr);
+}
diff --git a/libipq/libipq.pc.in b/libipq/libipq.pc.in
new file mode 100644
index 0000000..ea31ec7
--- /dev/null
+++ b/libipq/libipq.pc.in
@@ -0,0 +1,11 @@
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name:		libipq
+Description:	Interface to the (old) ip_queue mechanism
+Version:	@PACKAGE_VERSION@
+Libs:		-L${libdir} -lipq
+Cflags:		-I${includedir}
diff --git a/libiptc/.gitignore b/libiptc/.gitignore
new file mode 100644
index 0000000..49ca83d
--- /dev/null
+++ b/libiptc/.gitignore
@@ -0,0 +1 @@
+/*.pc
diff --git a/libiptc/Android.bp b/libiptc/Android.bp
new file mode 100644
index 0000000..d7297a9
--- /dev/null
+++ b/libiptc/Android.bp
@@ -0,0 +1,31 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_iptables_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL
+    default_applicable_licenses: ["external_iptables_license"],
+}
+
+cc_defaults {
+    name: "libiptc_defaults",
+    defaults: ["iptables_defaults"],
+
+    cflags: ["-Wno-pointer-sign"],
+}
+
+cc_library_static {
+    name: "libip4tc",
+    defaults: ["libiptc_defaults"],
+
+    srcs: ["libip4tc.c"],
+}
+
+cc_library_static {
+    name: "libip6tc",
+    defaults: ["libiptc_defaults"],
+
+    cflags: ["-Wno-unused-function"],
+
+    srcs: ["libip6tc.c"],
+}
diff --git a/libiptc/Android.mk b/libiptc/Android.mk
new file mode 100644
index 0000000..b3e8606
--- /dev/null
+++ b/libiptc/Android.mk
@@ -0,0 +1,63 @@
+LOCAL_PATH:= $(call my-dir)
+
+#----------------------------------------------------------------
+# libip4tc
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include/
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/../include
+
+# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
+LOCAL_CFLAGS:=-D__ANDROID__
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+LOCAL_CFLAGS += \
+    -Wall -Werror \
+    -Wno-pointer-arith \
+    -Wno-pointer-sign \
+    -Wno-sign-compare \
+    -Wno-unused-parameter \
+
+LOCAL_SRC_FILES:= \
+	libip4tc.c \
+
+
+LOCAL_LDFLAGS:=-version-info 1:0:1
+LOCAL_MODULE_TAGS:=
+LOCAL_MODULE:=libip4tc
+
+include $(BUILD_STATIC_LIBRARY)
+
+
+#----------------------------------------------------------------
+# libip6tc
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include/
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/../include
+
+# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
+LOCAL_CFLAGS:=-D__ANDROID__
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+LOCAL_CFLAGS += \
+    -Wall -Werror \
+    -Wno-pointer-arith \
+    -Wno-pointer-sign \
+    -Wno-sign-compare \
+    -Wno-unused-function \
+    -Wno-unused-parameter \
+
+LOCAL_SRC_FILES:= \
+	libip6tc.c \
+
+
+LOCAL_LDFLAGS:=-version-info 1:0:1
+LOCAL_MODULE_TAGS:=
+LOCAL_MODULE:=libip6tc
+
+include $(BUILD_STATIC_LIBRARY)
+
+#----------------------------------------------------------------
diff --git a/libiptc/Makefile.am b/libiptc/Makefile.am
new file mode 100644
index 0000000..464a069
--- /dev/null
+++ b/libiptc/Makefile.am
@@ -0,0 +1,12 @@
+# -*- Makefile -*-
+
+AM_CFLAGS        = ${regular_CFLAGS}
+AM_CPPFLAGS      = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include ${kinclude_CPPFLAGS}
+
+pkgconfig_DATA      = libiptc.pc libip4tc.pc libip6tc.pc
+
+lib_LTLIBRARIES     = libip4tc.la libip6tc.la
+libip4tc_la_SOURCES = libip4tc.c
+libip4tc_la_LDFLAGS = -version-info 2:0:0
+libip6tc_la_SOURCES = libip6tc.c
+libip6tc_la_LDFLAGS = -version-info 2:0:0
diff --git a/libiptc/libip4tc.c b/libiptc/libip4tc.c
new file mode 100644
index 0000000..78a896f
--- /dev/null
+++ b/libiptc/libip4tc.c
@@ -0,0 +1,311 @@
+/* Library which manipulates firewall rules.  Version 0.1. */
+
+/* Architecture of firewall rules is as follows:
+ *
+ * Chains go INPUT, FORWARD, OUTPUT then user chains.
+ * Each user chain starts with an ERROR node.
+ * Every chain ends with an unconditional jump: a RETURN for user chains,
+ * and a POLICY for built-ins.
+ */
+
+/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See
+   COPYING for details). */
+
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#ifdef DEBUG_CONNTRACK
+#define inline
+#endif
+
+#if !defined(__BIONIC__) && (!defined(__GLIBC__) || (__GLIBC__ < 2))
+typedef unsigned int socklen_t;
+#endif
+
+#include "libiptc/libiptc.h"
+
+#define IP_VERSION	4
+#define IP_OFFSET	0x1FFF
+
+#define HOOK_PRE_ROUTING	NF_IP_PRE_ROUTING
+#define HOOK_LOCAL_IN		NF_IP_LOCAL_IN
+#define HOOK_FORWARD		NF_IP_FORWARD
+#define HOOK_LOCAL_OUT		NF_IP_LOCAL_OUT
+#define HOOK_POST_ROUTING	NF_IP_POST_ROUTING
+
+#define STRUCT_ENTRY_TARGET	struct xt_entry_target
+#define STRUCT_ENTRY		struct ipt_entry
+#define STRUCT_ENTRY_MATCH	struct xt_entry_match
+#define STRUCT_GETINFO		struct ipt_getinfo
+#define STRUCT_GET_ENTRIES	struct ipt_get_entries
+#define STRUCT_COUNTERS		struct xt_counters
+#define STRUCT_COUNTERS_INFO	struct xt_counters_info
+#define STRUCT_STANDARD_TARGET	struct xt_standard_target
+#define STRUCT_REPLACE		struct ipt_replace
+
+#define ENTRY_ITERATE		IPT_ENTRY_ITERATE
+#define TABLE_MAXNAMELEN	XT_TABLE_MAXNAMELEN
+#define FUNCTION_MAXNAMELEN	XT_FUNCTION_MAXNAMELEN
+
+#define GET_TARGET		ipt_get_target
+
+#define ERROR_TARGET		XT_ERROR_TARGET
+#define NUMHOOKS		NF_IP_NUMHOOKS
+
+#define IPT_CHAINLABEL		xt_chainlabel
+
+#define TC_DUMP_ENTRIES		dump_entries
+#define TC_IS_CHAIN		iptc_is_chain
+#define TC_FIRST_CHAIN		iptc_first_chain
+#define TC_NEXT_CHAIN		iptc_next_chain
+#define TC_FIRST_RULE		iptc_first_rule
+#define TC_NEXT_RULE		iptc_next_rule
+#define TC_GET_TARGET		iptc_get_target
+#define TC_BUILTIN		iptc_builtin
+#define TC_GET_POLICY		iptc_get_policy
+#define TC_INSERT_ENTRY		iptc_insert_entry
+#define TC_REPLACE_ENTRY	iptc_replace_entry
+#define TC_APPEND_ENTRY		iptc_append_entry
+#define TC_CHECK_ENTRY		iptc_check_entry
+#define TC_DELETE_ENTRY		iptc_delete_entry
+#define TC_DELETE_NUM_ENTRY	iptc_delete_num_entry
+#define TC_FLUSH_ENTRIES	iptc_flush_entries
+#define TC_ZERO_ENTRIES		iptc_zero_entries
+#define TC_READ_COUNTER		iptc_read_counter
+#define TC_ZERO_COUNTER		iptc_zero_counter
+#define TC_SET_COUNTER		iptc_set_counter
+#define TC_CREATE_CHAIN		iptc_create_chain
+#define TC_GET_REFERENCES	iptc_get_references
+#define TC_DELETE_CHAIN		iptc_delete_chain
+#define TC_RENAME_CHAIN		iptc_rename_chain
+#define TC_SET_POLICY		iptc_set_policy
+#define TC_GET_RAW_SOCKET	iptc_get_raw_socket
+#define TC_INIT			iptc_init
+#define TC_FREE			iptc_free
+#define TC_COMMIT		iptc_commit
+#define TC_STRERROR		iptc_strerror
+#define TC_NUM_RULES		iptc_num_rules
+#define TC_GET_RULE		iptc_get_rule
+#define TC_OPS			iptc_ops
+
+#define TC_AF			AF_INET
+#define TC_IPPROTO		IPPROTO_IP
+
+#define SO_SET_REPLACE		IPT_SO_SET_REPLACE
+#define SO_SET_ADD_COUNTERS	IPT_SO_SET_ADD_COUNTERS
+#define SO_GET_INFO		IPT_SO_GET_INFO
+#define SO_GET_ENTRIES		IPT_SO_GET_ENTRIES
+#define SO_GET_VERSION		IPT_SO_GET_VERSION
+
+#define STANDARD_TARGET		XT_STANDARD_TARGET
+#define LABEL_RETURN		IPTC_LABEL_RETURN
+#define LABEL_ACCEPT		IPTC_LABEL_ACCEPT
+#define LABEL_DROP		IPTC_LABEL_DROP
+#define LABEL_QUEUE		IPTC_LABEL_QUEUE
+
+#define ALIGN			XT_ALIGN
+#define RETURN			XT_RETURN
+
+#include "libiptc.c"
+
+#define IP_PARTS_NATIVE(n)			\
+(unsigned int)((n)>>24)&0xFF,			\
+(unsigned int)((n)>>16)&0xFF,			\
+(unsigned int)((n)>>8)&0xFF,			\
+(unsigned int)((n)&0xFF)
+
+#define IP_PARTS(n) IP_PARTS_NATIVE(ntohl(n))
+
+static int
+dump_entry(struct ipt_entry *e, struct xtc_handle *const handle)
+{
+	size_t i;
+	STRUCT_ENTRY_TARGET *t;
+
+	printf("Entry %u (%lu):\n", iptcb_entry2index(handle, e),
+	       iptcb_entry2offset(handle, e));
+	printf("SRC IP: %u.%u.%u.%u/%u.%u.%u.%u\n",
+	       IP_PARTS(e->ip.src.s_addr),IP_PARTS(e->ip.smsk.s_addr));
+	printf("DST IP: %u.%u.%u.%u/%u.%u.%u.%u\n",
+	       IP_PARTS(e->ip.dst.s_addr),IP_PARTS(e->ip.dmsk.s_addr));
+	printf("Interface: `%s'/", e->ip.iniface);
+	for (i = 0; i < IFNAMSIZ; i++)
+		printf("%c", e->ip.iniface_mask[i] ? 'X' : '.');
+	printf("to `%s'/", e->ip.outiface);
+	for (i = 0; i < IFNAMSIZ; i++)
+		printf("%c", e->ip.outiface_mask[i] ? 'X' : '.');
+	printf("\nProtocol: %u\n", e->ip.proto);
+	printf("Flags: %02X\n", e->ip.flags);
+	printf("Invflags: %02X\n", e->ip.invflags);
+	printf("Counters: %llu packets, %llu bytes\n",
+	       (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);
+	printf("Cache: %08X\n", e->nfcache);
+
+	IPT_MATCH_ITERATE(e, print_match);
+
+	t = GET_TARGET(e);
+	printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size);
+	if (strcmp(t->u.user.name, STANDARD_TARGET) == 0) {
+		const unsigned char *data = t->data;
+		int pos = *(const int *)data;
+		if (pos < 0)
+			printf("verdict=%s\n",
+			       pos == -NF_ACCEPT-1 ? "NF_ACCEPT"
+			       : pos == -NF_DROP-1 ? "NF_DROP"
+			       : pos == -NF_QUEUE-1 ? "NF_QUEUE"
+			       : pos == RETURN ? "RETURN"
+			       : "UNKNOWN");
+		else
+			printf("verdict=%u\n", pos);
+	} else if (strcmp(t->u.user.name, XT_ERROR_TARGET) == 0)
+		printf("error=`%s'\n", t->data);
+
+	printf("\n");
+	return 0;
+}
+
+static unsigned char *
+is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b, unsigned char *matchmask)
+{
+	unsigned int i;
+	unsigned char *mptr;
+
+	/* Always compare head structures: ignore mask here. */
+	if (a->ip.src.s_addr != b->ip.src.s_addr
+	    || a->ip.dst.s_addr != b->ip.dst.s_addr
+	    || a->ip.smsk.s_addr != b->ip.smsk.s_addr
+	    || a->ip.dmsk.s_addr != b->ip.dmsk.s_addr
+	    || a->ip.proto != b->ip.proto
+	    || a->ip.flags != b->ip.flags
+	    || a->ip.invflags != b->ip.invflags)
+		return NULL;
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (a->ip.iniface_mask[i] != b->ip.iniface_mask[i])
+			return NULL;
+		if ((a->ip.iniface[i] & a->ip.iniface_mask[i])
+		    != (b->ip.iniface[i] & b->ip.iniface_mask[i]))
+			return NULL;
+		if (a->ip.outiface_mask[i] != b->ip.outiface_mask[i])
+			return NULL;
+		if ((a->ip.outiface[i] & a->ip.outiface_mask[i])
+		    != (b->ip.outiface[i] & b->ip.outiface_mask[i]))
+			return NULL;
+	}
+
+	if (a->target_offset != b->target_offset
+	    || a->next_offset != b->next_offset)
+		return NULL;
+
+	mptr = matchmask + sizeof(STRUCT_ENTRY);
+	if (IPT_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr))
+		return NULL;
+	mptr += XT_ALIGN(sizeof(struct xt_entry_target));
+
+	return mptr;
+}
+
+#if 0
+/***************************** DEBUGGING ********************************/
+static inline int
+unconditional(const struct ipt_ip *ip)
+{
+	unsigned int i;
+
+	for (i = 0; i < sizeof(*ip)/sizeof(uint32_t); i++)
+		if (((uint32_t *)ip)[i])
+			return 0;
+
+	return 1;
+}
+
+static inline int
+check_match(const STRUCT_ENTRY_MATCH *m, unsigned int *off)
+{
+	assert(m->u.match_size >= sizeof(STRUCT_ENTRY_MATCH));
+	assert(ALIGN(m->u.match_size) == m->u.match_size);
+
+	(*off) += m->u.match_size;
+	return 0;
+}
+
+static inline int
+check_entry(const STRUCT_ENTRY *e, unsigned int *i, unsigned int *off,
+	    unsigned int user_offset, int *was_return,
+	    struct xtc_handle *h)
+{
+	unsigned int toff;
+	STRUCT_STANDARD_TARGET *t;
+
+	assert(e->target_offset >= sizeof(STRUCT_ENTRY));
+	assert(e->next_offset >= e->target_offset
+	       + sizeof(STRUCT_ENTRY_TARGET));
+	toff = sizeof(STRUCT_ENTRY);
+	IPT_MATCH_ITERATE(e, check_match, &toff);
+
+	assert(toff == e->target_offset);
+
+	t = (STRUCT_STANDARD_TARGET *)
+		GET_TARGET((STRUCT_ENTRY *)e);
+	/* next_offset will have to be multiple of entry alignment. */
+	assert(e->next_offset == ALIGN(e->next_offset));
+	assert(e->target_offset == ALIGN(e->target_offset));
+	assert(t->target.u.target_size == ALIGN(t->target.u.target_size));
+	assert(!TC_IS_CHAIN(t->target.u.user.name, h));
+
+	if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0) {
+		assert(t->target.u.target_size
+		       == ALIGN(sizeof(STRUCT_STANDARD_TARGET)));
+
+		assert(t->verdict == -NF_DROP-1
+		       || t->verdict == -NF_ACCEPT-1
+		       || t->verdict == RETURN
+		       || t->verdict < (int)h->entries->size);
+
+		if (t->verdict >= 0) {
+			STRUCT_ENTRY *te = get_entry(h, t->verdict);
+			int idx;
+
+			idx = iptcb_entry2index(h, te);
+			assert(strcmp(GET_TARGET(te)->u.user.name,
+				      XT_ERROR_TARGET)
+			       != 0);
+			assert(te != e);
+
+			/* Prior node must be error node, or this node. */
+			assert(t->verdict == iptcb_entry2offset(h, e)+e->next_offset
+			       || strcmp(GET_TARGET(index2entry(h, idx-1))
+					 ->u.user.name, XT_ERROR_TARGET)
+			       == 0);
+		}
+
+		if (t->verdict == RETURN
+		    && unconditional(&e->ip)
+		    && e->target_offset == sizeof(*e))
+			*was_return = 1;
+		else
+			*was_return = 0;
+	} else if (strcmp(t->target.u.user.name, XT_ERROR_TARGET) == 0) {
+		assert(t->target.u.target_size
+		       == ALIGN(sizeof(struct ipt_error_target)));
+
+		/* If this is in user area, previous must have been return */
+		if (*off > user_offset)
+			assert(*was_return);
+
+		*was_return = 0;
+	}
+	else *was_return = 0;
+
+	if (*off == user_offset)
+		assert(strcmp(t->target.u.user.name, XT_ERROR_TARGET) == 0);
+
+	(*off) += e->next_offset;
+	(*i)++;
+	return 0;
+}
+#endif
diff --git a/libiptc/libip4tc.pc.in b/libiptc/libip4tc.pc.in
new file mode 100644
index 0000000..5efa1ca
--- /dev/null
+++ b/libiptc/libip4tc.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name:		libip4tc
+Description:	iptables IPv4 ruleset ADT and kernel interface
+Version:	@PACKAGE_VERSION@
+Libs:		-L${libdir} -lip4tc
+Cflags:		-I${includedir}
diff --git a/libiptc/libip6tc.c b/libiptc/libip6tc.c
new file mode 100644
index 0000000..06cd623
--- /dev/null
+++ b/libiptc/libip6tc.c
@@ -0,0 +1,260 @@
+/* Library which manipulates firewall rules.  Version 0.1. */
+
+/* Architecture of firewall rules is as follows:
+ *
+ * Chains go INPUT, FORWARD, OUTPUT then user chains.
+ * Each user chain starts with an ERROR node.
+ * Every chain ends with an unconditional jump: a RETURN for user chains,
+ * and a POLICY for built-ins.
+ */
+
+/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See
+   COPYING for details). */
+
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+
+#ifdef DEBUG_CONNTRACK
+#define inline
+#endif
+
+#if !defined(__BIONIC__) && (!defined(__GLIBC__) || (__GLIBC__ < 2))
+typedef unsigned int socklen_t;
+#endif
+
+#include "libiptc/libip6tc.h"
+
+#define HOOK_PRE_ROUTING	NF_IP6_PRE_ROUTING
+#define HOOK_LOCAL_IN		NF_IP6_LOCAL_IN
+#define HOOK_FORWARD		NF_IP6_FORWARD
+#define HOOK_LOCAL_OUT		NF_IP6_LOCAL_OUT
+#define HOOK_POST_ROUTING	NF_IP6_POST_ROUTING
+
+#define STRUCT_ENTRY_TARGET	struct xt_entry_target
+#define STRUCT_ENTRY		struct ip6t_entry
+#define STRUCT_ENTRY_MATCH	struct xt_entry_match
+#define STRUCT_GETINFO		struct ip6t_getinfo
+#define STRUCT_GET_ENTRIES	struct ip6t_get_entries
+#define STRUCT_COUNTERS		struct xt_counters
+#define STRUCT_COUNTERS_INFO	struct xt_counters_info
+#define STRUCT_STANDARD_TARGET	struct xt_standard_target
+#define STRUCT_REPLACE		struct ip6t_replace
+
+#define ENTRY_ITERATE		IP6T_ENTRY_ITERATE
+#define TABLE_MAXNAMELEN	XT_TABLE_MAXNAMELEN
+#define FUNCTION_MAXNAMELEN	XT_FUNCTION_MAXNAMELEN
+
+#define GET_TARGET		ip6t_get_target
+
+#define ERROR_TARGET		XT_ERROR_TARGET
+#define NUMHOOKS		NF_IP6_NUMHOOKS
+
+#define IPT_CHAINLABEL		xt_chainlabel
+
+#define TC_DUMP_ENTRIES		dump_entries6
+#define TC_IS_CHAIN		ip6tc_is_chain
+#define TC_FIRST_CHAIN		ip6tc_first_chain
+#define TC_NEXT_CHAIN		ip6tc_next_chain
+#define TC_FIRST_RULE		ip6tc_first_rule
+#define TC_NEXT_RULE		ip6tc_next_rule
+#define TC_GET_TARGET		ip6tc_get_target
+#define TC_BUILTIN		ip6tc_builtin
+#define TC_GET_POLICY		ip6tc_get_policy
+#define TC_INSERT_ENTRY		ip6tc_insert_entry
+#define TC_REPLACE_ENTRY	ip6tc_replace_entry
+#define TC_APPEND_ENTRY		ip6tc_append_entry
+#define TC_CHECK_ENTRY		ip6tc_check_entry
+#define TC_DELETE_ENTRY		ip6tc_delete_entry
+#define TC_DELETE_NUM_ENTRY	ip6tc_delete_num_entry
+#define TC_FLUSH_ENTRIES	ip6tc_flush_entries
+#define TC_ZERO_ENTRIES		ip6tc_zero_entries
+#define TC_ZERO_COUNTER		ip6tc_zero_counter
+#define TC_READ_COUNTER		ip6tc_read_counter
+#define TC_SET_COUNTER		ip6tc_set_counter
+#define TC_CREATE_CHAIN		ip6tc_create_chain
+#define TC_GET_REFERENCES	ip6tc_get_references
+#define TC_DELETE_CHAIN		ip6tc_delete_chain
+#define TC_RENAME_CHAIN		ip6tc_rename_chain
+#define TC_SET_POLICY		ip6tc_set_policy
+#define TC_GET_RAW_SOCKET	ip6tc_get_raw_socket
+#define TC_INIT			ip6tc_init
+#define TC_FREE			ip6tc_free
+#define TC_COMMIT		ip6tc_commit
+#define TC_STRERROR		ip6tc_strerror
+#define TC_NUM_RULES		ip6tc_num_rules
+#define TC_GET_RULE		ip6tc_get_rule
+#define TC_OPS			ip6tc_ops
+
+#define TC_AF			AF_INET6
+#define TC_IPPROTO		IPPROTO_IPV6
+
+#define SO_SET_REPLACE		IP6T_SO_SET_REPLACE
+#define SO_SET_ADD_COUNTERS	IP6T_SO_SET_ADD_COUNTERS
+#define SO_GET_INFO		IP6T_SO_GET_INFO
+#define SO_GET_ENTRIES		IP6T_SO_GET_ENTRIES
+#define SO_GET_VERSION		IP6T_SO_GET_VERSION
+
+#define STANDARD_TARGET		XT_STANDARD_TARGET
+#define LABEL_RETURN		IP6TC_LABEL_RETURN
+#define LABEL_ACCEPT		IP6TC_LABEL_ACCEPT
+#define LABEL_DROP		IP6TC_LABEL_DROP
+#define LABEL_QUEUE		IP6TC_LABEL_QUEUE
+
+#define ALIGN			XT_ALIGN
+#define RETURN			XT_RETURN
+
+#include "libiptc.c"
+
+#define BIT6(a, l) \
+ ((ntohl(a->s6_addr32[(l) / 32]) >> (31 - ((l) & 31))) & 1)
+
+static int
+ipv6_prefix_length(const struct in6_addr *a)
+{
+	int l, i;
+	for (l = 0; l < 128; l++) {
+		if (BIT6(a, l) == 0)
+			break;
+	}
+	for (i = l + 1; i < 128; i++) {
+		if (BIT6(a, i) == 1)
+			return -1;
+	}
+	return l;
+}
+
+static int
+dump_entry(struct ip6t_entry *e, struct xtc_handle *const handle)
+{
+	size_t i;
+	char buf[40];
+	int len;
+	struct xt_entry_target *t;
+	
+	printf("Entry %u (%lu):\n", iptcb_entry2index(handle, e),
+	       iptcb_entry2offset(handle, e));
+	puts("SRC IP: ");
+	inet_ntop(AF_INET6, &e->ipv6.src, buf, sizeof buf);
+	puts(buf);
+	putchar('/');
+	len = ipv6_prefix_length(&e->ipv6.smsk);
+	if (len != -1)
+		printf("%d", len);
+	else {
+		inet_ntop(AF_INET6, &e->ipv6.smsk, buf, sizeof buf);
+		puts(buf);
+	}
+	putchar('\n');
+	
+	puts("DST IP: ");
+	inet_ntop(AF_INET6, &e->ipv6.dst, buf, sizeof buf);
+	puts(buf);
+	putchar('/');
+	len = ipv6_prefix_length(&e->ipv6.dmsk);
+	if (len != -1)
+		printf("%d", len);
+	else {
+		inet_ntop(AF_INET6, &e->ipv6.dmsk, buf, sizeof buf);
+		puts(buf);
+	}
+	putchar('\n');
+	
+	printf("Interface: `%s'/", e->ipv6.iniface);
+	for (i = 0; i < IFNAMSIZ; i++)
+		printf("%c", e->ipv6.iniface_mask[i] ? 'X' : '.');
+	printf("to `%s'/", e->ipv6.outiface);
+	for (i = 0; i < IFNAMSIZ; i++)
+		printf("%c", e->ipv6.outiface_mask[i] ? 'X' : '.');
+	printf("\nProtocol: %u\n", e->ipv6.proto);
+	if (e->ipv6.flags & IP6T_F_TOS)
+		printf("TOS: %u\n", e->ipv6.tos);
+	printf("Flags: %02X\n", e->ipv6.flags);
+	printf("Invflags: %02X\n", e->ipv6.invflags);
+	printf("Counters: %llu packets, %llu bytes\n",
+	       (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);
+	printf("Cache: %08X\n", e->nfcache);
+	
+	IP6T_MATCH_ITERATE(e, print_match);
+
+	t = ip6t_get_target(e);
+	printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size);
+	if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0) {
+		const unsigned char *data = t->data;
+		int pos = *(const int *)data;
+		if (pos < 0)
+			printf("verdict=%s\n",
+			       pos == -NF_ACCEPT-1 ? "NF_ACCEPT"
+			       : pos == -NF_DROP-1 ? "NF_DROP"
+			       : pos == XT_RETURN ? "RETURN"
+			       : "UNKNOWN");
+		else
+			printf("verdict=%u\n", pos);
+	} else if (strcmp(t->u.user.name, XT_ERROR_TARGET) == 0)
+		printf("error=`%s'\n", t->data);
+
+	printf("\n");
+	return 0;
+}
+
+static unsigned char *
+is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b,
+	unsigned char *matchmask)
+{
+	unsigned int i;
+	unsigned char *mptr;
+
+	/* Always compare head structures: ignore mask here. */
+	if (memcmp(&a->ipv6.src, &b->ipv6.src, sizeof(struct in6_addr))
+	    || memcmp(&a->ipv6.dst, &b->ipv6.dst, sizeof(struct in6_addr))
+	    || memcmp(&a->ipv6.smsk, &b->ipv6.smsk, sizeof(struct in6_addr))
+	    || memcmp(&a->ipv6.dmsk, &b->ipv6.dmsk, sizeof(struct in6_addr))
+	    || a->ipv6.proto != b->ipv6.proto
+	    || a->ipv6.tos != b->ipv6.tos
+	    || a->ipv6.flags != b->ipv6.flags
+	    || a->ipv6.invflags != b->ipv6.invflags)
+		return NULL;
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (a->ipv6.iniface_mask[i] != b->ipv6.iniface_mask[i])
+			return NULL;
+		if ((a->ipv6.iniface[i] & a->ipv6.iniface_mask[i])
+		    != (b->ipv6.iniface[i] & b->ipv6.iniface_mask[i]))
+			return NULL;
+		if (a->ipv6.outiface_mask[i] != b->ipv6.outiface_mask[i])
+			return NULL;
+		if ((a->ipv6.outiface[i] & a->ipv6.outiface_mask[i])
+		    != (b->ipv6.outiface[i] & b->ipv6.outiface_mask[i]))
+			return NULL;
+	}
+
+	if (a->target_offset != b->target_offset
+	    || a->next_offset != b->next_offset)
+		return NULL;
+
+	mptr = matchmask + sizeof(STRUCT_ENTRY);
+	if (IP6T_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr))
+		return NULL;
+	mptr += XT_ALIGN(sizeof(struct xt_entry_target));
+
+	return mptr;
+}
+
+#if 0
+/* All zeroes == unconditional rule. */
+static inline int
+unconditional(const struct ip6t_ip6 *ipv6)
+{
+	unsigned int i;
+
+	for (i = 0; i < sizeof(*ipv6); i++)
+		if (((char *)ipv6)[i])
+			break;
+
+	return (i == sizeof(*ipv6));
+}
+#endif
diff --git a/libiptc/libip6tc.pc.in b/libiptc/libip6tc.pc.in
new file mode 100644
index 0000000..30a61b2
--- /dev/null
+++ b/libiptc/libip6tc.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name:		libip6tc
+Description:	iptables IPv6 ruleset ADT and kernel interface
+Version:	@PACKAGE_VERSION@
+Libs:		-L${libdir} -lip6tc
+Cflags:		-I${includedir}
diff --git a/libiptc/libiptc.c b/libiptc/libiptc.c
new file mode 100644
index 0000000..ceeb017
--- /dev/null
+++ b/libiptc/libiptc.c
@@ -0,0 +1,2751 @@
+/* Library which manipulates firewall rules.  Version $Revision$ */
+
+/* Architecture of firewall rules is as follows:
+ *
+ * Chains go INPUT, FORWARD, OUTPUT then user chains.
+ * Each user chain starts with an ERROR node.
+ * Every chain ends with an unconditional jump: a RETURN for user chains,
+ * and a POLICY for built-ins.
+ */
+
+/* (C) 1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See
+ * COPYING for details).
+ * (C) 2000-2004 by the Netfilter Core Team <coreteam@netfilter.org>
+ *
+ * 2003-Jun-20: Harald Welte <laforge@netfilter.org>:
+ *	- Reimplementation of chain cache to use offsets instead of entries
+ * 2003-Jun-23: Harald Welte <laforge@netfilter.org>:
+ * 	- performance optimization, sponsored by Astaro AG (http://www.astaro.com/)
+ * 	  don't rebuild the chain cache after every operation, instead fix it
+ * 	  up after a ruleset change.
+ * 2004-Aug-18: Harald Welte <laforge@netfilter.org>:
+ * 	- further performance work: total reimplementation of libiptc.
+ * 	- libiptc now has a real internal (linked-list) represntation of the
+ * 	  ruleset and a parser/compiler from/to this internal representation
+ * 	- again sponsored by Astaro AG (http://www.astaro.com/)
+ *
+ * 2008-Jan+Jul: Jesper Dangaard Brouer <hawk@comx.dk>
+ * 	- performance work: speedup chain list "name" searching.
+ * 	- performance work: speedup initial ruleset parsing.
+ * 	- sponsored by ComX Networks A/S (http://www.comx.dk/)
+ */
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdbool.h>
+#include <xtables.h>
+#include <libiptc/xtcshared.h>
+
+#include "linux_list.h"
+
+//#define IPTC_DEBUG2 1
+
+#ifdef IPTC_DEBUG2
+#include <fcntl.h>
+#define DEBUGP(x, args...)	fprintf(stderr, "%s: " x, __FUNCTION__, ## args)
+#define DEBUGP_C(x, args...)	fprintf(stderr, x, ## args)
+#else
+#define DEBUGP(x, args...)
+#define DEBUGP_C(x, args...)
+#endif
+
+#ifdef DEBUG
+#define debug(x, args...)	fprintf(stderr, x, ## args)
+#else
+#define debug(x, args...)
+#endif
+
+static void *iptc_fn = NULL;
+
+static const char *hooknames[] = {
+	[HOOK_PRE_ROUTING]	= "PREROUTING",
+	[HOOK_LOCAL_IN]		= "INPUT",
+	[HOOK_FORWARD]		= "FORWARD",
+	[HOOK_LOCAL_OUT]	= "OUTPUT",
+	[HOOK_POST_ROUTING]	= "POSTROUTING",
+};
+
+/* Convenience structures */
+struct chain_head;
+struct rule_head;
+
+struct counter_map
+{
+	enum {
+		COUNTER_MAP_NOMAP,
+		COUNTER_MAP_NORMAL_MAP,
+		COUNTER_MAP_ZEROED,
+		COUNTER_MAP_SET
+	} maptype;
+	unsigned int mappos;
+};
+
+enum iptcc_rule_type {
+	IPTCC_R_STANDARD,		/* standard target (ACCEPT, ...) */
+	IPTCC_R_MODULE,			/* extension module (SNAT, ...) */
+	IPTCC_R_FALLTHROUGH,		/* fallthrough rule */
+	IPTCC_R_JUMP,			/* jump to other chain */
+};
+
+struct rule_head
+{
+	struct list_head list;
+	struct chain_head *chain;
+	struct counter_map counter_map;
+
+	unsigned int index;		/* index (needed for counter_map) */
+	unsigned int offset;		/* offset in rule blob */
+
+	enum iptcc_rule_type type;
+	struct chain_head *jump;	/* jump target, if IPTCC_R_JUMP */
+
+	unsigned int size;		/* size of entry data */
+	STRUCT_ENTRY entry[0];
+};
+
+struct chain_head
+{
+	struct list_head list;
+	char name[TABLE_MAXNAMELEN];
+	unsigned int hooknum;		/* hook number+1 if builtin */
+	unsigned int references;	/* how many jumps reference us */
+	int verdict;			/* verdict if builtin */
+
+	STRUCT_COUNTERS counters;	/* per-chain counters */
+	struct counter_map counter_map;
+
+	unsigned int num_rules;		/* number of rules in list */
+	struct list_head rules;		/* list of rules */
+
+	unsigned int index;		/* index (needed for jump resolval) */
+	unsigned int head_offset;	/* offset in rule blob */
+	unsigned int foot_index;	/* index (needed for counter_map) */
+	unsigned int foot_offset;	/* offset in rule blob */
+};
+
+struct xtc_handle {
+	int sockfd;
+	int changed;			 /* Have changes been made? */
+
+	struct list_head chains;
+
+	struct chain_head *chain_iterator_cur;
+	struct rule_head *rule_iterator_cur;
+
+	unsigned int num_chains;         /* number of user defined chains */
+
+	struct chain_head **chain_index;   /* array for fast chain list access*/
+	unsigned int        chain_index_sz;/* size of chain index array */
+
+	int sorted_offsets; /* if chains are received sorted from kernel,
+			     * then the offsets are also sorted. Says if its
+			     * possible to bsearch offsets using chain_index.
+			     */
+
+	STRUCT_GETINFO info;
+	STRUCT_GET_ENTRIES *entries;
+};
+
+enum bsearch_type {
+	BSEARCH_NAME,	/* Binary search after chain name */
+	BSEARCH_OFFSET,	/* Binary search based on offset */
+};
+
+/* allocate a new chain head for the cache */
+static struct chain_head *iptcc_alloc_chain_head(const char *name, int hooknum)
+{
+	struct chain_head *c = malloc(sizeof(*c));
+	if (!c)
+		return NULL;
+	memset(c, 0, sizeof(*c));
+
+	strncpy(c->name, name, TABLE_MAXNAMELEN - 1);
+	c->hooknum = hooknum;
+	INIT_LIST_HEAD(&c->rules);
+
+	return c;
+}
+
+/* allocate and initialize a new rule for the cache */
+static struct rule_head *iptcc_alloc_rule(struct chain_head *c, unsigned int size)
+{
+	struct rule_head *r = malloc(sizeof(*r)+size);
+	if (!r)
+		return NULL;
+	memset(r, 0, sizeof(*r));
+
+	r->chain = c;
+	r->size = size;
+
+	return r;
+}
+
+/* notify us that the ruleset has been modified by the user */
+static inline void
+set_changed(struct xtc_handle *h)
+{
+	h->changed = 1;
+}
+
+/**********************************************************************
+ * iptc blob utility functions (iptcb_*)
+ **********************************************************************/
+
+static inline int
+iptcb_get_number(const STRUCT_ENTRY *i,
+	   const STRUCT_ENTRY *seek,
+	   unsigned int *pos)
+{
+	if (i == seek)
+		return 1;
+	(*pos)++;
+	return 0;
+}
+
+static inline int
+iptcb_get_entry_n(STRUCT_ENTRY *i,
+	    unsigned int number,
+	    unsigned int *pos,
+	    STRUCT_ENTRY **pe)
+{
+	if (*pos == number) {
+		*pe = i;
+		return 1;
+	}
+	(*pos)++;
+	return 0;
+}
+
+static inline STRUCT_ENTRY *
+iptcb_get_entry(struct xtc_handle *h, unsigned int offset)
+{
+	return (STRUCT_ENTRY *)((char *)h->entries->entrytable + offset);
+}
+
+static unsigned int
+iptcb_entry2index(struct xtc_handle *const h, const STRUCT_ENTRY *seek)
+{
+	unsigned int pos = 0;
+
+	if (ENTRY_ITERATE(h->entries->entrytable, h->entries->size,
+			  iptcb_get_number, seek, &pos) == 0) {
+		fprintf(stderr, "ERROR: offset %u not an entry!\n",
+			(unsigned int)((char *)seek - (char *)h->entries->entrytable));
+		abort();
+	}
+	return pos;
+}
+
+static inline STRUCT_ENTRY *
+iptcb_offset2entry(struct xtc_handle *h, unsigned int offset)
+{
+	return (STRUCT_ENTRY *) ((void *)h->entries->entrytable+offset);
+}
+
+
+static inline unsigned long
+iptcb_entry2offset(struct xtc_handle *const h, const STRUCT_ENTRY *e)
+{
+	return (void *)e - (void *)h->entries->entrytable;
+}
+
+static inline unsigned int
+iptcb_offset2index(struct xtc_handle *const h, unsigned int offset)
+{
+	return iptcb_entry2index(h, iptcb_offset2entry(h, offset));
+}
+
+/* Returns 0 if not hook entry, else hooknumber + 1 */
+static inline unsigned int
+iptcb_ent_is_hook_entry(STRUCT_ENTRY *e, struct xtc_handle *h)
+{
+	unsigned int i;
+
+	for (i = 0; i < NUMHOOKS; i++) {
+		if ((h->info.valid_hooks & (1 << i))
+		    && iptcb_get_entry(h, h->info.hook_entry[i]) == e)
+			return i+1;
+	}
+	return 0;
+}
+
+
+/**********************************************************************
+ * Chain index (cache utility) functions
+ **********************************************************************
+ * The chain index is an array with pointers into the chain list, with
+ * CHAIN_INDEX_BUCKET_LEN spacing.  This facilitates the ability to
+ * speedup chain list searching, by find a more optimal starting
+ * points when searching the linked list.
+ *
+ * The starting point can be found fast by using a binary search of
+ * the chain index. Thus, reducing the previous search complexity of
+ * O(n) to O(log(n/k) + k) where k is CHAIN_INDEX_BUCKET_LEN.
+ *
+ * A nice property of the chain index, is that the "bucket" list
+ * length is max CHAIN_INDEX_BUCKET_LEN (when just build, inserts will
+ * change this). Oppose to hashing, where the "bucket" list length can
+ * vary a lot.
+ */
+#ifndef CHAIN_INDEX_BUCKET_LEN
+#define CHAIN_INDEX_BUCKET_LEN 40
+#endif
+
+/* Another nice property of the chain index is that inserting/creating
+ * chains in chain list don't change the correctness of the chain
+ * index, it only causes longer lists in the buckets.
+ *
+ * To mitigate the performance penalty of longer bucket lists and the
+ * penalty of rebuilding, the chain index is rebuild only when
+ * CHAIN_INDEX_INSERT_MAX chains has been added.
+ */
+#ifndef CHAIN_INDEX_INSERT_MAX
+#define CHAIN_INDEX_INSERT_MAX 355
+#endif
+
+static inline unsigned int iptcc_is_builtin(struct chain_head *c);
+
+/* Use binary search in the chain index array, to find a chain_head
+ * pointer closest to the place of the searched name element.
+ *
+ * Notes that, binary search (obviously) requires that the chain list
+ * is sorted by name.
+ *
+ * The not so obvious: The chain index array, is actually both sorted
+ * by name and offset, at the same time!.  This is only true because,
+ * chain are stored sorted in the kernel (as we pushed it in sorted).
+ *
+ */
+static struct list_head *
+__iptcc_bsearch_chain_index(const char *name, unsigned int offset,
+			    unsigned int *idx, struct xtc_handle *handle,
+			    enum bsearch_type type)
+{
+	unsigned int pos, end;
+	int res;
+
+	struct list_head *list_pos;
+	list_pos=&handle->chains;
+
+	/* Check for empty array, e.g. no user defined chains */
+	if (handle->chain_index_sz == 0) {
+		debug("WARNING: handle->chain_index_sz == 0\n");
+		return list_pos;
+	}
+
+	/* Init */
+	end = handle->chain_index_sz;
+	pos = end / 2;
+
+	debug("bsearch Find chain:%s (pos:%d end:%d) (offset:%d)\n",
+	      name, pos, end, offset);
+
+	/* Loop */
+ loop:
+	if (!handle->chain_index[pos]) {
+		fprintf(stderr, "ERROR: NULL pointer chain_index[%d]\n", pos);
+		return &handle->chains; /* Be safe, return orig start pos */
+	}
+
+	debug("bsearch Index[%d] name:%s ",
+	      pos, handle->chain_index[pos]->name);
+
+	/* Support for different compare functions */
+	switch (type) {
+	case BSEARCH_NAME:
+		res = strcmp(name, handle->chain_index[pos]->name);
+		break;
+	case BSEARCH_OFFSET:
+		debug("head_offset:[%d] foot_offset:[%d] ",
+		      handle->chain_index[pos]->head_offset,
+		      handle->chain_index[pos]->foot_offset);
+		res = offset - handle->chain_index[pos]->head_offset;
+		break;
+	default:
+		fprintf(stderr, "ERROR: %d not a valid bsearch type\n",
+			type);
+		abort();
+		break;
+	}
+	debug("res:%d ", res);
+
+
+	list_pos = &handle->chain_index[pos]->list;
+	*idx = pos;
+
+	if (res == 0) { /* Found element, by direct hit */
+		debug("[found] Direct hit pos:%d end:%d\n", pos, end);
+		return list_pos;
+	} else if (res < 0) { /* Too far, jump back */
+		end = pos;
+		pos = pos / 2;
+
+		/* Exit case: First element of array */
+		if (end == 0) {
+			debug("[found] Reached first array elem (end%d)\n",end);
+			return list_pos;
+		}
+		debug("jump back to pos:%d (end:%d)\n", pos, end);
+		goto loop;
+	} else { /* res > 0; Not far enough, jump forward */
+
+		/* Exit case: Last element of array */
+		if (pos == handle->chain_index_sz-1) {
+			debug("[found] Last array elem (end:%d)\n", end);
+			return list_pos;
+		}
+
+		/* Exit case: Next index less, thus elem in this list section */
+		switch (type) {
+		case BSEARCH_NAME:
+			res = strcmp(name, handle->chain_index[pos+1]->name);
+			break;
+		case BSEARCH_OFFSET:
+			res = offset - handle->chain_index[pos+1]->head_offset;
+			break;
+		}
+
+		if (res < 0) {
+			debug("[found] closest list (end:%d)\n", end);
+			return list_pos;
+		}
+
+		pos = (pos+end)/2;
+		debug("jump forward to pos:%d (end:%d)\n", pos, end);
+		goto loop;
+	}
+}
+
+/* Wrapper for string chain name based bsearch */
+static struct list_head *
+iptcc_bsearch_chain_index(const char *name, unsigned int *idx,
+			  struct xtc_handle *handle)
+{
+	return __iptcc_bsearch_chain_index(name, 0, idx, handle, BSEARCH_NAME);
+}
+
+
+/* Wrapper for offset chain based bsearch */
+static struct list_head *
+iptcc_bsearch_chain_offset(unsigned int offset, unsigned int *idx,
+			  struct xtc_handle *handle)
+{
+	struct list_head *pos;
+
+	/* If chains were not received sorted from kernel, then the
+	 * offset bsearch is not possible.
+	 */
+	if (!handle->sorted_offsets)
+		pos = handle->chains.next;
+	else
+		pos = __iptcc_bsearch_chain_index(NULL, offset, idx, handle,
+						  BSEARCH_OFFSET);
+	return pos;
+}
+
+
+#ifdef DEBUG
+/* Trivial linear search of chain index. Function used for verifying
+   the output of bsearch function */
+static struct list_head *
+iptcc_linearly_search_chain_index(const char *name, struct xtc_handle *handle)
+{
+	unsigned int i=0;
+	int res=0;
+
+	struct list_head *list_pos;
+	list_pos = &handle->chains;
+
+	if (handle->chain_index_sz)
+		list_pos = &handle->chain_index[0]->list;
+
+	/* Linearly walk of chain index array */
+
+	for (i=0; i < handle->chain_index_sz; i++) {
+		if (handle->chain_index[i]) {
+			res = strcmp(handle->chain_index[i]->name, name);
+			if (res > 0)
+				break; // One step too far
+			list_pos = &handle->chain_index[i]->list;
+			if (res == 0)
+				break; // Direct hit
+		}
+	}
+
+	return list_pos;
+}
+#endif
+
+static int iptcc_chain_index_alloc(struct xtc_handle *h)
+{
+	unsigned int list_length = CHAIN_INDEX_BUCKET_LEN;
+	unsigned int array_elems;
+	unsigned int array_mem;
+
+	/* Allocate memory for the chain index array */
+	array_elems = (h->num_chains / list_length) +
+                      (h->num_chains % list_length ? 1 : 0);
+	array_mem   = sizeof(h->chain_index) * array_elems;
+
+	debug("Alloc Chain index, elems:%d mem:%d bytes\n",
+	      array_elems, array_mem);
+
+	h->chain_index = malloc(array_mem);
+	if (h->chain_index == NULL && array_mem > 0) {
+		h->chain_index_sz = 0;
+		return -ENOMEM;
+	}
+	memset(h->chain_index, 0, array_mem);
+	h->chain_index_sz = array_elems;
+
+	return 1;
+}
+
+static void iptcc_chain_index_free(struct xtc_handle *h)
+{
+	h->chain_index_sz = 0;
+	free(h->chain_index);
+}
+
+
+#ifdef DEBUG
+static void iptcc_chain_index_dump(struct xtc_handle *h)
+{
+	unsigned int i = 0;
+
+	/* Dump: contents of chain index array */
+	for (i=0; i < h->chain_index_sz; i++) {
+		if (h->chain_index[i]) {
+			fprintf(stderr, "Chain index[%d].name: %s\n",
+				i, h->chain_index[i]->name);
+		}
+	}
+}
+#endif
+
+/* Build the chain index */
+static int iptcc_chain_index_build(struct xtc_handle *h)
+{
+	unsigned int list_length = CHAIN_INDEX_BUCKET_LEN;
+	unsigned int chains = 0;
+	unsigned int cindex = 0;
+	struct chain_head *c;
+
+	/* Build up the chain index array here */
+	debug("Building chain index\n");
+
+	debug("Number of user defined chains:%d bucket_sz:%d array_sz:%d\n",
+		h->num_chains, list_length, h->chain_index_sz);
+
+	if (h->chain_index_sz == 0)
+		return 0;
+
+	list_for_each_entry(c, &h->chains, list) {
+
+		/* Issue: The index array needs to start after the
+		 * builtin chains, as they are not sorted */
+		if (!iptcc_is_builtin(c)) {
+			cindex=chains / list_length;
+
+			/* Safe guard, break out on array limit, this
+			 * is useful if chains are added and array is
+			 * rebuild, without realloc of memory. */
+			if (cindex >= h->chain_index_sz)
+				break;
+
+			if ((chains % list_length)== 0) {
+				debug("\nIndex[%d] Chains:", cindex);
+				h->chain_index[cindex] = c;
+			}
+			chains++;
+		}
+		debug("%s, ", c->name);
+	}
+	debug("\n");
+
+	return 1;
+}
+
+static int iptcc_chain_index_rebuild(struct xtc_handle *h)
+{
+	debug("REBUILD chain index array\n");
+	iptcc_chain_index_free(h);
+	if ((iptcc_chain_index_alloc(h)) < 0)
+		return -ENOMEM;
+	iptcc_chain_index_build(h);
+	return 1;
+}
+
+/* Delete chain (pointer) from index array.  Removing an element from
+ * the chain list only affects the chain index array, if the chain
+ * index points-to/uses that list pointer.
+ *
+ * There are different strategies, the simple and safe is to rebuild
+ * the chain index every time.  The more advanced is to update the
+ * array index to point to the next element, but that requires some
+ * house keeping and boundary checks.  The advanced is implemented, as
+ * the simple approach behaves badly when all chains are deleted
+ * because list_for_each processing will always hit the first chain
+ * index, thus causing a rebuild for every chain.
+ */
+static int iptcc_chain_index_delete_chain(struct chain_head *c, struct xtc_handle *h)
+{
+	struct list_head *index_ptr, *next;
+	struct chain_head *c2;
+	unsigned int idx, idx2;
+
+	index_ptr = iptcc_bsearch_chain_index(c->name, &idx, h);
+
+	debug("Del chain[%s] c->list:%p index_ptr:%p\n",
+	      c->name, &c->list, index_ptr);
+
+	/* Save the next pointer */
+	next = c->list.next;
+	list_del(&c->list);
+
+	if (index_ptr == &c->list) { /* Chain used as index ptr */
+
+		/* See if its possible to avoid a rebuild, by shifting
+		 * to next pointer.  Its possible if the next pointer
+		 * is located in the same index bucket.
+		 */
+		c2         = list_entry(next, struct chain_head, list);
+		iptcc_bsearch_chain_index(c2->name, &idx2, h);
+		if (idx != idx2) {
+			/* Rebuild needed */
+			return iptcc_chain_index_rebuild(h);
+		} else {
+			/* Avoiding rebuild */
+			debug("Update cindex[%d] with next ptr name:[%s]\n",
+			      idx, c2->name);
+			h->chain_index[idx]=c2;
+			return 0;
+		}
+	}
+	return 0;
+}
+
+
+/**********************************************************************
+ * iptc cache utility functions (iptcc_*)
+ **********************************************************************/
+
+/* Is the given chain builtin (1) or user-defined (0) */
+static inline unsigned int iptcc_is_builtin(struct chain_head *c)
+{
+	return (c->hooknum ? 1 : 0);
+}
+
+/* Get a specific rule within a chain */
+static struct rule_head *iptcc_get_rule_num(struct chain_head *c,
+					    unsigned int rulenum)
+{
+	struct rule_head *r;
+	unsigned int num = 0;
+
+	list_for_each_entry(r, &c->rules, list) {
+		num++;
+		if (num == rulenum)
+			return r;
+	}
+	return NULL;
+}
+
+/* Get a specific rule within a chain backwards */
+static struct rule_head *iptcc_get_rule_num_reverse(struct chain_head *c,
+					    unsigned int rulenum)
+{
+	struct rule_head *r;
+	unsigned int num = 0;
+
+	list_for_each_entry_reverse(r, &c->rules, list) {
+		num++;
+		if (num == rulenum)
+			return r;
+	}
+	return NULL;
+}
+
+/* Returns chain head if found, otherwise NULL. */
+static struct chain_head *
+iptcc_find_chain_by_offset(struct xtc_handle *handle, unsigned int offset)
+{
+	struct list_head *pos;
+	struct list_head *list_start_pos;
+	unsigned int i;
+
+	if (list_empty(&handle->chains))
+		return NULL;
+
+	/* Find a smart place to start the search */
+  	list_start_pos = iptcc_bsearch_chain_offset(offset, &i, handle);
+
+	/* Note that iptcc_bsearch_chain_offset() skips builtin
+	 * chains, but this function is only used for finding jump
+	 * targets, and a buildin chain is not a valid jump target */
+
+	debug("Offset:[%u] starting search at index:[%u]\n", offset, i);
+//	list_for_each(pos, &handle->chains) {
+	list_for_each(pos, list_start_pos->prev) {
+		struct chain_head *c = list_entry(pos, struct chain_head, list);
+		debug(".");
+		if (offset >= c->head_offset && offset <= c->foot_offset) {
+			debug("Offset search found chain:[%s]\n", c->name);
+			return c;
+		}
+	}
+
+	return NULL;
+}
+
+/* Returns chain head if found, otherwise NULL. */
+static struct chain_head *
+iptcc_find_label(const char *name, struct xtc_handle *handle)
+{
+	struct list_head *pos;
+	struct list_head *list_start_pos;
+	unsigned int i=0;
+	int res;
+
+	if (list_empty(&handle->chains))
+		return NULL;
+
+	/* First look at builtin chains */
+	list_for_each(pos, &handle->chains) {
+		struct chain_head *c = list_entry(pos, struct chain_head, list);
+		if (!iptcc_is_builtin(c))
+			break;
+		if (!strcmp(c->name, name))
+			return c;
+	}
+
+	/* Find a smart place to start the search via chain index */
+  	//list_start_pos = iptcc_linearly_search_chain_index(name, handle);
+  	list_start_pos = iptcc_bsearch_chain_index(name, &i, handle);
+
+	/* Handel if bsearch bails out early */
+	if (list_start_pos == &handle->chains) {
+		list_start_pos = pos;
+	}
+#ifdef DEBUG
+	else {
+		/* Verify result of bsearch against linearly index search */
+		struct list_head *test_pos;
+		struct chain_head *test_c, *tmp_c;
+		test_pos = iptcc_linearly_search_chain_index(name, handle);
+		if (list_start_pos != test_pos) {
+			debug("BUG in chain_index search\n");
+			test_c=list_entry(test_pos,      struct chain_head,list);
+			tmp_c =list_entry(list_start_pos,struct chain_head,list);
+			debug("Verify search found:\n");
+			debug(" Chain:%s\n", test_c->name);
+			debug("BSearch found:\n");
+			debug(" Chain:%s\n", tmp_c->name);
+			exit(42);
+		}
+	}
+#endif
+
+	/* Initial/special case, no user defined chains */
+	if (handle->num_chains == 0)
+		return NULL;
+
+	/* Start searching through the chain list */
+	list_for_each(pos, list_start_pos->prev) {
+		struct chain_head *c = list_entry(pos, struct chain_head, list);
+		res = strcmp(c->name, name);
+		debug("List search name:%s == %s res:%d\n", name, c->name, res);
+		if (res==0)
+			return c;
+
+		/* We can stop earlier as we know list is sorted */
+		if (res>0 && !iptcc_is_builtin(c)) { /* Walked too far*/
+			debug(" Not in list, walked too far, sorted list\n");
+			return NULL;
+		}
+
+		/* Stop on wrap around, if list head is reached */
+		if (pos == &handle->chains) {
+			debug("Stop, list head reached\n");
+			return NULL;
+		}
+	}
+
+	debug("List search NOT found name:%s\n", name);
+	return NULL;
+}
+
+/* called when rule is to be removed from cache */
+static void iptcc_delete_rule(struct rule_head *r)
+{
+	DEBUGP("deleting rule %p (offset %u)\n", r, r->offset);
+	/* clean up reference count of called chain */
+	if (r->type == IPTCC_R_JUMP
+	    && r->jump)
+		r->jump->references--;
+
+	list_del(&r->list);
+	free(r);
+}
+
+
+/**********************************************************************
+ * RULESET PARSER (blob -> cache)
+ **********************************************************************/
+
+/* Delete policy rule of previous chain, since cache doesn't contain
+ * chain policy rules.
+ * WARNING: This function has ugly design and relies on a lot of context, only
+ * to be called from specific places within the parser */
+static int __iptcc_p_del_policy(struct xtc_handle *h, unsigned int num)
+{
+	const unsigned char *data;
+
+	if (h->chain_iterator_cur) {
+		/* policy rule is last rule */
+		struct rule_head *pr = (struct rule_head *)
+			h->chain_iterator_cur->rules.prev;
+
+		/* save verdict */
+		data = GET_TARGET(pr->entry)->data;
+		h->chain_iterator_cur->verdict = *(const int *)data;
+
+		/* save counter and counter_map information */
+		h->chain_iterator_cur->counter_map.maptype =
+						COUNTER_MAP_ZEROED;
+		h->chain_iterator_cur->counter_map.mappos = num-1;
+		memcpy(&h->chain_iterator_cur->counters, &pr->entry->counters,
+			sizeof(h->chain_iterator_cur->counters));
+
+		/* foot_offset points to verdict rule */
+		h->chain_iterator_cur->foot_index = num;
+		h->chain_iterator_cur->foot_offset = pr->offset;
+
+		/* delete rule from cache */
+		iptcc_delete_rule(pr);
+		h->chain_iterator_cur->num_rules--;
+
+		return 1;
+	}
+	return 0;
+}
+
+/* alphabetically insert a chain into the list */
+static void iptc_insert_chain(struct xtc_handle *h, struct chain_head *c)
+{
+	struct chain_head *tmp;
+	struct list_head  *list_start_pos;
+	unsigned int i=1;
+
+	/* Find a smart place to start the insert search */
+  	list_start_pos = iptcc_bsearch_chain_index(c->name, &i, h);
+
+	/* Handle the case, where chain.name is smaller than index[0] */
+	if (i==0 && strcmp(c->name, h->chain_index[0]->name) <= 0) {
+		h->chain_index[0] = c; /* Update chain index head */
+		list_start_pos = h->chains.next;
+		debug("Update chain_index[0] with %s\n", c->name);
+	}
+
+	/* Handel if bsearch bails out early */
+	if (list_start_pos == &h->chains) {
+		list_start_pos = h->chains.next;
+	}
+
+	/* sort only user defined chains */
+	if (!c->hooknum) {
+		list_for_each_entry(tmp, list_start_pos->prev, list) {
+			if (!tmp->hooknum && strcmp(c->name, tmp->name) <= 0) {
+				list_add(&c->list, tmp->list.prev);
+				return;
+			}
+
+			/* Stop if list head is reached */
+			if (&tmp->list == &h->chains) {
+				debug("Insert, list head reached add to tail\n");
+				break;
+			}
+		}
+	}
+
+	/* survived till end of list: add at tail */
+	list_add_tail(&c->list, &h->chains);
+}
+
+/* Another ugly helper function split out of cache_add_entry to make it less
+ * spaghetti code */
+static void __iptcc_p_add_chain(struct xtc_handle *h, struct chain_head *c,
+				unsigned int offset, unsigned int *num)
+{
+	struct list_head  *tail = h->chains.prev;
+	struct chain_head *ctail;
+
+	__iptcc_p_del_policy(h, *num);
+
+	c->head_offset = offset;
+	c->index = *num;
+
+	/* Chains from kernel are already sorted, as they are inserted
+	 * sorted. But there exists an issue when shifting to 1.4.0
+	 * from an older version, as old versions allow last created
+	 * chain to be unsorted.
+	 */
+	if (iptcc_is_builtin(c)) /* Only user defined chains are sorted*/
+		list_add_tail(&c->list, &h->chains);
+	else {
+		ctail = list_entry(tail, struct chain_head, list);
+
+		if (strcmp(c->name, ctail->name) > 0 ||
+		    iptcc_is_builtin(ctail))
+			list_add_tail(&c->list, &h->chains);/* Already sorted*/
+		else {
+			iptc_insert_chain(h, c);/* Was not sorted */
+
+			/* Notice, if chains were not received sorted
+			 * from kernel, then an offset bsearch is no
+			 * longer valid.
+			 */
+			h->sorted_offsets = 0;
+
+			debug("NOTICE: chain:[%s] was NOT sorted(ctail:%s)\n",
+			      c->name, ctail->name);
+		}
+	}
+
+	h->chain_iterator_cur = c;
+}
+
+/* main parser function: add an entry from the blob to the cache */
+static int cache_add_entry(STRUCT_ENTRY *e,
+			   struct xtc_handle *h,
+			   STRUCT_ENTRY **prev,
+			   unsigned int *num)
+{
+	unsigned int builtin;
+	unsigned int offset = (char *)e - (char *)h->entries->entrytable;
+
+	DEBUGP("entering...");
+
+	/* Last entry ("policy rule"). End it.*/
+	if (iptcb_entry2offset(h,e) + e->next_offset == h->entries->size) {
+		/* This is the ERROR node at the end of the chain */
+		DEBUGP_C("%u:%u: end of table:\n", *num, offset);
+
+		__iptcc_p_del_policy(h, *num);
+
+		h->chain_iterator_cur = NULL;
+		goto out_inc;
+	}
+
+	/* We know this is the start of a new chain if it's an ERROR
+	 * target, or a hook entry point */
+
+	if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) == 0) {
+		struct chain_head *c =
+			iptcc_alloc_chain_head((const char *)GET_TARGET(e)->data, 0);
+		DEBUGP_C("%u:%u:new userdefined chain %s: %p\n", *num, offset,
+			(char *)c->name, c);
+		if (!c) {
+			errno = -ENOMEM;
+			return -1;
+		}
+		h->num_chains++; /* New user defined chain */
+
+		__iptcc_p_add_chain(h, c, offset, num);
+
+	} else if ((builtin = iptcb_ent_is_hook_entry(e, h)) != 0) {
+		struct chain_head *c =
+			iptcc_alloc_chain_head((char *)hooknames[builtin-1],
+						builtin);
+		DEBUGP_C("%u:%u new builtin chain: %p (rules=%p)\n",
+			*num, offset, c, &c->rules);
+		if (!c) {
+			errno = -ENOMEM;
+			return -1;
+		}
+
+		c->hooknum = builtin;
+
+		__iptcc_p_add_chain(h, c, offset, num);
+
+		/* FIXME: this is ugly. */
+		goto new_rule;
+	} else {
+		/* has to be normal rule */
+		struct rule_head *r;
+new_rule:
+
+		if (!(r = iptcc_alloc_rule(h->chain_iterator_cur,
+					   e->next_offset))) {
+			errno = ENOMEM;
+			return -1;
+		}
+		DEBUGP_C("%u:%u normal rule: %p: ", *num, offset, r);
+
+		r->index = *num;
+		r->offset = offset;
+		memcpy(r->entry, e, e->next_offset);
+		r->counter_map.maptype = COUNTER_MAP_NORMAL_MAP;
+		r->counter_map.mappos = r->index;
+
+		/* handling of jumps, etc. */
+		if (!strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET)) {
+			STRUCT_STANDARD_TARGET *t;
+
+			t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
+			if (t->target.u.target_size
+			    != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) {
+				errno = EINVAL;
+				free(r);
+				return -1;
+			}
+
+			if (t->verdict < 0) {
+				DEBUGP_C("standard, verdict=%d\n", t->verdict);
+				r->type = IPTCC_R_STANDARD;
+			} else if (t->verdict == r->offset+e->next_offset) {
+				DEBUGP_C("fallthrough\n");
+				r->type = IPTCC_R_FALLTHROUGH;
+			} else {
+				DEBUGP_C("jump, target=%u\n", t->verdict);
+				r->type = IPTCC_R_JUMP;
+				/* Jump target fixup has to be deferred
+				 * until second pass, since we migh not
+				 * yet have parsed the target */
+			}
+		} else {
+			DEBUGP_C("module, target=%s\n", GET_TARGET(e)->u.user.name);
+			r->type = IPTCC_R_MODULE;
+		}
+
+		list_add_tail(&r->list, &h->chain_iterator_cur->rules);
+		h->chain_iterator_cur->num_rules++;
+	}
+out_inc:
+	(*num)++;
+	return 0;
+}
+
+
+/* parse an iptables blob into it's pieces */
+static int parse_table(struct xtc_handle *h)
+{
+	STRUCT_ENTRY *prev;
+	unsigned int num = 0;
+	struct chain_head *c;
+
+	/* Assume that chains offsets are sorted, this verified during
+	   parsing of ruleset (in __iptcc_p_add_chain())*/
+	h->sorted_offsets = 1;
+
+	/* First pass: over ruleset blob */
+	ENTRY_ITERATE(h->entries->entrytable, h->entries->size,
+			cache_add_entry, h, &prev, &num);
+
+	/* Build the chain index, used for chain list search speedup */
+	if ((iptcc_chain_index_alloc(h)) < 0)
+		return -ENOMEM;
+	iptcc_chain_index_build(h);
+
+	/* Second pass: fixup parsed data from first pass */
+	list_for_each_entry(c, &h->chains, list) {
+		struct rule_head *r;
+		list_for_each_entry(r, &c->rules, list) {
+			struct chain_head *lc;
+			STRUCT_STANDARD_TARGET *t;
+
+			if (r->type != IPTCC_R_JUMP)
+				continue;
+
+			t = (STRUCT_STANDARD_TARGET *)GET_TARGET(r->entry);
+			lc = iptcc_find_chain_by_offset(h, t->verdict);
+			if (!lc)
+				return -1;
+			r->jump = lc;
+			lc->references++;
+		}
+	}
+
+	return 1;
+}
+
+
+/**********************************************************************
+ * RULESET COMPILATION (cache -> blob)
+ **********************************************************************/
+
+/* Convenience structures */
+struct iptcb_chain_start{
+	STRUCT_ENTRY e;
+	struct xt_error_target name;
+};
+#define IPTCB_CHAIN_START_SIZE	(sizeof(STRUCT_ENTRY) +			\
+				 ALIGN(sizeof(struct xt_error_target)))
+
+struct iptcb_chain_foot {
+	STRUCT_ENTRY e;
+	STRUCT_STANDARD_TARGET target;
+};
+#define IPTCB_CHAIN_FOOT_SIZE	(sizeof(STRUCT_ENTRY) +			\
+				 ALIGN(sizeof(STRUCT_STANDARD_TARGET)))
+
+struct iptcb_chain_error {
+	STRUCT_ENTRY entry;
+	struct xt_error_target target;
+};
+#define IPTCB_CHAIN_ERROR_SIZE	(sizeof(STRUCT_ENTRY) +			\
+				 ALIGN(sizeof(struct xt_error_target)))
+
+
+
+/* compile rule from cache into blob */
+static inline int iptcc_compile_rule (struct xtc_handle *h, STRUCT_REPLACE *repl, struct rule_head *r)
+{
+	/* handle jumps */
+	if (r->type == IPTCC_R_JUMP) {
+		STRUCT_STANDARD_TARGET *t;
+		t = (STRUCT_STANDARD_TARGET *)GET_TARGET(r->entry);
+		/* memset for memcmp convenience on delete/replace */
+		memset(t->target.u.user.name, 0, XT_EXTENSION_MAXNAMELEN);
+		strcpy(t->target.u.user.name, STANDARD_TARGET);
+		t->target.u.user.revision = 0;
+		/* Jumps can only happen to builtin chains, so we
+		 * can safely assume that they always have a header */
+		t->verdict = r->jump->head_offset + IPTCB_CHAIN_START_SIZE;
+	} else if (r->type == IPTCC_R_FALLTHROUGH) {
+		STRUCT_STANDARD_TARGET *t;
+		t = (STRUCT_STANDARD_TARGET *)GET_TARGET(r->entry);
+		t->verdict = r->offset + r->size;
+	}
+
+	/* copy entry from cache to blob */
+	memcpy((char *)repl->entries+r->offset, r->entry, r->size);
+
+	return 1;
+}
+
+/* compile chain from cache into blob */
+static int iptcc_compile_chain(struct xtc_handle *h, STRUCT_REPLACE *repl, struct chain_head *c)
+{
+	int ret;
+	struct rule_head *r;
+	struct iptcb_chain_start *head;
+	struct iptcb_chain_foot *foot;
+
+	/* only user-defined chains have heaer */
+	if (!iptcc_is_builtin(c)) {
+		/* put chain header in place */
+		head = (void *)repl->entries + c->head_offset;
+		head->e.target_offset = sizeof(STRUCT_ENTRY);
+		head->e.next_offset = IPTCB_CHAIN_START_SIZE;
+		strcpy(head->name.target.u.user.name, ERROR_TARGET);
+		head->name.target.u.target_size =
+				ALIGN(sizeof(struct xt_error_target));
+		strncpy(head->name.errorname, c->name, XT_FUNCTION_MAXNAMELEN);
+		head->name.errorname[XT_FUNCTION_MAXNAMELEN - 1] = '\0';
+	} else {
+		repl->hook_entry[c->hooknum-1] = c->head_offset;
+		repl->underflow[c->hooknum-1] = c->foot_offset;
+	}
+
+	/* iterate over rules */
+	list_for_each_entry(r, &c->rules, list) {
+		ret = iptcc_compile_rule(h, repl, r);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* put chain footer in place */
+	foot = (void *)repl->entries + c->foot_offset;
+	foot->e.target_offset = sizeof(STRUCT_ENTRY);
+	foot->e.next_offset = IPTCB_CHAIN_FOOT_SIZE;
+	strcpy(foot->target.target.u.user.name, STANDARD_TARGET);
+	foot->target.target.u.target_size =
+				ALIGN(sizeof(STRUCT_STANDARD_TARGET));
+	/* builtin targets have verdict, others return */
+	if (iptcc_is_builtin(c))
+		foot->target.verdict = c->verdict;
+	else
+		foot->target.verdict = RETURN;
+	/* set policy-counters */
+	foot->e.counters = c->counters;
+
+	return 0;
+}
+
+/* calculate offset and number for every rule in the cache */
+static int iptcc_compile_chain_offsets(struct xtc_handle *h, struct chain_head *c,
+				       unsigned int *offset, unsigned int *num)
+{
+	struct rule_head *r;
+
+	c->head_offset = *offset;
+	DEBUGP("%s: chain_head %u, offset=%u\n", c->name, *num, *offset);
+
+	if (!iptcc_is_builtin(c))  {
+		/* Chain has header */
+		*offset += sizeof(STRUCT_ENTRY)
+			     + ALIGN(sizeof(struct xt_error_target));
+		(*num)++;
+	}
+
+	list_for_each_entry(r, &c->rules, list) {
+		DEBUGP("rule %u, offset=%u, index=%u\n", *num, *offset, *num);
+		r->offset = *offset;
+		r->index = *num;
+		*offset += r->size;
+		(*num)++;
+	}
+
+	DEBUGP("%s; chain_foot %u, offset=%u, index=%u\n", c->name, *num,
+		*offset, *num);
+	c->foot_offset = *offset;
+	c->foot_index = *num;
+	*offset += sizeof(STRUCT_ENTRY)
+		   + ALIGN(sizeof(STRUCT_STANDARD_TARGET));
+	(*num)++;
+
+	return 1;
+}
+
+/* put the pieces back together again */
+static int iptcc_compile_table_prep(struct xtc_handle *h, unsigned int *size)
+{
+	struct chain_head *c;
+	unsigned int offset = 0, num = 0;
+	int ret = 0;
+
+	/* First pass: calculate offset for every rule */
+	list_for_each_entry(c, &h->chains, list) {
+		ret = iptcc_compile_chain_offsets(h, c, &offset, &num);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Append one error rule at end of chain */
+	num++;
+	offset += sizeof(STRUCT_ENTRY)
+		  + ALIGN(sizeof(struct xt_error_target));
+
+	/* ruleset size is now in offset */
+	*size = offset;
+	return num;
+}
+
+static int iptcc_compile_table(struct xtc_handle *h, STRUCT_REPLACE *repl)
+{
+	struct chain_head *c;
+	struct iptcb_chain_error *error;
+
+	/* Second pass: copy from cache to offsets, fill in jumps */
+	list_for_each_entry(c, &h->chains, list) {
+		int ret = iptcc_compile_chain(h, repl, c);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Append error rule at end of chain */
+	error = (void *)repl->entries + repl->size - IPTCB_CHAIN_ERROR_SIZE;
+	error->entry.target_offset = sizeof(STRUCT_ENTRY);
+	error->entry.next_offset = IPTCB_CHAIN_ERROR_SIZE;
+	error->target.target.u.user.target_size =
+		ALIGN(sizeof(struct xt_error_target));
+	strcpy((char *)&error->target.target.u.user.name, ERROR_TARGET);
+	strcpy((char *)&error->target.errorname, "ERROR");
+
+	return 1;
+}
+
+/**********************************************************************
+ * EXTERNAL API (operates on cache only)
+ **********************************************************************/
+
+/* Allocate handle of given size */
+static struct xtc_handle *
+alloc_handle(STRUCT_GETINFO *infop)
+{
+	struct xtc_handle *h;
+
+	h = malloc(sizeof(*h));
+	if (!h) {
+		errno = ENOMEM;
+		return NULL;
+	}
+	memset(h, 0, sizeof(*h));
+	INIT_LIST_HEAD(&h->chains);
+	strcpy(h->info.name, infop->name);
+
+	h->entries = malloc(sizeof(STRUCT_GET_ENTRIES) + infop->size);
+	if (!h->entries)
+		goto out_free_handle;
+
+	strcpy(h->entries->name, infop->name);
+	h->entries->size = infop->size;
+
+	return h;
+
+out_free_handle:
+	free(h);
+
+	return NULL;
+}
+
+
+struct xtc_handle *
+TC_INIT(const char *tablename)
+{
+	struct xtc_handle *h;
+	STRUCT_GETINFO info;
+	unsigned int tmp;
+	socklen_t s;
+	int sockfd;
+
+retry:
+	iptc_fn = TC_INIT;
+
+	if (strlen(tablename) >= TABLE_MAXNAMELEN) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);
+	if (sockfd < 0)
+		return NULL;
+
+	if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) {
+		fprintf(stderr, "Could not set close on exec: %s\n",
+			strerror(errno));
+		abort();
+	}
+
+	s = sizeof(info);
+
+	strcpy(info.name, tablename);
+	if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) {
+		close(sockfd);
+		return NULL;
+	}
+
+	DEBUGP("valid_hooks=0x%08x, num_entries=%u, size=%u\n",
+		info.valid_hooks, info.num_entries, info.size);
+
+	h = alloc_handle(&info);
+	if (h == NULL) {
+		close(sockfd);
+		return NULL;
+	}
+
+	/* Initialize current state */
+	h->sockfd = sockfd;
+	h->info = info;
+
+	h->entries->size = h->info.size;
+
+	tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;
+
+	if (getsockopt(h->sockfd, TC_IPPROTO, SO_GET_ENTRIES, h->entries,
+		       &tmp) < 0)
+		goto error;
+
+#ifdef IPTC_DEBUG2
+	{
+		int fd = open("/tmp/libiptc-so_get_entries.blob",
+				O_CREAT|O_WRONLY, 0644);
+		if (fd >= 0) {
+			write(fd, h->entries, tmp);
+			close(fd);
+		}
+	}
+#endif
+
+	if (parse_table(h) < 0)
+		goto error;
+
+	return h;
+error:
+	TC_FREE(h);
+	/* A different process changed the ruleset size, retry */
+	if (errno == EAGAIN)
+		goto retry;
+	return NULL;
+}
+
+void
+TC_FREE(struct xtc_handle *h)
+{
+	struct chain_head *c, *tmp;
+
+	iptc_fn = TC_FREE;
+	close(h->sockfd);
+
+	list_for_each_entry_safe(c, tmp, &h->chains, list) {
+		struct rule_head *r, *rtmp;
+
+		list_for_each_entry_safe(r, rtmp, &c->rules, list) {
+			free(r);
+		}
+
+		free(c);
+	}
+
+	iptcc_chain_index_free(h);
+
+	free(h->entries);
+	free(h);
+}
+
+static inline int
+print_match(const STRUCT_ENTRY_MATCH *m)
+{
+	printf("Match name: `%s'\n", m->u.user.name);
+	return 0;
+}
+
+static int dump_entry(STRUCT_ENTRY *e, struct xtc_handle *const handle);
+
+void
+TC_DUMP_ENTRIES(struct xtc_handle *const handle)
+{
+	iptc_fn = TC_DUMP_ENTRIES;
+
+	printf("libiptc v%s. %u bytes.\n",
+	       XTABLES_VERSION, handle->entries->size);
+	printf("Table `%s'\n", handle->info.name);
+	printf("Hooks: pre/in/fwd/out/post = %x/%x/%x/%x/%x\n",
+	       handle->info.hook_entry[HOOK_PRE_ROUTING],
+	       handle->info.hook_entry[HOOK_LOCAL_IN],
+	       handle->info.hook_entry[HOOK_FORWARD],
+	       handle->info.hook_entry[HOOK_LOCAL_OUT],
+	       handle->info.hook_entry[HOOK_POST_ROUTING]);
+	printf("Underflows: pre/in/fwd/out/post = %x/%x/%x/%x/%x\n",
+	       handle->info.underflow[HOOK_PRE_ROUTING],
+	       handle->info.underflow[HOOK_LOCAL_IN],
+	       handle->info.underflow[HOOK_FORWARD],
+	       handle->info.underflow[HOOK_LOCAL_OUT],
+	       handle->info.underflow[HOOK_POST_ROUTING]);
+
+	ENTRY_ITERATE(handle->entries->entrytable, handle->entries->size,
+		      dump_entry, handle);
+}
+
+/* Does this chain exist? */
+int TC_IS_CHAIN(const char *chain, struct xtc_handle *const handle)
+{
+	iptc_fn = TC_IS_CHAIN;
+	return iptcc_find_label(chain, handle) != NULL;
+}
+
+static void iptcc_chain_iterator_advance(struct xtc_handle *handle)
+{
+	struct chain_head *c = handle->chain_iterator_cur;
+
+	if (c->list.next == &handle->chains)
+		handle->chain_iterator_cur = NULL;
+	else
+		handle->chain_iterator_cur =
+			list_entry(c->list.next, struct chain_head, list);
+}
+
+/* Iterator functions to run through the chains. */
+const char *
+TC_FIRST_CHAIN(struct xtc_handle *handle)
+{
+	struct chain_head *c = list_entry(handle->chains.next,
+					  struct chain_head, list);
+
+	iptc_fn = TC_FIRST_CHAIN;
+
+
+	if (list_empty(&handle->chains)) {
+		DEBUGP(": no chains\n");
+		return NULL;
+	}
+
+	handle->chain_iterator_cur = c;
+	iptcc_chain_iterator_advance(handle);
+
+	DEBUGP(": returning `%s'\n", c->name);
+	return c->name;
+}
+
+/* Iterator functions to run through the chains.  Returns NULL at end. */
+const char *
+TC_NEXT_CHAIN(struct xtc_handle *handle)
+{
+	struct chain_head *c = handle->chain_iterator_cur;
+
+	iptc_fn = TC_NEXT_CHAIN;
+
+	if (!c) {
+		DEBUGP(": no more chains\n");
+		return NULL;
+	}
+
+	iptcc_chain_iterator_advance(handle);
+
+	DEBUGP(": returning `%s'\n", c->name);
+	return c->name;
+}
+
+/* Get first rule in the given chain: NULL for empty chain. */
+const STRUCT_ENTRY *
+TC_FIRST_RULE(const char *chain, struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	struct rule_head *r;
+
+	iptc_fn = TC_FIRST_RULE;
+
+	DEBUGP("first rule(%s): ", chain);
+
+	c = iptcc_find_label(chain, handle);
+	if (!c) {
+		errno = ENOENT;
+		return NULL;
+	}
+
+	/* Empty chain: single return/policy rule */
+	if (list_empty(&c->rules)) {
+		DEBUGP_C("no rules, returning NULL\n");
+		return NULL;
+	}
+
+	r = list_entry(c->rules.next, struct rule_head, list);
+	handle->rule_iterator_cur = r;
+	DEBUGP_C("%p\n", r);
+
+	return r->entry;
+}
+
+/* Returns NULL when rules run out. */
+const STRUCT_ENTRY *
+TC_NEXT_RULE(const STRUCT_ENTRY *prev, struct xtc_handle *handle)
+{
+	struct rule_head *r;
+
+	iptc_fn = TC_NEXT_RULE;
+	DEBUGP("rule_iterator_cur=%p...", handle->rule_iterator_cur);
+
+	if (handle->rule_iterator_cur == NULL) {
+		DEBUGP_C("returning NULL\n");
+		return NULL;
+	}
+
+	r = list_entry(handle->rule_iterator_cur->list.next,
+			struct rule_head, list);
+
+	iptc_fn = TC_NEXT_RULE;
+
+	DEBUGP_C("next=%p, head=%p...", &r->list,
+		&handle->rule_iterator_cur->chain->rules);
+
+	if (&r->list == &handle->rule_iterator_cur->chain->rules) {
+		handle->rule_iterator_cur = NULL;
+		DEBUGP_C("finished, returning NULL\n");
+		return NULL;
+	}
+
+	handle->rule_iterator_cur = r;
+
+	/* NOTE: prev is without any influence ! */
+	DEBUGP_C("returning rule %p\n", r);
+	return r->entry;
+}
+
+/* Returns a pointer to the target name of this position. */
+static const char *standard_target_map(int verdict)
+{
+	switch (verdict) {
+		case RETURN:
+			return LABEL_RETURN;
+			break;
+		case -NF_ACCEPT-1:
+			return LABEL_ACCEPT;
+			break;
+		case -NF_DROP-1:
+			return LABEL_DROP;
+			break;
+		case -NF_QUEUE-1:
+			return LABEL_QUEUE;
+			break;
+		default:
+			fprintf(stderr, "ERROR: %d not a valid target)\n",
+				verdict);
+			abort();
+			break;
+	}
+	/* not reached */
+	return NULL;
+}
+
+/* Returns a pointer to the target name of this position. */
+const char *TC_GET_TARGET(const STRUCT_ENTRY *ce,
+			  struct xtc_handle *handle)
+{
+	STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce;
+	struct rule_head *r = container_of(e, struct rule_head, entry[0]);
+	const unsigned char *data;
+
+	iptc_fn = TC_GET_TARGET;
+
+	switch(r->type) {
+		int spos;
+		case IPTCC_R_FALLTHROUGH:
+			return "";
+			break;
+		case IPTCC_R_JUMP:
+			DEBUGP("r=%p, jump=%p, name=`%s'\n", r, r->jump, r->jump->name);
+			return r->jump->name;
+			break;
+		case IPTCC_R_STANDARD:
+			data = GET_TARGET(e)->data;
+			spos = *(const int *)data;
+			DEBUGP("r=%p, spos=%d'\n", r, spos);
+			return standard_target_map(spos);
+			break;
+		case IPTCC_R_MODULE:
+			return GET_TARGET(e)->u.user.name;
+			break;
+	}
+	return NULL;
+}
+/* Is this a built-in chain?  Actually returns hook + 1. */
+int
+TC_BUILTIN(const char *chain, struct xtc_handle *const handle)
+{
+	struct chain_head *c;
+
+	iptc_fn = TC_BUILTIN;
+
+	c = iptcc_find_label(chain, handle);
+	if (!c) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	return iptcc_is_builtin(c);
+}
+
+/* Get the policy of a given built-in chain */
+const char *
+TC_GET_POLICY(const char *chain,
+	      STRUCT_COUNTERS *counters,
+	      struct xtc_handle *handle)
+{
+	struct chain_head *c;
+
+	iptc_fn = TC_GET_POLICY;
+
+	DEBUGP("called for chain %s\n", chain);
+
+	c = iptcc_find_label(chain, handle);
+	if (!c) {
+		errno = ENOENT;
+		return NULL;
+	}
+
+	if (!iptcc_is_builtin(c))
+		return NULL;
+
+	*counters = c->counters;
+
+	return standard_target_map(c->verdict);
+}
+
+static int
+iptcc_standard_map(struct rule_head *r, int verdict)
+{
+	STRUCT_ENTRY *e = r->entry;
+	STRUCT_STANDARD_TARGET *t;
+
+	t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
+
+	if (t->target.u.target_size
+	    != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) {
+		errno = EINVAL;
+		return 0;
+	}
+	/* memset for memcmp convenience on delete/replace */
+	memset(t->target.u.user.name, 0, XT_EXTENSION_MAXNAMELEN);
+	strcpy(t->target.u.user.name, STANDARD_TARGET);
+	t->target.u.user.revision = 0;
+	t->verdict = verdict;
+
+	r->type = IPTCC_R_STANDARD;
+
+	return 1;
+}
+
+static int
+iptcc_map_target(struct xtc_handle *const handle,
+	   struct rule_head *r,
+	   bool dry_run)
+{
+	STRUCT_ENTRY *e = r->entry;
+	STRUCT_ENTRY_TARGET *t = GET_TARGET(e);
+
+	/* Maybe it's empty (=> fall through) */
+	if (strcmp(t->u.user.name, "") == 0) {
+		r->type = IPTCC_R_FALLTHROUGH;
+		return 1;
+	}
+	/* Maybe it's a standard target name... */
+	else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0)
+		return iptcc_standard_map(r, -NF_ACCEPT - 1);
+	else if (strcmp(t->u.user.name, LABEL_DROP) == 0)
+		return iptcc_standard_map(r, -NF_DROP - 1);
+	else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0)
+		return iptcc_standard_map(r, -NF_QUEUE - 1);
+	else if (strcmp(t->u.user.name, LABEL_RETURN) == 0)
+		return iptcc_standard_map(r, RETURN);
+	else if (TC_BUILTIN(t->u.user.name, handle)) {
+		/* Can't jump to builtins. */
+		errno = EINVAL;
+		return 0;
+	} else {
+		/* Maybe it's an existing chain name. */
+		struct chain_head *c;
+		DEBUGP("trying to find chain `%s': ", t->u.user.name);
+
+		c = iptcc_find_label(t->u.user.name, handle);
+		if (c) {
+			DEBUGP_C("found!\n");
+			r->type = IPTCC_R_JUMP;
+			r->jump = c;
+			c->references++;
+			return 1;
+		}
+		DEBUGP_C("not found :(\n");
+	}
+
+	/* Must be a module?  If not, kernel will reject... */
+	/* memset to all 0 for your memcmp convenience: don't clear version */
+	memset(t->u.user.name + strlen(t->u.user.name),
+	       0,
+	       FUNCTION_MAXNAMELEN - 1 - strlen(t->u.user.name));
+	r->type = IPTCC_R_MODULE;
+	if (!dry_run)
+		set_changed(handle);
+	return 1;
+}
+
+/* Insert the entry `fw' in chain `chain' into position `rulenum'. */
+int
+TC_INSERT_ENTRY(const IPT_CHAINLABEL chain,
+		const STRUCT_ENTRY *e,
+		unsigned int rulenum,
+		struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	struct rule_head *r;
+	struct list_head *prev;
+
+	iptc_fn = TC_INSERT_ENTRY;
+
+	if (!(c = iptcc_find_label(chain, handle))) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	/* first rulenum index = 0
+	   first c->num_rules index = 1 */
+	if (rulenum > c->num_rules) {
+		errno = E2BIG;
+		return 0;
+	}
+
+	/* If we are inserting at the end just take advantage of the
+	   double linked list, insert will happen before the entry
+	   prev points to. */
+	if (rulenum == c->num_rules) {
+		prev = &c->rules;
+	} else if (rulenum + 1 <= c->num_rules/2) {
+		r = iptcc_get_rule_num(c, rulenum + 1);
+		prev = &r->list;
+	} else {
+		r = iptcc_get_rule_num_reverse(c, c->num_rules - rulenum);
+		prev = &r->list;
+	}
+
+	if (!(r = iptcc_alloc_rule(c, e->next_offset))) {
+		errno = ENOMEM;
+		return 0;
+	}
+
+	memcpy(r->entry, e, e->next_offset);
+	r->counter_map.maptype = COUNTER_MAP_SET;
+
+	if (!iptcc_map_target(handle, r, false)) {
+		free(r);
+		return 0;
+	}
+
+	list_add_tail(&r->list, prev);
+	c->num_rules++;
+
+	set_changed(handle);
+
+	return 1;
+}
+
+/* Atomically replace rule `rulenum' in `chain' with `fw'. */
+int
+TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain,
+		 const STRUCT_ENTRY *e,
+		 unsigned int rulenum,
+		 struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	struct rule_head *r, *old;
+
+	iptc_fn = TC_REPLACE_ENTRY;
+
+	if (!(c = iptcc_find_label(chain, handle))) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	if (rulenum >= c->num_rules) {
+		errno = E2BIG;
+		return 0;
+	}
+
+	/* Take advantage of the double linked list if possible. */
+	if (rulenum + 1 <= c->num_rules/2) {
+		old = iptcc_get_rule_num(c, rulenum + 1);
+	} else {
+		old = iptcc_get_rule_num_reverse(c, c->num_rules - rulenum);
+	}
+
+	if (!(r = iptcc_alloc_rule(c, e->next_offset))) {
+		errno = ENOMEM;
+		return 0;
+	}
+
+	memcpy(r->entry, e, e->next_offset);
+	r->counter_map.maptype = COUNTER_MAP_SET;
+
+	if (!iptcc_map_target(handle, r, false)) {
+		free(r);
+		return 0;
+	}
+
+	list_add(&r->list, &old->list);
+	iptcc_delete_rule(old);
+
+	set_changed(handle);
+
+	return 1;
+}
+
+/* Append entry `fw' to chain `chain'.  Equivalent to insert with
+   rulenum = length of chain. */
+int
+TC_APPEND_ENTRY(const IPT_CHAINLABEL chain,
+		const STRUCT_ENTRY *e,
+		struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	struct rule_head *r;
+
+	iptc_fn = TC_APPEND_ENTRY;
+	if (!(c = iptcc_find_label(chain, handle))) {
+		DEBUGP("unable to find chain `%s'\n", chain);
+		errno = ENOENT;
+		return 0;
+	}
+
+	if (!(r = iptcc_alloc_rule(c, e->next_offset))) {
+		DEBUGP("unable to allocate rule for chain `%s'\n", chain);
+		errno = ENOMEM;
+		return 0;
+	}
+
+	memcpy(r->entry, e, e->next_offset);
+	r->counter_map.maptype = COUNTER_MAP_SET;
+
+	if (!iptcc_map_target(handle, r, false)) {
+		DEBUGP("unable to map target of rule for chain `%s'\n", chain);
+		free(r);
+		return 0;
+	}
+
+	list_add_tail(&r->list, &c->rules);
+	c->num_rules++;
+
+	set_changed(handle);
+
+	return 1;
+}
+
+static inline int
+match_different(const STRUCT_ENTRY_MATCH *a,
+		const unsigned char *a_elems,
+		const unsigned char *b_elems,
+		unsigned char **maskptr)
+{
+	const STRUCT_ENTRY_MATCH *b;
+	unsigned int i;
+
+	/* Offset of b is the same as a. */
+	b = (void *)b_elems + ((unsigned char *)a - a_elems);
+
+	if (a->u.match_size != b->u.match_size)
+		return 1;
+
+	if (strcmp(a->u.user.name, b->u.user.name) != 0)
+		return 1;
+
+	*maskptr += ALIGN(sizeof(*a));
+
+	for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++)
+		if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0)
+			return 1;
+	*maskptr += i;
+	return 0;
+}
+
+static inline int
+target_same(struct rule_head *a, struct rule_head *b,const unsigned char *mask)
+{
+	unsigned int i;
+	STRUCT_ENTRY_TARGET *ta, *tb;
+
+	if (a->type != b->type)
+		return 0;
+
+	ta = GET_TARGET(a->entry);
+	tb = GET_TARGET(b->entry);
+
+	switch (a->type) {
+	case IPTCC_R_FALLTHROUGH:
+		return 1;
+	case IPTCC_R_JUMP:
+		return a->jump == b->jump;
+	case IPTCC_R_STANDARD:
+		return ((STRUCT_STANDARD_TARGET *)ta)->verdict
+			== ((STRUCT_STANDARD_TARGET *)tb)->verdict;
+	case IPTCC_R_MODULE:
+		if (ta->u.target_size != tb->u.target_size)
+			return 0;
+		if (strcmp(ta->u.user.name, tb->u.user.name) != 0)
+			return 0;
+
+		for (i = 0; i < ta->u.target_size - sizeof(*ta); i++)
+			if (((ta->data[i] ^ tb->data[i]) & mask[i]) != 0)
+				return 0;
+		return 1;
+	default:
+		fprintf(stderr, "ERROR: bad type %i\n", a->type);
+		abort();
+	}
+}
+
+static unsigned char *
+is_same(const STRUCT_ENTRY *a,
+	const STRUCT_ENTRY *b,
+	unsigned char *matchmask);
+
+
+/* find the first rule in `chain' which matches `fw' and remove it unless dry_run is set */
+static int delete_entry(const IPT_CHAINLABEL chain, const STRUCT_ENTRY *origfw,
+			unsigned char *matchmask, struct xtc_handle *handle,
+			bool dry_run)
+{
+	struct chain_head *c;
+	struct rule_head *r, *i;
+
+	iptc_fn = TC_DELETE_ENTRY;
+	if (!(c = iptcc_find_label(chain, handle))) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	/* Create a rule_head from origfw. */
+	r = iptcc_alloc_rule(c, origfw->next_offset);
+	if (!r) {
+		errno = ENOMEM;
+		return 0;
+	}
+
+	memcpy(r->entry, origfw, origfw->next_offset);
+	r->counter_map.maptype = COUNTER_MAP_NOMAP;
+	if (!iptcc_map_target(handle, r, dry_run)) {
+		DEBUGP("unable to map target of rule for chain `%s'\n", chain);
+		free(r);
+		return 0;
+	} else {
+		/* iptcc_map_target increment target chain references
+		 * since this is a fake rule only used for matching
+		 * the chain references count is decremented again.
+		 */
+		if (r->type == IPTCC_R_JUMP
+		    && r->jump)
+			r->jump->references--;
+	}
+
+	list_for_each_entry(i, &c->rules, list) {
+		unsigned char *mask;
+
+		mask = is_same(r->entry, i->entry, matchmask);
+		if (!mask)
+			continue;
+
+		if (!target_same(r, i, mask))
+			continue;
+
+		/* if we are just doing a dry run, we simply skip the rest */
+		if (dry_run){
+			free(r);
+			return 1;
+		}
+
+		/* If we are about to delete the rule that is the
+		 * current iterator, move rule iterator back.  next
+		 * pointer will then point to real next node */
+		if (i == handle->rule_iterator_cur) {
+			handle->rule_iterator_cur =
+				list_entry(handle->rule_iterator_cur->list.prev,
+					   struct rule_head, list);
+		}
+
+		c->num_rules--;
+		iptcc_delete_rule(i);
+
+		set_changed(handle);
+		free(r);
+		return 1;
+	}
+
+	free(r);
+	errno = ENOENT;
+	return 0;
+}
+
+/* check whether a specified rule is present */
+int TC_CHECK_ENTRY(const IPT_CHAINLABEL chain, const STRUCT_ENTRY *origfw,
+		   unsigned char *matchmask, struct xtc_handle *handle)
+{
+	/* do a dry-run delete to find out whether a matching rule exists */
+	return delete_entry(chain, origfw, matchmask, handle, true);
+}
+
+/* Delete the first rule in `chain' which matches `fw'. */
+int TC_DELETE_ENTRY(const IPT_CHAINLABEL chain,	const STRUCT_ENTRY *origfw,
+		    unsigned char *matchmask, struct xtc_handle *handle)
+{
+	return delete_entry(chain, origfw, matchmask, handle, false);
+}
+
+/* Delete the rule in position `rulenum' in `chain'. */
+int
+TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain,
+		    unsigned int rulenum,
+		    struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	struct rule_head *r;
+
+	iptc_fn = TC_DELETE_NUM_ENTRY;
+
+	if (!(c = iptcc_find_label(chain, handle))) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	if (rulenum >= c->num_rules) {
+		errno = E2BIG;
+		return 0;
+	}
+
+	/* Take advantage of the double linked list if possible. */
+	if (rulenum + 1 <= c->num_rules/2) {
+		r = iptcc_get_rule_num(c, rulenum + 1);
+	} else {
+		r = iptcc_get_rule_num_reverse(c, c->num_rules - rulenum);
+	}
+
+	/* If we are about to delete the rule that is the current
+	 * iterator, move rule iterator back.  next pointer will then
+	 * point to real next node */
+	if (r == handle->rule_iterator_cur) {
+		handle->rule_iterator_cur =
+			list_entry(handle->rule_iterator_cur->list.prev,
+				   struct rule_head, list);
+	}
+
+	c->num_rules--;
+	iptcc_delete_rule(r);
+
+	set_changed(handle);
+
+	return 1;
+}
+
+/* Flushes the entries in the given chain (ie. empties chain). */
+int
+TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	struct rule_head *r, *tmp;
+
+	iptc_fn = TC_FLUSH_ENTRIES;
+	if (!(c = iptcc_find_label(chain, handle))) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	list_for_each_entry_safe(r, tmp, &c->rules, list) {
+		iptcc_delete_rule(r);
+	}
+
+	c->num_rules = 0;
+
+	set_changed(handle);
+
+	return 1;
+}
+
+/* Zeroes the counters in a chain. */
+int
+TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	struct rule_head *r;
+
+	iptc_fn = TC_ZERO_ENTRIES;
+	if (!(c = iptcc_find_label(chain, handle))) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	if (c->counter_map.maptype == COUNTER_MAP_NORMAL_MAP)
+		c->counter_map.maptype = COUNTER_MAP_ZEROED;
+
+	list_for_each_entry(r, &c->rules, list) {
+		if (r->counter_map.maptype == COUNTER_MAP_NORMAL_MAP)
+			r->counter_map.maptype = COUNTER_MAP_ZEROED;
+	}
+
+	set_changed(handle);
+
+	return 1;
+}
+
+STRUCT_COUNTERS *
+TC_READ_COUNTER(const IPT_CHAINLABEL chain,
+		unsigned int rulenum,
+		struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	struct rule_head *r;
+
+	iptc_fn = TC_READ_COUNTER;
+
+	if (!(c = iptcc_find_label(chain, handle))) {
+		errno = ENOENT;
+		return NULL;
+	}
+
+	if (!(r = iptcc_get_rule_num(c, rulenum))) {
+		errno = E2BIG;
+		return NULL;
+	}
+
+	return &r->entry[0].counters;
+}
+
+int
+TC_ZERO_COUNTER(const IPT_CHAINLABEL chain,
+		unsigned int rulenum,
+		struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	struct rule_head *r;
+
+	iptc_fn = TC_ZERO_COUNTER;
+
+	if (!(c = iptcc_find_label(chain, handle))) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	if (!(r = iptcc_get_rule_num(c, rulenum))) {
+		errno = E2BIG;
+		return 0;
+	}
+
+	if (r->counter_map.maptype == COUNTER_MAP_NORMAL_MAP)
+		r->counter_map.maptype = COUNTER_MAP_ZEROED;
+
+	set_changed(handle);
+
+	return 1;
+}
+
+int
+TC_SET_COUNTER(const IPT_CHAINLABEL chain,
+	       unsigned int rulenum,
+	       STRUCT_COUNTERS *counters,
+	       struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	struct rule_head *r;
+	STRUCT_ENTRY *e;
+
+	iptc_fn = TC_SET_COUNTER;
+
+	if (!(c = iptcc_find_label(chain, handle))) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	if (!(r = iptcc_get_rule_num(c, rulenum))) {
+		errno = E2BIG;
+		return 0;
+	}
+
+	e = r->entry;
+	r->counter_map.maptype = COUNTER_MAP_SET;
+
+	memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS));
+
+	set_changed(handle);
+
+	return 1;
+}
+
+/* Creates a new chain. */
+/* To create a chain, create two rules: error node and unconditional
+ * return. */
+int
+TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, struct xtc_handle *handle)
+{
+	static struct chain_head *c;
+	int capacity;
+	int exceeded;
+
+	iptc_fn = TC_CREATE_CHAIN;
+
+	/* find_label doesn't cover built-in targets: DROP, ACCEPT,
+           QUEUE, RETURN. */
+	if (iptcc_find_label(chain, handle)
+	    || strcmp(chain, LABEL_DROP) == 0
+	    || strcmp(chain, LABEL_ACCEPT) == 0
+	    || strcmp(chain, LABEL_QUEUE) == 0
+	    || strcmp(chain, LABEL_RETURN) == 0) {
+		DEBUGP("Chain `%s' already exists\n", chain);
+		errno = EEXIST;
+		return 0;
+	}
+
+	if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) {
+		DEBUGP("Chain name `%s' too long\n", chain);
+		errno = EINVAL;
+		return 0;
+	}
+
+	c = iptcc_alloc_chain_head(chain, 0);
+	if (!c) {
+		DEBUGP("Cannot allocate memory for chain `%s'\n", chain);
+		errno = ENOMEM;
+		return 0;
+
+	}
+	handle->num_chains++; /* New user defined chain */
+
+	DEBUGP("Creating chain `%s'\n", chain);
+	iptc_insert_chain(handle, c); /* Insert sorted */
+
+	/* Inserting chains don't change the correctness of the chain
+	 * index (except if its smaller than index[0], but that
+	 * handled by iptc_insert_chain).  It only causes longer lists
+	 * in the buckets. Thus, only rebuild chain index when the
+	 * capacity is exceed with CHAIN_INDEX_INSERT_MAX chains.
+	 */
+	capacity = handle->chain_index_sz * CHAIN_INDEX_BUCKET_LEN;
+	exceeded = handle->num_chains - capacity;
+	if (exceeded > CHAIN_INDEX_INSERT_MAX) {
+		debug("Capacity(%d) exceeded(%d) rebuild (chains:%d)\n",
+		      capacity, exceeded, handle->num_chains);
+		iptcc_chain_index_rebuild(handle);
+	}
+
+	set_changed(handle);
+
+	return 1;
+}
+
+/* Get the number of references to this chain. */
+int
+TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain,
+		  struct xtc_handle *handle)
+{
+	struct chain_head *c;
+
+	iptc_fn = TC_GET_REFERENCES;
+	if (!(c = iptcc_find_label(chain, handle))) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	*ref = c->references;
+
+	return 1;
+}
+
+/* Deletes a chain. */
+int
+TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, struct xtc_handle *handle)
+{
+	unsigned int references;
+	struct chain_head *c;
+
+	iptc_fn = TC_DELETE_CHAIN;
+
+	if (!(c = iptcc_find_label(chain, handle))) {
+		DEBUGP("cannot find chain `%s'\n", chain);
+		errno = ENOENT;
+		return 0;
+	}
+
+	if (TC_BUILTIN(chain, handle)) {
+		DEBUGP("cannot remove builtin chain `%s'\n", chain);
+		errno = EINVAL;
+		return 0;
+	}
+
+	if (!TC_GET_REFERENCES(&references, chain, handle)) {
+		DEBUGP("cannot get references on chain `%s'\n", chain);
+		return 0;
+	}
+
+	if (references > 0) {
+		DEBUGP("chain `%s' still has references\n", chain);
+		errno = EMLINK;
+		return 0;
+	}
+
+	if (c->num_rules) {
+		DEBUGP("chain `%s' is not empty\n", chain);
+		errno = ENOTEMPTY;
+		return 0;
+	}
+
+	/* If we are about to delete the chain that is the current
+	 * iterator, move chain iterator forward. */
+	if (c == handle->chain_iterator_cur)
+		iptcc_chain_iterator_advance(handle);
+
+	handle->num_chains--; /* One user defined chain deleted */
+
+	//list_del(&c->list); /* Done in iptcc_chain_index_delete_chain() */
+	iptcc_chain_index_delete_chain(c, handle);
+	free(c);
+
+	DEBUGP("chain `%s' deleted\n", chain);
+
+	set_changed(handle);
+
+	return 1;
+}
+
+/* Renames a chain. */
+int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname,
+		    const IPT_CHAINLABEL newname,
+		    struct xtc_handle *handle)
+{
+	struct chain_head *c;
+	iptc_fn = TC_RENAME_CHAIN;
+
+	/* find_label doesn't cover built-in targets: DROP, ACCEPT,
+           QUEUE, RETURN. */
+	if (iptcc_find_label(newname, handle)
+	    || strcmp(newname, LABEL_DROP) == 0
+	    || strcmp(newname, LABEL_ACCEPT) == 0
+	    || strcmp(newname, LABEL_QUEUE) == 0
+	    || strcmp(newname, LABEL_RETURN) == 0) {
+		errno = EEXIST;
+		return 0;
+	}
+
+	if (!(c = iptcc_find_label(oldname, handle))
+	    || TC_BUILTIN(oldname, handle)) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) {
+		errno = EINVAL;
+		return 0;
+	}
+
+	/* This only unlinks "c" from the list, thus no free(c) */
+	iptcc_chain_index_delete_chain(c, handle);
+
+	/* Change the name of the chain */
+	strncpy(c->name, newname, sizeof(IPT_CHAINLABEL) - 1);
+
+	/* Insert sorted into to list again */
+	iptc_insert_chain(handle, c);
+
+	set_changed(handle);
+
+	return 1;
+}
+
+/* Sets the policy on a built-in chain. */
+int
+TC_SET_POLICY(const IPT_CHAINLABEL chain,
+	      const IPT_CHAINLABEL policy,
+	      STRUCT_COUNTERS *counters,
+	      struct xtc_handle *handle)
+{
+	struct chain_head *c;
+
+	iptc_fn = TC_SET_POLICY;
+
+	if (!(c = iptcc_find_label(chain, handle))) {
+		DEBUGP("cannot find chain `%s'\n", chain);
+		errno = ENOENT;
+		return 0;
+	}
+
+	if (!iptcc_is_builtin(c)) {
+		DEBUGP("cannot set policy of userdefinedchain `%s'\n", chain);
+		errno = ENOENT;
+		return 0;
+	}
+
+	if (strcmp(policy, LABEL_ACCEPT) == 0)
+		c->verdict = -NF_ACCEPT - 1;
+	else if (strcmp(policy, LABEL_DROP) == 0)
+		c->verdict = -NF_DROP - 1;
+	else {
+		errno = EINVAL;
+		return 0;
+	}
+
+	if (counters) {
+		/* set byte and packet counters */
+		memcpy(&c->counters, counters, sizeof(STRUCT_COUNTERS));
+		c->counter_map.maptype = COUNTER_MAP_SET;
+	} else {
+		c->counter_map.maptype = COUNTER_MAP_NOMAP;
+	}
+
+	set_changed(handle);
+
+	return 1;
+}
+
+/* Without this, on gcc 2.7.2.3, we get:
+   libiptc.c: In function `TC_COMMIT':
+   libiptc.c:833: fixed or forbidden register was spilled.
+   This may be due to a compiler bug or to impossible asm
+   statements or clauses.
+*/
+static void
+subtract_counters(STRUCT_COUNTERS *answer,
+		  const STRUCT_COUNTERS *a,
+		  const STRUCT_COUNTERS *b)
+{
+	answer->pcnt = a->pcnt - b->pcnt;
+	answer->bcnt = a->bcnt - b->bcnt;
+}
+
+
+static void counters_nomap(STRUCT_COUNTERS_INFO *newcounters, unsigned int idx)
+{
+	newcounters->counters[idx] = ((STRUCT_COUNTERS) { 0, 0});
+	DEBUGP_C("NOMAP => zero\n");
+}
+
+static void counters_normal_map(STRUCT_COUNTERS_INFO *newcounters,
+				STRUCT_REPLACE *repl, unsigned int idx,
+				unsigned int mappos)
+{
+	/* Original read: X.
+	 * Atomic read on replacement: X + Y.
+	 * Currently in kernel: Z.
+	 * Want in kernel: X + Y + Z.
+	 * => Add in X + Y
+	 * => Add in replacement read.
+	 */
+	newcounters->counters[idx] = repl->counters[mappos];
+	DEBUGP_C("NORMAL_MAP => mappos %u \n", mappos);
+}
+
+static void counters_map_zeroed(STRUCT_COUNTERS_INFO *newcounters,
+				STRUCT_REPLACE *repl, unsigned int idx,
+				unsigned int mappos, STRUCT_COUNTERS *counters)
+{
+	/* Original read: X.
+	 * Atomic read on replacement: X + Y.
+	 * Currently in kernel: Z.
+	 * Want in kernel: Y + Z.
+	 * => Add in Y.
+	 * => Add in (replacement read - original read).
+	 */
+	subtract_counters(&newcounters->counters[idx],
+			  &repl->counters[mappos],
+			  counters);
+	DEBUGP_C("ZEROED => mappos %u\n", mappos);
+}
+
+static void counters_map_set(STRUCT_COUNTERS_INFO *newcounters,
+                             unsigned int idx, STRUCT_COUNTERS *counters)
+{
+	/* Want to set counter (iptables-restore) */
+
+	memcpy(&newcounters->counters[idx], counters,
+		sizeof(STRUCT_COUNTERS));
+
+	DEBUGP_C("SET\n");
+}
+
+
+int
+TC_COMMIT(struct xtc_handle *handle)
+{
+	/* Replace, then map back the counters. */
+	STRUCT_REPLACE *repl;
+	STRUCT_COUNTERS_INFO *newcounters;
+	struct chain_head *c;
+	int ret;
+	size_t counterlen;
+	int new_number;
+	unsigned int new_size;
+
+	iptc_fn = TC_COMMIT;
+
+	/* Don't commit if nothing changed. */
+	if (!handle->changed)
+		goto finished;
+
+	new_number = iptcc_compile_table_prep(handle, &new_size);
+	if (new_number < 0) {
+		errno = ENOMEM;
+		goto out_zero;
+	}
+
+	repl = malloc(sizeof(*repl) + new_size);
+	if (!repl) {
+		errno = ENOMEM;
+		goto out_zero;
+	}
+	memset(repl, 0, sizeof(*repl) + new_size);
+
+#if 0
+	TC_DUMP_ENTRIES(*handle);
+#endif
+
+	counterlen = sizeof(STRUCT_COUNTERS_INFO)
+			+ sizeof(STRUCT_COUNTERS) * new_number;
+
+	/* These are the old counters we will get from kernel */
+	repl->counters = malloc(sizeof(STRUCT_COUNTERS)
+				* handle->info.num_entries);
+	if (!repl->counters) {
+		errno = ENOMEM;
+		goto out_free_repl;
+	}
+	/* These are the counters we're going to put back, later. */
+	newcounters = malloc(counterlen);
+	if (!newcounters) {
+		errno = ENOMEM;
+		goto out_free_repl_counters;
+	}
+	memset(newcounters, 0, counterlen);
+
+	strcpy(repl->name, handle->info.name);
+	repl->num_entries = new_number;
+	repl->size = new_size;
+
+	repl->num_counters = handle->info.num_entries;
+	repl->valid_hooks  = handle->info.valid_hooks;
+
+	DEBUGP("num_entries=%u, size=%u, num_counters=%u\n",
+		repl->num_entries, repl->size, repl->num_counters);
+
+	ret = iptcc_compile_table(handle, repl);
+	if (ret < 0) {
+		errno = ret;
+		goto out_free_newcounters;
+	}
+
+
+#ifdef IPTC_DEBUG2
+	{
+		int fd = open("/tmp/libiptc-so_set_replace.blob",
+				O_CREAT|O_WRONLY, 0644);
+		if (fd >= 0) {
+			write(fd, repl, sizeof(*repl) + repl->size);
+			close(fd);
+		}
+	}
+#endif
+
+	ret = setsockopt(handle->sockfd, TC_IPPROTO, SO_SET_REPLACE, repl,
+			 sizeof(*repl) + repl->size);
+	if (ret < 0)
+		goto out_free_newcounters;
+
+	/* Put counters back. */
+	strcpy(newcounters->name, handle->info.name);
+	newcounters->num_counters = new_number;
+
+	list_for_each_entry(c, &handle->chains, list) {
+		struct rule_head *r;
+
+		/* Builtin chains have their own counters */
+		if (iptcc_is_builtin(c)) {
+			DEBUGP("counter for chain-index %u: ", c->foot_index);
+			switch(c->counter_map.maptype) {
+			case COUNTER_MAP_NOMAP:
+				counters_nomap(newcounters, c->foot_index);
+				break;
+			case COUNTER_MAP_NORMAL_MAP:
+				counters_normal_map(newcounters, repl,
+						    c->foot_index,
+						    c->counter_map.mappos);
+				break;
+			case COUNTER_MAP_ZEROED:
+				counters_map_zeroed(newcounters, repl,
+						    c->foot_index,
+						    c->counter_map.mappos,
+						    &c->counters);
+				break;
+			case COUNTER_MAP_SET:
+				counters_map_set(newcounters, c->foot_index,
+						 &c->counters);
+				break;
+			}
+		}
+
+		list_for_each_entry(r, &c->rules, list) {
+			DEBUGP("counter for index %u: ", r->index);
+			switch (r->counter_map.maptype) {
+			case COUNTER_MAP_NOMAP:
+				counters_nomap(newcounters, r->index);
+				break;
+
+			case COUNTER_MAP_NORMAL_MAP:
+				counters_normal_map(newcounters, repl,
+						    r->index,
+						    r->counter_map.mappos);
+				break;
+
+			case COUNTER_MAP_ZEROED:
+				counters_map_zeroed(newcounters, repl,
+						    r->index,
+						    r->counter_map.mappos,
+						    &r->entry->counters);
+				break;
+
+			case COUNTER_MAP_SET:
+				counters_map_set(newcounters, r->index,
+						 &r->entry->counters);
+				break;
+			}
+		}
+	}
+
+#ifdef IPTC_DEBUG2
+	{
+		int fd = open("/tmp/libiptc-so_set_add_counters.blob",
+				O_CREAT|O_WRONLY, 0644);
+		if (fd >= 0) {
+			write(fd, newcounters, counterlen);
+			close(fd);
+		}
+	}
+#endif
+
+	ret = setsockopt(handle->sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS,
+			 newcounters, counterlen);
+	if (ret < 0)
+		goto out_free_newcounters;
+
+	free(repl->counters);
+	free(repl);
+	free(newcounters);
+
+finished:
+	return 1;
+
+out_free_newcounters:
+	free(newcounters);
+out_free_repl_counters:
+	free(repl->counters);
+out_free_repl:
+	free(repl);
+out_zero:
+	return 0;
+}
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *
+TC_STRERROR(int err)
+{
+	unsigned int i;
+	struct table_struct {
+		void *fn;
+		int err;
+		const char *message;
+	} table [] =
+	  { { TC_INIT, EPERM, "Permission denied (you must be root)" },
+	    { TC_INIT, EINVAL, "Module is wrong version" },
+	    { TC_INIT, ENOENT,
+		    "Table does not exist (do you need to insmod?)" },
+	    { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" },
+	    { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" },
+	    { TC_DELETE_CHAIN, EMLINK,
+	      "Can't delete chain with references left" },
+	    { TC_CREATE_CHAIN, EEXIST, "Chain already exists" },
+	    { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" },
+	    { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" },
+	    { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" },
+	    { TC_READ_COUNTER, E2BIG, "Index of counter too big" },
+	    { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" },
+	    { TC_INSERT_ENTRY, ELOOP, "Loop found in table" },
+	    { TC_INSERT_ENTRY, EINVAL, "Target problem" },
+	    /* ENOENT for DELETE probably means no matching rule */
+	    { TC_DELETE_ENTRY, ENOENT,
+	      "Bad rule (does a matching rule exist in that chain?)" },
+	    { TC_SET_POLICY, ENOENT,
+	      "Bad built-in chain name" },
+	    { TC_SET_POLICY, EINVAL,
+	      "Bad policy name" },
+
+	    { NULL, 0, "Incompatible with this kernel" },
+	    { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" },
+	    { NULL, ENOSYS, "Will be implemented real soon.  I promise ;)" },
+	    { NULL, ENOMEM, "Memory allocation problem" },
+	    { NULL, ENOENT, "No chain/target/match by that name" },
+	  };
+
+	for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) {
+		if ((!table[i].fn || table[i].fn == iptc_fn)
+		    && table[i].err == err)
+			return table[i].message;
+	}
+
+	return strerror(err);
+}
+
+const struct xtc_ops TC_OPS = {
+	.commit        = TC_COMMIT,
+	.init          = TC_INIT,
+	.free          = TC_FREE,
+	.builtin       = TC_BUILTIN,
+	.is_chain      = TC_IS_CHAIN,
+	.flush_entries = TC_FLUSH_ENTRIES,
+	.create_chain  = TC_CREATE_CHAIN,
+	.first_chain   = TC_FIRST_CHAIN,
+	.next_chain    = TC_NEXT_CHAIN,
+	.get_policy    = TC_GET_POLICY,
+	.set_policy    = TC_SET_POLICY,
+	.strerror      = TC_STRERROR,
+};
diff --git a/libiptc/libiptc.pc.in b/libiptc/libiptc.pc.in
new file mode 100644
index 0000000..0264bf0
--- /dev/null
+++ b/libiptc/libiptc.pc.in
@@ -0,0 +1,10 @@
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name:		libiptc
+Description:	iptables v4/v6 ruleset ADT and kernel interface
+Version:	@PACKAGE_VERSION@
+Requires:	libip4tc libip6tc
diff --git a/libiptc/linux_list.h b/libiptc/linux_list.h
new file mode 100644
index 0000000..559e33c
--- /dev/null
+++ b/libiptc/linux_list.h
@@ -0,0 +1,723 @@
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#undef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr:	the pointer to the member.
+ * @type:	the type of the container struct this is embedded in.
+ * @member:	the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({			\
+        const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
+        (type *)( (char *)__mptr - offsetof(type,member) );})
+
+/*
+ * Check at compile time that something is of a particular type.
+ * Always evaluates to 1 so you may use it easily in comparisons.
+ */
+#define typecheck(type,x) \
+({	type __dummy; \
+	typeof(x) __dummy2; \
+	(void)(&__dummy == &__dummy2); \
+	1; \
+})
+
+#define prefetch(x)		((void)0)
+
+/* empty define to make this work in userspace -HW */
+#define smp_wmb()
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1  ((void *) 0x00100100)
+#define LIST_POISON2  ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+	struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+	struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+			      struct list_head *prev,
+			      struct list_head *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head->prev, head);
+}
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add_rcu(struct list_head * new,
+		struct list_head * prev, struct list_head * next)
+{
+	new->next = next;
+	new->prev = prev;
+	smp_wmb();
+	next->prev = new;
+	prev->next = new;
+}
+
+/**
+ * list_add_rcu - add a new entry to rcu-protected list
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as list_add_rcu()
+ * or list_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * list_for_each_entry_rcu().
+ */
+static inline void list_add_rcu(struct list_head *new, struct list_head *head)
+{
+	__list_add_rcu(new, head, head->next);
+}
+
+/**
+ * list_add_tail_rcu - add a new entry to rcu-protected list
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as list_add_tail_rcu()
+ * or list_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * list_for_each_entry_rcu().
+ */
+static inline void list_add_tail_rcu(struct list_head *new,
+					struct list_head *head)
+{
+	__list_add_rcu(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	entry->next = LIST_POISON1;
+	entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_rcu - deletes entry from list without re-initialization
+ * @entry: the element to delete from the list.
+ *
+ * Note: list_empty on entry does not return true after this,
+ * the entry is in an undefined state. It is useful for RCU based
+ * lockfree traversal.
+ *
+ * In particular, it means that we can not poison the forward
+ * pointers that may still be used for walking the list.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as list_del_rcu()
+ * or list_add_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * list_for_each_entry_rcu().
+ *
+ * Note that the caller is not permitted to immediately free
+ * the newly deleted entry.  Instead, either synchronize_kernel()
+ * or call_rcu() must be used to defer freeing until an RCU
+ * grace period has elapsed.
+ */
+static inline void list_del_rcu(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+        __list_del(list->prev, list->next);
+        list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+				  struct list_head *head)
+{
+        __list_del(list->prev, list->next);
+        list_add_tail(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+	return head->next == head;
+}
+
+/**
+ * list_empty_careful - tests whether a list is
+ * empty _and_ checks that no other CPU might be
+ * in the process of still modifying either member
+ *
+ * NOTE: using list_empty_careful() without synchronization
+ * can only be safe if the only activity that can happen
+ * to the list entry is list_del_init(). Eg. it cannot be used
+ * if another CPU could re-list_add() it.
+ *
+ * @head: the list to test.
+ */
+static inline int list_empty_careful(const struct list_head *head)
+{
+	struct list_head *next = head->next;
+	return (next == head) && (next == head->prev);
+}
+
+static inline void __list_splice(struct list_head *list,
+				 struct list_head *head)
+{
+	struct list_head *first = list->next;
+	struct list_head *last = list->prev;
+	struct list_head *at = head->next;
+
+	first->prev = head;
+	head->next = first;
+
+	last->next = at;
+	at->prev = last;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(struct list_head *list, struct list_head *head)
+{
+	if (!list_empty(list))
+		__list_splice(list, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+				    struct list_head *head)
+{
+	if (!list_empty(list)) {
+		__list_splice(list, head);
+		INIT_LIST_HEAD(list);
+	}
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:	the &struct list_head pointer.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+	container_of(ptr, type, member)
+
+/**
+ * list_for_each	-	iterate over a list
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+#define list_for_each(pos, head) \
+	for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+        	pos = pos->next, prefetch(pos->next))
+
+/**
+ * __list_for_each	-	iterate over a list
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code, no prefetching is done.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev	-	iterate over a list backwards
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+	for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \
+        	pos = pos->prev, prefetch(pos->prev))
+
+/**
+ * list_for_each_safe	-	iterate over a list safe against removal of list entry
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @n:		another &struct list_head to use as temporary storage
+ * @head:	the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; pos != (head); \
+		pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry	-	iterate over list of given type
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member)				\
+	for (pos = list_entry((head)->next, typeof(*pos), member),	\
+		     prefetch(pos->member.next);			\
+	     &pos->member != (head); 					\
+	     pos = list_entry(pos->member.next, typeof(*pos), member),	\
+		     prefetch(pos->member.next))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member)			\
+	for (pos = list_entry((head)->prev, typeof(*pos), member),	\
+		     prefetch(pos->member.prev);			\
+	     &pos->member != (head); 					\
+	     pos = list_entry(pos->member.prev, typeof(*pos), member),	\
+		     prefetch(pos->member.prev))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use as a start point in
+ *			list_for_each_entry_continue
+ * @pos:	the type * to use as a start point
+ * @head:	the head of the list
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_prepare_entry(pos, head, member) \
+	((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue -	iterate over list of given type
+ *			continuing after existing point
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_continue(pos, head, member) 		\
+	for (pos = list_entry(pos->member.next, typeof(*pos), member),	\
+		     prefetch(pos->member.next);			\
+	     &pos->member != (head);					\
+	     pos = list_entry(pos->member.next, typeof(*pos), member),	\
+		     prefetch(pos->member.next))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos:	the type * to use as a loop counter.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member)			\
+	for (pos = list_entry((head)->next, typeof(*pos), member),	\
+		n = list_entry(pos->member.next, typeof(*pos), member);	\
+	     &pos->member != (head); 					\
+	     pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_rcu	-	iterate over an rcu-protected list
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_rcu(pos, head) \
+	for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+        	pos = pos->next, ({ smp_read_barrier_depends(); 0;}), prefetch(pos->next))
+
+#define __list_for_each_rcu(pos, head) \
+	for (pos = (head)->next; pos != (head); \
+        	pos = pos->next, ({ smp_read_barrier_depends(); 0;}))
+
+/**
+ * list_for_each_safe_rcu	-	iterate over an rcu-protected list safe
+ *					against removal of list entry
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @n:		another &struct list_head to use as temporary storage
+ * @head:	the head for your list.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_safe_rcu(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; pos != (head); \
+		pos = n, ({ smp_read_barrier_depends(); 0;}), n = pos->next)
+
+/**
+ * list_for_each_entry_rcu	-	iterate over rcu list of given type
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_entry_rcu(pos, head, member)			\
+	for (pos = list_entry((head)->next, typeof(*pos), member),	\
+		     prefetch(pos->member.next);			\
+	     &pos->member != (head); 					\
+	     pos = list_entry(pos->member.next, typeof(*pos), member),	\
+		     ({ smp_read_barrier_depends(); 0;}),		\
+		     prefetch(pos->member.next))
+
+
+/**
+ * list_for_each_continue_rcu	-	iterate over an rcu-protected list
+ *			continuing after existing point.
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_continue_rcu(pos, head) \
+	for ((pos) = (pos)->next, prefetch((pos)->next); (pos) != (head); \
+        	(pos) = (pos)->next, ({ smp_read_barrier_depends(); 0;}), prefetch((pos)->next))
+
+/*
+ * Double linked lists with a single pointer list head.
+ * Mostly useful for hash tables where the two pointer list head is
+ * too wasteful.
+ * You lose the ability to access the tail in O(1).
+ */
+
+struct hlist_head {
+	struct hlist_node *first;
+};
+
+struct hlist_node {
+	struct hlist_node *next, **pprev;
+};
+
+#define HLIST_HEAD_INIT { .first = NULL }
+#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }
+#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
+#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)
+
+static inline int hlist_unhashed(const struct hlist_node *h)
+{
+	return !h->pprev;
+}
+
+static inline int hlist_empty(const struct hlist_head *h)
+{
+	return !h->first;
+}
+
+static inline void __hlist_del(struct hlist_node *n)
+{
+	struct hlist_node *next = n->next;
+	struct hlist_node **pprev = n->pprev;
+	*pprev = next;
+	if (next)
+		next->pprev = pprev;
+}
+
+static inline void hlist_del(struct hlist_node *n)
+{
+	__hlist_del(n);
+	n->next = LIST_POISON1;
+	n->pprev = LIST_POISON2;
+}
+
+/**
+ * hlist_del_rcu - deletes entry from hash list without re-initialization
+ * @n: the element to delete from the hash list.
+ *
+ * Note: list_unhashed() on entry does not return true after this,
+ * the entry is in an undefined state. It is useful for RCU based
+ * lockfree traversal.
+ *
+ * In particular, it means that we can not poison the forward
+ * pointers that may still be used for walking the hash list.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as hlist_add_head_rcu()
+ * or hlist_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * hlist_for_each_entry().
+ */
+static inline void hlist_del_rcu(struct hlist_node *n)
+{
+	__hlist_del(n);
+	n->pprev = LIST_POISON2;
+}
+
+static inline void hlist_del_init(struct hlist_node *n)
+{
+	if (n->pprev)  {
+		__hlist_del(n);
+		INIT_HLIST_NODE(n);
+	}
+}
+
+#define hlist_del_rcu_init hlist_del_init
+
+static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
+{
+	struct hlist_node *first = h->first;
+	n->next = first;
+	if (first)
+		first->pprev = &n->next;
+	h->first = n;
+	n->pprev = &h->first;
+}
+
+
+/**
+ * hlist_add_head_rcu - adds the specified element to the specified hlist,
+ * while permitting racing traversals.
+ * @n: the element to add to the hash list.
+ * @h: the list to add to.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as hlist_add_head_rcu()
+ * or hlist_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * hlist_for_each_entry(), but only if smp_read_barrier_depends()
+ * is used to prevent memory-consistency problems on Alpha CPUs.
+ * Regardless of the type of CPU, the list-traversal primitive
+ * must be guarded by rcu_read_lock().
+ *
+ * OK, so why don't we have an hlist_for_each_entry_rcu()???
+ */
+static inline void hlist_add_head_rcu(struct hlist_node *n,
+					struct hlist_head *h)
+{
+	struct hlist_node *first = h->first;
+	n->next = first;
+	n->pprev = &h->first;
+	smp_wmb();
+	if (first)
+		first->pprev = &n->next;
+	h->first = n;
+}
+
+/* next must be != NULL */
+static inline void hlist_add_before(struct hlist_node *n,
+					struct hlist_node *next)
+{
+	n->pprev = next->pprev;
+	n->next = next;
+	next->pprev = &n->next;
+	*(n->pprev) = n;
+}
+
+static inline void hlist_add_after(struct hlist_node *n,
+					struct hlist_node *next)
+{
+	next->next = n->next;
+	n->next = next;
+	next->pprev = &n->next;
+
+	if(next->next)
+		next->next->pprev  = &next->next;
+}
+
+#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
+
+#define hlist_for_each(pos, head) \
+	for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
+	     pos = pos->next)
+
+#define hlist_for_each_safe(pos, n, head) \
+	for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
+	     pos = n)
+
+/**
+ * hlist_for_each_entry	- iterate over list of given type
+ * @tpos:	the type * to use as a loop counter.
+ * @pos:	the &struct hlist_node to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry(tpos, pos, head, member)			 \
+	for (pos = (head)->first;					 \
+	     pos && ({ prefetch(pos->next); 1;}) &&			 \
+		({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+	     pos = pos->next)
+
+/**
+ * hlist_for_each_entry_continue - iterate over a hlist continuing after existing point
+ * @tpos:	the type * to use as a loop counter.
+ * @pos:	the &struct hlist_node to use as a loop counter.
+ * @member:	the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_continue(tpos, pos, member)		 \
+	for (pos = (pos)->next;						 \
+	     pos && ({ prefetch(pos->next); 1;}) &&			 \
+		({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+	     pos = pos->next)
+
+/**
+ * hlist_for_each_entry_from - iterate over a hlist continuing from existing point
+ * @tpos:	the type * to use as a loop counter.
+ * @pos:	the &struct hlist_node to use as a loop counter.
+ * @member:	the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_from(tpos, pos, member)			 \
+	for (; pos && ({ prefetch(pos->next); 1;}) &&			 \
+		({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+	     pos = pos->next)
+
+/**
+ * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @tpos:	the type * to use as a loop counter.
+ * @pos:	the &struct hlist_node to use as a loop counter.
+ * @n:		another &struct hlist_node to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_safe(tpos, pos, n, head, member) 		 \
+	for (pos = (head)->first;					 \
+	     pos && ({ n = pos->next; 1; }) && 				 \
+		({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+	     pos = n)
+
+/**
+ * hlist_for_each_entry_rcu - iterate over rcu list of given type
+ * @pos:	the type * to use as a loop counter.
+ * @pos:	the &struct hlist_node to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the hlist_node within the struct.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as hlist_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define hlist_for_each_entry_rcu(tpos, pos, head, member)		 \
+	for (pos = (head)->first;					 \
+	     pos && ({ prefetch(pos->next); 1;}) &&			 \
+		({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+	     pos = pos->next, ({ smp_read_barrier_depends(); 0; }) )
+
+#endif
diff --git a/libiptc/linux_stddef.h b/libiptc/linux_stddef.h
new file mode 100644
index 0000000..56416f1
--- /dev/null
+++ b/libiptc/linux_stddef.h
@@ -0,0 +1,39 @@
+#ifndef _LINUX_STDDEF_H
+#define _LINUX_STDDEF_H
+
+#undef NULL
+#if defined(__cplusplus)
+#define NULL 0
+#else
+#define NULL ((void *)0)
+#endif
+
+#undef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr:	the pointer to the member.
+ * @type:	the type of the container struct this is embedded in.
+ * @member:	the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({			\
+        const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
+        (type *)( (char *)__mptr - offsetof(type,member) );})
+
+/*
+ * Check at compile time that something is of a particular type.
+ * Always evaluates to 1 so you may use it easily in comparisons.
+ */
+#define typecheck(type,x) \
+({	type __dummy; \
+	typeof(x) __dummy2; \
+	(void)(&__dummy == &__dummy2); \
+	1; \
+})
+
+
+#endif
diff --git a/libxtables/Android.bp b/libxtables/Android.bp
new file mode 100644
index 0000000..ed0cc44
--- /dev/null
+++ b/libxtables/Android.bp
@@ -0,0 +1,36 @@
+//----------------------------------------------------------------
+// libxtables
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_iptables_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL-2.0
+    //   SPDX-license-identifier-LGPL
+    default_applicable_licenses: ["external_iptables_license"],
+}
+
+cc_library_static {
+    name: "libxtables",
+    defaults: ["iptables_defaults"],
+
+    header_libs: [
+        "iptables_iptables_headers",
+        "iptables_config_header",
+    ],
+    export_header_lib_headers: ["iptables_headers"],
+
+    cflags: [
+        "-DNO_SHARED_LIBS=1",
+        "-DXTABLES_INTERNAL",
+        "-DXTABLES_LIBDIR=\"xtables_libdir_not_used\"",
+
+        "-Wno-missing-field-initializers",
+    ],
+
+    srcs: [
+        "xtables.c",
+        "xtoptions.c",
+    ],
+}
diff --git a/libxtables/Android.mk b/libxtables/Android.mk
new file mode 100644
index 0000000..1fb44e8
--- /dev/null
+++ b/libxtables/Android.mk
@@ -0,0 +1,46 @@
+LOCAL_PATH:= $(call my-dir)
+#----------------------------------------------------------------
+# libxtables
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include/ \
+	$(LOCAL_PATH)/../iptables/ \
+	$(LOCAL_PATH)/.. \
+
+LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
+LOCAL_CFLAGS+=-DXTABLES_INTERNAL
+LOCAL_CFLAGS+=-DXTABLES_LIBDIR=\"xtables_libdir_not_used\"
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+LOCAL_CFLAGS += -Wno-sign-compare -Wno-pointer-arith -Wno-type-limits -Wno-missing-field-initializers -Wno-unused-parameter -Wno-clobbered -Wno-tautological-compare -Werror
+
+LOCAL_LDFLAGS:=-version-info 10:0:0
+LOCAL_SRC_FILES:= \
+	xtables.c xtoptions.c
+
+LOCAL_MODULE:=libxtables
+
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include/ \
+	$(LOCAL_PATH)/../iptables/ \
+	$(LOCAL_PATH)/..
+
+LOCAL_CFLAGS:=-DSHARED_LIBS=1
+LOCAL_CFLAGS+=-DXTABLES_INTERNAL
+LOCAL_CFLAGS+=-DXTABLES_LIBDIR=\"xtables_libdir_not_used\"
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+
+LOCAL_CFLAGS += -Wno-sign-compare -Wno-pointer-arith -Wno-type-limits -Wno-missing-field-initializers -Wno-unused-parameter -Wno-clobbered -Wno-tautological-compare -Werror
+
+LOCAL_SRC_FILES:= \
+	xtables.c xtoptions.c
+
+LOCAL_MODULE:=libxtables
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/../include/
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/libxtables/Makefile.am b/libxtables/Makefile.am
new file mode 100644
index 0000000..8ff6b0c
--- /dev/null
+++ b/libxtables/Makefile.am
@@ -0,0 +1,20 @@
+# -*- Makefile -*-
+
+AM_CFLAGS   = ${regular_CFLAGS}
+AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include -I${top_srcdir}/iptables ${kinclude_CPPFLAGS}
+
+lib_LTLIBRARIES       = libxtables.la
+libxtables_la_SOURCES = xtables.c xtoptions.c getethertype.c
+libxtables_la_LDFLAGS = -version-info ${libxtables_vcurrent}:0:${libxtables_vage}
+libxtables_la_LIBADD  =
+if ENABLE_STATIC
+# With --enable-static, shipped extensions are linked into the main executable,
+# so we need all the LIBADDs here too
+libxtables_la_LIBADD += -lm ${libnetfilter_conntrack_LIBS}
+endif
+if ENABLE_SHARED
+libxtables_la_CFLAGS  = ${AM_CFLAGS}
+libxtables_la_LIBADD += -ldl
+else
+libxtables_la_CFLAGS  = ${AM_CFLAGS} -DNO_SHARED_LIBS=1
+endif
diff --git a/libxtables/getethertype.c b/libxtables/getethertype.c
new file mode 100644
index 0000000..59949b7
--- /dev/null
+++ b/libxtables/getethertype.c
@@ -0,0 +1,160 @@
+/*
+* getethertype.c
+*
+* This file was part of the NYS Library.
+*
+** The NYS Library is free software; you can redistribute it and/or
+** modify it under the terms of the GNU Library General Public License as
+** published by the Free Software Foundation; either version 2 of the
+** License, or (at your option) any later version.
+*
+* 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 2 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, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/********************************************************************
+* Description: Ethertype name service switch and the ethertypes 
+* database access functions
+* Author: Nick Fedchik <fnm@ukrsat.com>
+* Checker: Bart De Schuymer <bdschuym@pandora.be>
+* Origin: uClibc-0.9.16/libc/inet/getproto.c
+* Created at: Mon Nov 11 12:20:11 EET 2002
+********************************************************************/
+
+#include <ctype.h>
+#include <features.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/ether.h>
+#include <net/ethernet.h>
+#include <xtables.h>
+
+#define	MAXALIASES	35
+
+static FILE *etherf = NULL;
+static char line[BUFSIZ + 1];
+static struct xt_ethertypeent et_ent;
+static char *ethertype_aliases[MAXALIASES];
+static int ethertype_stayopen;
+
+static void setethertypeent(int f)
+{
+	if (etherf == NULL)
+		etherf = fopen(XT_PATH_ETHERTYPES, "r");
+	else
+		rewind(etherf);
+	ethertype_stayopen |= f;
+}
+
+static void endethertypeent(void)
+{
+	if (etherf) {
+		fclose(etherf);
+		etherf = NULL;
+	}
+	ethertype_stayopen = 0;
+}
+
+
+static struct xt_ethertypeent *getethertypeent(void)
+{
+	char *e;
+	char *endptr;
+	register char *cp, **q;
+
+	if (etherf == NULL
+	    && (etherf = fopen(XT_PATH_ETHERTYPES, "r")) == NULL) {
+		return (NULL);
+	}
+
+again:
+	if ((e = fgets(line, BUFSIZ, etherf)) == NULL) {
+		return (NULL);
+	}
+	if (*e == '#')
+		goto again;
+	cp = strpbrk(e, "#\n");
+	if (cp == NULL)
+		goto again;
+	*cp = '\0';
+	et_ent.e_name = e;
+	cp = strpbrk(e, " \t");
+	if (cp == NULL)
+		goto again;
+	*cp++ = '\0';
+	while (*cp == ' ' || *cp == '\t')
+		cp++;
+	e = strpbrk(cp, " \t");
+	if (e != NULL)
+		*e++ = '\0';
+// Check point
+	et_ent.e_ethertype = strtol(cp, &endptr, 16);
+	if (*endptr != '\0'
+	    || (et_ent.e_ethertype < ETH_ZLEN
+		|| et_ent.e_ethertype > 0xFFFF))
+		goto again;	// Skip invalid etherproto type entry
+	q = et_ent.e_aliases = ethertype_aliases;
+	if (e != NULL) {
+		cp = e;
+		while (cp && *cp) {
+			if (*cp == ' ' || *cp == '\t') {
+				cp++;
+				continue;
+			}
+			if (q < &ethertype_aliases[MAXALIASES - 1])
+				*q++ = cp;
+			cp = strpbrk(cp, " \t");
+			if (cp != NULL)
+				*cp++ = '\0';
+		}
+	}
+	*q = NULL;
+	return (&et_ent);
+}
+
+struct xt_ethertypeent *xtables_getethertypebyname(const char *name)
+{
+	register struct xt_ethertypeent *e;
+	register char **cp;
+
+	setethertypeent(ethertype_stayopen);
+	while ((e = getethertypeent()) != NULL) {
+		if (strcasecmp(e->e_name, name) == 0)
+			break;
+		for (cp = e->e_aliases; *cp != 0; cp++)
+			if (strcasecmp(*cp, name) == 0)
+				goto found;
+	}
+found:
+	if (!ethertype_stayopen)
+		endethertypeent();
+	return (e);
+}
+
+struct xt_ethertypeent *xtables_getethertypebynumber(int type)
+{
+	register struct xt_ethertypeent *e;
+
+	setethertypeent(ethertype_stayopen);
+	while ((e = getethertypeent()) != NULL)
+		if (e->e_ethertype == type)
+			break;
+	if (!ethertype_stayopen)
+		endethertypeent();
+	return (e);
+}
diff --git a/libxtables/xtables.c b/libxtables/xtables.c
new file mode 100644
index 0000000..35fa625
--- /dev/null
+++ b/libxtables/xtables.c
@@ -0,0 +1,2397 @@
+/*
+ * (C) 2000-2006 by the netfilter coreteam <coreteam@netfilter.org>:
+ *
+ *	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 2 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, write to the Free Software
+ *	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "config.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <spawn.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#if defined(HAVE_LINUX_MAGIC_H)
+#	include <linux/magic.h> /* for PROC_SUPER_MAGIC */
+#elif defined(HAVE_LINUX_PROC_FS_H)
+#	include <linux/proc_fs.h>	/* Linux 2.4 */
+#else
+#	define PROC_SUPER_MAGIC	0x9fa0
+#endif
+
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h/ip6_tables.h */
+#ifdef __BIONIC__
+#include <linux/if_ether.h> /* ETH_ALEN */
+#endif
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <libiptc/libxtc.h>
+
+#ifndef NO_SHARED_LIBS
+#include <dlfcn.h>
+#endif
+#ifndef IPT_SO_GET_REVISION_MATCH /* Old kernel source. */
+#	define IPT_SO_GET_REVISION_MATCH	(IPT_BASE_CTL + 2)
+#	define IPT_SO_GET_REVISION_TARGET	(IPT_BASE_CTL + 3)
+#endif
+#ifndef IP6T_SO_GET_REVISION_MATCH /* Old kernel source. */
+#	define IP6T_SO_GET_REVISION_MATCH	68
+#	define IP6T_SO_GET_REVISION_TARGET	69
+#endif
+#include <getopt.h>
+#include "iptables/internal.h"
+#include "xshared.h"
+
+#define NPROTO	255
+
+#ifndef PROC_SYS_MODPROBE
+#define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe"
+#endif
+
+/* we need this for ip6?tables-restore.  ip6?tables-restore.c sets line to the
+ * current line of the input file, in order  to give a more precise error
+ * message.  ip6?tables itself doesn't need this, so it is initialized to the
+ * magic number of -1 */
+int line = -1;
+
+void basic_exit_err(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+
+struct xtables_globals *xt_params = NULL;
+
+void basic_exit_err(enum xtables_exittype status, const char *msg, ...)
+{
+	va_list args;
+
+	va_start(args, msg);
+	fprintf(stderr, "%s v%s: ", xt_params->program_name, xt_params->program_version);
+	vfprintf(stderr, msg, args);
+	va_end(args);
+	fprintf(stderr, "\n");
+	exit(status);
+}
+
+void xtables_free_opts(int unused)
+{
+	if (xt_params->opts != xt_params->orig_opts) {
+		free(xt_params->opts);
+		xt_params->opts = NULL;
+	}
+}
+
+struct option *xtables_merge_options(struct option *orig_opts,
+				     struct option *oldopts,
+				     const struct option *newopts,
+				     unsigned int *option_offset)
+{
+	unsigned int num_oold = 0, num_old = 0, num_new = 0, i;
+	struct option *merge, *mp;
+
+	if (newopts == NULL)
+		return oldopts;
+
+	for (num_oold = 0; orig_opts[num_oold].name; num_oold++) ;
+	if (oldopts != NULL)
+		for (num_old = 0; oldopts[num_old].name; num_old++) ;
+	for (num_new = 0; newopts[num_new].name; num_new++) ;
+
+	/*
+	 * Since @oldopts also has @orig_opts already (and does so at the
+	 * start), skip these entries.
+	 */
+	if (oldopts != NULL) {
+		oldopts += num_oold;
+		num_old -= num_oold;
+	}
+
+	merge = malloc(sizeof(*mp) * (num_oold + num_old + num_new + 1));
+	if (merge == NULL)
+		return NULL;
+
+	/* Let the base options -[ADI...] have precedence over everything */
+	memcpy(merge, orig_opts, sizeof(*mp) * num_oold);
+	mp = merge + num_oold;
+
+	/* Second, the new options */
+	xt_params->option_offset += XT_OPTION_OFFSET_SCALE;
+	*option_offset = xt_params->option_offset;
+	memcpy(mp, newopts, sizeof(*mp) * num_new);
+
+	for (i = 0; i < num_new; ++i, ++mp)
+		mp->val += *option_offset;
+
+	/* Third, the old options */
+	if (oldopts != NULL) {
+		memcpy(mp, oldopts, sizeof(*mp) * num_old);
+		mp += num_old;
+	}
+	xtables_free_opts(0);
+
+	/* Clear trailing entry */
+	memset(mp, 0, sizeof(*mp));
+	return merge;
+}
+
+static const struct xtables_afinfo afinfo_ipv4 = {
+	.kmod          = "ip_tables",
+	.proc_exists   = "/proc/net/ip_tables_names",
+	.libprefix     = "libipt_",
+	.family	       = NFPROTO_IPV4,
+	.ipproto       = IPPROTO_IP,
+	.so_rev_match  = IPT_SO_GET_REVISION_MATCH,
+	.so_rev_target = IPT_SO_GET_REVISION_TARGET,
+};
+
+static const struct xtables_afinfo afinfo_ipv6 = {
+	.kmod          = "ip6_tables",
+	.proc_exists   = "/proc/net/ip6_tables_names",
+	.libprefix     = "libip6t_",
+	.family        = NFPROTO_IPV6,
+	.ipproto       = IPPROTO_IPV6,
+	.so_rev_match  = IP6T_SO_GET_REVISION_MATCH,
+	.so_rev_target = IP6T_SO_GET_REVISION_TARGET,
+};
+
+/* Dummy families for arptables-compat and ebtables-compat. Leave structure
+ * fields that we don't use unset.
+ */
+static const struct xtables_afinfo afinfo_bridge = {
+	.libprefix     = "libebt_",
+	.family        = NFPROTO_BRIDGE,
+};
+
+static const struct xtables_afinfo afinfo_arp = {
+	.libprefix     = "libarpt_",
+	.family        = NFPROTO_ARP,
+};
+
+const struct xtables_afinfo *afinfo;
+
+/* Search path for Xtables .so files */
+static const char *xtables_libdir;
+
+/* the path to command to load kernel module */
+const char *xtables_modprobe_program;
+
+/* Keep track of matches/targets pending full registration: linked lists. */
+struct xtables_match *xtables_pending_matches;
+struct xtables_target *xtables_pending_targets;
+
+/* Keep track of fully registered external matches/targets: linked lists. */
+struct xtables_match *xtables_matches;
+struct xtables_target *xtables_targets;
+
+/* Fully register a match/target which was previously partially registered. */
+static bool xtables_fully_register_pending_match(struct xtables_match *me,
+						 struct xtables_match *prev);
+static bool xtables_fully_register_pending_target(struct xtables_target *me,
+						  struct xtables_target *prev);
+
+#ifndef NO_SHARED_LIBS
+/* registry for loaded shared objects to close later */
+struct dlreg {
+	struct dlreg *next;
+	void *handle;
+};
+static struct dlreg *dlreg = NULL;
+
+static int dlreg_add(void *handle)
+{
+	struct dlreg *new = malloc(sizeof(*new));
+
+	if (!new)
+		return -1;
+
+	new->handle = handle;
+	new->next = dlreg;
+	dlreg = new;
+	return 0;
+}
+
+static void dlreg_free(void)
+{
+	struct dlreg *next;
+
+	while (dlreg) {
+		next = dlreg->next;
+		dlclose(dlreg->handle);
+		free(dlreg);
+		dlreg = next;
+	}
+}
+#endif
+
+void xtables_init(void)
+{
+	xtables_libdir = getenv("XTABLES_LIBDIR");
+	if (xtables_libdir != NULL)
+		return;
+	xtables_libdir = getenv("IPTABLES_LIB_DIR");
+	if (xtables_libdir != NULL) {
+		fprintf(stderr, "IPTABLES_LIB_DIR is deprecated, "
+		        "use XTABLES_LIBDIR.\n");
+		return;
+	}
+	/*
+	 * Well yes, IP6TABLES_LIB_DIR is of lower priority over
+	 * IPTABLES_LIB_DIR since this moved to libxtables; I think that is ok
+	 * for these env vars are deprecated anyhow, and in light of the
+	 * (shared) libxt_*.so files, makes less sense to have
+	 * IPTABLES_LIB_DIR != IP6TABLES_LIB_DIR.
+	 */
+	xtables_libdir = getenv("IP6TABLES_LIB_DIR");
+	if (xtables_libdir != NULL) {
+		fprintf(stderr, "IP6TABLES_LIB_DIR is deprecated, "
+		        "use XTABLES_LIBDIR.\n");
+		return;
+	}
+	xtables_libdir = XTABLES_LIBDIR;
+}
+
+void xtables_fini(void)
+{
+#ifndef NO_SHARED_LIBS
+	dlreg_free();
+#endif
+}
+
+void xtables_set_nfproto(uint8_t nfproto)
+{
+	switch (nfproto) {
+	case NFPROTO_IPV4:
+		afinfo = &afinfo_ipv4;
+		break;
+	case NFPROTO_IPV6:
+		afinfo = &afinfo_ipv6;
+		break;
+	case NFPROTO_BRIDGE:
+		afinfo = &afinfo_bridge;
+		break;
+	case NFPROTO_ARP:
+		afinfo = &afinfo_arp;
+		break;
+	default:
+		fprintf(stderr, "libxtables: unhandled NFPROTO in %s\n",
+		        __func__);
+	}
+}
+
+/**
+ * xtables_set_params - set the global parameters used by xtables
+ * @xtp:	input xtables_globals structure
+ *
+ * The app is expected to pass a valid xtables_globals data-filled
+ * with proper values
+ * @xtp cannot be NULL
+ *
+ * Returns -1 on failure to set and 0 on success
+ */
+int xtables_set_params(struct xtables_globals *xtp)
+{
+	if (!xtp) {
+		fprintf(stderr, "%s: Illegal global params\n",__func__);
+		return -1;
+	}
+
+	xt_params = xtp;
+
+	if (!xt_params->exit_err)
+		xt_params->exit_err = basic_exit_err;
+
+	return 0;
+}
+
+int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto)
+{
+	xtables_init();
+	xtables_set_nfproto(nfproto);
+	return xtables_set_params(xtp);
+}
+
+/**
+ * xtables_*alloc - wrappers that exit on failure
+ */
+void *xtables_calloc(size_t count, size_t size)
+{
+	void *p;
+
+	if ((p = calloc(count, size)) == NULL) {
+		perror("ip[6]tables: calloc failed");
+		exit(1);
+	}
+
+	return p;
+}
+
+void *xtables_malloc(size_t size)
+{
+	void *p;
+
+	if ((p = malloc(size)) == NULL) {
+		perror("ip[6]tables: malloc failed");
+		exit(1);
+	}
+
+	return p;
+}
+
+void *xtables_realloc(void *ptr, size_t size)
+{
+	void *p;
+
+	if ((p = realloc(ptr, size)) == NULL) {
+		perror("ip[6]tables: realloc failed");
+		exit(1);
+	}
+
+	return p;
+}
+
+static char *get_modprobe(void)
+{
+	int procfile;
+	char *ret;
+	int count;
+
+	procfile = open(PROC_SYS_MODPROBE, O_RDONLY);
+	if (procfile < 0)
+		return NULL;
+	if (fcntl(procfile, F_SETFD, FD_CLOEXEC) == -1) {
+		fprintf(stderr, "Could not set close on exec: %s\n",
+			strerror(errno));
+		exit(1);
+	}
+
+	ret = malloc(PATH_MAX);
+	if (ret) {
+		count = read(procfile, ret, PATH_MAX);
+		if (count > 0 && count < PATH_MAX)
+		{
+			if (ret[count - 1] == '\n')
+				ret[count - 1] = '\0';
+			else
+				ret[count] = '\0';
+			close(procfile);
+			return ret;
+		}
+	}
+	free(ret);
+	close(procfile);
+	return NULL;
+}
+
+int xtables_insmod(const char *modname, const char *modprobe, bool quiet)
+{
+	char *buf = NULL;
+	char *argv[4];
+	int status;
+	pid_t pid;
+
+	/* If they don't explicitly set it, read out of kernel */
+	if (!modprobe) {
+		buf = get_modprobe();
+		if (!buf)
+			return -1;
+		modprobe = buf;
+	}
+
+	argv[0] = (char *)modprobe;
+	argv[1] = (char *)modname;
+	argv[2] = quiet ? "-q" : NULL;
+	argv[3] = NULL;
+
+	/*
+	 * Need to flush the buffer, or the child may output it again
+	 * when switching the program thru execv.
+	 */
+	fflush(stdout);
+
+	if (posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL)) {
+		free(buf);
+		return -1;
+	} else {
+		waitpid(pid, &status, 0);
+	}
+
+	free(buf);
+	if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
+		return 0;
+	return -1;
+}
+
+/* return true if a given file exists within procfs */
+static bool proc_file_exists(const char *filename)
+{
+	struct stat s;
+	struct statfs f;
+
+	if (lstat(filename, &s))
+		return false;
+	if (!S_ISREG(s.st_mode))
+		return false;
+	if (statfs(filename, &f))
+		return false;
+	if (f.f_type != PROC_SUPER_MAGIC)
+		return false;
+	return true;
+}
+
+int xtables_load_ko(const char *modprobe, bool quiet)
+{
+	static bool loaded = false;
+	int ret;
+
+	if (loaded)
+		return 0;
+
+	if (proc_file_exists(afinfo->proc_exists)) {
+		loaded = true;
+		return 0;
+	};
+
+	ret = xtables_insmod(afinfo->kmod, modprobe, quiet);
+	if (ret == 0)
+		loaded = true;
+
+	return ret;
+}
+
+/**
+ * xtables_strtou{i,l} - string to number conversion
+ * @s:	input string
+ * @end:	like strtoul's "end" pointer
+ * @value:	pointer for result
+ * @min:	minimum accepted value
+ * @max:	maximum accepted value
+ *
+ * If @end is NULL, we assume the caller wants a "strict strtoul", and hence
+ * "15a" is rejected.
+ * In either case, the value obtained is compared for min-max compliance.
+ * Base is always 0, i.e. autodetect depending on @s.
+ *
+ * Returns true/false whether number was accepted. On failure, *value has
+ * undefined contents.
+ */
+bool xtables_strtoul(const char *s, char **end, uintmax_t *value,
+                     uintmax_t min, uintmax_t max)
+{
+	uintmax_t v;
+	const char *p;
+	char *my_end;
+
+	errno = 0;
+	/* Since strtoul allows leading minus, we have to check for ourself. */
+	for (p = s; isspace(*p); ++p)
+		;
+	if (*p == '-')
+		return false;
+	v = strtoumax(s, &my_end, 0);
+	if (my_end == s)
+		return false;
+	if (end != NULL)
+		*end = my_end;
+
+	if (errno != ERANGE && min <= v && (max == 0 || v <= max)) {
+		if (value != NULL)
+			*value = v;
+		if (end == NULL)
+			return *my_end == '\0';
+		return true;
+	}
+
+	return false;
+}
+
+bool xtables_strtoui(const char *s, char **end, unsigned int *value,
+                     unsigned int min, unsigned int max)
+{
+	uintmax_t v;
+	bool ret;
+
+	ret = xtables_strtoul(s, end, &v, min, max);
+	if (ret && value != NULL)
+		*value = v;
+	return ret;
+}
+
+int xtables_service_to_port(const char *name, const char *proto)
+{
+	struct servent *service;
+
+	if ((service = getservbyname(name, proto)) != NULL)
+		return ntohs((unsigned short) service->s_port);
+
+	return -1;
+}
+
+uint16_t xtables_parse_port(const char *port, const char *proto)
+{
+	unsigned int portnum;
+
+	if (xtables_strtoui(port, NULL, &portnum, 0, UINT16_MAX) ||
+	    (portnum = xtables_service_to_port(port, proto)) != (unsigned)-1)
+		return portnum;
+
+	xt_params->exit_err(PARAMETER_PROBLEM,
+		   "invalid port/service `%s' specified", port);
+}
+
+void xtables_parse_interface(const char *arg, char *vianame,
+			     unsigned char *mask)
+{
+	unsigned int vialen = strlen(arg);
+	unsigned int i;
+
+	memset(mask, 0, IFNAMSIZ);
+	memset(vianame, 0, IFNAMSIZ);
+
+	if (vialen + 1 > IFNAMSIZ)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			   "interface name `%s' must be shorter than IFNAMSIZ"
+			   " (%i)", arg, IFNAMSIZ-1);
+
+	strcpy(vianame, arg);
+	if (vialen == 0)
+		return;
+	else if (vianame[vialen - 1] == '+') {
+		memset(mask, 0xFF, vialen - 1);
+		/* Don't remove `+' here! -HW */
+	} else {
+		/* Include nul-terminator in match */
+		memset(mask, 0xFF, vialen + 1);
+	}
+
+	/* Display warning on invalid characters */
+	for (i = 0; vianame[i]; i++) {
+		if (vianame[i] == '/' || vianame[i] == ' ') {
+			fprintf(stderr,	"Warning: weird character in interface"
+				" `%s' ('/' and ' ' are not allowed by the kernel).\n",
+				vianame);
+			break;
+		}
+	}
+}
+
+#ifndef NO_SHARED_LIBS
+static void *load_extension(const char *search_path, const char *af_prefix,
+    const char *name, bool is_target)
+{
+	const char *all_prefixes[] = {af_prefix, "libxt_", NULL};
+	const char **prefix;
+	const char *dir = search_path, *next;
+	void *ptr = NULL;
+	struct stat sb;
+	char path[256];
+
+	do {
+		next = strchr(dir, ':');
+		if (next == NULL)
+			next = dir + strlen(dir);
+
+		for (prefix = all_prefixes; *prefix != NULL; ++prefix) {
+			void *handle;
+
+			snprintf(path, sizeof(path), "%.*s/%s%s.so",
+			         (unsigned int)(next - dir), dir,
+			         *prefix, name);
+
+			if (stat(path, &sb) != 0) {
+				if (errno == ENOENT)
+					continue;
+				fprintf(stderr, "%s: %s\n", path,
+					strerror(errno));
+				return NULL;
+			}
+			handle = dlopen(path, RTLD_NOW);
+			if (handle == NULL) {
+				fprintf(stderr, "%s: %s\n", path, dlerror());
+				break;
+			}
+
+			dlreg_add(handle);
+
+			if (is_target)
+				ptr = xtables_find_target(name, XTF_DONT_LOAD);
+			else
+				ptr = xtables_find_match(name,
+				      XTF_DONT_LOAD, NULL);
+
+			if (ptr != NULL)
+				return ptr;
+
+			errno = ENOENT;
+			return NULL;
+		}
+		dir = next + 1;
+	} while (*next != '\0');
+
+	return NULL;
+}
+#endif
+
+static bool extension_cmp(const char *name1, const char *name2, uint32_t family)
+{
+	if (strcmp(name1, name2) == 0 &&
+	    (family == afinfo->family ||
+	     family == NFPROTO_UNSPEC))
+		return true;
+
+	return false;
+}
+
+struct xtables_match *
+xtables_find_match(const char *name, enum xtables_tryload tryload,
+		   struct xtables_rule_match **matches)
+{
+	struct xtables_match *prev = NULL;
+	struct xtables_match **dptr;
+	struct xtables_match *ptr;
+	const char *icmp6 = "icmp6";
+
+	if (strlen(name) >= XT_EXTENSION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+			   "Invalid match name \"%s\" (%u chars max)",
+			   name, XT_EXTENSION_MAXNAMELEN - 1);
+
+	/* This is ugly as hell. Nonetheless, there is no way of changing
+	 * this without hurting backwards compatibility */
+	if ( (strcmp(name,"icmpv6") == 0) ||
+	     (strcmp(name,"ipv6-icmp") == 0) ||
+	     (strcmp(name,"icmp6") == 0) )
+		name = icmp6;
+
+	/* Trigger delayed initialization */
+	for (dptr = &xtables_pending_matches; *dptr; ) {
+		if (extension_cmp(name, (*dptr)->name, (*dptr)->family)) {
+			ptr = *dptr;
+			*dptr = (*dptr)->next;
+			if (xtables_fully_register_pending_match(ptr, prev)) {
+				prev = ptr;
+				continue;
+			} else if (prev) {
+				continue;
+			}
+			*dptr = ptr;
+		}
+		dptr = &((*dptr)->next);
+	}
+
+	for (ptr = xtables_matches; ptr; ptr = ptr->next) {
+		if (extension_cmp(name, ptr->name, ptr->family)) {
+			struct xtables_match *clone;
+
+			/* First match of this type: */
+			if (ptr->m == NULL)
+				break;
+
+			/* Second and subsequent clones */
+			clone = xtables_malloc(sizeof(struct xtables_match));
+			memcpy(clone, ptr, sizeof(struct xtables_match));
+			clone->udata = NULL;
+			clone->mflags = 0;
+			/* This is a clone: */
+			clone->next = clone;
+
+			ptr = clone;
+			break;
+		}
+	}
+
+#ifndef NO_SHARED_LIBS
+	if (!ptr && tryload != XTF_DONT_LOAD && tryload != XTF_DURING_LOAD) {
+		ptr = load_extension(xtables_libdir, afinfo->libprefix,
+		      name, false);
+
+		if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				   "Couldn't load match `%s':%s\n",
+				   name, strerror(errno));
+	}
+#else
+	if (ptr && !ptr->loaded) {
+		if (tryload != XTF_DONT_LOAD)
+			ptr->loaded = 1;
+		else
+			ptr = NULL;
+	}
+	if(!ptr && (tryload == XTF_LOAD_MUST_SUCCEED)) {
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			   "Couldn't find match `%s'\n", name);
+	}
+#endif
+
+	if (ptr && matches) {
+		struct xtables_rule_match **i;
+		struct xtables_rule_match *newentry;
+
+		newentry = xtables_malloc(sizeof(struct xtables_rule_match));
+
+		for (i = matches; *i; i = &(*i)->next) {
+			if (extension_cmp(name, (*i)->match->name,
+					  (*i)->match->family))
+				(*i)->completed = true;
+		}
+		newentry->match = ptr;
+		newentry->completed = false;
+		newentry->next = NULL;
+		*i = newentry;
+	}
+
+	return ptr;
+}
+
+struct xtables_match *
+xtables_find_match_revision(const char *name, enum xtables_tryload tryload,
+			    struct xtables_match *match, int revision)
+{
+	if (!match) {
+		match = xtables_find_match(name, tryload, NULL);
+		if (!match)
+			return NULL;
+	}
+
+	while (1) {
+		if (match->revision == revision)
+			return match;
+		match = match->next;
+		if (!match)
+			return NULL;
+		if (!extension_cmp(name, match->name, match->family))
+			return NULL;
+	}
+}
+
+struct xtables_target *
+xtables_find_target(const char *name, enum xtables_tryload tryload)
+{
+	struct xtables_target *prev = NULL;
+	struct xtables_target **dptr;
+	struct xtables_target *ptr;
+
+	/* Standard target? */
+	if (strcmp(name, "") == 0
+	    || strcmp(name, XTC_LABEL_ACCEPT) == 0
+	    || strcmp(name, XTC_LABEL_DROP) == 0
+	    || strcmp(name, XTC_LABEL_QUEUE) == 0
+	    || strcmp(name, XTC_LABEL_RETURN) == 0)
+		name = "standard";
+
+	/* Trigger delayed initialization */
+	for (dptr = &xtables_pending_targets; *dptr; ) {
+		if (extension_cmp(name, (*dptr)->name, (*dptr)->family)) {
+			ptr = *dptr;
+			*dptr = (*dptr)->next;
+			if (xtables_fully_register_pending_target(ptr, prev)) {
+				prev = ptr;
+				continue;
+			} else if (prev) {
+				continue;
+			}
+			*dptr = ptr;
+		}
+		dptr = &((*dptr)->next);
+	}
+
+	for (ptr = xtables_targets; ptr; ptr = ptr->next) {
+		if (extension_cmp(name, ptr->name, ptr->family)) {
+#if 0			/* Code block below causes memory leak.  (Bugs 162925719 and 168688680) */
+			struct xtables_target *clone;
+
+			/* First target of this type: */
+			if (ptr->t == NULL)
+				break;
+
+			/* Second and subsequent clones */
+			clone = xtables_malloc(sizeof(struct xtables_target));
+			memcpy(clone, ptr, sizeof(struct xtables_target));
+			clone->udata = NULL;
+			clone->tflags = 0;
+			/* This is a clone: */
+			clone->next = clone;
+
+			ptr = clone;
+#endif
+			break;
+		}
+	}
+
+#ifndef NO_SHARED_LIBS
+	if (!ptr && tryload != XTF_DONT_LOAD && tryload != XTF_DURING_LOAD) {
+		ptr = load_extension(xtables_libdir, afinfo->libprefix,
+		      name, true);
+
+		if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				   "Couldn't load target `%s':%s\n",
+				   name, strerror(errno));
+	}
+#else
+	if (ptr && !ptr->loaded) {
+		if (tryload != XTF_DONT_LOAD)
+			ptr->loaded = 1;
+		else
+			ptr = NULL;
+	}
+	if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED) {
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			   "Couldn't find target `%s'\n", name);
+	}
+#endif
+
+	if (ptr)
+		ptr->used = 1;
+
+	return ptr;
+}
+
+struct xtables_target *
+xtables_find_target_revision(const char *name, enum xtables_tryload tryload,
+			     struct xtables_target *target, int revision)
+{
+	if (!target) {
+		target = xtables_find_target(name, tryload);
+		if (!target)
+			return NULL;
+	}
+
+	while (1) {
+		if (target->revision == revision)
+			return target;
+		target = target->next;
+		if (!target)
+			return NULL;
+		if (!extension_cmp(name, target->name, target->family))
+			return NULL;
+	}
+}
+
+int xtables_compatible_revision(const char *name, uint8_t revision, int opt)
+{
+	struct xt_get_revision rev;
+	socklen_t s = sizeof(rev);
+	int max_rev, sockfd;
+
+	sockfd = socket(afinfo->family, SOCK_RAW, IPPROTO_RAW);
+	if (sockfd < 0) {
+		if (errno == EPERM) {
+			/* revision 0 is always supported. */
+			if (revision != 0)
+				fprintf(stderr, "%s: Could not determine whether "
+						"revision %u is supported, "
+						"assuming it is.\n",
+					name, revision);
+			return 1;
+		}
+		fprintf(stderr, "Could not open socket to kernel: %s\n",
+			strerror(errno));
+		exit(1);
+	}
+
+	if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) {
+		fprintf(stderr, "Could not set close on exec: %s\n",
+			strerror(errno));
+		exit(1);
+	}
+
+	xtables_load_ko(xtables_modprobe_program, true);
+
+	strncpy(rev.name, name, XT_EXTENSION_MAXNAMELEN - 1);
+	rev.name[XT_EXTENSION_MAXNAMELEN - 1] = '\0';
+	rev.revision = revision;
+
+	max_rev = getsockopt(sockfd, afinfo->ipproto, opt, &rev, &s);
+	if (max_rev < 0) {
+		/* Definitely don't support this? */
+		if (errno == ENOENT || errno == EPROTONOSUPPORT) {
+			close(sockfd);
+			return 0;
+		} else if (errno == ENOPROTOOPT) {
+			close(sockfd);
+			/* Assume only revision 0 support (old kernel) */
+			return (revision == 0);
+		} else {
+			fprintf(stderr, "getsockopt failed strangely: %s\n",
+				strerror(errno));
+			exit(1);
+		}
+	}
+	close(sockfd);
+	return 1;
+}
+
+
+static int compatible_match_revision(const char *name, uint8_t revision)
+{
+	return xt_params->compat_rev(name, revision, afinfo->so_rev_match);
+}
+
+static int compatible_target_revision(const char *name, uint8_t revision)
+{
+	return xt_params->compat_rev(name, revision, afinfo->so_rev_target);
+}
+
+static void xtables_check_options(const char *name, const struct option *opt)
+{
+	for (; opt->name != NULL; ++opt)
+		if (opt->val < 0 || opt->val >= XT_OPTION_OFFSET_SCALE) {
+			fprintf(stderr, "%s: Extension %s uses invalid "
+			        "option value %d\n",xt_params->program_name,
+			        name, opt->val);
+			exit(1);
+		}
+}
+
+static int xtables_match_prefer(const struct xtables_match *a,
+				const struct xtables_match *b);
+
+void xtables_register_match(struct xtables_match *me)
+{
+	struct xtables_match **pos;
+	bool seen_myself = false;
+
+	if (me->next) {
+		fprintf(stderr, "%s: match \"%s\" already registered\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->version == NULL) {
+		fprintf(stderr, "%s: match %s<%u> is missing a version\n",
+		        xt_params->program_name, me->name, me->revision);
+		exit(1);
+	}
+
+	if (me->size != XT_ALIGN(me->size)) {
+		fprintf(stderr, "%s: match \"%s\" has invalid size %u.\n",
+		        xt_params->program_name, me->name,
+		        (unsigned int)me->size);
+		exit(1);
+	}
+
+	if (strcmp(me->version, XTABLES_VERSION) != 0) {
+		fprintf(stderr, "%s: match \"%s\" has version \"%s\", "
+		        "but \"%s\" is required.\n",
+			xt_params->program_name, me->name,
+			me->version, XTABLES_VERSION);
+		exit(1);
+	}
+
+	if (strlen(me->name) >= XT_EXTENSION_MAXNAMELEN) {
+		fprintf(stderr, "%s: match `%s' has invalid name\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->real_name && strlen(me->real_name) >= XT_EXTENSION_MAXNAMELEN) {
+		fprintf(stderr, "%s: match `%s' has invalid real name\n",
+			xt_params->program_name, me->real_name);
+		exit(1);
+	}
+
+	if (me->family >= NPROTO) {
+		fprintf(stderr,
+			"%s: BUG: match %s has invalid protocol family\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->x6_options != NULL)
+		xtables_option_metavalidate(me->name, me->x6_options);
+	if (me->extra_opts != NULL)
+		xtables_check_options(me->name, me->extra_opts);
+
+	/* order into linked list of matches pending full registration */
+	for (pos = &xtables_pending_matches; *pos; pos = &(*pos)->next) {
+		/* group by name and family */
+		if (strcmp(me->name, (*pos)->name) ||
+		    me->family != (*pos)->family) {
+			if (seen_myself)
+				break; /* end of own group, append to it */
+			continue;
+		}
+		/* found own group */
+		seen_myself = true;
+		if (xtables_match_prefer(me, *pos) >= 0)
+			break; /* put preferred items first in group */
+	}
+	/* if own group was not found, prepend item */
+	if (!*pos && !seen_myself)
+		pos = &xtables_pending_matches;
+
+	me->next = *pos;
+	*pos = me;
+#ifdef DEBUG
+	printf("%s: inserted match %s (family %d, revision %d):\n",
+			__func__, me->name, me->family, me->revision);
+	for (pos = &xtables_pending_matches; *pos; pos = &(*pos)->next) {
+		printf("%s:\tmatch %s (family %d, revision %d)\n", __func__,
+		       (*pos)->name, (*pos)->family, (*pos)->revision);
+	}
+#endif
+}
+
+/**
+ * Compare two actions for their preference
+ * @a:	one action
+ * @b: 	another
+ *
+ * Like strcmp, returns a negative number if @a is less preferred than @b,
+ * positive number if @a is more preferred than @b, or zero if equally
+ * preferred.
+ */
+static int
+xtables_mt_prefer(bool a_alias, unsigned int a_rev, unsigned int a_fam,
+		  bool b_alias, unsigned int b_rev, unsigned int b_fam)
+{
+	/*
+	 * Alias ranks higher than no alias.
+	 * (We want the new action to be used whenever possible.)
+	 */
+	if (!a_alias && b_alias)
+		return -1;
+	if (a_alias && !b_alias)
+		return 1;
+
+	/* Higher revision ranks higher. */
+	if (a_rev < b_rev)
+		return -1;
+	if (a_rev > b_rev)
+		return 1;
+
+	/* NFPROTO_<specific> ranks higher than NFPROTO_UNSPEC. */
+	if (a_fam == NFPROTO_UNSPEC && b_fam != NFPROTO_UNSPEC)
+		return -1;
+	if (a_fam != NFPROTO_UNSPEC && b_fam == NFPROTO_UNSPEC)
+		return 1;
+
+	/* Must be the same thing. */
+	return 0;
+}
+
+static int xtables_match_prefer(const struct xtables_match *a,
+				const struct xtables_match *b)
+{
+	return xtables_mt_prefer(a->real_name != NULL,
+				 a->revision, a->family,
+				 b->real_name != NULL,
+				 b->revision, b->family);
+}
+
+static int xtables_target_prefer(const struct xtables_target *a,
+				 const struct xtables_target *b)
+{
+	/*
+	 * Note that if x->real_name==NULL, it will be set to x->name in
+	 * xtables_register_*; the direct pointer comparison here is therefore
+	 * legitimate to detect an alias.
+	 */
+	return xtables_mt_prefer(a->real_name != NULL,
+				 a->revision, a->family,
+				 b->real_name != NULL,
+				 b->revision, b->family);
+}
+
+static bool xtables_fully_register_pending_match(struct xtables_match *me,
+						 struct xtables_match *prev)
+{
+	struct xtables_match **i;
+	const char *rn;
+
+	/* See if new match can be used. */
+	rn = (me->real_name != NULL) ? me->real_name : me->name;
+	if (!compatible_match_revision(rn, me->revision))
+		return false;
+
+	if (!prev) {
+		/* Append to list. */
+		for (i = &xtables_matches; *i; i = &(*i)->next);
+	} else {
+		/* Append it */
+		i = &prev->next;
+		prev = prev->next;
+	}
+
+	me->next = prev;
+	*i = me;
+
+	me->m = NULL;
+	me->mflags = 0;
+
+	return true;
+}
+
+void xtables_register_matches(struct xtables_match *match, unsigned int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++)
+		xtables_register_match(&match[i]);
+}
+
+void xtables_register_target(struct xtables_target *me)
+{
+	struct xtables_target **pos;
+	bool seen_myself = false;
+
+	if (me->next) {
+		fprintf(stderr, "%s: target \"%s\" already registered\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->version == NULL) {
+		fprintf(stderr, "%s: target %s<%u> is missing a version\n",
+		        xt_params->program_name, me->name, me->revision);
+		exit(1);
+	}
+
+	if (me->size != XT_ALIGN(me->size)) {
+		fprintf(stderr, "%s: target \"%s\" has invalid size %u.\n",
+		        xt_params->program_name, me->name,
+		        (unsigned int)me->size);
+		exit(1);
+	}
+
+	if (strcmp(me->version, XTABLES_VERSION) != 0) {
+		fprintf(stderr, "%s: target \"%s\" has version \"%s\", "
+		        "but \"%s\" is required.\n",
+			xt_params->program_name, me->name,
+			me->version, XTABLES_VERSION);
+		exit(1);
+	}
+
+	if (strlen(me->name) >= XT_EXTENSION_MAXNAMELEN) {
+		fprintf(stderr, "%s: target `%s' has invalid name\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->real_name && strlen(me->real_name) >= XT_EXTENSION_MAXNAMELEN) {
+		fprintf(stderr, "%s: target `%s' has invalid real name\n",
+			xt_params->program_name, me->real_name);
+		exit(1);
+	}
+
+	if (me->family >= NPROTO) {
+		fprintf(stderr,
+			"%s: BUG: target %s has invalid protocol family\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->x6_options != NULL)
+		xtables_option_metavalidate(me->name, me->x6_options);
+	if (me->extra_opts != NULL)
+		xtables_check_options(me->name, me->extra_opts);
+
+	/* ignore not interested target */
+	if (me->family != afinfo->family && me->family != AF_UNSPEC)
+		return;
+
+	/* order into linked list of targets pending full registration */
+	for (pos = &xtables_pending_targets; *pos; pos = &(*pos)->next) {
+		/* group by name */
+		if (!extension_cmp(me->name, (*pos)->name, (*pos)->family)) {
+			if (seen_myself)
+				break; /* end of own group, append to it */
+			continue;
+		}
+		/* found own group */
+		seen_myself = true;
+		if (xtables_target_prefer(me, *pos) >= 0)
+			break; /* put preferred items first in group */
+	}
+	/* if own group was not found, prepend item */
+	if (!*pos && !seen_myself)
+		pos = &xtables_pending_targets;
+
+	me->next = *pos;
+	*pos = me;
+#ifdef DEBUG
+	printf("%s: inserted target %s (family %d, revision %d):\n",
+			__func__, me->name, me->family, me->revision);
+	for (pos = &xtables_pending_targets; *pos; pos = &(*pos)->next) {
+		printf("%s:\ttarget %s (family %d, revision %d)\n", __func__,
+		       (*pos)->name, (*pos)->family, (*pos)->revision);
+	}
+#endif
+}
+
+static bool xtables_fully_register_pending_target(struct xtables_target *me,
+						  struct xtables_target *prev)
+{
+	struct xtables_target **i;
+	const char *rn;
+
+	if (strcmp(me->name, "standard") != 0) {
+		/* See if new target can be used. */
+		rn = (me->real_name != NULL) ? me->real_name : me->name;
+		if (!compatible_target_revision(rn, me->revision))
+			return false;
+	}
+
+	if (!prev) {
+		/* Prepend to list. */
+		i = &xtables_targets;
+		prev = xtables_targets;
+	} else {
+		/* Append it */
+		i = &prev->next;
+		prev = prev->next;
+	}
+
+	me->next = prev;
+	*i = me;
+
+	me->t = NULL;
+	me->tflags = 0;
+
+	return true;
+}
+
+void xtables_register_targets(struct xtables_target *target, unsigned int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++)
+		xtables_register_target(&target[i]);
+}
+
+/* receives a list of xtables_rule_match, release them */
+void xtables_rule_matches_free(struct xtables_rule_match **matches)
+{
+	struct xtables_rule_match *matchp, *tmp;
+
+	for (matchp = *matches; matchp;) {
+		tmp = matchp->next;
+		if (matchp->match->m) {
+			free(matchp->match->m);
+			matchp->match->m = NULL;
+		}
+		if (matchp->match == matchp->match->next) {
+			free(matchp->match);
+			matchp->match = NULL;
+		}
+		free(matchp);
+		matchp = tmp;
+	}
+
+	*matches = NULL;
+}
+
+/**
+ * xtables_param_act - act on condition
+ * @status:	a constant from enum xtables_exittype
+ *
+ * %XTF_ONLY_ONCE: print error message that option may only be used once.
+ * @p1:		module name (e.g. "mark")
+ * @p2(...):	option in conflict (e.g. "--mark")
+ * @p3(...):	condition to match on (see extensions/ for examples)
+ *
+ * %XTF_NO_INVERT: option does not support inversion
+ * @p1:		module name
+ * @p2:		option in conflict
+ * @p3:		condition to match on
+ *
+ * %XTF_BAD_VALUE: bad value for option
+ * @p1:		module name
+ * @p2:		option with which the problem occurred (e.g. "--mark")
+ * @p3:		string the user passed in (e.g. "99999999999999")
+ *
+ * %XTF_ONE_ACTION: two mutually exclusive actions have been specified
+ * @p1:		module name
+ *
+ * Displays an error message and exits the program.
+ */
+void xtables_param_act(unsigned int status, const char *p1, ...)
+{
+	const char *p2, *p3;
+	va_list args;
+	bool b;
+
+	va_start(args, p1);
+
+	switch (status) {
+	case XTF_ONLY_ONCE:
+		p2 = va_arg(args, const char *);
+		b  = va_arg(args, unsigned int);
+		if (!b) {
+			va_end(args);
+			return;
+		}
+		xt_params->exit_err(PARAMETER_PROBLEM,
+		           "%s: \"%s\" option may only be specified once",
+		           p1, p2);
+		break;
+	case XTF_NO_INVERT:
+		p2 = va_arg(args, const char *);
+		b  = va_arg(args, unsigned int);
+		if (!b) {
+			va_end(args);
+			return;
+		}
+		xt_params->exit_err(PARAMETER_PROBLEM,
+		           "%s: \"%s\" option cannot be inverted", p1, p2);
+		break;
+	case XTF_BAD_VALUE:
+		p2 = va_arg(args, const char *);
+		p3 = va_arg(args, const char *);
+		xt_params->exit_err(PARAMETER_PROBLEM,
+		           "%s: Bad value for \"%s\" option: \"%s\"",
+		           p1, p2, p3);
+		break;
+	case XTF_ONE_ACTION:
+		b = va_arg(args, unsigned int);
+		if (!b) {
+			va_end(args);
+			return;
+		}
+		xt_params->exit_err(PARAMETER_PROBLEM,
+		           "%s: At most one action is possible", p1);
+		break;
+	default:
+		xt_params->exit_err(status, p1, args);
+		break;
+	}
+
+	va_end(args);
+}
+
+const char *xtables_ipaddr_to_numeric(const struct in_addr *addrp)
+{
+	static char buf[20];
+	const unsigned char *bytep = (const void *)&addrp->s_addr;
+
+	sprintf(buf, "%u.%u.%u.%u", bytep[0], bytep[1], bytep[2], bytep[3]);
+	return buf;
+}
+
+static const char *ipaddr_to_host(const struct in_addr *addr)
+{
+	static char hostname[NI_MAXHOST];
+	struct sockaddr_in saddr = {
+		.sin_family = AF_INET,
+		.sin_addr = *addr,
+	};
+	int err;
+
+
+	err = getnameinfo((const void *)&saddr, sizeof(struct sockaddr_in),
+		       hostname, sizeof(hostname) - 1, NULL, 0, 0);
+	if (err != 0)
+		return NULL;
+
+	return hostname;
+}
+
+static const char *ipaddr_to_network(const struct in_addr *addr)
+{
+	struct netent *net;
+
+	if ((net = getnetbyaddr(ntohl(addr->s_addr), AF_INET)) != NULL)
+		return net->n_name;
+
+	return NULL;
+}
+
+const char *xtables_ipaddr_to_anyname(const struct in_addr *addr)
+{
+	const char *name;
+
+	if ((name = ipaddr_to_host(addr)) != NULL ||
+	    (name = ipaddr_to_network(addr)) != NULL)
+		return name;
+
+	return xtables_ipaddr_to_numeric(addr);
+}
+
+int xtables_ipmask_to_cidr(const struct in_addr *mask)
+{
+	uint32_t maskaddr, bits;
+	int i;
+
+	maskaddr = ntohl(mask->s_addr);
+	/* shortcut for /32 networks */
+	if (maskaddr == 0xFFFFFFFFL)
+		return 32;
+
+	i = 32;
+	bits = 0xFFFFFFFEL;
+	while (--i >= 0 && maskaddr != bits)
+		bits <<= 1;
+	if (i >= 0)
+		return i;
+
+	/* this mask cannot be converted to CIDR notation */
+	return -1;
+}
+
+const char *xtables_ipmask_to_numeric(const struct in_addr *mask)
+{
+	static char buf[20];
+	uint32_t cidr;
+
+	cidr = xtables_ipmask_to_cidr(mask);
+	if (cidr == (unsigned int)-1) {
+		/* mask was not a decent combination of 1's and 0's */
+		sprintf(buf, "/%s", xtables_ipaddr_to_numeric(mask));
+		return buf;
+	} else if (cidr == 32) {
+		/* we don't want to see "/32" */
+		return "";
+	}
+
+	sprintf(buf, "/%d", cidr);
+	return buf;
+}
+
+static struct in_addr *__numeric_to_ipaddr(const char *dotted, bool is_mask)
+{
+	static struct in_addr addr;
+	unsigned char *addrp;
+	unsigned int onebyte;
+	char buf[20], *p, *q;
+	int i;
+
+	/* copy dotted string, because we need to modify it */
+	strncpy(buf, dotted, sizeof(buf) - 1);
+	buf[sizeof(buf) - 1] = '\0';
+	addrp = (void *)&addr.s_addr;
+
+	p = buf;
+	for (i = 0; i < 3; ++i) {
+		if ((q = strchr(p, '.')) == NULL) {
+			if (is_mask)
+				return NULL;
+
+			/* autocomplete, this is a network address */
+			if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX))
+				return NULL;
+
+			addrp[i] = onebyte;
+			while (i < 3)
+				addrp[++i] = 0;
+
+			return &addr;
+		}
+
+		*q = '\0';
+		if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX))
+			return NULL;
+
+		addrp[i] = onebyte;
+		p = q + 1;
+	}
+
+	/* we have checked 3 bytes, now we check the last one */
+	if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX))
+		return NULL;
+
+	addrp[3] = onebyte;
+	return &addr;
+}
+
+struct in_addr *xtables_numeric_to_ipaddr(const char *dotted)
+{
+	return __numeric_to_ipaddr(dotted, false);
+}
+
+struct in_addr *xtables_numeric_to_ipmask(const char *dotted)
+{
+	return __numeric_to_ipaddr(dotted, true);
+}
+
+static struct in_addr *network_to_ipaddr(const char *name)
+{
+	static struct in_addr addr;
+	struct netent *net;
+
+	if ((net = getnetbyname(name)) != NULL) {
+		if (net->n_addrtype != AF_INET)
+			return NULL;
+		addr.s_addr = htonl(net->n_net);
+		return &addr;
+	}
+
+	return NULL;
+}
+
+static struct in_addr *host_to_ipaddr(const char *name, unsigned int *naddr)
+{
+	struct in_addr *addr;
+	struct addrinfo hints;
+	struct addrinfo *res, *p;
+	int err;
+	unsigned int i;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family   = AF_INET;
+	hints.ai_socktype = SOCK_RAW;
+
+	*naddr = 0;
+	err = getaddrinfo(name, NULL, &hints, &res);
+	if (err != 0)
+		return NULL;
+	for (p = res; p != NULL; p = p->ai_next)
+		++*naddr;
+	addr = xtables_calloc(*naddr, sizeof(struct in_addr));
+	for (i = 0, p = res; p != NULL; p = p->ai_next)
+		memcpy(&addr[i++],
+		       &((const struct sockaddr_in *)p->ai_addr)->sin_addr,
+		       sizeof(struct in_addr));
+	freeaddrinfo(res);
+	return addr;
+}
+
+static struct in_addr *
+ipparse_hostnetwork(const char *name, unsigned int *naddrs)
+{
+	struct in_addr *addrptmp, *addrp;
+
+	if ((addrptmp = xtables_numeric_to_ipaddr(name)) != NULL ||
+	    (addrptmp = network_to_ipaddr(name)) != NULL) {
+		addrp = xtables_malloc(sizeof(struct in_addr));
+		memcpy(addrp, addrptmp, sizeof(*addrp));
+		*naddrs = 1;
+		return addrp;
+	}
+	if ((addrptmp = host_to_ipaddr(name, naddrs)) != NULL)
+		return addrptmp;
+
+	xt_params->exit_err(PARAMETER_PROBLEM, "host/network `%s' not found", name);
+}
+
+static struct in_addr *parse_ipmask(const char *mask)
+{
+	static struct in_addr maskaddr;
+	struct in_addr *addrp;
+	unsigned int bits;
+
+	if (mask == NULL) {
+		/* no mask at all defaults to 32 bits */
+		maskaddr.s_addr = 0xFFFFFFFF;
+		return &maskaddr;
+	}
+	if ((addrp = xtables_numeric_to_ipmask(mask)) != NULL)
+		/* dotted_to_addr already returns a network byte order addr */
+		return addrp;
+	if (!xtables_strtoui(mask, NULL, &bits, 0, 32))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			   "invalid mask `%s' specified", mask);
+	if (bits != 0) {
+		maskaddr.s_addr = htonl(0xFFFFFFFF << (32 - bits));
+		return &maskaddr;
+	}
+
+	maskaddr.s_addr = 0U;
+	return &maskaddr;
+}
+
+void xtables_ipparse_multiple(const char *name, struct in_addr **addrpp,
+                              struct in_addr **maskpp, unsigned int *naddrs)
+{
+	struct in_addr *addrp;
+	char buf[256], *p, *next;
+	unsigned int len, i, j, n, count = 1;
+	const char *loop = name;
+
+	while ((loop = strchr(loop, ',')) != NULL) {
+		++count;
+		++loop; /* skip ',' */
+	}
+
+	*addrpp = xtables_malloc(sizeof(struct in_addr) * count);
+	*maskpp = xtables_malloc(sizeof(struct in_addr) * count);
+
+	loop = name;
+
+	for (i = 0; i < count; ++i) {
+		while (isspace(*loop))
+			++loop;
+		next = strchr(loop, ',');
+		if (next != NULL)
+			len = next - loop;
+		else
+			len = strlen(loop);
+		if (len > sizeof(buf) - 1)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"Hostname too long");
+
+		strncpy(buf, loop, len);
+		buf[len] = '\0';
+		if ((p = strrchr(buf, '/')) != NULL) {
+			*p = '\0';
+			addrp = parse_ipmask(p + 1);
+		} else {
+			addrp = parse_ipmask(NULL);
+		}
+		memcpy(*maskpp + i, addrp, sizeof(*addrp));
+
+		/* if a null mask is given, the name is ignored, like in "any/0" */
+		if ((*maskpp + i)->s_addr == 0)
+			/*
+			 * A bit pointless to process multiple addresses
+			 * in this case...
+			 */
+			strcpy(buf, "0.0.0.0");
+
+		addrp = ipparse_hostnetwork(buf, &n);
+		if (n > 1) {
+			count += n - 1;
+			*addrpp = xtables_realloc(*addrpp,
+			          sizeof(struct in_addr) * count);
+			*maskpp = xtables_realloc(*maskpp,
+			          sizeof(struct in_addr) * count);
+			for (j = 0; j < n; ++j)
+				/* for each new addr */
+				memcpy(*addrpp + i + j, addrp + j,
+				       sizeof(*addrp));
+			for (j = 1; j < n; ++j)
+				/* for each new mask */
+				memcpy(*maskpp + i + j, *maskpp + i,
+				       sizeof(*addrp));
+			i += n - 1;
+		} else {
+			memcpy(*addrpp + i, addrp, sizeof(*addrp));
+		}
+		/* free what ipparse_hostnetwork had allocated: */
+		free(addrp);
+		if (next == NULL)
+			break;
+		loop = next + 1;
+	}
+	*naddrs = count;
+	for (i = 0; i < count; ++i)
+		(*addrpp+i)->s_addr &= (*maskpp+i)->s_addr;
+}
+
+
+/**
+ * xtables_ipparse_any - transform arbitrary name to in_addr
+ *
+ * Possible inputs (pseudo regex):
+ * 	m{^($hostname|$networkname|$ipaddr)(/$mask)?}
+ * "1.2.3.4/5", "1.2.3.4", "hostname", "networkname"
+ */
+void xtables_ipparse_any(const char *name, struct in_addr **addrpp,
+                         struct in_addr *maskp, unsigned int *naddrs)
+{
+	unsigned int i, j, k, n;
+	struct in_addr *addrp;
+	char buf[256], *p;
+
+	strncpy(buf, name, sizeof(buf) - 1);
+	buf[sizeof(buf) - 1] = '\0';
+	if ((p = strrchr(buf, '/')) != NULL) {
+		*p = '\0';
+		addrp = parse_ipmask(p + 1);
+	} else {
+		addrp = parse_ipmask(NULL);
+	}
+	memcpy(maskp, addrp, sizeof(*maskp));
+
+	/* if a null mask is given, the name is ignored, like in "any/0" */
+	if (maskp->s_addr == 0U)
+		strcpy(buf, "0.0.0.0");
+
+	addrp = *addrpp = ipparse_hostnetwork(buf, naddrs);
+	n = *naddrs;
+	for (i = 0, j = 0; i < n; ++i) {
+		addrp[j++].s_addr &= maskp->s_addr;
+		for (k = 0; k < j - 1; ++k)
+			if (addrp[k].s_addr == addrp[j-1].s_addr) {
+				/*
+				 * Nuke the dup by copying an address from the
+				 * tail here, and check the current position
+				 * again (--j).
+				 */
+				memcpy(&addrp[--j], &addrp[--*naddrs],
+				       sizeof(struct in_addr));
+				break;
+			}
+	}
+}
+
+const char *xtables_ip6addr_to_numeric(const struct in6_addr *addrp)
+{
+	/* 0000:0000:0000:0000:0000:0000:000.000.000.000
+	 * 0000:0000:0000:0000:0000:0000:0000:0000 */
+	static char buf[50+1];
+	return inet_ntop(AF_INET6, addrp, buf, sizeof(buf));
+}
+
+static const char *ip6addr_to_host(const struct in6_addr *addr)
+{
+	static char hostname[NI_MAXHOST];
+	struct sockaddr_in6 saddr;
+	int err;
+
+	memset(&saddr, 0, sizeof(struct sockaddr_in6));
+	memcpy(&saddr.sin6_addr, addr, sizeof(*addr));
+	saddr.sin6_family = AF_INET6;
+
+	err = getnameinfo((const void *)&saddr, sizeof(struct sockaddr_in6),
+			hostname, sizeof(hostname) - 1, NULL, 0, 0);
+	if (err != 0)
+		return NULL;
+
+	return hostname;
+}
+
+const char *xtables_ip6addr_to_anyname(const struct in6_addr *addr)
+{
+	const char *name;
+
+	if ((name = ip6addr_to_host(addr)) != NULL)
+		return name;
+
+	return xtables_ip6addr_to_numeric(addr);
+}
+
+int xtables_ip6mask_to_cidr(const struct in6_addr *k)
+{
+	unsigned int bits = 0;
+	uint32_t a, b, c, d;
+
+	a = ntohl(k->s6_addr32[0]);
+	b = ntohl(k->s6_addr32[1]);
+	c = ntohl(k->s6_addr32[2]);
+	d = ntohl(k->s6_addr32[3]);
+	while (a & 0x80000000U) {
+		++bits;
+		a <<= 1;
+		a  |= (b >> 31) & 1;
+		b <<= 1;
+		b  |= (c >> 31) & 1;
+		c <<= 1;
+		c  |= (d >> 31) & 1;
+		d <<= 1;
+	}
+	if (a != 0 || b != 0 || c != 0 || d != 0)
+		return -1;
+	return bits;
+}
+
+const char *xtables_ip6mask_to_numeric(const struct in6_addr *addrp)
+{
+	static char buf[50+2];
+	int l = xtables_ip6mask_to_cidr(addrp);
+
+	if (l == -1) {
+		strcpy(buf, "/");
+		strcat(buf, xtables_ip6addr_to_numeric(addrp));
+		return buf;
+	}
+	/* we don't want to see "/128" */
+	if (l == 128)
+		return "";
+	else
+		sprintf(buf, "/%d", l);
+	return buf;
+}
+
+struct in6_addr *xtables_numeric_to_ip6addr(const char *num)
+{
+	static struct in6_addr ap;
+	int err;
+
+	if ((err = inet_pton(AF_INET6, num, &ap)) == 1)
+		return &ap;
+
+	return NULL;
+}
+
+static struct in6_addr *
+host_to_ip6addr(const char *name, unsigned int *naddr)
+{
+	struct in6_addr *addr;
+	struct addrinfo hints;
+	struct addrinfo *res, *p;
+	int err;
+	unsigned int i;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family   = AF_INET6;
+	hints.ai_socktype = SOCK_RAW;
+
+	*naddr = 0;
+	err = getaddrinfo(name, NULL, &hints, &res);
+	if (err != 0)
+		return NULL;
+	/* Find length of address chain */
+	for (p = res; p != NULL; p = p->ai_next)
+		++*naddr;
+	/* Copy each element of the address chain */
+	addr = xtables_calloc(*naddr, sizeof(struct in6_addr));
+	for (i = 0, p = res; p != NULL; p = p->ai_next)
+		memcpy(&addr[i++],
+		       &((const struct sockaddr_in6 *)p->ai_addr)->sin6_addr,
+		       sizeof(struct in6_addr));
+	freeaddrinfo(res);
+	return addr;
+}
+
+static struct in6_addr *network_to_ip6addr(const char *name)
+{
+	/*	abort();*/
+	/* TODO: not implemented yet, but the exception breaks the
+	 *       name resolvation */
+	return NULL;
+}
+
+static struct in6_addr *
+ip6parse_hostnetwork(const char *name, unsigned int *naddrs)
+{
+	struct in6_addr *addrp, *addrptmp;
+
+	if ((addrptmp = xtables_numeric_to_ip6addr(name)) != NULL ||
+	    (addrptmp = network_to_ip6addr(name)) != NULL) {
+		addrp = xtables_malloc(sizeof(struct in6_addr));
+		memcpy(addrp, addrptmp, sizeof(*addrp));
+		*naddrs = 1;
+		return addrp;
+	}
+	if ((addrp = host_to_ip6addr(name, naddrs)) != NULL)
+		return addrp;
+
+	xt_params->exit_err(PARAMETER_PROBLEM, "host/network `%s' not found", name);
+}
+
+static struct in6_addr *parse_ip6mask(char *mask)
+{
+	static struct in6_addr maskaddr;
+	struct in6_addr *addrp;
+	unsigned int bits;
+
+	if (mask == NULL) {
+		/* no mask at all defaults to 128 bits */
+		memset(&maskaddr, 0xff, sizeof maskaddr);
+		return &maskaddr;
+	}
+	if ((addrp = xtables_numeric_to_ip6addr(mask)) != NULL)
+		return addrp;
+	if (!xtables_strtoui(mask, NULL, &bits, 0, 128))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			   "invalid mask `%s' specified", mask);
+	if (bits != 0) {
+		char *p = (void *)&maskaddr;
+		memset(p, 0xff, bits / 8);
+		memset(p + ((bits + 7) / 8), 0, (128 - bits) / 8);
+		if (bits < 128)
+			p[bits/8] = 0xff << (8 - (bits & 7));
+		return &maskaddr;
+	}
+
+	memset(&maskaddr, 0, sizeof(maskaddr));
+	return &maskaddr;
+}
+
+void
+xtables_ip6parse_multiple(const char *name, struct in6_addr **addrpp,
+		      struct in6_addr **maskpp, unsigned int *naddrs)
+{
+	static const struct in6_addr zero_addr;
+	struct in6_addr *addrp;
+	char buf[256], *p, *next;
+	unsigned int len, i, j, n, count = 1;
+	const char *loop = name;
+
+	while ((loop = strchr(loop, ',')) != NULL) {
+		++count;
+		++loop; /* skip ',' */
+	}
+
+	*addrpp = xtables_malloc(sizeof(struct in6_addr) * count);
+	*maskpp = xtables_malloc(sizeof(struct in6_addr) * count);
+
+	loop = name;
+
+	for (i = 0; i < count /*NB: count can grow*/; ++i) {
+		while (isspace(*loop))
+			++loop;
+		next = strchr(loop, ',');
+		if (next != NULL)
+			len = next - loop;
+		else
+			len = strlen(loop);
+		if (len > sizeof(buf) - 1)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"Hostname too long");
+
+		strncpy(buf, loop, len);
+		buf[len] = '\0';
+		if ((p = strrchr(buf, '/')) != NULL) {
+			*p = '\0';
+			addrp = parse_ip6mask(p + 1);
+		} else {
+			addrp = parse_ip6mask(NULL);
+		}
+		memcpy(*maskpp + i, addrp, sizeof(*addrp));
+
+		/* if a null mask is given, the name is ignored, like in "any/0" */
+		if (memcmp(*maskpp + i, &zero_addr, sizeof(zero_addr)) == 0)
+			strcpy(buf, "::");
+
+		addrp = ip6parse_hostnetwork(buf, &n);
+		if (n > 1) {
+			count += n - 1;
+			*addrpp = xtables_realloc(*addrpp,
+			          sizeof(struct in6_addr) * count);
+			*maskpp = xtables_realloc(*maskpp,
+			          sizeof(struct in6_addr) * count);
+			for (j = 0; j < n; ++j)
+				/* for each new addr */
+				memcpy(*addrpp + i + j, addrp + j,
+				       sizeof(*addrp));
+			for (j = 1; j < n; ++j)
+				/* for each new mask */
+				memcpy(*maskpp + i + j, *maskpp + i,
+				       sizeof(*addrp));
+			i += n - 1;
+		} else {
+			memcpy(*addrpp + i, addrp, sizeof(*addrp));
+		}
+		/* free what ip6parse_hostnetwork had allocated: */
+		free(addrp);
+		if (next == NULL)
+			break;
+		loop = next + 1;
+	}
+	*naddrs = count;
+	for (i = 0; i < count; ++i)
+		for (j = 0; j < 4; ++j)
+			(*addrpp+i)->s6_addr32[j] &= (*maskpp+i)->s6_addr32[j];
+}
+
+void xtables_ip6parse_any(const char *name, struct in6_addr **addrpp,
+                          struct in6_addr *maskp, unsigned int *naddrs)
+{
+	static const struct in6_addr zero_addr;
+	struct in6_addr *addrp;
+	unsigned int i, j, k, n;
+	char buf[256], *p;
+
+	strncpy(buf, name, sizeof(buf) - 1);
+	buf[sizeof(buf)-1] = '\0';
+	if ((p = strrchr(buf, '/')) != NULL) {
+		*p = '\0';
+		addrp = parse_ip6mask(p + 1);
+	} else {
+		addrp = parse_ip6mask(NULL);
+	}
+	memcpy(maskp, addrp, sizeof(*maskp));
+
+	/* if a null mask is given, the name is ignored, like in "any/0" */
+	if (memcmp(maskp, &zero_addr, sizeof(zero_addr)) == 0)
+		strcpy(buf, "::");
+
+	addrp = *addrpp = ip6parse_hostnetwork(buf, naddrs);
+	n = *naddrs;
+	for (i = 0, j = 0; i < n; ++i) {
+		for (k = 0; k < 4; ++k)
+			addrp[j].s6_addr32[k] &= maskp->s6_addr32[k];
+		++j;
+		for (k = 0; k < j - 1; ++k)
+			if (IN6_ARE_ADDR_EQUAL(&addrp[k], &addrp[j - 1])) {
+				/*
+				 * Nuke the dup by copying an address from the
+				 * tail here, and check the current position
+				 * again (--j).
+				 */
+				memcpy(&addrp[--j], &addrp[--*naddrs],
+				       sizeof(struct in_addr));
+				break;
+			}
+	}
+}
+
+void xtables_save_string(const char *value)
+{
+	static const char no_quote_chars[] = "_-0123456789"
+		"abcdefghijklmnopqrstuvwxyz"
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+	static const char escape_chars[] = "\"\\'";
+	size_t length;
+	const char *p;
+
+	length = strspn(value, no_quote_chars);
+	if (length > 0 && value[length] == 0) {
+		/* no quoting required */
+		putchar(' ');
+		fputs(value, stdout);
+	} else {
+		/* there is at least one dangerous character in the
+		   value, which we have to quote.  Write double quotes
+		   around the value and escape special characters with
+		   a backslash */
+		printf(" \"");
+
+		for (p = strpbrk(value, escape_chars); p != NULL;
+		     p = strpbrk(value, escape_chars)) {
+			if (p > value)
+				fwrite(value, 1, p - value, stdout);
+			putchar('\\');
+			putchar(*p);
+			value = p + 1;
+		}
+
+		/* print the rest and finish the double quoted
+		   string */
+		fputs(value, stdout);
+		putchar('\"');
+	}
+}
+
+const struct xtables_pprot xtables_chain_protos[] = {
+	{"tcp",       IPPROTO_TCP},
+	{"sctp",      IPPROTO_SCTP},
+	{"udp",       IPPROTO_UDP},
+	{"udplite",   IPPROTO_UDPLITE},
+	{"icmp",      IPPROTO_ICMP},
+	{"icmpv6",    IPPROTO_ICMPV6},
+	{"ipv6-icmp", IPPROTO_ICMPV6},
+	{"esp",       IPPROTO_ESP},
+	{"ah",        IPPROTO_AH},
+	{"ipv6-mh",   IPPROTO_MH},
+	{"mh",        IPPROTO_MH},
+	{"all",       0},
+	{NULL},
+};
+
+uint16_t
+xtables_parse_protocol(const char *s)
+{
+	const struct protoent *pent;
+	unsigned int proto, i;
+
+	if (xtables_strtoui(s, NULL, &proto, 0, UINT8_MAX))
+		return proto;
+
+	/* first deal with the special case of 'all' to prevent
+	 * people from being able to redefine 'all' in nsswitch
+	 * and/or provoke expensive [not working] ldap/nis/...
+	 * lookups */
+	if (strcmp(s, "all") == 0)
+		return 0;
+
+	pent = getprotobyname(s);
+	if (pent != NULL)
+		return pent->p_proto;
+
+	for (i = 0; i < ARRAY_SIZE(xtables_chain_protos); ++i) {
+		if (xtables_chain_protos[i].name == NULL)
+			continue;
+		if (strcmp(s, xtables_chain_protos[i].name) == 0)
+			return xtables_chain_protos[i].num;
+	}
+	xt_params->exit_err(PARAMETER_PROBLEM,
+		"unknown protocol \"%s\" specified", s);
+	return -1;
+}
+
+void xtables_print_num(uint64_t number, unsigned int format)
+{
+	if (!(format & FMT_KILOMEGAGIGA)) {
+		printf(FMT("%8llu ","%llu "), (unsigned long long)number);
+		return;
+	}
+	if (number <= 99999) {
+		printf(FMT("%5llu ","%llu "), (unsigned long long)number);
+		return;
+	}
+	number = (number + 500) / 1000;
+	if (number <= 9999) {
+		printf(FMT("%4lluK ","%lluK "), (unsigned long long)number);
+		return;
+	}
+	number = (number + 500) / 1000;
+	if (number <= 9999) {
+		printf(FMT("%4lluM ","%lluM "), (unsigned long long)number);
+		return;
+	}
+	number = (number + 500) / 1000;
+	if (number <= 9999) {
+		printf(FMT("%4lluG ","%lluG "), (unsigned long long)number);
+		return;
+	}
+	number = (number + 500) / 1000;
+	printf(FMT("%4lluT ","%lluT "), (unsigned long long)number);
+}
+
+#include <netinet/ether.h>
+
+static const unsigned char mac_type_unicast[ETH_ALEN] =   {};
+static const unsigned char msk_type_unicast[ETH_ALEN] =   {1};
+static const unsigned char mac_type_multicast[ETH_ALEN] = {1};
+static const unsigned char msk_type_multicast[ETH_ALEN] = {1};
+#define ALL_ONE_MAC {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+static const unsigned char mac_type_broadcast[ETH_ALEN] = ALL_ONE_MAC;
+static const unsigned char msk_type_broadcast[ETH_ALEN] = ALL_ONE_MAC;
+static const unsigned char mac_type_bridge_group[ETH_ALEN] = {0x01, 0x80, 0xc2};
+static const unsigned char msk_type_bridge_group[ETH_ALEN] = ALL_ONE_MAC;
+#undef ALL_ONE_MAC
+
+int xtables_parse_mac_and_mask(const char *from, void *to, void *mask)
+{
+	char *p;
+	int i;
+	struct ether_addr *addr = NULL;
+
+	if (strcasecmp(from, "Unicast") == 0) {
+		memcpy(to, mac_type_unicast, ETH_ALEN);
+		memcpy(mask, msk_type_unicast, ETH_ALEN);
+		return 0;
+	}
+	if (strcasecmp(from, "Multicast") == 0) {
+		memcpy(to, mac_type_multicast, ETH_ALEN);
+		memcpy(mask, msk_type_multicast, ETH_ALEN);
+		return 0;
+	}
+	if (strcasecmp(from, "Broadcast") == 0) {
+		memcpy(to, mac_type_broadcast, ETH_ALEN);
+		memcpy(mask, msk_type_broadcast, ETH_ALEN);
+		return 0;
+	}
+	if (strcasecmp(from, "BGA") == 0) {
+		memcpy(to, mac_type_bridge_group, ETH_ALEN);
+		memcpy(mask, msk_type_bridge_group, ETH_ALEN);
+		return 0;
+	}
+	if ( (p = strrchr(from, '/')) != NULL) {
+		*p = '\0';
+		if (!(addr = ether_aton(p + 1)))
+			return -1;
+		memcpy(mask, addr, ETH_ALEN);
+	} else
+		memset(mask, 0xff, ETH_ALEN);
+	if (!(addr = ether_aton(from)))
+		return -1;
+	memcpy(to, addr, ETH_ALEN);
+	for (i = 0; i < ETH_ALEN; i++)
+		((char *)to)[i] &= ((char *)mask)[i];
+	return 0;
+}
+
+int xtables_print_well_known_mac_and_mask(const void *mac, const void *mask)
+{
+	if (!memcmp(mac, mac_type_unicast, ETH_ALEN) &&
+	    !memcmp(mask, msk_type_unicast, ETH_ALEN))
+		printf("Unicast");
+	else if (!memcmp(mac, mac_type_multicast, ETH_ALEN) &&
+	         !memcmp(mask, msk_type_multicast, ETH_ALEN))
+		printf("Multicast");
+	else if (!memcmp(mac, mac_type_broadcast, ETH_ALEN) &&
+	         !memcmp(mask, msk_type_broadcast, ETH_ALEN))
+		printf("Broadcast");
+	else if (!memcmp(mac, mac_type_bridge_group, ETH_ALEN) &&
+	         !memcmp(mask, msk_type_bridge_group, ETH_ALEN))
+		printf("BGA");
+	else
+		return -1;
+	return 0;
+}
+
+void xtables_print_mac(const unsigned char *macaddress)
+{
+	unsigned int i;
+
+	printf("%02x", macaddress[0]);
+	for (i = 1; i < 6; ++i)
+		printf(":%02x", macaddress[i]);
+}
+
+void xtables_print_mac_and_mask(const unsigned char *mac, const unsigned char *mask)
+{
+	static const char hlpmsk[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+	xtables_print_mac(mac);
+
+	if (memcmp(mask, hlpmsk, 6) == 0)
+		return;
+
+	printf("/");
+	xtables_print_mac(mask);
+}
+
+void xtables_parse_val_mask(struct xt_option_call *cb,
+			    unsigned int *val, unsigned int *mask,
+			    const struct xtables_lmap *lmap)
+{
+	char *end;
+
+	*mask = ~0U;
+
+	if (!xtables_strtoui(cb->arg, &end, val, 0, UINT32_MAX)) {
+		if (lmap)
+			goto name2val;
+		else
+			goto bad_val;
+	}
+
+	if (*end == '\0')
+		return;
+
+	if (*end != '/') {
+		if (lmap)
+			goto name2val;
+		else
+			goto garbage;
+	}
+
+	if (!xtables_strtoui(end + 1, &end, mask, 0, UINT32_MAX))
+		goto bad_val;
+
+	if (*end == '\0')
+		return;
+
+garbage:
+	xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: trailing garbage after value "
+			"for option \"--%s\".\n",
+			cb->ext_name, cb->entry->name);
+
+bad_val:
+	xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: bad integer value for option \"--%s\", "
+			"or out of range.\n",
+			cb->ext_name, cb->entry->name);
+
+name2val:
+	*val = xtables_lmap_name2id(lmap, cb->arg);
+	if ((int)*val == -1)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: could not map name %s to an integer value "
+			"for option \"--%s\".\n",
+			cb->ext_name, cb->arg, cb->entry->name);
+}
+
+void xtables_print_val_mask(unsigned int val, unsigned int mask,
+			    const struct xtables_lmap *lmap)
+{
+	if (mask != ~0U) {
+		printf(" 0x%x/0x%x", val, mask);
+		return;
+	}
+
+	if (lmap) {
+		const char *name = xtables_lmap_id2name(lmap, val);
+
+		if (name) {
+			printf(" %s", name);
+			return;
+		}
+	}
+
+	printf(" 0x%x", val);
+}
+
+int kernel_version;
+
+void get_kernel_version(void)
+{
+	static struct utsname uts;
+	int x = 0, y = 0, z = 0;
+
+	if (uname(&uts) == -1) {
+		fprintf(stderr, "Unable to retrieve kernel version.\n");
+		xtables_free_opts(1);
+		exit(1);
+	}
+
+	sscanf(uts.release, "%d.%d.%d", &x, &y, &z);
+	kernel_version = LINUX_VERSION(x, y, z);
+}
+
+#include <linux/netfilter/nf_tables.h>
+
+struct xt_xlate {
+	struct {
+		char	*data;
+		int	size;
+		int	rem;
+		int	off;
+	} buf;
+	char comment[NFT_USERDATA_MAXLEN];
+};
+
+struct xt_xlate *xt_xlate_alloc(int size)
+{
+	struct xt_xlate *xl;
+
+	xl = malloc(sizeof(struct xt_xlate));
+	if (xl == NULL)
+		xtables_error(RESOURCE_PROBLEM, "OOM");
+
+	xl->buf.data = malloc(size);
+	if (xl->buf.data == NULL)
+		xtables_error(RESOURCE_PROBLEM, "OOM");
+
+	xl->buf.data[0] = '\0';
+	xl->buf.size = size;
+	xl->buf.rem = size;
+	xl->buf.off = 0;
+	xl->comment[0] = '\0';
+
+	return xl;
+}
+
+void xt_xlate_free(struct xt_xlate *xl)
+{
+	free(xl->buf.data);
+	free(xl);
+}
+
+void xt_xlate_add(struct xt_xlate *xl, const char *fmt, ...)
+{
+	va_list ap;
+	int len;
+
+	va_start(ap, fmt);
+	len = vsnprintf(xl->buf.data + xl->buf.off, xl->buf.rem, fmt, ap);
+	if (len < 0 || len >= xl->buf.rem)
+		xtables_error(RESOURCE_PROBLEM, "OOM");
+
+	va_end(ap);
+	xl->buf.rem -= len;
+	xl->buf.off += len;
+}
+
+void xt_xlate_add_comment(struct xt_xlate *xl, const char *comment)
+{
+	strncpy(xl->comment, comment, NFT_USERDATA_MAXLEN - 1);
+	xl->comment[NFT_USERDATA_MAXLEN - 1] = '\0';
+}
+
+const char *xt_xlate_get_comment(struct xt_xlate *xl)
+{
+	return xl->comment[0] ? xl->comment : NULL;
+}
+
+const char *xt_xlate_get(struct xt_xlate *xl)
+{
+	return xl->buf.data;
+}
diff --git a/libxtables/xtoptions.c b/libxtables/xtoptions.c
new file mode 100644
index 0000000..d329f2f
--- /dev/null
+++ b/libxtables/xtoptions.c
@@ -0,0 +1,1188 @@
+/*
+ *	Argument parser
+ *	Copyright © Jan Engelhardt, 2011
+ *
+ *	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 2 of
+ *	the License, or (at your option) any later version.
+ */
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include "xtables.h"
+#include "xshared.h"
+#ifndef IPTOS_NORMALSVC
+#	define IPTOS_NORMALSVC 0
+#endif
+
+#define XTOPT_MKPTR(cb) \
+	((void *)((char *)(cb)->data + (cb)->entry->ptroff))
+
+/**
+ * Simple key-value pairs for syslog levels
+ */
+struct syslog_level {
+	char name[8];
+	uint8_t level;
+};
+
+struct tos_value_mask {
+	uint8_t value, mask;
+};
+
+static const size_t xtopt_psize[] = {
+	/*
+	 * All types not listed here, and thus essentially being initialized to
+	 * zero have zero on purpose.
+	 */
+	[XTTYPE_UINT8]       = sizeof(uint8_t),
+	[XTTYPE_UINT16]      = sizeof(uint16_t),
+	[XTTYPE_UINT32]      = sizeof(uint32_t),
+	[XTTYPE_UINT64]      = sizeof(uint64_t),
+	[XTTYPE_UINT8RC]     = sizeof(uint8_t[2]),
+	[XTTYPE_UINT16RC]    = sizeof(uint16_t[2]),
+	[XTTYPE_UINT32RC]    = sizeof(uint32_t[2]),
+	[XTTYPE_UINT64RC]    = sizeof(uint64_t[2]),
+	[XTTYPE_DOUBLE]      = sizeof(double),
+	[XTTYPE_STRING]      = -1,
+	[XTTYPE_SYSLOGLEVEL] = sizeof(uint8_t),
+	[XTTYPE_HOST]        = sizeof(union nf_inet_addr),
+	[XTTYPE_HOSTMASK]    = sizeof(union nf_inet_addr),
+	[XTTYPE_PROTOCOL]    = sizeof(uint8_t),
+	[XTTYPE_PORT]        = sizeof(uint16_t),
+	[XTTYPE_PORTRC]      = sizeof(uint16_t[2]),
+	[XTTYPE_PLENMASK]    = sizeof(union nf_inet_addr),
+	[XTTYPE_ETHERMAC]    = sizeof(uint8_t[6]),
+};
+
+/**
+ * Creates getopt options from the x6-style option map, and assigns each a
+ * getopt id.
+ */
+struct option *
+xtables_options_xfrm(struct option *orig_opts, struct option *oldopts,
+		     const struct xt_option_entry *entry, unsigned int *offset)
+{
+	unsigned int num_orig, num_old = 0, num_new, i;
+	struct option *merge, *mp;
+
+	if (entry == NULL)
+		return oldopts;
+	for (num_orig = 0; orig_opts[num_orig].name != NULL; ++num_orig)
+		;
+	if (oldopts != NULL)
+		for (num_old = 0; oldopts[num_old].name != NULL; ++num_old)
+			;
+	for (num_new = 0; entry[num_new].name != NULL; ++num_new)
+		;
+
+	/*
+	 * Since @oldopts also has @orig_opts already (and does so at the
+	 * start), skip these entries.
+	 */
+	if (oldopts != NULL) {
+		oldopts += num_orig;
+		num_old -= num_orig;
+	}
+
+	merge = malloc(sizeof(*mp) * (num_orig + num_old + num_new + 1));
+	if (merge == NULL)
+		return NULL;
+
+	/* Let the base options -[ADI...] have precedence over everything */
+	memcpy(merge, orig_opts, sizeof(*mp) * num_orig);
+	mp = merge + num_orig;
+
+	/* Second, the new options */
+	xt_params->option_offset += XT_OPTION_OFFSET_SCALE;
+	*offset = xt_params->option_offset;
+
+	for (i = 0; i < num_new; ++i, ++mp, ++entry) {
+		mp->name         = entry->name;
+		mp->has_arg      = entry->type != XTTYPE_NONE;
+		mp->flag         = NULL;
+		mp->val          = entry->id + *offset;
+	}
+
+	/* Third, the old options */
+	if (oldopts != NULL) {
+		memcpy(mp, oldopts, sizeof(*mp) * num_old);
+		mp += num_old;
+	}
+	xtables_free_opts(0);
+
+	/* Clear trailing entry */
+	memset(mp, 0, sizeof(*mp));
+	return merge;
+}
+
+/**
+ * Give the upper limit for a certain type.
+ */
+static uintmax_t xtopt_max_by_type(enum xt_option_type type)
+{
+	switch (type) {
+	case XTTYPE_UINT8:
+	case XTTYPE_UINT8RC:
+		return UINT8_MAX;
+	case XTTYPE_UINT16:
+	case XTTYPE_UINT16RC:
+		return UINT16_MAX;
+	case XTTYPE_UINT32:
+	case XTTYPE_UINT32RC:
+		return UINT32_MAX;
+	case XTTYPE_UINT64:
+	case XTTYPE_UINT64RC:
+		return UINT64_MAX;
+	default:
+		return 0;
+	}
+}
+
+/**
+ * Return the size of a single entity based upon a type - predominantly an
+ * XTTYPE_UINT*RC type.
+ */
+static size_t xtopt_esize_by_type(enum xt_option_type type)
+{
+	switch (type) {
+	case XTTYPE_UINT8RC:
+		return xtopt_psize[XTTYPE_UINT8];
+	case XTTYPE_UINT16RC:
+		return xtopt_psize[XTTYPE_UINT16];
+	case XTTYPE_UINT32RC:
+		return xtopt_psize[XTTYPE_UINT32];
+	case XTTYPE_UINT64RC:
+		return xtopt_psize[XTTYPE_UINT64];
+	default:
+		return xtopt_psize[type];
+	}
+}
+
+/**
+ * Require a simple integer.
+ */
+static void xtopt_parse_int(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	uintmax_t lmin = 0, lmax = xtopt_max_by_type(entry->type);
+	uintmax_t value;
+
+	if (cb->entry->min != 0)
+		lmin = cb->entry->min;
+	if (cb->entry->max != 0)
+		lmax = cb->entry->max;
+
+	if (!xtables_strtoul(cb->arg, NULL, &value, lmin, lmax))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: bad value for option \"--%s\", "
+			"or out of range (%ju-%ju).\n",
+			cb->ext_name, entry->name, lmin, lmax);
+
+	if (entry->type == XTTYPE_UINT8) {
+		cb->val.u8 = value;
+		if (entry->flags & XTOPT_PUT)
+			*(uint8_t *)XTOPT_MKPTR(cb) = cb->val.u8;
+	} else if (entry->type == XTTYPE_UINT16) {
+		cb->val.u16 = value;
+		if (entry->flags & XTOPT_PUT)
+			*(uint16_t *)XTOPT_MKPTR(cb) = cb->val.u16;
+	} else if (entry->type == XTTYPE_UINT32) {
+		cb->val.u32 = value;
+		if (entry->flags & XTOPT_PUT)
+			*(uint32_t *)XTOPT_MKPTR(cb) = cb->val.u32;
+	} else if (entry->type == XTTYPE_UINT64) {
+		cb->val.u64 = value;
+		if (entry->flags & XTOPT_PUT)
+			*(uint64_t *)XTOPT_MKPTR(cb) = cb->val.u64;
+	}
+}
+
+/**
+ * Require a simple floating point number.
+ */
+static void xtopt_parse_float(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	double value;
+	char *end;
+
+	value = strtod(cb->arg, &end);
+	if (end == cb->arg || *end != '\0' ||
+	    (entry->min != entry->max &&
+	    (value < entry->min || value > entry->max)))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: bad value for option \"--%s\", "
+			"or out of range (%u-%u).\n",
+			cb->ext_name, entry->name, entry->min, entry->max);
+
+	cb->val.dbl = value;
+	if (entry->flags & XTOPT_PUT)
+		*(double *)XTOPT_MKPTR(cb) = cb->val.dbl;
+}
+
+/**
+ * Copy the parsed value to the appropriate entry in cb->val.
+ */
+static void xtopt_mint_value_to_cb(struct xt_option_call *cb, uintmax_t value)
+{
+	const struct xt_option_entry *entry = cb->entry;
+
+	if (cb->nvals >= ARRAY_SIZE(cb->val.u32_range))
+		return;
+	if (entry->type == XTTYPE_UINT8RC)
+		cb->val.u8_range[cb->nvals] = value;
+	else if (entry->type == XTTYPE_UINT16RC)
+		cb->val.u16_range[cb->nvals] = value;
+	else if (entry->type == XTTYPE_UINT32RC)
+		cb->val.u32_range[cb->nvals] = value;
+	else if (entry->type == XTTYPE_UINT64RC)
+		cb->val.u64_range[cb->nvals] = value;
+}
+
+/**
+ * Copy the parsed value to the data area, using appropriate type access.
+ */
+static void xtopt_mint_value_to_ptr(struct xt_option_call *cb, void **datap,
+				    uintmax_t value)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	void *data = *datap;
+
+	if (!(entry->flags & XTOPT_PUT))
+		return;
+	if (entry->type == XTTYPE_UINT8RC)
+		*(uint8_t *)data = value;
+	else if (entry->type == XTTYPE_UINT16RC)
+		*(uint16_t *)data = value;
+	else if (entry->type == XTTYPE_UINT32RC)
+		*(uint32_t *)data = value;
+	else if (entry->type == XTTYPE_UINT64RC)
+		*(uint64_t *)data = value;
+	data += xtopt_esize_by_type(entry->type);
+	*datap = data;
+}
+
+/**
+ * Multiple integer parse routine.
+ *
+ * This function is capable of parsing any number of fields. Only the first
+ * two values from the string will be put into @cb however (and as such,
+ * @cb->val.uXX_range is just that large) to cater for the few extensions that
+ * do not have a range[2] field, but {min, max}, and which cannot use
+ * XTOPT_POINTER.
+ */
+static void xtopt_parse_mint(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	const char *arg;
+	size_t esize = xtopt_esize_by_type(entry->type);
+	const uintmax_t lmax = xtopt_max_by_type(entry->type);
+	void *put = XTOPT_MKPTR(cb);
+	unsigned int maxiter;
+	uintmax_t value;
+	char *end = "";
+	char sep = ':';
+
+	maxiter = entry->size / esize;
+	if (maxiter == 0)
+		maxiter = ARRAY_SIZE(cb->val.u32_range);
+	if (entry->size % esize != 0)
+		xt_params->exit_err(OTHER_PROBLEM, "%s: memory block does "
+			"not have proper size\n", __func__);
+
+	cb->nvals = 0;
+	for (arg = cb->arg, end = (char *)arg; ; arg = end + 1) {
+		if (cb->nvals == maxiter)
+			xt_params->exit_err(PARAMETER_PROBLEM, "%s: Too many "
+				"components for option \"--%s\" (max: %u)\n",
+				cb->ext_name, entry->name, maxiter);
+		if (*arg == '\0' || *arg == sep) {
+			/* Default range components when field not spec'd. */
+			end = (char *)arg;
+			value = (cb->nvals == 1) ? lmax : 0;
+		} else {
+			if (!xtables_strtoul(arg, &end, &value, 0, lmax))
+				xt_params->exit_err(PARAMETER_PROBLEM,
+					"%s: bad value for option \"--%s\" near "
+					"\"%s\", or out of range (0-%ju).\n",
+					cb->ext_name, entry->name, arg, lmax);
+			if (*end != '\0' && *end != sep)
+				xt_params->exit_err(PARAMETER_PROBLEM,
+					"%s: Argument to \"--%s\" has "
+					"unexpected characters near \"%s\".\n",
+					cb->ext_name, entry->name, end);
+		}
+		xtopt_mint_value_to_cb(cb, value);
+		++cb->nvals;
+		xtopt_mint_value_to_ptr(cb, &put, value);
+		if (*end == '\0')
+			break;
+	}
+}
+
+static void xtopt_parse_string(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	size_t z = strlen(cb->arg);
+	char *p;
+
+	if (entry->min != 0 && z < entry->min)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"Argument must have a minimum length of "
+			"%u characters\n", entry->min);
+	if (entry->max != 0 && z > entry->max)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"Argument must have a maximum length of "
+			"%u characters\n", entry->max);
+	if (!(entry->flags & XTOPT_PUT))
+		return;
+	if (z >= entry->size)
+		z = entry->size - 1;
+	p = XTOPT_MKPTR(cb);
+	strncpy(p, cb->arg, z);
+	p[z] = '\0';
+}
+
+static const struct tos_symbol_info {
+	unsigned char value;
+	const char *name;
+} tos_symbol_names[] = {
+	{IPTOS_LOWDELAY,    "Minimize-Delay"},
+	{IPTOS_THROUGHPUT,  "Maximize-Throughput"},
+	{IPTOS_RELIABILITY, "Maximize-Reliability"},
+	{IPTOS_MINCOST,     "Minimize-Cost"},
+	{IPTOS_NORMALSVC,   "Normal-Service"},
+	{},
+};
+
+/*
+ * tos_parse_numeric - parse a string like "15/255"
+ *
+ * @str:	input string
+ * @tvm:	(value/mask) tuple
+ * @max:	maximum allowed value (must be pow(2,some_int)-1)
+ */
+static bool tos_parse_numeric(const char *str, struct xt_option_call *cb,
+                              unsigned int max)
+{
+	unsigned int value;
+	char *end;
+
+	xtables_strtoui(str, &end, &value, 0, max);
+	cb->val.tos_value = value;
+	cb->val.tos_mask  = max;
+
+	if (*end == '/') {
+		const char *p = end + 1;
+
+		if (!xtables_strtoui(p, &end, &value, 0, max))
+			xtables_error(PARAMETER_PROBLEM, "Illegal value: \"%s\"",
+			           str);
+		cb->val.tos_mask = value;
+	}
+
+	if (*end != '\0')
+		xtables_error(PARAMETER_PROBLEM, "Illegal value: \"%s\"", str);
+	return true;
+}
+
+/**
+ * @str:	input string
+ * @tvm:	(value/mask) tuple
+ * @def_mask:	mask to force when a symbolic name is used
+ */
+static void xtopt_parse_tosmask(struct xt_option_call *cb)
+{
+	const struct tos_symbol_info *symbol;
+	char *tmp;
+
+	if (xtables_strtoui(cb->arg, &tmp, NULL, 0, UINT8_MAX)) {
+		tos_parse_numeric(cb->arg, cb, UINT8_MAX);
+		return;
+	}
+	/*
+	 * This is our way we deal with different defaults
+	 * for different revisions.
+	 */
+	cb->val.tos_mask = cb->entry->max;
+	for (symbol = tos_symbol_names; symbol->name != NULL; ++symbol)
+		if (strcasecmp(cb->arg, symbol->name) == 0) {
+			cb->val.tos_value = symbol->value;
+			return;
+		}
+
+	xtables_error(PARAMETER_PROBLEM, "Symbolic name \"%s\" is unknown",
+		      cb->arg);
+}
+
+/**
+ * Validate the input for being conformant to "mark[/mask]".
+ */
+static void xtopt_parse_markmask(struct xt_option_call *cb)
+{
+	xtables_parse_mark_mask(cb, &cb->val.mark, &cb->val.mask);
+}
+
+static int xtopt_sysloglvl_compare(const void *a, const void *b)
+{
+	const char *name = a;
+	const struct syslog_level *entry = b;
+
+	return strcmp(name, entry->name);
+}
+
+static void xtopt_parse_sysloglevel(struct xt_option_call *cb)
+{
+	static const struct syslog_level log_names[] = { /* must be sorted */
+		{"alert",   LOG_ALERT},
+		{"crit",    LOG_CRIT},
+		{"debug",   LOG_DEBUG},
+		{"emerg",   LOG_EMERG},
+		{"error",   LOG_ERR}, /* deprecated */
+		{"info",    LOG_INFO},
+		{"notice",  LOG_NOTICE},
+		{"panic",   LOG_EMERG}, /* deprecated */
+		{"warning", LOG_WARNING},
+	};
+	const struct syslog_level *e;
+	unsigned int num = 0;
+
+	if (!xtables_strtoui(cb->arg, NULL, &num, 0, 7)) {
+		e = bsearch(cb->arg, log_names, ARRAY_SIZE(log_names),
+			    sizeof(*log_names), xtopt_sysloglvl_compare);
+		if (e == NULL)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"log level \"%s\" unknown\n", cb->arg);
+		num = e->level;
+	}
+	cb->val.syslog_level = num;
+	if (cb->entry->flags & XTOPT_PUT)
+		*(uint8_t *)XTOPT_MKPTR(cb) = num;
+}
+
+static void *xtables_sa_host(const void *sa, unsigned int afproto)
+{
+	if (afproto == AF_INET6)
+		return &((struct sockaddr_in6 *)sa)->sin6_addr;
+	else if (afproto == AF_INET)
+		return &((struct sockaddr_in *)sa)->sin_addr;
+	return (void *)sa;
+}
+
+static socklen_t xtables_sa_hostlen(unsigned int afproto)
+{
+	if (afproto == AF_INET6)
+		return sizeof(struct in6_addr);
+	else if (afproto == AF_INET)
+		return sizeof(struct in_addr);
+	return 0;
+}
+
+/**
+ * Accepts: a hostname (DNS), or a single inetaddr - without any mask. The
+ * result is stored in @cb->val.haddr. Additionally, @cb->val.hmask and
+ * @cb->val.hlen are set for completeness to the appropriate values.
+ */
+static void xtopt_parse_host(struct xt_option_call *cb)
+{
+	struct addrinfo hints = {.ai_family = afinfo->family};
+	unsigned int adcount = 0;
+	struct addrinfo *res, *p;
+	int ret;
+
+	ret = getaddrinfo(cb->arg, NULL, &hints, &res);
+	if (ret != 0)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"getaddrinfo: %s\n", gai_strerror(ret));
+
+	memset(&cb->val.hmask, 0xFF, sizeof(cb->val.hmask));
+	cb->val.hlen = (afinfo->family == NFPROTO_IPV4) ? 32 : 128;
+
+	for (p = res; p != NULL; p = p->ai_next) {
+		if (adcount == 0) {
+			memset(&cb->val.haddr, 0, sizeof(cb->val.haddr));
+			memcpy(&cb->val.haddr,
+			       xtables_sa_host(p->ai_addr, p->ai_family),
+			       xtables_sa_hostlen(p->ai_family));
+			++adcount;
+			continue;
+		}
+		if (memcmp(&cb->val.haddr,
+		    xtables_sa_host(p->ai_addr, p->ai_family),
+		    xtables_sa_hostlen(p->ai_family)) != 0)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"%s resolves to more than one address\n",
+				cb->arg);
+	}
+
+	freeaddrinfo(res);
+	if (cb->entry->flags & XTOPT_PUT)
+		/* Validation in xtables_option_metavalidate */
+		memcpy(XTOPT_MKPTR(cb), &cb->val.haddr,
+		       sizeof(cb->val.haddr));
+}
+
+/**
+ * @name:	port name, or number as a string (e.g. "http" or "80")
+ *
+ * Resolve a port name to a number. Returns the port number in integral
+ * form on success, or <0 on error. (errno will not be set.)
+ */
+static int xtables_getportbyname(const char *name)
+{
+	struct addrinfo *res = NULL, *p;
+	int ret;
+
+	ret = getaddrinfo(NULL, name, NULL, &res);
+	if (ret != 0)
+		return -1;
+	ret = -1;
+	for (p = res; p != NULL; p = p->ai_next) {
+		if (p->ai_family == AF_INET6) {
+			ret = ((struct sockaddr_in6 *)p->ai_addr)->sin6_port;
+			break;
+		} else if (p->ai_family == AF_INET) {
+			ret = ((struct sockaddr_in *)p->ai_addr)->sin_port;
+			break;
+		}
+	}
+	freeaddrinfo(res);
+	if (ret < 0)
+		return ret;
+	return ntohs(ret);
+}
+
+/**
+ * Validate and parse a protocol specification (number or name) by use of
+ * /etc/protocols and put the result into @cb->val.protocol.
+ */
+static void xtopt_parse_protocol(struct xt_option_call *cb)
+{
+	cb->val.protocol = xtables_parse_protocol(cb->arg);
+	if (cb->entry->flags & XTOPT_PUT)
+		*(uint8_t *)XTOPT_MKPTR(cb) = cb->val.protocol;
+}
+
+/**
+ * Validate and parse a port specification and put the result into
+ * @cb->val.port.
+ */
+static void xtopt_parse_port(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	int ret;
+
+	ret = xtables_getportbyname(cb->arg);
+	if (ret < 0)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"Port \"%s\" does not resolve to anything.\n",
+			cb->arg);
+	if (entry->flags & XTOPT_NBO)
+		ret = htons(ret);
+	cb->val.port = ret;
+	if (entry->flags & XTOPT_PUT)
+		*(uint16_t *)XTOPT_MKPTR(cb) = cb->val.port;
+}
+
+static void xtopt_parse_mport(struct xt_option_call *cb)
+{
+	static const size_t esize = sizeof(uint16_t);
+	const struct xt_option_entry *entry = cb->entry;
+	char *lo_arg, *wp_arg, *arg;
+	unsigned int maxiter;
+	int value;
+
+	wp_arg = lo_arg = strdup(cb->arg);
+	if (lo_arg == NULL)
+		xt_params->exit_err(RESOURCE_PROBLEM, "strdup");
+
+	maxiter = entry->size / esize;
+	if (maxiter == 0)
+		maxiter = 2; /* ARRAY_SIZE(cb->val.port_range) */
+	if (entry->size % esize != 0)
+		xt_params->exit_err(OTHER_PROBLEM, "%s: memory block does "
+			"not have proper size\n", __func__);
+
+	cb->val.port_range[0] = 0;
+	cb->val.port_range[1] = UINT16_MAX;
+	cb->nvals = 0;
+
+	while ((arg = strsep(&wp_arg, ":")) != NULL) {
+		if (cb->nvals == maxiter)
+			xt_params->exit_err(PARAMETER_PROBLEM, "%s: Too many "
+				"components for option \"--%s\" (max: %u)\n",
+				cb->ext_name, entry->name, maxiter);
+		if (*arg == '\0') {
+			++cb->nvals;
+			continue;
+		}
+
+		value = xtables_getportbyname(arg);
+		if (value < 0)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"Port \"%s\" does not resolve to "
+				"anything.\n", arg);
+		if (entry->flags & XTOPT_NBO)
+			value = htons(value);
+		if (cb->nvals < ARRAY_SIZE(cb->val.port_range))
+			cb->val.port_range[cb->nvals] = value;
+		++cb->nvals;
+	}
+
+	if (cb->nvals == 1) {
+		cb->val.port_range[1] = cb->val.port_range[0];
+		++cb->nvals;
+	}
+	if (entry->flags & XTOPT_PUT)
+		memcpy(XTOPT_MKPTR(cb), cb->val.port_range, sizeof(uint16_t) *
+		       (cb->nvals <= maxiter ? cb->nvals : maxiter));
+	free(lo_arg);
+}
+
+static int xtopt_parse_mask(struct xt_option_call *cb)
+{
+	struct addrinfo hints = {.ai_family = afinfo->family,
+				 .ai_flags = AI_NUMERICHOST };
+	struct addrinfo *res;
+	int ret;
+
+	ret = getaddrinfo(cb->arg, NULL, &hints, &res);
+	if (ret != 0)
+		return 0;
+
+	memcpy(&cb->val.hmask, xtables_sa_host(res->ai_addr, res->ai_family),
+	       xtables_sa_hostlen(res->ai_family));
+
+	switch(afinfo->family) {
+	case AF_INET:
+		cb->val.hlen = xtables_ipmask_to_cidr(&cb->val.hmask.in);
+		break;
+	case AF_INET6:
+		cb->val.hlen = xtables_ip6mask_to_cidr(&cb->val.hmask.in6);
+		break;
+	}
+
+	freeaddrinfo(res);
+	return 1;
+}
+
+/**
+ * Parse an integer and ensure it is within the address family's prefix length
+ * limits. The result is stored in @cb->val.hlen.
+ */
+static void xtopt_parse_plen(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	unsigned int prefix_len = 128; /* happiness is a warm gcc */
+
+	cb->val.hlen = (afinfo->family == NFPROTO_IPV4) ? 32 : 128;
+	if (!xtables_strtoui(cb->arg, NULL, &prefix_len, 0, cb->val.hlen)) {
+		/* Is this mask expressed in full format? e.g. 255.255.255.0 */
+		if (xtopt_parse_mask(cb))
+			return;
+
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: bad value for option \"--%s\", "
+			"neither a valid network mask "
+			"nor valid CIDR (%u-%u).\n",
+			cb->ext_name, entry->name, 0, cb->val.hlen);
+	}
+	cb->val.hlen = prefix_len;
+}
+
+/**
+ * Reuse xtopt_parse_plen for testing the integer. Afterwards convert this to
+ * a bitmask, and make it available through @cb->val.hmask (hlen remains
+ * valid). If %XTOPT_PUT is used, hmask will be copied to the target area.
+ */
+static void xtopt_parse_plenmask(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	uint32_t *mask = cb->val.hmask.all;
+
+	xtopt_parse_plen(cb);
+
+	memset(mask, 0xFF, sizeof(union nf_inet_addr));
+	/* This shifting is AF-independent. */
+	if (cb->val.hlen == 0) {
+		mask[0] = mask[1] = mask[2] = mask[3] = 0;
+	} else if (cb->val.hlen <= 32) {
+		mask[0] <<= 32 - cb->val.hlen;
+		mask[1] = mask[2] = mask[3] = 0;
+	} else if (cb->val.hlen <= 64) {
+		mask[1] <<= 32 - (cb->val.hlen - 32);
+		mask[2] = mask[3] = 0;
+	} else if (cb->val.hlen <= 96) {
+		mask[2] <<= 32 - (cb->val.hlen - 64);
+		mask[3] = 0;
+	} else if (cb->val.hlen <= 128) {
+		mask[3] <<= 32 - (cb->val.hlen - 96);
+	}
+	mask[0] = htonl(mask[0]);
+	mask[1] = htonl(mask[1]);
+	mask[2] = htonl(mask[2]);
+	mask[3] = htonl(mask[3]);
+	if (entry->flags & XTOPT_PUT)
+		memcpy(XTOPT_MKPTR(cb), mask, sizeof(union nf_inet_addr));
+}
+
+static void xtopt_parse_hostmask(struct xt_option_call *cb)
+{
+	const char *orig_arg = cb->arg;
+	char *work, *p;
+
+	if (strchr(cb->arg, '/') == NULL) {
+		xtopt_parse_host(cb);
+		return;
+	}
+	work = strdup(orig_arg);
+	if (work == NULL)
+		xt_params->exit_err(PARAMETER_PROBLEM, "strdup");
+	p = strchr(work, '/'); /* by def this can't be NULL now */
+	*p++ = '\0';
+	/*
+	 * Because xtopt_parse_host and xtopt_parse_plenmask would store
+	 * different things in the same target area, XTTYPE_HOSTMASK must
+	 * disallow XTOPT_PUT, which it does by forcing its absence,
+	 * cf. not being listed in xtopt_psize.
+	 */
+	cb->arg = work;
+	xtopt_parse_host(cb);
+	cb->arg = p;
+	xtopt_parse_plenmask(cb);
+	cb->arg = orig_arg;
+}
+
+static void xtopt_parse_ethermac(struct xt_option_call *cb)
+{
+	const char *arg = cb->arg;
+	unsigned int i;
+	char *end;
+
+	for (i = 0; i < ARRAY_SIZE(cb->val.ethermac) - 1; ++i) {
+		cb->val.ethermac[i] = strtoul(arg, &end, 16);
+		if (*end != ':' || end - arg > 2)
+			goto out;
+		arg = end + 1;
+	}
+	i = ARRAY_SIZE(cb->val.ethermac) - 1;
+	cb->val.ethermac[i] = strtoul(arg, &end, 16);
+	if (*end != '\0' || end - arg > 2)
+		goto out;
+	if (cb->entry->flags & XTOPT_PUT)
+		memcpy(XTOPT_MKPTR(cb), cb->val.ethermac,
+		       sizeof(cb->val.ethermac));
+	return;
+ out:
+	xt_params->exit_err(PARAMETER_PROBLEM, "Invalid MAC address specified.");
+}
+
+static void (*const xtopt_subparse[])(struct xt_option_call *) = {
+	[XTTYPE_UINT8]       = xtopt_parse_int,
+	[XTTYPE_UINT16]      = xtopt_parse_int,
+	[XTTYPE_UINT32]      = xtopt_parse_int,
+	[XTTYPE_UINT64]      = xtopt_parse_int,
+	[XTTYPE_UINT8RC]     = xtopt_parse_mint,
+	[XTTYPE_UINT16RC]    = xtopt_parse_mint,
+	[XTTYPE_UINT32RC]    = xtopt_parse_mint,
+	[XTTYPE_UINT64RC]    = xtopt_parse_mint,
+	[XTTYPE_DOUBLE]      = xtopt_parse_float,
+	[XTTYPE_STRING]      = xtopt_parse_string,
+	[XTTYPE_TOSMASK]     = xtopt_parse_tosmask,
+	[XTTYPE_MARKMASK32]  = xtopt_parse_markmask,
+	[XTTYPE_SYSLOGLEVEL] = xtopt_parse_sysloglevel,
+	[XTTYPE_HOST]        = xtopt_parse_host,
+	[XTTYPE_HOSTMASK]    = xtopt_parse_hostmask,
+	[XTTYPE_PROTOCOL]    = xtopt_parse_protocol,
+	[XTTYPE_PORT]        = xtopt_parse_port,
+	[XTTYPE_PORTRC]      = xtopt_parse_mport,
+	[XTTYPE_PLEN]        = xtopt_parse_plen,
+	[XTTYPE_PLENMASK]    = xtopt_parse_plenmask,
+	[XTTYPE_ETHERMAC]    = xtopt_parse_ethermac,
+};
+
+/**
+ * The master option parsing routine. May be used for the ".x6_parse"
+ * function pointer in extensions if fully automatic parsing is desired.
+ * It may be also called manually from a custom x6_parse function.
+ */
+void xtables_option_parse(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	unsigned int eflag = 1 << cb->entry->id;
+
+	/*
+	 * With {.id = P_FOO, .excl = P_FOO} we can have simple double-use
+	 * prevention. Though it turned out that this is too much typing (most
+	 * of the options are one-time use only), so now we also have
+	 * %XTOPT_MULTI.
+	 */
+	if ((!(entry->flags & XTOPT_MULTI) || (entry->excl & eflag)) &&
+	    cb->xflags & eflag)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: option \"--%s\" can only be used once.\n",
+			cb->ext_name, cb->entry->name);
+	if (cb->invert && !(entry->flags & XTOPT_INVERT))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: option \"--%s\" cannot be inverted.\n",
+			cb->ext_name, entry->name);
+	if (entry->type != XTTYPE_NONE && optarg == NULL)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: option \"--%s\" requires an argument.\n",
+			cb->ext_name, entry->name);
+	/*
+	 * Fill in fallback value for "nvals", in case an extension (as it
+	 * happened with libxt_conntrack.2) tries to read it, despite not using
+	 * a *RC option type.
+	 */
+	cb->nvals = 1;
+	if (entry->type < ARRAY_SIZE(xtopt_subparse) &&
+	    xtopt_subparse[entry->type] != NULL)
+		xtopt_subparse[entry->type](cb);
+	/* Exclusion with other flags tested later in finalize. */
+	cb->xflags |= 1 << entry->id;
+}
+
+/**
+ * Verifies that an extension's option map descriptor is valid, and ought to
+ * be called right after the extension has been loaded, and before option
+ * merging/xfrm.
+ */
+void xtables_option_metavalidate(const char *name,
+				 const struct xt_option_entry *entry)
+{
+	for (; entry->name != NULL; ++entry) {
+		if (entry->id >= CHAR_BIT * sizeof(unsigned int) ||
+		    entry->id >= XT_OPTION_OFFSET_SCALE)
+			xt_params->exit_err(OTHER_PROBLEM,
+				"Extension %s uses invalid ID %u\n",
+				name, entry->id);
+		if (!(entry->flags & XTOPT_PUT)) {
+			if (entry->ptroff != 0)
+				xt_params->exit_err(OTHER_PROBLEM,
+					"%s: ptroff for \"--%s\" is non-"
+					"zero but no XTOPT_PUT is specified. "
+					"Oversight?", name, entry->name);
+			continue;
+		}
+		if (entry->type >= ARRAY_SIZE(xtopt_psize) ||
+		    xtopt_psize[entry->type] == 0)
+			xt_params->exit_err(OTHER_PROBLEM,
+				"%s: entry type of option \"--%s\" cannot be "
+				"combined with XTOPT_PUT\n",
+				name, entry->name);
+		if (xtopt_psize[entry->type] != -1 &&
+		    xtopt_psize[entry->type] != entry->size)
+			xt_params->exit_err(OTHER_PROBLEM,
+				"%s: option \"--%s\" points to a memory block "
+				"of wrong size (expected %zu, got %zu)\n",
+				name, entry->name,
+				xtopt_psize[entry->type], entry->size);
+	}
+}
+
+/**
+ * Find an option entry by its id.
+ */
+static const struct xt_option_entry *
+xtables_option_lookup(const struct xt_option_entry *entry, unsigned int id)
+{
+	for (; entry->name != NULL; ++entry)
+		if (entry->id == id)
+			return entry;
+	return NULL;
+}
+
+/**
+ * @c:		getopt id (i.e. with offset)
+ * @fw:		struct ipt_entry or ip6t_entry
+ *
+ * Dispatch arguments to the appropriate parse function, based upon the
+ * extension's choice of API.
+ */
+void xtables_option_tpcall(unsigned int c, char **argv, bool invert,
+			   struct xtables_target *t, void *fw)
+{
+	struct xt_option_call cb;
+
+	if (t->x6_parse == NULL) {
+		if (t->parse != NULL)
+			t->parse(c - t->option_offset, argv, invert,
+				 &t->tflags, fw, &t->t);
+		return;
+	}
+
+	c -= t->option_offset;
+	cb.entry = xtables_option_lookup(t->x6_options, c);
+	if (cb.entry == NULL)
+		xtables_error(OTHER_PROBLEM,
+			"Extension does not know id %u\n", c);
+	cb.arg      = optarg;
+	cb.invert   = invert;
+	cb.ext_name = t->name;
+	cb.data     = t->t->data;
+	cb.xflags   = t->tflags;
+	cb.target   = &t->t;
+	cb.xt_entry = fw;
+	cb.udata    = t->udata;
+	t->x6_parse(&cb);
+	t->tflags = cb.xflags;
+}
+
+/**
+ * @c:		getopt id (i.e. with offset)
+ * @fw:		struct ipt_entry or ip6t_entry
+ *
+ * Dispatch arguments to the appropriate parse function, based upon the
+ * extension's choice of API.
+ */
+void xtables_option_mpcall(unsigned int c, char **argv, bool invert,
+			   struct xtables_match *m, void *fw)
+{
+	struct xt_option_call cb;
+
+	if (m->x6_parse == NULL) {
+		if (m->parse != NULL)
+			m->parse(c - m->option_offset, argv, invert,
+				 &m->mflags, fw, &m->m);
+		return;
+	}
+
+	c -= m->option_offset;
+	cb.entry = xtables_option_lookup(m->x6_options, c);
+	if (cb.entry == NULL)
+		xtables_error(OTHER_PROBLEM,
+			"Extension does not know id %u\n", c);
+	cb.arg      = optarg;
+	cb.invert   = invert;
+	cb.ext_name = m->name;
+	cb.data     = m->m->data;
+	cb.xflags   = m->mflags;
+	cb.match    = &m->m;
+	cb.xt_entry = fw;
+	cb.udata    = m->udata;
+	m->x6_parse(&cb);
+	m->mflags = cb.xflags;
+}
+
+/**
+ * @name:	name of extension
+ * @entry:	current option (from all ext's entries) being validated
+ * @xflags:	flags the extension has collected
+ * @i:		conflicting option (id) to test for
+ */
+static void
+xtables_option_fcheck2(const char *name, const struct xt_option_entry *entry,
+		       const struct xt_option_entry *other,
+		       unsigned int xflags)
+{
+	unsigned int ef = 1 << entry->id, of = 1 << other->id;
+
+	if (entry->also & of && !(xflags & of))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: option \"--%s\" also requires \"--%s\".\n",
+			name, entry->name, other->name);
+
+	if (!(entry->excl & of))
+		/* Use of entry does not collide with other option, good. */
+		return;
+	if ((xflags & (ef | of)) != (ef | of))
+		/* Conflicting options were not used. */
+		return;
+
+	xt_params->exit_err(PARAMETER_PROBLEM,
+		"%s: option \"--%s\" cannot be used together with \"--%s\".\n",
+		name, entry->name, other->name);
+}
+
+/**
+ * @name:	name of extension
+ * @xflags:	accumulated flags
+ * @entry:	extension's option table
+ *
+ * Check that all option constraints have been met. This effectively replaces
+ * ->final_check of the older API.
+ */
+void xtables_options_fcheck(const char *name, unsigned int xflags,
+			    const struct xt_option_entry *table)
+{
+	const struct xt_option_entry *entry, *other;
+	unsigned int i;
+
+	for (entry = table; entry->name != NULL; ++entry) {
+		if (entry->flags & XTOPT_MAND &&
+		    !(xflags & (1 << entry->id)))
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"%s: option \"--%s\" must be specified\n",
+				name, entry->name);
+		if (!(xflags & (1 << entry->id)))
+			/* Not required, not specified, thus skip. */
+			continue;
+
+		for (i = 0; i < CHAR_BIT * sizeof(entry->id); ++i) {
+			if (entry->id == i)
+				/*
+				 * Avoid conflict with self. Multi-use check
+				 * was done earlier in xtables_option_parse.
+				 */
+				continue;
+			other = xtables_option_lookup(table, i);
+			if (other == NULL)
+				continue;
+			xtables_option_fcheck2(name, entry, other, xflags);
+		}
+	}
+}
+
+/**
+ * Dispatch arguments to the appropriate final_check function, based upon the
+ * extension's choice of API.
+ */
+void xtables_option_tfcall(struct xtables_target *t)
+{
+	if (t->x6_fcheck != NULL) {
+		struct xt_fcheck_call cb;
+
+		cb.ext_name = t->name;
+		cb.data     = t->t->data;
+		cb.xflags   = t->tflags;
+		cb.udata    = t->udata;
+		t->x6_fcheck(&cb);
+	} else if (t->final_check != NULL) {
+		t->final_check(t->tflags);
+	}
+	if (t->x6_options != NULL)
+		xtables_options_fcheck(t->name, t->tflags, t->x6_options);
+}
+
+/**
+ * Dispatch arguments to the appropriate final_check function, based upon the
+ * extension's choice of API.
+ */
+void xtables_option_mfcall(struct xtables_match *m)
+{
+	if (m->x6_fcheck != NULL) {
+		struct xt_fcheck_call cb;
+
+		cb.ext_name = m->name;
+		cb.data     = m->m->data;
+		cb.xflags   = m->mflags;
+		cb.udata    = m->udata;
+		m->x6_fcheck(&cb);
+	} else if (m->final_check != NULL) {
+		m->final_check(m->mflags);
+	}
+	if (m->x6_options != NULL)
+		xtables_options_fcheck(m->name, m->mflags, m->x6_options);
+}
+
+struct xtables_lmap *xtables_lmap_init(const char *file)
+{
+	struct xtables_lmap *lmap_head = NULL, *lmap_prev = NULL, *lmap_this;
+	char buf[512];
+	FILE *fp;
+	char *cur, *nxt;
+	int id;
+
+	fp = fopen(file, "re");
+	if (fp == NULL)
+		return NULL;
+
+	while (fgets(buf, sizeof(buf), fp) != NULL) {
+		cur = buf;
+		while (isspace(*cur))
+			++cur;
+		if (*cur == '#' || *cur == '\n' || *cur == '\0')
+			continue;
+
+		/* iproute2 allows hex and dec format */
+		errno = 0;
+		id = strtoul(cur, &nxt, strncmp(cur, "0x", 2) == 0 ? 16 : 10);
+		if (nxt == cur || errno != 0)
+			continue;
+
+		/* same boundaries as in iproute2 */
+		if (id < 0 || id > 255)
+			continue;
+		cur = nxt;
+
+		if (!isspace(*cur))
+			continue;
+		while (isspace(*cur))
+			++cur;
+		if (*cur == '#' || *cur == '\n' || *cur == '\0')
+			continue;
+		nxt = cur;
+		while (*nxt != '\0' && !isspace(*nxt))
+			++nxt;
+		if (nxt == cur)
+			continue;
+		*nxt = '\0';
+
+		/* found valid data */
+		lmap_this = malloc(sizeof(*lmap_this));
+		if (lmap_this == NULL) {
+			perror("malloc");
+			goto out;
+		}
+		lmap_this->id   = id;
+		lmap_this->name = strdup(cur);
+		if (lmap_this->name == NULL) {
+			free(lmap_this);
+			goto out;
+		}
+		lmap_this->next = NULL;
+
+		if (lmap_prev != NULL)
+			lmap_prev->next = lmap_this;
+		else
+			lmap_head = lmap_this;
+		lmap_prev = lmap_this;
+	}
+
+	fclose(fp);
+	return lmap_head;
+ out:
+	fclose(fp);
+	xtables_lmap_free(lmap_head);
+	return NULL;
+}
+
+void xtables_lmap_free(struct xtables_lmap *head)
+{
+	struct xtables_lmap *next;
+
+	for (; head != NULL; head = next) {
+		next = head->next;
+		free(head->name);
+		free(head);
+	}
+}
+
+int xtables_lmap_name2id(const struct xtables_lmap *head, const char *name)
+{
+	for (; head != NULL; head = head->next)
+		if (strcmp(head->name, name) == 0)
+			return head->id;
+	return -1;
+}
+
+const char *xtables_lmap_id2name(const struct xtables_lmap *head, int id)
+{
+	for (; head != NULL; head = head->next)
+		if (head->id == id)
+			return head->name;
+	return NULL;
+}
diff --git a/m4/.gitignore b/m4/.gitignore
new file mode 100644
index 0000000..64d9bbc
--- /dev/null
+++ b/m4/.gitignore
@@ -0,0 +1,2 @@
+/libtool.m4
+/lt*.m4
diff --git a/utils/.gitignore b/utils/.gitignore
new file mode 100644
index 0000000..6300812
--- /dev/null
+++ b/utils/.gitignore
@@ -0,0 +1,4 @@
+/nfnl_osf
+/nfnl_osf.8
+/nfbpf_compile
+/nfbpf_compile.8
diff --git a/utils/Makefile.am b/utils/Makefile.am
new file mode 100644
index 0000000..42bd973
--- /dev/null
+++ b/utils/Makefile.am
@@ -0,0 +1,35 @@
+# -*- Makefile -*-
+
+AM_CFLAGS = ${regular_CFLAGS}
+AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include \
+              -I${top_srcdir}/include ${libnfnetlink_CFLAGS}
+
+sbin_PROGRAMS =
+pkgdata_DATA =
+man_MANS =
+
+if HAVE_LIBNFNETLINK
+man_MANS += nfnl_osf.8
+sbin_PROGRAMS += nfnl_osf
+pkgdata_DATA += pf.os
+
+nfnl_osf_LDADD = ${libnfnetlink_LIBS}
+
+uninstall-hook:
+	dir=${DESTDIR}${pkgdatadir}; { \
+		test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; \
+	} || rmdir -p --ignore-fail-on-non-empty "$$dir"
+endif
+
+if ENABLE_BPFC
+man_MANS += nfbpf_compile.8
+sbin_PROGRAMS += nfbpf_compile
+nfbpf_compile_LDADD = -lpcap
+endif
+
+if ENABLE_SYNCONF
+sbin_PROGRAMS += nfsynproxy
+nfsynproxy_LDADD = -lpcap
+endif
+
+CLEANFILES = nfnl_osf.8 nfbpf_compile.8
diff --git a/utils/nfbpf_compile.8.in b/utils/nfbpf_compile.8.in
new file mode 100644
index 0000000..d02979a
--- /dev/null
+++ b/utils/nfbpf_compile.8.in
@@ -0,0 +1,70 @@
+.TH NFBPF_COMPILE 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+
+.SH NAME
+nfbpf_compile \- generate bytecode for use with xt_bpf
+.SH SYNOPSIS
+
+.ad l
+.in +8
+.ti -8
+.B nfbpf_compile
+[
+.I LLTYPE
+]
+.I PROGRAM
+
+.ti -8
+.I LLTYPE
+:= {
+.BR EN10MB " | " RAW " | " SLIP " | "
+.I ...
+}
+
+.SH DESCRIPTION
+The
+.B nfbpf_compile
+utility aids in generating BPF byte code suitable for passing to
+the iptables
+.B bpf
+match.
+
+.SH OPTIONS
+
+.TP
+.I LLTYPE
+Link-layer header type to operate on. This is a name as defined in
+.RB < pcap/dlt.h >
+but with the leading
+.B DLT_
+prefix stripped. For use with iptables,
+.B RAW
+should be the right choice (it's also the default if not specified).
+
+.TP
+.I PROGRAM
+The BPF expression to compile, see
+.BR pcap-filter (7)
+for a description of the language.
+
+.SH EXIT STATUS
+The program returns 0 on success, 1 otherwise.
+
+.SH EXAMPLE
+Match incoming TCP packets with size bigger than 100 bytes:
+.P
+.in +8
+.EE
+bpf=$(nfbpf_compile 'tcp and greater 100')
+.br
+iptables -A INPUT -m bpf --bytecode "$bpf" -j ACCEPT
+.RE
+.P
+The description of
+.B bpf
+match in
+.BR iptables-extensions (8)
+lists a few more examples.
+
+.SH SEE ALSO
+.BR iptables-extensions (8),
+.BR pcap-filter (7)
diff --git a/utils/nfbpf_compile.c b/utils/nfbpf_compile.c
new file mode 100644
index 0000000..2c46c7b
--- /dev/null
+++ b/utils/nfbpf_compile.c
@@ -0,0 +1,55 @@
+/*
+ * BPF program compilation tool
+ *
+ * Generates decimal output, similar to `tcpdump -ddd ...`.
+ * Unlike tcpdump, will generate for any given link layer type.
+ *
+ * Written by Willem de Bruijn (willemb@google.com)
+ * Copyright Google, Inc. 2013
+ * Licensed under the GNU General Public License version 2 (GPLv2)
+*/
+
+#include <pcap.h>
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+	struct bpf_program program;
+	struct bpf_insn *ins;
+	int i, dlt = DLT_RAW;
+
+	if (argc < 2 || argc > 3) {
+		fprintf(stderr, "Usage:    %s [link] '<program>'\n\n"
+				"          link is a pcap linklayer type:\n"
+				"          one of EN10MB, RAW, SLIP, ...\n\n"
+				"Examples: %s RAW 'tcp and greater 100'\n"
+				"          %s EN10MB 'ip proto 47'\n'",
+				argv[0], argv[0], argv[0]);
+		return 1;
+	}
+
+	if (argc == 3) {
+		dlt = pcap_datalink_name_to_val(argv[1]);
+		if (dlt == -1) {
+			fprintf(stderr, "Unknown datalinktype: %s\n", argv[1]);
+			return 1;
+		}
+	}
+
+	if (pcap_compile_nopcap(65535, dlt, &program, argv[argc - 1], 1,
+				PCAP_NETMASK_UNKNOWN)) {
+		fprintf(stderr, "Compilation error\n");
+		return 1;
+	}
+
+	printf("%d,", program.bf_len);
+	ins = program.bf_insns;
+	for (i = 0; i < program.bf_len-1; ++ins, ++i)
+		printf("%u %u %u %u,", ins->code, ins->jt, ins->jf, ins->k);
+
+	printf("%u %u %u %u\n", ins->code, ins->jt, ins->jf, ins->k);
+
+	pcap_freecode(&program);
+	return 0;
+}
+
diff --git a/utils/nfnl_osf.8.in b/utils/nfnl_osf.8.in
new file mode 100644
index 0000000..140b5c3
--- /dev/null
+++ b/utils/nfnl_osf.8.in
@@ -0,0 +1,67 @@
+.TH NFNL_OSF 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+
+.SH NAME
+nfnl_osf \- OS fingerprint loader utility
+.SH SYNOPSIS
+
+.ad l
+.in +8
+.ti -8
+.B nfnl_osf
+.BI -f " fingerprints"
+[
+.B -d
+]
+
+.SH DESCRIPTION
+The
+.B nfnl_osf
+utility allows to load a set of operating system signatures into the kernel for
+later matching against using iptables'
+.B osf
+match.
+
+.SH OPTIONS
+
+.TP
+.BI -f " fingerprints"
+Read signatures from file
+.IR fingerprints .
+
+.TP
+.B -d
+Instead of adding the signatures from
+.I fingerprints
+into the kernel, remove them.
+
+.SH EXIT STATUS
+Exit status is 0 if command succeeded, otherwise a negative return code
+indicates the type of error which happened:
+
+.TP
+.B -1
+Illegal arguments passed, fingerprints file not readable or failure in netlink
+communication.
+
+.TP
+.B -ENOENT
+Fingerprints file not specified.
+
+.TP
+.B -EINVAL
+Netlink handle initialization failed or fingerprints file format invalid.
+
+.SH FILES
+
+An up to date set of operating system signatures can be downloaded from
+http://www.openbsd.org/cgi-bin/cvsweb/src/etc/pf.os .
+
+.SH SEE ALSO
+
+The description of
+.B osf
+match in
+.BR iptables-extensions (8)
+contains further information about the topic as well as example
+.B nfnl_osf
+invocations.
diff --git a/utils/nfnl_osf.c b/utils/nfnl_osf.c
new file mode 100644
index 0000000..8008e83
--- /dev/null
+++ b/utils/nfnl_osf.c
@@ -0,0 +1,492 @@
+/*
+ * Copyright (c) 2005 Evgeniy Polyakov <johnpol@2ka.mxt.ru>
+ * 
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/time.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+
+#include <linux/connector.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/unistd.h>
+
+#include <libnfnetlink/libnfnetlink.h>
+
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/xt_osf.h>
+
+#define OPTDEL			','
+#define OSFPDEL 		':'
+#define MAXOPTSTRLEN		128
+
+#ifndef NIPQUAD
+#define NIPQUAD(addr) \
+	((unsigned char *)&addr)[0], \
+	((unsigned char *)&addr)[1], \
+	((unsigned char *)&addr)[2], \
+	((unsigned char *)&addr)[3]
+#endif
+
+static struct nfnl_handle *nfnlh;
+static struct nfnl_subsys_handle *nfnlssh;
+
+static struct xt_osf_opt IANA_opts[] = {
+	{ .kind = 0, .length = 1,},
+	{ .kind=1, .length=1,},
+	{ .kind=2, .length=4,},
+	{ .kind=3, .length=3,},
+	{ .kind=4, .length=2,},
+	{ .kind=5, .length=1,},		/* SACK length is not defined */
+	{ .kind=6, .length=6,},
+	{ .kind=7, .length=6,},
+	{ .kind=8, .length=10,},
+	{ .kind=9, .length=2,},
+	{ .kind=10, .length=3,},
+	{ .kind=11, .length=1,},		/* CC: Suppose 1 */
+	{ .kind=12, .length=1,},		/* the same */
+	{ .kind=13, .length=1,},		/* and here too */
+	{ .kind=14, .length=3,},
+	{ .kind=15, .length=1,},		/* TCP Alternate Checksum Data. Length is not defined */
+	{ .kind=16, .length=1,},
+	{ .kind=17, .length=1,},
+	{ .kind=18, .length=3,},
+	{ .kind=19, .length=18,},
+	{ .kind=20, .length=1,},
+	{ .kind=21, .length=1,},
+	{ .kind=22, .length=1,},
+	{ .kind=23, .length=1,},
+	{ .kind=24, .length=1,},
+	{ .kind=25, .length=1,},
+	{ .kind=26, .length=1,},
+};
+
+static FILE *osf_log_stream;
+
+static void uloga(const char *f, ...)
+{
+	va_list ap;
+
+	if (!osf_log_stream)
+		osf_log_stream = stdout;
+
+	va_start(ap, f);
+	vfprintf(osf_log_stream, f, ap);
+	va_end(ap);
+
+	fflush(osf_log_stream);
+}
+
+static void ulog(const char *f, ...)
+{
+	char str[64];
+	struct tm tm;
+	struct timeval tv;
+	va_list ap;
+
+	if (!osf_log_stream)
+		osf_log_stream = stdout;
+
+	gettimeofday(&tv, NULL);
+	localtime_r((time_t *)&tv.tv_sec, &tm);
+	strftime(str, sizeof(str), "%F %R:%S", &tm);
+
+	fprintf(osf_log_stream, "%s.%lu %ld ", str, tv.tv_usec, syscall(__NR_gettid));
+
+	va_start(ap, f);
+	vfprintf(osf_log_stream, f, ap);
+	va_end(ap);
+
+	fflush(osf_log_stream);
+}
+
+#define ulog_err(f, a...) uloga(f ": %s [%d].\n", ##a, strerror(errno), errno)
+
+static char *xt_osf_strchr(char *ptr, char c)
+{
+	char *tmp;
+
+	tmp = strchr(ptr, c);
+	if (tmp)
+		*tmp = '\0';
+
+	while (tmp && isspace(*(tmp + 1)))
+		tmp++;
+
+	return tmp;
+}
+
+static void xt_osf_parse_opt(struct xt_osf_opt *opt, __u16 *optnum, char *obuf, int olen)
+{
+	int i, op;
+	char *ptr, wc;
+	unsigned long val;
+
+	ptr = &obuf[0];
+	i = 0;
+	while (ptr != NULL && i < olen && *ptr != 0) {
+		val = 0;
+		wc = OSF_WSS_PLAIN;
+		switch (obuf[i]) {
+		case 'N':
+			op = OSFOPT_NOP;
+			ptr = xt_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				*ptr = '\0';
+				ptr++;
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		case 'S':
+			op = OSFOPT_SACKP;
+			ptr = xt_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				*ptr = '\0';
+				ptr++;
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		case 'T':
+			op = OSFOPT_TS;
+			ptr = xt_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				*ptr = '\0';
+				ptr++;
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		case 'W':
+			op = OSFOPT_WSO;
+			ptr = xt_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				switch (obuf[i + 1]) {
+				case '%':
+					wc = OSF_WSS_MODULO;
+					break;
+				case 'S':
+					wc = OSF_WSS_MSS;
+					break;
+				case 'T':
+					wc = OSF_WSS_MTU;
+					break;
+				default:
+					wc = OSF_WSS_PLAIN;
+					break;
+				}
+
+				*ptr = '\0';
+				ptr++;
+				if (wc)
+					val = strtoul(&obuf[i + 2], NULL, 10);
+				else
+					val = strtoul(&obuf[i + 1], NULL, 10);
+				i += (int)(ptr - &obuf[i]);
+
+			} else
+				i++;
+			break;
+		case 'M':
+			op = OSFOPT_MSS;
+			ptr = xt_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				if (obuf[i + 1] == '%')
+					wc = OSF_WSS_MODULO;
+				*ptr = '\0';
+				ptr++;
+				if (wc)
+					val = strtoul(&obuf[i + 2], NULL, 10);
+				else
+					val = strtoul(&obuf[i + 1], NULL, 10);
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		case 'E':
+			op = OSFOPT_EOL;
+			ptr = xt_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				*ptr = '\0';
+				ptr++;
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		default:
+			op = OSFOPT_EMPTY;
+			ptr = xt_osf_strchr(&obuf[i], OPTDEL);
+			if (ptr) {
+				ptr++;
+				i += (int)(ptr - &obuf[i]);
+			} else
+				i++;
+			break;
+		}
+
+		if (op != OSFOPT_EMPTY) {
+			opt[*optnum].kind = IANA_opts[op].kind;
+			opt[*optnum].length = IANA_opts[op].length;
+			opt[*optnum].wc.wc = wc;
+			opt[*optnum].wc.val = val;
+			(*optnum)++;
+		}
+	}
+}
+
+static int osf_load_line(char *buffer, int len, int del)
+{
+	int i, cnt = 0;
+	char obuf[MAXOPTSTRLEN];
+	struct xt_osf_user_finger f;
+	char *pbeg, *pend;
+	char buf[NFNL_HEADER_LEN + NFA_LENGTH(sizeof(struct xt_osf_user_finger))];
+	struct nlmsghdr *nmh = (struct nlmsghdr *) buf;
+
+	memset(&f, 0, sizeof(struct xt_osf_user_finger));
+
+	ulog("Loading '%s'.\n", buffer);
+
+	for (i = 0; i < len && buffer[i] != '\0'; ++i) {
+		if (buffer[i] == ':')
+			cnt++;
+	}
+
+	if (cnt != 8) {
+		ulog("Wrong input line '%s': cnt: %d, must be 8, i: %d, must be %d.\n", buffer, cnt, i, len);
+		return -EINVAL;
+	}
+
+	memset(obuf, 0, sizeof(obuf));
+
+	pbeg = buffer;
+	pend = xt_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		if (pbeg[0] == 'S') {
+			f.wss.wc = OSF_WSS_MSS;
+			if (pbeg[1] == '%')
+				f.wss.val = strtoul(&pbeg[2], NULL, 10);
+			else if (pbeg[1] == '*')
+				f.wss.val = 0;
+			else
+				f.wss.val = strtoul(&pbeg[1], NULL, 10);
+		} else if (pbeg[0] == 'T') {
+			f.wss.wc = OSF_WSS_MTU;
+			if (pbeg[1] == '%')
+				f.wss.val = strtoul(&pbeg[2], NULL, 10);
+			else if (pbeg[1] == '*')
+				f.wss.val = 0;
+			else
+				f.wss.val = strtoul(&pbeg[1], NULL, 10);
+		} else if (pbeg[0] == '%') {
+			f.wss.wc = OSF_WSS_MODULO;
+			f.wss.val = strtoul(&pbeg[1], NULL, 10);
+		} else if (isdigit(pbeg[0])) {
+			f.wss.wc = OSF_WSS_PLAIN;
+			f.wss.val = strtoul(&pbeg[0], NULL, 10);
+		}
+
+		pbeg = pend + 1;
+	}
+	pend = xt_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		f.ttl = strtoul(pbeg, NULL, 10);
+		pbeg = pend + 1;
+	}
+	pend = xt_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		f.df = strtoul(pbeg, NULL, 10);
+		pbeg = pend + 1;
+	}
+	pend = xt_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		f.ss = strtoul(pbeg, NULL, 10);
+		pbeg = pend + 1;
+	}
+
+	pend = xt_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		i = sizeof(obuf);
+		snprintf(obuf, i, "%.*s,", i - 2, pbeg);
+		pbeg = pend + 1;
+	}
+
+	pend = xt_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		i = sizeof(f.genre);
+		if (pbeg[0] == '@' || pbeg[0] == '*')
+			pbeg++;
+		snprintf(f.genre, i, "%.*s", i - 1, pbeg);
+		pbeg = pend + 1;
+	}
+
+	pend = xt_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		i = sizeof(f.version);
+		snprintf(f.version, i, "%.*s", i - 1, pbeg);
+		pbeg = pend + 1;
+	}
+
+	pend = xt_osf_strchr(pbeg, OSFPDEL);
+	if (pend) {
+		*pend = '\0';
+		i = sizeof(f.subtype);
+		snprintf(f.subtype, i, "%.*s", i - 1, pbeg);
+	}
+
+	xt_osf_parse_opt(f.opt, &f.opt_num, obuf, sizeof(obuf));
+
+	memset(buf, 0, sizeof(buf));
+
+	if (del)
+		nfnl_fill_hdr(nfnlssh, nmh, 0, AF_UNSPEC, 0, OSF_MSG_REMOVE,
+			      NLM_F_ACK | NLM_F_REQUEST);
+	else
+		nfnl_fill_hdr(nfnlssh, nmh, 0, AF_UNSPEC, 0, OSF_MSG_ADD,
+			      NLM_F_ACK | NLM_F_REQUEST | NLM_F_CREATE);
+
+	nfnl_addattr_l(nmh, sizeof(buf), OSF_ATTR_FINGER, &f, sizeof(struct xt_osf_user_finger));
+
+	return nfnl_query(nfnlh, nmh);
+}
+
+static int osf_load_entries(char *path, int del)
+{
+	FILE *inf;
+	int err = 0, lineno = 0;
+	char buf[1024];
+
+	inf = fopen(path, "r");
+	if (!inf) {
+		ulog_err("Failed to open file '%s'", path);
+		return -1;
+	}
+
+	while(fgets(buf, sizeof(buf), inf)) {
+		int len, rc;
+
+		lineno++;
+
+		if (buf[0] == '#' || buf[0] == '\n' || buf[0] == '\r')
+			continue;
+
+		len = strlen(buf) - 1;
+
+		if (len <= 0)
+			continue;
+
+		buf[len] = '\0';
+
+		rc = osf_load_line(buf, len, del);
+		if (rc && (!del || errno != ENOENT)) {
+			ulog_err("Failed to load line %d", lineno);
+			err = rc;
+		}
+
+		memset(buf, 0, sizeof(buf));
+	}
+
+	fclose(inf);
+	return err;
+}
+
+int main(int argc, char *argv[])
+{
+	int ch, del = 0, err;
+	char *fingerprints = NULL;
+
+	while ((ch = getopt(argc, argv, "f:dh")) != -1) {
+		switch (ch) {
+			case 'f':
+				fingerprints = optarg;
+				break;
+			case 'd':
+				del = 1;
+				break;
+			default:
+				fprintf(stderr,
+					"Usage: %s -f fingerprints [-d]\n",
+					argv[0]);
+				return -1;
+		}
+	}
+
+	if (!fingerprints) {
+		err = -ENOENT;
+		ulog("Missing fingerprints file argument.\n");
+		goto err_out_exit;
+	}
+
+	nfnlh = nfnl_open();
+	if (!nfnlh) {
+		err = -EINVAL;
+		ulog_err("Failed to create nfnl handler");
+		goto err_out_exit;
+	}
+
+#ifndef NFNL_SUBSYS_OSF
+#define NFNL_SUBSYS_OSF	5
+#endif
+
+	nfnlssh = nfnl_subsys_open(nfnlh, NFNL_SUBSYS_OSF, OSF_MSG_MAX, 0);
+	if (!nfnlssh) {
+		err = -EINVAL;
+		ulog_err("Faied to create nfnl subsystem");
+		goto err_out_close;
+	}
+
+	err = osf_load_entries(fingerprints, del);
+	if (err)
+		goto err_out_close_subsys;
+
+	nfnl_subsys_close(nfnlssh);
+	nfnl_close(nfnlh);
+
+	return 0;
+
+err_out_close_subsys:
+	nfnl_subsys_close(nfnlssh);
+err_out_close:
+	nfnl_close(nfnlh);
+err_out_exit:
+	return err;
+}
diff --git a/utils/nfsynproxy.c b/utils/nfsynproxy.c
new file mode 100644
index 0000000..bf5c416
--- /dev/null
+++ b/utils/nfsynproxy.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2013 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <pcap/pcap.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+
+static const char *iface = "lo";
+static uint16_t port;
+static const char *chain = "SYNPROXY";
+
+static int parse_packet(const char *host, const uint8_t *data)
+{
+	const struct iphdr *iph = (void *)data + 14;
+	const struct tcphdr *th = (void *)iph + iph->ihl * 4;
+	int length;
+	uint8_t *ptr;
+
+	if (!th->syn || !th->ack)
+		return 0;
+
+	printf("-A %s -d %s -p tcp --dport %u "
+	       "-m state --state UNTRACKED,INVALID "
+	       "-j SYNPROXY ", chain, host, port);
+
+	/* ECE && !CWR */
+	if (th->res2 == 0x1)
+		printf("--ecn ");
+
+	length = th->doff * 4 - sizeof(*th);
+	ptr = (uint8_t *)(th + 1);
+	while (length > 0) {
+		int opcode = *ptr++;
+		int opsize;
+
+		switch (opcode) {
+		case TCPOPT_EOL:
+			return 1;
+		case TCPOPT_NOP:
+			length--;
+			continue;
+		default:
+			opsize = *ptr++;
+			if (opsize < 2)
+				return 1;
+			if (opsize > length)
+				return 1;
+
+			switch (opcode) {
+			case TCPOPT_MAXSEG:
+				if (opsize == TCPOLEN_MAXSEG)
+					printf("--mss %u ", ntohs(*(uint16_t *)ptr));
+				break;
+			case TCPOPT_WINDOW:
+				if (opsize == TCPOLEN_WINDOW)
+					printf("--wscale %u ", *ptr);
+				break;
+			case TCPOPT_TIMESTAMP:
+				if (opsize == TCPOLEN_TIMESTAMP)
+					printf("--timestamp ");
+				break;
+			case TCPOPT_SACK_PERMITTED:
+				if (opsize == TCPOLEN_SACK_PERMITTED)
+					printf("--sack-perm ");
+				break;
+			}
+
+			ptr += opsize - 2;
+			length -= opsize;
+		}
+	}
+	printf("\n");
+	return 1;
+}
+
+static void probe_host(const char *host)
+{
+	struct sockaddr_in sin;
+	char pcap_errbuf[PCAP_ERRBUF_SIZE];
+	struct pcap_pkthdr pkthdr;
+	const uint8_t *data;
+	struct bpf_program fp;
+	pcap_t *ph;
+	int fd;
+
+	ph = pcap_create(iface, pcap_errbuf);
+	if (ph == NULL) {
+		perror("pcap_create");
+		goto err1;
+	}
+
+	if (pcap_setnonblock(ph, 1, pcap_errbuf) == -1) {
+		perror("pcap_setnonblock");
+		goto err2;
+	}
+
+	if (pcap_setfilter(ph, &fp) == -1) {
+		pcap_perror(ph, "pcap_setfilter");
+		goto err2;
+	}
+
+	if (pcap_activate(ph) != 0) {
+		pcap_perror(ph, "pcap_activate");
+		goto err2;
+	}
+
+	if (pcap_compile(ph, &fp, "src host 127.0.0.1 and tcp and src port 80",
+			 1, PCAP_NETMASK_UNKNOWN) == -1) {
+		pcap_perror(ph, "pcap_compile");
+		goto err2;
+	}
+
+	fd = socket(AF_INET, SOCK_STREAM, 0);
+	if (fd < 0) {
+		perror("socket");
+		goto err3;
+	}
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family		= AF_INET;
+	sin.sin_port		= htons(port);
+	sin.sin_addr.s_addr	= inet_addr(host);
+
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+		perror("connect");
+		goto err4;
+	}
+
+	for (;;) {
+		data = pcap_next(ph, &pkthdr);
+		if (data == NULL)
+			break;
+		if (parse_packet(host, data))
+			break;
+	}
+
+	close(fd);
+
+err4:
+	close(fd);
+err3:
+	pcap_freecode(&fp);
+err2:
+	pcap_close(ph);
+err1:
+	return;
+}
+
+enum {
+	OPT_HELP	= 'h',
+	OPT_IFACE	= 'i',
+	OPT_PORT	= 'p',
+	OPT_CHAIN	= 'c',
+};
+
+static const struct option options[] = {
+	{ .name = "help",  .has_arg = false, .val = OPT_HELP },
+	{ .name = "iface", .has_arg = true,  .val = OPT_IFACE },
+	{ .name = "port" , .has_arg = true,  .val = OPT_PORT },
+	{ .name = "chain", .has_arg = true,  .val = OPT_CHAIN },
+	{ }
+};
+
+static void print_help(const char *name)
+{
+	printf("%s [ options ] address...\n"
+	       "\n"
+	       "Options:\n"
+	       " -i/--iface        Outbound interface\n"
+	       " -p/--port         Port number to probe\n"
+	       " -c/--chain        Chain name to use for rules\n"
+	       " -h/--help         Show this help\n",
+	       name);
+}
+
+int main(int argc, char **argv)
+{
+	int optidx = 0, c;
+
+	for (;;) {
+		c = getopt_long(argc, argv, "hi:p:c:", options, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case OPT_IFACE:
+			iface = optarg;
+			break;
+		case OPT_PORT:
+			port = atoi(optarg);
+			break;
+		case OPT_CHAIN:
+			chain = optarg;
+			break;
+		case OPT_HELP:
+			print_help(argv[0]);
+			exit(0);
+		case '?':
+			print_help(argv[0]);
+			exit(1);
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	while (argc > 0) {
+		probe_host(*argv);
+		argc--;
+		argv++;
+	}
+	return 0;
+}
diff --git a/utils/pf.os b/utils/pf.os
new file mode 100644
index 0000000..e285851
--- /dev/null
+++ b/utils/pf.os
@@ -0,0 +1,709 @@
+# $FreeBSD: head/etc/pf.os 258865 2013-12-03 04:32:02Z eadler $
+# $OpenBSD: pf.os,v 1.27 2016/09/03 17:08:57 sthen Exp $
+# passive OS fingerprinting
+# -------------------------
+#
+# SYN signatures. Those signatures work for SYN packets only (duh!).
+#
+# (C) Copyright 2000-2003 by Michal Zalewski <lcamtuf@coredump.cx>
+# (C) Copyright 2003 by Mike Frantzen <frantzen@w4g.org>
+#
+#  Permission to use, copy, modify, and distribute this software for any
+#  purpose with or without fee is hereby granted, provided that the above
+#  copyright notice and this permission notice appear in all copies.
+#
+#  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+#  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+#  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+#  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+#  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+#  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+#  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+#
+# This fingerprint database is adapted from Michal Zalewski's p0f passive
+# operating system package.  The last database sync was from a Nov 3 2003
+# p0f.fp.
+#
+#
+# Each line in this file specifies a single fingerprint. Please read the
+# information below carefully before attempting to append any signatures
+# reported as UNKNOWN to this file to avoid mistakes.
+#
+# We use the following set metrics for fingerprinting:
+#
+# - Window size (WSS) - a highly OS dependent setting used for TCP/IP
+#   performance control (max. amount of data to be sent without ACK).
+#   Some systems use a fixed value for initial packets. On other
+#   systems, it is a multiple of MSS or MTU (MSS+40). In some rare
+#   cases, the value is just arbitrary.
+#
+#   NEW SIGNATURE: if p0f reported a special value of 'Snn', the number
+#   appears to be a multiple of MSS (MSS*nn); a special value of 'Tnn'
+#   means it is a multiple of MTU ((MSS+40)*nn). Unless you notice the
+#   value of nn is not fixed (unlikely), just copy the Snn or Tnn token
+#   literally. If you know this device has a simple stack and a fixed
+#   MTU, you can however multiply S value by MSS, or T value by MSS+40,
+#   and put it instead of Snn or Tnn.
+#
+#   If WSS otherwise looks like a fixed value (for example a multiple
+#   of two), or if you can confirm the value is fixed, please quote
+#   it literally. If there's no apparent pattern in WSS chosen, you
+#   should consider wildcarding this value.
+#
+# - Overall packet size - a function of all IP and TCP options and bugs.
+#
+#   NEW SIGNATURE: Copy this value literally.
+#
+# - Initial TTL - We check the actual TTL of a received packet. It can't
+#   be higher than the initial TTL, and also shouldn't be dramatically
+#   lower (maximum distance is defined as 40 hops).
+#
+#   NEW SIGNATURE: *Never* copy TTL from a p0f-reported signature literally.
+#   You need to determine the initial TTL. The best way to do it is to
+#   check the documentation for a remote system, or check its settings.
+#   A fairly good method is to simply round the observed TTL up to
+#   32, 64, 128, or 255, but it should be noted that some obscure devices
+#   might not use round TTLs (in particular, some shoddy appliances use
+#   "original" initial TTL settings). If not sure, you can see how many
+#   hops you're away from the remote party with traceroute or mtr.
+#
+# - Don't fragment flag (DF) - some modern OSes set this to implement PMTU
+#   discovery. Others do not bother.
+#
+#   NEW SIGNATURE: Copy this value literally.
+#
+# - Maximum segment size (MSS) - this setting is usually link-dependent. P0f
+#   uses it to determine link type of the remote host.
+#
+#   NEW SIGNATURE: Always wildcard this value, except for rare cases when
+#   you have an appliance with a fixed value, know the system supports only
+#   a very limited number of network interface types, or know the system
+#   is using a value it pulled out of nowhere.  Specific unique MSS
+#   can be used to tell Google crawlbots from the rest of the population.
+#
+# - Window scaling (WSCALE) - this feature is used to scale WSS.
+#   It extends the size of a TCP/IP window to 32 bits. Some modern
+#   systems implement this feature.
+#
+#   NEW SIGNATURE: Observe several signatures. Initial WSCALE is often set
+#   to zero or other low value. There's usually no need to wildcard this
+#   parameter.
+#
+# - Timestamp - some systems that implement timestamps set them to
+#   zero in the initial SYN. This case is detected and handled appropriately.
+#
+# - Selective ACK permitted - a flag set by systems that implement
+#   selective ACK functionality.
+#
+# - The sequence of TCP all options (MSS, window scaling, selective ACK
+#   permitted, timestamp, NOP). Other than the options previously
+#   discussed, p0f also checks for timestamp option (a silly
+#   extension to broadcast your uptime ;-), NOP options (used for
+#   header padding) and sackOK option (selective ACK feature).
+#
+#   NEW SIGNATURE: Copy the sequence literally.
+#
+# To wildcard any value (except for initial TTL or TCP options), replace
+# it with '*'. You can also use a modulo operator to match any values
+# that divide by nnn - '%nnn'.
+#
+# Fingerprint entry format:
+#
+# wwww:ttt:D:ss:OOO...:OS:Version:Subtype:Details
+#
+# wwww     - window size (can be *, %nnn, Snn or Tnn).  The special values
+#            "S" and "T" which are a multiple of MSS or a multiple of MTU
+#            respectively.
+# ttt      - initial TTL
+# D        - don't fragment bit (0 - not set, 1 - set)
+# ss       - overall SYN packet size
+# OOO      - option value and order specification (see below)
+# OS       - OS genre (Linux, Solaris, Windows)
+# Version  - OS Version (2.0.27 on x86, etc)
+# Subtype  - OS subtype or patchlevel (SP3, lo0)
+# details  - Generic OS details
+#
+# If OS genre starts with '*', p0f will not show distance, link type
+# and timestamp data. It is useful for userland TCP/IP stacks of
+# network scanners and so on, where many settings are randomized or
+# bogus.
+#
+# If OS genre starts with @, it denotes an approximate hit for a group
+# of operating systems (signature reporting still enabled in this case).
+# Use this feature at the end of this file to catch cases for which
+# you don't have a precise match, but can tell it's Windows or FreeBSD
+# or whatnot by looking at, say, flag layout alone.
+#
+# Option block description is a list of comma or space separated
+# options in the order they appear in the packet:
+#
+# N	   - NOP option
+# Wnnn	   - window scaling option, value nnn (or * or %nnn)
+# Mnnn	   - maximum segment size option, value nnn (or * or %nnn)
+# S	   - selective ACK OK
+# T	   - timestamp
+# T0	   - timestamp with a zero value
+#
+# To denote no TCP options, use a single '.'.
+#
+# Please report any additions to this file, or any inaccuracies or
+# problems spotted, to the maintainers: lcamtuf@coredump.cx,
+# frantzen@openbsd.org and bugs@openbsd.org with a tcpdump packet
+# capture of the relevant SYN packet(s)
+#
+# A test and submission page is available at
+# http://lcamtuf.coredump.cx/p0f-help/
+#
+#
+# WARNING WARNING WARNING
+# -----------------------
+#
+# Do not add a system X as OS Y just because NMAP says so. It is often
+# the case that X is a NAT firewall. While nmap is talking to the
+# device itself, p0f is fingerprinting the guy behind the firewall
+# instead.
+#
+# When in doubt, use common sense, don't add something that looks like
+# a completely different system as Linux or FreeBSD or LinkSys router.
+# Check DNS name, establish a connection to the remote host and look
+# at SYN+ACK - does it look similar?
+#
+# Some users tweak their TCP/IP settings - enable or disable RFC1323
+# functionality, enable or disable timestamps or selective ACK,
+# disable PMTU discovery, change MTU and so on. Always compare a new rule
+# to other fingerprints for this system, and verify the system isn't
+# "customized" before adding it. It is OK to add signature variants
+# caused by a commonly used software (personal firewalls, security
+# packages, etc), but it makes no sense to try to add every single
+# possible /proc/sys/net/ipv4 tweak on Linux or so.
+#
+# KEEP IN MIND: Some packet firewalls configured to normalize outgoing
+# traffic (OpenBSD pf with "scrub" enabled, for example) will, well,
+# normalize packets. Signatures will not correspond to the originating
+# system (and probably not quite to the firewall either).
+#
+# NOTE: Try to keep this file in some reasonable order, from most to
+# least likely systems. This will speed up operation. Also keep most
+# generic and broad rules near the end.
+#
+
+##########################
+# Standard OS signatures #
+##########################
+
+# ----------------- AIX ---------------------
+
+# AIX is first because its signatures are close to NetBSD, MacOS X and
+# Linux 2.0, but it uses a fairly rare MSSes, at least sometimes...
+# This is a shoddy hack, though.
+
+45046:64:0:44:M*:		AIX:4.3::AIX 4.3
+16384:64:0:44:M512:		AIX:4.3:2-3:AIX 4.3.2 and earlier
+
+16384:64:0:60:M512,N,W%2,N,N,T:		AIX:4.3:3:AIX 4.3.3-5.2
+16384:64:0:60:M512,N,W%2,N,N,T:		AIX:5.1-5.2::AIX 4.3.3-5.2
+32768:64:0:60:M512,N,W%2,N,N,T:		AIX:4.3:3:AIX 4.3.3-5.2
+32768:64:0:60:M512,N,W%2,N,N,T:		AIX:5.1-5.2::AIX 4.3.3-5.2
+65535:64:0:60:M512,N,W%2,N,N,T:		AIX:4.3:3:AIX 4.3.3-5.2
+65535:64:0:60:M512,N,W%2,N,N,T:		AIX:5.1-5.2::AIX 4.3.3-5.2
+65535:64:0:64:M*,N,W1,N,N,T,N,N,S:	AIX:5.3:ML1:AIX 5.3 ML1
+
+# ----------------- Linux -------------------
+
+# S1:64:0:44:M*:A:		Linux:1.2::Linux 1.2.x (XXX quirks support)
+512:64:0:44:M*:			Linux:2.0:3x:Linux 2.0.3x
+16384:64:0:44:M*:		Linux:2.0:3x:Linux 2.0.3x
+
+# Endian snafu! Nelson says "ha-ha":
+2:64:0:44:M*:			Linux:2.0:3x:Linux 2.0.3x (MkLinux) on Mac
+64:64:0:44:M*:			Linux:2.0:3x:Linux 2.0.3x (MkLinux) on Mac
+
+
+S4:64:1:60:M1360,S,T,N,W0:	Linux:google::Linux (Google crawlbot)
+
+S2:64:1:60:M*,S,T,N,W0:		Linux:2.4::Linux 2.4 (big boy)
+S3:64:1:60:M*,S,T,N,W0:		Linux:2.4:.18-21:Linux 2.4.18 and newer
+S4:64:1:60:M*,S,T,N,W0:		Linux:2.4::Linux 2.4/2.6 <= 2.6.7
+S4:64:1:60:M*,S,T,N,W0:		Linux:2.6:.1-7:Linux 2.4/2.6 <= 2.6.7
+
+S4:64:1:60:M*,S,T,N,W5:		Linux:2.6::Linux 2.6 (newer, 1)
+S4:64:1:60:M*,S,T,N,W6:		Linux:2.6::Linux 2.6 (newer, 2)
+S4:64:1:60:M*,S,T,N,W7:		Linux:2.6::Linux 2.6 (newer, 3)
+T4:64:1:60:M*,S,T,N,W7:		Linux:2.6::Linux 2.6 (newer, 4)
+
+S10:64:1:60:M*,S,T,N,W4:	Linux:3.0::Linux 3.0
+
+S3:64:1:60:M*,S,T,N,W1:		Linux:2.5::Linux 2.5 (sometimes 2.4)
+S4:64:1:60:M*,S,T,N,W1:		Linux:2.5-2.6::Linux 2.5/2.6
+S3:64:1:60:M*,S,T,N,W2:		Linux:2.5::Linux 2.5 (sometimes 2.4)
+S4:64:1:60:M*,S,T,N,W2:		Linux:2.5::Linux 2.5 (sometimes 2.4)
+
+S20:64:1:60:M*,S,T,N,W0:	Linux:2.2:20-25:Linux 2.2.20 and newer
+S22:64:1:60:M*,S,T,N,W0:	Linux:2.2::Linux 2.2
+S11:64:1:60:M*,S,T,N,W0:	Linux:2.2::Linux 2.2
+
+# Popular cluster config scripts disable timestamps and
+# selective ACK:
+S4:64:1:48:M1460,N,W0:		Linux:2.4:cluster:Linux 2.4 in cluster
+
+# This needs to be investigated. On some systems, WSS
+# is selected as a multiple of MTU instead of MSS. I got
+# many submissions for this for many late versions of 2.4:
+T4:64:1:60:M1412,S,T,N,W0:	Linux:2.4::Linux 2.4 (late, uncommon)
+
+# This happens only over loopback, but let's make folks happy:
+32767:64:1:60:M16396,S,T,N,W0:	Linux:2.4:lo0:Linux 2.4 (local)
+S8:64:1:60:M3884,S,T,N,W0:	Linux:2.2:lo0:Linux 2.2 (local)
+
+# Opera visitors:
+16384:64:1:60:M*,S,T,N,W0:	Linux:2.2:Opera:Linux 2.2 (Opera?)
+32767:64:1:60:M*,S,T,N,W0:	Linux:2.4:Opera:Linux 2.4 (Opera?)
+
+# Some fairly common mods:
+S4:64:1:52:M*,N,N,S,N,W0:	Linux:2.4:ts:Linux 2.4 w/o timestamps
+S22:64:1:52:M*,N,N,S,N,W0:	Linux:2.2:ts:Linux 2.2 w/o timestamps
+
+
+# ----------------- FreeBSD -----------------
+
+16384:64:1:44:M*:		FreeBSD:2.0-2.2::FreeBSD 2.0-4.2
+16384:64:1:44:M*:		FreeBSD:3.0-3.5::FreeBSD 2.0-4.2
+16384:64:1:44:M*:		FreeBSD:4.0-4.2::FreeBSD 2.0-4.2
+16384:64:1:60:M*,N,W0,N,N,T:	FreeBSD:4.4::FreeBSD 4.4
+
+1024:64:1:60:M*,N,W0,N,N,T:	FreeBSD:4.4::FreeBSD 4.4
+
+57344:64:1:44:M*:		FreeBSD:4.6-4.8:noRFC1323:FreeBSD 4.6-4.8 (no RFC1323)
+57344:64:1:60:M*,N,W0,N,N,T:	FreeBSD:4.6-4.9::FreeBSD 4.6-4.9
+
+32768:64:1:60:M*,N,W0,N,N,T:	FreeBSD:4.8-4.11::FreeBSD 4.8-5.1 (or MacOS X)
+32768:64:1:60:M*,N,W0,N,N,T:	FreeBSD:5.0-5.1::FreeBSD 4.8-5.1 (or MacOS X)
+65535:64:1:60:M*,N,W0,N,N,T:	FreeBSD:4.8-4.11::FreeBSD 4.8-5.2 (or MacOS X)
+65535:64:1:60:M*,N,W0,N,N,T:	FreeBSD:5.0-5.2::FreeBSD 4.8-5.2 (or MacOS X)
+65535:64:1:60:M*,N,W1,N,N,T:	FreeBSD:4.7-4.11::FreeBSD 4.7-5.2
+65535:64:1:60:M*,N,W1,N,N,T:	FreeBSD:5.0-5.2::FreeBSD 4.7-5.2
+
+# XXX need quirks support
+# 65535:64:1:60:M*,N,W0,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (1)
+# 65535:64:1:60:M*,N,W1,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (2)
+# 65535:64:1:60:M*,N,W2,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (3)
+# 65535:64:1:44:M*:Z:FreeBSD:5.2::FreeBSD 5.2 (no RFC1323)
+
+# 16384:64:1:60:M*,N,N,N,N,N,N,T:FreeBSD:4.4:noTS:FreeBSD 4.4 (w/o timestamps)
+
+# ----------------- NetBSD ------------------
+
+16384:64:0:60:M*,N,W0,N,N,T:	NetBSD:1.3::NetBSD 1.3
+65535:64:0:60:M*,N,W0,N,N,T0:	NetBSD:1.6:opera:NetBSD 1.6 (Opera)
+16384:64:0:60:M*,N,W0,N,N,T0:	NetBSD:1.6::NetBSD 1.6
+16384:64:1:60:M*,N,W0,N,N,T0:	NetBSD:1.6:df:NetBSD 1.6 (DF)
+65535:64:1:60:M*,N,W1,N,N,T0:	NetBSD:1.6::NetBSD 1.6W-current (DF)
+65535:64:1:60:M*,N,W0,N,N,T0:	NetBSD:1.6::NetBSD 1.6X (DF)
+32768:64:1:60:M*,N,W0,N,N,T0:	NetBSD:1.6:randomization:NetBSD 1.6ZH-current (w/ ip_id randomization)
+
+# ----------------- OpenBSD -----------------
+
+16384:64:0:60:M*,N,W0,N,N,T:		OpenBSD:2.6::NetBSD 1.3 (or OpenBSD 2.6)
+16384:64:1:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.0-4.8::OpenBSD 3.0-4.8
+16384:64:0:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.0-4.8:no-df:OpenBSD 3.0-4.8 (scrub no-df)
+57344:64:1:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.3-4.0::OpenBSD 3.3-4.0
+57344:64:0:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.3-4.0:no-df:OpenBSD 3.3-4.0 (scrub no-df)
+
+65535:64:1:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.0-4.0:opera:OpenBSD 3.0-4.0 (Opera)
+
+16384:64:1:64:M*,N,N,S,N,W3,N,N,T:	OpenBSD:4.9::OpenBSD 4.9
+16384:64:0:64:M*,N,N,S,N,W3,N,N,T:	OpenBSD:4.9:no-df:OpenBSD 4.9 (scrub no-df)
+
+16384:64:1:64:M*,N,N,S,N,W6,N,N,T:      OpenBSD:6.1::OpenBSD 6.1
+16384:64:0:64:M*,N,N,S,N,W6,N,N,T:      OpenBSD:6.1:no-df:OpenBSD 6.1 (scrub no-df)
+
+# ----------------- DragonFly BSD -----------------
+
+57344:64:1:60:M*,N,W0,N,N,T:		DragonFly:1.0:A:DragonFly 1.0A
+57344:64:0:64:M*,N,W0,N,N,S,N,N,T:	DragonFly:1.2-1.12::DragonFly 1.2-1.12
+5840:64:1:60:M*,S,T,N,W4:			DragonFly:2.0-2.1::DragonFly 2.0-2.1
+57344:64:0:64:M*,N,W0,N,N,S,N,N,T:	DragonFly:2.2-2.3::DragonFly 2.2-2.3
+57344:64:0:64:M*,N,W5,N,N,S,N,N,T:	DragonFly:2.4-2.7::DragonFly 2.4-2.7
+
+# ----------------- Solaris -----------------
+
+S17:64:1:64:N,W3,N,N,T0,N,N,S,M*:	Solaris:8:RFC1323:Solaris 8 RFC1323
+S17:64:1:48:N,N,S,M*:			Solaris:8::Solaris 8
+S17:255:1:44:M*:			Solaris:2.5-2.7::Solaris 2.5 to 7
+
+S6:255:1:44:M*:				Solaris:2.6-2.7::Solaris 2.6 to 7
+S23:255:1:44:M*:			Solaris:2.5:1:Solaris 2.5.1
+S34:64:1:48:M*,N,N,S:			Solaris:2.9::Solaris 9
+S44:255:1:44:M*:			Solaris:2.7::Solaris 7
+
+4096:64:0:44:M1460:			SunOS:4.1::SunOS 4.1.x
+
+S34:64:1:52:M*,N,W0,N,N,S:		Solaris:10:beta:Solaris 10 (beta)
+32850:64:1:64:M*,N,N,T,N,W1,N,N,S:	Solaris:10::Solaris 10 1203
+
+# ----------------- IRIX --------------------
+
+49152:64:0:44:M*:			IRIX:6.4::IRIX 6.4
+61440:64:0:44:M*:			IRIX:6.2-6.5::IRIX 6.2-6.5
+49152:64:0:52:M*,N,W2,N,N,S:		IRIX:6.5:RFC1323:IRIX 6.5 (RFC1323)
+49152:64:0:52:M*,N,W3,N,N,S:		IRIX:6.5:RFC1323:IRIX 6.5 (RFC1323)
+
+61440:64:0:48:M*,N,N,S:			IRIX:6.5:12-21:IRIX 6.5.12 - 6.5.21
+49152:64:0:48:M*,N,N,S:			IRIX:6.5:15-21:IRIX 6.5.15 - 6.5.21
+
+49152:60:0:64:M*,N,W2,N,N,T,N,N,S:	IRIX:6.5:IP27:IRIX 6.5 IP27
+
+
+# ----------------- Tru64 -------------------
+
+32768:64:1:48:M*,N,W0:			Tru64:4.0::Tru64 4.0 (or OS/2 Warp 4)
+32768:64:0:48:M*,N,W0:			Tru64:5.0::Tru64 5.0
+8192:64:0:44:M1460:			Tru64:5.1:noRFC1323:Tru64 6.1 (no RFC1323) (or QNX 6)
+61440:64:0:48:M*,N,W0:			Tru64:5.1a:JP4:Tru64 v5.1a JP4 (or OpenVMS 7.x on Compaq 5.x stack)
+
+# ----------------- OpenVMS -----------------
+
+6144:64:1:60:M*,N,W0,N,N,T:		OpenVMS:7.2::OpenVMS 7.2 (Multinet 4.4 stack)
+
+# ----------------- MacOS -------------------
+
+# XXX Need EOL tcp opt support
+# S2:255:1:48:M*,W0,E:.:MacOS:8.6 classic
+
+# XXX some of these use EOL too
+16616:255:1:48:M*,W0:			MacOS:7.3-7.6:OTTCP:MacOS 7.3-8.6 (OTTCP)
+16616:255:1:48:M*,W0:			MacOS:8.0-8.6:OTTCP:MacOS 7.3-8.6 (OTTCP)
+16616:255:1:48:M*,N,N,N:		MacOS:8.1-8.6:OTTCP:MacOS 8.1-8.6 (OTTCP)
+32768:255:1:48:M*,W0,N:			MacOS:9.0-9.2::MacOS 9.0-9.2
+65535:255:1:48:M*,N,N,N,N:		MacOS:9.1::MacOS 9.1 (OT 2.7.4)
+
+
+# ----------------- Windows -----------------
+
+# Windows TCP/IP stack is a mess. For most recent XP, 2000 and
+# even 98, the patchlevel, not the actual OS version, is more
+# relevant to the signature. They share the same code, so it would
+# seem. Luckily for us, almost all Windows 9x boxes have an
+# awkward MSS of 536, which I use to tell one from another
+# in most difficult cases.
+
+8192:32:1:44:M*:			Windows:3.11::Windows 3.11 (Tucows)
+S44:64:1:64:M*,N,W0,N,N,T0,N,N,S:	Windows:95::Windows 95
+8192:128:1:64:M*,N,W0,N,N,T0,N,N,S:	Windows:95:b:Windows 95b
+
+# There were so many tweaking tools and so many stack versions for
+# Windows 98 it is no longer possible to tell them from each other
+# without some very serious research. Until then, there's an insane
+# number of signatures, for your amusement:
+
+S44:32:1:48:M*,N,N,S:			Windows:98:lowTTL:Windows 98 (low TTL)
+8192:32:1:48:M*,N,N,S:			Windows:98:lowTTL:Windows 98 (low TTL)
+%8192:64:1:48:M536,N,N,S:		Windows:98::Windows 98
+%8192:128:1:48:M536,N,N,S:		Windows:98::Windows 98
+S4:64:1:48:M*,N,N,S:			Windows:98::Windows 98
+S6:64:1:48:M*,N,N,S:			Windows:98::Windows 98
+S12:64:1:48:M*,N,N,S:			Windows:98::Windows 98
+T30:64:1:64:M1460,N,W0,N,N,T0,N,N,S:	Windows:98::Windows 98
+32767:64:1:48:M*,N,N,S:			Windows:98::Windows 98
+37300:64:1:48:M*,N,N,S:			Windows:98::Windows 98
+46080:64:1:52:M*,N,W3,N,N,S:		Windows:98:RFC1323:Windows 98 (RFC1323)
+65535:64:1:44:M*:			Windows:98:noSack:Windows 98 (no sack)
+S16:128:1:48:M*,N,N,S:			Windows:98::Windows 98
+S16:128:1:64:M*,N,W0,N,N,T0,N,N,S:	Windows:98::Windows 98
+S26:128:1:48:M*,N,N,S:			Windows:98::Windows 98
+T30:128:1:48:M*,N,N,S:			Windows:98::Windows 98
+32767:128:1:52:M*,N,W0,N,N,S:		Windows:98::Windows 98
+60352:128:1:48:M*,N,N,S:		Windows:98::Windows 98
+60352:128:1:64:M*,N,W2,N,N,T0,N,N,S:	Windows:98::Windows 98
+
+# What's with 1414 on NT?
+T31:128:1:44:M1414:			Windows:NT:4.0:Windows NT 4.0 SP6a
+64512:128:1:44:M1414:			Windows:NT:4.0:Windows NT 4.0 SP6a
+8192:128:1:44:M*:			Windows:NT:4.0:Windows NT 4.0 (older)
+
+# Windows XP and 2000. Most of the signatures that were
+# either dubious or non-specific (no service pack data)
+# were deleted and replaced with generics at the end.
+
+65535:128:1:48:M*,N,N,S:		Windows:2000:SP4:Windows 2000 SP4, XP SP1
+65535:128:1:48:M*,N,N,S:		Windows:XP:SP1:Windows 2000 SP4, XP SP1
+%8192:128:1:48:M*,N,N,S:		Windows:2000:SP2+:Windows 2000 SP2, XP SP1 (seldom 98 4.10.2222)
+%8192:128:1:48:M*,N,N,S:		Windows:XP:SP1:Windows 2000 SP2, XP SP1 (seldom 98 4.10.2222)
+S20:128:1:48:M*,N,N,S:			Windows:2000::Windows 2000/XP SP3
+S20:128:1:48:M*,N,N,S:			Windows:XP:SP3:Windows 2000/XP SP3
+S45:128:1:48:M*,N,N,S:			Windows:2000:SP4:Windows 2000 SP4, XP SP 1
+S45:128:1:48:M*,N,N,S:			Windows:XP:SP1:Windows 2000 SP4, XP SP 1
+40320:128:1:48:M*,N,N,S:		Windows:2000:SP4:Windows 2000 SP4
+
+S6:128:1:48:M*,N,N,S:			Windows:2000:SP2:Windows XP, 2000 SP2+
+S6:128:1:48:M*,N,N,S:			Windows:XP::Windows XP, 2000 SP2+
+S12:128:1:48:M*,N,N,S:			Windows:XP:SP1:Windows XP SP1
+S44:128:1:48:M*,N,N,S:			Windows:2000:SP3:Windows Pro SP1, 2000 SP3
+S44:128:1:48:M*,N,N,S:			Windows:XP:SP1:Windows Pro SP1, 2000 SP3
+64512:128:1:48:M*,N,N,S:		Windows:2000:SP3:Windows SP1, 2000 SP3
+64512:128:1:48:M*,N,N,S:		Windows:XP:SP1:Windows SP1, 2000 SP3
+32767:128:1:48:M*,N,N,S:		Windows:2000:SP4:Windows SP1, 2000 SP4
+32767:128:1:48:M*,N,N,S:		Windows:XP:SP1:Windows SP1, 2000 SP4
+
+8192:128:1:52:M*,N,W2,N,N,S:		Windows:Vista::Windows Vista/7
+
+# Odds, ends, mods:
+
+S52:128:1:48:M1260,N,N,S:		Windows:2000:cisco:Windows XP/2000 via Cisco
+S52:128:1:48:M1260,N,N,S:		Windows:XP:cisco:Windows XP/2000 via Cisco
+65520:128:1:48:M*,N,N,S:		Windows:XP::Windows XP bare-bone
+16384:128:1:52:M536,N,W0,N,N,S:		Windows:2000:ZoneAlarm:Windows 2000 w/ZoneAlarm?
+2048:255:0:40:.:			Windows:.NET::Windows .NET Enterprise Server
+
+44620:64:0:48:M*,N,N,S:			Windows:ME::Windows ME no SP (?)
+S6:255:1:48:M536,N,N,S:			Windows:95:winsock2:Windows 95 winsock 2
+32768:32:1:52:M1460,N,W0,N,N,S:		Windows:2003:AS:Windows 2003 AS
+
+
+# No need to be more specific, it passes:
+# *:128:1:48:M*,N,N,S:U:-Windows:XP/2000 while downloading (leak!) XXX quirk
+# there is an equiv similar generic sig w/o the quirk
+
+# ----------------- HP/UX -------------------
+
+32768:64:1:44:M*:			HP-UX:B.10.20::HP-UX B.10.20
+32768:64:0:48:M*,W0,N:			HP-UX:11.0::HP-UX 11.0
+32768:64:1:48:M*,W0,N:			HP-UX:11.10::HP-UX 11.0 or 11.11
+32768:64:1:48:M*,W0,N:			HP-UX:11.11::HP-UX 11.0 or 11.11
+
+# Whoa. Hardcore WSS.
+0:64:0:48:M*,W0,N:			HP-UX:B.11.00:A:HP-UX B.11.00 A (RFC1323)
+
+# ----------------- RiscOS ------------------
+
+# We don't yet support the ?12 TCP option
+#16384:64:1:68:M1460,N,W0,N,N,T,N,N,?12:	RISCOS:3.70-4.36::RISC OS 3.70-4.36
+12288:32:0:44:M536:				RISC OS:3.70:4.10:RISC OS 3.70 inet 4.10
+
+# XXX quirk
+# 4096:64:1:56:M1460,N,N,T:T:			RISC OS:3.70:freenet:RISC OS 3.70 freenet 2.00
+
+
+
+# ----------------- BSD/OS ------------------
+
+# Once again, power of two WSS is also shared by MacOS X with DF set
+8192:64:1:60:M1460,N,W0,N,N,T:		BSD/OS:3.1::BSD/OS 3.1-4.3 (or MacOS X 10.2 w/DF)
+8192:64:1:60:M1460,N,W0,N,N,T:		BSD/OS:4.0-4.3::BSD/OS 3.1-4.3 (or MacOS X 10.2)
+
+
+# ---------------- NewtonOS -----------------
+
+4096:64:0:44:M1420:		NewtonOS:2.1::NewtonOS 2.1
+
+# ---------------- NeXTSTEP -----------------
+
+S4:64:0:44:M1024:		NeXTSTEP:3.3::NeXTSTEP 3.3
+S8:64:0:44:M512:		NeXTSTEP:3.3::NeXTSTEP 3.3
+
+# ------------------ BeOS -------------------
+
+1024:255:0:48:M*,N,W0:		BeOS:5.0-5.1::BeOS 5.0-5.1
+12288:255:0:44:M1402:		BeOS:5.0::BeOS 5.0.x
+
+# ------------------ OS/400 -----------------
+
+8192:64:1:60:M1440,N,W0,N,N,T:	OS/400:VR4::OS/400 VR4/R5
+8192:64:1:60:M1440,N,W0,N,N,T:	OS/400:VR5::OS/400 VR4/R5
+4096:64:1:60:M1440,N,W0,N,N,T:	OS/400:V4R5:CF67032:OS/400 V4R5 + CF67032
+
+# XXX quirk
+# 28672:64:0:44:M1460:A:OS/390:?
+
+# ------------------ ULTRIX -----------------
+
+16384:64:0:40:.:		ULTRIX:4.5::ULTRIX 4.5
+
+# ------------------- QNX -------------------
+
+S16:64:0:44:M512:		QNX:::QNX demodisk
+
+# ------------------ Novell -----------------
+
+16384:128:1:44:M1460:		Novell:NetWare:5.0:Novel Netware 5.0
+6144:128:1:44:M1460:		Novell:IntranetWare:4.11:Novell IntranetWare 4.11
+6144:128:1:44:M1368:		Novell:BorderManager::Novell BorderManager ?
+
+6144:128:1:52:M*,W0,N,S,N,N:	Novell:Netware:6:Novell Netware 6 SP3
+
+
+# ----------------- SCO ------------------
+S3:64:1:60:M1460,N,W0,N,N,T:	SCO:UnixWare:7.1:SCO UnixWare 7.1
+S17:64:1:60:M1380,N,W0,N,N,T:	SCO:UnixWare:7.1:SCO UnixWare 7.1.3 MP3
+S23:64:1:44:M1380:		SCO:OpenServer:5.0:SCO OpenServer 5.0
+
+# ------------------- DOS -------------------
+
+2048:255:0:44:M536:		DOS:WATTCP:1.05:DOS Arachne via WATTCP/1.05
+T2:255:0:44:M984:		DOS:WATTCP:1.05Arachne:Arachne via WATTCP/1.05 (eepro)
+
+# ------------------ OS/2 -------------------
+
+S56:64:0:44:M512:		OS/2:4::OS/2 4
+28672:64:0:44:M1460:		OS/2:4::OS/2 Warp 4.0
+
+# ----------------- TOPS-20 -----------------
+
+# Another hardcore MSS, one of the ACK leakers hunted down.
+# XXX QUIRK 0:64:0:44:M1460:A:TOPS-20:version 7
+0:64:0:44:M1460:		TOPS-20:7::TOPS-20 version 7
+
+# ----------------- FreeMiNT ----------------
+
+S44:255:0:44:M536:		FreeMiNT:1:16A:FreeMiNT 1 patch 16A (Atari)
+
+# ------------------ AMIGA ------------------
+
+# XXX TCP option 12
+# S32:64:1:56:M*,N,N,S,N,N,?12:.:AMIGA:3.9 BB2 with Miami stack
+
+# ------------------ Plan9 ------------------
+
+65535:255:0:48:M1460,W0,N:	Plan9:4::Plan9 edition 4
+
+# ----------------- AMIGAOS -----------------
+
+16384:64:1:48:M1560,N,N,S:	AMIGAOS:3.9::AMIGAOS 3.9 BB2 MiamiDX
+
+###########################################
+# Appliance / embedded / other signatures #
+###########################################
+
+# ---------- Firewalls / routers ------------
+
+S12:64:1:44:M1460:			@Checkpoint:::Checkpoint (unknown 1)
+S12:64:1:48:N,N,S,M1460:		@Checkpoint:::Checkpoint (unknown 2)
+4096:32:0:44:M1460:			ExtremeWare:4.x::ExtremeWare 4.x
+
+# XXX TCP option 12
+# S32:64:0:68:M512,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO w/Checkpoint NG FP3
+# S16:64:0:68:M1024,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO 3.7 build 026
+
+S4:64:1:60:W0,N,S,T,M1460:		FortiNet:FortiGate:50:FortiNet FortiGate 50
+
+8192:64:1:44:M1460:			Eagle:::Eagle Secure Gateway
+
+S52:128:1:48:M1260,N,N,N,N:		LinkSys:WRV54G::LinkSys WRV54G VPN router
+
+
+
+# ------- Switches and other stuff ----------
+
+4128:255:0:44:M*:			Cisco:::Cisco Catalyst 3500, 7500 etc
+S8:255:0:44:M*:				Cisco:12008::Cisco 12008
+60352:128:1:64:M1460,N,W2,N,N,T,N,N,S:	Alteon:ACEswitch::Alteon ACEswitch
+64512:128:1:44:M1370:			Nortel:Contivity Client::Nortel Conectivity Client
+
+
+# ---------- Caches and whatnots ------------
+
+S4:64:1:52:M1460,N,N,S,N,W0:		AOL:web cache::AOL web cache
+
+32850:64:1:64:N,W1,N,N,T,N,N,S,M*:	NetApp:5.x::NetApp Data OnTap 5.x
+16384:64:1:64:M1460,N,N,S,N,W0,N:	NetApp:5.3:1:NetApp 5.3.1
+65535:64:0:64:M1460,N,N,S,N,W*,N,N,T:	NetApp:5.3-5.5::NetApp 5.3-5.5
+65535:64:0:60:M1460,N,W0,N,N,T:		NetApp:CacheFlow::NetApp CacheFlow
+8192:64:1:64:M1460,N,N,S,N,W0,N,N,T:	NetApp:5.2:1:NetApp NetCache 5.2.1
+20480:64:1:64:M1460,N,N,S,N,W0,N,N,T:	NetApp:4.1::NetApp NetCache4.1
+
+65535:64:0:60:M1460,N,W0,N,N,T:		CacheFlow:4.1::CacheFlow CacheOS 4.1
+8192:64:0:60:M1380,N,N,N,N,N,N,T:	CacheFlow:1.1::CacheFlow CacheOS 1.1
+
+S4:64:0:48:M1460,N,N,S:			Cisco:Content Engine::Cisco Content Engine
+
+27085:128:0:40:.:			Dell:PowerApp cache::Dell PowerApp (Linux-based)
+
+65535:255:1:48:N,W1,M1460:		Inktomi:crawler::Inktomi crawler
+S1:255:1:60:M1460,S,T,N,W0:		LookSmart:ZyBorg::LookSmart ZyBorg
+
+16384:255:0:40:.:			Proxyblocker:::Proxyblocker (what's this?)
+
+65535:255:0:48:M*,N,N,S:		Redline:::Redline T|X 2200
+
+32696:128:0:40:M1460:			Spirent:Avalanche::Spirent Web Avalanche HTTP benchmarking engine
+
+# ----------- Embedded systems --------------
+
+S9:255:0:44:M536:			PalmOS:Tungsten:C:PalmOS Tungsten C
+S5:255:0:44:M536:			PalmOS:3::PalmOS 3/4
+S5:255:0:44:M536:			PalmOS:4::PalmOS 3/4
+S4:255:0:44:M536:			PalmOS:3:5:PalmOS 3.5
+2948:255:0:44:M536:			PalmOS:3:5:PalmOS 3.5.3 (Handera)
+S29:255:0:44:M536:			PalmOS:5::PalmOS 5.0
+16384:255:0:44:M1398:			PalmOS:5.2:Clie:PalmOS 5.2 (Clie)
+S14:255:0:44:M1350:			PalmOS:5.2:Treo:PalmOS 5.2.1 (Treo)
+
+S23:64:1:64:N,W1,N,N,T,N,N,S,M1460:	SymbianOS:7::SymbianOS 7
+
+8192:255:0:44:M1460:			SymbianOS:6048::Symbian OS 6048 (Nokia 7650?)
+8192:255:0:44:M536:			SymbianOS:9210::Symbian OS (Nokia 9210?)
+S22:64:1:56:M1460,T,S:			SymbianOS:P800::Symbian OS ? (SE P800?)
+S36:64:1:56:M1360,T,S:			SymbianOS:6600::Symbian OS 60xx (Nokia 6600?)
+
+
+# Perhaps S4?
+5840:64:1:60:M1452,S,T,N,W1:		Zaurus:3.10::Zaurus 3.10
+
+32768:128:1:64:M1460,N,W0,N,N,T0,N,N,S:	PocketPC:2002::PocketPC 2002
+
+S1:255:0:44:M346:			Contiki:1.1:rc0:Contiki 1.1-rc0
+
+4096:128:0:44:M1460:			Sega:Dreamcast:3.0:Sega Dreamcast Dreamkey 3.0
+T5:64:0:44:M536:			Sega:Dreamcast:HKT-3020:Sega Dreamcast HKT-3020 (browser disc 51027)
+S22:64:1:44:M1460:			Sony:PS2::Sony Playstation 2 (SOCOM?)
+
+S12:64:0:44:M1452:			AXIS:5600:v5.64:AXIS Printer Server 5600 v5.64
+
+3100:32:1:44:M1460:			Windows:CE:2.0:Windows CE 2.0
+
+####################
+# Fancy signatures #
+####################
+
+1024:64:0:40:.:				*NMAP:syn scan:1:NMAP syn scan (1)
+2048:64:0:40:.:				*NMAP:syn scan:2:NMAP syn scan (2)
+3072:64:0:40:.:				*NMAP:syn scan:3:NMAP syn scan (3)
+4096:64:0:40:.:				*NMAP:syn scan:4:NMAP syn scan (4)
+
+# Requires quirks support
+# 1024:64:0:40:.:A:*NMAP:TCP sweep probe (1)
+# 2048:64:0:40:.:A:*NMAP:TCP sweep probe (2)
+# 3072:64:0:40:.:A:*NMAP:TCP sweep probe (3)
+# 4096:64:0:40:.:A:*NMAP:TCP sweep probe (4)
+
+1024:64:0:60:W10,N,M265,T:		*NMAP:OS:1:NMAP OS detection probe (1)
+2048:64:0:60:W10,N,M265,T:		*NMAP:OS:2:NMAP OS detection probe (2)
+3072:64:0:60:W10,N,M265,T:		*NMAP:OS:3:NMAP OS detection probe (3)
+4096:64:0:60:W10,N,M265,T:		*NMAP:OS:4:NMAP OS detection probe (4)
+
+32767:64:0:40:.:			*NAST:::NASTsyn scan
+
+# Requires quirks support
+# 12345:255:0:40:.:A:-p0f:sendsyn utility
+
+
+#####################################
+# Generic signatures - just in case #
+#####################################
+
+#*:64:1:60:M*,N,W*,N,N,T:		@FreeBSD:4.0-4.9::FreeBSD 4.x/5.x
+#*:64:1:60:M*,N,W*,N,N,T:		@FreeBSD:5.0-5.1::FreeBSD 4.x/5.x
+
+*:128:1:52:M*,N,W0,N,N,S:		@Windows:XP:RFC1323:Windows XP/2000 (RFC1323 no tstamp)
+*:128:1:52:M*,N,W0,N,N,S:		@Windows:2000:RFC1323:Windows XP/2000 (RFC1323 no tstamp)
+*:128:1:52:M*,N,W*,N,N,S:		@Windows:XP:RFC1323:Windows XP/2000 (RFC1323 no tstamp)
+*:128:1:52:M*,N,W*,N,N,S:		@Windows:2000:RFC1323:Windows XP/2000 (RFC1323 no tstamp)
+*:128:1:64:M*,N,W0,N,N,T0,N,N,S:	@Windows:XP:RFC1323:Windows XP/2000 (RFC1323)
+*:128:1:64:M*,N,W0,N,N,T0,N,N,S:	@Windows:2000:RFC1323:Windows XP/2000 (RFC1323)
+*:128:1:64:M*,N,W*,N,N,T0,N,N,S:	@Windows:XP:RFC1323:Windows XP (RFC1323, w+)
+*:128:1:48:M536,N,N,S:			@Windows:98::Windows 98
+*:128:1:48:M*,N,N,S:			@Windows:XP::Windows XP/2000
+*:128:1:48:M*,N,N,S:			@Windows:2000::Windows XP/2000
+
+
diff --git a/xlate-test.py b/xlate-test.py
new file mode 100755
index 0000000..4c014f9
--- /dev/null
+++ b/xlate-test.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+# encoding: utf-8
+
+import os
+import sys
+import shlex
+import argparse
+from subprocess import Popen, PIPE
+
+keywords = ("iptables-translate", "ip6tables-translate", "ebtables-translate")
+xtables_nft_multi = 'xtables-nft-multi'
+
+if sys.stdout.isatty():
+    colors = {"magenta": "\033[95m", "green": "\033[92m", "yellow": "\033[93m",
+              "red": "\033[91m", "end": "\033[0m"}
+else:
+    colors = {"magenta": "", "green": "", "yellow": "", "red": "", "end": ""}
+
+
+def magenta(string):
+    return colors["magenta"] + string + colors["end"]
+
+
+def red(string):
+    return colors["red"] + string + colors["end"]
+
+
+def yellow(string):
+    return colors["yellow"] + string + colors["end"]
+
+
+def green(string):
+    return colors["green"] + string + colors["end"]
+
+
+def run_test(name, payload):
+    global xtables_nft_multi
+    test_passed = True
+    tests = passed = failed = errors = 0
+    result = []
+
+    for line in payload:
+        if line.startswith(keywords):
+            tests += 1
+            process = Popen([ xtables_nft_multi ] + shlex.split(line), stdout=PIPE, stderr=PIPE)
+            (output, error) = process.communicate()
+            if process.returncode == 0:
+                translation = output.decode("utf-8").rstrip(" \n")
+                expected = next(payload).rstrip(" \n")
+                if translation != expected:
+                    test_passed = False
+                    failed += 1
+                    result.append(name + ": " + red("Fail"))
+                    result.append(magenta("src: ") + line.rstrip(" \n"))
+                    result.append(magenta("exp: ") + expected)
+                    result.append(magenta("res: ") + translation + "\n")
+                    test_passed = False
+                else:
+                    passed += 1
+            else:
+                test_passed = False
+                errors += 1
+                result.append(name + ": " + red("Error: ") + "iptables-translate failure")
+                result.append(error.decode("utf-8"))
+    if (passed == tests) and not args.test:
+        print(name + ": " + green("OK"))
+    if not test_passed:
+        print("\n".join(result))
+    if args.test:
+        print("1 test file, %d tests, %d tests passed, %d tests failed, %d errors" % (tests, passed, failed, errors))
+    else:
+        return tests, passed, failed, errors
+
+
+def load_test_files():
+    test_files = total_tests = total_passed = total_error = total_failed = 0
+    for test in sorted(os.listdir("extensions")):
+        if test.endswith(".txlate"):
+            with open("extensions/" + test, "r") as payload:
+                tests, passed, failed, errors = run_test(test, payload)
+                test_files += 1
+                total_tests += tests
+                total_passed += passed
+                total_failed += failed
+                total_error += errors
+
+
+    print("%d test files, %d tests, %d tests passed, %d tests failed, %d errors" % (test_files, total_tests, total_passed, total_failed, total_error))
+
+def main():
+    global xtables_nft_multi
+    if not args.host:
+        os.putenv("XTABLES_LIBDIR", os.path.abspath("extensions"))
+        xtables_nft_multi = os.path.abspath(os.path.curdir) \
+                            + '/iptables/' + xtables_nft_multi
+
+    if args.test:
+        if not args.test.endswith(".txlate"):
+            args.test += ".txlate"
+        try:
+            with open(args.test, "r") as payload:
+                run_test(args.test, payload)
+        except IOError:
+            print(red("Error: ") + "test file does not exist")
+    else:
+        load_test_files()
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-H', '--host', action='store_true',
+                    help='Run tests against installed binaries')
+parser.add_argument("test", nargs="?", help="run only the specified test file")
+args = parser.parse_args()
+main()