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