Project import generated by Copybara.

GitOrigin-RevId: d28750d2694715fda73560ae6bda40d25f9c059b
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..901467b
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,130 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# clang-format configuration file. Intended for clang-format >= 4.
+#
+# For more information, see:
+#
+#   Documentation/process/clang-format.rst
+#   https://clang.llvm.org/docs/ClangFormat.html
+#   https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+#
+---
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+#AlignEscapedNewlines: Left # Unknown to clang-format-4.0
+AlignOperands: true
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+  AfterClass: false
+  AfterControlStatement: false
+  AfterEnum: false
+  AfterFunction: true
+  AfterNamespace: true
+  AfterObjCDeclaration: false
+  AfterStruct: false
+  AfterUnion: false
+  #AfterExternBlock: false # Unknown to clang-format-5.0
+  BeforeCatch: false
+  BeforeElse: false
+  IndentBraces: false
+  #SplitEmptyFunction: true # Unknown to clang-format-4.0
+  #SplitEmptyRecord: true # Unknown to clang-format-4.0
+  #SplitEmptyNamespace: true # Unknown to clang-format-4.0
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+#CompactNamespaces: false # Unknown to clang-format-4.0
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 8
+ContinuationIndentWidth: 8
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+#FixNamespaceComments: false # Unknown to clang-format-4.0
+
+# Taken from:
+#   git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \
+#   | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$,  - '\1'," \
+#   | sort | uniq
+ForEachMacros:
+  - 'list_for_each_entry'
+  - 'list_for_each_entry_safe'
+  - 'mnl_attr_for_each_nested'
+  - 'hlist_for_each'
+  - 'hlist_for_each_safe'
+  - 'hlist_for_each_entry'
+
+#IncludeBlocks: Preserve # Unknown to clang-format-5.0
+IncludeCategories:
+  - Regex: '.*'
+    Priority: 1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+#IndentPPDirectives: None # Unknown to clang-format-5.0
+IndentWidth: 8
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: Inner
+#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
+ObjCBlockIndentWidth: 8
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+
+# Taken from git's rules
+#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakString: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+
+PointerAlignment: Right
+ReflowComments: false
+SortIncludes: false
+#SortUsingDeclarations: false # Unknown to clang-format-4.0
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0
+#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0
+SpaceBeforeParens: ControlStatements
+#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp03
+TabWidth: 8
+UseTab: Always
+...
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e5234a3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+# locally generated
+Config
+static-syms.h
+config.*
+*.o
+*.a
+*.so
+*~
+\#*#
+
+# cscope
+cscope.*
+ncscope.*
+tags
+TAGS
+
+# git files that we don't want to ignore even it they are dot-files
+!.gitignore
+!.mailmap
+
+# for patch generation
+*.diff
+*.patch
+*.orig
+*.rej
+
+# for quilt
+.pc
+patches
+series
+
+# for gdb
+.gdbinit
+.gdb_history
+*.gdb
+
+# tests
+testsuite/results
+testsuite/iproute2/iproute2-this
+testsuite/tools/generate_nlmsg
+testsuite/tests/ip/link/dev_wo_vf_rate.nl
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..fd40c91
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,22 @@
+#
+# This list is used by git-shortlog to fix a few botched name translations
+# in the git archive, either because the author's full name was messed up
+# and/or not always written the same way, making contributions from the
+# same person appearing not to be so or badly displayed.
+#
+# Format
+#  Full name <goodaddress> <badaddress> 
+Steve Wise <larrystevenwise@gmail.com> <swise@opengridcomputing.com>
+Steve Wise <larrystevenwise@gmail.com> <swise@chelsio.com>
+
+Stephen Hemminger <stephen@networkplumber.org> <sthemmin@microsoft.com>
+Stephen Hemminger <stephen@networkplumber.org> <shemming@brocade.com>
+Stephen Hemminger <stephen@networkplumber.org> <stephen.hemminger@vyatta.com>
+Stephen Hemminger <stephen@networkplumber.org> <shemminger@vyatta.com>
+Stephen Hemminger <stephen@networkplumber.org> <shemminger>
+Stephen Hemminger <stephen@networkplumber.org> <shemminger@linux-foundation.org>
+Stephen Hemminger <stephen@networkplumber.org> <shemminger@osdl.org>
+Stephen Hemminger <stephen@networkplumber.org> <osdl.org!shemminger>
+Stephen Hemminger <stephen@networkplumber.org> <osdl.net!shemminger>
+
+David Ahern <dsahern@gmail.com> <dsa@cumulusnetworks.com>
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..5053e7d
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1 @@
+include $(call all-subdir-makefiles)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..3912109
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, 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 Library 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 St, 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 Library General
+Public License instead of this License.
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..b84e1b6
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0b79b1f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: GPL-2.0
+# Top level Makefile for iproute2
+
+ifeq ("$(origin V)", "command line")
+VERBOSE = $(V)
+endif
+ifndef VERBOSE
+VERBOSE = 0
+endif
+
+ifeq ($(VERBOSE),0)
+MAKEFLAGS += --no-print-directory
+endif
+
+PREFIX?=/usr
+LIBDIR?=$(PREFIX)/lib
+SBINDIR?=/sbin
+CONFDIR?=/etc/iproute2
+NETNS_RUN_DIR?=/var/run/netns
+NETNS_ETC_DIR?=/etc/netns
+DATADIR?=$(PREFIX)/share
+HDRDIR?=$(PREFIX)/include/iproute2
+DOCDIR?=$(DATADIR)/doc/iproute2
+MANDIR?=$(DATADIR)/man
+ARPDDIR?=/var/lib/arpd
+KERNEL_INCLUDE?=/usr/include
+BASH_COMPDIR?=$(DATADIR)/bash-completion/completions
+
+# Path to db_185.h include
+DBM_INCLUDE:=$(DESTDIR)/usr/include
+
+SHARED_LIBS = y
+
+DEFINES= -DRESOLVE_HOSTNAMES -DLIBDIR=\"$(LIBDIR)\"
+ifneq ($(SHARED_LIBS),y)
+DEFINES+= -DNO_SHARED_LIBS
+endif
+
+DEFINES+=-DCONFDIR=\"$(CONFDIR)\" \
+         -DNETNS_RUN_DIR=\"$(NETNS_RUN_DIR)\" \
+         -DNETNS_ETC_DIR=\"$(NETNS_ETC_DIR)\"
+
+#options for mpls
+ADDLIB+=mpls_ntop.o mpls_pton.o
+
+CC := gcc
+HOSTCC ?= $(CC)
+DEFINES += -D_GNU_SOURCE
+# Turn on transparent support for LFS
+DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
+CCOPTS = -O2 -pipe
+WFLAGS := -Wall -Wstrict-prototypes  -Wmissing-prototypes
+WFLAGS += -Wmissing-declarations -Wold-style-definition -Wformat=2
+
+CFLAGS := $(WFLAGS) $(CCOPTS) -I../include -I../include/uapi $(DEFINES) $(CFLAGS)
+YACCFLAGS = -d -t -v
+
+SUBDIRS=lib ip tc bridge misc netem genl tipc devlink rdma man
+
+LIBNETLINK=../lib/libutil.a ../lib/libnetlink.a
+LDLIBS += $(LIBNETLINK)
+
+all: config.mk
+	@set -e; \
+	for i in $(SUBDIRS); \
+	do echo; echo $$i; $(MAKE) $(MFLAGS) -C $$i; done
+
+help:
+	@echo "Make Targets:"
+	@echo " all                 - build binaries"
+	@echo " clean               - remove products of build"
+	@echo " distclean           - remove configuration and build"
+	@echo " install             - install binaries on local machine"
+	@echo " check               - run tests"
+	@echo " cscope              - build cscope database"
+	@echo " snapshot            - generate version number header"
+	@echo ""
+	@echo "Make Arguments:"
+	@echo " V=[0|1]             - set build verbosity level"
+
+config.mk:
+	sh configure $(KERNEL_INCLUDE)
+
+install: all
+	install -m 0755 -d $(DESTDIR)$(SBINDIR)
+	install -m 0755 -d $(DESTDIR)$(CONFDIR)
+	install -m 0755 -d $(DESTDIR)$(ARPDDIR)
+	install -m 0755 -d $(DESTDIR)$(HDRDIR)
+	@for i in $(SUBDIRS);  do $(MAKE) -C $$i install; done
+	install -m 0644 $(shell find etc/iproute2 -maxdepth 1 -type f) $(DESTDIR)$(CONFDIR)
+	install -m 0755 -d $(DESTDIR)$(BASH_COMPDIR)
+	install -m 0644 bash-completion/tc $(DESTDIR)$(BASH_COMPDIR)
+	install -m 0644 include/bpf_elf.h $(DESTDIR)$(HDRDIR)
+
+snapshot:
+	echo "static const char SNAPSHOT[] = \""`date +%y%m%d`"\";" \
+		> include/SNAPSHOT.h
+
+clean:
+	@for i in $(SUBDIRS) testsuite; \
+	do $(MAKE) $(MFLAGS) -C $$i clean; done
+
+clobber:
+	touch config.mk
+	$(MAKE) $(MFLAGS) clean
+	rm -f config.mk cscope.*
+
+distclean: clobber
+
+check: all
+	$(MAKE) -C testsuite
+	$(MAKE) -C testsuite alltests
+	@if command -v man >/dev/null 2>&1; then \
+		echo "Checking manpages for syntax errors..."; \
+		$(MAKE) -C man check; \
+	else \
+		echo "man not installed, skipping checks for syntax errors."; \
+	fi
+
+cscope:
+	cscope -b -q -R -Iinclude -sip -slib -smisc -snetem -stc
+
+.EXPORT_ALL_VARIABLES:
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/README b/README
new file mode 100644
index 0000000..bc82187
--- /dev/null
+++ b/README
@@ -0,0 +1,47 @@
+This is a set of utilities for Linux networking.
+
+Information:
+    https://wiki.linuxfoundation.org/networking/iproute2
+
+Download:
+    http://www.kernel.org/pub/linux/utils/net/iproute2/
+
+Stable version repository:
+    git://git.kernel.org/pub/scm/network/iproute2/iproute2.git
+
+Development repository:
+    git://git.kernel.org/pub/scm/network/iproute2/iproute2-next.git
+
+How to compile this.
+--------------------
+1. libdbm
+
+arpd needs to have the berkeleydb development libraries. For Debian
+users this is the package with a name like libdbX.X-dev.
+DBM_INCLUDE points to the directory with db_185.h which
+is the include file used by arpd to get to the old format Berkeley
+database routines.  Often this is in the db-devel package.
+
+2. make
+
+The makefile will automatically build a config.mk file which
+contains definitions of libraries that may or may not be available
+on the system such as: ATM, ELF, MNL, and SELINUX.
+
+3. To make documentation, cd to doc/ directory , then
+   look at start of Makefile and set correct values for
+   PAGESIZE=a4		, ie: a4 , letter ...	(string)
+   PAGESPERPAGE=2	, ie: 1 , 2 ...		(numeric)
+   and make there. It assumes, that latex, dvips and psnup
+   are in your path.
+
+4. This package includes matching sanitized kernel headers because
+   the build environment may not have up to date versions. See Makefile
+   if you have special requirements and need to point at different
+   kernel include files.
+
+Stephen Hemminger
+stephen@networkplumber.org
+
+Alexey Kuznetsov
+kuznet@ms2.inr.ac.ru
diff --git a/README.devel b/README.devel
new file mode 100644
index 0000000..60c9546
--- /dev/null
+++ b/README.devel
@@ -0,0 +1,18 @@
+Iproute2 development is closely tied to Linux kernel networking
+development. Most new features require a kernel and a utility component.
+
+Please submit both to the Linux networking mailing list
+   <netdev@vger.kernel.org>
+
+The current source for the stable version is in the git repository:
+    git://git.kernel.org/pub/scm/network/iproute2/iproute2.git
+
+The development git repository is available at the following address:
+    git://git.kernel.org/pub/scm/network/iproute2/iproute2-next.git
+
+The stable repository contains the source corresponding to the
+current code in the Linux networking tree (net), which in turn is
+aligned on the mainline Linux kernel (ie follows Linus).
+The iproute2-next repository tracks the code intended for the next
+release; it corresponds with networking development tree (net-next)
+in the kernel.
diff --git a/bash-completion/tc b/bash-completion/tc
new file mode 100644
index 0000000..007e1c2
--- /dev/null
+++ b/bash-completion/tc
@@ -0,0 +1,800 @@
+# tc(8) completion                                         -*- shell-script -*-
+# Copyright 2016 6WIND S.A.
+# Copyright 2016 Quentin Monnet <quentin.monnet@6wind.com>
+
+QDISC_KIND=' choke codel bfifo pfifo pfifo_head_drop fq fq_codel gred hhf \
+            mqprio multiq netem pfifo_fast pie red rr sfb sfq tbf atm cbq drr \
+            dsmark hfsc htb prio qfq '
+FILTER_KIND=' basic bpf cgroup flow flower fw route rsvp tcindex u32 matchall '
+ACTION_KIND=' gact mirred bpf sample '
+
+# Takes a list of words in argument; each one of them is added to COMPREPLY if
+# it is not already present on the command line. Returns no value.
+_tc_once_attr()
+{
+    local w subcword found
+    for w in $*; do
+        found=0
+        for (( subcword=3; subcword < ${#words[@]}-1; subcword++ )); do
+            if [[ $w == ${words[subcword]} ]]; then
+                found=1
+                break
+            fi
+        done
+        [[ $found -eq 0 ]] && \
+            COMPREPLY+=( $( compgen -W "$w" -- "$cur" ) )
+    done
+}
+
+# Takes a list of words in argument; each one of them is added to COMPREPLY if
+# it is not already present on the command line from the provided index. Returns
+# no value.
+_tc_once_attr_from()
+{
+    local w subcword found from=$1
+    shift
+    for w in $*; do
+        found=0
+        for (( subcword=$from; subcword < ${#words[@]}-1; subcword++ )); do
+            if [[ $w == ${words[subcword]} ]]; then
+                found=1
+                break
+            fi
+        done
+        [[ $found -eq 0 ]] && \
+            COMPREPLY+=( $( compgen -W "$w" -- "$cur" ) )
+    done
+}
+
+# Takes a list of words in argument; adds them all to COMPREPLY if none of them
+# is already present on the command line. Returns no value.
+_tc_one_of_list()
+{
+    local w subcword
+    for w in $*; do
+        for (( subcword=3; subcword < ${#words[@]}-1; subcword++ )); do
+            [[ $w == ${words[subcword]} ]] && return 1
+        done
+    done
+    COMPREPLY+=( $( compgen -W "$*" -- "$cur" ) )
+}
+
+# Takes a list of words in argument; adds them all to COMPREPLY if none of them
+# is already present on the command line from the provided index. Returns no
+# value.
+_tc_one_of_list_from()
+{
+    local w subcword from=$1
+    shift
+    for w in $*; do
+        for (( subcword=$from; subcword < ${#words[@]}-1; subcword++ )); do
+            [[ $w == ${words[subcword]} ]] && return 1
+        done
+    done
+    COMPREPLY+=( $( compgen -W "$*" -- "$cur" ) )
+}
+
+# Returns "$cur ${cur}arg1 ${cur}arg2 ..."
+_tc_expand_units()
+{
+    [[ $cur =~ ^[0-9]+ ]] || return 1
+    local value=${cur%%[^0-9]*}
+    [[ $cur == $value ]] && echo $cur
+    echo ${@/#/$value}
+}
+
+# Complete based on given word, usually $prev (or possibly the word before),
+# for when an argument or an option name has but a few possible arguments (so
+# tc does not take particular commands into account here).
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_direct_complete()
+{
+    case $1 in
+        # Command options
+        dev)
+            _available_interfaces
+            return 0
+            ;;
+        classid)
+            return 0
+            ;;
+        estimator)
+            local list=$( _tc_expand_units 'secs' 'msecs' 'usecs' )
+            COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
+            return 0
+            ;;
+        handle)
+            return 0
+            ;;
+        parent|flowid)
+            local i iface ids cmd
+            for (( i=3; i < ${#words[@]}-2; i++ )); do
+                [[ ${words[i]} == dev ]] && iface=${words[i+1]}
+                break
+            done
+            for cmd in qdisc class; do
+                if [[ -n $iface ]]; then
+                    ids+=$( tc $cmd show dev $iface 2>/dev/null | \
+                        cut -d\  -f 3 )" "
+                else
+                    ids+=$( tc $cmd show 2>/dev/null | cut -d\  -f 3 )
+                fi
+            done
+            [[ $ids != " " ]] && \
+                COMPREPLY+=( $( compgen -W "$ids" -- "$cur" ) )
+            return 0
+            ;;
+        protocol) # list comes from lib/ll_proto.c
+            COMPREPLY+=( $( compgen -W ' 802.1Q 802.1ad 802_2 802_3 LLDP aarp \
+                all aoe arp atalk atmfate atmmpoa ax25 bpq can control cust \
+                ddcmp dec diag dna_dl dna_rc dna_rt econet ieeepup ieeepupat \
+                ip ipv4 ipv6 ipx irda lat localtalk loop mobitex ppp_disc \
+                ppp_mp ppp_ses ppptalk pup pupat rarp sca snap tipc tr_802_2 \
+                wan_ppp x25' -- "$cur" ) )
+            return 0
+            ;;
+        prio)
+            return 0
+            ;;
+        stab)
+            COMPREPLY+=( $( compgen -W 'mtu tsize mpu overhead
+                linklayer' -- "$cur" ) )
+            ;;
+
+        # Qdiscs and classes options
+        alpha|bands|beta|buckets|corrupt|debug|decrement|default|\
+        default_index|depth|direct_qlen|divisor|duplicate|ewma|flow_limit|\
+        flows|hh_limit|increment|indices|linklayer|non_hh_weight|num_tc|\
+        penalty_burst|penalty_rate|prio|priomap|probability|queues|r2q|\
+        reorder|vq|vqs)
+            return 0
+            ;;
+        setup)
+            COMPREPLY+=( $( compgen -W 'vqs' -- "$cur" ) )
+            return 0
+            ;;
+        hw)
+            COMPREPLY+=( $( compgen -W '1 0' -- "$cur" ) )
+            return 0
+            ;;
+        distribution)
+            COMPREPLY+=( $( compgen -W 'uniform normal pareto
+                paretonormal' -- "$cur" ) )
+            return 0
+            ;;
+        loss)
+            COMPREPLY+=( $( compgen -W 'random state gmodel' -- "$cur" ) )
+            return 0
+            ;;
+
+        # Qdiscs and classes options options
+        gap|gmodel|state)
+            return 0
+            ;;
+
+        # Filters options
+        map)
+            COMPREPLY+=( $( compgen -W 'key' -- "$cur" ) )
+            return 0
+            ;;
+        hash)
+            COMPREPLY+=( $( compgen -W 'keys' -- "$cur" ) )
+            return 0
+            ;;
+        indev)
+            _available_interfaces
+            return 0
+            ;;
+        eth_type)
+            COMPREPLY+=( $( compgen -W 'ipv4 ipv6' -- "$cur" ) )
+            return 0
+            ;;
+        ip_proto)
+            COMPREPLY+=( $( compgen -W 'tcp udp' -- "$cur" ) )
+            return 0
+            ;;
+
+        # Filters options options
+        key|keys)
+            [[ ${words[@]} =~ graft ]] && return 1
+            COMPREPLY+=( $( compgen -W 'src dst proto proto-src proto-dst iif \
+                priority mark nfct nfct-src nfct-dst nfct-proto-src \
+                nfct-proto-dst rt-classid sk-uid sk-gid vlan-tag rxhash' -- \
+                "$cur" ) )
+            return 0
+            ;;
+
+        # BPF options - used for filters, actions, and exec
+        export|bytecode|bytecode-file|object-file)
+            _filedir
+            return 0
+            ;;
+        object-pinned|graft) # Pinned object is probably under /sys/fs/bpf/
+            [[ -n "$cur" ]] && _filedir && return 0
+            COMPREPLY=( $( compgen -G "/sys/fs/bpf/*" -- "$cur" ) ) || _filedir
+            compopt -o nospace
+            return 0
+            ;;
+        section)
+            if (type objdump > /dev/null 2>&1) ; then
+                local fword objfile section_list
+                for (( fword=3; fword < ${#words[@]}-3; fword++ )); do
+                    if [[ ${words[fword]} == object-file ]]; then
+                        objfile=${words[fword+1]}
+                        break
+                    fi
+                done
+                section_list=$( objdump -h $objfile 2>/dev/null | \
+                    sed -n 's/^ *[0-9]\+ \([^ ]*\) *.*/\1/p' )
+                COMPREPLY+=( $( compgen -W "$section_list" -- "$cur" ) )
+            fi
+            return 0
+            ;;
+        import|run)
+            _filedir
+            return 0
+            ;;
+        type)
+            COMPREPLY+=( $( compgen -W 'cls act' -- "$cur" ) )
+            return 0
+            ;;
+
+        # Actions options
+        random)
+            _tc_one_of_list 'netrand determ'
+            return 0
+            ;;
+
+        # Units for option arguments
+        bandwidth|maxrate|peakrate|rate)
+            local list=$( _tc_expand_units 'bit' \
+                'kbit' 'kibit' 'kbps' 'kibps' \
+                'mbit' 'mibit' 'mbps' 'mibps' \
+                'gbit' 'gibit' 'gbps' 'gibps' \
+                'tbit' 'tibit' 'tbps' 'tibps' )
+            COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
+            ;;
+        admit_bytes|avpkt|burst|cell|initial_quantum|limit|max|min|mtu|mpu|\
+        overhead|quantum|redflowlist)
+            local list=$( _tc_expand_units \
+                'b' 'kbit' 'k' 'mbit' 'm' 'gbit' 'g' )
+            COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
+            ;;
+        db|delay|evict_timeout|interval|latency|perturb|rehash|reset_timeout|\
+        target|tupdate)
+            local list=$( _tc_expand_units 'secs' 'msecs' 'usecs' )
+            COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
+            ;;
+    esac
+    return 1
+}
+
+# Complete with options names for qdiscs. Each qdisc has its own set of options
+# and it seems we cannot really parse it from anywhere, so we add it manually
+# in this function.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_qdisc_options()
+{
+    case $1 in
+        choke)
+            _tc_once_attr 'limit bandwidth ecn min max burst'
+            return 0
+            ;;
+        codel)
+            _tc_once_attr 'limit target interval'
+            _tc_one_of_list 'ecn noecn'
+            return 0
+            ;;
+        bfifo|pfifo|pfifo_head_drop)
+            _tc_once_attr 'limit'
+            return 0
+            ;;
+        fq)
+            _tc_once_attr 'limit flow_limit quantum initial_quantum maxrate \
+                buckets'
+            _tc_one_of_list 'pacing nopacing'
+            return 0
+            ;;
+        fq_codel)
+            _tc_once_attr 'limit flows target interval quantum'
+            _tc_one_of_list 'ecn noecn'
+            return 0
+            ;;
+        gred)
+            _tc_once_attr 'setup vqs default grio vq prio limit min max avpkt \
+                burst probability bandwidth ecn harddrop'
+            return 0
+            ;;
+        hhf)
+            _tc_once_attr 'limit quantum hh_limit reset_timeout admit_bytes \
+                evict_timeout non_hh_weight'
+            return 0
+            ;;
+        mqprio)
+            _tc_once_attr 'num_tc map queues hw'
+            return 0
+            ;;
+        netem)
+            _tc_once_attr 'delay distribution corrupt duplicate loss ecn \
+                reorder rate'
+            return 0
+            ;;
+        pie)
+            _tc_once_attr 'limit target tupdate alpha beta'
+            _tc_one_of_list 'bytemode nobytemode'
+            _tc_one_of_list 'ecn noecn'
+            return 0
+            ;;
+        red)
+            _tc_once_attr 'limit min max avpkt burst adaptive probability \
+                bandwidth ecn harddrop'
+            return 0
+            ;;
+        rr|prio)
+            _tc_once_attr 'bands priomap multiqueue'
+            return 0
+            ;;
+        sfb)
+            _tc_once_attr 'rehash db limit max target increment decrement \
+                penalty_rate penalty_burst'
+            return 0
+            ;;
+        sfq)
+            _tc_once_attr 'limit perturb quantum divisor flows depth headdrop \
+                redflowlimit min max avpkt burst probability ecn harddrop'
+            return 0
+            ;;
+        tbf)
+            _tc_once_attr 'limit burst rate mtu peakrate latency overhead \
+                linklayer'
+            return 0
+            ;;
+        cbq)
+            _tc_once_attr 'bandwidth avpkt mpu cell ewma'
+            return 0
+            ;;
+        dsmark)
+            _tc_once_attr 'indices default_index set_tc_index'
+            return 0
+            ;;
+        hfsc)
+            _tc_once_attr 'default'
+            return 0
+            ;;
+        htb)
+            _tc_once_attr 'default r2q direct_qlen debug'
+            return 0
+            ;;
+        multiq|pfifo_fast|atm|drr|qfq)
+            return 0
+            ;;
+    esac
+    return 1
+}
+
+# Complete with options names for BPF filters or actions.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_bpf_options()
+{
+    [[ ${words[${#words[@]}-3]} == object-file ]] && \
+        _tc_once_attr 'section export'
+    [[ ${words[${#words[@]}-5]} == object-file ]] && \
+        [[ ${words[${#words[@]}-3]} =~ (section|export) ]] && \
+        _tc_once_attr 'section export'
+    _tc_one_of_list 'bytecode bytecode-file object-file object-pinned'
+    _tc_once_attr 'verbose index direct-action action classid'
+    return 0
+}
+
+# Complete with options names for filter actions.
+# This function is recursive, thus allowing multiple actions statement to be
+# parsed.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_filter_action_options()
+{
+    for ((acwd=$1; acwd < ${#words[@]}-1; acwd++));
+    do
+        if [[ action == ${words[acwd]} ]]; then
+            _tc_filter_action_options $((acwd+1)) && return 0
+        fi
+    done
+
+    local action acwd
+    for ((acwd=$1; acwd < ${#words[@]}-1; acwd++)); do
+        if [[ $ACTION_KIND =~ ' '${words[acwd]}' ' ]]; then
+            _tc_one_of_list_from $acwd action
+            _tc_action_options $acwd && return 0
+        fi
+    done
+    _tc_one_of_list_from $acwd $ACTION_KIND
+    return 0
+}
+
+# Complete with options names for filters.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_filter_options()
+{
+
+    for ((acwd=$1; acwd < ${#words[@]}-1; acwd++));
+    do
+        if [[ action == ${words[acwd]} ]]; then
+            _tc_filter_action_options $((acwd+1)) && return 0
+        fi
+    done
+
+    filter=${words[$1]}
+    case $filter in
+        basic)
+            _tc_once_attr 'match action classid'
+            return 0
+            ;;
+        bpf)
+            _tc_bpf_options
+            return 0
+            ;;
+        cgroup)
+            _tc_once_attr 'match action'
+            return 0
+            ;;
+        flow)
+            local i
+            for (( i=5; i < ${#words[@]}-1; i++ )); do
+                if [[ ${words[i]} =~ ^keys?$ ]]; then
+                    _tc_direct_complete 'key'
+                    COMPREPLY+=( $( compgen -W 'or and xor rshift addend' -- \
+                        "$cur" ) )
+                    break
+                fi
+            done
+            _tc_once_attr 'map hash divisor baseclass match action'
+            return 0
+            ;;
+        matchall)
+            _tc_once_attr 'action classid skip_sw skip_hw'
+            return 0
+            ;;
+        flower)
+            _tc_once_attr 'action classid indev dst_mac src_mac eth_type \
+                ip_proto dst_ip src_ip dst_port src_port'
+            return 0
+            ;;
+        fw)
+            _tc_once_attr 'action classid'
+            return 0
+            ;;
+        route)
+            _tc_one_of_list 'from fromif'
+            _tc_once_attr 'to classid action'
+            return 0
+            ;;
+        rsvp)
+            _tc_once_attr 'ipproto session sender classid action tunnelid \
+                tunnel flowlabel spi/ah spi/esp u8 u16 u32'
+            [[ ${words[${#words[@]}-3]} == tunnel ]] && \
+                    COMPREPLY+=( $( compgen -W 'skip' -- "$cur" ) )
+            [[ ${words[${#words[@]}-3]} =~ u(8|16|32) ]] && \
+                    COMPREPLY+=( $( compgen -W 'mask' -- "$cur" ) )
+            [[ ${words[${#words[@]}-3]} == mask ]] && \
+                    COMPREPLY+=( $( compgen -W 'at' -- "$cur" ) )
+            return 0
+            ;;
+        tcindex)
+            _tc_once_attr 'hash mask shift classid action'
+            _tc_one_of_list 'pass_on fall_through'
+            return 0
+            ;;
+        u32)
+            _tc_once_attr 'match link classid action offset ht hashkey sample'
+            COMPREPLY+=( $( compgen -W 'ip ip6 udp tcp icmp u8 u16 u32 mark \
+                divisor' -- "$cur" ) )
+            return 0
+            ;;
+    esac
+    return 1
+}
+
+# Complete with options names for actions.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_action_options()
+{
+    local from=$1
+    local action=${words[from]}
+    case $action in
+        bpf)
+            _tc_bpf_options
+            return 0
+            ;;
+        mirred)
+            _tc_one_of_list_from $from 'ingress egress'
+            _tc_one_of_list_from $from 'mirror redirect'
+            _tc_once_attr_from $from 'index dev'
+            return 0
+            ;;
+        sample)
+            _tc_once_attr_from $from 'rate'
+            _tc_once_attr_from $from 'trunc'
+            _tc_once_attr_from $from 'group'
+            return 0
+            ;;
+        gact)
+            _tc_one_of_list_from $from 'reclassify drop continue pass'
+            _tc_once_attr_from $from 'random'
+            return 0
+            ;;
+    esac
+    return 1
+}
+
+# Complete with options names for exec.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_exec_options()
+{
+    case $1 in
+        import)
+            [[ ${words[${#words[@]}-3]} == import ]] && \
+                _tc_once_attr 'run'
+            return 0
+            ;;
+        graft)
+            COMPREPLY+=( $( compgen -W 'key type' -- "$cur" ) )
+            [[ ${words[${#words[@]}-3]} == object-file ]] && \
+                _tc_once_attr 'type'
+            _tc_bpf_options
+            return 0
+            ;;
+    esac
+    return 1
+}
+
+# Main completion function
+# Logic is as follows:
+#   1. Check if previous word is a global option; if so, propose arguments.
+#   2. Check if current word is a global option; if so, propose completion.
+#   3. Check for the presence of a main command (qdisc|class|filter|...). If
+#      there is one, first call _tc_direct_complete to see if previous word is
+#      waiting for a particular completion. If so, propose completion and exit.
+#   4. Extract main command and -- if available -- its subcommand
+#      (add|delete|show|...).
+#   5. Propose completion based on main and sub- command in use. Additional
+#      functions may be called for qdiscs, classes or filter options.
+_tc()
+{
+    local cur prev words cword
+    _init_completion || return
+
+    case $prev in
+        -V|-Version)
+            return 0
+            ;;
+        -b|-batch|-cf|-conf)
+            _filedir
+            return 0
+            ;;
+        -force)
+            COMPREPLY=( $( compgen -W '-batch' -- "$cur" ) )
+            return 0
+            ;;
+        -nm|name)
+            [[ -r /etc/iproute2/tc_cls ]] || \
+                COMPREPLY=( $( compgen -W '-conf' -- "$cur" ) )
+            return 0
+            ;;
+        -n|-net|-netns)
+            local nslist=$( ip netns list 2>/dev/null )
+            COMPREPLY+=( $( compgen -W "$nslist" -- "$cur" ) )
+            return 0
+            ;;
+        -tshort)
+            _tc_once_attr '-statistics'
+            COMPREPLY+=( $( compgen -W 'monitor' -- "$cur" ) )
+            return 0
+            ;;
+        -timestamp)
+            _tc_once_attr '-statistics -tshort'
+            COMPREPLY+=( $( compgen -W 'monitor' -- "$cur" ) )
+            return 0
+            ;;
+    esac
+
+    # Search for main commands
+    local subcword cmd subcmd
+    for (( subcword=1; subcword < ${#words[@]}-1; subcword++ )); do
+        [[ ${words[subcword]} == -b?(atch) ]] && return 0
+        [[ -n $cmd ]] && subcmd=${words[subcword]} && break
+        [[ ${words[subcword]} != -* && \
+            ${words[subcword-1]} != -@(n?(et?(ns))|c?(on)f) ]] && \
+            cmd=${words[subcword]}
+    done
+
+    if [[ -z $cmd ]]; then
+        case $cur in
+            -*)
+                local c='-Version -statistics -details -raw -pretty \
+                    -iec -graphe -batch -name -netns -timestamp'
+                [[ $cword -eq 1 ]] && c+=' -force'
+                COMPREPLY=( $( compgen -W "$c" -- "$cur" ) )
+                return 0
+                ;;
+            *)
+                COMPREPLY=( $( compgen -W "help $( tc help 2>&1 | \
+                    command sed \
+                    -e '/OBJECT := /!d' \
+                    -e 's/.*{//' \
+                    -e 's/}.*//' \
+                    -e \ 's/|//g' )" -- "$cur" ) )
+                return 0
+                ;;
+        esac
+    fi
+
+    [[ $subcmd == help ]] && return 0
+
+    # For this set of commands we may create COMPREPLY just by analysing the
+    # previous word, if it expects for a specific list of options or values.
+    if [[ $cmd =~ (qdisc|class|filter|action|exec) ]]; then
+        _tc_direct_complete $prev && return 0
+        if [[ ${words[${#words[@]}-3]} == estimator ]]; then
+            local list=$( _tc_expand_units 'secs' 'msecs' 'usecs' )
+            COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) ) && return 0
+        fi
+    fi
+
+    # Completion depends on main command and subcommand in use.
+    case $cmd in
+        qdisc)
+            case $subcmd in
+                add|change|replace|link|del|delete)
+                    if [[ $(($cword-$subcword)) -eq 1 ]]; then
+                        COMPREPLY=( $( compgen -W 'dev' -- "$cur" ) )
+                        return 0
+                    fi
+                    local qdisc qdwd
+                    for ((qdwd=$subcword; qdwd < ${#words[@]}-1; qdwd++)); do
+                        if [[ $QDISC_KIND =~ ' '${words[qdwd]}' ' ]]; then
+                            qdisc=${words[qdwd]}
+                            _tc_qdisc_options $qdisc && return 0
+                        fi
+                    done
+                    _tc_one_of_list $QDISC_KIND
+                    _tc_one_of_list 'root ingress parent clsact'
+                    _tc_once_attr 'handle estimator stab'
+                    ;;
+                show)
+                    _tc_once_attr 'dev'
+                    _tc_one_of_list 'ingress clsact'
+                    _tc_once_attr '-statistics -details -raw -pretty -iec \
+                        -graph -name'
+                    ;;
+                help)
+                    return 0
+                    ;;
+                *)
+                    [[ $cword -eq $subcword ]] && \
+                        COMPREPLY=( $( compgen -W 'help add delete change \
+                            replace link show' -- "$cur" ) )
+                    ;;
+            esac
+            ;;
+
+        class)
+            case $subcmd in
+                add|change|replace|del|delete)
+                    if [[ $(($cword-$subcword)) -eq 1 ]]; then
+                        COMPREPLY=( $( compgen -W 'dev' -- "$cur" ) )
+                        return 0
+                    fi
+                    local qdisc qdwd
+                    for ((qdwd=$subcword; qdwd < ${#words[@]}-1; qdwd++)); do
+                        if [[ $QDISC_KIND =~ ' '${words[qdwd]}' ' ]]; then
+                            qdisc=${words[qdwd]}
+                            _tc_qdisc_options $qdisc && return 0
+                        fi
+                    done
+                    _tc_one_of_list $QDISC_KIND
+                    _tc_one_of_list 'root parent'
+                    _tc_once_attr 'classid'
+                    ;;
+                show)
+                    _tc_once_attr 'dev'
+                    _tc_one_of_list 'root parent'
+                    _tc_once_attr '-statistics -details -raw -pretty -iec \
+                        -graph -name'
+                    ;;
+                help)
+                    return 0
+                    ;;
+                *)
+                    [[ $cword -eq $subcword ]] && \
+                        COMPREPLY=( $( compgen -W 'help add delete change \
+                            replace show' -- "$cur" ) )
+                    ;;
+            esac
+            ;;
+
+        filter)
+            case $subcmd in
+                add|change|replace|del|delete)
+                    if [[ $(($cword-$subcword)) -eq 1 ]]; then
+                        COMPREPLY=( $( compgen -W 'dev' -- "$cur" ) )
+                        return 0
+                    fi
+                    local filter fltwd
+                    for ((fltwd=$subcword; fltwd < ${#words[@]}-1; fltwd++));
+                    do
+                        if [[ $FILTER_KIND =~ ' '${words[fltwd]}' ' ]]; then
+                            _tc_filter_options $fltwd && return 0
+                        fi
+                    done
+                    _tc_one_of_list $FILTER_KIND
+                    _tc_one_of_list 'root ingress egress parent'
+                    _tc_once_attr 'handle estimator pref protocol'
+                    ;;
+                show)
+                    _tc_once_attr 'dev'
+                    _tc_one_of_list 'root ingress egress parent'
+                    _tc_once_attr '-statistics -details -raw -pretty -iec \
+                        -graph -name'
+                    ;;
+                help)
+                    return 0
+                    ;;
+                *)
+                    [[ $cword -eq $subcword ]] && \
+                        COMPREPLY=( $( compgen -W 'help add delete change \
+                            replace show' -- "$cur" ) )
+                    ;;
+            esac
+            ;;
+
+        action)
+            case $subcmd in
+                add|change|replace)
+                    local action acwd
+                    for ((acwd=$subcword; acwd < ${#words[@]}-1; acwd++)); do
+                        if [[ $ACTION_KIND =~ ' '${words[acwd]}' ' ]]; then
+                            _tc_action_options $acwd && return 0
+                        fi
+                    done
+                    _tc_one_of_list $ACTION_KIND
+                    ;;
+                get|del|delete)
+                    _tc_once_attr 'index'
+                    ;;
+                lst|list|flush|show)
+                    _tc_one_of_list $ACTION_KIND
+                    ;;
+                *)
+                    [[ $cword -eq $subcword ]] && \
+                        COMPREPLY=( $( compgen -W 'help add delete change \
+                            replace show list flush action' -- "$cur" ) )
+                    ;;
+            esac
+            ;;
+
+        monitor)
+            COMPREPLY=( $( compgen -W 'help' -- "$cur" ) )
+            ;;
+
+        exec)
+            case $subcmd in
+                bpf)
+                    local excmd exwd EXEC_KIND=' import debug graft '
+                    for ((exwd=$subcword; exwd < ${#words[@]}-1; exwd++)); do
+                        if [[ $EXEC_KIND =~ ' '${words[exwd]}' ' ]]; then
+                            excmd=${words[exwd]}
+                            _tc_exec_options $excmd && return 0
+                        fi
+                    done
+                    _tc_one_of_list $EXEC_KIND
+                    ;;
+                *)
+                    [[ $cword -eq $subcword ]] && \
+                        COMPREPLY=( $( compgen -W 'bpf' -- "$cur" ) )
+                    ;;
+            esac
+            ;;
+    esac
+} &&
+complete -F _tc tc
+
+# ex: ts=4 sw=4 et filetype=sh
diff --git a/bridge/.gitignore b/bridge/.gitignore
new file mode 100644
index 0000000..7096907
--- /dev/null
+++ b/bridge/.gitignore
@@ -0,0 +1 @@
+bridge
diff --git a/bridge/Android.mk b/bridge/Android.mk
new file mode 100644
index 0000000..cf0487f
--- /dev/null
+++ b/bridge/Android.mk
@@ -0,0 +1,25 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := bridge
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := bridge.c \
+		   fdb.c \
+		   monitor.c \
+		   link.c \
+		   mdb.c \
+		   vlan.c
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include \
+		    $(LOCAL_PATH)/../include/uapi
+LOCAL_STATIC_LIBRARIES := libnetlink \
+			  libiprouteutil
+LOCAL_CFLAGS := \
+    -O2 -W -Wall \
+    -Wno-implicit-function-declaration \
+    -Wno-missing-field-initializers \
+    -Wno-pointer-arith \
+    -Wno-sign-compare \
+    -Wno-unused-parameter \
+    -Werror \
+    -D_GNU_SOURCE -DHAVE_SETNS
+include $(BUILD_EXECUTABLE)
diff --git a/bridge/Makefile b/bridge/Makefile
new file mode 100644
index 0000000..c6b7d08
--- /dev/null
+++ b/bridge/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+BROBJ = bridge.o fdb.o monitor.o link.o mdb.o vlan.o
+
+include ../config.mk
+
+all: bridge
+
+bridge: $(BROBJ) $(LIBNETLINK)
+	$(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+	install -m 0755 bridge $(DESTDIR)$(SBINDIR)
+
+clean:
+	rm -f $(BROBJ) bridge
diff --git a/bridge/br_common.h b/bridge/br_common.h
new file mode 100644
index 0000000..b5798da
--- /dev/null
+++ b/bridge/br_common.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define MDB_RTA(r) \
+		((struct rtattr *)(((char *)(r)) + RTA_ALIGN(sizeof(struct br_mdb_entry))))
+
+#define MDB_RTR_RTA(r) \
+		((struct rtattr *)(((char *)(r)) + RTA_ALIGN(sizeof(__u32))))
+
+void print_vlan_info(struct rtattr *tb, int ifindex);
+int print_linkinfo(struct nlmsghdr *n, void *arg);
+int print_mdb_mon(struct nlmsghdr *n, void *arg);
+int print_fdb(struct nlmsghdr *n, void *arg);
+
+int do_fdb(int argc, char **argv);
+int do_mdb(int argc, char **argv);
+int do_monitor(int argc, char **argv);
+int do_vlan(int argc, char **argv);
+int do_link(int argc, char **argv);
+
+extern int preferred_family;
+extern int show_stats;
+extern int show_details;
+extern int timestamp;
+extern int compress_vlans;
+extern int json;
+extern struct rtnl_handle rth;
diff --git a/bridge/bridge.c b/bridge/bridge.c
new file mode 100644
index 0000000..a50d9d5
--- /dev/null
+++ b/bridge/bridge.c
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Get/set/delete bridge with netlink
+ *
+ * Authors:	Stephen Hemminger <shemminger@vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <errno.h>
+
+#include "SNAPSHOT.h"
+#include "utils.h"
+#include "br_common.h"
+#include "namespace.h"
+#include "color.h"
+
+struct rtnl_handle rth = { .fd = -1 };
+int preferred_family = AF_UNSPEC;
+int oneline;
+int show_stats;
+int show_details;
+static int color;
+int compress_vlans;
+int json;
+int timestamp;
+static const char *batch_file;
+int force;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+"Usage: bridge [ OPTIONS ] OBJECT { COMMAND | help }\n"
+"       bridge [ -force ] -batch filename\n"
+"where	OBJECT := { link | fdb | mdb | vlan | monitor }\n"
+"	OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] |\n"
+"		     -o[neline] | -t[imestamp] | -n[etns] name |\n"
+"		     -c[ompressvlans] -color -p[retty] -j[son] }\n");
+	exit(-1);
+}
+
+static int do_help(int argc, char **argv)
+{
+	usage();
+}
+
+
+static const struct cmd {
+	const char *cmd;
+	int (*func)(int argc, char **argv);
+} cmds[] = {
+	{ "link",	do_link },
+	{ "fdb",	do_fdb },
+	{ "mdb",	do_mdb },
+	{ "vlan",	do_vlan },
+	{ "monitor",	do_monitor },
+	{ "help",	do_help },
+	{ 0 }
+};
+
+static int do_cmd(const char *argv0, int argc, char **argv)
+{
+	const struct cmd *c;
+
+	for (c = cmds; c->cmd; ++c) {
+		if (matches(argv0, c->cmd) == 0)
+			return c->func(argc-1, argv+1);
+	}
+
+	fprintf(stderr,
+		"Object \"%s\" is unknown, try \"bridge help\".\n", argv0);
+	return -1;
+}
+
+static int batch(const char *name)
+{
+	char *line = NULL;
+	size_t len = 0;
+	int ret = EXIT_SUCCESS;
+
+	if (name && strcmp(name, "-") != 0) {
+		if (freopen(name, "r", stdin) == NULL) {
+			fprintf(stderr,
+				"Cannot open file \"%s\" for reading: %s\n",
+				name, strerror(errno));
+			return EXIT_FAILURE;
+		}
+	}
+
+	if (rtnl_open(&rth, 0) < 0) {
+		fprintf(stderr, "Cannot open rtnetlink\n");
+		return EXIT_FAILURE;
+	}
+
+	rtnl_set_strict_dump(&rth);
+
+	cmdlineno = 0;
+	while (getcmdline(&line, &len, stdin) != -1) {
+		char *largv[100];
+		int largc;
+
+		largc = makeargs(line, largv, 100);
+		if (largc == 0)
+			continue;       /* blank line */
+
+		if (do_cmd(largv[0], largc, largv)) {
+			fprintf(stderr, "Command failed %s:%d\n",
+				name, cmdlineno);
+			ret = EXIT_FAILURE;
+			if (!force)
+				break;
+		}
+	}
+	if (line)
+		free(line);
+
+	rtnl_close(&rth);
+	return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+	while (argc > 1) {
+		const char *opt = argv[1];
+
+		if (strcmp(opt, "--") == 0) {
+			argc--; argv++;
+			break;
+		}
+		if (opt[0] != '-')
+			break;
+		if (opt[1] == '-')
+			opt++;
+
+		if (matches(opt, "-help") == 0) {
+			usage();
+		} else if (matches(opt, "-Version") == 0) {
+			printf("bridge utility, 0.0\n");
+			exit(0);
+		} else if (matches(opt, "-stats") == 0 ||
+			   matches(opt, "-statistics") == 0) {
+			++show_stats;
+		} else if (matches(opt, "-details") == 0) {
+			++show_details;
+		} else if (matches(opt, "-oneline") == 0) {
+			++oneline;
+		} else if (matches(opt, "-timestamp") == 0) {
+			++timestamp;
+		} else if (matches(opt, "-family") == 0) {
+			argc--;
+			argv++;
+			if (argc <= 1)
+				usage();
+			if (strcmp(argv[1], "inet") == 0)
+				preferred_family = AF_INET;
+			else if (strcmp(argv[1], "inet6") == 0)
+				preferred_family = AF_INET6;
+			else if (strcmp(argv[1], "help") == 0)
+				usage();
+			else
+				invarg("invalid protocol family", argv[1]);
+		} else if (strcmp(opt, "-4") == 0) {
+			preferred_family = AF_INET;
+		} else if (strcmp(opt, "-6") == 0) {
+			preferred_family = AF_INET6;
+		} else if (matches(opt, "-netns") == 0) {
+			NEXT_ARG();
+			if (netns_switch(argv[1]))
+				exit(-1);
+		} else if (matches(opt, "-compressvlans") == 0) {
+			++compress_vlans;
+		} else if (matches_color(opt, &color)) {
+		} else if (matches(opt, "-force") == 0) {
+			++force;
+		} else if (matches(opt, "-json") == 0) {
+			++json;
+		} else if (matches(opt, "-pretty") == 0) {
+			++pretty;
+		} else if (matches(opt, "-batch") == 0) {
+			argc--;
+			argv++;
+			if (argc <= 1)
+				usage();
+			batch_file = argv[1];
+		} else {
+			fprintf(stderr,
+				"Option \"%s\" is unknown, try \"bridge help\".\n",
+				opt);
+			exit(-1);
+		}
+		argc--;	argv++;
+	}
+
+	_SL_ = oneline ? "\\" : "\n";
+
+	check_enable_color(color, json);
+
+	if (batch_file)
+		return batch(batch_file);
+
+	if (rtnl_open(&rth, 0) < 0)
+		exit(1);
+
+	rtnl_set_strict_dump(&rth);
+
+	if (argc > 1)
+		return do_cmd(argv[1], argc-1, argv+1);
+
+	rtnl_close(&rth);
+	usage();
+}
diff --git a/bridge/fdb.c b/bridge/fdb.c
new file mode 100644
index 0000000..941ce2d
--- /dev/null
+++ b/bridge/fdb.c
@@ -0,0 +1,545 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Get/set/delete fdb table with netlink
+ *
+ * TODO: merge/replace this with ip neighbour
+ *
+ * Authors:	Stephen Hemminger <shemminger@vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <linux/neighbour.h>
+#include <string.h>
+#include <limits.h>
+#include <stdbool.h>
+
+#include "json_print.h"
+#include "libnetlink.h"
+#include "br_common.h"
+#include "rt_names.h"
+#include "utils.h"
+
+static unsigned int filter_index, filter_vlan, filter_state, filter_master;
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: bridge fdb { add | append | del | replace } ADDR dev DEV\n"
+		"              [ self ] [ master ] [ use ] [ router ] [ extern_learn ]\n"
+		"              [ sticky ] [ local | static | dynamic ] [ dst IPADDR ]\n"
+		"              [ vlan VID ] [ port PORT] [ vni VNI ] [ via DEV ]\n"
+		"              [ src_vni VNI ]\n"
+		"       bridge fdb [ show [ br BRDEV ] [ brport DEV ] [ vlan VID ] [ state STATE ] ]\n");
+	exit(-1);
+}
+
+static const char *state_n2a(unsigned int s)
+{
+	static char buf[32];
+
+	if (s & NUD_PERMANENT)
+		return "permanent";
+
+	if (s & NUD_NOARP)
+		return "static";
+
+	if (s & NUD_STALE)
+		return "stale";
+
+	if (s & NUD_REACHABLE)
+		return "";
+
+	sprintf(buf, "state=%#x", s);
+	return buf;
+}
+
+static int state_a2n(unsigned int *s, const char *arg)
+{
+	if (matches(arg, "permanent") == 0)
+		*s = NUD_PERMANENT;
+	else if (matches(arg, "static") == 0 || matches(arg, "temp") == 0)
+		*s = NUD_NOARP;
+	else if (matches(arg, "stale") == 0)
+		*s = NUD_STALE;
+	else if (matches(arg, "reachable") == 0 || matches(arg, "dynamic") == 0)
+		*s = NUD_REACHABLE;
+	else if (strcmp(arg, "all") == 0)
+		*s = ~0;
+	else if (get_unsigned(s, arg, 0))
+		return -1;
+
+	return 0;
+}
+
+static void fdb_print_flags(FILE *fp, unsigned int flags)
+{
+	open_json_array(PRINT_JSON,
+			is_json_context() ?  "flags" : "");
+
+	if (flags & NTF_SELF)
+		print_string(PRINT_ANY, NULL, "%s ", "self");
+
+	if (flags & NTF_ROUTER)
+		print_string(PRINT_ANY, NULL, "%s ", "router");
+
+	if (flags & NTF_EXT_LEARNED)
+		print_string(PRINT_ANY, NULL, "%s ", "extern_learn");
+
+	if (flags & NTF_OFFLOADED)
+		print_string(PRINT_ANY, NULL, "%s ", "offload");
+
+	if (flags & NTF_MASTER)
+		print_string(PRINT_ANY, NULL, "%s ", "master");
+
+	if (flags & NTF_STICKY)
+		print_string(PRINT_ANY, NULL, "%s ", "sticky");
+
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static void fdb_print_stats(FILE *fp, const struct nda_cacheinfo *ci)
+{
+	static int hz;
+
+	if (!hz)
+		hz = get_user_hz();
+
+	if (is_json_context()) {
+		print_uint(PRINT_JSON, "used", NULL,
+				 ci->ndm_used / hz);
+		print_uint(PRINT_JSON, "updated", NULL,
+				ci->ndm_updated / hz);
+	} else {
+		fprintf(fp, "used %d/%d ", ci->ndm_used / hz,
+					ci->ndm_updated / hz);
+
+	}
+}
+
+int print_fdb(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = arg;
+	struct ndmsg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[NDA_MAX+1];
+	__u16 vid = 0;
+
+	if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH) {
+		fprintf(stderr, "Not RTM_NEWNEIGH: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (r->ndm_family != AF_BRIDGE)
+		return 0;
+
+	if (filter_index && filter_index != r->ndm_ifindex)
+		return 0;
+
+	if (filter_state && !(r->ndm_state & filter_state))
+		return 0;
+
+	parse_rtattr(tb, NDA_MAX, NDA_RTA(r),
+		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+	if (tb[NDA_VLAN])
+		vid = rta_getattr_u16(tb[NDA_VLAN]);
+
+	if (filter_vlan && filter_vlan != vid)
+		return 0;
+
+	open_json_object(NULL);
+	if (n->nlmsg_type == RTM_DELNEIGH)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	if (tb[NDA_LLADDR]) {
+		const char *lladdr;
+		SPRINT_BUF(b1);
+
+		lladdr = ll_addr_n2a(RTA_DATA(tb[NDA_LLADDR]),
+				     RTA_PAYLOAD(tb[NDA_LLADDR]),
+				     ll_index_to_type(r->ndm_ifindex),
+				     b1, sizeof(b1));
+
+		print_color_string(PRINT_ANY, COLOR_MAC,
+				   "mac", "%s ", lladdr);
+	}
+
+	if (!filter_index && r->ndm_ifindex)
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "ifname", "dev %s ",
+				   ll_index_to_name(r->ndm_ifindex));
+
+	if (tb[NDA_DST]) {
+		int family = AF_INET;
+		const char *dst;
+
+		if (RTA_PAYLOAD(tb[NDA_DST]) == sizeof(struct in6_addr))
+			family = AF_INET6;
+
+		dst = format_host(family,
+				  RTA_PAYLOAD(tb[NDA_DST]),
+				  RTA_DATA(tb[NDA_DST]));
+
+		print_color_string(PRINT_ANY,
+				   ifa_family_color(family),
+				    "dst", "dst %s ", dst);
+	}
+
+	if (vid)
+		print_uint(PRINT_ANY,
+				 "vlan", "vlan %hu ", vid);
+
+	if (tb[NDA_PORT])
+		print_uint(PRINT_ANY,
+				 "port", "port %u ",
+				 rta_getattr_be16(tb[NDA_PORT]));
+
+	if (tb[NDA_VNI])
+		print_uint(PRINT_ANY,
+				 "vni", "vni %u ",
+				 rta_getattr_u32(tb[NDA_VNI]));
+
+	if (tb[NDA_SRC_VNI])
+		print_uint(PRINT_ANY,
+				 "src_vni", "src_vni %u ",
+				rta_getattr_u32(tb[NDA_SRC_VNI]));
+
+	if (tb[NDA_IFINDEX]) {
+		unsigned int ifindex = rta_getattr_u32(tb[NDA_IFINDEX]);
+
+		if (tb[NDA_LINK_NETNSID])
+			print_uint(PRINT_ANY,
+					 "viaIfIndex", "via ifindex %u ",
+					 ifindex);
+		else
+			print_string(PRINT_ANY,
+					   "viaIf", "via %s ",
+					   ll_index_to_name(ifindex));
+	}
+
+	if (tb[NDA_LINK_NETNSID])
+		print_uint(PRINT_ANY,
+				 "linkNetNsId", "link-netnsid %d ",
+				 rta_getattr_u32(tb[NDA_LINK_NETNSID]));
+
+	if (show_stats && tb[NDA_CACHEINFO])
+		fdb_print_stats(fp, RTA_DATA(tb[NDA_CACHEINFO]));
+
+	fdb_print_flags(fp, r->ndm_flags);
+
+
+	if (tb[NDA_MASTER])
+		print_string(PRINT_ANY, "master", "master %s ",
+			     ll_index_to_name(rta_getattr_u32(tb[NDA_MASTER])));
+
+	print_string(PRINT_ANY, "state", "%s\n",
+			   state_n2a(r->ndm_state));
+	close_json_object();
+	fflush(fp);
+	return 0;
+}
+
+static int fdb_linkdump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+	int err;
+
+	if (filter_index) {
+		struct ifinfomsg *ifm = NLMSG_DATA(nlh);
+
+		ifm->ifi_index = filter_index;
+	}
+
+	if (filter_master) {
+		err = addattr32(nlh, reqlen, IFLA_MASTER, filter_master);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int fdb_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+	int err;
+
+	if (filter_index) {
+		struct ndmsg *ndm = NLMSG_DATA(nlh);
+
+		ndm->ndm_ifindex = filter_index;
+	}
+
+	if (filter_master) {
+		err = addattr32(nlh, reqlen, NDA_MASTER, filter_master);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int fdb_show(int argc, char **argv)
+{
+	char *filter_dev = NULL;
+	char *br = NULL;
+	int rc;
+
+	while (argc > 0) {
+		if ((strcmp(*argv, "brport") == 0) || strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			filter_dev = *argv;
+		} else if (strcmp(*argv, "br") == 0) {
+			NEXT_ARG();
+			br = *argv;
+		} else if (strcmp(*argv, "vlan") == 0) {
+			NEXT_ARG();
+			if (filter_vlan)
+				duparg("vlan", *argv);
+			filter_vlan = atoi(*argv);
+		} else if (strcmp(*argv, "state") == 0) {
+			unsigned int state;
+
+			NEXT_ARG();
+			if (state_a2n(&state, *argv))
+				invarg("invalid state", *argv);
+			filter_state |= state;
+		} else {
+			if (matches(*argv, "help") == 0)
+				usage();
+		}
+		argc--; argv++;
+	}
+
+	if (br) {
+		int br_ifindex = ll_name_to_index(br);
+
+		if (br_ifindex == 0) {
+			fprintf(stderr, "Cannot find bridge device \"%s\"\n", br);
+			return -1;
+		}
+		filter_master = br_ifindex;
+	}
+
+	/*we'll keep around filter_dev for older kernels */
+	if (filter_dev) {
+		filter_index = ll_name_to_index(filter_dev);
+		if (!filter_index)
+			return nodev(filter_dev);
+	}
+
+	if (rth.flags & RTNL_HANDLE_F_STRICT_CHK)
+		rc = rtnl_neighdump_req(&rth, PF_BRIDGE, fdb_dump_filter);
+	else
+		rc = rtnl_fdb_linkdump_req_filter_fn(&rth, fdb_linkdump_filter);
+	if (rc < 0) {
+		perror("Cannot send dump request");
+		exit(1);
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&rth, print_fdb, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	delete_json_obj();
+	fflush(stdout);
+
+	return 0;
+}
+
+static int fdb_modify(int cmd, int flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct ndmsg		ndm;
+		char			buf[256];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST | flags,
+		.n.nlmsg_type = cmd,
+		.ndm.ndm_family = PF_BRIDGE,
+		.ndm.ndm_state = NUD_NOARP,
+	};
+	char *addr = NULL;
+	char *d = NULL;
+	char abuf[ETH_ALEN];
+	int dst_ok = 0;
+	inet_prefix dst;
+	unsigned long port = 0;
+	unsigned long vni = ~0;
+	unsigned long src_vni = ~0;
+	unsigned int via = 0;
+	char *endptr;
+	short vid = -1;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (strcmp(*argv, "dst") == 0) {
+			NEXT_ARG();
+			if (dst_ok)
+				duparg2("dst", *argv);
+			get_addr(&dst, *argv, preferred_family);
+			dst_ok = 1;
+		} else if (strcmp(*argv, "port") == 0) {
+
+			NEXT_ARG();
+			port = strtoul(*argv, &endptr, 0);
+			if (endptr && *endptr) {
+				struct servent *pse;
+
+				pse = getservbyname(*argv, "udp");
+				if (!pse)
+					invarg("invalid port\n", *argv);
+				port = ntohs(pse->s_port);
+			} else if (port > 0xffff)
+				invarg("invalid port\n", *argv);
+		} else if (strcmp(*argv, "vni") == 0) {
+			NEXT_ARG();
+			vni = strtoul(*argv, &endptr, 0);
+			if ((endptr && *endptr) ||
+			    (vni >> 24) || vni == ULONG_MAX)
+				invarg("invalid VNI\n", *argv);
+		} else if (strcmp(*argv, "src_vni") == 0) {
+			NEXT_ARG();
+			src_vni = strtoul(*argv, &endptr, 0);
+			if ((endptr && *endptr) ||
+			    (src_vni >> 24) || src_vni == ULONG_MAX)
+				invarg("invalid src VNI\n", *argv);
+		} else if (strcmp(*argv, "via") == 0) {
+			NEXT_ARG();
+			via = ll_name_to_index(*argv);
+			if (!via)
+				exit(nodev(*argv));
+		} else if (strcmp(*argv, "self") == 0) {
+			req.ndm.ndm_flags |= NTF_SELF;
+		} else if (matches(*argv, "master") == 0) {
+			req.ndm.ndm_flags |= NTF_MASTER;
+		} else if (matches(*argv, "router") == 0) {
+			req.ndm.ndm_flags |= NTF_ROUTER;
+		} else if (matches(*argv, "local") == 0 ||
+			   matches(*argv, "permanent") == 0) {
+			req.ndm.ndm_state |= NUD_PERMANENT;
+		} else if (matches(*argv, "temp") == 0 ||
+			   matches(*argv, "static") == 0) {
+			req.ndm.ndm_state |= NUD_REACHABLE;
+		} else if (matches(*argv, "dynamic") == 0) {
+			req.ndm.ndm_state |= NUD_REACHABLE;
+			req.ndm.ndm_state &= ~NUD_NOARP;
+		} else if (matches(*argv, "vlan") == 0) {
+			if (vid >= 0)
+				duparg2("vlan", *argv);
+			NEXT_ARG();
+			vid = atoi(*argv);
+		} else if (matches(*argv, "use") == 0) {
+			req.ndm.ndm_flags |= NTF_USE;
+		} else if (matches(*argv, "extern_learn") == 0) {
+			req.ndm.ndm_flags |= NTF_EXT_LEARNED;
+		} else if (matches(*argv, "sticky") == 0) {
+			req.ndm.ndm_flags |= NTF_STICKY;
+		} else {
+			if (strcmp(*argv, "to") == 0)
+				NEXT_ARG();
+
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (addr)
+				duparg2("to", *argv);
+			addr = *argv;
+		}
+		argc--; argv++;
+	}
+
+	if (d == NULL || addr == NULL) {
+		fprintf(stderr, "Device and address are required arguments.\n");
+		return -1;
+	}
+
+	/* Assume self */
+	if (!(req.ndm.ndm_flags&(NTF_SELF|NTF_MASTER)))
+		req.ndm.ndm_flags |= NTF_SELF;
+
+	/* Assume permanent */
+	if (!(req.ndm.ndm_state&(NUD_PERMANENT|NUD_REACHABLE)))
+		req.ndm.ndm_state |= NUD_PERMANENT;
+
+	if (sscanf(addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+		   abuf, abuf+1, abuf+2,
+		   abuf+3, abuf+4, abuf+5) != 6) {
+		fprintf(stderr, "Invalid mac address %s\n", addr);
+		return -1;
+	}
+
+	addattr_l(&req.n, sizeof(req), NDA_LLADDR, abuf, ETH_ALEN);
+	if (dst_ok)
+		addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen);
+
+	if (vid >= 0)
+		addattr16(&req.n, sizeof(req), NDA_VLAN, vid);
+
+	if (port) {
+		unsigned short dport;
+
+		dport = htons((unsigned short)port);
+		addattr16(&req.n, sizeof(req), NDA_PORT, dport);
+	}
+	if (vni != ~0)
+		addattr32(&req.n, sizeof(req), NDA_VNI, vni);
+	if (src_vni != ~0)
+		addattr32(&req.n, sizeof(req), NDA_SRC_VNI, src_vni);
+	if (via)
+		addattr32(&req.n, sizeof(req), NDA_IFINDEX, via);
+
+	req.ndm.ndm_ifindex = ll_name_to_index(d);
+	if (!req.ndm.ndm_ifindex)
+		return nodev(d);
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -1;
+
+	return 0;
+}
+
+int do_fdb(int argc, char **argv)
+{
+	ll_init_map(&rth);
+
+	if (argc > 0) {
+		if (matches(*argv, "add") == 0)
+			return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+		if (matches(*argv, "append") == 0)
+			return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_APPEND, argc-1, argv+1);
+		if (matches(*argv, "replace") == 0)
+			return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
+		if (matches(*argv, "delete") == 0)
+			return fdb_modify(RTM_DELNEIGH, 0, argc-1, argv+1);
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return fdb_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return fdb_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"bridge fdb help\".\n", *argv);
+	exit(-1);
+}
diff --git a/bridge/link.c b/bridge/link.c
new file mode 100644
index 0000000..074edf0
--- /dev/null
+++ b/bridge/link.c
@@ -0,0 +1,586 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <linux/if.h>
+#include <linux/if_bridge.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "json_print.h"
+#include "libnetlink.h"
+#include "utils.h"
+#include "br_common.h"
+
+static unsigned int filter_index;
+
+static const char *port_states[] = {
+	[BR_STATE_DISABLED] = "disabled",
+	[BR_STATE_LISTENING] = "listening",
+	[BR_STATE_LEARNING] = "learning",
+	[BR_STATE_FORWARDING] = "forwarding",
+	[BR_STATE_BLOCKING] = "blocking",
+};
+
+static const char *hw_mode[] = {
+	"VEB", "VEPA"
+};
+
+static void print_link_flags(FILE *fp, unsigned int flags, unsigned int mdown)
+{
+	open_json_array(PRINT_ANY, is_json_context() ? "flags" : "<");
+	if (flags & IFF_UP && !(flags & IFF_RUNNING))
+		print_string(PRINT_ANY, NULL,
+			     flags ? "%s," : "%s", "NO-CARRIER");
+	flags &= ~IFF_RUNNING;
+
+#define _PF(f) if (flags&IFF_##f) {					\
+		flags &= ~IFF_##f ;					\
+		print_string(PRINT_ANY, NULL, flags ? "%s," : "%s", #f); }
+	_PF(LOOPBACK);
+	_PF(BROADCAST);
+	_PF(POINTOPOINT);
+	_PF(MULTICAST);
+	_PF(NOARP);
+	_PF(ALLMULTI);
+	_PF(PROMISC);
+	_PF(MASTER);
+	_PF(SLAVE);
+	_PF(DEBUG);
+	_PF(DYNAMIC);
+	_PF(AUTOMEDIA);
+	_PF(PORTSEL);
+	_PF(NOTRAILERS);
+	_PF(UP);
+	_PF(LOWER_UP);
+	_PF(DORMANT);
+	_PF(ECHO);
+#undef _PF
+	if (flags)
+		print_hex(PRINT_ANY, NULL, "%x", flags);
+	if (mdown)
+		print_string(PRINT_ANY, NULL, ",%s", "M-DOWN");
+	close_json_array(PRINT_ANY, "> ");
+}
+
+static void print_portstate(__u8 state)
+{
+	if (state <= BR_STATE_BLOCKING)
+		print_string(PRINT_ANY, "state",
+			     "state %s ", port_states[state]);
+	else
+		print_uint(PRINT_ANY, "state",
+			     "state (%d) ", state);
+}
+
+static void print_onoff(FILE *fp, const char *flag, __u8 val)
+{
+	if (is_json_context())
+		print_bool(PRINT_JSON, flag, NULL, val);
+	else
+		fprintf(fp, "%s %s ", flag, val ? "on" : "off");
+}
+
+static void print_hwmode(__u16 mode)
+{
+	if (mode >= ARRAY_SIZE(hw_mode))
+		print_0xhex(PRINT_ANY, "hwmode",
+			    "hwmode %#llx ", mode);
+	else
+		print_string(PRINT_ANY, "hwmode",
+			     "hwmode %s ", hw_mode[mode]);
+}
+
+static void print_protinfo(FILE *fp, struct rtattr *attr)
+{
+	if (attr->rta_type & NLA_F_NESTED) {
+		struct rtattr *prtb[IFLA_BRPORT_MAX + 1];
+
+		parse_rtattr_nested(prtb, IFLA_BRPORT_MAX, attr);
+
+		if (prtb[IFLA_BRPORT_STATE])
+			print_portstate(rta_getattr_u8(prtb[IFLA_BRPORT_STATE]));
+
+		if (prtb[IFLA_BRPORT_PRIORITY])
+			print_uint(PRINT_ANY, "priority",
+				   "priority %u ",
+				   rta_getattr_u16(prtb[IFLA_BRPORT_PRIORITY]));
+
+		if (prtb[IFLA_BRPORT_COST])
+			print_uint(PRINT_ANY, "cost",
+				   "cost %u ",
+				   rta_getattr_u32(prtb[IFLA_BRPORT_COST]));
+
+		if (!show_details)
+			return;
+
+		if (!is_json_context())
+			fprintf(fp, "%s    ", _SL_);
+
+		if (prtb[IFLA_BRPORT_MODE])
+			print_onoff(fp, "hairpin",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_MODE]));
+		if (prtb[IFLA_BRPORT_GUARD])
+			print_onoff(fp, "guard",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_GUARD]));
+		if (prtb[IFLA_BRPORT_PROTECT])
+			print_onoff(fp, "root_block",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_PROTECT]));
+		if (prtb[IFLA_BRPORT_FAST_LEAVE])
+			print_onoff(fp, "fastleave",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_FAST_LEAVE]));
+		if (prtb[IFLA_BRPORT_LEARNING])
+			print_onoff(fp, "learning",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_LEARNING]));
+		if (prtb[IFLA_BRPORT_LEARNING_SYNC])
+			print_onoff(fp, "learning_sync",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_LEARNING_SYNC]));
+		if (prtb[IFLA_BRPORT_UNICAST_FLOOD])
+			print_onoff(fp, "flood",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_UNICAST_FLOOD]));
+		if (prtb[IFLA_BRPORT_MCAST_FLOOD])
+			print_onoff(fp, "mcast_flood",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_MCAST_FLOOD]));
+		if (prtb[IFLA_BRPORT_MCAST_TO_UCAST])
+			print_onoff(fp, "mcast_to_unicast",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_MCAST_TO_UCAST]));
+		if (prtb[IFLA_BRPORT_NEIGH_SUPPRESS])
+			print_onoff(fp, "neigh_suppress",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_NEIGH_SUPPRESS]));
+		if (prtb[IFLA_BRPORT_VLAN_TUNNEL])
+			print_onoff(fp, "vlan_tunnel",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_VLAN_TUNNEL]));
+
+		if (prtb[IFLA_BRPORT_BACKUP_PORT]) {
+			int ifidx;
+
+			ifidx = rta_getattr_u32(prtb[IFLA_BRPORT_BACKUP_PORT]);
+			print_string(PRINT_ANY,
+				     "backup_port", "backup_port %s ",
+				     ll_index_to_name(ifidx));
+		}
+
+		if (prtb[IFLA_BRPORT_ISOLATED])
+			print_onoff(fp, "isolated",
+				    rta_getattr_u8(prtb[IFLA_BRPORT_ISOLATED]));
+	} else
+		print_portstate(rta_getattr_u8(attr));
+}
+
+
+/*
+ * This is reported by HW devices that have some bridging
+ * capabilities.
+ */
+static void print_af_spec(struct rtattr *attr, int ifindex)
+{
+	struct rtattr *aftb[IFLA_BRIDGE_MAX+1];
+
+	parse_rtattr_nested(aftb, IFLA_BRIDGE_MAX, attr);
+
+	if (aftb[IFLA_BRIDGE_MODE])
+		print_hwmode(rta_getattr_u16(aftb[IFLA_BRIDGE_MODE]));
+
+	if (!show_details)
+		return;
+
+	if (aftb[IFLA_BRIDGE_VLAN_INFO])
+		print_vlan_info(aftb[IFLA_BRIDGE_VLAN_INFO], ifindex);
+}
+
+int print_linkinfo(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = arg;
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_MAX+1];
+	unsigned int m_flag = 0;
+	int len = n->nlmsg_len;
+	const char *name;
+
+	len -= NLMSG_LENGTH(sizeof(*ifi));
+	if (len < 0) {
+		fprintf(stderr, "Message too short!\n");
+		return -1;
+	}
+
+	if (!(ifi->ifi_family == AF_BRIDGE || ifi->ifi_family == AF_UNSPEC))
+		return 0;
+
+	if (filter_index && filter_index != ifi->ifi_index)
+		return 0;
+
+	parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(ifi), len, NLA_F_NESTED);
+
+	name = get_ifname_rta(ifi->ifi_index, tb[IFLA_IFNAME]);
+	if (!name)
+		return -1;
+
+	open_json_object(NULL);
+	if (n->nlmsg_type == RTM_DELLINK)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	print_int(PRINT_ANY, "ifindex", "%d: ", ifi->ifi_index);
+	m_flag = print_name_and_link("%s: ", name, tb);
+	print_link_flags(fp, ifi->ifi_flags, m_flag);
+
+	if (tb[IFLA_MTU])
+		print_int(PRINT_ANY,
+			  "mtu", "mtu %u ",
+			  rta_getattr_u32(tb[IFLA_MTU]));
+
+	if (tb[IFLA_MASTER]) {
+		int master = rta_getattr_u32(tb[IFLA_MASTER]);
+
+		print_string(PRINT_ANY, "master", "master %s ",
+			     ll_index_to_name(master));
+	}
+
+	if (tb[IFLA_PROTINFO])
+		print_protinfo(fp, tb[IFLA_PROTINFO]);
+
+	if (tb[IFLA_AF_SPEC])
+		print_af_spec(tb[IFLA_AF_SPEC], ifi->ifi_index);
+
+	print_string(PRINT_FP, NULL, "%s", "\n");
+	close_json_object();
+	fflush(fp);
+	return 0;
+}
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: bridge link set dev DEV [ cost COST ] [ priority PRIO ] [ state STATE ]\n"
+		"                               [ guard {on | off} ]\n"
+		"                               [ hairpin {on | off} ]\n"
+		"                               [ fastleave {on | off} ]\n"
+		"                               [ root_block {on | off} ]\n"
+		"                               [ learning {on | off} ]\n"
+		"                               [ learning_sync {on | off} ]\n"
+		"                               [ flood {on | off} ]\n"
+		"                               [ mcast_flood {on | off} ]\n"
+		"                               [ mcast_to_unicast {on | off} ]\n"
+		"                               [ neigh_suppress {on | off} ]\n"
+		"                               [ vlan_tunnel {on | off} ]\n"
+		"                               [ isolated {on | off} ]\n"
+		"                               [ hwmode {vepa | veb} ]\n"
+		"                               [ backup_port DEVICE ] [ nobackup_port ]\n"
+		"                               [ self ] [ master ]\n"
+		"       bridge link show [dev DEV]\n");
+	exit(-1);
+}
+
+static bool on_off(char *arg, __s8 *attr, char *val)
+{
+	if (strcmp(val, "on") == 0)
+		*attr = 1;
+	else if (strcmp(val, "off") == 0)
+		*attr = 0;
+	else {
+		fprintf(stderr,
+			"Error: argument of \"%s\" must be \"on\" or \"off\"\n",
+			arg);
+		return false;
+	}
+
+	return true;
+}
+
+static int brlink_modify(int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr  n;
+		struct ifinfomsg ifm;
+		char             buf[512];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_SETLINK,
+		.ifm.ifi_family = PF_BRIDGE,
+	};
+	char *d = NULL;
+	int backup_port_idx = -1;
+	__s8 neigh_suppress = -1;
+	__s8 learning = -1;
+	__s8 learning_sync = -1;
+	__s8 flood = -1;
+	__s8 vlan_tunnel = -1;
+	__s8 mcast_flood = -1;
+	__s8 mcast_to_unicast = -1;
+	__s8 isolated = -1;
+	__s8 hairpin = -1;
+	__s8 bpdu_guard = -1;
+	__s8 fast_leave = -1;
+	__s8 root_block = -1;
+	__u32 cost = 0;
+	__s16 priority = -1;
+	__s8 state = -1;
+	__s16 mode = -1;
+	__u16 flags = 0;
+	struct rtattr *nest;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (strcmp(*argv, "guard") == 0) {
+			NEXT_ARG();
+			if (!on_off("guard", &bpdu_guard, *argv))
+				return -1;
+		} else if (strcmp(*argv, "hairpin") == 0) {
+			NEXT_ARG();
+			if (!on_off("hairpin", &hairpin, *argv))
+				return -1;
+		} else if (strcmp(*argv, "fastleave") == 0) {
+			NEXT_ARG();
+			if (!on_off("fastleave", &fast_leave, *argv))
+				return -1;
+		} else if (strcmp(*argv, "root_block") == 0) {
+			NEXT_ARG();
+			if (!on_off("root_block", &root_block, *argv))
+				return -1;
+		} else if (strcmp(*argv, "learning") == 0) {
+			NEXT_ARG();
+			if (!on_off("learning", &learning, *argv))
+				return -1;
+		} else if (strcmp(*argv, "learning_sync") == 0) {
+			NEXT_ARG();
+			if (!on_off("learning_sync", &learning_sync, *argv))
+				return -1;
+		} else if (strcmp(*argv, "flood") == 0) {
+			NEXT_ARG();
+			if (!on_off("flood", &flood, *argv))
+				return -1;
+		} else if (strcmp(*argv, "mcast_flood") == 0) {
+			NEXT_ARG();
+			if (!on_off("mcast_flood", &mcast_flood, *argv))
+				return -1;
+		} else if (strcmp(*argv, "mcast_to_unicast") == 0) {
+			NEXT_ARG();
+			if (!on_off("mcast_to_unicast", &mcast_to_unicast, *argv))
+				return -1;
+		} else if (strcmp(*argv, "cost") == 0) {
+			NEXT_ARG();
+			cost = atoi(*argv);
+		} else if (strcmp(*argv, "priority") == 0) {
+			NEXT_ARG();
+			priority = atoi(*argv);
+		} else if (strcmp(*argv, "state") == 0) {
+			NEXT_ARG();
+			char *endptr;
+			size_t nstates = ARRAY_SIZE(port_states);
+
+			state = strtol(*argv, &endptr, 10);
+			if (!(**argv != '\0' && *endptr == '\0')) {
+				for (state = 0; state < nstates; state++)
+					if (strcmp(port_states[state], *argv) == 0)
+						break;
+				if (state == nstates) {
+					fprintf(stderr,
+						"Error: invalid STP port state\n");
+					return -1;
+				}
+			}
+		} else if (strcmp(*argv, "hwmode") == 0) {
+			NEXT_ARG();
+			flags = BRIDGE_FLAGS_SELF;
+			if (strcmp(*argv, "vepa") == 0)
+				mode = BRIDGE_MODE_VEPA;
+			else if (strcmp(*argv, "veb") == 0)
+				mode = BRIDGE_MODE_VEB;
+			else {
+				fprintf(stderr,
+					"Mode argument must be \"vepa\" or \"veb\".\n");
+				return -1;
+			}
+		} else if (strcmp(*argv, "self") == 0) {
+			flags |= BRIDGE_FLAGS_SELF;
+		} else if (strcmp(*argv, "master") == 0) {
+			flags |= BRIDGE_FLAGS_MASTER;
+		} else if (strcmp(*argv, "neigh_suppress") == 0) {
+			NEXT_ARG();
+			if (!on_off("neigh_suppress", &neigh_suppress,
+				    *argv))
+				return -1;
+		} else if (strcmp(*argv, "vlan_tunnel") == 0) {
+			NEXT_ARG();
+			if (!on_off("vlan_tunnel", &vlan_tunnel,
+				    *argv))
+				return -1;
+		} else if (strcmp(*argv, "isolated") == 0) {
+			NEXT_ARG();
+			if (!on_off("isolated", &isolated, *argv))
+				return -1;
+		} else if (strcmp(*argv, "backup_port") == 0) {
+			NEXT_ARG();
+			backup_port_idx = ll_name_to_index(*argv);
+			if (!backup_port_idx) {
+				fprintf(stderr, "Error: device %s does not exist\n",
+					*argv);
+				return -1;
+			}
+		} else if (strcmp(*argv, "nobackup_port") == 0) {
+			backup_port_idx = 0;
+		} else {
+			usage();
+		}
+		argc--; argv++;
+	}
+	if (d == NULL) {
+		fprintf(stderr, "Device is a required argument.\n");
+		return -1;
+	}
+
+
+	req.ifm.ifi_index = ll_name_to_index(d);
+	if (req.ifm.ifi_index == 0) {
+		fprintf(stderr, "Cannot find bridge device \"%s\"\n", d);
+		return -1;
+	}
+
+	/* Nested PROTINFO attribute.  Contains: port flags, cost, priority and
+	 * state.
+	 */
+	nest = addattr_nest(&req.n, sizeof(req),
+			    IFLA_PROTINFO | NLA_F_NESTED);
+	/* Flags first */
+	if (bpdu_guard >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_GUARD, bpdu_guard);
+	if (hairpin >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_MODE, hairpin);
+	if (fast_leave >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_FAST_LEAVE,
+			 fast_leave);
+	if (root_block >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_PROTECT, root_block);
+	if (flood >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_UNICAST_FLOOD, flood);
+	if (mcast_flood >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_MCAST_FLOOD,
+			 mcast_flood);
+	if (mcast_to_unicast >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_MCAST_TO_UCAST,
+			 mcast_to_unicast);
+	if (learning >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_LEARNING, learning);
+	if (learning_sync >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_LEARNING_SYNC,
+			 learning_sync);
+
+	if (cost > 0)
+		addattr32(&req.n, sizeof(req), IFLA_BRPORT_COST, cost);
+
+	if (priority >= 0)
+		addattr16(&req.n, sizeof(req), IFLA_BRPORT_PRIORITY, priority);
+
+	if (state >= 0)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_STATE, state);
+
+	if (neigh_suppress != -1)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_NEIGH_SUPPRESS,
+			 neigh_suppress);
+	if (vlan_tunnel != -1)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_VLAN_TUNNEL,
+			 vlan_tunnel);
+	if (isolated != -1)
+		addattr8(&req.n, sizeof(req), IFLA_BRPORT_ISOLATED, isolated);
+
+	if (backup_port_idx != -1)
+		addattr32(&req.n, sizeof(req), IFLA_BRPORT_BACKUP_PORT,
+			  backup_port_idx);
+
+	addattr_nest_end(&req.n, nest);
+
+	/* IFLA_AF_SPEC nested attribute. Contains IFLA_BRIDGE_FLAGS that
+	 * designates master or self operation and IFLA_BRIDGE_MODE
+	 * for hw 'vepa' or 'veb' operation modes. The hwmodes are
+	 * only valid in 'self' mode on some devices so far.
+	 */
+	if (mode >= 0 || flags > 0) {
+		nest = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+
+		if (flags > 0)
+			addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, flags);
+
+		if (mode >= 0)
+			addattr16(&req.n, sizeof(req), IFLA_BRIDGE_MODE, mode);
+
+		addattr_nest_end(&req.n, nest);
+	}
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -1;
+
+	return 0;
+}
+
+static int brlink_show(int argc, char **argv)
+{
+	char *filter_dev = NULL;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (filter_dev)
+				duparg("dev", *argv);
+			filter_dev = *argv;
+		}
+		argc--; argv++;
+	}
+
+	if (filter_dev) {
+		filter_index = ll_name_to_index(filter_dev);
+		if (!filter_index)
+			return nodev(filter_dev);
+	}
+
+	if (show_details) {
+		if (rtnl_linkdump_req_filter(&rth, PF_BRIDGE,
+					     (compress_vlans ?
+					      RTEXT_FILTER_BRVLAN_COMPRESSED :
+					      RTEXT_FILTER_BRVLAN)) < 0) {
+			perror("Cannon send dump request");
+			exit(1);
+		}
+	} else {
+		if (rtnl_linkdump_req(&rth, PF_BRIDGE) < 0) {
+			perror("Cannon send dump request");
+			exit(1);
+		}
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&rth, print_linkinfo, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+
+	delete_json_obj();
+	fflush(stdout);
+	return 0;
+}
+
+int do_link(int argc, char **argv)
+{
+	ll_init_map(&rth);
+	if (argc > 0) {
+		if (matches(*argv, "set") == 0 ||
+		    matches(*argv, "change") == 0)
+			return brlink_modify(argc-1, argv+1);
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return brlink_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return brlink_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"bridge link help\".\n", *argv);
+	exit(-1);
+}
diff --git a/bridge/mdb.c b/bridge/mdb.c
new file mode 100644
index 0000000..928ae56
--- /dev/null
+++ b/bridge/mdb.c
@@ -0,0 +1,462 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Get mdb table with netlink
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "libnetlink.h"
+#include "br_common.h"
+#include "rt_names.h"
+#include "utils.h"
+#include "json_print.h"
+
+#ifndef MDBA_RTA
+#define MDBA_RTA(r) \
+	((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct br_port_msg))))
+#endif
+
+static unsigned int filter_index, filter_vlan;
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: bridge mdb { add | del } dev DEV port PORT grp GROUP [permanent | temp] [vid VID]\n"
+		"       bridge mdb {show} [ dev DEV ] [ vid VID ]\n");
+	exit(-1);
+}
+
+static bool is_temp_mcast_rtr(__u8 type)
+{
+	return type == MDB_RTR_TYPE_TEMP_QUERY || type == MDB_RTR_TYPE_TEMP;
+}
+
+static const char *format_timer(__u32 ticks)
+{
+	struct timeval tv;
+	static char tbuf[32];
+
+	__jiffies_to_tv(&tv, ticks);
+	snprintf(tbuf, sizeof(tbuf), "%4lu.%.2lu",
+		 (unsigned long)tv.tv_sec,
+		 (unsigned long)tv.tv_usec / 10000);
+
+	return tbuf;
+}
+
+static void __print_router_port_stats(FILE *f, struct rtattr *pattr)
+{
+	struct rtattr *tb[MDBA_ROUTER_PATTR_MAX + 1];
+
+	parse_rtattr(tb, MDBA_ROUTER_PATTR_MAX, MDB_RTR_RTA(RTA_DATA(pattr)),
+		     RTA_PAYLOAD(pattr) - RTA_ALIGN(sizeof(uint32_t)));
+
+	if (tb[MDBA_ROUTER_PATTR_TIMER]) {
+		__u32 timer = rta_getattr_u32(tb[MDBA_ROUTER_PATTR_TIMER]);
+
+		print_string(PRINT_ANY, "timer", " %s",
+			     format_timer(timer));
+	}
+
+	if (tb[MDBA_ROUTER_PATTR_TYPE]) {
+		__u8 type = rta_getattr_u8(tb[MDBA_ROUTER_PATTR_TYPE]);
+
+		print_string(PRINT_ANY, "type", " %s",
+			     is_temp_mcast_rtr(type) ? "temp" : "permanent");
+	}
+}
+
+static void br_print_router_ports(FILE *f, struct rtattr *attr,
+				  const char *brifname)
+{
+	int rem = RTA_PAYLOAD(attr);
+	struct rtattr *i;
+
+	if (is_json_context())
+		open_json_array(PRINT_JSON, brifname);
+	else if (!show_stats)
+		fprintf(f, "router ports on %s: ", brifname);
+
+	for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		uint32_t *port_ifindex = RTA_DATA(i);
+		const char *port_ifname = ll_index_to_name(*port_ifindex);
+
+		if (is_json_context()) {
+			open_json_object(NULL);
+			print_string(PRINT_JSON, "port", NULL, port_ifname);
+
+			if (show_stats)
+				__print_router_port_stats(f, i);
+			close_json_object();
+		} else if (show_stats) {
+			fprintf(f, "router ports on %s: %s",
+				brifname, port_ifname);
+
+			__print_router_port_stats(f, i);
+			fprintf(f, "\n");
+		} else {
+			fprintf(f, "%s ", port_ifname);
+		}
+	}
+
+	if (!show_stats)
+		print_nl();
+
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e,
+			    struct nlmsghdr *n, struct rtattr **tb)
+{
+	SPRINT_BUF(abuf);
+	const char *dev;
+	const void *src;
+	int af;
+
+	if (filter_vlan && e->vid != filter_vlan)
+		return;
+
+	af = e->addr.proto == htons(ETH_P_IP) ? AF_INET : AF_INET6;
+	src = af == AF_INET ? (const void *)&e->addr.u.ip4 :
+			      (const void *)&e->addr.u.ip6;
+	dev = ll_index_to_name(ifindex);
+
+	open_json_object(NULL);
+
+	print_int(PRINT_JSON, "index", NULL, ifindex);
+	print_color_string(PRINT_ANY, COLOR_IFNAME, "dev", "dev %s", dev);
+	print_string(PRINT_ANY, "port", " port %s",
+		     ll_index_to_name(e->ifindex));
+
+	print_color_string(PRINT_ANY, ifa_family_color(af),
+			    "grp", " grp %s",
+			    inet_ntop(af, src, abuf, sizeof(abuf)));
+
+	print_string(PRINT_ANY, "state", " %s",
+			   (e->state & MDB_PERMANENT) ? "permanent" : "temp");
+
+	open_json_array(PRINT_JSON, "flags");
+	if (e->flags & MDB_FLAGS_OFFLOAD)
+		print_string(PRINT_ANY, NULL, " %s", "offload");
+	close_json_array(PRINT_JSON, NULL);
+
+	if (e->vid)
+		print_uint(PRINT_ANY, "vid", " vid %u", e->vid);
+
+	if (show_stats && tb && tb[MDBA_MDB_EATTR_TIMER]) {
+		__u32 timer = rta_getattr_u32(tb[MDBA_MDB_EATTR_TIMER]);
+
+		print_string(PRINT_ANY, "timer", " %s",
+			     format_timer(timer));
+	}
+
+	print_nl();
+	close_json_object();
+}
+
+static void br_print_mdb_entry(FILE *f, int ifindex, struct rtattr *attr,
+			       struct nlmsghdr *n)
+{
+	struct rtattr *etb[MDBA_MDB_EATTR_MAX + 1];
+	struct br_mdb_entry *e;
+	struct rtattr *i;
+	int rem;
+
+	rem = RTA_PAYLOAD(attr);
+	for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		e = RTA_DATA(i);
+		parse_rtattr(etb, MDBA_MDB_EATTR_MAX, MDB_RTA(RTA_DATA(i)),
+			     RTA_PAYLOAD(i) - RTA_ALIGN(sizeof(*e)));
+		print_mdb_entry(f, ifindex, e, n, etb);
+	}
+}
+
+static void print_mdb_entries(FILE *fp, struct nlmsghdr *n,
+			      int ifindex,  struct rtattr *mdb)
+{
+	int rem = RTA_PAYLOAD(mdb);
+	struct rtattr *i;
+
+	for (i = RTA_DATA(mdb); RTA_OK(i, rem); i = RTA_NEXT(i, rem))
+		br_print_mdb_entry(fp, ifindex, i, n);
+}
+
+static void print_router_entries(FILE *fp, struct nlmsghdr *n,
+				 int ifindex, struct rtattr *router)
+{
+	const char *brifname = ll_index_to_name(ifindex);
+
+	if (n->nlmsg_type == RTM_GETMDB) {
+		if (show_details)
+			br_print_router_ports(fp, router, brifname);
+	} else {
+		struct rtattr *i = RTA_DATA(router);
+		uint32_t *port_ifindex = RTA_DATA(i);
+		const char *port_name = ll_index_to_name(*port_ifindex);
+
+		if (is_json_context()) {
+			open_json_array(PRINT_JSON, brifname);
+			open_json_object(NULL);
+
+			print_string(PRINT_JSON, "port", NULL,
+				     port_name);
+			close_json_object();
+			close_json_array(PRINT_JSON, NULL);
+		} else {
+			fprintf(fp, "router port dev %s master %s\n",
+				port_name, brifname);
+		}
+	}
+}
+
+static int __parse_mdb_nlmsg(struct nlmsghdr *n, struct rtattr **tb)
+{
+	struct br_port_msg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+
+	if (n->nlmsg_type != RTM_GETMDB &&
+	    n->nlmsg_type != RTM_NEWMDB &&
+	    n->nlmsg_type != RTM_DELMDB) {
+		fprintf(stderr,
+			"Not RTM_GETMDB, RTM_NEWMDB or RTM_DELMDB: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (filter_index && filter_index != r->ifindex)
+		return 0;
+
+	parse_rtattr(tb, MDBA_MAX, MDBA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+	return 1;
+}
+
+static int print_mdbs(struct nlmsghdr *n, void *arg)
+{
+	struct br_port_msg *r = NLMSG_DATA(n);
+	struct rtattr *tb[MDBA_MAX+1];
+	FILE *fp = arg;
+	int ret;
+
+	ret = __parse_mdb_nlmsg(n, tb);
+	if (ret != 1)
+		return ret;
+
+	if (tb[MDBA_MDB])
+		print_mdb_entries(fp, n, r->ifindex, tb[MDBA_MDB]);
+
+	return 0;
+}
+
+static int print_rtrs(struct nlmsghdr *n, void *arg)
+{
+	struct br_port_msg *r = NLMSG_DATA(n);
+	struct rtattr *tb[MDBA_MAX+1];
+	FILE *fp = arg;
+	int ret;
+
+	ret = __parse_mdb_nlmsg(n, tb);
+	if (ret != 1)
+		return ret;
+
+	if (tb[MDBA_ROUTER])
+		print_router_entries(fp, n, r->ifindex, tb[MDBA_ROUTER]);
+
+	return 0;
+}
+
+int print_mdb_mon(struct nlmsghdr *n, void *arg)
+{
+	struct br_port_msg *r = NLMSG_DATA(n);
+	struct rtattr *tb[MDBA_MAX+1];
+	FILE *fp = arg;
+	int ret;
+
+	ret = __parse_mdb_nlmsg(n, tb);
+	if (ret != 1)
+		return ret;
+
+	if (n->nlmsg_type == RTM_DELMDB)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	if (tb[MDBA_MDB])
+		print_mdb_entries(fp, n, r->ifindex, tb[MDBA_MDB]);
+
+	if (tb[MDBA_ROUTER])
+		print_router_entries(fp, n, r->ifindex, tb[MDBA_ROUTER]);
+
+	return 0;
+}
+
+static int mdb_show(int argc, char **argv)
+{
+	char *filter_dev = NULL;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (filter_dev)
+				duparg("dev", *argv);
+			filter_dev = *argv;
+		} else if (strcmp(*argv, "vid") == 0) {
+			NEXT_ARG();
+			if (filter_vlan)
+				duparg("vid", *argv);
+			filter_vlan = atoi(*argv);
+		}
+		argc--; argv++;
+	}
+
+	if (filter_dev) {
+		filter_index = ll_name_to_index(filter_dev);
+		if (!filter_index)
+			return nodev(filter_dev);
+	}
+
+	new_json_obj(json);
+	open_json_object(NULL);
+
+	/* get mdb entries */
+	if (rtnl_mdbdump_req(&rth, PF_BRIDGE) < 0) {
+		perror("Cannot send dump request");
+		return -1;
+	}
+
+	open_json_array(PRINT_JSON, "mdb");
+	if (rtnl_dump_filter(&rth, print_mdbs, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return -1;
+	}
+	close_json_array(PRINT_JSON, NULL);
+
+	/* get router ports */
+	if (rtnl_mdbdump_req(&rth, PF_BRIDGE) < 0) {
+		perror("Cannot send dump request");
+		return -1;
+	}
+
+	open_json_object("router");
+	if (rtnl_dump_filter(&rth, print_rtrs, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return -1;
+	}
+	close_json_object();
+
+	close_json_object();
+	delete_json_obj();
+	fflush(stdout);
+
+	return 0;
+}
+
+static int mdb_modify(int cmd, int flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct br_port_msg	bpm;
+		char			buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_port_msg)),
+		.n.nlmsg_flags = NLM_F_REQUEST | flags,
+		.n.nlmsg_type = cmd,
+		.bpm.family = PF_BRIDGE,
+	};
+	struct br_mdb_entry entry = {};
+	char *d = NULL, *p = NULL, *grp = NULL;
+	short vid = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (strcmp(*argv, "grp") == 0) {
+			NEXT_ARG();
+			grp = *argv;
+		} else if (strcmp(*argv, "port") == 0) {
+			NEXT_ARG();
+			p = *argv;
+		} else if (strcmp(*argv, "permanent") == 0) {
+			if (cmd == RTM_NEWMDB)
+				entry.state |= MDB_PERMANENT;
+		} else if (strcmp(*argv, "temp") == 0) {
+			;/* nothing */
+		} else if (strcmp(*argv, "vid") == 0) {
+			NEXT_ARG();
+			vid = atoi(*argv);
+		} else {
+			if (matches(*argv, "help") == 0)
+				usage();
+		}
+		argc--; argv++;
+	}
+
+	if (d == NULL || grp == NULL || p == NULL) {
+		fprintf(stderr, "Device, group address and port name are required arguments.\n");
+		return -1;
+	}
+
+	req.bpm.ifindex = ll_name_to_index(d);
+	if (!req.bpm.ifindex)
+		return nodev(d);
+
+	entry.ifindex = ll_name_to_index(p);
+	if (!entry.ifindex)
+		return nodev(p);
+
+	if (!inet_pton(AF_INET, grp, &entry.addr.u.ip4)) {
+		if (!inet_pton(AF_INET6, grp, &entry.addr.u.ip6)) {
+			fprintf(stderr, "Invalid address \"%s\"\n", grp);
+			return -1;
+		} else
+			entry.addr.proto = htons(ETH_P_IPV6);
+	} else
+		entry.addr.proto = htons(ETH_P_IP);
+
+	entry.vid = vid;
+	addattr_l(&req.n, sizeof(req), MDBA_SET_ENTRY, &entry, sizeof(entry));
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -1;
+
+	return 0;
+}
+
+int do_mdb(int argc, char **argv)
+{
+	ll_init_map(&rth);
+
+	if (argc > 0) {
+		if (matches(*argv, "add") == 0)
+			return mdb_modify(RTM_NEWMDB, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+		if (matches(*argv, "delete") == 0)
+			return mdb_modify(RTM_DELMDB, 0, argc-1, argv+1);
+
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return mdb_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return mdb_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"bridge mdb help\".\n", *argv);
+	exit(-1);
+}
diff --git a/bridge/monitor.c b/bridge/monitor.c
new file mode 100644
index 0000000..08439a6
--- /dev/null
+++ b/bridge/monitor.c
@@ -0,0 +1,143 @@
+/*
+ * brmonitor.c		"bridge monitor"
+ *
+ *		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.
+ *
+ * Authors:	Stephen Hemminger <shemminger@vyatta.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/neighbour.h>
+#include <string.h>
+
+#include "utils.h"
+#include "br_common.h"
+
+
+static void usage(void) __attribute__((noreturn));
+static int prefix_banner;
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: bridge monitor [file | link | fdb | mdb | all]\n");
+	exit(-1);
+}
+
+static int accept_msg(struct rtnl_ctrl_data *ctrl,
+		      struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = arg;
+
+	if (timestamp)
+		print_timestamp(fp);
+
+	switch (n->nlmsg_type) {
+	case RTM_NEWLINK:
+	case RTM_DELLINK:
+		if (prefix_banner)
+			fprintf(fp, "[LINK]");
+
+		return print_linkinfo(n, arg);
+
+	case RTM_NEWNEIGH:
+	case RTM_DELNEIGH:
+		if (prefix_banner)
+			fprintf(fp, "[NEIGH]");
+		return print_fdb(n, arg);
+
+	case RTM_NEWMDB:
+	case RTM_DELMDB:
+		if (prefix_banner)
+			fprintf(fp, "[MDB]");
+		return print_mdb_mon(n, arg);
+
+	case NLMSG_TSTAMP:
+		print_nlmsg_timestamp(fp, n);
+		return 0;
+
+	default:
+		return 0;
+	}
+}
+
+int do_monitor(int argc, char **argv)
+{
+	char *file = NULL;
+	unsigned int groups = ~RTMGRP_TC;
+	int llink = 0;
+	int lneigh = 0;
+	int lmdb = 0;
+
+	rtnl_close(&rth);
+
+	while (argc > 0) {
+		if (matches(*argv, "file") == 0) {
+			NEXT_ARG();
+			file = *argv;
+		} else if (matches(*argv, "link") == 0) {
+			llink = 1;
+			groups = 0;
+		} else if (matches(*argv, "fdb") == 0) {
+			lneigh = 1;
+			groups = 0;
+		} else if (matches(*argv, "mdb") == 0) {
+			lmdb = 1;
+			groups = 0;
+		} else if (strcmp(*argv, "all") == 0) {
+			groups = ~RTMGRP_TC;
+			prefix_banner = 1;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			fprintf(stderr, "Argument \"%s\" is unknown, try \"bridge monitor help\".\n", *argv);
+			exit(-1);
+		}
+		argc--;	argv++;
+	}
+
+	if (llink)
+		groups |= nl_mgrp(RTNLGRP_LINK);
+
+	if (lneigh) {
+		groups |= nl_mgrp(RTNLGRP_NEIGH);
+	}
+
+	if (lmdb) {
+		groups |= nl_mgrp(RTNLGRP_MDB);
+	}
+
+	if (file) {
+		FILE *fp;
+		int err;
+
+		fp = fopen(file, "r");
+		if (fp == NULL) {
+			perror("Cannot fopen");
+			exit(-1);
+		}
+		err = rtnl_from_file(fp, accept_msg, stdout);
+		fclose(fp);
+		return err;
+	}
+
+	if (rtnl_open(&rth, groups) < 0)
+		exit(1);
+	ll_init_map(&rth);
+
+	if (rtnl_listen(&rth, accept_msg, stdout) < 0)
+		exit(2);
+
+	return 0;
+}
diff --git a/bridge/vlan.c b/bridge/vlan.c
new file mode 100644
index 0000000..6d33b0a
--- /dev/null
+++ b/bridge/vlan.c
@@ -0,0 +1,693 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <string.h>
+
+#include "json_print.h"
+#include "libnetlink.h"
+#include "br_common.h"
+#include "utils.h"
+
+static unsigned int filter_index, filter_vlan;
+static int show_vlan_tunnel_info = 0;
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: bridge vlan { add | del } vid VLAN_ID dev DEV [ tunnel_info id TUNNEL_ID ]\n"
+		"                                                     [ pvid ] [ untagged ]\n"
+		"                                                     [ self ] [ master ]\n"
+		"       bridge vlan { show } [ dev DEV ] [ vid VLAN_ID ]\n"
+		"       bridge vlan { tunnelshow } [ dev DEV ] [ vid VLAN_ID ]\n");
+	exit(-1);
+}
+
+static int parse_tunnel_info(int *argcp, char ***argvp, __u32 *tun_id_start,
+			     __u32 *tun_id_end)
+{
+	char **argv = *argvp;
+	int argc = *argcp;
+	char *t;
+
+	NEXT_ARG();
+	if (!matches(*argv, "id")) {
+		NEXT_ARG();
+		t = strchr(*argv, '-');
+		if (t) {
+			*t = '\0';
+			if (get_u32(tun_id_start, *argv, 0) ||
+				    *tun_id_start >= 1u << 24)
+				invarg("invalid tun id", *argv);
+			if (get_u32(tun_id_end, t + 1, 0) ||
+				    *tun_id_end >= 1u << 24)
+				invarg("invalid tun id", *argv);
+
+		} else {
+			if (get_u32(tun_id_start, *argv, 0) ||
+				    *tun_id_start >= 1u << 24)
+				invarg("invalid tun id", *argv);
+		}
+	} else {
+		invarg("tunnel id expected", *argv);
+	}
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static int add_tunnel_info(struct nlmsghdr *n, int reqsize,
+			   __u16 vid, __u32 tun_id, __u16 flags)
+{
+	struct rtattr *tinfo;
+
+	tinfo = addattr_nest(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_INFO);
+	addattr32(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_ID, tun_id);
+	addattr32(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_VID, vid);
+	addattr32(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, flags);
+
+	addattr_nest_end(n, tinfo);
+
+	return 0;
+}
+
+static int add_tunnel_info_range(struct nlmsghdr *n, int reqsize,
+				 __u16 vid_start, int16_t vid_end,
+				 __u32 tun_id_start, __u32 tun_id_end)
+{
+	if (vid_end != -1 && (vid_end - vid_start) > 0) {
+		add_tunnel_info(n, reqsize, vid_start, tun_id_start,
+				BRIDGE_VLAN_INFO_RANGE_BEGIN);
+
+		add_tunnel_info(n, reqsize, vid_end, tun_id_end,
+				BRIDGE_VLAN_INFO_RANGE_END);
+	} else {
+		add_tunnel_info(n, reqsize, vid_start, tun_id_start, 0);
+	}
+
+	return 0;
+}
+
+static int add_vlan_info_range(struct nlmsghdr *n, int reqsize, __u16 vid_start,
+			       int16_t vid_end, __u16 flags)
+{
+	struct bridge_vlan_info vinfo = {};
+
+	vinfo.flags = flags;
+	vinfo.vid = vid_start;
+	if (vid_end != -1) {
+		/* send vlan range start */
+		addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo,
+			  sizeof(vinfo));
+		vinfo.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN;
+
+		/* Now send the vlan range end */
+		vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_END;
+		vinfo.vid = vid_end;
+		addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo,
+			  sizeof(vinfo));
+	} else {
+		addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo,
+			  sizeof(vinfo));
+	}
+
+	return 0;
+}
+
+static int vlan_modify(int cmd, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct ifinfomsg	ifm;
+		char			buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = cmd,
+		.ifm.ifi_family = PF_BRIDGE,
+	};
+	char *d = NULL;
+	short vid = -1;
+	short vid_end = -1;
+	struct rtattr *afspec;
+	struct bridge_vlan_info vinfo = {};
+	bool tunnel_info_set = false;
+	unsigned short flags = 0;
+	__u32 tun_id_start = 0;
+	__u32 tun_id_end = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (strcmp(*argv, "vid") == 0) {
+			char *p;
+
+			NEXT_ARG();
+			p = strchr(*argv, '-');
+			if (p) {
+				*p = '\0';
+				p++;
+				vid = atoi(*argv);
+				vid_end = atoi(p);
+				vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
+			} else {
+				vid = atoi(*argv);
+			}
+		} else if (strcmp(*argv, "self") == 0) {
+			flags |= BRIDGE_FLAGS_SELF;
+		} else if (strcmp(*argv, "master") == 0) {
+			flags |= BRIDGE_FLAGS_MASTER;
+		} else if (strcmp(*argv, "pvid") == 0) {
+			vinfo.flags |= BRIDGE_VLAN_INFO_PVID;
+		} else if (strcmp(*argv, "untagged") == 0) {
+			vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
+		} else if (strcmp(*argv, "tunnel_info") == 0) {
+				if (parse_tunnel_info(&argc, &argv,
+						      &tun_id_start,
+						      &tun_id_end))
+					return -1;
+				tunnel_info_set = true;
+		} else {
+			if (matches(*argv, "help") == 0)
+				NEXT_ARG();
+		}
+		argc--; argv++;
+	}
+
+	if (d == NULL || vid == -1) {
+		fprintf(stderr, "Device and VLAN ID are required arguments.\n");
+		return -1;
+	}
+
+	req.ifm.ifi_index = ll_name_to_index(d);
+	if (req.ifm.ifi_index == 0) {
+		fprintf(stderr, "Cannot find bridge device \"%s\"\n", d);
+		return -1;
+	}
+
+	if (vid >= 4096) {
+		fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid);
+		return -1;
+	}
+
+	if (vinfo.flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
+		if (vid_end == -1 || vid_end >= 4096 || vid >= vid_end) {
+			fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n",
+				vid, vid_end);
+			return -1;
+		}
+		if (vinfo.flags & BRIDGE_VLAN_INFO_PVID) {
+			fprintf(stderr,
+				"pvid cannot be configured for a vlan range\n");
+			return -1;
+		}
+	}
+
+	afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+
+	if (flags)
+		addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, flags);
+
+	if (tunnel_info_set)
+		add_tunnel_info_range(&req.n, sizeof(req), vid, vid_end,
+				      tun_id_start, tun_id_end);
+	else
+		add_vlan_info_range(&req.n, sizeof(req), vid, vid_end,
+				    vinfo.flags);
+
+	addattr_nest_end(&req.n, afspec);
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -1;
+
+	return 0;
+}
+
+/* In order to use this function for both filtering and non-filtering cases
+ * we need to make it a tristate:
+ * return -1 - if filtering we've gone over so don't continue
+ * return  0 - skip entry and continue (applies to range start or to entries
+ *             which are less than filter_vlan)
+ * return  1 - print the entry and continue
+ */
+static int filter_vlan_check(__u16 vid, __u16 flags)
+{
+	/* if we're filtering we should stop on the first greater entry */
+	if (filter_vlan && vid > filter_vlan &&
+	    !(flags & BRIDGE_VLAN_INFO_RANGE_END))
+		return -1;
+	if ((flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) ||
+	    vid < filter_vlan)
+		return 0;
+
+	return 1;
+}
+
+static void open_vlan_port(int ifi_index, const char *fmt)
+{
+	open_json_object(NULL);
+	print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname", fmt,
+			   ll_index_to_name(ifi_index));
+	open_json_array(PRINT_JSON, "vlans");
+}
+
+static void close_vlan_port(void)
+{
+	close_json_array(PRINT_JSON, NULL);
+	close_json_object();
+}
+
+static void print_range(const char *name, __u16 start, __u16 id)
+{
+	char end[64];
+
+	snprintf(end, sizeof(end), "%sEnd", name);
+
+	print_hu(PRINT_ANY, name, "\t %hu", start);
+	if (start != id)
+		print_hu(PRINT_ANY, end, "-%hu", id);
+
+}
+
+static void print_vlan_tunnel_info(FILE *fp, struct rtattr *tb, int ifindex)
+{
+	struct rtattr *i, *list = tb;
+	int rem = RTA_PAYLOAD(list);
+	__u16 last_vid_start = 0;
+	__u32 last_tunid_start = 0;
+
+	if (!filter_vlan)
+		open_vlan_port(ifindex, "%s");
+
+	open_json_array(PRINT_JSON, "tunnel");
+	for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		struct rtattr *ttb[IFLA_BRIDGE_VLAN_TUNNEL_MAX+1];
+		__u32 tunnel_id = 0;
+		__u16 tunnel_vid = 0;
+		__u16 tunnel_flags = 0;
+		int vcheck_ret;
+
+		if (i->rta_type != IFLA_BRIDGE_VLAN_TUNNEL_INFO)
+			continue;
+
+		parse_rtattr(ttb, IFLA_BRIDGE_VLAN_TUNNEL_MAX,
+			     RTA_DATA(i), RTA_PAYLOAD(i));
+
+		if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_VID])
+			tunnel_vid =
+				rta_getattr_u32(ttb[IFLA_BRIDGE_VLAN_TUNNEL_VID]);
+		else
+			continue;
+
+		if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_ID])
+			tunnel_id =
+				rta_getattr_u32(ttb[IFLA_BRIDGE_VLAN_TUNNEL_ID]);
+
+		if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS])
+			tunnel_flags =
+				rta_getattr_u32(ttb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]);
+
+		if (!(tunnel_flags & BRIDGE_VLAN_INFO_RANGE_END)) {
+			last_vid_start = tunnel_vid;
+			last_tunid_start = tunnel_id;
+		}
+
+		vcheck_ret = filter_vlan_check(tunnel_vid, tunnel_flags);
+		if (vcheck_ret == -1)
+			break;
+		else if (vcheck_ret == 0)
+			continue;
+
+		if (tunnel_flags & BRIDGE_VLAN_INFO_RANGE_BEGIN)
+			continue;
+
+		if (filter_vlan)
+			open_vlan_port(ifindex, "%s");
+
+		open_json_object(NULL);
+		print_range("vlan", last_vid_start, tunnel_vid);
+		print_range("tunid", last_tunid_start, tunnel_id);
+		close_json_object();
+
+		print_string(PRINT_FP, NULL, "%s", _SL_);
+		if (filter_vlan)
+			close_vlan_port();
+	}
+
+	if (!filter_vlan)
+		close_vlan_port();
+}
+
+static int print_vlan_tunnel(struct nlmsghdr *n, void *arg)
+{
+	struct ifinfomsg *ifm = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_MAX+1];
+	int len = n->nlmsg_len;
+	FILE *fp = arg;
+
+	if (n->nlmsg_type != RTM_NEWLINK) {
+		fprintf(stderr, "Not RTM_NEWLINK: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(sizeof(*ifm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (ifm->ifi_family != AF_BRIDGE)
+		return 0;
+
+	if (filter_index && filter_index != ifm->ifi_index)
+		return 0;
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifm), len);
+
+	/* if AF_SPEC isn't there, vlan table is not preset for this port */
+	if (!tb[IFLA_AF_SPEC]) {
+		if (!filter_vlan && !is_json_context()) {
+			color_fprintf(fp, COLOR_IFNAME, "%s",
+				      ll_index_to_name(ifm->ifi_index));
+			fprintf(fp, "\tNone\n");
+		}
+		return 0;
+	}
+
+	print_vlan_tunnel_info(fp, tb[IFLA_AF_SPEC], ifm->ifi_index);
+
+	fflush(fp);
+	return 0;
+}
+
+static int print_vlan(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = arg;
+	struct ifinfomsg *ifm = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[IFLA_MAX+1];
+
+	if (n->nlmsg_type != RTM_NEWLINK) {
+		fprintf(stderr, "Not RTM_NEWLINK: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(sizeof(*ifm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (ifm->ifi_family != AF_BRIDGE)
+		return 0;
+
+	if (filter_index && filter_index != ifm->ifi_index)
+		return 0;
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifm), len);
+
+	/* if AF_SPEC isn't there, vlan table is not preset for this port */
+	if (!tb[IFLA_AF_SPEC]) {
+		if (!filter_vlan && !is_json_context()) {
+			color_fprintf(fp, COLOR_IFNAME, "%s",
+				      ll_index_to_name(ifm->ifi_index));
+			fprintf(fp, "\tNone\n");
+		}
+		return 0;
+	}
+
+	print_vlan_info(tb[IFLA_AF_SPEC], ifm->ifi_index);
+	print_string(PRINT_FP, NULL, "%s", _SL_);
+
+	fflush(fp);
+	return 0;
+}
+
+static void print_vlan_flags(__u16 flags)
+{
+	if (flags == 0)
+		return;
+
+	open_json_array(PRINT_JSON, "flags");
+	if (flags & BRIDGE_VLAN_INFO_PVID)
+		print_string(PRINT_ANY, NULL, " %s", "PVID");
+
+	if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
+		print_string(PRINT_ANY, NULL, " %s", "Egress Untagged");
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static void print_one_vlan_stats(const struct bridge_vlan_xstats *vstats)
+{
+	open_json_object(NULL);
+	print_hu(PRINT_ANY, "vid", " %hu", vstats->vid);
+
+	print_vlan_flags(vstats->flags);
+
+	print_lluint(PRINT_ANY, "rx_bytes",
+		     "\n                   RX: %llu bytes",
+		     vstats->rx_bytes);
+	print_lluint(PRINT_ANY, "rx_packets", " %llu packets\n",
+		vstats->rx_packets);
+	print_lluint(PRINT_ANY, "tx_bytes",
+		     "                   TX: %llu bytes",
+		     vstats->tx_bytes);
+	print_lluint(PRINT_ANY, "tx_packets", " %llu packets\n",
+		vstats->tx_packets);
+	close_json_object();
+}
+
+static void print_vlan_stats_attr(struct rtattr *attr, int ifindex)
+{
+	struct rtattr *brtb[LINK_XSTATS_TYPE_MAX+1];
+	struct rtattr *i, *list;
+	bool found_vlan = false;
+	int rem;
+
+	parse_rtattr(brtb, LINK_XSTATS_TYPE_MAX, RTA_DATA(attr),
+		     RTA_PAYLOAD(attr));
+	if (!brtb[LINK_XSTATS_TYPE_BRIDGE])
+		return;
+
+	list = brtb[LINK_XSTATS_TYPE_BRIDGE];
+	rem = RTA_PAYLOAD(list);
+
+	for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		const struct bridge_vlan_xstats *vstats = RTA_DATA(i);
+
+		if (i->rta_type != BRIDGE_XSTATS_VLAN)
+			continue;
+
+		if (filter_vlan && filter_vlan != vstats->vid)
+			continue;
+
+		/* skip pure port entries, they'll be dumped via the slave stats call */
+		if ((vstats->flags & BRIDGE_VLAN_INFO_MASTER) &&
+		    !(vstats->flags & BRIDGE_VLAN_INFO_BRENTRY))
+			continue;
+
+		/* found vlan stats, first time print the interface name */
+		if (!found_vlan) {
+			open_vlan_port(ifindex, "%-16s");
+			found_vlan = true;
+		} else {
+			print_string(PRINT_FP, NULL, "%-16s", "");
+		}
+		print_one_vlan_stats(vstats);
+	}
+
+	/* vlan_port is opened only if there are any vlan stats */
+	if (found_vlan)
+		close_vlan_port();
+}
+
+static int print_vlan_stats(struct nlmsghdr *n, void *arg)
+{
+	struct if_stats_msg *ifsm = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_STATS_MAX+1];
+	int len = n->nlmsg_len;
+	FILE *fp = arg;
+
+	len -= NLMSG_LENGTH(sizeof(*ifsm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (filter_index && filter_index != ifsm->ifindex)
+		return 0;
+
+	parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len);
+
+	/* We have to check if any of the two attrs are usable */
+	if (tb[IFLA_STATS_LINK_XSTATS])
+		print_vlan_stats_attr(tb[IFLA_STATS_LINK_XSTATS],
+				      ifsm->ifindex);
+
+	if (tb[IFLA_STATS_LINK_XSTATS_SLAVE])
+		print_vlan_stats_attr(tb[IFLA_STATS_LINK_XSTATS_SLAVE],
+				      ifsm->ifindex);
+
+	fflush(fp);
+	return 0;
+}
+
+static int vlan_show(int argc, char **argv)
+{
+	char *filter_dev = NULL;
+	int ret = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (filter_dev)
+				duparg("dev", *argv);
+			filter_dev = *argv;
+		} else if (strcmp(*argv, "vid") == 0) {
+			NEXT_ARG();
+			if (filter_vlan)
+				duparg("vid", *argv);
+			filter_vlan = atoi(*argv);
+		}
+		argc--; argv++;
+	}
+
+	if (filter_dev) {
+		filter_index = ll_name_to_index(filter_dev);
+		if (!filter_index)
+			return nodev(filter_dev);
+	}
+
+	new_json_obj(json);
+
+	if (!show_stats) {
+		if (rtnl_linkdump_req_filter(&rth, PF_BRIDGE,
+					     (compress_vlans ?
+					      RTEXT_FILTER_BRVLAN_COMPRESSED :
+					      RTEXT_FILTER_BRVLAN)) < 0) {
+			perror("Cannont send dump request");
+			exit(1);
+		}
+
+		if (!is_json_context()) {
+			if (show_vlan_tunnel_info)
+				printf("port\tvlan ids\ttunnel id\n");
+			else
+				printf("port\tvlan ids\n");
+		}
+
+		if (show_vlan_tunnel_info)
+			ret = rtnl_dump_filter(&rth, print_vlan_tunnel,
+					       stdout);
+		else
+			ret = rtnl_dump_filter(&rth, print_vlan, stdout);
+		if (ret < 0) {
+			fprintf(stderr, "Dump ternminated\n");
+			exit(1);
+		}
+	} else {
+		__u32 filt_mask;
+
+		filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS);
+		if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) {
+			perror("Cannont send dump request");
+			exit(1);
+		}
+
+		if (!is_json_context())
+			printf("%-16s vlan id\n", "port");
+
+		if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+
+		filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS_SLAVE);
+		if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) {
+			perror("Cannont send slave dump request");
+			exit(1);
+		}
+
+		if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+	}
+
+	delete_json_obj();
+	fflush(stdout);
+	return 0;
+}
+
+void print_vlan_info(struct rtattr *tb, int ifindex)
+{
+	struct rtattr *i, *list = tb;
+	int rem = RTA_PAYLOAD(list);
+	__u16 last_vid_start = 0;
+
+	open_vlan_port(ifindex, "%s");
+
+	for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		struct bridge_vlan_info *vinfo;
+		int vcheck_ret;
+
+		if (i->rta_type != IFLA_BRIDGE_VLAN_INFO)
+			continue;
+
+		vinfo = RTA_DATA(i);
+
+		if (!(vinfo->flags & BRIDGE_VLAN_INFO_RANGE_END))
+			last_vid_start = vinfo->vid;
+		vcheck_ret = filter_vlan_check(vinfo->vid, vinfo->flags);
+		if (vcheck_ret == -1)
+			break;
+		else if (vcheck_ret == 0)
+			continue;
+
+		open_json_object(NULL);
+		print_range("vlan", last_vid_start, vinfo->vid);
+
+		print_vlan_flags(vinfo->flags);
+		close_json_object();
+		print_string(PRINT_FP, NULL, "%s", _SL_);
+	}
+	close_vlan_port();
+}
+
+int do_vlan(int argc, char **argv)
+{
+	ll_init_map(&rth);
+
+	if (argc > 0) {
+		if (matches(*argv, "add") == 0)
+			return vlan_modify(RTM_SETLINK, argc-1, argv+1);
+		if (matches(*argv, "delete") == 0)
+			return vlan_modify(RTM_DELLINK, argc-1, argv+1);
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return vlan_show(argc-1, argv+1);
+		if (matches(*argv, "tunnelshow") == 0) {
+			show_vlan_tunnel_info = 1;
+			return vlan_show(argc-1, argv+1);
+		}
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else {
+		return vlan_show(0, NULL);
+	}
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"bridge vlan help\".\n", *argv);
+	exit(-1);
+}
diff --git a/configure b/configure
new file mode 100755
index 0000000..45fcffb
--- /dev/null
+++ b/configure
@@ -0,0 +1,403 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# This is not an autoconf generated configure
+#
+INCLUDE=${1:-"$PWD/include"}
+
+# Output file which is input to Makefile
+CONFIG=config.mk
+
+# Make a temp directory in build tree.
+TMPDIR=$(mktemp -d config.XXXXXX)
+trap 'status=$?; rm -rf $TMPDIR; exit $status' EXIT HUP INT QUIT TERM
+
+check_toolchain()
+{
+    : ${PKG_CONFIG:=pkg-config}
+    : ${AR=ar}
+    : ${CC=gcc}
+    echo "PKG_CONFIG:=${PKG_CONFIG}" >>$CONFIG
+    echo "AR:=${AR}" >>$CONFIG
+    echo "CC:=${CC}" >>$CONFIG
+}
+
+check_atm()
+{
+    cat >$TMPDIR/atmtest.c <<EOF
+#include <atm.h>
+int main(int argc, char **argv) {
+	struct atm_qos qos;
+	(void) text2qos("aal5,ubr:sdu=9180,rx:none",&qos,0);
+	return 0;
+}
+EOF
+
+    if $CC -I$INCLUDE -o $TMPDIR/atmtest $TMPDIR/atmtest.c -latm >/dev/null 2>&1; then
+	echo "TC_CONFIG_ATM:=y" >>$CONFIG
+	echo yes
+    else
+	echo no
+    fi
+    rm -f $TMPDIR/atmtest.c $TMPDIR/atmtest
+}
+
+check_xtables()
+{
+	if ! ${PKG_CONFIG} xtables --exists; then
+		echo "TC_CONFIG_NO_XT:=y" >>$CONFIG
+	fi
+}
+
+check_xt()
+{
+    #check if we have xtables from iptables >= 1.4.5.
+    cat >$TMPDIR/ipttest.c <<EOF
+#include <xtables.h>
+#include <linux/netfilter.h>
+static struct xtables_globals test_globals = {
+	.option_offset = 0,
+	.program_name = "tc-ipt",
+	.program_version = XTABLES_VERSION,
+	.orig_opts = NULL,
+	.opts = NULL,
+	.exit_err = NULL,
+};
+
+int main(int argc, char **argv)
+{
+	xtables_init_all(&test_globals, NFPROTO_IPV4);
+	return 0;
+}
+EOF
+
+    if $CC -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL \
+	$(${PKG_CONFIG} xtables --cflags --libs) -ldl >/dev/null 2>&1; then
+	echo "TC_CONFIG_XT:=y" >>$CONFIG
+	echo "using xtables"
+    fi
+    rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
+}
+
+check_xt_old()
+{
+    # bail if previous XT checks has already succeeded.
+    grep -q TC_CONFIG_XT $CONFIG && return
+
+    #check if we don't need our internal header ..
+    cat >$TMPDIR/ipttest.c <<EOF
+#include <xtables.h>
+char *lib_dir;
+unsigned int global_option_offset = 0;
+const char *program_version = XTABLES_VERSION;
+const char *program_name = "tc-ipt";
+struct afinfo afinfo = {
+	.libprefix      = "libxt_",
+};
+
+void exit_error(enum exittype status, const char *msg, ...)
+{
+}
+
+int main(int argc, char **argv) {
+
+	return 0;
+}
+
+EOF
+
+    if $CC -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL -ldl >/dev/null 2>&1; then
+	echo "TC_CONFIG_XT_OLD:=y" >>$CONFIG
+	echo "using old xtables (no need for xt-internal.h)"
+    fi
+    rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
+}
+
+check_xt_old_internal_h()
+{
+    # bail if previous XT checks has already succeeded.
+    grep -q TC_CONFIG_XT $CONFIG && return
+
+    #check if we need our own internal.h
+    cat >$TMPDIR/ipttest.c <<EOF
+#include <xtables.h>
+#include "xt-internal.h"
+char *lib_dir;
+unsigned int global_option_offset = 0;
+const char *program_version = XTABLES_VERSION;
+const char *program_name = "tc-ipt";
+struct afinfo afinfo = {
+	.libprefix      = "libxt_",
+};
+
+void exit_error(enum exittype status, const char *msg, ...)
+{
+}
+
+int main(int argc, char **argv) {
+
+	return 0;
+}
+
+EOF
+	if $CC -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL -ldl >/dev/null 2>&1; then
+	    echo "using old xtables with xt-internal.h"
+	    echo "TC_CONFIG_XT_OLD_H:=y" >>$CONFIG
+	fi
+	rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
+}
+
+check_ipt()
+{
+	if ! grep TC_CONFIG_XT $CONFIG > /dev/null; then
+		echo "using iptables"
+	fi
+}
+
+check_ipt_lib_dir()
+{
+	IPT_LIB_DIR=$(${PKG_CONFIG} --variable=xtlibdir xtables)
+	if [ -n "$IPT_LIB_DIR" ]; then
+		echo $IPT_LIB_DIR
+		echo "IPT_LIB_DIR:=$IPT_LIB_DIR" >> $CONFIG
+		return
+	fi
+
+	for dir in /lib /usr/lib /usr/local/lib; do
+		for file in "xtables" "iptables"; do
+			file="$dir/$file/lib*t_*so"
+			if [ -f $file ]; then
+				echo ${file%/*}
+				echo "IPT_LIB_DIR:=${file%/*}" >> $CONFIG
+				return
+			fi
+		done
+	done
+	echo "not found!"
+}
+
+check_setns()
+{
+    cat >$TMPDIR/setnstest.c <<EOF
+#include <sched.h>
+int main(int argc, char **argv)
+{
+	(void)setns(0,0);
+	return 0;
+}
+EOF
+    if $CC -I$INCLUDE -o $TMPDIR/setnstest $TMPDIR/setnstest.c >/dev/null 2>&1; then
+	echo "IP_CONFIG_SETNS:=y" >>$CONFIG
+	echo "yes"
+	echo "CFLAGS += -DHAVE_SETNS" >>$CONFIG
+    else
+	echo "no"
+    fi
+    rm -f $TMPDIR/setnstest.c $TMPDIR/setnstest
+}
+
+check_ipset()
+{
+    cat >$TMPDIR/ipsettest.c <<EOF
+#include <linux/netfilter/ipset/ip_set.h>
+#ifndef IP_SET_INVALID
+#define IPSET_DIM_MAX 3
+typedef unsigned short ip_set_id_t;
+#endif
+#include <linux/netfilter/xt_set.h>
+
+struct xt_set_info info;
+#if IPSET_PROTOCOL == 6
+int main(void)
+{
+	return IPSET_MAXNAMELEN;
+}
+#else
+#error unknown ipset version
+#endif
+EOF
+
+    if $CC -I$INCLUDE -o $TMPDIR/ipsettest $TMPDIR/ipsettest.c >/dev/null 2>&1; then
+	echo "TC_CONFIG_IPSET:=y" >>$CONFIG
+	echo "yes"
+    else
+	echo "no"
+    fi
+    rm -f $TMPDIR/ipsettest.c $TMPDIR/ipsettest
+}
+
+check_elf()
+{
+    if ${PKG_CONFIG} libelf --exists; then
+	echo "HAVE_ELF:=y" >>$CONFIG
+	echo "yes"
+
+	echo 'CFLAGS += -DHAVE_ELF' `${PKG_CONFIG} libelf --cflags` >> $CONFIG
+	echo 'LDLIBS += ' `${PKG_CONFIG} libelf --libs` >>$CONFIG
+    else
+	echo "no"
+    fi
+}
+
+check_selinux()
+# SELinux is a compile time option in the ss utility
+{
+	if ${PKG_CONFIG} libselinux --exists; then
+		echo "HAVE_SELINUX:=y" >>$CONFIG
+		echo "yes"
+
+		echo 'LDLIBS +=' `${PKG_CONFIG} --libs libselinux` >>$CONFIG
+		echo 'CFLAGS += -DHAVE_SELINUX' `${PKG_CONFIG} --cflags libselinux` >>$CONFIG
+	else
+		echo "no"
+	fi
+}
+
+check_mnl()
+{
+	if ${PKG_CONFIG} libmnl --exists; then
+		echo "HAVE_MNL:=y" >>$CONFIG
+		echo "yes"
+
+		echo 'CFLAGS += -DHAVE_LIBMNL' `${PKG_CONFIG} libmnl --cflags` >>$CONFIG
+		echo 'LDLIBS +=' `${PKG_CONFIG} libmnl --libs` >> $CONFIG
+	else
+		echo "no"
+	fi
+}
+
+check_berkeley_db()
+{
+    cat >$TMPDIR/dbtest.c <<EOF
+#include <fcntl.h>
+#include <stdlib.h>
+#include <db_185.h>
+int main(int argc, char **argv) {
+	dbopen("/tmp/xxx_test_db.db", O_CREAT|O_RDWR, 0644, DB_HASH, NULL);
+	return 0;
+}
+EOF
+    if $CC -I$INCLUDE -o $TMPDIR/dbtest $TMPDIR/dbtest.c -ldb >/dev/null 2>&1; then
+	echo "HAVE_BERKELEY_DB:=y" >>$CONFIG
+	echo "yes"
+    else
+	echo "no"
+    fi
+    rm -f $TMPDIR/dbtest.c $TMPDIR/dbtest
+}
+
+check_strlcpy()
+{
+    cat >$TMPDIR/strtest.c <<EOF
+#include <string.h>
+int main(int argc, char **argv) {
+	char dst[10];
+	strlcpy(dst, "test", sizeof(dst));
+	return 0;
+}
+EOF
+    if $CC -I$INCLUDE -o $TMPDIR/strtest $TMPDIR/strtest.c >/dev/null 2>&1; then
+	echo "no"
+    else
+	if ${PKG_CONFIG} libbsd --exists; then
+		echo 'CFLAGS += -DHAVE_LIBBSD' `${PKG_CONFIG} libbsd --cflags` >>$CONFIG
+		echo 'LDLIBS +=' `${PKG_CONFIG} libbsd --libs` >> $CONFIG
+		echo "no"
+	else
+		echo 'CFLAGS += -DNEED_STRLCPY' >>$CONFIG
+		echo "yes"
+	fi
+    fi
+    rm -f $TMPDIR/strtest.c $TMPDIR/strtest
+}
+
+check_cap()
+{
+	if ${PKG_CONFIG} libcap --exists; then
+		echo "HAVE_CAP:=y" >>$CONFIG
+		echo "yes"
+
+		echo 'CFLAGS += -DHAVE_LIBCAP' `${PKG_CONFIG} libcap --cflags` >>$CONFIG
+		echo 'LDLIBS +=' `${PKG_CONFIG} libcap --libs` >> $CONFIG
+	else
+		echo "no"
+	fi
+}
+
+quiet_config()
+{
+	cat <<EOF
+# user can control verbosity similar to kernel builds (e.g., V=1)
+ifeq ("\$(origin V)", "command line")
+  VERBOSE = \$(V)
+endif
+ifndef VERBOSE
+  VERBOSE = 0
+endif
+ifeq (\$(VERBOSE),1)
+  Q =
+else
+  Q = @
+endif
+
+ifeq (\$(VERBOSE), 0)
+    QUIET_CC       = @echo '    CC       '\$@;
+    QUIET_AR       = @echo '    AR       '\$@;
+    QUIET_LINK     = @echo '    LINK     '\$@;
+    QUIET_YACC     = @echo '    YACC     '\$@;
+    QUIET_LEX      = @echo '    LEX      '\$@;
+endif
+EOF
+}
+
+echo "# Generated config based on" $INCLUDE >$CONFIG
+quiet_config >> $CONFIG
+
+check_toolchain
+
+echo "TC schedulers"
+
+echo -n " ATM	"
+check_atm
+
+check_xtables
+if ! grep -q TC_CONFIG_NO_XT $CONFIG; then
+	echo -n " IPT	"
+	check_xt
+	check_xt_old
+	check_xt_old_internal_h
+	check_ipt
+
+	echo -n " IPSET  "
+	check_ipset
+fi
+
+echo
+if ! grep -q TC_CONFIG_NO_XT $CONFIG; then
+	echo -n "iptables modules directory: "
+	check_ipt_lib_dir
+fi
+
+echo -n "libc has setns: "
+check_setns
+
+echo -n "SELinux support: "
+check_selinux
+
+echo -n "ELF support: "
+check_elf
+
+echo -n "libmnl support: "
+check_mnl
+
+echo -n "Berkeley DB: "
+check_berkeley_db
+
+echo -n "need for strlcpy: "
+check_strlcpy
+
+echo -n "libcap support: "
+check_cap
+
+echo >> $CONFIG
+echo "%.o: %.c" >> $CONFIG
+echo '	$(QUIET_CC)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CPPFLAGS) -c -o $@ $<' >> $CONFIG
diff --git a/devlink/.gitignore b/devlink/.gitignore
new file mode 100644
index 0000000..08d175f
--- /dev/null
+++ b/devlink/.gitignore
@@ -0,0 +1 @@
+devlink
diff --git a/devlink/Makefile b/devlink/Makefile
new file mode 100644
index 0000000..7da7d1f
--- /dev/null
+++ b/devlink/Makefile
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../config.mk
+
+TARGETS :=
+
+ifeq ($(HAVE_MNL),y)
+
+DEVLINKOBJ = devlink.o mnlg.o
+TARGETS += devlink
+
+endif
+
+all: $(TARGETS) $(LIBS)
+
+devlink: $(DEVLINKOBJ)
+	$(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+	for i in $(TARGETS); \
+	do install -m 0755 $$i $(DESTDIR)$(SBINDIR); \
+	done
+
+clean:
+	rm -f $(DEVLINKOBJ) $(TARGETS)
diff --git a/devlink/devlink.c b/devlink/devlink.c
new file mode 100644
index 0000000..056ac95
--- /dev/null
+++ b/devlink/devlink.c
@@ -0,0 +1,7257 @@
+/*
+ * devlink.c	Devlink tool
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@mellanox.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <limits.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/sysinfo.h>
+#define _LINUX_SYSINFO_H /* avoid collision with musl header */
+#include <linux/genetlink.h>
+#include <linux/devlink.h>
+#include <libmnl/libmnl.h>
+#include <netinet/ether.h>
+#include <sys/types.h>
+
+#include "SNAPSHOT.h"
+#include "list.h"
+#include "mnlg.h"
+#include "json_writer.h"
+#include "utils.h"
+
+#define ESWITCH_MODE_LEGACY "legacy"
+#define ESWITCH_MODE_SWITCHDEV "switchdev"
+#define ESWITCH_INLINE_MODE_NONE "none"
+#define ESWITCH_INLINE_MODE_LINK "link"
+#define ESWITCH_INLINE_MODE_NETWORK "network"
+#define ESWITCH_INLINE_MODE_TRANSPORT "transport"
+
+#define PARAM_CMODE_RUNTIME_STR "runtime"
+#define PARAM_CMODE_DRIVERINIT_STR "driverinit"
+#define PARAM_CMODE_PERMANENT_STR "permanent"
+#define DL_ARGS_REQUIRED_MAX_ERR_LEN 80
+
+#define HEALTH_REPORTER_STATE_HEALTHY_STR "healthy"
+#define HEALTH_REPORTER_STATE_ERROR_STR "error"
+#define HEALTH_REPORTER_TIMESTAMP_FMT_LEN 80
+
+static int g_new_line_count;
+static int g_indent_level;
+static bool g_indent_newline;
+
+#define INDENT_STR_STEP 2
+#define INDENT_STR_MAXLEN 32
+static char g_indent_str[INDENT_STR_MAXLEN + 1] = "";
+
+static void __attribute__((format(printf, 1, 2)))
+pr_err(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+}
+
+static void __attribute__((format(printf, 1, 2)))
+pr_out(const char *fmt, ...)
+{
+	va_list ap;
+
+	if (g_indent_newline) {
+		printf("%s", g_indent_str);
+		g_indent_newline = false;
+	}
+	va_start(ap, fmt);
+	vprintf(fmt, ap);
+	va_end(ap);
+	g_new_line_count = 0;
+}
+
+static void __attribute__((format(printf, 2, 3)))
+pr_out_sp(unsigned int num, const char *fmt, ...)
+{
+	va_list ap;
+	int ret;
+
+	va_start(ap, fmt);
+	ret = vprintf(fmt, ap);
+	va_end(ap);
+
+	if (ret < num)
+		printf("%*s", num - ret, "");
+	g_new_line_count = 0;			\
+}
+
+static void __attribute__((format(printf, 1, 2)))
+pr_out_tty(const char *fmt, ...)
+{
+	va_list ap;
+
+	if (!isatty(STDOUT_FILENO))
+		return;
+	va_start(ap, fmt);
+	vprintf(fmt, ap);
+	va_end(ap);
+}
+
+static void __pr_out_indent_inc(void)
+{
+	if (g_indent_level + INDENT_STR_STEP > INDENT_STR_MAXLEN)
+		return;
+	g_indent_level += INDENT_STR_STEP;
+	memset(g_indent_str, ' ', sizeof(g_indent_str));
+	g_indent_str[g_indent_level] = '\0';
+}
+
+static void __pr_out_indent_dec(void)
+{
+	if (g_indent_level - INDENT_STR_STEP < 0)
+		return;
+	g_indent_level -= INDENT_STR_STEP;
+	g_indent_str[g_indent_level] = '\0';
+}
+
+static void __pr_out_newline(void)
+{
+	if (g_new_line_count < 1) {
+		pr_out("\n");
+		g_indent_newline = true;
+	}
+	g_new_line_count++;
+}
+
+static int _mnlg_socket_recv_run(struct mnlg_socket *nlg,
+				 mnl_cb_t data_cb, void *data)
+{
+	int err;
+
+	err = mnlg_socket_recv_run(nlg, data_cb, data);
+	if (err < 0) {
+		pr_err("devlink answers: %s\n", strerror(errno));
+		return -errno;
+	}
+	return 0;
+}
+
+static int _mnlg_socket_send(struct mnlg_socket *nlg,
+			     const struct nlmsghdr *nlh)
+{
+	int err;
+
+	err = mnlg_socket_send(nlg, nlh);
+	if (err < 0) {
+		pr_err("Failed to call mnlg_socket_send\n");
+		return -errno;
+	}
+	return 0;
+}
+
+static int _mnlg_socket_sndrcv(struct mnlg_socket *nlg,
+			       const struct nlmsghdr *nlh,
+			       mnl_cb_t data_cb, void *data)
+{
+	int err;
+
+	err = _mnlg_socket_send(nlg, nlh);
+	if (err)
+		return err;
+	return _mnlg_socket_recv_run(nlg, data_cb, data);
+}
+
+static int _mnlg_socket_group_add(struct mnlg_socket *nlg,
+				  const char *group_name)
+{
+	int err;
+
+	err = mnlg_socket_group_add(nlg, group_name);
+	if (err < 0) {
+		pr_err("Failed to call mnlg_socket_group_add\n");
+		return -errno;
+	}
+	return 0;
+}
+
+struct ifname_map {
+	struct list_head list;
+	char *bus_name;
+	char *dev_name;
+	uint32_t port_index;
+	char *ifname;
+};
+
+static struct ifname_map *ifname_map_alloc(const char *bus_name,
+					   const char *dev_name,
+					   uint32_t port_index,
+					   const char *ifname)
+{
+	struct ifname_map *ifname_map;
+
+	ifname_map = calloc(1, sizeof(*ifname_map));
+	if (!ifname_map)
+		return NULL;
+	ifname_map->bus_name = strdup(bus_name);
+	ifname_map->dev_name = strdup(dev_name);
+	ifname_map->port_index = port_index;
+	ifname_map->ifname = strdup(ifname);
+	if (!ifname_map->bus_name || !ifname_map->dev_name ||
+	    !ifname_map->ifname) {
+		free(ifname_map->ifname);
+		free(ifname_map->dev_name);
+		free(ifname_map->bus_name);
+		free(ifname_map);
+		return NULL;
+	}
+	return ifname_map;
+}
+
+static void ifname_map_free(struct ifname_map *ifname_map)
+{
+	free(ifname_map->ifname);
+	free(ifname_map->dev_name);
+	free(ifname_map->bus_name);
+	free(ifname_map);
+}
+
+#define DL_OPT_HANDLE		BIT(0)
+#define DL_OPT_HANDLEP		BIT(1)
+#define DL_OPT_PORT_TYPE	BIT(2)
+#define DL_OPT_PORT_COUNT	BIT(3)
+#define DL_OPT_SB		BIT(4)
+#define DL_OPT_SB_POOL		BIT(5)
+#define DL_OPT_SB_SIZE		BIT(6)
+#define DL_OPT_SB_TYPE		BIT(7)
+#define DL_OPT_SB_THTYPE	BIT(8)
+#define DL_OPT_SB_TH		BIT(9)
+#define DL_OPT_SB_TC		BIT(10)
+#define DL_OPT_ESWITCH_MODE	BIT(11)
+#define DL_OPT_ESWITCH_INLINE_MODE	BIT(12)
+#define DL_OPT_DPIPE_TABLE_NAME	BIT(13)
+#define DL_OPT_DPIPE_TABLE_COUNTERS	BIT(14)
+#define DL_OPT_ESWITCH_ENCAP_MODE	BIT(15)
+#define DL_OPT_RESOURCE_PATH	BIT(16)
+#define DL_OPT_RESOURCE_SIZE	BIT(17)
+#define DL_OPT_PARAM_NAME	BIT(18)
+#define DL_OPT_PARAM_VALUE	BIT(19)
+#define DL_OPT_PARAM_CMODE	BIT(20)
+#define DL_OPT_HANDLE_REGION		BIT(21)
+#define DL_OPT_REGION_SNAPSHOT_ID	BIT(22)
+#define DL_OPT_REGION_ADDRESS		BIT(23)
+#define DL_OPT_REGION_LENGTH		BIT(24)
+#define DL_OPT_FLASH_FILE_NAME	BIT(25)
+#define DL_OPT_FLASH_COMPONENT	BIT(26)
+#define DL_OPT_HEALTH_REPORTER_NAME	BIT(27)
+#define DL_OPT_HEALTH_REPORTER_GRACEFUL_PERIOD	BIT(28)
+#define DL_OPT_HEALTH_REPORTER_AUTO_RECOVER	BIT(29)
+#define DL_OPT_TRAP_NAME		BIT(30)
+#define DL_OPT_TRAP_ACTION		BIT(31)
+#define DL_OPT_TRAP_GROUP_NAME		BIT(32)
+
+struct dl_opts {
+	uint64_t present; /* flags of present items */
+	char *bus_name;
+	char *dev_name;
+	uint32_t port_index;
+	enum devlink_port_type port_type;
+	uint32_t port_count;
+	uint32_t sb_index;
+	uint16_t sb_pool_index;
+	uint32_t sb_pool_size;
+	enum devlink_sb_pool_type sb_pool_type;
+	enum devlink_sb_threshold_type sb_pool_thtype;
+	uint32_t sb_threshold;
+	uint16_t sb_tc_index;
+	enum devlink_eswitch_mode eswitch_mode;
+	enum devlink_eswitch_inline_mode eswitch_inline_mode;
+	const char *dpipe_table_name;
+	bool dpipe_counters_enable;
+	bool eswitch_encap_mode;
+	const char *resource_path;
+	uint32_t resource_size;
+	uint32_t resource_id;
+	bool resource_id_valid;
+	const char *param_name;
+	const char *param_value;
+	enum devlink_param_cmode cmode;
+	char *region_name;
+	uint32_t region_snapshot_id;
+	uint64_t region_address;
+	uint64_t region_length;
+	const char *flash_file_name;
+	const char *flash_component;
+	const char *reporter_name;
+	uint64_t reporter_graceful_period;
+	bool reporter_auto_recover;
+	const char *trap_name;
+	const char *trap_group_name;
+	enum devlink_trap_action trap_action;
+};
+
+struct dl {
+	struct mnlg_socket *nlg;
+	struct list_head ifname_map_list;
+	int argc;
+	char **argv;
+	bool no_nice_names;
+	struct dl_opts opts;
+	json_writer_t *jw;
+	bool json_output;
+	bool pretty_output;
+	bool verbose;
+	bool stats;
+	struct {
+		bool present;
+		char *bus_name;
+		char *dev_name;
+		uint32_t port_index;
+	} arr_last;
+};
+
+static int dl_argc(struct dl *dl)
+{
+	return dl->argc;
+}
+
+static char *dl_argv(struct dl *dl)
+{
+	if (dl_argc(dl) == 0)
+		return NULL;
+	return *dl->argv;
+}
+
+static void dl_arg_inc(struct dl *dl)
+{
+	if (dl_argc(dl) == 0)
+		return;
+	dl->argc--;
+	dl->argv++;
+}
+
+static char *dl_argv_next(struct dl *dl)
+{
+	char *ret;
+
+	if (dl_argc(dl) == 0)
+		return NULL;
+
+	ret = *dl->argv;
+	dl_arg_inc(dl);
+	return ret;
+}
+
+static char *dl_argv_index(struct dl *dl, unsigned int index)
+{
+	if (index >= dl_argc(dl))
+		return NULL;
+	return dl->argv[index];
+}
+
+static int strcmpx(const char *str1, const char *str2)
+{
+	if (strlen(str1) > strlen(str2))
+		return -1;
+	return strncmp(str1, str2, strlen(str1));
+}
+
+static bool dl_argv_match(struct dl *dl, const char *pattern)
+{
+	if (dl_argc(dl) == 0)
+		return false;
+	return strcmpx(dl_argv(dl), pattern) == 0;
+}
+
+static bool dl_no_arg(struct dl *dl)
+{
+	return dl_argc(dl) == 0;
+}
+
+static void __pr_out_indent_newline(struct dl *dl)
+{
+	if (!g_indent_newline && !dl->json_output)
+		pr_out(" ");
+}
+
+static const enum mnl_attr_data_type devlink_policy[DEVLINK_ATTR_MAX + 1] = {
+	[DEVLINK_ATTR_BUS_NAME] = MNL_TYPE_NUL_STRING,
+	[DEVLINK_ATTR_DEV_NAME] = MNL_TYPE_NUL_STRING,
+	[DEVLINK_ATTR_PORT_INDEX] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_PORT_TYPE] = MNL_TYPE_U16,
+	[DEVLINK_ATTR_PORT_DESIRED_TYPE] = MNL_TYPE_U16,
+	[DEVLINK_ATTR_PORT_NETDEV_IFINDEX] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_PORT_NETDEV_NAME] = MNL_TYPE_NUL_STRING,
+	[DEVLINK_ATTR_PORT_IBDEV_NAME] = MNL_TYPE_NUL_STRING,
+	[DEVLINK_ATTR_SB_INDEX] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_SB_SIZE] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_SB_INGRESS_POOL_COUNT] = MNL_TYPE_U16,
+	[DEVLINK_ATTR_SB_EGRESS_POOL_COUNT] = MNL_TYPE_U16,
+	[DEVLINK_ATTR_SB_INGRESS_TC_COUNT] = MNL_TYPE_U16,
+	[DEVLINK_ATTR_SB_EGRESS_TC_COUNT] = MNL_TYPE_U16,
+	[DEVLINK_ATTR_SB_POOL_INDEX] = MNL_TYPE_U16,
+	[DEVLINK_ATTR_SB_POOL_TYPE] = MNL_TYPE_U8,
+	[DEVLINK_ATTR_SB_POOL_SIZE] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE] = MNL_TYPE_U8,
+	[DEVLINK_ATTR_SB_THRESHOLD] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_SB_TC_INDEX] = MNL_TYPE_U16,
+	[DEVLINK_ATTR_SB_OCC_CUR] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_SB_OCC_MAX] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_ESWITCH_MODE] = MNL_TYPE_U16,
+	[DEVLINK_ATTR_ESWITCH_INLINE_MODE] = MNL_TYPE_U8,
+	[DEVLINK_ATTR_ESWITCH_ENCAP_MODE] = MNL_TYPE_U8,
+	[DEVLINK_ATTR_DPIPE_TABLES] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_TABLE] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_TABLE_NAME] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_DPIPE_TABLE_SIZE] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_DPIPE_TABLE_MATCHES] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_TABLE_ACTIONS] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED] =  MNL_TYPE_U8,
+	[DEVLINK_ATTR_DPIPE_ENTRIES] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_ENTRY] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_ENTRY_INDEX] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_ENTRY_COUNTER] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_DPIPE_MATCH] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_MATCH_VALUE] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_MATCH_TYPE] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_DPIPE_ACTION] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_ACTION_VALUE] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_ACTION_TYPE] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_DPIPE_VALUE_MAPPING] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_DPIPE_HEADERS] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_HEADER] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_HEADER_NAME] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_DPIPE_HEADER_ID] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_DPIPE_HEADER_FIELDS] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_HEADER_GLOBAL] = MNL_TYPE_U8,
+	[DEVLINK_ATTR_DPIPE_HEADER_INDEX] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_DPIPE_FIELD] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_DPIPE_FIELD_NAME] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_DPIPE_FIELD_ID] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_PARAM] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_PARAM_NAME] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_PARAM_TYPE] = MNL_TYPE_U8,
+	[DEVLINK_ATTR_PARAM_VALUES_LIST] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_PARAM_VALUE] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_PARAM_VALUE_CMODE] = MNL_TYPE_U8,
+	[DEVLINK_ATTR_REGION_NAME] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_REGION_SIZE] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_REGION_SNAPSHOTS] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_REGION_SNAPSHOT] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_REGION_SNAPSHOT_ID] = MNL_TYPE_U32,
+	[DEVLINK_ATTR_REGION_CHUNKS] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_REGION_CHUNK] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_REGION_CHUNK_DATA] = MNL_TYPE_BINARY,
+	[DEVLINK_ATTR_REGION_CHUNK_ADDR] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_REGION_CHUNK_LEN] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_INFO_DRIVER_NAME] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_INFO_SERIAL_NUMBER] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_INFO_VERSION_FIXED] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_INFO_VERSION_RUNNING] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_INFO_VERSION_STORED] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_INFO_VERSION_NAME] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_INFO_VERSION_VALUE] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_HEALTH_REPORTER] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_HEALTH_REPORTER_NAME] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_HEALTH_REPORTER_STATE] = MNL_TYPE_U8,
+	[DEVLINK_ATTR_HEALTH_REPORTER_ERR_COUNT] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_HEALTH_REPORTER_RECOVER_COUNT] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_STATS] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_TRAP_NAME] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_TRAP_ACTION] = MNL_TYPE_U8,
+	[DEVLINK_ATTR_TRAP_TYPE] = MNL_TYPE_U8,
+	[DEVLINK_ATTR_TRAP_GENERIC] = MNL_TYPE_FLAG,
+	[DEVLINK_ATTR_TRAP_METADATA] = MNL_TYPE_NESTED,
+	[DEVLINK_ATTR_TRAP_GROUP_NAME] = MNL_TYPE_STRING,
+	[DEVLINK_ATTR_RELOAD_FAILED] = MNL_TYPE_U8,
+};
+
+static const enum mnl_attr_data_type
+devlink_stats_policy[DEVLINK_ATTR_STATS_MAX + 1] = {
+	[DEVLINK_ATTR_STATS_RX_PACKETS] = MNL_TYPE_U64,
+	[DEVLINK_ATTR_STATS_RX_BYTES] = MNL_TYPE_U64,
+};
+
+static int attr_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type;
+
+	if (mnl_attr_type_valid(attr, DEVLINK_ATTR_MAX) < 0)
+		return MNL_CB_OK;
+
+	type = mnl_attr_get_type(attr);
+	if (mnl_attr_validate(attr, devlink_policy[type]) < 0)
+		return MNL_CB_ERROR;
+
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static int attr_stats_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type;
+
+	/* Allow the tool to work on top of newer kernels that might contain
+	 * more attributes.
+	 */
+	if (mnl_attr_type_valid(attr, DEVLINK_ATTR_STATS_MAX) < 0)
+		return MNL_CB_OK;
+
+	type = mnl_attr_get_type(attr);
+	if (mnl_attr_validate(attr, devlink_stats_policy[type]) < 0)
+		return MNL_CB_ERROR;
+
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static int ifname_map_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct dl *dl = data;
+	struct ifname_map *ifname_map;
+	const char *bus_name;
+	const char *dev_name;
+	uint32_t port_ifindex;
+	const char *port_ifname;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_PORT_INDEX])
+		return MNL_CB_ERROR;
+
+	if (!tb[DEVLINK_ATTR_PORT_NETDEV_NAME])
+		return MNL_CB_OK;
+
+	bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+	dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+	port_ifindex = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]);
+	port_ifname = mnl_attr_get_str(tb[DEVLINK_ATTR_PORT_NETDEV_NAME]);
+	ifname_map = ifname_map_alloc(bus_name, dev_name,
+				      port_ifindex, port_ifname);
+	if (!ifname_map)
+		return MNL_CB_ERROR;
+	list_add(&ifname_map->list, &dl->ifname_map_list);
+
+	return MNL_CB_OK;
+}
+
+static void ifname_map_fini(struct dl *dl)
+{
+	struct ifname_map *ifname_map, *tmp;
+
+	list_for_each_entry_safe(ifname_map, tmp,
+				 &dl->ifname_map_list, list) {
+		list_del(&ifname_map->list);
+		ifname_map_free(ifname_map);
+	}
+}
+
+static int ifname_map_init(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	INIT_LIST_HEAD(&dl->ifname_map_list);
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_PORT_GET,
+			       NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, ifname_map_cb, dl);
+	if (err) {
+		ifname_map_fini(dl);
+		return err;
+	}
+	return 0;
+}
+
+static int ifname_map_lookup(struct dl *dl, const char *ifname,
+			     char **p_bus_name, char **p_dev_name,
+			     uint32_t *p_port_index)
+{
+	struct ifname_map *ifname_map;
+
+	list_for_each_entry(ifname_map, &dl->ifname_map_list, list) {
+		if (strcmp(ifname, ifname_map->ifname) == 0) {
+			*p_bus_name = ifname_map->bus_name;
+			*p_dev_name = ifname_map->dev_name;
+			*p_port_index = ifname_map->port_index;
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+static int ifname_map_rev_lookup(struct dl *dl, const char *bus_name,
+				 const char *dev_name, uint32_t port_index,
+				 char **p_ifname)
+{
+	struct ifname_map *ifname_map;
+
+	list_for_each_entry(ifname_map, &dl->ifname_map_list, list) {
+		if (strcmp(bus_name, ifname_map->bus_name) == 0 &&
+		    strcmp(dev_name, ifname_map->dev_name) == 0 &&
+		    port_index == ifname_map->port_index) {
+			*p_ifname = ifname_map->ifname;
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+static unsigned int strslashcount(char *str)
+{
+	unsigned int count = 0;
+	char *pos = str;
+
+	while ((pos = strchr(pos, '/'))) {
+		count++;
+		pos++;
+	}
+	return count;
+}
+
+static int strslashrsplit(char *str, char **before, char **after)
+{
+	char *slash;
+
+	slash = strrchr(str, '/');
+	if (!slash)
+		return -EINVAL;
+	*slash = '\0';
+	*before = str;
+	*after = slash + 1;
+	return 0;
+}
+
+static int strtouint64_t(const char *str, uint64_t *p_val)
+{
+	char *endptr;
+	unsigned long long int val;
+
+	val = strtoull(str, &endptr, 10);
+	if (endptr == str || *endptr != '\0')
+		return -EINVAL;
+	if (val > ULONG_MAX)
+		return -ERANGE;
+	*p_val = val;
+	return 0;
+}
+
+static int strtouint32_t(const char *str, uint32_t *p_val)
+{
+	char *endptr;
+	unsigned long int val;
+
+	val = strtoul(str, &endptr, 10);
+	if (endptr == str || *endptr != '\0')
+		return -EINVAL;
+	if (val > UINT_MAX)
+		return -ERANGE;
+	*p_val = val;
+	return 0;
+}
+
+static int strtouint16_t(const char *str, uint16_t *p_val)
+{
+	char *endptr;
+	unsigned long int val;
+
+	val = strtoul(str, &endptr, 10);
+	if (endptr == str || *endptr != '\0')
+		return -EINVAL;
+	if (val > USHRT_MAX)
+		return -ERANGE;
+	*p_val = val;
+	return 0;
+}
+
+static int strtouint8_t(const char *str, uint8_t *p_val)
+{
+	char *endptr;
+	unsigned long int val;
+
+	val = strtoul(str, &endptr, 10);
+	if (endptr == str || *endptr != '\0')
+		return -EINVAL;
+	if (val > UCHAR_MAX)
+		return -ERANGE;
+	*p_val = val;
+	return 0;
+}
+
+static int strtobool(const char *str, bool *p_val)
+{
+	bool val;
+
+	if (!strcmp(str, "true") || !strcmp(str, "1"))
+		val = true;
+	else if (!strcmp(str, "false") || !strcmp(str, "0"))
+		val = false;
+	else
+		return -EINVAL;
+	*p_val = val;
+	return 0;
+}
+
+static int __dl_argv_handle(char *str, char **p_bus_name, char **p_dev_name)
+{
+	strslashrsplit(str, p_bus_name, p_dev_name);
+	return 0;
+}
+
+static int dl_argv_handle(struct dl *dl, char **p_bus_name, char **p_dev_name)
+{
+	char *str = dl_argv_next(dl);
+
+	if (!str) {
+		pr_err("Devlink identification (\"bus_name/dev_name\") expected\n");
+		return -EINVAL;
+	}
+	if (strslashcount(str) != 1) {
+		pr_err("Wrong devlink identification string format.\n");
+		pr_err("Expected \"bus_name/dev_name\".\n");
+		return -EINVAL;
+	}
+	return __dl_argv_handle(str, p_bus_name, p_dev_name);
+}
+
+static int __dl_argv_handle_port(char *str,
+				 char **p_bus_name, char **p_dev_name,
+				 uint32_t *p_port_index)
+{
+	char *handlestr;
+	char *portstr;
+	int err;
+
+	err = strslashrsplit(str, &handlestr, &portstr);
+	if (err) {
+		pr_err("Port identification \"%s\" is invalid\n", str);
+		return err;
+	}
+	err = strtouint32_t(portstr, p_port_index);
+	if (err) {
+		pr_err("Port index \"%s\" is not a number or not within range\n",
+		       portstr);
+		return err;
+	}
+	err = strslashrsplit(handlestr, p_bus_name, p_dev_name);
+	if (err) {
+		pr_err("Port identification \"%s\" is invalid\n", str);
+		return err;
+	}
+	return 0;
+}
+
+static int __dl_argv_handle_port_ifname(struct dl *dl, char *str,
+					char **p_bus_name, char **p_dev_name,
+					uint32_t *p_port_index)
+{
+	int err;
+
+	err = ifname_map_lookup(dl, str, p_bus_name, p_dev_name,
+				p_port_index);
+	if (err) {
+		pr_err("Netdevice \"%s\" not found\n", str);
+		return err;
+	}
+	return 0;
+}
+
+static int dl_argv_handle_port(struct dl *dl, char **p_bus_name,
+			       char **p_dev_name, uint32_t *p_port_index)
+{
+	char *str = dl_argv_next(dl);
+	unsigned int slash_count;
+
+	if (!str) {
+		pr_err("Port identification (\"bus_name/dev_name/port_index\" or \"netdev ifname\") expected.\n");
+		return -EINVAL;
+	}
+	slash_count = strslashcount(str);
+	switch (slash_count) {
+	case 0:
+		return __dl_argv_handle_port_ifname(dl, str, p_bus_name,
+						    p_dev_name, p_port_index);
+	case 2:
+		return __dl_argv_handle_port(str, p_bus_name,
+					     p_dev_name, p_port_index);
+	default:
+		pr_err("Wrong port identification string format.\n");
+		pr_err("Expected \"bus_name/dev_name/port_index\" or \"netdev_ifname\".\n");
+		return -EINVAL;
+	}
+}
+
+static int dl_argv_handle_both(struct dl *dl, char **p_bus_name,
+			       char **p_dev_name, uint32_t *p_port_index,
+			       uint64_t *p_handle_bit)
+{
+	char *str = dl_argv_next(dl);
+	unsigned int slash_count;
+	int err;
+
+	if (!str) {
+		pr_err("One of following identifications expected:\n"
+		       "Devlink identification (\"bus_name/dev_name\")\n"
+		       "Port identification (\"bus_name/dev_name/port_index\" or \"netdev ifname\")\n");
+		return -EINVAL;
+	}
+	slash_count = strslashcount(str);
+	if (slash_count == 1) {
+		err = __dl_argv_handle(str, p_bus_name, p_dev_name);
+		if (err)
+			return err;
+		*p_handle_bit = DL_OPT_HANDLE;
+	} else if (slash_count == 2) {
+		err = __dl_argv_handle_port(str, p_bus_name,
+					    p_dev_name, p_port_index);
+		if (err)
+			return err;
+		*p_handle_bit = DL_OPT_HANDLEP;
+	} else if (slash_count == 0) {
+		err = __dl_argv_handle_port_ifname(dl, str, p_bus_name,
+						   p_dev_name, p_port_index);
+		if (err)
+			return err;
+		*p_handle_bit = DL_OPT_HANDLEP;
+	} else {
+		pr_err("Wrong port identification string format.\n");
+		pr_err("Expected \"bus_name/dev_name\" or \"bus_name/dev_name/port_index\" or \"netdev_ifname\".\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int __dl_argv_handle_region(char *str, char **p_bus_name,
+				   char **p_dev_name, char **p_region)
+{
+	char *handlestr;
+	int err;
+
+	err = strslashrsplit(str, &handlestr, p_region);
+	if (err) {
+		pr_err("Region identification \"%s\" is invalid\n", str);
+		return err;
+	}
+	err = strslashrsplit(handlestr, p_bus_name, p_dev_name);
+	if (err) {
+		pr_err("Region identification \"%s\" is invalid\n", str);
+		return err;
+	}
+	return 0;
+}
+
+static int dl_argv_handle_region(struct dl *dl, char **p_bus_name,
+					char **p_dev_name, char **p_region)
+{
+	char *str = dl_argv_next(dl);
+	unsigned int slash_count;
+
+	if (!str) {
+		pr_err("Expected \"bus_name/dev_name/region\" identification.\n");
+		return -EINVAL;
+	}
+
+	slash_count = strslashcount(str);
+	if (slash_count != 2) {
+		pr_err("Wrong region identification string format.\n");
+		pr_err("Expected \"bus_name/dev_name/region\" identification.\n"".\n");
+		return -EINVAL;
+	}
+
+	return __dl_argv_handle_region(str, p_bus_name, p_dev_name, p_region);
+}
+
+static int dl_argv_uint64_t(struct dl *dl, uint64_t *p_val)
+{
+	char *str = dl_argv_next(dl);
+	int err;
+
+	if (!str) {
+		pr_err("Unsigned number argument expected\n");
+		return -EINVAL;
+	}
+
+	err = strtouint64_t(str, p_val);
+	if (err) {
+		pr_err("\"%s\" is not a number or not within range\n", str);
+		return err;
+	}
+	return 0;
+}
+
+static int dl_argv_uint32_t(struct dl *dl, uint32_t *p_val)
+{
+	char *str = dl_argv_next(dl);
+	int err;
+
+	if (!str) {
+		pr_err("Unsigned number argument expected\n");
+		return -EINVAL;
+	}
+
+	err = strtouint32_t(str, p_val);
+	if (err) {
+		pr_err("\"%s\" is not a number or not within range\n", str);
+		return err;
+	}
+	return 0;
+}
+
+static int dl_argv_uint16_t(struct dl *dl, uint16_t *p_val)
+{
+	char *str = dl_argv_next(dl);
+	int err;
+
+	if (!str) {
+		pr_err("Unsigned number argument expected\n");
+		return -EINVAL;
+	}
+
+	err = strtouint16_t(str, p_val);
+	if (err) {
+		pr_err("\"%s\" is not a number or not within range\n", str);
+		return err;
+	}
+	return 0;
+}
+
+static int dl_argv_bool(struct dl *dl, bool *p_val)
+{
+	char *str = dl_argv_next(dl);
+	int err;
+
+	if (!str) {
+		pr_err("Boolean argument expected\n");
+		return -EINVAL;
+	}
+
+	err = strtobool(str, p_val);
+	if (err) {
+		pr_err("\"%s\" is not a valid boolean value\n", str);
+		return err;
+	}
+	return 0;
+}
+
+static int dl_argv_str(struct dl *dl, const char **p_str)
+{
+	const char *str = dl_argv_next(dl);
+
+	if (!str) {
+		pr_err("String parameter expected\n");
+		return -EINVAL;
+	}
+	*p_str = str;
+	return 0;
+}
+
+static int port_type_get(const char *typestr, enum devlink_port_type *p_type)
+{
+	if (strcmp(typestr, "auto") == 0) {
+		*p_type = DEVLINK_PORT_TYPE_AUTO;
+	} else if (strcmp(typestr, "eth") == 0) {
+		*p_type = DEVLINK_PORT_TYPE_ETH;
+	} else if (strcmp(typestr, "ib") == 0) {
+		*p_type = DEVLINK_PORT_TYPE_IB;
+	} else {
+		pr_err("Unknown port type \"%s\"\n", typestr);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int pool_type_get(const char *typestr, enum devlink_sb_pool_type *p_type)
+{
+	if (strcmp(typestr, "ingress") == 0) {
+		*p_type = DEVLINK_SB_POOL_TYPE_INGRESS;
+	} else if (strcmp(typestr, "egress") == 0) {
+		*p_type = DEVLINK_SB_POOL_TYPE_EGRESS;
+	} else {
+		pr_err("Unknown pool type \"%s\"\n", typestr);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int threshold_type_get(const char *typestr,
+			      enum devlink_sb_threshold_type *p_type)
+{
+	if (strcmp(typestr, "static") == 0) {
+		*p_type = DEVLINK_SB_THRESHOLD_TYPE_STATIC;
+	} else if (strcmp(typestr, "dynamic") == 0) {
+		*p_type = DEVLINK_SB_THRESHOLD_TYPE_DYNAMIC;
+	} else {
+		pr_err("Unknown threshold type \"%s\"\n", typestr);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int eswitch_mode_get(const char *typestr,
+			    enum devlink_eswitch_mode *p_mode)
+{
+	if (strcmp(typestr, ESWITCH_MODE_LEGACY) == 0) {
+		*p_mode = DEVLINK_ESWITCH_MODE_LEGACY;
+	} else if (strcmp(typestr, ESWITCH_MODE_SWITCHDEV) == 0) {
+		*p_mode = DEVLINK_ESWITCH_MODE_SWITCHDEV;
+	} else {
+		pr_err("Unknown eswitch mode \"%s\"\n", typestr);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int eswitch_inline_mode_get(const char *typestr,
+				   enum devlink_eswitch_inline_mode *p_mode)
+{
+	if (strcmp(typestr, ESWITCH_INLINE_MODE_NONE) == 0) {
+		*p_mode = DEVLINK_ESWITCH_INLINE_MODE_NONE;
+	} else if (strcmp(typestr, ESWITCH_INLINE_MODE_LINK) == 0) {
+		*p_mode = DEVLINK_ESWITCH_INLINE_MODE_LINK;
+	} else if (strcmp(typestr, ESWITCH_INLINE_MODE_NETWORK) == 0) {
+		*p_mode = DEVLINK_ESWITCH_INLINE_MODE_NETWORK;
+	} else if (strcmp(typestr, ESWITCH_INLINE_MODE_TRANSPORT) == 0) {
+		*p_mode = DEVLINK_ESWITCH_INLINE_MODE_TRANSPORT;
+	} else {
+		pr_err("Unknown eswitch inline mode \"%s\"\n", typestr);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int dpipe_counters_enable_get(const char *typestr,
+				     bool *counters_enable)
+{
+	if (strcmp(typestr, "enable") == 0) {
+		*counters_enable = 1;
+	} else if (strcmp(typestr, "disable") == 0) {
+		*counters_enable = 0;
+	} else {
+		pr_err("Unknown counter_state \"%s\"\n", typestr);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int eswitch_encap_mode_get(const char *typestr, bool *p_mode)
+{
+	if (strcmp(typestr, "enable") == 0) {
+		*p_mode = true;
+	} else if (strcmp(typestr, "disable") == 0) {
+		*p_mode = false;
+	} else {
+		pr_err("Unknown eswitch encap mode \"%s\"\n", typestr);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int param_cmode_get(const char *cmodestr,
+			   enum devlink_param_cmode *cmode)
+{
+	if (strcmp(cmodestr, PARAM_CMODE_RUNTIME_STR) == 0) {
+		*cmode = DEVLINK_PARAM_CMODE_RUNTIME;
+	} else if (strcmp(cmodestr, PARAM_CMODE_DRIVERINIT_STR) == 0) {
+		*cmode = DEVLINK_PARAM_CMODE_DRIVERINIT;
+	} else if (strcmp(cmodestr, PARAM_CMODE_PERMANENT_STR) == 0) {
+		*cmode = DEVLINK_PARAM_CMODE_PERMANENT;
+	} else {
+		pr_err("Unknown configuration mode \"%s\"\n", cmodestr);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int trap_action_get(const char *actionstr,
+			   enum devlink_trap_action *p_action)
+{
+	if (strcmp(actionstr, "drop") == 0) {
+		*p_action = DEVLINK_TRAP_ACTION_DROP;
+	} else if (strcmp(actionstr, "trap") == 0) {
+		*p_action = DEVLINK_TRAP_ACTION_TRAP;
+	} else {
+		pr_err("Unknown trap action \"%s\"\n", actionstr);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+struct dl_args_metadata {
+	uint64_t o_flag;
+	char err_msg[DL_ARGS_REQUIRED_MAX_ERR_LEN];
+};
+
+static const struct dl_args_metadata dl_args_required[] = {
+	{DL_OPT_PORT_TYPE,	      "Port type not set."},
+	{DL_OPT_PORT_COUNT,	      "Port split count option expected."},
+	{DL_OPT_SB_POOL,	      "Pool index option expected."},
+	{DL_OPT_SB_SIZE,	      "Pool size option expected."},
+	{DL_OPT_SB_TYPE,	      "Pool type option expected."},
+	{DL_OPT_SB_THTYPE,	      "Pool threshold type option expected."},
+	{DL_OPT_SB_TH,		      "Threshold option expected."},
+	{DL_OPT_SB_TC,		      "TC index option expected."},
+	{DL_OPT_ESWITCH_MODE,	      "E-Switch mode option expected."},
+	{DL_OPT_ESWITCH_INLINE_MODE,  "E-Switch inline-mode option expected."},
+	{DL_OPT_DPIPE_TABLE_NAME,     "Dpipe table name expected."},
+	{DL_OPT_DPIPE_TABLE_COUNTERS, "Dpipe table counter state expected."},
+	{DL_OPT_ESWITCH_ENCAP_MODE,   "E-Switch encapsulation option expected."},
+	{DL_OPT_RESOURCE_PATH,	      "Resource path expected."},
+	{DL_OPT_RESOURCE_SIZE,	      "Resource size expected."},
+	{DL_OPT_PARAM_NAME,	      "Parameter name expected."},
+	{DL_OPT_PARAM_VALUE,	      "Value to set expected."},
+	{DL_OPT_PARAM_CMODE,	      "Configuration mode expected."},
+	{DL_OPT_REGION_SNAPSHOT_ID,   "Region snapshot id expected."},
+	{DL_OPT_REGION_ADDRESS,	      "Region address value expected."},
+	{DL_OPT_REGION_LENGTH,	      "Region length value expected."},
+	{DL_OPT_HEALTH_REPORTER_NAME, "Reporter's name is expected."},
+	{DL_OPT_TRAP_NAME,            "Trap's name is expected."},
+	{DL_OPT_TRAP_GROUP_NAME,      "Trap group's name is expected."},
+};
+
+static int dl_args_finding_required_validate(uint64_t o_required,
+					     uint64_t o_found)
+{
+	uint64_t o_flag;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(dl_args_required); i++) {
+		o_flag = dl_args_required[i].o_flag;
+		if ((o_required & o_flag) && !(o_found & o_flag)) {
+			pr_err("%s\n", dl_args_required[i].err_msg);
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+static int dl_argv_parse(struct dl *dl, uint64_t o_required,
+			 uint64_t o_optional)
+{
+	struct dl_opts *opts = &dl->opts;
+	uint64_t o_all = o_required | o_optional;
+	uint64_t o_found = 0;
+	int err;
+
+	if (o_required & DL_OPT_HANDLE && o_required & DL_OPT_HANDLEP) {
+		uint64_t handle_bit;
+
+		err = dl_argv_handle_both(dl, &opts->bus_name, &opts->dev_name,
+					  &opts->port_index, &handle_bit);
+		if (err)
+			return err;
+		o_found |= handle_bit;
+	} else if (o_required & DL_OPT_HANDLE) {
+		err = dl_argv_handle(dl, &opts->bus_name, &opts->dev_name);
+		if (err)
+			return err;
+		o_found |= DL_OPT_HANDLE;
+	} else if (o_required & DL_OPT_HANDLEP) {
+		err = dl_argv_handle_port(dl, &opts->bus_name, &opts->dev_name,
+					  &opts->port_index);
+		if (err)
+			return err;
+		o_found |= DL_OPT_HANDLEP;
+	} else if (o_required & DL_OPT_HANDLE_REGION) {
+		err = dl_argv_handle_region(dl, &opts->bus_name,
+					    &opts->dev_name,
+					    &opts->region_name);
+		if (err)
+			return err;
+		o_found |= DL_OPT_HANDLE_REGION;
+	}
+
+	while (dl_argc(dl)) {
+		if (dl_argv_match(dl, "type") &&
+		    (o_all & DL_OPT_PORT_TYPE)) {
+			const char *typestr;
+
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &typestr);
+			if (err)
+				return err;
+			err = port_type_get(typestr, &opts->port_type);
+			if (err)
+				return err;
+			o_found |= DL_OPT_PORT_TYPE;
+		} else if (dl_argv_match(dl, "count") &&
+			   (o_all & DL_OPT_PORT_COUNT)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint32_t(dl, &opts->port_count);
+			if (err)
+				return err;
+			o_found |= DL_OPT_PORT_COUNT;
+		} else if (dl_argv_match(dl, "sb") &&
+			   (o_all & DL_OPT_SB)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint32_t(dl, &opts->sb_index);
+			if (err)
+				return err;
+			o_found |= DL_OPT_SB;
+		} else if (dl_argv_match(dl, "pool") &&
+			   (o_all & DL_OPT_SB_POOL)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint16_t(dl, &opts->sb_pool_index);
+			if (err)
+				return err;
+			o_found |= DL_OPT_SB_POOL;
+		} else if (dl_argv_match(dl, "size") &&
+			   (o_all & DL_OPT_SB_SIZE)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint32_t(dl, &opts->sb_pool_size);
+			if (err)
+				return err;
+			o_found |= DL_OPT_SB_SIZE;
+		} else if (dl_argv_match(dl, "type") &&
+			   (o_all & DL_OPT_SB_TYPE)) {
+			const char *typestr;
+
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &typestr);
+			if (err)
+				return err;
+			err = pool_type_get(typestr, &opts->sb_pool_type);
+			if (err)
+				return err;
+			o_found |= DL_OPT_SB_TYPE;
+		} else if (dl_argv_match(dl, "thtype") &&
+			   (o_all & DL_OPT_SB_THTYPE)) {
+			const char *typestr;
+
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &typestr);
+			if (err)
+				return err;
+			err = threshold_type_get(typestr,
+						 &opts->sb_pool_thtype);
+			if (err)
+				return err;
+			o_found |= DL_OPT_SB_THTYPE;
+		} else if (dl_argv_match(dl, "th") &&
+			   (o_all & DL_OPT_SB_TH)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint32_t(dl, &opts->sb_threshold);
+			if (err)
+				return err;
+			o_found |= DL_OPT_SB_TH;
+		} else if (dl_argv_match(dl, "tc") &&
+			   (o_all & DL_OPT_SB_TC)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint16_t(dl, &opts->sb_tc_index);
+			if (err)
+				return err;
+			o_found |= DL_OPT_SB_TC;
+		} else if (dl_argv_match(dl, "mode") &&
+			   (o_all & DL_OPT_ESWITCH_MODE)) {
+			const char *typestr;
+
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &typestr);
+			if (err)
+				return err;
+			err = eswitch_mode_get(typestr, &opts->eswitch_mode);
+			if (err)
+				return err;
+			o_found |= DL_OPT_ESWITCH_MODE;
+		} else if (dl_argv_match(dl, "inline-mode") &&
+			   (o_all & DL_OPT_ESWITCH_INLINE_MODE)) {
+			const char *typestr;
+
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &typestr);
+			if (err)
+				return err;
+			err = eswitch_inline_mode_get(
+				typestr, &opts->eswitch_inline_mode);
+			if (err)
+				return err;
+			o_found |= DL_OPT_ESWITCH_INLINE_MODE;
+		} else if (dl_argv_match(dl, "name") &&
+			   (o_all & DL_OPT_DPIPE_TABLE_NAME)) {
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &opts->dpipe_table_name);
+			if (err)
+				return err;
+			o_found |= DL_OPT_DPIPE_TABLE_NAME;
+		} else if (dl_argv_match(dl, "counters") &&
+			   (o_all & DL_OPT_DPIPE_TABLE_COUNTERS)) {
+			const char *typestr;
+
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &typestr);
+			if (err)
+				return err;
+			err = dpipe_counters_enable_get(typestr,
+							&opts->dpipe_counters_enable);
+			if (err)
+				return err;
+			o_found |= DL_OPT_DPIPE_TABLE_COUNTERS;
+		} else if (dl_argv_match(dl, "encap") &&
+			   (o_all & DL_OPT_ESWITCH_ENCAP_MODE)) {
+			const char *typestr;
+
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &typestr);
+			if (err)
+				return err;
+			err = eswitch_encap_mode_get(typestr,
+						     &opts->eswitch_encap_mode);
+			if (err)
+				return err;
+			o_found |= DL_OPT_ESWITCH_ENCAP_MODE;
+		} else if (dl_argv_match(dl, "path") &&
+			   (o_all & DL_OPT_RESOURCE_PATH)) {
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &opts->resource_path);
+			if (err)
+				return err;
+			o_found |= DL_OPT_RESOURCE_PATH;
+		} else if (dl_argv_match(dl, "size") &&
+			   (o_all & DL_OPT_RESOURCE_SIZE)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint32_t(dl, &opts->resource_size);
+			if (err)
+				return err;
+			o_found |= DL_OPT_RESOURCE_SIZE;
+		} else if (dl_argv_match(dl, "name") &&
+			   (o_all & DL_OPT_PARAM_NAME)) {
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &opts->param_name);
+			if (err)
+				return err;
+			o_found |= DL_OPT_PARAM_NAME;
+		} else if (dl_argv_match(dl, "value") &&
+			   (o_all & DL_OPT_PARAM_VALUE)) {
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &opts->param_value);
+			if (err)
+				return err;
+			o_found |= DL_OPT_PARAM_VALUE;
+		} else if (dl_argv_match(dl, "cmode") &&
+			   (o_all & DL_OPT_PARAM_CMODE)) {
+			const char *cmodestr;
+
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &cmodestr);
+			if (err)
+				return err;
+			err = param_cmode_get(cmodestr, &opts->cmode);
+			if (err)
+				return err;
+			o_found |= DL_OPT_PARAM_CMODE;
+		} else if (dl_argv_match(dl, "snapshot") &&
+			   (o_all & DL_OPT_REGION_SNAPSHOT_ID)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint32_t(dl, &opts->region_snapshot_id);
+			if (err)
+				return err;
+			o_found |= DL_OPT_REGION_SNAPSHOT_ID;
+		} else if (dl_argv_match(dl, "address") &&
+			   (o_all & DL_OPT_REGION_ADDRESS)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint64_t(dl, &opts->region_address);
+			if (err)
+				return err;
+			o_found |= DL_OPT_REGION_ADDRESS;
+		} else if (dl_argv_match(dl, "length") &&
+			   (o_all & DL_OPT_REGION_LENGTH)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint64_t(dl, &opts->region_length);
+			if (err)
+				return err;
+			o_found |= DL_OPT_REGION_LENGTH;
+		} else if (dl_argv_match(dl, "file") &&
+			   (o_all & DL_OPT_FLASH_FILE_NAME)) {
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &opts->flash_file_name);
+			if (err)
+				return err;
+			o_found |= DL_OPT_FLASH_FILE_NAME;
+		} else if (dl_argv_match(dl, "component") &&
+			   (o_all & DL_OPT_FLASH_COMPONENT)) {
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &opts->flash_component);
+			if (err)
+				return err;
+			o_found |= DL_OPT_FLASH_COMPONENT;
+		} else if (dl_argv_match(dl, "reporter") &&
+			   (o_all & DL_OPT_HEALTH_REPORTER_NAME)) {
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &opts->reporter_name);
+			if (err)
+				return err;
+			o_found |= DL_OPT_HEALTH_REPORTER_NAME;
+		} else if (dl_argv_match(dl, "grace_period") &&
+			   (o_all & DL_OPT_HEALTH_REPORTER_GRACEFUL_PERIOD)) {
+			dl_arg_inc(dl);
+			err = dl_argv_uint64_t(dl,
+					       &opts->reporter_graceful_period);
+			if (err)
+				return err;
+			o_found |= DL_OPT_HEALTH_REPORTER_GRACEFUL_PERIOD;
+		} else if (dl_argv_match(dl, "auto_recover") &&
+			(o_all & DL_OPT_HEALTH_REPORTER_AUTO_RECOVER)) {
+			dl_arg_inc(dl);
+			err = dl_argv_bool(dl, &opts->reporter_auto_recover);
+			if (err)
+				return err;
+			o_found |= DL_OPT_HEALTH_REPORTER_AUTO_RECOVER;
+		} else if (dl_argv_match(dl, "trap") &&
+			   (o_all & DL_OPT_TRAP_NAME)) {
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &opts->trap_name);
+			if (err)
+				return err;
+			o_found |= DL_OPT_TRAP_NAME;
+		} else if (dl_argv_match(dl, "group") &&
+			   (o_all & DL_OPT_TRAP_GROUP_NAME)) {
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &opts->trap_group_name);
+			if (err)
+				return err;
+			o_found |= DL_OPT_TRAP_GROUP_NAME;
+		} else if (dl_argv_match(dl, "action") &&
+			   (o_all & DL_OPT_TRAP_ACTION)) {
+			const char *actionstr;
+
+			dl_arg_inc(dl);
+			err = dl_argv_str(dl, &actionstr);
+			if (err)
+				return err;
+			err = trap_action_get(actionstr, &opts->trap_action);
+			if (err)
+				return err;
+			o_found |= DL_OPT_TRAP_ACTION;
+		} else {
+			pr_err("Unknown option \"%s\"\n", dl_argv(dl));
+			return -EINVAL;
+		}
+	}
+
+	opts->present = o_found;
+
+	if ((o_optional & DL_OPT_SB) && !(o_found & DL_OPT_SB)) {
+		opts->sb_index = 0;
+		opts->present |= DL_OPT_SB;
+	}
+
+	return dl_args_finding_required_validate(o_required, o_found);
+}
+
+static void dl_opts_put(struct nlmsghdr *nlh, struct dl *dl)
+{
+	struct dl_opts *opts = &dl->opts;
+
+	if (opts->present & DL_OPT_HANDLE) {
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, opts->bus_name);
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, opts->dev_name);
+	} else if (opts->present & DL_OPT_HANDLEP) {
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, opts->bus_name);
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, opts->dev_name);
+		mnl_attr_put_u32(nlh, DEVLINK_ATTR_PORT_INDEX,
+				 opts->port_index);
+	} else if (opts->present & DL_OPT_HANDLE_REGION) {
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, opts->bus_name);
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, opts->dev_name);
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_REGION_NAME,
+				  opts->region_name);
+	}
+	if (opts->present & DL_OPT_PORT_TYPE)
+		mnl_attr_put_u16(nlh, DEVLINK_ATTR_PORT_TYPE,
+				 opts->port_type);
+	if (opts->present & DL_OPT_PORT_COUNT)
+		mnl_attr_put_u32(nlh, DEVLINK_ATTR_PORT_SPLIT_COUNT,
+				 opts->port_count);
+	if (opts->present & DL_OPT_SB)
+		mnl_attr_put_u32(nlh, DEVLINK_ATTR_SB_INDEX,
+				 opts->sb_index);
+	if (opts->present & DL_OPT_SB_POOL)
+		mnl_attr_put_u16(nlh, DEVLINK_ATTR_SB_POOL_INDEX,
+				 opts->sb_pool_index);
+	if (opts->present & DL_OPT_SB_SIZE)
+		mnl_attr_put_u32(nlh, DEVLINK_ATTR_SB_POOL_SIZE,
+				 opts->sb_pool_size);
+	if (opts->present & DL_OPT_SB_TYPE)
+		mnl_attr_put_u8(nlh, DEVLINK_ATTR_SB_POOL_TYPE,
+				opts->sb_pool_type);
+	if (opts->present & DL_OPT_SB_THTYPE)
+		mnl_attr_put_u8(nlh, DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE,
+				opts->sb_pool_thtype);
+	if (opts->present & DL_OPT_SB_TH)
+		mnl_attr_put_u32(nlh, DEVLINK_ATTR_SB_THRESHOLD,
+				 opts->sb_threshold);
+	if (opts->present & DL_OPT_SB_TC)
+		mnl_attr_put_u16(nlh, DEVLINK_ATTR_SB_TC_INDEX,
+				 opts->sb_tc_index);
+	if (opts->present & DL_OPT_ESWITCH_MODE)
+		mnl_attr_put_u16(nlh, DEVLINK_ATTR_ESWITCH_MODE,
+				 opts->eswitch_mode);
+	if (opts->present & DL_OPT_ESWITCH_INLINE_MODE)
+		mnl_attr_put_u8(nlh, DEVLINK_ATTR_ESWITCH_INLINE_MODE,
+				opts->eswitch_inline_mode);
+	if (opts->present & DL_OPT_DPIPE_TABLE_NAME)
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_DPIPE_TABLE_NAME,
+				  opts->dpipe_table_name);
+	if (opts->present & DL_OPT_DPIPE_TABLE_COUNTERS)
+		mnl_attr_put_u8(nlh, DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED,
+				opts->dpipe_counters_enable);
+	if (opts->present & DL_OPT_ESWITCH_ENCAP_MODE)
+		mnl_attr_put_u8(nlh, DEVLINK_ATTR_ESWITCH_ENCAP_MODE,
+				opts->eswitch_encap_mode);
+	if ((opts->present & DL_OPT_RESOURCE_PATH) && opts->resource_id_valid)
+		mnl_attr_put_u64(nlh, DEVLINK_ATTR_RESOURCE_ID,
+				 opts->resource_id);
+	if (opts->present & DL_OPT_RESOURCE_SIZE)
+		mnl_attr_put_u64(nlh, DEVLINK_ATTR_RESOURCE_SIZE,
+				 opts->resource_size);
+	if (opts->present & DL_OPT_PARAM_NAME)
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_PARAM_NAME,
+				  opts->param_name);
+	if (opts->present & DL_OPT_PARAM_CMODE)
+		mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_VALUE_CMODE,
+				opts->cmode);
+	if (opts->present & DL_OPT_REGION_SNAPSHOT_ID)
+		mnl_attr_put_u32(nlh, DEVLINK_ATTR_REGION_SNAPSHOT_ID,
+				 opts->region_snapshot_id);
+	if (opts->present & DL_OPT_REGION_ADDRESS)
+		mnl_attr_put_u64(nlh, DEVLINK_ATTR_REGION_CHUNK_ADDR,
+				 opts->region_address);
+	if (opts->present & DL_OPT_REGION_LENGTH)
+		mnl_attr_put_u64(nlh, DEVLINK_ATTR_REGION_CHUNK_LEN,
+				 opts->region_length);
+	if (opts->present & DL_OPT_FLASH_FILE_NAME)
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME,
+				  opts->flash_file_name);
+	if (opts->present & DL_OPT_FLASH_COMPONENT)
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_FLASH_UPDATE_COMPONENT,
+				  opts->flash_component);
+	if (opts->present & DL_OPT_HEALTH_REPORTER_NAME)
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_HEALTH_REPORTER_NAME,
+				  opts->reporter_name);
+	if (opts->present & DL_OPT_HEALTH_REPORTER_GRACEFUL_PERIOD)
+		mnl_attr_put_u64(nlh,
+				 DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD,
+				 opts->reporter_graceful_period);
+	if (opts->present & DL_OPT_HEALTH_REPORTER_AUTO_RECOVER)
+		mnl_attr_put_u8(nlh, DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER,
+				opts->reporter_auto_recover);
+	if (opts->present & DL_OPT_TRAP_NAME)
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_TRAP_NAME,
+				  opts->trap_name);
+	if (opts->present & DL_OPT_TRAP_GROUP_NAME)
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_TRAP_GROUP_NAME,
+				  opts->trap_group_name);
+	if (opts->present & DL_OPT_TRAP_ACTION)
+		mnl_attr_put_u8(nlh, DEVLINK_ATTR_TRAP_ACTION,
+				opts->trap_action);
+
+}
+
+static int dl_argv_parse_put(struct nlmsghdr *nlh, struct dl *dl,
+			     uint64_t o_required, uint64_t o_optional)
+{
+	int err;
+
+	err = dl_argv_parse(dl, o_required, o_optional);
+	if (err)
+		return err;
+	dl_opts_put(nlh, dl);
+	return 0;
+}
+
+static bool dl_dump_filter(struct dl *dl, struct nlattr **tb)
+{
+	struct dl_opts *opts = &dl->opts;
+	struct nlattr *attr_bus_name = tb[DEVLINK_ATTR_BUS_NAME];
+	struct nlattr *attr_dev_name = tb[DEVLINK_ATTR_DEV_NAME];
+	struct nlattr *attr_port_index = tb[DEVLINK_ATTR_PORT_INDEX];
+	struct nlattr *attr_sb_index = tb[DEVLINK_ATTR_SB_INDEX];
+
+	if (opts->present & DL_OPT_HANDLE &&
+	    attr_bus_name && attr_dev_name) {
+		const char *bus_name = mnl_attr_get_str(attr_bus_name);
+		const char *dev_name = mnl_attr_get_str(attr_dev_name);
+
+		if (strcmp(bus_name, opts->bus_name) != 0 ||
+		    strcmp(dev_name, opts->dev_name) != 0)
+			return false;
+	}
+	if (opts->present & DL_OPT_HANDLEP &&
+	    attr_bus_name && attr_dev_name && attr_port_index) {
+		const char *bus_name = mnl_attr_get_str(attr_bus_name);
+		const char *dev_name = mnl_attr_get_str(attr_dev_name);
+		uint32_t port_index = mnl_attr_get_u32(attr_port_index);
+
+		if (strcmp(bus_name, opts->bus_name) != 0 ||
+		    strcmp(dev_name, opts->dev_name) != 0 ||
+		    port_index != opts->port_index)
+			return false;
+	}
+	if (opts->present & DL_OPT_SB && attr_sb_index) {
+		uint32_t sb_index = mnl_attr_get_u32(attr_sb_index);
+
+		if (sb_index != opts->sb_index)
+			return false;
+	}
+	return true;
+}
+
+static void cmd_dev_help(void)
+{
+	pr_err("Usage: devlink dev show [ DEV ]\n");
+	pr_err("       devlink dev eswitch set DEV [ mode { legacy | switchdev } ]\n");
+	pr_err("                               [ inline-mode { none | link | network | transport } ]\n");
+	pr_err("                               [ encap { disable | enable } ]\n");
+	pr_err("       devlink dev eswitch show DEV\n");
+	pr_err("       devlink dev param set DEV name PARAMETER value VALUE cmode { permanent | driverinit | runtime }\n");
+	pr_err("       devlink dev param show [DEV name PARAMETER]\n");
+	pr_err("       devlink dev reload DEV\n");
+	pr_err("       devlink dev info [ DEV ]\n");
+	pr_err("       devlink dev flash DEV file PATH [ component NAME ]\n");
+}
+
+static bool cmp_arr_last_handle(struct dl *dl, const char *bus_name,
+				const char *dev_name)
+{
+	if (!dl->arr_last.present)
+		return false;
+	return strcmp(dl->arr_last.bus_name, bus_name) == 0 &&
+	       strcmp(dl->arr_last.dev_name, dev_name) == 0;
+}
+
+static void arr_last_handle_set(struct dl *dl, const char *bus_name,
+				const char *dev_name)
+{
+	dl->arr_last.present = true;
+	free(dl->arr_last.dev_name);
+	free(dl->arr_last.bus_name);
+	dl->arr_last.bus_name = strdup(bus_name);
+	dl->arr_last.dev_name = strdup(dev_name);
+}
+
+static bool should_arr_last_handle_start(struct dl *dl, const char *bus_name,
+					 const char *dev_name)
+{
+	return !cmp_arr_last_handle(dl, bus_name, dev_name);
+}
+
+static bool should_arr_last_handle_end(struct dl *dl, const char *bus_name,
+				       const char *dev_name)
+{
+	return dl->arr_last.present &&
+	       !cmp_arr_last_handle(dl, bus_name, dev_name);
+}
+
+static void __pr_out_handle_start(struct dl *dl, struct nlattr **tb,
+				  bool content, bool array)
+{
+	const char *bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+	const char *dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+	char buf[64];
+
+	sprintf(buf, "%s/%s", bus_name, dev_name);
+
+	if (dl->json_output) {
+		if (array) {
+			if (should_arr_last_handle_end(dl, bus_name, dev_name))
+				jsonw_end_array(dl->jw);
+			if (should_arr_last_handle_start(dl, bus_name,
+							 dev_name)) {
+				jsonw_name(dl->jw, buf);
+				jsonw_start_array(dl->jw);
+				jsonw_start_object(dl->jw);
+				arr_last_handle_set(dl, bus_name, dev_name);
+			} else {
+				jsonw_start_object(dl->jw);
+			}
+		} else {
+			jsonw_name(dl->jw, buf);
+			jsonw_start_object(dl->jw);
+		}
+	} else {
+		if (array) {
+			if (should_arr_last_handle_end(dl, bus_name, dev_name))
+				__pr_out_indent_dec();
+			if (should_arr_last_handle_start(dl, bus_name,
+							 dev_name)) {
+				pr_out("%s%s", buf, content ? ":" : "");
+				__pr_out_newline();
+				__pr_out_indent_inc();
+				arr_last_handle_set(dl, bus_name, dev_name);
+			}
+		} else {
+			pr_out("%s%s", buf, content ? ":" : "");
+		}
+	}
+}
+
+static void pr_out_handle_start_arr(struct dl *dl, struct nlattr **tb)
+{
+	__pr_out_handle_start(dl, tb, true, true);
+}
+
+static void pr_out_handle_end(struct dl *dl)
+{
+	if (dl->json_output)
+		jsonw_end_object(dl->jw);
+	else
+		__pr_out_newline();
+}
+
+static void pr_out_handle(struct dl *dl, struct nlattr **tb)
+{
+	__pr_out_handle_start(dl, tb, false, false);
+	pr_out_handle_end(dl);
+}
+
+static bool cmp_arr_last_port_handle(struct dl *dl, const char *bus_name,
+				     const char *dev_name, uint32_t port_index)
+{
+	return cmp_arr_last_handle(dl, bus_name, dev_name) &&
+	       dl->arr_last.port_index == port_index;
+}
+
+static void arr_last_port_handle_set(struct dl *dl, const char *bus_name,
+				     const char *dev_name, uint32_t port_index)
+{
+	arr_last_handle_set(dl, bus_name, dev_name);
+	dl->arr_last.port_index = port_index;
+}
+
+static bool should_arr_last_port_handle_start(struct dl *dl,
+					      const char *bus_name,
+					      const char *dev_name,
+					      uint32_t port_index)
+{
+	return !cmp_arr_last_port_handle(dl, bus_name, dev_name, port_index);
+}
+
+static bool should_arr_last_port_handle_end(struct dl *dl,
+					    const char *bus_name,
+					    const char *dev_name,
+					    uint32_t port_index)
+{
+	return dl->arr_last.present &&
+	       !cmp_arr_last_port_handle(dl, bus_name, dev_name, port_index);
+}
+
+static void __pr_out_port_handle_start(struct dl *dl, const char *bus_name,
+				       const char *dev_name,
+				       uint32_t port_index, bool try_nice,
+				       bool array)
+{
+	static char buf[64];
+	char *ifname = NULL;
+
+	if (dl->no_nice_names || !try_nice ||
+	    ifname_map_rev_lookup(dl, bus_name, dev_name,
+				  port_index, &ifname) != 0)
+		sprintf(buf, "%s/%s/%d", bus_name, dev_name, port_index);
+	else
+		sprintf(buf, "%s", ifname);
+
+	if (dl->json_output) {
+		if (array) {
+			if (should_arr_last_port_handle_end(dl, bus_name,
+							    dev_name,
+							    port_index))
+				jsonw_end_array(dl->jw);
+			if (should_arr_last_port_handle_start(dl, bus_name,
+							      dev_name,
+							      port_index)) {
+				jsonw_name(dl->jw, buf);
+				jsonw_start_array(dl->jw);
+				jsonw_start_object(dl->jw);
+				arr_last_port_handle_set(dl, bus_name, dev_name,
+							 port_index);
+			} else {
+				jsonw_start_object(dl->jw);
+			}
+		} else {
+			jsonw_name(dl->jw, buf);
+			jsonw_start_object(dl->jw);
+		}
+	} else {
+		pr_out("%s:", buf);
+	}
+}
+
+static void pr_out_port_handle_start(struct dl *dl, struct nlattr **tb, bool try_nice)
+{
+	const char *bus_name;
+	const char *dev_name;
+	uint32_t port_index;
+
+	bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+	dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+	port_index = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]);
+	__pr_out_port_handle_start(dl, bus_name, dev_name, port_index, try_nice, false);
+}
+
+static void pr_out_port_handle_start_arr(struct dl *dl, struct nlattr **tb, bool try_nice)
+{
+	const char *bus_name;
+	const char *dev_name;
+	uint32_t port_index;
+
+	bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+	dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+	port_index = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]);
+	__pr_out_port_handle_start(dl, bus_name, dev_name, port_index, try_nice, true);
+}
+
+static void pr_out_port_handle_end(struct dl *dl)
+{
+	if (dl->json_output)
+		jsonw_end_object(dl->jw);
+	else
+		pr_out("\n");
+}
+
+
+static void pr_out_str(struct dl *dl, const char *name, const char *val)
+{
+	__pr_out_indent_newline(dl);
+	if (dl->json_output)
+		jsonw_string_field(dl->jw, name, val);
+	else
+		pr_out("%s %s", name, val);
+}
+
+static void pr_out_bool(struct dl *dl, const char *name, bool val)
+{
+	if (dl->json_output)
+		jsonw_bool_field(dl->jw, name, val);
+	else
+		pr_out_str(dl, name, val ? "true" : "false");
+}
+
+static void pr_out_uint(struct dl *dl, const char *name, unsigned int val)
+{
+	__pr_out_indent_newline(dl);
+	if (dl->json_output)
+		jsonw_uint_field(dl->jw, name, val);
+	else
+		pr_out("%s %u", name, val);
+}
+
+static void pr_out_u64(struct dl *dl, const char *name, uint64_t val)
+{
+	__pr_out_indent_newline(dl);
+	if (val == (uint64_t) -1)
+		return pr_out_str(dl, name, "unlimited");
+
+	if (dl->json_output)
+		jsonw_u64_field(dl->jw, name, val);
+	else
+		pr_out("%s %"PRIu64, name, val);
+}
+
+static void pr_out_bool_value(struct dl *dl, bool value)
+{
+	__pr_out_indent_newline(dl);
+	if (dl->json_output)
+		jsonw_bool(dl->jw, value);
+	else
+		pr_out("%s", value ? "true" : "false");
+}
+
+static void pr_out_uint_value(struct dl *dl, unsigned int value)
+{
+	__pr_out_indent_newline(dl);
+	if (dl->json_output)
+		jsonw_uint(dl->jw, value);
+	else
+		pr_out("%u", value);
+}
+
+static void pr_out_uint64_value(struct dl *dl, uint64_t value)
+{
+	__pr_out_indent_newline(dl);
+	if (dl->json_output)
+		jsonw_u64(dl->jw, value);
+	else
+		pr_out("%"PRIu64, value);
+}
+
+static bool is_binary_eol(int i)
+{
+	return !(i%16);
+}
+
+static void pr_out_binary_value(struct dl *dl, uint8_t *data, uint32_t len)
+{
+	int i = 0;
+
+	while (i < len) {
+		if (dl->json_output)
+			jsonw_printf(dl->jw, "%d", data[i]);
+		else
+			pr_out("%02x ", data[i]);
+		i++;
+		if (!dl->json_output && is_binary_eol(i))
+			__pr_out_newline();
+	}
+	if (!dl->json_output && !is_binary_eol(i))
+		__pr_out_newline();
+}
+
+static void pr_out_str_value(struct dl *dl, const char *value)
+{
+	__pr_out_indent_newline(dl);
+	if (dl->json_output)
+		jsonw_string(dl->jw, value);
+	else
+		pr_out("%s", value);
+}
+
+static void pr_out_name(struct dl *dl, const char *name)
+{
+	__pr_out_indent_newline(dl);
+	if (dl->json_output)
+		jsonw_name(dl->jw, name);
+	else
+		pr_out("%s:", name);
+}
+
+static void pr_out_region_chunk_start(struct dl *dl, uint64_t addr)
+{
+	if (dl->json_output) {
+		jsonw_name(dl->jw, "address");
+		jsonw_uint(dl->jw, addr);
+		jsonw_name(dl->jw, "data");
+		jsonw_start_array(dl->jw);
+	}
+}
+
+static void pr_out_region_chunk_end(struct dl *dl)
+{
+	if (dl->json_output)
+		jsonw_end_array(dl->jw);
+}
+
+static void pr_out_region_chunk(struct dl *dl, uint8_t *data, uint32_t len,
+				uint64_t addr)
+{
+	static uint64_t align_val;
+	uint32_t i = 0;
+
+	pr_out_region_chunk_start(dl, addr);
+	while (i < len) {
+		if (!dl->json_output)
+			if (!(align_val % 16))
+				pr_out("%s%016"PRIx64" ",
+				       align_val ? "\n" : "",
+				       addr);
+
+		align_val++;
+
+		if (dl->json_output)
+			jsonw_printf(dl->jw, "%d", data[i]);
+		else
+			pr_out("%02x ", data[i]);
+
+		addr++;
+		i++;
+	}
+	pr_out_region_chunk_end(dl);
+}
+
+static void pr_out_section_start(struct dl *dl, const char *name)
+{
+	if (dl->json_output) {
+		jsonw_start_object(dl->jw);
+		jsonw_name(dl->jw, name);
+		jsonw_start_object(dl->jw);
+	}
+}
+
+static void pr_out_section_end(struct dl *dl)
+{
+	if (dl->json_output) {
+		if (dl->arr_last.present)
+			jsonw_end_array(dl->jw);
+		jsonw_end_object(dl->jw);
+		jsonw_end_object(dl->jw);
+	}
+}
+
+static void pr_out_array_start(struct dl *dl, const char *name)
+{
+	if (dl->json_output) {
+		jsonw_name(dl->jw, name);
+		jsonw_start_array(dl->jw);
+	} else {
+		__pr_out_indent_inc();
+		__pr_out_newline();
+		pr_out("%s:", name);
+		__pr_out_indent_inc();
+		__pr_out_newline();
+	}
+}
+
+static void pr_out_array_end(struct dl *dl)
+{
+	if (dl->json_output) {
+		jsonw_end_array(dl->jw);
+	} else {
+		__pr_out_indent_dec();
+		__pr_out_indent_dec();
+	}
+}
+
+static void pr_out_object_start(struct dl *dl, const char *name)
+{
+	if (dl->json_output) {
+		jsonw_name(dl->jw, name);
+		jsonw_start_object(dl->jw);
+	} else {
+		__pr_out_indent_inc();
+		__pr_out_newline();
+		pr_out("%s:", name);
+		__pr_out_indent_inc();
+		__pr_out_newline();
+	}
+}
+
+static void pr_out_object_end(struct dl *dl)
+{
+	if (dl->json_output) {
+		jsonw_end_object(dl->jw);
+	} else {
+		__pr_out_indent_dec();
+		__pr_out_indent_dec();
+	}
+}
+
+static void pr_out_entry_start(struct dl *dl)
+{
+	if (dl->json_output)
+		jsonw_start_object(dl->jw);
+}
+
+static void pr_out_entry_end(struct dl *dl)
+{
+	if (dl->json_output)
+		jsonw_end_object(dl->jw);
+	else
+		__pr_out_newline();
+}
+
+static void pr_out_stats(struct dl *dl, struct nlattr *nla_stats)
+{
+	struct nlattr *tb[DEVLINK_ATTR_STATS_MAX + 1] = {};
+	int err;
+
+	if (!dl->stats)
+		return;
+
+	err = mnl_attr_parse_nested(nla_stats, attr_stats_cb, tb);
+	if (err != MNL_CB_OK)
+		return;
+
+	pr_out_object_start(dl, "stats");
+	pr_out_object_start(dl, "rx");
+	if (tb[DEVLINK_ATTR_STATS_RX_BYTES])
+		pr_out_u64(dl, "bytes",
+			   mnl_attr_get_u64(tb[DEVLINK_ATTR_STATS_RX_BYTES]));
+	if (tb[DEVLINK_ATTR_STATS_RX_PACKETS])
+		pr_out_u64(dl, "packets",
+			   mnl_attr_get_u64(tb[DEVLINK_ATTR_STATS_RX_PACKETS]));
+	pr_out_object_end(dl);
+	pr_out_object_end(dl);
+}
+
+static const char *param_cmode_name(uint8_t cmode)
+{
+	switch (cmode) {
+	case DEVLINK_PARAM_CMODE_RUNTIME:
+		return PARAM_CMODE_RUNTIME_STR;
+	case DEVLINK_PARAM_CMODE_DRIVERINIT:
+		return PARAM_CMODE_DRIVERINIT_STR;
+	case DEVLINK_PARAM_CMODE_PERMANENT:
+		return PARAM_CMODE_PERMANENT_STR;
+	default: return "<unknown type>";
+	}
+}
+
+static const char *eswitch_mode_name(uint32_t mode)
+{
+	switch (mode) {
+	case DEVLINK_ESWITCH_MODE_LEGACY: return ESWITCH_MODE_LEGACY;
+	case DEVLINK_ESWITCH_MODE_SWITCHDEV: return ESWITCH_MODE_SWITCHDEV;
+	default: return "<unknown mode>";
+	}
+}
+
+static const char *eswitch_inline_mode_name(uint32_t mode)
+{
+	switch (mode) {
+	case DEVLINK_ESWITCH_INLINE_MODE_NONE:
+		return ESWITCH_INLINE_MODE_NONE;
+	case DEVLINK_ESWITCH_INLINE_MODE_LINK:
+		return ESWITCH_INLINE_MODE_LINK;
+	case DEVLINK_ESWITCH_INLINE_MODE_NETWORK:
+		return ESWITCH_INLINE_MODE_NETWORK;
+	case DEVLINK_ESWITCH_INLINE_MODE_TRANSPORT:
+		return ESWITCH_INLINE_MODE_TRANSPORT;
+	default:
+		return "<unknown mode>";
+	}
+}
+
+static void pr_out_eswitch(struct dl *dl, struct nlattr **tb)
+{
+	__pr_out_handle_start(dl, tb, true, false);
+
+	if (tb[DEVLINK_ATTR_ESWITCH_MODE])
+		pr_out_str(dl, "mode",
+			   eswitch_mode_name(mnl_attr_get_u16(tb[DEVLINK_ATTR_ESWITCH_MODE])));
+
+	if (tb[DEVLINK_ATTR_ESWITCH_INLINE_MODE])
+		pr_out_str(dl, "inline-mode",
+			   eswitch_inline_mode_name(mnl_attr_get_u8(
+				   tb[DEVLINK_ATTR_ESWITCH_INLINE_MODE])));
+
+	if (tb[DEVLINK_ATTR_ESWITCH_ENCAP_MODE]) {
+		bool encap_mode = !!mnl_attr_get_u8(tb[DEVLINK_ATTR_ESWITCH_ENCAP_MODE]);
+
+		pr_out_str(dl, "encap", encap_mode ? "enable" : "disable");
+	}
+
+	pr_out_handle_end(dl);
+}
+
+static int cmd_dev_eswitch_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dl *dl = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+		return MNL_CB_ERROR;
+	pr_out_eswitch(dl, tb);
+	return MNL_CB_OK;
+}
+
+static int cmd_dev_eswitch_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_ESWITCH_GET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, 0);
+	if (err)
+		return err;
+
+	pr_out_section_start(dl, "dev");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dev_eswitch_show_cb, dl);
+	pr_out_section_end(dl);
+	return err;
+}
+
+static int cmd_dev_eswitch_set(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_ESWITCH_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE,
+				DL_OPT_ESWITCH_MODE |
+				DL_OPT_ESWITCH_INLINE_MODE |
+				DL_OPT_ESWITCH_ENCAP_MODE);
+
+	if (err)
+		return err;
+
+	if (dl->opts.present == 1) {
+		pr_err("Need to set at least one option\n");
+		return -ENOENT;
+	}
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_dev_eswitch(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		cmd_dev_help();
+		return 0;
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_dev_eswitch_set(dl);
+	} else if (dl_argv_match(dl, "show")) {
+		dl_arg_inc(dl);
+		return cmd_dev_eswitch_show(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+struct param_val_conv {
+	const char *name;
+	const char *vstr;
+	uint32_t vuint;
+};
+
+static bool param_val_conv_exists(const struct param_val_conv *param_val_conv,
+				  uint32_t len, const char *name)
+{
+	uint32_t i;
+
+	for (i = 0; i < len; i++)
+		if (!strcmp(param_val_conv[i].name, name))
+			return true;
+
+	return false;
+}
+
+static int
+param_val_conv_uint_get(const struct param_val_conv *param_val_conv,
+			uint32_t len, const char *name, const char *vstr,
+			uint32_t *vuint)
+{
+	uint32_t i;
+
+	for (i = 0; i < len; i++)
+		if (!strcmp(param_val_conv[i].name, name) &&
+		    !strcmp(param_val_conv[i].vstr, vstr)) {
+			*vuint = param_val_conv[i].vuint;
+			return 0;
+		}
+
+	return -ENOENT;
+}
+
+static int
+param_val_conv_str_get(const struct param_val_conv *param_val_conv,
+		       uint32_t len, const char *name, uint32_t vuint,
+		       const char **vstr)
+{
+	uint32_t i;
+
+	for (i = 0; i < len; i++)
+		if (!strcmp(param_val_conv[i].name, name) &&
+		    param_val_conv[i].vuint == vuint) {
+			*vstr = param_val_conv[i].vstr;
+			return 0;
+		}
+
+	return -ENOENT;
+}
+
+static const struct param_val_conv param_val_conv[] = {
+	{
+		.name = "fw_load_policy",
+		.vstr = "driver",
+		.vuint = DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DRIVER,
+	},
+	{
+		.name = "fw_load_policy",
+		.vstr = "flash",
+		.vuint = DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_FLASH,
+	},
+	{
+		.name = "reset_dev_on_drv_probe",
+		.vstr = "unknown",
+		.vuint = DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_UNKNOWN,
+	},
+	{
+		.name = "fw_load_policy",
+		.vstr = "unknown",
+		.vuint = DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_UNKNOWN,
+	},
+	{
+		.name = "reset_dev_on_drv_probe",
+		.vstr = "always",
+		.vuint = DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_ALWAYS,
+	},
+	{
+		.name = "reset_dev_on_drv_probe",
+		.vstr = "never",
+		.vuint = DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_NEVER,
+	},
+	{
+		.name = "reset_dev_on_drv_probe",
+		.vstr = "disk",
+		.vuint = DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_DISK,
+	},
+};
+
+#define PARAM_VAL_CONV_LEN ARRAY_SIZE(param_val_conv)
+
+static void pr_out_param_value(struct dl *dl, const char *nla_name,
+			       int nla_type, struct nlattr *nl)
+{
+	struct nlattr *nla_value[DEVLINK_ATTR_MAX + 1] = {};
+	struct nlattr *val_attr;
+	const char *vstr;
+	bool conv_exists;
+	int err;
+
+	err = mnl_attr_parse_nested(nl, attr_cb, nla_value);
+	if (err != MNL_CB_OK)
+		return;
+
+	if (!nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE] ||
+	    (nla_type != MNL_TYPE_FLAG &&
+	     !nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA]))
+		return;
+
+	pr_out_str(dl, "cmode",
+		   param_cmode_name(mnl_attr_get_u8(nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE])));
+	val_attr = nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA];
+
+	conv_exists = param_val_conv_exists(param_val_conv, PARAM_VAL_CONV_LEN,
+					    nla_name);
+
+	switch (nla_type) {
+	case MNL_TYPE_U8:
+		if (conv_exists) {
+			err = param_val_conv_str_get(param_val_conv,
+						     PARAM_VAL_CONV_LEN,
+						     nla_name,
+						     mnl_attr_get_u8(val_attr),
+						     &vstr);
+			if (err)
+				return;
+			pr_out_str(dl, "value", vstr);
+		} else {
+			pr_out_uint(dl, "value", mnl_attr_get_u8(val_attr));
+		}
+		break;
+	case MNL_TYPE_U16:
+		if (conv_exists) {
+			err = param_val_conv_str_get(param_val_conv,
+						     PARAM_VAL_CONV_LEN,
+						     nla_name,
+						     mnl_attr_get_u16(val_attr),
+						     &vstr);
+			if (err)
+				return;
+			pr_out_str(dl, "value", vstr);
+		} else {
+			pr_out_uint(dl, "value", mnl_attr_get_u16(val_attr));
+		}
+		break;
+	case MNL_TYPE_U32:
+		if (conv_exists) {
+			err = param_val_conv_str_get(param_val_conv,
+						     PARAM_VAL_CONV_LEN,
+						     nla_name,
+						     mnl_attr_get_u32(val_attr),
+						     &vstr);
+			if (err)
+				return;
+			pr_out_str(dl, "value", vstr);
+		} else {
+			pr_out_uint(dl, "value", mnl_attr_get_u32(val_attr));
+		}
+		break;
+	case MNL_TYPE_STRING:
+		pr_out_str(dl, "value", mnl_attr_get_str(val_attr));
+		break;
+	case MNL_TYPE_FLAG:
+		pr_out_bool(dl, "value", val_attr ? true : false);
+		break;
+	}
+}
+
+static void pr_out_param(struct dl *dl, struct nlattr **tb, bool array)
+{
+	struct nlattr *nla_param[DEVLINK_ATTR_MAX + 1] = {};
+	struct nlattr *param_value_attr;
+	const char *nla_name;
+	int nla_type;
+	int err;
+
+	err = mnl_attr_parse_nested(tb[DEVLINK_ATTR_PARAM], attr_cb, nla_param);
+	if (err != MNL_CB_OK)
+		return;
+	if (!nla_param[DEVLINK_ATTR_PARAM_NAME] ||
+	    !nla_param[DEVLINK_ATTR_PARAM_TYPE] ||
+	    !nla_param[DEVLINK_ATTR_PARAM_VALUES_LIST])
+		return;
+
+	if (array)
+		pr_out_handle_start_arr(dl, tb);
+	else
+		__pr_out_handle_start(dl, tb, true, false);
+
+	nla_type = mnl_attr_get_u8(nla_param[DEVLINK_ATTR_PARAM_TYPE]);
+
+	nla_name = mnl_attr_get_str(nla_param[DEVLINK_ATTR_PARAM_NAME]);
+	pr_out_str(dl, "name", nla_name);
+
+	if (!nla_param[DEVLINK_ATTR_PARAM_GENERIC])
+		pr_out_str(dl, "type", "driver-specific");
+	else
+		pr_out_str(dl, "type", "generic");
+
+	pr_out_array_start(dl, "values");
+	mnl_attr_for_each_nested(param_value_attr,
+				 nla_param[DEVLINK_ATTR_PARAM_VALUES_LIST]) {
+		pr_out_entry_start(dl);
+		pr_out_param_value(dl, nla_name, nla_type, param_value_attr);
+		pr_out_entry_end(dl);
+	}
+	pr_out_array_end(dl);
+	pr_out_handle_end(dl);
+}
+
+static int cmd_dev_param_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct dl *dl = data;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_PARAM])
+		return MNL_CB_ERROR;
+	pr_out_param(dl, tb, true);
+	return MNL_CB_OK;
+}
+
+struct param_ctx {
+	struct dl *dl;
+	int nla_type;
+	union {
+		uint8_t vu8;
+		uint16_t vu16;
+		uint32_t vu32;
+		const char *vstr;
+		bool vbool;
+	} value;
+};
+
+static int cmd_dev_param_set_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *nla_param[DEVLINK_ATTR_MAX + 1] = {};
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct nlattr *param_value_attr;
+	enum devlink_param_cmode cmode;
+	struct param_ctx *ctx = data;
+	struct dl *dl = ctx->dl;
+	int nla_type;
+	int err;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_PARAM])
+		return MNL_CB_ERROR;
+
+	err = mnl_attr_parse_nested(tb[DEVLINK_ATTR_PARAM], attr_cb, nla_param);
+	if (err != MNL_CB_OK)
+		return MNL_CB_ERROR;
+
+	if (!nla_param[DEVLINK_ATTR_PARAM_TYPE] ||
+	    !nla_param[DEVLINK_ATTR_PARAM_VALUES_LIST])
+		return MNL_CB_ERROR;
+
+	nla_type = mnl_attr_get_u8(nla_param[DEVLINK_ATTR_PARAM_TYPE]);
+	mnl_attr_for_each_nested(param_value_attr,
+				 nla_param[DEVLINK_ATTR_PARAM_VALUES_LIST]) {
+		struct nlattr *nla_value[DEVLINK_ATTR_MAX + 1] = {};
+		struct nlattr *val_attr;
+
+		err = mnl_attr_parse_nested(param_value_attr,
+					    attr_cb, nla_value);
+		if (err != MNL_CB_OK)
+			return MNL_CB_ERROR;
+
+		if (!nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE] ||
+		    (nla_type != MNL_TYPE_FLAG &&
+		     !nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA]))
+			return MNL_CB_ERROR;
+
+		cmode = mnl_attr_get_u8(nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE]);
+		if (cmode == dl->opts.cmode) {
+			val_attr = nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA];
+			switch (nla_type) {
+			case MNL_TYPE_U8:
+				ctx->value.vu8 = mnl_attr_get_u8(val_attr);
+				break;
+			case MNL_TYPE_U16:
+				ctx->value.vu16 = mnl_attr_get_u16(val_attr);
+				break;
+			case MNL_TYPE_U32:
+				ctx->value.vu32 = mnl_attr_get_u32(val_attr);
+				break;
+			case MNL_TYPE_STRING:
+				ctx->value.vstr = mnl_attr_get_str(val_attr);
+				break;
+			case MNL_TYPE_FLAG:
+				ctx->value.vbool = val_attr ? true : false;
+				break;
+			}
+			break;
+		}
+	}
+	ctx->nla_type = nla_type;
+	return MNL_CB_OK;
+}
+
+static int cmd_dev_param_set(struct dl *dl)
+{
+	struct param_ctx ctx = {};
+	struct nlmsghdr *nlh;
+	bool conv_exists;
+	uint32_t val_u32;
+	uint16_t val_u16;
+	uint8_t val_u8;
+	bool val_bool;
+	int err;
+
+	err = dl_argv_parse(dl, DL_OPT_HANDLE |
+			    DL_OPT_PARAM_NAME |
+			    DL_OPT_PARAM_VALUE |
+			    DL_OPT_PARAM_CMODE, 0);
+	if (err)
+		return err;
+
+	/* Get value type */
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_PARAM_GET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+	dl_opts_put(nlh, dl);
+
+	ctx.dl = dl;
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dev_param_set_cb, &ctx);
+	if (err)
+		return err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_PARAM_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+	dl_opts_put(nlh, dl);
+
+	conv_exists = param_val_conv_exists(param_val_conv, PARAM_VAL_CONV_LEN,
+					    dl->opts.param_name);
+
+	mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_TYPE, ctx.nla_type);
+	switch (ctx.nla_type) {
+	case MNL_TYPE_U8:
+		if (conv_exists) {
+			err = param_val_conv_uint_get(param_val_conv,
+						      PARAM_VAL_CONV_LEN,
+						      dl->opts.param_name,
+						      dl->opts.param_value,
+						      &val_u32);
+			val_u8 = val_u32;
+		} else {
+			err = strtouint8_t(dl->opts.param_value, &val_u8);
+		}
+		if (err)
+			goto err_param_value_parse;
+		if (val_u8 == ctx.value.vu8)
+			return 0;
+		mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u8);
+		break;
+	case MNL_TYPE_U16:
+		if (conv_exists) {
+			err = param_val_conv_uint_get(param_val_conv,
+						      PARAM_VAL_CONV_LEN,
+						      dl->opts.param_name,
+						      dl->opts.param_value,
+						      &val_u32);
+			val_u16 = val_u32;
+		} else {
+			err = strtouint16_t(dl->opts.param_value, &val_u16);
+		}
+		if (err)
+			goto err_param_value_parse;
+		if (val_u16 == ctx.value.vu16)
+			return 0;
+		mnl_attr_put_u16(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u16);
+		break;
+	case MNL_TYPE_U32:
+		if (conv_exists)
+			err = param_val_conv_uint_get(param_val_conv,
+						      PARAM_VAL_CONV_LEN,
+						      dl->opts.param_name,
+						      dl->opts.param_value,
+						      &val_u32);
+		else
+			err = strtouint32_t(dl->opts.param_value, &val_u32);
+		if (err)
+			goto err_param_value_parse;
+		if (val_u32 == ctx.value.vu32)
+			return 0;
+		mnl_attr_put_u32(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u32);
+		break;
+	case MNL_TYPE_FLAG:
+		err = strtobool(dl->opts.param_value, &val_bool);
+		if (err)
+			goto err_param_value_parse;
+		if (val_bool == ctx.value.vbool)
+			return 0;
+		if (val_bool)
+			mnl_attr_put(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
+				     0, NULL);
+		break;
+	case MNL_TYPE_STRING:
+		mnl_attr_put_strz(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
+				  dl->opts.param_value);
+		if (!strcmp(dl->opts.param_value, ctx.value.vstr))
+			return 0;
+		break;
+	default:
+		printf("Value type not supported\n");
+		return -ENOTSUP;
+	}
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+
+err_param_value_parse:
+	pr_err("Value \"%s\" is not a number or not within range\n",
+	       dl->opts.param_value);
+	return err;
+}
+
+static int cmd_dev_param_show(struct dl *dl)
+{
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	struct nlmsghdr *nlh;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_PARAM_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE |
+					DL_OPT_PARAM_NAME, 0);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "param");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dev_param_show_cb, dl);
+	pr_out_section_end(dl);
+	return err;
+}
+
+static int cmd_dev_param(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_dev_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+		dl_arg_inc(dl);
+		return cmd_dev_param_show(dl);
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_dev_param_set(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+static int cmd_dev_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dl *dl = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	uint8_t reload_failed = 0;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+		return MNL_CB_ERROR;
+
+	if (tb[DEVLINK_ATTR_RELOAD_FAILED])
+		reload_failed = mnl_attr_get_u8(tb[DEVLINK_ATTR_RELOAD_FAILED]);
+
+	if (reload_failed) {
+		__pr_out_handle_start(dl, tb, true, false);
+		pr_out_bool(dl, "reload_failed", true);
+		pr_out_handle_end(dl);
+	} else {
+		pr_out_handle(dl, tb);
+	}
+
+	return MNL_CB_OK;
+}
+
+static int cmd_dev_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, 0);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "dev");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dev_show_cb, dl);
+	pr_out_section_end(dl);
+	return err;
+}
+
+static void cmd_dev_reload_help(void)
+{
+	pr_err("Usage: devlink dev reload [ DEV ]\n");
+}
+
+static int cmd_dev_reload(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		cmd_dev_reload_help();
+		return 0;
+	}
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_RELOAD,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, 0);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static void pr_out_versions_single(struct dl *dl, const struct nlmsghdr *nlh,
+				   const char *name, int type)
+{
+	struct nlattr *version;
+
+	mnl_attr_for_each(version, nlh, sizeof(struct genlmsghdr)) {
+		struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+		const char *ver_value;
+		const char *ver_name;
+		int err;
+
+		if (mnl_attr_get_type(version) != type)
+			continue;
+
+		err = mnl_attr_parse_nested(version, attr_cb, tb);
+		if (err != MNL_CB_OK)
+			continue;
+
+		if (!tb[DEVLINK_ATTR_INFO_VERSION_NAME] ||
+		    !tb[DEVLINK_ATTR_INFO_VERSION_VALUE])
+			continue;
+
+		if (name) {
+			pr_out_object_start(dl, name);
+			name = NULL;
+		}
+
+		ver_name = mnl_attr_get_str(tb[DEVLINK_ATTR_INFO_VERSION_NAME]);
+		ver_value = mnl_attr_get_str(tb[DEVLINK_ATTR_INFO_VERSION_VALUE]);
+
+		pr_out_str(dl, ver_name, ver_value);
+		if (!dl->json_output)
+			__pr_out_newline();
+	}
+
+	if (!name)
+		pr_out_object_end(dl);
+}
+
+static void pr_out_info(struct dl *dl, const struct nlmsghdr *nlh,
+			struct nlattr **tb, bool has_versions)
+{
+	__pr_out_handle_start(dl, tb, true, false);
+
+	__pr_out_indent_inc();
+	if (tb[DEVLINK_ATTR_INFO_DRIVER_NAME]) {
+		struct nlattr *nla_drv = tb[DEVLINK_ATTR_INFO_DRIVER_NAME];
+
+		if (!dl->json_output)
+			__pr_out_newline();
+		pr_out_str(dl, "driver", mnl_attr_get_str(nla_drv));
+	}
+
+	if (tb[DEVLINK_ATTR_INFO_SERIAL_NUMBER]) {
+		struct nlattr *nla_sn = tb[DEVLINK_ATTR_INFO_SERIAL_NUMBER];
+
+		if (!dl->json_output)
+			__pr_out_newline();
+		pr_out_str(dl, "serial_number", mnl_attr_get_str(nla_sn));
+	}
+	__pr_out_indent_dec();
+
+	if (has_versions) {
+		pr_out_object_start(dl, "versions");
+
+		pr_out_versions_single(dl, nlh, "fixed",
+				       DEVLINK_ATTR_INFO_VERSION_FIXED);
+		pr_out_versions_single(dl, nlh, "running",
+				       DEVLINK_ATTR_INFO_VERSION_RUNNING);
+		pr_out_versions_single(dl, nlh, "stored",
+				       DEVLINK_ATTR_INFO_VERSION_STORED);
+
+		pr_out_object_end(dl);
+	}
+
+	pr_out_handle_end(dl);
+}
+
+static int cmd_versions_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	bool has_versions, has_info;
+	struct dl *dl = data;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+		return MNL_CB_ERROR;
+
+	has_versions = tb[DEVLINK_ATTR_INFO_VERSION_FIXED] ||
+		tb[DEVLINK_ATTR_INFO_VERSION_RUNNING] ||
+		tb[DEVLINK_ATTR_INFO_VERSION_STORED];
+	has_info = tb[DEVLINK_ATTR_INFO_DRIVER_NAME] ||
+		tb[DEVLINK_ATTR_INFO_SERIAL_NUMBER] ||
+		has_versions;
+
+	if (has_info)
+		pr_out_info(dl, nlh, tb, has_versions);
+
+	return MNL_CB_OK;
+}
+
+static void cmd_dev_info_help(void)
+{
+	pr_err("Usage: devlink dev info [ DEV ]\n");
+}
+
+static int cmd_dev_info(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	int err;
+
+	if (dl_argv_match(dl, "help")) {
+		cmd_dev_info_help();
+		return 0;
+	}
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_INFO_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, 0);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "info");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_versions_show_cb, dl);
+	pr_out_section_end(dl);
+	return err;
+}
+
+static void cmd_dev_flash_help(void)
+{
+	pr_err("Usage: devlink dev flash DEV file PATH [ component NAME ]\n");
+}
+
+
+struct cmd_dev_flash_status_ctx {
+	struct dl *dl;
+	char *last_msg;
+	char *last_component;
+	uint8_t not_first:1,
+		last_pc:1,
+		received_end:1,
+		flash_done:1;
+};
+
+static int nullstrcmp(const char *str1, const char *str2)
+{
+	if (str1 && str2)
+		return strcmp(str1, str2);
+	if (!str1 && !str2)
+		return 0;
+	return str1 ? 1 : -1;
+}
+
+static int cmd_dev_flash_status_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct cmd_dev_flash_status_ctx *ctx = data;
+	struct dl_opts *opts = &ctx->dl->opts;
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	const char *component = NULL;
+	uint64_t done = 0, total = 0;
+	const char *msg = NULL;
+	const char *bus_name;
+	const char *dev_name;
+
+	if (genl->cmd != DEVLINK_CMD_FLASH_UPDATE_STATUS &&
+	    genl->cmd != DEVLINK_CMD_FLASH_UPDATE_END)
+		return MNL_CB_STOP;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+		return MNL_CB_ERROR;
+	bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+	dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+	if (strcmp(bus_name, opts->bus_name) ||
+	    strcmp(dev_name, opts->dev_name))
+		return MNL_CB_ERROR;
+
+	if (genl->cmd == DEVLINK_CMD_FLASH_UPDATE_END && ctx->not_first) {
+		pr_out("\n");
+		free(ctx->last_msg);
+		free(ctx->last_component);
+		ctx->received_end = 1;
+		return MNL_CB_STOP;
+	}
+
+	if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG])
+		msg = mnl_attr_get_str(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG]);
+	if (tb[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT])
+		component = mnl_attr_get_str(tb[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT]);
+	if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE])
+		done = mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE]);
+	if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL])
+		total = mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL]);
+
+	if (!nullstrcmp(msg, ctx->last_msg) &&
+	    !nullstrcmp(component, ctx->last_component) &&
+	    ctx->last_pc && ctx->not_first) {
+		pr_out_tty("\b\b\b\b\b"); /* clean percentage */
+	} else {
+		if (ctx->not_first)
+			pr_out("\n");
+		if (component) {
+			pr_out("[%s] ", component);
+			free(ctx->last_component);
+			ctx->last_component = strdup(component);
+		}
+		if (msg) {
+			pr_out("%s", msg);
+			free(ctx->last_msg);
+			ctx->last_msg = strdup(msg);
+		}
+	}
+	if (total) {
+		pr_out_tty(" %3lu%%", (done * 100) / total);
+		ctx->last_pc = 1;
+	} else {
+		ctx->last_pc = 0;
+	}
+	fflush(stdout);
+	ctx->not_first = 1;
+
+	return MNL_CB_STOP;
+}
+
+static int cmd_dev_flash_fds_process(struct cmd_dev_flash_status_ctx *ctx,
+				     struct mnlg_socket *nlg_ntf,
+				     int pipe_r)
+{
+	int nlfd = mnlg_socket_get_fd(nlg_ntf);
+	fd_set fds[3];
+	int fdmax;
+	int i;
+	int err;
+	int err2;
+
+	for (i = 0; i < 3; i++)
+		FD_ZERO(&fds[i]);
+	FD_SET(pipe_r, &fds[0]);
+	fdmax = pipe_r + 1;
+	FD_SET(nlfd, &fds[0]);
+	if (nlfd >= fdmax)
+		fdmax = nlfd + 1;
+
+	while (select(fdmax, &fds[0], &fds[1], &fds[2], NULL) < 0) {
+		if (errno == EINTR)
+			continue;
+		pr_err("select() failed\n");
+		return -errno;
+	}
+	if (FD_ISSET(nlfd, &fds[0])) {
+		err = _mnlg_socket_recv_run(nlg_ntf,
+					    cmd_dev_flash_status_cb, ctx);
+		if (err)
+			return err;
+	}
+	if (FD_ISSET(pipe_r, &fds[0])) {
+		err = read(pipe_r, &err2, sizeof(err2));
+		if (err == -1) {
+			pr_err("Failed to read pipe\n");
+			return -errno;
+		}
+		if (err2)
+			return err2;
+		ctx->flash_done = 1;
+	}
+	return 0;
+}
+
+
+static int cmd_dev_flash(struct dl *dl)
+{
+	struct cmd_dev_flash_status_ctx ctx = {.dl = dl,};
+	struct mnlg_socket *nlg_ntf;
+	struct nlmsghdr *nlh;
+	int pipe_r, pipe_w;
+	int pipe_fds[2];
+	pid_t pid;
+	int err;
+
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		cmd_dev_flash_help();
+		return 0;
+	}
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_FLASH_UPDATE,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE | DL_OPT_FLASH_FILE_NAME,
+				DL_OPT_FLASH_COMPONENT);
+	if (err)
+		return err;
+
+	nlg_ntf = mnlg_socket_open(DEVLINK_GENL_NAME, DEVLINK_GENL_VERSION);
+	if (!nlg_ntf)
+		return err;
+
+	err = _mnlg_socket_group_add(nlg_ntf, DEVLINK_GENL_MCGRP_CONFIG_NAME);
+	if (err)
+		return err;
+
+	err = pipe(pipe_fds);
+	if (err == -1)
+		return -errno;
+	pipe_r = pipe_fds[0];
+	pipe_w = pipe_fds[1];
+
+	pid = fork();
+	if (pid == -1) {
+		close(pipe_r);
+		close(pipe_w);
+		return -errno;
+	} else if (!pid) {
+		/* In child, just execute the flash and pass returned
+		 * value through pipe once it is done.
+		 */
+		close(pipe_r);
+		err = _mnlg_socket_send(dl->nlg, nlh);
+		write(pipe_w, &err, sizeof(err));
+		close(pipe_w);
+		exit(0);
+	}
+	close(pipe_w);
+
+	do {
+		err = cmd_dev_flash_fds_process(&ctx, nlg_ntf, pipe_r);
+		if (err)
+			goto out;
+	} while (!ctx.flash_done || (ctx.not_first && !ctx.received_end));
+
+	err = _mnlg_socket_recv_run(dl->nlg, NULL, NULL);
+out:
+	close(pipe_r);
+	mnlg_socket_close(nlg_ntf);
+	return err;
+}
+
+static int cmd_dev(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_dev_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+		dl_arg_inc(dl);
+		return cmd_dev_show(dl);
+	} else if (dl_argv_match(dl, "eswitch")) {
+		dl_arg_inc(dl);
+		return cmd_dev_eswitch(dl);
+	} else if (dl_argv_match(dl, "reload")) {
+		dl_arg_inc(dl);
+		return cmd_dev_reload(dl);
+	} else if (dl_argv_match(dl, "param")) {
+		dl_arg_inc(dl);
+		return cmd_dev_param(dl);
+	} else if (dl_argv_match(dl, "info")) {
+		dl_arg_inc(dl);
+		return cmd_dev_info(dl);
+	} else if (dl_argv_match(dl, "flash")) {
+		dl_arg_inc(dl);
+		return cmd_dev_flash(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static void cmd_port_help(void)
+{
+	pr_err("Usage: devlink port show [ DEV/PORT_INDEX ]\n");
+	pr_err("       devlink port set DEV/PORT_INDEX [ type { eth | ib | auto} ]\n");
+	pr_err("       devlink port split DEV/PORT_INDEX count COUNT\n");
+	pr_err("       devlink port unsplit DEV/PORT_INDEX\n");
+}
+
+static const char *port_type_name(uint32_t type)
+{
+	switch (type) {
+	case DEVLINK_PORT_TYPE_NOTSET: return "notset";
+	case DEVLINK_PORT_TYPE_AUTO: return "auto";
+	case DEVLINK_PORT_TYPE_ETH: return "eth";
+	case DEVLINK_PORT_TYPE_IB: return "ib";
+	default: return "<unknown type>";
+	}
+}
+
+static const char *port_flavour_name(uint16_t flavour)
+{
+	switch (flavour) {
+	case DEVLINK_PORT_FLAVOUR_PHYSICAL:
+		return "physical";
+	case DEVLINK_PORT_FLAVOUR_CPU:
+		return "cpu";
+	case DEVLINK_PORT_FLAVOUR_DSA:
+		return "dsa";
+	case DEVLINK_PORT_FLAVOUR_PCI_PF:
+		return "pcipf";
+	case DEVLINK_PORT_FLAVOUR_PCI_VF:
+		return "pcivf";
+	default:
+		return "<unknown flavour>";
+	}
+}
+
+static void pr_out_port_pfvf_num(struct dl *dl, struct nlattr **tb)
+{
+	uint16_t fn_num;
+
+	if (tb[DEVLINK_ATTR_PORT_PCI_PF_NUMBER]) {
+		fn_num = mnl_attr_get_u16(tb[DEVLINK_ATTR_PORT_PCI_PF_NUMBER]);
+		pr_out_uint(dl, "pfnum", fn_num);
+	}
+	if (tb[DEVLINK_ATTR_PORT_PCI_VF_NUMBER]) {
+		fn_num = mnl_attr_get_u16(tb[DEVLINK_ATTR_PORT_PCI_VF_NUMBER]);
+		pr_out_uint(dl, "vfnum", fn_num);
+	}
+}
+
+static void pr_out_port(struct dl *dl, struct nlattr **tb)
+{
+	struct nlattr *pt_attr = tb[DEVLINK_ATTR_PORT_TYPE];
+	struct nlattr *dpt_attr = tb[DEVLINK_ATTR_PORT_DESIRED_TYPE];
+
+	pr_out_port_handle_start(dl, tb, false);
+	if (pt_attr) {
+		uint16_t port_type = mnl_attr_get_u16(pt_attr);
+
+		pr_out_str(dl, "type", port_type_name(port_type));
+		if (dpt_attr) {
+			uint16_t des_port_type = mnl_attr_get_u16(dpt_attr);
+
+			if (port_type != des_port_type)
+				pr_out_str(dl, "des_type",
+					   port_type_name(des_port_type));
+		}
+	}
+	if (tb[DEVLINK_ATTR_PORT_NETDEV_NAME])
+		pr_out_str(dl, "netdev",
+			   mnl_attr_get_str(tb[DEVLINK_ATTR_PORT_NETDEV_NAME]));
+	if (tb[DEVLINK_ATTR_PORT_IBDEV_NAME])
+		pr_out_str(dl, "ibdev",
+			   mnl_attr_get_str(tb[DEVLINK_ATTR_PORT_IBDEV_NAME]));
+	if (tb[DEVLINK_ATTR_PORT_FLAVOUR]) {
+		uint16_t port_flavour =
+				mnl_attr_get_u16(tb[DEVLINK_ATTR_PORT_FLAVOUR]);
+
+		pr_out_str(dl, "flavour", port_flavour_name(port_flavour));
+
+		switch (port_flavour) {
+		case DEVLINK_PORT_FLAVOUR_PCI_PF:
+		case DEVLINK_PORT_FLAVOUR_PCI_VF:
+			pr_out_port_pfvf_num(dl, tb);
+			break;
+		default:
+			break;
+		}
+	}
+	if (tb[DEVLINK_ATTR_PORT_NUMBER]) {
+		uint32_t port_number;
+
+		port_number = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_NUMBER]);
+		pr_out_uint(dl, "port", port_number);
+	}
+	if (tb[DEVLINK_ATTR_PORT_SPLIT_GROUP])
+		pr_out_uint(dl, "split_group",
+			    mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_SPLIT_GROUP]));
+	pr_out_port_handle_end(dl);
+}
+
+static int cmd_port_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dl *dl = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_PORT_INDEX])
+		return MNL_CB_ERROR;
+	pr_out_port(dl, tb);
+	return MNL_CB_OK;
+}
+
+static int cmd_port_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_PORT_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP, 0);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "port");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_port_show_cb, dl);
+	pr_out_section_end(dl);
+	return err;
+}
+
+static int cmd_port_set(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_PORT_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP | DL_OPT_PORT_TYPE, 0);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_port_split(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_PORT_SPLIT,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP | DL_OPT_PORT_COUNT, 0);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_port_unsplit(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_PORT_UNSPLIT,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP, 0);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_port(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_port_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list") ||  dl_no_arg(dl)) {
+		dl_arg_inc(dl);
+		return cmd_port_show(dl);
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_port_set(dl);
+	} else if (dl_argv_match(dl, "split")) {
+		dl_arg_inc(dl);
+		return cmd_port_split(dl);
+	} else if (dl_argv_match(dl, "unsplit")) {
+		dl_arg_inc(dl);
+		return cmd_port_unsplit(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static void cmd_sb_help(void)
+{
+	pr_err("Usage: devlink sb show [ DEV [ sb SB_INDEX ] ]\n");
+	pr_err("       devlink sb pool show [ DEV [ sb SB_INDEX ] pool POOL_INDEX ]\n");
+	pr_err("       devlink sb pool set DEV [ sb SB_INDEX ] pool POOL_INDEX\n");
+	pr_err("                           size POOL_SIZE thtype { static | dynamic }\n");
+	pr_err("       devlink sb port pool show [ DEV/PORT_INDEX [ sb SB_INDEX ]\n");
+	pr_err("                                   pool POOL_INDEX ]\n");
+	pr_err("       devlink sb port pool set DEV/PORT_INDEX [ sb SB_INDEX ]\n");
+	pr_err("                                pool POOL_INDEX th THRESHOLD\n");
+	pr_err("       devlink sb tc bind show [ DEV/PORT_INDEX [ sb SB_INDEX ] tc TC_INDEX\n");
+	pr_err("                                 type { ingress | egress } ]\n");
+	pr_err("       devlink sb tc bind set DEV/PORT_INDEX [ sb SB_INDEX ] tc TC_INDEX\n");
+	pr_err("                              type { ingress | egress } pool POOL_INDEX\n");
+	pr_err("                              th THRESHOLD\n");
+	pr_err("       devlink sb occupancy show { DEV | DEV/PORT_INDEX } [ sb SB_INDEX ]\n");
+	pr_err("       devlink sb occupancy snapshot DEV [ sb SB_INDEX ]\n");
+	pr_err("       devlink sb occupancy clearmax DEV [ sb SB_INDEX ]\n");
+}
+
+static void pr_out_sb(struct dl *dl, struct nlattr **tb)
+{
+	pr_out_handle_start_arr(dl, tb);
+	pr_out_uint(dl, "sb",
+		    mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_INDEX]));
+	pr_out_uint(dl, "size",
+		    mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_SIZE]));
+	pr_out_uint(dl, "ing_pools",
+		    mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_INGRESS_POOL_COUNT]));
+	pr_out_uint(dl, "eg_pools",
+		    mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_EGRESS_POOL_COUNT]));
+	pr_out_uint(dl, "ing_tcs",
+		    mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_INGRESS_TC_COUNT]));
+	pr_out_uint(dl, "eg_tcs",
+		    mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_EGRESS_TC_COUNT]));
+	pr_out_handle_end(dl);
+}
+
+static int cmd_sb_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dl *dl = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_SB_INDEX] || !tb[DEVLINK_ATTR_SB_SIZE] ||
+	    !tb[DEVLINK_ATTR_SB_INGRESS_POOL_COUNT] ||
+	    !tb[DEVLINK_ATTR_SB_EGRESS_POOL_COUNT] ||
+	    !tb[DEVLINK_ATTR_SB_INGRESS_TC_COUNT] ||
+	    !tb[DEVLINK_ATTR_SB_EGRESS_TC_COUNT])
+		return MNL_CB_ERROR;
+	pr_out_sb(dl, tb);
+	return MNL_CB_OK;
+}
+
+static int cmd_sb_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, DL_OPT_SB);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "sb");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_sb_show_cb, dl);
+	pr_out_section_end(dl);
+	return err;
+}
+
+static const char *pool_type_name(uint8_t type)
+{
+	switch (type) {
+	case DEVLINK_SB_POOL_TYPE_INGRESS: return "ingress";
+	case DEVLINK_SB_POOL_TYPE_EGRESS: return "egress";
+	default: return "<unknown type>";
+	}
+}
+
+static const char *threshold_type_name(uint8_t type)
+{
+	switch (type) {
+	case DEVLINK_SB_THRESHOLD_TYPE_STATIC: return "static";
+	case DEVLINK_SB_THRESHOLD_TYPE_DYNAMIC: return "dynamic";
+	default: return "<unknown type>";
+	}
+}
+
+static void pr_out_sb_pool(struct dl *dl, struct nlattr **tb)
+{
+	pr_out_handle_start_arr(dl, tb);
+	pr_out_uint(dl, "sb",
+		    mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_INDEX]));
+	pr_out_uint(dl, "pool",
+		    mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_POOL_INDEX]));
+	pr_out_str(dl, "type",
+		   pool_type_name(mnl_attr_get_u8(tb[DEVLINK_ATTR_SB_POOL_TYPE])));
+	pr_out_uint(dl, "size",
+		    mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_POOL_SIZE]));
+	pr_out_str(dl, "thtype",
+		   threshold_type_name(mnl_attr_get_u8(tb[DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE])));
+	if (tb[DEVLINK_ATTR_SB_POOL_CELL_SIZE])
+		pr_out_uint(dl, "cell_size",
+			    mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_POOL_CELL_SIZE]));
+	pr_out_handle_end(dl);
+}
+
+static int cmd_sb_pool_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dl *dl = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_SB_INDEX] || !tb[DEVLINK_ATTR_SB_POOL_INDEX] ||
+	    !tb[DEVLINK_ATTR_SB_POOL_TYPE] || !tb[DEVLINK_ATTR_SB_POOL_SIZE] ||
+	    !tb[DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE])
+		return MNL_CB_ERROR;
+	pr_out_sb_pool(dl, tb);
+	return MNL_CB_OK;
+}
+
+static int cmd_sb_pool_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_POOL_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE | DL_OPT_SB_POOL,
+					DL_OPT_SB);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "pool");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_sb_pool_show_cb, dl);
+	pr_out_section_end(dl);
+	return err;
+}
+
+static int cmd_sb_pool_set(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_POOL_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE | DL_OPT_SB_POOL |
+				DL_OPT_SB_SIZE | DL_OPT_SB_THTYPE, DL_OPT_SB);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_sb_pool(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_sb_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+		dl_arg_inc(dl);
+		return cmd_sb_pool_show(dl);
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_sb_pool_set(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static void pr_out_sb_port_pool(struct dl *dl, struct nlattr **tb)
+{
+	pr_out_port_handle_start_arr(dl, tb, true);
+	pr_out_uint(dl, "sb",
+		    mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_INDEX]));
+	pr_out_uint(dl, "pool",
+		    mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_POOL_INDEX]));
+	pr_out_uint(dl, "threshold",
+		    mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_THRESHOLD]));
+	pr_out_port_handle_end(dl);
+}
+
+static int cmd_sb_port_pool_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dl *dl = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_PORT_INDEX] || !tb[DEVLINK_ATTR_SB_INDEX] ||
+	    !tb[DEVLINK_ATTR_SB_POOL_INDEX] || !tb[DEVLINK_ATTR_SB_THRESHOLD])
+		return MNL_CB_ERROR;
+	pr_out_sb_port_pool(dl, tb);
+	return MNL_CB_OK;
+}
+
+static int cmd_sb_port_pool_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_PORT_POOL_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl,
+					DL_OPT_HANDLEP | DL_OPT_SB_POOL,
+					DL_OPT_SB);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "port_pool");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_sb_port_pool_show_cb, dl);
+	pr_out_section_end(dl);
+	return 0;
+}
+
+static int cmd_sb_port_pool_set(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_PORT_POOL_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP | DL_OPT_SB_POOL |
+				DL_OPT_SB_TH, DL_OPT_SB);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_sb_port_pool(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_sb_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+		dl_arg_inc(dl);
+		return cmd_sb_port_pool_show(dl);
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_sb_port_pool_set(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static int cmd_sb_port(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		cmd_sb_help();
+		return 0;
+	} else if (dl_argv_match(dl, "pool")) {
+		dl_arg_inc(dl);
+		return cmd_sb_port_pool(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static void pr_out_sb_tc_bind(struct dl *dl, struct nlattr **tb)
+{
+	pr_out_port_handle_start_arr(dl, tb, true);
+	pr_out_uint(dl, "sb",
+	       mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_INDEX]));
+	pr_out_uint(dl, "tc",
+	       mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_TC_INDEX]));
+	pr_out_str(dl, "type",
+	       pool_type_name(mnl_attr_get_u8(tb[DEVLINK_ATTR_SB_POOL_TYPE])));
+	pr_out_uint(dl, "pool",
+	       mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_POOL_INDEX]));
+	pr_out_uint(dl, "threshold",
+	       mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_THRESHOLD]));
+	pr_out_port_handle_end(dl);
+}
+
+static int cmd_sb_tc_bind_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dl *dl = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_PORT_INDEX] || !tb[DEVLINK_ATTR_SB_INDEX] ||
+	    !tb[DEVLINK_ATTR_SB_TC_INDEX] || !tb[DEVLINK_ATTR_SB_POOL_TYPE] ||
+	    !tb[DEVLINK_ATTR_SB_POOL_INDEX] || !tb[DEVLINK_ATTR_SB_THRESHOLD])
+		return MNL_CB_ERROR;
+	pr_out_sb_tc_bind(dl, tb);
+	return MNL_CB_OK;
+}
+
+static int cmd_sb_tc_bind_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_TC_POOL_BIND_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP | DL_OPT_SB_TC |
+					DL_OPT_SB_TYPE, DL_OPT_SB);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "tc_bind");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_sb_tc_bind_show_cb, dl);
+	pr_out_section_end(dl);
+	return err;
+}
+
+static int cmd_sb_tc_bind_set(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_TC_POOL_BIND_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP | DL_OPT_SB_TC |
+				DL_OPT_SB_TYPE | DL_OPT_SB_POOL | DL_OPT_SB_TH,
+				DL_OPT_SB);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_sb_tc_bind(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_sb_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+		dl_arg_inc(dl);
+		return cmd_sb_tc_bind_show(dl);
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_sb_tc_bind_set(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static int cmd_sb_tc(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		cmd_sb_help();
+		return 0;
+	} else if (dl_argv_match(dl, "bind")) {
+		dl_arg_inc(dl);
+		return cmd_sb_tc_bind(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+struct occ_item {
+	struct list_head list;
+	uint32_t index;
+	uint32_t cur;
+	uint32_t max;
+	uint32_t bound_pool_index;
+};
+
+struct occ_port {
+	struct list_head list;
+	char *bus_name;
+	char *dev_name;
+	uint32_t port_index;
+	uint32_t sb_index;
+	struct list_head pool_list;
+	struct list_head ing_tc_list;
+	struct list_head eg_tc_list;
+};
+
+struct occ_show {
+	struct dl *dl;
+	int err;
+	struct list_head port_list;
+};
+
+static struct occ_item *occ_item_alloc(void)
+{
+	return calloc(1, sizeof(struct occ_item));
+}
+
+static void occ_item_free(struct occ_item *occ_item)
+{
+	free(occ_item);
+}
+
+static struct occ_port *occ_port_alloc(uint32_t port_index)
+{
+	struct occ_port *occ_port;
+
+	occ_port = calloc(1, sizeof(*occ_port));
+	if (!occ_port)
+		return NULL;
+	occ_port->port_index = port_index;
+	INIT_LIST_HEAD(&occ_port->pool_list);
+	INIT_LIST_HEAD(&occ_port->ing_tc_list);
+	INIT_LIST_HEAD(&occ_port->eg_tc_list);
+	return occ_port;
+}
+
+static void occ_port_free(struct occ_port *occ_port)
+{
+	struct occ_item *occ_item, *tmp;
+
+	list_for_each_entry_safe(occ_item, tmp, &occ_port->pool_list, list)
+		occ_item_free(occ_item);
+	list_for_each_entry_safe(occ_item, tmp, &occ_port->ing_tc_list, list)
+		occ_item_free(occ_item);
+	list_for_each_entry_safe(occ_item, tmp, &occ_port->eg_tc_list, list)
+		occ_item_free(occ_item);
+}
+
+static struct occ_show *occ_show_alloc(struct dl *dl)
+{
+	struct occ_show *occ_show;
+
+	occ_show = calloc(1, sizeof(*occ_show));
+	if (!occ_show)
+		return NULL;
+	occ_show->dl = dl;
+	INIT_LIST_HEAD(&occ_show->port_list);
+	return occ_show;
+}
+
+static void occ_show_free(struct occ_show *occ_show)
+{
+	struct occ_port *occ_port, *tmp;
+
+	list_for_each_entry_safe(occ_port, tmp, &occ_show->port_list, list)
+		occ_port_free(occ_port);
+}
+
+static struct occ_port *occ_port_get(struct occ_show *occ_show,
+				     struct nlattr **tb)
+{
+	struct occ_port *occ_port;
+	uint32_t port_index;
+
+	port_index = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]);
+
+	list_for_each_entry_reverse(occ_port, &occ_show->port_list, list) {
+		if (occ_port->port_index == port_index)
+			return occ_port;
+	}
+	occ_port = occ_port_alloc(port_index);
+	if (!occ_port)
+		return NULL;
+	list_add_tail(&occ_port->list, &occ_show->port_list);
+	return occ_port;
+}
+
+static void pr_out_occ_show_item_list(const char *label, struct list_head *list,
+				      bool bound_pool)
+{
+	struct occ_item *occ_item;
+	int i = 1;
+
+	pr_out_sp(7, "  %s:", label);
+	list_for_each_entry(occ_item, list, list) {
+		if ((i - 1) % 4 == 0 && i != 1)
+			pr_out_sp(7, " ");
+		if (bound_pool)
+			pr_out_sp(7, "%2u(%u):", occ_item->index,
+				  occ_item->bound_pool_index);
+		else
+			pr_out_sp(7, "%2u:", occ_item->index);
+		pr_out_sp(21, "%10u/%u", occ_item->cur, occ_item->max);
+		if (i++ % 4 == 0)
+			pr_out("\n");
+	}
+	if ((i - 1) % 4 != 0)
+		pr_out("\n");
+}
+
+static void pr_out_json_occ_show_item_list(struct dl *dl, const char *label,
+					   struct list_head *list,
+					   bool bound_pool)
+{
+	struct occ_item *occ_item;
+	char buf[32];
+
+	jsonw_name(dl->jw, label);
+	jsonw_start_object(dl->jw);
+	list_for_each_entry(occ_item, list, list) {
+		sprintf(buf, "%u", occ_item->index);
+		jsonw_name(dl->jw, buf);
+		jsonw_start_object(dl->jw);
+		if (bound_pool)
+			jsonw_uint_field(dl->jw, "bound_pool",
+					 occ_item->bound_pool_index);
+		jsonw_uint_field(dl->jw, "current", occ_item->cur);
+		jsonw_uint_field(dl->jw, "max", occ_item->max);
+		jsonw_end_object(dl->jw);
+	}
+	jsonw_end_object(dl->jw);
+}
+
+static void pr_out_occ_show_port(struct dl *dl, struct occ_port *occ_port)
+{
+	if (dl->json_output) {
+		pr_out_json_occ_show_item_list(dl, "pool",
+					       &occ_port->pool_list, false);
+		pr_out_json_occ_show_item_list(dl, "itc",
+					       &occ_port->ing_tc_list, true);
+		pr_out_json_occ_show_item_list(dl, "etc",
+					       &occ_port->eg_tc_list, true);
+	} else {
+		pr_out("\n");
+		pr_out_occ_show_item_list("pool", &occ_port->pool_list, false);
+		pr_out_occ_show_item_list("itc", &occ_port->ing_tc_list, true);
+		pr_out_occ_show_item_list("etc", &occ_port->eg_tc_list, true);
+	}
+}
+
+static void pr_out_occ_show(struct occ_show *occ_show)
+{
+	struct dl *dl = occ_show->dl;
+	struct dl_opts *opts = &dl->opts;
+	struct occ_port *occ_port;
+
+	list_for_each_entry(occ_port, &occ_show->port_list, list) {
+		__pr_out_port_handle_start(dl, opts->bus_name, opts->dev_name,
+					   occ_port->port_index, true, false);
+		pr_out_occ_show_port(dl, occ_port);
+		pr_out_port_handle_end(dl);
+	}
+}
+
+static void cmd_sb_occ_port_pool_process(struct occ_show *occ_show,
+					 struct nlattr **tb)
+{
+	struct occ_port *occ_port;
+	struct occ_item *occ_item;
+
+	if (occ_show->err || !dl_dump_filter(occ_show->dl, tb))
+		return;
+
+	occ_port = occ_port_get(occ_show, tb);
+	if (!occ_port) {
+		occ_show->err = -ENOMEM;
+		return;
+	}
+
+	occ_item = occ_item_alloc();
+	if (!occ_item) {
+		occ_show->err = -ENOMEM;
+		return;
+	}
+	occ_item->index = mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_POOL_INDEX]);
+	occ_item->cur = mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_OCC_CUR]);
+	occ_item->max = mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_OCC_MAX]);
+	list_add_tail(&occ_item->list, &occ_port->pool_list);
+}
+
+static int cmd_sb_occ_port_pool_process_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct occ_show *occ_show = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_PORT_INDEX] || !tb[DEVLINK_ATTR_SB_INDEX] ||
+	    !tb[DEVLINK_ATTR_SB_POOL_INDEX] ||
+	    !tb[DEVLINK_ATTR_SB_OCC_CUR] || !tb[DEVLINK_ATTR_SB_OCC_MAX])
+		return MNL_CB_ERROR;
+	cmd_sb_occ_port_pool_process(occ_show, tb);
+	return MNL_CB_OK;
+}
+
+static void cmd_sb_occ_tc_pool_process(struct occ_show *occ_show,
+				       struct nlattr **tb)
+{
+	struct occ_port *occ_port;
+	struct occ_item *occ_item;
+	uint8_t pool_type;
+
+	if (occ_show->err || !dl_dump_filter(occ_show->dl, tb))
+		return;
+
+	occ_port = occ_port_get(occ_show, tb);
+	if (!occ_port) {
+		occ_show->err = -ENOMEM;
+		return;
+	}
+
+	occ_item = occ_item_alloc();
+	if (!occ_item) {
+		occ_show->err = -ENOMEM;
+		return;
+	}
+	occ_item->index = mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_TC_INDEX]);
+	occ_item->cur = mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_OCC_CUR]);
+	occ_item->max = mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_OCC_MAX]);
+	occ_item->bound_pool_index =
+			mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_POOL_INDEX]);
+	pool_type = mnl_attr_get_u8(tb[DEVLINK_ATTR_SB_POOL_TYPE]);
+	if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS)
+		list_add_tail(&occ_item->list, &occ_port->ing_tc_list);
+	else if (pool_type == DEVLINK_SB_POOL_TYPE_EGRESS)
+		list_add_tail(&occ_item->list, &occ_port->eg_tc_list);
+	else
+		occ_item_free(occ_item);
+}
+
+static int cmd_sb_occ_tc_pool_process_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct occ_show *occ_show = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_PORT_INDEX] || !tb[DEVLINK_ATTR_SB_INDEX] ||
+	    !tb[DEVLINK_ATTR_SB_TC_INDEX] || !tb[DEVLINK_ATTR_SB_POOL_TYPE] ||
+	    !tb[DEVLINK_ATTR_SB_POOL_INDEX] ||
+	    !tb[DEVLINK_ATTR_SB_OCC_CUR] || !tb[DEVLINK_ATTR_SB_OCC_MAX])
+		return MNL_CB_ERROR;
+	cmd_sb_occ_tc_pool_process(occ_show, tb);
+	return MNL_CB_OK;
+}
+
+static int cmd_sb_occ_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	struct occ_show *occ_show;
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
+	int err;
+
+	err = dl_argv_parse(dl, DL_OPT_HANDLE | DL_OPT_HANDLEP, DL_OPT_SB);
+	if (err)
+		return err;
+
+	occ_show = occ_show_alloc(dl);
+	if (!occ_show)
+		return -ENOMEM;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_PORT_POOL_GET, flags);
+
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh,
+				  cmd_sb_occ_port_pool_process_cb, occ_show);
+	if (err)
+		goto out;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_TC_POOL_BIND_GET, flags);
+
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh,
+				  cmd_sb_occ_tc_pool_process_cb, occ_show);
+	if (err)
+		goto out;
+
+	pr_out_section_start(dl, "occupancy");
+	pr_out_occ_show(occ_show);
+	pr_out_section_end(dl);
+
+out:
+	occ_show_free(occ_show);
+	return err;
+}
+
+static int cmd_sb_occ_snapshot(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_OCC_SNAPSHOT,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, DL_OPT_SB);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_sb_occ_clearmax(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_SB_OCC_MAX_CLEAR,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, DL_OPT_SB);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_sb_occ(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		cmd_sb_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list")) {
+		dl_arg_inc(dl);
+		return cmd_sb_occ_show(dl);
+	} else if (dl_argv_match(dl, "snapshot")) {
+		dl_arg_inc(dl);
+		return cmd_sb_occ_snapshot(dl);
+	} else if (dl_argv_match(dl, "clearmax")) {
+		dl_arg_inc(dl);
+		return cmd_sb_occ_clearmax(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static int cmd_sb(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_sb_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+		dl_arg_inc(dl);
+		return cmd_sb_show(dl);
+	} else if (dl_argv_match(dl, "pool")) {
+		dl_arg_inc(dl);
+		return cmd_sb_pool(dl);
+	} else if (dl_argv_match(dl, "port")) {
+		dl_arg_inc(dl);
+		return cmd_sb_port(dl);
+	} else if (dl_argv_match(dl, "tc")) {
+		dl_arg_inc(dl);
+		return cmd_sb_tc(dl);
+	} else if (dl_argv_match(dl, "occupancy")) {
+		dl_arg_inc(dl);
+		return cmd_sb_occ(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static const char *cmd_name(uint8_t cmd)
+{
+	switch (cmd) {
+	case DEVLINK_CMD_UNSPEC: return "unspec";
+	case DEVLINK_CMD_GET: return "get";
+	case DEVLINK_CMD_SET: return "set";
+	case DEVLINK_CMD_NEW: return "new";
+	case DEVLINK_CMD_DEL: return "del";
+	case DEVLINK_CMD_PORT_GET: return "get";
+	case DEVLINK_CMD_PORT_SET: return "set";
+	case DEVLINK_CMD_PORT_NEW: return "new";
+	case DEVLINK_CMD_PORT_DEL: return "del";
+	case DEVLINK_CMD_PARAM_GET: return "get";
+	case DEVLINK_CMD_PARAM_SET: return "set";
+	case DEVLINK_CMD_PARAM_NEW: return "new";
+	case DEVLINK_CMD_PARAM_DEL: return "del";
+	case DEVLINK_CMD_REGION_GET: return "get";
+	case DEVLINK_CMD_REGION_SET: return "set";
+	case DEVLINK_CMD_REGION_NEW: return "new";
+	case DEVLINK_CMD_REGION_DEL: return "del";
+	case DEVLINK_CMD_FLASH_UPDATE: return "begin";
+	case DEVLINK_CMD_FLASH_UPDATE_END: return "end";
+	case DEVLINK_CMD_FLASH_UPDATE_STATUS: return "status";
+	case DEVLINK_CMD_TRAP_GET: return "get";
+	case DEVLINK_CMD_TRAP_SET: return "set";
+	case DEVLINK_CMD_TRAP_NEW: return "new";
+	case DEVLINK_CMD_TRAP_DEL: return "del";
+	case DEVLINK_CMD_TRAP_GROUP_GET: return "get";
+	case DEVLINK_CMD_TRAP_GROUP_SET: return "set";
+	case DEVLINK_CMD_TRAP_GROUP_NEW: return "new";
+	case DEVLINK_CMD_TRAP_GROUP_DEL: return "del";
+	default: return "<unknown cmd>";
+	}
+}
+
+static const char *cmd_obj(uint8_t cmd)
+{
+	switch (cmd) {
+	case DEVLINK_CMD_UNSPEC: return "unspec";
+	case DEVLINK_CMD_GET:
+	case DEVLINK_CMD_SET:
+	case DEVLINK_CMD_NEW:
+	case DEVLINK_CMD_DEL:
+		return "dev";
+	case DEVLINK_CMD_PORT_GET:
+	case DEVLINK_CMD_PORT_SET:
+	case DEVLINK_CMD_PORT_NEW:
+	case DEVLINK_CMD_PORT_DEL:
+		return "port";
+	case DEVLINK_CMD_PARAM_GET:
+	case DEVLINK_CMD_PARAM_SET:
+	case DEVLINK_CMD_PARAM_NEW:
+	case DEVLINK_CMD_PARAM_DEL:
+		return "param";
+	case DEVLINK_CMD_REGION_GET:
+	case DEVLINK_CMD_REGION_SET:
+	case DEVLINK_CMD_REGION_NEW:
+	case DEVLINK_CMD_REGION_DEL:
+		return "region";
+	case DEVLINK_CMD_FLASH_UPDATE:
+	case DEVLINK_CMD_FLASH_UPDATE_END:
+	case DEVLINK_CMD_FLASH_UPDATE_STATUS:
+		return "flash";
+	case DEVLINK_CMD_TRAP_GET:
+	case DEVLINK_CMD_TRAP_SET:
+	case DEVLINK_CMD_TRAP_NEW:
+	case DEVLINK_CMD_TRAP_DEL:
+		return "trap";
+	case DEVLINK_CMD_TRAP_GROUP_GET:
+	case DEVLINK_CMD_TRAP_GROUP_SET:
+	case DEVLINK_CMD_TRAP_GROUP_NEW:
+	case DEVLINK_CMD_TRAP_GROUP_DEL:
+		return "trap-group";
+	default: return "<unknown obj>";
+	}
+}
+
+static void pr_out_mon_header(uint8_t cmd)
+{
+	pr_out("[%s,%s] ", cmd_obj(cmd), cmd_name(cmd));
+}
+
+static bool cmd_filter_check(struct dl *dl, uint8_t cmd)
+{
+	const char *obj = cmd_obj(cmd);
+	unsigned int index = 0;
+	const char *cur_obj;
+
+	if (dl_no_arg(dl))
+		return true;
+	while ((cur_obj = dl_argv_index(dl, index++))) {
+		if (strcmp(cur_obj, obj) == 0 || strcmp(cur_obj, "all") == 0)
+			return true;
+	}
+	return false;
+}
+
+static void pr_out_flash_update(struct dl *dl, struct nlattr **tb)
+{
+	__pr_out_handle_start(dl, tb, true, false);
+
+	if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG])
+		pr_out_str(dl, "msg",
+			   mnl_attr_get_str(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG]));
+
+	if (tb[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT])
+		pr_out_str(dl, "component",
+			   mnl_attr_get_str(tb[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT]));
+
+	if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE])
+		pr_out_u64(dl, "done",
+			   mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE]));
+
+	if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL])
+		pr_out_u64(dl, "total",
+			   mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL]));
+
+	pr_out_handle_end(dl);
+}
+
+static void pr_out_region(struct dl *dl, struct nlattr **tb);
+static void pr_out_trap(struct dl *dl, struct nlattr **tb, bool array);
+static void pr_out_trap_group(struct dl *dl, struct nlattr **tb, bool array);
+
+static int cmd_mon_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dl *dl = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	uint8_t cmd = genl->cmd;
+
+	if (!cmd_filter_check(dl, cmd))
+		return MNL_CB_OK;
+
+	switch (cmd) {
+	case DEVLINK_CMD_GET: /* fall through */
+	case DEVLINK_CMD_SET: /* fall through */
+	case DEVLINK_CMD_NEW: /* fall through */
+	case DEVLINK_CMD_DEL:
+		mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+		if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+			return MNL_CB_ERROR;
+		pr_out_mon_header(genl->cmd);
+		pr_out_handle(dl, tb);
+		break;
+	case DEVLINK_CMD_PORT_GET: /* fall through */
+	case DEVLINK_CMD_PORT_SET: /* fall through */
+	case DEVLINK_CMD_PORT_NEW: /* fall through */
+	case DEVLINK_CMD_PORT_DEL:
+		mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+		if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+		    !tb[DEVLINK_ATTR_PORT_INDEX])
+			return MNL_CB_ERROR;
+		pr_out_mon_header(genl->cmd);
+		pr_out_port(dl, tb);
+		break;
+	case DEVLINK_CMD_PARAM_GET: /* fall through */
+	case DEVLINK_CMD_PARAM_SET: /* fall through */
+	case DEVLINK_CMD_PARAM_NEW: /* fall through */
+	case DEVLINK_CMD_PARAM_DEL:
+		mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+		if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+		    !tb[DEVLINK_ATTR_PARAM])
+			return MNL_CB_ERROR;
+		pr_out_mon_header(genl->cmd);
+		pr_out_param(dl, tb, false);
+		break;
+	case DEVLINK_CMD_REGION_GET: /* fall through */
+	case DEVLINK_CMD_REGION_SET: /* fall through */
+	case DEVLINK_CMD_REGION_NEW: /* fall through */
+	case DEVLINK_CMD_REGION_DEL:
+		mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+		if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+		    !tb[DEVLINK_ATTR_REGION_NAME])
+			return MNL_CB_ERROR;
+		pr_out_mon_header(genl->cmd);
+		pr_out_region(dl, tb);
+		break;
+	case DEVLINK_CMD_FLASH_UPDATE: /* fall through */
+	case DEVLINK_CMD_FLASH_UPDATE_END: /* fall through */
+	case DEVLINK_CMD_FLASH_UPDATE_STATUS:
+		mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+		if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+			return MNL_CB_ERROR;
+		pr_out_mon_header(genl->cmd);
+		pr_out_flash_update(dl, tb);
+		break;
+	case DEVLINK_CMD_TRAP_GET: /* fall through */
+	case DEVLINK_CMD_TRAP_SET: /* fall through */
+	case DEVLINK_CMD_TRAP_NEW: /* fall through */
+	case DEVLINK_CMD_TRAP_DEL:
+		mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+		if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+		    !tb[DEVLINK_ATTR_TRAP_NAME] ||
+		    !tb[DEVLINK_ATTR_TRAP_TYPE] ||
+		    !tb[DEVLINK_ATTR_TRAP_ACTION] ||
+		    !tb[DEVLINK_ATTR_TRAP_GROUP_NAME] ||
+		    !tb[DEVLINK_ATTR_TRAP_METADATA] ||
+		    !tb[DEVLINK_ATTR_STATS])
+			return MNL_CB_ERROR;
+		pr_out_mon_header(genl->cmd);
+		pr_out_trap(dl, tb, false);
+		break;
+	case DEVLINK_CMD_TRAP_GROUP_GET: /* fall through */
+	case DEVLINK_CMD_TRAP_GROUP_SET: /* fall through */
+	case DEVLINK_CMD_TRAP_GROUP_NEW: /* fall through */
+	case DEVLINK_CMD_TRAP_GROUP_DEL:
+		mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+		if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+		    !tb[DEVLINK_ATTR_TRAP_GROUP_NAME] ||
+		    !tb[DEVLINK_ATTR_STATS])
+			return MNL_CB_ERROR;
+		pr_out_mon_header(genl->cmd);
+		pr_out_trap_group(dl, tb, false);
+		break;
+	}
+	return MNL_CB_OK;
+}
+
+static int cmd_mon_show(struct dl *dl)
+{
+	int err;
+	unsigned int index = 0;
+	const char *cur_obj;
+
+	while ((cur_obj = dl_argv_index(dl, index++))) {
+		if (strcmp(cur_obj, "all") != 0 &&
+		    strcmp(cur_obj, "dev") != 0 &&
+		    strcmp(cur_obj, "port") != 0 &&
+		    strcmp(cur_obj, "trap") != 0 &&
+		    strcmp(cur_obj, "trap-group") != 0) {
+			pr_err("Unknown object \"%s\"\n", cur_obj);
+			return -EINVAL;
+		}
+	}
+	err = _mnlg_socket_group_add(dl->nlg, DEVLINK_GENL_MCGRP_CONFIG_NAME);
+	if (err)
+		return err;
+	err = _mnlg_socket_recv_run(dl->nlg, cmd_mon_show_cb, dl);
+	if (err)
+		return err;
+	return 0;
+}
+
+static void cmd_mon_help(void)
+{
+	pr_err("Usage: devlink monitor [ all | OBJECT-LIST ]\n"
+	       "where  OBJECT-LIST := { dev | port | trap | trap-group }\n");
+}
+
+static int cmd_mon(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_mon_help();
+		return 0;
+	}
+	return cmd_mon_show(dl);
+}
+
+struct dpipe_field {
+	char *name;
+	unsigned int id;
+	unsigned int bitwidth;
+	enum devlink_dpipe_field_mapping_type mapping_type;
+};
+
+struct dpipe_header {
+	struct list_head list;
+	char *name;
+	unsigned int id;
+	struct dpipe_field *fields;
+	unsigned int fields_count;
+};
+
+struct dpipe_table {
+	struct list_head list;
+	char *name;
+	unsigned int resource_id;
+	bool resource_valid;
+};
+
+struct dpipe_tables {
+	struct list_head table_list;
+};
+
+struct resource {
+	char *name;
+	uint64_t size;
+	uint64_t size_new;
+	uint64_t size_min;
+	uint64_t size_max;
+	uint64_t size_gran;
+	enum devlink_resource_unit unit;
+	bool size_valid;
+	uint64_t size_occ;
+	bool occ_valid;
+	uint64_t id;
+	struct list_head list;
+	struct list_head resource_list;
+	struct resource *parent;
+};
+
+struct resources {
+	struct list_head resource_list;
+};
+
+struct resource_ctx {
+	struct dl *dl;
+	int err;
+	struct resources *resources;
+	struct dpipe_tables *tables;
+	bool print_resources;
+	bool pending_change;
+};
+
+static struct resource *resource_alloc(void)
+{
+	struct resource *resource;
+
+	resource = calloc(1, sizeof(struct resource));
+	if (!resource)
+		return NULL;
+	INIT_LIST_HEAD(&resource->resource_list);
+	return resource;
+}
+
+static void resource_free(struct resource *resource)
+{
+	struct resource *child_resource, *tmp;
+
+	list_for_each_entry_safe(child_resource, tmp, &resource->resource_list,
+				 list) {
+		free(child_resource->name);
+		resource_free(child_resource);
+	}
+	free(resource);
+}
+
+static struct resources *resources_alloc(void)
+{
+	struct resources *resources;
+
+	resources = calloc(1, sizeof(struct resources));
+	if (!resources)
+		return NULL;
+	INIT_LIST_HEAD(&resources->resource_list);
+	return resources;
+}
+
+static void resources_free(struct resources *resources)
+{
+	struct resource *resource, *tmp;
+
+	list_for_each_entry_safe(resource, tmp, &resources->resource_list, list)
+		resource_free(resource);
+}
+
+static int resource_ctx_init(struct resource_ctx *ctx, struct dl *dl)
+{
+	ctx->resources = resources_alloc();
+	if (!ctx->resources)
+		return -ENOMEM;
+	ctx->dl = dl;
+	return 0;
+}
+
+static void resource_ctx_fini(struct resource_ctx *ctx)
+{
+	resources_free(ctx->resources);
+}
+
+struct dpipe_ctx {
+	struct dl *dl;
+	int err;
+	struct list_head global_headers;
+	struct list_head local_headers;
+	struct dpipe_tables *tables;
+	struct resources *resources;
+	bool print_headers;
+	bool print_tables;
+};
+
+static struct dpipe_header *dpipe_header_alloc(unsigned int fields_count)
+{
+	struct dpipe_header *header;
+
+	header = calloc(1, sizeof(struct dpipe_header));
+	if (!header)
+		return NULL;
+	header->fields = calloc(fields_count, sizeof(struct dpipe_field));
+	if (!header->fields)
+		goto err_fields_alloc;
+	header->fields_count = fields_count;
+	return header;
+
+err_fields_alloc:
+	free(header);
+	return NULL;
+}
+
+static void dpipe_header_free(struct dpipe_header *header)
+{
+	free(header->fields);
+	free(header);
+}
+
+static void dpipe_header_clear(struct dpipe_header *header)
+{
+	struct dpipe_field *field;
+	int i;
+
+	for (i = 0; i < header->fields_count; i++) {
+		field = &header->fields[i];
+		free(field->name);
+	}
+	free(header->name);
+}
+
+static void dpipe_header_add(struct dpipe_ctx *ctx,
+			     struct dpipe_header *header, bool global)
+{
+	if (global)
+		list_add(&header->list, &ctx->global_headers);
+	else
+		list_add(&header->list, &ctx->local_headers);
+}
+
+static void dpipe_header_del(struct dpipe_header *header)
+{
+	list_del(&header->list);
+}
+
+static struct dpipe_table *dpipe_table_alloc(void)
+{
+	return calloc(1, sizeof(struct dpipe_table));
+}
+
+static void dpipe_table_free(struct dpipe_table *table)
+{
+	free(table);
+}
+
+static struct dpipe_tables *dpipe_tables_alloc(void)
+{
+	struct dpipe_tables *tables;
+
+	tables = calloc(1, sizeof(struct dpipe_tables));
+	if (!tables)
+		return NULL;
+	INIT_LIST_HEAD(&tables->table_list);
+	return tables;
+}
+
+static void dpipe_tables_free(struct dpipe_tables *tables)
+{
+	struct dpipe_table *table, *tmp;
+
+	list_for_each_entry_safe(table, tmp, &tables->table_list, list)
+		dpipe_table_free(table);
+	free(tables);
+}
+
+static int dpipe_ctx_init(struct dpipe_ctx *ctx, struct dl *dl)
+{
+	ctx->tables = dpipe_tables_alloc();
+	if (!ctx->tables)
+		return -ENOMEM;
+
+	ctx->dl = dl;
+	INIT_LIST_HEAD(&ctx->global_headers);
+	INIT_LIST_HEAD(&ctx->local_headers);
+	return 0;
+}
+
+static void dpipe_ctx_fini(struct dpipe_ctx *ctx)
+{
+	struct dpipe_header *header, *tmp;
+
+	list_for_each_entry_safe(header, tmp, &ctx->global_headers,
+				 list) {
+		dpipe_header_del(header);
+		dpipe_header_clear(header);
+		dpipe_header_free(header);
+	}
+	list_for_each_entry_safe(header, tmp, &ctx->local_headers,
+				 list) {
+		dpipe_header_del(header);
+		dpipe_header_clear(header);
+		dpipe_header_free(header);
+	}
+	dpipe_tables_free(ctx->tables);
+}
+
+static const char *dpipe_header_id2s(struct dpipe_ctx *ctx,
+				     uint32_t header_id, bool global)
+{
+	struct list_head *header_list;
+	struct dpipe_header *header;
+
+	if (global)
+		header_list = &ctx->global_headers;
+	else
+		header_list = &ctx->local_headers;
+	list_for_each_entry(header, header_list, list) {
+		if (header->id != header_id)
+			continue;
+		return header->name;
+	}
+	return NULL;
+}
+
+static const char *dpipe_field_id2s(struct dpipe_ctx *ctx,
+				    uint32_t header_id,
+				    uint32_t field_id, bool global)
+{
+	struct list_head *header_list;
+	struct dpipe_header *header;
+
+	if (global)
+		header_list = &ctx->global_headers;
+	else
+		header_list = &ctx->local_headers;
+	list_for_each_entry(header, header_list, list) {
+		if (header->id != header_id)
+			continue;
+		return header->fields[field_id].name;
+	}
+	return NULL;
+}
+
+static const char *
+dpipe_field_mapping_e2s(enum devlink_dpipe_field_mapping_type mapping_type)
+{
+	switch (mapping_type) {
+	case DEVLINK_DPIPE_FIELD_MAPPING_TYPE_NONE:
+		return NULL;
+	case DEVLINK_DPIPE_FIELD_MAPPING_TYPE_IFINDEX:
+		return "ifindex";
+	default:
+		return "<unknown>";
+	}
+}
+
+static const char *
+dpipe_mapping_get(struct dpipe_ctx *ctx, uint32_t header_id,
+		  uint32_t field_id, bool global)
+{
+	enum devlink_dpipe_field_mapping_type mapping_type;
+	struct list_head *header_list;
+	struct dpipe_header *header;
+
+	if (global)
+		header_list = &ctx->global_headers;
+	else
+		header_list = &ctx->local_headers;
+	list_for_each_entry(header, header_list, list) {
+		if (header->id != header_id)
+			continue;
+		mapping_type = header->fields[field_id].mapping_type;
+		return dpipe_field_mapping_e2s(mapping_type);
+	}
+	return NULL;
+}
+
+static void pr_out_dpipe_fields(struct dpipe_ctx *ctx,
+				struct dpipe_field *fields,
+				unsigned int field_count)
+{
+	struct dpipe_field *field;
+	int i;
+
+	for (i = 0; i < field_count; i++) {
+		field = &fields[i];
+		pr_out_entry_start(ctx->dl);
+		pr_out_str(ctx->dl, "name", field->name);
+		if (ctx->dl->verbose)
+			pr_out_uint(ctx->dl, "id", field->id);
+		pr_out_uint(ctx->dl, "bitwidth", field->bitwidth);
+		if (field->mapping_type)
+			pr_out_str(ctx->dl, "mapping_type",
+				   dpipe_field_mapping_e2s(field->mapping_type));
+		pr_out_entry_end(ctx->dl);
+	}
+}
+
+static void
+pr_out_dpipe_header(struct dpipe_ctx *ctx, struct nlattr **tb,
+		    struct dpipe_header *header, bool global)
+{
+	pr_out_handle_start_arr(ctx->dl, tb);
+	pr_out_str(ctx->dl, "name", header->name);
+	if (ctx->dl->verbose) {
+		pr_out_uint(ctx->dl, "id", header->id);
+		pr_out_str(ctx->dl, "global",
+			   global ? "true" : "false");
+	}
+	pr_out_array_start(ctx->dl, "field");
+	pr_out_dpipe_fields(ctx, header->fields,
+			    header->fields_count);
+	pr_out_array_end(ctx->dl);
+	pr_out_handle_end(ctx->dl);
+}
+
+static void pr_out_dpipe_headers(struct dpipe_ctx *ctx,
+				 struct nlattr **tb)
+{
+	struct dpipe_header *header;
+
+	list_for_each_entry(header, &ctx->local_headers, list)
+		pr_out_dpipe_header(ctx, tb, header, false);
+
+	list_for_each_entry(header, &ctx->global_headers, list)
+		pr_out_dpipe_header(ctx, tb, header, true);
+}
+
+static int dpipe_header_field_get(struct nlattr *nl, struct dpipe_field *field)
+{
+	struct nlattr *nla_field[DEVLINK_ATTR_MAX + 1] = {};
+	const char *name;
+	int err;
+
+	err = mnl_attr_parse_nested(nl, attr_cb, nla_field);
+	if (err != MNL_CB_OK)
+		return -EINVAL;
+	if (!nla_field[DEVLINK_ATTR_DPIPE_FIELD_ID] ||
+	    !nla_field[DEVLINK_ATTR_DPIPE_FIELD_NAME] ||
+	    !nla_field[DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH] ||
+	    !nla_field[DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE])
+		return -EINVAL;
+
+	name = mnl_attr_get_str(nla_field[DEVLINK_ATTR_DPIPE_FIELD_NAME]);
+	field->id = mnl_attr_get_u32(nla_field[DEVLINK_ATTR_DPIPE_FIELD_ID]);
+	field->bitwidth = mnl_attr_get_u32(nla_field[DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH]);
+	field->name = strdup(name);
+	if (!field->name)
+		return -ENOMEM;
+	field->mapping_type = mnl_attr_get_u32(nla_field[DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE]);
+	return 0;
+}
+
+static int dpipe_header_fields_get(struct nlattr *nla_fields,
+				   struct dpipe_field *fields)
+{
+	struct nlattr *nla_field;
+	int count = 0;
+	int err;
+
+	mnl_attr_for_each_nested(nla_field, nla_fields) {
+		err = dpipe_header_field_get(nla_field, &fields[count]);
+		if (err)
+			return err;
+		count++;
+	}
+	return 0;
+}
+
+static unsigned int dpipe_header_field_count_get(struct nlattr *nla_fields)
+{
+	struct nlattr *nla_field;
+	unsigned int count = 0;
+
+	mnl_attr_for_each_nested(nla_field, nla_fields)
+		count++;
+	return count;
+}
+
+static int dpipe_header_get(struct dpipe_ctx *ctx, struct nlattr *nl)
+{
+	struct nlattr *nla_header[DEVLINK_ATTR_MAX + 1] = {};
+	struct dpipe_header *header;
+	unsigned int fields_count;
+	const char *header_name;
+	bool global;
+	int err;
+
+	err = mnl_attr_parse_nested(nl, attr_cb, nla_header);
+	if (err != MNL_CB_OK)
+		return -EINVAL;
+
+	if (!nla_header[DEVLINK_ATTR_DPIPE_HEADER_NAME] ||
+	    !nla_header[DEVLINK_ATTR_DPIPE_HEADER_ID] ||
+	    !nla_header[DEVLINK_ATTR_DPIPE_HEADER_FIELDS])
+		return -EINVAL;
+
+	fields_count = dpipe_header_field_count_get(nla_header[DEVLINK_ATTR_DPIPE_HEADER_FIELDS]);
+	header = dpipe_header_alloc(fields_count);
+	if (!header)
+		return -ENOMEM;
+
+	header_name = mnl_attr_get_str(nla_header[DEVLINK_ATTR_DPIPE_HEADER_NAME]);
+	header->name = strdup(header_name);
+	header->id = mnl_attr_get_u32(nla_header[DEVLINK_ATTR_DPIPE_HEADER_ID]);
+	header->fields_count = fields_count;
+	global = !!mnl_attr_get_u8(nla_header[DEVLINK_ATTR_DPIPE_HEADER_GLOBAL]);
+
+	err = dpipe_header_fields_get(nla_header[DEVLINK_ATTR_DPIPE_HEADER_FIELDS],
+				      header->fields);
+	if (err)
+		goto err_field_get;
+	dpipe_header_add(ctx, header, global);
+	return 0;
+
+err_field_get:
+	dpipe_header_free(header);
+	return err;
+}
+
+static int dpipe_headers_get(struct dpipe_ctx *ctx, struct nlattr **tb)
+{
+	struct nlattr *nla_headers = tb[DEVLINK_ATTR_DPIPE_HEADERS];
+	struct nlattr *nla_header;
+	int err;
+
+	mnl_attr_for_each_nested(nla_header, nla_headers) {
+		err = dpipe_header_get(ctx, nla_header);
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+static int cmd_dpipe_header_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dpipe_ctx *ctx = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	int err;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_DPIPE_HEADERS])
+		return MNL_CB_ERROR;
+	err = dpipe_headers_get(ctx, tb);
+	if (err) {
+		ctx->err = err;
+		return MNL_CB_ERROR;
+	}
+
+	if (ctx->print_headers)
+		pr_out_dpipe_headers(ctx, tb);
+	return MNL_CB_OK;
+}
+
+static int cmd_dpipe_headers_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	struct dpipe_ctx ctx = {};
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_HEADERS_GET, flags);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, 0);
+	if (err)
+		return err;
+
+	err = dpipe_ctx_init(&ctx, dl);
+	if (err)
+		return err;
+
+	ctx.print_headers = true;
+
+	pr_out_section_start(dl, "header");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_header_cb, &ctx);
+	if (err)
+		pr_err("error get headers %s\n", strerror(ctx.err));
+	pr_out_section_end(dl);
+
+	dpipe_ctx_fini(&ctx);
+	return err;
+}
+
+static void cmd_dpipe_header_help(void)
+{
+	pr_err("Usage: devlink dpipe headers show DEV\n");
+}
+
+static int cmd_dpipe_header(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		cmd_dpipe_header_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show")) {
+		dl_arg_inc(dl);
+		return cmd_dpipe_headers_show(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static const char
+*dpipe_action_type_e2s(enum devlink_dpipe_action_type action_type)
+{
+	switch (action_type) {
+	case DEVLINK_DPIPE_ACTION_TYPE_FIELD_MODIFY:
+		return "field_modify";
+	default:
+		return "<unknown>";
+	}
+}
+
+struct dpipe_op_info {
+	uint32_t header_id;
+	uint32_t field_id;
+	bool header_global;
+};
+
+struct dpipe_action {
+	struct dpipe_op_info info;
+	uint32_t type;
+};
+
+static void pr_out_dpipe_action(struct dpipe_action *action,
+				struct dpipe_ctx *ctx)
+{
+	struct dpipe_op_info *op_info = &action->info;
+	const char *mapping;
+
+	pr_out_str(ctx->dl, "type",
+		   dpipe_action_type_e2s(action->type));
+	pr_out_str(ctx->dl, "header",
+		   dpipe_header_id2s(ctx, op_info->header_id,
+				     op_info->header_global));
+	pr_out_str(ctx->dl, "field",
+		   dpipe_field_id2s(ctx, op_info->header_id,
+				    op_info->field_id,
+				    op_info->header_global));
+	mapping = dpipe_mapping_get(ctx, op_info->header_id,
+				    op_info->field_id,
+				    op_info->header_global);
+	if (mapping)
+		pr_out_str(ctx->dl, "mapping", mapping);
+}
+
+static int dpipe_action_parse(struct dpipe_action *action, struct nlattr *nl)
+{
+	struct nlattr *nla_action[DEVLINK_ATTR_MAX + 1] = {};
+	int err;
+
+	err = mnl_attr_parse_nested(nl, attr_cb, nla_action);
+	if (err != MNL_CB_OK)
+		return -EINVAL;
+
+	if (!nla_action[DEVLINK_ATTR_DPIPE_ACTION_TYPE] ||
+	    !nla_action[DEVLINK_ATTR_DPIPE_HEADER_INDEX] ||
+	    !nla_action[DEVLINK_ATTR_DPIPE_HEADER_ID] ||
+	    !nla_action[DEVLINK_ATTR_DPIPE_FIELD_ID]) {
+		return -EINVAL;
+	}
+
+	action->type = mnl_attr_get_u32(nla_action[DEVLINK_ATTR_DPIPE_ACTION_TYPE]);
+	action->info.header_id = mnl_attr_get_u32(nla_action[DEVLINK_ATTR_DPIPE_HEADER_ID]);
+	action->info.field_id = mnl_attr_get_u32(nla_action[DEVLINK_ATTR_DPIPE_FIELD_ID]);
+	action->info.header_global = !!mnl_attr_get_u8(nla_action[DEVLINK_ATTR_DPIPE_HEADER_GLOBAL]);
+
+	return 0;
+}
+
+static int dpipe_table_actions_show(struct dpipe_ctx *ctx,
+				    struct nlattr *nla_actions)
+{
+	struct nlattr *nla_action;
+	struct dpipe_action action;
+
+	mnl_attr_for_each_nested(nla_action, nla_actions) {
+		pr_out_entry_start(ctx->dl);
+		if (dpipe_action_parse(&action, nla_action))
+			goto err_action_parse;
+		pr_out_dpipe_action(&action, ctx);
+		pr_out_entry_end(ctx->dl);
+	}
+	return 0;
+
+err_action_parse:
+	pr_out_entry_end(ctx->dl);
+	return -EINVAL;
+}
+
+static const char *
+dpipe_match_type_e2s(enum devlink_dpipe_match_type match_type)
+{
+	switch (match_type) {
+	case DEVLINK_DPIPE_MATCH_TYPE_FIELD_EXACT:
+		return "field_exact";
+	default:
+		return "<unknown>";
+	}
+}
+
+struct dpipe_match {
+	struct dpipe_op_info info;
+	uint32_t type;
+};
+
+static void pr_out_dpipe_match(struct dpipe_match *match,
+			       struct dpipe_ctx *ctx)
+{
+	struct dpipe_op_info *op_info = &match->info;
+	const char *mapping;
+
+	pr_out_str(ctx->dl, "type",
+		   dpipe_match_type_e2s(match->type));
+	pr_out_str(ctx->dl, "header",
+		   dpipe_header_id2s(ctx, op_info->header_id,
+				     op_info->header_global));
+	pr_out_str(ctx->dl, "field",
+		   dpipe_field_id2s(ctx, op_info->header_id,
+				    op_info->field_id,
+				    op_info->header_global));
+	mapping = dpipe_mapping_get(ctx, op_info->header_id,
+				    op_info->field_id,
+				    op_info->header_global);
+	if (mapping)
+		pr_out_str(ctx->dl, "mapping", mapping);
+}
+
+static int dpipe_match_parse(struct dpipe_match *match,
+			     struct nlattr *nl)
+
+{
+	struct nlattr *nla_match[DEVLINK_ATTR_MAX + 1] = {};
+	int err;
+
+	err = mnl_attr_parse_nested(nl, attr_cb, nla_match);
+	if (err != MNL_CB_OK)
+		return -EINVAL;
+
+	if (!nla_match[DEVLINK_ATTR_DPIPE_MATCH_TYPE] ||
+	    !nla_match[DEVLINK_ATTR_DPIPE_HEADER_INDEX] ||
+	    !nla_match[DEVLINK_ATTR_DPIPE_HEADER_ID] ||
+	    !nla_match[DEVLINK_ATTR_DPIPE_FIELD_ID]) {
+		return -EINVAL;
+	}
+
+	match->type = mnl_attr_get_u32(nla_match[DEVLINK_ATTR_DPIPE_MATCH_TYPE]);
+	match->info.header_id = mnl_attr_get_u32(nla_match[DEVLINK_ATTR_DPIPE_HEADER_ID]);
+	match->info.field_id = mnl_attr_get_u32(nla_match[DEVLINK_ATTR_DPIPE_FIELD_ID]);
+	match->info.header_global = !!mnl_attr_get_u8(nla_match[DEVLINK_ATTR_DPIPE_HEADER_GLOBAL]);
+
+	return 0;
+}
+
+static int dpipe_table_matches_show(struct dpipe_ctx *ctx,
+				    struct nlattr *nla_matches)
+{
+	struct nlattr *nla_match;
+	struct dpipe_match match;
+
+	mnl_attr_for_each_nested(nla_match, nla_matches) {
+		pr_out_entry_start(ctx->dl);
+		if (dpipe_match_parse(&match, nla_match))
+			goto err_match_parse;
+		pr_out_dpipe_match(&match, ctx);
+		pr_out_entry_end(ctx->dl);
+	}
+	return 0;
+
+err_match_parse:
+	pr_out_entry_end(ctx->dl);
+	return -EINVAL;
+}
+
+static struct resource *
+resource_find(struct resources *resources, struct resource *resource,
+	      uint64_t resource_id)
+{
+	struct list_head *list_head;
+
+	if (!resource)
+		list_head = &resources->resource_list;
+	else
+		list_head = &resource->resource_list;
+
+	list_for_each_entry(resource, list_head, list) {
+		struct resource *child_resource;
+
+		if (resource->id == resource_id)
+			return resource;
+
+		child_resource = resource_find(resources, resource,
+					       resource_id);
+		if (child_resource)
+			return child_resource;
+	}
+	return NULL;
+}
+
+static void
+resource_path_print(struct dl *dl, struct resources *resources,
+		    uint64_t resource_id)
+{
+	struct resource *resource, *parent_resource;
+	const char del[] = "/";
+	int path_len = 0;
+	char *path;
+
+	resource = resource_find(resources, NULL, resource_id);
+	if (!resource)
+		return;
+
+	for (parent_resource = resource; parent_resource;
+	     parent_resource = parent_resource->parent)
+		path_len += strlen(parent_resource->name) + 1;
+
+	path_len++;
+	path = calloc(1, path_len);
+	if (!path)
+		return;
+
+	path += path_len - 1;
+	for (parent_resource = resource; parent_resource;
+		parent_resource = parent_resource->parent) {
+		path -= strlen(parent_resource->name);
+		memcpy(path, parent_resource->name,
+		       strlen(parent_resource->name));
+		path -= strlen(del);
+		memcpy(path, del, strlen(del));
+	}
+	pr_out_str(dl, "resource_path", path);
+	free(path);
+}
+
+static int dpipe_table_show(struct dpipe_ctx *ctx, struct nlattr *nl)
+{
+	struct nlattr *nla_table[DEVLINK_ATTR_MAX + 1] = {};
+	struct dpipe_table *table;
+	uint32_t resource_units;
+	bool counters_enabled;
+	bool resource_valid;
+	uint32_t size;
+	int err;
+
+	err = mnl_attr_parse_nested(nl, attr_cb, nla_table);
+	if (err != MNL_CB_OK)
+		return -EINVAL;
+
+	if (!nla_table[DEVLINK_ATTR_DPIPE_TABLE_NAME] ||
+	    !nla_table[DEVLINK_ATTR_DPIPE_TABLE_SIZE] ||
+	    !nla_table[DEVLINK_ATTR_DPIPE_TABLE_ACTIONS] ||
+	    !nla_table[DEVLINK_ATTR_DPIPE_TABLE_MATCHES] ||
+	    !nla_table[DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED]) {
+		return -EINVAL;
+	}
+
+	table = dpipe_table_alloc();
+	if (!table)
+		return -ENOMEM;
+
+	table->name = strdup(mnl_attr_get_str(nla_table[DEVLINK_ATTR_DPIPE_TABLE_NAME]));
+	size = mnl_attr_get_u32(nla_table[DEVLINK_ATTR_DPIPE_TABLE_SIZE]);
+	counters_enabled = !!mnl_attr_get_u8(nla_table[DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED]);
+
+	resource_valid = nla_table[DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID] &&
+			 ctx->resources;
+	if (resource_valid) {
+		table->resource_id = mnl_attr_get_u64(nla_table[DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID]);
+		table->resource_valid = true;
+	}
+
+	list_add_tail(&table->list, &ctx->tables->table_list);
+	if (!ctx->print_tables)
+		return 0;
+
+	pr_out_str(ctx->dl, "name", table->name);
+	pr_out_uint(ctx->dl, "size", size);
+	pr_out_str(ctx->dl, "counters_enabled",
+		   counters_enabled ? "true" : "false");
+
+	if (resource_valid) {
+		resource_units = mnl_attr_get_u32(nla_table[DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_UNITS]);
+		resource_path_print(ctx->dl, ctx->resources,
+				    table->resource_id);
+		pr_out_uint(ctx->dl, "resource_units", resource_units);
+	}
+
+	pr_out_array_start(ctx->dl, "match");
+	if (dpipe_table_matches_show(ctx, nla_table[DEVLINK_ATTR_DPIPE_TABLE_MATCHES]))
+		goto err_matches_show;
+	pr_out_array_end(ctx->dl);
+
+	pr_out_array_start(ctx->dl, "action");
+	if (dpipe_table_actions_show(ctx, nla_table[DEVLINK_ATTR_DPIPE_TABLE_ACTIONS]))
+		goto err_actions_show;
+	pr_out_array_end(ctx->dl);
+
+	return 0;
+
+err_actions_show:
+err_matches_show:
+	pr_out_array_end(ctx->dl);
+	return -EINVAL;
+}
+
+static int dpipe_tables_show(struct dpipe_ctx *ctx, struct nlattr **tb)
+{
+	struct nlattr *nla_tables = tb[DEVLINK_ATTR_DPIPE_TABLES];
+	struct nlattr *nla_table;
+
+	mnl_attr_for_each_nested(nla_table, nla_tables) {
+		if (ctx->print_tables)
+			pr_out_handle_start_arr(ctx->dl, tb);
+		if (dpipe_table_show(ctx, nla_table))
+			goto err_table_show;
+		if (ctx->print_tables)
+			pr_out_handle_end(ctx->dl);
+	}
+	return 0;
+
+err_table_show:
+	if (ctx->print_tables)
+		pr_out_handle_end(ctx->dl);
+	return -EINVAL;
+}
+
+static int cmd_dpipe_table_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dpipe_ctx *ctx = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_DPIPE_TABLES])
+		return MNL_CB_ERROR;
+
+	if (dpipe_tables_show(ctx, tb))
+		return MNL_CB_ERROR;
+	return MNL_CB_OK;
+}
+
+static int cmd_resource_dump_cb(const struct nlmsghdr *nlh, void *data);
+
+static int cmd_dpipe_table_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	struct dpipe_ctx dpipe_ctx = {};
+	struct resource_ctx resource_ctx = {};
+	uint16_t flags = NLM_F_REQUEST;
+	int err;
+
+	err = dl_argv_parse(dl, DL_OPT_HANDLE, DL_OPT_DPIPE_TABLE_NAME);
+	if (err)
+		return err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_HEADERS_GET, flags);
+
+	err = dpipe_ctx_init(&dpipe_ctx, dl);
+	if (err)
+		return err;
+
+	dpipe_ctx.print_tables = true;
+
+	dl_opts_put(nlh, dl);
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_header_cb,
+				  &dpipe_ctx);
+	if (err) {
+		pr_err("error get headers %s\n", strerror(dpipe_ctx.err));
+		goto err_headers_get;
+	}
+
+	err = resource_ctx_init(&resource_ctx, dl);
+	if (err)
+		goto err_resource_ctx_init;
+
+	resource_ctx.print_resources = false;
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_RESOURCE_DUMP, flags);
+	dl_opts_put(nlh, dl);
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_resource_dump_cb,
+				  &resource_ctx);
+	if (!err)
+		dpipe_ctx.resources = resource_ctx.resources;
+
+	flags = NLM_F_REQUEST | NLM_F_ACK;
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_TABLE_GET, flags);
+	dl_opts_put(nlh, dl);
+
+	pr_out_section_start(dl, "table");
+	_mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_table_show_cb, &dpipe_ctx);
+	pr_out_section_end(dl);
+
+	resource_ctx_fini(&resource_ctx);
+	dpipe_ctx_fini(&dpipe_ctx);
+	return 0;
+
+err_resource_ctx_init:
+err_headers_get:
+	dpipe_ctx_fini(&dpipe_ctx);
+	return err;
+}
+
+static int cmd_dpipe_table_set(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_TABLE_COUNTERS_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl,
+				DL_OPT_HANDLE | DL_OPT_DPIPE_TABLE_NAME |
+				DL_OPT_DPIPE_TABLE_COUNTERS, 0);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+enum dpipe_value_type {
+	DPIPE_VALUE_TYPE_VALUE,
+	DPIPE_VALUE_TYPE_MASK,
+};
+
+static const char *
+dpipe_value_type_e2s(enum dpipe_value_type type)
+{
+	switch (type) {
+	case DPIPE_VALUE_TYPE_VALUE:
+		return "value";
+	case DPIPE_VALUE_TYPE_MASK:
+		return "value_mask";
+	default:
+		return "<unknown>";
+	}
+}
+
+struct dpipe_field_printer {
+	unsigned int field_id;
+	void (*printer)(struct dpipe_ctx *, enum dpipe_value_type, void *);
+};
+
+struct dpipe_header_printer {
+	struct dpipe_field_printer *printers;
+	unsigned int printers_count;
+	unsigned int header_id;
+};
+
+static void dpipe_field_printer_ipv4_addr(struct dpipe_ctx *ctx,
+					  enum dpipe_value_type type,
+					  void *value)
+{
+	struct in_addr ip_addr;
+
+	ip_addr.s_addr = htonl(*(uint32_t *)value);
+	pr_out_str(ctx->dl, dpipe_value_type_e2s(type), inet_ntoa(ip_addr));
+}
+
+static void
+dpipe_field_printer_ethernet_addr(struct dpipe_ctx *ctx,
+				  enum dpipe_value_type type,
+				  void *value)
+{
+	pr_out_str(ctx->dl, dpipe_value_type_e2s(type),
+		   ether_ntoa((struct ether_addr *)value));
+}
+
+static void dpipe_field_printer_ipv6_addr(struct dpipe_ctx *ctx,
+					  enum dpipe_value_type type,
+					  void *value)
+{
+	char str[INET6_ADDRSTRLEN];
+
+	inet_ntop(AF_INET6, value, str, INET6_ADDRSTRLEN);
+	pr_out_str(ctx->dl, dpipe_value_type_e2s(type), str);
+}
+
+static struct dpipe_field_printer dpipe_field_printers_ipv4[] = {
+	{
+		.printer = dpipe_field_printer_ipv4_addr,
+		.field_id = DEVLINK_DPIPE_FIELD_IPV4_DST_IP,
+	}
+};
+
+static struct dpipe_header_printer dpipe_header_printer_ipv4  = {
+	.printers = dpipe_field_printers_ipv4,
+	.printers_count = ARRAY_SIZE(dpipe_field_printers_ipv4),
+	.header_id = DEVLINK_DPIPE_HEADER_IPV4,
+};
+
+static struct dpipe_field_printer dpipe_field_printers_ethernet[] = {
+	{
+		.printer = dpipe_field_printer_ethernet_addr,
+		.field_id = DEVLINK_DPIPE_FIELD_ETHERNET_DST_MAC,
+	},
+};
+
+static struct dpipe_header_printer dpipe_header_printer_ethernet = {
+	.printers = dpipe_field_printers_ethernet,
+	.printers_count = ARRAY_SIZE(dpipe_field_printers_ethernet),
+	.header_id = DEVLINK_DPIPE_HEADER_ETHERNET,
+};
+
+static struct dpipe_field_printer dpipe_field_printers_ipv6[] = {
+	{
+		.printer = dpipe_field_printer_ipv6_addr,
+		.field_id = DEVLINK_DPIPE_FIELD_IPV6_DST_IP,
+	}
+};
+
+static struct dpipe_header_printer dpipe_header_printer_ipv6 = {
+	.printers = dpipe_field_printers_ipv6,
+	.printers_count = ARRAY_SIZE(dpipe_field_printers_ipv6),
+	.header_id = DEVLINK_DPIPE_HEADER_IPV6,
+};
+
+static struct dpipe_header_printer *dpipe_header_printers[] = {
+	&dpipe_header_printer_ipv4,
+	&dpipe_header_printer_ethernet,
+	&dpipe_header_printer_ipv6,
+};
+
+static int dpipe_print_prot_header(struct dpipe_ctx *ctx,
+				   struct dpipe_op_info *info,
+				   enum dpipe_value_type type,
+				   void *value)
+{
+	unsigned int header_printers_count = ARRAY_SIZE(dpipe_header_printers);
+	struct dpipe_header_printer *header_printer;
+	struct dpipe_field_printer *field_printer;
+	unsigned int field_printers_count;
+	int j;
+	int i;
+
+	for (i = 0; i < header_printers_count; i++) {
+		header_printer = dpipe_header_printers[i];
+		if (header_printer->header_id != info->header_id)
+			continue;
+		field_printers_count = header_printer->printers_count;
+		for (j = 0; j < field_printers_count; j++) {
+			field_printer = &header_printer->printers[j];
+			if (field_printer->field_id != info->field_id)
+				continue;
+			field_printer->printer(ctx, type, value);
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static void __pr_out_entry_value(struct dpipe_ctx *ctx,
+				 void *value,
+				 unsigned int value_len,
+				 struct dpipe_op_info *info,
+				 enum dpipe_value_type type)
+{
+	if (info->header_global &&
+	    !dpipe_print_prot_header(ctx, info, type, value))
+		return;
+
+	if (value_len == sizeof(uint32_t)) {
+		uint32_t *value_32 = value;
+
+		pr_out_uint(ctx->dl, dpipe_value_type_e2s(type), *value_32);
+	}
+}
+
+static void pr_out_dpipe_entry_value(struct dpipe_ctx *ctx,
+				     struct nlattr **nla_match_value,
+				     struct dpipe_op_info *info)
+{
+	void *value, *value_mask;
+	uint32_t value_mapping;
+	uint16_t value_len;
+	bool mask, mapping;
+
+	mask = !!nla_match_value[DEVLINK_ATTR_DPIPE_VALUE_MASK];
+	mapping = !!nla_match_value[DEVLINK_ATTR_DPIPE_VALUE_MAPPING];
+
+	value_len = mnl_attr_get_payload_len(nla_match_value[DEVLINK_ATTR_DPIPE_VALUE]);
+	value = mnl_attr_get_payload(nla_match_value[DEVLINK_ATTR_DPIPE_VALUE]);
+
+	if (mapping) {
+		value_mapping = mnl_attr_get_u32(nla_match_value[DEVLINK_ATTR_DPIPE_VALUE_MAPPING]);
+		pr_out_uint(ctx->dl, "mapping_value", value_mapping);
+	}
+
+	if (mask) {
+		value_mask = mnl_attr_get_payload(nla_match_value[DEVLINK_ATTR_DPIPE_VALUE]);
+		__pr_out_entry_value(ctx, value_mask, value_len, info,
+				     DPIPE_VALUE_TYPE_MASK);
+	}
+
+	__pr_out_entry_value(ctx, value, value_len, info, DPIPE_VALUE_TYPE_VALUE);
+}
+
+static int dpipe_entry_match_value_show(struct dpipe_ctx *ctx,
+					struct nlattr *nl)
+{
+	struct nlattr *nla_match_value[DEVLINK_ATTR_MAX + 1] = {};
+	struct dpipe_match match;
+	int err;
+
+	err = mnl_attr_parse_nested(nl, attr_cb, nla_match_value);
+	if (err != MNL_CB_OK)
+		return -EINVAL;
+
+	if (!nla_match_value[DEVLINK_ATTR_DPIPE_MATCH] ||
+	    !nla_match_value[DEVLINK_ATTR_DPIPE_VALUE]) {
+		return -EINVAL;
+	}
+
+	pr_out_entry_start(ctx->dl);
+	if (dpipe_match_parse(&match,
+			      nla_match_value[DEVLINK_ATTR_DPIPE_MATCH]))
+		goto err_match_parse;
+	pr_out_dpipe_match(&match, ctx);
+	pr_out_dpipe_entry_value(ctx, nla_match_value, &match.info);
+	pr_out_entry_end(ctx->dl);
+
+	return 0;
+
+err_match_parse:
+	pr_out_entry_end(ctx->dl);
+	return -EINVAL;
+}
+
+static int dpipe_entry_action_value_show(struct dpipe_ctx *ctx,
+					 struct nlattr *nl)
+{
+	struct nlattr *nla_action_value[DEVLINK_ATTR_MAX + 1] = {};
+	struct dpipe_action action;
+	int err;
+
+	err = mnl_attr_parse_nested(nl, attr_cb, nla_action_value);
+	if (err != MNL_CB_OK)
+		return -EINVAL;
+
+	if (!nla_action_value[DEVLINK_ATTR_DPIPE_ACTION] ||
+	    !nla_action_value[DEVLINK_ATTR_DPIPE_VALUE]) {
+		return -EINVAL;
+	}
+
+	pr_out_entry_start(ctx->dl);
+	if (dpipe_action_parse(&action,
+			       nla_action_value[DEVLINK_ATTR_DPIPE_ACTION]))
+		goto err_action_parse;
+	pr_out_dpipe_action(&action, ctx);
+	pr_out_dpipe_entry_value(ctx, nla_action_value, &action.info);
+	pr_out_entry_end(ctx->dl);
+
+	return 0;
+
+err_action_parse:
+	pr_out_entry_end(ctx->dl);
+	return -EINVAL;
+}
+
+static int
+dpipe_tables_action_values_show(struct dpipe_ctx *ctx,
+				struct nlattr *nla_action_values)
+{
+	struct nlattr *nla_action_value;
+
+	mnl_attr_for_each_nested(nla_action_value, nla_action_values) {
+		if (dpipe_entry_action_value_show(ctx, nla_action_value))
+			return -EINVAL;
+	}
+	return 0;
+}
+
+static int
+dpipe_tables_match_values_show(struct dpipe_ctx *ctx,
+			       struct nlattr *nla_match_values)
+{
+	struct nlattr *nla_match_value;
+
+	mnl_attr_for_each_nested(nla_match_value, nla_match_values) {
+		if (dpipe_entry_match_value_show(ctx, nla_match_value))
+			return -EINVAL;
+	}
+	return 0;
+}
+
+static int dpipe_entry_show(struct dpipe_ctx *ctx, struct nlattr *nl)
+{
+	struct nlattr *nla_entry[DEVLINK_ATTR_MAX + 1] = {};
+	uint32_t entry_index;
+	uint64_t counter;
+	int err;
+
+	err = mnl_attr_parse_nested(nl, attr_cb, nla_entry);
+	if (err != MNL_CB_OK)
+		return -EINVAL;
+
+	if (!nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_INDEX] ||
+	    !nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES] ||
+	    !nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES]) {
+		return -EINVAL;
+	}
+
+	entry_index = mnl_attr_get_u32(nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_INDEX]);
+	pr_out_uint(ctx->dl, "index", entry_index);
+
+	if (nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_COUNTER]) {
+		counter = mnl_attr_get_u64(nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_COUNTER]);
+		pr_out_uint(ctx->dl, "counter", counter);
+	}
+
+	pr_out_array_start(ctx->dl, "match_value");
+	if (dpipe_tables_match_values_show(ctx,
+					   nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES]))
+		goto err_match_values_show;
+	pr_out_array_end(ctx->dl);
+
+	pr_out_array_start(ctx->dl, "action_value");
+	if (dpipe_tables_action_values_show(ctx,
+					    nla_entry[DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES]))
+		goto err_action_values_show;
+	pr_out_array_end(ctx->dl);
+	return 0;
+
+err_action_values_show:
+err_match_values_show:
+	pr_out_array_end(ctx->dl);
+	return -EINVAL;
+}
+
+static int dpipe_table_entries_show(struct dpipe_ctx *ctx, struct nlattr **tb)
+{
+	struct nlattr *nla_entries = tb[DEVLINK_ATTR_DPIPE_ENTRIES];
+	struct nlattr *nla_entry;
+
+	mnl_attr_for_each_nested(nla_entry, nla_entries) {
+		pr_out_handle_start_arr(ctx->dl, tb);
+		if (dpipe_entry_show(ctx, nla_entry))
+			goto err_entry_show;
+		pr_out_handle_end(ctx->dl);
+	}
+	return 0;
+
+err_entry_show:
+	pr_out_handle_end(ctx->dl);
+	return -EINVAL;
+}
+
+static int cmd_dpipe_table_entry_dump_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct dpipe_ctx *ctx = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_DPIPE_ENTRIES])
+		return MNL_CB_ERROR;
+
+	if (dpipe_table_entries_show(ctx, tb))
+		return MNL_CB_ERROR;
+	return MNL_CB_OK;
+}
+
+static int cmd_dpipe_table_dump(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	struct dpipe_ctx ctx = {};
+	uint16_t flags = NLM_F_REQUEST;
+	int err;
+
+	err = dpipe_ctx_init(&ctx, dl);
+	if (err)
+		return err;
+
+	err = dl_argv_parse(dl, DL_OPT_HANDLE | DL_OPT_DPIPE_TABLE_NAME, 0);
+	if (err)
+		goto out;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_HEADERS_GET, flags);
+	dl_opts_put(nlh, dl);
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_header_cb, &ctx);
+	if (err) {
+		pr_err("error get headers %s\n", strerror(ctx.err));
+		goto out;
+	}
+
+	flags = NLM_F_REQUEST | NLM_F_ACK;
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_ENTRIES_GET, flags);
+	dl_opts_put(nlh, dl);
+
+	pr_out_section_start(dl, "table_entry");
+	_mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_table_entry_dump_cb, &ctx);
+	pr_out_section_end(dl);
+out:
+	dpipe_ctx_fini(&ctx);
+	return err;
+}
+
+static void cmd_dpipe_table_help(void)
+{
+	pr_err("Usage: devlink dpipe table [ OBJECT-LIST ]\n"
+	       "where  OBJECT-LIST := { show | set | dump }\n");
+}
+
+static int cmd_dpipe_table(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		cmd_dpipe_table_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show")) {
+		dl_arg_inc(dl);
+		return cmd_dpipe_table_show(dl);
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_dpipe_table_set(dl);
+	}  else if (dl_argv_match(dl, "dump")) {
+		dl_arg_inc(dl);
+		return cmd_dpipe_table_dump(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static void cmd_dpipe_help(void)
+{
+	pr_err("Usage: devlink dpipe [ OBJECT-LIST ]\n"
+	       "where  OBJECT-LIST := { header | table }\n");
+}
+
+static int cmd_dpipe(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		cmd_dpipe_help();
+		return 0;
+	} else if (dl_argv_match(dl, "header")) {
+		dl_arg_inc(dl);
+		return cmd_dpipe_header(dl);
+	} else if (dl_argv_match(dl, "table")) {
+		dl_arg_inc(dl);
+		return cmd_dpipe_table(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static int
+resource_parse(struct resource_ctx *ctx, struct resource *resource,
+	       struct nlattr **nla_resource)
+{
+	if (!nla_resource[DEVLINK_ATTR_RESOURCE_NAME] ||
+	    !nla_resource[DEVLINK_ATTR_RESOURCE_SIZE] ||
+	    !nla_resource[DEVLINK_ATTR_RESOURCE_ID] ||
+	    !nla_resource[DEVLINK_ATTR_RESOURCE_UNIT] ||
+	    !nla_resource[DEVLINK_ATTR_RESOURCE_SIZE_MIN] ||
+	    !nla_resource[DEVLINK_ATTR_RESOURCE_SIZE_MAX] ||
+	    !nla_resource[DEVLINK_ATTR_RESOURCE_SIZE_GRAN]) {
+		return -EINVAL;
+	}
+
+	resource->name = strdup(mnl_attr_get_str(nla_resource[DEVLINK_ATTR_RESOURCE_NAME]));
+	resource->size = mnl_attr_get_u64(nla_resource[DEVLINK_ATTR_RESOURCE_SIZE]);
+	resource->id = mnl_attr_get_u64(nla_resource[DEVLINK_ATTR_RESOURCE_ID]);
+	resource->unit = mnl_attr_get_u8(nla_resource[DEVLINK_ATTR_RESOURCE_UNIT]);
+	resource->size_min = mnl_attr_get_u64(nla_resource[DEVLINK_ATTR_RESOURCE_SIZE_MIN]);
+	resource->size_max = mnl_attr_get_u64(nla_resource[DEVLINK_ATTR_RESOURCE_SIZE_MAX]);
+	resource->size_gran = mnl_attr_get_u64(nla_resource[DEVLINK_ATTR_RESOURCE_SIZE_GRAN]);
+
+	if (nla_resource[DEVLINK_ATTR_RESOURCE_SIZE_NEW])
+		resource->size_new = mnl_attr_get_u64(nla_resource[DEVLINK_ATTR_RESOURCE_SIZE_NEW]);
+	else
+		resource->size_new = resource->size;
+
+	if (nla_resource[DEVLINK_ATTR_RESOURCE_OCC]) {
+		resource->size_occ = mnl_attr_get_u64(nla_resource[DEVLINK_ATTR_RESOURCE_OCC]);
+		resource->occ_valid = true;
+	}
+
+	if (resource->size_new != resource->size)
+		ctx->pending_change = true;
+
+	return 0;
+}
+
+static int
+resource_get(struct resource_ctx *ctx, struct resource *resource,
+	     struct resource *parent_resource, struct nlattr *nl)
+{
+	struct nlattr *nla_resource[DEVLINK_ATTR_MAX + 1] = {};
+	struct nlattr *nla_child_resource;
+	struct nlattr *nla_resources;
+	bool top = false;
+	int err;
+
+	if (!resource) {
+		nla_resources = nl;
+		top = true;
+		goto out;
+	}
+
+	err = mnl_attr_parse_nested(nl, attr_cb, nla_resource);
+	if (err != MNL_CB_OK)
+		return -EINVAL;
+
+	err = resource_parse(ctx, resource, nla_resource);
+	if (err)
+		return err;
+
+	resource->parent = parent_resource;
+	if (!nla_resource[DEVLINK_ATTR_RESOURCE_LIST])
+		return 0;
+
+	resource->size_valid = !!mnl_attr_get_u8(nla_resource[DEVLINK_ATTR_RESOURCE_SIZE_VALID]);
+	nla_resources = nla_resource[DEVLINK_ATTR_RESOURCE_LIST];
+out:
+	mnl_attr_for_each_nested(nla_child_resource, nla_resources) {
+		struct resource *child_resource;
+		struct list_head *list;
+
+		child_resource = resource_alloc();
+		if (!child_resource)
+			return -ENOMEM;
+
+		if (top)
+			list = &ctx->resources->resource_list;
+		else
+			list = &resource->resource_list;
+
+		list_add_tail(&child_resource->list, list);
+		err = resource_get(ctx, child_resource, resource,
+				   nla_child_resource);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static const char *resource_unit_str_get(enum devlink_resource_unit unit)
+{
+	switch (unit) {
+	case DEVLINK_RESOURCE_UNIT_ENTRY: return "entry";
+	default: return "<unknown unit>";
+	}
+}
+
+static void resource_show(struct resource *resource,
+			  struct resource_ctx *ctx)
+{
+	struct resource *child_resource;
+	struct dpipe_table *table;
+	struct dl *dl = ctx->dl;
+	bool array = false;
+
+	pr_out_str(dl, "name", resource->name);
+	if (dl->verbose)
+		resource_path_print(dl, ctx->resources, resource->id);
+	pr_out_u64(dl, "size", resource->size);
+	if (resource->size != resource->size_new)
+		pr_out_u64(dl, "size_new", resource->size_new);
+	if (resource->occ_valid)
+		pr_out_uint(dl, "occ", resource->size_occ);
+	pr_out_str(dl, "unit", resource_unit_str_get(resource->unit));
+
+	if (resource->size_min != resource->size_max) {
+		pr_out_uint(dl, "size_min", resource->size_min);
+		pr_out_u64(dl, "size_max", resource->size_max);
+		pr_out_uint(dl, "size_gran", resource->size_gran);
+	}
+
+	list_for_each_entry(table, &ctx->tables->table_list, list)
+		if (table->resource_id == resource->id &&
+		    table->resource_valid)
+			array = true;
+
+	if (array)
+		pr_out_array_start(dl, "dpipe_tables");
+	else
+		pr_out_str(dl, "dpipe_tables", "none");
+
+	list_for_each_entry(table, &ctx->tables->table_list, list) {
+		if (table->resource_id != resource->id ||
+		    !table->resource_valid)
+			continue;
+		pr_out_entry_start(dl);
+		pr_out_str(dl, "table_name", table->name);
+		pr_out_entry_end(dl);
+	}
+	if (array)
+		pr_out_array_end(dl);
+
+	if (list_empty(&resource->resource_list))
+		return;
+
+	if (ctx->pending_change)
+		pr_out_str(dl, "size_valid", resource->size_valid ?
+			   "true" : "false");
+	pr_out_array_start(dl, "resources");
+	list_for_each_entry(child_resource, &resource->resource_list, list) {
+		pr_out_entry_start(dl);
+		resource_show(child_resource, ctx);
+		pr_out_entry_end(dl);
+	}
+	pr_out_array_end(dl);
+}
+
+static void
+resources_show(struct resource_ctx *ctx, struct nlattr **tb)
+{
+	struct resources *resources = ctx->resources;
+	struct resource *resource;
+
+	list_for_each_entry(resource, &resources->resource_list, list) {
+		pr_out_handle_start_arr(ctx->dl, tb);
+		resource_show(resource, ctx);
+		pr_out_handle_end(ctx->dl);
+	}
+}
+
+static int resources_get(struct resource_ctx *ctx, struct nlattr **tb)
+{
+	return resource_get(ctx, NULL, NULL, tb[DEVLINK_ATTR_RESOURCE_LIST]);
+}
+
+static int cmd_resource_dump_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct resource_ctx *ctx = data;
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	int err;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_RESOURCE_LIST])
+		return MNL_CB_ERROR;
+
+	err = resources_get(ctx, tb);
+	if (err) {
+		ctx->err = err;
+		return MNL_CB_ERROR;
+	}
+
+	if (ctx->print_resources)
+		resources_show(ctx, tb);
+
+	return MNL_CB_OK;
+}
+
+static int cmd_resource_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	struct dpipe_ctx dpipe_ctx = {};
+	struct resource_ctx resource_ctx = {};
+	int err;
+
+	err = dl_argv_parse(dl, DL_OPT_HANDLE, 0);
+	if (err)
+		return err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_DPIPE_TABLE_GET,
+			       NLM_F_REQUEST);
+	dl_opts_put(nlh, dl);
+
+	err = dpipe_ctx_init(&dpipe_ctx, dl);
+	if (err)
+		return err;
+
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_dpipe_table_show_cb,
+				  &dpipe_ctx);
+	if (err) {
+		pr_err("error get tables %s\n", strerror(dpipe_ctx.err));
+		goto out;
+	}
+
+	err = resource_ctx_init(&resource_ctx, dl);
+	if (err)
+		goto out;
+
+	resource_ctx.print_resources = true;
+	resource_ctx.tables = dpipe_ctx.tables;
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_RESOURCE_DUMP,
+			       NLM_F_REQUEST | NLM_F_ACK);
+	dl_opts_put(nlh, dl);
+	pr_out_section_start(dl, "resources");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_resource_dump_cb,
+				  &resource_ctx);
+	pr_out_section_end(dl);
+	resource_ctx_fini(&resource_ctx);
+out:
+	dpipe_ctx_fini(&dpipe_ctx);
+	return err;
+}
+
+static void cmd_resource_help(void)
+{
+	pr_err("Usage: devlink resource show DEV\n"
+	       "       devlink resource set DEV path PATH size SIZE\n");
+}
+
+static struct resource *
+resource_find_by_name(struct list_head *list, char *name)
+{
+	struct resource *resource;
+
+	list_for_each_entry(resource, list, list) {
+		if (!strcmp(resource->name, name))
+			return resource;
+	}
+	return NULL;
+}
+
+static int
+resource_path_parse(struct resource_ctx *ctx, const char *resource_path,
+		    uint32_t *p_resource_id, bool *p_resource_valid)
+{
+	struct resource *resource;
+	uint32_t resource_id = 0;
+	char *resource_path_dup;
+	struct list_head *list;
+	const char del[] = "/";
+	char *resource_name;
+
+	resource_path_dup = strdup(resource_path);
+	list = &ctx->resources->resource_list;
+	resource_name = strtok(resource_path_dup, del);
+	while (resource_name != NULL) {
+		resource = resource_find_by_name(list, resource_name);
+		if (!resource)
+			goto err_resource_lookup;
+
+		list = &resource->resource_list;
+		resource_name = strtok(NULL, del);
+		resource_id = resource->id;
+	}
+	free(resource_path_dup);
+	*p_resource_valid = true;
+	*p_resource_id = resource_id;
+	return 0;
+
+err_resource_lookup:
+	free(resource_path_dup);
+	return -EINVAL;
+}
+
+static int cmd_resource_set(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	struct resource_ctx ctx = {};
+	int err;
+
+	err = resource_ctx_init(&ctx, dl);
+	if (err)
+		return err;
+
+	ctx.print_resources = false;
+	err = dl_argv_parse(dl, DL_OPT_HANDLE | DL_OPT_RESOURCE_PATH |
+			    DL_OPT_RESOURCE_SIZE, 0);
+	if (err)
+		goto out;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_RESOURCE_DUMP,
+			       NLM_F_REQUEST);
+	dl_opts_put(nlh, dl);
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_resource_dump_cb, &ctx);
+	if (err) {
+		pr_err("error getting resources %s\n", strerror(ctx.err));
+		goto out;
+	}
+
+	err = resource_path_parse(&ctx, dl->opts.resource_path,
+				  &dl->opts.resource_id,
+				  &dl->opts.resource_id_valid);
+	if (err) {
+		pr_err("error parsing resource path %s\n", strerror(-err));
+		goto out;
+	}
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_RESOURCE_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	dl_opts_put(nlh, dl);
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+out:
+	resource_ctx_fini(&ctx);
+	return err;
+}
+
+static int cmd_resource(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		cmd_resource_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show")) {
+		dl_arg_inc(dl);
+		return cmd_resource_show(dl);
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_resource_set(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static void pr_out_region_handle_start(struct dl *dl, struct nlattr **tb)
+{
+	const char *bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+	const char *dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+	const char *region_name = mnl_attr_get_str(tb[DEVLINK_ATTR_REGION_NAME]);
+	char buf[256];
+
+	sprintf(buf, "%s/%s/%s", bus_name, dev_name, region_name);
+	if (dl->json_output) {
+		jsonw_name(dl->jw, buf);
+		jsonw_start_object(dl->jw);
+	} else {
+		pr_out("%s:", buf);
+	}
+}
+
+static void pr_out_region_handle_end(struct dl *dl)
+{
+	if (dl->json_output)
+		jsonw_end_object(dl->jw);
+	else
+		pr_out("\n");
+}
+
+static void pr_out_region_snapshots_start(struct dl *dl, bool array)
+{
+	__pr_out_indent_newline(dl);
+	if (dl->json_output) {
+		jsonw_name(dl->jw, "snapshot");
+		jsonw_start_array(dl->jw);
+	} else {
+		pr_out("snapshot %s", array ? "[" : "");
+	}
+}
+
+static void pr_out_region_snapshots_end(struct dl *dl, bool array)
+{
+	if (dl->json_output)
+		jsonw_end_array(dl->jw);
+	else if (array)
+		pr_out("]");
+}
+
+static void pr_out_region_snapshots_id(struct dl *dl, struct nlattr **tb, int index)
+{
+	uint32_t snapshot_id;
+
+	if (!tb[DEVLINK_ATTR_REGION_SNAPSHOT_ID])
+		return;
+
+	snapshot_id = mnl_attr_get_u32(tb[DEVLINK_ATTR_REGION_SNAPSHOT_ID]);
+
+	if (dl->json_output)
+		jsonw_uint(dl->jw, snapshot_id);
+	else
+		pr_out("%s%u", index ? " " : "", snapshot_id);
+}
+
+static void pr_out_snapshots(struct dl *dl, struct nlattr **tb)
+{
+	struct nlattr *tb_snapshot[DEVLINK_ATTR_MAX + 1] = {};
+	struct nlattr *nla_sanpshot;
+	int err, index = 0;
+
+	pr_out_region_snapshots_start(dl, true);
+	mnl_attr_for_each_nested(nla_sanpshot, tb[DEVLINK_ATTR_REGION_SNAPSHOTS]) {
+		err = mnl_attr_parse_nested(nla_sanpshot, attr_cb, tb_snapshot);
+		if (err != MNL_CB_OK)
+			return;
+		pr_out_region_snapshots_id(dl, tb_snapshot, index++);
+	}
+	pr_out_region_snapshots_end(dl, true);
+}
+
+static void pr_out_snapshot(struct dl *dl, struct nlattr **tb)
+{
+	pr_out_region_snapshots_start(dl, false);
+	pr_out_region_snapshots_id(dl, tb, 0);
+	pr_out_region_snapshots_end(dl, false);
+}
+
+static void pr_out_region(struct dl *dl, struct nlattr **tb)
+{
+	pr_out_region_handle_start(dl, tb);
+
+	if (tb[DEVLINK_ATTR_REGION_SIZE])
+		pr_out_u64(dl, "size",
+			   mnl_attr_get_u64(tb[DEVLINK_ATTR_REGION_SIZE]));
+
+	if (tb[DEVLINK_ATTR_REGION_SNAPSHOTS])
+		pr_out_snapshots(dl, tb);
+
+	if (tb[DEVLINK_ATTR_REGION_SNAPSHOT_ID])
+		pr_out_snapshot(dl, tb);
+
+	pr_out_region_handle_end(dl);
+}
+
+static int cmd_region_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct dl *dl = data;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_REGION_NAME] || !tb[DEVLINK_ATTR_REGION_SIZE])
+		return MNL_CB_ERROR;
+
+	pr_out_region(dl, tb);
+
+	return MNL_CB_OK;
+}
+
+static int cmd_region_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_REGION_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE_REGION, 0);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "regions");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_region_show_cb, dl);
+	pr_out_section_end(dl);
+	return err;
+}
+
+static int cmd_region_snapshot_del(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_REGION_DEL,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE_REGION |
+				DL_OPT_REGION_SNAPSHOT_ID, 0);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_region_read_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nlattr *nla_entry, *nla_chunk_data, *nla_chunk_addr;
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *tb_field[DEVLINK_ATTR_MAX + 1] = {};
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct dl *dl = data;
+	int err;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_REGION_CHUNKS])
+		return MNL_CB_ERROR;
+
+	mnl_attr_for_each_nested(nla_entry, tb[DEVLINK_ATTR_REGION_CHUNKS]) {
+		err = mnl_attr_parse_nested(nla_entry, attr_cb, tb_field);
+		if (err != MNL_CB_OK)
+			return MNL_CB_ERROR;
+
+		nla_chunk_data = tb_field[DEVLINK_ATTR_REGION_CHUNK_DATA];
+		if (!nla_chunk_data)
+			continue;
+
+		nla_chunk_addr = tb_field[DEVLINK_ATTR_REGION_CHUNK_ADDR];
+		if (!nla_chunk_addr)
+			continue;
+
+		pr_out_region_chunk(dl, mnl_attr_get_payload(nla_chunk_data),
+				    mnl_attr_get_payload_len(nla_chunk_data),
+				    mnl_attr_get_u64(nla_chunk_addr));
+	}
+	return MNL_CB_OK;
+}
+
+static int cmd_region_dump(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_REGION_READ,
+			       NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE_REGION |
+				DL_OPT_REGION_SNAPSHOT_ID, 0);
+	if (err)
+		return err;
+
+	pr_out_section_start(dl, "dump");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_region_read_cb, dl);
+	pr_out_section_end(dl);
+	if (!dl->json_output)
+		pr_out("\n");
+	return err;
+}
+
+static int cmd_region_read(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_REGION_READ,
+			       NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE_REGION |
+				DL_OPT_REGION_ADDRESS | DL_OPT_REGION_LENGTH |
+				DL_OPT_REGION_SNAPSHOT_ID, 0);
+	if (err)
+		return err;
+
+	pr_out_section_start(dl, "read");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_region_read_cb, dl);
+	pr_out_section_end(dl);
+	if (!dl->json_output)
+		pr_out("\n");
+	return err;
+}
+
+static void cmd_region_help(void)
+{
+	pr_err("Usage: devlink region show [ DEV/REGION ]\n");
+	pr_err("       devlink region del DEV/REGION snapshot SNAPSHOT_ID\n");
+	pr_err("       devlink region dump DEV/REGION [ snapshot SNAPSHOT_ID ]\n");
+	pr_err("       devlink region read DEV/REGION [ snapshot SNAPSHOT_ID ] address ADDRESS length LENGTH\n");
+}
+
+static int cmd_region(struct dl *dl)
+{
+	if (dl_no_arg(dl)) {
+		return cmd_region_show(dl);
+	} else if (dl_argv_match(dl, "help")) {
+		cmd_region_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show")) {
+		dl_arg_inc(dl);
+		return cmd_region_show(dl);
+	} else if (dl_argv_match(dl, "del")) {
+		dl_arg_inc(dl);
+		return cmd_region_snapshot_del(dl);
+	} else if (dl_argv_match(dl, "dump")) {
+		dl_arg_inc(dl);
+		return cmd_region_dump(dl);
+	} else if (dl_argv_match(dl, "read")) {
+		dl_arg_inc(dl);
+		return cmd_region_read(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static int cmd_health_set_params(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_HEALTH_REPORTER_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+	err = dl_argv_parse(dl, DL_OPT_HANDLE | DL_OPT_HEALTH_REPORTER_NAME,
+			    DL_OPT_HEALTH_REPORTER_GRACEFUL_PERIOD |
+			    DL_OPT_HEALTH_REPORTER_AUTO_RECOVER);
+	if (err)
+		return err;
+
+	dl_opts_put(nlh, dl);
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_health_dump_clear(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_HEALTH_REPORTER_DUMP_CLEAR,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl,
+				DL_OPT_HANDLE | DL_OPT_HEALTH_REPORTER_NAME, 0);
+	if (err)
+		return err;
+
+	dl_opts_put(nlh, dl);
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int fmsg_value_show(struct dl *dl, int type, struct nlattr *nl_data)
+{
+	uint8_t *data;
+	uint32_t len;
+
+	switch (type) {
+	case MNL_TYPE_FLAG:
+		pr_out_bool_value(dl, mnl_attr_get_u8(nl_data));
+		break;
+	case MNL_TYPE_U8:
+		pr_out_uint_value(dl, mnl_attr_get_u8(nl_data));
+		break;
+	case MNL_TYPE_U16:
+		pr_out_uint_value(dl, mnl_attr_get_u16(nl_data));
+		break;
+	case MNL_TYPE_U32:
+		pr_out_uint_value(dl, mnl_attr_get_u32(nl_data));
+		break;
+	case MNL_TYPE_U64:
+		pr_out_uint64_value(dl, mnl_attr_get_u64(nl_data));
+		break;
+	case MNL_TYPE_NUL_STRING:
+		pr_out_str_value(dl, mnl_attr_get_str(nl_data));
+		break;
+	case MNL_TYPE_BINARY:
+		len = mnl_attr_get_payload_len(nl_data);
+		data = mnl_attr_get_payload(nl_data);
+		pr_out_binary_value(dl, data, len);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return MNL_CB_OK;
+}
+
+struct nest_entry {
+	int attr_type;
+	struct list_head list;
+};
+
+struct fmsg_cb_data {
+	struct dl *dl;
+	uint8_t value_type;
+	struct list_head entry_list;
+};
+
+static int cmd_fmsg_nest_queue(struct fmsg_cb_data *fmsg_data,
+			       uint8_t *attr_value, bool insert)
+{
+	struct nest_entry *entry;
+
+	if (insert) {
+		entry = malloc(sizeof(struct nest_entry));
+		if (!entry)
+			return -ENOMEM;
+
+		entry->attr_type = *attr_value;
+		list_add(&entry->list, &fmsg_data->entry_list);
+	} else {
+		if (list_empty(&fmsg_data->entry_list))
+			return MNL_CB_ERROR;
+		entry = list_first_entry(&fmsg_data->entry_list,
+					 struct nest_entry, list);
+		*attr_value = entry->attr_type;
+		list_del(&entry->list);
+		free(entry);
+	}
+	return MNL_CB_OK;
+}
+
+static int cmd_fmsg_nest(struct fmsg_cb_data *fmsg_data, uint8_t nest_value,
+			 bool start)
+{
+	struct dl *dl = fmsg_data->dl;
+	uint8_t value = nest_value;
+	int err;
+
+	err = cmd_fmsg_nest_queue(fmsg_data, &value, start);
+	if (err != MNL_CB_OK)
+		return err;
+
+	switch (value) {
+	case DEVLINK_ATTR_FMSG_OBJ_NEST_START:
+		if (start)
+			pr_out_entry_start(dl);
+		else
+			pr_out_entry_end(dl);
+		break;
+	case DEVLINK_ATTR_FMSG_PAIR_NEST_START:
+		break;
+	case DEVLINK_ATTR_FMSG_ARR_NEST_START:
+		if (dl->json_output) {
+			if (start)
+				jsonw_start_array(dl->jw);
+			else
+				jsonw_end_array(dl->jw);
+		} else {
+			if (start) {
+				__pr_out_newline();
+				__pr_out_indent_inc();
+			} else {
+				__pr_out_indent_dec();
+			}
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+	return MNL_CB_OK;
+}
+
+static int cmd_fmsg_object_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct fmsg_cb_data *fmsg_data = data;
+	struct dl *dl = fmsg_data->dl;
+	struct nlattr *nla_object;
+	int attr_type;
+	int err;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_FMSG])
+		return MNL_CB_ERROR;
+
+	mnl_attr_for_each_nested(nla_object, tb[DEVLINK_ATTR_FMSG]) {
+		attr_type = mnl_attr_get_type(nla_object);
+		switch (attr_type) {
+		case DEVLINK_ATTR_FMSG_OBJ_NEST_START:
+		case DEVLINK_ATTR_FMSG_PAIR_NEST_START:
+		case DEVLINK_ATTR_FMSG_ARR_NEST_START:
+			err = cmd_fmsg_nest(fmsg_data, attr_type, true);
+			if (err != MNL_CB_OK)
+				return err;
+			break;
+		case DEVLINK_ATTR_FMSG_NEST_END:
+			err = cmd_fmsg_nest(fmsg_data, attr_type, false);
+			if (err != MNL_CB_OK)
+				return err;
+			break;
+		case DEVLINK_ATTR_FMSG_OBJ_NAME:
+			pr_out_name(dl, mnl_attr_get_str(nla_object));
+			break;
+		case DEVLINK_ATTR_FMSG_OBJ_VALUE_TYPE:
+			fmsg_data->value_type = mnl_attr_get_u8(nla_object);
+			break;
+		case DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA:
+			err = fmsg_value_show(dl, fmsg_data->value_type,
+					      nla_object);
+			if (err != MNL_CB_OK)
+				return err;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+	return MNL_CB_OK;
+}
+
+static int cmd_health_object_common(struct dl *dl, uint8_t cmd, uint16_t flags)
+{
+	struct fmsg_cb_data data;
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, cmd, flags | NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl,
+				DL_OPT_HANDLE | DL_OPT_HEALTH_REPORTER_NAME, 0);
+	if (err)
+		return err;
+
+	data.dl = dl;
+	INIT_LIST_HEAD(&data.entry_list);
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_fmsg_object_cb, &data);
+	return err;
+}
+
+static int cmd_health_dump_show(struct dl *dl)
+{
+	return cmd_health_object_common(dl,
+					DEVLINK_CMD_HEALTH_REPORTER_DUMP_GET,
+					NLM_F_DUMP);
+}
+
+static int cmd_health_diagnose(struct dl *dl)
+{
+	return cmd_health_object_common(dl,
+					DEVLINK_CMD_HEALTH_REPORTER_DIAGNOSE,
+					0);
+}
+
+static int cmd_health_recover(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_HEALTH_REPORTER_RECOVER,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl,
+				DL_OPT_HANDLE | DL_OPT_HEALTH_REPORTER_NAME, 0);
+	if (err)
+		return err;
+
+	dl_opts_put(nlh, dl);
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+enum devlink_health_reporter_state {
+	DEVLINK_HEALTH_REPORTER_STATE_HEALTHY,
+	DEVLINK_HEALTH_REPORTER_STATE_ERROR,
+};
+
+static const char *health_state_name(uint8_t state)
+{
+	switch (state) {
+	case DEVLINK_HEALTH_REPORTER_STATE_HEALTHY:
+		return HEALTH_REPORTER_STATE_HEALTHY_STR;
+	case DEVLINK_HEALTH_REPORTER_STATE_ERROR:
+		return HEALTH_REPORTER_STATE_ERROR_STR;
+	default:
+		return "<unknown state>";
+	}
+}
+
+static void format_logtime(uint64_t time_ms, char *ts_date, char *ts_time)
+{
+	struct sysinfo s_info;
+	struct tm *info;
+	time_t now, sec;
+	int err;
+
+	time(&now);
+	info = localtime(&now);
+	err = sysinfo(&s_info);
+	if (err)
+		goto out;
+	/* Subtract uptime in sec from now yields the time of system
+	 * uptime. To this, add time_ms which is the amount of
+	 * milliseconds elapsed between uptime and the dump taken.
+	 */
+	sec = now - s_info.uptime + time_ms / 1000;
+	info = localtime(&sec);
+out:
+	strftime(ts_date, HEALTH_REPORTER_TIMESTAMP_FMT_LEN, "%Y-%m-%d", info);
+	strftime(ts_time, HEALTH_REPORTER_TIMESTAMP_FMT_LEN, "%H:%M:%S", info);
+}
+
+static void pr_out_health(struct dl *dl, struct nlattr **tb_health)
+{
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	enum devlink_health_reporter_state state;
+	const struct nlattr *attr;
+	uint64_t time_ms;
+	int err;
+
+	err = mnl_attr_parse_nested(tb_health[DEVLINK_ATTR_HEALTH_REPORTER],
+				    attr_cb, tb);
+	if (err != MNL_CB_OK)
+		return;
+
+	if (!tb[DEVLINK_ATTR_HEALTH_REPORTER_NAME] ||
+	    !tb[DEVLINK_ATTR_HEALTH_REPORTER_ERR_COUNT] ||
+	    !tb[DEVLINK_ATTR_HEALTH_REPORTER_RECOVER_COUNT] ||
+	    !tb[DEVLINK_ATTR_HEALTH_REPORTER_STATE])
+		return;
+
+	pr_out_handle_start_arr(dl, tb_health);
+
+	pr_out_str(dl, "reporter",
+		   mnl_attr_get_str(tb[DEVLINK_ATTR_HEALTH_REPORTER_NAME]));
+	if (!dl->json_output) {
+		__pr_out_newline();
+		__pr_out_indent_inc();
+	}
+	state = mnl_attr_get_u8(tb[DEVLINK_ATTR_HEALTH_REPORTER_STATE]);
+	pr_out_str(dl, "state", health_state_name(state));
+	pr_out_u64(dl, "error",
+		   mnl_attr_get_u64(tb[DEVLINK_ATTR_HEALTH_REPORTER_ERR_COUNT]));
+	pr_out_u64(dl, "recover",
+		   mnl_attr_get_u64(tb[DEVLINK_ATTR_HEALTH_REPORTER_RECOVER_COUNT]));
+	if (tb[DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS]) {
+		char dump_date[HEALTH_REPORTER_TIMESTAMP_FMT_LEN];
+		char dump_time[HEALTH_REPORTER_TIMESTAMP_FMT_LEN];
+
+		attr = tb[DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS];
+		time_ms = mnl_attr_get_u64(attr);
+		format_logtime(time_ms, dump_date, dump_time);
+
+		pr_out_str(dl, "last_dump_date", dump_date);
+		pr_out_str(dl, "last_dump_time", dump_time);
+	}
+	if (tb[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD])
+		pr_out_u64(dl, "grace_period",
+			   mnl_attr_get_u64(tb[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD]));
+	if (tb[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER])
+		pr_out_bool(dl, "auto_recover",
+			    mnl_attr_get_u8(tb[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER]));
+
+	__pr_out_indent_dec();
+	pr_out_handle_end(dl);
+}
+
+static int cmd_health_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct dl *dl = data;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_HEALTH_REPORTER])
+		return MNL_CB_ERROR;
+
+	pr_out_health(dl, tb);
+
+	return MNL_CB_OK;
+}
+
+static int cmd_health_show(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_HEALTH_REPORTER_GET,
+			       flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl,
+					DL_OPT_HANDLE |
+					DL_OPT_HEALTH_REPORTER_NAME, 0);
+		if (err)
+			return err;
+	}
+	pr_out_section_start(dl, "health");
+
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_health_show_cb, dl);
+	pr_out_section_end(dl);
+	return err;
+}
+
+static void cmd_health_help(void)
+{
+	pr_err("Usage: devlink health show [ dev DEV reporter REPORTER_NAME ]\n");
+	pr_err("       devlink health recover DEV reporter REPORTER_NAME\n");
+	pr_err("       devlink health diagnose DEV reporter REPORTER_NAME\n");
+	pr_err("       devlink health dump show DEV reporter REPORTER_NAME\n");
+	pr_err("       devlink health dump clear DEV reporter REPORTER_NAME\n");
+	pr_err("       devlink health set DEV reporter REPORTER_NAME { grace_period | auto_recover } { msec | boolean }\n");
+}
+
+static int cmd_health(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_health_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+		dl_arg_inc(dl);
+		return cmd_health_show(dl);
+	} else if (dl_argv_match(dl, "recover")) {
+		dl_arg_inc(dl);
+		return cmd_health_recover(dl);
+	} else if (dl_argv_match(dl, "diagnose")) {
+		dl_arg_inc(dl);
+		return cmd_health_diagnose(dl);
+	} else if (dl_argv_match(dl, "dump")) {
+		dl_arg_inc(dl);
+		if (dl_argv_match(dl, "show")) {
+			dl_arg_inc(dl);
+			return cmd_health_dump_show(dl);
+		} else if (dl_argv_match(dl, "clear")) {
+			dl_arg_inc(dl);
+			return cmd_health_dump_clear(dl);
+		}
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_health_set_params(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static const char *trap_type_name(uint8_t type)
+{
+	switch (type) {
+	case DEVLINK_TRAP_TYPE_DROP:
+		return "drop";
+	case DEVLINK_TRAP_TYPE_EXCEPTION:
+		return "exception";
+	default:
+		return "<unknown type>";
+	}
+}
+
+static const char *trap_action_name(uint8_t action)
+{
+	switch (action) {
+	case DEVLINK_TRAP_ACTION_DROP:
+		return "drop";
+	case DEVLINK_TRAP_ACTION_TRAP:
+		return "trap";
+	default:
+		return "<unknown action>";
+	}
+}
+
+static const char *trap_metadata_name(const struct nlattr *attr)
+{
+	switch (attr->nla_type) {
+	case DEVLINK_ATTR_TRAP_METADATA_TYPE_IN_PORT:
+		return "input_port";
+	default:
+		return "<unknown metadata type>";
+	}
+}
+static void pr_out_trap_metadata(struct dl *dl, struct nlattr *attr)
+{
+	struct nlattr *attr_metadata;
+
+	pr_out_array_start(dl, "metadata");
+	mnl_attr_for_each_nested(attr_metadata, attr)
+		pr_out_str_value(dl, trap_metadata_name(attr_metadata));
+	pr_out_array_end(dl);
+}
+
+static void pr_out_trap(struct dl *dl, struct nlattr **tb, bool array)
+{
+	uint8_t action = mnl_attr_get_u8(tb[DEVLINK_ATTR_TRAP_ACTION]);
+	uint8_t type = mnl_attr_get_u8(tb[DEVLINK_ATTR_TRAP_TYPE]);
+
+	if (array)
+		pr_out_handle_start_arr(dl, tb);
+	else
+		__pr_out_handle_start(dl, tb, true, false);
+
+	pr_out_str(dl, "name", mnl_attr_get_str(tb[DEVLINK_ATTR_TRAP_NAME]));
+	pr_out_str(dl, "type", trap_type_name(type));
+	pr_out_bool(dl, "generic", !!tb[DEVLINK_ATTR_TRAP_GENERIC]);
+	pr_out_str(dl, "action", trap_action_name(action));
+	pr_out_str(dl, "group",
+		   mnl_attr_get_str(tb[DEVLINK_ATTR_TRAP_GROUP_NAME]));
+	if (dl->verbose)
+		pr_out_trap_metadata(dl, tb[DEVLINK_ATTR_TRAP_METADATA]);
+	pr_out_stats(dl, tb[DEVLINK_ATTR_STATS]);
+	pr_out_handle_end(dl);
+}
+
+static int cmd_trap_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct dl *dl = data;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_TRAP_NAME] || !tb[DEVLINK_ATTR_TRAP_TYPE] ||
+	    !tb[DEVLINK_ATTR_TRAP_ACTION] ||
+	    !tb[DEVLINK_ATTR_TRAP_GROUP_NAME] ||
+	    !tb[DEVLINK_ATTR_TRAP_METADATA] || !tb[DEVLINK_ATTR_STATS])
+		return MNL_CB_ERROR;
+
+	pr_out_trap(dl, tb, true);
+
+	return MNL_CB_OK;
+}
+
+static void cmd_trap_help(void)
+{
+	pr_err("Usage: devlink trap set DEV trap TRAP [ action { trap | drop } ]\n");
+	pr_err("       devlink trap show [ DEV trap TRAP ]\n");
+	pr_err("       devlink trap group set DEV group GROUP [ action { trap | drop } ]\n");
+	pr_err("       devlink trap group show [ DEV group GROUP ]\n");
+}
+
+static int cmd_trap_show(struct dl *dl)
+{
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	struct nlmsghdr *nlh;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_TRAP_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl,
+					DL_OPT_HANDLE | DL_OPT_TRAP_NAME, 0);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "trap");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_trap_show_cb, dl);
+	pr_out_section_end(dl);
+
+	return err;
+}
+
+static int cmd_trap_set(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_TRAP_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE | DL_OPT_TRAP_NAME,
+				DL_OPT_TRAP_ACTION);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static void pr_out_trap_group(struct dl *dl, struct nlattr **tb, bool array)
+{
+	if (array)
+		pr_out_handle_start_arr(dl, tb);
+	else
+		__pr_out_handle_start(dl, tb, true, false);
+
+	pr_out_str(dl, "name",
+		   mnl_attr_get_str(tb[DEVLINK_ATTR_TRAP_GROUP_NAME]));
+	pr_out_bool(dl, "generic", !!tb[DEVLINK_ATTR_TRAP_GENERIC]);
+	pr_out_stats(dl, tb[DEVLINK_ATTR_STATS]);
+	pr_out_handle_end(dl);
+}
+
+static int cmd_trap_group_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+	struct dl *dl = data;
+
+	mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+	if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+	    !tb[DEVLINK_ATTR_TRAP_GROUP_NAME] || !tb[DEVLINK_ATTR_STATS])
+		return MNL_CB_ERROR;
+
+	pr_out_trap_group(dl, tb, true);
+
+	return MNL_CB_OK;
+}
+
+static int cmd_trap_group_show(struct dl *dl)
+{
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+	struct nlmsghdr *nlh;
+	int err;
+
+	if (dl_argc(dl) == 0)
+		flags |= NLM_F_DUMP;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_TRAP_GROUP_GET, flags);
+
+	if (dl_argc(dl) > 0) {
+		err = dl_argv_parse_put(nlh, dl,
+					DL_OPT_HANDLE | DL_OPT_TRAP_GROUP_NAME,
+					0);
+		if (err)
+			return err;
+	}
+
+	pr_out_section_start(dl, "trap_group");
+	err = _mnlg_socket_sndrcv(dl->nlg, nlh, cmd_trap_group_show_cb, dl);
+	pr_out_section_end(dl);
+
+	return err;
+}
+
+static int cmd_trap_group_set(struct dl *dl)
+{
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlh = mnlg_msg_prepare(dl->nlg, DEVLINK_CMD_TRAP_GROUP_SET,
+			       NLM_F_REQUEST | NLM_F_ACK);
+
+	err = dl_argv_parse_put(nlh, dl,
+				DL_OPT_HANDLE | DL_OPT_TRAP_GROUP_NAME,
+				DL_OPT_TRAP_ACTION);
+	if (err)
+		return err;
+
+	return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_trap_group(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_trap_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+		dl_arg_inc(dl);
+		return cmd_trap_group_show(dl);
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_trap_group_set(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static int cmd_trap(struct dl *dl)
+{
+	if (dl_argv_match(dl, "help")) {
+		cmd_trap_help();
+		return 0;
+	} else if (dl_argv_match(dl, "show") ||
+		   dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+		dl_arg_inc(dl);
+		return cmd_trap_show(dl);
+	} else if (dl_argv_match(dl, "set")) {
+		dl_arg_inc(dl);
+		return cmd_trap_set(dl);
+	} else if (dl_argv_match(dl, "group")) {
+		dl_arg_inc(dl);
+		return cmd_trap_group(dl);
+	}
+	pr_err("Command \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static void help(void)
+{
+	pr_err("Usage: devlink [ OPTIONS ] OBJECT { COMMAND | help }\n"
+	       "       devlink [ -f[orce] ] -b[atch] filename\n"
+	       "where  OBJECT := { dev | port | sb | monitor | dpipe | resource | region | health | trap }\n"
+	       "       OPTIONS := { -V[ersion] | -n[o-nice-names] | -j[son] | -p[retty] | -v[erbose] -s[tatistics] }\n");
+}
+
+static int dl_cmd(struct dl *dl, int argc, char **argv)
+{
+	dl->argc = argc;
+	dl->argv = argv;
+
+	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+		help();
+		return 0;
+	} else if (dl_argv_match(dl, "dev")) {
+		dl_arg_inc(dl);
+		return cmd_dev(dl);
+	} else if (dl_argv_match(dl, "port")) {
+		dl_arg_inc(dl);
+		return cmd_port(dl);
+	} else if (dl_argv_match(dl, "sb")) {
+		dl_arg_inc(dl);
+		return cmd_sb(dl);
+	} else if (dl_argv_match(dl, "monitor")) {
+		dl_arg_inc(dl);
+		return cmd_mon(dl);
+	} else if (dl_argv_match(dl, "dpipe")) {
+		dl_arg_inc(dl);
+		return cmd_dpipe(dl);
+	} else if (dl_argv_match(dl, "resource")) {
+		dl_arg_inc(dl);
+		return cmd_resource(dl);
+	} else if (dl_argv_match(dl, "region")) {
+		dl_arg_inc(dl);
+		return cmd_region(dl);
+	} else if (dl_argv_match(dl, "health")) {
+		dl_arg_inc(dl);
+		return cmd_health(dl);
+	} else if (dl_argv_match(dl, "trap")) {
+		dl_arg_inc(dl);
+		return cmd_trap(dl);
+	}
+	pr_err("Object \"%s\" not found\n", dl_argv(dl));
+	return -ENOENT;
+}
+
+static int dl_init(struct dl *dl)
+{
+	int err;
+
+	dl->nlg = mnlg_socket_open(DEVLINK_GENL_NAME, DEVLINK_GENL_VERSION);
+	if (!dl->nlg) {
+		pr_err("Failed to connect to devlink Netlink\n");
+		return -errno;
+	}
+
+	err = ifname_map_init(dl);
+	if (err) {
+		pr_err("Failed to create index map\n");
+		goto err_ifname_map_create;
+	}
+	if (dl->json_output) {
+		dl->jw = jsonw_new(stdout);
+		if (!dl->jw) {
+			pr_err("Failed to create JSON writer\n");
+			goto err_json_new;
+		}
+		jsonw_pretty(dl->jw, dl->pretty_output);
+	}
+	return 0;
+
+err_json_new:
+	ifname_map_fini(dl);
+err_ifname_map_create:
+	mnlg_socket_close(dl->nlg);
+	return err;
+}
+
+static void dl_fini(struct dl *dl)
+{
+	if (dl->json_output)
+		jsonw_destroy(&dl->jw);
+	ifname_map_fini(dl);
+	mnlg_socket_close(dl->nlg);
+}
+
+static struct dl *dl_alloc(void)
+{
+	struct dl *dl;
+
+	dl = calloc(1, sizeof(*dl));
+	if (!dl)
+		return NULL;
+	return dl;
+}
+
+static void dl_free(struct dl *dl)
+{
+	free(dl);
+}
+
+static int dl_batch(struct dl *dl, const char *name, bool force)
+{
+	char *line = NULL;
+	size_t len = 0;
+	int ret = EXIT_SUCCESS;
+
+	if (name && strcmp(name, "-") != 0) {
+		if (freopen(name, "r", stdin) == NULL) {
+			fprintf(stderr,
+				"Cannot open file \"%s\" for reading: %s\n",
+				name, strerror(errno));
+			return EXIT_FAILURE;
+		}
+	}
+
+	cmdlineno = 0;
+	while (getcmdline(&line, &len, stdin) != -1) {
+		char *largv[100];
+		int largc;
+
+		largc = makeargs(line, largv, 100);
+		if (!largc)
+			continue;	/* blank line */
+
+		if (dl_cmd(dl, largc, largv)) {
+			fprintf(stderr, "Command failed %s:%d\n",
+				name, cmdlineno);
+			ret = EXIT_FAILURE;
+			if (!force)
+				break;
+		}
+	}
+
+	if (line)
+		free(line);
+
+	return ret;
+}
+
+int main(int argc, char **argv)
+{
+	static const struct option long_options[] = {
+		{ "Version",		no_argument,		NULL, 'V' },
+		{ "force",		no_argument,		NULL, 'f' },
+		{ "batch",		required_argument,	NULL, 'b' },
+		{ "no-nice-names",	no_argument,		NULL, 'n' },
+		{ "json",		no_argument,		NULL, 'j' },
+		{ "pretty",		no_argument,		NULL, 'p' },
+		{ "verbose",		no_argument,		NULL, 'v' },
+		{ "statistics",		no_argument,		NULL, 's' },
+		{ NULL, 0, NULL, 0 }
+	};
+	const char *batch_file = NULL;
+	bool force = false;
+	struct dl *dl;
+	int opt;
+	int err;
+	int ret;
+
+	dl = dl_alloc();
+	if (!dl) {
+		pr_err("Failed to allocate memory for devlink\n");
+		return EXIT_FAILURE;
+	}
+
+	while ((opt = getopt_long(argc, argv, "Vfb:njpvs",
+				  long_options, NULL)) >= 0) {
+
+		switch (opt) {
+		case 'V':
+			printf("devlink utility, iproute2-ss%s\n", SNAPSHOT);
+			ret = EXIT_SUCCESS;
+			goto dl_free;
+		case 'f':
+			force = true;
+			break;
+		case 'b':
+			batch_file = optarg;
+			break;
+		case 'n':
+			dl->no_nice_names = true;
+			break;
+		case 'j':
+			dl->json_output = true;
+			break;
+		case 'p':
+			dl->pretty_output = true;
+			break;
+		case 'v':
+			dl->verbose = true;
+			break;
+		case 's':
+			dl->stats = true;
+			break;
+		default:
+			pr_err("Unknown option.\n");
+			help();
+			ret = EXIT_FAILURE;
+			goto dl_free;
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	err = dl_init(dl);
+	if (err) {
+		ret = EXIT_FAILURE;
+		goto dl_free;
+	}
+
+	if (batch_file)
+		err = dl_batch(dl, batch_file, force);
+	else
+		err = dl_cmd(dl, argc, argv);
+
+	if (err) {
+		ret = EXIT_FAILURE;
+		goto dl_fini;
+	}
+
+	ret = EXIT_SUCCESS;
+
+dl_fini:
+	dl_fini(dl);
+dl_free:
+	dl_free(dl);
+
+	return ret;
+}
diff --git a/devlink/mnlg.c b/devlink/mnlg.c
new file mode 100644
index 0000000..c7d25e8
--- /dev/null
+++ b/devlink/mnlg.c
@@ -0,0 +1,327 @@
+/*
+ *   mnlg.c	Generic Netlink helpers for libmnl
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@mellanox.com>
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <libmnl/libmnl.h>
+#include <linux/genetlink.h>
+
+#include "libnetlink.h"
+#include "utils.h"
+#include "mnlg.h"
+
+struct mnlg_socket {
+	struct mnl_socket *nl;
+	char *buf;
+	uint32_t id;
+	uint8_t version;
+	unsigned int seq;
+	unsigned int portid;
+};
+
+static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+					   uint16_t flags, uint32_t id,
+					   uint8_t version)
+{
+	struct nlmsghdr *nlh;
+	struct genlmsghdr *genl;
+
+	nlh = mnl_nlmsg_put_header(nlg->buf);
+	nlh->nlmsg_type	= id;
+	nlh->nlmsg_flags = flags;
+	nlg->seq = time(NULL);
+	nlh->nlmsg_seq = nlg->seq;
+
+	genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+	genl->cmd = cmd;
+	genl->version = version;
+
+	return nlh;
+}
+
+struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+				  uint16_t flags)
+{
+	return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version);
+}
+
+int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh)
+{
+	return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len);
+}
+
+static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data)
+{
+	return MNL_CB_OK;
+}
+
+static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data)
+{
+	const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+
+	/* Netlink subsystems returns the errno value with different signess */
+	if (err->error < 0)
+		errno = -err->error;
+	else
+		errno = err->error;
+
+	if (nl_dump_ext_ack(nlh, NULL))
+		return MNL_CB_ERROR;
+
+	return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+}
+
+static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data)
+{
+	int len = *(int *)NLMSG_DATA(nlh);
+
+	if (len < 0) {
+		errno = -len;
+		nl_dump_ext_ack_done(nlh, len);
+		return MNL_CB_ERROR;
+	}
+	return MNL_CB_STOP;
+}
+
+static mnl_cb_t mnlg_cb_array[NLMSG_MIN_TYPE] = {
+	[NLMSG_NOOP]	= mnlg_cb_noop,
+	[NLMSG_ERROR]	= mnlg_cb_error,
+	[NLMSG_DONE]	= mnlg_cb_stop,
+	[NLMSG_OVERRUN]	= mnlg_cb_noop,
+};
+
+int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data)
+{
+	int err;
+
+	do {
+		err = mnl_socket_recvfrom(nlg->nl, nlg->buf,
+					  MNL_SOCKET_BUFFER_SIZE);
+		if (err <= 0)
+			break;
+		err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid,
+				  data_cb, data, mnlg_cb_array,
+				  ARRAY_SIZE(mnlg_cb_array));
+	} while (err > 0);
+
+	return err;
+}
+
+struct group_info {
+	bool found;
+	uint32_t id;
+	const char *name;
+};
+
+static int parse_mc_grps_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type = mnl_attr_get_type(attr);
+
+	if (mnl_attr_type_valid(attr, CTRL_ATTR_MCAST_GRP_MAX) < 0)
+		return MNL_CB_OK;
+
+	switch (type) {
+	case CTRL_ATTR_MCAST_GRP_ID:
+		if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+			return MNL_CB_ERROR;
+		break;
+	case CTRL_ATTR_MCAST_GRP_NAME:
+		if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0)
+			return MNL_CB_ERROR;
+		break;
+	}
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static void parse_genl_mc_grps(struct nlattr *nested,
+			       struct group_info *group_info)
+{
+	struct nlattr *pos;
+	const char *name;
+
+	mnl_attr_for_each_nested(pos, nested) {
+		struct nlattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1] = {};
+
+		mnl_attr_parse_nested(pos, parse_mc_grps_cb, tb);
+		if (!tb[CTRL_ATTR_MCAST_GRP_NAME] ||
+		    !tb[CTRL_ATTR_MCAST_GRP_ID])
+			continue;
+
+		name = mnl_attr_get_str(tb[CTRL_ATTR_MCAST_GRP_NAME]);
+		if (strcmp(name, group_info->name) != 0)
+			continue;
+
+		group_info->id = mnl_attr_get_u32(tb[CTRL_ATTR_MCAST_GRP_ID]);
+		group_info->found = true;
+	}
+}
+
+static int get_group_id_attr_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type = mnl_attr_get_type(attr);
+
+	if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+		return MNL_CB_ERROR;
+
+	if (type == CTRL_ATTR_MCAST_GROUPS &&
+	    mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
+		return MNL_CB_ERROR;
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static int get_group_id_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct group_info *group_info = data;
+	struct nlattr *tb[CTRL_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), get_group_id_attr_cb, tb);
+	if (!tb[CTRL_ATTR_MCAST_GROUPS])
+		return MNL_CB_ERROR;
+	parse_genl_mc_grps(tb[CTRL_ATTR_MCAST_GROUPS], group_info);
+	return MNL_CB_OK;
+}
+
+int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name)
+{
+	struct nlmsghdr *nlh;
+	struct group_info group_info;
+	int err;
+
+	nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
+				 NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
+	mnl_attr_put_u16(nlh, CTRL_ATTR_FAMILY_ID, nlg->id);
+
+	err = mnlg_socket_send(nlg, nlh);
+	if (err < 0)
+		return err;
+
+	group_info.found = false;
+	group_info.name = group_name;
+	err = mnlg_socket_recv_run(nlg, get_group_id_cb, &group_info);
+	if (err < 0)
+		return err;
+
+	if (!group_info.found) {
+		errno = ENOENT;
+		return -1;
+	}
+
+	err = mnl_socket_setsockopt(nlg->nl, NETLINK_ADD_MEMBERSHIP,
+				    &group_info.id, sizeof(group_info.id));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int get_family_id_attr_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type = mnl_attr_get_type(attr);
+
+	if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+		return MNL_CB_ERROR;
+
+	if (type == CTRL_ATTR_FAMILY_ID &&
+	    mnl_attr_validate(attr, MNL_TYPE_U16) < 0)
+		return MNL_CB_ERROR;
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static int get_family_id_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t *p_id = data;
+	struct nlattr *tb[CTRL_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+	mnl_attr_parse(nlh, sizeof(*genl), get_family_id_attr_cb, tb);
+	if (!tb[CTRL_ATTR_FAMILY_ID])
+		return MNL_CB_ERROR;
+	*p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+	return MNL_CB_OK;
+}
+
+struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version)
+{
+	struct mnlg_socket *nlg;
+	struct nlmsghdr *nlh;
+	int one = 1;
+	int err;
+
+	nlg = malloc(sizeof(*nlg));
+	if (!nlg)
+		return NULL;
+
+	nlg->buf = malloc(MNL_SOCKET_BUFFER_SIZE);
+	if (!nlg->buf)
+		goto err_buf_alloc;
+
+	nlg->nl = mnl_socket_open(NETLINK_GENERIC);
+	if (!nlg->nl)
+		goto err_mnl_socket_open;
+
+	/* Older kernels may no support capped/extended ACK reporting */
+	mnl_socket_setsockopt(nlg->nl, NETLINK_CAP_ACK, &one, sizeof(one));
+	mnl_socket_setsockopt(nlg->nl, NETLINK_EXT_ACK, &one, sizeof(one));
+
+	err = mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID);
+	if (err < 0)
+		goto err_mnl_socket_bind;
+
+	nlg->portid = mnl_socket_get_portid(nlg->nl);
+
+	nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
+				 NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
+	mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name);
+
+	err = mnlg_socket_send(nlg, nlh);
+	if (err < 0)
+		goto err_mnlg_socket_send;
+
+	err = mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id);
+	if (err < 0)
+		goto err_mnlg_socket_recv_run;
+
+	nlg->version = version;
+	return nlg;
+
+err_mnlg_socket_recv_run:
+err_mnlg_socket_send:
+err_mnl_socket_bind:
+	mnl_socket_close(nlg->nl);
+err_mnl_socket_open:
+	free(nlg->buf);
+err_buf_alloc:
+	free(nlg);
+	return NULL;
+}
+
+void mnlg_socket_close(struct mnlg_socket *nlg)
+{
+	mnl_socket_close(nlg->nl);
+	free(nlg->buf);
+	free(nlg);
+}
+
+int mnlg_socket_get_fd(struct mnlg_socket *nlg)
+{
+	return mnl_socket_get_fd(nlg->nl);
+}
diff --git a/devlink/mnlg.h b/devlink/mnlg.h
new file mode 100644
index 0000000..61bc5a3
--- /dev/null
+++ b/devlink/mnlg.h
@@ -0,0 +1,28 @@
+/*
+ *   mnlg.h	Generic Netlink helpers for libmnl
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@mellanox.com>
+ */
+
+#ifndef _MNLG_H_
+#define _MNLG_H_
+
+#include <libmnl/libmnl.h>
+
+struct mnlg_socket;
+
+struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+				  uint16_t flags);
+int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh);
+int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data);
+int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name);
+struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version);
+void mnlg_socket_close(struct mnlg_socket *nlg);
+int mnlg_socket_get_fd(struct mnlg_socket *nlg);
+
+#endif /* _MNLG_H_ */
diff --git a/doc/actions/actions-general b/doc/actions/actions-general
new file mode 100644
index 0000000..407a514
--- /dev/null
+++ b/doc/actions/actions-general
@@ -0,0 +1,256 @@
+
+This documented is slightly dated but should give you idea of how things
+work.
+
+What is it?
+-----------
+
+An extension to the filtering/classification architecture of Linux Traffic
+Control.
+Up to 2.6.8 the only action that could be "attached" to a filter was policing.
+i.e you could say something like:
+
+-----
+tc filter add dev lo parent ffff: protocol ip prio 10 u32 match ip src \
+127.0.0.1/32 flowid 1:1 police mtu 4000 rate 1500kbit burst 90k
+-----
+
+which implies "if a packet is seen on the ingress of the lo device with
+a source IP address of 127.0.0.1/32 we give it a classification id  of 1:1 and
+we execute a policing action which rate limits its bandwidth utilization
+to 1.5Mbps".
+
+The new extensions allow for more than just policing actions to be added.
+They are also fully backward compatible. If you have a kernel that doesn't
+understand them, then the effect is null i.e if you have a newer tc
+but older kernel, the actions are not installed. Likewise if you
+have a newer kernel but older tc, obviously the tc will use current
+syntax which will work fine. Of course to get the required effect you need
+both newer tc and kernel. If you are reading this you have the
+right tc ;->
+
+A side effect is that we can now get stateless firewalling to work with tc.
+Essentially this is now an alternative to iptables.
+I won't go into details of my dislike for iptables at times, but
+scalability is one of the main issues; however, if you need stateful
+classification - use netfilter (for now).
+
+This stuff works on both ingress and egress qdiscs.
+
+Features
+--------
+
+1) new additional syntax and actions enabled. Note old syntax is still valid.
+
+Essentially this is still the same syntax as tc with a new construct
+"action". The syntax is of the form:
+tc filter add <DEVICE> parent 1:0 protocol ip prio 10 <Filter description>
+flowid 1:1 action <ACTION description>*
+
+You can have as many actions as you want (within sensible reasoning).
+
+In the past the only real action was the policer; i.e you could do something
+along the lines of:
+tc filter add dev lo parent ffff: protocol ip prio 10 u32 \
+match ip src 127.0.0.1/32 flowid 1:1 \
+police mtu 4000 rate 1500kbit burst 90k
+
+Although you can still use the same syntax, now you can say:
+
+tc filter add dev lo parent 1:0 protocol ip prio 10 u32 \
+match ip src 127.0.0.1/32 flowid 1:1 \
+action police mtu 4000 rate 1500kbit burst 90k
+
+" generic Actions" (gact) at the moment are:
+{ drop, pass, reclassify, continue}
+(If you have others, no listed here give me a reason and we will add them)
++drop says to drop the packet
++pass and ok (are equivalent) says to accept it
++reclassify requests for reclassification of the packet
++continue requests for next lookup to match
+
+2)In order to take advantage of some of the targets written by the
+iptables people, a classifier can have a packet being massaged by an
+iptable target. I have only tested with mangler targets up to now.
+(infact anything that is not in the mangling table is disabled right now)
+
+In terms of hooks:
+*ingress is mapped to pre-routing hook
+*egress is mapped to post-routing hook
+I don't see much value in the other hooks, if you see it and email me good
+reasons, the addition is trivial.
+
+Example syntax for iptables targets usage becomes:
+tc filter add ..... u32 <u32 syntax> action ipt -j <iptables target syntax>
+
+example:
+tc filter add dev lo parent ffff: protocol ip prio 8 u32 \
+match ip dst 127.0.0.8/32 flowid 1:12 \
+action ipt -j mark --set-mark 2
+
+NOTE: flowid 1:12 is parsed flowid 0x1:0x12.  Make sure if you want flowid
+decimal 12, then use flowid 1:c.
+
+3) A feature i call pipe
+The motivation is derived from Unix pipe mechanism but applied to packets.
+Essentially take a matching packet and pass it through
+action1 | action2 | action3 etc.
+You could do something similar to this with the tc policer and the "continue"
+operator but this rather restricts it to just the policer and requires
+multiple rules (and lookups, hence quiet inefficient);
+
+as an example -- and please note that this is just an example _not_ The
+Word Youve Been Waiting For (yes i have had problems giving examples
+which ended becoming dogma in documents and people modifying them a little
+to look clever);
+
+i selected the metering rates to be small so that i can show better how
+things work.
+
+The script below does the following:
+- an incoming packet from 10.0.0.21 is first given a firewall mark of 1.
+
+- It is then metered to make sure it does not exceed its allocated rate of
+1Kbps. If it doesn't exceed rate, this is where we terminate action execution.
+
+- If it does exceed its rate, its "color" changes to a mark of 2 and it is
+then passed through a second meter.
+
+-The second meter is shared across all flows on that device [i am surpised
+that this seems to be not a well know feature of the policer; Bert was telling
+me that someone was writing a qdisc just to do sharing across multiple devices;
+it must be the summer heat again; weve had someone doing that every year around
+summer  -- the key to sharing is to use a operator "index" in your policer
+rules (example "index 20"). All your rules have to use the same index to
+share.]
+
+-If the second meter is exceeded the color of the flow changes further to 3.
+
+-We then pass the packet to another meter which is shared across all devices
+in the system. If this meter is exceeded we drop the packet.
+
+Note the mark can be used further up the system to do things like policy
+or more interesting things on the egress.
+
+------------------ cut here -------------------------------
+#
+# Add an ingress qdisc on eth0
+tc qdisc add dev eth0 ingress
+#
+#if you see an incoming packet from 10.0.0.21
+tc filter add dev eth0 parent ffff: protocol ip prio 1 \
+u32 match ip src 10.0.0.21/32 flowid 1:15 \
+#
+# first give it a mark of 1
+action ipt -j mark --set-mark 1 index 2 \
+#
+# then pass it through a policer which allows 1kbps; if the flow
+# doesn't exceed that rate, this is where we stop, if it exceeds we
+# pipe the packet to the next action
+action police rate 1kbit burst 9k pipe \
+#
+# which marks the packet fwmark as 2 and pipes
+action ipt -j mark --set-mark 2 \
+#
+# next attempt to borrow b/width from a meter
+# used across all flows incoming on eth0("index 30")
+# and if that is exceeded we pipe to the next action
+action police index 30 mtu 5000 rate 1kbit burst 10k pipe \
+# mark it as fwmark 3 if exceeded
+action ipt -j mark --set-mark 3 \
+# and then attempt to borrow from a meter used by all devices in the
+# system. Should this be exceeded, drop the packet on the floor.
+action police index 20 mtu 5000 rate 1kbit burst 90k drop
+---------------------------------
+
+Now lets see the actions installed with
+"tc filter show parent ffff: dev eth0"
+
+-------- output -----------
+jroot# tc filter show parent ffff: dev eth0
+filter protocol ip pref 1 u32
+filter protocol ip pref 1 u32 fh 800: ht divisor 1
+filter protocol ip pref 1 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:15
+
+   action order 1: tablename: mangle  hook: NF_IP_PRE_ROUTING
+        target MARK set 0x1  index 2
+
+   action order 2: police 1 action pipe rate 1Kbit burst 9Kb mtu 2Kb
+
+   action order 3: tablename: mangle  hook: NF_IP_PRE_ROUTING
+        target MARK set 0x2  index 1
+
+   action order 4: police 30 action pipe rate 1Kbit burst 10Kb mtu 5000b
+
+   action order 5: tablename: mangle  hook: NF_IP_PRE_ROUTING
+        target MARK set 0x3  index 3
+
+   action order 6: police 20 action drop rate 1Kbit burst 90Kb mtu 5000b
+
+  match 0a000015/ffffffff at 12
+-------------------------------
+
+Note the ordering of the actions is based on the order in which we entered
+them. In the future i will add explicit priorities.
+
+Now lets run a ping -f from 10.0.0.21 to this host; stop the ping after
+you see a few lines of dots
+
+----
+[root@jzny hadi]# ping -f  10.0.0.22
+PING 10.0.0.22 (10.0.0.22): 56 data bytes
+....................................................................................................................................................................................................................................................................................................................................................................................................................................................
+--- 10.0.0.22 ping statistics ---
+2248 packets transmitted, 1811 packets received, 19% packet loss
+round-trip min/avg/max = 0.7/9.3/20.1 ms
+-----------------------------
+
+Now lets take a look at the stats with "tc -s filter show parent ffff: dev eth0"
+
+--------------
+jroot# tc -s filter show parent ffff: dev eth0
+filter protocol ip pref 1 u32
+filter protocol ip pref 1 u32 fh 800: ht divisor 1
+filter protocol ip pref 1 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1
+5
+
+   action order 1: tablename: mangle  hook: NF_IP_PRE_ROUTING
+        target MARK set 0x1  index 2
+         Sent 188832 bytes 2248 pkts (dropped 0, overlimits 0)
+
+   action order 2: police 1 action pipe rate 1Kbit burst 9Kb mtu 2Kb
+         Sent 188832 bytes 2248 pkts (dropped 0, overlimits 2122)
+
+   action order 3: tablename: mangle  hook: NF_IP_PRE_ROUTING
+        target MARK set 0x2  index 1
+         Sent 178248 bytes 2122 pkts (dropped 0, overlimits 0)
+
+   action order 4: police 30 action pipe rate 1Kbit burst 10Kb mtu 5000b
+         Sent 178248 bytes 2122 pkts (dropped 0, overlimits 1945)
+
+   action order 5: tablename: mangle  hook: NF_IP_PRE_ROUTING
+        target MARK set 0x3  index 3
+         Sent 163380 bytes 1945 pkts (dropped 0, overlimits 0)
+
+   action order 6: police 20 action drop rate 1Kbit burst 90Kb mtu 5000b
+         Sent 163380 bytes 1945 pkts (dropped 0, overlimits 437)
+
+  match 0a000015/ffffffff at 12
+-------------------------------
+
+Neat, eh?
+
+
+Want to  write an action module?
+------------------------------
+Its easy. Either look at the code or send me email. I will document at
+some point; will also accept documentation.
+
+TODO
+----
+
+Lotsa goodies/features coming. Requests also being accepted.
+At the moment the focus has been on getting the architecture in place.
+Expect new things in the spurious time i have to work on this
+(particularly around end of year when i have typically get time off
+from work).
diff --git a/doc/actions/gact-usage b/doc/actions/gact-usage
new file mode 100644
index 0000000..5fc3e62
--- /dev/null
+++ b/doc/actions/gact-usage
@@ -0,0 +1,78 @@
+
+gact <ACTION> [RAND] [INDEX]
+
+Where:
+	ACTION := reclassify | drop | continue | pass | ok
+	RAND := random <RANDTYPE> <ACTION> <VAL>
+	RANDTYPE := netrand | determ
+        VAL : = value not exceeding 10000
+        INDEX := index value used
+
+ACTION semantics
+- pass and ok are equivalent to accept
+- continue allows to restart classification lookup
+- drop drops packets
+- reclassify implies continue classification where we left off
+
+randomization
+--------------
+
+At the moment there are only two algorithms. One is deterministic
+and the other uses internal kernel netrand.
+
+Examples:
+
+Rules can be installed on both ingress and egress - this shows ingress
+only
+
+tc qdisc add dev eth0 ingress
+
+# example 1
+tc filter add dev eth0 parent ffff: protocol ip prio 6 u32 match ip src \
+10.0.0.9/32 flowid 1:16 action drop
+
+ping -c 20 10.0.0.9
+
+--
+filter u32
+filter u32 fh 800: ht divisor 1
+filter u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:16  (rule hit 32 success 20)
+  match 0a000009/ffffffff at 12 (success 20 )
+        action order 1: gact action drop
+         random type none pass val 0
+         index 1 ref 1 bind 1 installed 59 sec used 35 sec
+         Sent 1680 bytes 20 pkts (dropped 20, overlimits 0 )
+
+----
+
+# example 2
+#allow 1 out 10 randomly using the netrand generator
+tc filter add dev eth0 parent ffff: protocol ip prio 6 u32 match ip src \
+10.0.0.9/32 flowid 1:16 action drop random netrand ok 10
+
+ping -c 20 10.0.0.9
+
+----
+filter protocol ip pref 6 u32 filter protocol ip pref 6 u32 fh 800: ht divisor 1filter protocol ip pref 6 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:16  (rule hit 20 success 20)
+  match 0a000009/ffffffff at 12 (success 20 )
+        action order 1: gact action drop
+         random type netrand pass val 10
+         index 5 ref 1 bind 1 installed 49 sec used 25 sec
+         Sent 1680 bytes 20 pkts (dropped 16, overlimits 0 )
+
+--------
+#alternative: deterministically accept every second packet
+tc filter add dev eth0 parent ffff: protocol ip prio 6 u32 match ip src \
+10.0.0.9/32 flowid 1:16 action drop random determ ok 2
+
+ping -c 20 10.0.0.9
+
+tc -s filter show parent ffff: dev eth0
+-----
+filter protocol ip pref 6 u32 filter protocol ip pref 6 u32 fh 800: ht divisor 1filter protocol ip pref 6 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:16  (rule hit 20 success 20)
+  match 0a000009/ffffffff at 12 (success 20 )
+        action order 1: gact action drop
+         random type determ pass val 2
+         index 4 ref 1 bind 1 installed 118 sec used 82 sec
+         Sent 1680 bytes 20 pkts (dropped 10, overlimits 0 )
+-----
diff --git a/doc/actions/ifb-README b/doc/actions/ifb-README
new file mode 100644
index 0000000..5fe9171
--- /dev/null
+++ b/doc/actions/ifb-README
@@ -0,0 +1,125 @@
+
+IFB is intended to replace IMQ.
+Advantage over current IMQ; cleaner in particular in in SMP;
+with a _lot_ less code.
+
+Known IMQ/IFB USES
+------------------
+
+As far as i know the reasons listed below is why people use IMQ.
+It would be nice to know of anything else that i missed.
+
+1) qdiscs/policies that are per device as opposed to system wide.
+IFB allows for sharing.
+
+2) Allows for queueing incoming traffic for shaping instead of
+dropping. I am not aware of any study that shows policing is
+worse than shaping in achieving the end goal of rate control.
+I would be interested if anyone is experimenting.
+
+3) Very interesting use: if you are serving p2p you may want to give
+preference to your own locally originated traffic (when responses come back)
+vs someone using your system to do bittorent. So QoSing based on state
+comes in as the solution. What people did to achieve this was stick
+the IMQ somewhere prelocal hook.
+I think this is a pretty neat feature to have in Linux in general.
+(i.e not just for IMQ).
+But i won't go back to putting netfilter hooks in the device to satisfy
+this.  I also don't think its worth it hacking ifb some more to be
+aware of say L3 info and play ip rule tricks to achieve this.
+--> Instead the plan is to have a conntrack related action. This action will
+selectively either query/create conntrack state on incoming packets.
+Packets could then be redirected to ifb based on what happens -> eg
+on incoming packets; if we find they are of known state we could send to
+a different queue than one which didn't have existing state. This
+all however is dependent on whatever rules the admin enters.
+
+At the moment this 3rd function does not exist yet. I have decided that
+instead of sitting on the patch for another year, to release it and then
+if there is pressure i will add this feature.
+
+An example, to provide functionality that most people use IMQ for below:
+
+--------
+export TC="/sbin/tc"
+
+$TC qdisc add dev ifb0 root handle 1: prio
+$TC qdisc add dev ifb0 parent 1:1 handle 10: sfq
+$TC qdisc add dev ifb0 parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000
+$TC qdisc add dev ifb0 parent 1:3 handle 30: sfq
+$TC filter add dev ifb0 protocol ip pref 1 parent 1: handle 1 fw classid 1:1
+$TC filter add dev ifb0 protocol ip pref 2 parent 1: handle 2 fw classid 1:2
+
+ifconfig ifb0 up
+
+$TC qdisc add dev eth0 ingress
+
+# redirect all IP packets arriving in eth0 to ifb0
+# use mark 1 --> puts them onto class 1:1
+$TC filter add dev eth0 parent ffff: protocol ip prio 10 u32 \
+match u32 0 0 flowid 1:1 \
+action ipt -j MARK --set-mark 1 \
+action mirred egress redirect dev ifb0
+
+--------
+
+
+Run A Little test:
+
+from another machine ping so that you have packets going into the box:
+-----
+[root@jzny action-tests]# ping 10.22
+PING 10.22 (10.0.0.22): 56 data bytes
+64 bytes from 10.0.0.22: icmp_seq=0 ttl=64 time=2.8 ms
+64 bytes from 10.0.0.22: icmp_seq=1 ttl=64 time=0.6 ms
+64 bytes from 10.0.0.22: icmp_seq=2 ttl=64 time=0.6 ms
+
+--- 10.22 ping statistics ---
+3 packets transmitted, 3 packets received, 0% packet loss
+round-trip min/avg/max = 0.6/1.3/2.8 ms
+[root@jzny action-tests]#
+-----
+Now look at some stats:
+
+---
+[root@jmandrake]:~# $TC -s filter show parent ffff: dev eth0
+filter protocol ip pref 10 u32
+filter protocol ip pref 10 u32 fh 800: ht divisor 1
+filter protocol ip pref 10 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1
+  match 00000000/00000000 at 0
+        action order 1: tablename: mangle  hook: NF_IP_PRE_ROUTING
+        target MARK set 0x1
+        index 1 ref 1 bind 1 installed 4195sec  used 27sec
+         Sent 252 bytes 3 pkts (dropped 0, overlimits 0)
+
+        action order 2: mirred (Egress Redirect to device ifb0) stolen
+        index 1 ref 1 bind 1 installed 165 sec used 27 sec
+         Sent 252 bytes 3 pkts (dropped 0, overlimits 0)
+
+[root@jmandrake]:~# $TC -s qdisc
+qdisc sfq 30: dev ifb0 limit 128p quantum 1514b
+ Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
+qdisc tbf 20: dev ifb0 rate 20Kbit burst 1575b lat 2147.5s
+ Sent 210 bytes 3 pkts (dropped 0, overlimits 0)
+qdisc sfq 10: dev ifb0 limit 128p quantum 1514b
+ Sent 294 bytes 3 pkts (dropped 0, overlimits 0)
+qdisc prio 1: dev ifb0 bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
+ Sent 504 bytes 6 pkts (dropped 0, overlimits 0)
+qdisc ingress ffff: dev eth0 ----------------
+ Sent 308 bytes 5 pkts (dropped 0, overlimits 0)
+
+[root@jmandrake]:~# ifconfig ifb0
+ifb0    Link encap:Ethernet  HWaddr 00:00:00:00:00:00
+          inet6 addr: fe80::200:ff:fe00:0/64 Scope:Link
+          UP BROADCAST RUNNING NOARP  MTU:1500  Metric:1
+          RX packets:6 errors:0 dropped:3 overruns:0 frame:0
+          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
+          collisions:0 txqueuelen:32
+          RX bytes:504 (504.0 b)  TX bytes:252 (252.0 b)
+-----
+
+You send it any packet not originating from the actions it will drop them.
+[In this case the three dropped packets were ipv6 ndisc].
+
+cheers,
+jamal
diff --git a/doc/actions/mirred-usage b/doc/actions/mirred-usage
new file mode 100644
index 0000000..482ff66
--- /dev/null
+++ b/doc/actions/mirred-usage
@@ -0,0 +1,164 @@
+
+Very funky action. I do plan to add to a few more things to it
+This is the basic stuff. Idea borrowed from the way ethernet switches
+mirror and redirect packets. The main difference with say a vannila
+ethernet switch is that you can use u32 classifier to select a
+flow to be mirrored. High end switches typically can select based
+on more than just a port (eg a 5 tuple classifier). They may also be
+capable of redirecting.
+
+Usage:
+
+mirred <DIRECTION> <ACTION> [index INDEX] <dev DEVICENAME>
+where:
+DIRECTION := <ingress | egress>
+ACTION := <mirror | redirect>
+INDEX is the specific policy instance id
+DEVICENAME is the devicename
+
+Direction:
+- Ingress is not supported at the moment. It will be in the
+future as well as mirror/redirecting to a socket.
+
+Action:
+- Mirror takes a copy of the packet and sends it to specified
+dev ("port" in ethernet switch/bridging terminology)
+- redirect
+steals the packet and redirects to specified destination dev.
+
+What NOT to do if you don't want your machine to crash:
+------------------------------------------------------
+
+Do not create loops!
+Loops are not hard to create in the egress qdiscs.
+
+Here are simple rules to follow if you don't want to get
+hurt:
+A) Do not have the same packet go to same netdevice twice
+in a single graph of policies. Your machine will just hang!
+This is design intent _not a bug_ to teach you some lessons.
+
+In the future if there are easy ways to do this in the kernel
+without affecting other packets not interested in this feature
+I will add them. At the moment that is not clear.
+
+Some examples of bad things NOT to do:
+1) redirecting eth0 to eth0
+2) eth0->eth1-> eth0
+3) eth0->lo-> eth1-> eth0
+
+B) Do not redirect from one IFB device to another.
+Remember that IFB is a very specialized case of packet redirecting
+device. Instead of redirecting it puts packets at the exact spot
+on the stack it found them from.
+Redirecting from ifbX->ifbY will actually not crash your machine but your
+packets will all be dropped (this is much simpler to detect
+and resolve and is only affecting users of ifb as opposed to the
+whole stack).
+
+In the case of A) the problem has to do with a recursive contention
+for the devices queue lock and in the second case for the transmit lock.
+
+Some examples:
+-------------
+
+1) Mirror all packets arriving on eth0 to be sent out on eth1.
+You may have a sniffer or some accounting box hooked up on eth1.
+
+---
+tc qdisc add dev eth0 ingress
+tc filter add dev eth0 parent ffff: protocol ip prio 10 u32 \
+match u32 0 0 flowid 1:2 action mirred egress mirror dev eth1
+---
+
+If you replace "mirror" with "redirect" then not a copy but rather
+the original packet is sent to eth1.
+
+2) Host A is hooked  up to us on eth0
+
+# redirect all packets arriving on ingress of lo to eth0
+---
+tc qdisc add dev lo ingress
+tc filter add dev lo parent ffff: protocol ip prio 10 u32 \
+match u32 0 0 flowid 1:2 action mirred egress redirect dev eth0
+---
+
+On host A start a tcpdump on interface connecting to us.
+
+on our host ping -c 2 127.0.0.1
+
+Ping would fail since all packets are heading out eth0
+tcpudmp on host A would show them
+
+if you substitute the redirect with mirror above as in:
+tc filter add dev lo parent ffff: protocol ip prio 10 u32 \
+match u32 0 0 flowid 1:2 action mirred egress mirror dev eth0
+
+Then you should see the packets on both host A and the local
+stack (i.e ping would work).
+
+3) Even more funky example:
+
+#
+#allow 1 out 10 packets on ingress of lo to randomly make it to the
+# host A (Randomness uses the netrand generator)
+#
+---
+tc filter add dev lo parent ffff: protocol ip prio 10 u32 \
+match u32 0 0 flowid 1:2 \
+action drop random determ ok 10\
+action mirred egress mirror dev eth0
+---
+
+4)
+# for packets from 10.0.0.9 going out on eth0 (could be local
+# IP or something # we are forwarding) -
+# if exceeding a 100Kbps rate, then redirect to eth1
+#
+
+---
+tc qdisc add dev eth0 handle 1:0 root prio
+tc filter add dev eth0 parent 1:0 protocol ip prio 6 u32 \
+match ip src 10.0.0.9/32 flowid 1:16 \
+action police rate 100kbit burst 90k ok \
+action mirred egress mirror dev eth1
+---
+
+A more interesting example is when you mirror flows to a dummy device
+so you could tcpdump them (dummy by defaults drops all packets it sees).
+This is a very useful debug feature.
+
+Lets say you are policing packets from alias 192.168.200.200/32
+you don't want those to exceed 100kbps going out.
+
+---
+tc qdisc add dev eth0 handle 1:0 root prio
+tc filter add dev eth0 parent 1: protocol ip prio 10 u32 \
+match ip src 192.168.200.200/32 flowid 1:2 \
+action police rate 100kbit burst 90k drop
+---
+
+If you run tcpdump on eth0 you will see all packets going out
+with src 192.168.200.200/32 dropped or not (since tcpdump shows
+all packets being egressed).
+Extend the rule a little to see only the packets making it out.
+
+---
+tc qdisc add dev eth0 handle 1:0 root prio
+tc filter add dev eth0 parent 1: protocol ip prio 10 u32 \
+match ip src 192.168.200.200/32 flowid 1:2 \
+action police rate 10kbit burst 90k drop \
+action mirred egress mirror dev dummy0
+---
+
+Now fire tcpdump on dummy0 to see only those packets ..
+tcpdump -n -i dummy0 -x -e -t
+
+Essentially a good debugging/logging interface (sort of like
+BSDs speacialized log device does without needing one).
+
+If you replace mirror with redirect, those packets will be
+blackholed and will never make it out.
+
+cheers,
+jamal
diff --git a/etc/iproute2/bpf_pinning b/etc/iproute2/bpf_pinning
new file mode 100644
index 0000000..2b39c70
--- /dev/null
+++ b/etc/iproute2/bpf_pinning
@@ -0,0 +1,6 @@
+#
+# subpath mappings from mount point for pinning
+#
+#3	tracing
+#4	foo/bar
+#5	tc/cls1
diff --git a/etc/iproute2/ematch_map b/etc/iproute2/ematch_map
new file mode 100644
index 0000000..4d6bb2f
--- /dev/null
+++ b/etc/iproute2/ematch_map
@@ -0,0 +1,8 @@
+# lookup table for ematch kinds
+1	cmp
+2	nbyte
+3	u32
+4	meta
+7	canid
+8	ipset
+9	ipt
diff --git a/etc/iproute2/group b/etc/iproute2/group
new file mode 100644
index 0000000..6f000b2
--- /dev/null
+++ b/etc/iproute2/group
@@ -0,0 +1,2 @@
+# device group names
+0	default
diff --git a/etc/iproute2/nl_protos b/etc/iproute2/nl_protos
new file mode 100644
index 0000000..7c17cf0
--- /dev/null
+++ b/etc/iproute2/nl_protos
@@ -0,0 +1,23 @@
+# Netlink protocol names mapping
+
+0   rtnl
+1   unused
+2   usersock
+3   fw
+4   tcpdiag
+5   nflog
+6   xfrm
+7   selinux
+8   iscsi
+9   audit
+10  fiblookup
+11  connector
+12  nft
+13  ip6fw
+14  dec-rt
+15  uevent
+16  genl
+18  scsi-trans
+19  ecryptfs
+20  rdma
+21  crypto
diff --git a/etc/iproute2/rt_dsfield b/etc/iproute2/rt_dsfield
new file mode 100644
index 0000000..1426d60
--- /dev/null
+++ b/etc/iproute2/rt_dsfield
@@ -0,0 +1,26 @@
+# Differentiated field values
+# These include the DSCP and unused bits
+0x0	default
+# Newer RFC2597 values
+0x28	AF11
+0x30	AF12
+0x38	AF13
+0x48	AF21
+0x50	AF22
+0x58	AF23
+0x68	AF31
+0x70	AF32
+0x78	AF33
+0x88	AF41
+0x90	AF42
+0x98	AF43
+# Older values RFC2474
+0x20	CS1
+0x40	CS2
+0x60	CS3
+0x80	CS4
+0xA0	CS5
+0xC0	CS6
+0xE0	CS7
+# RFC 2598
+0xB8	EF
diff --git a/etc/iproute2/rt_protos b/etc/iproute2/rt_protos
new file mode 100644
index 0000000..b3a0ec8
--- /dev/null
+++ b/etc/iproute2/rt_protos
@@ -0,0 +1,23 @@
+#
+# Reserved protocols.
+#
+0	unspec
+1	redirect
+2	kernel
+3	boot
+4	static
+8	gated
+9	ra
+10	mrt
+11	zebra
+12	bird
+13	dnrouted
+14	xorp
+15	ntk
+16      dhcp
+42	babel
+186	bgp
+187	isis
+188	ospf
+189	rip
+192	eigrp
diff --git a/etc/iproute2/rt_protos.d/README b/etc/iproute2/rt_protos.d/README
new file mode 100644
index 0000000..f9c599c
--- /dev/null
+++ b/etc/iproute2/rt_protos.d/README
@@ -0,0 +1,2 @@
+Each file in this directory is an rt_protos configuration file. iproute2
+commands scan this directory processing all files that end in '.conf'.
diff --git a/etc/iproute2/rt_realms b/etc/iproute2/rt_realms
new file mode 100644
index 0000000..eedd76d
--- /dev/null
+++ b/etc/iproute2/rt_realms
@@ -0,0 +1,13 @@
+#
+# reserved values
+#
+0	cosmos
+#
+# local
+#
+#1	inr.ac
+#2	inr.ruhep
+#3	freenet
+#4	radio-msu
+#5	russia
+#6	internet
diff --git a/etc/iproute2/rt_scopes b/etc/iproute2/rt_scopes
new file mode 100644
index 0000000..8514bc1
--- /dev/null
+++ b/etc/iproute2/rt_scopes
@@ -0,0 +1,11 @@
+#
+# reserved values
+#
+0	global
+255	nowhere
+254	host
+253	link
+#
+# pseudo-reserved
+#
+200	site
diff --git a/etc/iproute2/rt_tables b/etc/iproute2/rt_tables
new file mode 100644
index 0000000..541abfd
--- /dev/null
+++ b/etc/iproute2/rt_tables
@@ -0,0 +1,11 @@
+#
+# reserved values
+#
+255	local
+254	main
+253	default
+0	unspec
+#
+# local
+#
+#1	inr.ruhep
diff --git a/etc/iproute2/rt_tables.d/README b/etc/iproute2/rt_tables.d/README
new file mode 100644
index 0000000..0920cb1
--- /dev/null
+++ b/etc/iproute2/rt_tables.d/README
@@ -0,0 +1,2 @@
+Each file in this directory is an rt_tables configuration file. iproute2
+commands scan this directory processing all files that end in '.conf'.
diff --git a/examples/bpf/README b/examples/bpf/README
new file mode 100644
index 0000000..1bbdda3
--- /dev/null
+++ b/examples/bpf/README
@@ -0,0 +1,8 @@
+eBPF toy code examples (running in kernel) to familiarize yourself
+with syntax and features:
+
+ - bpf_shared.c		-> Ingress/egress map sharing example
+ - bpf_tailcall.c	-> Using tail call chains
+ - bpf_cyclic.c		-> Simple cycle as tail calls
+ - bpf_graft.c		-> Demo on altering runtime behaviour
+ - bpf_map_in_map.c     -> Using map in map example
diff --git a/examples/bpf/bpf_cyclic.c b/examples/bpf/bpf_cyclic.c
new file mode 100644
index 0000000..11d1c06
--- /dev/null
+++ b/examples/bpf/bpf_cyclic.c
@@ -0,0 +1,35 @@
+#include "../../include/bpf_api.h"
+
+/* Cyclic dependency example to test the kernel's runtime upper
+ * bound on loops. Also demonstrates on how to use direct-actions,
+ * loaded as: tc filter add [...] bpf da obj [...]
+ */
+#define JMP_MAP_ID	0xabccba
+
+struct bpf_elf_map __section_maps jmp_tc = {
+	.type		= BPF_MAP_TYPE_PROG_ARRAY,
+	.id		= JMP_MAP_ID,
+	.size_key	= sizeof(uint32_t),
+	.size_value	= sizeof(uint32_t),
+	.pinning	= PIN_OBJECT_NS,
+	.max_elem	= 1,
+};
+
+__section_tail(JMP_MAP_ID, 0)
+int cls_loop(struct __sk_buff *skb)
+{
+	printt("cb: %u\n", skb->cb[0]++);
+	tail_call(skb, &jmp_tc, 0);
+
+	skb->tc_classid = TC_H_MAKE(1, 42);
+	return TC_ACT_OK;
+}
+
+__section_cls_entry
+int cls_entry(struct __sk_buff *skb)
+{
+	tail_call(skb, &jmp_tc, 0);
+	return TC_ACT_SHOT;
+}
+
+BPF_LICENSE("GPL");
diff --git a/examples/bpf/bpf_graft.c b/examples/bpf/bpf_graft.c
new file mode 100644
index 0000000..07113d4
--- /dev/null
+++ b/examples/bpf/bpf_graft.c
@@ -0,0 +1,66 @@
+#include "../../include/bpf_api.h"
+
+/* This example demonstrates how classifier run-time behaviour
+ * can be altered with tail calls. We start out with an empty
+ * jmp_tc array, then add section aaa to the array slot 0, and
+ * later on atomically replace it with section bbb. Note that
+ * as shown in other examples, the tc loader can prepopulate
+ * tail called sections, here we start out with an empty one
+ * on purpose to show it can also be done this way.
+ *
+ * tc filter add dev foo parent ffff: bpf obj graft.o
+ * tc exec bpf dbg
+ *   [...]
+ *   Socket Thread-20229 [001] ..s. 138993.003923: : fallthrough
+ *   <idle>-0            [001] ..s. 138993.202265: : fallthrough
+ *   Socket Thread-20229 [001] ..s. 138994.004149: : fallthrough
+ *   [...]
+ *
+ * tc exec bpf graft m:globals/jmp_tc key 0 obj graft.o sec aaa
+ * tc exec bpf dbg
+ *   [...]
+ *   Socket Thread-19818 [002] ..s. 139012.053587: : aaa
+ *   <idle>-0            [002] ..s. 139012.172359: : aaa
+ *   Socket Thread-19818 [001] ..s. 139012.173556: : aaa
+ *   [...]
+ *
+ * tc exec bpf graft m:globals/jmp_tc key 0 obj graft.o sec bbb
+ * tc exec bpf dbg
+ *   [...]
+ *   Socket Thread-19818 [002] ..s. 139022.102967: : bbb
+ *   <idle>-0            [002] ..s. 139022.155640: : bbb
+ *   Socket Thread-19818 [001] ..s. 139022.156730: : bbb
+ *   [...]
+ */
+
+struct bpf_elf_map __section_maps jmp_tc = {
+	.type		= BPF_MAP_TYPE_PROG_ARRAY,
+	.size_key	= sizeof(uint32_t),
+	.size_value	= sizeof(uint32_t),
+	.pinning	= PIN_GLOBAL_NS,
+	.max_elem	= 1,
+};
+
+__section("aaa")
+int cls_aaa(struct __sk_buff *skb)
+{
+	printt("aaa\n");
+	return TC_H_MAKE(1, 42);
+}
+
+__section("bbb")
+int cls_bbb(struct __sk_buff *skb)
+{
+	printt("bbb\n");
+	return TC_H_MAKE(1, 43);
+}
+
+__section_cls_entry
+int cls_entry(struct __sk_buff *skb)
+{
+	tail_call(skb, &jmp_tc, 0);
+	printt("fallthrough\n");
+	return BPF_H_DEFAULT;
+}
+
+BPF_LICENSE("GPL");
diff --git a/examples/bpf/bpf_map_in_map.c b/examples/bpf/bpf_map_in_map.c
new file mode 100644
index 0000000..ff0e623
--- /dev/null
+++ b/examples/bpf/bpf_map_in_map.c
@@ -0,0 +1,56 @@
+#include "../../include/bpf_api.h"
+
+#define MAP_INNER_ID	42
+
+struct bpf_elf_map __section_maps map_inner = {
+	.type		= BPF_MAP_TYPE_ARRAY,
+	.size_key	= sizeof(uint32_t),
+	.size_value	= sizeof(uint32_t),
+	.id		= MAP_INNER_ID,
+	.inner_idx	= 0,
+	.pinning	= PIN_GLOBAL_NS,
+	.max_elem	= 1,
+};
+
+struct bpf_elf_map __section_maps map_outer = {
+	.type		= BPF_MAP_TYPE_ARRAY_OF_MAPS,
+	.size_key	= sizeof(uint32_t),
+	.size_value	= sizeof(uint32_t),
+	.inner_id	= MAP_INNER_ID,
+	.pinning	= PIN_GLOBAL_NS,
+	.max_elem	= 1,
+};
+
+__section("egress")
+int emain(struct __sk_buff *skb)
+{
+	struct bpf_elf_map *map_inner;
+	int key = 0, *val;
+
+	map_inner = map_lookup_elem(&map_outer, &key);
+	if (map_inner) {
+		val = map_lookup_elem(map_inner, &key);
+		if (val)
+			lock_xadd(val, 1);
+	}
+
+	return BPF_H_DEFAULT;
+}
+
+__section("ingress")
+int imain(struct __sk_buff *skb)
+{
+	struct bpf_elf_map *map_inner;
+	int key = 0, *val;
+
+	map_inner = map_lookup_elem(&map_outer, &key);
+	if (map_inner) {
+		val = map_lookup_elem(map_inner, &key);
+		if (val)
+			printt("map val: %d\n", *val);
+	}
+
+	return BPF_H_DEFAULT;
+}
+
+BPF_LICENSE("GPL");
diff --git a/examples/bpf/bpf_shared.c b/examples/bpf/bpf_shared.c
new file mode 100644
index 0000000..21fe6f1
--- /dev/null
+++ b/examples/bpf/bpf_shared.c
@@ -0,0 +1,53 @@
+#include "../../include/bpf_api.h"
+
+/* Minimal, stand-alone toy map pinning example:
+ *
+ * clang -target bpf -O2 [...] -o bpf_shared.o -c bpf_shared.c
+ * tc filter add dev foo parent 1: bpf obj bpf_shared.o sec egress
+ * tc filter add dev foo parent ffff: bpf obj bpf_shared.o sec ingress
+ *
+ * Both classifier will share the very same map instance in this example,
+ * so map content can be accessed from ingress *and* egress side!
+ *
+ * This example has a pinning of PIN_OBJECT_NS, so it's private and
+ * thus shared among various program sections within the object.
+ *
+ * A setting of PIN_GLOBAL_NS would place it into a global namespace,
+ * so that it can be shared among different object files. A setting
+ * of PIN_NONE (= 0) means no sharing, so each tc invocation a new map
+ * instance is being created.
+ */
+
+struct bpf_elf_map __section_maps map_sh = {
+	.type		= BPF_MAP_TYPE_ARRAY,
+	.size_key	= sizeof(uint32_t),
+	.size_value	= sizeof(uint32_t),
+	.pinning	= PIN_OBJECT_NS, /* or PIN_GLOBAL_NS, or PIN_NONE */
+	.max_elem	= 1,
+};
+
+__section("egress")
+int emain(struct __sk_buff *skb)
+{
+	int key = 0, *val;
+
+	val = map_lookup_elem(&map_sh, &key);
+	if (val)
+		lock_xadd(val, 1);
+
+	return BPF_H_DEFAULT;
+}
+
+__section("ingress")
+int imain(struct __sk_buff *skb)
+{
+	int key = 0, *val;
+
+	val = map_lookup_elem(&map_sh, &key);
+	if (val)
+		printt("map val: %d\n", *val);
+
+	return BPF_H_DEFAULT;
+}
+
+BPF_LICENSE("GPL");
diff --git a/examples/bpf/bpf_tailcall.c b/examples/bpf/bpf_tailcall.c
new file mode 100644
index 0000000..161eb60
--- /dev/null
+++ b/examples/bpf/bpf_tailcall.c
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include "../../include/bpf_api.h"
+
+#define ENTRY_INIT	3
+#define ENTRY_0		0
+#define ENTRY_1		1
+#define MAX_JMP_SIZE	2
+
+#define FOO		42
+#define BAR		43
+
+/* This example doesn't really do anything useful, but it's purpose is to
+ * demonstrate eBPF tail calls on a very simple example.
+ *
+ * cls_entry() is our classifier entry point, from there we jump based on
+ * skb->hash into cls_case1() or cls_case2(). They are both part of the
+ * program array jmp_tc. Indicated via __section_tail(), the tc loader
+ * populates the program arrays with the loaded file descriptors already.
+ *
+ * To demonstrate nested jumps, cls_case2() jumps within the same jmp_tc
+ * array to cls_case1(). And whenever we arrive at cls_case1(), we jump
+ * into cls_exit(), part of the jump array jmp_ex.
+ *
+ * Also, to show it's possible, all programs share map_sh and dump the value
+ * that the entry point incremented. The sections that are loaded into a
+ * program array can be atomically replaced during run-time, e.g. to change
+ * classifier behaviour.
+ */
+
+struct bpf_elf_map __section_maps jmp_tc = {
+	.type		= BPF_MAP_TYPE_PROG_ARRAY,
+	.id		= FOO,
+	.size_key	= sizeof(uint32_t),
+	.size_value	= sizeof(uint32_t),
+	.pinning	= PIN_OBJECT_NS,
+	.max_elem	= MAX_JMP_SIZE,
+};
+
+struct bpf_elf_map __section_maps jmp_ex = {
+	.type		= BPF_MAP_TYPE_PROG_ARRAY,
+	.id		= BAR,
+	.size_key	= sizeof(uint32_t),
+	.size_value	= sizeof(uint32_t),
+	.pinning	= PIN_OBJECT_NS,
+	.max_elem	= 1,
+};
+
+struct bpf_elf_map __section_maps map_sh = {
+	.type		= BPF_MAP_TYPE_ARRAY,
+	.size_key	= sizeof(uint32_t),
+	.size_value	= sizeof(uint32_t),
+	.pinning	= PIN_OBJECT_NS,
+	.max_elem	= 1,
+};
+
+__section_tail(FOO, ENTRY_0)
+int cls_case1(struct __sk_buff *skb)
+{
+	int key = 0, *val;
+
+	val = map_lookup_elem(&map_sh, &key);
+	if (val)
+		printt("case1: map-val: %d from:%u\n", *val, skb->cb[0]);
+
+	skb->cb[0] = ENTRY_0;
+	tail_call(skb, &jmp_ex, ENTRY_0);
+
+	return BPF_H_DEFAULT;
+}
+
+__section_tail(FOO, ENTRY_1)
+int cls_case2(struct __sk_buff *skb)
+{
+	int key = 0, *val;
+
+	val = map_lookup_elem(&map_sh, &key);
+	if (val)
+		printt("case2: map-val: %d from:%u\n", *val, skb->cb[0]);
+
+	skb->cb[0] = ENTRY_1;
+	tail_call(skb, &jmp_tc, ENTRY_0);
+
+	return BPF_H_DEFAULT;
+}
+
+__section_tail(BAR, ENTRY_0)
+int cls_exit(struct __sk_buff *skb)
+{
+	int key = 0, *val;
+
+	val = map_lookup_elem(&map_sh, &key);
+	if (val)
+		printt("exit: map-val: %d from:%u\n", *val, skb->cb[0]);
+
+	/* Termination point. */
+	return BPF_H_DEFAULT;
+}
+
+__section_cls_entry
+int cls_entry(struct __sk_buff *skb)
+{
+	int key = 0, *val;
+
+	/* For transferring state, we can use skb->cb[0] ... skb->cb[4]. */
+	val = map_lookup_elem(&map_sh, &key);
+	if (val) {
+		lock_xadd(val, 1);
+
+		skb->cb[0] = ENTRY_INIT;
+		tail_call(skb, &jmp_tc, skb->hash & (MAX_JMP_SIZE - 1));
+	}
+
+	printt("fallthrough\n");
+	return BPF_H_DEFAULT;
+}
+
+BPF_LICENSE("GPL");
diff --git a/genl/.gitignore b/genl/.gitignore
new file mode 100644
index 0000000..383ef04
--- /dev/null
+++ b/genl/.gitignore
@@ -0,0 +1 @@
+genl
diff --git a/genl/Makefile b/genl/Makefile
new file mode 100644
index 0000000..2b7a45b
--- /dev/null
+++ b/genl/Makefile
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: GPL-2.0
+GENLOBJ=genl.o
+
+include ../config.mk
+SHARED_LIBS ?= y
+
+CFLAGS += -fno-strict-aliasing
+
+GENLMODULES :=
+GENLMODULES += ctrl.o
+
+GENLOBJ += $(GENLMODULES)
+
+GENLLIB :=
+
+ifeq ($(SHARED_LIBS),y)
+LDFLAGS += -Wl,-export-dynamic
+LDLIBS  += -lm -ldl
+endif
+
+all: genl
+
+genl: $(GENLOBJ) $(LIBNETLINK) $(LIBUTIL) $(GENLLIB)
+	$(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+	install -m 0755 genl $(DESTDIR)$(SBINDIR)
+
+clean:
+	rm -f $(GENLOBJ) $(GENLLIB) genl
+
+ifneq ($(SHARED_LIBS),y)
+
+genl: static-syms.o
+static-syms.o: static-syms.h
+static-syms.h: $(wildcard *.c)
+	files="$^" ; \
+	for s in `grep -B 3 '\<dlsym' $$files | sed -n '/snprintf/{s:.*"\([^"]*\)".*:\1:;s:%s::;p}'` ; do \
+		sed -n '/'$$s'[^ ]* =/{s:.* \([^ ]*'$$s'[^ ]*\) .*:extern char \1[] __attribute__((weak)); if (!strcmp(sym, "\1")) return \1;:;p}' $$files ; \
+	done > $@
+
+endif
diff --git a/genl/ctrl.c b/genl/ctrl.c
new file mode 100644
index 0000000..0fb464b
--- /dev/null
+++ b/genl/ctrl.c
@@ -0,0 +1,340 @@
+/*
+ * ctrl.c	generic netlink controller
+ *
+ *		This program is free software; you can distribute 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.
+ *
+ * Authors:	J Hadi Salim (hadi@cyberus.ca)
+ *		Johannes Berg (johannes@sipsolutions.net)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "genl_utils.h"
+
+#define GENL_MAX_FAM_OPS	256
+#define GENL_MAX_FAM_GRPS	256
+
+static int usage(void)
+{
+	fprintf(stderr,"Usage: ctrl <CMD>\n" \
+		       "CMD   := get <PARMS> | list | monitor\n" \
+		       "PARMS := name <name> | id <id>\n" \
+		       "Examples:\n" \
+		       "\tctrl ls\n" \
+		       "\tctrl monitor\n" \
+		       "\tctrl get name foobar\n" \
+		       "\tctrl get id 0xF\n");
+	return -1;
+}
+
+static void print_ctrl_cmd_flags(FILE *fp, __u32 fl)
+{
+	fprintf(fp, "\n\t\tCapabilities (0x%x):\n ", fl);
+	if (!fl) {
+		fprintf(fp, "\n");
+		return;
+	}
+	fprintf(fp, "\t\t ");
+
+	if (fl & GENL_ADMIN_PERM)
+		fprintf(fp, " requires admin permission;");
+	if (fl & GENL_CMD_CAP_DO)
+		fprintf(fp, " can doit;");
+	if (fl & GENL_CMD_CAP_DUMP)
+		fprintf(fp, " can dumpit;");
+	if (fl & GENL_CMD_CAP_HASPOL)
+		fprintf(fp, " has policy");
+
+	fprintf(fp, "\n");
+}
+
+static int print_ctrl_cmds(FILE *fp, struct rtattr *arg, __u32 ctrl_ver)
+{
+	struct rtattr *tb[CTRL_ATTR_OP_MAX + 1];
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, CTRL_ATTR_OP_MAX, arg);
+	if (tb[CTRL_ATTR_OP_ID]) {
+		__u32 *id = RTA_DATA(tb[CTRL_ATTR_OP_ID]);
+		fprintf(fp, " ID-0x%x ",*id);
+	}
+	/* we are only gonna do this for newer version of the controller */
+	if (tb[CTRL_ATTR_OP_FLAGS] && ctrl_ver >= 0x2) {
+		__u32 *fl = RTA_DATA(tb[CTRL_ATTR_OP_FLAGS]);
+		print_ctrl_cmd_flags(fp, *fl);
+	}
+	return 0;
+
+}
+
+static int print_ctrl_grp(FILE *fp, struct rtattr *arg, __u32 ctrl_ver)
+{
+	struct rtattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1];
+
+	if (arg == NULL)
+		return -1;
+
+	parse_rtattr_nested(tb, CTRL_ATTR_MCAST_GRP_MAX, arg);
+	if (tb[2]) {
+		__u32 *id = RTA_DATA(tb[CTRL_ATTR_MCAST_GRP_ID]);
+		fprintf(fp, " ID-0x%x ",*id);
+	}
+	if (tb[1]) {
+		char *name = RTA_DATA(tb[CTRL_ATTR_MCAST_GRP_NAME]);
+		fprintf(fp, " name: %s ", name);
+	}
+	return 0;
+
+}
+
+/*
+ * The controller sends one nlmsg per family
+*/
+static int print_ctrl(struct rtnl_ctrl_data *ctrl,
+		      struct nlmsghdr *n, void *arg)
+{
+	struct rtattr *tb[CTRL_ATTR_MAX + 1];
+	struct genlmsghdr *ghdr = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *attrs;
+	FILE *fp = (FILE *) arg;
+	__u32 ctrl_v = 0x1;
+
+	if (n->nlmsg_type !=  GENL_ID_CTRL) {
+		fprintf(stderr, "Not a controller message, nlmsg_len=%d "
+			"nlmsg_type=0x%x\n", n->nlmsg_len, n->nlmsg_type);
+		return 0;
+	}
+
+	if (ghdr->cmd != CTRL_CMD_GETFAMILY &&
+	    ghdr->cmd != CTRL_CMD_DELFAMILY &&
+	    ghdr->cmd != CTRL_CMD_NEWFAMILY &&
+	    ghdr->cmd != CTRL_CMD_NEWMCAST_GRP &&
+	    ghdr->cmd != CTRL_CMD_DELMCAST_GRP) {
+		fprintf(stderr, "Unknown controller command %d\n", ghdr->cmd);
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+
+	if (len < 0) {
+		fprintf(stderr, "wrong controller message len %d\n", len);
+		return -1;
+	}
+
+	attrs = (struct rtattr *) ((char *) ghdr + GENL_HDRLEN);
+	parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len);
+
+	if (tb[CTRL_ATTR_FAMILY_NAME]) {
+		char *name = RTA_DATA(tb[CTRL_ATTR_FAMILY_NAME]);
+		fprintf(fp, "\nName: %s\n",name);
+	}
+	if (tb[CTRL_ATTR_FAMILY_ID]) {
+		__u16 *id = RTA_DATA(tb[CTRL_ATTR_FAMILY_ID]);
+		fprintf(fp, "\tID: 0x%x ",*id);
+	}
+	if (tb[CTRL_ATTR_VERSION]) {
+		__u32 *v = RTA_DATA(tb[CTRL_ATTR_VERSION]);
+		fprintf(fp, " Version: 0x%x ",*v);
+		ctrl_v = *v;
+	}
+	if (tb[CTRL_ATTR_HDRSIZE]) {
+		__u32 *h = RTA_DATA(tb[CTRL_ATTR_HDRSIZE]);
+		fprintf(fp, " header size: %d ",*h);
+	}
+	if (tb[CTRL_ATTR_MAXATTR]) {
+		__u32 *ma = RTA_DATA(tb[CTRL_ATTR_MAXATTR]);
+		fprintf(fp, " max attribs: %d ",*ma);
+	}
+	/* end of family definitions .. */
+	fprintf(fp,"\n");
+	if (tb[CTRL_ATTR_OPS]) {
+		struct rtattr *tb2[GENL_MAX_FAM_OPS];
+		int i=0;
+		parse_rtattr_nested(tb2, GENL_MAX_FAM_OPS, tb[CTRL_ATTR_OPS]);
+		fprintf(fp, "\tcommands supported: \n");
+		for (i = 0; i < GENL_MAX_FAM_OPS; i++) {
+			if (tb2[i]) {
+				fprintf(fp, "\t\t#%d: ", i);
+				if (0 > print_ctrl_cmds(fp, tb2[i], ctrl_v)) {
+					fprintf(fp, "Error printing command\n");
+				}
+				/* for next command */
+				fprintf(fp,"\n");
+			}
+		}
+
+		/* end of family::cmds definitions .. */
+		fprintf(fp,"\n");
+	}
+
+	if (tb[CTRL_ATTR_MCAST_GROUPS]) {
+		struct rtattr *tb2[GENL_MAX_FAM_GRPS + 1];
+		int i;
+
+		parse_rtattr_nested(tb2, GENL_MAX_FAM_GRPS,
+				    tb[CTRL_ATTR_MCAST_GROUPS]);
+		fprintf(fp, "\tmulticast groups:\n");
+
+		for (i = 0; i < GENL_MAX_FAM_GRPS; i++) {
+			if (tb2[i]) {
+				fprintf(fp, "\t\t#%d: ", i);
+				if (0 > print_ctrl_grp(fp, tb2[i], ctrl_v))
+					fprintf(fp, "Error printing group\n");
+				/* for next group */
+				fprintf(fp,"\n");
+			}
+		}
+
+		/* end of family::groups definitions .. */
+		fprintf(fp,"\n");
+	}
+
+	fflush(fp);
+	return 0;
+}
+
+static int print_ctrl2(struct nlmsghdr *n, void *arg)
+{
+	return print_ctrl(NULL, n, arg);
+}
+
+static int ctrl_list(int cmd, int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	int ret = -1;
+	char d[GENL_NAMSIZ];
+	struct {
+		struct nlmsghdr         n;
+		struct genlmsghdr	g;
+		char                    buf[4096];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN),
+		.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+		.n.nlmsg_type = GENL_ID_CTRL,
+		.g.cmd = CTRL_CMD_GETFAMILY,
+	};
+	struct nlmsghdr *nlh = &req.n;
+	struct nlmsghdr *answer = NULL;
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_GENERIC) < 0) {
+		fprintf(stderr, "Cannot open generic netlink socket\n");
+		exit(1);
+	}
+
+	if (cmd == CTRL_CMD_GETFAMILY) {
+		if (argc != 2) {
+			fprintf(stderr, "Wrong number of params\n");
+			return -1;
+		}
+
+		if (matches(*argv, "name") == 0) {
+			NEXT_ARG();
+			strlcpy(d, *argv, sizeof(d));
+			addattr_l(nlh, 128, CTRL_ATTR_FAMILY_NAME,
+				  d, strlen(d) + 1);
+		} else if (matches(*argv, "id") == 0) {
+			__u16 id;
+			NEXT_ARG();
+			if (get_u16(&id, *argv, 0)) {
+				fprintf(stderr, "Illegal \"id\"\n");
+				goto ctrl_done;
+			}
+
+			addattr_l(nlh, 128, CTRL_ATTR_FAMILY_ID, &id, 2);
+
+		} else {
+			fprintf(stderr, "Wrong params\n");
+			goto ctrl_done;
+		}
+
+		if (rtnl_talk(&rth, nlh, &answer) < 0) {
+			fprintf(stderr, "Error talking to the kernel\n");
+			goto ctrl_done;
+		}
+
+		if (print_ctrl2(answer, (void *) stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			goto ctrl_done;
+		}
+
+	}
+
+	if (cmd == CTRL_CMD_UNSPEC) {
+		nlh->nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
+		nlh->nlmsg_seq = rth.dump = ++rth.seq;
+
+		if (rtnl_send(&rth, nlh, nlh->nlmsg_len) < 0) {
+			perror("Failed to send dump request\n");
+			goto ctrl_done;
+		}
+
+		rtnl_dump_filter(&rth, print_ctrl2, stdout);
+
+        }
+
+	ret = 0;
+ctrl_done:
+	free(answer);
+	rtnl_close(&rth);
+	return ret;
+}
+
+static int ctrl_listen(int argc, char **argv)
+{
+	struct rtnl_handle rth;
+
+	if (rtnl_open_byproto(&rth, nl_mgrp(GENL_ID_CTRL), NETLINK_GENERIC) < 0) {
+		fprintf(stderr, "Canot open generic netlink socket\n");
+		return -1;
+	}
+
+	if (rtnl_listen(&rth, print_ctrl, (void *) stdout) < 0)
+		return -1;
+
+	return 0;
+}
+
+static int parse_ctrl(struct genl_util *a, int argc, char **argv)
+{
+	argv++;
+	if (--argc <= 0) {
+		fprintf(stderr, "wrong controller params\n");
+		return -1;
+	}
+
+	if (matches(*argv, "monitor") == 0)
+		return ctrl_listen(argc-1, argv+1);
+	if (matches(*argv, "get") == 0)
+		return ctrl_list(CTRL_CMD_GETFAMILY, argc-1, argv+1);
+	if (matches(*argv, "list") == 0 ||
+	    matches(*argv, "show") == 0 ||
+	    matches(*argv, "lst") == 0)
+		return ctrl_list(CTRL_CMD_UNSPEC, argc-1, argv+1);
+	if (matches(*argv, "help") == 0)
+		return usage();
+
+	fprintf(stderr, "ctrl command \"%s\" is unknown, try \"ctrl help\".\n",
+		*argv);
+
+	return -1;
+}
+
+struct genl_util ctrl_genl_util = {
+	.name = "ctrl",
+	.parse_genlopt = parse_ctrl,
+	.print_genlopt = print_ctrl2,
+};
diff --git a/genl/genl.c b/genl/genl.c
new file mode 100644
index 0000000..aba3c13
--- /dev/null
+++ b/genl/genl.c
@@ -0,0 +1,149 @@
+/*
+ * genl.c		"genl" utility frontend.
+ *
+ *		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.
+ *
+ * Authors:	Jamal Hadi Salim
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h> /* until we put our own header */
+#include "SNAPSHOT.h"
+#include "utils.h"
+#include "genl_utils.h"
+
+int show_stats;
+int show_details;
+int show_raw;
+
+static void *BODY;
+static struct genl_util *genl_list;
+
+
+static int print_nofopt(struct nlmsghdr *n, void *arg)
+{
+	fprintf((FILE *) arg, "unknown genl type ..\n");
+	return 0;
+}
+
+static int parse_nofopt(struct genl_util *f, int argc, char **argv)
+{
+	if (argc) {
+		fprintf(stderr,
+			"Unknown genl \"%s\", hence option \"%s\" is unparsable\n",
+			f->name, *argv);
+		return -1;
+	}
+
+	return 0;
+}
+
+static struct genl_util *get_genl_kind(const char *str)
+{
+	void *dlh;
+	char buf[256];
+	struct genl_util *f;
+
+	for (f = genl_list; f; f = f->next)
+		if (strcmp(f->name, str) == 0)
+			return f;
+
+	snprintf(buf, sizeof(buf), "%s.so", str);
+	dlh = dlopen(buf, RTLD_LAZY);
+	if (dlh == NULL) {
+		dlh = BODY;
+		if (dlh == NULL) {
+			dlh = BODY = dlopen(NULL, RTLD_LAZY);
+			if (dlh == NULL)
+				goto noexist;
+		}
+	}
+
+	snprintf(buf, sizeof(buf), "%s_genl_util", str);
+
+	f = dlsym(dlh, buf);
+	if (f == NULL)
+		goto noexist;
+reg:
+	f->next = genl_list;
+	genl_list = f;
+	return f;
+
+noexist:
+	f = calloc(1, sizeof(*f));
+	if (f) {
+		strncpy(f->name, str, 15);
+		f->parse_genlopt = parse_nofopt;
+		f->print_genlopt = print_nofopt;
+		goto reg;
+	}
+	return f;
+}
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: genl [ OPTIONS ] OBJECT [help] }\n"
+		"where  OBJECT := { ctrl etc }\n"
+		"       OPTIONS := { -s[tatistics] | -d[etails] | -r[aw] | -V[ersion] | -h[elp] }\n");
+	exit(-1);
+}
+
+int main(int argc, char **argv)
+{
+	while (argc > 1) {
+		if (argv[1][0] != '-')
+			break;
+		if (matches(argv[1], "-stats") == 0 ||
+		    matches(argv[1], "-statistics") == 0) {
+			++show_stats;
+		} else if (matches(argv[1], "-details") == 0) {
+			++show_details;
+		} else if (matches(argv[1], "-raw") == 0) {
+			++show_raw;
+		} else if (matches(argv[1], "-Version") == 0) {
+			printf("genl utility, iproute2-ss%s\n", SNAPSHOT);
+			exit(0);
+		} else if (matches(argv[1], "-help") == 0) {
+			usage();
+		} else {
+			fprintf(stderr,
+				"Option \"%s\" is unknown, try \"genl -help\".\n",
+				argv[1]);
+			exit(-1);
+		}
+		argc--;	argv++;
+	}
+
+	if (argc > 1) {
+		struct genl_util *a;
+		int ret;
+
+		a = get_genl_kind(argv[1]);
+		if (!a) {
+			fprintf(stderr, "bad genl %s\n", argv[1]);
+			exit(-1);
+		}
+
+		ret = a->parse_genlopt(a, argc-1, argv+1);
+		return ret;
+	}
+
+	usage();
+}
diff --git a/genl/genl_utils.h b/genl/genl_utils.h
new file mode 100644
index 0000000..a8d433a
--- /dev/null
+++ b/genl/genl_utils.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _TC_UTIL_H_
+#define _TC_UTIL_H_ 1
+
+#include "utils.h"
+#include "linux/genetlink.h"
+
+struct genl_util
+{
+	struct  genl_util *next;
+	char	name[16];
+	int	(*parse_genlopt)(struct genl_util *fu, int argc, char **argv);
+	int	(*print_genlopt)(struct nlmsghdr *n, void *arg);
+};
+
+#endif
diff --git a/genl/static-syms.c b/genl/static-syms.c
new file mode 100644
index 0000000..47c4092
--- /dev/null
+++ b/genl/static-syms.c
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This file creates a dummy version of dynamic loading
+ * for environments where dynamic linking
+ * is not used or available.
+ */
+
+#include <string.h>
+#include "dlfcn.h"
+
+void *_dlsym(const char *sym)
+{
+#include "static-syms.h"
+	return NULL;
+}
diff --git a/include/SNAPSHOT.h b/include/SNAPSHOT.h
new file mode 100644
index 0000000..b98ad50
--- /dev/null
+++ b/include/SNAPSHOT.h
@@ -0,0 +1 @@
+static const char SNAPSHOT[] = "191125";
diff --git a/include/bpf_api.h b/include/bpf_api.h
new file mode 100644
index 0000000..89d3488
--- /dev/null
+++ b/include/bpf_api.h
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __BPF_API__
+#define __BPF_API__
+
+/* Note:
+ *
+ * This file can be included into eBPF kernel programs. It contains
+ * a couple of useful helper functions, map/section ABI (bpf_elf.h),
+ * misc macros and some eBPF specific LLVM built-ins.
+ */
+
+#include <stdint.h>
+
+#include <linux/pkt_cls.h>
+#include <linux/bpf.h>
+#include <linux/filter.h>
+
+#include <asm/byteorder.h>
+
+#include "bpf_elf.h"
+
+/** Misc macros. */
+
+#ifndef __stringify
+# define __stringify(X)		#X
+#endif
+
+#ifndef __maybe_unused
+# define __maybe_unused		__attribute__((__unused__))
+#endif
+
+#ifndef offsetof
+# define offsetof(TYPE, MEMBER)	__builtin_offsetof(TYPE, MEMBER)
+#endif
+
+#ifndef likely
+# define likely(X)		__builtin_expect(!!(X), 1)
+#endif
+
+#ifndef unlikely
+# define unlikely(X)		__builtin_expect(!!(X), 0)
+#endif
+
+#ifndef htons
+# define htons(X)		__constant_htons((X))
+#endif
+
+#ifndef ntohs
+# define ntohs(X)		__constant_ntohs((X))
+#endif
+
+#ifndef htonl
+# define htonl(X)		__constant_htonl((X))
+#endif
+
+#ifndef ntohl
+# define ntohl(X)		__constant_ntohl((X))
+#endif
+
+#ifndef __inline__
+# define __inline__		__attribute__((always_inline))
+#endif
+
+/** Section helper macros. */
+
+#ifndef __section
+# define __section(NAME)						\
+	__attribute__((section(NAME), used))
+#endif
+
+#ifndef __section_tail
+# define __section_tail(ID, KEY)					\
+	__section(__stringify(ID) "/" __stringify(KEY))
+#endif
+
+#ifndef __section_xdp_entry
+# define __section_xdp_entry						\
+	__section(ELF_SECTION_PROG)
+#endif
+
+#ifndef __section_cls_entry
+# define __section_cls_entry						\
+	__section(ELF_SECTION_CLASSIFIER)
+#endif
+
+#ifndef __section_act_entry
+# define __section_act_entry						\
+	__section(ELF_SECTION_ACTION)
+#endif
+
+#ifndef __section_lwt_entry
+# define __section_lwt_entry						\
+	__section(ELF_SECTION_PROG)
+#endif
+
+#ifndef __section_license
+# define __section_license						\
+	__section(ELF_SECTION_LICENSE)
+#endif
+
+#ifndef __section_maps
+# define __section_maps							\
+	__section(ELF_SECTION_MAPS)
+#endif
+
+/** Declaration helper macros. */
+
+#ifndef BPF_LICENSE
+# define BPF_LICENSE(NAME)						\
+	char ____license[] __section_license = NAME
+#endif
+
+/** Classifier helper */
+
+#ifndef BPF_H_DEFAULT
+# define BPF_H_DEFAULT	-1
+#endif
+
+/** BPF helper functions for tc. Individual flags are in linux/bpf.h */
+
+#ifndef __BPF_FUNC
+# define __BPF_FUNC(NAME, ...)						\
+	(* NAME)(__VA_ARGS__) __maybe_unused
+#endif
+
+#ifndef BPF_FUNC
+# define BPF_FUNC(NAME, ...)						\
+	__BPF_FUNC(NAME, __VA_ARGS__) = (void *) BPF_FUNC_##NAME
+#endif
+
+/* Map access/manipulation */
+static void *BPF_FUNC(map_lookup_elem, void *map, const void *key);
+static int BPF_FUNC(map_update_elem, void *map, const void *key,
+		    const void *value, uint32_t flags);
+static int BPF_FUNC(map_delete_elem, void *map, const void *key);
+
+/* Time access */
+static uint64_t BPF_FUNC(ktime_get_ns);
+
+/* Debugging */
+
+/* FIXME: __attribute__ ((format(printf, 1, 3))) not possible unless
+ * llvm bug https://llvm.org/bugs/show_bug.cgi?id=26243 gets resolved.
+ * It would require ____fmt to be made const, which generates a reloc
+ * entry (non-map).
+ */
+static void BPF_FUNC(trace_printk, const char *fmt, int fmt_size, ...);
+
+#ifndef printt
+# define printt(fmt, ...)						\
+	({								\
+		char ____fmt[] = fmt;					\
+		trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__);	\
+	})
+#endif
+
+/* Random numbers */
+static uint32_t BPF_FUNC(get_prandom_u32);
+
+/* Tail calls */
+static void BPF_FUNC(tail_call, struct __sk_buff *skb, void *map,
+		     uint32_t index);
+
+/* System helpers */
+static uint32_t BPF_FUNC(get_smp_processor_id);
+static uint32_t BPF_FUNC(get_numa_node_id);
+
+/* Packet misc meta data */
+static uint32_t BPF_FUNC(get_cgroup_classid, struct __sk_buff *skb);
+static int BPF_FUNC(skb_under_cgroup, void *map, uint32_t index);
+
+static uint32_t BPF_FUNC(get_route_realm, struct __sk_buff *skb);
+static uint32_t BPF_FUNC(get_hash_recalc, struct __sk_buff *skb);
+static uint32_t BPF_FUNC(set_hash_invalid, struct __sk_buff *skb);
+
+/* Packet redirection */
+static int BPF_FUNC(redirect, int ifindex, uint32_t flags);
+static int BPF_FUNC(clone_redirect, struct __sk_buff *skb, int ifindex,
+		    uint32_t flags);
+
+/* Packet manipulation */
+static int BPF_FUNC(skb_load_bytes, struct __sk_buff *skb, uint32_t off,
+		    void *to, uint32_t len);
+static int BPF_FUNC(skb_store_bytes, struct __sk_buff *skb, uint32_t off,
+		    const void *from, uint32_t len, uint32_t flags);
+
+static int BPF_FUNC(l3_csum_replace, struct __sk_buff *skb, uint32_t off,
+		    uint32_t from, uint32_t to, uint32_t flags);
+static int BPF_FUNC(l4_csum_replace, struct __sk_buff *skb, uint32_t off,
+		    uint32_t from, uint32_t to, uint32_t flags);
+static int BPF_FUNC(csum_diff, const void *from, uint32_t from_size,
+		    const void *to, uint32_t to_size, uint32_t seed);
+static int BPF_FUNC(csum_update, struct __sk_buff *skb, uint32_t wsum);
+
+static int BPF_FUNC(skb_change_type, struct __sk_buff *skb, uint32_t type);
+static int BPF_FUNC(skb_change_proto, struct __sk_buff *skb, uint32_t proto,
+		    uint32_t flags);
+static int BPF_FUNC(skb_change_tail, struct __sk_buff *skb, uint32_t nlen,
+		    uint32_t flags);
+
+static int BPF_FUNC(skb_pull_data, struct __sk_buff *skb, uint32_t len);
+
+/* Event notification */
+static int __BPF_FUNC(skb_event_output, struct __sk_buff *skb, void *map,
+		      uint64_t index, const void *data, uint32_t size) =
+		      (void *) BPF_FUNC_perf_event_output;
+
+/* Packet vlan encap/decap */
+static int BPF_FUNC(skb_vlan_push, struct __sk_buff *skb, uint16_t proto,
+		    uint16_t vlan_tci);
+static int BPF_FUNC(skb_vlan_pop, struct __sk_buff *skb);
+
+/* Packet tunnel encap/decap */
+static int BPF_FUNC(skb_get_tunnel_key, struct __sk_buff *skb,
+		    struct bpf_tunnel_key *to, uint32_t size, uint32_t flags);
+static int BPF_FUNC(skb_set_tunnel_key, struct __sk_buff *skb,
+		    const struct bpf_tunnel_key *from, uint32_t size,
+		    uint32_t flags);
+
+static int BPF_FUNC(skb_get_tunnel_opt, struct __sk_buff *skb,
+		    void *to, uint32_t size);
+static int BPF_FUNC(skb_set_tunnel_opt, struct __sk_buff *skb,
+		    const void *from, uint32_t size);
+
+/** LLVM built-ins, mem*() routines work for constant size */
+
+#ifndef lock_xadd
+# define lock_xadd(ptr, val)	((void) __sync_fetch_and_add(ptr, val))
+#endif
+
+#ifndef memset
+# define memset(s, c, n)	__builtin_memset((s), (c), (n))
+#endif
+
+#ifndef memcpy
+# define memcpy(d, s, n)	__builtin_memcpy((d), (s), (n))
+#endif
+
+#ifndef memmove
+# define memmove(d, s, n)	__builtin_memmove((d), (s), (n))
+#endif
+
+/* FIXME: __builtin_memcmp() is not yet fully useable unless llvm bug
+ * https://llvm.org/bugs/show_bug.cgi?id=26218 gets resolved. Also
+ * this one would generate a reloc entry (non-map), otherwise.
+ */
+#if 0
+#ifndef memcmp
+# define memcmp(a, b, n)	__builtin_memcmp((a), (b), (n))
+#endif
+#endif
+
+unsigned long long load_byte(void *skb, unsigned long long off)
+	asm ("llvm.bpf.load.byte");
+
+unsigned long long load_half(void *skb, unsigned long long off)
+	asm ("llvm.bpf.load.half");
+
+unsigned long long load_word(void *skb, unsigned long long off)
+	asm ("llvm.bpf.load.word");
+
+#endif /* __BPF_API__ */
diff --git a/include/bpf_elf.h b/include/bpf_elf.h
new file mode 100644
index 0000000..84e8ae0
--- /dev/null
+++ b/include/bpf_elf.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __BPF_ELF__
+#define __BPF_ELF__
+
+#include <asm/types.h>
+
+/* Note:
+ *
+ * Below ELF section names and bpf_elf_map structure definition
+ * are not (!) kernel ABI. It's rather a "contract" between the
+ * application and the BPF loader in tc. For compatibility, the
+ * section names should stay as-is. Introduction of aliases, if
+ * needed, are a possibility, though.
+ */
+
+/* ELF section names, etc */
+#define ELF_SECTION_LICENSE	"license"
+#define ELF_SECTION_MAPS	"maps"
+#define ELF_SECTION_PROG	"prog"
+#define ELF_SECTION_CLASSIFIER	"classifier"
+#define ELF_SECTION_ACTION	"action"
+
+#define ELF_MAX_MAPS		64
+#define ELF_MAX_LICENSE_LEN	128
+
+/* Object pinning settings */
+#define PIN_NONE		0
+#define PIN_OBJECT_NS		1
+#define PIN_GLOBAL_NS		2
+
+/* ELF map definition */
+struct bpf_elf_map {
+	__u32 type;
+	__u32 size_key;
+	__u32 size_value;
+	__u32 max_elem;
+	__u32 flags;
+	__u32 id;
+	__u32 pinning;
+	__u32 inner_id;
+	__u32 inner_idx;
+};
+
+#define BPF_ANNOTATE_KV_PAIR(name, type_key, type_val)		\
+	struct ____btf_map_##name {				\
+		type_key key;					\
+		type_val value;					\
+	};							\
+	struct ____btf_map_##name				\
+	    __attribute__ ((section(".maps." #name), used))	\
+	    ____btf_map_##name = { }
+
+#endif /* __BPF_ELF__ */
diff --git a/include/bpf_scm.h b/include/bpf_scm.h
new file mode 100644
index 0000000..669f053
--- /dev/null
+++ b/include/bpf_scm.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __BPF_SCM__
+#define __BPF_SCM__
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "utils.h"
+#include "bpf_elf.h"
+
+#define BPF_SCM_AUX_VER		1
+#define BPF_SCM_MAX_FDS		ELF_MAX_MAPS
+#define BPF_SCM_MSG_SIZE	1024
+
+struct bpf_elf_st {
+	dev_t st_dev;
+	ino_t st_ino;
+};
+
+struct bpf_map_aux {
+	unsigned short uds_ver;
+	unsigned short num_ent;
+	char obj_name[64];
+	struct bpf_elf_st obj_st;
+	struct bpf_elf_map ent[BPF_SCM_MAX_FDS];
+};
+
+struct bpf_map_set_msg {
+	struct msghdr hdr;
+	struct iovec iov;
+	char msg_buf[BPF_SCM_MSG_SIZE];
+	struct bpf_map_aux aux;
+};
+
+static inline int *bpf_map_set_init(struct bpf_map_set_msg *msg,
+				    struct sockaddr_un *addr,
+				    unsigned int addr_len)
+{
+	const unsigned int cmsg_ctl_len = sizeof(int) * BPF_SCM_MAX_FDS;
+	struct cmsghdr *cmsg;
+
+	msg->iov.iov_base = &msg->aux;
+	msg->iov.iov_len = sizeof(msg->aux);
+
+	msg->hdr.msg_iov = &msg->iov;
+	msg->hdr.msg_iovlen = 1;
+
+	msg->hdr.msg_name = (struct sockaddr *)addr;
+	msg->hdr.msg_namelen = addr_len;
+
+	BUILD_BUG_ON(sizeof(msg->msg_buf) < cmsg_ctl_len);
+	msg->hdr.msg_control = &msg->msg_buf;
+	msg->hdr.msg_controllen	= cmsg_ctl_len;
+
+	cmsg = CMSG_FIRSTHDR(&msg->hdr);
+	cmsg->cmsg_len = msg->hdr.msg_controllen;
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type	= SCM_RIGHTS;
+
+	return (int *)CMSG_DATA(cmsg);
+}
+
+static inline void bpf_map_set_init_single(struct bpf_map_set_msg *msg,
+					   int num)
+{
+	struct cmsghdr *cmsg;
+
+	msg->hdr.msg_controllen = CMSG_LEN(sizeof(int) * num);
+	msg->iov.iov_len = offsetof(struct bpf_map_aux, ent) +
+			   sizeof(struct bpf_elf_map) * num;
+
+	cmsg = CMSG_FIRSTHDR(&msg->hdr);
+	cmsg->cmsg_len = msg->hdr.msg_controllen;
+}
+
+#endif /* __BPF_SCM__ */
diff --git a/include/bpf_util.h b/include/bpf_util.h
new file mode 100644
index 0000000..63db07c
--- /dev/null
+++ b/include/bpf_util.h
@@ -0,0 +1,303 @@
+/*
+ * bpf_util.h	BPF common code
+ *
+ *		This program is free software; you can distribute 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.
+ *
+ * Authors:	Daniel Borkmann <daniel@iogearbox.net>
+ *		Jiri Pirko <jiri@resnulli.us>
+ */
+
+#ifndef __BPF_UTIL__
+#define __BPF_UTIL__
+
+#include <linux/bpf.h>
+#include <linux/btf.h>
+#include <linux/filter.h>
+#include <linux/magic.h>
+#include <linux/elf-em.h>
+#include <linux/if_alg.h>
+
+#include "utils.h"
+#include "bpf_scm.h"
+
+#define BPF_ENV_UDS	"TC_BPF_UDS"
+#define BPF_ENV_MNT	"TC_BPF_MNT"
+
+#ifndef BPF_MAX_LOG
+# define BPF_MAX_LOG	4096
+#endif
+
+#define BPF_DIR_GLOBALS	"globals"
+
+#ifndef BPF_FS_MAGIC
+# define BPF_FS_MAGIC	0xcafe4a11
+#endif
+
+#define BPF_DIR_MNT	"/sys/fs/bpf"
+
+#ifndef TRACEFS_MAGIC
+# define TRACEFS_MAGIC	0x74726163
+#endif
+
+#define TRACE_DIR_MNT	"/sys/kernel/tracing"
+
+#ifndef AF_ALG
+# define AF_ALG		38
+#endif
+
+#ifndef EM_BPF
+# define EM_BPF		247
+#endif
+
+struct bpf_cfg_ops {
+	void (*cbpf_cb)(void *nl, const struct sock_filter *ops, int ops_len);
+	void (*ebpf_cb)(void *nl, int fd, const char *annotation);
+};
+
+enum bpf_mode {
+	CBPF_BYTECODE,
+	CBPF_FILE,
+	EBPF_OBJECT,
+	EBPF_PINNED,
+	BPF_MODE_MAX,
+};
+
+struct bpf_cfg_in {
+	const char *object;
+	const char *section;
+	const char *uds;
+	enum bpf_prog_type type;
+	enum bpf_mode mode;
+	__u32 ifindex;
+	bool verbose;
+	int argc;
+	char **argv;
+	struct sock_filter opcodes[BPF_MAXINSNS];
+	union {
+		int n_opcodes;
+		int prog_fd;
+	};
+};
+
+/* ALU ops on registers, bpf_add|sub|...: dst_reg += src_reg */
+
+#define BPF_ALU64_REG(OP, DST, SRC)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU64 | BPF_OP(OP) | BPF_X,	\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+#define BPF_ALU32_REG(OP, DST, SRC)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU | BPF_OP(OP) | BPF_X,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+/* ALU ops on immediates, bpf_add|sub|...: dst_reg += imm32 */
+
+#define BPF_ALU64_IMM(OP, DST, IMM)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU64 | BPF_OP(OP) | BPF_K,	\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+#define BPF_ALU32_IMM(OP, DST, IMM)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU | BPF_OP(OP) | BPF_K,		\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+/* Short form of mov, dst_reg = src_reg */
+
+#define BPF_MOV64_REG(DST, SRC)					\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU64 | BPF_MOV | BPF_X,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+#define BPF_MOV32_REG(DST, SRC)					\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU | BPF_MOV | BPF_X,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+/* Short form of mov, dst_reg = imm32 */
+
+#define BPF_MOV64_IMM(DST, IMM)					\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU64 | BPF_MOV | BPF_K,		\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+#define BPF_MOV32_IMM(DST, IMM)					\
+	((struct bpf_insn) {					\
+		.code  = BPF_ALU | BPF_MOV | BPF_K,		\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+/* BPF_LD_IMM64 macro encodes single 'load 64-bit immediate' insn */
+#define BPF_LD_IMM64(DST, IMM)					\
+	BPF_LD_IMM64_RAW(DST, 0, IMM)
+
+#define BPF_LD_IMM64_RAW(DST, SRC, IMM)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_LD | BPF_DW | BPF_IMM,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = (__u32) (IMM) }),			\
+	((struct bpf_insn) {					\
+		.code  = 0, /* zero is reserved opcode */	\
+		.dst_reg = 0,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = ((__u64) (IMM)) >> 32 })
+
+#ifndef BPF_PSEUDO_MAP_FD
+# define BPF_PSEUDO_MAP_FD	1
+#endif
+
+/* pseudo BPF_LD_IMM64 insn used to refer to process-local map_fd */
+#define BPF_LD_MAP_FD(DST, MAP_FD)				\
+	BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD)
+
+
+/* Direct packet access, R0 = *(uint *) (skb->data + imm32) */
+
+#define BPF_LD_ABS(SIZE, IMM)					\
+	((struct bpf_insn) {					\
+		.code  = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS,	\
+		.dst_reg = 0,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+/* Memory load, dst_reg = *(uint *) (src_reg + off16) */
+
+#define BPF_LDX_MEM(SIZE, DST, SRC, OFF)			\
+	((struct bpf_insn) {					\
+		.code  = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM,	\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = 0 })
+
+/* Memory store, *(uint *) (dst_reg + off16) = src_reg */
+
+#define BPF_STX_MEM(SIZE, DST, SRC, OFF)			\
+	((struct bpf_insn) {					\
+		.code  = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM,	\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = 0 })
+
+/* Memory store, *(uint *) (dst_reg + off16) = imm32 */
+
+#define BPF_ST_MEM(SIZE, DST, OFF, IMM)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM,	\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = OFF,					\
+		.imm   = IMM })
+
+/* Conditional jumps against registers, if (dst_reg 'op' src_reg) goto pc + off16 */
+
+#define BPF_JMP_REG(OP, DST, SRC, OFF)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_JMP | BPF_OP(OP) | BPF_X,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = 0 })
+
+/* Conditional jumps against immediates, if (dst_reg 'op' imm32) goto pc + off16 */
+
+#define BPF_JMP_IMM(OP, DST, IMM, OFF)				\
+	((struct bpf_insn) {					\
+		.code  = BPF_JMP | BPF_OP(OP) | BPF_K,		\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = OFF,					\
+		.imm   = IMM })
+
+/* Raw code statement block */
+
+#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM)			\
+	((struct bpf_insn) {					\
+		.code  = CODE,					\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = IMM })
+
+/* Program exit */
+
+#define BPF_EXIT_INSN()						\
+	((struct bpf_insn) {					\
+		.code  = BPF_JMP | BPF_EXIT,			\
+		.dst_reg = 0,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+int bpf_parse_common(struct bpf_cfg_in *cfg, const struct bpf_cfg_ops *ops);
+int bpf_load_common(struct bpf_cfg_in *cfg, const struct bpf_cfg_ops *ops,
+		    void *nl);
+int bpf_parse_and_load_common(struct bpf_cfg_in *cfg,
+			      const struct bpf_cfg_ops *ops, void *nl);
+
+const char *bpf_prog_to_default_section(enum bpf_prog_type type);
+
+int bpf_graft_map(const char *map_path, uint32_t *key, int argc, char **argv);
+int bpf_trace_pipe(void);
+
+void bpf_print_ops(struct rtattr *bpf_ops, __u16 len);
+
+int bpf_prog_load(enum bpf_prog_type type, const struct bpf_insn *insns,
+		  size_t size_insns, const char *license, char *log,
+		  size_t size_log);
+
+int bpf_prog_attach_fd(int prog_fd, int target_fd, enum bpf_attach_type type);
+int bpf_prog_detach_fd(int target_fd, enum bpf_attach_type type);
+
+int bpf_dump_prog_info(FILE *f, uint32_t id);
+
+#ifdef HAVE_ELF
+int bpf_send_map_fds(const char *path, const char *obj);
+int bpf_recv_map_fds(const char *path, int *fds, struct bpf_map_aux *aux,
+		     unsigned int entries);
+#else
+static inline int bpf_send_map_fds(const char *path, const char *obj)
+{
+	return 0;
+}
+
+static inline int bpf_recv_map_fds(const char *path, int *fds,
+				   struct bpf_map_aux *aux,
+				   unsigned int entries)
+{
+	return -1;
+}
+#endif /* HAVE_ELF */
+#endif /* __BPF_UTIL__ */
diff --git a/include/color.h b/include/color.h
new file mode 100644
index 0000000..17ec56f
--- /dev/null
+++ b/include/color.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __COLOR_H__
+#define __COLOR_H__ 1
+
+#include <stdbool.h>
+
+enum color_attr {
+	COLOR_IFNAME,
+	COLOR_MAC,
+	COLOR_INET,
+	COLOR_INET6,
+	COLOR_OPERSTATE_UP,
+	COLOR_OPERSTATE_DOWN,
+	COLOR_NONE
+};
+
+enum color_opt {
+	COLOR_OPT_NEVER = 0,
+	COLOR_OPT_AUTO = 1,
+	COLOR_OPT_ALWAYS = 2
+};
+
+bool check_enable_color(int color, int json);
+bool matches_color(const char *arg, int *val);
+int color_fprintf(FILE *fp, enum color_attr attr, const char *fmt, ...);
+enum color_attr ifa_family_color(__u8 ifa_family);
+enum color_attr oper_state_color(__u8 state);
+
+#endif
diff --git a/include/dlfcn.h b/include/dlfcn.h
new file mode 100644
index 0000000..1d8890a
--- /dev/null
+++ b/include/dlfcn.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Stub dlfcn implementation for systems that lack shared library support
+ * but obviously can still reference compiled-in symbols.
+ */
+
+#ifndef NO_SHARED_LIBS
+#include_next <dlfcn.h>
+#else
+
+#define RTLD_LAZY 0
+#define RTLD_GLOBAL 1
+#define _FAKE_DLFCN_HDL (void *)0xbeefcafe
+
+static inline void *dlopen(const char *file, int flag)
+{
+	if (file == NULL)
+		return _FAKE_DLFCN_HDL;
+	else
+		return NULL;
+}
+
+void *_dlsym(const char *sym);
+static inline void *dlsym(void *handle, const char *sym)
+{
+	if (handle != _FAKE_DLFCN_HDL)
+		return NULL;
+	return _dlsym(sym);
+}
+
+static inline char *dlerror(void)
+{
+	return NULL;
+}
+
+static inline int dlclose(void *handle)
+{
+	return (handle == _FAKE_DLFCN_HDL) ? 0 : 1;
+}
+
+#endif
diff --git a/include/ip6tables.h b/include/ip6tables.h
new file mode 100644
index 0000000..bfb2868
--- /dev/null
+++ b/include/ip6tables.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _IP6TABLES_USER_H
+#define _IP6TABLES_USER_H
+
+#include <netinet/ip.h>
+#include <xtables.h>
+#include <libiptc/libip6tc.h>
+#include <iptables/internal.h>
+
+/* Your shared library should call one of these. */
+extern int do_command6(int argc, char *argv[], char **table,
+		       struct xtc_handle **handle, bool restore);
+
+extern int for_each_chain6(int (*fn)(const xt_chainlabel, int, struct xtc_handle *), int verbose, int builtinstoo, struct xtc_handle *handle);
+extern int flush_entries6(const xt_chainlabel chain, int verbose, struct xtc_handle *handle);
+extern int delete_chain6(const xt_chainlabel chain, int verbose, struct xtc_handle *handle);
+void print_rule6(const struct ip6t_entry *e, struct xtc_handle *h, const char *chain, int counters);
+
+extern struct xtables_globals ip6tables_globals;
+
+#endif /*_IP6TABLES_USER_H*/
diff --git a/include/iptables.h b/include/iptables.h
new file mode 100644
index 0000000..78bc378
--- /dev/null
+++ b/include/iptables.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _IPTABLES_USER_H
+#define _IPTABLES_USER_H
+
+#include <netinet/ip.h>
+#include <xtables.h>
+#include <libiptc/libiptc.h>
+#include <iptables/internal.h>
+
+/* Your shared library should call one of these. */
+extern int do_command4(int argc, char *argv[], char **table,
+		      struct xtc_handle **handle, bool restore);
+extern int delete_chain4(const xt_chainlabel chain, int verbose,
+			struct xtc_handle *handle);
+extern int flush_entries4(const xt_chainlabel chain, int verbose, 
+			struct xtc_handle *handle);
+extern int for_each_chain4(int (*fn)(const xt_chainlabel, int, struct xtc_handle *),
+		int verbose, int builtinstoo, struct xtc_handle *handle);
+extern void print_rule4(const struct ipt_entry *e,
+		struct xtc_handle *handle, const char *chain, int counters);
+
+extern struct xtables_globals iptables_globals;
+
+extern struct xtables_globals xtables_globals;
+
+#endif /*_IPTABLES_USER_H*/
diff --git a/include/iptables/internal.h b/include/iptables/internal.h
new file mode 100644
index 0000000..1fd1372
--- /dev/null
+++ b/include/iptables/internal.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef IPTABLES_INTERNAL_H
+#define IPTABLES_INTERNAL_H 1
+
+#define IPTABLES_VERSION "1.6.0"
+
+/**
+ * Program's own name and version.
+ */
+extern const char *program_name, *program_version;
+
+extern int line;
+
+#endif /* IPTABLES_INTERNAL_H */
diff --git a/include/json_print.h b/include/json_print.h
new file mode 100644
index 0000000..fe92d14
--- /dev/null
+++ b/include/json_print.h
@@ -0,0 +1,77 @@
+/*
+ * json_print.h		"print regular or json output, based on json_writer".
+ *
+ *             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.
+ *
+ * Authors:    Julien Fortin, <julien@cumulusnetworks.com>
+ */
+
+#ifndef _JSON_PRINT_H_
+#define _JSON_PRINT_H_
+
+#include "json_writer.h"
+#include "color.h"
+
+json_writer_t *get_json_writer(void);
+
+/*
+ * use:
+ *      - PRINT_ANY for context based output
+ *      - PRINT_FP for non json specific output
+ *      - PRINT_JSON for json specific output
+ */
+enum output_type {
+	PRINT_FP = 1,
+	PRINT_JSON = 2,
+	PRINT_ANY = 4,
+};
+
+void new_json_obj(int json);
+void delete_json_obj(void);
+
+bool is_json_context(void);
+
+void fflush_fp(void);
+
+void open_json_object(const char *str);
+void close_json_object(void);
+void open_json_array(enum output_type type, const char *delim);
+void close_json_array(enum output_type type, const char *delim);
+
+void print_nl(void);
+
+#define _PRINT_FUNC(type_name, type)					\
+	void print_color_##type_name(enum output_type t,		\
+				     enum color_attr color,		\
+				     const char *key,			\
+				     const char *fmt,			\
+				     type value);			\
+									\
+	static inline void print_##type_name(enum output_type t,	\
+					     const char *key,		\
+					     const char *fmt,		\
+					     type value)		\
+	{								\
+		print_color_##type_name(t, COLOR_NONE, key, fmt, value);	\
+	}
+
+_PRINT_FUNC(int, int)
+_PRINT_FUNC(s64, int64_t)
+_PRINT_FUNC(bool, bool)
+_PRINT_FUNC(null, const char*)
+_PRINT_FUNC(string, const char*)
+_PRINT_FUNC(uint, unsigned int)
+_PRINT_FUNC(u64, uint64_t)
+_PRINT_FUNC(hhu, unsigned char)
+_PRINT_FUNC(hu, unsigned short)
+_PRINT_FUNC(hex, unsigned int)
+_PRINT_FUNC(0xhex, unsigned long long)
+_PRINT_FUNC(luint, unsigned long)
+_PRINT_FUNC(lluint, unsigned long long)
+_PRINT_FUNC(float, double)
+#undef _PRINT_FUNC
+
+#endif /* _JSON_PRINT_H_ */
diff --git a/include/json_writer.h b/include/json_writer.h
new file mode 100644
index 0000000..b52dc2d
--- /dev/null
+++ b/include/json_writer.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
+/*
+ * Simple streaming JSON writer
+ *
+ * This takes care of the annoying bits of JSON syntax like the commas
+ * after elements
+ *
+ * Authors:	Stephen Hemminger <stephen@networkplumber.org>
+ */
+
+#ifndef _JSON_WRITER_H_
+#define _JSON_WRITER_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/* Opaque class structure */
+typedef struct json_writer json_writer_t;
+
+/* Create a new JSON stream */
+json_writer_t *jsonw_new(FILE *f);
+/* End output to JSON stream */
+void jsonw_destroy(json_writer_t **self_p);
+
+/* Cause output to have pretty whitespace */
+void jsonw_pretty(json_writer_t *self, bool on);
+
+/* Add property name */
+void jsonw_name(json_writer_t *self, const char *name);
+
+/* Add value  */
+__attribute__((format(printf, 2, 3)))
+void jsonw_printf(json_writer_t *self, const char *fmt, ...);
+void jsonw_string(json_writer_t *self, const char *value);
+void jsonw_bool(json_writer_t *self, bool value);
+void jsonw_float(json_writer_t *self, double number);
+void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num);
+void jsonw_uint(json_writer_t *self, unsigned int number);
+void jsonw_u64(json_writer_t *self, uint64_t number);
+void jsonw_xint(json_writer_t *self, uint64_t number);
+void jsonw_hhu(json_writer_t *self, unsigned char num);
+void jsonw_hu(json_writer_t *self, unsigned short number);
+void jsonw_int(json_writer_t *self, int number);
+void jsonw_s64(json_writer_t *self, int64_t number);
+void jsonw_null(json_writer_t *self);
+void jsonw_luint(json_writer_t *self, unsigned long num);
+void jsonw_lluint(json_writer_t *self, unsigned long long num);
+
+/* Useful Combinations of name and value */
+void jsonw_string_field(json_writer_t *self, const char *prop, const char *val);
+void jsonw_bool_field(json_writer_t *self, const char *prop, bool value);
+void jsonw_float_field(json_writer_t *self, const char *prop, double num);
+void jsonw_uint_field(json_writer_t *self, const char *prop, unsigned int num);
+void jsonw_u64_field(json_writer_t *self, const char *prop, uint64_t num);
+void jsonw_xint_field(json_writer_t *self, const char *prop, uint64_t num);
+void jsonw_hhu_field(json_writer_t *self, const char *prop, unsigned char num);
+void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num);
+void jsonw_int_field(json_writer_t *self, const char *prop, int num);
+void jsonw_s64_field(json_writer_t *self, const char *prop, int64_t num);
+void jsonw_null_field(json_writer_t *self, const char *prop);
+void jsonw_luint_field(json_writer_t *self, const char *prop,
+			unsigned long num);
+void jsonw_lluint_field(json_writer_t *self, const char *prop,
+			unsigned long long num);
+
+/* Collections */
+void jsonw_start_object(json_writer_t *self);
+void jsonw_end_object(json_writer_t *self);
+
+void jsonw_start_array(json_writer_t *self);
+void jsonw_end_array(json_writer_t *self);
+
+/* Override default exception handling */
+typedef void (jsonw_err_handler_fn)(const char *);
+
+#endif /* _JSON_WRITER_H_ */
diff --git a/include/libgenl.h b/include/libgenl.h
new file mode 100644
index 0000000..656493a
--- /dev/null
+++ b/include/libgenl.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LIBGENL_H__
+#define __LIBGENL_H__
+
+#include "libnetlink.h"
+
+#define GENL_REQUEST(_req, _bufsiz, _family, _hdrsiz, _ver, _cmd, _flags) \
+struct {								\
+	struct nlmsghdr		n;					\
+	struct genlmsghdr	g;					\
+	char			buf[NLMSG_ALIGN(_hdrsiz) + (_bufsiz)];	\
+} _req = {								\
+	.n = {								\
+		.nlmsg_type = (_family),				\
+		.nlmsg_flags = (_flags),				\
+		.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN + (_hdrsiz)),	\
+	},								\
+	.g = {								\
+		.cmd = (_cmd),						\
+		.version = (_ver),					\
+	},								\
+}
+
+int genl_resolve_family(struct rtnl_handle *grth, const char *family);
+int genl_init_handle(struct rtnl_handle *grth, const char *family,
+		     int *genl_family);
+
+#endif /* __LIBGENL_H__ */
diff --git a/include/libiptc/ipt_kernel_headers.h b/include/libiptc/ipt_kernel_headers.h
new file mode 100644
index 0000000..3d2a2a3
--- /dev/null
+++ b/include/libiptc/ipt_kernel_headers.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* This is the userspace/kernel interface for Generic IP Chains,
+   required for libc6. */
+#ifndef _FWCHAINS_KERNEL_HEADERS_H
+#define _FWCHAINS_KERNEL_HEADERS_H
+
+#include <limits.h>
+
+#include <netinet/ip.h>
+#include <netinet/in.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <net/if.h>
+#include <sys/types.h>
+#endif
diff --git a/include/libiptc/libip6tc.h b/include/libiptc/libip6tc.h
new file mode 100644
index 0000000..cd588de
--- /dev/null
+++ b/include/libiptc/libip6tc.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LIBIP6TC_H
+#define _LIBIP6TC_H
+/* Library which manipulates firewall rules. Version 0.2. */
+
+#include <linux/types.h>
+#include <libiptc/ipt_kernel_headers.h>
+#ifdef __cplusplus
+#	include <climits>
+#else
+#	include <limits.h> /* INT_MAX in ip6_tables.h */
+#endif
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <libiptc/xtcshared.h>
+
+#define ip6tc_handle xtc_handle
+#define ip6t_chainlabel xt_chainlabel
+
+#define IP6TC_LABEL_ACCEPT "ACCEPT"
+#define IP6TC_LABEL_DROP "DROP"
+#define IP6TC_LABEL_QUEUE   "QUEUE"
+#define IP6TC_LABEL_RETURN "RETURN"
+
+/* Does this chain exist? */
+int ip6tc_is_chain(const char *chain, struct xtc_handle *const handle);
+
+/* Take a snapshot of the rules. Returns NULL on error. */
+struct xtc_handle *ip6tc_init(const char *tablename);
+
+/* Cleanup after ip6tc_init(). */
+void ip6tc_free(struct xtc_handle *h);
+
+/* Iterator functions to run through the chains.  Returns NULL at end. */
+const char *ip6tc_first_chain(struct xtc_handle *handle);
+const char *ip6tc_next_chain(struct xtc_handle *handle);
+
+/* Get first rule in the given chain: NULL for empty chain. */
+const struct ip6t_entry *ip6tc_first_rule(const char *chain,
+					  struct xtc_handle *handle);
+
+/* Returns NULL when rules run out. */
+const struct ip6t_entry *ip6tc_next_rule(const struct ip6t_entry *prev,
+					 struct xtc_handle *handle);
+
+/* Returns a pointer to the target name of this position. */
+const char *ip6tc_get_target(const struct ip6t_entry *e,
+			     struct xtc_handle *handle);
+
+/* Is this a built-in chain? */
+int ip6tc_builtin(const char *chain, struct xtc_handle *const handle);
+
+/* Get the policy of a given built-in chain */
+const char *ip6tc_get_policy(const char *chain,
+			     struct xt_counters *counters,
+			     struct xtc_handle *handle);
+
+/* These functions return TRUE for OK or 0 and set errno. If errno ==
+   0, it means there was a version error (ie. upgrade libiptc). */
+/* Rule numbers start at 1 for the first rule. */
+
+/* Insert the entry `fw' in chain `chain' into position `rulenum'. */
+int ip6tc_insert_entry(const xt_chainlabel chain,
+		       const struct ip6t_entry *e,
+		       unsigned int rulenum,
+		       struct xtc_handle *handle);
+
+/* Atomically replace rule `rulenum' in `chain' with `fw'. */
+int ip6tc_replace_entry(const xt_chainlabel chain,
+			const struct ip6t_entry *e,
+			unsigned int rulenum,
+			struct xtc_handle *handle);
+
+/* Append entry `fw' to chain `chain'. Equivalent to insert with
+   rulenum = length of chain. */
+int ip6tc_append_entry(const xt_chainlabel chain,
+		       const struct ip6t_entry *e,
+		       struct xtc_handle *handle);
+
+/* Check whether a matching rule exists */
+int ip6tc_check_entry(const xt_chainlabel chain,
+		       const struct ip6t_entry *origfw,
+		       unsigned char *matchmask,
+		       struct xtc_handle *handle);
+
+/* Delete the first rule in `chain' which matches `fw'. */
+int ip6tc_delete_entry(const xt_chainlabel chain,
+		       const struct ip6t_entry *origfw,
+		       unsigned char *matchmask,
+		       struct xtc_handle *handle);
+
+/* Delete the rule in position `rulenum' in `chain'. */
+int ip6tc_delete_num_entry(const xt_chainlabel chain,
+			   unsigned int rulenum,
+			   struct xtc_handle *handle);
+
+/* Check the packet `fw' on chain `chain'. Returns the verdict, or
+   NULL and sets errno. */
+const char *ip6tc_check_packet(const xt_chainlabel chain,
+			       struct ip6t_entry *,
+			       struct xtc_handle *handle);
+
+/* Flushes the entries in the given chain (ie. empties chain). */
+int ip6tc_flush_entries(const xt_chainlabel chain,
+			struct xtc_handle *handle);
+
+/* Zeroes the counters in a chain. */
+int ip6tc_zero_entries(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
+
+/* Creates a new chain. */
+int ip6tc_create_chain(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
+
+/* Deletes a chain. */
+int ip6tc_delete_chain(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
+
+/* Renames a chain. */
+int ip6tc_rename_chain(const xt_chainlabel oldname,
+		       const xt_chainlabel newname,
+		       struct xtc_handle *handle);
+
+/* Sets the policy on a built-in chain. */
+int ip6tc_set_policy(const xt_chainlabel chain,
+		     const xt_chainlabel policy,
+		     struct xt_counters *counters,
+		     struct xtc_handle *handle);
+
+/* Get the number of references to this chain */
+int ip6tc_get_references(unsigned int *ref, const xt_chainlabel chain,
+			 struct xtc_handle *handle);
+
+/* read packet and byte counters for a specific rule */
+struct xt_counters *ip6tc_read_counter(const xt_chainlabel chain,
+					unsigned int rulenum,
+					struct xtc_handle *handle);
+
+/* zero packet and byte counters for a specific rule */
+int ip6tc_zero_counter(const xt_chainlabel chain,
+		       unsigned int rulenum,
+		       struct xtc_handle *handle);
+
+/* set packet and byte counters for a specific rule */
+int ip6tc_set_counter(const xt_chainlabel chain,
+		      unsigned int rulenum,
+		      struct xt_counters *counters,
+		      struct xtc_handle *handle);
+
+/* Makes the actual changes. */
+int ip6tc_commit(struct xtc_handle *handle);
+
+/* Get raw socket. */
+int ip6tc_get_raw_socket(void);
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *ip6tc_strerror(int err);
+
+extern void dump_entries6(struct xtc_handle *const);
+
+extern const struct xtc_ops ip6tc_ops;
+
+#endif /* _LIBIP6TC_H */
diff --git a/include/libiptc/libiptc.h b/include/libiptc/libiptc.h
new file mode 100644
index 0000000..1bfe4e1
--- /dev/null
+++ b/include/libiptc/libiptc.h
@@ -0,0 +1,173 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LIBIPTC_H
+#define _LIBIPTC_H
+/* Library which manipulates filtering rules. */
+
+#include <linux/types.h>
+#include <libiptc/ipt_kernel_headers.h>
+#ifdef __cplusplus
+#	include <climits>
+#else
+#	include <limits.h> /* INT_MAX in ip_tables.h */
+#endif
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <libiptc/xtcshared.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define iptc_handle xtc_handle
+#define ipt_chainlabel xt_chainlabel
+
+#define IPTC_LABEL_ACCEPT  "ACCEPT"
+#define IPTC_LABEL_DROP    "DROP"
+#define IPTC_LABEL_QUEUE   "QUEUE"
+#define IPTC_LABEL_RETURN  "RETURN"
+
+/* Does this chain exist? */
+int iptc_is_chain(const char *chain, struct xtc_handle *const handle);
+
+/* Take a snapshot of the rules.  Returns NULL on error. */
+struct xtc_handle *iptc_init(const char *tablename);
+
+/* Cleanup after iptc_init(). */
+void iptc_free(struct xtc_handle *h);
+
+/* Iterator functions to run through the chains.  Returns NULL at end. */
+const char *iptc_first_chain(struct xtc_handle *handle);
+const char *iptc_next_chain(struct xtc_handle *handle);
+
+/* Get first rule in the given chain: NULL for empty chain. */
+const struct ipt_entry *iptc_first_rule(const char *chain,
+					struct xtc_handle *handle);
+
+/* Returns NULL when rules run out. */
+const struct ipt_entry *iptc_next_rule(const struct ipt_entry *prev,
+				       struct xtc_handle *handle);
+
+/* Returns a pointer to the target name of this entry. */
+const char *iptc_get_target(const struct ipt_entry *e,
+			    struct xtc_handle *handle);
+
+/* Is this a built-in chain? */
+int iptc_builtin(const char *chain, struct xtc_handle *const handle);
+
+/* Get the policy of a given built-in chain */
+const char *iptc_get_policy(const char *chain,
+			    struct xt_counters *counter,
+			    struct xtc_handle *handle);
+
+/* These functions return TRUE for OK or 0 and set errno.  If errno ==
+   0, it means there was a version error (ie. upgrade libiptc). */
+/* Rule numbers start at 1 for the first rule. */
+
+/* Insert the entry `e' in chain `chain' into position `rulenum'. */
+int iptc_insert_entry(const xt_chainlabel chain,
+		      const struct ipt_entry *e,
+		      unsigned int rulenum,
+		      struct xtc_handle *handle);
+
+/* Atomically replace rule `rulenum' in `chain' with `e'. */
+int iptc_replace_entry(const xt_chainlabel chain,
+		       const struct ipt_entry *e,
+		       unsigned int rulenum,
+		       struct xtc_handle *handle);
+
+/* Append entry `e' to chain `chain'.  Equivalent to insert with
+   rulenum = length of chain. */
+int iptc_append_entry(const xt_chainlabel chain,
+		      const struct ipt_entry *e,
+		      struct xtc_handle *handle);
+
+/* Check whether a mathching rule exists */
+int iptc_check_entry(const xt_chainlabel chain,
+		      const struct ipt_entry *origfw,
+		      unsigned char *matchmask,
+		      struct xtc_handle *handle);
+
+/* Delete the first rule in `chain' which matches `e', subject to
+   matchmask (array of length == origfw) */
+int iptc_delete_entry(const xt_chainlabel chain,
+		      const struct ipt_entry *origfw,
+		      unsigned char *matchmask,
+		      struct xtc_handle *handle);
+
+/* Delete the rule in position `rulenum' in `chain'. */
+int iptc_delete_num_entry(const xt_chainlabel chain,
+			  unsigned int rulenum,
+			  struct xtc_handle *handle);
+
+/* Check the packet `e' on chain `chain'.  Returns the verdict, or
+   NULL and sets errno. */
+const char *iptc_check_packet(const xt_chainlabel chain,
+			      struct ipt_entry *entry,
+			      struct xtc_handle *handle);
+
+/* Flushes the entries in the given chain (ie. empties chain). */
+int iptc_flush_entries(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
+
+/* Zeroes the counters in a chain. */
+int iptc_zero_entries(const xt_chainlabel chain,
+		      struct xtc_handle *handle);
+
+/* Creates a new chain. */
+int iptc_create_chain(const xt_chainlabel chain,
+		      struct xtc_handle *handle);
+
+/* Deletes a chain. */
+int iptc_delete_chain(const xt_chainlabel chain,
+		      struct xtc_handle *handle);
+
+/* Renames a chain. */
+int iptc_rename_chain(const xt_chainlabel oldname,
+		      const xt_chainlabel newname,
+		      struct xtc_handle *handle);
+
+/* Sets the policy on a built-in chain. */
+int iptc_set_policy(const xt_chainlabel chain,
+		    const xt_chainlabel policy,
+		    struct xt_counters *counters,
+		    struct xtc_handle *handle);
+
+/* Get the number of references to this chain */
+int iptc_get_references(unsigned int *ref,
+			const xt_chainlabel chain,
+			struct xtc_handle *handle);
+
+/* read packet and byte counters for a specific rule */
+struct xt_counters *iptc_read_counter(const xt_chainlabel chain,
+				       unsigned int rulenum,
+				       struct xtc_handle *handle);
+
+/* zero packet and byte counters for a specific rule */
+int iptc_zero_counter(const xt_chainlabel chain,
+		      unsigned int rulenum,
+		      struct xtc_handle *handle);
+
+/* set packet and byte counters for a specific rule */
+int iptc_set_counter(const xt_chainlabel chain,
+		     unsigned int rulenum,
+		     struct xt_counters *counters,
+		     struct xtc_handle *handle);
+
+/* Makes the actual changes. */
+int iptc_commit(struct xtc_handle *handle);
+
+/* Get raw socket. */
+int iptc_get_raw_socket(void);
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *iptc_strerror(int err);
+
+extern void dump_entries(struct xtc_handle *const);
+
+extern const struct xtc_ops iptc_ops;
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _LIBIPTC_H */
diff --git a/include/libiptc/libxtc.h b/include/libiptc/libxtc.h
new file mode 100644
index 0000000..1e9596a
--- /dev/null
+++ b/include/libiptc/libxtc.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LIBXTC_H
+#define _LIBXTC_H
+/* Library which manipulates filtering rules. */
+
+#include <libiptc/ipt_kernel_headers.h>
+#include <linux/netfilter/x_tables.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef XT_MIN_ALIGN
+/* xt_entry has pointers and u_int64_t's in it, so if you align to
+   it, you'll also align to any crazy matches and targets someone
+   might write */
+#define XT_MIN_ALIGN (__alignof__(struct xt_entry))
+#endif
+
+#ifndef XT_ALIGN
+#define XT_ALIGN(s) (((s) + ((XT_MIN_ALIGN)-1)) & ~((XT_MIN_ALIGN)-1))
+#endif
+
+#define XTC_LABEL_ACCEPT  "ACCEPT"
+#define XTC_LABEL_DROP    "DROP"
+#define XTC_LABEL_QUEUE   "QUEUE"
+#define XTC_LABEL_RETURN  "RETURN"
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBXTC_H */
diff --git a/include/libiptc/xtcshared.h b/include/libiptc/xtcshared.h
new file mode 100644
index 0000000..278a58f
--- /dev/null
+++ b/include/libiptc/xtcshared.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LIBXTC_SHARED_H
+#define _LIBXTC_SHARED_H 1
+
+typedef char xt_chainlabel[32];
+struct xtc_handle;
+struct xt_counters;
+
+struct xtc_ops {
+	int (*commit)(struct xtc_handle *);
+	void (*free)(struct xtc_handle *);
+	int (*builtin)(const char *, struct xtc_handle *const);
+	int (*is_chain)(const char *, struct xtc_handle *const);
+	int (*flush_entries)(const xt_chainlabel, struct xtc_handle *);
+	int (*create_chain)(const xt_chainlabel, struct xtc_handle *);
+	int (*set_policy)(const xt_chainlabel, const xt_chainlabel,
+			  struct xt_counters *, struct xtc_handle *);
+	const char *(*strerror)(int);
+};
+
+#endif /* _LIBXTC_SHARED_H */
diff --git a/include/libnetlink.h b/include/libnetlink.h
new file mode 100644
index 0000000..8ebdc6d
--- /dev/null
+++ b/include/libnetlink.h
@@ -0,0 +1,286 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LIBNETLINK_H__
+#define __LIBNETLINK_H__ 1
+
+#include <stdio.h>
+#include <string.h>
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_link.h>
+#include <linux/if_addr.h>
+#include <linux/neighbour.h>
+#include <linux/netconf.h>
+#include <arpa/inet.h>
+
+struct rtnl_handle {
+	int			fd;
+	struct sockaddr_nl	local;
+	struct sockaddr_nl	peer;
+	__u32			seq;
+	__u32			dump;
+	int			proto;
+	FILE		       *dump_fp;
+#define RTNL_HANDLE_F_LISTEN_ALL_NSID		0x01
+#define RTNL_HANDLE_F_SUPPRESS_NLERR		0x02
+#define RTNL_HANDLE_F_STRICT_CHK		0x04
+	int			flags;
+};
+
+struct nlmsg_list {
+	struct nlmsg_list *next;
+	struct nlmsghdr   h;
+};
+
+struct nlmsg_chain {
+	struct nlmsg_list *head;
+	struct nlmsg_list *tail;
+};
+
+extern int rcvbuf;
+
+int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions)
+	__attribute__((warn_unused_result));
+
+int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions,
+			     int protocol)
+	__attribute__((warn_unused_result));
+int rtnl_add_nl_group(struct rtnl_handle *rth, unsigned int group)
+	__attribute__((warn_unused_result));
+void rtnl_close(struct rtnl_handle *rth);
+void rtnl_set_strict_dump(struct rtnl_handle *rth);
+
+typedef int (*req_filter_fn_t)(struct nlmsghdr *nlh, int reqlen);
+
+int rtnl_addrdump_req(struct rtnl_handle *rth, int family,
+		      req_filter_fn_t filter_fn)
+	__attribute__((warn_unused_result));
+int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family)
+	__attribute__((warn_unused_result));
+int rtnl_routedump_req(struct rtnl_handle *rth, int family,
+		       req_filter_fn_t filter_fn)
+	__attribute__((warn_unused_result));
+int rtnl_ruledump_req(struct rtnl_handle *rth, int family)
+	__attribute__((warn_unused_result));
+int rtnl_neighdump_req(struct rtnl_handle *rth, int family,
+		       req_filter_fn_t filter_fn)
+	__attribute__((warn_unused_result));
+int rtnl_neightbldump_req(struct rtnl_handle *rth, int family)
+	__attribute__((warn_unused_result));
+int rtnl_mdbdump_req(struct rtnl_handle *rth, int family)
+	__attribute__((warn_unused_result));
+int rtnl_netconfdump_req(struct rtnl_handle *rth, int family)
+	__attribute__((warn_unused_result));
+
+int rtnl_linkdump_req(struct rtnl_handle *rth, int fam)
+	__attribute__((warn_unused_result));
+int rtnl_linkdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask)
+	__attribute__((warn_unused_result));
+
+int rtnl_linkdump_req_filter_fn(struct rtnl_handle *rth, int fam,
+				req_filter_fn_t fn)
+	__attribute__((warn_unused_result));
+int rtnl_fdb_linkdump_req_filter_fn(struct rtnl_handle *rth,
+				    req_filter_fn_t filter_fn)
+	__attribute__((warn_unused_result));
+int rtnl_nsiddump_req_filter_fn(struct rtnl_handle *rth, int family,
+				req_filter_fn_t filter_fn)
+	__attribute__((warn_unused_result));
+int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask)
+	__attribute__((warn_unused_result));
+int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req,
+			     int len)
+	__attribute__((warn_unused_result));
+int rtnl_dump_request_n(struct rtnl_handle *rth, struct nlmsghdr *n)
+	__attribute__((warn_unused_result));
+
+int rtnl_nexthopdump_req(struct rtnl_handle *rth, int family,
+			 req_filter_fn_t filter_fn)
+	__attribute__((warn_unused_result));
+
+struct rtnl_ctrl_data {
+	int	nsid;
+};
+
+typedef int (*rtnl_filter_t)(struct nlmsghdr *n, void *);
+
+typedef int (*rtnl_listen_filter_t)(struct rtnl_ctrl_data *,
+				    struct nlmsghdr *n, void *);
+
+typedef int (*nl_ext_ack_fn_t)(const char *errmsg, uint32_t off,
+			       const struct nlmsghdr *inner_nlh);
+
+struct rtnl_dump_filter_arg {
+	rtnl_filter_t filter;
+	void *arg1;
+	__u16 nc_flags;
+};
+
+int rtnl_dump_filter_nc(struct rtnl_handle *rth,
+			rtnl_filter_t filter,
+			void *arg, __u16 nc_flags);
+#define rtnl_dump_filter(rth, filter, arg) \
+	rtnl_dump_filter_nc(rth, filter, arg, 0)
+int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
+	      struct nlmsghdr **answer)
+	__attribute__((warn_unused_result));
+int rtnl_talk_iov(struct rtnl_handle *rtnl, struct iovec *iovec, size_t iovlen,
+		  struct nlmsghdr **answer)
+	__attribute__((warn_unused_result));
+int rtnl_talk_suppress_rtnl_errmsg(struct rtnl_handle *rtnl, struct nlmsghdr *n,
+				   struct nlmsghdr **answer)
+	__attribute__((warn_unused_result));
+int rtnl_send(struct rtnl_handle *rth, const void *buf, int)
+	__attribute__((warn_unused_result));
+int rtnl_send_check(struct rtnl_handle *rth, const void *buf, int)
+	__attribute__((warn_unused_result));
+int nl_dump_ext_ack(const struct nlmsghdr *nlh, nl_ext_ack_fn_t errfn);
+int nl_dump_ext_ack_done(const struct nlmsghdr *nlh, int error);
+
+int addattr(struct nlmsghdr *n, int maxlen, int type);
+int addattr8(struct nlmsghdr *n, int maxlen, int type, __u8 data);
+int addattr16(struct nlmsghdr *n, int maxlen, int type, __u16 data);
+int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data);
+int addattr64(struct nlmsghdr *n, int maxlen, int type, __u64 data);
+int addattrstrz(struct nlmsghdr *n, int maxlen, int type, const char *data);
+
+int addattr_l(struct nlmsghdr *n, int maxlen, int type,
+	      const void *data, int alen);
+int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len);
+struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type);
+int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest);
+struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, int type,
+				   const void *data, int len);
+int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *nest);
+int rta_addattr8(struct rtattr *rta, int maxlen, int type, __u8 data);
+int rta_addattr16(struct rtattr *rta, int maxlen, int type, __u16 data);
+int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data);
+int rta_addattr64(struct rtattr *rta, int maxlen, int type, __u64 data);
+int rta_addattr_l(struct rtattr *rta, int maxlen, int type,
+		  const void *data, int alen);
+
+int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len);
+int parse_rtattr_flags(struct rtattr *tb[], int max, struct rtattr *rta,
+			      int len, unsigned short flags);
+struct rtattr *parse_rtattr_one(int type, struct rtattr *rta, int len);
+int __parse_rtattr_nested_compat(struct rtattr *tb[], int max, struct rtattr *rta, int len);
+
+struct rtattr *rta_nest(struct rtattr *rta, int maxlen, int type);
+int rta_nest_end(struct rtattr *rta, struct rtattr *nest);
+
+#define RTA_TAIL(rta) \
+		((struct rtattr *) (((void *) (rta)) + \
+				    RTA_ALIGN((rta)->rta_len)))
+
+#define parse_rtattr_nested(tb, max, rta) \
+	(parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta)))
+
+#define parse_rtattr_one_nested(type, rta) \
+	(parse_rtattr_one(type, RTA_DATA(rta), RTA_PAYLOAD(rta)))
+
+#define parse_rtattr_nested_compat(tb, max, rta, data, len) \
+	({ data = RTA_PAYLOAD(rta) >= len ? RTA_DATA(rta) : NULL;	\
+		__parse_rtattr_nested_compat(tb, max, rta, len); })
+
+static inline __u8 rta_getattr_u8(const struct rtattr *rta)
+{
+	return *(__u8 *)RTA_DATA(rta);
+}
+static inline __u16 rta_getattr_u16(const struct rtattr *rta)
+{
+	return *(__u16 *)RTA_DATA(rta);
+}
+static inline __be16 rta_getattr_be16(const struct rtattr *rta)
+{
+	return ntohs(rta_getattr_u16(rta));
+}
+static inline __u32 rta_getattr_u32(const struct rtattr *rta)
+{
+	return *(__u32 *)RTA_DATA(rta);
+}
+static inline __be32 rta_getattr_be32(const struct rtattr *rta)
+{
+	return ntohl(rta_getattr_u32(rta));
+}
+static inline __u64 rta_getattr_u64(const struct rtattr *rta)
+{
+	__u64 tmp;
+
+	memcpy(&tmp, RTA_DATA(rta), sizeof(__u64));
+	return tmp;
+}
+static inline __s32 rta_getattr_s32(const struct rtattr *rta)
+{
+	return *(__s32 *)RTA_DATA(rta);
+}
+static inline __s64 rta_getattr_s64(const struct rtattr *rta)
+{
+	__s64 tmp;
+
+	memcpy(&tmp, RTA_DATA(rta), sizeof(tmp));
+	return tmp;
+}
+static inline const char *rta_getattr_str(const struct rtattr *rta)
+{
+	return (const char *)RTA_DATA(rta);
+}
+
+int rtnl_listen_all_nsid(struct rtnl_handle *);
+int rtnl_listen(struct rtnl_handle *, rtnl_listen_filter_t handler,
+		void *jarg);
+int rtnl_from_file(FILE *, rtnl_listen_filter_t handler,
+		   void *jarg);
+
+#define NLMSG_TAIL(nmsg) \
+	((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+#ifndef IFA_RTA
+#define IFA_RTA(r) \
+	((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))))
+#endif
+#ifndef IFA_PAYLOAD
+#define IFA_PAYLOAD(n)	NLMSG_PAYLOAD(n, sizeof(struct ifaddrmsg))
+#endif
+
+#ifndef IFLA_RTA
+#define IFLA_RTA(r) \
+	((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg))))
+#endif
+#ifndef IFLA_PAYLOAD
+#define IFLA_PAYLOAD(n)	NLMSG_PAYLOAD(n, sizeof(struct ifinfomsg))
+#endif
+
+#ifndef NDA_RTA
+#define NDA_RTA(r) \
+	((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg))))
+#endif
+#ifndef NDA_PAYLOAD
+#define NDA_PAYLOAD(n)	NLMSG_PAYLOAD(n, sizeof(struct ndmsg))
+#endif
+
+#ifndef NDTA_RTA
+#define NDTA_RTA(r) \
+	((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ndtmsg))))
+#endif
+#ifndef NDTA_PAYLOAD
+#define NDTA_PAYLOAD(n) NLMSG_PAYLOAD(n, sizeof(struct ndtmsg))
+#endif
+
+#ifndef NETNS_RTA
+#define NETNS_RTA(r) \
+	((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))))
+#endif
+#ifndef NETNS_PAYLOAD
+#define NETNS_PAYLOAD(n)	NLMSG_PAYLOAD(n, sizeof(struct rtgenmsg))
+#endif
+
+#ifndef IFLA_STATS_RTA
+#define IFLA_STATS_RTA(r) \
+	((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct if_stats_msg))))
+#endif
+
+/* User defined nlmsg_type which is used mostly for logging netlink
+ * messages from dump file */
+#define NLMSG_TSTAMP	15
+
+#endif /* __LIBNETLINK_H__ */
diff --git a/include/list.h b/include/list.h
new file mode 100644
index 0000000..5d86b13
--- /dev/null
+++ b/include/list.h
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LIST_H__
+#define __LIST_H__ 1
+/* List and hash list stuff from kernel */
+
+#include <stddef.h>
+
+#define container_of(ptr, type, member) ({			\
+	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
+	(type *)( (char *)__mptr - offsetof(type,member) );})
+
+struct list_head {
+	struct list_head *next, *prev;
+};
+
+static inline void INIT_LIST_HEAD(struct list_head *list)
+{
+	list->next = list;
+	list->prev = list;
+}
+
+static inline void __list_add(struct list_head *new,
+			      struct list_head *prev,
+			      struct list_head *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head->prev, head);
+}
+
+static inline void __list_del(struct list_head *prev, struct list_head *next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+static inline void list_del(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+}
+
+#define list_entry(ptr, type, member) \
+	container_of(ptr, type, member)
+
+#define list_first_entry(ptr, type, member) \
+	list_entry((ptr)->next, type, member)
+
+#define list_last_entry(ptr, type, member) \
+	list_entry((ptr)->prev, type, member)
+
+#define list_next_entry(pos, member) \
+	list_entry((pos)->member.next, typeof(*(pos)), member)
+
+#define list_prev_entry(pos, member) \
+	list_entry((pos)->member.prev, typeof(*(pos)), member)
+
+#define list_for_each_entry(pos, head, member)				\
+	for (pos = list_first_entry(head, typeof(*pos), member);	\
+	     &pos->member != (head);					\
+	     pos = list_next_entry(pos, member))
+
+#define list_for_each_entry_safe(pos, n, head, member)			\
+	for (pos = list_first_entry(head, typeof(*pos), member),	\
+		n = list_next_entry(pos, member);			\
+	     &pos->member != (head);					\
+	     pos = n, n = list_next_entry(n, member))
+
+#define list_for_each_entry_reverse(pos, head, member)			\
+	for (pos = list_last_entry(head, typeof(*pos), member);		\
+	     &pos->member != (head);					\
+	     pos = list_prev_entry(pos, member))
+
+struct hlist_head {
+	struct hlist_node *first;
+};
+
+struct hlist_node {
+	struct hlist_node *next, **pprev;
+};
+
+static inline void hlist_del(struct hlist_node *n)
+{
+	struct hlist_node *next = n->next;
+	struct hlist_node **pprev = n->pprev;
+	*pprev = next;
+	if (next)
+		next->pprev = pprev;
+}
+
+static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
+{
+	struct hlist_node *first = h->first;
+	n->next = first;
+	if (first)
+		first->pprev = &n->next;
+	h->first = n;
+	n->pprev = &h->first;
+}
+
+static inline int list_empty(const struct list_head *head)
+{
+	return head->next == head;
+}
+
+#define hlist_for_each(pos, head) \
+	for (pos = (head)->first; pos ; pos = pos->next)
+
+
+#define hlist_for_each_safe(pos, n, head) \
+	for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
+	     pos = n)
+
+#define hlist_entry_safe(ptr, type, member) \
+	({ typeof(ptr) ____ptr = (ptr); \
+	   ____ptr ? hlist_entry(____ptr, type, member) : NULL; \
+	})
+
+#define hlist_for_each_entry(pos, head, member)				\
+	for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member);\
+	     pos;							\
+	     pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
+
+#endif /* __LIST_H__ */
diff --git a/include/ll_map.h b/include/ll_map.h
new file mode 100644
index 0000000..4de1041
--- /dev/null
+++ b/include/ll_map.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LL_MAP_H__
+#define __LL_MAP_H__ 1
+
+int ll_remember_index(struct nlmsghdr *n, void *arg);
+
+void ll_init_map(struct rtnl_handle *rth);
+unsigned ll_name_to_index(const char *name);
+const char *ll_index_to_name(unsigned idx);
+int ll_index_to_type(unsigned idx);
+int ll_index_to_flags(unsigned idx);
+void ll_drop_by_index(unsigned index);
+unsigned namehash(const char *str);
+
+const char *ll_idx_n2a(unsigned int idx);
+
+#endif /* __LL_MAP_H__ */
diff --git a/include/names.h b/include/names.h
new file mode 100644
index 0000000..2fcaacc
--- /dev/null
+++ b/include/names.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef DB_NAMES_H_
+#define DB_NAMES_H_ 1
+
+#define IDNAME_MAX 256
+
+struct db_entry {
+	struct db_entry *next;
+	unsigned int id;
+	char *name;
+};
+
+struct db_names {
+	unsigned int size;
+	struct db_entry *cached;
+	struct db_entry **hash;
+	int max;
+};
+
+struct db_names *db_names_alloc(void);
+int db_names_load(struct db_names *db, const char *path);
+void db_names_free(struct db_names *db);
+
+char *id_to_name(struct db_names *db, int id, char *name);
+
+#endif
diff --git a/include/namespace.h b/include/namespace.h
new file mode 100644
index 0000000..e47f9b5
--- /dev/null
+++ b/include/namespace.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __NAMESPACE_H__
+#define __NAMESPACE_H__ 1
+
+#include <sched.h>
+#include <sys/mount.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <errno.h>
+
+#ifndef NETNS_RUN_DIR
+#define NETNS_RUN_DIR "/var/run/netns"
+#endif
+
+#ifndef NETNS_ETC_DIR
+#define NETNS_ETC_DIR "/etc/netns"
+#endif
+
+#ifndef CLONE_NEWNET
+#define CLONE_NEWNET 0x40000000	/* New network namespace (lo, device, names sockets, etc) */
+#endif
+
+#ifndef MNT_DETACH
+#define MNT_DETACH	0x00000002	/* Just detach from the tree */
+#endif /* MNT_DETACH */
+
+/* sys/mount.h may be out too old to have these */
+#ifndef MS_REC
+#define MS_REC		16384
+#endif
+
+#ifndef MS_SLAVE
+#define MS_SLAVE	(1 << 19)
+#endif
+
+#ifndef MS_SHARED
+#define MS_SHARED	(1 << 20)
+#endif
+
+#ifndef HAVE_SETNS
+static inline int setns(int fd, int nstype)
+{
+#ifdef __NR_setns
+	return syscall(__NR_setns, fd, nstype);
+#else
+	errno = ENOSYS;
+	return -1;
+#endif
+}
+#endif /* HAVE_SETNS */
+
+int netns_switch(char *netns);
+int netns_get_fd(const char *netns);
+int netns_foreach(int (*func)(char *nsname, void *arg), void *arg);
+
+struct netns_func {
+	int (*func)(char *nsname, void *arg);
+	void *arg;
+};
+
+#endif /* __NAMESPACE_H__ */
diff --git a/include/netinet/tcp.h b/include/netinet/tcp.h
new file mode 100644
index 0000000..3f890a1
--- /dev/null
+++ b/include/netinet/tcp.h
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 1982, 1986, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)tcp.h	8.1 (Berkeley) 6/10/93
+ */
+
+#ifndef _NETINET_TCP_H
+#define _NETINET_TCP_H	1
+
+#include <features.h>
+
+/*
+ * User-settable options (used with setsockopt).
+ */
+#define	TCP_NODELAY	 1	/* Don't delay send to coalesce packets  */
+#define	TCP_MAXSEG	 2	/* Set maximum segment size  */
+#define TCP_CORK	 3	/* Control sending of partial frames  */
+#define TCP_KEEPIDLE	 4	/* Start keeplives after this period */
+#define TCP_KEEPINTVL	 5	/* Interval between keepalives */
+#define TCP_KEEPCNT	 6	/* Number of keepalives before death */
+#define TCP_SYNCNT	 7	/* Number of SYN retransmits */
+#define TCP_LINGER2	 8	/* Life time of orphaned FIN-WAIT-2 state */
+#define TCP_DEFER_ACCEPT 9	/* Wake up listener only when data arrive */
+#define TCP_WINDOW_CLAMP 10	/* Bound advertised window */
+#define TCP_INFO	 11	/* Information about this connection. */
+#define	TCP_QUICKACK	 12	/* Bock/reenable quick ACKs.  */
+#define TCP_CONGESTION	 13	/* Congestion control algorithm.  */
+
+#ifdef __USE_MISC
+# include <sys/types.h>
+
+# ifdef __FAVOR_BSD
+typedef	u_int32_t tcp_seq;
+/*
+ * TCP header.
+ * Per RFC 793, September, 1981.
+ */
+struct tcphdr
+  {
+    u_int16_t th_sport;		/* source port */
+    u_int16_t th_dport;		/* destination port */
+    tcp_seq th_seq;		/* sequence number */
+    tcp_seq th_ack;		/* acknowledgement number */
+#  if __BYTE_ORDER == __LITTLE_ENDIAN
+    u_int8_t th_x2:4;		/* (unused) */
+    u_int8_t th_off:4;		/* data offset */
+#  endif
+#  if __BYTE_ORDER == __BIG_ENDIAN
+    u_int8_t th_off:4;		/* data offset */
+    u_int8_t th_x2:4;		/* (unused) */
+#  endif
+    u_int8_t th_flags;
+#  define TH_FIN	0x01
+#  define TH_SYN	0x02
+#  define TH_RST	0x04
+#  define TH_PUSH	0x08
+#  define TH_ACK	0x10
+#  define TH_URG	0x20
+    u_int16_t th_win;		/* window */
+    u_int16_t th_sum;		/* checksum */
+    u_int16_t th_urp;		/* urgent pointer */
+};
+
+# else /* !__FAVOR_BSD */
+struct tcphdr
+  {
+    u_int16_t source;
+    u_int16_t dest;
+    u_int32_t seq;
+    u_int32_t ack_seq;
+#  if __BYTE_ORDER == __LITTLE_ENDIAN
+    u_int16_t res1:4;
+    u_int16_t doff:4;
+    u_int16_t fin:1;
+    u_int16_t syn:1;
+    u_int16_t rst:1;
+    u_int16_t psh:1;
+    u_int16_t ack:1;
+    u_int16_t urg:1;
+    u_int16_t res2:2;
+#  elif __BYTE_ORDER == __BIG_ENDIAN
+    u_int16_t doff:4;
+    u_int16_t res1:4;
+    u_int16_t res2:2;
+    u_int16_t urg:1;
+    u_int16_t ack:1;
+    u_int16_t psh:1;
+    u_int16_t rst:1;
+    u_int16_t syn:1;
+    u_int16_t fin:1;
+#  else
+#   error "Adjust your <bits/endian.h> defines"
+#  endif
+    u_int16_t window;
+    u_int16_t check;
+    u_int16_t urg_ptr;
+};
+# endif /* __FAVOR_BSD */
+
+enum
+{
+  TCP_ESTABLISHED = 1,
+  TCP_SYN_SENT,
+  TCP_SYN_RECV,
+  TCP_FIN_WAIT1,
+  TCP_FIN_WAIT2,
+  TCP_TIME_WAIT,
+  TCP_CLOSE,
+  TCP_CLOSE_WAIT,
+  TCP_LAST_ACK,
+  TCP_LISTEN,
+  TCP_CLOSING   /* now a valid state */
+};
+
+# define TCPOPT_EOL		0
+# define TCPOPT_NOP		1
+# define TCPOPT_MAXSEG		2
+# define TCPOLEN_MAXSEG		4
+# define TCPOPT_WINDOW		3
+# define TCPOLEN_WINDOW		3
+# define TCPOPT_SACK_PERMITTED	4		/* Experimental */
+# define TCPOLEN_SACK_PERMITTED	2
+# define TCPOPT_SACK		5		/* Experimental */
+# define TCPOPT_TIMESTAMP	8
+# define TCPOLEN_TIMESTAMP	10
+# define TCPOLEN_TSTAMP_APPA	(TCPOLEN_TIMESTAMP+2) /* appendix A */
+
+# define TCPOPT_TSTAMP_HDR	\
+    (TCPOPT_NOP<<24|TCPOPT_NOP<<16|TCPOPT_TIMESTAMP<<8|TCPOLEN_TIMESTAMP)
+
+/*
+ * Default maximum segment size for TCP.
+ * With an IP MSS of 576, this is 536,
+ * but 512 is probably more convenient.
+ * This should be defined as MIN(512, IP_MSS - sizeof (struct tcpiphdr)).
+ */
+# define TCP_MSS	512
+
+# define TCP_MAXWIN	65535	/* largest value for (unscaled) window */
+
+# define TCP_MAX_WINSHIFT	14	/* maximum window shift */
+
+# define SOL_TCP		6	/* TCP level */
+
+
+# define TCPI_OPT_TIMESTAMPS	1
+# define TCPI_OPT_SACK		2
+# define TCPI_OPT_WSCALE	4
+# define TCPI_OPT_ECN		8
+# define TCPI_OPT_ECN_SEEN	16
+
+/* Values for tcpi_state.  */
+enum tcp_ca_state
+{
+  TCP_CA_Open = 0,
+  TCP_CA_Disorder = 1,
+  TCP_CA_CWR = 2,
+  TCP_CA_Recovery = 3,
+  TCP_CA_Loss = 4
+};
+
+struct tcp_info
+{
+  u_int8_t	tcpi_state;
+  u_int8_t	tcpi_ca_state;
+  u_int8_t	tcpi_retransmits;
+  u_int8_t	tcpi_probes;
+  u_int8_t	tcpi_backoff;
+  u_int8_t	tcpi_options;
+  u_int8_t	tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
+
+  u_int32_t	tcpi_rto;
+  u_int32_t	tcpi_ato;
+  u_int32_t	tcpi_snd_mss;
+  u_int32_t	tcpi_rcv_mss;
+
+  u_int32_t	tcpi_unacked;
+  u_int32_t	tcpi_sacked;
+  u_int32_t	tcpi_lost;
+  u_int32_t	tcpi_retrans;
+  u_int32_t	tcpi_fackets;
+
+  /* Times. */
+  u_int32_t	tcpi_last_data_sent;
+  u_int32_t	tcpi_last_ack_sent;	/* Not remembered, sorry.  */
+  u_int32_t	tcpi_last_data_recv;
+  u_int32_t	tcpi_last_ack_recv;
+
+  /* Metrics. */
+  u_int32_t	tcpi_pmtu;
+  u_int32_t	tcpi_rcv_ssthresh;
+  u_int32_t	tcpi_rtt;
+  u_int32_t	tcpi_rttvar;
+  u_int32_t	tcpi_snd_ssthresh;
+  u_int32_t	tcpi_snd_cwnd;
+  u_int32_t	tcpi_advmss;
+  u_int32_t	tcpi_reordering;
+  u_int32_t	tcpi_rcv_rtt;
+  u_int32_t	tcpi_rcv_space;
+  u_int32_t	tcpi_total_retrans;
+
+};
+
+#endif /* Misc.  */
+
+#endif /* netinet/tcp.h */
diff --git a/include/rt_names.h b/include/rt_names.h
new file mode 100644
index 0000000..62ebbd6
--- /dev/null
+++ b/include/rt_names.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef RT_NAMES_H_
+#define RT_NAMES_H_ 1
+
+#include <asm/types.h>
+
+const char *rtnl_rtprot_n2a(int id, char *buf, int len);
+const char *rtnl_rtscope_n2a(int id, char *buf, int len);
+const char *rtnl_rttable_n2a(__u32 id, char *buf, int len);
+const char *rtnl_rtrealm_n2a(int id, char *buf, int len);
+const char *rtnl_dsfield_n2a(int id, char *buf, int len);
+const char *rtnl_group_n2a(int id, char *buf, int len);
+
+int rtnl_rtprot_a2n(__u32 *id, const char *arg);
+int rtnl_rtscope_a2n(__u32 *id, const char *arg);
+int rtnl_rttable_a2n(__u32 *id, const char *arg);
+int rtnl_rtrealm_a2n(__u32 *id, const char *arg);
+int rtnl_dsfield_a2n(__u32 *id, const char *arg);
+int rtnl_group_a2n(int *id, const char *arg);
+
+const char *inet_proto_n2a(int proto, char *buf, int len);
+int inet_proto_a2n(const char *buf);
+
+
+const char * ll_type_n2a(int type, char *buf, int len);
+const char *ll_addr_n2a(const unsigned char *addr, int alen,
+			int type, char *buf, int blen);
+int ll_addr_a2n(char *lladdr, int len, const char *arg);
+
+const char * ll_proto_n2a(unsigned short id, char *buf, int len);
+int ll_proto_a2n(unsigned short *id, const char *buf);
+
+const char *nl_proto_n2a(int id, char *buf, int len);
+int nl_proto_a2n(__u32 *id, const char *arg);
+
+#endif
diff --git a/include/rtm_map.h b/include/rtm_map.h
new file mode 100644
index 0000000..f85e52c
--- /dev/null
+++ b/include/rtm_map.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __RTM_MAP_H__
+#define __RTM_MAP_H__ 1
+
+char *rtnl_rtntype_n2a(int id, char *buf, int len);
+int rtnl_rtntype_a2n(int *id, char *arg);
+
+int get_rt_realms_or_raw(__u32 *realms, char *arg);
+
+#endif /* __RTM_MAP_H__ */
diff --git a/include/uapi/asm-generic/sockios.h b/include/uapi/asm-generic/sockios.h
new file mode 100644
index 0000000..44fa3ed
--- /dev/null
+++ b/include/uapi/asm-generic/sockios.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __ASM_GENERIC_SOCKIOS_H
+#define __ASM_GENERIC_SOCKIOS_H
+
+/* Socket-level I/O control calls. */
+#define FIOSETOWN	0x8901
+#define SIOCSPGRP	0x8902
+#define FIOGETOWN	0x8903
+#define SIOCGPGRP	0x8904
+#define SIOCATMARK	0x8905
+#define SIOCGSTAMP_OLD	0x8906		/* Get stamp (timeval) */
+#define SIOCGSTAMPNS_OLD 0x8907		/* Get stamp (timespec) */
+
+#endif /* __ASM_GENERIC_SOCKIOS_H */
diff --git a/include/uapi/linux/atm.h b/include/uapi/linux/atm.h
new file mode 100644
index 0000000..e33ff6b
--- /dev/null
+++ b/include/uapi/linux/atm.h
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* atm.h - general ATM declarations */
+ 
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+ 
+
+/*
+ * WARNING: User-space programs should not #include <linux/atm.h> directly.
+ *          Instead, #include <atm.h>
+ */
+
+#ifndef _LINUX_ATM_H
+#define _LINUX_ATM_H
+
+/*
+ * BEGIN_xx and END_xx markers are used for automatic generation of
+ * documentation. Do not change them.
+ */
+
+
+#include <linux/atmapi.h>
+#include <linux/atmsap.h>
+#include <linux/atmioc.h>
+#include <linux/types.h>
+
+
+/* general ATM constants */
+#define ATM_CELL_SIZE		    53	/* ATM cell size incl. header */
+#define ATM_CELL_PAYLOAD	    48	/* ATM payload size */
+#define ATM_AAL0_SDU		    52	/* AAL0 SDU size */
+#define ATM_MAX_AAL34_PDU	 65535	/* maximum AAL3/4 PDU payload */
+#define ATM_AAL5_TRAILER	     8	/* AAL5 trailer size */
+#define ATM_MAX_AAL5_PDU	 65535	/* maximum AAL5 PDU payload */
+#define ATM_MAX_CDV		  9999	/* maximum (default) CDV */
+#define ATM_NOT_RSV_VCI		    32	/* first non-reserved VCI value */
+
+#define ATM_MAX_VPI		   255	/* maximum VPI at the UNI */
+#define ATM_MAX_VPI_NNI		  4096	/* maximum VPI at the NNI */
+#define ATM_MAX_VCI		 65535	/* maximum VCI */
+
+
+/* "protcol" values for the socket system call */
+#define ATM_NO_AAL	0		/* AAL not specified */
+#define ATM_AAL0	13		/* "raw" ATM cells */
+#define ATM_AAL1	1		/* AAL1 (CBR) */
+#define ATM_AAL2	2		/* AAL2 (VBR) */
+#define ATM_AAL34	3		/* AAL3/4 (data) */
+#define ATM_AAL5	5		/* AAL5 (data) */
+
+/*
+ * socket option name coding functions
+ *
+ * Note that __SO_ENCODE and __SO_LEVEL are somewhat a hack since the
+ * << 22 only reserves 9 bits for the level.  On some architectures
+ * SOL_SOCKET is 0xFFFF, so that's a bit of a problem
+ */
+
+#define __SO_ENCODE(l,n,t)	((((l) & 0x1FF) << 22) | ((n) << 16) | \
+				sizeof(t))
+#define __SO_LEVEL_MATCH(c,m)	(((c) >> 22) == ((m) & 0x1FF))
+#define __SO_NUMBER(c)		(((c) >> 16) & 0x3f)
+#define __SO_SIZE(c)		((c) & 0x3fff)
+
+/*
+ * ATM layer
+ */
+
+#define SO_SETCLP	__SO_ENCODE(SOL_ATM,0,int)
+			    /* set CLP bit value - TODO */
+#define SO_CIRANGE	__SO_ENCODE(SOL_ATM,1,struct atm_cirange)
+			    /* connection identifier range; socket must be
+			       bound or connected */
+#define SO_ATMQOS	__SO_ENCODE(SOL_ATM,2,struct atm_qos)
+			    /* Quality of Service setting */
+#define SO_ATMSAP	__SO_ENCODE(SOL_ATM,3,struct atm_sap)
+			    /* Service Access Point */
+#define SO_ATMPVC	__SO_ENCODE(SOL_ATM,4,struct sockaddr_atmpvc)
+			    /* "PVC" address (also for SVCs); get only */
+#define SO_MULTIPOINT	__SO_ENCODE(SOL_ATM, 5, int)
+			    /* make this vc a p2mp */
+
+
+/*
+ * Note @@@: since the socket layers don't really distinguish the control and
+ * the data plane but generally seems to be data plane-centric, any layer is
+ * about equally wrong for the SAP. If you have a better idea about this,
+ * please speak up ...
+ */
+
+
+/* ATM cell header (for AAL0) */
+
+/* BEGIN_CH */
+#define ATM_HDR_GFC_MASK	0xf0000000
+#define ATM_HDR_GFC_SHIFT	28
+#define ATM_HDR_VPI_MASK	0x0ff00000
+#define ATM_HDR_VPI_SHIFT	20
+#define ATM_HDR_VCI_MASK	0x000ffff0
+#define ATM_HDR_VCI_SHIFT	4
+#define ATM_HDR_PTI_MASK	0x0000000e
+#define ATM_HDR_PTI_SHIFT	1
+#define ATM_HDR_CLP		0x00000001
+/* END_CH */
+
+
+/* PTI codings */
+
+/* BEGIN_PTI */
+#define ATM_PTI_US0	0  /* user data cell, congestion not exp, SDU-type 0 */
+#define ATM_PTI_US1	1  /* user data cell, congestion not exp, SDU-type 1 */
+#define ATM_PTI_UCES0	2  /* user data cell, cong. experienced, SDU-type 0 */
+#define ATM_PTI_UCES1	3  /* user data cell, cong. experienced, SDU-type 1 */
+#define ATM_PTI_SEGF5	4  /* segment OAM F5 flow related cell */
+#define ATM_PTI_E2EF5	5  /* end-to-end OAM F5 flow related cell */
+#define ATM_PTI_RSV_RM	6  /* reserved for traffic control/resource mgmt */
+#define ATM_PTI_RSV	7  /* reserved */
+/* END_PTI */
+
+
+/*
+ * The following items should stay in linux/atm.h, which should be linked to
+ * netatm/atm.h
+ */
+
+/* Traffic description */
+
+#define ATM_NONE	0		/* no traffic */
+#define ATM_UBR		1
+#define ATM_CBR		2
+#define ATM_VBR		3
+#define ATM_ABR		4
+#define ATM_ANYCLASS	5		/* compatible with everything */
+
+#define ATM_MAX_PCR	-1		/* maximum available PCR */
+
+struct atm_trafprm {
+	unsigned char	traffic_class;	/* traffic class (ATM_UBR, ...) */
+	int		max_pcr;	/* maximum PCR in cells per second */
+	int		pcr;		/* desired PCR in cells per second */
+	int		min_pcr;	/* minimum PCR in cells per second */
+	int		max_cdv;	/* maximum CDV in microseconds */
+	int		max_sdu;	/* maximum SDU in bytes */
+        /* extra params for ABR */
+        unsigned int 	icr;         	/* Initial Cell Rate (24-bit) */
+        unsigned int	tbe;		/* Transient Buffer Exposure (24-bit) */ 
+        unsigned int 	frtt : 24;	/* Fixed Round Trip Time (24-bit) */
+        unsigned int 	rif  : 4;       /* Rate Increment Factor (4-bit) */
+        unsigned int 	rdf  : 4;       /* Rate Decrease Factor (4-bit) */
+        unsigned int nrm_pres  :1;      /* nrm present bit */
+        unsigned int trm_pres  :1;     	/* rm present bit */
+        unsigned int adtf_pres :1;     	/* adtf present bit */
+        unsigned int cdf_pres  :1;    	/* cdf present bit*/
+        unsigned int nrm       :3;     	/* Max # of Cells for each forward RM cell (3-bit) */
+        unsigned int trm       :3;    	/* Time between forward RM cells (3-bit) */    
+	unsigned int adtf      :10;     /* ACR Decrease Time Factor (10-bit) */
+	unsigned int cdf       :3;      /* Cutoff Decrease Factor (3-bit) */
+        unsigned int spare     :9;      /* spare bits */ 
+};
+
+struct atm_qos {
+	struct atm_trafprm txtp;	/* parameters in TX direction */
+	struct atm_trafprm rxtp __ATM_API_ALIGN;
+					/* parameters in RX direction */
+	unsigned char aal __ATM_API_ALIGN;
+};
+
+/* PVC addressing */
+
+#define ATM_ITF_ANY	-1		/* "magic" PVC address values */
+#define ATM_VPI_ANY	-1
+#define ATM_VCI_ANY	-1
+#define ATM_VPI_UNSPEC	-2
+#define ATM_VCI_UNSPEC	-2
+
+
+struct sockaddr_atmpvc {
+	unsigned short 	sap_family;	/* address family, AF_ATMPVC  */
+	struct {			/* PVC address */
+		short	itf;		/* ATM interface */
+		short	vpi;		/* VPI (only 8 bits at UNI) */
+		int	vci;		/* VCI (only 16 bits at UNI) */
+	} sap_addr __ATM_API_ALIGN;	/* PVC address */
+};
+
+/* SVC addressing */
+
+#define	ATM_ESA_LEN	20		/* ATM End System Address length */
+#define ATM_E164_LEN	12		/* maximum E.164 number length */
+
+#define ATM_AFI_DCC	0x39		/* DCC ATM Format */
+#define ATM_AFI_ICD	0x47		/* ICD ATM Format */
+#define ATM_AFI_E164	0x45		/* E.164 ATM Format */
+#define ATM_AFI_LOCAL	0x49		/* Local ATM Format */ 
+
+#define ATM_AFI_DCC_GROUP	0xBD	/* DCC ATM Group Format */
+#define ATM_AFI_ICD_GROUP	0xC5	/* ICD ATM Group Format */
+#define ATM_AFI_E164_GROUP	0xC3	/* E.164 ATM Group Format */
+#define ATM_AFI_LOCAL_GROUP	0xC7	/* Local ATM Group Format */
+
+#define ATM_LIJ_NONE	0		/* no leaf-initiated join */
+#define ATM_LIJ		1		/* request joining */
+#define ATM_LIJ_RPJ	2		/* set to root-prompted join */
+#define ATM_LIJ_NJ	3		/* set to network join */
+
+
+struct sockaddr_atmsvc {
+    unsigned short 	sas_family;	/* address family, AF_ATMSVC */
+    struct {				/* SVC address */
+        unsigned char	prv[ATM_ESA_LEN];/* private ATM address */
+        char		pub[ATM_E164_LEN+1]; /* public address (E.164) */
+    					/* unused addresses must be bzero'ed */
+	char		lij_type;	/* role in LIJ call; one of ATM_LIJ* */
+	__u32	lij_id;		/* LIJ call identifier */
+    } sas_addr __ATM_API_ALIGN;		/* SVC address */
+};
+
+
+static __inline__ int atmsvc_addr_in_use(struct sockaddr_atmsvc addr)
+{
+	return *addr.sas_addr.prv || *addr.sas_addr.pub;
+}
+
+
+static __inline__ int atmpvc_addr_in_use(struct sockaddr_atmpvc addr)
+{
+	return addr.sap_addr.itf || addr.sap_addr.vpi || addr.sap_addr.vci;
+}
+
+
+/*
+ * Some stuff for linux/sockios.h
+ */
+
+struct atmif_sioc {
+	int number;
+	int length;
+	void *arg;
+};
+
+
+typedef unsigned short atm_backend_t;
+#endif /* _LINUX_ATM_H */
diff --git a/include/uapi/linux/atmapi.h b/include/uapi/linux/atmapi.h
new file mode 100644
index 0000000..c9bf5c2
--- /dev/null
+++ b/include/uapi/linux/atmapi.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* atmapi.h - ATM API user space/kernel compatibility */
+ 
+/* Written 1999,2000 by Werner Almesberger, EPFL ICA */
+ 
+
+#ifndef _LINUX_ATMAPI_H
+#define _LINUX_ATMAPI_H
+
+#if defined(__sparc__) || defined(__ia64__)
+/* such alignment is not required on 32 bit sparcs, but we can't
+   figure that we are on a sparc64 while compiling user-space programs. */
+#define __ATM_API_ALIGN	__attribute__((aligned(8)))
+#else
+#define __ATM_API_ALIGN
+#endif
+
+
+/*
+ * Opaque type for kernel pointers. Note that _ is never accessed. We need
+ * the struct in order hide the array, so that we can make simple assignments
+ * instead of being forced to use memcpy. It also improves error reporting for
+ * code that still assumes that we're passing unsigned longs.
+ *
+ * Convention: NULL pointers are passed as a field of all zeroes.
+ */
+ 
+typedef struct { unsigned char _[8]; } __ATM_API_ALIGN atm_kptr_t;
+
+#endif
diff --git a/include/uapi/linux/atmarp.h b/include/uapi/linux/atmarp.h
new file mode 100644
index 0000000..8e44d12
--- /dev/null
+++ b/include/uapi/linux/atmarp.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* atmarp.h - ATM ARP protocol and kernel-demon interface definitions */
+ 
+/* Written 1995-1999 by Werner Almesberger, EPFL LRC/ICA */
+ 
+
+#ifndef _LINUX_ATMARP_H
+#define _LINUX_ATMARP_H
+
+#include <linux/types.h>
+#include <linux/atmapi.h>
+#include <linux/atmioc.h>
+
+
+#define ATMARP_RETRY_DELAY 30		/* request next resolution or forget
+					   NAK after 30 sec - should go into
+					   atmclip.h */
+#define ATMARP_MAX_UNRES_PACKETS 5	/* queue that many packets while
+					   waiting for the resolver */
+
+
+#define ATMARPD_CTRL	_IO('a',ATMIOC_CLIP+1)	/* become atmarpd ctrl sock */
+#define ATMARP_MKIP	_IO('a',ATMIOC_CLIP+2)	/* attach socket to IP */
+#define ATMARP_SETENTRY	_IO('a',ATMIOC_CLIP+3)	/* fill or hide ARP entry */
+#define ATMARP_ENCAP	_IO('a',ATMIOC_CLIP+5)	/* change encapsulation */
+
+
+enum atmarp_ctrl_type {
+	act_invalid,		/* catch uninitialized structures */
+	act_need,		/* need address resolution */
+	act_up,			/* interface is coming up */
+	act_down,		/* interface is going down */
+	act_change		/* interface configuration has changed */
+};
+
+struct atmarp_ctrl {
+	enum atmarp_ctrl_type	type;	/* message type */
+	int			itf_num;/* interface number (if present) */
+	__be32			ip;	/* IP address (act_need only) */
+};
+
+#endif
diff --git a/include/uapi/linux/atmdev.h b/include/uapi/linux/atmdev.h
new file mode 100644
index 0000000..9bdb96a
--- /dev/null
+++ b/include/uapi/linux/atmdev.h
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* atmdev.h - ATM device driver declarations and various related items */
+ 
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+ 
+
+#ifndef LINUX_ATMDEV_H
+#define LINUX_ATMDEV_H
+
+
+#include <linux/atmapi.h>
+#include <linux/atm.h>
+#include <linux/atmioc.h>
+
+
+#define ESI_LEN		6
+
+#define ATM_OC3_PCR	(155520000/270*260/8/53)
+			/* OC3 link rate:  155520000 bps
+			   SONET overhead: /270*260 (9 section, 1 path)
+			   bits per cell:  /8/53
+			   max cell rate:  353207.547 cells/sec */
+#define ATM_25_PCR	((25600000/8-8000)/54)
+			/* 25 Mbps ATM cell rate (59111) */
+#define ATM_OC12_PCR	(622080000/1080*1040/8/53)
+			/* OC12 link rate: 622080000 bps
+			   SONET overhead: /1080*1040
+			   bits per cell:  /8/53
+			   max cell rate:  1412830.188 cells/sec */
+#define ATM_DS3_PCR	(8000*12)
+			/* DS3: 12 cells in a 125 usec time slot */
+
+
+#define __AAL_STAT_ITEMS \
+    __HANDLE_ITEM(tx);			/* TX okay */ \
+    __HANDLE_ITEM(tx_err);		/* TX errors */ \
+    __HANDLE_ITEM(rx);			/* RX okay */ \
+    __HANDLE_ITEM(rx_err);		/* RX errors */ \
+    __HANDLE_ITEM(rx_drop);		/* RX out of memory */
+
+struct atm_aal_stats {
+#define __HANDLE_ITEM(i) int i
+	__AAL_STAT_ITEMS
+#undef __HANDLE_ITEM
+};
+
+
+struct atm_dev_stats {
+	struct atm_aal_stats aal0;
+	struct atm_aal_stats aal34;
+	struct atm_aal_stats aal5;
+} __ATM_API_ALIGN;
+
+
+#define ATM_GETLINKRATE	_IOW('a',ATMIOC_ITF+1,struct atmif_sioc)
+					/* get link rate */
+#define ATM_GETNAMES	_IOW('a',ATMIOC_ITF+3,struct atm_iobuf)
+					/* get interface names (numbers) */
+#define ATM_GETTYPE	_IOW('a',ATMIOC_ITF+4,struct atmif_sioc)
+					/* get interface type name */
+#define ATM_GETESI	_IOW('a',ATMIOC_ITF+5,struct atmif_sioc)
+					/* get interface ESI */
+#define ATM_GETADDR	_IOW('a',ATMIOC_ITF+6,struct atmif_sioc)
+					/* get itf's local ATM addr. list */
+#define ATM_RSTADDR	_IOW('a',ATMIOC_ITF+7,struct atmif_sioc)
+					/* reset itf's ATM address list */
+#define ATM_ADDADDR	_IOW('a',ATMIOC_ITF+8,struct atmif_sioc)
+					/* add a local ATM address */
+#define ATM_DELADDR	_IOW('a',ATMIOC_ITF+9,struct atmif_sioc)
+					/* remove a local ATM address */
+#define ATM_GETCIRANGE	_IOW('a',ATMIOC_ITF+10,struct atmif_sioc)
+					/* get connection identifier range */
+#define ATM_SETCIRANGE	_IOW('a',ATMIOC_ITF+11,struct atmif_sioc)
+					/* set connection identifier range */
+#define ATM_SETESI	_IOW('a',ATMIOC_ITF+12,struct atmif_sioc)
+					/* set interface ESI */
+#define ATM_SETESIF	_IOW('a',ATMIOC_ITF+13,struct atmif_sioc)
+					/* force interface ESI */
+#define ATM_ADDLECSADDR	_IOW('a', ATMIOC_ITF+14, struct atmif_sioc)
+					/* register a LECS address */
+#define ATM_DELLECSADDR	_IOW('a', ATMIOC_ITF+15, struct atmif_sioc)
+					/* unregister a LECS address */
+#define ATM_GETLECSADDR	_IOW('a', ATMIOC_ITF+16, struct atmif_sioc)
+					/* retrieve LECS address(es) */
+
+#define ATM_GETSTAT	_IOW('a',ATMIOC_SARCOM+0,struct atmif_sioc)
+					/* get AAL layer statistics */
+#define ATM_GETSTATZ	_IOW('a',ATMIOC_SARCOM+1,struct atmif_sioc)
+					/* get AAL layer statistics and zero */
+#define ATM_GETLOOP	_IOW('a',ATMIOC_SARCOM+2,struct atmif_sioc)
+					/* get loopback mode */
+#define ATM_SETLOOP	_IOW('a',ATMIOC_SARCOM+3,struct atmif_sioc)
+					/* set loopback mode */
+#define ATM_QUERYLOOP	_IOW('a',ATMIOC_SARCOM+4,struct atmif_sioc)
+					/* query supported loopback modes */
+#define ATM_SETSC	_IOW('a',ATMIOC_SPECIAL+1,int)
+					/* enable or disable single-copy */
+#define ATM_SETBACKEND	_IOW('a',ATMIOC_SPECIAL+2,atm_backend_t)
+					/* set backend handler */
+#define ATM_NEWBACKENDIF _IOW('a',ATMIOC_SPECIAL+3,atm_backend_t)
+					/* use backend to make new if */
+#define ATM_ADDPARTY  	_IOW('a', ATMIOC_SPECIAL+4,struct atm_iobuf)
+ 					/* add party to p2mp call */
+#ifdef CONFIG_COMPAT
+/* It actually takes struct sockaddr_atmsvc, not struct atm_iobuf */
+#define COMPAT_ATM_ADDPARTY  	_IOW('a', ATMIOC_SPECIAL+4,struct compat_atm_iobuf)
+#endif
+#define ATM_DROPPARTY 	_IOW('a', ATMIOC_SPECIAL+5,int)
+					/* drop party from p2mp call */
+
+/*
+ * These are backend handkers that can be set via the ATM_SETBACKEND call
+ * above.  In the future we may support dynamic loading of these - for now,
+ * they're just being used to share the ATMIOC_BACKEND ioctls
+ */
+#define ATM_BACKEND_RAW		0	
+#define ATM_BACKEND_PPP		1	/* PPPoATM - RFC2364 */
+#define ATM_BACKEND_BR2684	2	/* Bridged RFC1483/2684 */
+
+/* for ATM_GETTYPE */
+#define ATM_ITFTYP_LEN	8	/* maximum length of interface type name */
+
+/*
+ * Loopback modes for ATM_{PHY,SAR}_{GET,SET}LOOP
+ */
+
+/* Point of loopback				CPU-->SAR-->PHY-->line--> ... */
+#define __ATM_LM_NONE	0	/* no loop back     ^     ^     ^      ^      */
+#define __ATM_LM_AAL	1	/* loop back PDUs --'     |     |      |      */
+#define __ATM_LM_ATM	2	/* loop back ATM cells ---'     |      |      */
+/* RESERVED		4	loop back on PHY side  ---'		      */
+#define __ATM_LM_PHY	8	/* loop back bits (digital) ----'      |      */
+#define __ATM_LM_ANALOG 16	/* loop back the analog signal --------'      */
+
+/* Direction of loopback */
+#define __ATM_LM_MKLOC(n)	((n))	    /* Local (i.e. loop TX to RX) */
+#define __ATM_LM_MKRMT(n)	((n) << 8)  /* Remote (i.e. loop RX to TX) */
+
+#define __ATM_LM_XTLOC(n)	((n) & 0xff)
+#define __ATM_LM_XTRMT(n)	(((n) >> 8) & 0xff)
+
+#define ATM_LM_NONE	0	/* no loopback */
+
+#define ATM_LM_LOC_AAL	__ATM_LM_MKLOC(__ATM_LM_AAL)
+#define ATM_LM_LOC_ATM	__ATM_LM_MKLOC(__ATM_LM_ATM)
+#define ATM_LM_LOC_PHY	__ATM_LM_MKLOC(__ATM_LM_PHY)
+#define ATM_LM_LOC_ANALOG __ATM_LM_MKLOC(__ATM_LM_ANALOG)
+
+#define ATM_LM_RMT_AAL	__ATM_LM_MKRMT(__ATM_LM_AAL)
+#define ATM_LM_RMT_ATM	__ATM_LM_MKRMT(__ATM_LM_ATM)
+#define ATM_LM_RMT_PHY	__ATM_LM_MKRMT(__ATM_LM_PHY)
+#define ATM_LM_RMT_ANALOG __ATM_LM_MKRMT(__ATM_LM_ANALOG)
+
+/*
+ * Note: ATM_LM_LOC_* and ATM_LM_RMT_* can be combined, provided that
+ * __ATM_LM_XTLOC(x) <= __ATM_LM_XTRMT(x)
+ */
+
+
+struct atm_iobuf {
+	int length;
+	void *buffer;
+};
+
+/* for ATM_GETCIRANGE / ATM_SETCIRANGE */
+
+#define ATM_CI_MAX      -1              /* use maximum range of VPI/VCI */
+ 
+struct atm_cirange {
+	signed char	vpi_bits;	/* 1..8, ATM_CI_MAX (-1) for maximum */
+	signed char	vci_bits;	/* 1..16, ATM_CI_MAX (-1) for maximum */
+};
+
+/* for ATM_SETSC; actually taken from the ATM_VF number space */
+
+#define ATM_SC_RX	1024		/* enable RX single-copy */
+#define ATM_SC_TX	2048		/* enable TX single-copy */
+
+#define ATM_BACKLOG_DEFAULT 32 /* if we get more, we're likely to time out
+				  anyway */
+
+/* MF: change_qos (Modify) flags */
+
+#define ATM_MF_IMMED	 1	/* Block until change is effective */
+#define ATM_MF_INC_RSV	 2	/* Change reservation on increase */
+#define ATM_MF_INC_SHP	 4	/* Change shaping on increase */
+#define ATM_MF_DEC_RSV	 8	/* Change reservation on decrease */
+#define ATM_MF_DEC_SHP	16	/* Change shaping on decrease */
+#define ATM_MF_BWD	32	/* Set the backward direction parameters */
+
+#define ATM_MF_SET	(ATM_MF_INC_RSV | ATM_MF_INC_SHP | ATM_MF_DEC_RSV | \
+			  ATM_MF_DEC_SHP | ATM_MF_BWD)
+
+/*
+ * ATM_VS_* are used to express VC state in a human-friendly way.
+ */
+
+#define ATM_VS_IDLE	0	/* VC is not used */
+#define ATM_VS_CONNECTED 1	/* VC is connected */
+#define ATM_VS_CLOSING	2	/* VC is closing */
+#define ATM_VS_LISTEN	3	/* VC is listening for incoming setups */
+#define ATM_VS_INUSE	4	/* VC is in use (registered with atmsigd) */
+#define ATM_VS_BOUND	5	/* VC is bound */
+
+#define ATM_VS2TXT_MAP \
+    "IDLE", "CONNECTED", "CLOSING", "LISTEN", "INUSE", "BOUND"
+
+#define ATM_VF2TXT_MAP \
+    "ADDR",	"READY",	"PARTIAL",	"REGIS", \
+    "RELEASED", "HASQOS",	"LISTEN",	"META", \
+    "256",	"512",		"1024",		"2048", \
+    "SESSION",	"HASSAP",	"BOUND",	"CLOSE"
+
+
+
+#endif /* LINUX_ATMDEV_H */
diff --git a/include/uapi/linux/atmioc.h b/include/uapi/linux/atmioc.h
new file mode 100644
index 0000000..cd7655e
--- /dev/null
+++ b/include/uapi/linux/atmioc.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* atmioc.h - ranges for ATM-related ioctl numbers */
+ 
+/* Written 1995-1999 by Werner Almesberger, EPFL LRC/ICA */
+
+
+/*
+ * See http://icawww1.epfl.ch/linux-atm/magic.html for the complete list of
+ * "magic" ioctl numbers.
+ */
+
+
+#ifndef _LINUX_ATMIOC_H
+#define _LINUX_ATMIOC_H
+
+#include <asm/ioctl.h>
+		/* everybody including atmioc.h will also need _IO{,R,W,WR} */
+
+#define ATMIOC_PHYCOM	  0x00 /* PHY device common ioctls, globally unique */
+#define ATMIOC_PHYCOM_END 0x0f
+#define ATMIOC_PHYTYP	  0x10 /* PHY dev type ioctls, unique per PHY type */
+#define ATMIOC_PHYTYP_END 0x2f
+#define ATMIOC_PHYPRV	  0x30 /* PHY dev private ioctls, unique per driver */
+#define ATMIOC_PHYPRV_END 0x4f
+#define ATMIOC_SARCOM	  0x50 /* SAR device common ioctls, globally unique */
+#define ATMIOC_SARCOM_END 0x50
+#define ATMIOC_SARPRV	  0x60 /* SAR dev private ioctls, unique per driver */
+#define ATMIOC_SARPRV_END 0x7f
+#define ATMIOC_ITF	  0x80 /* Interface ioctls, globally unique */
+#define ATMIOC_ITF_END	  0x8f
+#define ATMIOC_BACKEND	  0x90 /* ATM generic backend ioctls, u. per backend */
+#define ATMIOC_BACKEND_END 0xaf
+/* 0xb0-0xbf: Reserved for future use */
+#define ATMIOC_AREQUIPA	  0xc0 /* Application requested IP over ATM, glob. u. */
+#define ATMIOC_LANE	  0xd0 /* LAN Emulation, globally unique */
+#define ATMIOC_MPOA       0xd8 /* MPOA, globally unique */
+#define	ATMIOC_CLIP	  0xe0 /* Classical IP over ATM control, globally u. */
+#define	ATMIOC_CLIP_END	  0xef
+#define	ATMIOC_SPECIAL	  0xf0 /* Special-purpose controls, globally unique */
+#define	ATMIOC_SPECIAL_END 0xff
+
+#endif
diff --git a/include/uapi/linux/atmsap.h b/include/uapi/linux/atmsap.h
new file mode 100644
index 0000000..fc05248
--- /dev/null
+++ b/include/uapi/linux/atmsap.h
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* atmsap.h - ATM Service Access Point addressing definitions */
+
+/* Written 1995-1999 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#ifndef _LINUX_ATMSAP_H
+#define _LINUX_ATMSAP_H
+
+#include <linux/atmapi.h>
+
+/*
+ * BEGIN_xx and END_xx markers are used for automatic generation of
+ * documentation. Do not change them.
+ */
+
+
+/*
+ * Layer 2 protocol identifiers
+ */
+
+/* BEGIN_L2 */
+#define ATM_L2_NONE	0	/* L2 not specified */
+#define ATM_L2_ISO1745  0x01	/* Basic mode ISO 1745 */
+#define ATM_L2_Q291	0x02	/* ITU-T Q.291 (Rec. I.441) */
+#define ATM_L2_X25_LL	0x06	/* ITU-T X.25, link layer */
+#define ATM_L2_X25_ML	0x07	/* ITU-T X.25, multilink */
+#define ATM_L2_LAPB	0x08	/* Extended LAPB, half-duplex (Rec. T.71) */
+#define ATM_L2_HDLC_ARM	0x09	/* HDLC ARM (ISO/IEC 4335) */
+#define ATM_L2_HDLC_NRM	0x0a	/* HDLC NRM (ISO/IEC 4335) */
+#define ATM_L2_HDLC_ABM	0x0b	/* HDLC ABM (ISO/IEC 4335) */
+#define ATM_L2_ISO8802	0x0c	/* LAN LLC (ISO/IEC 8802/2) */
+#define ATM_L2_X75	0x0d	/* ITU-T X.75, SLP */
+#define ATM_L2_Q922	0x0e	/* ITU-T Q.922 */
+#define ATM_L2_USER	0x10	/* user-specified */
+#define ATM_L2_ISO7776	0x11	/* ISO 7776 DTE-DTE */
+/* END_L2 */
+
+
+/*
+ * Layer 3 protocol identifiers
+ */
+
+/* BEGIN_L3 */
+#define ATM_L3_NONE	0	/* L3 not specified */
+#define ATM_L3_X25	0x06	/* ITU-T X.25, packet layer */
+#define ATM_L3_ISO8208	0x07	/* ISO/IEC 8208 */
+#define ATM_L3_X223	0x08	/* ITU-T X.223 | ISO/IEC 8878 */
+#define ATM_L3_ISO8473	0x09	/* ITU-T X.233 | ISO/IEC 8473 */
+#define ATM_L3_T70	0x0a	/* ITU-T T.70 minimum network layer */
+#define ATM_L3_TR9577	0x0b	/* ISO/IEC TR 9577 */
+#define ATM_L3_H310	0x0c	/* ITU-T Recommendation H.310 */
+#define ATM_L3_H321	0x0d	/* ITU-T Recommendation H.321 */
+#define ATM_L3_USER	0x10	/* user-specified */
+/* END_L3 */
+
+
+/*
+ * High layer identifiers
+ */
+
+/* BEGIN_HL */
+#define ATM_HL_NONE	0	/* HL not specified */
+#define ATM_HL_ISO	0x01	/* ISO */
+#define ATM_HL_USER	0x02	/* user-specific */
+#define ATM_HL_HLP	0x03	/* high layer profile - UNI 3.0 only */
+#define ATM_HL_VENDOR	0x04	/* vendor-specific application identifier */
+/* END_HL */
+
+
+/*
+ * ITU-T coded mode of operation
+ */
+
+/* BEGIN_IMD */
+#define ATM_IMD_NONE	 0	/* mode not specified */
+#define ATM_IMD_NORMAL	 1	/* normal mode of operation */
+#define ATM_IMD_EXTENDED 2	/* extended mode of operation */
+/* END_IMD */
+
+/*
+ * H.310 code points
+ */
+
+#define ATM_TT_NONE	0	/* terminal type not specified */
+#define ATM_TT_RX	1	/* receive only */
+#define ATM_TT_TX	2	/* send only */
+#define ATM_TT_RXTX	3	/* receive and send */
+
+#define ATM_MC_NONE	0	/* no multiplexing */
+#define ATM_MC_TS	1	/* transport stream (TS) */
+#define ATM_MC_TS_FEC	2	/* transport stream with forward error corr. */
+#define ATM_MC_PS	3	/* program stream (PS) */
+#define ATM_MC_PS_FEC	4	/* program stream with forward error corr. */
+#define ATM_MC_H221	5	/* ITU-T Rec. H.221 */
+
+/*
+ * SAP structures
+ */
+
+#define ATM_MAX_HLI	8	/* maximum high-layer information length */
+
+
+struct atm_blli {
+    unsigned char l2_proto;	/* layer 2 protocol */
+    union {
+	struct {
+	    unsigned char mode;	/* mode of operation (ATM_IMD_xxx), 0 if */
+				/* absent */
+	    unsigned char window; /* window size (k), 1-127 (0 to omit) */
+	} itu;			/* ITU-T encoding */
+	unsigned char user;	/* user-specified l2 information */
+    } l2;
+    unsigned char l3_proto;	/* layer 3 protocol */
+    union {
+	struct {
+	    unsigned char mode;	/* mode of operation (ATM_IMD_xxx), 0 if */
+				/* absent */
+	    unsigned char def_size; /* default packet size (log2), 4-12 (0 to */
+				    /* omit) */
+	    unsigned char window;/* packet window size, 1-127 (0 to omit) */
+	} itu;			/* ITU-T encoding */
+	unsigned char user;	/* user specified l3 information */
+	struct {		      /* if l3_proto = ATM_L3_H310 */
+	    unsigned char term_type;  /* terminal type */
+	    unsigned char fw_mpx_cap; /* forward multiplexing capability */
+				      /* only if term_type != ATM_TT_NONE */
+	    unsigned char bw_mpx_cap; /* backward multiplexing capability */
+				      /* only if term_type != ATM_TT_NONE */
+	} h310;
+	struct {		  /* if l3_proto = ATM_L3_TR9577 */
+	    unsigned char ipi;	  /* initial protocol id */
+	    unsigned char snap[5];/* IEEE 802.1 SNAP identifier */
+				  /* (only if ipi == NLPID_IEEE802_1_SNAP) */
+	} tr9577;
+    } l3;
+} __ATM_API_ALIGN;
+
+
+struct atm_bhli {
+    unsigned char hl_type;	/* high layer information type */
+    unsigned char hl_length;	/* length (only if hl_type == ATM_HL_USER || */
+				/* hl_type == ATM_HL_ISO) */
+    unsigned char hl_info[ATM_MAX_HLI];/* high layer information */
+};
+
+
+#define ATM_MAX_BLLI	3		/* maximum number of BLLI elements */
+
+
+struct atm_sap {
+	struct atm_bhli bhli;		/* local SAP, high-layer information */
+	struct atm_blli blli[ATM_MAX_BLLI] __ATM_API_ALIGN;
+					/* local SAP, low-layer info */
+};
+
+
+static __inline__ int blli_in_use(struct atm_blli blli)
+{
+	return blli.l2_proto || blli.l3_proto;
+}
+
+#endif
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
new file mode 100644
index 0000000..f7a7553
--- /dev/null
+++ b/include/uapi/linux/bpf.h
@@ -0,0 +1,3616 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+#ifndef __LINUX_BPF_H__
+#define __LINUX_BPF_H__
+
+#include <linux/types.h>
+#include <linux/bpf_common.h>
+
+/* Extended instruction set based on top of classic BPF */
+
+/* instruction classes */
+#define BPF_JMP32	0x06	/* jmp mode in word width */
+#define BPF_ALU64	0x07	/* alu mode in double word width */
+
+/* ld/ldx fields */
+#define BPF_DW		0x18	/* double word (64-bit) */
+#define BPF_XADD	0xc0	/* exclusive add */
+
+/* alu/jmp fields */
+#define BPF_MOV		0xb0	/* mov reg to reg */
+#define BPF_ARSH	0xc0	/* sign extending arithmetic shift right */
+
+/* change endianness of a register */
+#define BPF_END		0xd0	/* flags for endianness conversion: */
+#define BPF_TO_LE	0x00	/* convert to little-endian */
+#define BPF_TO_BE	0x08	/* convert to big-endian */
+#define BPF_FROM_LE	BPF_TO_LE
+#define BPF_FROM_BE	BPF_TO_BE
+
+/* jmp encodings */
+#define BPF_JNE		0x50	/* jump != */
+#define BPF_JLT		0xa0	/* LT is unsigned, '<' */
+#define BPF_JLE		0xb0	/* LE is unsigned, '<=' */
+#define BPF_JSGT	0x60	/* SGT is signed '>', GT in x86 */
+#define BPF_JSGE	0x70	/* SGE is signed '>=', GE in x86 */
+#define BPF_JSLT	0xc0	/* SLT is signed, '<' */
+#define BPF_JSLE	0xd0	/* SLE is signed, '<=' */
+#define BPF_CALL	0x80	/* function call */
+#define BPF_EXIT	0x90	/* function return */
+
+/* Register numbers */
+enum {
+	BPF_REG_0 = 0,
+	BPF_REG_1,
+	BPF_REG_2,
+	BPF_REG_3,
+	BPF_REG_4,
+	BPF_REG_5,
+	BPF_REG_6,
+	BPF_REG_7,
+	BPF_REG_8,
+	BPF_REG_9,
+	BPF_REG_10,
+	__MAX_BPF_REG,
+};
+
+/* BPF has 10 general purpose 64-bit registers and stack frame. */
+#define MAX_BPF_REG	__MAX_BPF_REG
+
+struct bpf_insn {
+	__u8	code;		/* opcode */
+	__u8	dst_reg:4;	/* dest register */
+	__u8	src_reg:4;	/* source register */
+	__s16	off;		/* signed offset */
+	__s32	imm;		/* signed immediate constant */
+};
+
+/* Key of an a BPF_MAP_TYPE_LPM_TRIE entry */
+struct bpf_lpm_trie_key {
+	__u32	prefixlen;	/* up to 32 for AF_INET, 128 for AF_INET6 */
+	__u8	data[0];	/* Arbitrary size */
+};
+
+struct bpf_cgroup_storage_key {
+	__u64	cgroup_inode_id;	/* cgroup inode id */
+	__u32	attach_type;		/* program attach type */
+};
+
+/* BPF syscall commands, see bpf(2) man-page for details. */
+enum bpf_cmd {
+	BPF_MAP_CREATE,
+	BPF_MAP_LOOKUP_ELEM,
+	BPF_MAP_UPDATE_ELEM,
+	BPF_MAP_DELETE_ELEM,
+	BPF_MAP_GET_NEXT_KEY,
+	BPF_PROG_LOAD,
+	BPF_OBJ_PIN,
+	BPF_OBJ_GET,
+	BPF_PROG_ATTACH,
+	BPF_PROG_DETACH,
+	BPF_PROG_TEST_RUN,
+	BPF_PROG_GET_NEXT_ID,
+	BPF_MAP_GET_NEXT_ID,
+	BPF_PROG_GET_FD_BY_ID,
+	BPF_MAP_GET_FD_BY_ID,
+	BPF_OBJ_GET_INFO_BY_FD,
+	BPF_PROG_QUERY,
+	BPF_RAW_TRACEPOINT_OPEN,
+	BPF_BTF_LOAD,
+	BPF_BTF_GET_FD_BY_ID,
+	BPF_TASK_FD_QUERY,
+	BPF_MAP_LOOKUP_AND_DELETE_ELEM,
+	BPF_MAP_FREEZE,
+	BPF_BTF_GET_NEXT_ID,
+};
+
+enum bpf_map_type {
+	BPF_MAP_TYPE_UNSPEC,
+	BPF_MAP_TYPE_HASH,
+	BPF_MAP_TYPE_ARRAY,
+	BPF_MAP_TYPE_PROG_ARRAY,
+	BPF_MAP_TYPE_PERF_EVENT_ARRAY,
+	BPF_MAP_TYPE_PERCPU_HASH,
+	BPF_MAP_TYPE_PERCPU_ARRAY,
+	BPF_MAP_TYPE_STACK_TRACE,
+	BPF_MAP_TYPE_CGROUP_ARRAY,
+	BPF_MAP_TYPE_LRU_HASH,
+	BPF_MAP_TYPE_LRU_PERCPU_HASH,
+	BPF_MAP_TYPE_LPM_TRIE,
+	BPF_MAP_TYPE_ARRAY_OF_MAPS,
+	BPF_MAP_TYPE_HASH_OF_MAPS,
+	BPF_MAP_TYPE_DEVMAP,
+	BPF_MAP_TYPE_SOCKMAP,
+	BPF_MAP_TYPE_CPUMAP,
+	BPF_MAP_TYPE_XSKMAP,
+	BPF_MAP_TYPE_SOCKHASH,
+	BPF_MAP_TYPE_CGROUP_STORAGE,
+	BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
+	BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
+	BPF_MAP_TYPE_QUEUE,
+	BPF_MAP_TYPE_STACK,
+	BPF_MAP_TYPE_SK_STORAGE,
+	BPF_MAP_TYPE_DEVMAP_HASH,
+};
+
+/* Note that tracing related programs such as
+ * BPF_PROG_TYPE_{KPROBE,TRACEPOINT,PERF_EVENT,RAW_TRACEPOINT}
+ * are not subject to a stable API since kernel internal data
+ * structures can change from release to release and may
+ * therefore break existing tracing BPF programs. Tracing BPF
+ * programs correspond to /a/ specific kernel which is to be
+ * analyzed, and not /a/ specific kernel /and/ all future ones.
+ */
+enum bpf_prog_type {
+	BPF_PROG_TYPE_UNSPEC,
+	BPF_PROG_TYPE_SOCKET_FILTER,
+	BPF_PROG_TYPE_KPROBE,
+	BPF_PROG_TYPE_SCHED_CLS,
+	BPF_PROG_TYPE_SCHED_ACT,
+	BPF_PROG_TYPE_TRACEPOINT,
+	BPF_PROG_TYPE_XDP,
+	BPF_PROG_TYPE_PERF_EVENT,
+	BPF_PROG_TYPE_CGROUP_SKB,
+	BPF_PROG_TYPE_CGROUP_SOCK,
+	BPF_PROG_TYPE_LWT_IN,
+	BPF_PROG_TYPE_LWT_OUT,
+	BPF_PROG_TYPE_LWT_XMIT,
+	BPF_PROG_TYPE_SOCK_OPS,
+	BPF_PROG_TYPE_SK_SKB,
+	BPF_PROG_TYPE_CGROUP_DEVICE,
+	BPF_PROG_TYPE_SK_MSG,
+	BPF_PROG_TYPE_RAW_TRACEPOINT,
+	BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
+	BPF_PROG_TYPE_LWT_SEG6LOCAL,
+	BPF_PROG_TYPE_LIRC_MODE2,
+	BPF_PROG_TYPE_SK_REUSEPORT,
+	BPF_PROG_TYPE_FLOW_DISSECTOR,
+	BPF_PROG_TYPE_CGROUP_SYSCTL,
+	BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,
+	BPF_PROG_TYPE_CGROUP_SOCKOPT,
+};
+
+enum bpf_attach_type {
+	BPF_CGROUP_INET_INGRESS,
+	BPF_CGROUP_INET_EGRESS,
+	BPF_CGROUP_INET_SOCK_CREATE,
+	BPF_CGROUP_SOCK_OPS,
+	BPF_SK_SKB_STREAM_PARSER,
+	BPF_SK_SKB_STREAM_VERDICT,
+	BPF_CGROUP_DEVICE,
+	BPF_SK_MSG_VERDICT,
+	BPF_CGROUP_INET4_BIND,
+	BPF_CGROUP_INET6_BIND,
+	BPF_CGROUP_INET4_CONNECT,
+	BPF_CGROUP_INET6_CONNECT,
+	BPF_CGROUP_INET4_POST_BIND,
+	BPF_CGROUP_INET6_POST_BIND,
+	BPF_CGROUP_UDP4_SENDMSG,
+	BPF_CGROUP_UDP6_SENDMSG,
+	BPF_LIRC_MODE2,
+	BPF_FLOW_DISSECTOR,
+	BPF_CGROUP_SYSCTL,
+	BPF_CGROUP_UDP4_RECVMSG,
+	BPF_CGROUP_UDP6_RECVMSG,
+	BPF_CGROUP_GETSOCKOPT,
+	BPF_CGROUP_SETSOCKOPT,
+	__MAX_BPF_ATTACH_TYPE
+};
+
+#define MAX_BPF_ATTACH_TYPE __MAX_BPF_ATTACH_TYPE
+
+/* cgroup-bpf attach flags used in BPF_PROG_ATTACH command
+ *
+ * NONE(default): No further bpf programs allowed in the subtree.
+ *
+ * BPF_F_ALLOW_OVERRIDE: If a sub-cgroup installs some bpf program,
+ * the program in this cgroup yields to sub-cgroup program.
+ *
+ * BPF_F_ALLOW_MULTI: If a sub-cgroup installs some bpf program,
+ * that cgroup program gets run in addition to the program in this cgroup.
+ *
+ * Only one program is allowed to be attached to a cgroup with
+ * NONE or BPF_F_ALLOW_OVERRIDE flag.
+ * Attaching another program on top of NONE or BPF_F_ALLOW_OVERRIDE will
+ * release old program and attach the new one. Attach flags has to match.
+ *
+ * Multiple programs are allowed to be attached to a cgroup with
+ * BPF_F_ALLOW_MULTI flag. They are executed in FIFO order
+ * (those that were attached first, run first)
+ * The programs of sub-cgroup are executed first, then programs of
+ * this cgroup and then programs of parent cgroup.
+ * When children program makes decision (like picking TCP CA or sock bind)
+ * parent program has a chance to override it.
+ *
+ * A cgroup with MULTI or OVERRIDE flag allows any attach flags in sub-cgroups.
+ * A cgroup with NONE doesn't allow any programs in sub-cgroups.
+ * Ex1:
+ * cgrp1 (MULTI progs A, B) ->
+ *    cgrp2 (OVERRIDE prog C) ->
+ *      cgrp3 (MULTI prog D) ->
+ *        cgrp4 (OVERRIDE prog E) ->
+ *          cgrp5 (NONE prog F)
+ * the event in cgrp5 triggers execution of F,D,A,B in that order.
+ * if prog F is detached, the execution is E,D,A,B
+ * if prog F and D are detached, the execution is E,A,B
+ * if prog F, E and D are detached, the execution is C,A,B
+ *
+ * All eligible programs are executed regardless of return code from
+ * earlier programs.
+ */
+#define BPF_F_ALLOW_OVERRIDE	(1U << 0)
+#define BPF_F_ALLOW_MULTI	(1U << 1)
+
+/* If BPF_F_STRICT_ALIGNMENT is used in BPF_PROG_LOAD command, the
+ * verifier will perform strict alignment checking as if the kernel
+ * has been built with CONFIG_EFFICIENT_UNALIGNED_ACCESS not set,
+ * and NET_IP_ALIGN defined to 2.
+ */
+#define BPF_F_STRICT_ALIGNMENT	(1U << 0)
+
+/* If BPF_F_ANY_ALIGNMENT is used in BPF_PROF_LOAD command, the
+ * verifier will allow any alignment whatsoever.  On platforms
+ * with strict alignment requirements for loads ands stores (such
+ * as sparc and mips) the verifier validates that all loads and
+ * stores provably follow this requirement.  This flag turns that
+ * checking and enforcement off.
+ *
+ * It is mostly used for testing when we want to validate the
+ * context and memory access aspects of the verifier, but because
+ * of an unaligned access the alignment check would trigger before
+ * the one we are interested in.
+ */
+#define BPF_F_ANY_ALIGNMENT	(1U << 1)
+
+/* BPF_F_TEST_RND_HI32 is used in BPF_PROG_LOAD command for testing purpose.
+ * Verifier does sub-register def/use analysis and identifies instructions whose
+ * def only matters for low 32-bit, high 32-bit is never referenced later
+ * through implicit zero extension. Therefore verifier notifies JIT back-ends
+ * that it is safe to ignore clearing high 32-bit for these instructions. This
+ * saves some back-ends a lot of code-gen. However such optimization is not
+ * necessary on some arches, for example x86_64, arm64 etc, whose JIT back-ends
+ * hence hasn't used verifier's analysis result. But, we really want to have a
+ * way to be able to verify the correctness of the described optimization on
+ * x86_64 on which testsuites are frequently exercised.
+ *
+ * So, this flag is introduced. Once it is set, verifier will randomize high
+ * 32-bit for those instructions who has been identified as safe to ignore them.
+ * Then, if verifier is not doing correct analysis, such randomization will
+ * regress tests to expose bugs.
+ */
+#define BPF_F_TEST_RND_HI32	(1U << 2)
+
+/* The verifier internal test flag. Behavior is undefined */
+#define BPF_F_TEST_STATE_FREQ	(1U << 3)
+
+/* When BPF ldimm64's insn[0].src_reg != 0 then this can have
+ * two extensions:
+ *
+ * insn[0].src_reg:  BPF_PSEUDO_MAP_FD   BPF_PSEUDO_MAP_VALUE
+ * insn[0].imm:      map fd              map fd
+ * insn[1].imm:      0                   offset into value
+ * insn[0].off:      0                   0
+ * insn[1].off:      0                   0
+ * ldimm64 rewrite:  address of map      address of map[0]+offset
+ * verifier type:    CONST_PTR_TO_MAP    PTR_TO_MAP_VALUE
+ */
+#define BPF_PSEUDO_MAP_FD	1
+#define BPF_PSEUDO_MAP_VALUE	2
+
+/* when bpf_call->src_reg == BPF_PSEUDO_CALL, bpf_call->imm == pc-relative
+ * offset to another bpf function
+ */
+#define BPF_PSEUDO_CALL		1
+
+/* flags for BPF_MAP_UPDATE_ELEM command */
+#define BPF_ANY		0 /* create new element or update existing */
+#define BPF_NOEXIST	1 /* create new element if it didn't exist */
+#define BPF_EXIST	2 /* update existing element */
+#define BPF_F_LOCK	4 /* spin_lock-ed map_lookup/map_update */
+
+/* flags for BPF_MAP_CREATE command */
+#define BPF_F_NO_PREALLOC	(1U << 0)
+/* Instead of having one common LRU list in the
+ * BPF_MAP_TYPE_LRU_[PERCPU_]HASH map, use a percpu LRU list
+ * which can scale and perform better.
+ * Note, the LRU nodes (including free nodes) cannot be moved
+ * across different LRU lists.
+ */
+#define BPF_F_NO_COMMON_LRU	(1U << 1)
+/* Specify numa node during map creation */
+#define BPF_F_NUMA_NODE		(1U << 2)
+
+#define BPF_OBJ_NAME_LEN 16U
+
+/* Flags for accessing BPF object from syscall side. */
+#define BPF_F_RDONLY		(1U << 3)
+#define BPF_F_WRONLY		(1U << 4)
+
+/* Flag for stack_map, store build_id+offset instead of pointer */
+#define BPF_F_STACK_BUILD_ID	(1U << 5)
+
+/* Zero-initialize hash function seed. This should only be used for testing. */
+#define BPF_F_ZERO_SEED		(1U << 6)
+
+/* Flags for accessing BPF object from program side. */
+#define BPF_F_RDONLY_PROG	(1U << 7)
+#define BPF_F_WRONLY_PROG	(1U << 8)
+
+/* Clone map from listener for newly accepted socket */
+#define BPF_F_CLONE		(1U << 9)
+
+/* flags for BPF_PROG_QUERY */
+#define BPF_F_QUERY_EFFECTIVE	(1U << 0)
+
+enum bpf_stack_build_id_status {
+	/* user space need an empty entry to identify end of a trace */
+	BPF_STACK_BUILD_ID_EMPTY = 0,
+	/* with valid build_id and offset */
+	BPF_STACK_BUILD_ID_VALID = 1,
+	/* couldn't get build_id, fallback to ip */
+	BPF_STACK_BUILD_ID_IP = 2,
+};
+
+#define BPF_BUILD_ID_SIZE 20
+struct bpf_stack_build_id {
+	__s32		status;
+	unsigned char	build_id[BPF_BUILD_ID_SIZE];
+	union {
+		__u64	offset;
+		__u64	ip;
+	};
+};
+
+union bpf_attr {
+	struct { /* anonymous struct used by BPF_MAP_CREATE command */
+		__u32	map_type;	/* one of enum bpf_map_type */
+		__u32	key_size;	/* size of key in bytes */
+		__u32	value_size;	/* size of value in bytes */
+		__u32	max_entries;	/* max number of entries in a map */
+		__u32	map_flags;	/* BPF_MAP_CREATE related
+					 * flags defined above.
+					 */
+		__u32	inner_map_fd;	/* fd pointing to the inner map */
+		__u32	numa_node;	/* numa node (effective only if
+					 * BPF_F_NUMA_NODE is set).
+					 */
+		char	map_name[BPF_OBJ_NAME_LEN];
+		__u32	map_ifindex;	/* ifindex of netdev to create on */
+		__u32	btf_fd;		/* fd pointing to a BTF type data */
+		__u32	btf_key_type_id;	/* BTF type_id of the key */
+		__u32	btf_value_type_id;	/* BTF type_id of the value */
+	};
+
+	struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */
+		__u32		map_fd;
+		__aligned_u64	key;
+		union {
+			__aligned_u64 value;
+			__aligned_u64 next_key;
+		};
+		__u64		flags;
+	};
+
+	struct { /* anonymous struct used by BPF_PROG_LOAD command */
+		__u32		prog_type;	/* one of enum bpf_prog_type */
+		__u32		insn_cnt;
+		__aligned_u64	insns;
+		__aligned_u64	license;
+		__u32		log_level;	/* verbosity level of verifier */
+		__u32		log_size;	/* size of user buffer */
+		__aligned_u64	log_buf;	/* user supplied buffer */
+		__u32		kern_version;	/* not used */
+		__u32		prog_flags;
+		char		prog_name[BPF_OBJ_NAME_LEN];
+		__u32		prog_ifindex;	/* ifindex of netdev to prep for */
+		/* For some prog types expected attach type must be known at
+		 * load time to verify attach type specific parts of prog
+		 * (context accesses, allowed helpers, etc).
+		 */
+		__u32		expected_attach_type;
+		__u32		prog_btf_fd;	/* fd pointing to BTF type data */
+		__u32		func_info_rec_size;	/* userspace bpf_func_info size */
+		__aligned_u64	func_info;	/* func info */
+		__u32		func_info_cnt;	/* number of bpf_func_info records */
+		__u32		line_info_rec_size;	/* userspace bpf_line_info size */
+		__aligned_u64	line_info;	/* line info */
+		__u32		line_info_cnt;	/* number of bpf_line_info records */
+	};
+
+	struct { /* anonymous struct used by BPF_OBJ_* commands */
+		__aligned_u64	pathname;
+		__u32		bpf_fd;
+		__u32		file_flags;
+	};
+
+	struct { /* anonymous struct used by BPF_PROG_ATTACH/DETACH commands */
+		__u32		target_fd;	/* container object to attach to */
+		__u32		attach_bpf_fd;	/* eBPF program to attach */
+		__u32		attach_type;
+		__u32		attach_flags;
+	};
+
+	struct { /* anonymous struct used by BPF_PROG_TEST_RUN command */
+		__u32		prog_fd;
+		__u32		retval;
+		__u32		data_size_in;	/* input: len of data_in */
+		__u32		data_size_out;	/* input/output: len of data_out
+						 *   returns ENOSPC if data_out
+						 *   is too small.
+						 */
+		__aligned_u64	data_in;
+		__aligned_u64	data_out;
+		__u32		repeat;
+		__u32		duration;
+		__u32		ctx_size_in;	/* input: len of ctx_in */
+		__u32		ctx_size_out;	/* input/output: len of ctx_out
+						 *   returns ENOSPC if ctx_out
+						 *   is too small.
+						 */
+		__aligned_u64	ctx_in;
+		__aligned_u64	ctx_out;
+	} test;
+
+	struct { /* anonymous struct used by BPF_*_GET_*_ID */
+		union {
+			__u32		start_id;
+			__u32		prog_id;
+			__u32		map_id;
+			__u32		btf_id;
+		};
+		__u32		next_id;
+		__u32		open_flags;
+	};
+
+	struct { /* anonymous struct used by BPF_OBJ_GET_INFO_BY_FD */
+		__u32		bpf_fd;
+		__u32		info_len;
+		__aligned_u64	info;
+	} info;
+
+	struct { /* anonymous struct used by BPF_PROG_QUERY command */
+		__u32		target_fd;	/* container object to query */
+		__u32		attach_type;
+		__u32		query_flags;
+		__u32		attach_flags;
+		__aligned_u64	prog_ids;
+		__u32		prog_cnt;
+	} query;
+
+	struct {
+		__u64 name;
+		__u32 prog_fd;
+	} raw_tracepoint;
+
+	struct { /* anonymous struct for BPF_BTF_LOAD */
+		__aligned_u64	btf;
+		__aligned_u64	btf_log_buf;
+		__u32		btf_size;
+		__u32		btf_log_size;
+		__u32		btf_log_level;
+	};
+
+	struct {
+		__u32		pid;		/* input: pid */
+		__u32		fd;		/* input: fd */
+		__u32		flags;		/* input: flags */
+		__u32		buf_len;	/* input/output: buf len */
+		__aligned_u64	buf;		/* input/output:
+						 *   tp_name for tracepoint
+						 *   symbol for kprobe
+						 *   filename for uprobe
+						 */
+		__u32		prog_id;	/* output: prod_id */
+		__u32		fd_type;	/* output: BPF_FD_TYPE_* */
+		__u64		probe_offset;	/* output: probe_offset */
+		__u64		probe_addr;	/* output: probe_addr */
+	} task_fd_query;
+} __attribute__((aligned(8)));
+
+/* The description below is an attempt at providing documentation to eBPF
+ * developers about the multiple available eBPF helper functions. It can be
+ * parsed and used to produce a manual page. The workflow is the following,
+ * and requires the rst2man utility:
+ *
+ *     $ ./scripts/bpf_helpers_doc.py \
+ *             --filename include/uapi/linux/bpf.h > /tmp/bpf-helpers.rst
+ *     $ rst2man /tmp/bpf-helpers.rst > /tmp/bpf-helpers.7
+ *     $ man /tmp/bpf-helpers.7
+ *
+ * Note that in order to produce this external documentation, some RST
+ * formatting is used in the descriptions to get "bold" and "italics" in
+ * manual pages. Also note that the few trailing white spaces are
+ * intentional, removing them would break paragraphs for rst2man.
+ *
+ * Start of BPF helper function descriptions:
+ *
+ * void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
+ * 	Description
+ * 		Perform a lookup in *map* for an entry associated to *key*.
+ * 	Return
+ * 		Map value associated to *key*, or **NULL** if no entry was
+ * 		found.
+ *
+ * int bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)
+ * 	Description
+ * 		Add or update the value of the entry associated to *key* in
+ * 		*map* with *value*. *flags* is one of:
+ *
+ * 		**BPF_NOEXIST**
+ * 			The entry for *key* must not exist in the map.
+ * 		**BPF_EXIST**
+ * 			The entry for *key* must already exist in the map.
+ * 		**BPF_ANY**
+ * 			No condition on the existence of the entry for *key*.
+ *
+ * 		Flag value **BPF_NOEXIST** cannot be used for maps of types
+ * 		**BPF_MAP_TYPE_ARRAY** or **BPF_MAP_TYPE_PERCPU_ARRAY**  (all
+ * 		elements always exist), the helper would return an error.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_map_delete_elem(struct bpf_map *map, const void *key)
+ * 	Description
+ * 		Delete entry with *key* from *map*.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_probe_read(void *dst, u32 size, const void *src)
+ * 	Description
+ * 		For tracing programs, safely attempt to read *size* bytes from
+ * 		address *src* and store the data in *dst*.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * u64 bpf_ktime_get_ns(void)
+ * 	Description
+ * 		Return the time elapsed since system boot, in nanoseconds.
+ * 	Return
+ * 		Current *ktime*.
+ *
+ * int bpf_trace_printk(const char *fmt, u32 fmt_size, ...)
+ * 	Description
+ * 		This helper is a "printk()-like" facility for debugging. It
+ * 		prints a message defined by format *fmt* (of size *fmt_size*)
+ * 		to file *\/sys/kernel/debug/tracing/trace* from DebugFS, if
+ * 		available. It can take up to three additional **u64**
+ * 		arguments (as an eBPF helpers, the total number of arguments is
+ * 		limited to five).
+ *
+ * 		Each time the helper is called, it appends a line to the trace.
+ * 		Lines are discarded while *\/sys/kernel/debug/tracing/trace* is
+ * 		open, use *\/sys/kernel/debug/tracing/trace_pipe* to avoid this.
+ * 		The format of the trace is customizable, and the exact output
+ * 		one will get depends on the options set in
+ * 		*\/sys/kernel/debug/tracing/trace_options* (see also the
+ * 		*README* file under the same directory). However, it usually
+ * 		defaults to something like:
+ *
+ * 		::
+ *
+ * 			telnet-470   [001] .N.. 419421.045894: 0x00000001: <formatted msg>
+ *
+ * 		In the above:
+ *
+ * 			* ``telnet`` is the name of the current task.
+ * 			* ``470`` is the PID of the current task.
+ * 			* ``001`` is the CPU number on which the task is
+ * 			  running.
+ * 			* In ``.N..``, each character refers to a set of
+ * 			  options (whether irqs are enabled, scheduling
+ * 			  options, whether hard/softirqs are running, level of
+ * 			  preempt_disabled respectively). **N** means that
+ * 			  **TIF_NEED_RESCHED** and **PREEMPT_NEED_RESCHED**
+ * 			  are set.
+ * 			* ``419421.045894`` is a timestamp.
+ * 			* ``0x00000001`` is a fake value used by BPF for the
+ * 			  instruction pointer register.
+ * 			* ``<formatted msg>`` is the message formatted with
+ * 			  *fmt*.
+ *
+ * 		The conversion specifiers supported by *fmt* are similar, but
+ * 		more limited than for printk(). They are **%d**, **%i**,
+ * 		**%u**, **%x**, **%ld**, **%li**, **%lu**, **%lx**, **%lld**,
+ * 		**%lli**, **%llu**, **%llx**, **%p**, **%s**. No modifier (size
+ * 		of field, padding with zeroes, etc.) is available, and the
+ * 		helper will return **-EINVAL** (but print nothing) if it
+ * 		encounters an unknown specifier.
+ *
+ * 		Also, note that **bpf_trace_printk**\ () is slow, and should
+ * 		only be used for debugging purposes. For this reason, a notice
+ * 		bloc (spanning several lines) is printed to kernel logs and
+ * 		states that the helper should not be used "for production use"
+ * 		the first time this helper is used (or more precisely, when
+ * 		**trace_printk**\ () buffers are allocated). For passing values
+ * 		to user space, perf events should be preferred.
+ * 	Return
+ * 		The number of bytes written to the buffer, or a negative error
+ * 		in case of failure.
+ *
+ * u32 bpf_get_prandom_u32(void)
+ * 	Description
+ * 		Get a pseudo-random number.
+ *
+ * 		From a security point of view, this helper uses its own
+ * 		pseudo-random internal state, and cannot be used to infer the
+ * 		seed of other random functions in the kernel. However, it is
+ * 		essential to note that the generator used by the helper is not
+ * 		cryptographically secure.
+ * 	Return
+ * 		A random 32-bit unsigned value.
+ *
+ * u32 bpf_get_smp_processor_id(void)
+ * 	Description
+ * 		Get the SMP (symmetric multiprocessing) processor id. Note that
+ * 		all programs run with preemption disabled, which means that the
+ * 		SMP processor id is stable during all the execution of the
+ * 		program.
+ * 	Return
+ * 		The SMP id of the processor running the program.
+ *
+ * int bpf_skb_store_bytes(struct sk_buff *skb, u32 offset, const void *from, u32 len, u64 flags)
+ * 	Description
+ * 		Store *len* bytes from address *from* into the packet
+ * 		associated to *skb*, at *offset*. *flags* are a combination of
+ * 		**BPF_F_RECOMPUTE_CSUM** (automatically recompute the
+ * 		checksum for the packet after storing the bytes) and
+ * 		**BPF_F_INVALIDATE_HASH** (set *skb*\ **->hash**, *skb*\
+ * 		**->swhash** and *skb*\ **->l4hash** to 0).
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_l3_csum_replace(struct sk_buff *skb, u32 offset, u64 from, u64 to, u64 size)
+ * 	Description
+ * 		Recompute the layer 3 (e.g. IP) checksum for the packet
+ * 		associated to *skb*. Computation is incremental, so the helper
+ * 		must know the former value of the header field that was
+ * 		modified (*from*), the new value of this field (*to*), and the
+ * 		number of bytes (2 or 4) for this field, stored in *size*.
+ * 		Alternatively, it is possible to store the difference between
+ * 		the previous and the new values of the header field in *to*, by
+ * 		setting *from* and *size* to 0. For both methods, *offset*
+ * 		indicates the location of the IP checksum within the packet.
+ *
+ * 		This helper works in combination with **bpf_csum_diff**\ (),
+ * 		which does not update the checksum in-place, but offers more
+ * 		flexibility and can handle sizes larger than 2 or 4 for the
+ * 		checksum to update.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_l4_csum_replace(struct sk_buff *skb, u32 offset, u64 from, u64 to, u64 flags)
+ * 	Description
+ * 		Recompute the layer 4 (e.g. TCP, UDP or ICMP) checksum for the
+ * 		packet associated to *skb*. Computation is incremental, so the
+ * 		helper must know the former value of the header field that was
+ * 		modified (*from*), the new value of this field (*to*), and the
+ * 		number of bytes (2 or 4) for this field, stored on the lowest
+ * 		four bits of *flags*. Alternatively, it is possible to store
+ * 		the difference between the previous and the new values of the
+ * 		header field in *to*, by setting *from* and the four lowest
+ * 		bits of *flags* to 0. For both methods, *offset* indicates the
+ * 		location of the IP checksum within the packet. In addition to
+ * 		the size of the field, *flags* can be added (bitwise OR) actual
+ * 		flags. With **BPF_F_MARK_MANGLED_0**, a null checksum is left
+ * 		untouched (unless **BPF_F_MARK_ENFORCE** is added as well), and
+ * 		for updates resulting in a null checksum the value is set to
+ * 		**CSUM_MANGLED_0** instead. Flag **BPF_F_PSEUDO_HDR** indicates
+ * 		the checksum is to be computed against a pseudo-header.
+ *
+ * 		This helper works in combination with **bpf_csum_diff**\ (),
+ * 		which does not update the checksum in-place, but offers more
+ * 		flexibility and can handle sizes larger than 2 or 4 for the
+ * 		checksum to update.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_tail_call(void *ctx, struct bpf_map *prog_array_map, u32 index)
+ * 	Description
+ * 		This special helper is used to trigger a "tail call", or in
+ * 		other words, to jump into another eBPF program. The same stack
+ * 		frame is used (but values on stack and in registers for the
+ * 		caller are not accessible to the callee). This mechanism allows
+ * 		for program chaining, either for raising the maximum number of
+ * 		available eBPF instructions, or to execute given programs in
+ * 		conditional blocks. For security reasons, there is an upper
+ * 		limit to the number of successive tail calls that can be
+ * 		performed.
+ *
+ * 		Upon call of this helper, the program attempts to jump into a
+ * 		program referenced at index *index* in *prog_array_map*, a
+ * 		special map of type **BPF_MAP_TYPE_PROG_ARRAY**, and passes
+ * 		*ctx*, a pointer to the context.
+ *
+ * 		If the call succeeds, the kernel immediately runs the first
+ * 		instruction of the new program. This is not a function call,
+ * 		and it never returns to the previous program. If the call
+ * 		fails, then the helper has no effect, and the caller continues
+ * 		to run its subsequent instructions. A call can fail if the
+ * 		destination program for the jump does not exist (i.e. *index*
+ * 		is superior to the number of entries in *prog_array_map*), or
+ * 		if the maximum number of tail calls has been reached for this
+ * 		chain of programs. This limit is defined in the kernel by the
+ * 		macro **MAX_TAIL_CALL_CNT** (not accessible to user space),
+ * 		which is currently set to 32.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_clone_redirect(struct sk_buff *skb, u32 ifindex, u64 flags)
+ * 	Description
+ * 		Clone and redirect the packet associated to *skb* to another
+ * 		net device of index *ifindex*. Both ingress and egress
+ * 		interfaces can be used for redirection. The **BPF_F_INGRESS**
+ * 		value in *flags* is used to make the distinction (ingress path
+ * 		is selected if the flag is present, egress path otherwise).
+ * 		This is the only flag supported for now.
+ *
+ * 		In comparison with **bpf_redirect**\ () helper,
+ * 		**bpf_clone_redirect**\ () has the associated cost of
+ * 		duplicating the packet buffer, but this can be executed out of
+ * 		the eBPF program. Conversely, **bpf_redirect**\ () is more
+ * 		efficient, but it is handled through an action code where the
+ * 		redirection happens only after the eBPF program has returned.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * u64 bpf_get_current_pid_tgid(void)
+ * 	Return
+ * 		A 64-bit integer containing the current tgid and pid, and
+ * 		created as such:
+ * 		*current_task*\ **->tgid << 32 \|**
+ * 		*current_task*\ **->pid**.
+ *
+ * u64 bpf_get_current_uid_gid(void)
+ * 	Return
+ * 		A 64-bit integer containing the current GID and UID, and
+ * 		created as such: *current_gid* **<< 32 \|** *current_uid*.
+ *
+ * int bpf_get_current_comm(char *buf, u32 size_of_buf)
+ * 	Description
+ * 		Copy the **comm** attribute of the current task into *buf* of
+ * 		*size_of_buf*. The **comm** attribute contains the name of
+ * 		the executable (excluding the path) for the current task. The
+ * 		*size_of_buf* must be strictly positive. On success, the
+ * 		helper makes sure that the *buf* is NUL-terminated. On failure,
+ * 		it is filled with zeroes.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * u32 bpf_get_cgroup_classid(struct sk_buff *skb)
+ * 	Description
+ * 		Retrieve the classid for the current task, i.e. for the net_cls
+ * 		cgroup to which *skb* belongs.
+ *
+ * 		This helper can be used on TC egress path, but not on ingress.
+ *
+ * 		The net_cls cgroup provides an interface to tag network packets
+ * 		based on a user-provided identifier for all traffic coming from
+ * 		the tasks belonging to the related cgroup. See also the related
+ * 		kernel documentation, available from the Linux sources in file
+ * 		*Documentation/admin-guide/cgroup-v1/net_cls.rst*.
+ *
+ * 		The Linux kernel has two versions for cgroups: there are
+ * 		cgroups v1 and cgroups v2. Both are available to users, who can
+ * 		use a mixture of them, but note that the net_cls cgroup is for
+ * 		cgroup v1 only. This makes it incompatible with BPF programs
+ * 		run on cgroups, which is a cgroup-v2-only feature (a socket can
+ * 		only hold data for one version of cgroups at a time).
+ *
+ * 		This helper is only available is the kernel was compiled with
+ * 		the **CONFIG_CGROUP_NET_CLASSID** configuration option set to
+ * 		"**y**" or to "**m**".
+ * 	Return
+ * 		The classid, or 0 for the default unconfigured classid.
+ *
+ * int bpf_skb_vlan_push(struct sk_buff *skb, __be16 vlan_proto, u16 vlan_tci)
+ * 	Description
+ * 		Push a *vlan_tci* (VLAN tag control information) of protocol
+ * 		*vlan_proto* to the packet associated to *skb*, then update
+ * 		the checksum. Note that if *vlan_proto* is different from
+ * 		**ETH_P_8021Q** and **ETH_P_8021AD**, it is considered to
+ * 		be **ETH_P_8021Q**.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_skb_vlan_pop(struct sk_buff *skb)
+ * 	Description
+ * 		Pop a VLAN header from the packet associated to *skb*.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_skb_get_tunnel_key(struct sk_buff *skb, struct bpf_tunnel_key *key, u32 size, u64 flags)
+ * 	Description
+ * 		Get tunnel metadata. This helper takes a pointer *key* to an
+ * 		empty **struct bpf_tunnel_key** of **size**, that will be
+ * 		filled with tunnel metadata for the packet associated to *skb*.
+ * 		The *flags* can be set to **BPF_F_TUNINFO_IPV6**, which
+ * 		indicates that the tunnel is based on IPv6 protocol instead of
+ * 		IPv4.
+ *
+ * 		The **struct bpf_tunnel_key** is an object that generalizes the
+ * 		principal parameters used by various tunneling protocols into a
+ * 		single struct. This way, it can be used to easily make a
+ * 		decision based on the contents of the encapsulation header,
+ * 		"summarized" in this struct. In particular, it holds the IP
+ * 		address of the remote end (IPv4 or IPv6, depending on the case)
+ * 		in *key*\ **->remote_ipv4** or *key*\ **->remote_ipv6**. Also,
+ * 		this struct exposes the *key*\ **->tunnel_id**, which is
+ * 		generally mapped to a VNI (Virtual Network Identifier), making
+ * 		it programmable together with the **bpf_skb_set_tunnel_key**\
+ * 		() helper.
+ *
+ * 		Let's imagine that the following code is part of a program
+ * 		attached to the TC ingress interface, on one end of a GRE
+ * 		tunnel, and is supposed to filter out all messages coming from
+ * 		remote ends with IPv4 address other than 10.0.0.1:
+ *
+ * 		::
+ *
+ * 			int ret;
+ * 			struct bpf_tunnel_key key = {};
+ * 			
+ * 			ret = bpf_skb_get_tunnel_key(skb, &key, sizeof(key), 0);
+ * 			if (ret < 0)
+ * 				return TC_ACT_SHOT;	// drop packet
+ * 			
+ * 			if (key.remote_ipv4 != 0x0a000001)
+ * 				return TC_ACT_SHOT;	// drop packet
+ * 			
+ * 			return TC_ACT_OK;		// accept packet
+ *
+ * 		This interface can also be used with all encapsulation devices
+ * 		that can operate in "collect metadata" mode: instead of having
+ * 		one network device per specific configuration, the "collect
+ * 		metadata" mode only requires a single device where the
+ * 		configuration can be extracted from this helper.
+ *
+ * 		This can be used together with various tunnels such as VXLan,
+ * 		Geneve, GRE or IP in IP (IPIP).
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_skb_set_tunnel_key(struct sk_buff *skb, struct bpf_tunnel_key *key, u32 size, u64 flags)
+ * 	Description
+ * 		Populate tunnel metadata for packet associated to *skb.* The
+ * 		tunnel metadata is set to the contents of *key*, of *size*. The
+ * 		*flags* can be set to a combination of the following values:
+ *
+ * 		**BPF_F_TUNINFO_IPV6**
+ * 			Indicate that the tunnel is based on IPv6 protocol
+ * 			instead of IPv4.
+ * 		**BPF_F_ZERO_CSUM_TX**
+ * 			For IPv4 packets, add a flag to tunnel metadata
+ * 			indicating that checksum computation should be skipped
+ * 			and checksum set to zeroes.
+ * 		**BPF_F_DONT_FRAGMENT**
+ * 			Add a flag to tunnel metadata indicating that the
+ * 			packet should not be fragmented.
+ * 		**BPF_F_SEQ_NUMBER**
+ * 			Add a flag to tunnel metadata indicating that a
+ * 			sequence number should be added to tunnel header before
+ * 			sending the packet. This flag was added for GRE
+ * 			encapsulation, but might be used with other protocols
+ * 			as well in the future.
+ *
+ * 		Here is a typical usage on the transmit path:
+ *
+ * 		::
+ *
+ * 			struct bpf_tunnel_key key;
+ * 			     populate key ...
+ * 			bpf_skb_set_tunnel_key(skb, &key, sizeof(key), 0);
+ * 			bpf_clone_redirect(skb, vxlan_dev_ifindex, 0);
+ *
+ * 		See also the description of the **bpf_skb_get_tunnel_key**\ ()
+ * 		helper for additional information.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * u64 bpf_perf_event_read(struct bpf_map *map, u64 flags)
+ * 	Description
+ * 		Read the value of a perf event counter. This helper relies on a
+ * 		*map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. The nature of
+ * 		the perf event counter is selected when *map* is updated with
+ * 		perf event file descriptors. The *map* is an array whose size
+ * 		is the number of available CPUs, and each cell contains a value
+ * 		relative to one CPU. The value to retrieve is indicated by
+ * 		*flags*, that contains the index of the CPU to look up, masked
+ * 		with **BPF_F_INDEX_MASK**. Alternatively, *flags* can be set to
+ * 		**BPF_F_CURRENT_CPU** to indicate that the value for the
+ * 		current CPU should be retrieved.
+ *
+ * 		Note that before Linux 4.13, only hardware perf event can be
+ * 		retrieved.
+ *
+ * 		Also, be aware that the newer helper
+ * 		**bpf_perf_event_read_value**\ () is recommended over
+ * 		**bpf_perf_event_read**\ () in general. The latter has some ABI
+ * 		quirks where error and counter value are used as a return code
+ * 		(which is wrong to do since ranges may overlap). This issue is
+ * 		fixed with **bpf_perf_event_read_value**\ (), which at the same
+ * 		time provides more features over the **bpf_perf_event_read**\
+ * 		() interface. Please refer to the description of
+ * 		**bpf_perf_event_read_value**\ () for details.
+ * 	Return
+ * 		The value of the perf event counter read from the map, or a
+ * 		negative error code in case of failure.
+ *
+ * int bpf_redirect(u32 ifindex, u64 flags)
+ * 	Description
+ * 		Redirect the packet to another net device of index *ifindex*.
+ * 		This helper is somewhat similar to **bpf_clone_redirect**\
+ * 		(), except that the packet is not cloned, which provides
+ * 		increased performance.
+ *
+ * 		Except for XDP, both ingress and egress interfaces can be used
+ * 		for redirection. The **BPF_F_INGRESS** value in *flags* is used
+ * 		to make the distinction (ingress path is selected if the flag
+ * 		is present, egress path otherwise). Currently, XDP only
+ * 		supports redirection to the egress interface, and accepts no
+ * 		flag at all.
+ *
+ * 		The same effect can be attained with the more generic
+ * 		**bpf_redirect_map**\ (), which requires specific maps to be
+ * 		used but offers better performance.
+ * 	Return
+ * 		For XDP, the helper returns **XDP_REDIRECT** on success or
+ * 		**XDP_ABORTED** on error. For other program types, the values
+ * 		are **TC_ACT_REDIRECT** on success or **TC_ACT_SHOT** on
+ * 		error.
+ *
+ * u32 bpf_get_route_realm(struct sk_buff *skb)
+ * 	Description
+ * 		Retrieve the realm or the route, that is to say the
+ * 		**tclassid** field of the destination for the *skb*. The
+ * 		indentifier retrieved is a user-provided tag, similar to the
+ * 		one used with the net_cls cgroup (see description for
+ * 		**bpf_get_cgroup_classid**\ () helper), but here this tag is
+ * 		held by a route (a destination entry), not by a task.
+ *
+ * 		Retrieving this identifier works with the clsact TC egress hook
+ * 		(see also **tc-bpf(8)**), or alternatively on conventional
+ * 		classful egress qdiscs, but not on TC ingress path. In case of
+ * 		clsact TC egress hook, this has the advantage that, internally,
+ * 		the destination entry has not been dropped yet in the transmit
+ * 		path. Therefore, the destination entry does not need to be
+ * 		artificially held via **netif_keep_dst**\ () for a classful
+ * 		qdisc until the *skb* is freed.
+ *
+ * 		This helper is available only if the kernel was compiled with
+ * 		**CONFIG_IP_ROUTE_CLASSID** configuration option.
+ * 	Return
+ * 		The realm of the route for the packet associated to *skb*, or 0
+ * 		if none was found.
+ *
+ * int bpf_perf_event_output(struct pt_regs *ctx, struct bpf_map *map, u64 flags, void *data, u64 size)
+ * 	Description
+ * 		Write raw *data* blob into a special BPF perf event held by
+ * 		*map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. This perf
+ * 		event must have the following attributes: **PERF_SAMPLE_RAW**
+ * 		as **sample_type**, **PERF_TYPE_SOFTWARE** as **type**, and
+ * 		**PERF_COUNT_SW_BPF_OUTPUT** as **config**.
+ *
+ * 		The *flags* are used to indicate the index in *map* for which
+ * 		the value must be put, masked with **BPF_F_INDEX_MASK**.
+ * 		Alternatively, *flags* can be set to **BPF_F_CURRENT_CPU**
+ * 		to indicate that the index of the current CPU core should be
+ * 		used.
+ *
+ * 		The value to write, of *size*, is passed through eBPF stack and
+ * 		pointed by *data*.
+ *
+ * 		The context of the program *ctx* needs also be passed to the
+ * 		helper.
+ *
+ * 		On user space, a program willing to read the values needs to
+ * 		call **perf_event_open**\ () on the perf event (either for
+ * 		one or for all CPUs) and to store the file descriptor into the
+ * 		*map*. This must be done before the eBPF program can send data
+ * 		into it. An example is available in file
+ * 		*samples/bpf/trace_output_user.c* in the Linux kernel source
+ * 		tree (the eBPF program counterpart is in
+ * 		*samples/bpf/trace_output_kern.c*).
+ *
+ * 		**bpf_perf_event_output**\ () achieves better performance
+ * 		than **bpf_trace_printk**\ () for sharing data with user
+ * 		space, and is much better suitable for streaming data from eBPF
+ * 		programs.
+ *
+ * 		Note that this helper is not restricted to tracing use cases
+ * 		and can be used with programs attached to TC or XDP as well,
+ * 		where it allows for passing data to user space listeners. Data
+ * 		can be:
+ *
+ * 		* Only custom structs,
+ * 		* Only the packet payload, or
+ * 		* A combination of both.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_skb_load_bytes(const struct sk_buff *skb, u32 offset, void *to, u32 len)
+ * 	Description
+ * 		This helper was provided as an easy way to load data from a
+ * 		packet. It can be used to load *len* bytes from *offset* from
+ * 		the packet associated to *skb*, into the buffer pointed by
+ * 		*to*.
+ *
+ * 		Since Linux 4.7, usage of this helper has mostly been replaced
+ * 		by "direct packet access", enabling packet data to be
+ * 		manipulated with *skb*\ **->data** and *skb*\ **->data_end**
+ * 		pointing respectively to the first byte of packet data and to
+ * 		the byte after the last byte of packet data. However, it
+ * 		remains useful if one wishes to read large quantities of data
+ * 		at once from a packet into the eBPF stack.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_get_stackid(struct pt_regs *ctx, struct bpf_map *map, u64 flags)
+ * 	Description
+ * 		Walk a user or a kernel stack and return its id. To achieve
+ * 		this, the helper needs *ctx*, which is a pointer to the context
+ * 		on which the tracing program is executed, and a pointer to a
+ * 		*map* of type **BPF_MAP_TYPE_STACK_TRACE**.
+ *
+ * 		The last argument, *flags*, holds the number of stack frames to
+ * 		skip (from 0 to 255), masked with
+ * 		**BPF_F_SKIP_FIELD_MASK**. The next bits can be used to set
+ * 		a combination of the following flags:
+ *
+ * 		**BPF_F_USER_STACK**
+ * 			Collect a user space stack instead of a kernel stack.
+ * 		**BPF_F_FAST_STACK_CMP**
+ * 			Compare stacks by hash only.
+ * 		**BPF_F_REUSE_STACKID**
+ * 			If two different stacks hash into the same *stackid*,
+ * 			discard the old one.
+ *
+ * 		The stack id retrieved is a 32 bit long integer handle which
+ * 		can be further combined with other data (including other stack
+ * 		ids) and used as a key into maps. This can be useful for
+ * 		generating a variety of graphs (such as flame graphs or off-cpu
+ * 		graphs).
+ *
+ * 		For walking a stack, this helper is an improvement over
+ * 		**bpf_probe_read**\ (), which can be used with unrolled loops
+ * 		but is not efficient and consumes a lot of eBPF instructions.
+ * 		Instead, **bpf_get_stackid**\ () can collect up to
+ * 		**PERF_MAX_STACK_DEPTH** both kernel and user frames. Note that
+ * 		this limit can be controlled with the **sysctl** program, and
+ * 		that it should be manually increased in order to profile long
+ * 		user stacks (such as stacks for Java programs). To do so, use:
+ *
+ * 		::
+ *
+ * 			# sysctl kernel.perf_event_max_stack=<new value>
+ * 	Return
+ * 		The positive or null stack id on success, or a negative error
+ * 		in case of failure.
+ *
+ * s64 bpf_csum_diff(__be32 *from, u32 from_size, __be32 *to, u32 to_size, __wsum seed)
+ * 	Description
+ * 		Compute a checksum difference, from the raw buffer pointed by
+ * 		*from*, of length *from_size* (that must be a multiple of 4),
+ * 		towards the raw buffer pointed by *to*, of size *to_size*
+ * 		(same remark). An optional *seed* can be added to the value
+ * 		(this can be cascaded, the seed may come from a previous call
+ * 		to the helper).
+ *
+ * 		This is flexible enough to be used in several ways:
+ *
+ * 		* With *from_size* == 0, *to_size* > 0 and *seed* set to
+ * 		  checksum, it can be used when pushing new data.
+ * 		* With *from_size* > 0, *to_size* == 0 and *seed* set to
+ * 		  checksum, it can be used when removing data from a packet.
+ * 		* With *from_size* > 0, *to_size* > 0 and *seed* set to 0, it
+ * 		  can be used to compute a diff. Note that *from_size* and
+ * 		  *to_size* do not need to be equal.
+ *
+ * 		This helper can be used in combination with
+ * 		**bpf_l3_csum_replace**\ () and **bpf_l4_csum_replace**\ (), to
+ * 		which one can feed in the difference computed with
+ * 		**bpf_csum_diff**\ ().
+ * 	Return
+ * 		The checksum result, or a negative error code in case of
+ * 		failure.
+ *
+ * int bpf_skb_get_tunnel_opt(struct sk_buff *skb, u8 *opt, u32 size)
+ * 	Description
+ * 		Retrieve tunnel options metadata for the packet associated to
+ * 		*skb*, and store the raw tunnel option data to the buffer *opt*
+ * 		of *size*.
+ *
+ * 		This helper can be used with encapsulation devices that can
+ * 		operate in "collect metadata" mode (please refer to the related
+ * 		note in the description of **bpf_skb_get_tunnel_key**\ () for
+ * 		more details). A particular example where this can be used is
+ * 		in combination with the Geneve encapsulation protocol, where it
+ * 		allows for pushing (with **bpf_skb_get_tunnel_opt**\ () helper)
+ * 		and retrieving arbitrary TLVs (Type-Length-Value headers) from
+ * 		the eBPF program. This allows for full customization of these
+ * 		headers.
+ * 	Return
+ * 		The size of the option data retrieved.
+ *
+ * int bpf_skb_set_tunnel_opt(struct sk_buff *skb, u8 *opt, u32 size)
+ * 	Description
+ * 		Set tunnel options metadata for the packet associated to *skb*
+ * 		to the option data contained in the raw buffer *opt* of *size*.
+ *
+ * 		See also the description of the **bpf_skb_get_tunnel_opt**\ ()
+ * 		helper for additional information.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_skb_change_proto(struct sk_buff *skb, __be16 proto, u64 flags)
+ * 	Description
+ * 		Change the protocol of the *skb* to *proto*. Currently
+ * 		supported are transition from IPv4 to IPv6, and from IPv6 to
+ * 		IPv4. The helper takes care of the groundwork for the
+ * 		transition, including resizing the socket buffer. The eBPF
+ * 		program is expected to fill the new headers, if any, via
+ * 		**skb_store_bytes**\ () and to recompute the checksums with
+ * 		**bpf_l3_csum_replace**\ () and **bpf_l4_csum_replace**\
+ * 		(). The main case for this helper is to perform NAT64
+ * 		operations out of an eBPF program.
+ *
+ * 		Internally, the GSO type is marked as dodgy so that headers are
+ * 		checked and segments are recalculated by the GSO/GRO engine.
+ * 		The size for GSO target is adapted as well.
+ *
+ * 		All values for *flags* are reserved for future usage, and must
+ * 		be left at zero.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_skb_change_type(struct sk_buff *skb, u32 type)
+ * 	Description
+ * 		Change the packet type for the packet associated to *skb*. This
+ * 		comes down to setting *skb*\ **->pkt_type** to *type*, except
+ * 		the eBPF program does not have a write access to *skb*\
+ * 		**->pkt_type** beside this helper. Using a helper here allows
+ * 		for graceful handling of errors.
+ *
+ * 		The major use case is to change incoming *skb*s to
+ * 		**PACKET_HOST** in a programmatic way instead of having to
+ * 		recirculate via **redirect**\ (..., **BPF_F_INGRESS**), for
+ * 		example.
+ *
+ * 		Note that *type* only allows certain values. At this time, they
+ * 		are:
+ *
+ * 		**PACKET_HOST**
+ * 			Packet is for us.
+ * 		**PACKET_BROADCAST**
+ * 			Send packet to all.
+ * 		**PACKET_MULTICAST**
+ * 			Send packet to group.
+ * 		**PACKET_OTHERHOST**
+ * 			Send packet to someone else.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_skb_under_cgroup(struct sk_buff *skb, struct bpf_map *map, u32 index)
+ * 	Description
+ * 		Check whether *skb* is a descendant of the cgroup2 held by
+ * 		*map* of type **BPF_MAP_TYPE_CGROUP_ARRAY**, at *index*.
+ * 	Return
+ * 		The return value depends on the result of the test, and can be:
+ *
+ * 		* 0, if the *skb* failed the cgroup2 descendant test.
+ * 		* 1, if the *skb* succeeded the cgroup2 descendant test.
+ * 		* A negative error code, if an error occurred.
+ *
+ * u32 bpf_get_hash_recalc(struct sk_buff *skb)
+ * 	Description
+ * 		Retrieve the hash of the packet, *skb*\ **->hash**. If it is
+ * 		not set, in particular if the hash was cleared due to mangling,
+ * 		recompute this hash. Later accesses to the hash can be done
+ * 		directly with *skb*\ **->hash**.
+ *
+ * 		Calling **bpf_set_hash_invalid**\ (), changing a packet
+ * 		prototype with **bpf_skb_change_proto**\ (), or calling
+ * 		**bpf_skb_store_bytes**\ () with the
+ * 		**BPF_F_INVALIDATE_HASH** are actions susceptible to clear
+ * 		the hash and to trigger a new computation for the next call to
+ * 		**bpf_get_hash_recalc**\ ().
+ * 	Return
+ * 		The 32-bit hash.
+ *
+ * u64 bpf_get_current_task(void)
+ * 	Return
+ * 		A pointer to the current task struct.
+ *
+ * int bpf_probe_write_user(void *dst, const void *src, u32 len)
+ * 	Description
+ * 		Attempt in a safe way to write *len* bytes from the buffer
+ * 		*src* to *dst* in memory. It only works for threads that are in
+ * 		user context, and *dst* must be a valid user space address.
+ *
+ * 		This helper should not be used to implement any kind of
+ * 		security mechanism because of TOC-TOU attacks, but rather to
+ * 		debug, divert, and manipulate execution of semi-cooperative
+ * 		processes.
+ *
+ * 		Keep in mind that this feature is meant for experiments, and it
+ * 		has a risk of crashing the system and running programs.
+ * 		Therefore, when an eBPF program using this helper is attached,
+ * 		a warning including PID and process name is printed to kernel
+ * 		logs.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_current_task_under_cgroup(struct bpf_map *map, u32 index)
+ * 	Description
+ * 		Check whether the probe is being run is the context of a given
+ * 		subset of the cgroup2 hierarchy. The cgroup2 to test is held by
+ * 		*map* of type **BPF_MAP_TYPE_CGROUP_ARRAY**, at *index*.
+ * 	Return
+ * 		The return value depends on the result of the test, and can be:
+ *
+ * 		* 0, if the *skb* task belongs to the cgroup2.
+ * 		* 1, if the *skb* task does not belong to the cgroup2.
+ * 		* A negative error code, if an error occurred.
+ *
+ * int bpf_skb_change_tail(struct sk_buff *skb, u32 len, u64 flags)
+ * 	Description
+ * 		Resize (trim or grow) the packet associated to *skb* to the
+ * 		new *len*. The *flags* are reserved for future usage, and must
+ * 		be left at zero.
+ *
+ * 		The basic idea is that the helper performs the needed work to
+ * 		change the size of the packet, then the eBPF program rewrites
+ * 		the rest via helpers like **bpf_skb_store_bytes**\ (),
+ * 		**bpf_l3_csum_replace**\ (), **bpf_l3_csum_replace**\ ()
+ * 		and others. This helper is a slow path utility intended for
+ * 		replies with control messages. And because it is targeted for
+ * 		slow path, the helper itself can afford to be slow: it
+ * 		implicitly linearizes, unclones and drops offloads from the
+ * 		*skb*.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_skb_pull_data(struct sk_buff *skb, u32 len)
+ * 	Description
+ * 		Pull in non-linear data in case the *skb* is non-linear and not
+ * 		all of *len* are part of the linear section. Make *len* bytes
+ * 		from *skb* readable and writable. If a zero value is passed for
+ * 		*len*, then the whole length of the *skb* is pulled.
+ *
+ * 		This helper is only needed for reading and writing with direct
+ * 		packet access.
+ *
+ * 		For direct packet access, testing that offsets to access
+ * 		are within packet boundaries (test on *skb*\ **->data_end**) is
+ * 		susceptible to fail if offsets are invalid, or if the requested
+ * 		data is in non-linear parts of the *skb*. On failure the
+ * 		program can just bail out, or in the case of a non-linear
+ * 		buffer, use a helper to make the data available. The
+ * 		**bpf_skb_load_bytes**\ () helper is a first solution to access
+ * 		the data. Another one consists in using **bpf_skb_pull_data**
+ * 		to pull in once the non-linear parts, then retesting and
+ * 		eventually access the data.
+ *
+ * 		At the same time, this also makes sure the *skb* is uncloned,
+ * 		which is a necessary condition for direct write. As this needs
+ * 		to be an invariant for the write part only, the verifier
+ * 		detects writes and adds a prologue that is calling
+ * 		**bpf_skb_pull_data()** to effectively unclone the *skb* from
+ * 		the very beginning in case it is indeed cloned.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * s64 bpf_csum_update(struct sk_buff *skb, __wsum csum)
+ * 	Description
+ * 		Add the checksum *csum* into *skb*\ **->csum** in case the
+ * 		driver has supplied a checksum for the entire packet into that
+ * 		field. Return an error otherwise. This helper is intended to be
+ * 		used in combination with **bpf_csum_diff**\ (), in particular
+ * 		when the checksum needs to be updated after data has been
+ * 		written into the packet through direct packet access.
+ * 	Return
+ * 		The checksum on success, or a negative error code in case of
+ * 		failure.
+ *
+ * void bpf_set_hash_invalid(struct sk_buff *skb)
+ * 	Description
+ * 		Invalidate the current *skb*\ **->hash**. It can be used after
+ * 		mangling on headers through direct packet access, in order to
+ * 		indicate that the hash is outdated and to trigger a
+ * 		recalculation the next time the kernel tries to access this
+ * 		hash or when the **bpf_get_hash_recalc**\ () helper is called.
+ *
+ * int bpf_get_numa_node_id(void)
+ * 	Description
+ * 		Return the id of the current NUMA node. The primary use case
+ * 		for this helper is the selection of sockets for the local NUMA
+ * 		node, when the program is attached to sockets using the
+ * 		**SO_ATTACH_REUSEPORT_EBPF** option (see also **socket(7)**),
+ * 		but the helper is also available to other eBPF program types,
+ * 		similarly to **bpf_get_smp_processor_id**\ ().
+ * 	Return
+ * 		The id of current NUMA node.
+ *
+ * int bpf_skb_change_head(struct sk_buff *skb, u32 len, u64 flags)
+ * 	Description
+ * 		Grows headroom of packet associated to *skb* and adjusts the
+ * 		offset of the MAC header accordingly, adding *len* bytes of
+ * 		space. It automatically extends and reallocates memory as
+ * 		required.
+ *
+ * 		This helper can be used on a layer 3 *skb* to push a MAC header
+ * 		for redirection into a layer 2 device.
+ *
+ * 		All values for *flags* are reserved for future usage, and must
+ * 		be left at zero.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_xdp_adjust_head(struct xdp_buff *xdp_md, int delta)
+ * 	Description
+ * 		Adjust (move) *xdp_md*\ **->data** by *delta* bytes. Note that
+ * 		it is possible to use a negative value for *delta*. This helper
+ * 		can be used to prepare the packet for pushing or popping
+ * 		headers.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr)
+ * 	Description
+ * 		Copy a NUL terminated string from an unsafe address
+ * 		*unsafe_ptr* to *dst*. The *size* should include the
+ * 		terminating NUL byte. In case the string length is smaller than
+ * 		*size*, the target is not padded with further NUL bytes. If the
+ * 		string length is larger than *size*, just *size*-1 bytes are
+ * 		copied and the last byte is set to NUL.
+ *
+ * 		On success, the length of the copied string is returned. This
+ * 		makes this helper useful in tracing programs for reading
+ * 		strings, and more importantly to get its length at runtime. See
+ * 		the following snippet:
+ *
+ * 		::
+ *
+ * 			SEC("kprobe/sys_open")
+ * 			void bpf_sys_open(struct pt_regs *ctx)
+ * 			{
+ * 			        char buf[PATHLEN]; // PATHLEN is defined to 256
+ * 			        int res = bpf_probe_read_str(buf, sizeof(buf),
+ * 				                             ctx->di);
+ *
+ * 				// Consume buf, for example push it to
+ * 				// userspace via bpf_perf_event_output(); we
+ * 				// can use res (the string length) as event
+ * 				// size, after checking its boundaries.
+ * 			}
+ *
+ * 		In comparison, using **bpf_probe_read()** helper here instead
+ * 		to read the string would require to estimate the length at
+ * 		compile time, and would often result in copying more memory
+ * 		than necessary.
+ *
+ * 		Another useful use case is when parsing individual process
+ * 		arguments or individual environment variables navigating
+ * 		*current*\ **->mm->arg_start** and *current*\
+ * 		**->mm->env_start**: using this helper and the return value,
+ * 		one can quickly iterate at the right offset of the memory area.
+ * 	Return
+ * 		On success, the strictly positive length of the string,
+ * 		including the trailing NUL character. On error, a negative
+ * 		value.
+ *
+ * u64 bpf_get_socket_cookie(struct sk_buff *skb)
+ * 	Description
+ * 		If the **struct sk_buff** pointed by *skb* has a known socket,
+ * 		retrieve the cookie (generated by the kernel) of this socket.
+ * 		If no cookie has been set yet, generate a new cookie. Once
+ * 		generated, the socket cookie remains stable for the life of the
+ * 		socket. This helper can be useful for monitoring per socket
+ * 		networking traffic statistics as it provides a global socket
+ * 		identifier that can be assumed unique.
+ * 	Return
+ * 		A 8-byte long non-decreasing number on success, or 0 if the
+ * 		socket field is missing inside *skb*.
+ *
+ * u64 bpf_get_socket_cookie(struct bpf_sock_addr *ctx)
+ * 	Description
+ * 		Equivalent to bpf_get_socket_cookie() helper that accepts
+ * 		*skb*, but gets socket from **struct bpf_sock_addr** context.
+ * 	Return
+ * 		A 8-byte long non-decreasing number.
+ *
+ * u64 bpf_get_socket_cookie(struct bpf_sock_ops *ctx)
+ * 	Description
+ * 		Equivalent to bpf_get_socket_cookie() helper that accepts
+ * 		*skb*, but gets socket from **struct bpf_sock_ops** context.
+ * 	Return
+ * 		A 8-byte long non-decreasing number.
+ *
+ * u32 bpf_get_socket_uid(struct sk_buff *skb)
+ * 	Return
+ * 		The owner UID of the socket associated to *skb*. If the socket
+ * 		is **NULL**, or if it is not a full socket (i.e. if it is a
+ * 		time-wait or a request socket instead), **overflowuid** value
+ * 		is returned (note that **overflowuid** might also be the actual
+ * 		UID value for the socket).
+ *
+ * u32 bpf_set_hash(struct sk_buff *skb, u32 hash)
+ * 	Description
+ * 		Set the full hash for *skb* (set the field *skb*\ **->hash**)
+ * 		to value *hash*.
+ * 	Return
+ * 		0
+ *
+ * int bpf_setsockopt(struct bpf_sock_ops *bpf_socket, int level, int optname, char *optval, int optlen)
+ * 	Description
+ * 		Emulate a call to **setsockopt()** on the socket associated to
+ * 		*bpf_socket*, which must be a full socket. The *level* at
+ * 		which the option resides and the name *optname* of the option
+ * 		must be specified, see **setsockopt(2)** for more information.
+ * 		The option value of length *optlen* is pointed by *optval*.
+ *
+ * 		This helper actually implements a subset of **setsockopt()**.
+ * 		It supports the following *level*\ s:
+ *
+ * 		* **SOL_SOCKET**, which supports the following *optname*\ s:
+ * 		  **SO_RCVBUF**, **SO_SNDBUF**, **SO_MAX_PACING_RATE**,
+ * 		  **SO_PRIORITY**, **SO_RCVLOWAT**, **SO_MARK**.
+ * 		* **IPPROTO_TCP**, which supports the following *optname*\ s:
+ * 		  **TCP_CONGESTION**, **TCP_BPF_IW**,
+ * 		  **TCP_BPF_SNDCWND_CLAMP**.
+ * 		* **IPPROTO_IP**, which supports *optname* **IP_TOS**.
+ * 		* **IPPROTO_IPV6**, which supports *optname* **IPV6_TCLASS**.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_skb_adjust_room(struct sk_buff *skb, s32 len_diff, u32 mode, u64 flags)
+ * 	Description
+ * 		Grow or shrink the room for data in the packet associated to
+ * 		*skb* by *len_diff*, and according to the selected *mode*.
+ *
+ *		There are two supported modes at this time:
+ *
+ *		* **BPF_ADJ_ROOM_MAC**: Adjust room at the mac layer
+ *		  (room space is added or removed below the layer 2 header).
+ *
+ * 		* **BPF_ADJ_ROOM_NET**: Adjust room at the network layer
+ * 		  (room space is added or removed below the layer 3 header).
+ *
+ *		The following flags are supported at this time:
+ *
+ *		* **BPF_F_ADJ_ROOM_FIXED_GSO**: Do not adjust gso_size.
+ *		  Adjusting mss in this way is not allowed for datagrams.
+ *
+ *		* **BPF_F_ADJ_ROOM_ENCAP_L3_IPV4**,
+ *		  **BPF_F_ADJ_ROOM_ENCAP_L3_IPV6**:
+ *		  Any new space is reserved to hold a tunnel header.
+ *		  Configure skb offsets and other fields accordingly.
+ *
+ *		* **BPF_F_ADJ_ROOM_ENCAP_L4_GRE**,
+ *		  **BPF_F_ADJ_ROOM_ENCAP_L4_UDP**:
+ *		  Use with ENCAP_L3 flags to further specify the tunnel type.
+ *
+ *		* **BPF_F_ADJ_ROOM_ENCAP_L2**\ (*len*):
+ *		  Use with ENCAP_L3/L4 flags to further specify the tunnel
+ *		  type; *len* is the length of the inner MAC header.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_redirect_map(struct bpf_map *map, u32 key, u64 flags)
+ * 	Description
+ * 		Redirect the packet to the endpoint referenced by *map* at
+ * 		index *key*. Depending on its type, this *map* can contain
+ * 		references to net devices (for forwarding packets through other
+ * 		ports), or to CPUs (for redirecting XDP frames to another CPU;
+ * 		but this is only implemented for native XDP (with driver
+ * 		support) as of this writing).
+ *
+ * 		The lower two bits of *flags* are used as the return code if
+ * 		the map lookup fails. This is so that the return value can be
+ * 		one of the XDP program return codes up to XDP_TX, as chosen by
+ * 		the caller. Any higher bits in the *flags* argument must be
+ * 		unset.
+ *
+ * 		When used to redirect packets to net devices, this helper
+ * 		provides a high performance increase over **bpf_redirect**\ ().
+ * 		This is due to various implementation details of the underlying
+ * 		mechanisms, one of which is the fact that **bpf_redirect_map**\
+ * 		() tries to send packet as a "bulk" to the device.
+ * 	Return
+ * 		**XDP_REDIRECT** on success, or **XDP_ABORTED** on error.
+ *
+ * int bpf_sk_redirect_map(struct bpf_map *map, u32 key, u64 flags)
+ * 	Description
+ * 		Redirect the packet to the socket referenced by *map* (of type
+ * 		**BPF_MAP_TYPE_SOCKMAP**) at index *key*. Both ingress and
+ * 		egress interfaces can be used for redirection. The
+ * 		**BPF_F_INGRESS** value in *flags* is used to make the
+ * 		distinction (ingress path is selected if the flag is present,
+ * 		egress path otherwise). This is the only flag supported for now.
+ * 	Return
+ * 		**SK_PASS** on success, or **SK_DROP** on error.
+ *
+ * int bpf_sock_map_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags)
+ * 	Description
+ * 		Add an entry to, or update a *map* referencing sockets. The
+ * 		*skops* is used as a new value for the entry associated to
+ * 		*key*. *flags* is one of:
+ *
+ * 		**BPF_NOEXIST**
+ * 			The entry for *key* must not exist in the map.
+ * 		**BPF_EXIST**
+ * 			The entry for *key* must already exist in the map.
+ * 		**BPF_ANY**
+ * 			No condition on the existence of the entry for *key*.
+ *
+ * 		If the *map* has eBPF programs (parser and verdict), those will
+ * 		be inherited by the socket being added. If the socket is
+ * 		already attached to eBPF programs, this results in an error.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_xdp_adjust_meta(struct xdp_buff *xdp_md, int delta)
+ * 	Description
+ * 		Adjust the address pointed by *xdp_md*\ **->data_meta** by
+ * 		*delta* (which can be positive or negative). Note that this
+ * 		operation modifies the address stored in *xdp_md*\ **->data**,
+ * 		so the latter must be loaded only after the helper has been
+ * 		called.
+ *
+ * 		The use of *xdp_md*\ **->data_meta** is optional and programs
+ * 		are not required to use it. The rationale is that when the
+ * 		packet is processed with XDP (e.g. as DoS filter), it is
+ * 		possible to push further meta data along with it before passing
+ * 		to the stack, and to give the guarantee that an ingress eBPF
+ * 		program attached as a TC classifier on the same device can pick
+ * 		this up for further post-processing. Since TC works with socket
+ * 		buffers, it remains possible to set from XDP the **mark** or
+ * 		**priority** pointers, or other pointers for the socket buffer.
+ * 		Having this scratch space generic and programmable allows for
+ * 		more flexibility as the user is free to store whatever meta
+ * 		data they need.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_perf_event_read_value(struct bpf_map *map, u64 flags, struct bpf_perf_event_value *buf, u32 buf_size)
+ * 	Description
+ * 		Read the value of a perf event counter, and store it into *buf*
+ * 		of size *buf_size*. This helper relies on a *map* of type
+ * 		**BPF_MAP_TYPE_PERF_EVENT_ARRAY**. The nature of the perf event
+ * 		counter is selected when *map* is updated with perf event file
+ * 		descriptors. The *map* is an array whose size is the number of
+ * 		available CPUs, and each cell contains a value relative to one
+ * 		CPU. The value to retrieve is indicated by *flags*, that
+ * 		contains the index of the CPU to look up, masked with
+ * 		**BPF_F_INDEX_MASK**. Alternatively, *flags* can be set to
+ * 		**BPF_F_CURRENT_CPU** to indicate that the value for the
+ * 		current CPU should be retrieved.
+ *
+ * 		This helper behaves in a way close to
+ * 		**bpf_perf_event_read**\ () helper, save that instead of
+ * 		just returning the value observed, it fills the *buf*
+ * 		structure. This allows for additional data to be retrieved: in
+ * 		particular, the enabled and running times (in *buf*\
+ * 		**->enabled** and *buf*\ **->running**, respectively) are
+ * 		copied. In general, **bpf_perf_event_read_value**\ () is
+ * 		recommended over **bpf_perf_event_read**\ (), which has some
+ * 		ABI issues and provides fewer functionalities.
+ *
+ * 		These values are interesting, because hardware PMU (Performance
+ * 		Monitoring Unit) counters are limited resources. When there are
+ * 		more PMU based perf events opened than available counters,
+ * 		kernel will multiplex these events so each event gets certain
+ * 		percentage (but not all) of the PMU time. In case that
+ * 		multiplexing happens, the number of samples or counter value
+ * 		will not reflect the case compared to when no multiplexing
+ * 		occurs. This makes comparison between different runs difficult.
+ * 		Typically, the counter value should be normalized before
+ * 		comparing to other experiments. The usual normalization is done
+ * 		as follows.
+ *
+ * 		::
+ *
+ * 			normalized_counter = counter * t_enabled / t_running
+ *
+ * 		Where t_enabled is the time enabled for event and t_running is
+ * 		the time running for event since last normalization. The
+ * 		enabled and running times are accumulated since the perf event
+ * 		open. To achieve scaling factor between two invocations of an
+ * 		eBPF program, users can can use CPU id as the key (which is
+ * 		typical for perf array usage model) to remember the previous
+ * 		value and do the calculation inside the eBPF program.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_perf_prog_read_value(struct bpf_perf_event_data *ctx, struct bpf_perf_event_value *buf, u32 buf_size)
+ * 	Description
+ * 		For en eBPF program attached to a perf event, retrieve the
+ * 		value of the event counter associated to *ctx* and store it in
+ * 		the structure pointed by *buf* and of size *buf_size*. Enabled
+ * 		and running times are also stored in the structure (see
+ * 		description of helper **bpf_perf_event_read_value**\ () for
+ * 		more details).
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_getsockopt(struct bpf_sock_ops *bpf_socket, int level, int optname, char *optval, int optlen)
+ * 	Description
+ * 		Emulate a call to **getsockopt()** on the socket associated to
+ * 		*bpf_socket*, which must be a full socket. The *level* at
+ * 		which the option resides and the name *optname* of the option
+ * 		must be specified, see **getsockopt(2)** for more information.
+ * 		The retrieved value is stored in the structure pointed by
+ * 		*opval* and of length *optlen*.
+ *
+ * 		This helper actually implements a subset of **getsockopt()**.
+ * 		It supports the following *level*\ s:
+ *
+ * 		* **IPPROTO_TCP**, which supports *optname*
+ * 		  **TCP_CONGESTION**.
+ * 		* **IPPROTO_IP**, which supports *optname* **IP_TOS**.
+ * 		* **IPPROTO_IPV6**, which supports *optname* **IPV6_TCLASS**.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_override_return(struct pt_regs *regs, u64 rc)
+ * 	Description
+ * 		Used for error injection, this helper uses kprobes to override
+ * 		the return value of the probed function, and to set it to *rc*.
+ * 		The first argument is the context *regs* on which the kprobe
+ * 		works.
+ *
+ * 		This helper works by setting setting the PC (program counter)
+ * 		to an override function which is run in place of the original
+ * 		probed function. This means the probed function is not run at
+ * 		all. The replacement function just returns with the required
+ * 		value.
+ *
+ * 		This helper has security implications, and thus is subject to
+ * 		restrictions. It is only available if the kernel was compiled
+ * 		with the **CONFIG_BPF_KPROBE_OVERRIDE** configuration
+ * 		option, and in this case it only works on functions tagged with
+ * 		**ALLOW_ERROR_INJECTION** in the kernel code.
+ *
+ * 		Also, the helper is only available for the architectures having
+ * 		the CONFIG_FUNCTION_ERROR_INJECTION option. As of this writing,
+ * 		x86 architecture is the only one to support this feature.
+ * 	Return
+ * 		0
+ *
+ * int bpf_sock_ops_cb_flags_set(struct bpf_sock_ops *bpf_sock, int argval)
+ * 	Description
+ * 		Attempt to set the value of the **bpf_sock_ops_cb_flags** field
+ * 		for the full TCP socket associated to *bpf_sock_ops* to
+ * 		*argval*.
+ *
+ * 		The primary use of this field is to determine if there should
+ * 		be calls to eBPF programs of type
+ * 		**BPF_PROG_TYPE_SOCK_OPS** at various points in the TCP
+ * 		code. A program of the same type can change its value, per
+ * 		connection and as necessary, when the connection is
+ * 		established. This field is directly accessible for reading, but
+ * 		this helper must be used for updates in order to return an
+ * 		error if an eBPF program tries to set a callback that is not
+ * 		supported in the current kernel.
+ *
+ * 		*argval* is a flag array which can combine these flags:
+ *
+ * 		* **BPF_SOCK_OPS_RTO_CB_FLAG** (retransmission time out)
+ * 		* **BPF_SOCK_OPS_RETRANS_CB_FLAG** (retransmission)
+ * 		* **BPF_SOCK_OPS_STATE_CB_FLAG** (TCP state change)
+ * 		* **BPF_SOCK_OPS_RTT_CB_FLAG** (every RTT)
+ *
+ * 		Therefore, this function can be used to clear a callback flag by
+ * 		setting the appropriate bit to zero. e.g. to disable the RTO
+ * 		callback:
+ *
+ * 		**bpf_sock_ops_cb_flags_set(bpf_sock,**
+ * 			**bpf_sock->bpf_sock_ops_cb_flags & ~BPF_SOCK_OPS_RTO_CB_FLAG)**
+ *
+ * 		Here are some examples of where one could call such eBPF
+ * 		program:
+ *
+ * 		* When RTO fires.
+ * 		* When a packet is retransmitted.
+ * 		* When the connection terminates.
+ * 		* When a packet is sent.
+ * 		* When a packet is received.
+ * 	Return
+ * 		Code **-EINVAL** if the socket is not a full TCP socket;
+ * 		otherwise, a positive number containing the bits that could not
+ * 		be set is returned (which comes down to 0 if all bits were set
+ * 		as required).
+ *
+ * int bpf_msg_redirect_map(struct sk_msg_buff *msg, struct bpf_map *map, u32 key, u64 flags)
+ * 	Description
+ * 		This helper is used in programs implementing policies at the
+ * 		socket level. If the message *msg* is allowed to pass (i.e. if
+ * 		the verdict eBPF program returns **SK_PASS**), redirect it to
+ * 		the socket referenced by *map* (of type
+ * 		**BPF_MAP_TYPE_SOCKMAP**) at index *key*. Both ingress and
+ * 		egress interfaces can be used for redirection. The
+ * 		**BPF_F_INGRESS** value in *flags* is used to make the
+ * 		distinction (ingress path is selected if the flag is present,
+ * 		egress path otherwise). This is the only flag supported for now.
+ * 	Return
+ * 		**SK_PASS** on success, or **SK_DROP** on error.
+ *
+ * int bpf_msg_apply_bytes(struct sk_msg_buff *msg, u32 bytes)
+ * 	Description
+ * 		For socket policies, apply the verdict of the eBPF program to
+ * 		the next *bytes* (number of bytes) of message *msg*.
+ *
+ * 		For example, this helper can be used in the following cases:
+ *
+ * 		* A single **sendmsg**\ () or **sendfile**\ () system call
+ * 		  contains multiple logical messages that the eBPF program is
+ * 		  supposed to read and for which it should apply a verdict.
+ * 		* An eBPF program only cares to read the first *bytes* of a
+ * 		  *msg*. If the message has a large payload, then setting up
+ * 		  and calling the eBPF program repeatedly for all bytes, even
+ * 		  though the verdict is already known, would create unnecessary
+ * 		  overhead.
+ *
+ * 		When called from within an eBPF program, the helper sets a
+ * 		counter internal to the BPF infrastructure, that is used to
+ * 		apply the last verdict to the next *bytes*. If *bytes* is
+ * 		smaller than the current data being processed from a
+ * 		**sendmsg**\ () or **sendfile**\ () system call, the first
+ * 		*bytes* will be sent and the eBPF program will be re-run with
+ * 		the pointer for start of data pointing to byte number *bytes*
+ * 		**+ 1**. If *bytes* is larger than the current data being
+ * 		processed, then the eBPF verdict will be applied to multiple
+ * 		**sendmsg**\ () or **sendfile**\ () calls until *bytes* are
+ * 		consumed.
+ *
+ * 		Note that if a socket closes with the internal counter holding
+ * 		a non-zero value, this is not a problem because data is not
+ * 		being buffered for *bytes* and is sent as it is received.
+ * 	Return
+ * 		0
+ *
+ * int bpf_msg_cork_bytes(struct sk_msg_buff *msg, u32 bytes)
+ * 	Description
+ * 		For socket policies, prevent the execution of the verdict eBPF
+ * 		program for message *msg* until *bytes* (byte number) have been
+ * 		accumulated.
+ *
+ * 		This can be used when one needs a specific number of bytes
+ * 		before a verdict can be assigned, even if the data spans
+ * 		multiple **sendmsg**\ () or **sendfile**\ () calls. The extreme
+ * 		case would be a user calling **sendmsg**\ () repeatedly with
+ * 		1-byte long message segments. Obviously, this is bad for
+ * 		performance, but it is still valid. If the eBPF program needs
+ * 		*bytes* bytes to validate a header, this helper can be used to
+ * 		prevent the eBPF program to be called again until *bytes* have
+ * 		been accumulated.
+ * 	Return
+ * 		0
+ *
+ * int bpf_msg_pull_data(struct sk_msg_buff *msg, u32 start, u32 end, u64 flags)
+ * 	Description
+ * 		For socket policies, pull in non-linear data from user space
+ * 		for *msg* and set pointers *msg*\ **->data** and *msg*\
+ * 		**->data_end** to *start* and *end* bytes offsets into *msg*,
+ * 		respectively.
+ *
+ * 		If a program of type **BPF_PROG_TYPE_SK_MSG** is run on a
+ * 		*msg* it can only parse data that the (**data**, **data_end**)
+ * 		pointers have already consumed. For **sendmsg**\ () hooks this
+ * 		is likely the first scatterlist element. But for calls relying
+ * 		on the **sendpage** handler (e.g. **sendfile**\ ()) this will
+ * 		be the range (**0**, **0**) because the data is shared with
+ * 		user space and by default the objective is to avoid allowing
+ * 		user space to modify data while (or after) eBPF verdict is
+ * 		being decided. This helper can be used to pull in data and to
+ * 		set the start and end pointer to given values. Data will be
+ * 		copied if necessary (i.e. if data was not linear and if start
+ * 		and end pointers do not point to the same chunk).
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ *
+ * 		All values for *flags* are reserved for future usage, and must
+ * 		be left at zero.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_bind(struct bpf_sock_addr *ctx, struct sockaddr *addr, int addr_len)
+ * 	Description
+ * 		Bind the socket associated to *ctx* to the address pointed by
+ * 		*addr*, of length *addr_len*. This allows for making outgoing
+ * 		connection from the desired IP address, which can be useful for
+ * 		example when all processes inside a cgroup should use one
+ * 		single IP address on a host that has multiple IP configured.
+ *
+ * 		This helper works for IPv4 and IPv6, TCP and UDP sockets. The
+ * 		domain (*addr*\ **->sa_family**) must be **AF_INET** (or
+ * 		**AF_INET6**). Looking for a free port to bind to can be
+ * 		expensive, therefore binding to port is not permitted by the
+ * 		helper: *addr*\ **->sin_port** (or **sin6_port**, respectively)
+ * 		must be set to zero.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_xdp_adjust_tail(struct xdp_buff *xdp_md, int delta)
+ * 	Description
+ * 		Adjust (move) *xdp_md*\ **->data_end** by *delta* bytes. It is
+ * 		only possible to shrink the packet as of this writing,
+ * 		therefore *delta* must be a negative integer.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_skb_get_xfrm_state(struct sk_buff *skb, u32 index, struct bpf_xfrm_state *xfrm_state, u32 size, u64 flags)
+ * 	Description
+ * 		Retrieve the XFRM state (IP transform framework, see also
+ * 		**ip-xfrm(8)**) at *index* in XFRM "security path" for *skb*.
+ *
+ * 		The retrieved value is stored in the **struct bpf_xfrm_state**
+ * 		pointed by *xfrm_state* and of length *size*.
+ *
+ * 		All values for *flags* are reserved for future usage, and must
+ * 		be left at zero.
+ *
+ * 		This helper is available only if the kernel was compiled with
+ * 		**CONFIG_XFRM** configuration option.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_get_stack(struct pt_regs *regs, void *buf, u32 size, u64 flags)
+ * 	Description
+ * 		Return a user or a kernel stack in bpf program provided buffer.
+ * 		To achieve this, the helper needs *ctx*, which is a pointer
+ * 		to the context on which the tracing program is executed.
+ * 		To store the stacktrace, the bpf program provides *buf* with
+ * 		a nonnegative *size*.
+ *
+ * 		The last argument, *flags*, holds the number of stack frames to
+ * 		skip (from 0 to 255), masked with
+ * 		**BPF_F_SKIP_FIELD_MASK**. The next bits can be used to set
+ * 		the following flags:
+ *
+ * 		**BPF_F_USER_STACK**
+ * 			Collect a user space stack instead of a kernel stack.
+ * 		**BPF_F_USER_BUILD_ID**
+ * 			Collect buildid+offset instead of ips for user stack,
+ * 			only valid if **BPF_F_USER_STACK** is also specified.
+ *
+ * 		**bpf_get_stack**\ () can collect up to
+ * 		**PERF_MAX_STACK_DEPTH** both kernel and user frames, subject
+ * 		to sufficient large buffer size. Note that
+ * 		this limit can be controlled with the **sysctl** program, and
+ * 		that it should be manually increased in order to profile long
+ * 		user stacks (such as stacks for Java programs). To do so, use:
+ *
+ * 		::
+ *
+ * 			# sysctl kernel.perf_event_max_stack=<new value>
+ * 	Return
+ * 		A non-negative value equal to or less than *size* on success,
+ * 		or a negative error in case of failure.
+ *
+ * int bpf_skb_load_bytes_relative(const struct sk_buff *skb, u32 offset, void *to, u32 len, u32 start_header)
+ * 	Description
+ * 		This helper is similar to **bpf_skb_load_bytes**\ () in that
+ * 		it provides an easy way to load *len* bytes from *offset*
+ * 		from the packet associated to *skb*, into the buffer pointed
+ * 		by *to*. The difference to **bpf_skb_load_bytes**\ () is that
+ * 		a fifth argument *start_header* exists in order to select a
+ * 		base offset to start from. *start_header* can be one of:
+ *
+ * 		**BPF_HDR_START_MAC**
+ * 			Base offset to load data from is *skb*'s mac header.
+ * 		**BPF_HDR_START_NET**
+ * 			Base offset to load data from is *skb*'s network header.
+ *
+ * 		In general, "direct packet access" is the preferred method to
+ * 		access packet data, however, this helper is in particular useful
+ * 		in socket filters where *skb*\ **->data** does not always point
+ * 		to the start of the mac header and where "direct packet access"
+ * 		is not available.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_fib_lookup(void *ctx, struct bpf_fib_lookup *params, int plen, u32 flags)
+ *	Description
+ *		Do FIB lookup in kernel tables using parameters in *params*.
+ *		If lookup is successful and result shows packet is to be
+ *		forwarded, the neighbor tables are searched for the nexthop.
+ *		If successful (ie., FIB lookup shows forwarding and nexthop
+ *		is resolved), the nexthop address is returned in ipv4_dst
+ *		or ipv6_dst based on family, smac is set to mac address of
+ *		egress device, dmac is set to nexthop mac address, rt_metric
+ *		is set to metric from route (IPv4/IPv6 only), and ifindex
+ *		is set to the device index of the nexthop from the FIB lookup.
+ *
+ *		*plen* argument is the size of the passed in struct.
+ *		*flags* argument can be a combination of one or more of the
+ *		following values:
+ *
+ *		**BPF_FIB_LOOKUP_DIRECT**
+ *			Do a direct table lookup vs full lookup using FIB
+ *			rules.
+ *		**BPF_FIB_LOOKUP_OUTPUT**
+ *			Perform lookup from an egress perspective (default is
+ *			ingress).
+ *
+ *		*ctx* is either **struct xdp_md** for XDP programs or
+ *		**struct sk_buff** tc cls_act programs.
+ *	Return
+ *		* < 0 if any input argument is invalid
+ *		*   0 on success (packet is forwarded, nexthop neighbor exists)
+ *		* > 0 one of **BPF_FIB_LKUP_RET_** codes explaining why the
+ *		  packet is not forwarded or needs assist from full stack
+ *
+ * int bpf_sock_hash_update(struct bpf_sock_ops_kern *skops, struct bpf_map *map, void *key, u64 flags)
+ *	Description
+ *		Add an entry to, or update a sockhash *map* referencing sockets.
+ *		The *skops* is used as a new value for the entry associated to
+ *		*key*. *flags* is one of:
+ *
+ *		**BPF_NOEXIST**
+ *			The entry for *key* must not exist in the map.
+ *		**BPF_EXIST**
+ *			The entry for *key* must already exist in the map.
+ *		**BPF_ANY**
+ *			No condition on the existence of the entry for *key*.
+ *
+ *		If the *map* has eBPF programs (parser and verdict), those will
+ *		be inherited by the socket being added. If the socket is
+ *		already attached to eBPF programs, this results in an error.
+ *	Return
+ *		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_msg_redirect_hash(struct sk_msg_buff *msg, struct bpf_map *map, void *key, u64 flags)
+ *	Description
+ *		This helper is used in programs implementing policies at the
+ *		socket level. If the message *msg* is allowed to pass (i.e. if
+ *		the verdict eBPF program returns **SK_PASS**), redirect it to
+ *		the socket referenced by *map* (of type
+ *		**BPF_MAP_TYPE_SOCKHASH**) using hash *key*. Both ingress and
+ *		egress interfaces can be used for redirection. The
+ *		**BPF_F_INGRESS** value in *flags* is used to make the
+ *		distinction (ingress path is selected if the flag is present,
+ *		egress path otherwise). This is the only flag supported for now.
+ *	Return
+ *		**SK_PASS** on success, or **SK_DROP** on error.
+ *
+ * int bpf_sk_redirect_hash(struct sk_buff *skb, struct bpf_map *map, void *key, u64 flags)
+ *	Description
+ *		This helper is used in programs implementing policies at the
+ *		skb socket level. If the sk_buff *skb* is allowed to pass (i.e.
+ *		if the verdeict eBPF program returns **SK_PASS**), redirect it
+ *		to the socket referenced by *map* (of type
+ *		**BPF_MAP_TYPE_SOCKHASH**) using hash *key*. Both ingress and
+ *		egress interfaces can be used for redirection. The
+ *		**BPF_F_INGRESS** value in *flags* is used to make the
+ *		distinction (ingress path is selected if the flag is present,
+ *		egress otherwise). This is the only flag supported for now.
+ *	Return
+ *		**SK_PASS** on success, or **SK_DROP** on error.
+ *
+ * int bpf_lwt_push_encap(struct sk_buff *skb, u32 type, void *hdr, u32 len)
+ *	Description
+ *		Encapsulate the packet associated to *skb* within a Layer 3
+ *		protocol header. This header is provided in the buffer at
+ *		address *hdr*, with *len* its size in bytes. *type* indicates
+ *		the protocol of the header and can be one of:
+ *
+ *		**BPF_LWT_ENCAP_SEG6**
+ *			IPv6 encapsulation with Segment Routing Header
+ *			(**struct ipv6_sr_hdr**). *hdr* only contains the SRH,
+ *			the IPv6 header is computed by the kernel.
+ *		**BPF_LWT_ENCAP_SEG6_INLINE**
+ *			Only works if *skb* contains an IPv6 packet. Insert a
+ *			Segment Routing Header (**struct ipv6_sr_hdr**) inside
+ *			the IPv6 header.
+ *		**BPF_LWT_ENCAP_IP**
+ *			IP encapsulation (GRE/GUE/IPIP/etc). The outer header
+ *			must be IPv4 or IPv6, followed by zero or more
+ *			additional headers, up to **LWT_BPF_MAX_HEADROOM**
+ *			total bytes in all prepended headers. Please note that
+ *			if **skb_is_gso**\ (*skb*) is true, no more than two
+ *			headers can be prepended, and the inner header, if
+ *			present, should be either GRE or UDP/GUE.
+ *
+ *		**BPF_LWT_ENCAP_SEG6**\ \* types can be called by BPF programs
+ *		of type **BPF_PROG_TYPE_LWT_IN**; **BPF_LWT_ENCAP_IP** type can
+ *		be called by bpf programs of types **BPF_PROG_TYPE_LWT_IN** and
+ *		**BPF_PROG_TYPE_LWT_XMIT**.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ *	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_lwt_seg6_store_bytes(struct sk_buff *skb, u32 offset, const void *from, u32 len)
+ *	Description
+ *		Store *len* bytes from address *from* into the packet
+ *		associated to *skb*, at *offset*. Only the flags, tag and TLVs
+ *		inside the outermost IPv6 Segment Routing Header can be
+ *		modified through this helper.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ *	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_lwt_seg6_adjust_srh(struct sk_buff *skb, u32 offset, s32 delta)
+ *	Description
+ *		Adjust the size allocated to TLVs in the outermost IPv6
+ *		Segment Routing Header contained in the packet associated to
+ *		*skb*, at position *offset* by *delta* bytes. Only offsets
+ *		after the segments are accepted. *delta* can be as well
+ *		positive (growing) as negative (shrinking).
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ *	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_lwt_seg6_action(struct sk_buff *skb, u32 action, void *param, u32 param_len)
+ *	Description
+ *		Apply an IPv6 Segment Routing action of type *action* to the
+ *		packet associated to *skb*. Each action takes a parameter
+ *		contained at address *param*, and of length *param_len* bytes.
+ *		*action* can be one of:
+ *
+ *		**SEG6_LOCAL_ACTION_END_X**
+ *			End.X action: Endpoint with Layer-3 cross-connect.
+ *			Type of *param*: **struct in6_addr**.
+ *		**SEG6_LOCAL_ACTION_END_T**
+ *			End.T action: Endpoint with specific IPv6 table lookup.
+ *			Type of *param*: **int**.
+ *		**SEG6_LOCAL_ACTION_END_B6**
+ *			End.B6 action: Endpoint bound to an SRv6 policy.
+ *			Type of *param*: **struct ipv6_sr_hdr**.
+ *		**SEG6_LOCAL_ACTION_END_B6_ENCAP**
+ *			End.B6.Encap action: Endpoint bound to an SRv6
+ *			encapsulation policy.
+ *			Type of *param*: **struct ipv6_sr_hdr**.
+ *
+ * 		A call to this helper is susceptible to change the underlying
+ * 		packet buffer. Therefore, at load time, all checks on pointers
+ * 		previously done by the verifier are invalidated and must be
+ * 		performed again, if the helper is used in combination with
+ * 		direct packet access.
+ *	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_rc_repeat(void *ctx)
+ *	Description
+ *		This helper is used in programs implementing IR decoding, to
+ *		report a successfully decoded repeat key message. This delays
+ *		the generation of a key up event for previously generated
+ *		key down event.
+ *
+ *		Some IR protocols like NEC have a special IR message for
+ *		repeating last button, for when a button is held down.
+ *
+ *		The *ctx* should point to the lirc sample as passed into
+ *		the program.
+ *
+ *		This helper is only available is the kernel was compiled with
+ *		the **CONFIG_BPF_LIRC_MODE2** configuration option set to
+ *		"**y**".
+ *	Return
+ *		0
+ *
+ * int bpf_rc_keydown(void *ctx, u32 protocol, u64 scancode, u32 toggle)
+ *	Description
+ *		This helper is used in programs implementing IR decoding, to
+ *		report a successfully decoded key press with *scancode*,
+ *		*toggle* value in the given *protocol*. The scancode will be
+ *		translated to a keycode using the rc keymap, and reported as
+ *		an input key down event. After a period a key up event is
+ *		generated. This period can be extended by calling either
+ *		**bpf_rc_keydown**\ () again with the same values, or calling
+ *		**bpf_rc_repeat**\ ().
+ *
+ *		Some protocols include a toggle bit, in case the button	was
+ *		released and pressed again between consecutive scancodes.
+ *
+ *		The *ctx* should point to the lirc sample as passed into
+ *		the program.
+ *
+ *		The *protocol* is the decoded protocol number (see
+ *		**enum rc_proto** for some predefined values).
+ *
+ *		This helper is only available is the kernel was compiled with
+ *		the **CONFIG_BPF_LIRC_MODE2** configuration option set to
+ *		"**y**".
+ *	Return
+ *		0
+ *
+ * u64 bpf_skb_cgroup_id(struct sk_buff *skb)
+ * 	Description
+ * 		Return the cgroup v2 id of the socket associated with the *skb*.
+ * 		This is roughly similar to the **bpf_get_cgroup_classid**\ ()
+ * 		helper for cgroup v1 by providing a tag resp. identifier that
+ * 		can be matched on or used for map lookups e.g. to implement
+ * 		policy. The cgroup v2 id of a given path in the hierarchy is
+ * 		exposed in user space through the f_handle API in order to get
+ * 		to the same 64-bit id.
+ *
+ * 		This helper can be used on TC egress path, but not on ingress,
+ * 		and is available only if the kernel was compiled with the
+ * 		**CONFIG_SOCK_CGROUP_DATA** configuration option.
+ * 	Return
+ * 		The id is returned or 0 in case the id could not be retrieved.
+ *
+ * u64 bpf_get_current_cgroup_id(void)
+ * 	Return
+ * 		A 64-bit integer containing the current cgroup id based
+ * 		on the cgroup within which the current task is running.
+ *
+ * void *bpf_get_local_storage(void *map, u64 flags)
+ *	Description
+ *		Get the pointer to the local storage area.
+ *		The type and the size of the local storage is defined
+ *		by the *map* argument.
+ *		The *flags* meaning is specific for each map type,
+ *		and has to be 0 for cgroup local storage.
+ *
+ *		Depending on the BPF program type, a local storage area
+ *		can be shared between multiple instances of the BPF program,
+ *		running simultaneously.
+ *
+ *		A user should care about the synchronization by himself.
+ *		For example, by using the **BPF_STX_XADD** instruction to alter
+ *		the shared data.
+ *	Return
+ *		A pointer to the local storage area.
+ *
+ * int bpf_sk_select_reuseport(struct sk_reuseport_md *reuse, struct bpf_map *map, void *key, u64 flags)
+ *	Description
+ *		Select a **SO_REUSEPORT** socket from a
+ *		**BPF_MAP_TYPE_REUSEPORT_ARRAY** *map*.
+ *		It checks the selected socket is matching the incoming
+ *		request in the socket buffer.
+ *	Return
+ *		0 on success, or a negative error in case of failure.
+ *
+ * u64 bpf_skb_ancestor_cgroup_id(struct sk_buff *skb, int ancestor_level)
+ *	Description
+ *		Return id of cgroup v2 that is ancestor of cgroup associated
+ *		with the *skb* at the *ancestor_level*.  The root cgroup is at
+ *		*ancestor_level* zero and each step down the hierarchy
+ *		increments the level. If *ancestor_level* == level of cgroup
+ *		associated with *skb*, then return value will be same as that
+ *		of **bpf_skb_cgroup_id**\ ().
+ *
+ *		The helper is useful to implement policies based on cgroups
+ *		that are upper in hierarchy than immediate cgroup associated
+ *		with *skb*.
+ *
+ *		The format of returned id and helper limitations are same as in
+ *		**bpf_skb_cgroup_id**\ ().
+ *	Return
+ *		The id is returned or 0 in case the id could not be retrieved.
+ *
+ * struct bpf_sock *bpf_sk_lookup_tcp(void *ctx, struct bpf_sock_tuple *tuple, u32 tuple_size, u64 netns, u64 flags)
+ *	Description
+ *		Look for TCP socket matching *tuple*, optionally in a child
+ *		network namespace *netns*. The return value must be checked,
+ *		and if non-**NULL**, released via **bpf_sk_release**\ ().
+ *
+ *		The *ctx* should point to the context of the program, such as
+ *		the skb or socket (depending on the hook in use). This is used
+ *		to determine the base network namespace for the lookup.
+ *
+ *		*tuple_size* must be one of:
+ *
+ *		**sizeof**\ (*tuple*\ **->ipv4**)
+ *			Look for an IPv4 socket.
+ *		**sizeof**\ (*tuple*\ **->ipv6**)
+ *			Look for an IPv6 socket.
+ *
+ *		If the *netns* is a negative signed 32-bit integer, then the
+ *		socket lookup table in the netns associated with the *ctx* will
+ *		will be used. For the TC hooks, this is the netns of the device
+ *		in the skb. For socket hooks, this is the netns of the socket.
+ *		If *netns* is any other signed 32-bit value greater than or
+ *		equal to zero then it specifies the ID of the netns relative to
+ *		the netns associated with the *ctx*. *netns* values beyond the
+ *		range of 32-bit integers are reserved for future use.
+ *
+ *		All values for *flags* are reserved for future usage, and must
+ *		be left at zero.
+ *
+ *		This helper is available only if the kernel was compiled with
+ *		**CONFIG_NET** configuration option.
+ *	Return
+ *		Pointer to **struct bpf_sock**, or **NULL** in case of failure.
+ *		For sockets with reuseport option, the **struct bpf_sock**
+ *		result is from *reuse*\ **->socks**\ [] using the hash of the
+ *		tuple.
+ *
+ * struct bpf_sock *bpf_sk_lookup_udp(void *ctx, struct bpf_sock_tuple *tuple, u32 tuple_size, u64 netns, u64 flags)
+ *	Description
+ *		Look for UDP socket matching *tuple*, optionally in a child
+ *		network namespace *netns*. The return value must be checked,
+ *		and if non-**NULL**, released via **bpf_sk_release**\ ().
+ *
+ *		The *ctx* should point to the context of the program, such as
+ *		the skb or socket (depending on the hook in use). This is used
+ *		to determine the base network namespace for the lookup.
+ *
+ *		*tuple_size* must be one of:
+ *
+ *		**sizeof**\ (*tuple*\ **->ipv4**)
+ *			Look for an IPv4 socket.
+ *		**sizeof**\ (*tuple*\ **->ipv6**)
+ *			Look for an IPv6 socket.
+ *
+ *		If the *netns* is a negative signed 32-bit integer, then the
+ *		socket lookup table in the netns associated with the *ctx* will
+ *		will be used. For the TC hooks, this is the netns of the device
+ *		in the skb. For socket hooks, this is the netns of the socket.
+ *		If *netns* is any other signed 32-bit value greater than or
+ *		equal to zero then it specifies the ID of the netns relative to
+ *		the netns associated with the *ctx*. *netns* values beyond the
+ *		range of 32-bit integers are reserved for future use.
+ *
+ *		All values for *flags* are reserved for future usage, and must
+ *		be left at zero.
+ *
+ *		This helper is available only if the kernel was compiled with
+ *		**CONFIG_NET** configuration option.
+ *	Return
+ *		Pointer to **struct bpf_sock**, or **NULL** in case of failure.
+ *		For sockets with reuseport option, the **struct bpf_sock**
+ *		result is from *reuse*\ **->socks**\ [] using the hash of the
+ *		tuple.
+ *
+ * int bpf_sk_release(struct bpf_sock *sock)
+ *	Description
+ *		Release the reference held by *sock*. *sock* must be a
+ *		non-**NULL** pointer that was returned from
+ *		**bpf_sk_lookup_xxx**\ ().
+ *	Return
+ *		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_map_push_elem(struct bpf_map *map, const void *value, u64 flags)
+ * 	Description
+ * 		Push an element *value* in *map*. *flags* is one of:
+ *
+ * 		**BPF_EXIST**
+ * 			If the queue/stack is full, the oldest element is
+ * 			removed to make room for this.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_map_pop_elem(struct bpf_map *map, void *value)
+ * 	Description
+ * 		Pop an element from *map*.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_map_peek_elem(struct bpf_map *map, void *value)
+ * 	Description
+ * 		Get an element from *map* without removing it.
+ * 	Return
+ * 		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_msg_push_data(struct sk_buff *skb, u32 start, u32 len, u64 flags)
+ *	Description
+ *		For socket policies, insert *len* bytes into *msg* at offset
+ *		*start*.
+ *
+ *		If a program of type **BPF_PROG_TYPE_SK_MSG** is run on a
+ *		*msg* it may want to insert metadata or options into the *msg*.
+ *		This can later be read and used by any of the lower layer BPF
+ *		hooks.
+ *
+ *		This helper may fail if under memory pressure (a malloc
+ *		fails) in these cases BPF programs will get an appropriate
+ *		error and BPF programs will need to handle them.
+ *	Return
+ *		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_msg_pop_data(struct sk_msg_buff *msg, u32 start, u32 pop, u64 flags)
+ *	Description
+ *		Will remove *pop* bytes from a *msg* starting at byte *start*.
+ *		This may result in **ENOMEM** errors under certain situations if
+ *		an allocation and copy are required due to a full ring buffer.
+ *		However, the helper will try to avoid doing the allocation
+ *		if possible. Other errors can occur if input parameters are
+ *		invalid either due to *start* byte not being valid part of *msg*
+ *		payload and/or *pop* value being to large.
+ *	Return
+ *		0 on success, or a negative error in case of failure.
+ *
+ * int bpf_rc_pointer_rel(void *ctx, s32 rel_x, s32 rel_y)
+ *	Description
+ *		This helper is used in programs implementing IR decoding, to
+ *		report a successfully decoded pointer movement.
+ *
+ *		The *ctx* should point to the lirc sample as passed into
+ *		the program.
+ *
+ *		This helper is only available is the kernel was compiled with
+ *		the **CONFIG_BPF_LIRC_MODE2** configuration option set to
+ *		"**y**".
+ *	Return
+ *		0
+ *
+ * int bpf_spin_lock(struct bpf_spin_lock *lock)
+ *	Description
+ *		Acquire a spinlock represented by the pointer *lock*, which is
+ *		stored as part of a value of a map. Taking the lock allows to
+ *		safely update the rest of the fields in that value. The
+ *		spinlock can (and must) later be released with a call to
+ *		**bpf_spin_unlock**\ (\ *lock*\ ).
+ *
+ *		Spinlocks in BPF programs come with a number of restrictions
+ *		and constraints:
+ *
+ *		* **bpf_spin_lock** objects are only allowed inside maps of
+ *		  types **BPF_MAP_TYPE_HASH** and **BPF_MAP_TYPE_ARRAY** (this
+ *		  list could be extended in the future).
+ *		* BTF description of the map is mandatory.
+ *		* The BPF program can take ONE lock at a time, since taking two
+ *		  or more could cause dead locks.
+ *		* Only one **struct bpf_spin_lock** is allowed per map element.
+ *		* When the lock is taken, calls (either BPF to BPF or helpers)
+ *		  are not allowed.
+ *		* The **BPF_LD_ABS** and **BPF_LD_IND** instructions are not
+ *		  allowed inside a spinlock-ed region.
+ *		* The BPF program MUST call **bpf_spin_unlock**\ () to release
+ *		  the lock, on all execution paths, before it returns.
+ *		* The BPF program can access **struct bpf_spin_lock** only via
+ *		  the **bpf_spin_lock**\ () and **bpf_spin_unlock**\ ()
+ *		  helpers. Loading or storing data into the **struct
+ *		  bpf_spin_lock** *lock*\ **;** field of a map is not allowed.
+ *		* To use the **bpf_spin_lock**\ () helper, the BTF description
+ *		  of the map value must be a struct and have **struct
+ *		  bpf_spin_lock** *anyname*\ **;** field at the top level.
+ *		  Nested lock inside another struct is not allowed.
+ *		* The **struct bpf_spin_lock** *lock* field in a map value must
+ *		  be aligned on a multiple of 4 bytes in that value.
+ *		* Syscall with command **BPF_MAP_LOOKUP_ELEM** does not copy
+ *		  the **bpf_spin_lock** field to user space.
+ *		* Syscall with command **BPF_MAP_UPDATE_ELEM**, or update from
+ *		  a BPF program, do not update the **bpf_spin_lock** field.
+ *		* **bpf_spin_lock** cannot be on the stack or inside a
+ *		  networking packet (it can only be inside of a map values).
+ *		* **bpf_spin_lock** is available to root only.
+ *		* Tracing programs and socket filter programs cannot use
+ *		  **bpf_spin_lock**\ () due to insufficient preemption checks
+ *		  (but this may change in the future).
+ *		* **bpf_spin_lock** is not allowed in inner maps of map-in-map.
+ *	Return
+ *		0
+ *
+ * int bpf_spin_unlock(struct bpf_spin_lock *lock)
+ *	Description
+ *		Release the *lock* previously locked by a call to
+ *		**bpf_spin_lock**\ (\ *lock*\ ).
+ *	Return
+ *		0
+ *
+ * struct bpf_sock *bpf_sk_fullsock(struct bpf_sock *sk)
+ *	Description
+ *		This helper gets a **struct bpf_sock** pointer such
+ *		that all the fields in this **bpf_sock** can be accessed.
+ *	Return
+ *		A **struct bpf_sock** pointer on success, or **NULL** in
+ *		case of failure.
+ *
+ * struct bpf_tcp_sock *bpf_tcp_sock(struct bpf_sock *sk)
+ *	Description
+ *		This helper gets a **struct bpf_tcp_sock** pointer from a
+ *		**struct bpf_sock** pointer.
+ *	Return
+ *		A **struct bpf_tcp_sock** pointer on success, or **NULL** in
+ *		case of failure.
+ *
+ * int bpf_skb_ecn_set_ce(struct sk_buf *skb)
+ *	Description
+ *		Set ECN (Explicit Congestion Notification) field of IP header
+ *		to **CE** (Congestion Encountered) if current value is **ECT**
+ *		(ECN Capable Transport). Otherwise, do nothing. Works with IPv6
+ *		and IPv4.
+ *	Return
+ *		1 if the **CE** flag is set (either by the current helper call
+ *		or because it was already present), 0 if it is not set.
+ *
+ * struct bpf_sock *bpf_get_listener_sock(struct bpf_sock *sk)
+ *	Description
+ *		Return a **struct bpf_sock** pointer in **TCP_LISTEN** state.
+ *		**bpf_sk_release**\ () is unnecessary and not allowed.
+ *	Return
+ *		A **struct bpf_sock** pointer on success, or **NULL** in
+ *		case of failure.
+ *
+ * struct bpf_sock *bpf_skc_lookup_tcp(void *ctx, struct bpf_sock_tuple *tuple, u32 tuple_size, u64 netns, u64 flags)
+ *	Description
+ *		Look for TCP socket matching *tuple*, optionally in a child
+ *		network namespace *netns*. The return value must be checked,
+ *		and if non-**NULL**, released via **bpf_sk_release**\ ().
+ *
+ *		This function is identical to **bpf_sk_lookup_tcp**\ (), except
+ *		that it also returns timewait or request sockets. Use
+ *		**bpf_sk_fullsock**\ () or **bpf_tcp_sock**\ () to access the
+ *		full structure.
+ *
+ *		This helper is available only if the kernel was compiled with
+ *		**CONFIG_NET** configuration option.
+ *	Return
+ *		Pointer to **struct bpf_sock**, or **NULL** in case of failure.
+ *		For sockets with reuseport option, the **struct bpf_sock**
+ *		result is from *reuse*\ **->socks**\ [] using the hash of the
+ *		tuple.
+ *
+ * int bpf_tcp_check_syncookie(struct bpf_sock *sk, void *iph, u32 iph_len, struct tcphdr *th, u32 th_len)
+ * 	Description
+ * 		Check whether *iph* and *th* contain a valid SYN cookie ACK for
+ * 		the listening socket in *sk*.
+ *
+ * 		*iph* points to the start of the IPv4 or IPv6 header, while
+ * 		*iph_len* contains **sizeof**\ (**struct iphdr**) or
+ * 		**sizeof**\ (**struct ip6hdr**).
+ *
+ * 		*th* points to the start of the TCP header, while *th_len*
+ * 		contains **sizeof**\ (**struct tcphdr**).
+ *
+ * 	Return
+ * 		0 if *iph* and *th* are a valid SYN cookie ACK, or a negative
+ * 		error otherwise.
+ *
+ * int bpf_sysctl_get_name(struct bpf_sysctl *ctx, char *buf, size_t buf_len, u64 flags)
+ *	Description
+ *		Get name of sysctl in /proc/sys/ and copy it into provided by
+ *		program buffer *buf* of size *buf_len*.
+ *
+ *		The buffer is always NUL terminated, unless it's zero-sized.
+ *
+ *		If *flags* is zero, full name (e.g. "net/ipv4/tcp_mem") is
+ *		copied. Use **BPF_F_SYSCTL_BASE_NAME** flag to copy base name
+ *		only (e.g. "tcp_mem").
+ *	Return
+ *		Number of character copied (not including the trailing NUL).
+ *
+ *		**-E2BIG** if the buffer wasn't big enough (*buf* will contain
+ *		truncated name in this case).
+ *
+ * int bpf_sysctl_get_current_value(struct bpf_sysctl *ctx, char *buf, size_t buf_len)
+ *	Description
+ *		Get current value of sysctl as it is presented in /proc/sys
+ *		(incl. newline, etc), and copy it as a string into provided
+ *		by program buffer *buf* of size *buf_len*.
+ *
+ *		The whole value is copied, no matter what file position user
+ *		space issued e.g. sys_read at.
+ *
+ *		The buffer is always NUL terminated, unless it's zero-sized.
+ *	Return
+ *		Number of character copied (not including the trailing NUL).
+ *
+ *		**-E2BIG** if the buffer wasn't big enough (*buf* will contain
+ *		truncated name in this case).
+ *
+ *		**-EINVAL** if current value was unavailable, e.g. because
+ *		sysctl is uninitialized and read returns -EIO for it.
+ *
+ * int bpf_sysctl_get_new_value(struct bpf_sysctl *ctx, char *buf, size_t buf_len)
+ *	Description
+ *		Get new value being written by user space to sysctl (before
+ *		the actual write happens) and copy it as a string into
+ *		provided by program buffer *buf* of size *buf_len*.
+ *
+ *		User space may write new value at file position > 0.
+ *
+ *		The buffer is always NUL terminated, unless it's zero-sized.
+ *	Return
+ *		Number of character copied (not including the trailing NUL).
+ *
+ *		**-E2BIG** if the buffer wasn't big enough (*buf* will contain
+ *		truncated name in this case).
+ *
+ *		**-EINVAL** if sysctl is being read.
+ *
+ * int bpf_sysctl_set_new_value(struct bpf_sysctl *ctx, const char *buf, size_t buf_len)
+ *	Description
+ *		Override new value being written by user space to sysctl with
+ *		value provided by program in buffer *buf* of size *buf_len*.
+ *
+ *		*buf* should contain a string in same form as provided by user
+ *		space on sysctl write.
+ *
+ *		User space may write new value at file position > 0. To override
+ *		the whole sysctl value file position should be set to zero.
+ *	Return
+ *		0 on success.
+ *
+ *		**-E2BIG** if the *buf_len* is too big.
+ *
+ *		**-EINVAL** if sysctl is being read.
+ *
+ * int bpf_strtol(const char *buf, size_t buf_len, u64 flags, long *res)
+ *	Description
+ *		Convert the initial part of the string from buffer *buf* of
+ *		size *buf_len* to a long integer according to the given base
+ *		and save the result in *res*.
+ *
+ *		The string may begin with an arbitrary amount of white space
+ *		(as determined by **isspace**\ (3)) followed by a single
+ *		optional '**-**' sign.
+ *
+ *		Five least significant bits of *flags* encode base, other bits
+ *		are currently unused.
+ *
+ *		Base must be either 8, 10, 16 or 0 to detect it automatically
+ *		similar to user space **strtol**\ (3).
+ *	Return
+ *		Number of characters consumed on success. Must be positive but
+ *		no more than *buf_len*.
+ *
+ *		**-EINVAL** if no valid digits were found or unsupported base
+ *		was provided.
+ *
+ *		**-ERANGE** if resulting value was out of range.
+ *
+ * int bpf_strtoul(const char *buf, size_t buf_len, u64 flags, unsigned long *res)
+ *	Description
+ *		Convert the initial part of the string from buffer *buf* of
+ *		size *buf_len* to an unsigned long integer according to the
+ *		given base and save the result in *res*.
+ *
+ *		The string may begin with an arbitrary amount of white space
+ *		(as determined by **isspace**\ (3)).
+ *
+ *		Five least significant bits of *flags* encode base, other bits
+ *		are currently unused.
+ *
+ *		Base must be either 8, 10, 16 or 0 to detect it automatically
+ *		similar to user space **strtoul**\ (3).
+ *	Return
+ *		Number of characters consumed on success. Must be positive but
+ *		no more than *buf_len*.
+ *
+ *		**-EINVAL** if no valid digits were found or unsupported base
+ *		was provided.
+ *
+ *		**-ERANGE** if resulting value was out of range.
+ *
+ * void *bpf_sk_storage_get(struct bpf_map *map, struct bpf_sock *sk, void *value, u64 flags)
+ *	Description
+ *		Get a bpf-local-storage from a *sk*.
+ *
+ *		Logically, it could be thought of getting the value from
+ *		a *map* with *sk* as the **key**.  From this
+ *		perspective,  the usage is not much different from
+ *		**bpf_map_lookup_elem**\ (*map*, **&**\ *sk*) except this
+ *		helper enforces the key must be a full socket and the map must
+ *		be a **BPF_MAP_TYPE_SK_STORAGE** also.
+ *
+ *		Underneath, the value is stored locally at *sk* instead of
+ *		the *map*.  The *map* is used as the bpf-local-storage
+ *		"type". The bpf-local-storage "type" (i.e. the *map*) is
+ *		searched against all bpf-local-storages residing at *sk*.
+ *
+ *		An optional *flags* (**BPF_SK_STORAGE_GET_F_CREATE**) can be
+ *		used such that a new bpf-local-storage will be
+ *		created if one does not exist.  *value* can be used
+ *		together with **BPF_SK_STORAGE_GET_F_CREATE** to specify
+ *		the initial value of a bpf-local-storage.  If *value* is
+ *		**NULL**, the new bpf-local-storage will be zero initialized.
+ *	Return
+ *		A bpf-local-storage pointer is returned on success.
+ *
+ *		**NULL** if not found or there was an error in adding
+ *		a new bpf-local-storage.
+ *
+ * int bpf_sk_storage_delete(struct bpf_map *map, struct bpf_sock *sk)
+ *	Description
+ *		Delete a bpf-local-storage from a *sk*.
+ *	Return
+ *		0 on success.
+ *
+ *		**-ENOENT** if the bpf-local-storage cannot be found.
+ *
+ * int bpf_send_signal(u32 sig)
+ *	Description
+ *		Send signal *sig* to the current task.
+ *	Return
+ *		0 on success or successfully queued.
+ *
+ *		**-EBUSY** if work queue under nmi is full.
+ *
+ *		**-EINVAL** if *sig* is invalid.
+ *
+ *		**-EPERM** if no permission to send the *sig*.
+ *
+ *		**-EAGAIN** if bpf program can try again.
+ *
+ * s64 bpf_tcp_gen_syncookie(struct bpf_sock *sk, void *iph, u32 iph_len, struct tcphdr *th, u32 th_len)
+ *	Description
+ *		Try to issue a SYN cookie for the packet with corresponding
+ *		IP/TCP headers, *iph* and *th*, on the listening socket in *sk*.
+ *
+ *		*iph* points to the start of the IPv4 or IPv6 header, while
+ *		*iph_len* contains **sizeof**\ (**struct iphdr**) or
+ *		**sizeof**\ (**struct ip6hdr**).
+ *
+ *		*th* points to the start of the TCP header, while *th_len*
+ *		contains the length of the TCP header.
+ *
+ *	Return
+ *		On success, lower 32 bits hold the generated SYN cookie in
+ *		followed by 16 bits which hold the MSS value for that cookie,
+ *		and the top 16 bits are unused.
+ *
+ *		On failure, the returned value is one of the following:
+ *
+ *		**-EINVAL** SYN cookie cannot be issued due to error
+ *
+ *		**-ENOENT** SYN cookie should not be issued (no SYN flood)
+ *
+ *		**-EOPNOTSUPP** kernel configuration does not enable SYN cookies
+ *
+ *		**-EPROTONOSUPPORT** IP packet version is not 4 or 6
+ */
+#define __BPF_FUNC_MAPPER(FN)		\
+	FN(unspec),			\
+	FN(map_lookup_elem),		\
+	FN(map_update_elem),		\
+	FN(map_delete_elem),		\
+	FN(probe_read),			\
+	FN(ktime_get_ns),		\
+	FN(trace_printk),		\
+	FN(get_prandom_u32),		\
+	FN(get_smp_processor_id),	\
+	FN(skb_store_bytes),		\
+	FN(l3_csum_replace),		\
+	FN(l4_csum_replace),		\
+	FN(tail_call),			\
+	FN(clone_redirect),		\
+	FN(get_current_pid_tgid),	\
+	FN(get_current_uid_gid),	\
+	FN(get_current_comm),		\
+	FN(get_cgroup_classid),		\
+	FN(skb_vlan_push),		\
+	FN(skb_vlan_pop),		\
+	FN(skb_get_tunnel_key),		\
+	FN(skb_set_tunnel_key),		\
+	FN(perf_event_read),		\
+	FN(redirect),			\
+	FN(get_route_realm),		\
+	FN(perf_event_output),		\
+	FN(skb_load_bytes),		\
+	FN(get_stackid),		\
+	FN(csum_diff),			\
+	FN(skb_get_tunnel_opt),		\
+	FN(skb_set_tunnel_opt),		\
+	FN(skb_change_proto),		\
+	FN(skb_change_type),		\
+	FN(skb_under_cgroup),		\
+	FN(get_hash_recalc),		\
+	FN(get_current_task),		\
+	FN(probe_write_user),		\
+	FN(current_task_under_cgroup),	\
+	FN(skb_change_tail),		\
+	FN(skb_pull_data),		\
+	FN(csum_update),		\
+	FN(set_hash_invalid),		\
+	FN(get_numa_node_id),		\
+	FN(skb_change_head),		\
+	FN(xdp_adjust_head),		\
+	FN(probe_read_str),		\
+	FN(get_socket_cookie),		\
+	FN(get_socket_uid),		\
+	FN(set_hash),			\
+	FN(setsockopt),			\
+	FN(skb_adjust_room),		\
+	FN(redirect_map),		\
+	FN(sk_redirect_map),		\
+	FN(sock_map_update),		\
+	FN(xdp_adjust_meta),		\
+	FN(perf_event_read_value),	\
+	FN(perf_prog_read_value),	\
+	FN(getsockopt),			\
+	FN(override_return),		\
+	FN(sock_ops_cb_flags_set),	\
+	FN(msg_redirect_map),		\
+	FN(msg_apply_bytes),		\
+	FN(msg_cork_bytes),		\
+	FN(msg_pull_data),		\
+	FN(bind),			\
+	FN(xdp_adjust_tail),		\
+	FN(skb_get_xfrm_state),		\
+	FN(get_stack),			\
+	FN(skb_load_bytes_relative),	\
+	FN(fib_lookup),			\
+	FN(sock_hash_update),		\
+	FN(msg_redirect_hash),		\
+	FN(sk_redirect_hash),		\
+	FN(lwt_push_encap),		\
+	FN(lwt_seg6_store_bytes),	\
+	FN(lwt_seg6_adjust_srh),	\
+	FN(lwt_seg6_action),		\
+	FN(rc_repeat),			\
+	FN(rc_keydown),			\
+	FN(skb_cgroup_id),		\
+	FN(get_current_cgroup_id),	\
+	FN(get_local_storage),		\
+	FN(sk_select_reuseport),	\
+	FN(skb_ancestor_cgroup_id),	\
+	FN(sk_lookup_tcp),		\
+	FN(sk_lookup_udp),		\
+	FN(sk_release),			\
+	FN(map_push_elem),		\
+	FN(map_pop_elem),		\
+	FN(map_peek_elem),		\
+	FN(msg_push_data),		\
+	FN(msg_pop_data),		\
+	FN(rc_pointer_rel),		\
+	FN(spin_lock),			\
+	FN(spin_unlock),		\
+	FN(sk_fullsock),		\
+	FN(tcp_sock),			\
+	FN(skb_ecn_set_ce),		\
+	FN(get_listener_sock),		\
+	FN(skc_lookup_tcp),		\
+	FN(tcp_check_syncookie),	\
+	FN(sysctl_get_name),		\
+	FN(sysctl_get_current_value),	\
+	FN(sysctl_get_new_value),	\
+	FN(sysctl_set_new_value),	\
+	FN(strtol),			\
+	FN(strtoul),			\
+	FN(sk_storage_get),		\
+	FN(sk_storage_delete),		\
+	FN(send_signal),		\
+	FN(tcp_gen_syncookie),
+
+/* integer value in 'imm' field of BPF_CALL instruction selects which helper
+ * function eBPF program intends to call
+ */
+#define __BPF_ENUM_FN(x) BPF_FUNC_ ## x
+enum bpf_func_id {
+	__BPF_FUNC_MAPPER(__BPF_ENUM_FN)
+	__BPF_FUNC_MAX_ID,
+};
+#undef __BPF_ENUM_FN
+
+/* All flags used by eBPF helper functions, placed here. */
+
+/* BPF_FUNC_skb_store_bytes flags. */
+#define BPF_F_RECOMPUTE_CSUM		(1ULL << 0)
+#define BPF_F_INVALIDATE_HASH		(1ULL << 1)
+
+/* BPF_FUNC_l3_csum_replace and BPF_FUNC_l4_csum_replace flags.
+ * First 4 bits are for passing the header field size.
+ */
+#define BPF_F_HDR_FIELD_MASK		0xfULL
+
+/* BPF_FUNC_l4_csum_replace flags. */
+#define BPF_F_PSEUDO_HDR		(1ULL << 4)
+#define BPF_F_MARK_MANGLED_0		(1ULL << 5)
+#define BPF_F_MARK_ENFORCE		(1ULL << 6)
+
+/* BPF_FUNC_clone_redirect and BPF_FUNC_redirect flags. */
+#define BPF_F_INGRESS			(1ULL << 0)
+
+/* BPF_FUNC_skb_set_tunnel_key and BPF_FUNC_skb_get_tunnel_key flags. */
+#define BPF_F_TUNINFO_IPV6		(1ULL << 0)
+
+/* flags for both BPF_FUNC_get_stackid and BPF_FUNC_get_stack. */
+#define BPF_F_SKIP_FIELD_MASK		0xffULL
+#define BPF_F_USER_STACK		(1ULL << 8)
+/* flags used by BPF_FUNC_get_stackid only. */
+#define BPF_F_FAST_STACK_CMP		(1ULL << 9)
+#define BPF_F_REUSE_STACKID		(1ULL << 10)
+/* flags used by BPF_FUNC_get_stack only. */
+#define BPF_F_USER_BUILD_ID		(1ULL << 11)
+
+/* BPF_FUNC_skb_set_tunnel_key flags. */
+#define BPF_F_ZERO_CSUM_TX		(1ULL << 1)
+#define BPF_F_DONT_FRAGMENT		(1ULL << 2)
+#define BPF_F_SEQ_NUMBER		(1ULL << 3)
+
+/* BPF_FUNC_perf_event_output, BPF_FUNC_perf_event_read and
+ * BPF_FUNC_perf_event_read_value flags.
+ */
+#define BPF_F_INDEX_MASK		0xffffffffULL
+#define BPF_F_CURRENT_CPU		BPF_F_INDEX_MASK
+/* BPF_FUNC_perf_event_output for sk_buff input context. */
+#define BPF_F_CTXLEN_MASK		(0xfffffULL << 32)
+
+/* Current network namespace */
+#define BPF_F_CURRENT_NETNS		(-1L)
+
+/* BPF_FUNC_skb_adjust_room flags. */
+#define BPF_F_ADJ_ROOM_FIXED_GSO	(1ULL << 0)
+
+#define BPF_ADJ_ROOM_ENCAP_L2_MASK	0xff
+#define BPF_ADJ_ROOM_ENCAP_L2_SHIFT	56
+
+#define BPF_F_ADJ_ROOM_ENCAP_L3_IPV4	(1ULL << 1)
+#define BPF_F_ADJ_ROOM_ENCAP_L3_IPV6	(1ULL << 2)
+#define BPF_F_ADJ_ROOM_ENCAP_L4_GRE	(1ULL << 3)
+#define BPF_F_ADJ_ROOM_ENCAP_L4_UDP	(1ULL << 4)
+#define BPF_F_ADJ_ROOM_ENCAP_L2(len)	(((__u64)len & \
+					  BPF_ADJ_ROOM_ENCAP_L2_MASK) \
+					 << BPF_ADJ_ROOM_ENCAP_L2_SHIFT)
+
+/* BPF_FUNC_sysctl_get_name flags. */
+#define BPF_F_SYSCTL_BASE_NAME		(1ULL << 0)
+
+/* BPF_FUNC_sk_storage_get flags */
+#define BPF_SK_STORAGE_GET_F_CREATE	(1ULL << 0)
+
+/* Mode for BPF_FUNC_skb_adjust_room helper. */
+enum bpf_adj_room_mode {
+	BPF_ADJ_ROOM_NET,
+	BPF_ADJ_ROOM_MAC,
+};
+
+/* Mode for BPF_FUNC_skb_load_bytes_relative helper. */
+enum bpf_hdr_start_off {
+	BPF_HDR_START_MAC,
+	BPF_HDR_START_NET,
+};
+
+/* Encapsulation type for BPF_FUNC_lwt_push_encap helper. */
+enum bpf_lwt_encap_mode {
+	BPF_LWT_ENCAP_SEG6,
+	BPF_LWT_ENCAP_SEG6_INLINE,
+	BPF_LWT_ENCAP_IP,
+};
+
+#define __bpf_md_ptr(type, name)	\
+union {					\
+	type name;			\
+	__u64 :64;			\
+} __attribute__((aligned(8)))
+
+/* user accessible mirror of in-kernel sk_buff.
+ * new fields can only be added to the end of this structure
+ */
+struct __sk_buff {
+	__u32 len;
+	__u32 pkt_type;
+	__u32 mark;
+	__u32 queue_mapping;
+	__u32 protocol;
+	__u32 vlan_present;
+	__u32 vlan_tci;
+	__u32 vlan_proto;
+	__u32 priority;
+	__u32 ingress_ifindex;
+	__u32 ifindex;
+	__u32 tc_index;
+	__u32 cb[5];
+	__u32 hash;
+	__u32 tc_classid;
+	__u32 data;
+	__u32 data_end;
+	__u32 napi_id;
+
+	/* Accessed by BPF_PROG_TYPE_sk_skb types from here to ... */
+	__u32 family;
+	__u32 remote_ip4;	/* Stored in network byte order */
+	__u32 local_ip4;	/* Stored in network byte order */
+	__u32 remote_ip6[4];	/* Stored in network byte order */
+	__u32 local_ip6[4];	/* Stored in network byte order */
+	__u32 remote_port;	/* Stored in network byte order */
+	__u32 local_port;	/* stored in host byte order */
+	/* ... here. */
+
+	__u32 data_meta;
+	__bpf_md_ptr(struct bpf_flow_keys *, flow_keys);
+	__u64 tstamp;
+	__u32 wire_len;
+	__u32 gso_segs;
+	__bpf_md_ptr(struct bpf_sock *, sk);
+};
+
+struct bpf_tunnel_key {
+	__u32 tunnel_id;
+	union {
+		__u32 remote_ipv4;
+		__u32 remote_ipv6[4];
+	};
+	__u8 tunnel_tos;
+	__u8 tunnel_ttl;
+	__u16 tunnel_ext;	/* Padding, future use. */
+	__u32 tunnel_label;
+};
+
+/* user accessible mirror of in-kernel xfrm_state.
+ * new fields can only be added to the end of this structure
+ */
+struct bpf_xfrm_state {
+	__u32 reqid;
+	__u32 spi;	/* Stored in network byte order */
+	__u16 family;
+	__u16 ext;	/* Padding, future use. */
+	union {
+		__u32 remote_ipv4;	/* Stored in network byte order */
+		__u32 remote_ipv6[4];	/* Stored in network byte order */
+	};
+};
+
+/* Generic BPF return codes which all BPF program types may support.
+ * The values are binary compatible with their TC_ACT_* counter-part to
+ * provide backwards compatibility with existing SCHED_CLS and SCHED_ACT
+ * programs.
+ *
+ * XDP is handled seprately, see XDP_*.
+ */
+enum bpf_ret_code {
+	BPF_OK = 0,
+	/* 1 reserved */
+	BPF_DROP = 2,
+	/* 3-6 reserved */
+	BPF_REDIRECT = 7,
+	/* >127 are reserved for prog type specific return codes.
+	 *
+	 * BPF_LWT_REROUTE: used by BPF_PROG_TYPE_LWT_IN and
+	 *    BPF_PROG_TYPE_LWT_XMIT to indicate that skb had been
+	 *    changed and should be routed based on its new L3 header.
+	 *    (This is an L3 redirect, as opposed to L2 redirect
+	 *    represented by BPF_REDIRECT above).
+	 */
+	BPF_LWT_REROUTE = 128,
+};
+
+struct bpf_sock {
+	__u32 bound_dev_if;
+	__u32 family;
+	__u32 type;
+	__u32 protocol;
+	__u32 mark;
+	__u32 priority;
+	/* IP address also allows 1 and 2 bytes access */
+	__u32 src_ip4;
+	__u32 src_ip6[4];
+	__u32 src_port;		/* host byte order */
+	__u32 dst_port;		/* network byte order */
+	__u32 dst_ip4;
+	__u32 dst_ip6[4];
+	__u32 state;
+};
+
+struct bpf_tcp_sock {
+	__u32 snd_cwnd;		/* Sending congestion window		*/
+	__u32 srtt_us;		/* smoothed round trip time << 3 in usecs */
+	__u32 rtt_min;
+	__u32 snd_ssthresh;	/* Slow start size threshold		*/
+	__u32 rcv_nxt;		/* What we want to receive next		*/
+	__u32 snd_nxt;		/* Next sequence we send		*/
+	__u32 snd_una;		/* First byte we want an ack for	*/
+	__u32 mss_cache;	/* Cached effective mss, not including SACKS */
+	__u32 ecn_flags;	/* ECN status bits.			*/
+	__u32 rate_delivered;	/* saved rate sample: packets delivered */
+	__u32 rate_interval_us;	/* saved rate sample: time elapsed */
+	__u32 packets_out;	/* Packets which are "in flight"	*/
+	__u32 retrans_out;	/* Retransmitted packets out		*/
+	__u32 total_retrans;	/* Total retransmits for entire connection */
+	__u32 segs_in;		/* RFC4898 tcpEStatsPerfSegsIn
+				 * total number of segments in.
+				 */
+	__u32 data_segs_in;	/* RFC4898 tcpEStatsPerfDataSegsIn
+				 * total number of data segments in.
+				 */
+	__u32 segs_out;		/* RFC4898 tcpEStatsPerfSegsOut
+				 * The total number of segments sent.
+				 */
+	__u32 data_segs_out;	/* RFC4898 tcpEStatsPerfDataSegsOut
+				 * total number of data segments sent.
+				 */
+	__u32 lost_out;		/* Lost packets			*/
+	__u32 sacked_out;	/* SACK'd packets			*/
+	__u64 bytes_received;	/* RFC4898 tcpEStatsAppHCThruOctetsReceived
+				 * sum(delta(rcv_nxt)), or how many bytes
+				 * were acked.
+				 */
+	__u64 bytes_acked;	/* RFC4898 tcpEStatsAppHCThruOctetsAcked
+				 * sum(delta(snd_una)), or how many bytes
+				 * were acked.
+				 */
+	__u32 dsack_dups;	/* RFC4898 tcpEStatsStackDSACKDups
+				 * total number of DSACK blocks received
+				 */
+	__u32 delivered;	/* Total data packets delivered incl. rexmits */
+	__u32 delivered_ce;	/* Like the above but only ECE marked packets */
+	__u32 icsk_retransmits;	/* Number of unrecovered [RTO] timeouts */
+};
+
+struct bpf_sock_tuple {
+	union {
+		struct {
+			__be32 saddr;
+			__be32 daddr;
+			__be16 sport;
+			__be16 dport;
+		} ipv4;
+		struct {
+			__be32 saddr[4];
+			__be32 daddr[4];
+			__be16 sport;
+			__be16 dport;
+		} ipv6;
+	};
+};
+
+struct bpf_xdp_sock {
+	__u32 queue_id;
+};
+
+#define XDP_PACKET_HEADROOM 256
+
+/* User return codes for XDP prog type.
+ * A valid XDP program must return one of these defined values. All other
+ * return codes are reserved for future use. Unknown return codes will
+ * result in packet drops and a warning via bpf_warn_invalid_xdp_action().
+ */
+enum xdp_action {
+	XDP_ABORTED = 0,
+	XDP_DROP,
+	XDP_PASS,
+	XDP_TX,
+	XDP_REDIRECT,
+};
+
+/* user accessible metadata for XDP packet hook
+ * new fields must be added to the end of this structure
+ */
+struct xdp_md {
+	__u32 data;
+	__u32 data_end;
+	__u32 data_meta;
+	/* Below access go through struct xdp_rxq_info */
+	__u32 ingress_ifindex; /* rxq->dev->ifindex */
+	__u32 rx_queue_index;  /* rxq->queue_index  */
+};
+
+enum sk_action {
+	SK_DROP = 0,
+	SK_PASS,
+};
+
+/* user accessible metadata for SK_MSG packet hook, new fields must
+ * be added to the end of this structure
+ */
+struct sk_msg_md {
+	__bpf_md_ptr(void *, data);
+	__bpf_md_ptr(void *, data_end);
+
+	__u32 family;
+	__u32 remote_ip4;	/* Stored in network byte order */
+	__u32 local_ip4;	/* Stored in network byte order */
+	__u32 remote_ip6[4];	/* Stored in network byte order */
+	__u32 local_ip6[4];	/* Stored in network byte order */
+	__u32 remote_port;	/* Stored in network byte order */
+	__u32 local_port;	/* stored in host byte order */
+	__u32 size;		/* Total size of sk_msg */
+};
+
+struct sk_reuseport_md {
+	/*
+	 * Start of directly accessible data. It begins from
+	 * the tcp/udp header.
+	 */
+	__bpf_md_ptr(void *, data);
+	/* End of directly accessible data */
+	__bpf_md_ptr(void *, data_end);
+	/*
+	 * Total length of packet (starting from the tcp/udp header).
+	 * Note that the directly accessible bytes (data_end - data)
+	 * could be less than this "len".  Those bytes could be
+	 * indirectly read by a helper "bpf_skb_load_bytes()".
+	 */
+	__u32 len;
+	/*
+	 * Eth protocol in the mac header (network byte order). e.g.
+	 * ETH_P_IP(0x0800) and ETH_P_IPV6(0x86DD)
+	 */
+	__u32 eth_protocol;
+	__u32 ip_protocol;	/* IP protocol. e.g. IPPROTO_TCP, IPPROTO_UDP */
+	__u32 bind_inany;	/* Is sock bound to an INANY address? */
+	__u32 hash;		/* A hash of the packet 4 tuples */
+};
+
+#define BPF_TAG_SIZE	8
+
+struct bpf_prog_info {
+	__u32 type;
+	__u32 id;
+	__u8  tag[BPF_TAG_SIZE];
+	__u32 jited_prog_len;
+	__u32 xlated_prog_len;
+	__aligned_u64 jited_prog_insns;
+	__aligned_u64 xlated_prog_insns;
+	__u64 load_time;	/* ns since boottime */
+	__u32 created_by_uid;
+	__u32 nr_map_ids;
+	__aligned_u64 map_ids;
+	char name[BPF_OBJ_NAME_LEN];
+	__u32 ifindex;
+	__u32 gpl_compatible:1;
+	__u32 :31; /* alignment pad */
+	__u64 netns_dev;
+	__u64 netns_ino;
+	__u32 nr_jited_ksyms;
+	__u32 nr_jited_func_lens;
+	__aligned_u64 jited_ksyms;
+	__aligned_u64 jited_func_lens;
+	__u32 btf_id;
+	__u32 func_info_rec_size;
+	__aligned_u64 func_info;
+	__u32 nr_func_info;
+	__u32 nr_line_info;
+	__aligned_u64 line_info;
+	__aligned_u64 jited_line_info;
+	__u32 nr_jited_line_info;
+	__u32 line_info_rec_size;
+	__u32 jited_line_info_rec_size;
+	__u32 nr_prog_tags;
+	__aligned_u64 prog_tags;
+	__u64 run_time_ns;
+	__u64 run_cnt;
+} __attribute__((aligned(8)));
+
+struct bpf_map_info {
+	__u32 type;
+	__u32 id;
+	__u32 key_size;
+	__u32 value_size;
+	__u32 max_entries;
+	__u32 map_flags;
+	char  name[BPF_OBJ_NAME_LEN];
+	__u32 ifindex;
+	__u32 :32;
+	__u64 netns_dev;
+	__u64 netns_ino;
+	__u32 btf_id;
+	__u32 btf_key_type_id;
+	__u32 btf_value_type_id;
+} __attribute__((aligned(8)));
+
+struct bpf_btf_info {
+	__aligned_u64 btf;
+	__u32 btf_size;
+	__u32 id;
+} __attribute__((aligned(8)));
+
+/* User bpf_sock_addr struct to access socket fields and sockaddr struct passed
+ * by user and intended to be used by socket (e.g. to bind to, depends on
+ * attach attach type).
+ */
+struct bpf_sock_addr {
+	__u32 user_family;	/* Allows 4-byte read, but no write. */
+	__u32 user_ip4;		/* Allows 1,2,4-byte read and 4-byte write.
+				 * Stored in network byte order.
+				 */
+	__u32 user_ip6[4];	/* Allows 1,2,4,8-byte read and 4,8-byte write.
+				 * Stored in network byte order.
+				 */
+	__u32 user_port;	/* Allows 4-byte read and write.
+				 * Stored in network byte order
+				 */
+	__u32 family;		/* Allows 4-byte read, but no write */
+	__u32 type;		/* Allows 4-byte read, but no write */
+	__u32 protocol;		/* Allows 4-byte read, but no write */
+	__u32 msg_src_ip4;	/* Allows 1,2,4-byte read and 4-byte write.
+				 * Stored in network byte order.
+				 */
+	__u32 msg_src_ip6[4];	/* Allows 1,2,4,8-byte read and 4,8-byte write.
+				 * Stored in network byte order.
+				 */
+	__bpf_md_ptr(struct bpf_sock *, sk);
+};
+
+/* User bpf_sock_ops struct to access socket values and specify request ops
+ * and their replies.
+ * Some of this fields are in network (bigendian) byte order and may need
+ * to be converted before use (bpf_ntohl() defined in samples/bpf/bpf_endian.h).
+ * New fields can only be added at the end of this structure
+ */
+struct bpf_sock_ops {
+	__u32 op;
+	union {
+		__u32 args[4];		/* Optionally passed to bpf program */
+		__u32 reply;		/* Returned by bpf program	    */
+		__u32 replylong[4];	/* Optionally returned by bpf prog  */
+	};
+	__u32 family;
+	__u32 remote_ip4;	/* Stored in network byte order */
+	__u32 local_ip4;	/* Stored in network byte order */
+	__u32 remote_ip6[4];	/* Stored in network byte order */
+	__u32 local_ip6[4];	/* Stored in network byte order */
+	__u32 remote_port;	/* Stored in network byte order */
+	__u32 local_port;	/* stored in host byte order */
+	__u32 is_fullsock;	/* Some TCP fields are only valid if
+				 * there is a full socket. If not, the
+				 * fields read as zero.
+				 */
+	__u32 snd_cwnd;
+	__u32 srtt_us;		/* Averaged RTT << 3 in usecs */
+	__u32 bpf_sock_ops_cb_flags; /* flags defined in uapi/linux/tcp.h */
+	__u32 state;
+	__u32 rtt_min;
+	__u32 snd_ssthresh;
+	__u32 rcv_nxt;
+	__u32 snd_nxt;
+	__u32 snd_una;
+	__u32 mss_cache;
+	__u32 ecn_flags;
+	__u32 rate_delivered;
+	__u32 rate_interval_us;
+	__u32 packets_out;
+	__u32 retrans_out;
+	__u32 total_retrans;
+	__u32 segs_in;
+	__u32 data_segs_in;
+	__u32 segs_out;
+	__u32 data_segs_out;
+	__u32 lost_out;
+	__u32 sacked_out;
+	__u32 sk_txhash;
+	__u64 bytes_received;
+	__u64 bytes_acked;
+	__bpf_md_ptr(struct bpf_sock *, sk);
+};
+
+/* Definitions for bpf_sock_ops_cb_flags */
+#define BPF_SOCK_OPS_RTO_CB_FLAG	(1<<0)
+#define BPF_SOCK_OPS_RETRANS_CB_FLAG	(1<<1)
+#define BPF_SOCK_OPS_STATE_CB_FLAG	(1<<2)
+#define BPF_SOCK_OPS_RTT_CB_FLAG	(1<<3)
+#define BPF_SOCK_OPS_ALL_CB_FLAGS       0xF		/* Mask of all currently
+							 * supported cb flags
+							 */
+
+/* List of known BPF sock_ops operators.
+ * New entries can only be added at the end
+ */
+enum {
+	BPF_SOCK_OPS_VOID,
+	BPF_SOCK_OPS_TIMEOUT_INIT,	/* Should return SYN-RTO value to use or
+					 * -1 if default value should be used
+					 */
+	BPF_SOCK_OPS_RWND_INIT,		/* Should return initial advertized
+					 * window (in packets) or -1 if default
+					 * value should be used
+					 */
+	BPF_SOCK_OPS_TCP_CONNECT_CB,	/* Calls BPF program right before an
+					 * active connection is initialized
+					 */
+	BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB,	/* Calls BPF program when an
+						 * active connection is
+						 * established
+						 */
+	BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB,	/* Calls BPF program when a
+						 * passive connection is
+						 * established
+						 */
+	BPF_SOCK_OPS_NEEDS_ECN,		/* If connection's congestion control
+					 * needs ECN
+					 */
+	BPF_SOCK_OPS_BASE_RTT,		/* Get base RTT. The correct value is
+					 * based on the path and may be
+					 * dependent on the congestion control
+					 * algorithm. In general it indicates
+					 * a congestion threshold. RTTs above
+					 * this indicate congestion
+					 */
+	BPF_SOCK_OPS_RTO_CB,		/* Called when an RTO has triggered.
+					 * Arg1: value of icsk_retransmits
+					 * Arg2: value of icsk_rto
+					 * Arg3: whether RTO has expired
+					 */
+	BPF_SOCK_OPS_RETRANS_CB,	/* Called when skb is retransmitted.
+					 * Arg1: sequence number of 1st byte
+					 * Arg2: # segments
+					 * Arg3: return value of
+					 *       tcp_transmit_skb (0 => success)
+					 */
+	BPF_SOCK_OPS_STATE_CB,		/* Called when TCP changes state.
+					 * Arg1: old_state
+					 * Arg2: new_state
+					 */
+	BPF_SOCK_OPS_TCP_LISTEN_CB,	/* Called on listen(2), right after
+					 * socket transition to LISTEN state.
+					 */
+	BPF_SOCK_OPS_RTT_CB,		/* Called on every RTT.
+					 */
+};
+
+/* List of TCP states. There is a build check in net/ipv4/tcp.c to detect
+ * changes between the TCP and BPF versions. Ideally this should never happen.
+ * If it does, we need to add code to convert them before calling
+ * the BPF sock_ops function.
+ */
+enum {
+	BPF_TCP_ESTABLISHED = 1,
+	BPF_TCP_SYN_SENT,
+	BPF_TCP_SYN_RECV,
+	BPF_TCP_FIN_WAIT1,
+	BPF_TCP_FIN_WAIT2,
+	BPF_TCP_TIME_WAIT,
+	BPF_TCP_CLOSE,
+	BPF_TCP_CLOSE_WAIT,
+	BPF_TCP_LAST_ACK,
+	BPF_TCP_LISTEN,
+	BPF_TCP_CLOSING,	/* Now a valid state */
+	BPF_TCP_NEW_SYN_RECV,
+
+	BPF_TCP_MAX_STATES	/* Leave at the end! */
+};
+
+#define TCP_BPF_IW		1001	/* Set TCP initial congestion window */
+#define TCP_BPF_SNDCWND_CLAMP	1002	/* Set sndcwnd_clamp */
+
+struct bpf_perf_event_value {
+	__u64 counter;
+	__u64 enabled;
+	__u64 running;
+};
+
+#define BPF_DEVCG_ACC_MKNOD	(1ULL << 0)
+#define BPF_DEVCG_ACC_READ	(1ULL << 1)
+#define BPF_DEVCG_ACC_WRITE	(1ULL << 2)
+
+#define BPF_DEVCG_DEV_BLOCK	(1ULL << 0)
+#define BPF_DEVCG_DEV_CHAR	(1ULL << 1)
+
+struct bpf_cgroup_dev_ctx {
+	/* access_type encoded as (BPF_DEVCG_ACC_* << 16) | BPF_DEVCG_DEV_* */
+	__u32 access_type;
+	__u32 major;
+	__u32 minor;
+};
+
+struct bpf_raw_tracepoint_args {
+	__u64 args[0];
+};
+
+/* DIRECT:  Skip the FIB rules and go to FIB table associated with device
+ * OUTPUT:  Do lookup from egress perspective; default is ingress
+ */
+#define BPF_FIB_LOOKUP_DIRECT  (1U << 0)
+#define BPF_FIB_LOOKUP_OUTPUT  (1U << 1)
+
+enum {
+	BPF_FIB_LKUP_RET_SUCCESS,      /* lookup successful */
+	BPF_FIB_LKUP_RET_BLACKHOLE,    /* dest is blackholed; can be dropped */
+	BPF_FIB_LKUP_RET_UNREACHABLE,  /* dest is unreachable; can be dropped */
+	BPF_FIB_LKUP_RET_PROHIBIT,     /* dest not allowed; can be dropped */
+	BPF_FIB_LKUP_RET_NOT_FWDED,    /* packet is not forwarded */
+	BPF_FIB_LKUP_RET_FWD_DISABLED, /* fwding is not enabled on ingress */
+	BPF_FIB_LKUP_RET_UNSUPP_LWT,   /* fwd requires encapsulation */
+	BPF_FIB_LKUP_RET_NO_NEIGH,     /* no neighbor entry for nh */
+	BPF_FIB_LKUP_RET_FRAG_NEEDED,  /* fragmentation required to fwd */
+};
+
+struct bpf_fib_lookup {
+	/* input:  network family for lookup (AF_INET, AF_INET6)
+	 * output: network family of egress nexthop
+	 */
+	__u8	family;
+
+	/* set if lookup is to consider L4 data - e.g., FIB rules */
+	__u8	l4_protocol;
+	__be16	sport;
+	__be16	dport;
+
+	/* total length of packet from network header - used for MTU check */
+	__u16	tot_len;
+
+	/* input: L3 device index for lookup
+	 * output: device index from FIB lookup
+	 */
+	__u32	ifindex;
+
+	union {
+		/* inputs to lookup */
+		__u8	tos;		/* AF_INET  */
+		__be32	flowinfo;	/* AF_INET6, flow_label + priority */
+
+		/* output: metric of fib result (IPv4/IPv6 only) */
+		__u32	rt_metric;
+	};
+
+	union {
+		__be32		ipv4_src;
+		__u32		ipv6_src[4];  /* in6_addr; network order */
+	};
+
+	/* input to bpf_fib_lookup, ipv{4,6}_dst is destination address in
+	 * network header. output: bpf_fib_lookup sets to gateway address
+	 * if FIB lookup returns gateway route
+	 */
+	union {
+		__be32		ipv4_dst;
+		__u32		ipv6_dst[4];  /* in6_addr; network order */
+	};
+
+	/* output */
+	__be16	h_vlan_proto;
+	__be16	h_vlan_TCI;
+	__u8	smac[6];     /* ETH_ALEN */
+	__u8	dmac[6];     /* ETH_ALEN */
+};
+
+enum bpf_task_fd_type {
+	BPF_FD_TYPE_RAW_TRACEPOINT,	/* tp name */
+	BPF_FD_TYPE_TRACEPOINT,		/* tp name */
+	BPF_FD_TYPE_KPROBE,		/* (symbol + offset) or addr */
+	BPF_FD_TYPE_KRETPROBE,		/* (symbol + offset) or addr */
+	BPF_FD_TYPE_UPROBE,		/* filename + offset */
+	BPF_FD_TYPE_URETPROBE,		/* filename + offset */
+};
+
+#define BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG		(1U << 0)
+#define BPF_FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL		(1U << 1)
+#define BPF_FLOW_DISSECTOR_F_STOP_AT_ENCAP		(1U << 2)
+
+struct bpf_flow_keys {
+	__u16	nhoff;
+	__u16	thoff;
+	__u16	addr_proto;			/* ETH_P_* of valid addrs */
+	__u8	is_frag;
+	__u8	is_first_frag;
+	__u8	is_encap;
+	__u8	ip_proto;
+	__be16	n_proto;
+	__be16	sport;
+	__be16	dport;
+	union {
+		struct {
+			__be32	ipv4_src;
+			__be32	ipv4_dst;
+		};
+		struct {
+			__u32	ipv6_src[4];	/* in6_addr; network order */
+			__u32	ipv6_dst[4];	/* in6_addr; network order */
+		};
+	};
+	__u32	flags;
+	__be32	flow_label;
+};
+
+struct bpf_func_info {
+	__u32	insn_off;
+	__u32	type_id;
+};
+
+#define BPF_LINE_INFO_LINE_NUM(line_col)	((line_col) >> 10)
+#define BPF_LINE_INFO_LINE_COL(line_col)	((line_col) & 0x3ff)
+
+struct bpf_line_info {
+	__u32	insn_off;
+	__u32	file_name_off;
+	__u32	line_off;
+	__u32	line_col;
+};
+
+struct bpf_spin_lock {
+	__u32	val;
+};
+
+struct bpf_sysctl {
+	__u32	write;		/* Sysctl is being read (= 0) or written (= 1).
+				 * Allows 1,2,4-byte read, but no write.
+				 */
+	__u32	file_pos;	/* Sysctl file position to read from, write to.
+				 * Allows 1,2,4-byte read an 4-byte write.
+				 */
+};
+
+struct bpf_sockopt {
+	__bpf_md_ptr(struct bpf_sock *, sk);
+	__bpf_md_ptr(void *, optval);
+	__bpf_md_ptr(void *, optval_end);
+
+	__s32	level;
+	__s32	optname;
+	__s32	optlen;
+	__s32	retval;
+};
+
+#endif /* __LINUX_BPF_H__ */
diff --git a/include/uapi/linux/bpf_common.h b/include/uapi/linux/bpf_common.h
new file mode 100644
index 0000000..f0fe139
--- /dev/null
+++ b/include/uapi/linux/bpf_common.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_BPF_COMMON_H__
+#define __LINUX_BPF_COMMON_H__
+
+/* Instruction classes */
+#define BPF_CLASS(code) ((code) & 0x07)
+#define		BPF_LD		0x00
+#define		BPF_LDX		0x01
+#define		BPF_ST		0x02
+#define		BPF_STX		0x03
+#define		BPF_ALU		0x04
+#define		BPF_JMP		0x05
+#define		BPF_RET		0x06
+#define		BPF_MISC        0x07
+
+/* ld/ldx fields */
+#define BPF_SIZE(code)  ((code) & 0x18)
+#define		BPF_W		0x00 /* 32-bit */
+#define		BPF_H		0x08 /* 16-bit */
+#define		BPF_B		0x10 /*  8-bit */
+/* eBPF		BPF_DW		0x18    64-bit */
+#define BPF_MODE(code)  ((code) & 0xe0)
+#define		BPF_IMM		0x00
+#define		BPF_ABS		0x20
+#define		BPF_IND		0x40
+#define		BPF_MEM		0x60
+#define		BPF_LEN		0x80
+#define		BPF_MSH		0xa0
+
+/* alu/jmp fields */
+#define BPF_OP(code)    ((code) & 0xf0)
+#define		BPF_ADD		0x00
+#define		BPF_SUB		0x10
+#define		BPF_MUL		0x20
+#define		BPF_DIV		0x30
+#define		BPF_OR		0x40
+#define		BPF_AND		0x50
+#define		BPF_LSH		0x60
+#define		BPF_RSH		0x70
+#define		BPF_NEG		0x80
+#define		BPF_MOD		0x90
+#define		BPF_XOR		0xa0
+
+#define		BPF_JA		0x00
+#define		BPF_JEQ		0x10
+#define		BPF_JGT		0x20
+#define		BPF_JGE		0x30
+#define		BPF_JSET        0x40
+#define BPF_SRC(code)   ((code) & 0x08)
+#define		BPF_K		0x00
+#define		BPF_X		0x08
+
+#ifndef BPF_MAXINSNS
+#define BPF_MAXINSNS 4096
+#endif
+
+#endif /* __LINUX_BPF_COMMON_H__ */
diff --git a/include/uapi/linux/btf.h b/include/uapi/linux/btf.h
new file mode 100644
index 0000000..d28dd89
--- /dev/null
+++ b/include/uapi/linux/btf.h
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* Copyright (c) 2018 Facebook */
+#ifndef __LINUX_BTF_H__
+#define __LINUX_BTF_H__
+
+#include <linux/types.h>
+
+#define BTF_MAGIC	0xeB9F
+#define BTF_VERSION	1
+
+struct btf_header {
+	__u16	magic;
+	__u8	version;
+	__u8	flags;
+	__u32	hdr_len;
+
+	/* All offsets are in bytes relative to the end of this header */
+	__u32	type_off;	/* offset of type section	*/
+	__u32	type_len;	/* length of type section	*/
+	__u32	str_off;	/* offset of string section	*/
+	__u32	str_len;	/* length of string section	*/
+};
+
+/* Max # of type identifier */
+#define BTF_MAX_TYPE	0x000fffff
+/* Max offset into the string section */
+#define BTF_MAX_NAME_OFFSET	0x00ffffff
+/* Max # of struct/union/enum members or func args */
+#define BTF_MAX_VLEN	0xffff
+
+struct btf_type {
+	__u32 name_off;
+	/* "info" bits arrangement
+	 * bits  0-15: vlen (e.g. # of struct's members)
+	 * bits 16-23: unused
+	 * bits 24-27: kind (e.g. int, ptr, array...etc)
+	 * bits 28-30: unused
+	 * bit     31: kind_flag, currently used by
+	 *             struct, union and fwd
+	 */
+	__u32 info;
+	/* "size" is used by INT, ENUM, STRUCT, UNION and DATASEC.
+	 * "size" tells the size of the type it is describing.
+	 *
+	 * "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT,
+	 * FUNC, FUNC_PROTO and VAR.
+	 * "type" is a type_id referring to another type.
+	 */
+	union {
+		__u32 size;
+		__u32 type;
+	};
+};
+
+#define BTF_INFO_KIND(info)	(((info) >> 24) & 0x0f)
+#define BTF_INFO_VLEN(info)	((info) & 0xffff)
+#define BTF_INFO_KFLAG(info)	((info) >> 31)
+
+#define BTF_KIND_UNKN		0	/* Unknown	*/
+#define BTF_KIND_INT		1	/* Integer	*/
+#define BTF_KIND_PTR		2	/* Pointer	*/
+#define BTF_KIND_ARRAY		3	/* Array	*/
+#define BTF_KIND_STRUCT		4	/* Struct	*/
+#define BTF_KIND_UNION		5	/* Union	*/
+#define BTF_KIND_ENUM		6	/* Enumeration	*/
+#define BTF_KIND_FWD		7	/* Forward	*/
+#define BTF_KIND_TYPEDEF	8	/* Typedef	*/
+#define BTF_KIND_VOLATILE	9	/* Volatile	*/
+#define BTF_KIND_CONST		10	/* Const	*/
+#define BTF_KIND_RESTRICT	11	/* Restrict	*/
+#define BTF_KIND_FUNC		12	/* Function	*/
+#define BTF_KIND_FUNC_PROTO	13	/* Function Proto	*/
+#define BTF_KIND_VAR		14	/* Variable	*/
+#define BTF_KIND_DATASEC	15	/* Section	*/
+#define BTF_KIND_MAX		BTF_KIND_DATASEC
+#define NR_BTF_KINDS		(BTF_KIND_MAX + 1)
+
+/* For some specific BTF_KIND, "struct btf_type" is immediately
+ * followed by extra data.
+ */
+
+/* BTF_KIND_INT is followed by a u32 and the following
+ * is the 32 bits arrangement:
+ */
+#define BTF_INT_ENCODING(VAL)	(((VAL) & 0x0f000000) >> 24)
+#define BTF_INT_OFFSET(VAL)	(((VAL) & 0x00ff0000) >> 16)
+#define BTF_INT_BITS(VAL)	((VAL)  & 0x000000ff)
+
+/* Attributes stored in the BTF_INT_ENCODING */
+#define BTF_INT_SIGNED	(1 << 0)
+#define BTF_INT_CHAR	(1 << 1)
+#define BTF_INT_BOOL	(1 << 2)
+
+/* BTF_KIND_ENUM is followed by multiple "struct btf_enum".
+ * The exact number of btf_enum is stored in the vlen (of the
+ * info in "struct btf_type").
+ */
+struct btf_enum {
+	__u32	name_off;
+	__s32	val;
+};
+
+/* BTF_KIND_ARRAY is followed by one "struct btf_array" */
+struct btf_array {
+	__u32	type;
+	__u32	index_type;
+	__u32	nelems;
+};
+
+/* BTF_KIND_STRUCT and BTF_KIND_UNION are followed
+ * by multiple "struct btf_member".  The exact number
+ * of btf_member is stored in the vlen (of the info in
+ * "struct btf_type").
+ */
+struct btf_member {
+	__u32	name_off;
+	__u32	type;
+	/* If the type info kind_flag is set, the btf_member offset
+	 * contains both member bitfield size and bit offset. The
+	 * bitfield size is set for bitfield members. If the type
+	 * info kind_flag is not set, the offset contains only bit
+	 * offset.
+	 */
+	__u32	offset;
+};
+
+/* If the struct/union type info kind_flag is set, the
+ * following two macros are used to access bitfield_size
+ * and bit_offset from btf_member.offset.
+ */
+#define BTF_MEMBER_BITFIELD_SIZE(val)	((val) >> 24)
+#define BTF_MEMBER_BIT_OFFSET(val)	((val) & 0xffffff)
+
+/* BTF_KIND_FUNC_PROTO is followed by multiple "struct btf_param".
+ * The exact number of btf_param is stored in the vlen (of the
+ * info in "struct btf_type").
+ */
+struct btf_param {
+	__u32	name_off;
+	__u32	type;
+};
+
+enum {
+	BTF_VAR_STATIC = 0,
+	BTF_VAR_GLOBAL_ALLOCATED,
+};
+
+/* BTF_KIND_VAR is followed by a single "struct btf_var" to describe
+ * additional information related to the variable such as its linkage.
+ */
+struct btf_var {
+	__u32	linkage;
+};
+
+/* BTF_KIND_DATASEC is followed by multiple "struct btf_var_secinfo"
+ * to describe all BTF_KIND_VAR types it contains along with it's
+ * in-section offset as well as size.
+ */
+struct btf_var_secinfo {
+	__u32	type;
+	__u32	offset;
+	__u32	size;
+};
+
+#endif /* __LINUX_BTF_H__ */
diff --git a/include/uapi/linux/can.h b/include/uapi/linux/can.h
new file mode 100644
index 0000000..55b6660
--- /dev/null
+++ b/include/uapi/linux/can.h
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: ((GPL-2.0-only WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * linux/can.h
+ *
+ * Definitions for CAN network layer (socket addr / CAN frame / CAN filter)
+ *
+ * Authors: Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
+ *          Urs Thuermann   <urs.thuermann@volkswagen.de>
+ * Copyright (c) 2002-2007 Volkswagen Group Electronic Research
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Volkswagen nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * Alternatively, provided that this notice is retained in full, this
+ * software may be distributed under the terms of the GNU General
+ * Public License ("GPL") version 2, in which case the provisions of the
+ * GPL apply INSTEAD OF those given above.
+ *
+ * The provided data structures and external interfaces from this code
+ * are not restricted to be used by modules with a GPL compatible license.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CAN_H
+#define _CAN_H
+
+#include <linux/types.h>
+#include <linux/socket.h>
+
+/* controller area network (CAN) kernel definitions */
+
+/* special address description flags for the CAN_ID */
+#define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */
+#define CAN_RTR_FLAG 0x40000000U /* remote transmission request */
+#define CAN_ERR_FLAG 0x20000000U /* error message frame */
+
+/* valid bits in CAN ID for frame formats */
+#define CAN_SFF_MASK 0x000007FFU /* standard frame format (SFF) */
+#define CAN_EFF_MASK 0x1FFFFFFFU /* extended frame format (EFF) */
+#define CAN_ERR_MASK 0x1FFFFFFFU /* omit EFF, RTR, ERR flags */
+
+/*
+ * Controller Area Network Identifier structure
+ *
+ * bit 0-28	: CAN identifier (11/29 bit)
+ * bit 29	: error message frame flag (0 = data frame, 1 = error message)
+ * bit 30	: remote transmission request flag (1 = rtr frame)
+ * bit 31	: frame format flag (0 = standard 11 bit, 1 = extended 29 bit)
+ */
+typedef __u32 canid_t;
+
+#define CAN_SFF_ID_BITS		11
+#define CAN_EFF_ID_BITS		29
+
+/*
+ * Controller Area Network Error Message Frame Mask structure
+ *
+ * bit 0-28	: error class mask (see include/uapi/linux/can/error.h)
+ * bit 29-31	: set to zero
+ */
+typedef __u32 can_err_mask_t;
+
+/* CAN payload length and DLC definitions according to ISO 11898-1 */
+#define CAN_MAX_DLC 8
+#define CAN_MAX_DLEN 8
+
+/* CAN FD payload length and DLC definitions according to ISO 11898-7 */
+#define CANFD_MAX_DLC 15
+#define CANFD_MAX_DLEN 64
+
+/**
+ * struct can_frame - basic CAN frame structure
+ * @can_id:  CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
+ * @can_dlc: frame payload length in byte (0 .. 8) aka data length code
+ *           N.B. the DLC field from ISO 11898-1 Chapter 8.4.2.3 has a 1:1
+ *           mapping of the 'data length code' to the real payload length
+ * @__pad:   padding
+ * @__res0:  reserved / padding
+ * @__res1:  reserved / padding
+ * @data:    CAN frame payload (up to 8 byte)
+ */
+struct can_frame {
+	canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
+	__u8    can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */
+	__u8    __pad;   /* padding */
+	__u8    __res0;  /* reserved / padding */
+	__u8    __res1;  /* reserved / padding */
+	__u8    data[CAN_MAX_DLEN] __attribute__((aligned(8)));
+};
+
+/*
+ * defined bits for canfd_frame.flags
+ *
+ * The use of struct canfd_frame implies the Extended Data Length (EDL) bit to
+ * be set in the CAN frame bitstream on the wire. The EDL bit switch turns
+ * the CAN controllers bitstream processor into the CAN FD mode which creates
+ * two new options within the CAN FD frame specification:
+ *
+ * Bit Rate Switch - to indicate a second bitrate is/was used for the payload
+ * Error State Indicator - represents the error state of the transmitting node
+ *
+ * As the CANFD_ESI bit is internally generated by the transmitting CAN
+ * controller only the CANFD_BRS bit is relevant for real CAN controllers when
+ * building a CAN FD frame for transmission. Setting the CANFD_ESI bit can make
+ * sense for virtual CAN interfaces to test applications with echoed frames.
+ */
+#define CANFD_BRS 0x01 /* bit rate switch (second bitrate for payload data) */
+#define CANFD_ESI 0x02 /* error state indicator of the transmitting node */
+
+/**
+ * struct canfd_frame - CAN flexible data rate frame structure
+ * @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
+ * @len:    frame payload length in byte (0 .. CANFD_MAX_DLEN)
+ * @flags:  additional flags for CAN FD
+ * @__res0: reserved / padding
+ * @__res1: reserved / padding
+ * @data:   CAN FD frame payload (up to CANFD_MAX_DLEN byte)
+ */
+struct canfd_frame {
+	canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
+	__u8    len;     /* frame payload length in byte */
+	__u8    flags;   /* additional flags for CAN FD */
+	__u8    __res0;  /* reserved / padding */
+	__u8    __res1;  /* reserved / padding */
+	__u8    data[CANFD_MAX_DLEN] __attribute__((aligned(8)));
+};
+
+#define CAN_MTU		(sizeof(struct can_frame))
+#define CANFD_MTU	(sizeof(struct canfd_frame))
+
+/* particular protocols of the protocol family PF_CAN */
+#define CAN_RAW		1 /* RAW sockets */
+#define CAN_BCM		2 /* Broadcast Manager */
+#define CAN_TP16	3 /* VAG Transport Protocol v1.6 */
+#define CAN_TP20	4 /* VAG Transport Protocol v2.0 */
+#define CAN_MCNET	5 /* Bosch MCNet */
+#define CAN_ISOTP	6 /* ISO 15765-2 Transport Protocol */
+#define CAN_J1939	7 /* SAE J1939 */
+#define CAN_NPROTO	8
+
+#define SOL_CAN_BASE 100
+
+/**
+ * struct sockaddr_can - the sockaddr structure for CAN sockets
+ * @can_family:  address family number AF_CAN.
+ * @can_ifindex: CAN network interface index.
+ * @can_addr:    protocol specific address information
+ */
+struct sockaddr_can {
+	__kernel_sa_family_t can_family;
+	int         can_ifindex;
+	union {
+		/* transport protocol class address information (e.g. ISOTP) */
+		struct { canid_t rx_id, tx_id; } tp;
+
+		/* J1939 address information */
+		struct {
+			/* 8 byte name when using dynamic addressing */
+			__u64 name;
+
+			/* pgn:
+			 * 8 bit: PS in PDU2 case, else 0
+			 * 8 bit: PF
+			 * 1 bit: DP
+			 * 1 bit: reserved
+			 */
+			__u32 pgn;
+
+			/* 1 byte address */
+			__u8 addr;
+		} j1939;
+
+		/* reserved for future CAN protocols address information */
+	} can_addr;
+};
+
+/**
+ * struct can_filter - CAN ID based filter in can_register().
+ * @can_id:   relevant bits of CAN ID which are not masked out.
+ * @can_mask: CAN mask (see description)
+ *
+ * Description:
+ * A filter matches, when
+ *
+ *          <received_can_id> & mask == can_id & mask
+ *
+ * The filter can be inverted (CAN_INV_FILTER bit set in can_id) or it can
+ * filter for error message frames (CAN_ERR_FLAG bit set in mask).
+ */
+struct can_filter {
+	canid_t can_id;
+	canid_t can_mask;
+};
+
+#define CAN_INV_FILTER 0x20000000U /* to be set in can_filter.can_id */
+#define CAN_RAW_FILTER_MAX 512 /* maximum number of can_filter set via setsockopt() */
+
+#endif /* !_UAPI_CAN_H */
diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netlink.h
new file mode 100644
index 0000000..7159dc6
--- /dev/null
+++ b/include/uapi/linux/can/netlink.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+/*
+ * linux/can/netlink.h
+ *
+ * Definitions for the CAN netlink interface
+ *
+ * Copyright (c) 2009 Wolfgang Grandegger <wg@grandegger.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the version 2 of the GNU General Public License
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _CAN_NETLINK_H
+#define _CAN_NETLINK_H
+
+#include <linux/types.h>
+
+/*
+ * CAN bit-timing parameters
+ *
+ * For further information, please read chapter "8 BIT TIMING
+ * REQUIREMENTS" of the "Bosch CAN Specification version 2.0"
+ * at http://www.semiconductors.bosch.de/pdf/can2spec.pdf.
+ */
+struct can_bittiming {
+	__u32 bitrate;		/* Bit-rate in bits/second */
+	__u32 sample_point;	/* Sample point in one-tenth of a percent */
+	__u32 tq;		/* Time quanta (TQ) in nanoseconds */
+	__u32 prop_seg;		/* Propagation segment in TQs */
+	__u32 phase_seg1;	/* Phase buffer segment 1 in TQs */
+	__u32 phase_seg2;	/* Phase buffer segment 2 in TQs */
+	__u32 sjw;		/* Synchronisation jump width in TQs */
+	__u32 brp;		/* Bit-rate prescaler */
+};
+
+/*
+ * CAN hardware-dependent bit-timing constant
+ *
+ * Used for calculating and checking bit-timing parameters
+ */
+struct can_bittiming_const {
+	char name[16];		/* Name of the CAN controller hardware */
+	__u32 tseg1_min;	/* Time segment 1 = prop_seg + phase_seg1 */
+	__u32 tseg1_max;
+	__u32 tseg2_min;	/* Time segment 2 = phase_seg2 */
+	__u32 tseg2_max;
+	__u32 sjw_max;		/* Synchronisation jump width */
+	__u32 brp_min;		/* Bit-rate prescaler */
+	__u32 brp_max;
+	__u32 brp_inc;
+};
+
+/*
+ * CAN clock parameters
+ */
+struct can_clock {
+	__u32 freq;		/* CAN system clock frequency in Hz */
+};
+
+/*
+ * CAN operational and error states
+ */
+enum can_state {
+	CAN_STATE_ERROR_ACTIVE = 0,	/* RX/TX error count < 96 */
+	CAN_STATE_ERROR_WARNING,	/* RX/TX error count < 128 */
+	CAN_STATE_ERROR_PASSIVE,	/* RX/TX error count < 256 */
+	CAN_STATE_BUS_OFF,		/* RX/TX error count >= 256 */
+	CAN_STATE_STOPPED,		/* Device is stopped */
+	CAN_STATE_SLEEPING,		/* Device is sleeping */
+	CAN_STATE_MAX
+};
+
+/*
+ * CAN bus error counters
+ */
+struct can_berr_counter {
+	__u16 txerr;
+	__u16 rxerr;
+};
+
+/*
+ * CAN controller mode
+ */
+struct can_ctrlmode {
+	__u32 mask;
+	__u32 flags;
+};
+
+#define CAN_CTRLMODE_LOOPBACK		0x01	/* Loopback mode */
+#define CAN_CTRLMODE_LISTENONLY		0x02	/* Listen-only mode */
+#define CAN_CTRLMODE_3_SAMPLES		0x04	/* Triple sampling mode */
+#define CAN_CTRLMODE_ONE_SHOT		0x08	/* One-Shot mode */
+#define CAN_CTRLMODE_BERR_REPORTING	0x10	/* Bus-error reporting */
+#define CAN_CTRLMODE_FD			0x20	/* CAN FD mode */
+#define CAN_CTRLMODE_PRESUME_ACK	0x40	/* Ignore missing CAN ACKs */
+#define CAN_CTRLMODE_FD_NON_ISO		0x80	/* CAN FD in non-ISO mode */
+
+/*
+ * CAN device statistics
+ */
+struct can_device_stats {
+	__u32 bus_error;	/* Bus errors */
+	__u32 error_warning;	/* Changes to error warning state */
+	__u32 error_passive;	/* Changes to error passive state */
+	__u32 bus_off;		/* Changes to bus off state */
+	__u32 arbitration_lost; /* Arbitration lost errors */
+	__u32 restarts;		/* CAN controller re-starts */
+};
+
+/*
+ * CAN netlink interface
+ */
+enum {
+	IFLA_CAN_UNSPEC,
+	IFLA_CAN_BITTIMING,
+	IFLA_CAN_BITTIMING_CONST,
+	IFLA_CAN_CLOCK,
+	IFLA_CAN_STATE,
+	IFLA_CAN_CTRLMODE,
+	IFLA_CAN_RESTART_MS,
+	IFLA_CAN_RESTART,
+	IFLA_CAN_BERR_COUNTER,
+	IFLA_CAN_DATA_BITTIMING,
+	IFLA_CAN_DATA_BITTIMING_CONST,
+	IFLA_CAN_TERMINATION,
+	IFLA_CAN_TERMINATION_CONST,
+	IFLA_CAN_BITRATE_CONST,
+	IFLA_CAN_DATA_BITRATE_CONST,
+	IFLA_CAN_BITRATE_MAX,
+	__IFLA_CAN_MAX
+};
+
+#define IFLA_CAN_MAX	(__IFLA_CAN_MAX - 1)
+
+/* u16 termination range: 1..65535 Ohms */
+#define CAN_TERMINATION_DISABLED 0
+
+#endif /* !_UAPI_CAN_NETLINK_H */
diff --git a/include/uapi/linux/can/vxcan.h b/include/uapi/linux/can/vxcan.h
new file mode 100644
index 0000000..3e3d2eb
--- /dev/null
+++ b/include/uapi/linux/can/vxcan.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+#ifndef _CAN_VXCAN_H
+#define _CAN_VXCAN_H
+
+enum {
+	VXCAN_INFO_UNSPEC,
+	VXCAN_INFO_PEER,
+
+	__VXCAN_INFO_MAX
+#define VXCAN_INFO_MAX	(__VXCAN_INFO_MAX - 1)
+};
+
+#endif
diff --git a/include/uapi/linux/const.h b/include/uapi/linux/const.h
new file mode 100644
index 0000000..fd885c7
--- /dev/null
+++ b/include/uapi/linux/const.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* const.h: Macros for dealing with constants.  */
+
+#ifndef _LINUX_CONST_H
+#define _LINUX_CONST_H
+
+/* Some constant macros are used in both assembler and
+ * C code.  Therefore we cannot annotate them always with
+ * 'UL' and other type specifiers unilaterally.  We
+ * use the following macros to deal with this.
+ *
+ * Similarly, _AT() will cast an expression with a type in C, but
+ * leave it unchanged in asm.
+ */
+
+#ifdef __ASSEMBLY__
+#define _AC(X,Y)	X
+#define _AT(T,X)	X
+#else
+#define __AC(X,Y)	(X##Y)
+#define _AC(X,Y)	__AC(X,Y)
+#define _AT(T,X)	((T)(X))
+#endif
+
+#define _UL(x)		(_AC(x, UL))
+#define _ULL(x)		(_AC(x, ULL))
+
+#define _BITUL(x)	(_UL(1) << (x))
+#define _BITULL(x)	(_ULL(1) << (x))
+
+#endif /* _LINUX_CONST_H */
diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h
new file mode 100644
index 0000000..84c0caf
--- /dev/null
+++ b/include/uapi/linux/devlink.h
@@ -0,0 +1,471 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * include/uapi/linux/devlink.h - Network physical device Netlink interface
+ * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
+ * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _LINUX_DEVLINK_H_
+#define _LINUX_DEVLINK_H_
+
+#define DEVLINK_GENL_NAME "devlink"
+#define DEVLINK_GENL_VERSION 0x1
+#define DEVLINK_GENL_MCGRP_CONFIG_NAME "config"
+
+enum devlink_command {
+	/* don't change the order or add anything between, this is ABI! */
+	DEVLINK_CMD_UNSPEC,
+
+	DEVLINK_CMD_GET,		/* can dump */
+	DEVLINK_CMD_SET,
+	DEVLINK_CMD_NEW,
+	DEVLINK_CMD_DEL,
+
+	DEVLINK_CMD_PORT_GET,		/* can dump */
+	DEVLINK_CMD_PORT_SET,
+	DEVLINK_CMD_PORT_NEW,
+	DEVLINK_CMD_PORT_DEL,
+
+	DEVLINK_CMD_PORT_SPLIT,
+	DEVLINK_CMD_PORT_UNSPLIT,
+
+	DEVLINK_CMD_SB_GET,		/* can dump */
+	DEVLINK_CMD_SB_SET,
+	DEVLINK_CMD_SB_NEW,
+	DEVLINK_CMD_SB_DEL,
+
+	DEVLINK_CMD_SB_POOL_GET,	/* can dump */
+	DEVLINK_CMD_SB_POOL_SET,
+	DEVLINK_CMD_SB_POOL_NEW,
+	DEVLINK_CMD_SB_POOL_DEL,
+
+	DEVLINK_CMD_SB_PORT_POOL_GET,	/* can dump */
+	DEVLINK_CMD_SB_PORT_POOL_SET,
+	DEVLINK_CMD_SB_PORT_POOL_NEW,
+	DEVLINK_CMD_SB_PORT_POOL_DEL,
+
+	DEVLINK_CMD_SB_TC_POOL_BIND_GET,	/* can dump */
+	DEVLINK_CMD_SB_TC_POOL_BIND_SET,
+	DEVLINK_CMD_SB_TC_POOL_BIND_NEW,
+	DEVLINK_CMD_SB_TC_POOL_BIND_DEL,
+
+	/* Shared buffer occupancy monitoring commands */
+	DEVLINK_CMD_SB_OCC_SNAPSHOT,
+	DEVLINK_CMD_SB_OCC_MAX_CLEAR,
+
+	DEVLINK_CMD_ESWITCH_GET,
+#define DEVLINK_CMD_ESWITCH_MODE_GET /* obsolete, never use this! */ \
+	DEVLINK_CMD_ESWITCH_GET
+
+	DEVLINK_CMD_ESWITCH_SET,
+#define DEVLINK_CMD_ESWITCH_MODE_SET /* obsolete, never use this! */ \
+	DEVLINK_CMD_ESWITCH_SET
+
+	DEVLINK_CMD_DPIPE_TABLE_GET,
+	DEVLINK_CMD_DPIPE_ENTRIES_GET,
+	DEVLINK_CMD_DPIPE_HEADERS_GET,
+	DEVLINK_CMD_DPIPE_TABLE_COUNTERS_SET,
+	DEVLINK_CMD_RESOURCE_SET,
+	DEVLINK_CMD_RESOURCE_DUMP,
+
+	/* Hot driver reload, makes configuration changes take place. The
+	 * devlink instance is not released during the process.
+	 */
+	DEVLINK_CMD_RELOAD,
+
+	DEVLINK_CMD_PARAM_GET,		/* can dump */
+	DEVLINK_CMD_PARAM_SET,
+	DEVLINK_CMD_PARAM_NEW,
+	DEVLINK_CMD_PARAM_DEL,
+
+	DEVLINK_CMD_REGION_GET,
+	DEVLINK_CMD_REGION_SET,
+	DEVLINK_CMD_REGION_NEW,
+	DEVLINK_CMD_REGION_DEL,
+	DEVLINK_CMD_REGION_READ,
+
+	DEVLINK_CMD_PORT_PARAM_GET,	/* can dump */
+	DEVLINK_CMD_PORT_PARAM_SET,
+	DEVLINK_CMD_PORT_PARAM_NEW,
+	DEVLINK_CMD_PORT_PARAM_DEL,
+
+	DEVLINK_CMD_INFO_GET,		/* can dump */
+
+	DEVLINK_CMD_HEALTH_REPORTER_GET,
+	DEVLINK_CMD_HEALTH_REPORTER_SET,
+	DEVLINK_CMD_HEALTH_REPORTER_RECOVER,
+	DEVLINK_CMD_HEALTH_REPORTER_DIAGNOSE,
+	DEVLINK_CMD_HEALTH_REPORTER_DUMP_GET,
+	DEVLINK_CMD_HEALTH_REPORTER_DUMP_CLEAR,
+
+	DEVLINK_CMD_FLASH_UPDATE,
+	DEVLINK_CMD_FLASH_UPDATE_END,		/* notification only */
+	DEVLINK_CMD_FLASH_UPDATE_STATUS,	/* notification only */
+
+	DEVLINK_CMD_TRAP_GET,		/* can dump */
+	DEVLINK_CMD_TRAP_SET,
+	DEVLINK_CMD_TRAP_NEW,
+	DEVLINK_CMD_TRAP_DEL,
+
+	DEVLINK_CMD_TRAP_GROUP_GET,	/* can dump */
+	DEVLINK_CMD_TRAP_GROUP_SET,
+	DEVLINK_CMD_TRAP_GROUP_NEW,
+	DEVLINK_CMD_TRAP_GROUP_DEL,
+
+	/* add new commands above here */
+	__DEVLINK_CMD_MAX,
+	DEVLINK_CMD_MAX = __DEVLINK_CMD_MAX - 1
+};
+
+enum devlink_port_type {
+	DEVLINK_PORT_TYPE_NOTSET,
+	DEVLINK_PORT_TYPE_AUTO,
+	DEVLINK_PORT_TYPE_ETH,
+	DEVLINK_PORT_TYPE_IB,
+};
+
+enum devlink_sb_pool_type {
+	DEVLINK_SB_POOL_TYPE_INGRESS,
+	DEVLINK_SB_POOL_TYPE_EGRESS,
+};
+
+/* static threshold - limiting the maximum number of bytes.
+ * dynamic threshold - limiting the maximum number of bytes
+ *   based on the currently available free space in the shared buffer pool.
+ *   In this mode, the maximum quota is calculated based
+ *   on the following formula:
+ *     max_quota = alpha / (1 + alpha) * Free_Buffer
+ *   While Free_Buffer is the amount of none-occupied buffer associated to
+ *   the relevant pool.
+ *   The value range which can be passed is 0-20 and serves
+ *   for computation of alpha by following formula:
+ *     alpha = 2 ^ (passed_value - 10)
+ */
+
+enum devlink_sb_threshold_type {
+	DEVLINK_SB_THRESHOLD_TYPE_STATIC,
+	DEVLINK_SB_THRESHOLD_TYPE_DYNAMIC,
+};
+
+#define DEVLINK_SB_THRESHOLD_TO_ALPHA_MAX 20
+
+enum devlink_eswitch_mode {
+	DEVLINK_ESWITCH_MODE_LEGACY,
+	DEVLINK_ESWITCH_MODE_SWITCHDEV,
+};
+
+enum devlink_eswitch_inline_mode {
+	DEVLINK_ESWITCH_INLINE_MODE_NONE,
+	DEVLINK_ESWITCH_INLINE_MODE_LINK,
+	DEVLINK_ESWITCH_INLINE_MODE_NETWORK,
+	DEVLINK_ESWITCH_INLINE_MODE_TRANSPORT,
+};
+
+enum devlink_eswitch_encap_mode {
+	DEVLINK_ESWITCH_ENCAP_MODE_NONE,
+	DEVLINK_ESWITCH_ENCAP_MODE_BASIC,
+};
+
+enum devlink_port_flavour {
+	DEVLINK_PORT_FLAVOUR_PHYSICAL, /* Any kind of a port physically
+					* facing the user.
+					*/
+	DEVLINK_PORT_FLAVOUR_CPU, /* CPU port */
+	DEVLINK_PORT_FLAVOUR_DSA, /* Distributed switch architecture
+				   * interconnect port.
+				   */
+	DEVLINK_PORT_FLAVOUR_PCI_PF, /* Represents eswitch port for
+				      * the PCI PF. It is an internal
+				      * port that faces the PCI PF.
+				      */
+	DEVLINK_PORT_FLAVOUR_PCI_VF, /* Represents eswitch port
+				      * for the PCI VF. It is an internal
+				      * port that faces the PCI VF.
+				      */
+};
+
+enum devlink_param_cmode {
+	DEVLINK_PARAM_CMODE_RUNTIME,
+	DEVLINK_PARAM_CMODE_DRIVERINIT,
+	DEVLINK_PARAM_CMODE_PERMANENT,
+
+	/* Add new configuration modes above */
+	__DEVLINK_PARAM_CMODE_MAX,
+	DEVLINK_PARAM_CMODE_MAX = __DEVLINK_PARAM_CMODE_MAX - 1
+};
+
+enum devlink_param_fw_load_policy_value {
+	DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DRIVER,
+	DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_FLASH,
+	DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DISK,
+	DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_UNKNOWN,
+};
+
+enum devlink_param_reset_dev_on_drv_probe_value {
+	DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_UNKNOWN,
+	DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_ALWAYS,
+	DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_NEVER,
+	DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_DISK,
+};
+
+enum {
+	DEVLINK_ATTR_STATS_RX_PACKETS,		/* u64 */
+	DEVLINK_ATTR_STATS_RX_BYTES,		/* u64 */
+
+	__DEVLINK_ATTR_STATS_MAX,
+	DEVLINK_ATTR_STATS_MAX = __DEVLINK_ATTR_STATS_MAX - 1
+};
+
+/**
+ * enum devlink_trap_action - Packet trap action.
+ * @DEVLINK_TRAP_ACTION_DROP: Packet is dropped by the device and a copy is not
+ *                            sent to the CPU.
+ * @DEVLINK_TRAP_ACTION_TRAP: The sole copy of the packet is sent to the CPU.
+ */
+enum devlink_trap_action {
+	DEVLINK_TRAP_ACTION_DROP,
+	DEVLINK_TRAP_ACTION_TRAP,
+};
+
+/**
+ * enum devlink_trap_type - Packet trap type.
+ * @DEVLINK_TRAP_TYPE_DROP: Trap reason is a drop. Trapped packets are only
+ *                          processed by devlink and not injected to the
+ *                          kernel's Rx path.
+ * @DEVLINK_TRAP_TYPE_EXCEPTION: Trap reason is an exception. Packet was not
+ *                               forwarded as intended due to an exception
+ *                               (e.g., missing neighbour entry) and trapped to
+ *                               control plane for resolution. Trapped packets
+ *                               are processed by devlink and injected to
+ *                               the kernel's Rx path.
+ */
+enum devlink_trap_type {
+	DEVLINK_TRAP_TYPE_DROP,
+	DEVLINK_TRAP_TYPE_EXCEPTION,
+};
+
+enum {
+	/* Trap can report input port as metadata */
+	DEVLINK_ATTR_TRAP_METADATA_TYPE_IN_PORT,
+};
+
+enum devlink_attr {
+	/* don't change the order or add anything between, this is ABI! */
+	DEVLINK_ATTR_UNSPEC,
+
+	/* bus name + dev name together are a handle for devlink entity */
+	DEVLINK_ATTR_BUS_NAME,			/* string */
+	DEVLINK_ATTR_DEV_NAME,			/* string */
+
+	DEVLINK_ATTR_PORT_INDEX,		/* u32 */
+	DEVLINK_ATTR_PORT_TYPE,			/* u16 */
+	DEVLINK_ATTR_PORT_DESIRED_TYPE,		/* u16 */
+	DEVLINK_ATTR_PORT_NETDEV_IFINDEX,	/* u32 */
+	DEVLINK_ATTR_PORT_NETDEV_NAME,		/* string */
+	DEVLINK_ATTR_PORT_IBDEV_NAME,		/* string */
+	DEVLINK_ATTR_PORT_SPLIT_COUNT,		/* u32 */
+	DEVLINK_ATTR_PORT_SPLIT_GROUP,		/* u32 */
+	DEVLINK_ATTR_SB_INDEX,			/* u32 */
+	DEVLINK_ATTR_SB_SIZE,			/* u32 */
+	DEVLINK_ATTR_SB_INGRESS_POOL_COUNT,	/* u16 */
+	DEVLINK_ATTR_SB_EGRESS_POOL_COUNT,	/* u16 */
+	DEVLINK_ATTR_SB_INGRESS_TC_COUNT,	/* u16 */
+	DEVLINK_ATTR_SB_EGRESS_TC_COUNT,	/* u16 */
+	DEVLINK_ATTR_SB_POOL_INDEX,		/* u16 */
+	DEVLINK_ATTR_SB_POOL_TYPE,		/* u8 */
+	DEVLINK_ATTR_SB_POOL_SIZE,		/* u32 */
+	DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE,	/* u8 */
+	DEVLINK_ATTR_SB_THRESHOLD,		/* u32 */
+	DEVLINK_ATTR_SB_TC_INDEX,		/* u16 */
+	DEVLINK_ATTR_SB_OCC_CUR,		/* u32 */
+	DEVLINK_ATTR_SB_OCC_MAX,		/* u32 */
+	DEVLINK_ATTR_ESWITCH_MODE,		/* u16 */
+	DEVLINK_ATTR_ESWITCH_INLINE_MODE,	/* u8 */
+
+	DEVLINK_ATTR_DPIPE_TABLES,		/* nested */
+	DEVLINK_ATTR_DPIPE_TABLE,		/* nested */
+	DEVLINK_ATTR_DPIPE_TABLE_NAME,		/* string */
+	DEVLINK_ATTR_DPIPE_TABLE_SIZE,		/* u64 */
+	DEVLINK_ATTR_DPIPE_TABLE_MATCHES,	/* nested */
+	DEVLINK_ATTR_DPIPE_TABLE_ACTIONS,	/* nested */
+	DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED,	/* u8 */
+
+	DEVLINK_ATTR_DPIPE_ENTRIES,		/* nested */
+	DEVLINK_ATTR_DPIPE_ENTRY,		/* nested */
+	DEVLINK_ATTR_DPIPE_ENTRY_INDEX,		/* u64 */
+	DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES,	/* nested */
+	DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES,	/* nested */
+	DEVLINK_ATTR_DPIPE_ENTRY_COUNTER,	/* u64 */
+
+	DEVLINK_ATTR_DPIPE_MATCH,		/* nested */
+	DEVLINK_ATTR_DPIPE_MATCH_VALUE,		/* nested */
+	DEVLINK_ATTR_DPIPE_MATCH_TYPE,		/* u32 */
+
+	DEVLINK_ATTR_DPIPE_ACTION,		/* nested */
+	DEVLINK_ATTR_DPIPE_ACTION_VALUE,	/* nested */
+	DEVLINK_ATTR_DPIPE_ACTION_TYPE,		/* u32 */
+
+	DEVLINK_ATTR_DPIPE_VALUE,
+	DEVLINK_ATTR_DPIPE_VALUE_MASK,
+	DEVLINK_ATTR_DPIPE_VALUE_MAPPING,	/* u32 */
+
+	DEVLINK_ATTR_DPIPE_HEADERS,		/* nested */
+	DEVLINK_ATTR_DPIPE_HEADER,		/* nested */
+	DEVLINK_ATTR_DPIPE_HEADER_NAME,		/* string */
+	DEVLINK_ATTR_DPIPE_HEADER_ID,		/* u32 */
+	DEVLINK_ATTR_DPIPE_HEADER_FIELDS,	/* nested */
+	DEVLINK_ATTR_DPIPE_HEADER_GLOBAL,	/* u8 */
+	DEVLINK_ATTR_DPIPE_HEADER_INDEX,	/* u32 */
+
+	DEVLINK_ATTR_DPIPE_FIELD,		/* nested */
+	DEVLINK_ATTR_DPIPE_FIELD_NAME,		/* string */
+	DEVLINK_ATTR_DPIPE_FIELD_ID,		/* u32 */
+	DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH,	/* u32 */
+	DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE,	/* u32 */
+
+	DEVLINK_ATTR_PAD,
+
+	DEVLINK_ATTR_ESWITCH_ENCAP_MODE,	/* u8 */
+	DEVLINK_ATTR_RESOURCE_LIST,		/* nested */
+	DEVLINK_ATTR_RESOURCE,			/* nested */
+	DEVLINK_ATTR_RESOURCE_NAME,		/* string */
+	DEVLINK_ATTR_RESOURCE_ID,		/* u64 */
+	DEVLINK_ATTR_RESOURCE_SIZE,		/* u64 */
+	DEVLINK_ATTR_RESOURCE_SIZE_NEW,		/* u64 */
+	DEVLINK_ATTR_RESOURCE_SIZE_VALID,	/* u8 */
+	DEVLINK_ATTR_RESOURCE_SIZE_MIN,		/* u64 */
+	DEVLINK_ATTR_RESOURCE_SIZE_MAX,		/* u64 */
+	DEVLINK_ATTR_RESOURCE_SIZE_GRAN,        /* u64 */
+	DEVLINK_ATTR_RESOURCE_UNIT,		/* u8 */
+	DEVLINK_ATTR_RESOURCE_OCC,		/* u64 */
+	DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_ID,	/* u64 */
+	DEVLINK_ATTR_DPIPE_TABLE_RESOURCE_UNITS,/* u64 */
+
+	DEVLINK_ATTR_PORT_FLAVOUR,		/* u16 */
+	DEVLINK_ATTR_PORT_NUMBER,		/* u32 */
+	DEVLINK_ATTR_PORT_SPLIT_SUBPORT_NUMBER,	/* u32 */
+
+	DEVLINK_ATTR_PARAM,			/* nested */
+	DEVLINK_ATTR_PARAM_NAME,		/* string */
+	DEVLINK_ATTR_PARAM_GENERIC,		/* flag */
+	DEVLINK_ATTR_PARAM_TYPE,		/* u8 */
+	DEVLINK_ATTR_PARAM_VALUES_LIST,		/* nested */
+	DEVLINK_ATTR_PARAM_VALUE,		/* nested */
+	DEVLINK_ATTR_PARAM_VALUE_DATA,		/* dynamic */
+	DEVLINK_ATTR_PARAM_VALUE_CMODE,		/* u8 */
+
+	DEVLINK_ATTR_REGION_NAME,               /* string */
+	DEVLINK_ATTR_REGION_SIZE,               /* u64 */
+	DEVLINK_ATTR_REGION_SNAPSHOTS,          /* nested */
+	DEVLINK_ATTR_REGION_SNAPSHOT,           /* nested */
+	DEVLINK_ATTR_REGION_SNAPSHOT_ID,        /* u32 */
+
+	DEVLINK_ATTR_REGION_CHUNKS,             /* nested */
+	DEVLINK_ATTR_REGION_CHUNK,              /* nested */
+	DEVLINK_ATTR_REGION_CHUNK_DATA,         /* binary */
+	DEVLINK_ATTR_REGION_CHUNK_ADDR,         /* u64 */
+	DEVLINK_ATTR_REGION_CHUNK_LEN,          /* u64 */
+
+	DEVLINK_ATTR_INFO_DRIVER_NAME,		/* string */
+	DEVLINK_ATTR_INFO_SERIAL_NUMBER,	/* string */
+	DEVLINK_ATTR_INFO_VERSION_FIXED,	/* nested */
+	DEVLINK_ATTR_INFO_VERSION_RUNNING,	/* nested */
+	DEVLINK_ATTR_INFO_VERSION_STORED,	/* nested */
+	DEVLINK_ATTR_INFO_VERSION_NAME,		/* string */
+	DEVLINK_ATTR_INFO_VERSION_VALUE,	/* string */
+
+	DEVLINK_ATTR_SB_POOL_CELL_SIZE,		/* u32 */
+
+	DEVLINK_ATTR_FMSG,			/* nested */
+	DEVLINK_ATTR_FMSG_OBJ_NEST_START,	/* flag */
+	DEVLINK_ATTR_FMSG_PAIR_NEST_START,	/* flag */
+	DEVLINK_ATTR_FMSG_ARR_NEST_START,	/* flag */
+	DEVLINK_ATTR_FMSG_NEST_END,		/* flag */
+	DEVLINK_ATTR_FMSG_OBJ_NAME,		/* string */
+	DEVLINK_ATTR_FMSG_OBJ_VALUE_TYPE,	/* u8 */
+	DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA,	/* dynamic */
+
+	DEVLINK_ATTR_HEALTH_REPORTER,			/* nested */
+	DEVLINK_ATTR_HEALTH_REPORTER_NAME,		/* string */
+	DEVLINK_ATTR_HEALTH_REPORTER_STATE,		/* u8 */
+	DEVLINK_ATTR_HEALTH_REPORTER_ERR_COUNT,		/* u64 */
+	DEVLINK_ATTR_HEALTH_REPORTER_RECOVER_COUNT,	/* u64 */
+	DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS,		/* u64 */
+	DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD,	/* u64 */
+	DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER,	/* u8 */
+
+	DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME,	/* string */
+	DEVLINK_ATTR_FLASH_UPDATE_COMPONENT,	/* string */
+	DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG,	/* string */
+	DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE,	/* u64 */
+	DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL,	/* u64 */
+
+	DEVLINK_ATTR_PORT_PCI_PF_NUMBER,	/* u16 */
+	DEVLINK_ATTR_PORT_PCI_VF_NUMBER,	/* u16 */
+
+	DEVLINK_ATTR_STATS,				/* nested */
+
+	DEVLINK_ATTR_TRAP_NAME,				/* string */
+	/* enum devlink_trap_action */
+	DEVLINK_ATTR_TRAP_ACTION,			/* u8 */
+	/* enum devlink_trap_type */
+	DEVLINK_ATTR_TRAP_TYPE,				/* u8 */
+	DEVLINK_ATTR_TRAP_GENERIC,			/* flag */
+	DEVLINK_ATTR_TRAP_METADATA,			/* nested */
+	DEVLINK_ATTR_TRAP_GROUP_NAME,			/* string */
+
+	DEVLINK_ATTR_RELOAD_FAILED,			/* u8 0 or 1 */
+
+	DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS_NS,	/* u64 */
+	/* add new attributes above here, update the policy in devlink.c */
+
+	__DEVLINK_ATTR_MAX,
+	DEVLINK_ATTR_MAX = __DEVLINK_ATTR_MAX - 1
+};
+
+/* Mapping between internal resource described by the field and system
+ * structure
+ */
+enum devlink_dpipe_field_mapping_type {
+	DEVLINK_DPIPE_FIELD_MAPPING_TYPE_NONE,
+	DEVLINK_DPIPE_FIELD_MAPPING_TYPE_IFINDEX,
+};
+
+/* Match type - specify the type of the match */
+enum devlink_dpipe_match_type {
+	DEVLINK_DPIPE_MATCH_TYPE_FIELD_EXACT,
+};
+
+/* Action type - specify the action type */
+enum devlink_dpipe_action_type {
+	DEVLINK_DPIPE_ACTION_TYPE_FIELD_MODIFY,
+};
+
+enum devlink_dpipe_field_ethernet_id {
+	DEVLINK_DPIPE_FIELD_ETHERNET_DST_MAC,
+};
+
+enum devlink_dpipe_field_ipv4_id {
+	DEVLINK_DPIPE_FIELD_IPV4_DST_IP,
+};
+
+enum devlink_dpipe_field_ipv6_id {
+	DEVLINK_DPIPE_FIELD_IPV6_DST_IP,
+};
+
+enum devlink_dpipe_header_id {
+	DEVLINK_DPIPE_HEADER_ETHERNET,
+	DEVLINK_DPIPE_HEADER_IPV4,
+	DEVLINK_DPIPE_HEADER_IPV6,
+};
+
+enum devlink_resource_unit {
+	DEVLINK_RESOURCE_UNIT_ENTRY,
+};
+
+#endif /* _LINUX_DEVLINK_H_ */
diff --git a/include/uapi/linux/elf-em.h b/include/uapi/linux/elf-em.h
new file mode 100644
index 0000000..f47e853
--- /dev/null
+++ b/include/uapi/linux/elf-em.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_ELF_EM_H
+#define _LINUX_ELF_EM_H
+
+/* These constants define the various ELF target machines */
+#define EM_NONE		0
+#define EM_M32		1
+#define EM_SPARC	2
+#define EM_386		3
+#define EM_68K		4
+#define EM_88K		5
+#define EM_486		6	/* Perhaps disused */
+#define EM_860		7
+#define EM_MIPS		8	/* MIPS R3000 (officially, big-endian only) */
+				/* Next two are historical and binaries and
+				   modules of these types will be rejected by
+				   Linux.  */
+#define EM_MIPS_RS3_LE	10	/* MIPS R3000 little-endian */
+#define EM_MIPS_RS4_BE	10	/* MIPS R4000 big-endian */
+
+#define EM_PARISC	15	/* HPPA */
+#define EM_SPARC32PLUS	18	/* Sun's "v8plus" */
+#define EM_PPC		20	/* PowerPC */
+#define EM_PPC64	21	 /* PowerPC64 */
+#define EM_SPU		23	/* Cell BE SPU */
+#define EM_ARM		40	/* ARM 32 bit */
+#define EM_SH		42	/* SuperH */
+#define EM_SPARCV9	43	/* SPARC v9 64-bit */
+#define EM_H8_300	46	/* Renesas H8/300 */
+#define EM_IA_64	50	/* HP/Intel IA-64 */
+#define EM_X86_64	62	/* AMD x86-64 */
+#define EM_S390		22	/* IBM S/390 */
+#define EM_CRIS		76	/* Axis Communications 32-bit embedded processor */
+#define EM_M32R		88	/* Renesas M32R */
+#define EM_MN10300	89	/* Panasonic/MEI MN10300, AM33 */
+#define EM_OPENRISC     92     /* OpenRISC 32-bit embedded processor */
+#define EM_ARCOMPACT	93	/* ARCompact processor */
+#define EM_XTENSA	94	/* Tensilica Xtensa Architecture */
+#define EM_BLACKFIN     106     /* ADI Blackfin Processor */
+#define EM_UNICORE	110	/* UniCore-32 */
+#define EM_ALTERA_NIOS2	113	/* Altera Nios II soft-core processor */
+#define EM_TI_C6000	140	/* TI C6X DSPs */
+#define EM_HEXAGON	164	/* QUALCOMM Hexagon */
+#define EM_NDS32	167	/* Andes Technology compact code size
+				   embedded RISC processor family */
+#define EM_AARCH64	183	/* ARM 64 bit */
+#define EM_TILEPRO	188	/* Tilera TILEPro */
+#define EM_MICROBLAZE	189	/* Xilinx MicroBlaze */
+#define EM_TILEGX	191	/* Tilera TILE-Gx */
+#define EM_ARCV2	195	/* ARCv2 Cores */
+#define EM_RISCV	243	/* RISC-V */
+#define EM_BPF		247	/* Linux BPF - in-kernel virtual machine */
+#define EM_CSKY		252	/* C-SKY */
+#define EM_FRV		0x5441	/* Fujitsu FR-V */
+
+/*
+ * This is an interim value that we will use until the committee comes
+ * up with a final number.
+ */
+#define EM_ALPHA	0x9026
+
+/* Bogus old m32r magic number, used by old tools. */
+#define EM_CYGNUS_M32R	0x9041
+/* This is the old interim value for S/390 architecture */
+#define EM_S390_OLD	0xA390
+/* Also Panasonic/MEI MN10300, AM33 */
+#define EM_CYGNUS_MN10300 0xbeef
+
+
+#endif /* _LINUX_ELF_EM_H */
diff --git a/include/uapi/linux/fib_rules.h b/include/uapi/linux/fib_rules.h
new file mode 100644
index 0000000..232df14
--- /dev/null
+++ b/include/uapi/linux/fib_rules.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_FIB_RULES_H
+#define __LINUX_FIB_RULES_H
+
+#include <linux/types.h>
+#include <linux/rtnetlink.h>
+
+/* rule is permanent, and cannot be deleted */
+#define FIB_RULE_PERMANENT	0x00000001
+#define FIB_RULE_INVERT		0x00000002
+#define FIB_RULE_UNRESOLVED	0x00000004
+#define FIB_RULE_IIF_DETACHED	0x00000008
+#define FIB_RULE_DEV_DETACHED	FIB_RULE_IIF_DETACHED
+#define FIB_RULE_OIF_DETACHED	0x00000010
+
+/* try to find source address in routing lookups */
+#define FIB_RULE_FIND_SADDR	0x00010000
+
+struct fib_rule_hdr {
+	__u8		family;
+	__u8		dst_len;
+	__u8		src_len;
+	__u8		tos;
+
+	__u8		table;
+	__u8		res1;   /* reserved */
+	__u8		res2;	/* reserved */
+	__u8		action;
+
+	__u32		flags;
+};
+
+struct fib_rule_uid_range {
+	__u32		start;
+	__u32		end;
+};
+
+struct fib_rule_port_range {
+	__u16		start;
+	__u16		end;
+};
+
+enum {
+	FRA_UNSPEC,
+	FRA_DST,	/* destination address */
+	FRA_SRC,	/* source address */
+	FRA_IIFNAME,	/* interface name */
+#define FRA_IFNAME	FRA_IIFNAME
+	FRA_GOTO,	/* target to jump to (FR_ACT_GOTO) */
+	FRA_UNUSED2,
+	FRA_PRIORITY,	/* priority/preference */
+	FRA_UNUSED3,
+	FRA_UNUSED4,
+	FRA_UNUSED5,
+	FRA_FWMARK,	/* mark */
+	FRA_FLOW,	/* flow/class id */
+	FRA_TUN_ID,
+	FRA_SUPPRESS_IFGROUP,
+	FRA_SUPPRESS_PREFIXLEN,
+	FRA_TABLE,	/* Extended table id */
+	FRA_FWMASK,	/* mask for netfilter mark */
+	FRA_OIFNAME,
+	FRA_PAD,
+	FRA_L3MDEV,	/* iif or oif is l3mdev goto its table */
+	FRA_UID_RANGE,	/* UID range */
+	FRA_PROTOCOL,   /* Originator of the rule */
+	FRA_IP_PROTO,	/* ip proto */
+	FRA_SPORT_RANGE, /* sport */
+	FRA_DPORT_RANGE, /* dport */
+	__FRA_MAX
+};
+
+#define FRA_MAX (__FRA_MAX - 1)
+
+enum {
+	FR_ACT_UNSPEC,
+	FR_ACT_TO_TBL,		/* Pass to fixed table */
+	FR_ACT_GOTO,		/* Jump to another rule */
+	FR_ACT_NOP,		/* No operation */
+	FR_ACT_RES3,
+	FR_ACT_RES4,
+	FR_ACT_BLACKHOLE,	/* Drop without notification */
+	FR_ACT_UNREACHABLE,	/* Drop with ENETUNREACH */
+	FR_ACT_PROHIBIT,	/* Drop with EACCES */
+	__FR_ACT_MAX,
+};
+
+#define FR_ACT_MAX (__FR_ACT_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/filter.h b/include/uapi/linux/filter.h
new file mode 100644
index 0000000..eaef459
--- /dev/null
+++ b/include/uapi/linux/filter.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Linux Socket Filter Data Structures
+ */
+
+#ifndef __LINUX_FILTER_H__
+#define __LINUX_FILTER_H__
+
+
+#include <linux/types.h>
+#include <linux/bpf_common.h>
+
+/*
+ * Current version of the filter code architecture.
+ */
+#define BPF_MAJOR_VERSION 1
+#define BPF_MINOR_VERSION 1
+
+/*
+ *	Try and keep these values and structures similar to BSD, especially
+ *	the BPF code definitions which need to match so you can share filters
+ */
+ 
+struct sock_filter {	/* Filter block */
+	__u16	code;   /* Actual filter code */
+	__u8	jt;	/* Jump true */
+	__u8	jf;	/* Jump false */
+	__u32	k;      /* Generic multiuse field */
+};
+
+struct sock_fprog {	/* Required for SO_ATTACH_FILTER. */
+	unsigned short		len;	/* Number of filter blocks */
+	struct sock_filter *filter;
+};
+
+/* ret - BPF_K and BPF_X also apply */
+#define BPF_RVAL(code)  ((code) & 0x18)
+#define         BPF_A           0x10
+
+/* misc */
+#define BPF_MISCOP(code) ((code) & 0xf8)
+#define         BPF_TAX         0x00
+#define         BPF_TXA         0x80
+
+/*
+ * Macros for filter block array initializers.
+ */
+#ifndef BPF_STMT
+#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k }
+#endif
+#ifndef BPF_JUMP
+#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k }
+#endif
+
+/*
+ * Number of scratch memory words for: BPF_ST and BPF_STX
+ */
+#define BPF_MEMWORDS 16
+
+/* RATIONALE. Negative offsets are invalid in BPF.
+   We use them to reference ancillary data.
+   Unlike introduction new instructions, it does not break
+   existing compilers/optimizers.
+ */
+#define SKF_AD_OFF    (-0x1000)
+#define SKF_AD_PROTOCOL 0
+#define SKF_AD_PKTTYPE 	4
+#define SKF_AD_IFINDEX 	8
+#define SKF_AD_NLATTR	12
+#define SKF_AD_NLATTR_NEST	16
+#define SKF_AD_MARK 	20
+#define SKF_AD_QUEUE	24
+#define SKF_AD_HATYPE	28
+#define SKF_AD_RXHASH	32
+#define SKF_AD_CPU	36
+#define SKF_AD_ALU_XOR_X	40
+#define SKF_AD_VLAN_TAG	44
+#define SKF_AD_VLAN_TAG_PRESENT 48
+#define SKF_AD_PAY_OFFSET	52
+#define SKF_AD_RANDOM	56
+#define SKF_AD_VLAN_TPID	60
+#define SKF_AD_MAX	64
+
+#define SKF_NET_OFF	(-0x100000)
+#define SKF_LL_OFF	(-0x200000)
+
+#define BPF_NET_OFF	SKF_NET_OFF
+#define BPF_LL_OFF	SKF_LL_OFF
+
+#endif /* __LINUX_FILTER_H__ */
diff --git a/include/uapi/linux/fou.h b/include/uapi/linux/fou.h
new file mode 100644
index 0000000..9f91511
--- /dev/null
+++ b/include/uapi/linux/fou.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* fou.h - FOU Interface */
+
+#ifndef _LINUX_FOU_H
+#define _LINUX_FOU_H
+
+/* NETLINK_GENERIC related info
+ */
+#define FOU_GENL_NAME		"fou"
+#define FOU_GENL_VERSION	0x1
+
+enum {
+	FOU_ATTR_UNSPEC,
+	FOU_ATTR_PORT,				/* u16 */
+	FOU_ATTR_AF,				/* u8 */
+	FOU_ATTR_IPPROTO,			/* u8 */
+	FOU_ATTR_TYPE,				/* u8 */
+	FOU_ATTR_REMCSUM_NOPARTIAL,		/* flag */
+	FOU_ATTR_LOCAL_V4,			/* u32 */
+	FOU_ATTR_LOCAL_V6,			/* in6_addr */
+	FOU_ATTR_PEER_V4,			/* u32 */
+	FOU_ATTR_PEER_V6,			/* in6_addr */
+	FOU_ATTR_PEER_PORT,			/* u16 */
+	FOU_ATTR_IFINDEX,			/* s32 */
+
+	__FOU_ATTR_MAX,
+};
+
+#define FOU_ATTR_MAX		(__FOU_ATTR_MAX - 1)
+
+enum {
+	FOU_CMD_UNSPEC,
+	FOU_CMD_ADD,
+	FOU_CMD_DEL,
+	FOU_CMD_GET,
+
+	__FOU_CMD_MAX,
+};
+
+enum {
+	FOU_ENCAP_UNSPEC,
+	FOU_ENCAP_DIRECT,
+	FOU_ENCAP_GUE,
+};
+
+#define FOU_CMD_MAX	(__FOU_CMD_MAX - 1)
+
+#endif /* _LINUX_FOU_H */
diff --git a/include/uapi/linux/gen_stats.h b/include/uapi/linux/gen_stats.h
new file mode 100644
index 0000000..065408e
--- /dev/null
+++ b/include/uapi/linux/gen_stats.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_GEN_STATS_H
+#define __LINUX_GEN_STATS_H
+
+#include <linux/types.h>
+
+enum {
+	TCA_STATS_UNSPEC,
+	TCA_STATS_BASIC,
+	TCA_STATS_RATE_EST,
+	TCA_STATS_QUEUE,
+	TCA_STATS_APP,
+	TCA_STATS_RATE_EST64,
+	TCA_STATS_PAD,
+	TCA_STATS_BASIC_HW,
+	__TCA_STATS_MAX,
+};
+#define TCA_STATS_MAX (__TCA_STATS_MAX - 1)
+
+/**
+ * struct gnet_stats_basic - byte/packet throughput statistics
+ * @bytes: number of seen bytes
+ * @packets: number of seen packets
+ */
+struct gnet_stats_basic {
+	__u64	bytes;
+	__u32	packets;
+};
+struct gnet_stats_basic_packed {
+	__u64	bytes;
+	__u32	packets;
+} __attribute__ ((packed));
+
+/**
+ * struct gnet_stats_rate_est - rate estimator
+ * @bps: current byte rate
+ * @pps: current packet rate
+ */
+struct gnet_stats_rate_est {
+	__u32	bps;
+	__u32	pps;
+};
+
+/**
+ * struct gnet_stats_rate_est64 - rate estimator
+ * @bps: current byte rate
+ * @pps: current packet rate
+ */
+struct gnet_stats_rate_est64 {
+	__u64	bps;
+	__u64	pps;
+};
+
+/**
+ * struct gnet_stats_queue - queuing statistics
+ * @qlen: queue length
+ * @backlog: backlog size of queue
+ * @drops: number of dropped packets
+ * @requeues: number of requeues
+ * @overlimits: number of enqueues over the limit
+ */
+struct gnet_stats_queue {
+	__u32	qlen;
+	__u32	backlog;
+	__u32	drops;
+	__u32	requeues;
+	__u32	overlimits;
+};
+
+/**
+ * struct gnet_estimator - rate estimator configuration
+ * @interval: sampling period
+ * @ewma_log: the log of measurement window weight
+ */
+struct gnet_estimator {
+	signed char	interval;
+	unsigned char	ewma_log;
+};
+
+
+#endif /* __LINUX_GEN_STATS_H */
diff --git a/include/uapi/linux/genetlink.h b/include/uapi/linux/genetlink.h
new file mode 100644
index 0000000..1317119
--- /dev/null
+++ b/include/uapi/linux/genetlink.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_GENERIC_NETLINK_H
+#define __LINUX_GENERIC_NETLINK_H
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+#define GENL_NAMSIZ	16	/* length of family name */
+
+#define GENL_MIN_ID	NLMSG_MIN_TYPE
+#define GENL_MAX_ID	1023
+
+struct genlmsghdr {
+	__u8	cmd;
+	__u8	version;
+	__u16	reserved;
+};
+
+#define GENL_HDRLEN	NLMSG_ALIGN(sizeof(struct genlmsghdr))
+
+#define GENL_ADMIN_PERM		0x01
+#define GENL_CMD_CAP_DO		0x02
+#define GENL_CMD_CAP_DUMP	0x04
+#define GENL_CMD_CAP_HASPOL	0x08
+#define GENL_UNS_ADMIN_PERM	0x10
+
+/*
+ * List of reserved static generic netlink identifiers:
+ */
+#define GENL_ID_CTRL		NLMSG_MIN_TYPE
+#define GENL_ID_VFS_DQUOT	(NLMSG_MIN_TYPE + 1)
+#define GENL_ID_PMCRAID		(NLMSG_MIN_TYPE + 2)
+/* must be last reserved + 1 */
+#define GENL_START_ALLOC	(NLMSG_MIN_TYPE + 3)
+
+/**************************************************************************
+ * Controller
+ **************************************************************************/
+
+enum {
+	CTRL_CMD_UNSPEC,
+	CTRL_CMD_NEWFAMILY,
+	CTRL_CMD_DELFAMILY,
+	CTRL_CMD_GETFAMILY,
+	CTRL_CMD_NEWOPS,
+	CTRL_CMD_DELOPS,
+	CTRL_CMD_GETOPS,
+	CTRL_CMD_NEWMCAST_GRP,
+	CTRL_CMD_DELMCAST_GRP,
+	CTRL_CMD_GETMCAST_GRP, /* unused */
+	__CTRL_CMD_MAX,
+};
+
+#define CTRL_CMD_MAX (__CTRL_CMD_MAX - 1)
+
+enum {
+	CTRL_ATTR_UNSPEC,
+	CTRL_ATTR_FAMILY_ID,
+	CTRL_ATTR_FAMILY_NAME,
+	CTRL_ATTR_VERSION,
+	CTRL_ATTR_HDRSIZE,
+	CTRL_ATTR_MAXATTR,
+	CTRL_ATTR_OPS,
+	CTRL_ATTR_MCAST_GROUPS,
+	__CTRL_ATTR_MAX,
+};
+
+#define CTRL_ATTR_MAX (__CTRL_ATTR_MAX - 1)
+
+enum {
+	CTRL_ATTR_OP_UNSPEC,
+	CTRL_ATTR_OP_ID,
+	CTRL_ATTR_OP_FLAGS,
+	__CTRL_ATTR_OP_MAX,
+};
+
+#define CTRL_ATTR_OP_MAX (__CTRL_ATTR_OP_MAX - 1)
+
+enum {
+	CTRL_ATTR_MCAST_GRP_UNSPEC,
+	CTRL_ATTR_MCAST_GRP_NAME,
+	CTRL_ATTR_MCAST_GRP_ID,
+	__CTRL_ATTR_MCAST_GRP_MAX,
+};
+
+#define CTRL_ATTR_MCAST_GRP_MAX (__CTRL_ATTR_MCAST_GRP_MAX - 1)
+
+
+#endif /* __LINUX_GENERIC_NETLINK_H */
diff --git a/include/uapi/linux/hdlc/ioctl.h b/include/uapi/linux/hdlc/ioctl.h
new file mode 100644
index 0000000..0fe4238
--- /dev/null
+++ b/include/uapi/linux/hdlc/ioctl.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __HDLC_IOCTL_H__
+#define __HDLC_IOCTL_H__
+
+
+#define GENERIC_HDLC_VERSION 4	/* For synchronization with sethdlc utility */
+
+#define CLOCK_DEFAULT   0	/* Default setting */
+#define CLOCK_EXT	1	/* External TX and RX clock - DTE */
+#define CLOCK_INT	2	/* Internal TX and RX clock - DCE */
+#define CLOCK_TXINT	3	/* Internal TX and external RX clock */
+#define CLOCK_TXFROMRX	4	/* TX clock derived from external RX clock */
+
+
+#define ENCODING_DEFAULT	0 /* Default setting */
+#define ENCODING_NRZ		1
+#define ENCODING_NRZI		2
+#define ENCODING_FM_MARK	3
+#define ENCODING_FM_SPACE	4
+#define ENCODING_MANCHESTER	5
+
+
+#define PARITY_DEFAULT		0 /* Default setting */
+#define PARITY_NONE		1 /* No parity */
+#define PARITY_CRC16_PR0	2 /* CRC16, initial value 0x0000 */
+#define PARITY_CRC16_PR1	3 /* CRC16, initial value 0xFFFF */
+#define PARITY_CRC16_PR0_CCITT	4 /* CRC16, initial 0x0000, ITU-T version */
+#define PARITY_CRC16_PR1_CCITT	5 /* CRC16, initial 0xFFFF, ITU-T version */
+#define PARITY_CRC32_PR0_CCITT	6 /* CRC32, initial value 0x00000000 */
+#define PARITY_CRC32_PR1_CCITT	7 /* CRC32, initial value 0xFFFFFFFF */
+
+#define LMI_DEFAULT		0 /* Default setting */
+#define LMI_NONE		1 /* No LMI, all PVCs are static */
+#define LMI_ANSI		2 /* ANSI Annex D */
+#define LMI_CCITT		3 /* ITU-T Annex A */
+#define LMI_CISCO		4 /* The "original" LMI, aka Gang of Four */
+
+#ifndef __ASSEMBLY__
+
+typedef struct {
+	unsigned int clock_rate; /* bits per second */
+	unsigned int clock_type; /* internal, external, TX-internal etc. */
+	unsigned short loopback;
+} sync_serial_settings;          /* V.35, V.24, X.21 */
+
+typedef struct {
+	unsigned int clock_rate; /* bits per second */
+	unsigned int clock_type; /* internal, external, TX-internal etc. */
+	unsigned short loopback;
+	unsigned int slot_map;
+} te1_settings;                  /* T1, E1 */
+
+typedef struct {
+	unsigned short encoding;
+	unsigned short parity;
+} raw_hdlc_proto;
+
+typedef struct {
+	unsigned int t391;
+	unsigned int t392;
+	unsigned int n391;
+	unsigned int n392;
+	unsigned int n393;
+	unsigned short lmi;
+	unsigned short dce; /* 1 for DCE (network side) operation */
+} fr_proto;
+
+typedef struct {
+	unsigned int dlci;
+} fr_proto_pvc;          /* for creating/deleting FR PVCs */
+
+typedef struct {
+	unsigned int dlci;
+	char master[IFNAMSIZ];	/* Name of master FRAD device */
+}fr_proto_pvc_info;		/* for returning PVC information only */
+
+typedef struct {
+    unsigned int interval;
+    unsigned int timeout;
+} cisco_proto;
+
+/* PPP doesn't need any info now - supply length = 0 to ioctl */
+
+#endif /* __ASSEMBLY__ */
+#endif /* __HDLC_IOCTL_H__ */
diff --git a/include/uapi/linux/icmpv6.h b/include/uapi/linux/icmpv6.h
new file mode 100644
index 0000000..1dc7cc6
--- /dev/null
+++ b/include/uapi/linux/icmpv6.h
@@ -0,0 +1,173 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_ICMPV6_H
+#define _LINUX_ICMPV6_H
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+struct icmp6hdr {
+
+	__u8		icmp6_type;
+	__u8		icmp6_code;
+	__sum16		icmp6_cksum;
+
+
+	union {
+		__be32			un_data32[1];
+		__be16			un_data16[2];
+		__u8			un_data8[4];
+
+		struct icmpv6_echo {
+			__be16		identifier;
+			__be16		sequence;
+		} u_echo;
+
+                struct icmpv6_nd_advt {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+                        __u32		reserved:5,
+                        		override:1,
+                        		solicited:1,
+                        		router:1,
+					reserved2:24;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+                        __u32		router:1,
+					solicited:1,
+                        		override:1,
+                        		reserved:29;
+#else
+#error	"Please fix <asm/byteorder.h>"
+#endif						
+                } u_nd_advt;
+
+                struct icmpv6_nd_ra {
+			__u8		hop_limit;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+			__u8		reserved:3,
+					router_pref:2,
+					home_agent:1,
+					other:1,
+					managed:1;
+
+#elif defined(__BIG_ENDIAN_BITFIELD)
+			__u8		managed:1,
+					other:1,
+					home_agent:1,
+					router_pref:2,
+					reserved:3;
+#else
+#error	"Please fix <asm/byteorder.h>"
+#endif
+			__be16		rt_lifetime;
+                } u_nd_ra;
+
+	} icmp6_dataun;
+
+#define icmp6_identifier	icmp6_dataun.u_echo.identifier
+#define icmp6_sequence		icmp6_dataun.u_echo.sequence
+#define icmp6_pointer		icmp6_dataun.un_data32[0]
+#define icmp6_mtu		icmp6_dataun.un_data32[0]
+#define icmp6_unused		icmp6_dataun.un_data32[0]
+#define icmp6_maxdelay		icmp6_dataun.un_data16[0]
+#define icmp6_router		icmp6_dataun.u_nd_advt.router
+#define icmp6_solicited		icmp6_dataun.u_nd_advt.solicited
+#define icmp6_override		icmp6_dataun.u_nd_advt.override
+#define icmp6_ndiscreserved	icmp6_dataun.u_nd_advt.reserved
+#define icmp6_hop_limit		icmp6_dataun.u_nd_ra.hop_limit
+#define icmp6_addrconf_managed	icmp6_dataun.u_nd_ra.managed
+#define icmp6_addrconf_other	icmp6_dataun.u_nd_ra.other
+#define icmp6_rt_lifetime	icmp6_dataun.u_nd_ra.rt_lifetime
+#define icmp6_router_pref	icmp6_dataun.u_nd_ra.router_pref
+};
+
+
+#define ICMPV6_ROUTER_PREF_LOW		0x3
+#define ICMPV6_ROUTER_PREF_MEDIUM	0x0
+#define ICMPV6_ROUTER_PREF_HIGH		0x1
+#define ICMPV6_ROUTER_PREF_INVALID	0x2
+
+#define ICMPV6_DEST_UNREACH		1
+#define ICMPV6_PKT_TOOBIG		2
+#define ICMPV6_TIME_EXCEED		3
+#define ICMPV6_PARAMPROB		4
+
+#define ICMPV6_ERRMSG_MAX       127
+
+#define ICMPV6_INFOMSG_MASK		0x80
+
+#define ICMPV6_ECHO_REQUEST		128
+#define ICMPV6_ECHO_REPLY		129
+#define ICMPV6_MGM_QUERY		130
+#define ICMPV6_MGM_REPORT       	131
+#define ICMPV6_MGM_REDUCTION    	132
+
+#define ICMPV6_NI_QUERY			139
+#define ICMPV6_NI_REPLY			140
+
+#define ICMPV6_MLD2_REPORT		143
+
+#define ICMPV6_DHAAD_REQUEST		144
+#define ICMPV6_DHAAD_REPLY		145
+#define ICMPV6_MOBILE_PREFIX_SOL	146
+#define ICMPV6_MOBILE_PREFIX_ADV	147
+
+#define ICMPV6_MRDISC_ADV		151
+
+#define ICMPV6_MSG_MAX          255
+
+/*
+ *	Codes for Destination Unreachable
+ */
+#define ICMPV6_NOROUTE			0
+#define ICMPV6_ADM_PROHIBITED		1
+#define ICMPV6_NOT_NEIGHBOUR		2
+#define ICMPV6_ADDR_UNREACH		3
+#define ICMPV6_PORT_UNREACH		4
+#define ICMPV6_POLICY_FAIL		5
+#define ICMPV6_REJECT_ROUTE		6
+
+/*
+ *	Codes for Time Exceeded
+ */
+#define ICMPV6_EXC_HOPLIMIT		0
+#define ICMPV6_EXC_FRAGTIME		1
+
+/*
+ *	Codes for Parameter Problem
+ */
+#define ICMPV6_HDR_FIELD		0
+#define ICMPV6_UNK_NEXTHDR		1
+#define ICMPV6_UNK_OPTION		2
+
+/*
+ *	constants for (set|get)sockopt
+ */
+
+#define ICMPV6_FILTER			1
+
+/*
+ *	ICMPV6 filter
+ */
+
+#define ICMPV6_FILTER_BLOCK		1
+#define ICMPV6_FILTER_PASS		2
+#define ICMPV6_FILTER_BLOCKOTHERS	3
+#define ICMPV6_FILTER_PASSONLY		4
+
+struct icmp6_filter {
+	__u32		data[8];
+};
+
+/*
+ *	Definitions for MLDv2
+ */
+#define MLD2_MODE_IS_INCLUDE	1
+#define MLD2_MODE_IS_EXCLUDE	2
+#define MLD2_CHANGE_TO_INCLUDE	3
+#define MLD2_CHANGE_TO_EXCLUDE	4
+#define MLD2_ALLOW_NEW_SOURCES	5
+#define MLD2_BLOCK_OLD_SOURCES	6
+
+#define MLD2_ALL_MCR_INIT { { { 0xff,0x02,0,0,0,0,0,0,0,0,0,0,0,0,0,0x16 } } }
+
+
+#endif /* _LINUX_ICMPV6_H */
diff --git a/include/uapi/linux/if.h b/include/uapi/linux/if.h
new file mode 100644
index 0000000..495cdd2
--- /dev/null
+++ b/include/uapi/linux/if.h
@@ -0,0 +1,293 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Global definitions for the INET interface module.
+ *
+ * Version:	@(#)if.h	1.0.2	04/18/93
+ *
+ * Authors:	Original taken from Berkeley UNIX 4.3, (c) UCB 1982-1988
+ *		Ross Biro
+ *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ */
+#ifndef _LINUX_IF_H
+#define _LINUX_IF_H
+
+#include <linux/libc-compat.h>          /* for compatibility with glibc */
+#include <linux/types.h>		/* for "__kernel_caddr_t" et al	*/
+#include <linux/socket.h>		/* for "struct sockaddr" et al	*/
+		/* for "__user" et al           */
+
+#include <sys/socket.h>			/* for struct sockaddr.		*/
+
+#if __UAPI_DEF_IF_IFNAMSIZ
+#define	IFNAMSIZ	16
+#endif /* __UAPI_DEF_IF_IFNAMSIZ */
+#define	IFALIASZ	256
+#include <linux/hdlc/ioctl.h>
+
+/* For glibc compatibility. An empty enum does not compile. */
+#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO != 0 || \
+    __UAPI_DEF_IF_NET_DEVICE_FLAGS != 0
+/**
+ * enum net_device_flags - &struct net_device flags
+ *
+ * These are the &struct net_device flags, they can be set by drivers, the
+ * kernel and some can be triggered by userspace. Userspace can query and
+ * set these flags using userspace utilities but there is also a sysfs
+ * entry available for all dev flags which can be queried and set. These flags
+ * are shared for all types of net_devices. The sysfs entries are available
+ * via /sys/class/net/<dev>/flags. Flags which can be toggled through sysfs
+ * are annotated below, note that only a few flags can be toggled and some
+ * other flags are always preserved from the original net_device flags
+ * even if you try to set them via sysfs. Flags which are always preserved
+ * are kept under the flag grouping @IFF_VOLATILE. Flags which are __volatile__
+ * are annotated below as such.
+ *
+ * You should have a pretty good reason to be extending these flags.
+ *
+ * @IFF_UP: interface is up. Can be toggled through sysfs.
+ * @IFF_BROADCAST: broadcast address valid. Volatile.
+ * @IFF_DEBUG: turn on debugging. Can be toggled through sysfs.
+ * @IFF_LOOPBACK: is a loopback net. Volatile.
+ * @IFF_POINTOPOINT: interface is has p-p link. Volatile.
+ * @IFF_NOTRAILERS: avoid use of trailers. Can be toggled through sysfs.
+ *	Volatile.
+ * @IFF_RUNNING: interface RFC2863 OPER_UP. Volatile.
+ * @IFF_NOARP: no ARP protocol. Can be toggled through sysfs. Volatile.
+ * @IFF_PROMISC: receive all packets. Can be toggled through sysfs.
+ * @IFF_ALLMULTI: receive all multicast packets. Can be toggled through
+ *	sysfs.
+ * @IFF_MASTER: master of a load balancer. Volatile.
+ * @IFF_SLAVE: slave of a load balancer. Volatile.
+ * @IFF_MULTICAST: Supports multicast. Can be toggled through sysfs.
+ * @IFF_PORTSEL: can set media type. Can be toggled through sysfs.
+ * @IFF_AUTOMEDIA: auto media select active. Can be toggled through sysfs.
+ * @IFF_DYNAMIC: dialup device with changing addresses. Can be toggled
+ *	through sysfs.
+ * @IFF_LOWER_UP: driver signals L1 up. Volatile.
+ * @IFF_DORMANT: driver signals dormant. Volatile.
+ * @IFF_ECHO: echo sent packets. Volatile.
+ */
+enum net_device_flags {
+/* for compatibility with glibc net/if.h */
+#if __UAPI_DEF_IF_NET_DEVICE_FLAGS
+	IFF_UP				= 1<<0,  /* sysfs */
+	IFF_BROADCAST			= 1<<1,  /* __volatile__ */
+	IFF_DEBUG			= 1<<2,  /* sysfs */
+	IFF_LOOPBACK			= 1<<3,  /* __volatile__ */
+	IFF_POINTOPOINT			= 1<<4,  /* __volatile__ */
+	IFF_NOTRAILERS			= 1<<5,  /* sysfs */
+	IFF_RUNNING			= 1<<6,  /* __volatile__ */
+	IFF_NOARP			= 1<<7,  /* sysfs */
+	IFF_PROMISC			= 1<<8,  /* sysfs */
+	IFF_ALLMULTI			= 1<<9,  /* sysfs */
+	IFF_MASTER			= 1<<10, /* __volatile__ */
+	IFF_SLAVE			= 1<<11, /* __volatile__ */
+	IFF_MULTICAST			= 1<<12, /* sysfs */
+	IFF_PORTSEL			= 1<<13, /* sysfs */
+	IFF_AUTOMEDIA			= 1<<14, /* sysfs */
+	IFF_DYNAMIC			= 1<<15, /* sysfs */
+#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS */
+#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO
+	IFF_LOWER_UP			= 1<<16, /* __volatile__ */
+	IFF_DORMANT			= 1<<17, /* __volatile__ */
+	IFF_ECHO			= 1<<18, /* __volatile__ */
+#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */
+};
+#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO != 0 || __UAPI_DEF_IF_NET_DEVICE_FLAGS != 0 */
+
+/* for compatibility with glibc net/if.h */
+#if __UAPI_DEF_IF_NET_DEVICE_FLAGS
+#define IFF_UP				IFF_UP
+#define IFF_BROADCAST			IFF_BROADCAST
+#define IFF_DEBUG			IFF_DEBUG
+#define IFF_LOOPBACK			IFF_LOOPBACK
+#define IFF_POINTOPOINT			IFF_POINTOPOINT
+#define IFF_NOTRAILERS			IFF_NOTRAILERS
+#define IFF_RUNNING			IFF_RUNNING
+#define IFF_NOARP			IFF_NOARP
+#define IFF_PROMISC			IFF_PROMISC
+#define IFF_ALLMULTI			IFF_ALLMULTI
+#define IFF_MASTER			IFF_MASTER
+#define IFF_SLAVE			IFF_SLAVE
+#define IFF_MULTICAST			IFF_MULTICAST
+#define IFF_PORTSEL			IFF_PORTSEL
+#define IFF_AUTOMEDIA			IFF_AUTOMEDIA
+#define IFF_DYNAMIC			IFF_DYNAMIC
+#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS */
+
+#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO
+#define IFF_LOWER_UP			IFF_LOWER_UP
+#define IFF_DORMANT			IFF_DORMANT
+#define IFF_ECHO			IFF_ECHO
+#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */
+
+#define IFF_VOLATILE	(IFF_LOOPBACK|IFF_POINTOPOINT|IFF_BROADCAST|IFF_ECHO|\
+		IFF_MASTER|IFF_SLAVE|IFF_RUNNING|IFF_LOWER_UP|IFF_DORMANT)
+
+#define IF_GET_IFACE	0x0001		/* for querying only */
+#define IF_GET_PROTO	0x0002
+
+/* For definitions see hdlc.h */
+#define IF_IFACE_V35	0x1000		/* V.35 serial interface	*/
+#define IF_IFACE_V24	0x1001		/* V.24 serial interface	*/
+#define IF_IFACE_X21	0x1002		/* X.21 serial interface	*/
+#define IF_IFACE_T1	0x1003		/* T1 telco serial interface	*/
+#define IF_IFACE_E1	0x1004		/* E1 telco serial interface	*/
+#define IF_IFACE_SYNC_SERIAL 0x1005	/* can't be set by software	*/
+#define IF_IFACE_X21D   0x1006          /* X.21 Dual Clocking (FarSite) */
+
+/* For definitions see hdlc.h */
+#define IF_PROTO_HDLC	0x2000		/* raw HDLC protocol		*/
+#define IF_PROTO_PPP	0x2001		/* PPP protocol			*/
+#define IF_PROTO_CISCO	0x2002		/* Cisco HDLC protocol		*/
+#define IF_PROTO_FR	0x2003		/* Frame Relay protocol		*/
+#define IF_PROTO_FR_ADD_PVC 0x2004	/*    Create FR PVC		*/
+#define IF_PROTO_FR_DEL_PVC 0x2005	/*    Delete FR PVC		*/
+#define IF_PROTO_X25	0x2006		/* X.25				*/
+#define IF_PROTO_HDLC_ETH 0x2007	/* raw HDLC, Ethernet emulation	*/
+#define IF_PROTO_FR_ADD_ETH_PVC 0x2008	/*  Create FR Ethernet-bridged PVC */
+#define IF_PROTO_FR_DEL_ETH_PVC 0x2009	/*  Delete FR Ethernet-bridged PVC */
+#define IF_PROTO_FR_PVC	0x200A		/* for reading PVC status	*/
+#define IF_PROTO_FR_ETH_PVC 0x200B
+#define IF_PROTO_RAW    0x200C          /* RAW Socket                   */
+
+/* RFC 2863 operational status */
+enum {
+	IF_OPER_UNKNOWN,
+	IF_OPER_NOTPRESENT,
+	IF_OPER_DOWN,
+	IF_OPER_LOWERLAYERDOWN,
+	IF_OPER_TESTING,
+	IF_OPER_DORMANT,
+	IF_OPER_UP,
+};
+
+/* link modes */
+enum {
+	IF_LINK_MODE_DEFAULT,
+	IF_LINK_MODE_DORMANT,	/* limit upward transition to dormant */
+};
+
+/*
+ *	Device mapping structure. I'd just gone off and designed a 
+ *	beautiful scheme using only loadable modules with arguments
+ *	for driver options and along come the PCMCIA people 8)
+ *
+ *	Ah well. The get() side of this is good for WDSETUP, and it'll
+ *	be handy for debugging things. The set side is fine for now and
+ *	being very small might be worth keeping for clean configuration.
+ */
+
+/* for compatibility with glibc net/if.h */
+#if __UAPI_DEF_IF_IFMAP
+struct ifmap {
+	unsigned long mem_start;
+	unsigned long mem_end;
+	unsigned short base_addr; 
+	unsigned char irq;
+	unsigned char dma;
+	unsigned char port;
+	/* 3 bytes spare */
+};
+#endif /* __UAPI_DEF_IF_IFMAP */
+
+struct if_settings {
+	unsigned int type;	/* Type of physical device or protocol */
+	unsigned int size;	/* Size of the data allocated by the caller */
+	union {
+		/* {atm/eth/dsl}_settings anyone ? */
+		raw_hdlc_proto		*raw_hdlc;
+		cisco_proto		*cisco;
+		fr_proto		*fr;
+		fr_proto_pvc		*fr_pvc;
+		fr_proto_pvc_info	*fr_pvc_info;
+
+		/* interface settings */
+		sync_serial_settings	*sync;
+		te1_settings		*te1;
+	} ifs_ifsu;
+};
+
+/*
+ * Interface request structure used for socket
+ * ioctl's.  All interface ioctl's must have parameter
+ * definitions which begin with ifr_name.  The
+ * remainder may be interface specific.
+ */
+
+/* for compatibility with glibc net/if.h */
+#if __UAPI_DEF_IF_IFREQ
+struct ifreq {
+#define IFHWADDRLEN	6
+	union
+	{
+		char	ifrn_name[IFNAMSIZ];		/* if name, e.g. "en0" */
+	} ifr_ifrn;
+	
+	union {
+		struct	sockaddr ifru_addr;
+		struct	sockaddr ifru_dstaddr;
+		struct	sockaddr ifru_broadaddr;
+		struct	sockaddr ifru_netmask;
+		struct  sockaddr ifru_hwaddr;
+		short	ifru_flags;
+		int	ifru_ivalue;
+		int	ifru_mtu;
+		struct  ifmap ifru_map;
+		char	ifru_slave[IFNAMSIZ];	/* Just fits the size */
+		char	ifru_newname[IFNAMSIZ];
+		void *	ifru_data;
+		struct	if_settings ifru_settings;
+	} ifr_ifru;
+};
+#endif /* __UAPI_DEF_IF_IFREQ */
+
+#define ifr_name	ifr_ifrn.ifrn_name	/* interface name 	*/
+#define ifr_hwaddr	ifr_ifru.ifru_hwaddr	/* MAC address 		*/
+#define	ifr_addr	ifr_ifru.ifru_addr	/* address		*/
+#define	ifr_dstaddr	ifr_ifru.ifru_dstaddr	/* other end of p-p lnk	*/
+#define	ifr_broadaddr	ifr_ifru.ifru_broadaddr	/* broadcast address	*/
+#define	ifr_netmask	ifr_ifru.ifru_netmask	/* interface net mask	*/
+#define	ifr_flags	ifr_ifru.ifru_flags	/* flags		*/
+#define	ifr_metric	ifr_ifru.ifru_ivalue	/* metric		*/
+#define	ifr_mtu		ifr_ifru.ifru_mtu	/* mtu			*/
+#define ifr_map		ifr_ifru.ifru_map	/* device map		*/
+#define ifr_slave	ifr_ifru.ifru_slave	/* slave device		*/
+#define	ifr_data	ifr_ifru.ifru_data	/* for use by interface	*/
+#define ifr_ifindex	ifr_ifru.ifru_ivalue	/* interface index	*/
+#define ifr_bandwidth	ifr_ifru.ifru_ivalue    /* link bandwidth	*/
+#define ifr_qlen	ifr_ifru.ifru_ivalue	/* Queue length 	*/
+#define ifr_newname	ifr_ifru.ifru_newname	/* New name		*/
+#define ifr_settings	ifr_ifru.ifru_settings	/* Device/proto settings*/
+
+/*
+ * Structure used in SIOCGIFCONF request.
+ * Used to retrieve interface configuration
+ * for machine (useful for programs which
+ * must know all networks accessible).
+ */
+
+/* for compatibility with glibc net/if.h */
+#if __UAPI_DEF_IF_IFCONF
+struct ifconf  {
+	int	ifc_len;			/* size of buffer	*/
+	union {
+		char *ifcu_buf;
+		struct ifreq *ifcu_req;
+	} ifc_ifcu;
+};
+#endif /* __UAPI_DEF_IF_IFCONF */
+
+#define	ifc_buf	ifc_ifcu.ifcu_buf		/* buffer address	*/
+#define	ifc_req	ifc_ifcu.ifcu_req		/* array of structures	*/
+
+#endif /* _LINUX_IF_H */
diff --git a/include/uapi/linux/if_addr.h b/include/uapi/linux/if_addr.h
new file mode 100644
index 0000000..c4dd87f
--- /dev/null
+++ b/include/uapi/linux/if_addr.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_IF_ADDR_H
+#define __LINUX_IF_ADDR_H
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+struct ifaddrmsg {
+	__u8		ifa_family;
+	__u8		ifa_prefixlen;	/* The prefix length		*/
+	__u8		ifa_flags;	/* Flags			*/
+	__u8		ifa_scope;	/* Address scope		*/
+	__u32		ifa_index;	/* Link index			*/
+};
+
+/*
+ * Important comment:
+ * IFA_ADDRESS is prefix address, rather than local interface address.
+ * It makes no difference for normally configured broadcast interfaces,
+ * but for point-to-point IFA_ADDRESS is DESTINATION address,
+ * local address is supplied in IFA_LOCAL attribute.
+ *
+ * IFA_FLAGS is a u32 attribute that extends the u8 field ifa_flags.
+ * If present, the value from struct ifaddrmsg will be ignored.
+ */
+enum {
+	IFA_UNSPEC,
+	IFA_ADDRESS,
+	IFA_LOCAL,
+	IFA_LABEL,
+	IFA_BROADCAST,
+	IFA_ANYCAST,
+	IFA_CACHEINFO,
+	IFA_MULTICAST,
+	IFA_FLAGS,
+	IFA_RT_PRIORITY,  /* u32, priority/metric for prefix route */
+	IFA_TARGET_NETNSID,
+	__IFA_MAX,
+};
+
+#define IFA_MAX (__IFA_MAX - 1)
+
+/* ifa_flags */
+#define IFA_F_SECONDARY		0x01
+#define IFA_F_TEMPORARY		IFA_F_SECONDARY
+
+#define	IFA_F_NODAD		0x02
+#define IFA_F_OPTIMISTIC	0x04
+#define IFA_F_DADFAILED		0x08
+#define	IFA_F_HOMEADDRESS	0x10
+#define IFA_F_DEPRECATED	0x20
+#define IFA_F_TENTATIVE		0x40
+#define IFA_F_PERMANENT		0x80
+#define IFA_F_MANAGETEMPADDR	0x100
+#define IFA_F_NOPREFIXROUTE	0x200
+#define IFA_F_MCAUTOJOIN	0x400
+#define IFA_F_STABLE_PRIVACY	0x800
+
+struct ifa_cacheinfo {
+	__u32	ifa_prefered;
+	__u32	ifa_valid;
+	__u32	cstamp; /* created timestamp, hundredths of seconds */
+	__u32	tstamp; /* updated timestamp, hundredths of seconds */
+};
+
+/* backwards compatibility for userspace */
+#define IFA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))))
+#define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg))
+
+#endif
diff --git a/include/uapi/linux/if_addrlabel.h b/include/uapi/linux/if_addrlabel.h
new file mode 100644
index 0000000..d1f5974
--- /dev/null
+++ b/include/uapi/linux/if_addrlabel.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * if_addrlabel.h - netlink interface for address labels
+ *
+ * Copyright (C)2007 USAGI/WIDE Project,  All Rights Reserved.
+ *
+ * Authors:
+ *	YOSHIFUJI Hideaki @ USAGI/WIDE <yoshfuji@linux-ipv6.org>
+ */
+
+#ifndef __LINUX_IF_ADDRLABEL_H
+#define __LINUX_IF_ADDRLABEL_H
+
+#include <linux/types.h>
+
+struct ifaddrlblmsg {
+	__u8		ifal_family;		/* Address family */
+	__u8		__ifal_reserved;	/* Reserved */
+	__u8		ifal_prefixlen;		/* Prefix length */
+	__u8		ifal_flags;		/* Flags */
+	__u32		ifal_index;		/* Link index */
+	__u32		ifal_seq;		/* sequence number */
+};
+
+enum {
+	IFAL_ADDRESS = 1,
+	IFAL_LABEL = 2,
+	__IFAL_MAX
+};
+
+#define IFAL_MAX	(__IFAL_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/if_alg.h b/include/uapi/linux/if_alg.h
new file mode 100644
index 0000000..bc2bcde
--- /dev/null
+++ b/include/uapi/linux/if_alg.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * if_alg: User-space algorithm interface
+ *
+ * Copyright (c) 2010 Herbert Xu <herbert@gondor.apana.org.au>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ */
+
+#ifndef _LINUX_IF_ALG_H
+#define _LINUX_IF_ALG_H
+
+#include <linux/types.h>
+
+struct sockaddr_alg {
+	__u16	salg_family;
+	__u8	salg_type[14];
+	__u32	salg_feat;
+	__u32	salg_mask;
+	__u8	salg_name[64];
+};
+
+struct af_alg_iv {
+	__u32	ivlen;
+	__u8	iv[0];
+};
+
+/* Socket options */
+#define ALG_SET_KEY			1
+#define ALG_SET_IV			2
+#define ALG_SET_OP			3
+#define ALG_SET_AEAD_ASSOCLEN		4
+#define ALG_SET_AEAD_AUTHSIZE		5
+
+/* Operations */
+#define ALG_OP_DECRYPT			0
+#define ALG_OP_ENCRYPT			1
+
+#endif	/* _LINUX_IF_ALG_H */
diff --git a/include/uapi/linux/if_arp.h b/include/uapi/linux/if_arp.h
new file mode 100644
index 0000000..dbfbc22
--- /dev/null
+++ b/include/uapi/linux/if_arp.h
@@ -0,0 +1,164 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Global definitions for the ARP (RFC 826) protocol.
+ *
+ * Version:	@(#)if_arp.h	1.0.1	04/16/93
+ *
+ * Authors:	Original taken from Berkeley UNIX 4.3, (c) UCB 1986-1988
+ *		Portions taken from the KA9Q/NOS (v2.00m PA0GRI) source.
+ *		Ross Biro
+ *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *		Florian La Roche,
+ *		Jonathan Layes <layes@loran.com>
+ *		Arnaldo Carvalho de Melo <acme@conectiva.com.br> ARPHRD_HWX25
+ *
+ *		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.
+ */
+#ifndef _LINUX_IF_ARP_H
+#define _LINUX_IF_ARP_H
+
+#include <linux/netdevice.h>
+
+/* ARP protocol HARDWARE identifiers. */
+#define ARPHRD_NETROM	0		/* from KA9Q: NET/ROM pseudo	*/
+#define ARPHRD_ETHER 	1		/* Ethernet 10Mbps		*/
+#define	ARPHRD_EETHER	2		/* Experimental Ethernet	*/
+#define	ARPHRD_AX25	3		/* AX.25 Level 2		*/
+#define	ARPHRD_PRONET	4		/* PROnet token ring		*/
+#define	ARPHRD_CHAOS	5		/* Chaosnet			*/
+#define	ARPHRD_IEEE802	6		/* IEEE 802.2 Ethernet/TR/TB	*/
+#define	ARPHRD_ARCNET	7		/* ARCnet			*/
+#define	ARPHRD_APPLETLK	8		/* APPLEtalk			*/
+#define ARPHRD_DLCI	15		/* Frame Relay DLCI		*/
+#define ARPHRD_ATM	19		/* ATM 				*/
+#define ARPHRD_METRICOM	23		/* Metricom STRIP (new IANA id)	*/
+#define	ARPHRD_IEEE1394	24		/* IEEE 1394 IPv4 - RFC 2734	*/
+#define ARPHRD_EUI64	27		/* EUI-64                       */
+#define ARPHRD_INFINIBAND 32		/* InfiniBand			*/
+
+/* Dummy types for non ARP hardware */
+#define ARPHRD_SLIP	256
+#define ARPHRD_CSLIP	257
+#define ARPHRD_SLIP6	258
+#define ARPHRD_CSLIP6	259
+#define ARPHRD_RSRVD	260		/* Notional KISS type 		*/
+#define ARPHRD_ADAPT	264
+#define ARPHRD_ROSE	270
+#define ARPHRD_X25	271		/* CCITT X.25			*/
+#define ARPHRD_HWX25	272		/* Boards with X.25 in firmware	*/
+#define ARPHRD_CAN	280		/* Controller Area Network      */
+#define ARPHRD_PPP	512
+#define ARPHRD_CISCO	513		/* Cisco HDLC	 		*/
+#define ARPHRD_HDLC	ARPHRD_CISCO
+#define ARPHRD_LAPB	516		/* LAPB				*/
+#define ARPHRD_DDCMP    517		/* Digital's DDCMP protocol     */
+#define ARPHRD_RAWHDLC	518		/* Raw HDLC			*/
+#define ARPHRD_RAWIP    519		/* Raw IP                       */
+
+#define ARPHRD_TUNNEL	768		/* IPIP tunnel			*/
+#define ARPHRD_TUNNEL6	769		/* IP6IP6 tunnel       		*/
+#define ARPHRD_FRAD	770             /* Frame Relay Access Device    */
+#define ARPHRD_SKIP	771		/* SKIP vif			*/
+#define ARPHRD_LOOPBACK	772		/* Loopback device		*/
+#define ARPHRD_LOCALTLK 773		/* Localtalk device		*/
+#define ARPHRD_FDDI	774		/* Fiber Distributed Data Interface */
+#define ARPHRD_BIF      775             /* AP1000 BIF                   */
+#define ARPHRD_SIT	776		/* sit0 device - IPv6-in-IPv4	*/
+#define ARPHRD_IPDDP	777		/* IP over DDP tunneller	*/
+#define ARPHRD_IPGRE	778		/* GRE over IP			*/
+#define ARPHRD_PIMREG	779		/* PIMSM register interface	*/
+#define ARPHRD_HIPPI	780		/* High Performance Parallel Interface */
+#define ARPHRD_ASH	781		/* Nexus 64Mbps Ash		*/
+#define ARPHRD_ECONET	782		/* Acorn Econet			*/
+#define ARPHRD_IRDA 	783		/* Linux-IrDA			*/
+/* ARP works differently on different FC media .. so  */
+#define ARPHRD_FCPP	784		/* Point to point fibrechannel	*/
+#define ARPHRD_FCAL	785		/* Fibrechannel arbitrated loop */
+#define ARPHRD_FCPL	786		/* Fibrechannel public loop	*/
+#define ARPHRD_FCFABRIC	787		/* Fibrechannel fabric		*/
+	/* 787->799 reserved for fibrechannel media types */
+#define ARPHRD_IEEE802_TR 800		/* Magic type ident for TR	*/
+#define ARPHRD_IEEE80211 801		/* IEEE 802.11			*/
+#define ARPHRD_IEEE80211_PRISM 802	/* IEEE 802.11 + Prism2 header  */
+#define ARPHRD_IEEE80211_RADIOTAP 803	/* IEEE 802.11 + radiotap header */
+#define ARPHRD_IEEE802154	  804
+#define ARPHRD_IEEE802154_MONITOR 805	/* IEEE 802.15.4 network monitor */
+
+#define ARPHRD_PHONET	820		/* PhoNet media type		*/
+#define ARPHRD_PHONET_PIPE 821		/* PhoNet pipe header		*/
+#define ARPHRD_CAIF	822		/* CAIF media type		*/
+#define ARPHRD_IP6GRE	823		/* GRE over IPv6		*/
+#define ARPHRD_NETLINK	824		/* Netlink header		*/
+#define ARPHRD_6LOWPAN	825		/* IPv6 over LoWPAN             */
+#define ARPHRD_VSOCKMON	826		/* Vsock monitor header		*/
+
+#define ARPHRD_VOID	  0xFFFF	/* Void type, nothing is known */
+#define ARPHRD_NONE	  0xFFFE	/* zero header length */
+
+/* ARP protocol opcodes. */
+#define	ARPOP_REQUEST	1		/* ARP request			*/
+#define	ARPOP_REPLY	2		/* ARP reply			*/
+#define	ARPOP_RREQUEST	3		/* RARP request			*/
+#define	ARPOP_RREPLY	4		/* RARP reply			*/
+#define	ARPOP_InREQUEST	8		/* InARP request		*/
+#define	ARPOP_InREPLY	9		/* InARP reply			*/
+#define	ARPOP_NAK	10		/* (ATM)ARP NAK			*/
+
+
+/* ARP ioctl request. */
+struct arpreq {
+	struct sockaddr	arp_pa;		/* protocol address		 */
+	struct sockaddr	arp_ha;		/* hardware address		 */
+	int		arp_flags;	/* flags			 */
+	struct sockaddr arp_netmask;    /* netmask (only for proxy arps) */
+	char		arp_dev[IFNAMSIZ];
+};
+
+struct arpreq_old {
+	struct sockaddr	arp_pa;		/* protocol address		 */
+	struct sockaddr	arp_ha;		/* hardware address		 */
+	int		arp_flags;	/* flags			 */
+	struct sockaddr	arp_netmask;    /* netmask (only for proxy arps) */
+};
+
+/* ARP Flag values. */
+#define ATF_COM		0x02		/* completed entry (ha valid)	*/
+#define	ATF_PERM	0x04		/* permanent entry		*/
+#define	ATF_PUBL	0x08		/* publish entry		*/
+#define	ATF_USETRAILERS	0x10		/* has requested trailers	*/
+#define ATF_NETMASK     0x20            /* want to use a netmask (only
+					   for proxy entries) */
+#define ATF_DONTPUB	0x40		/* don't answer this addresses	*/
+
+/*
+ *	This structure defines an ethernet arp header.
+ */
+
+struct arphdr {
+	__be16		ar_hrd;		/* format of hardware address	*/
+	__be16		ar_pro;		/* format of protocol address	*/
+	unsigned char	ar_hln;		/* length of hardware address	*/
+	unsigned char	ar_pln;		/* length of protocol address	*/
+	__be16		ar_op;		/* ARP opcode (command)		*/
+
+#if 0
+	 /*
+	  *	 Ethernet looks like this : This bit is variable sized however...
+	  */
+	unsigned char		ar_sha[ETH_ALEN];	/* sender hardware address	*/
+	unsigned char		ar_sip[4];		/* sender IP address		*/
+	unsigned char		ar_tha[ETH_ALEN];	/* target hardware address	*/
+	unsigned char		ar_tip[4];		/* target IP address		*/
+#endif
+
+};
+
+
+#endif /* _LINUX_IF_ARP_H */
diff --git a/include/uapi/linux/if_bonding.h b/include/uapi/linux/if_bonding.h
new file mode 100644
index 0000000..790585f
--- /dev/null
+++ b/include/uapi/linux/if_bonding.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: GPL-1.0+ WITH Linux-syscall-note */
+/*
+ * Bond several ethernet interfaces into a Cisco, running 'Etherchannel'.
+ *
+ *
+ * Portions are (c) Copyright 1995 Simon "Guru Aleph-Null" Janes
+ * NCM: Network and Communications Management, Inc.
+ *
+ * BUT, I'm the one who modified it for ethernet, so:
+ * (c) Copyright 1999, Thomas Davis, tadavis@lbl.gov
+ *
+ *	This software may be used and distributed according to the terms
+ *	of the GNU Public License, incorporated herein by reference.
+ *
+ * 2003/03/18 - Amir Noam <amir.noam at intel dot com>
+ *	- Added support for getting slave's speed and duplex via ethtool.
+ *	  Needed for 802.3ad and other future modes.
+ *
+ * 2003/03/18 - Tsippy Mendelson <tsippy.mendelson at intel dot com> and
+ *		Shmulik Hen <shmulik.hen at intel dot com>
+ *	- Enable support of modes that need to use the unique mac address of
+ *	  each slave.
+ *
+ * 2003/03/18 - Tsippy Mendelson <tsippy.mendelson at intel dot com> and
+ *		Amir Noam <amir.noam at intel dot com>
+ *	- Moved driver's private data types to bonding.h
+ *
+ * 2003/03/18 - Amir Noam <amir.noam at intel dot com>,
+ *		Tsippy Mendelson <tsippy.mendelson at intel dot com> and
+ *		Shmulik Hen <shmulik.hen at intel dot com>
+ *	- Added support for IEEE 802.3ad Dynamic link aggregation mode.
+ *
+ * 2003/05/01 - Amir Noam <amir.noam at intel dot com>
+ *	- Added ABI version control to restore compatibility between
+ *	  new/old ifenslave and new/old bonding.
+ *
+ * 2003/12/01 - Shmulik Hen <shmulik.hen at intel dot com>
+ *	- Code cleanup and style changes
+ *
+ * 2005/05/05 - Jason Gabler <jygabler at lbl dot gov>
+ *      - added definitions for various XOR hashing policies
+ */
+
+#ifndef _LINUX_IF_BONDING_H
+#define _LINUX_IF_BONDING_H
+
+#include <linux/if.h>
+#include <linux/types.h>
+#include <linux/if_ether.h>
+
+/* userland - kernel ABI version (2003/05/08) */
+#define BOND_ABI_VERSION 2
+
+/*
+ * We can remove these ioctl definitions in 2.5.  People should use the
+ * SIOC*** versions of them instead
+ */
+#define BOND_ENSLAVE_OLD		(SIOCDEVPRIVATE)
+#define BOND_RELEASE_OLD		(SIOCDEVPRIVATE + 1)
+#define BOND_SETHWADDR_OLD		(SIOCDEVPRIVATE + 2)
+#define BOND_SLAVE_INFO_QUERY_OLD	(SIOCDEVPRIVATE + 11)
+#define BOND_INFO_QUERY_OLD		(SIOCDEVPRIVATE + 12)
+#define BOND_CHANGE_ACTIVE_OLD		(SIOCDEVPRIVATE + 13)
+
+#define BOND_CHECK_MII_STATUS	(SIOCGMIIPHY)
+
+#define BOND_MODE_ROUNDROBIN	0
+#define BOND_MODE_ACTIVEBACKUP	1
+#define BOND_MODE_XOR		2
+#define BOND_MODE_BROADCAST	3
+#define BOND_MODE_8023AD        4
+#define BOND_MODE_TLB           5
+#define BOND_MODE_ALB		6 /* TLB + RLB (receive load balancing) */
+
+/* each slave's link has 4 states */
+#define BOND_LINK_UP    0           /* link is up and running */
+#define BOND_LINK_FAIL  1           /* link has just gone down */
+#define BOND_LINK_DOWN  2           /* link has been down for too long time */
+#define BOND_LINK_BACK  3           /* link is going back */
+
+/* each slave has several states */
+#define BOND_STATE_ACTIVE       0   /* link is active */
+#define BOND_STATE_BACKUP       1   /* link is backup */
+
+#define BOND_DEFAULT_MAX_BONDS  1   /* Default maximum number of devices to support */
+
+#define BOND_DEFAULT_TX_QUEUES 16   /* Default number of tx queues per device */
+
+#define BOND_DEFAULT_RESEND_IGMP	1 /* Default number of IGMP membership reports */
+
+/* hashing types */
+#define BOND_XMIT_POLICY_LAYER2		0 /* layer 2 (MAC only), default */
+#define BOND_XMIT_POLICY_LAYER34	1 /* layer 3+4 (IP ^ (TCP || UDP)) */
+#define BOND_XMIT_POLICY_LAYER23	2 /* layer 2+3 (IP ^ MAC) */
+#define BOND_XMIT_POLICY_ENCAP23	3 /* encapsulated layer 2+3 */
+#define BOND_XMIT_POLICY_ENCAP34	4 /* encapsulated layer 3+4 */
+
+typedef struct ifbond {
+	__s32 bond_mode;
+	__s32 num_slaves;
+	__s32 miimon;
+} ifbond;
+
+typedef struct ifslave {
+	__s32 slave_id; /* Used as an IN param to the BOND_SLAVE_INFO_QUERY ioctl */
+	char slave_name[IFNAMSIZ];
+	__s8 link;
+	__s8 state;
+	__u32  link_failure_count;
+} ifslave;
+
+struct ad_info {
+	__u16 aggregator_id;
+	__u16 ports;
+	__u16 actor_key;
+	__u16 partner_key;
+	__u8 partner_system[ETH_ALEN];
+};
+
+/* Embedded inside LINK_XSTATS_TYPE_BOND */
+enum {
+	BOND_XSTATS_UNSPEC,
+	BOND_XSTATS_3AD,
+	__BOND_XSTATS_MAX
+};
+#define BOND_XSTATS_MAX (__BOND_XSTATS_MAX - 1)
+
+/* Embedded inside BOND_XSTATS_3AD */
+enum {
+	BOND_3AD_STAT_LACPDU_RX,
+	BOND_3AD_STAT_LACPDU_TX,
+	BOND_3AD_STAT_LACPDU_UNKNOWN_RX,
+	BOND_3AD_STAT_LACPDU_ILLEGAL_RX,
+	BOND_3AD_STAT_MARKER_RX,
+	BOND_3AD_STAT_MARKER_TX,
+	BOND_3AD_STAT_MARKER_RESP_RX,
+	BOND_3AD_STAT_MARKER_RESP_TX,
+	BOND_3AD_STAT_MARKER_UNKNOWN_RX,
+	BOND_3AD_STAT_PAD,
+	__BOND_3AD_STAT_MAX
+};
+#define BOND_3AD_STAT_MAX (__BOND_3AD_STAT_MAX - 1)
+
+#endif /* _LINUX_IF_BONDING_H */
+
+/*
+ * Local variables:
+ *  version-control: t
+ *  kept-new-versions: 5
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ *  tab-width: 8
+ * End:
+ */
+
diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
new file mode 100644
index 0000000..31fc51b
--- /dev/null
+++ b/include/uapi/linux/if_bridge.h
@@ -0,0 +1,317 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ *	Linux ethernet bridge
+ *
+ *	Authors:
+ *	Lennert Buytenhek		<buytenh@gnu.org>
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version
+ *	2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _LINUX_IF_BRIDGE_H
+#define _LINUX_IF_BRIDGE_H
+
+#include <linux/types.h>
+#include <linux/if_ether.h>
+#include <linux/in6.h>
+
+#define SYSFS_BRIDGE_ATTR	"bridge"
+#define SYSFS_BRIDGE_FDB	"brforward"
+#define SYSFS_BRIDGE_PORT_SUBDIR "brif"
+#define SYSFS_BRIDGE_PORT_ATTR	"brport"
+#define SYSFS_BRIDGE_PORT_LINK	"bridge"
+
+#define BRCTL_VERSION 1
+
+#define BRCTL_GET_VERSION 0
+#define BRCTL_GET_BRIDGES 1
+#define BRCTL_ADD_BRIDGE 2
+#define BRCTL_DEL_BRIDGE 3
+#define BRCTL_ADD_IF 4
+#define BRCTL_DEL_IF 5
+#define BRCTL_GET_BRIDGE_INFO 6
+#define BRCTL_GET_PORT_LIST 7
+#define BRCTL_SET_BRIDGE_FORWARD_DELAY 8
+#define BRCTL_SET_BRIDGE_HELLO_TIME 9
+#define BRCTL_SET_BRIDGE_MAX_AGE 10
+#define BRCTL_SET_AGEING_TIME 11
+#define BRCTL_SET_GC_INTERVAL 12
+#define BRCTL_GET_PORT_INFO 13
+#define BRCTL_SET_BRIDGE_STP_STATE 14
+#define BRCTL_SET_BRIDGE_PRIORITY 15
+#define BRCTL_SET_PORT_PRIORITY 16
+#define BRCTL_SET_PATH_COST 17
+#define BRCTL_GET_FDB_ENTRIES 18
+
+#define BR_STATE_DISABLED 0
+#define BR_STATE_LISTENING 1
+#define BR_STATE_LEARNING 2
+#define BR_STATE_FORWARDING 3
+#define BR_STATE_BLOCKING 4
+
+struct __bridge_info {
+	__u64 designated_root;
+	__u64 bridge_id;
+	__u32 root_path_cost;
+	__u32 max_age;
+	__u32 hello_time;
+	__u32 forward_delay;
+	__u32 bridge_max_age;
+	__u32 bridge_hello_time;
+	__u32 bridge_forward_delay;
+	__u8 topology_change;
+	__u8 topology_change_detected;
+	__u8 root_port;
+	__u8 stp_enabled;
+	__u32 ageing_time;
+	__u32 gc_interval;
+	__u32 hello_timer_value;
+	__u32 tcn_timer_value;
+	__u32 topology_change_timer_value;
+	__u32 gc_timer_value;
+};
+
+struct __port_info {
+	__u64 designated_root;
+	__u64 designated_bridge;
+	__u16 port_id;
+	__u16 designated_port;
+	__u32 path_cost;
+	__u32 designated_cost;
+	__u8 state;
+	__u8 top_change_ack;
+	__u8 config_pending;
+	__u8 unused0;
+	__u32 message_age_timer_value;
+	__u32 forward_delay_timer_value;
+	__u32 hold_timer_value;
+};
+
+struct __fdb_entry {
+	__u8 mac_addr[ETH_ALEN];
+	__u8 port_no;
+	__u8 is_local;
+	__u32 ageing_timer_value;
+	__u8 port_hi;
+	__u8 pad0;
+	__u16 unused;
+};
+
+/* Bridge Flags */
+#define BRIDGE_FLAGS_MASTER	1	/* Bridge command to/from master */
+#define BRIDGE_FLAGS_SELF	2	/* Bridge command to/from lowerdev */
+
+#define BRIDGE_MODE_VEB		0	/* Default loopback mode */
+#define BRIDGE_MODE_VEPA	1	/* 802.1Qbg defined VEPA mode */
+#define BRIDGE_MODE_UNDEF	0xFFFF  /* mode undefined */
+
+/* Bridge management nested attributes
+ * [IFLA_AF_SPEC] = {
+ *     [IFLA_BRIDGE_FLAGS]
+ *     [IFLA_BRIDGE_MODE]
+ *     [IFLA_BRIDGE_VLAN_INFO]
+ * }
+ */
+enum {
+	IFLA_BRIDGE_FLAGS,
+	IFLA_BRIDGE_MODE,
+	IFLA_BRIDGE_VLAN_INFO,
+	IFLA_BRIDGE_VLAN_TUNNEL_INFO,
+	__IFLA_BRIDGE_MAX,
+};
+#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1)
+
+#define BRIDGE_VLAN_INFO_MASTER	(1<<0)	/* Operate on Bridge device as well */
+#define BRIDGE_VLAN_INFO_PVID	(1<<1)	/* VLAN is PVID, ingress untagged */
+#define BRIDGE_VLAN_INFO_UNTAGGED	(1<<2)	/* VLAN egresses untagged */
+#define BRIDGE_VLAN_INFO_RANGE_BEGIN	(1<<3) /* VLAN is start of vlan range */
+#define BRIDGE_VLAN_INFO_RANGE_END	(1<<4) /* VLAN is end of vlan range */
+#define BRIDGE_VLAN_INFO_BRENTRY	(1<<5) /* Global bridge VLAN entry */
+
+struct bridge_vlan_info {
+	__u16 flags;
+	__u16 vid;
+};
+
+enum {
+	IFLA_BRIDGE_VLAN_TUNNEL_UNSPEC,
+	IFLA_BRIDGE_VLAN_TUNNEL_ID,
+	IFLA_BRIDGE_VLAN_TUNNEL_VID,
+	IFLA_BRIDGE_VLAN_TUNNEL_FLAGS,
+	__IFLA_BRIDGE_VLAN_TUNNEL_MAX,
+};
+
+#define IFLA_BRIDGE_VLAN_TUNNEL_MAX (__IFLA_BRIDGE_VLAN_TUNNEL_MAX - 1)
+
+struct bridge_vlan_xstats {
+	__u64 rx_bytes;
+	__u64 rx_packets;
+	__u64 tx_bytes;
+	__u64 tx_packets;
+	__u16 vid;
+	__u16 flags;
+	__u32 pad2;
+};
+
+/* Bridge multicast database attributes
+ * [MDBA_MDB] = {
+ *     [MDBA_MDB_ENTRY] = {
+ *         [MDBA_MDB_ENTRY_INFO] {
+ *		struct br_mdb_entry
+ *		[MDBA_MDB_EATTR attributes]
+ *         }
+ *     }
+ * }
+ * [MDBA_ROUTER] = {
+ *    [MDBA_ROUTER_PORT] = {
+ *        u32 ifindex
+ *        [MDBA_ROUTER_PATTR attributes]
+ *    }
+ * }
+ */
+enum {
+	MDBA_UNSPEC,
+	MDBA_MDB,
+	MDBA_ROUTER,
+	__MDBA_MAX,
+};
+#define MDBA_MAX (__MDBA_MAX - 1)
+
+enum {
+	MDBA_MDB_UNSPEC,
+	MDBA_MDB_ENTRY,
+	__MDBA_MDB_MAX,
+};
+#define MDBA_MDB_MAX (__MDBA_MDB_MAX - 1)
+
+enum {
+	MDBA_MDB_ENTRY_UNSPEC,
+	MDBA_MDB_ENTRY_INFO,
+	__MDBA_MDB_ENTRY_MAX,
+};
+#define MDBA_MDB_ENTRY_MAX (__MDBA_MDB_ENTRY_MAX - 1)
+
+/* per mdb entry additional attributes */
+enum {
+	MDBA_MDB_EATTR_UNSPEC,
+	MDBA_MDB_EATTR_TIMER,
+	__MDBA_MDB_EATTR_MAX
+};
+#define MDBA_MDB_EATTR_MAX (__MDBA_MDB_EATTR_MAX - 1)
+
+/* multicast router types */
+enum {
+	MDB_RTR_TYPE_DISABLED,
+	MDB_RTR_TYPE_TEMP_QUERY,
+	MDB_RTR_TYPE_PERM,
+	MDB_RTR_TYPE_TEMP
+};
+
+enum {
+	MDBA_ROUTER_UNSPEC,
+	MDBA_ROUTER_PORT,
+	__MDBA_ROUTER_MAX,
+};
+#define MDBA_ROUTER_MAX (__MDBA_ROUTER_MAX - 1)
+
+/* router port attributes */
+enum {
+	MDBA_ROUTER_PATTR_UNSPEC,
+	MDBA_ROUTER_PATTR_TIMER,
+	MDBA_ROUTER_PATTR_TYPE,
+	__MDBA_ROUTER_PATTR_MAX
+};
+#define MDBA_ROUTER_PATTR_MAX (__MDBA_ROUTER_PATTR_MAX - 1)
+
+struct br_port_msg {
+	__u8  family;
+	__u32 ifindex;
+};
+
+struct br_mdb_entry {
+	__u32 ifindex;
+#define MDB_TEMPORARY 0
+#define MDB_PERMANENT 1
+	__u8 state;
+#define MDB_FLAGS_OFFLOAD	(1 << 0)
+#define MDB_FLAGS_FAST_LEAVE	(1 << 1)
+	__u8 flags;
+	__u16 vid;
+	struct {
+		union {
+			__be32	ip4;
+			struct in6_addr ip6;
+		} u;
+		__be16		proto;
+	} addr;
+};
+
+enum {
+	MDBA_SET_ENTRY_UNSPEC,
+	MDBA_SET_ENTRY,
+	__MDBA_SET_ENTRY_MAX,
+};
+#define MDBA_SET_ENTRY_MAX (__MDBA_SET_ENTRY_MAX - 1)
+
+/* Embedded inside LINK_XSTATS_TYPE_BRIDGE */
+enum {
+	BRIDGE_XSTATS_UNSPEC,
+	BRIDGE_XSTATS_VLAN,
+	BRIDGE_XSTATS_MCAST,
+	BRIDGE_XSTATS_PAD,
+	__BRIDGE_XSTATS_MAX
+};
+#define BRIDGE_XSTATS_MAX (__BRIDGE_XSTATS_MAX - 1)
+
+enum {
+	BR_MCAST_DIR_RX,
+	BR_MCAST_DIR_TX,
+	BR_MCAST_DIR_SIZE
+};
+
+/* IGMP/MLD statistics */
+struct br_mcast_stats {
+	__u64 igmp_v1queries[BR_MCAST_DIR_SIZE];
+	__u64 igmp_v2queries[BR_MCAST_DIR_SIZE];
+	__u64 igmp_v3queries[BR_MCAST_DIR_SIZE];
+	__u64 igmp_leaves[BR_MCAST_DIR_SIZE];
+	__u64 igmp_v1reports[BR_MCAST_DIR_SIZE];
+	__u64 igmp_v2reports[BR_MCAST_DIR_SIZE];
+	__u64 igmp_v3reports[BR_MCAST_DIR_SIZE];
+	__u64 igmp_parse_errors;
+
+	__u64 mld_v1queries[BR_MCAST_DIR_SIZE];
+	__u64 mld_v2queries[BR_MCAST_DIR_SIZE];
+	__u64 mld_leaves[BR_MCAST_DIR_SIZE];
+	__u64 mld_v1reports[BR_MCAST_DIR_SIZE];
+	__u64 mld_v2reports[BR_MCAST_DIR_SIZE];
+	__u64 mld_parse_errors;
+
+	__u64 mcast_bytes[BR_MCAST_DIR_SIZE];
+	__u64 mcast_packets[BR_MCAST_DIR_SIZE];
+};
+
+/* bridge boolean options
+ * BR_BOOLOPT_NO_LL_LEARN - disable learning from link-local packets
+ *
+ * IMPORTANT: if adding a new option do not forget to handle
+ *            it in br_boolopt_toggle/get and bridge sysfs
+ */
+enum br_boolopt_id {
+	BR_BOOLOPT_NO_LL_LEARN,
+	BR_BOOLOPT_MAX
+};
+
+/* struct br_boolopt_multi - change multiple bridge boolean options
+ *
+ * @optval: new option values (bit per option)
+ * @optmask: options to change (bit per option)
+ */
+struct br_boolopt_multi {
+	__u32 optval;
+	__u32 optmask;
+};
+#endif /* _LINUX_IF_BRIDGE_H */
diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
new file mode 100644
index 0000000..728c42d
--- /dev/null
+++ b/include/uapi/linux/if_ether.h
@@ -0,0 +1,171 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Global definitions for the Ethernet IEEE 802.3 interface.
+ *
+ * Version:	@(#)if_ether.h	1.0.1a	02/08/94
+ *
+ * Author:	Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *		Donald Becker, <becker@super.org>
+ *		Alan Cox, <alan@lxorguk.ukuu.org.uk>
+ *		Steve Whitehouse, <gw7rrm@eeshack3.swan.ac.uk>
+ *
+ *		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.
+ */
+
+#ifndef _LINUX_IF_ETHER_H
+#define _LINUX_IF_ETHER_H
+
+#include <linux/types.h>
+
+/*
+ *	IEEE 802.3 Ethernet magic constants.  The frame sizes omit the preamble
+ *	and FCS/CRC (frame check sequence).
+ */
+
+#define ETH_ALEN	6		/* Octets in one ethernet addr	 */
+#define ETH_TLEN	2		/* Octets in ethernet type field */
+#define ETH_HLEN	14		/* Total octets in header.	 */
+#define ETH_ZLEN	60		/* Min. octets in frame sans FCS */
+#define ETH_DATA_LEN	1500		/* Max. octets in payload	 */
+#define ETH_FRAME_LEN	1514		/* Max. octets in frame sans FCS */
+#define ETH_FCS_LEN	4		/* Octets in the FCS		 */
+
+#define ETH_MIN_MTU	68		/* Min IPv4 MTU per RFC791	*/
+#define ETH_MAX_MTU	0xFFFFU		/* 65535, same as IP_MAX_MTU	*/
+
+/*
+ *	These are the defined Ethernet Protocol ID's.
+ */
+
+#define ETH_P_LOOP	0x0060		/* Ethernet Loopback packet	*/
+#define ETH_P_PUP	0x0200		/* Xerox PUP packet		*/
+#define ETH_P_PUPAT	0x0201		/* Xerox PUP Addr Trans packet	*/
+#define ETH_P_TSN	0x22F0		/* TSN (IEEE 1722) packet	*/
+#define ETH_P_ERSPAN2	0x22EB		/* ERSPAN version 2 (type III)	*/
+#define ETH_P_IP	0x0800		/* Internet Protocol packet	*/
+#define ETH_P_X25	0x0805		/* CCITT X.25			*/
+#define ETH_P_ARP	0x0806		/* Address Resolution packet	*/
+#define	ETH_P_BPQ	0x08FF		/* G8BPQ AX.25 Ethernet Packet	[ NOT AN OFFICIALLY REGISTERED ID ] */
+#define ETH_P_IEEEPUP	0x0a00		/* Xerox IEEE802.3 PUP packet */
+#define ETH_P_IEEEPUPAT	0x0a01		/* Xerox IEEE802.3 PUP Addr Trans packet */
+#define ETH_P_BATMAN	0x4305		/* B.A.T.M.A.N.-Advanced packet [ NOT AN OFFICIALLY REGISTERED ID ] */
+#define ETH_P_DEC       0x6000          /* DEC Assigned proto           */
+#define ETH_P_DNA_DL    0x6001          /* DEC DNA Dump/Load            */
+#define ETH_P_DNA_RC    0x6002          /* DEC DNA Remote Console       */
+#define ETH_P_DNA_RT    0x6003          /* DEC DNA Routing              */
+#define ETH_P_LAT       0x6004          /* DEC LAT                      */
+#define ETH_P_DIAG      0x6005          /* DEC Diagnostics              */
+#define ETH_P_CUST      0x6006          /* DEC Customer use             */
+#define ETH_P_SCA       0x6007          /* DEC Systems Comms Arch       */
+#define ETH_P_TEB	0x6558		/* Trans Ether Bridging		*/
+#define ETH_P_RARP      0x8035		/* Reverse Addr Res packet	*/
+#define ETH_P_ATALK	0x809B		/* Appletalk DDP		*/
+#define ETH_P_AARP	0x80F3		/* Appletalk AARP		*/
+#define ETH_P_8021Q	0x8100          /* 802.1Q VLAN Extended Header  */
+#define ETH_P_ERSPAN	0x88BE		/* ERSPAN type II		*/
+#define ETH_P_IPX	0x8137		/* IPX over DIX			*/
+#define ETH_P_IPV6	0x86DD		/* IPv6 over bluebook		*/
+#define ETH_P_PAUSE	0x8808		/* IEEE Pause frames. See 802.3 31B */
+#define ETH_P_SLOW	0x8809		/* Slow Protocol. See 802.3ad 43B */
+#define ETH_P_WCCP	0x883E		/* Web-cache coordination protocol
+					 * defined in draft-wilson-wrec-wccp-v2-00.txt */
+#define ETH_P_MPLS_UC	0x8847		/* MPLS Unicast traffic		*/
+#define ETH_P_MPLS_MC	0x8848		/* MPLS Multicast traffic	*/
+#define ETH_P_ATMMPOA	0x884c		/* MultiProtocol Over ATM	*/
+#define ETH_P_PPP_DISC	0x8863		/* PPPoE discovery messages     */
+#define ETH_P_PPP_SES	0x8864		/* PPPoE session messages	*/
+#define ETH_P_LINK_CTL	0x886c		/* HPNA, wlan link local tunnel */
+#define ETH_P_ATMFATE	0x8884		/* Frame-based ATM Transport
+					 * over Ethernet
+					 */
+#define ETH_P_PAE	0x888E		/* Port Access Entity (IEEE 802.1X) */
+#define ETH_P_AOE	0x88A2		/* ATA over Ethernet		*/
+#define ETH_P_8021AD	0x88A8          /* 802.1ad Service VLAN		*/
+#define ETH_P_802_EX1	0x88B5		/* 802.1 Local Experimental 1.  */
+#define ETH_P_PREAUTH	0x88C7		/* 802.11 Preauthentication */
+#define ETH_P_TIPC	0x88CA		/* TIPC 			*/
+#define ETH_P_LLDP	0x88CC		/* Link Layer Discovery Protocol */
+#define ETH_P_MACSEC	0x88E5		/* 802.1ae MACsec */
+#define ETH_P_8021AH	0x88E7          /* 802.1ah Backbone Service Tag */
+#define ETH_P_MVRP	0x88F5          /* 802.1Q MVRP                  */
+#define ETH_P_1588	0x88F7		/* IEEE 1588 Timesync */
+#define ETH_P_NCSI	0x88F8		/* NCSI protocol		*/
+#define ETH_P_PRP	0x88FB		/* IEC 62439-3 PRP/HSRv0	*/
+#define ETH_P_FCOE	0x8906		/* Fibre Channel over Ethernet  */
+#define ETH_P_IBOE	0x8915		/* Infiniband over Ethernet	*/
+#define ETH_P_TDLS	0x890D          /* TDLS */
+#define ETH_P_FIP	0x8914		/* FCoE Initialization Protocol */
+#define ETH_P_80221	0x8917		/* IEEE 802.21 Media Independent Handover Protocol */
+#define ETH_P_HSR	0x892F		/* IEC 62439-3 HSRv1	*/
+#define ETH_P_NSH	0x894F		/* Network Service Header */
+#define ETH_P_LOOPBACK	0x9000		/* Ethernet loopback packet, per IEEE 802.3 */
+#define ETH_P_QINQ1	0x9100		/* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
+#define ETH_P_QINQ2	0x9200		/* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
+#define ETH_P_QINQ3	0x9300		/* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
+#define ETH_P_EDSA	0xDADA		/* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
+#define ETH_P_DSA_8021Q	0xDADB		/* Fake VLAN Header for DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
+#define ETH_P_IFE	0xED3E		/* ForCES inter-FE LFB type */
+#define ETH_P_AF_IUCV   0xFBFB		/* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */
+
+#define ETH_P_802_3_MIN	0x0600		/* If the value in the ethernet type is less than this value
+					 * then the frame is Ethernet II. Else it is 802.3 */
+
+/*
+ *	Non DIX types. Won't clash for 1500 types.
+ */
+
+#define ETH_P_802_3	0x0001		/* Dummy type for 802.3 frames  */
+#define ETH_P_AX25	0x0002		/* Dummy protocol id for AX.25  */
+#define ETH_P_ALL	0x0003		/* Every packet (be careful!!!) */
+#define ETH_P_802_2	0x0004		/* 802.2 frames 		*/
+#define ETH_P_SNAP	0x0005		/* Internal only		*/
+#define ETH_P_DDCMP     0x0006          /* DEC DDCMP: Internal only     */
+#define ETH_P_WAN_PPP   0x0007          /* Dummy type for WAN PPP frames*/
+#define ETH_P_PPP_MP    0x0008          /* Dummy type for PPP MP frames */
+#define ETH_P_LOCALTALK 0x0009		/* Localtalk pseudo type 	*/
+#define ETH_P_CAN	0x000C		/* CAN: Controller Area Network */
+#define ETH_P_CANFD	0x000D		/* CANFD: CAN flexible data rate*/
+#define ETH_P_PPPTALK	0x0010		/* Dummy type for Atalk over PPP*/
+#define ETH_P_TR_802_2	0x0011		/* 802.2 frames 		*/
+#define ETH_P_MOBITEX	0x0015		/* Mobitex (kaz@cafe.net)	*/
+#define ETH_P_CONTROL	0x0016		/* Card specific control frames */
+#define ETH_P_IRDA	0x0017		/* Linux-IrDA			*/
+#define ETH_P_ECONET	0x0018		/* Acorn Econet			*/
+#define ETH_P_HDLC	0x0019		/* HDLC frames			*/
+#define ETH_P_ARCNET	0x001A		/* 1A for ArcNet :-)            */
+#define ETH_P_DSA	0x001B		/* Distributed Switch Arch.	*/
+#define ETH_P_TRAILER	0x001C		/* Trailer switch tagging	*/
+#define ETH_P_PHONET	0x00F5		/* Nokia Phonet frames          */
+#define ETH_P_IEEE802154 0x00F6		/* IEEE802.15.4 frame		*/
+#define ETH_P_CAIF	0x00F7		/* ST-Ericsson CAIF protocol	*/
+#define ETH_P_XDSA	0x00F8		/* Multiplexed DSA protocol	*/
+#define ETH_P_MAP	0x00F9		/* Qualcomm multiplexing and
+					 * aggregation protocol
+					 */
+
+/*
+ *	This is an Ethernet frame header.
+ */
+
+/* allow libcs like musl to deactivate this, glibc does not implement this. */
+#ifndef __UAPI_DEF_ETHHDR
+#define __UAPI_DEF_ETHHDR		1
+#endif
+
+#if __UAPI_DEF_ETHHDR
+struct ethhdr {
+	unsigned char	h_dest[ETH_ALEN];	/* destination eth addr	*/
+	unsigned char	h_source[ETH_ALEN];	/* source ether addr	*/
+	__be16		h_proto;		/* packet type ID field	*/
+} __attribute__((packed));
+#endif
+
+
+#endif /* _LINUX_IF_ETHER_H */
diff --git a/include/uapi/linux/if_infiniband.h b/include/uapi/linux/if_infiniband.h
new file mode 100644
index 0000000..0fc33bf
--- /dev/null
+++ b/include/uapi/linux/if_infiniband.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+/*
+ * This software is available to you under a choice of one of two
+ * licenses.  You may choose to be licensed under the terms of the GNU
+ * General Public License (GPL) Version 2, available at
+ * <http://www.fsf.org/copyleft/gpl.html>, or the OpenIB.org BSD
+ * license, available in the LICENSE.TXT file accompanying this
+ * software.  These details are also available at
+ * <http://www.openfabrics.org/software_license.htm>.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Copyright (c) 2004 Topspin Communications.  All rights reserved.
+ *
+ * $Id$
+ */
+
+#ifndef _LINUX_IF_INFINIBAND_H
+#define _LINUX_IF_INFINIBAND_H
+
+#define INFINIBAND_ALEN		20	/* Octets in IPoIB HW addr	*/
+
+#endif /* _LINUX_IF_INFINIBAND_H */
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
new file mode 100644
index 0000000..d36919f
--- /dev/null
+++ b/include/uapi/linux/if_link.h
@@ -0,0 +1,1029 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_IF_LINK_H
+#define _LINUX_IF_LINK_H
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+/* This struct should be in sync with struct rtnl_link_stats64 */
+struct rtnl_link_stats {
+	__u32	rx_packets;		/* total packets received	*/
+	__u32	tx_packets;		/* total packets transmitted	*/
+	__u32	rx_bytes;		/* total bytes received 	*/
+	__u32	tx_bytes;		/* total bytes transmitted	*/
+	__u32	rx_errors;		/* bad packets received		*/
+	__u32	tx_errors;		/* packet transmit problems	*/
+	__u32	rx_dropped;		/* no space in linux buffers	*/
+	__u32	tx_dropped;		/* no space available in linux	*/
+	__u32	multicast;		/* multicast packets received	*/
+	__u32	collisions;
+
+	/* detailed rx_errors: */
+	__u32	rx_length_errors;
+	__u32	rx_over_errors;		/* receiver ring buff overflow	*/
+	__u32	rx_crc_errors;		/* recved pkt with crc error	*/
+	__u32	rx_frame_errors;	/* recv'd frame alignment error */
+	__u32	rx_fifo_errors;		/* recv'r fifo overrun		*/
+	__u32	rx_missed_errors;	/* receiver missed packet	*/
+
+	/* detailed tx_errors */
+	__u32	tx_aborted_errors;
+	__u32	tx_carrier_errors;
+	__u32	tx_fifo_errors;
+	__u32	tx_heartbeat_errors;
+	__u32	tx_window_errors;
+
+	/* for cslip etc */
+	__u32	rx_compressed;
+	__u32	tx_compressed;
+
+	__u32	rx_nohandler;		/* dropped, no handler found	*/
+};
+
+/* The main device statistics structure */
+struct rtnl_link_stats64 {
+	__u64	rx_packets;		/* total packets received	*/
+	__u64	tx_packets;		/* total packets transmitted	*/
+	__u64	rx_bytes;		/* total bytes received 	*/
+	__u64	tx_bytes;		/* total bytes transmitted	*/
+	__u64	rx_errors;		/* bad packets received		*/
+	__u64	tx_errors;		/* packet transmit problems	*/
+	__u64	rx_dropped;		/* no space in linux buffers	*/
+	__u64	tx_dropped;		/* no space available in linux	*/
+	__u64	multicast;		/* multicast packets received	*/
+	__u64	collisions;
+
+	/* detailed rx_errors: */
+	__u64	rx_length_errors;
+	__u64	rx_over_errors;		/* receiver ring buff overflow	*/
+	__u64	rx_crc_errors;		/* recved pkt with crc error	*/
+	__u64	rx_frame_errors;	/* recv'd frame alignment error */
+	__u64	rx_fifo_errors;		/* recv'r fifo overrun		*/
+	__u64	rx_missed_errors;	/* receiver missed packet	*/
+
+	/* detailed tx_errors */
+	__u64	tx_aborted_errors;
+	__u64	tx_carrier_errors;
+	__u64	tx_fifo_errors;
+	__u64	tx_heartbeat_errors;
+	__u64	tx_window_errors;
+
+	/* for cslip etc */
+	__u64	rx_compressed;
+	__u64	tx_compressed;
+
+	__u64	rx_nohandler;		/* dropped, no handler found	*/
+};
+
+/* The struct should be in sync with struct ifmap */
+struct rtnl_link_ifmap {
+	__u64	mem_start;
+	__u64	mem_end;
+	__u64	base_addr;
+	__u16	irq;
+	__u8	dma;
+	__u8	port;
+};
+
+/*
+ * IFLA_AF_SPEC
+ *   Contains nested attributes for address family specific attributes.
+ *   Each address family may create a attribute with the address family
+ *   number as type and create its own attribute structure in it.
+ *
+ *   Example:
+ *   [IFLA_AF_SPEC] = {
+ *       [AF_INET] = {
+ *           [IFLA_INET_CONF] = ...,
+ *       },
+ *       [AF_INET6] = {
+ *           [IFLA_INET6_FLAGS] = ...,
+ *           [IFLA_INET6_CONF] = ...,
+ *       }
+ *   }
+ */
+
+enum {
+	IFLA_UNSPEC,
+	IFLA_ADDRESS,
+	IFLA_BROADCAST,
+	IFLA_IFNAME,
+	IFLA_MTU,
+	IFLA_LINK,
+	IFLA_QDISC,
+	IFLA_STATS,
+	IFLA_COST,
+#define IFLA_COST IFLA_COST
+	IFLA_PRIORITY,
+#define IFLA_PRIORITY IFLA_PRIORITY
+	IFLA_MASTER,
+#define IFLA_MASTER IFLA_MASTER
+	IFLA_WIRELESS,		/* Wireless Extension event - see wireless.h */
+#define IFLA_WIRELESS IFLA_WIRELESS
+	IFLA_PROTINFO,		/* Protocol specific information for a link */
+#define IFLA_PROTINFO IFLA_PROTINFO
+	IFLA_TXQLEN,
+#define IFLA_TXQLEN IFLA_TXQLEN
+	IFLA_MAP,
+#define IFLA_MAP IFLA_MAP
+	IFLA_WEIGHT,
+#define IFLA_WEIGHT IFLA_WEIGHT
+	IFLA_OPERSTATE,
+	IFLA_LINKMODE,
+	IFLA_LINKINFO,
+#define IFLA_LINKINFO IFLA_LINKINFO
+	IFLA_NET_NS_PID,
+	IFLA_IFALIAS,
+	IFLA_NUM_VF,		/* Number of VFs if device is SR-IOV PF */
+	IFLA_VFINFO_LIST,
+	IFLA_STATS64,
+	IFLA_VF_PORTS,
+	IFLA_PORT_SELF,
+	IFLA_AF_SPEC,
+	IFLA_GROUP,		/* Group the device belongs to */
+	IFLA_NET_NS_FD,
+	IFLA_EXT_MASK,		/* Extended info mask, VFs, etc */
+	IFLA_PROMISCUITY,	/* Promiscuity count: > 0 means acts PROMISC */
+#define IFLA_PROMISCUITY IFLA_PROMISCUITY
+	IFLA_NUM_TX_QUEUES,
+	IFLA_NUM_RX_QUEUES,
+	IFLA_CARRIER,
+	IFLA_PHYS_PORT_ID,
+	IFLA_CARRIER_CHANGES,
+	IFLA_PHYS_SWITCH_ID,
+	IFLA_LINK_NETNSID,
+	IFLA_PHYS_PORT_NAME,
+	IFLA_PROTO_DOWN,
+	IFLA_GSO_MAX_SEGS,
+	IFLA_GSO_MAX_SIZE,
+	IFLA_PAD,
+	IFLA_XDP,
+	IFLA_EVENT,
+	IFLA_NEW_NETNSID,
+	IFLA_IF_NETNSID,
+	IFLA_TARGET_NETNSID = IFLA_IF_NETNSID, /* new alias */
+	IFLA_CARRIER_UP_COUNT,
+	IFLA_CARRIER_DOWN_COUNT,
+	IFLA_NEW_IFINDEX,
+	IFLA_MIN_MTU,
+	IFLA_MAX_MTU,
+	__IFLA_MAX
+};
+
+
+#define IFLA_MAX (__IFLA_MAX - 1)
+
+/* backwards compatibility for userspace */
+#define IFLA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg))))
+#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg))
+
+enum {
+	IFLA_INET_UNSPEC,
+	IFLA_INET_CONF,
+	__IFLA_INET_MAX,
+};
+
+#define IFLA_INET_MAX (__IFLA_INET_MAX - 1)
+
+/* ifi_flags.
+
+   IFF_* flags.
+
+   The only change is:
+   IFF_LOOPBACK, IFF_BROADCAST and IFF_POINTOPOINT are
+   more not changeable by user. They describe link media
+   characteristics and set by device driver.
+
+   Comments:
+   - Combination IFF_BROADCAST|IFF_POINTOPOINT is invalid
+   - If neither of these three flags are set;
+     the interface is NBMA.
+
+   - IFF_MULTICAST does not mean anything special:
+   multicasts can be used on all not-NBMA links.
+   IFF_MULTICAST means that this media uses special encapsulation
+   for multicast frames. Apparently, all IFF_POINTOPOINT and
+   IFF_BROADCAST devices are able to use multicasts too.
+ */
+
+/* IFLA_LINK.
+   For usual devices it is equal ifi_index.
+   If it is a "virtual interface" (f.e. tunnel), ifi_link
+   can point to real physical interface (f.e. for bandwidth calculations),
+   or maybe 0, what means, that real media is unknown (usual
+   for IPIP tunnels, when route to endpoint is allowed to change)
+ */
+
+/* Subtype attributes for IFLA_PROTINFO */
+enum {
+	IFLA_INET6_UNSPEC,
+	IFLA_INET6_FLAGS,	/* link flags			*/
+	IFLA_INET6_CONF,	/* sysctl parameters		*/
+	IFLA_INET6_STATS,	/* statistics			*/
+	IFLA_INET6_MCAST,	/* MC things. What of them?	*/
+	IFLA_INET6_CACHEINFO,	/* time values and max reasm size */
+	IFLA_INET6_ICMP6STATS,	/* statistics (icmpv6)		*/
+	IFLA_INET6_TOKEN,	/* device token			*/
+	IFLA_INET6_ADDR_GEN_MODE, /* implicit address generator mode */
+	__IFLA_INET6_MAX
+};
+
+#define IFLA_INET6_MAX	(__IFLA_INET6_MAX - 1)
+
+enum in6_addr_gen_mode {
+	IN6_ADDR_GEN_MODE_EUI64,
+	IN6_ADDR_GEN_MODE_NONE,
+	IN6_ADDR_GEN_MODE_STABLE_PRIVACY,
+	IN6_ADDR_GEN_MODE_RANDOM,
+};
+
+/* Bridge section */
+
+enum {
+	IFLA_BR_UNSPEC,
+	IFLA_BR_FORWARD_DELAY,
+	IFLA_BR_HELLO_TIME,
+	IFLA_BR_MAX_AGE,
+	IFLA_BR_AGEING_TIME,
+	IFLA_BR_STP_STATE,
+	IFLA_BR_PRIORITY,
+	IFLA_BR_VLAN_FILTERING,
+	IFLA_BR_VLAN_PROTOCOL,
+	IFLA_BR_GROUP_FWD_MASK,
+	IFLA_BR_ROOT_ID,
+	IFLA_BR_BRIDGE_ID,
+	IFLA_BR_ROOT_PORT,
+	IFLA_BR_ROOT_PATH_COST,
+	IFLA_BR_TOPOLOGY_CHANGE,
+	IFLA_BR_TOPOLOGY_CHANGE_DETECTED,
+	IFLA_BR_HELLO_TIMER,
+	IFLA_BR_TCN_TIMER,
+	IFLA_BR_TOPOLOGY_CHANGE_TIMER,
+	IFLA_BR_GC_TIMER,
+	IFLA_BR_GROUP_ADDR,
+	IFLA_BR_FDB_FLUSH,
+	IFLA_BR_MCAST_ROUTER,
+	IFLA_BR_MCAST_SNOOPING,
+	IFLA_BR_MCAST_QUERY_USE_IFADDR,
+	IFLA_BR_MCAST_QUERIER,
+	IFLA_BR_MCAST_HASH_ELASTICITY,
+	IFLA_BR_MCAST_HASH_MAX,
+	IFLA_BR_MCAST_LAST_MEMBER_CNT,
+	IFLA_BR_MCAST_STARTUP_QUERY_CNT,
+	IFLA_BR_MCAST_LAST_MEMBER_INTVL,
+	IFLA_BR_MCAST_MEMBERSHIP_INTVL,
+	IFLA_BR_MCAST_QUERIER_INTVL,
+	IFLA_BR_MCAST_QUERY_INTVL,
+	IFLA_BR_MCAST_QUERY_RESPONSE_INTVL,
+	IFLA_BR_MCAST_STARTUP_QUERY_INTVL,
+	IFLA_BR_NF_CALL_IPTABLES,
+	IFLA_BR_NF_CALL_IP6TABLES,
+	IFLA_BR_NF_CALL_ARPTABLES,
+	IFLA_BR_VLAN_DEFAULT_PVID,
+	IFLA_BR_PAD,
+	IFLA_BR_VLAN_STATS_ENABLED,
+	IFLA_BR_MCAST_STATS_ENABLED,
+	IFLA_BR_MCAST_IGMP_VERSION,
+	IFLA_BR_MCAST_MLD_VERSION,
+	IFLA_BR_VLAN_STATS_PER_PORT,
+	IFLA_BR_MULTI_BOOLOPT,
+	__IFLA_BR_MAX,
+};
+
+#define IFLA_BR_MAX	(__IFLA_BR_MAX - 1)
+
+struct ifla_bridge_id {
+	__u8	prio[2];
+	__u8	addr[6]; /* ETH_ALEN */
+};
+
+enum {
+	BRIDGE_MODE_UNSPEC,
+	BRIDGE_MODE_HAIRPIN,
+};
+
+enum {
+	IFLA_BRPORT_UNSPEC,
+	IFLA_BRPORT_STATE,	/* Spanning tree state     */
+	IFLA_BRPORT_PRIORITY,	/* "             priority  */
+	IFLA_BRPORT_COST,	/* "             cost      */
+	IFLA_BRPORT_MODE,	/* mode (hairpin)          */
+	IFLA_BRPORT_GUARD,	/* bpdu guard              */
+	IFLA_BRPORT_PROTECT,	/* root port protection    */
+	IFLA_BRPORT_FAST_LEAVE,	/* multicast fast leave    */
+	IFLA_BRPORT_LEARNING,	/* mac learning */
+	IFLA_BRPORT_UNICAST_FLOOD, /* flood unicast traffic */
+	IFLA_BRPORT_PROXYARP,	/* proxy ARP */
+	IFLA_BRPORT_LEARNING_SYNC, /* mac learning sync from device */
+	IFLA_BRPORT_PROXYARP_WIFI, /* proxy ARP for Wi-Fi */
+	IFLA_BRPORT_ROOT_ID,	/* designated root */
+	IFLA_BRPORT_BRIDGE_ID,	/* designated bridge */
+	IFLA_BRPORT_DESIGNATED_PORT,
+	IFLA_BRPORT_DESIGNATED_COST,
+	IFLA_BRPORT_ID,
+	IFLA_BRPORT_NO,
+	IFLA_BRPORT_TOPOLOGY_CHANGE_ACK,
+	IFLA_BRPORT_CONFIG_PENDING,
+	IFLA_BRPORT_MESSAGE_AGE_TIMER,
+	IFLA_BRPORT_FORWARD_DELAY_TIMER,
+	IFLA_BRPORT_HOLD_TIMER,
+	IFLA_BRPORT_FLUSH,
+	IFLA_BRPORT_MULTICAST_ROUTER,
+	IFLA_BRPORT_PAD,
+	IFLA_BRPORT_MCAST_FLOOD,
+	IFLA_BRPORT_MCAST_TO_UCAST,
+	IFLA_BRPORT_VLAN_TUNNEL,
+	IFLA_BRPORT_BCAST_FLOOD,
+	IFLA_BRPORT_GROUP_FWD_MASK,
+	IFLA_BRPORT_NEIGH_SUPPRESS,
+	IFLA_BRPORT_ISOLATED,
+	IFLA_BRPORT_BACKUP_PORT,
+	__IFLA_BRPORT_MAX
+};
+#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
+
+struct ifla_cacheinfo {
+	__u32	max_reasm_len;
+	__u32	tstamp;		/* ipv6InterfaceTable updated timestamp */
+	__u32	reachable_time;
+	__u32	retrans_time;
+};
+
+enum {
+	IFLA_INFO_UNSPEC,
+	IFLA_INFO_KIND,
+	IFLA_INFO_DATA,
+	IFLA_INFO_XSTATS,
+	IFLA_INFO_SLAVE_KIND,
+	IFLA_INFO_SLAVE_DATA,
+	__IFLA_INFO_MAX,
+};
+
+#define IFLA_INFO_MAX	(__IFLA_INFO_MAX - 1)
+
+/* VLAN section */
+
+enum {
+	IFLA_VLAN_UNSPEC,
+	IFLA_VLAN_ID,
+	IFLA_VLAN_FLAGS,
+	IFLA_VLAN_EGRESS_QOS,
+	IFLA_VLAN_INGRESS_QOS,
+	IFLA_VLAN_PROTOCOL,
+	__IFLA_VLAN_MAX,
+};
+
+#define IFLA_VLAN_MAX	(__IFLA_VLAN_MAX - 1)
+
+struct ifla_vlan_flags {
+	__u32	flags;
+	__u32	mask;
+};
+
+enum {
+	IFLA_VLAN_QOS_UNSPEC,
+	IFLA_VLAN_QOS_MAPPING,
+	__IFLA_VLAN_QOS_MAX
+};
+
+#define IFLA_VLAN_QOS_MAX	(__IFLA_VLAN_QOS_MAX - 1)
+
+struct ifla_vlan_qos_mapping {
+	__u32 from;
+	__u32 to;
+};
+
+/* MACVLAN section */
+enum {
+	IFLA_MACVLAN_UNSPEC,
+	IFLA_MACVLAN_MODE,
+	IFLA_MACVLAN_FLAGS,
+	IFLA_MACVLAN_MACADDR_MODE,
+	IFLA_MACVLAN_MACADDR,
+	IFLA_MACVLAN_MACADDR_DATA,
+	IFLA_MACVLAN_MACADDR_COUNT,
+	__IFLA_MACVLAN_MAX,
+};
+
+#define IFLA_MACVLAN_MAX (__IFLA_MACVLAN_MAX - 1)
+
+enum macvlan_mode {
+	MACVLAN_MODE_PRIVATE = 1, /* don't talk to other macvlans */
+	MACVLAN_MODE_VEPA    = 2, /* talk to other ports through ext bridge */
+	MACVLAN_MODE_BRIDGE  = 4, /* talk to bridge ports directly */
+	MACVLAN_MODE_PASSTHRU = 8,/* take over the underlying device */
+	MACVLAN_MODE_SOURCE  = 16,/* use source MAC address list to assign */
+};
+
+enum macvlan_macaddr_mode {
+	MACVLAN_MACADDR_ADD,
+	MACVLAN_MACADDR_DEL,
+	MACVLAN_MACADDR_FLUSH,
+	MACVLAN_MACADDR_SET,
+};
+
+#define MACVLAN_FLAG_NOPROMISC	1
+
+/* VRF section */
+enum {
+	IFLA_VRF_UNSPEC,
+	IFLA_VRF_TABLE,
+	__IFLA_VRF_MAX
+};
+
+#define IFLA_VRF_MAX (__IFLA_VRF_MAX - 1)
+
+enum {
+	IFLA_VRF_PORT_UNSPEC,
+	IFLA_VRF_PORT_TABLE,
+	__IFLA_VRF_PORT_MAX
+};
+
+#define IFLA_VRF_PORT_MAX (__IFLA_VRF_PORT_MAX - 1)
+
+/* MACSEC section */
+enum {
+	IFLA_MACSEC_UNSPEC,
+	IFLA_MACSEC_SCI,
+	IFLA_MACSEC_PORT,
+	IFLA_MACSEC_ICV_LEN,
+	IFLA_MACSEC_CIPHER_SUITE,
+	IFLA_MACSEC_WINDOW,
+	IFLA_MACSEC_ENCODING_SA,
+	IFLA_MACSEC_ENCRYPT,
+	IFLA_MACSEC_PROTECT,
+	IFLA_MACSEC_INC_SCI,
+	IFLA_MACSEC_ES,
+	IFLA_MACSEC_SCB,
+	IFLA_MACSEC_REPLAY_PROTECT,
+	IFLA_MACSEC_VALIDATION,
+	IFLA_MACSEC_PAD,
+	__IFLA_MACSEC_MAX,
+};
+
+#define IFLA_MACSEC_MAX (__IFLA_MACSEC_MAX - 1)
+
+/* XFRM section */
+enum {
+	IFLA_XFRM_UNSPEC,
+	IFLA_XFRM_LINK,
+	IFLA_XFRM_IF_ID,
+	__IFLA_XFRM_MAX
+};
+
+#define IFLA_XFRM_MAX (__IFLA_XFRM_MAX - 1)
+
+enum macsec_validation_type {
+	MACSEC_VALIDATE_DISABLED = 0,
+	MACSEC_VALIDATE_CHECK = 1,
+	MACSEC_VALIDATE_STRICT = 2,
+	__MACSEC_VALIDATE_END,
+	MACSEC_VALIDATE_MAX = __MACSEC_VALIDATE_END - 1,
+};
+
+/* IPVLAN section */
+enum {
+	IFLA_IPVLAN_UNSPEC,
+	IFLA_IPVLAN_MODE,
+	IFLA_IPVLAN_FLAGS,
+	__IFLA_IPVLAN_MAX
+};
+
+#define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1)
+
+enum ipvlan_mode {
+	IPVLAN_MODE_L2 = 0,
+	IPVLAN_MODE_L3,
+	IPVLAN_MODE_L3S,
+	IPVLAN_MODE_MAX
+};
+
+#define IPVLAN_F_PRIVATE	0x01
+#define IPVLAN_F_VEPA		0x02
+
+/* VXLAN section */
+enum {
+	IFLA_VXLAN_UNSPEC,
+	IFLA_VXLAN_ID,
+	IFLA_VXLAN_GROUP,	/* group or remote address */
+	IFLA_VXLAN_LINK,
+	IFLA_VXLAN_LOCAL,
+	IFLA_VXLAN_TTL,
+	IFLA_VXLAN_TOS,
+	IFLA_VXLAN_LEARNING,
+	IFLA_VXLAN_AGEING,
+	IFLA_VXLAN_LIMIT,
+	IFLA_VXLAN_PORT_RANGE,	/* source port */
+	IFLA_VXLAN_PROXY,
+	IFLA_VXLAN_RSC,
+	IFLA_VXLAN_L2MISS,
+	IFLA_VXLAN_L3MISS,
+	IFLA_VXLAN_PORT,	/* destination port */
+	IFLA_VXLAN_GROUP6,
+	IFLA_VXLAN_LOCAL6,
+	IFLA_VXLAN_UDP_CSUM,
+	IFLA_VXLAN_UDP_ZERO_CSUM6_TX,
+	IFLA_VXLAN_UDP_ZERO_CSUM6_RX,
+	IFLA_VXLAN_REMCSUM_TX,
+	IFLA_VXLAN_REMCSUM_RX,
+	IFLA_VXLAN_GBP,
+	IFLA_VXLAN_REMCSUM_NOPARTIAL,
+	IFLA_VXLAN_COLLECT_METADATA,
+	IFLA_VXLAN_LABEL,
+	IFLA_VXLAN_GPE,
+	IFLA_VXLAN_TTL_INHERIT,
+	IFLA_VXLAN_DF,
+	__IFLA_VXLAN_MAX
+};
+#define IFLA_VXLAN_MAX	(__IFLA_VXLAN_MAX - 1)
+
+struct ifla_vxlan_port_range {
+	__be16	low;
+	__be16	high;
+};
+
+enum ifla_vxlan_df {
+	VXLAN_DF_UNSET = 0,
+	VXLAN_DF_SET,
+	VXLAN_DF_INHERIT,
+	__VXLAN_DF_END,
+	VXLAN_DF_MAX = __VXLAN_DF_END - 1,
+};
+
+/* GENEVE section */
+enum {
+	IFLA_GENEVE_UNSPEC,
+	IFLA_GENEVE_ID,
+	IFLA_GENEVE_REMOTE,
+	IFLA_GENEVE_TTL,
+	IFLA_GENEVE_TOS,
+	IFLA_GENEVE_PORT,	/* destination port */
+	IFLA_GENEVE_COLLECT_METADATA,
+	IFLA_GENEVE_REMOTE6,
+	IFLA_GENEVE_UDP_CSUM,
+	IFLA_GENEVE_UDP_ZERO_CSUM6_TX,
+	IFLA_GENEVE_UDP_ZERO_CSUM6_RX,
+	IFLA_GENEVE_LABEL,
+	IFLA_GENEVE_TTL_INHERIT,
+	IFLA_GENEVE_DF,
+	__IFLA_GENEVE_MAX
+};
+#define IFLA_GENEVE_MAX	(__IFLA_GENEVE_MAX - 1)
+
+enum ifla_geneve_df {
+	GENEVE_DF_UNSET = 0,
+	GENEVE_DF_SET,
+	GENEVE_DF_INHERIT,
+	__GENEVE_DF_END,
+	GENEVE_DF_MAX = __GENEVE_DF_END - 1,
+};
+
+/* PPP section */
+enum {
+	IFLA_PPP_UNSPEC,
+	IFLA_PPP_DEV_FD,
+	__IFLA_PPP_MAX
+};
+#define IFLA_PPP_MAX (__IFLA_PPP_MAX - 1)
+
+/* GTP section */
+
+enum ifla_gtp_role {
+	GTP_ROLE_GGSN = 0,
+	GTP_ROLE_SGSN,
+};
+
+enum {
+	IFLA_GTP_UNSPEC,
+	IFLA_GTP_FD0,
+	IFLA_GTP_FD1,
+	IFLA_GTP_PDP_HASHSIZE,
+	IFLA_GTP_ROLE,
+	__IFLA_GTP_MAX,
+};
+#define IFLA_GTP_MAX (__IFLA_GTP_MAX - 1)
+
+/* Bonding section */
+
+enum {
+	IFLA_BOND_UNSPEC,
+	IFLA_BOND_MODE,
+	IFLA_BOND_ACTIVE_SLAVE,
+	IFLA_BOND_MIIMON,
+	IFLA_BOND_UPDELAY,
+	IFLA_BOND_DOWNDELAY,
+	IFLA_BOND_USE_CARRIER,
+	IFLA_BOND_ARP_INTERVAL,
+	IFLA_BOND_ARP_IP_TARGET,
+	IFLA_BOND_ARP_VALIDATE,
+	IFLA_BOND_ARP_ALL_TARGETS,
+	IFLA_BOND_PRIMARY,
+	IFLA_BOND_PRIMARY_RESELECT,
+	IFLA_BOND_FAIL_OVER_MAC,
+	IFLA_BOND_XMIT_HASH_POLICY,
+	IFLA_BOND_RESEND_IGMP,
+	IFLA_BOND_NUM_PEER_NOTIF,
+	IFLA_BOND_ALL_SLAVES_ACTIVE,
+	IFLA_BOND_MIN_LINKS,
+	IFLA_BOND_LP_INTERVAL,
+	IFLA_BOND_PACKETS_PER_SLAVE,
+	IFLA_BOND_AD_LACP_RATE,
+	IFLA_BOND_AD_SELECT,
+	IFLA_BOND_AD_INFO,
+	IFLA_BOND_AD_ACTOR_SYS_PRIO,
+	IFLA_BOND_AD_USER_PORT_KEY,
+	IFLA_BOND_AD_ACTOR_SYSTEM,
+	IFLA_BOND_TLB_DYNAMIC_LB,
+	IFLA_BOND_PEER_NOTIF_DELAY,
+	__IFLA_BOND_MAX,
+};
+
+#define IFLA_BOND_MAX	(__IFLA_BOND_MAX - 1)
+
+enum {
+	IFLA_BOND_AD_INFO_UNSPEC,
+	IFLA_BOND_AD_INFO_AGGREGATOR,
+	IFLA_BOND_AD_INFO_NUM_PORTS,
+	IFLA_BOND_AD_INFO_ACTOR_KEY,
+	IFLA_BOND_AD_INFO_PARTNER_KEY,
+	IFLA_BOND_AD_INFO_PARTNER_MAC,
+	__IFLA_BOND_AD_INFO_MAX,
+};
+
+#define IFLA_BOND_AD_INFO_MAX	(__IFLA_BOND_AD_INFO_MAX - 1)
+
+enum {
+	IFLA_BOND_SLAVE_UNSPEC,
+	IFLA_BOND_SLAVE_STATE,
+	IFLA_BOND_SLAVE_MII_STATUS,
+	IFLA_BOND_SLAVE_LINK_FAILURE_COUNT,
+	IFLA_BOND_SLAVE_PERM_HWADDR,
+	IFLA_BOND_SLAVE_QUEUE_ID,
+	IFLA_BOND_SLAVE_AD_AGGREGATOR_ID,
+	IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE,
+	IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE,
+	__IFLA_BOND_SLAVE_MAX,
+};
+
+#define IFLA_BOND_SLAVE_MAX	(__IFLA_BOND_SLAVE_MAX - 1)
+
+/* SR-IOV virtual function management section */
+
+enum {
+	IFLA_VF_INFO_UNSPEC,
+	IFLA_VF_INFO,
+	__IFLA_VF_INFO_MAX,
+};
+
+#define IFLA_VF_INFO_MAX (__IFLA_VF_INFO_MAX - 1)
+
+enum {
+	IFLA_VF_UNSPEC,
+	IFLA_VF_MAC,		/* Hardware queue specific attributes */
+	IFLA_VF_VLAN,		/* VLAN ID and QoS */
+	IFLA_VF_TX_RATE,	/* Max TX Bandwidth Allocation */
+	IFLA_VF_SPOOFCHK,	/* Spoof Checking on/off switch */
+	IFLA_VF_LINK_STATE,	/* link state enable/disable/auto switch */
+	IFLA_VF_RATE,		/* Min and Max TX Bandwidth Allocation */
+	IFLA_VF_RSS_QUERY_EN,	/* RSS Redirection Table and Hash Key query
+				 * on/off switch
+				 */
+	IFLA_VF_STATS,		/* network device statistics */
+	IFLA_VF_TRUST,		/* Trust VF */
+	IFLA_VF_IB_NODE_GUID,	/* VF Infiniband node GUID */
+	IFLA_VF_IB_PORT_GUID,	/* VF Infiniband port GUID */
+	IFLA_VF_VLAN_LIST,	/* nested list of vlans, option for QinQ */
+	IFLA_VF_BROADCAST,	/* VF broadcast */
+	__IFLA_VF_MAX,
+};
+
+#define IFLA_VF_MAX (__IFLA_VF_MAX - 1)
+
+struct ifla_vf_mac {
+	__u32 vf;
+	__u8 mac[32]; /* MAX_ADDR_LEN */
+};
+
+struct ifla_vf_broadcast {
+	__u8 broadcast[32];
+};
+
+struct ifla_vf_vlan {
+	__u32 vf;
+	__u32 vlan; /* 0 - 4095, 0 disables VLAN filter */
+	__u32 qos;
+};
+
+enum {
+	IFLA_VF_VLAN_INFO_UNSPEC,
+	IFLA_VF_VLAN_INFO,	/* VLAN ID, QoS and VLAN protocol */
+	__IFLA_VF_VLAN_INFO_MAX,
+};
+
+#define IFLA_VF_VLAN_INFO_MAX (__IFLA_VF_VLAN_INFO_MAX - 1)
+#define MAX_VLAN_LIST_LEN 1
+
+struct ifla_vf_vlan_info {
+	__u32 vf;
+	__u32 vlan; /* 0 - 4095, 0 disables VLAN filter */
+	__u32 qos;
+	__be16 vlan_proto; /* VLAN protocol either 802.1Q or 802.1ad */
+};
+
+struct ifla_vf_tx_rate {
+	__u32 vf;
+	__u32 rate; /* Max TX bandwidth in Mbps, 0 disables throttling */
+};
+
+struct ifla_vf_rate {
+	__u32 vf;
+	__u32 min_tx_rate; /* Min Bandwidth in Mbps */
+	__u32 max_tx_rate; /* Max Bandwidth in Mbps */
+};
+
+struct ifla_vf_spoofchk {
+	__u32 vf;
+	__u32 setting;
+};
+
+struct ifla_vf_guid {
+	__u32 vf;
+	__u64 guid;
+};
+
+enum {
+	IFLA_VF_LINK_STATE_AUTO,	/* link state of the uplink */
+	IFLA_VF_LINK_STATE_ENABLE,	/* link always up */
+	IFLA_VF_LINK_STATE_DISABLE,	/* link always down */
+	__IFLA_VF_LINK_STATE_MAX,
+};
+
+struct ifla_vf_link_state {
+	__u32 vf;
+	__u32 link_state;
+};
+
+struct ifla_vf_rss_query_en {
+	__u32 vf;
+	__u32 setting;
+};
+
+enum {
+	IFLA_VF_STATS_RX_PACKETS,
+	IFLA_VF_STATS_TX_PACKETS,
+	IFLA_VF_STATS_RX_BYTES,
+	IFLA_VF_STATS_TX_BYTES,
+	IFLA_VF_STATS_BROADCAST,
+	IFLA_VF_STATS_MULTICAST,
+	IFLA_VF_STATS_PAD,
+	IFLA_VF_STATS_RX_DROPPED,
+	IFLA_VF_STATS_TX_DROPPED,
+	__IFLA_VF_STATS_MAX,
+};
+
+#define IFLA_VF_STATS_MAX (__IFLA_VF_STATS_MAX - 1)
+
+struct ifla_vf_trust {
+	__u32 vf;
+	__u32 setting;
+};
+
+/* VF ports management section
+ *
+ *	Nested layout of set/get msg is:
+ *
+ *		[IFLA_NUM_VF]
+ *		[IFLA_VF_PORTS]
+ *			[IFLA_VF_PORT]
+ *				[IFLA_PORT_*], ...
+ *			[IFLA_VF_PORT]
+ *				[IFLA_PORT_*], ...
+ *			...
+ *		[IFLA_PORT_SELF]
+ *			[IFLA_PORT_*], ...
+ */
+
+enum {
+	IFLA_VF_PORT_UNSPEC,
+	IFLA_VF_PORT,			/* nest */
+	__IFLA_VF_PORT_MAX,
+};
+
+#define IFLA_VF_PORT_MAX (__IFLA_VF_PORT_MAX - 1)
+
+enum {
+	IFLA_PORT_UNSPEC,
+	IFLA_PORT_VF,			/* __u32 */
+	IFLA_PORT_PROFILE,		/* string */
+	IFLA_PORT_VSI_TYPE,		/* 802.1Qbg (pre-)standard VDP */
+	IFLA_PORT_INSTANCE_UUID,	/* binary UUID */
+	IFLA_PORT_HOST_UUID,		/* binary UUID */
+	IFLA_PORT_REQUEST,		/* __u8 */
+	IFLA_PORT_RESPONSE,		/* __u16, output only */
+	__IFLA_PORT_MAX,
+};
+
+#define IFLA_PORT_MAX (__IFLA_PORT_MAX - 1)
+
+#define PORT_PROFILE_MAX	40
+#define PORT_UUID_MAX		16
+#define PORT_SELF_VF		-1
+
+enum {
+	PORT_REQUEST_PREASSOCIATE = 0,
+	PORT_REQUEST_PREASSOCIATE_RR,
+	PORT_REQUEST_ASSOCIATE,
+	PORT_REQUEST_DISASSOCIATE,
+};
+
+enum {
+	PORT_VDP_RESPONSE_SUCCESS = 0,
+	PORT_VDP_RESPONSE_INVALID_FORMAT,
+	PORT_VDP_RESPONSE_INSUFFICIENT_RESOURCES,
+	PORT_VDP_RESPONSE_UNUSED_VTID,
+	PORT_VDP_RESPONSE_VTID_VIOLATION,
+	PORT_VDP_RESPONSE_VTID_VERSION_VIOALTION,
+	PORT_VDP_RESPONSE_OUT_OF_SYNC,
+	/* 0x08-0xFF reserved for future VDP use */
+	PORT_PROFILE_RESPONSE_SUCCESS = 0x100,
+	PORT_PROFILE_RESPONSE_INPROGRESS,
+	PORT_PROFILE_RESPONSE_INVALID,
+	PORT_PROFILE_RESPONSE_BADSTATE,
+	PORT_PROFILE_RESPONSE_INSUFFICIENT_RESOURCES,
+	PORT_PROFILE_RESPONSE_ERROR,
+};
+
+struct ifla_port_vsi {
+	__u8 vsi_mgr_id;
+	__u8 vsi_type_id[3];
+	__u8 vsi_type_version;
+	__u8 pad[3];
+};
+
+
+/* IPoIB section */
+
+enum {
+	IFLA_IPOIB_UNSPEC,
+	IFLA_IPOIB_PKEY,
+	IFLA_IPOIB_MODE,
+	IFLA_IPOIB_UMCAST,
+	__IFLA_IPOIB_MAX
+};
+
+enum {
+	IPOIB_MODE_DATAGRAM  = 0, /* using unreliable datagram QPs */
+	IPOIB_MODE_CONNECTED = 1, /* using connected QPs */
+};
+
+#define IFLA_IPOIB_MAX (__IFLA_IPOIB_MAX - 1)
+
+
+/* HSR section */
+
+enum {
+	IFLA_HSR_UNSPEC,
+	IFLA_HSR_SLAVE1,
+	IFLA_HSR_SLAVE2,
+	IFLA_HSR_MULTICAST_SPEC,	/* Last byte of supervision addr */
+	IFLA_HSR_SUPERVISION_ADDR,	/* Supervision frame multicast addr */
+	IFLA_HSR_SEQ_NR,
+	IFLA_HSR_VERSION,		/* HSR version */
+	__IFLA_HSR_MAX,
+};
+
+#define IFLA_HSR_MAX (__IFLA_HSR_MAX - 1)
+
+/* STATS section */
+
+struct if_stats_msg {
+	__u8  family;
+	__u8  pad1;
+	__u16 pad2;
+	__u32 ifindex;
+	__u32 filter_mask;
+};
+
+/* A stats attribute can be netdev specific or a global stat.
+ * For netdev stats, lets use the prefix IFLA_STATS_LINK_*
+ */
+enum {
+	IFLA_STATS_UNSPEC, /* also used as 64bit pad attribute */
+	IFLA_STATS_LINK_64,
+	IFLA_STATS_LINK_XSTATS,
+	IFLA_STATS_LINK_XSTATS_SLAVE,
+	IFLA_STATS_LINK_OFFLOAD_XSTATS,
+	IFLA_STATS_AF_SPEC,
+	__IFLA_STATS_MAX,
+};
+
+#define IFLA_STATS_MAX (__IFLA_STATS_MAX - 1)
+
+#define IFLA_STATS_FILTER_BIT(ATTR)	(1 << (ATTR - 1))
+
+/* These are embedded into IFLA_STATS_LINK_XSTATS:
+ * [IFLA_STATS_LINK_XSTATS]
+ * -> [LINK_XSTATS_TYPE_xxx]
+ *    -> [rtnl link type specific attributes]
+ */
+enum {
+	LINK_XSTATS_TYPE_UNSPEC,
+	LINK_XSTATS_TYPE_BRIDGE,
+	LINK_XSTATS_TYPE_BOND,
+	__LINK_XSTATS_TYPE_MAX
+};
+#define LINK_XSTATS_TYPE_MAX (__LINK_XSTATS_TYPE_MAX - 1)
+
+/* These are stats embedded into IFLA_STATS_LINK_OFFLOAD_XSTATS */
+enum {
+	IFLA_OFFLOAD_XSTATS_UNSPEC,
+	IFLA_OFFLOAD_XSTATS_CPU_HIT, /* struct rtnl_link_stats64 */
+	__IFLA_OFFLOAD_XSTATS_MAX
+};
+#define IFLA_OFFLOAD_XSTATS_MAX (__IFLA_OFFLOAD_XSTATS_MAX - 1)
+
+/* XDP section */
+
+#define XDP_FLAGS_UPDATE_IF_NOEXIST	(1U << 0)
+#define XDP_FLAGS_SKB_MODE		(1U << 1)
+#define XDP_FLAGS_DRV_MODE		(1U << 2)
+#define XDP_FLAGS_HW_MODE		(1U << 3)
+#define XDP_FLAGS_MODES			(XDP_FLAGS_SKB_MODE | \
+					 XDP_FLAGS_DRV_MODE | \
+					 XDP_FLAGS_HW_MODE)
+#define XDP_FLAGS_MASK			(XDP_FLAGS_UPDATE_IF_NOEXIST | \
+					 XDP_FLAGS_MODES)
+
+/* These are stored into IFLA_XDP_ATTACHED on dump. */
+enum {
+	XDP_ATTACHED_NONE = 0,
+	XDP_ATTACHED_DRV,
+	XDP_ATTACHED_SKB,
+	XDP_ATTACHED_HW,
+	XDP_ATTACHED_MULTI,
+};
+
+enum {
+	IFLA_XDP_UNSPEC,
+	IFLA_XDP_FD,
+	IFLA_XDP_ATTACHED,
+	IFLA_XDP_FLAGS,
+	IFLA_XDP_PROG_ID,
+	IFLA_XDP_DRV_PROG_ID,
+	IFLA_XDP_SKB_PROG_ID,
+	IFLA_XDP_HW_PROG_ID,
+	__IFLA_XDP_MAX,
+};
+
+#define IFLA_XDP_MAX (__IFLA_XDP_MAX - 1)
+
+enum {
+	IFLA_EVENT_NONE,
+	IFLA_EVENT_REBOOT,		/* internal reset / reboot */
+	IFLA_EVENT_FEATURES,		/* change in offload features */
+	IFLA_EVENT_BONDING_FAILOVER,	/* change in active slave */
+	IFLA_EVENT_NOTIFY_PEERS,	/* re-sent grat. arp/ndisc */
+	IFLA_EVENT_IGMP_RESEND,		/* re-sent IGMP JOIN */
+	IFLA_EVENT_BONDING_OPTIONS,	/* change in bonding options */
+};
+
+/* tun section */
+
+enum {
+	IFLA_TUN_UNSPEC,
+	IFLA_TUN_OWNER,
+	IFLA_TUN_GROUP,
+	IFLA_TUN_TYPE,
+	IFLA_TUN_PI,
+	IFLA_TUN_VNET_HDR,
+	IFLA_TUN_PERSIST,
+	IFLA_TUN_MULTI_QUEUE,
+	IFLA_TUN_NUM_QUEUES,
+	IFLA_TUN_NUM_DISABLED_QUEUES,
+	__IFLA_TUN_MAX,
+};
+
+#define IFLA_TUN_MAX (__IFLA_TUN_MAX - 1)
+
+/* rmnet section */
+
+#define RMNET_FLAGS_INGRESS_DEAGGREGATION         (1U << 0)
+#define RMNET_FLAGS_INGRESS_MAP_COMMANDS          (1U << 1)
+#define RMNET_FLAGS_INGRESS_MAP_CKSUMV4           (1U << 2)
+#define RMNET_FLAGS_EGRESS_MAP_CKSUMV4            (1U << 3)
+
+enum {
+	IFLA_RMNET_UNSPEC,
+	IFLA_RMNET_MUX_ID,
+	IFLA_RMNET_FLAGS,
+	__IFLA_RMNET_MAX,
+};
+
+#define IFLA_RMNET_MAX	(__IFLA_RMNET_MAX - 1)
+
+struct ifla_rmnet_flags {
+	__u32	flags;
+	__u32	mask;
+};
+
+#endif /* _LINUX_IF_LINK_H */
diff --git a/include/uapi/linux/if_macsec.h b/include/uapi/linux/if_macsec.h
new file mode 100644
index 0000000..7743993
--- /dev/null
+++ b/include/uapi/linux/if_macsec.h
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * include/uapi/linux/if_macsec.h - MACsec device
+ *
+ * Copyright (c) 2015 Sabrina Dubroca <sd@queasysnail.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _MACSEC_H
+#define _MACSEC_H
+
+#include <linux/types.h>
+
+#define MACSEC_GENL_NAME "macsec"
+#define MACSEC_GENL_VERSION 1
+
+#define MACSEC_MAX_KEY_LEN 128
+
+#define MACSEC_KEYID_LEN 16
+
+/* cipher IDs as per IEEE802.1AEbn-2011 */
+#define MACSEC_CIPHER_ID_GCM_AES_128 0x0080C20001000001ULL
+#define MACSEC_CIPHER_ID_GCM_AES_256 0x0080C20001000002ULL
+
+/* deprecated cipher ID for GCM-AES-128 */
+#define MACSEC_DEFAULT_CIPHER_ID     0x0080020001000001ULL
+#define MACSEC_DEFAULT_CIPHER_ALT    MACSEC_CIPHER_ID_GCM_AES_128
+
+#define MACSEC_MIN_ICV_LEN 8
+#define MACSEC_MAX_ICV_LEN 32
+/* upper limit for ICV length as recommended by IEEE802.1AE-2006 */
+#define MACSEC_STD_ICV_LEN 16
+
+enum macsec_attrs {
+	MACSEC_ATTR_UNSPEC,
+	MACSEC_ATTR_IFINDEX,     /* u32, ifindex of the MACsec netdevice */
+	MACSEC_ATTR_RXSC_CONFIG, /* config, nested macsec_rxsc_attrs */
+	MACSEC_ATTR_SA_CONFIG,   /* config, nested macsec_sa_attrs */
+	MACSEC_ATTR_SECY,        /* dump, nested macsec_secy_attrs */
+	MACSEC_ATTR_TXSA_LIST,   /* dump, nested, macsec_sa_attrs for each TXSA */
+	MACSEC_ATTR_RXSC_LIST,   /* dump, nested, macsec_rxsc_attrs for each RXSC */
+	MACSEC_ATTR_TXSC_STATS,  /* dump, nested, macsec_txsc_stats_attr */
+	MACSEC_ATTR_SECY_STATS,  /* dump, nested, macsec_secy_stats_attr */
+	__MACSEC_ATTR_END,
+	NUM_MACSEC_ATTR = __MACSEC_ATTR_END,
+	MACSEC_ATTR_MAX = __MACSEC_ATTR_END - 1,
+};
+
+enum macsec_secy_attrs {
+	MACSEC_SECY_ATTR_UNSPEC,
+	MACSEC_SECY_ATTR_SCI,
+	MACSEC_SECY_ATTR_ENCODING_SA,
+	MACSEC_SECY_ATTR_WINDOW,
+	MACSEC_SECY_ATTR_CIPHER_SUITE,
+	MACSEC_SECY_ATTR_ICV_LEN,
+	MACSEC_SECY_ATTR_PROTECT,
+	MACSEC_SECY_ATTR_REPLAY,
+	MACSEC_SECY_ATTR_OPER,
+	MACSEC_SECY_ATTR_VALIDATE,
+	MACSEC_SECY_ATTR_ENCRYPT,
+	MACSEC_SECY_ATTR_INC_SCI,
+	MACSEC_SECY_ATTR_ES,
+	MACSEC_SECY_ATTR_SCB,
+	MACSEC_SECY_ATTR_PAD,
+	__MACSEC_SECY_ATTR_END,
+	NUM_MACSEC_SECY_ATTR = __MACSEC_SECY_ATTR_END,
+	MACSEC_SECY_ATTR_MAX = __MACSEC_SECY_ATTR_END - 1,
+};
+
+enum macsec_rxsc_attrs {
+	MACSEC_RXSC_ATTR_UNSPEC,
+	MACSEC_RXSC_ATTR_SCI,     /* config/dump, u64 */
+	MACSEC_RXSC_ATTR_ACTIVE,  /* config/dump, u8 0..1 */
+	MACSEC_RXSC_ATTR_SA_LIST, /* dump, nested */
+	MACSEC_RXSC_ATTR_STATS,   /* dump, nested, macsec_rxsc_stats_attr */
+	MACSEC_RXSC_ATTR_PAD,
+	__MACSEC_RXSC_ATTR_END,
+	NUM_MACSEC_RXSC_ATTR = __MACSEC_RXSC_ATTR_END,
+	MACSEC_RXSC_ATTR_MAX = __MACSEC_RXSC_ATTR_END - 1,
+};
+
+enum macsec_sa_attrs {
+	MACSEC_SA_ATTR_UNSPEC,
+	MACSEC_SA_ATTR_AN,     /* config/dump, u8 0..3 */
+	MACSEC_SA_ATTR_ACTIVE, /* config/dump, u8 0..1 */
+	MACSEC_SA_ATTR_PN,     /* config/dump, u32 */
+	MACSEC_SA_ATTR_KEY,    /* config, data */
+	MACSEC_SA_ATTR_KEYID,  /* config/dump, 128-bit */
+	MACSEC_SA_ATTR_STATS,  /* dump, nested, macsec_sa_stats_attr */
+	MACSEC_SA_ATTR_PAD,
+	__MACSEC_SA_ATTR_END,
+	NUM_MACSEC_SA_ATTR = __MACSEC_SA_ATTR_END,
+	MACSEC_SA_ATTR_MAX = __MACSEC_SA_ATTR_END - 1,
+};
+
+enum macsec_nl_commands {
+	MACSEC_CMD_GET_TXSC,
+	MACSEC_CMD_ADD_RXSC,
+	MACSEC_CMD_DEL_RXSC,
+	MACSEC_CMD_UPD_RXSC,
+	MACSEC_CMD_ADD_TXSA,
+	MACSEC_CMD_DEL_TXSA,
+	MACSEC_CMD_UPD_TXSA,
+	MACSEC_CMD_ADD_RXSA,
+	MACSEC_CMD_DEL_RXSA,
+	MACSEC_CMD_UPD_RXSA,
+};
+
+/* u64 per-RXSC stats */
+enum macsec_rxsc_stats_attr {
+	MACSEC_RXSC_STATS_ATTR_UNSPEC,
+	MACSEC_RXSC_STATS_ATTR_IN_OCTETS_VALIDATED,
+	MACSEC_RXSC_STATS_ATTR_IN_OCTETS_DECRYPTED,
+	MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNCHECKED,
+	MACSEC_RXSC_STATS_ATTR_IN_PKTS_DELAYED,
+	MACSEC_RXSC_STATS_ATTR_IN_PKTS_OK,
+	MACSEC_RXSC_STATS_ATTR_IN_PKTS_INVALID,
+	MACSEC_RXSC_STATS_ATTR_IN_PKTS_LATE,
+	MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_VALID,
+	MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_USING_SA,
+	MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNUSED_SA,
+	MACSEC_RXSC_STATS_ATTR_PAD,
+	__MACSEC_RXSC_STATS_ATTR_END,
+	NUM_MACSEC_RXSC_STATS_ATTR = __MACSEC_RXSC_STATS_ATTR_END,
+	MACSEC_RXSC_STATS_ATTR_MAX = __MACSEC_RXSC_STATS_ATTR_END - 1,
+};
+
+/* u32 per-{RX,TX}SA stats */
+enum macsec_sa_stats_attr {
+	MACSEC_SA_STATS_ATTR_UNSPEC,
+	MACSEC_SA_STATS_ATTR_IN_PKTS_OK,
+	MACSEC_SA_STATS_ATTR_IN_PKTS_INVALID,
+	MACSEC_SA_STATS_ATTR_IN_PKTS_NOT_VALID,
+	MACSEC_SA_STATS_ATTR_IN_PKTS_NOT_USING_SA,
+	MACSEC_SA_STATS_ATTR_IN_PKTS_UNUSED_SA,
+	MACSEC_SA_STATS_ATTR_OUT_PKTS_PROTECTED,
+	MACSEC_SA_STATS_ATTR_OUT_PKTS_ENCRYPTED,
+	__MACSEC_SA_STATS_ATTR_END,
+	NUM_MACSEC_SA_STATS_ATTR = __MACSEC_SA_STATS_ATTR_END,
+	MACSEC_SA_STATS_ATTR_MAX = __MACSEC_SA_STATS_ATTR_END - 1,
+};
+
+/* u64 per-TXSC stats */
+enum macsec_txsc_stats_attr {
+	MACSEC_TXSC_STATS_ATTR_UNSPEC,
+	MACSEC_TXSC_STATS_ATTR_OUT_PKTS_PROTECTED,
+	MACSEC_TXSC_STATS_ATTR_OUT_PKTS_ENCRYPTED,
+	MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_PROTECTED,
+	MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_ENCRYPTED,
+	MACSEC_TXSC_STATS_ATTR_PAD,
+	__MACSEC_TXSC_STATS_ATTR_END,
+	NUM_MACSEC_TXSC_STATS_ATTR = __MACSEC_TXSC_STATS_ATTR_END,
+	MACSEC_TXSC_STATS_ATTR_MAX = __MACSEC_TXSC_STATS_ATTR_END - 1,
+};
+
+/* u64 per-SecY stats */
+enum macsec_secy_stats_attr {
+	MACSEC_SECY_STATS_ATTR_UNSPEC,
+	MACSEC_SECY_STATS_ATTR_OUT_PKTS_UNTAGGED,
+	MACSEC_SECY_STATS_ATTR_IN_PKTS_UNTAGGED,
+	MACSEC_SECY_STATS_ATTR_OUT_PKTS_TOO_LONG,
+	MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_TAG,
+	MACSEC_SECY_STATS_ATTR_IN_PKTS_BAD_TAG,
+	MACSEC_SECY_STATS_ATTR_IN_PKTS_UNKNOWN_SCI,
+	MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_SCI,
+	MACSEC_SECY_STATS_ATTR_IN_PKTS_OVERRUN,
+	MACSEC_SECY_STATS_ATTR_PAD,
+	__MACSEC_SECY_STATS_ATTR_END,
+	NUM_MACSEC_SECY_STATS_ATTR = __MACSEC_SECY_STATS_ATTR_END,
+	MACSEC_SECY_STATS_ATTR_MAX = __MACSEC_SECY_STATS_ATTR_END - 1,
+};
+
+#endif /* _MACSEC_H */
diff --git a/include/uapi/linux/if_packet.h b/include/uapi/linux/if_packet.h
new file mode 100644
index 0000000..3d884d6
--- /dev/null
+++ b/include/uapi/linux/if_packet.h
@@ -0,0 +1,304 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_IF_PACKET_H
+#define __LINUX_IF_PACKET_H
+
+#include <linux/types.h>
+
+struct sockaddr_pkt {
+	unsigned short spkt_family;
+	unsigned char spkt_device[14];
+	__be16 spkt_protocol;
+};
+
+struct sockaddr_ll {
+	unsigned short	sll_family;
+	__be16		sll_protocol;
+	int		sll_ifindex;
+	unsigned short	sll_hatype;
+	unsigned char	sll_pkttype;
+	unsigned char	sll_halen;
+	unsigned char	sll_addr[8];
+};
+
+/* Packet types */
+
+#define PACKET_HOST		0		/* To us		*/
+#define PACKET_BROADCAST	1		/* To all		*/
+#define PACKET_MULTICAST	2		/* To group		*/
+#define PACKET_OTHERHOST	3		/* To someone else 	*/
+#define PACKET_OUTGOING		4		/* Outgoing of any type */
+#define PACKET_LOOPBACK		5		/* MC/BRD frame looped back */
+#define PACKET_USER		6		/* To user space	*/
+#define PACKET_KERNEL		7		/* To kernel space	*/
+/* Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space */
+#define PACKET_FASTROUTE	6		/* Fastrouted frame	*/
+
+/* Packet socket options */
+
+#define PACKET_ADD_MEMBERSHIP		1
+#define PACKET_DROP_MEMBERSHIP		2
+#define PACKET_RECV_OUTPUT		3
+/* Value 4 is still used by obsolete turbo-packet. */
+#define PACKET_RX_RING			5
+#define PACKET_STATISTICS		6
+#define PACKET_COPY_THRESH		7
+#define PACKET_AUXDATA			8
+#define PACKET_ORIGDEV			9
+#define PACKET_VERSION			10
+#define PACKET_HDRLEN			11
+#define PACKET_RESERVE			12
+#define PACKET_TX_RING			13
+#define PACKET_LOSS			14
+#define PACKET_VNET_HDR			15
+#define PACKET_TX_TIMESTAMP		16
+#define PACKET_TIMESTAMP		17
+#define PACKET_FANOUT			18
+#define PACKET_TX_HAS_OFF		19
+#define PACKET_QDISC_BYPASS		20
+#define PACKET_ROLLOVER_STATS		21
+#define PACKET_FANOUT_DATA		22
+#define PACKET_IGNORE_OUTGOING		23
+
+#define PACKET_FANOUT_HASH		0
+#define PACKET_FANOUT_LB		1
+#define PACKET_FANOUT_CPU		2
+#define PACKET_FANOUT_ROLLOVER		3
+#define PACKET_FANOUT_RND		4
+#define PACKET_FANOUT_QM		5
+#define PACKET_FANOUT_CBPF		6
+#define PACKET_FANOUT_EBPF		7
+#define PACKET_FANOUT_FLAG_ROLLOVER	0x1000
+#define PACKET_FANOUT_FLAG_UNIQUEID	0x2000
+#define PACKET_FANOUT_FLAG_DEFRAG	0x8000
+
+struct tpacket_stats {
+	unsigned int	tp_packets;
+	unsigned int	tp_drops;
+};
+
+struct tpacket_stats_v3 {
+	unsigned int	tp_packets;
+	unsigned int	tp_drops;
+	unsigned int	tp_freeze_q_cnt;
+};
+
+struct tpacket_rollover_stats {
+	__aligned_u64	tp_all;
+	__aligned_u64	tp_huge;
+	__aligned_u64	tp_failed;
+};
+
+union tpacket_stats_u {
+	struct tpacket_stats stats1;
+	struct tpacket_stats_v3 stats3;
+};
+
+struct tpacket_auxdata {
+	__u32		tp_status;
+	__u32		tp_len;
+	__u32		tp_snaplen;
+	__u16		tp_mac;
+	__u16		tp_net;
+	__u16		tp_vlan_tci;
+	__u16		tp_vlan_tpid;
+};
+
+/* Rx ring - header status */
+#define TP_STATUS_KERNEL		      0
+#define TP_STATUS_USER			(1 << 0)
+#define TP_STATUS_COPY			(1 << 1)
+#define TP_STATUS_LOSING		(1 << 2)
+#define TP_STATUS_CSUMNOTREADY		(1 << 3)
+#define TP_STATUS_VLAN_VALID		(1 << 4) /* auxdata has valid tp_vlan_tci */
+#define TP_STATUS_BLK_TMO		(1 << 5)
+#define TP_STATUS_VLAN_TPID_VALID	(1 << 6) /* auxdata has valid tp_vlan_tpid */
+#define TP_STATUS_CSUM_VALID		(1 << 7)
+
+/* Tx ring - header status */
+#define TP_STATUS_AVAILABLE	      0
+#define TP_STATUS_SEND_REQUEST	(1 << 0)
+#define TP_STATUS_SENDING	(1 << 1)
+#define TP_STATUS_WRONG_FORMAT	(1 << 2)
+
+/* Rx and Tx ring - header status */
+#define TP_STATUS_TS_SOFTWARE		(1 << 29)
+#define TP_STATUS_TS_SYS_HARDWARE	(1 << 30) /* deprecated, never set */
+#define TP_STATUS_TS_RAW_HARDWARE	(1U << 31)
+
+/* Rx ring - feature request bits */
+#define TP_FT_REQ_FILL_RXHASH	0x1
+
+struct tpacket_hdr {
+	unsigned long	tp_status;
+	unsigned int	tp_len;
+	unsigned int	tp_snaplen;
+	unsigned short	tp_mac;
+	unsigned short	tp_net;
+	unsigned int	tp_sec;
+	unsigned int	tp_usec;
+};
+
+#define TPACKET_ALIGNMENT	16
+#define TPACKET_ALIGN(x)	(((x)+TPACKET_ALIGNMENT-1)&~(TPACKET_ALIGNMENT-1))
+#define TPACKET_HDRLEN		(TPACKET_ALIGN(sizeof(struct tpacket_hdr)) + sizeof(struct sockaddr_ll))
+
+struct tpacket2_hdr {
+	__u32		tp_status;
+	__u32		tp_len;
+	__u32		tp_snaplen;
+	__u16		tp_mac;
+	__u16		tp_net;
+	__u32		tp_sec;
+	__u32		tp_nsec;
+	__u16		tp_vlan_tci;
+	__u16		tp_vlan_tpid;
+	__u8		tp_padding[4];
+};
+
+struct tpacket_hdr_variant1 {
+	__u32	tp_rxhash;
+	__u32	tp_vlan_tci;
+	__u16	tp_vlan_tpid;
+	__u16	tp_padding;
+};
+
+struct tpacket3_hdr {
+	__u32		tp_next_offset;
+	__u32		tp_sec;
+	__u32		tp_nsec;
+	__u32		tp_snaplen;
+	__u32		tp_len;
+	__u32		tp_status;
+	__u16		tp_mac;
+	__u16		tp_net;
+	/* pkt_hdr variants */
+	union {
+		struct tpacket_hdr_variant1 hv1;
+	};
+	__u8		tp_padding[8];
+};
+
+struct tpacket_bd_ts {
+	unsigned int ts_sec;
+	union {
+		unsigned int ts_usec;
+		unsigned int ts_nsec;
+	};
+};
+
+struct tpacket_hdr_v1 {
+	__u32	block_status;
+	__u32	num_pkts;
+	__u32	offset_to_first_pkt;
+
+	/* Number of valid bytes (including padding)
+	 * blk_len <= tp_block_size
+	 */
+	__u32	blk_len;
+
+	/*
+	 * Quite a few uses of sequence number:
+	 * 1. Make sure cache flush etc worked.
+	 *    Well, one can argue - why not use the increasing ts below?
+	 *    But look at 2. below first.
+	 * 2. When you pass around blocks to other user space decoders,
+	 *    you can see which blk[s] is[are] outstanding etc.
+	 * 3. Validate kernel code.
+	 */
+	__aligned_u64	seq_num;
+
+	/*
+	 * ts_last_pkt:
+	 *
+	 * Case 1.	Block has 'N'(N >=1) packets and TMO'd(timed out)
+	 *		ts_last_pkt == 'time-stamp of last packet' and NOT the
+	 *		time when the timer fired and the block was closed.
+	 *		By providing the ts of the last packet we can absolutely
+	 *		guarantee that time-stamp wise, the first packet in the
+	 *		next block will never precede the last packet of the
+	 *		previous block.
+	 * Case 2.	Block has zero packets and TMO'd
+	 *		ts_last_pkt = time when the timer fired and the block
+	 *		was closed.
+	 * Case 3.	Block has 'N' packets and NO TMO.
+	 *		ts_last_pkt = time-stamp of the last pkt in the block.
+	 *
+	 * ts_first_pkt:
+	 *		Is always the time-stamp when the block was opened.
+	 *		Case a)	ZERO packets
+	 *			No packets to deal with but atleast you know the
+	 *			time-interval of this block.
+	 *		Case b) Non-zero packets
+	 *			Use the ts of the first packet in the block.
+	 *
+	 */
+	struct tpacket_bd_ts	ts_first_pkt, ts_last_pkt;
+};
+
+union tpacket_bd_header_u {
+	struct tpacket_hdr_v1 bh1;
+};
+
+struct tpacket_block_desc {
+	__u32 version;
+	__u32 offset_to_priv;
+	union tpacket_bd_header_u hdr;
+};
+
+#define TPACKET2_HDRLEN		(TPACKET_ALIGN(sizeof(struct tpacket2_hdr)) + sizeof(struct sockaddr_ll))
+#define TPACKET3_HDRLEN		(TPACKET_ALIGN(sizeof(struct tpacket3_hdr)) + sizeof(struct sockaddr_ll))
+
+enum tpacket_versions {
+	TPACKET_V1,
+	TPACKET_V2,
+	TPACKET_V3
+};
+
+/*
+   Frame structure:
+
+   - Start. Frame must be aligned to TPACKET_ALIGNMENT=16
+   - struct tpacket_hdr
+   - pad to TPACKET_ALIGNMENT=16
+   - struct sockaddr_ll
+   - Gap, chosen so that packet data (Start+tp_net) alignes to TPACKET_ALIGNMENT=16
+   - Start+tp_mac: [ Optional MAC header ]
+   - Start+tp_net: Packet data, aligned to TPACKET_ALIGNMENT=16.
+   - Pad to align to TPACKET_ALIGNMENT=16
+ */
+
+struct tpacket_req {
+	unsigned int	tp_block_size;	/* Minimal size of contiguous block */
+	unsigned int	tp_block_nr;	/* Number of blocks */
+	unsigned int	tp_frame_size;	/* Size of frame */
+	unsigned int	tp_frame_nr;	/* Total number of frames */
+};
+
+struct tpacket_req3 {
+	unsigned int	tp_block_size;	/* Minimal size of contiguous block */
+	unsigned int	tp_block_nr;	/* Number of blocks */
+	unsigned int	tp_frame_size;	/* Size of frame */
+	unsigned int	tp_frame_nr;	/* Total number of frames */
+	unsigned int	tp_retire_blk_tov; /* timeout in msecs */
+	unsigned int	tp_sizeof_priv; /* offset to private data area */
+	unsigned int	tp_feature_req_word;
+};
+
+union tpacket_req_u {
+	struct tpacket_req	req;
+	struct tpacket_req3	req3;
+};
+
+struct packet_mreq {
+	int		mr_ifindex;
+	unsigned short	mr_type;
+	unsigned short	mr_alen;
+	unsigned char	mr_address[8];
+};
+
+#define PACKET_MR_MULTICAST	0
+#define PACKET_MR_PROMISC	1
+#define PACKET_MR_ALLMULTI	2
+#define PACKET_MR_UNICAST	3
+
+#endif
diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h
new file mode 100644
index 0000000..8489ae0
--- /dev/null
+++ b/include/uapi/linux/if_tun.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ *  Universal TUN/TAP device driver.
+ *  Copyright (C) 1999-2000 Maxim Krasnyansky <max_mk@yahoo.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ */
+
+#ifndef __IF_TUN_H
+#define __IF_TUN_H
+
+#include <linux/types.h>
+#include <linux/if_ether.h>
+#include <linux/filter.h>
+
+/* Read queue size */
+#define TUN_READQ_SIZE	500
+/* TUN device type flags: deprecated. Use IFF_TUN/IFF_TAP instead. */
+#define TUN_TUN_DEV 	IFF_TUN
+#define TUN_TAP_DEV	IFF_TAP
+#define TUN_TYPE_MASK   0x000f
+
+/* Ioctl defines */
+#define TUNSETNOCSUM  _IOW('T', 200, int) 
+#define TUNSETDEBUG   _IOW('T', 201, int) 
+#define TUNSETIFF     _IOW('T', 202, int) 
+#define TUNSETPERSIST _IOW('T', 203, int) 
+#define TUNSETOWNER   _IOW('T', 204, int)
+#define TUNSETLINK    _IOW('T', 205, int)
+#define TUNSETGROUP   _IOW('T', 206, int)
+#define TUNGETFEATURES _IOR('T', 207, unsigned int)
+#define TUNSETOFFLOAD  _IOW('T', 208, unsigned int)
+#define TUNSETTXFILTER _IOW('T', 209, unsigned int)
+#define TUNGETIFF      _IOR('T', 210, unsigned int)
+#define TUNGETSNDBUF   _IOR('T', 211, int)
+#define TUNSETSNDBUF   _IOW('T', 212, int)
+#define TUNATTACHFILTER _IOW('T', 213, struct sock_fprog)
+#define TUNDETACHFILTER _IOW('T', 214, struct sock_fprog)
+#define TUNGETVNETHDRSZ _IOR('T', 215, int)
+#define TUNSETVNETHDRSZ _IOW('T', 216, int)
+#define TUNSETQUEUE  _IOW('T', 217, int)
+#define TUNSETIFINDEX	_IOW('T', 218, unsigned int)
+#define TUNGETFILTER _IOR('T', 219, struct sock_fprog)
+#define TUNSETVNETLE _IOW('T', 220, int)
+#define TUNGETVNETLE _IOR('T', 221, int)
+/* The TUNSETVNETBE and TUNGETVNETBE ioctls are for cross-endian support on
+ * little-endian hosts. Not all kernel configurations support them, but all
+ * configurations that support SET also support GET.
+ */
+#define TUNSETVNETBE _IOW('T', 222, int)
+#define TUNGETVNETBE _IOR('T', 223, int)
+#define TUNSETSTEERINGEBPF _IOR('T', 224, int)
+#define TUNSETFILTEREBPF _IOR('T', 225, int)
+#define TUNSETCARRIER _IOW('T', 226, int)
+#define TUNGETDEVNETNS _IO('T', 227)
+
+/* TUNSETIFF ifr flags */
+#define IFF_TUN		0x0001
+#define IFF_TAP		0x0002
+#define IFF_NAPI	0x0010
+#define IFF_NAPI_FRAGS	0x0020
+#define IFF_NO_PI	0x1000
+/* This flag has no real effect */
+#define IFF_ONE_QUEUE	0x2000
+#define IFF_VNET_HDR	0x4000
+#define IFF_TUN_EXCL	0x8000
+#define IFF_MULTI_QUEUE 0x0100
+#define IFF_ATTACH_QUEUE 0x0200
+#define IFF_DETACH_QUEUE 0x0400
+/* read-only flag */
+#define IFF_PERSIST	0x0800
+#define IFF_NOFILTER	0x1000
+
+/* Socket options */
+#define TUN_TX_TIMESTAMP 1
+
+/* Features for GSO (TUNSETOFFLOAD). */
+#define TUN_F_CSUM	0x01	/* You can hand me unchecksummed packets. */
+#define TUN_F_TSO4	0x02	/* I can handle TSO for IPv4 packets */
+#define TUN_F_TSO6	0x04	/* I can handle TSO for IPv6 packets */
+#define TUN_F_TSO_ECN	0x08	/* I can handle TSO with ECN bits. */
+#define TUN_F_UFO	0x10	/* I can handle UFO packets */
+
+/* Protocol info prepended to the packets (when IFF_NO_PI is not set) */
+#define TUN_PKT_STRIP	0x0001
+struct tun_pi {
+	__u16  flags;
+	__be16 proto;
+};
+
+/*
+ * Filter spec (used for SETXXFILTER ioctls)
+ * This stuff is applicable only to the TAP (Ethernet) devices.
+ * If the count is zero the filter is disabled and the driver accepts
+ * all packets (promisc mode).
+ * If the filter is enabled in order to accept broadcast packets
+ * broadcast addr must be explicitly included in the addr list.
+ */
+#define TUN_FLT_ALLMULTI 0x0001 /* Accept all multicast packets */
+struct tun_filter {
+	__u16  flags; /* TUN_FLT_ flags see above */
+	__u16  count; /* Number of addresses */
+	__u8   addr[0][ETH_ALEN];
+};
+
+#endif /* __IF_TUN_H */
diff --git a/include/uapi/linux/if_tunnel.h b/include/uapi/linux/if_tunnel.h
new file mode 100644
index 0000000..c7f0a5e
--- /dev/null
+++ b/include/uapi/linux/if_tunnel.h
@@ -0,0 +1,183 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _IF_TUNNEL_H_
+#define _IF_TUNNEL_H_
+
+#include <linux/types.h>
+#include <linux/if.h>
+#include <linux/ip.h>
+#include <linux/in6.h>
+#include <asm/byteorder.h>
+
+
+#define SIOCGETTUNNEL   (SIOCDEVPRIVATE + 0)
+#define SIOCADDTUNNEL   (SIOCDEVPRIVATE + 1)
+#define SIOCDELTUNNEL   (SIOCDEVPRIVATE + 2)
+#define SIOCCHGTUNNEL   (SIOCDEVPRIVATE + 3)
+#define SIOCGETPRL      (SIOCDEVPRIVATE + 4)
+#define SIOCADDPRL      (SIOCDEVPRIVATE + 5)
+#define SIOCDELPRL      (SIOCDEVPRIVATE + 6)
+#define SIOCCHGPRL      (SIOCDEVPRIVATE + 7)
+#define SIOCGET6RD      (SIOCDEVPRIVATE + 8)
+#define SIOCADD6RD      (SIOCDEVPRIVATE + 9)
+#define SIOCDEL6RD      (SIOCDEVPRIVATE + 10)
+#define SIOCCHG6RD      (SIOCDEVPRIVATE + 11)
+
+#define GRE_CSUM	__cpu_to_be16(0x8000)
+#define GRE_ROUTING	__cpu_to_be16(0x4000)
+#define GRE_KEY		__cpu_to_be16(0x2000)
+#define GRE_SEQ		__cpu_to_be16(0x1000)
+#define GRE_STRICT	__cpu_to_be16(0x0800)
+#define GRE_REC		__cpu_to_be16(0x0700)
+#define GRE_ACK		__cpu_to_be16(0x0080)
+#define GRE_FLAGS	__cpu_to_be16(0x0078)
+#define GRE_VERSION	__cpu_to_be16(0x0007)
+
+#define GRE_IS_CSUM(f)		((f) & GRE_CSUM)
+#define GRE_IS_ROUTING(f)	((f) & GRE_ROUTING)
+#define GRE_IS_KEY(f)		((f) & GRE_KEY)
+#define GRE_IS_SEQ(f)		((f) & GRE_SEQ)
+#define GRE_IS_STRICT(f)	((f) & GRE_STRICT)
+#define GRE_IS_REC(f)		((f) & GRE_REC)
+#define GRE_IS_ACK(f)		((f) & GRE_ACK)
+
+#define GRE_VERSION_0		__cpu_to_be16(0x0000)
+#define GRE_VERSION_1		__cpu_to_be16(0x0001)
+#define GRE_PROTO_PPP		__cpu_to_be16(0x880b)
+#define GRE_PPTP_KEY_MASK	__cpu_to_be32(0xffff)
+
+struct ip_tunnel_parm {
+	char			name[IFNAMSIZ];
+	int			link;
+	__be16			i_flags;
+	__be16			o_flags;
+	__be32			i_key;
+	__be32			o_key;
+	struct iphdr		iph;
+};
+
+enum {
+	IFLA_IPTUN_UNSPEC,
+	IFLA_IPTUN_LINK,
+	IFLA_IPTUN_LOCAL,
+	IFLA_IPTUN_REMOTE,
+	IFLA_IPTUN_TTL,
+	IFLA_IPTUN_TOS,
+	IFLA_IPTUN_ENCAP_LIMIT,
+	IFLA_IPTUN_FLOWINFO,
+	IFLA_IPTUN_FLAGS,
+	IFLA_IPTUN_PROTO,
+	IFLA_IPTUN_PMTUDISC,
+	IFLA_IPTUN_6RD_PREFIX,
+	IFLA_IPTUN_6RD_RELAY_PREFIX,
+	IFLA_IPTUN_6RD_PREFIXLEN,
+	IFLA_IPTUN_6RD_RELAY_PREFIXLEN,
+	IFLA_IPTUN_ENCAP_TYPE,
+	IFLA_IPTUN_ENCAP_FLAGS,
+	IFLA_IPTUN_ENCAP_SPORT,
+	IFLA_IPTUN_ENCAP_DPORT,
+	IFLA_IPTUN_COLLECT_METADATA,
+	IFLA_IPTUN_FWMARK,
+	__IFLA_IPTUN_MAX,
+};
+#define IFLA_IPTUN_MAX	(__IFLA_IPTUN_MAX - 1)
+
+enum tunnel_encap_types {
+	TUNNEL_ENCAP_NONE,
+	TUNNEL_ENCAP_FOU,
+	TUNNEL_ENCAP_GUE,
+	TUNNEL_ENCAP_MPLS,
+};
+
+#define TUNNEL_ENCAP_FLAG_CSUM		(1<<0)
+#define TUNNEL_ENCAP_FLAG_CSUM6		(1<<1)
+#define TUNNEL_ENCAP_FLAG_REMCSUM	(1<<2)
+
+/* SIT-mode i_flags */
+#define	SIT_ISATAP	0x0001
+
+struct ip_tunnel_prl {
+	__be32			addr;
+	__u16			flags;
+	__u16			__reserved;
+	__u32			datalen;
+	__u32			__reserved2;
+	/* data follows */
+};
+
+/* PRL flags */
+#define	PRL_DEFAULT		0x0001
+
+struct ip_tunnel_6rd {
+	struct in6_addr		prefix;
+	__be32			relay_prefix;
+	__u16			prefixlen;
+	__u16			relay_prefixlen;
+};
+
+enum {
+	IFLA_GRE_UNSPEC,
+	IFLA_GRE_LINK,
+	IFLA_GRE_IFLAGS,
+	IFLA_GRE_OFLAGS,
+	IFLA_GRE_IKEY,
+	IFLA_GRE_OKEY,
+	IFLA_GRE_LOCAL,
+	IFLA_GRE_REMOTE,
+	IFLA_GRE_TTL,
+	IFLA_GRE_TOS,
+	IFLA_GRE_PMTUDISC,
+	IFLA_GRE_ENCAP_LIMIT,
+	IFLA_GRE_FLOWINFO,
+	IFLA_GRE_FLAGS,
+	IFLA_GRE_ENCAP_TYPE,
+	IFLA_GRE_ENCAP_FLAGS,
+	IFLA_GRE_ENCAP_SPORT,
+	IFLA_GRE_ENCAP_DPORT,
+	IFLA_GRE_COLLECT_METADATA,
+	IFLA_GRE_IGNORE_DF,
+	IFLA_GRE_FWMARK,
+	IFLA_GRE_ERSPAN_INDEX,
+	IFLA_GRE_ERSPAN_VER,
+	IFLA_GRE_ERSPAN_DIR,
+	IFLA_GRE_ERSPAN_HWID,
+	__IFLA_GRE_MAX,
+};
+
+#define IFLA_GRE_MAX	(__IFLA_GRE_MAX - 1)
+
+/* VTI-mode i_flags */
+#define VTI_ISVTI ((__be16)0x0001)
+
+enum {
+	IFLA_VTI_UNSPEC,
+	IFLA_VTI_LINK,
+	IFLA_VTI_IKEY,
+	IFLA_VTI_OKEY,
+	IFLA_VTI_LOCAL,
+	IFLA_VTI_REMOTE,
+	IFLA_VTI_FWMARK,
+	__IFLA_VTI_MAX,
+};
+
+#define IFLA_VTI_MAX	(__IFLA_VTI_MAX - 1)
+
+#define TUNNEL_CSUM		__cpu_to_be16(0x01)
+#define TUNNEL_ROUTING		__cpu_to_be16(0x02)
+#define TUNNEL_KEY		__cpu_to_be16(0x04)
+#define TUNNEL_SEQ		__cpu_to_be16(0x08)
+#define TUNNEL_STRICT		__cpu_to_be16(0x10)
+#define TUNNEL_REC		__cpu_to_be16(0x20)
+#define TUNNEL_VERSION		__cpu_to_be16(0x40)
+#define TUNNEL_NO_KEY		__cpu_to_be16(0x80)
+#define TUNNEL_DONT_FRAGMENT    __cpu_to_be16(0x0100)
+#define TUNNEL_OAM		__cpu_to_be16(0x0200)
+#define TUNNEL_CRIT_OPT		__cpu_to_be16(0x0400)
+#define TUNNEL_GENEVE_OPT	__cpu_to_be16(0x0800)
+#define TUNNEL_VXLAN_OPT	__cpu_to_be16(0x1000)
+#define TUNNEL_NOCACHE		__cpu_to_be16(0x2000)
+#define TUNNEL_ERSPAN_OPT	__cpu_to_be16(0x4000)
+
+#define TUNNEL_OPTIONS_PRESENT \
+		(TUNNEL_GENEVE_OPT | TUNNEL_VXLAN_OPT | TUNNEL_ERSPAN_OPT)
+
+#endif /* _IF_TUNNEL_H_ */
diff --git a/include/uapi/linux/if_vlan.h b/include/uapi/linux/if_vlan.h
new file mode 100644
index 0000000..04bca79
--- /dev/null
+++ b/include/uapi/linux/if_vlan.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * VLAN		An implementation of 802.1Q VLAN tagging.
+ *
+ * Authors:	Ben Greear <greearb@candelatech.com>
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ *
+ */
+
+#ifndef _LINUX_IF_VLAN_H_
+#define _LINUX_IF_VLAN_H_
+
+
+/* VLAN IOCTLs are found in sockios.h */
+
+/* Passed in vlan_ioctl_args structure to determine behaviour. */
+enum vlan_ioctl_cmds {
+	ADD_VLAN_CMD,
+	DEL_VLAN_CMD,
+	SET_VLAN_INGRESS_PRIORITY_CMD,
+	SET_VLAN_EGRESS_PRIORITY_CMD,
+	GET_VLAN_INGRESS_PRIORITY_CMD,
+	GET_VLAN_EGRESS_PRIORITY_CMD,
+	SET_VLAN_NAME_TYPE_CMD,
+	SET_VLAN_FLAG_CMD,
+	GET_VLAN_REALDEV_NAME_CMD, /* If this works, you know it's a VLAN device, btw */
+	GET_VLAN_VID_CMD /* Get the VID of this VLAN (specified by name) */
+};
+
+enum vlan_flags {
+	VLAN_FLAG_REORDER_HDR		= 0x1,
+	VLAN_FLAG_GVRP			= 0x2,
+	VLAN_FLAG_LOOSE_BINDING		= 0x4,
+	VLAN_FLAG_MVRP			= 0x8,
+	VLAN_FLAG_BRIDGE_BINDING	= 0x10,
+};
+
+enum vlan_name_types {
+	VLAN_NAME_TYPE_PLUS_VID, /* Name will look like:  vlan0005 */
+	VLAN_NAME_TYPE_RAW_PLUS_VID, /* name will look like:  eth1.0005 */
+	VLAN_NAME_TYPE_PLUS_VID_NO_PAD, /* Name will look like:  vlan5 */
+	VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, /* Name will look like:  eth0.5 */
+	VLAN_NAME_TYPE_HIGHEST
+};
+
+struct vlan_ioctl_args {
+	int cmd; /* Should be one of the vlan_ioctl_cmds enum above. */
+	char device1[24];
+
+        union {
+		char device2[24];
+		int VID;
+		unsigned int skb_priority;
+		unsigned int name_type;
+		unsigned int bind_type;
+		unsigned int flag; /* Matches vlan_dev_priv flags */
+        } u;
+
+	short vlan_qos;   
+};
+
+#endif /* _LINUX_IF_VLAN_H_ */
diff --git a/include/uapi/linux/ife.h b/include/uapi/linux/ife.h
new file mode 100644
index 0000000..bdd953c
--- /dev/null
+++ b/include/uapi/linux/ife.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __UAPI_IFE_H
+#define __UAPI_IFE_H
+
+#define IFE_METAHDRLEN 2
+
+enum {
+	IFE_META_SKBMARK = 1,
+	IFE_META_HASHID,
+	IFE_META_PRIO,
+	IFE_META_QMAP,
+	IFE_META_TCINDEX,
+	__IFE_META_MAX
+};
+
+/*Can be overridden at runtime by module option*/
+#define IFE_META_MAX (__IFE_META_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/ila.h b/include/uapi/linux/ila.h
new file mode 100644
index 0000000..6a6c97c
--- /dev/null
+++ b/include/uapi/linux/ila.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* ila.h - ILA Interface */
+
+#ifndef _LINUX_ILA_H
+#define _LINUX_ILA_H
+
+/* NETLINK_GENERIC related info */
+#define ILA_GENL_NAME		"ila"
+#define ILA_GENL_VERSION	0x1
+
+enum {
+	ILA_ATTR_UNSPEC,
+	ILA_ATTR_LOCATOR,			/* u64 */
+	ILA_ATTR_IDENTIFIER,			/* u64 */
+	ILA_ATTR_LOCATOR_MATCH,			/* u64 */
+	ILA_ATTR_IFINDEX,			/* s32 */
+	ILA_ATTR_DIR,				/* u32 */
+	ILA_ATTR_PAD,
+	ILA_ATTR_CSUM_MODE,			/* u8 */
+	ILA_ATTR_IDENT_TYPE,			/* u8 */
+	ILA_ATTR_HOOK_TYPE,			/* u8 */
+
+	__ILA_ATTR_MAX,
+};
+
+#define ILA_ATTR_MAX		(__ILA_ATTR_MAX - 1)
+
+enum {
+	ILA_CMD_UNSPEC,
+	ILA_CMD_ADD,
+	ILA_CMD_DEL,
+	ILA_CMD_GET,
+	ILA_CMD_FLUSH,
+
+	__ILA_CMD_MAX,
+};
+
+#define ILA_CMD_MAX	(__ILA_CMD_MAX - 1)
+
+#define ILA_DIR_IN	(1 << 0)
+#define ILA_DIR_OUT	(1 << 1)
+
+enum {
+	ILA_CSUM_ADJUST_TRANSPORT,
+	ILA_CSUM_NEUTRAL_MAP,
+	ILA_CSUM_NO_ACTION,
+	ILA_CSUM_NEUTRAL_MAP_AUTO,
+};
+
+enum {
+	ILA_ATYPE_IID = 0,
+	ILA_ATYPE_LUID,
+	ILA_ATYPE_VIRT_V4,
+	ILA_ATYPE_VIRT_UNI_V6,
+	ILA_ATYPE_VIRT_MULTI_V6,
+	ILA_ATYPE_NONLOCAL_ADDR,
+	ILA_ATYPE_RSVD_1,
+	ILA_ATYPE_RSVD_2,
+
+	ILA_ATYPE_USE_FORMAT = 32, /* Get type from type field in identifier */
+};
+
+enum {
+	ILA_HOOK_ROUTE_OUTPUT,
+	ILA_HOOK_ROUTE_INPUT,
+};
+
+#endif /* _LINUX_ILA_H */
diff --git a/include/uapi/linux/in.h b/include/uapi/linux/in.h
new file mode 100644
index 0000000..ac079ab
--- /dev/null
+++ b/include/uapi/linux/in.h
@@ -0,0 +1,306 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Definitions of the Internet Protocol.
+ *
+ * Version:	@(#)in.h	1.0.1	04/21/93
+ *
+ * Authors:	Original taken from the GNU Project <netinet/in.h> file.
+ *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ */
+#ifndef _LINUX_IN_H
+#define _LINUX_IN_H
+
+#include <linux/types.h>
+#include <linux/libc-compat.h>
+#include <linux/socket.h>
+
+#if __UAPI_DEF_IN_IPPROTO
+/* Standard well-defined IP protocols.  */
+enum {
+  IPPROTO_IP = 0,		/* Dummy protocol for TCP		*/
+#define IPPROTO_IP		IPPROTO_IP
+  IPPROTO_ICMP = 1,		/* Internet Control Message Protocol	*/
+#define IPPROTO_ICMP		IPPROTO_ICMP
+  IPPROTO_IGMP = 2,		/* Internet Group Management Protocol	*/
+#define IPPROTO_IGMP		IPPROTO_IGMP
+  IPPROTO_IPIP = 4,		/* IPIP tunnels (older KA9Q tunnels use 94) */
+#define IPPROTO_IPIP		IPPROTO_IPIP
+  IPPROTO_TCP = 6,		/* Transmission Control Protocol	*/
+#define IPPROTO_TCP		IPPROTO_TCP
+  IPPROTO_EGP = 8,		/* Exterior Gateway Protocol		*/
+#define IPPROTO_EGP		IPPROTO_EGP
+  IPPROTO_PUP = 12,		/* PUP protocol				*/
+#define IPPROTO_PUP		IPPROTO_PUP
+  IPPROTO_UDP = 17,		/* User Datagram Protocol		*/
+#define IPPROTO_UDP		IPPROTO_UDP
+  IPPROTO_IDP = 22,		/* XNS IDP protocol			*/
+#define IPPROTO_IDP		IPPROTO_IDP
+  IPPROTO_TP = 29,		/* SO Transport Protocol Class 4	*/
+#define IPPROTO_TP		IPPROTO_TP
+  IPPROTO_DCCP = 33,		/* Datagram Congestion Control Protocol */
+#define IPPROTO_DCCP		IPPROTO_DCCP
+  IPPROTO_IPV6 = 41,		/* IPv6-in-IPv4 tunnelling		*/
+#define IPPROTO_IPV6		IPPROTO_IPV6
+  IPPROTO_RSVP = 46,		/* RSVP Protocol			*/
+#define IPPROTO_RSVP		IPPROTO_RSVP
+  IPPROTO_GRE = 47,		/* Cisco GRE tunnels (rfc 1701,1702)	*/
+#define IPPROTO_GRE		IPPROTO_GRE
+  IPPROTO_ESP = 50,		/* Encapsulation Security Payload protocol */
+#define IPPROTO_ESP		IPPROTO_ESP
+  IPPROTO_AH = 51,		/* Authentication Header protocol	*/
+#define IPPROTO_AH		IPPROTO_AH
+  IPPROTO_MTP = 92,		/* Multicast Transport Protocol		*/
+#define IPPROTO_MTP		IPPROTO_MTP
+  IPPROTO_BEETPH = 94,		/* IP option pseudo header for BEET	*/
+#define IPPROTO_BEETPH		IPPROTO_BEETPH
+  IPPROTO_ENCAP = 98,		/* Encapsulation Header			*/
+#define IPPROTO_ENCAP		IPPROTO_ENCAP
+  IPPROTO_PIM = 103,		/* Protocol Independent Multicast	*/
+#define IPPROTO_PIM		IPPROTO_PIM
+  IPPROTO_COMP = 108,		/* Compression Header Protocol		*/
+#define IPPROTO_COMP		IPPROTO_COMP
+  IPPROTO_SCTP = 132,		/* Stream Control Transport Protocol	*/
+#define IPPROTO_SCTP		IPPROTO_SCTP
+  IPPROTO_UDPLITE = 136,	/* UDP-Lite (RFC 3828)			*/
+#define IPPROTO_UDPLITE		IPPROTO_UDPLITE
+  IPPROTO_MPLS = 137,		/* MPLS in IP (RFC 4023)		*/
+#define IPPROTO_MPLS		IPPROTO_MPLS
+  IPPROTO_RAW = 255,		/* Raw IP packets			*/
+#define IPPROTO_RAW		IPPROTO_RAW
+  IPPROTO_MAX
+};
+#endif
+
+#if __UAPI_DEF_IN_ADDR
+/* Internet address. */
+struct in_addr {
+	__be32	s_addr;
+};
+#endif
+
+#define IP_TOS		1
+#define IP_TTL		2
+#define IP_HDRINCL	3
+#define IP_OPTIONS	4
+#define IP_ROUTER_ALERT	5
+#define IP_RECVOPTS	6
+#define IP_RETOPTS	7
+#define IP_PKTINFO	8
+#define IP_PKTOPTIONS	9
+#define IP_MTU_DISCOVER	10
+#define IP_RECVERR	11
+#define IP_RECVTTL	12
+#define	IP_RECVTOS	13
+#define IP_MTU		14
+#define IP_FREEBIND	15
+#define IP_IPSEC_POLICY	16
+#define IP_XFRM_POLICY	17
+#define IP_PASSSEC	18
+#define IP_TRANSPARENT	19
+
+/* BSD compatibility */
+#define IP_RECVRETOPTS	IP_RETOPTS
+
+/* TProxy original addresses */
+#define IP_ORIGDSTADDR       20
+#define IP_RECVORIGDSTADDR   IP_ORIGDSTADDR
+
+#define IP_MINTTL       21
+#define IP_NODEFRAG     22
+#define IP_CHECKSUM	23
+#define IP_BIND_ADDRESS_NO_PORT	24
+#define IP_RECVFRAGSIZE	25
+
+/* IP_MTU_DISCOVER values */
+#define IP_PMTUDISC_DONT		0	/* Never send DF frames */
+#define IP_PMTUDISC_WANT		1	/* Use per route hints	*/
+#define IP_PMTUDISC_DO			2	/* Always DF		*/
+#define IP_PMTUDISC_PROBE		3       /* Ignore dst pmtu      */
+/* Always use interface mtu (ignores dst pmtu) but don't set DF flag.
+ * Also incoming ICMP frag_needed notifications will be ignored on
+ * this socket to prevent accepting spoofed ones.
+ */
+#define IP_PMTUDISC_INTERFACE		4
+/* weaker version of IP_PMTUDISC_INTERFACE, which allos packets to get
+ * fragmented if they exeed the interface mtu
+ */
+#define IP_PMTUDISC_OMIT		5
+
+#define IP_MULTICAST_IF			32
+#define IP_MULTICAST_TTL 		33
+#define IP_MULTICAST_LOOP 		34
+#define IP_ADD_MEMBERSHIP		35
+#define IP_DROP_MEMBERSHIP		36
+#define IP_UNBLOCK_SOURCE		37
+#define IP_BLOCK_SOURCE			38
+#define IP_ADD_SOURCE_MEMBERSHIP	39
+#define IP_DROP_SOURCE_MEMBERSHIP	40
+#define IP_MSFILTER			41
+#define MCAST_JOIN_GROUP		42
+#define MCAST_BLOCK_SOURCE		43
+#define MCAST_UNBLOCK_SOURCE		44
+#define MCAST_LEAVE_GROUP		45
+#define MCAST_JOIN_SOURCE_GROUP		46
+#define MCAST_LEAVE_SOURCE_GROUP	47
+#define MCAST_MSFILTER			48
+#define IP_MULTICAST_ALL		49
+#define IP_UNICAST_IF			50
+
+#define MCAST_EXCLUDE	0
+#define MCAST_INCLUDE	1
+
+/* These need to appear somewhere around here */
+#define IP_DEFAULT_MULTICAST_TTL        1
+#define IP_DEFAULT_MULTICAST_LOOP       1
+
+/* Request struct for multicast socket ops */
+
+#if __UAPI_DEF_IP_MREQ
+struct ip_mreq  {
+	struct in_addr imr_multiaddr;	/* IP multicast address of group */
+	struct in_addr imr_interface;	/* local IP address of interface */
+};
+
+struct ip_mreqn {
+	struct in_addr	imr_multiaddr;		/* IP multicast address of group */
+	struct in_addr	imr_address;		/* local IP address of interface */
+	int		imr_ifindex;		/* Interface index */
+};
+
+struct ip_mreq_source {
+	__be32		imr_multiaddr;
+	__be32		imr_interface;
+	__be32		imr_sourceaddr;
+};
+
+struct ip_msfilter {
+	__be32		imsf_multiaddr;
+	__be32		imsf_interface;
+	__u32		imsf_fmode;
+	__u32		imsf_numsrc;
+	__be32		imsf_slist[1];
+};
+
+#define IP_MSFILTER_SIZE(numsrc) \
+	(sizeof(struct ip_msfilter) - sizeof(__u32) \
+	+ (numsrc) * sizeof(__u32))
+
+struct group_req {
+	__u32				 gr_interface;	/* interface index */
+	struct __kernel_sockaddr_storage gr_group;	/* group address */
+};
+
+struct group_source_req {
+	__u32				 gsr_interface;	/* interface index */
+	struct __kernel_sockaddr_storage gsr_group;	/* group address */
+	struct __kernel_sockaddr_storage gsr_source;	/* source address */
+};
+
+struct group_filter {
+	__u32				 gf_interface;	/* interface index */
+	struct __kernel_sockaddr_storage gf_group;	/* multicast address */
+	__u32				 gf_fmode;	/* filter mode */
+	__u32				 gf_numsrc;	/* number of sources */
+	struct __kernel_sockaddr_storage gf_slist[1];	/* interface index */
+};
+
+#define GROUP_FILTER_SIZE(numsrc) \
+	(sizeof(struct group_filter) - sizeof(struct __kernel_sockaddr_storage) \
+	+ (numsrc) * sizeof(struct __kernel_sockaddr_storage))
+#endif
+
+#if __UAPI_DEF_IN_PKTINFO
+struct in_pktinfo {
+	int		ipi_ifindex;
+	struct in_addr	ipi_spec_dst;
+	struct in_addr	ipi_addr;
+};
+#endif
+
+/* Structure describing an Internet (IP) socket address. */
+#if  __UAPI_DEF_SOCKADDR_IN
+#define __SOCK_SIZE__	16		/* sizeof(struct sockaddr)	*/
+struct sockaddr_in {
+  __kernel_sa_family_t	sin_family;	/* Address family		*/
+  __be16		sin_port;	/* Port number			*/
+  struct in_addr	sin_addr;	/* Internet address		*/
+
+  /* Pad to size of `struct sockaddr'. */
+  unsigned char		__pad[__SOCK_SIZE__ - sizeof(short int) -
+			sizeof(unsigned short int) - sizeof(struct in_addr)];
+};
+#define sin_zero	__pad		/* for BSD UNIX comp. -FvK	*/
+#endif
+
+#if __UAPI_DEF_IN_CLASS
+/*
+ * Definitions of the bits in an Internet address integer.
+ * On subnets, host and network parts are found according
+ * to the subnet mask, not these masks.
+ */
+#define	IN_CLASSA(a)		((((long int) (a)) & 0x80000000) == 0)
+#define	IN_CLASSA_NET		0xff000000
+#define	IN_CLASSA_NSHIFT	24
+#define	IN_CLASSA_HOST		(0xffffffff & ~IN_CLASSA_NET)
+#define	IN_CLASSA_MAX		128
+
+#define	IN_CLASSB(a)		((((long int) (a)) & 0xc0000000) == 0x80000000)
+#define	IN_CLASSB_NET		0xffff0000
+#define	IN_CLASSB_NSHIFT	16
+#define	IN_CLASSB_HOST		(0xffffffff & ~IN_CLASSB_NET)
+#define	IN_CLASSB_MAX		65536
+
+#define	IN_CLASSC(a)		((((long int) (a)) & 0xe0000000) == 0xc0000000)
+#define	IN_CLASSC_NET		0xffffff00
+#define	IN_CLASSC_NSHIFT	8
+#define	IN_CLASSC_HOST		(0xffffffff & ~IN_CLASSC_NET)
+
+#define	IN_CLASSD(a)		((((long int) (a)) & 0xf0000000) == 0xe0000000)
+#define	IN_MULTICAST(a)		IN_CLASSD(a)
+#define	IN_MULTICAST_NET	0xe0000000
+
+#define	IN_BADCLASS(a)		(((long int) (a) ) == (long int)0xffffffff)
+#define	IN_EXPERIMENTAL(a)	IN_BADCLASS((a))
+
+#define	IN_CLASSE(a)		((((long int) (a)) & 0xf0000000) == 0xf0000000)
+#define	IN_CLASSE_NET		0xffffffff
+#define	IN_CLASSE_NSHIFT	0
+
+/* Address to accept any incoming messages. */
+#define	INADDR_ANY		((unsigned long int) 0x00000000)
+
+/* Address to send to all hosts. */
+#define	INADDR_BROADCAST	((unsigned long int) 0xffffffff)
+
+/* Address indicating an error return. */
+#define	INADDR_NONE		((unsigned long int) 0xffffffff)
+
+/* Network number for local host loopback. */
+#define	IN_LOOPBACKNET		127
+
+/* Address to loopback in software to local host.  */
+#define	INADDR_LOOPBACK		0x7f000001	/* 127.0.0.1   */
+#define	IN_LOOPBACK(a)		((((long int) (a)) & 0xff000000) == 0x7f000000)
+
+/* Defines for Multicast INADDR */
+#define INADDR_UNSPEC_GROUP		0xe0000000U	/* 224.0.0.0   */
+#define INADDR_ALLHOSTS_GROUP		0xe0000001U	/* 224.0.0.1   */
+#define INADDR_ALLRTRS_GROUP		0xe0000002U	/* 224.0.0.2 */
+#define INADDR_ALLSNOOPERS_GROUP	0xe000006aU	/* 224.0.0.106 */
+#define INADDR_MAX_LOCAL_GROUP		0xe00000ffU	/* 224.0.0.255 */
+#endif
+
+/* <asm/byteorder.h> contains the htonl type stuff.. */
+#include <asm/byteorder.h> 
+
+
+#endif /* _LINUX_IN_H */
diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h
new file mode 100644
index 0000000..d016ac9
--- /dev/null
+++ b/include/uapi/linux/in6.h
@@ -0,0 +1,300 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ *	Types and definitions for AF_INET6 
+ *	Linux INET6 implementation 
+ *
+ *	Authors:
+ *	Pedro Roque		<roque@di.fc.ul.pt>	
+ *
+ *	Sources:
+ *	IPv6 Program Interfaces for BSD Systems
+ *      <draft-ietf-ipngwg-bsd-api-05.txt>
+ *
+ *	Advanced Sockets API for IPv6
+ *	<draft-stevens-advanced-api-00.txt>
+ *
+ *	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.
+ */
+
+#ifndef _LINUX_IN6_H
+#define _LINUX_IN6_H
+
+#include <linux/types.h>
+#include <linux/libc-compat.h>
+
+/*
+ *	IPv6 address structure
+ */
+
+#if __UAPI_DEF_IN6_ADDR
+struct in6_addr {
+	union {
+		__u8		u6_addr8[16];
+#if __UAPI_DEF_IN6_ADDR_ALT
+		__be16		u6_addr16[8];
+		__be32		u6_addr32[4];
+#endif
+	} in6_u;
+#define s6_addr			in6_u.u6_addr8
+#if __UAPI_DEF_IN6_ADDR_ALT
+#define s6_addr16		in6_u.u6_addr16
+#define s6_addr32		in6_u.u6_addr32
+#endif
+};
+#endif /* __UAPI_DEF_IN6_ADDR */
+
+#if __UAPI_DEF_SOCKADDR_IN6
+struct sockaddr_in6 {
+	unsigned short int	sin6_family;    /* AF_INET6 */
+	__be16			sin6_port;      /* Transport layer port # */
+	__be32			sin6_flowinfo;  /* IPv6 flow information */
+	struct in6_addr		sin6_addr;      /* IPv6 address */
+	__u32			sin6_scope_id;  /* scope id (new in RFC2553) */
+};
+#endif /* __UAPI_DEF_SOCKADDR_IN6 */
+
+#if __UAPI_DEF_IPV6_MREQ
+struct ipv6_mreq {
+	/* IPv6 multicast address of group */
+	struct in6_addr ipv6mr_multiaddr;
+
+	/* local IPv6 address of interface */
+	int		ipv6mr_ifindex;
+};
+#endif /* __UAPI_DEF_IVP6_MREQ */
+
+#define ipv6mr_acaddr	ipv6mr_multiaddr
+
+struct in6_flowlabel_req {
+	struct in6_addr	flr_dst;
+	__be32	flr_label;
+	__u8	flr_action;
+	__u8	flr_share;
+	__u16	flr_flags;
+	__u16 	flr_expires;
+	__u16	flr_linger;
+	__u32	__flr_pad;
+	/* Options in format of IPV6_PKTOPTIONS */
+};
+
+#define IPV6_FL_A_GET	0
+#define IPV6_FL_A_PUT	1
+#define IPV6_FL_A_RENEW	2
+
+#define IPV6_FL_F_CREATE	1
+#define IPV6_FL_F_EXCL		2
+#define IPV6_FL_F_REFLECT	4
+#define IPV6_FL_F_REMOTE	8
+
+#define IPV6_FL_S_NONE		0
+#define IPV6_FL_S_EXCL		1
+#define IPV6_FL_S_PROCESS	2
+#define IPV6_FL_S_USER		3
+#define IPV6_FL_S_ANY		255
+
+
+/*
+ *	Bitmask constant declarations to help applications select out the 
+ *	flow label and priority fields.
+ *
+ *	Note that this are in host byte order while the flowinfo field of
+ *	sockaddr_in6 is in network byte order.
+ */
+
+#define IPV6_FLOWINFO_FLOWLABEL		0x000fffff
+#define IPV6_FLOWINFO_PRIORITY		0x0ff00000
+
+/* These definitions are obsolete */
+#define IPV6_PRIORITY_UNCHARACTERIZED	0x0000
+#define IPV6_PRIORITY_FILLER		0x0100
+#define IPV6_PRIORITY_UNATTENDED	0x0200
+#define IPV6_PRIORITY_RESERVED1		0x0300
+#define IPV6_PRIORITY_BULK		0x0400
+#define IPV6_PRIORITY_RESERVED2		0x0500
+#define IPV6_PRIORITY_INTERACTIVE	0x0600
+#define IPV6_PRIORITY_CONTROL		0x0700
+#define IPV6_PRIORITY_8			0x0800
+#define IPV6_PRIORITY_9			0x0900
+#define IPV6_PRIORITY_10		0x0a00
+#define IPV6_PRIORITY_11		0x0b00
+#define IPV6_PRIORITY_12		0x0c00
+#define IPV6_PRIORITY_13		0x0d00
+#define IPV6_PRIORITY_14		0x0e00
+#define IPV6_PRIORITY_15		0x0f00
+
+/*
+ *	IPV6 extension headers
+ */
+#if __UAPI_DEF_IPPROTO_V6
+#define IPPROTO_HOPOPTS		0	/* IPv6 hop-by-hop options	*/
+#define IPPROTO_ROUTING		43	/* IPv6 routing header		*/
+#define IPPROTO_FRAGMENT	44	/* IPv6 fragmentation header	*/
+#define IPPROTO_ICMPV6		58	/* ICMPv6			*/
+#define IPPROTO_NONE		59	/* IPv6 no next header		*/
+#define IPPROTO_DSTOPTS		60	/* IPv6 destination options	*/
+#define IPPROTO_MH		135	/* IPv6 mobility header		*/
+#endif /* __UAPI_DEF_IPPROTO_V6 */
+
+/*
+ *	IPv6 TLV options.
+ */
+#define IPV6_TLV_PAD1		0
+#define IPV6_TLV_PADN		1
+#define IPV6_TLV_ROUTERALERT	5
+#define IPV6_TLV_CALIPSO	7	/* RFC 5570 */
+#define IPV6_TLV_JUMBO		194
+#define IPV6_TLV_HAO		201	/* home address option */
+
+/*
+ *	IPV6 socket options
+ */
+#if __UAPI_DEF_IPV6_OPTIONS
+#define IPV6_ADDRFORM		1
+#define IPV6_2292PKTINFO	2
+#define IPV6_2292HOPOPTS	3
+#define IPV6_2292DSTOPTS	4
+#define IPV6_2292RTHDR		5
+#define IPV6_2292PKTOPTIONS	6
+#define IPV6_CHECKSUM		7
+#define IPV6_2292HOPLIMIT	8
+#define IPV6_NEXTHOP		9
+#define IPV6_AUTHHDR		10	/* obsolete */
+#define IPV6_FLOWINFO		11
+
+#define IPV6_UNICAST_HOPS	16
+#define IPV6_MULTICAST_IF	17
+#define IPV6_MULTICAST_HOPS	18
+#define IPV6_MULTICAST_LOOP	19
+#define IPV6_ADD_MEMBERSHIP	20
+#define IPV6_DROP_MEMBERSHIP	21
+#define IPV6_ROUTER_ALERT	22
+#define IPV6_MTU_DISCOVER	23
+#define IPV6_MTU		24
+#define IPV6_RECVERR		25
+#define IPV6_V6ONLY		26
+#define IPV6_JOIN_ANYCAST	27
+#define IPV6_LEAVE_ANYCAST	28
+#define IPV6_MULTICAST_ALL	29
+#define IPV6_ROUTER_ALERT_ISOLATE	30
+
+/* IPV6_MTU_DISCOVER values */
+#define IPV6_PMTUDISC_DONT		0
+#define IPV6_PMTUDISC_WANT		1
+#define IPV6_PMTUDISC_DO		2
+#define IPV6_PMTUDISC_PROBE		3
+/* same as IPV6_PMTUDISC_PROBE, provided for symetry with IPv4
+ * also see comments on IP_PMTUDISC_INTERFACE
+ */
+#define IPV6_PMTUDISC_INTERFACE		4
+/* weaker version of IPV6_PMTUDISC_INTERFACE, which allows packets to
+ * get fragmented if they exceed the interface mtu
+ */
+#define IPV6_PMTUDISC_OMIT		5
+
+/* Flowlabel */
+#define IPV6_FLOWLABEL_MGR	32
+#define IPV6_FLOWINFO_SEND	33
+
+#define IPV6_IPSEC_POLICY	34
+#define IPV6_XFRM_POLICY	35
+#define IPV6_HDRINCL		36
+#endif
+
+/*
+ * Multicast:
+ * Following socket options are shared between IPv4 and IPv6.
+ *
+ * MCAST_JOIN_GROUP		42
+ * MCAST_BLOCK_SOURCE		43
+ * MCAST_UNBLOCK_SOURCE		44
+ * MCAST_LEAVE_GROUP		45
+ * MCAST_JOIN_SOURCE_GROUP	46
+ * MCAST_LEAVE_SOURCE_GROUP	47
+ * MCAST_MSFILTER		48
+ */
+
+/*
+ * Advanced API (RFC3542) (1)
+ *
+ * Note: IPV6_RECVRTHDRDSTOPTS does not exist. see net/ipv6/datagram.c.
+ */
+
+#define IPV6_RECVPKTINFO	49
+#define IPV6_PKTINFO		50
+#define IPV6_RECVHOPLIMIT	51
+#define IPV6_HOPLIMIT		52
+#define IPV6_RECVHOPOPTS	53
+#define IPV6_HOPOPTS		54
+#define IPV6_RTHDRDSTOPTS	55
+#define IPV6_RECVRTHDR		56
+#define IPV6_RTHDR		57
+#define IPV6_RECVDSTOPTS	58
+#define IPV6_DSTOPTS		59
+#define IPV6_RECVPATHMTU	60
+#define IPV6_PATHMTU		61
+#define IPV6_DONTFRAG		62
+#if 0	/* not yet */
+#define IPV6_USE_MIN_MTU	63
+#endif
+
+/*
+ * Netfilter (1)
+ *
+ * Following socket options are used in ip6_tables;
+ * see include/linux/netfilter_ipv6/ip6_tables.h.
+ *
+ * IP6T_SO_SET_REPLACE / IP6T_SO_GET_INFO		64
+ * IP6T_SO_SET_ADD_COUNTERS / IP6T_SO_GET_ENTRIES	65
+ */
+
+/*
+ * Advanced API (RFC3542) (2)
+ */
+#define IPV6_RECVTCLASS		66
+#define IPV6_TCLASS		67
+
+/*
+ * Netfilter (2)
+ *
+ * Following socket options are used in ip6_tables;
+ * see include/linux/netfilter_ipv6/ip6_tables.h.
+ *
+ * IP6T_SO_GET_REVISION_MATCH	68
+ * IP6T_SO_GET_REVISION_TARGET	69
+ * IP6T_SO_ORIGINAL_DST		80
+ */
+
+#define IPV6_AUTOFLOWLABEL	70
+/* RFC5014: Source address selection */
+#define IPV6_ADDR_PREFERENCES	72
+
+#define IPV6_PREFER_SRC_TMP		0x0001
+#define IPV6_PREFER_SRC_PUBLIC		0x0002
+#define IPV6_PREFER_SRC_PUBTMP_DEFAULT	0x0100
+#define IPV6_PREFER_SRC_COA		0x0004
+#define IPV6_PREFER_SRC_HOME		0x0400
+#define IPV6_PREFER_SRC_CGA		0x0008
+#define IPV6_PREFER_SRC_NONCGA		0x0800
+
+/* RFC5082: Generalized Ttl Security Mechanism */
+#define IPV6_MINHOPCOUNT		73
+
+#define IPV6_ORIGDSTADDR        74
+#define IPV6_RECVORIGDSTADDR    IPV6_ORIGDSTADDR
+#define IPV6_TRANSPARENT        75
+#define IPV6_UNICAST_IF         76
+#define IPV6_RECVFRAGSIZE	77
+#define IPV6_FREEBIND		78
+
+/*
+ * Multicast Routing:
+ * see include/uapi/linux/mroute6.h.
+ *
+ * MRT6_BASE			200
+ * ...
+ * MRT6_MAX
+ */
+#endif /* _LINUX_IN6_H */
diff --git a/include/uapi/linux/in_route.h b/include/uapi/linux/in_route.h
new file mode 100644
index 0000000..0cc2c23
--- /dev/null
+++ b/include/uapi/linux/in_route.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_IN_ROUTE_H
+#define _LINUX_IN_ROUTE_H
+
+/* IPv4 routing cache flags */
+
+#define RTCF_DEAD	RTNH_F_DEAD
+#define RTCF_ONLINK	RTNH_F_ONLINK
+
+/* Obsolete flag. About to be deleted */
+#define RTCF_NOPMTUDISC RTM_F_NOPMTUDISC
+
+#define RTCF_NOTIFY	0x00010000
+#define RTCF_DIRECTDST	0x00020000 /* unused */
+#define RTCF_REDIRECTED	0x00040000
+#define RTCF_TPROXY	0x00080000 /* unused */
+
+#define RTCF_FAST	0x00200000 /* unused */
+#define RTCF_MASQ	0x00400000 /* unused */
+#define RTCF_SNAT	0x00800000 /* unused */
+#define RTCF_DOREDIRECT 0x01000000
+#define RTCF_DIRECTSRC	0x04000000
+#define RTCF_DNAT	0x08000000
+#define RTCF_BROADCAST	0x10000000
+#define RTCF_MULTICAST	0x20000000
+#define RTCF_REJECT	0x40000000 /* unused */
+#define RTCF_LOCAL	0x80000000
+
+#define RTCF_NAT	(RTCF_DNAT|RTCF_SNAT)
+
+#define RT_TOS(tos)	((tos)&IPTOS_TOS_MASK)
+
+#endif /* _LINUX_IN_ROUTE_H */
diff --git a/include/uapi/linux/inet_diag.h b/include/uapi/linux/inet_diag.h
new file mode 100644
index 0000000..3dff684
--- /dev/null
+++ b/include/uapi/linux/inet_diag.h
@@ -0,0 +1,214 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _INET_DIAG_H_
+#define _INET_DIAG_H_
+
+#include <linux/types.h>
+
+/* Just some random number */
+#define TCPDIAG_GETSOCK 18
+#define DCCPDIAG_GETSOCK 19
+
+#define INET_DIAG_GETSOCK_MAX 24
+
+/* Socket identity */
+struct inet_diag_sockid {
+	__be16	idiag_sport;
+	__be16	idiag_dport;
+	__be32	idiag_src[4];
+	__be32	idiag_dst[4];
+	__u32	idiag_if;
+	__u32	idiag_cookie[2];
+#define INET_DIAG_NOCOOKIE (~0U)
+};
+
+/* Request structure */
+
+struct inet_diag_req {
+	__u8	idiag_family;		/* Family of addresses. */
+	__u8	idiag_src_len;
+	__u8	idiag_dst_len;
+	__u8	idiag_ext;		/* Query extended information */
+
+	struct inet_diag_sockid id;
+
+	__u32	idiag_states;		/* States to dump */
+	__u32	idiag_dbs;		/* Tables to dump (NI) */
+};
+
+struct inet_diag_req_v2 {
+	__u8	sdiag_family;
+	__u8	sdiag_protocol;
+	__u8	idiag_ext;
+	__u8	pad;
+	__u32	idiag_states;
+	struct inet_diag_sockid id;
+};
+
+/*
+ * SOCK_RAW sockets require the underlied protocol to be
+ * additionally specified so we can use @pad member for
+ * this, but we can't rename it because userspace programs
+ * still may depend on this name. Instead lets use another
+ * structure definition as an alias for struct
+ * @inet_diag_req_v2.
+ */
+struct inet_diag_req_raw {
+	__u8	sdiag_family;
+	__u8	sdiag_protocol;
+	__u8	idiag_ext;
+	__u8	sdiag_raw_protocol;
+	__u32	idiag_states;
+	struct inet_diag_sockid id;
+};
+
+enum {
+	INET_DIAG_REQ_NONE,
+	INET_DIAG_REQ_BYTECODE,
+};
+
+#define INET_DIAG_REQ_MAX INET_DIAG_REQ_BYTECODE
+
+/* Bytecode is sequence of 4 byte commands followed by variable arguments.
+ * All the commands identified by "code" are conditional jumps forward:
+ * to offset cc+"yes" or to offset cc+"no". "yes" is supposed to be
+ * length of the command and its arguments.
+ */
+ 
+struct inet_diag_bc_op {
+	unsigned char	code;
+	unsigned char	yes;
+	unsigned short	no;
+};
+
+enum {
+	INET_DIAG_BC_NOP,
+	INET_DIAG_BC_JMP,
+	INET_DIAG_BC_S_GE,
+	INET_DIAG_BC_S_LE,
+	INET_DIAG_BC_D_GE,
+	INET_DIAG_BC_D_LE,
+	INET_DIAG_BC_AUTO,
+	INET_DIAG_BC_S_COND,
+	INET_DIAG_BC_D_COND,
+	INET_DIAG_BC_DEV_COND,   /* u32 ifindex */
+	INET_DIAG_BC_MARK_COND,
+	INET_DIAG_BC_S_EQ,
+	INET_DIAG_BC_D_EQ,
+};
+
+struct inet_diag_hostcond {
+	__u8	family;
+	__u8	prefix_len;
+	int	port;
+	__be32	addr[0];
+};
+
+struct inet_diag_markcond {
+	__u32 mark;
+	__u32 mask;
+};
+
+/* Base info structure. It contains socket identity (addrs/ports/cookie)
+ * and, alas, the information shown by netstat. */
+struct inet_diag_msg {
+	__u8	idiag_family;
+	__u8	idiag_state;
+	__u8	idiag_timer;
+	__u8	idiag_retrans;
+
+	struct inet_diag_sockid id;
+
+	__u32	idiag_expires;
+	__u32	idiag_rqueue;
+	__u32	idiag_wqueue;
+	__u32	idiag_uid;
+	__u32	idiag_inode;
+};
+
+/* Extensions */
+
+enum {
+	INET_DIAG_NONE,
+	INET_DIAG_MEMINFO,
+	INET_DIAG_INFO,
+	INET_DIAG_VEGASINFO,
+	INET_DIAG_CONG,
+	INET_DIAG_TOS,
+	INET_DIAG_TCLASS,
+	INET_DIAG_SKMEMINFO,
+	INET_DIAG_SHUTDOWN,
+
+	/*
+	 * Next extenstions cannot be requested in struct inet_diag_req_v2:
+	 * its field idiag_ext has only 8 bits.
+	 */
+
+	INET_DIAG_DCTCPINFO,	/* request as INET_DIAG_VEGASINFO */
+	INET_DIAG_PROTOCOL,	/* response attribute only */
+	INET_DIAG_SKV6ONLY,
+	INET_DIAG_LOCALS,
+	INET_DIAG_PEERS,
+	INET_DIAG_PAD,
+	INET_DIAG_MARK,		/* only with CAP_NET_ADMIN */
+	INET_DIAG_BBRINFO,	/* request as INET_DIAG_VEGASINFO */
+	INET_DIAG_CLASS_ID,	/* request as INET_DIAG_TCLASS */
+	INET_DIAG_MD5SIG,
+	INET_DIAG_ULP_INFO,
+	__INET_DIAG_MAX,
+};
+
+#define INET_DIAG_MAX (__INET_DIAG_MAX - 1)
+
+enum {
+	INET_ULP_INFO_UNSPEC,
+	INET_ULP_INFO_NAME,
+	INET_ULP_INFO_TLS,
+	__INET_ULP_INFO_MAX,
+};
+#define INET_ULP_INFO_MAX (__INET_ULP_INFO_MAX - 1)
+
+/* INET_DIAG_MEM */
+
+struct inet_diag_meminfo {
+	__u32	idiag_rmem;
+	__u32	idiag_wmem;
+	__u32	idiag_fmem;
+	__u32	idiag_tmem;
+};
+
+/* INET_DIAG_VEGASINFO */
+
+struct tcpvegas_info {
+	__u32	tcpv_enabled;
+	__u32	tcpv_rttcnt;
+	__u32	tcpv_rtt;
+	__u32	tcpv_minrtt;
+};
+
+/* INET_DIAG_DCTCPINFO */
+
+struct tcp_dctcp_info {
+	__u16	dctcp_enabled;
+	__u16	dctcp_ce_state;
+	__u32	dctcp_alpha;
+	__u32	dctcp_ab_ecn;
+	__u32	dctcp_ab_tot;
+};
+
+/* INET_DIAG_BBRINFO */
+
+struct tcp_bbr_info {
+	/* u64 bw: max-filtered BW (app throughput) estimate in Byte per sec: */
+	__u32	bbr_bw_lo;		/* lower 32 bits of bw */
+	__u32	bbr_bw_hi;		/* upper 32 bits of bw */
+	__u32	bbr_min_rtt;		/* min-filtered RTT in uSec */
+	__u32	bbr_pacing_gain;	/* pacing gain shifted left 8 bits */
+	__u32	bbr_cwnd_gain;		/* cwnd gain shifted left 8 bits */
+};
+
+union tcp_cc_info {
+	struct tcpvegas_info	vegas;
+	struct tcp_dctcp_info	dctcp;
+	struct tcp_bbr_info	bbr;
+};
+#endif /* _INET_DIAG_H_ */
diff --git a/include/uapi/linux/ip.h b/include/uapi/linux/ip.h
new file mode 100644
index 0000000..f4ecd2f
--- /dev/null
+++ b/include/uapi/linux/ip.h
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Definitions for the IP protocol.
+ *
+ * Version:	@(#)ip.h	1.0.2	04/28/93
+ *
+ * Authors:	Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ */
+#ifndef _LINUX_IP_H
+#define _LINUX_IP_H
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+#define IPTOS_TOS_MASK		0x1E
+#define IPTOS_TOS(tos)		((tos)&IPTOS_TOS_MASK)
+#define	IPTOS_LOWDELAY		0x10
+#define	IPTOS_THROUGHPUT	0x08
+#define	IPTOS_RELIABILITY	0x04
+#define	IPTOS_MINCOST		0x02
+
+#define IPTOS_PREC_MASK		0xE0
+#define IPTOS_PREC(tos)		((tos)&IPTOS_PREC_MASK)
+#define IPTOS_PREC_NETCONTROL           0xe0
+#define IPTOS_PREC_INTERNETCONTROL      0xc0
+#define IPTOS_PREC_CRITIC_ECP           0xa0
+#define IPTOS_PREC_FLASHOVERRIDE        0x80
+#define IPTOS_PREC_FLASH                0x60
+#define IPTOS_PREC_IMMEDIATE            0x40
+#define IPTOS_PREC_PRIORITY             0x20
+#define IPTOS_PREC_ROUTINE              0x00
+
+
+/* IP options */
+#define IPOPT_COPY		0x80
+#define IPOPT_CLASS_MASK	0x60
+#define IPOPT_NUMBER_MASK	0x1f
+
+#define	IPOPT_COPIED(o)		((o)&IPOPT_COPY)
+#define	IPOPT_CLASS(o)		((o)&IPOPT_CLASS_MASK)
+#define	IPOPT_NUMBER(o)		((o)&IPOPT_NUMBER_MASK)
+
+#define	IPOPT_CONTROL		0x00
+#define	IPOPT_RESERVED1		0x20
+#define	IPOPT_MEASUREMENT	0x40
+#define	IPOPT_RESERVED2		0x60
+
+#define IPOPT_END	(0 |IPOPT_CONTROL)
+#define IPOPT_NOOP	(1 |IPOPT_CONTROL)
+#define IPOPT_SEC	(2 |IPOPT_CONTROL|IPOPT_COPY)
+#define IPOPT_LSRR	(3 |IPOPT_CONTROL|IPOPT_COPY)
+#define IPOPT_TIMESTAMP	(4 |IPOPT_MEASUREMENT)
+#define IPOPT_CIPSO	(6 |IPOPT_CONTROL|IPOPT_COPY)
+#define IPOPT_RR	(7 |IPOPT_CONTROL)
+#define IPOPT_SID	(8 |IPOPT_CONTROL|IPOPT_COPY)
+#define IPOPT_SSRR	(9 |IPOPT_CONTROL|IPOPT_COPY)
+#define IPOPT_RA	(20|IPOPT_CONTROL|IPOPT_COPY)
+
+#define IPVERSION	4
+#define MAXTTL		255
+#define IPDEFTTL	64
+
+#define IPOPT_OPTVAL 0
+#define IPOPT_OLEN   1
+#define IPOPT_OFFSET 2
+#define IPOPT_MINOFF 4
+#define MAX_IPOPTLEN 40
+#define IPOPT_NOP IPOPT_NOOP
+#define IPOPT_EOL IPOPT_END
+#define IPOPT_TS  IPOPT_TIMESTAMP
+
+#define	IPOPT_TS_TSONLY		0		/* timestamps only */
+#define	IPOPT_TS_TSANDADDR	1		/* timestamps and addresses */
+#define	IPOPT_TS_PRESPEC	3		/* specified modules only */
+
+#define IPV4_BEET_PHMAXLEN 8
+
+struct iphdr {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	__u8	ihl:4,
+		version:4;
+#elif defined (__BIG_ENDIAN_BITFIELD)
+	__u8	version:4,
+  		ihl:4;
+#else
+#error	"Please fix <asm/byteorder.h>"
+#endif
+	__u8	tos;
+	__be16	tot_len;
+	__be16	id;
+	__be16	frag_off;
+	__u8	ttl;
+	__u8	protocol;
+	__sum16	check;
+	__be32	saddr;
+	__be32	daddr;
+	/*The options start here. */
+};
+
+
+struct ip_auth_hdr {
+	__u8  nexthdr;
+	__u8  hdrlen;		/* This one is measured in 32 bit units! */
+	__be16 reserved;
+	__be32 spi;
+	__be32 seq_no;		/* Sequence number */
+	__u8  auth_data[0];	/* Variable len but >=4. Mind the 64 bit alignment! */
+};
+
+struct ip_esp_hdr {
+	__be32 spi;
+	__be32 seq_no;		/* Sequence number */
+	__u8  enc_data[0];	/* Variable len but >=8. Mind the 64 bit alignment! */
+};
+
+struct ip_comp_hdr {
+	__u8 nexthdr;
+	__u8 flags;
+	__be16 cpi;
+};
+
+struct ip_beet_phdr {
+	__u8 nexthdr;
+	__u8 hdrlen;
+	__u8 padlen;
+	__u8 reserved;
+};
+
+/* index values for the variables in ipv4_devconf */
+enum
+{
+	IPV4_DEVCONF_FORWARDING=1,
+	IPV4_DEVCONF_MC_FORWARDING,
+	IPV4_DEVCONF_PROXY_ARP,
+	IPV4_DEVCONF_ACCEPT_REDIRECTS,
+	IPV4_DEVCONF_SECURE_REDIRECTS,
+	IPV4_DEVCONF_SEND_REDIRECTS,
+	IPV4_DEVCONF_SHARED_MEDIA,
+	IPV4_DEVCONF_RP_FILTER,
+	IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE,
+	IPV4_DEVCONF_BOOTP_RELAY,
+	IPV4_DEVCONF_LOG_MARTIANS,
+	IPV4_DEVCONF_TAG,
+	IPV4_DEVCONF_ARPFILTER,
+	IPV4_DEVCONF_MEDIUM_ID,
+	IPV4_DEVCONF_NOXFRM,
+	IPV4_DEVCONF_NOPOLICY,
+	IPV4_DEVCONF_FORCE_IGMP_VERSION,
+	IPV4_DEVCONF_ARP_ANNOUNCE,
+	IPV4_DEVCONF_ARP_IGNORE,
+	IPV4_DEVCONF_PROMOTE_SECONDARIES,
+	IPV4_DEVCONF_ARP_ACCEPT,
+	IPV4_DEVCONF_ARP_NOTIFY,
+	IPV4_DEVCONF_ACCEPT_LOCAL,
+	IPV4_DEVCONF_SRC_VMARK,
+	IPV4_DEVCONF_PROXY_ARP_PVLAN,
+	IPV4_DEVCONF_ROUTE_LOCALNET,
+	IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL,
+	IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL,
+	IPV4_DEVCONF_IGNORE_ROUTES_WITH_LINKDOWN,
+	IPV4_DEVCONF_DROP_UNICAST_IN_L2_MULTICAST,
+	IPV4_DEVCONF_DROP_GRATUITOUS_ARP,
+	IPV4_DEVCONF_BC_FORWARDING,
+	__IPV4_DEVCONF_MAX
+};
+
+#define IPV4_DEVCONF_MAX (__IPV4_DEVCONF_MAX - 1)
+
+#endif /* _LINUX_IP_H */
diff --git a/include/uapi/linux/ip6_tunnel.h b/include/uapi/linux/ip6_tunnel.h
new file mode 100644
index 0000000..0245269
--- /dev/null
+++ b/include/uapi/linux/ip6_tunnel.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _IP6_TUNNEL_H
+#define _IP6_TUNNEL_H
+
+#include <linux/types.h>
+#include <linux/if.h>		/* For IFNAMSIZ. */
+#include <linux/in6.h>		/* For struct in6_addr. */
+
+#define IPV6_TLV_TNL_ENCAP_LIMIT 4
+#define IPV6_DEFAULT_TNL_ENCAP_LIMIT 4
+
+/* don't add encapsulation limit if one isn't present in inner packet */
+#define IP6_TNL_F_IGN_ENCAP_LIMIT 0x1
+/* copy the traffic class field from the inner packet */
+#define IP6_TNL_F_USE_ORIG_TCLASS 0x2
+/* copy the flowlabel from the inner packet */
+#define IP6_TNL_F_USE_ORIG_FLOWLABEL 0x4
+/* being used for Mobile IPv6 */
+#define IP6_TNL_F_MIP6_DEV 0x8
+/* copy DSCP from the outer packet */
+#define IP6_TNL_F_RCV_DSCP_COPY 0x10
+/* copy fwmark from inner packet */
+#define IP6_TNL_F_USE_ORIG_FWMARK 0x20
+/* allow remote endpoint on the local node */
+#define IP6_TNL_F_ALLOW_LOCAL_REMOTE 0x40
+
+struct ip6_tnl_parm {
+	char name[IFNAMSIZ];	/* name of tunnel device */
+	int link;		/* ifindex of underlying L2 interface */
+	__u8 proto;		/* tunnel protocol */
+	__u8 encap_limit;	/* encapsulation limit for tunnel */
+	__u8 hop_limit;		/* hop limit for tunnel */
+	__be32 flowinfo;	/* traffic class and flowlabel for tunnel */
+	__u32 flags;		/* tunnel flags */
+	struct in6_addr laddr;	/* local tunnel end-point address */
+	struct in6_addr raddr;	/* remote tunnel end-point address */
+};
+
+struct ip6_tnl_parm2 {
+	char name[IFNAMSIZ];	/* name of tunnel device */
+	int link;		/* ifindex of underlying L2 interface */
+	__u8 proto;		/* tunnel protocol */
+	__u8 encap_limit;	/* encapsulation limit for tunnel */
+	__u8 hop_limit;		/* hop limit for tunnel */
+	__be32 flowinfo;	/* traffic class and flowlabel for tunnel */
+	__u32 flags;		/* tunnel flags */
+	struct in6_addr laddr;	/* local tunnel end-point address */
+	struct in6_addr raddr;	/* remote tunnel end-point address */
+
+	__be16			i_flags;
+	__be16			o_flags;
+	__be32			i_key;
+	__be32			o_key;
+};
+
+#endif
diff --git a/include/uapi/linux/ipsec.h b/include/uapi/linux/ipsec.h
new file mode 100644
index 0000000..50d8ee1
--- /dev/null
+++ b/include/uapi/linux/ipsec.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_IPSEC_H
+#define _LINUX_IPSEC_H
+
+/* The definitions, required to talk to KAME racoon IKE. */
+
+#include <linux/pfkeyv2.h>
+
+#define IPSEC_PORT_ANY		0
+#define IPSEC_ULPROTO_ANY	255
+#define IPSEC_PROTO_ANY		255
+
+enum {
+	IPSEC_MODE_ANY		= 0,	/* We do not support this for SA */
+	IPSEC_MODE_TRANSPORT	= 1,
+	IPSEC_MODE_TUNNEL	= 2,
+	IPSEC_MODE_BEET         = 3
+};
+
+enum {
+	IPSEC_DIR_ANY		= 0,
+	IPSEC_DIR_INBOUND	= 1,
+	IPSEC_DIR_OUTBOUND	= 2,
+	IPSEC_DIR_FWD		= 3,	/* It is our own */
+	IPSEC_DIR_MAX		= 4,
+	IPSEC_DIR_INVALID	= 5
+};
+
+enum {
+	IPSEC_POLICY_DISCARD	= 0,
+	IPSEC_POLICY_NONE	= 1,
+	IPSEC_POLICY_IPSEC	= 2,
+	IPSEC_POLICY_ENTRUST	= 3,
+	IPSEC_POLICY_BYPASS	= 4
+};
+
+enum {
+	IPSEC_LEVEL_DEFAULT	= 0,
+	IPSEC_LEVEL_USE		= 1,
+	IPSEC_LEVEL_REQUIRE	= 2,
+	IPSEC_LEVEL_UNIQUE	= 3
+};
+
+#define IPSEC_MANUAL_REQID_MAX	0x3fff
+
+#define IPSEC_REPLAYWSIZE  32
+
+#endif	/* _LINUX_IPSEC_H */
diff --git a/include/uapi/linux/kernel.h b/include/uapi/linux/kernel.h
new file mode 100644
index 0000000..d99ffa1
--- /dev/null
+++ b/include/uapi/linux/kernel.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_KERNEL_H
+#define _LINUX_KERNEL_H
+
+#include <linux/sysinfo.h>
+
+/*
+ * 'kernel.h' contains some often-used function prototypes etc
+ */
+#define __ALIGN_KERNEL(x, a)		__ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
+#define __ALIGN_KERNEL_MASK(x, mask)	(((x) + (mask)) & ~(mask))
+
+#define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
+
+#endif /* _LINUX_KERNEL_H */
diff --git a/include/uapi/linux/l2tp.h b/include/uapi/linux/l2tp.h
new file mode 100644
index 0000000..131c3a2
--- /dev/null
+++ b/include/uapi/linux/l2tp.h
@@ -0,0 +1,201 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * L2TP-over-IP socket for L2TPv3.
+ *
+ * Author: James Chapman <jchapman@katalix.com>
+ */
+
+#ifndef _LINUX_L2TP_H_
+#define _LINUX_L2TP_H_
+
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+
+#define IPPROTO_L2TP		115
+
+/**
+ * struct sockaddr_l2tpip - the sockaddr structure for L2TP-over-IP sockets
+ * @l2tp_family:  address family number AF_L2TPIP.
+ * @l2tp_addr:    protocol specific address information
+ * @l2tp_conn_id: connection id of tunnel
+ */
+#define __SOCK_SIZE__	16		/* sizeof(struct sockaddr)	*/
+struct sockaddr_l2tpip {
+	/* The first fields must match struct sockaddr_in */
+	__kernel_sa_family_t l2tp_family; /* AF_INET */
+	__be16		l2tp_unused;	/* INET port number (unused) */
+	struct in_addr	l2tp_addr;	/* Internet address */
+
+	__u32		l2tp_conn_id;	/* Connection ID of tunnel */
+
+	/* Pad to size of `struct sockaddr'. */
+	unsigned char	__pad[__SOCK_SIZE__ -
+			      sizeof(__kernel_sa_family_t) -
+			      sizeof(__be16) - sizeof(struct in_addr) -
+			      sizeof(__u32)];
+};
+
+/**
+ * struct sockaddr_l2tpip6 - the sockaddr structure for L2TP-over-IPv6 sockets
+ * @l2tp_family:  address family number AF_L2TPIP.
+ * @l2tp_addr:    protocol specific address information
+ * @l2tp_conn_id: connection id of tunnel
+ */
+struct sockaddr_l2tpip6 {
+	/* The first fields must match struct sockaddr_in6 */
+	__kernel_sa_family_t l2tp_family; /* AF_INET6 */
+	__be16		l2tp_unused;	/* INET port number (unused) */
+	__be32		l2tp_flowinfo;	/* IPv6 flow information */
+	struct in6_addr	l2tp_addr;	/* IPv6 address */
+	__u32		l2tp_scope_id;	/* scope id (new in RFC2553) */
+	__u32		l2tp_conn_id;	/* Connection ID of tunnel */
+};
+
+/*****************************************************************************
+ *  NETLINK_GENERIC netlink family.
+ *****************************************************************************/
+
+/*
+ * Commands.
+ * Valid TLVs of each command are:-
+ * TUNNEL_CREATE	- CONN_ID, pw_type, netns, ifname, ipinfo, udpinfo, udpcsum
+ * TUNNEL_DELETE	- CONN_ID
+ * TUNNEL_MODIFY	- CONN_ID, udpcsum
+ * TUNNEL_GETSTATS	- CONN_ID, (stats)
+ * TUNNEL_GET		- CONN_ID, (...)
+ * SESSION_CREATE	- SESSION_ID, PW_TYPE, cookie, peer_cookie, l2spec
+ * SESSION_DELETE	- SESSION_ID
+ * SESSION_MODIFY	- SESSION_ID
+ * SESSION_GET		- SESSION_ID, (...)
+ * SESSION_GETSTATS	- SESSION_ID, (stats)
+ *
+ */
+enum {
+	L2TP_CMD_NOOP,
+	L2TP_CMD_TUNNEL_CREATE,
+	L2TP_CMD_TUNNEL_DELETE,
+	L2TP_CMD_TUNNEL_MODIFY,
+	L2TP_CMD_TUNNEL_GET,
+	L2TP_CMD_SESSION_CREATE,
+	L2TP_CMD_SESSION_DELETE,
+	L2TP_CMD_SESSION_MODIFY,
+	L2TP_CMD_SESSION_GET,
+	__L2TP_CMD_MAX,
+};
+
+#define L2TP_CMD_MAX			(__L2TP_CMD_MAX - 1)
+
+/*
+ * ATTR types defined for L2TP
+ */
+enum {
+	L2TP_ATTR_NONE,			/* no data */
+	L2TP_ATTR_PW_TYPE,		/* u16, enum l2tp_pwtype */
+	L2TP_ATTR_ENCAP_TYPE,		/* u16, enum l2tp_encap_type */
+	L2TP_ATTR_OFFSET,		/* u16 (not used) */
+	L2TP_ATTR_DATA_SEQ,		/* u16 (not used) */
+	L2TP_ATTR_L2SPEC_TYPE,		/* u8, enum l2tp_l2spec_type */
+	L2TP_ATTR_L2SPEC_LEN,		/* u8 (not used) */
+	L2TP_ATTR_PROTO_VERSION,	/* u8 */
+	L2TP_ATTR_IFNAME,		/* string */
+	L2TP_ATTR_CONN_ID,		/* u32 */
+	L2TP_ATTR_PEER_CONN_ID,		/* u32 */
+	L2TP_ATTR_SESSION_ID,		/* u32 */
+	L2TP_ATTR_PEER_SESSION_ID,	/* u32 */
+	L2TP_ATTR_UDP_CSUM,		/* u8 */
+	L2TP_ATTR_VLAN_ID,		/* u16 (not used) */
+	L2TP_ATTR_COOKIE,		/* 0, 4 or 8 bytes */
+	L2TP_ATTR_PEER_COOKIE,		/* 0, 4 or 8 bytes */
+	L2TP_ATTR_DEBUG,		/* u32, enum l2tp_debug_flags */
+	L2TP_ATTR_RECV_SEQ,		/* u8 */
+	L2TP_ATTR_SEND_SEQ,		/* u8 */
+	L2TP_ATTR_LNS_MODE,		/* u8 */
+	L2TP_ATTR_USING_IPSEC,		/* u8 */
+	L2TP_ATTR_RECV_TIMEOUT,		/* msec */
+	L2TP_ATTR_FD,			/* int */
+	L2TP_ATTR_IP_SADDR,		/* u32 */
+	L2TP_ATTR_IP_DADDR,		/* u32 */
+	L2TP_ATTR_UDP_SPORT,		/* u16 */
+	L2TP_ATTR_UDP_DPORT,		/* u16 */
+	L2TP_ATTR_MTU,			/* u16 (not used) */
+	L2TP_ATTR_MRU,			/* u16 (not used) */
+	L2TP_ATTR_STATS,		/* nested */
+	L2TP_ATTR_IP6_SADDR,		/* struct in6_addr */
+	L2TP_ATTR_IP6_DADDR,		/* struct in6_addr */
+	L2TP_ATTR_UDP_ZERO_CSUM6_TX,	/* flag */
+	L2TP_ATTR_UDP_ZERO_CSUM6_RX,	/* flag */
+	L2TP_ATTR_PAD,
+	__L2TP_ATTR_MAX,
+};
+
+#define L2TP_ATTR_MAX			(__L2TP_ATTR_MAX - 1)
+
+/* Nested in L2TP_ATTR_STATS */
+enum {
+	L2TP_ATTR_STATS_NONE,		/* no data */
+	L2TP_ATTR_TX_PACKETS,		/* u64 */
+	L2TP_ATTR_TX_BYTES,		/* u64 */
+	L2TP_ATTR_TX_ERRORS,		/* u64 */
+	L2TP_ATTR_RX_PACKETS,		/* u64 */
+	L2TP_ATTR_RX_BYTES,		/* u64 */
+	L2TP_ATTR_RX_SEQ_DISCARDS,	/* u64 */
+	L2TP_ATTR_RX_OOS_PACKETS,	/* u64 */
+	L2TP_ATTR_RX_ERRORS,		/* u64 */
+	L2TP_ATTR_STATS_PAD,
+	__L2TP_ATTR_STATS_MAX,
+};
+
+#define L2TP_ATTR_STATS_MAX		(__L2TP_ATTR_STATS_MAX - 1)
+
+enum l2tp_pwtype {
+	L2TP_PWTYPE_NONE = 0x0000,
+	L2TP_PWTYPE_ETH_VLAN = 0x0004,
+	L2TP_PWTYPE_ETH = 0x0005,
+	L2TP_PWTYPE_PPP = 0x0007,
+	L2TP_PWTYPE_PPP_AC = 0x0008,
+	L2TP_PWTYPE_IP = 0x000b,
+	__L2TP_PWTYPE_MAX
+};
+
+enum l2tp_l2spec_type {
+	L2TP_L2SPECTYPE_NONE,
+	L2TP_L2SPECTYPE_DEFAULT,
+};
+
+enum l2tp_encap_type {
+	L2TP_ENCAPTYPE_UDP,
+	L2TP_ENCAPTYPE_IP,
+};
+
+/* For L2TP_ATTR_DATA_SEQ. Unused. */
+enum l2tp_seqmode {
+	L2TP_SEQ_NONE = 0,
+	L2TP_SEQ_IP = 1,
+	L2TP_SEQ_ALL = 2,
+};
+
+/**
+ * enum l2tp_debug_flags - debug message categories for L2TP tunnels/sessions
+ *
+ * @L2TP_MSG_DEBUG: verbose debug (if compiled in)
+ * @L2TP_MSG_CONTROL: userspace - kernel interface
+ * @L2TP_MSG_SEQ: sequence numbers
+ * @L2TP_MSG_DATA: data packets
+ */
+enum l2tp_debug_flags {
+	L2TP_MSG_DEBUG		= (1 << 0),
+	L2TP_MSG_CONTROL	= (1 << 1),
+	L2TP_MSG_SEQ		= (1 << 2),
+	L2TP_MSG_DATA		= (1 << 3),
+};
+
+/*
+ * NETLINK_GENERIC related info
+ */
+#define L2TP_GENL_NAME		"l2tp"
+#define L2TP_GENL_VERSION	0x1
+#define L2TP_GENL_MCGROUP       "l2tp"
+
+#endif /* _LINUX_L2TP_H_ */
diff --git a/include/uapi/linux/libc-compat.h b/include/uapi/linux/libc-compat.h
new file mode 100644
index 0000000..a159991
--- /dev/null
+++ b/include/uapi/linux/libc-compat.h
@@ -0,0 +1,267 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Compatibility interface for userspace libc header coordination:
+ *
+ * Define compatibility macros that are used to control the inclusion or
+ * exclusion of UAPI structures and definitions in coordination with another
+ * userspace C library.
+ *
+ * This header is intended to solve the problem of UAPI definitions that
+ * conflict with userspace definitions. If a UAPI header has such conflicting
+ * definitions then the solution is as follows:
+ *
+ * * Synchronize the UAPI header and the libc headers so either one can be
+ *   used and such that the ABI is preserved. If this is not possible then
+ *   no simple compatibility interface exists (you need to write translating
+ *   wrappers and rename things) and you can't use this interface.
+ *
+ * Then follow this process:
+ *
+ * (a) Include libc-compat.h in the UAPI header.
+ *      e.g. #include <linux/libc-compat.h>
+ *     This include must be as early as possible.
+ *
+ * (b) In libc-compat.h add enough code to detect that the comflicting
+ *     userspace libc header has been included first.
+ *
+ * (c) If the userspace libc header has been included first define a set of
+ *     guard macros of the form __UAPI_DEF_FOO and set their values to 1, else
+ *     set their values to 0.
+ *
+ * (d) Back in the UAPI header with the conflicting definitions, guard the
+ *     definitions with:
+ *     #if __UAPI_DEF_FOO
+ *       ...
+ *     #endif
+ *
+ * This fixes the situation where the linux headers are included *after* the
+ * libc headers. To fix the problem with the inclusion in the other order the
+ * userspace libc headers must be fixed like this:
+ *
+ * * For all definitions that conflict with kernel definitions wrap those
+ *   defines in the following:
+ *   #if !__UAPI_DEF_FOO
+ *     ...
+ *   #endif
+ *
+ * This prevents the redefinition of a construct already defined by the kernel.
+ */
+#ifndef _LIBC_COMPAT_H
+#define _LIBC_COMPAT_H
+
+/* We have included glibc headers... */
+#if defined(__GLIBC__)
+
+/* Coordinate with glibc net/if.h header. */
+#if defined(_NET_IF_H) && defined(__USE_MISC)
+
+/* GLIBC headers included first so don't define anything
+ * that would already be defined. */
+
+#define __UAPI_DEF_IF_IFCONF 0
+#define __UAPI_DEF_IF_IFMAP 0
+#define __UAPI_DEF_IF_IFNAMSIZ 0
+#define __UAPI_DEF_IF_IFREQ 0
+/* Everything up to IFF_DYNAMIC, matches net/if.h until glibc 2.23 */
+#define __UAPI_DEF_IF_NET_DEVICE_FLAGS 0
+/* For the future if glibc adds IFF_LOWER_UP, IFF_DORMANT and IFF_ECHO */
+#ifndef __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO
+#define __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO 1
+#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */
+
+#else /* _NET_IF_H */
+
+/* Linux headers included first, and we must define everything
+ * we need. The expectation is that glibc will check the
+ * __UAPI_DEF_* defines and adjust appropriately. */
+
+#define __UAPI_DEF_IF_IFCONF 1
+#define __UAPI_DEF_IF_IFMAP 1
+#define __UAPI_DEF_IF_IFNAMSIZ 1
+#define __UAPI_DEF_IF_IFREQ 1
+/* Everything up to IFF_DYNAMIC, matches net/if.h until glibc 2.23 */
+#define __UAPI_DEF_IF_NET_DEVICE_FLAGS 1
+/* For the future if glibc adds IFF_LOWER_UP, IFF_DORMANT and IFF_ECHO */
+#define __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO 1
+
+#endif /* _NET_IF_H */
+
+/* Coordinate with glibc netinet/in.h header. */
+#if defined(_NETINET_IN_H)
+
+/* GLIBC headers included first so don't define anything
+ * that would already be defined. */
+#define __UAPI_DEF_IN_ADDR		0
+#define __UAPI_DEF_IN_IPPROTO		0
+#define __UAPI_DEF_IN_PKTINFO		0
+#define __UAPI_DEF_IP_MREQ		0
+#define __UAPI_DEF_SOCKADDR_IN		0
+#define __UAPI_DEF_IN_CLASS		0
+
+#define __UAPI_DEF_IN6_ADDR		0
+/* The exception is the in6_addr macros which must be defined
+ * if the glibc code didn't define them. This guard matches
+ * the guard in glibc/inet/netinet/in.h which defines the
+ * additional in6_addr macros e.g. s6_addr16, and s6_addr32. */
+#if defined(__USE_MISC) || defined (__USE_GNU)
+#define __UAPI_DEF_IN6_ADDR_ALT		0
+#else
+#define __UAPI_DEF_IN6_ADDR_ALT		1
+#endif
+#define __UAPI_DEF_SOCKADDR_IN6		0
+#define __UAPI_DEF_IPV6_MREQ		0
+#define __UAPI_DEF_IPPROTO_V6		0
+#define __UAPI_DEF_IPV6_OPTIONS		0
+#define __UAPI_DEF_IN6_PKTINFO		0
+#define __UAPI_DEF_IP6_MTUINFO		0
+
+#else
+
+/* Linux headers included first, and we must define everything
+ * we need. The expectation is that glibc will check the
+ * __UAPI_DEF_* defines and adjust appropriately. */
+#define __UAPI_DEF_IN_ADDR		1
+#define __UAPI_DEF_IN_IPPROTO		1
+#define __UAPI_DEF_IN_PKTINFO		1
+#define __UAPI_DEF_IP_MREQ		1
+#define __UAPI_DEF_SOCKADDR_IN		1
+#define __UAPI_DEF_IN_CLASS		1
+
+#define __UAPI_DEF_IN6_ADDR		1
+/* We unconditionally define the in6_addr macros and glibc must
+ * coordinate. */
+#define __UAPI_DEF_IN6_ADDR_ALT		1
+#define __UAPI_DEF_SOCKADDR_IN6		1
+#define __UAPI_DEF_IPV6_MREQ		1
+#define __UAPI_DEF_IPPROTO_V6		1
+#define __UAPI_DEF_IPV6_OPTIONS		1
+#define __UAPI_DEF_IN6_PKTINFO		1
+#define __UAPI_DEF_IP6_MTUINFO		1
+
+#endif /* _NETINET_IN_H */
+
+/* Coordinate with glibc netipx/ipx.h header. */
+#if defined(__NETIPX_IPX_H)
+
+#define __UAPI_DEF_SOCKADDR_IPX			0
+#define __UAPI_DEF_IPX_ROUTE_DEFINITION		0
+#define __UAPI_DEF_IPX_INTERFACE_DEFINITION	0
+#define __UAPI_DEF_IPX_CONFIG_DATA		0
+#define __UAPI_DEF_IPX_ROUTE_DEF		0
+
+#else /* defined(__NETIPX_IPX_H) */
+
+#define __UAPI_DEF_SOCKADDR_IPX			1
+#define __UAPI_DEF_IPX_ROUTE_DEFINITION		1
+#define __UAPI_DEF_IPX_INTERFACE_DEFINITION	1
+#define __UAPI_DEF_IPX_CONFIG_DATA		1
+#define __UAPI_DEF_IPX_ROUTE_DEF		1
+
+#endif /* defined(__NETIPX_IPX_H) */
+
+/* Definitions for xattr.h */
+#if defined(_SYS_XATTR_H)
+#define __UAPI_DEF_XATTR		0
+#else
+#define __UAPI_DEF_XATTR		1
+#endif
+
+/* If we did not see any headers from any supported C libraries,
+ * or we are being included in the kernel, then define everything
+ * that we need. Check for previous __UAPI_* definitions to give
+ * unsupported C libraries a way to opt out of any kernel definition. */
+#else /* !defined(__GLIBC__) */
+
+/* Definitions for if.h */
+#ifndef __UAPI_DEF_IF_IFCONF
+#define __UAPI_DEF_IF_IFCONF 1
+#endif
+#ifndef __UAPI_DEF_IF_IFMAP
+#define __UAPI_DEF_IF_IFMAP 1
+#endif
+#ifndef __UAPI_DEF_IF_IFNAMSIZ
+#define __UAPI_DEF_IF_IFNAMSIZ 1
+#endif
+#ifndef __UAPI_DEF_IF_IFREQ
+#define __UAPI_DEF_IF_IFREQ 1
+#endif
+/* Everything up to IFF_DYNAMIC, matches net/if.h until glibc 2.23 */
+#ifndef __UAPI_DEF_IF_NET_DEVICE_FLAGS
+#define __UAPI_DEF_IF_NET_DEVICE_FLAGS 1
+#endif
+/* For the future if glibc adds IFF_LOWER_UP, IFF_DORMANT and IFF_ECHO */
+#ifndef __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO
+#define __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO 1
+#endif
+
+/* Definitions for in.h */
+#ifndef __UAPI_DEF_IN_ADDR
+#define __UAPI_DEF_IN_ADDR		1
+#endif
+#ifndef __UAPI_DEF_IN_IPPROTO
+#define __UAPI_DEF_IN_IPPROTO		1
+#endif
+#ifndef __UAPI_DEF_IN_PKTINFO
+#define __UAPI_DEF_IN_PKTINFO		1
+#endif
+#ifndef __UAPI_DEF_IP_MREQ
+#define __UAPI_DEF_IP_MREQ		1
+#endif
+#ifndef __UAPI_DEF_SOCKADDR_IN
+#define __UAPI_DEF_SOCKADDR_IN		1
+#endif
+#ifndef __UAPI_DEF_IN_CLASS
+#define __UAPI_DEF_IN_CLASS		1
+#endif
+
+/* Definitions for in6.h */
+#ifndef __UAPI_DEF_IN6_ADDR
+#define __UAPI_DEF_IN6_ADDR		1
+#endif
+#ifndef __UAPI_DEF_IN6_ADDR_ALT
+#define __UAPI_DEF_IN6_ADDR_ALT		1
+#endif
+#ifndef __UAPI_DEF_SOCKADDR_IN6
+#define __UAPI_DEF_SOCKADDR_IN6		1
+#endif
+#ifndef __UAPI_DEF_IPV6_MREQ
+#define __UAPI_DEF_IPV6_MREQ		1
+#endif
+#ifndef __UAPI_DEF_IPPROTO_V6
+#define __UAPI_DEF_IPPROTO_V6		1
+#endif
+#ifndef __UAPI_DEF_IPV6_OPTIONS
+#define __UAPI_DEF_IPV6_OPTIONS		1
+#endif
+#ifndef __UAPI_DEF_IN6_PKTINFO
+#define __UAPI_DEF_IN6_PKTINFO		1
+#endif
+#ifndef __UAPI_DEF_IP6_MTUINFO
+#define __UAPI_DEF_IP6_MTUINFO		1
+#endif
+
+/* Definitions for ipx.h */
+#ifndef __UAPI_DEF_SOCKADDR_IPX
+#define __UAPI_DEF_SOCKADDR_IPX			1
+#endif
+#ifndef __UAPI_DEF_IPX_ROUTE_DEFINITION
+#define __UAPI_DEF_IPX_ROUTE_DEFINITION		1
+#endif
+#ifndef __UAPI_DEF_IPX_INTERFACE_DEFINITION
+#define __UAPI_DEF_IPX_INTERFACE_DEFINITION	1
+#endif
+#ifndef __UAPI_DEF_IPX_CONFIG_DATA
+#define __UAPI_DEF_IPX_CONFIG_DATA		1
+#endif
+#ifndef __UAPI_DEF_IPX_ROUTE_DEF
+#define __UAPI_DEF_IPX_ROUTE_DEF		1
+#endif
+
+/* Definitions for xattr.h */
+#ifndef __UAPI_DEF_XATTR
+#define __UAPI_DEF_XATTR		1
+#endif
+
+#endif /* __GLIBC__ */
+
+#endif /* _LIBC_COMPAT_H */
diff --git a/include/uapi/linux/limits.h b/include/uapi/linux/limits.h
new file mode 100644
index 0000000..c3547f0
--- /dev/null
+++ b/include/uapi/linux/limits.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_LIMITS_H
+#define _LINUX_LIMITS_H
+
+#define NR_OPEN	        1024
+
+#define NGROUPS_MAX    65536	/* supplemental group IDs are available */
+#define ARG_MAX       131072	/* # bytes of args + environ for exec() */
+#define LINK_MAX         127	/* # links a file may have */
+#define MAX_CANON        255	/* size of the canonical input queue */
+#define MAX_INPUT        255	/* size of the type-ahead buffer */
+#define NAME_MAX         255	/* # chars in a file name */
+#define PATH_MAX        4096	/* # chars in a path name including nul */
+#define PIPE_BUF        4096	/* # bytes in atomic write to a pipe */
+#define XATTR_NAME_MAX   255	/* # chars in an extended attribute name */
+#define XATTR_SIZE_MAX 65536	/* size of an extended attribute value (64k) */
+#define XATTR_LIST_MAX 65536	/* size of extended attribute namelist (64k) */
+
+#define RTSIG_MAX	  32
+
+#endif
diff --git a/include/uapi/linux/lwtunnel.h b/include/uapi/linux/lwtunnel.h
new file mode 100644
index 0000000..3f3fe6f
--- /dev/null
+++ b/include/uapi/linux/lwtunnel.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LWTUNNEL_H_
+#define _LWTUNNEL_H_
+
+#include <linux/types.h>
+
+enum lwtunnel_encap_types {
+	LWTUNNEL_ENCAP_NONE,
+	LWTUNNEL_ENCAP_MPLS,
+	LWTUNNEL_ENCAP_IP,
+	LWTUNNEL_ENCAP_ILA,
+	LWTUNNEL_ENCAP_IP6,
+	LWTUNNEL_ENCAP_SEG6,
+	LWTUNNEL_ENCAP_BPF,
+	LWTUNNEL_ENCAP_SEG6_LOCAL,
+	__LWTUNNEL_ENCAP_MAX,
+};
+
+#define LWTUNNEL_ENCAP_MAX (__LWTUNNEL_ENCAP_MAX - 1)
+
+enum lwtunnel_ip_t {
+	LWTUNNEL_IP_UNSPEC,
+	LWTUNNEL_IP_ID,
+	LWTUNNEL_IP_DST,
+	LWTUNNEL_IP_SRC,
+	LWTUNNEL_IP_TTL,
+	LWTUNNEL_IP_TOS,
+	LWTUNNEL_IP_FLAGS,
+	LWTUNNEL_IP_PAD,
+	__LWTUNNEL_IP_MAX,
+};
+
+#define LWTUNNEL_IP_MAX (__LWTUNNEL_IP_MAX - 1)
+
+enum lwtunnel_ip6_t {
+	LWTUNNEL_IP6_UNSPEC,
+	LWTUNNEL_IP6_ID,
+	LWTUNNEL_IP6_DST,
+	LWTUNNEL_IP6_SRC,
+	LWTUNNEL_IP6_HOPLIMIT,
+	LWTUNNEL_IP6_TC,
+	LWTUNNEL_IP6_FLAGS,
+	LWTUNNEL_IP6_PAD,
+	__LWTUNNEL_IP6_MAX,
+};
+
+#define LWTUNNEL_IP6_MAX (__LWTUNNEL_IP6_MAX - 1)
+
+enum {
+	LWT_BPF_PROG_UNSPEC,
+	LWT_BPF_PROG_FD,
+	LWT_BPF_PROG_NAME,
+	__LWT_BPF_PROG_MAX,
+};
+
+#define LWT_BPF_PROG_MAX (__LWT_BPF_PROG_MAX - 1)
+
+enum {
+	LWT_BPF_UNSPEC,
+	LWT_BPF_IN,
+	LWT_BPF_OUT,
+	LWT_BPF_XMIT,
+	LWT_BPF_XMIT_HEADROOM,
+	__LWT_BPF_MAX,
+};
+
+#define LWT_BPF_MAX (__LWT_BPF_MAX - 1)
+
+#define LWT_BPF_MAX_HEADROOM 256
+
+#endif /* _LWTUNNEL_H_ */
diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
new file mode 100644
index 0000000..903cc2d
--- /dev/null
+++ b/include/uapi/linux/magic.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_MAGIC_H__
+#define __LINUX_MAGIC_H__
+
+#define ADFS_SUPER_MAGIC	0xadf5
+#define AFFS_SUPER_MAGIC	0xadff
+#define AFS_SUPER_MAGIC                0x5346414F
+#define AUTOFS_SUPER_MAGIC	0x0187
+#define CODA_SUPER_MAGIC	0x73757245
+#define CRAMFS_MAGIC		0x28cd3d45	/* some random number */
+#define CRAMFS_MAGIC_WEND	0x453dcd28	/* magic number with the wrong endianess */
+#define DEBUGFS_MAGIC          0x64626720
+#define SECURITYFS_MAGIC	0x73636673
+#define SELINUX_MAGIC		0xf97cff8c
+#define SMACK_MAGIC		0x43415d53	/* "SMAC" */
+#define RAMFS_MAGIC		0x858458f6	/* some random number */
+#define TMPFS_MAGIC		0x01021994
+#define HUGETLBFS_MAGIC 	0x958458f6	/* some random number */
+#define SQUASHFS_MAGIC		0x73717368
+#define ECRYPTFS_SUPER_MAGIC	0xf15f
+#define EFS_SUPER_MAGIC		0x414A53
+#define EROFS_SUPER_MAGIC_V1	0xE0F5E1E2
+#define EXT2_SUPER_MAGIC	0xEF53
+#define EXT3_SUPER_MAGIC	0xEF53
+#define XENFS_SUPER_MAGIC	0xabba1974
+#define EXT4_SUPER_MAGIC	0xEF53
+#define BTRFS_SUPER_MAGIC	0x9123683E
+#define NILFS_SUPER_MAGIC	0x3434
+#define F2FS_SUPER_MAGIC	0xF2F52010
+#define HPFS_SUPER_MAGIC	0xf995e849
+#define ISOFS_SUPER_MAGIC	0x9660
+#define JFFS2_SUPER_MAGIC	0x72b6
+#define XFS_SUPER_MAGIC		0x58465342	/* "XFSB" */
+#define PSTOREFS_MAGIC		0x6165676C
+#define EFIVARFS_MAGIC		0xde5e81e4
+#define HOSTFS_SUPER_MAGIC	0x00c0ffee
+#define OVERLAYFS_SUPER_MAGIC	0x794c7630
+
+#define MINIX_SUPER_MAGIC	0x137F		/* minix v1 fs, 14 char names */
+#define MINIX_SUPER_MAGIC2	0x138F		/* minix v1 fs, 30 char names */
+#define MINIX2_SUPER_MAGIC	0x2468		/* minix v2 fs, 14 char names */
+#define MINIX2_SUPER_MAGIC2	0x2478		/* minix v2 fs, 30 char names */
+#define MINIX3_SUPER_MAGIC	0x4d5a		/* minix v3 fs, 60 char names */
+
+#define MSDOS_SUPER_MAGIC	0x4d44		/* MD */
+#define NCP_SUPER_MAGIC		0x564c		/* Guess, what 0x564c is :-) */
+#define NFS_SUPER_MAGIC		0x6969
+#define OCFS2_SUPER_MAGIC	0x7461636f
+#define OPENPROM_SUPER_MAGIC	0x9fa1
+#define QNX4_SUPER_MAGIC	0x002f		/* qnx4 fs detection */
+#define QNX6_SUPER_MAGIC	0x68191122	/* qnx6 fs detection */
+#define AFS_FS_MAGIC		0x6B414653
+
+#define REISERFS_SUPER_MAGIC	0x52654973	/* used by gcc */
+					/* used by file system utilities that
+	                                   look at the superblock, etc.  */
+#define REISERFS_SUPER_MAGIC_STRING	"ReIsErFs"
+#define REISER2FS_SUPER_MAGIC_STRING	"ReIsEr2Fs"
+#define REISER2FS_JR_SUPER_MAGIC_STRING	"ReIsEr3Fs"
+
+#define SMB_SUPER_MAGIC		0x517B
+#define CGROUP_SUPER_MAGIC	0x27e0eb
+#define CGROUP2_SUPER_MAGIC	0x63677270
+
+#define RDTGROUP_SUPER_MAGIC	0x7655821
+
+#define STACK_END_MAGIC		0x57AC6E9D
+
+#define TRACEFS_MAGIC          0x74726163
+
+#define V9FS_MAGIC		0x01021997
+
+#define BDEVFS_MAGIC            0x62646576
+#define DAXFS_MAGIC             0x64646178
+#define BINFMTFS_MAGIC          0x42494e4d
+#define DEVPTS_SUPER_MAGIC	0x1cd1
+#define BINDERFS_SUPER_MAGIC	0x6c6f6f70
+#define FUTEXFS_SUPER_MAGIC	0xBAD1DEA
+#define PIPEFS_MAGIC            0x50495045
+#define PROC_SUPER_MAGIC	0x9fa0
+#define SOCKFS_MAGIC		0x534F434B
+#define SYSFS_MAGIC		0x62656572
+#define USBDEVICE_SUPER_MAGIC	0x9fa2
+#define MTD_INODE_FS_MAGIC      0x11307854
+#define ANON_INODE_FS_MAGIC	0x09041934
+#define BTRFS_TEST_MAGIC	0x73727279
+#define NSFS_MAGIC		0x6e736673
+#define BPF_FS_MAGIC		0xcafe4a11
+#define AAFS_MAGIC		0x5a3c69f0
+
+/* Since UDF 2.01 is ISO 13346 based... */
+#define UDF_SUPER_MAGIC		0x15013346
+#define BALLOON_KVM_MAGIC	0x13661366
+#define ZSMALLOC_MAGIC		0x58295829
+#define DMA_BUF_MAGIC		0x444d4142	/* "DMAB" */
+#define Z3FOLD_MAGIC		0x33
+
+#endif /* __LINUX_MAGIC_H__ */
diff --git a/include/uapi/linux/mpls.h b/include/uapi/linux/mpls.h
new file mode 100644
index 0000000..9effbf9
--- /dev/null
+++ b/include/uapi/linux/mpls.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _MPLS_H
+#define _MPLS_H
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+/* Reference: RFC 5462, RFC 3032
+ *
+ *  0                   1                   2                   3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                Label                  | TC  |S|       TTL     |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *	Label:  Label Value, 20 bits
+ *	TC:     Traffic Class field, 3 bits
+ *	S:      Bottom of Stack, 1 bit
+ *	TTL:    Time to Live, 8 bits
+ */
+
+struct mpls_label {
+	__be32 entry;
+};
+
+#define MPLS_LS_LABEL_MASK      0xFFFFF000
+#define MPLS_LS_LABEL_SHIFT     12
+#define MPLS_LS_TC_MASK         0x00000E00
+#define MPLS_LS_TC_SHIFT        9
+#define MPLS_LS_S_MASK          0x00000100
+#define MPLS_LS_S_SHIFT         8
+#define MPLS_LS_TTL_MASK        0x000000FF
+#define MPLS_LS_TTL_SHIFT       0
+
+/* Reserved labels */
+#define MPLS_LABEL_IPV4NULL		0 /* RFC3032 */
+#define MPLS_LABEL_RTALERT		1 /* RFC3032 */
+#define MPLS_LABEL_IPV6NULL		2 /* RFC3032 */
+#define MPLS_LABEL_IMPLNULL		3 /* RFC3032 */
+#define MPLS_LABEL_ENTROPY		7 /* RFC6790 */
+#define MPLS_LABEL_GAL			13 /* RFC5586 */
+#define MPLS_LABEL_OAMALERT		14 /* RFC3429 */
+#define MPLS_LABEL_EXTENSION		15 /* RFC7274 */
+
+#define MPLS_LABEL_FIRST_UNRESERVED	16 /* RFC3032 */
+
+/* These are embedded into IFLA_STATS_AF_SPEC:
+ * [IFLA_STATS_AF_SPEC]
+ * -> [AF_MPLS]
+ *    -> [MPLS_STATS_xxx]
+ *
+ * Attributes:
+ * [MPLS_STATS_LINK] = {
+ *     struct mpls_link_stats
+ * }
+ */
+enum {
+	MPLS_STATS_UNSPEC, /* also used as 64bit pad attribute */
+	MPLS_STATS_LINK,
+	__MPLS_STATS_MAX,
+};
+
+#define MPLS_STATS_MAX (__MPLS_STATS_MAX - 1)
+
+struct mpls_link_stats {
+	__u64	rx_packets;		/* total packets received	*/
+	__u64	tx_packets;		/* total packets transmitted	*/
+	__u64	rx_bytes;		/* total bytes received		*/
+	__u64	tx_bytes;		/* total bytes transmitted	*/
+	__u64	rx_errors;		/* bad packets received		*/
+	__u64	tx_errors;		/* packet transmit problems	*/
+	__u64	rx_dropped;		/* packet dropped on receive	*/
+	__u64	tx_dropped;		/* packet dropped on transmit	*/
+	__u64	rx_noroute;		/* no route for packet dest	*/
+};
+
+#endif /* _MPLS_H */
diff --git a/include/uapi/linux/mpls_iptunnel.h b/include/uapi/linux/mpls_iptunnel.h
new file mode 100644
index 0000000..2c69b7d
--- /dev/null
+++ b/include/uapi/linux/mpls_iptunnel.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ *	mpls tunnel api
+ *
+ *	Authors:
+ *		Roopa Prabhu <roopa@cumulusnetworks.com>
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version
+ *	2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _LINUX_MPLS_IPTUNNEL_H
+#define _LINUX_MPLS_IPTUNNEL_H
+
+/* MPLS tunnel attributes
+ * [RTA_ENCAP] = {
+ *     [MPLS_IPTUNNEL_DST]
+ *     [MPLS_IPTUNNEL_TTL]
+ * }
+ */
+enum {
+	MPLS_IPTUNNEL_UNSPEC,
+	MPLS_IPTUNNEL_DST,
+	MPLS_IPTUNNEL_TTL,
+	__MPLS_IPTUNNEL_MAX,
+};
+#define MPLS_IPTUNNEL_MAX (__MPLS_IPTUNNEL_MAX - 1)
+
+#endif /* _LINUX_MPLS_IPTUNNEL_H */
diff --git a/include/uapi/linux/neighbour.h b/include/uapi/linux/neighbour.h
new file mode 100644
index 0000000..cd144e3
--- /dev/null
+++ b/include/uapi/linux/neighbour.h
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_NEIGHBOUR_H
+#define __LINUX_NEIGHBOUR_H
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+struct ndmsg {
+	__u8		ndm_family;
+	__u8		ndm_pad1;
+	__u16		ndm_pad2;
+	__s32		ndm_ifindex;
+	__u16		ndm_state;
+	__u8		ndm_flags;
+	__u8		ndm_type;
+};
+
+enum {
+	NDA_UNSPEC,
+	NDA_DST,
+	NDA_LLADDR,
+	NDA_CACHEINFO,
+	NDA_PROBES,
+	NDA_VLAN,
+	NDA_PORT,
+	NDA_VNI,
+	NDA_IFINDEX,
+	NDA_MASTER,
+	NDA_LINK_NETNSID,
+	NDA_SRC_VNI,
+	NDA_PROTOCOL,  /* Originator of entry */
+	__NDA_MAX
+};
+
+#define NDA_MAX (__NDA_MAX - 1)
+
+/*
+ *	Neighbor Cache Entry Flags
+ */
+
+#define NTF_USE		0x01
+#define NTF_SELF	0x02
+#define NTF_MASTER	0x04
+#define NTF_PROXY	0x08	/* == ATF_PUBL */
+#define NTF_EXT_LEARNED	0x10
+#define NTF_OFFLOADED   0x20
+#define NTF_STICKY	0x40
+#define NTF_ROUTER	0x80
+
+/*
+ *	Neighbor Cache Entry States.
+ */
+
+#define NUD_INCOMPLETE	0x01
+#define NUD_REACHABLE	0x02
+#define NUD_STALE	0x04
+#define NUD_DELAY	0x08
+#define NUD_PROBE	0x10
+#define NUD_FAILED	0x20
+
+/* Dummy states */
+#define NUD_NOARP	0x40
+#define NUD_PERMANENT	0x80
+#define NUD_NONE	0x00
+
+/* NUD_NOARP & NUD_PERMANENT are pseudostates, they never change
+   and make no address resolution or NUD.
+   NUD_PERMANENT also cannot be deleted by garbage collectors.
+ */
+
+struct nda_cacheinfo {
+	__u32		ndm_confirmed;
+	__u32		ndm_used;
+	__u32		ndm_updated;
+	__u32		ndm_refcnt;
+};
+
+/*****************************************************************
+ *		Neighbour tables specific messages.
+ *
+ * To retrieve the neighbour tables send RTM_GETNEIGHTBL with the
+ * NLM_F_DUMP flag set. Every neighbour table configuration is
+ * spread over multiple messages to avoid running into message
+ * size limits on systems with many interfaces. The first message
+ * in the sequence transports all not device specific data such as
+ * statistics, configuration, and the default parameter set.
+ * This message is followed by 0..n messages carrying device
+ * specific parameter sets.
+ * Although the ordering should be sufficient, NDTA_NAME can be
+ * used to identify sequences. The initial message can be identified
+ * by checking for NDTA_CONFIG. The device specific messages do
+ * not contain this TLV but have NDTPA_IFINDEX set to the
+ * corresponding interface index.
+ *
+ * To change neighbour table attributes, send RTM_SETNEIGHTBL
+ * with NDTA_NAME set. Changeable attribute include NDTA_THRESH[1-3],
+ * NDTA_GC_INTERVAL, and all TLVs in NDTA_PARMS unless marked
+ * otherwise. Device specific parameter sets can be changed by
+ * setting NDTPA_IFINDEX to the interface index of the corresponding
+ * device.
+ ****/
+
+struct ndt_stats {
+	__u64		ndts_allocs;
+	__u64		ndts_destroys;
+	__u64		ndts_hash_grows;
+	__u64		ndts_res_failed;
+	__u64		ndts_lookups;
+	__u64		ndts_hits;
+	__u64		ndts_rcv_probes_mcast;
+	__u64		ndts_rcv_probes_ucast;
+	__u64		ndts_periodic_gc_runs;
+	__u64		ndts_forced_gc_runs;
+	__u64		ndts_table_fulls;
+};
+
+enum {
+	NDTPA_UNSPEC,
+	NDTPA_IFINDEX,			/* u32, unchangeable */
+	NDTPA_REFCNT,			/* u32, read-only */
+	NDTPA_REACHABLE_TIME,		/* u64, read-only, msecs */
+	NDTPA_BASE_REACHABLE_TIME,	/* u64, msecs */
+	NDTPA_RETRANS_TIME,		/* u64, msecs */
+	NDTPA_GC_STALETIME,		/* u64, msecs */
+	NDTPA_DELAY_PROBE_TIME,		/* u64, msecs */
+	NDTPA_QUEUE_LEN,		/* u32 */
+	NDTPA_APP_PROBES,		/* u32 */
+	NDTPA_UCAST_PROBES,		/* u32 */
+	NDTPA_MCAST_PROBES,		/* u32 */
+	NDTPA_ANYCAST_DELAY,		/* u64, msecs */
+	NDTPA_PROXY_DELAY,		/* u64, msecs */
+	NDTPA_PROXY_QLEN,		/* u32 */
+	NDTPA_LOCKTIME,			/* u64, msecs */
+	NDTPA_QUEUE_LENBYTES,		/* u32 */
+	NDTPA_MCAST_REPROBES,		/* u32 */
+	NDTPA_PAD,
+	__NDTPA_MAX
+};
+#define NDTPA_MAX (__NDTPA_MAX - 1)
+
+struct ndtmsg {
+	__u8		ndtm_family;
+	__u8		ndtm_pad1;
+	__u16		ndtm_pad2;
+};
+
+struct ndt_config {
+	__u16		ndtc_key_len;
+	__u16		ndtc_entry_size;
+	__u32		ndtc_entries;
+	__u32		ndtc_last_flush;	/* delta to now in msecs */
+	__u32		ndtc_last_rand;		/* delta to now in msecs */
+	__u32		ndtc_hash_rnd;
+	__u32		ndtc_hash_mask;
+	__u32		ndtc_hash_chain_gc;
+	__u32		ndtc_proxy_qlen;
+};
+
+enum {
+	NDTA_UNSPEC,
+	NDTA_NAME,			/* char *, unchangeable */
+	NDTA_THRESH1,			/* u32 */
+	NDTA_THRESH2,			/* u32 */
+	NDTA_THRESH3,			/* u32 */
+	NDTA_CONFIG,			/* struct ndt_config, read-only */
+	NDTA_PARMS,			/* nested TLV NDTPA_* */
+	NDTA_STATS,			/* struct ndt_stats, read-only */
+	NDTA_GC_INTERVAL,		/* u64, msecs */
+	NDTA_PAD,
+	__NDTA_MAX
+};
+#define NDTA_MAX (__NDTA_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/net.h b/include/uapi/linux/net.h
new file mode 100644
index 0000000..4754f70
--- /dev/null
+++ b/include/uapi/linux/net.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * NET		An implementation of the SOCKET network access protocol.
+ *		This is the master header file for the Linux NET layer,
+ *		or, in plain English: the networking handling part of the
+ *		kernel.
+ *
+ * Version:	@(#)net.h	1.0.3	05/25/93
+ *
+ * Authors:	Orest Zborowski, <obz@Kodak.COM>
+ *		Ross Biro
+ *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ */
+#ifndef _LINUX_NET_H
+#define _LINUX_NET_H
+
+#include <linux/socket.h>
+#include <asm/socket.h>
+
+#define NPROTO		AF_MAX
+
+#define SYS_SOCKET	1		/* sys_socket(2)		*/
+#define SYS_BIND	2		/* sys_bind(2)			*/
+#define SYS_CONNECT	3		/* sys_connect(2)		*/
+#define SYS_LISTEN	4		/* sys_listen(2)		*/
+#define SYS_ACCEPT	5		/* sys_accept(2)		*/
+#define SYS_GETSOCKNAME	6		/* sys_getsockname(2)		*/
+#define SYS_GETPEERNAME	7		/* sys_getpeername(2)		*/
+#define SYS_SOCKETPAIR	8		/* sys_socketpair(2)		*/
+#define SYS_SEND	9		/* sys_send(2)			*/
+#define SYS_RECV	10		/* sys_recv(2)			*/
+#define SYS_SENDTO	11		/* sys_sendto(2)		*/
+#define SYS_RECVFROM	12		/* sys_recvfrom(2)		*/
+#define SYS_SHUTDOWN	13		/* sys_shutdown(2)		*/
+#define SYS_SETSOCKOPT	14		/* sys_setsockopt(2)		*/
+#define SYS_GETSOCKOPT	15		/* sys_getsockopt(2)		*/
+#define SYS_SENDMSG	16		/* sys_sendmsg(2)		*/
+#define SYS_RECVMSG	17		/* sys_recvmsg(2)		*/
+#define SYS_ACCEPT4	18		/* sys_accept4(2)		*/
+#define SYS_RECVMMSG	19		/* sys_recvmmsg(2)		*/
+#define SYS_SENDMMSG	20		/* sys_sendmmsg(2)		*/
+
+typedef enum {
+	SS_FREE = 0,			/* not allocated		*/
+	SS_UNCONNECTED,			/* unconnected to any socket	*/
+	SS_CONNECTING,			/* in process of connecting	*/
+	SS_CONNECTED,			/* connected to socket		*/
+	SS_DISCONNECTING		/* in process of disconnecting	*/
+} socket_state;
+
+#define __SO_ACCEPTCON	(1 << 16)	/* performed a listen		*/
+
+#endif /* _LINUX_NET_H */
diff --git a/include/uapi/linux/net_namespace.h b/include/uapi/linux/net_namespace.h
new file mode 100644
index 0000000..fa81f1e
--- /dev/null
+++ b/include/uapi/linux/net_namespace.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* Copyright (c) 2015 6WIND S.A.
+ * Author: Nicolas Dichtel <nicolas.dichtel@6wind.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+#ifndef _LINUX_NET_NAMESPACE_H_
+#define _LINUX_NET_NAMESPACE_H_
+
+/* Attributes of RTM_NEWNSID/RTM_GETNSID messages */
+enum {
+	NETNSA_NONE,
+#define NETNSA_NSID_NOT_ASSIGNED -1
+	NETNSA_NSID,
+	NETNSA_PID,
+	NETNSA_FD,
+	NETNSA_TARGET_NSID,
+	NETNSA_CURRENT_NSID,
+	__NETNSA_MAX,
+};
+
+#define NETNSA_MAX		(__NETNSA_MAX - 1)
+
+#endif /* _LINUX_NET_NAMESPACE_H_ */
diff --git a/include/uapi/linux/netconf.h b/include/uapi/linux/netconf.h
new file mode 100644
index 0000000..229e885
--- /dev/null
+++ b/include/uapi/linux/netconf.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_NETCONF_H_
+#define _LINUX_NETCONF_H_
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+struct netconfmsg {
+	__u8	ncm_family;
+};
+
+enum {
+	NETCONFA_UNSPEC,
+	NETCONFA_IFINDEX,
+	NETCONFA_FORWARDING,
+	NETCONFA_RP_FILTER,
+	NETCONFA_MC_FORWARDING,
+	NETCONFA_PROXY_NEIGH,
+	NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN,
+	NETCONFA_INPUT,
+	NETCONFA_BC_FORWARDING,
+	__NETCONFA_MAX
+};
+#define NETCONFA_MAX	(__NETCONFA_MAX - 1)
+#define NETCONFA_ALL	-1
+
+#define NETCONFA_IFINDEX_ALL		-1
+#define NETCONFA_IFINDEX_DEFAULT	-2
+
+#endif /* _LINUX_NETCONF_H_ */
diff --git a/include/uapi/linux/netdevice.h b/include/uapi/linux/netdevice.h
new file mode 100644
index 0000000..86d961c
--- /dev/null
+++ b/include/uapi/linux/netdevice.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Definitions for the Interfaces handler.
+ *
+ * Version:	@(#)dev.h	1.0.10	08/12/93
+ *
+ * Authors:	Ross Biro
+ *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *		Corey Minyard <wf-rch!minyard@relay.EU.net>
+ *		Donald J. Becker, <becker@cesdis.gsfc.nasa.gov>
+ *		Alan Cox, <alan@lxorguk.ukuu.org.uk>
+ *		Bjorn Ekwall. <bj0rn@blox.se>
+ *              Pekka Riikonen <priikone@poseidon.pspt.fi>
+ *
+ *		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.
+ *
+ *		Moved to /usr/include/linux for NET3
+ */
+#ifndef _LINUX_NETDEVICE_H
+#define _LINUX_NETDEVICE_H
+
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/if_link.h>
+
+
+#define MAX_ADDR_LEN	32		/* Largest hardware address length */
+
+/* Initial net device group. All devices belong to group 0 by default. */
+#define INIT_NETDEV_GROUP	0
+
+
+/* interface name assignment types (sysfs name_assign_type attribute) */
+#define NET_NAME_UNKNOWN	0	/* unknown origin (not exposed to userspace) */
+#define NET_NAME_ENUM		1	/* enumerated by kernel */
+#define NET_NAME_PREDICTABLE	2	/* predictably named by the kernel */
+#define NET_NAME_USER		3	/* provided by user-space */
+#define NET_NAME_RENAMED	4	/* renamed by user-space */
+
+/* Media selection options. */
+enum {
+        IF_PORT_UNKNOWN = 0,
+        IF_PORT_10BASE2,
+        IF_PORT_10BASET,
+        IF_PORT_AUI,
+        IF_PORT_100BASET,
+        IF_PORT_100BASETX,
+        IF_PORT_100BASEFX
+};
+
+/* hardware address assignment types */
+#define NET_ADDR_PERM		0	/* address is permanent (default) */
+#define NET_ADDR_RANDOM		1	/* address is generated randomly */
+#define NET_ADDR_STOLEN		2	/* address is stolen from other device */
+#define NET_ADDR_SET		3	/* address is set using
+					 * dev_set_mac_address() */
+
+#endif /* _LINUX_NETDEVICE_H */
diff --git a/include/uapi/linux/netfilter.h b/include/uapi/linux/netfilter.h
new file mode 100644
index 0000000..899be98
--- /dev/null
+++ b/include/uapi/linux/netfilter.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_NETFILTER_H
+#define __LINUX_NETFILTER_H
+
+#include <linux/types.h>
+
+#include <linux/in.h>
+#include <linux/in6.h>
+
+/* Responses from hook functions. */
+#define NF_DROP 0
+#define NF_ACCEPT 1
+#define NF_STOLEN 2
+#define NF_QUEUE 3
+#define NF_REPEAT 4
+#define NF_STOP 5	/* Deprecated, for userspace nf_queue compatibility. */
+#define NF_MAX_VERDICT NF_STOP
+
+/* we overload the higher bits for encoding auxiliary data such as the queue
+ * number or errno values. Not nice, but better than additional function
+ * arguments. */
+#define NF_VERDICT_MASK 0x000000ff
+
+/* extra verdict flags have mask 0x0000ff00 */
+#define NF_VERDICT_FLAG_QUEUE_BYPASS	0x00008000
+
+/* queue number (NF_QUEUE) or errno (NF_DROP) */
+#define NF_VERDICT_QMASK 0xffff0000
+#define NF_VERDICT_QBITS 16
+
+#define NF_QUEUE_NR(x) ((((x) << 16) & NF_VERDICT_QMASK) | NF_QUEUE)
+
+#define NF_DROP_ERR(x) (((-x) << 16) | NF_DROP)
+
+/* only for userspace compatibility */
+
+/* NF_VERDICT_BITS should be 8 now, but userspace might break if this changes */
+#define NF_VERDICT_BITS 16
+
+enum nf_inet_hooks {
+	NF_INET_PRE_ROUTING,
+	NF_INET_LOCAL_IN,
+	NF_INET_FORWARD,
+	NF_INET_LOCAL_OUT,
+	NF_INET_POST_ROUTING,
+	NF_INET_NUMHOOKS
+};
+
+enum nf_dev_hooks {
+	NF_NETDEV_INGRESS,
+	NF_NETDEV_NUMHOOKS
+};
+
+enum {
+	NFPROTO_UNSPEC =  0,
+	NFPROTO_INET   =  1,
+	NFPROTO_IPV4   =  2,
+	NFPROTO_ARP    =  3,
+	NFPROTO_NETDEV =  5,
+	NFPROTO_BRIDGE =  7,
+	NFPROTO_IPV6   = 10,
+	NFPROTO_DECNET = 12,
+	NFPROTO_NUMPROTO,
+};
+
+union nf_inet_addr {
+	__u32		all[4];
+	__be32		ip;
+	__be32		ip6[4];
+	struct in_addr	in;
+	struct in6_addr	in6;
+};
+
+#endif /* __LINUX_NETFILTER_H */
diff --git a/include/uapi/linux/netfilter/ipset/ip_set.h b/include/uapi/linux/netfilter/ipset/ip_set.h
new file mode 100644
index 0000000..c512003
--- /dev/null
+++ b/include/uapi/linux/netfilter/ipset/ip_set.h
@@ -0,0 +1,310 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ *                         Patrick Schaaf <bof@bof.de>
+ *                         Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 2003-2011 Jozsef Kadlecsik <kadlec@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _IP_SET_H
+#define _IP_SET_H
+
+#include <linux/types.h>
+
+/* The protocol versions */
+#define IPSET_PROTOCOL		7
+#define IPSET_PROTOCOL_MIN	6
+
+/* The max length of strings including NUL: set and type identifiers */
+#define IPSET_MAXNAMELEN	32
+
+/* The maximum permissible comment length we will accept over netlink */
+#define IPSET_MAX_COMMENT_SIZE	255
+
+/* Message types and commands */
+enum ipset_cmd {
+	IPSET_CMD_NONE,
+	IPSET_CMD_PROTOCOL,	/* 1: Return protocol version */
+	IPSET_CMD_CREATE,	/* 2: Create a new (empty) set */
+	IPSET_CMD_DESTROY,	/* 3: Destroy a (empty) set */
+	IPSET_CMD_FLUSH,	/* 4: Remove all elements from a set */
+	IPSET_CMD_RENAME,	/* 5: Rename a set */
+	IPSET_CMD_SWAP,		/* 6: Swap two sets */
+	IPSET_CMD_LIST,		/* 7: List sets */
+	IPSET_CMD_SAVE,		/* 8: Save sets */
+	IPSET_CMD_ADD,		/* 9: Add an element to a set */
+	IPSET_CMD_DEL,		/* 10: Delete an element from a set */
+	IPSET_CMD_TEST,		/* 11: Test an element in a set */
+	IPSET_CMD_HEADER,	/* 12: Get set header data only */
+	IPSET_CMD_TYPE,		/* 13: Get set type */
+	IPSET_CMD_GET_BYNAME,	/* 14: Get set index by name */
+	IPSET_CMD_GET_BYINDEX,	/* 15: Get set name by index */
+	IPSET_MSG_MAX,		/* Netlink message commands */
+
+	/* Commands in userspace: */
+	IPSET_CMD_RESTORE = IPSET_MSG_MAX, /* 16: Enter restore mode */
+	IPSET_CMD_HELP,		/* 17: Get help */
+	IPSET_CMD_VERSION,	/* 18: Get program version */
+	IPSET_CMD_QUIT,		/* 19: Quit from interactive mode */
+
+	IPSET_CMD_MAX,
+
+	IPSET_CMD_COMMIT = IPSET_CMD_MAX, /* 20: Commit buffered commands */
+};
+
+/* Attributes at command level */
+enum {
+	IPSET_ATTR_UNSPEC,
+	IPSET_ATTR_PROTOCOL,	/* 1: Protocol version */
+	IPSET_ATTR_SETNAME,	/* 2: Name of the set */
+	IPSET_ATTR_TYPENAME,	/* 3: Typename */
+	IPSET_ATTR_SETNAME2 = IPSET_ATTR_TYPENAME, /* Setname at rename/swap */
+	IPSET_ATTR_REVISION,	/* 4: Settype revision */
+	IPSET_ATTR_FAMILY,	/* 5: Settype family */
+	IPSET_ATTR_FLAGS,	/* 6: Flags at command level */
+	IPSET_ATTR_DATA,	/* 7: Nested attributes */
+	IPSET_ATTR_ADT,		/* 8: Multiple data containers */
+	IPSET_ATTR_LINENO,	/* 9: Restore lineno */
+	IPSET_ATTR_PROTOCOL_MIN, /* 10: Minimal supported version number */
+	IPSET_ATTR_REVISION_MIN	= IPSET_ATTR_PROTOCOL_MIN, /* type rev min */
+	IPSET_ATTR_INDEX,	/* 11: Kernel index of set */
+	__IPSET_ATTR_CMD_MAX,
+};
+#define IPSET_ATTR_CMD_MAX	(__IPSET_ATTR_CMD_MAX - 1)
+
+/* CADT specific attributes */
+enum {
+	IPSET_ATTR_IP = IPSET_ATTR_UNSPEC + 1,
+	IPSET_ATTR_IP_FROM = IPSET_ATTR_IP,
+	IPSET_ATTR_IP_TO,	/* 2 */
+	IPSET_ATTR_CIDR,	/* 3 */
+	IPSET_ATTR_PORT,	/* 4 */
+	IPSET_ATTR_PORT_FROM = IPSET_ATTR_PORT,
+	IPSET_ATTR_PORT_TO,	/* 5 */
+	IPSET_ATTR_TIMEOUT,	/* 6 */
+	IPSET_ATTR_PROTO,	/* 7 */
+	IPSET_ATTR_CADT_FLAGS,	/* 8 */
+	IPSET_ATTR_CADT_LINENO = IPSET_ATTR_LINENO,	/* 9 */
+	IPSET_ATTR_MARK,	/* 10 */
+	IPSET_ATTR_MARKMASK,	/* 11 */
+	/* Reserve empty slots */
+	IPSET_ATTR_CADT_MAX = 16,
+	/* Create-only specific attributes */
+	IPSET_ATTR_GC,
+	IPSET_ATTR_HASHSIZE,
+	IPSET_ATTR_MAXELEM,
+	IPSET_ATTR_NETMASK,
+	IPSET_ATTR_PROBES,
+	IPSET_ATTR_RESIZE,
+	IPSET_ATTR_SIZE,
+	/* Kernel-only */
+	IPSET_ATTR_ELEMENTS,
+	IPSET_ATTR_REFERENCES,
+	IPSET_ATTR_MEMSIZE,
+
+	__IPSET_ATTR_CREATE_MAX,
+};
+#define IPSET_ATTR_CREATE_MAX	(__IPSET_ATTR_CREATE_MAX - 1)
+
+/* ADT specific attributes */
+enum {
+	IPSET_ATTR_ETHER = IPSET_ATTR_CADT_MAX + 1,
+	IPSET_ATTR_NAME,
+	IPSET_ATTR_NAMEREF,
+	IPSET_ATTR_IP2,
+	IPSET_ATTR_CIDR2,
+	IPSET_ATTR_IP2_TO,
+	IPSET_ATTR_IFACE,
+	IPSET_ATTR_BYTES,
+	IPSET_ATTR_PACKETS,
+	IPSET_ATTR_COMMENT,
+	IPSET_ATTR_SKBMARK,
+	IPSET_ATTR_SKBPRIO,
+	IPSET_ATTR_SKBQUEUE,
+	IPSET_ATTR_PAD,
+	__IPSET_ATTR_ADT_MAX,
+};
+#define IPSET_ATTR_ADT_MAX	(__IPSET_ATTR_ADT_MAX - 1)
+
+/* IP specific attributes */
+enum {
+	IPSET_ATTR_IPADDR_IPV4 = IPSET_ATTR_UNSPEC + 1,
+	IPSET_ATTR_IPADDR_IPV6,
+	__IPSET_ATTR_IPADDR_MAX,
+};
+#define IPSET_ATTR_IPADDR_MAX	(__IPSET_ATTR_IPADDR_MAX - 1)
+
+/* Error codes */
+enum ipset_errno {
+	IPSET_ERR_PRIVATE = 4096,
+	IPSET_ERR_PROTOCOL,
+	IPSET_ERR_FIND_TYPE,
+	IPSET_ERR_MAX_SETS,
+	IPSET_ERR_BUSY,
+	IPSET_ERR_EXIST_SETNAME2,
+	IPSET_ERR_TYPE_MISMATCH,
+	IPSET_ERR_EXIST,
+	IPSET_ERR_INVALID_CIDR,
+	IPSET_ERR_INVALID_NETMASK,
+	IPSET_ERR_INVALID_FAMILY,
+	IPSET_ERR_TIMEOUT,
+	IPSET_ERR_REFERENCED,
+	IPSET_ERR_IPADDR_IPV4,
+	IPSET_ERR_IPADDR_IPV6,
+	IPSET_ERR_COUNTER,
+	IPSET_ERR_COMMENT,
+	IPSET_ERR_INVALID_MARKMASK,
+	IPSET_ERR_SKBINFO,
+
+	/* Type specific error codes */
+	IPSET_ERR_TYPE_SPECIFIC = 4352,
+};
+
+/* Flags at command level or match/target flags, lower half of cmdattrs*/
+enum ipset_cmd_flags {
+	IPSET_FLAG_BIT_EXIST	= 0,
+	IPSET_FLAG_EXIST	= (1 << IPSET_FLAG_BIT_EXIST),
+	IPSET_FLAG_BIT_LIST_SETNAME = 1,
+	IPSET_FLAG_LIST_SETNAME	= (1 << IPSET_FLAG_BIT_LIST_SETNAME),
+	IPSET_FLAG_BIT_LIST_HEADER = 2,
+	IPSET_FLAG_LIST_HEADER	= (1 << IPSET_FLAG_BIT_LIST_HEADER),
+	IPSET_FLAG_BIT_SKIP_COUNTER_UPDATE = 3,
+	IPSET_FLAG_SKIP_COUNTER_UPDATE =
+		(1 << IPSET_FLAG_BIT_SKIP_COUNTER_UPDATE),
+	IPSET_FLAG_BIT_SKIP_SUBCOUNTER_UPDATE = 4,
+	IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE =
+		(1 << IPSET_FLAG_BIT_SKIP_SUBCOUNTER_UPDATE),
+	IPSET_FLAG_BIT_MATCH_COUNTERS = 5,
+	IPSET_FLAG_MATCH_COUNTERS = (1 << IPSET_FLAG_BIT_MATCH_COUNTERS),
+	IPSET_FLAG_BIT_RETURN_NOMATCH = 7,
+	IPSET_FLAG_RETURN_NOMATCH = (1 << IPSET_FLAG_BIT_RETURN_NOMATCH),
+	IPSET_FLAG_BIT_MAP_SKBMARK = 8,
+	IPSET_FLAG_MAP_SKBMARK = (1 << IPSET_FLAG_BIT_MAP_SKBMARK),
+	IPSET_FLAG_BIT_MAP_SKBPRIO = 9,
+	IPSET_FLAG_MAP_SKBPRIO = (1 << IPSET_FLAG_BIT_MAP_SKBPRIO),
+	IPSET_FLAG_BIT_MAP_SKBQUEUE = 10,
+	IPSET_FLAG_MAP_SKBQUEUE = (1 << IPSET_FLAG_BIT_MAP_SKBQUEUE),
+	IPSET_FLAG_CMD_MAX = 15,
+};
+
+/* Flags at CADT attribute level, upper half of cmdattrs */
+enum ipset_cadt_flags {
+	IPSET_FLAG_BIT_BEFORE	= 0,
+	IPSET_FLAG_BEFORE	= (1 << IPSET_FLAG_BIT_BEFORE),
+	IPSET_FLAG_BIT_PHYSDEV	= 1,
+	IPSET_FLAG_PHYSDEV	= (1 << IPSET_FLAG_BIT_PHYSDEV),
+	IPSET_FLAG_BIT_NOMATCH	= 2,
+	IPSET_FLAG_NOMATCH	= (1 << IPSET_FLAG_BIT_NOMATCH),
+	IPSET_FLAG_BIT_WITH_COUNTERS = 3,
+	IPSET_FLAG_WITH_COUNTERS = (1 << IPSET_FLAG_BIT_WITH_COUNTERS),
+	IPSET_FLAG_BIT_WITH_COMMENT = 4,
+	IPSET_FLAG_WITH_COMMENT = (1 << IPSET_FLAG_BIT_WITH_COMMENT),
+	IPSET_FLAG_BIT_WITH_FORCEADD = 5,
+	IPSET_FLAG_WITH_FORCEADD = (1 << IPSET_FLAG_BIT_WITH_FORCEADD),
+	IPSET_FLAG_BIT_WITH_SKBINFO = 6,
+	IPSET_FLAG_WITH_SKBINFO = (1 << IPSET_FLAG_BIT_WITH_SKBINFO),
+	IPSET_FLAG_CADT_MAX	= 15,
+};
+
+/* The flag bits which correspond to the non-extension create flags */
+enum ipset_create_flags {
+	IPSET_CREATE_FLAG_BIT_FORCEADD = 0,
+	IPSET_CREATE_FLAG_FORCEADD = (1 << IPSET_CREATE_FLAG_BIT_FORCEADD),
+	IPSET_CREATE_FLAG_BIT_MAX = 7,
+};
+
+/* Commands with settype-specific attributes */
+enum ipset_adt {
+	IPSET_ADD,
+	IPSET_DEL,
+	IPSET_TEST,
+	IPSET_ADT_MAX,
+	IPSET_CREATE = IPSET_ADT_MAX,
+	IPSET_CADT_MAX,
+};
+
+/* Sets are identified by an index in kernel space. Tweak with ip_set_id_t
+ * and IPSET_INVALID_ID if you want to increase the max number of sets.
+ * Also, IPSET_ATTR_INDEX must be changed.
+ */
+typedef __u16 ip_set_id_t;
+
+#define IPSET_INVALID_ID		65535
+
+enum ip_set_dim {
+	IPSET_DIM_ZERO = 0,
+	IPSET_DIM_ONE,
+	IPSET_DIM_TWO,
+	IPSET_DIM_THREE,
+	/* Max dimension in elements.
+	 * If changed, new revision of iptables match/target is required.
+	 */
+	IPSET_DIM_MAX = 6,
+	/* Backward compatibility: set match revision 2 */
+	IPSET_BIT_RETURN_NOMATCH = 7,
+};
+
+/* Option flags for kernel operations */
+enum ip_set_kopt {
+	IPSET_INV_MATCH = (1 << IPSET_DIM_ZERO),
+	IPSET_DIM_ONE_SRC = (1 << IPSET_DIM_ONE),
+	IPSET_DIM_TWO_SRC = (1 << IPSET_DIM_TWO),
+	IPSET_DIM_THREE_SRC = (1 << IPSET_DIM_THREE),
+	IPSET_RETURN_NOMATCH = (1 << IPSET_BIT_RETURN_NOMATCH),
+};
+
+enum {
+	IPSET_COUNTER_NONE = 0,
+	IPSET_COUNTER_EQ,
+	IPSET_COUNTER_NE,
+	IPSET_COUNTER_LT,
+	IPSET_COUNTER_GT,
+};
+
+/* Backward compatibility for set match v3 */
+struct ip_set_counter_match0 {
+	__u8 op;
+	__u64 value;
+};
+
+struct ip_set_counter_match {
+	__aligned_u64 value;
+	__u8 op;
+};
+
+/* Interface to iptables/ip6tables */
+
+#define SO_IP_SET		83
+
+union ip_set_name_index {
+	char name[IPSET_MAXNAMELEN];
+	ip_set_id_t index;
+};
+
+#define IP_SET_OP_GET_BYNAME	0x00000006	/* Get set index by name */
+struct ip_set_req_get_set {
+	unsigned int op;
+	unsigned int version;
+	union ip_set_name_index set;
+};
+
+#define IP_SET_OP_GET_BYINDEX	0x00000007	/* Get set name by index */
+/* Uses ip_set_req_get_set */
+
+#define IP_SET_OP_GET_FNAME	0x00000008	/* Get set index and family */
+struct ip_set_req_get_set_family {
+	unsigned int op;
+	unsigned int version;
+	unsigned int family;
+	union ip_set_name_index set;
+};
+
+#define IP_SET_OP_VERSION	0x00000100	/* Ask kernel version */
+struct ip_set_req_version {
+	unsigned int op;
+	unsigned int version;
+};
+
+#endif /* _IP_SET_H */
diff --git a/include/uapi/linux/netfilter/x_tables.h b/include/uapi/linux/netfilter/x_tables.h
new file mode 100644
index 0000000..ae2fd12
--- /dev/null
+++ b/include/uapi/linux/netfilter/x_tables.h
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _X_TABLES_H
+#define _X_TABLES_H
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#define XT_FUNCTION_MAXNAMELEN 30
+#define XT_EXTENSION_MAXNAMELEN 29
+#define XT_TABLE_MAXNAMELEN 32
+
+struct xt_entry_match {
+	union {
+		struct {
+			__u16 match_size;
+
+			/* Used by userspace */
+			char name[XT_EXTENSION_MAXNAMELEN];
+			__u8 revision;
+		} user;
+		struct {
+			__u16 match_size;
+
+			/* Used inside the kernel */
+			struct xt_match *match;
+		} kernel;
+
+		/* Total length */
+		__u16 match_size;
+	} u;
+
+	unsigned char data[0];
+};
+
+struct xt_entry_target {
+	union {
+		struct {
+			__u16 target_size;
+
+			/* Used by userspace */
+			char name[XT_EXTENSION_MAXNAMELEN];
+			__u8 revision;
+		} user;
+		struct {
+			__u16 target_size;
+
+			/* Used inside the kernel */
+			struct xt_target *target;
+		} kernel;
+
+		/* Total length */
+		__u16 target_size;
+	} u;
+
+	unsigned char data[0];
+};
+
+#define XT_TARGET_INIT(__name, __size)					       \
+{									       \
+	.target.u.user = {						       \
+		.target_size	= XT_ALIGN(__size),			       \
+		.name		= __name,				       \
+	},								       \
+}
+
+struct xt_standard_target {
+	struct xt_entry_target target;
+	int verdict;
+};
+
+struct xt_error_target {
+	struct xt_entry_target target;
+	char errorname[XT_FUNCTION_MAXNAMELEN];
+};
+
+/* The argument to IPT_SO_GET_REVISION_*.  Returns highest revision
+ * kernel supports, if >= revision. */
+struct xt_get_revision {
+	char name[XT_EXTENSION_MAXNAMELEN];
+	__u8 revision;
+};
+
+/* CONTINUE verdict for targets */
+#define XT_CONTINUE 0xFFFFFFFF
+
+/* For standard target */
+#define XT_RETURN (-NF_REPEAT - 1)
+
+/* this is a dummy structure to find out the alignment requirement for a struct
+ * containing all the fundamental data types that are used in ipt_entry,
+ * ip6t_entry and arpt_entry.  This sucks, and it is a hack.  It will be my
+ * personal pleasure to remove it -HW
+ */
+struct _xt_align {
+	__u8 u8;
+	__u16 u16;
+	__u32 u32;
+	__u64 u64;
+};
+
+#define XT_ALIGN(s) __ALIGN_KERNEL((s), __alignof__(struct _xt_align))
+
+/* Standard return verdict, or do jump. */
+#define XT_STANDARD_TARGET ""
+/* Error verdict. */
+#define XT_ERROR_TARGET "ERROR"
+
+#define SET_COUNTER(c,b,p) do { (c).bcnt = (b); (c).pcnt = (p); } while(0)
+#define ADD_COUNTER(c,b,p) do { (c).bcnt += (b); (c).pcnt += (p); } while(0)
+
+struct xt_counters {
+	__u64 pcnt, bcnt;			/* Packet and byte counters */
+};
+
+/* The argument to IPT_SO_ADD_COUNTERS. */
+struct xt_counters_info {
+	/* Which table. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	unsigned int num_counters;
+
+	/* The counters (actually `number' of these). */
+	struct xt_counters counters[0];
+};
+
+#define XT_INV_PROTO		0x40	/* Invert the sense of PROTO. */
+
+/* fn returns 0 to continue iteration */
+#define XT_MATCH_ITERATE(type, e, fn, args...)			\
+({								\
+	unsigned int __i;					\
+	int __ret = 0;						\
+	struct xt_entry_match *__m;				\
+								\
+	for (__i = sizeof(type);				\
+	     __i < (e)->target_offset;				\
+	     __i += __m->u.match_size) {			\
+		__m = (void *)e + __i;				\
+								\
+		__ret = fn(__m , ## args);			\
+		if (__ret != 0)					\
+			break;					\
+	}							\
+	__ret;							\
+})
+
+/* fn returns 0 to continue iteration */
+#define XT_ENTRY_ITERATE_CONTINUE(type, entries, size, n, fn, args...) \
+({								\
+	unsigned int __i, __n;					\
+	int __ret = 0;						\
+	type *__entry;						\
+								\
+	for (__i = 0, __n = 0; __i < (size);			\
+	     __i += __entry->next_offset, __n++) { 		\
+		__entry = (void *)(entries) + __i;		\
+		if (__n < n)					\
+			continue;				\
+								\
+		__ret = fn(__entry , ## args);			\
+		if (__ret != 0)					\
+			break;					\
+	}							\
+	__ret;							\
+})
+
+/* fn returns 0 to continue iteration */
+#define XT_ENTRY_ITERATE(type, entries, size, fn, args...) \
+	XT_ENTRY_ITERATE_CONTINUE(type, entries, size, 0, fn, args)
+
+
+/* pos is normally a struct ipt_entry/ip6t_entry/etc. */
+#define xt_entry_foreach(pos, ehead, esize) \
+	for ((pos) = (typeof(pos))(ehead); \
+	     (pos) < (typeof(pos))((char *)(ehead) + (esize)); \
+	     (pos) = (typeof(pos))((char *)(pos) + (pos)->next_offset))
+
+/* can only be xt_entry_match, so no use of typeof here */
+#define xt_ematch_foreach(pos, entry) \
+	for ((pos) = (struct xt_entry_match *)entry->elems; \
+	     (pos) < (struct xt_entry_match *)((char *)(entry) + \
+	             (entry)->target_offset); \
+	     (pos) = (struct xt_entry_match *)((char *)(pos) + \
+	             (pos)->u.match_size))
+
+
+#endif /* _X_TABLES_H */
diff --git a/include/uapi/linux/netfilter/xt_set.h b/include/uapi/linux/netfilter/xt_set.h
new file mode 100644
index 0000000..8c1ca66
--- /dev/null
+++ b/include/uapi/linux/netfilter/xt_set.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _XT_SET_H
+#define _XT_SET_H
+
+#include <linux/types.h>
+#include <linux/netfilter/ipset/ip_set.h>
+
+/* Revision 0 interface: backward compatible with netfilter/iptables */
+
+/*
+ * Option flags for kernel operations (xt_set_info_v0)
+ */
+#define IPSET_SRC		0x01	/* Source match/add */
+#define IPSET_DST		0x02	/* Destination match/add */
+#define IPSET_MATCH_INV		0x04	/* Inverse matching */
+
+struct xt_set_info_v0 {
+	ip_set_id_t index;
+	union {
+		__u32 flags[IPSET_DIM_MAX + 1];
+		struct {
+			__u32 __flags[IPSET_DIM_MAX];
+			__u8 dim;
+			__u8 flags;
+		} compat;
+	} u;
+};
+
+/* match and target infos */
+struct xt_set_info_match_v0 {
+	struct xt_set_info_v0 match_set;
+};
+
+struct xt_set_info_target_v0 {
+	struct xt_set_info_v0 add_set;
+	struct xt_set_info_v0 del_set;
+};
+
+/* Revision 1  match and target */
+
+struct xt_set_info {
+	ip_set_id_t index;
+	__u8 dim;
+	__u8 flags;
+};
+
+/* match and target infos */
+struct xt_set_info_match_v1 {
+	struct xt_set_info match_set;
+};
+
+struct xt_set_info_target_v1 {
+	struct xt_set_info add_set;
+	struct xt_set_info del_set;
+};
+
+/* Revision 2 target */
+
+struct xt_set_info_target_v2 {
+	struct xt_set_info add_set;
+	struct xt_set_info del_set;
+	__u32 flags;
+	__u32 timeout;
+};
+
+/* Revision 3 match */
+
+struct xt_set_info_match_v3 {
+	struct xt_set_info match_set;
+	struct ip_set_counter_match0 packets;
+	struct ip_set_counter_match0 bytes;
+	__u32 flags;
+};
+
+/* Revision 3 target */
+
+struct xt_set_info_target_v3 {
+	struct xt_set_info add_set;
+	struct xt_set_info del_set;
+	struct xt_set_info map_set;
+	__u32 flags;
+	__u32 timeout;
+};
+
+/* Revision 4 match */
+
+struct xt_set_info_match_v4 {
+	struct xt_set_info match_set;
+	struct ip_set_counter_match packets;
+	struct ip_set_counter_match bytes;
+	__u32 flags;
+};
+
+#endif /*_XT_SET_H*/
diff --git a/include/uapi/linux/netfilter/xt_tcpudp.h b/include/uapi/linux/netfilter/xt_tcpudp.h
new file mode 100644
index 0000000..658c169
--- /dev/null
+++ b/include/uapi/linux/netfilter/xt_tcpudp.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _XT_TCPUDP_H
+#define _XT_TCPUDP_H
+
+#include <linux/types.h>
+
+/* TCP matching stuff */
+struct xt_tcp {
+	__u16 spts[2];			/* Source port range. */
+	__u16 dpts[2];			/* Destination port range. */
+	__u8 option;			/* TCP Option iff non-zero*/
+	__u8 flg_mask;			/* TCP flags mask byte */
+	__u8 flg_cmp;			/* TCP flags compare byte */
+	__u8 invflags;			/* Inverse flags */
+};
+
+/* Values for "inv" field in struct ipt_tcp. */
+#define XT_TCP_INV_SRCPT	0x01	/* Invert the sense of source ports. */
+#define XT_TCP_INV_DSTPT	0x02	/* Invert the sense of dest ports. */
+#define XT_TCP_INV_FLAGS	0x04	/* Invert the sense of TCP flags. */
+#define XT_TCP_INV_OPTION	0x08	/* Invert the sense of option test. */
+#define XT_TCP_INV_MASK		0x0F	/* All possible flags. */
+
+/* UDP matching stuff */
+struct xt_udp {
+	__u16 spts[2];			/* Source port range. */
+	__u16 dpts[2];			/* Destination port range. */
+	__u8 invflags;			/* Inverse flags */
+};
+
+/* Values for "invflags" field in struct ipt_udp. */
+#define XT_UDP_INV_SRCPT	0x01	/* Invert the sense of source ports. */
+#define XT_UDP_INV_DSTPT	0x02	/* Invert the sense of dest ports. */
+#define XT_UDP_INV_MASK	0x03	/* All possible flags. */
+
+
+#endif
diff --git a/include/uapi/linux/netfilter_ipv4.h b/include/uapi/linux/netfilter_ipv4.h
new file mode 100644
index 0000000..96979e3
--- /dev/null
+++ b/include/uapi/linux/netfilter_ipv4.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* IPv4-specific defines for netfilter. 
+ * (C)1998 Rusty Russell -- This code is GPL.
+ */
+#ifndef __LINUX_IP_NETFILTER_H
+#define __LINUX_IP_NETFILTER_H
+
+
+#include <linux/netfilter.h>
+
+/* only for userspace compatibility */
+
+#include <limits.h> /* for INT_MIN, INT_MAX */
+
+/* IP Hooks */
+/* After promisc drops, checksum checks. */
+#define NF_IP_PRE_ROUTING	0
+/* If the packet is destined for this box. */
+#define NF_IP_LOCAL_IN		1
+/* If the packet is destined for another interface. */
+#define NF_IP_FORWARD		2
+/* Packets coming from a local process. */
+#define NF_IP_LOCAL_OUT		3
+/* Packets about to hit the wire. */
+#define NF_IP_POST_ROUTING	4
+#define NF_IP_NUMHOOKS		5
+
+enum nf_ip_hook_priorities {
+	NF_IP_PRI_FIRST = INT_MIN,
+	NF_IP_PRI_RAW_BEFORE_DEFRAG = -450,
+	NF_IP_PRI_CONNTRACK_DEFRAG = -400,
+	NF_IP_PRI_RAW = -300,
+	NF_IP_PRI_SELINUX_FIRST = -225,
+	NF_IP_PRI_CONNTRACK = -200,
+	NF_IP_PRI_MANGLE = -150,
+	NF_IP_PRI_NAT_DST = -100,
+	NF_IP_PRI_FILTER = 0,
+	NF_IP_PRI_SECURITY = 50,
+	NF_IP_PRI_NAT_SRC = 100,
+	NF_IP_PRI_SELINUX_LAST = 225,
+	NF_IP_PRI_CONNTRACK_HELPER = 300,
+	NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
+	NF_IP_PRI_LAST = INT_MAX,
+};
+
+/* Arguments for setsockopt SOL_IP: */
+/* 2.0 firewalling went from 64 through 71 (and +256, +512, etc). */
+/* 2.2 firewalling (+ masq) went from 64 through 76 */
+/* 2.4 firewalling went 64 through 67. */
+#define SO_ORIGINAL_DST 80
+
+
+#endif /* __LINUX_IP_NETFILTER_H */
diff --git a/include/uapi/linux/netfilter_ipv4/ip_tables.h b/include/uapi/linux/netfilter_ipv4/ip_tables.h
new file mode 100644
index 0000000..409cff7
--- /dev/null
+++ b/include/uapi/linux/netfilter_ipv4/ip_tables.h
@@ -0,0 +1,229 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * 25-Jul-1998 Major changes to allow for ip chain table
+ *
+ * 3-Jan-2000 Named tables to allow packet selection for different uses.
+ */
+
+/*
+ * 	Format of an IP firewall descriptor
+ *
+ * 	src, dst, src_mask, dst_mask are always stored in network byte order.
+ * 	flags are stored in host byte order (of course).
+ * 	Port numbers are stored in HOST byte order.
+ */
+
+#ifndef _IPTABLES_H
+#define _IPTABLES_H
+
+#include <linux/types.h>
+
+#include <linux/if.h>
+#include <linux/netfilter_ipv4.h>
+
+#include <linux/netfilter/x_tables.h>
+
+#define IPT_FUNCTION_MAXNAMELEN XT_FUNCTION_MAXNAMELEN
+#define IPT_TABLE_MAXNAMELEN XT_TABLE_MAXNAMELEN
+#define ipt_match xt_match
+#define ipt_target xt_target
+#define ipt_table xt_table
+#define ipt_get_revision xt_get_revision
+#define ipt_entry_match xt_entry_match
+#define ipt_entry_target xt_entry_target
+#define ipt_standard_target xt_standard_target
+#define ipt_error_target xt_error_target
+#define ipt_counters xt_counters
+#define IPT_CONTINUE XT_CONTINUE
+#define IPT_RETURN XT_RETURN
+
+/* This group is older than old (iptables < v1.4.0-rc1~89) */
+#include <linux/netfilter/xt_tcpudp.h>
+#define ipt_udp xt_udp
+#define ipt_tcp xt_tcp
+#define IPT_TCP_INV_SRCPT	XT_TCP_INV_SRCPT
+#define IPT_TCP_INV_DSTPT	XT_TCP_INV_DSTPT
+#define IPT_TCP_INV_FLAGS	XT_TCP_INV_FLAGS
+#define IPT_TCP_INV_OPTION	XT_TCP_INV_OPTION
+#define IPT_TCP_INV_MASK	XT_TCP_INV_MASK
+#define IPT_UDP_INV_SRCPT	XT_UDP_INV_SRCPT
+#define IPT_UDP_INV_DSTPT	XT_UDP_INV_DSTPT
+#define IPT_UDP_INV_MASK	XT_UDP_INV_MASK
+
+/* The argument to IPT_SO_ADD_COUNTERS. */
+#define ipt_counters_info xt_counters_info
+/* Standard return verdict, or do jump. */
+#define IPT_STANDARD_TARGET XT_STANDARD_TARGET
+/* Error verdict. */
+#define IPT_ERROR_TARGET XT_ERROR_TARGET
+
+/* fn returns 0 to continue iteration */
+#define IPT_MATCH_ITERATE(e, fn, args...) \
+	XT_MATCH_ITERATE(struct ipt_entry, e, fn, ## args)
+
+/* fn returns 0 to continue iteration */
+#define IPT_ENTRY_ITERATE(entries, size, fn, args...) \
+	XT_ENTRY_ITERATE(struct ipt_entry, entries, size, fn, ## args)
+
+/* Yes, Virginia, you have to zero the padding. */
+struct ipt_ip {
+	/* Source and destination IP addr */
+	struct in_addr src, dst;
+	/* Mask for src and dest IP addr */
+	struct in_addr smsk, dmsk;
+	char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+
+	/* Protocol, 0 = ANY */
+	__u16 proto;
+
+	/* Flags word */
+	__u8 flags;
+	/* Inverse flags */
+	__u8 invflags;
+};
+
+/* Values for "flag" field in struct ipt_ip (general ip structure). */
+#define IPT_F_FRAG		0x01	/* Set if rule is a fragment rule */
+#define IPT_F_GOTO		0x02	/* Set if jump is a goto */
+#define IPT_F_MASK		0x03	/* All possible flag bits mask. */
+
+/* Values for "inv" field in struct ipt_ip. */
+#define IPT_INV_VIA_IN		0x01	/* Invert the sense of IN IFACE. */
+#define IPT_INV_VIA_OUT		0x02	/* Invert the sense of OUT IFACE */
+#define IPT_INV_TOS		0x04	/* Invert the sense of TOS. */
+#define IPT_INV_SRCIP		0x08	/* Invert the sense of SRC IP. */
+#define IPT_INV_DSTIP		0x10	/* Invert the sense of DST OP. */
+#define IPT_INV_FRAG		0x20	/* Invert the sense of FRAG. */
+#define IPT_INV_PROTO		XT_INV_PROTO
+#define IPT_INV_MASK		0x7F	/* All possible flag bits mask. */
+
+/* This structure defines each of the firewall rules.  Consists of 3
+   parts which are 1) general IP header stuff 2) match specific
+   stuff 3) the target to perform if the rule matches */
+struct ipt_entry {
+	struct ipt_ip ip;
+
+	/* Mark with fields that we care about. */
+	unsigned int nfcache;
+
+	/* Size of ipt_entry + matches */
+	__u16 target_offset;
+	/* Size of ipt_entry + matches + target */
+	__u16 next_offset;
+
+	/* Back pointer */
+	unsigned int comefrom;
+
+	/* Packet and byte counters. */
+	struct xt_counters counters;
+
+	/* The matches (if any), then the target. */
+	unsigned char elems[0];
+};
+
+/*
+ * New IP firewall options for [gs]etsockopt at the RAW IP level.
+ * Unlike BSD Linux inherits IP options so you don't have to use a raw
+ * socket for this. Instead we check rights in the calls.
+ *
+ * ATTENTION: check linux/in.h before adding new number here.
+ */
+#define IPT_BASE_CTL		64
+
+#define IPT_SO_SET_REPLACE	(IPT_BASE_CTL)
+#define IPT_SO_SET_ADD_COUNTERS	(IPT_BASE_CTL + 1)
+#define IPT_SO_SET_MAX		IPT_SO_SET_ADD_COUNTERS
+
+#define IPT_SO_GET_INFO			(IPT_BASE_CTL)
+#define IPT_SO_GET_ENTRIES		(IPT_BASE_CTL + 1)
+#define IPT_SO_GET_REVISION_MATCH	(IPT_BASE_CTL + 2)
+#define IPT_SO_GET_REVISION_TARGET	(IPT_BASE_CTL + 3)
+#define IPT_SO_GET_MAX			IPT_SO_GET_REVISION_TARGET
+
+/* ICMP matching stuff */
+struct ipt_icmp {
+	__u8 type;				/* type to match */
+	__u8 code[2];				/* range of code */
+	__u8 invflags;				/* Inverse flags */
+};
+
+/* Values for "inv" field for struct ipt_icmp. */
+#define IPT_ICMP_INV	0x01	/* Invert the sense of type/code test */
+
+/* The argument to IPT_SO_GET_INFO */
+struct ipt_getinfo {
+	/* Which table: caller fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Kernel fills these in. */
+	/* Which hook entry points are valid: bitmask */
+	unsigned int valid_hooks;
+
+	/* Hook entry points: one per netfilter hook. */
+	unsigned int hook_entry[NF_INET_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_INET_NUMHOOKS];
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Size of entries. */
+	unsigned int size;
+};
+
+/* The argument to IPT_SO_SET_REPLACE. */
+struct ipt_replace {
+	/* Which table. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Which hook entry points are valid: bitmask.  You can't
+           change this. */
+	unsigned int valid_hooks;
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Total size of new entries */
+	unsigned int size;
+
+	/* Hook entry points. */
+	unsigned int hook_entry[NF_INET_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_INET_NUMHOOKS];
+
+	/* Information about old entries: */
+	/* Number of counters (must be equal to current number of entries). */
+	unsigned int num_counters;
+	/* The old entries' counters. */
+	struct xt_counters *counters;
+
+	/* The entries (hang off end: not really an array). */
+	struct ipt_entry entries[0];
+};
+
+/* The argument to IPT_SO_GET_ENTRIES. */
+struct ipt_get_entries {
+	/* Which table: user fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* User fills this in: total entry size. */
+	unsigned int size;
+
+	/* The entries. */
+	struct ipt_entry entrytable[0];
+};
+
+/* Helper functions */
+static __inline__ struct xt_entry_target *
+ipt_get_target(struct ipt_entry *e)
+{
+	return (void *)e + e->target_offset;
+}
+
+/*
+ *	Main firewall chains definitions and global var's definitions.
+ */
+#endif /* _IPTABLES_H */
diff --git a/include/uapi/linux/netfilter_ipv6.h b/include/uapi/linux/netfilter_ipv6.h
new file mode 100644
index 0000000..eedf7a2
--- /dev/null
+++ b/include/uapi/linux/netfilter_ipv6.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* IPv6-specific defines for netfilter. 
+ * (C)1998 Rusty Russell -- This code is GPL.
+ * (C)1999 David Jeffery
+ *   this header was blatantly ripped from netfilter_ipv4.h 
+ *   it's amazing what adding a bunch of 6s can do =8^)
+ */
+#ifndef __LINUX_IP6_NETFILTER_H
+#define __LINUX_IP6_NETFILTER_H
+
+
+#include <linux/netfilter.h>
+
+/* only for userspace compatibility */
+
+#include <limits.h> /* for INT_MIN, INT_MAX */
+
+/* IP6 Hooks */
+/* After promisc drops, checksum checks. */
+#define NF_IP6_PRE_ROUTING	0
+/* If the packet is destined for this box. */
+#define NF_IP6_LOCAL_IN		1
+/* If the packet is destined for another interface. */
+#define NF_IP6_FORWARD		2
+/* Packets coming from a local process. */
+#define NF_IP6_LOCAL_OUT		3
+/* Packets about to hit the wire. */
+#define NF_IP6_POST_ROUTING	4
+#define NF_IP6_NUMHOOKS		5
+
+
+enum nf_ip6_hook_priorities {
+	NF_IP6_PRI_FIRST = INT_MIN,
+	NF_IP6_PRI_RAW_BEFORE_DEFRAG = -450,
+	NF_IP6_PRI_CONNTRACK_DEFRAG = -400,
+	NF_IP6_PRI_RAW = -300,
+	NF_IP6_PRI_SELINUX_FIRST = -225,
+	NF_IP6_PRI_CONNTRACK = -200,
+	NF_IP6_PRI_MANGLE = -150,
+	NF_IP6_PRI_NAT_DST = -100,
+	NF_IP6_PRI_FILTER = 0,
+	NF_IP6_PRI_SECURITY = 50,
+	NF_IP6_PRI_NAT_SRC = 100,
+	NF_IP6_PRI_SELINUX_LAST = 225,
+	NF_IP6_PRI_CONNTRACK_HELPER = 300,
+	NF_IP6_PRI_LAST = INT_MAX,
+};
+
+
+#endif /* __LINUX_IP6_NETFILTER_H */
diff --git a/include/uapi/linux/netfilter_ipv6/ip6_tables.h b/include/uapi/linux/netfilter_ipv6/ip6_tables.h
new file mode 100644
index 0000000..7ae314b
--- /dev/null
+++ b/include/uapi/linux/netfilter_ipv6/ip6_tables.h
@@ -0,0 +1,270 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * 25-Jul-1998 Major changes to allow for ip chain table
+ *
+ * 3-Jan-2000 Named tables to allow packet selection for different uses.
+ */
+
+/*
+ * 	Format of an IP6 firewall descriptor
+ *
+ * 	src, dst, src_mask, dst_mask are always stored in network byte order.
+ * 	flags are stored in host byte order (of course).
+ * 	Port numbers are stored in HOST byte order.
+ */
+
+#ifndef _IP6_TABLES_H
+#define _IP6_TABLES_H
+
+#include <linux/types.h>
+
+#include <linux/if.h>
+#include <linux/netfilter_ipv6.h>
+
+#include <linux/netfilter/x_tables.h>
+
+#define IP6T_FUNCTION_MAXNAMELEN XT_FUNCTION_MAXNAMELEN
+#define IP6T_TABLE_MAXNAMELEN XT_TABLE_MAXNAMELEN
+#define ip6t_match xt_match
+#define ip6t_target xt_target
+#define ip6t_table xt_table
+#define ip6t_get_revision xt_get_revision
+#define ip6t_entry_match xt_entry_match
+#define ip6t_entry_target xt_entry_target
+#define ip6t_standard_target xt_standard_target
+#define ip6t_error_target xt_error_target
+#define ip6t_counters xt_counters
+#define IP6T_CONTINUE XT_CONTINUE
+#define IP6T_RETURN XT_RETURN
+
+/* Pre-iptables-1.4.0 */
+#include <linux/netfilter/xt_tcpudp.h>
+#define ip6t_tcp xt_tcp
+#define ip6t_udp xt_udp
+#define IP6T_TCP_INV_SRCPT	XT_TCP_INV_SRCPT
+#define IP6T_TCP_INV_DSTPT	XT_TCP_INV_DSTPT
+#define IP6T_TCP_INV_FLAGS	XT_TCP_INV_FLAGS
+#define IP6T_TCP_INV_OPTION	XT_TCP_INV_OPTION
+#define IP6T_TCP_INV_MASK	XT_TCP_INV_MASK
+#define IP6T_UDP_INV_SRCPT	XT_UDP_INV_SRCPT
+#define IP6T_UDP_INV_DSTPT	XT_UDP_INV_DSTPT
+#define IP6T_UDP_INV_MASK	XT_UDP_INV_MASK
+
+#define ip6t_counters_info xt_counters_info
+#define IP6T_STANDARD_TARGET XT_STANDARD_TARGET
+#define IP6T_ERROR_TARGET XT_ERROR_TARGET
+#define IP6T_MATCH_ITERATE(e, fn, args...) \
+	XT_MATCH_ITERATE(struct ip6t_entry, e, fn, ## args)
+#define IP6T_ENTRY_ITERATE(entries, size, fn, args...) \
+	XT_ENTRY_ITERATE(struct ip6t_entry, entries, size, fn, ## args)
+
+/* Yes, Virginia, you have to zero the padding. */
+struct ip6t_ip6 {
+	/* Source and destination IP6 addr */
+	struct in6_addr src, dst;		
+	/* Mask for src and dest IP6 addr */
+	struct in6_addr smsk, dmsk;
+	char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+
+	/* Upper protocol number
+	 * - The allowed value is 0 (any) or protocol number of last parsable
+	 *   header, which is 50 (ESP), 59 (No Next Header), 135 (MH), or
+	 *   the non IPv6 extension headers.
+	 * - The protocol numbers of IPv6 extension headers except of ESP and
+	 *   MH do not match any packets.
+	 * - You also need to set IP6T_FLAGS_PROTO to "flags" to check protocol.
+	 */
+	__u16 proto;
+	/* TOS to match iff flags & IP6T_F_TOS */
+	__u8 tos;
+
+	/* Flags word */
+	__u8 flags;
+	/* Inverse flags */
+	__u8 invflags;
+};
+
+/* Values for "flag" field in struct ip6t_ip6 (general ip6 structure). */
+#define IP6T_F_PROTO		0x01	/* Set if rule cares about upper 
+					   protocols */
+#define IP6T_F_TOS		0x02	/* Match the TOS. */
+#define IP6T_F_GOTO		0x04	/* Set if jump is a goto */
+#define IP6T_F_MASK		0x07	/* All possible flag bits mask. */
+
+/* Values for "inv" field in struct ip6t_ip6. */
+#define IP6T_INV_VIA_IN		0x01	/* Invert the sense of IN IFACE. */
+#define IP6T_INV_VIA_OUT		0x02	/* Invert the sense of OUT IFACE */
+#define IP6T_INV_TOS		0x04	/* Invert the sense of TOS. */
+#define IP6T_INV_SRCIP		0x08	/* Invert the sense of SRC IP. */
+#define IP6T_INV_DSTIP		0x10	/* Invert the sense of DST OP. */
+#define IP6T_INV_FRAG		0x20	/* Invert the sense of FRAG. */
+#define IP6T_INV_PROTO		XT_INV_PROTO
+#define IP6T_INV_MASK		0x7F	/* All possible flag bits mask. */
+
+/* This structure defines each of the firewall rules.  Consists of 3
+   parts which are 1) general IP header stuff 2) match specific
+   stuff 3) the target to perform if the rule matches */
+struct ip6t_entry {
+	struct ip6t_ip6 ipv6;
+
+	/* Mark with fields that we care about. */
+	unsigned int nfcache;
+
+	/* Size of ipt_entry + matches */
+	__u16 target_offset;
+	/* Size of ipt_entry + matches + target */
+	__u16 next_offset;
+
+	/* Back pointer */
+	unsigned int comefrom;
+
+	/* Packet and byte counters. */
+	struct xt_counters counters;
+
+	/* The matches (if any), then the target. */
+	unsigned char elems[0];
+};
+
+/* Standard entry */
+struct ip6t_standard {
+	struct ip6t_entry entry;
+	struct xt_standard_target target;
+};
+
+struct ip6t_error {
+	struct ip6t_entry entry;
+	struct xt_error_target target;
+};
+
+#define IP6T_ENTRY_INIT(__size)						       \
+{									       \
+	.target_offset	= sizeof(struct ip6t_entry),			       \
+	.next_offset	= (__size),					       \
+}
+
+#define IP6T_STANDARD_INIT(__verdict)					       \
+{									       \
+	.entry		= IP6T_ENTRY_INIT(sizeof(struct ip6t_standard)),       \
+	.target		= XT_TARGET_INIT(XT_STANDARD_TARGET,		       \
+					 sizeof(struct xt_standard_target)),   \
+	.target.verdict	= -(__verdict) - 1,				       \
+}
+
+#define IP6T_ERROR_INIT							       \
+{									       \
+	.entry		= IP6T_ENTRY_INIT(sizeof(struct ip6t_error)),	       \
+	.target		= XT_TARGET_INIT(XT_ERROR_TARGET,		       \
+					 sizeof(struct xt_error_target)),      \
+	.target.errorname = "ERROR",					       \
+}
+
+/*
+ * New IP firewall options for [gs]etsockopt at the RAW IP level.
+ * Unlike BSD Linux inherits IP options so you don't have to use
+ * a raw socket for this. Instead we check rights in the calls.
+ *
+ * ATTENTION: check linux/in6.h before adding new number here.
+ */
+#define IP6T_BASE_CTL			64
+
+#define IP6T_SO_SET_REPLACE		(IP6T_BASE_CTL)
+#define IP6T_SO_SET_ADD_COUNTERS	(IP6T_BASE_CTL + 1)
+#define IP6T_SO_SET_MAX			IP6T_SO_SET_ADD_COUNTERS
+
+#define IP6T_SO_GET_INFO		(IP6T_BASE_CTL)
+#define IP6T_SO_GET_ENTRIES		(IP6T_BASE_CTL + 1)
+#define IP6T_SO_GET_REVISION_MATCH	(IP6T_BASE_CTL + 4)
+#define IP6T_SO_GET_REVISION_TARGET	(IP6T_BASE_CTL + 5)
+#define IP6T_SO_GET_MAX			IP6T_SO_GET_REVISION_TARGET
+
+/* obtain original address if REDIRECT'd connection */
+#define IP6T_SO_ORIGINAL_DST            80
+
+/* ICMP matching stuff */
+struct ip6t_icmp {
+	__u8 type;				/* type to match */
+	__u8 code[2];				/* range of code */
+	__u8 invflags;				/* Inverse flags */
+};
+
+/* Values for "inv" field for struct ipt_icmp. */
+#define IP6T_ICMP_INV	0x01	/* Invert the sense of type/code test */
+
+/* The argument to IP6T_SO_GET_INFO */
+struct ip6t_getinfo {
+	/* Which table: caller fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Kernel fills these in. */
+	/* Which hook entry points are valid: bitmask */
+	unsigned int valid_hooks;
+
+	/* Hook entry points: one per netfilter hook. */
+	unsigned int hook_entry[NF_INET_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_INET_NUMHOOKS];
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Size of entries. */
+	unsigned int size;
+};
+
+/* The argument to IP6T_SO_SET_REPLACE. */
+struct ip6t_replace {
+	/* Which table. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Which hook entry points are valid: bitmask.  You can't
+           change this. */
+	unsigned int valid_hooks;
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Total size of new entries */
+	unsigned int size;
+
+	/* Hook entry points. */
+	unsigned int hook_entry[NF_INET_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_INET_NUMHOOKS];
+
+	/* Information about old entries: */
+	/* Number of counters (must be equal to current number of entries). */
+	unsigned int num_counters;
+	/* The old entries' counters. */
+	struct xt_counters *counters;
+
+	/* The entries (hang off end: not really an array). */
+	struct ip6t_entry entries[0];
+};
+
+/* The argument to IP6T_SO_GET_ENTRIES. */
+struct ip6t_get_entries {
+	/* Which table: user fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* User fills this in: total entry size. */
+	unsigned int size;
+
+	/* The entries. */
+	struct ip6t_entry entrytable[0];
+};
+
+/* Helper functions */
+static __inline__ struct xt_entry_target *
+ip6t_get_target(struct ip6t_entry *e)
+{
+	return (void *)e + e->target_offset;
+}
+
+/*
+ *	Main firewall chains definitions and global var's definitions.
+ */
+
+#endif /* _IP6_TABLES_H */
diff --git a/include/uapi/linux/netlink.h b/include/uapi/linux/netlink.h
new file mode 100644
index 0000000..2c28d32
--- /dev/null
+++ b/include/uapi/linux/netlink.h
@@ -0,0 +1,248 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_NETLINK_H
+#define __LINUX_NETLINK_H
+
+#include <linux/kernel.h>
+#include <linux/socket.h> /* for __kernel_sa_family_t */
+#include <linux/types.h>
+
+#define NETLINK_ROUTE		0	/* Routing/device hook				*/
+#define NETLINK_UNUSED		1	/* Unused number				*/
+#define NETLINK_USERSOCK	2	/* Reserved for user mode socket protocols 	*/
+#define NETLINK_FIREWALL	3	/* Unused number, formerly ip_queue		*/
+#define NETLINK_SOCK_DIAG	4	/* socket monitoring				*/
+#define NETLINK_NFLOG		5	/* netfilter/iptables ULOG */
+#define NETLINK_XFRM		6	/* ipsec */
+#define NETLINK_SELINUX		7	/* SELinux event notifications */
+#define NETLINK_ISCSI		8	/* Open-iSCSI */
+#define NETLINK_AUDIT		9	/* auditing */
+#define NETLINK_FIB_LOOKUP	10	
+#define NETLINK_CONNECTOR	11
+#define NETLINK_NETFILTER	12	/* netfilter subsystem */
+#define NETLINK_IP6_FW		13
+#define NETLINK_DNRTMSG		14	/* DECnet routing messages */
+#define NETLINK_KOBJECT_UEVENT	15	/* Kernel messages to userspace */
+#define NETLINK_GENERIC		16
+/* leave room for NETLINK_DM (DM Events) */
+#define NETLINK_SCSITRANSPORT	18	/* SCSI Transports */
+#define NETLINK_ECRYPTFS	19
+#define NETLINK_RDMA		20
+#define NETLINK_CRYPTO		21	/* Crypto layer */
+#define NETLINK_SMC		22	/* SMC monitoring */
+
+#define NETLINK_INET_DIAG	NETLINK_SOCK_DIAG
+
+#define MAX_LINKS 32		
+
+struct sockaddr_nl {
+	__kernel_sa_family_t	nl_family;	/* AF_NETLINK	*/
+	unsigned short	nl_pad;		/* zero		*/
+	__u32		nl_pid;		/* port ID	*/
+       	__u32		nl_groups;	/* multicast groups mask */
+};
+
+struct nlmsghdr {
+	__u32		nlmsg_len;	/* Length of message including header */
+	__u16		nlmsg_type;	/* Message content */
+	__u16		nlmsg_flags;	/* Additional flags */
+	__u32		nlmsg_seq;	/* Sequence number */
+	__u32		nlmsg_pid;	/* Sending process port ID */
+};
+
+/* Flags values */
+
+#define NLM_F_REQUEST		0x01	/* It is request message. 	*/
+#define NLM_F_MULTI		0x02	/* Multipart message, terminated by NLMSG_DONE */
+#define NLM_F_ACK		0x04	/* Reply with ack, with zero or error code */
+#define NLM_F_ECHO		0x08	/* Echo this request 		*/
+#define NLM_F_DUMP_INTR		0x10	/* Dump was inconsistent due to sequence change */
+#define NLM_F_DUMP_FILTERED	0x20	/* Dump was filtered as requested */
+
+/* Modifiers to GET request */
+#define NLM_F_ROOT	0x100	/* specify tree	root	*/
+#define NLM_F_MATCH	0x200	/* return all matching	*/
+#define NLM_F_ATOMIC	0x400	/* atomic GET		*/
+#define NLM_F_DUMP	(NLM_F_ROOT|NLM_F_MATCH)
+
+/* Modifiers to NEW request */
+#define NLM_F_REPLACE	0x100	/* Override existing		*/
+#define NLM_F_EXCL	0x200	/* Do not touch, if it exists	*/
+#define NLM_F_CREATE	0x400	/* Create, if it does not exist	*/
+#define NLM_F_APPEND	0x800	/* Add to end of list		*/
+
+/* Modifiers to DELETE request */
+#define NLM_F_NONREC	0x100	/* Do not delete recursively	*/
+
+/* Flags for ACK message */
+#define NLM_F_CAPPED	0x100	/* request was capped */
+#define NLM_F_ACK_TLVS	0x200	/* extended ACK TVLs were included */
+
+/*
+   4.4BSD ADD		NLM_F_CREATE|NLM_F_EXCL
+   4.4BSD CHANGE	NLM_F_REPLACE
+
+   True CHANGE		NLM_F_CREATE|NLM_F_REPLACE
+   Append		NLM_F_CREATE
+   Check		NLM_F_EXCL
+ */
+
+#define NLMSG_ALIGNTO	4U
+#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
+#define NLMSG_HDRLEN	 ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
+#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
+#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
+#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
+#define NLMSG_NEXT(nlh,len)	 ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
+				  (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
+#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
+			   (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
+			   (nlh)->nlmsg_len <= (len))
+#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
+
+#define NLMSG_NOOP		0x1	/* Nothing.		*/
+#define NLMSG_ERROR		0x2	/* Error		*/
+#define NLMSG_DONE		0x3	/* End of a dump	*/
+#define NLMSG_OVERRUN		0x4	/* Data lost		*/
+
+#define NLMSG_MIN_TYPE		0x10	/* < 0x10: reserved control messages */
+
+struct nlmsgerr {
+	int		error;
+	struct nlmsghdr msg;
+	/*
+	 * followed by the message contents unless NETLINK_CAP_ACK was set
+	 * or the ACK indicates success (error == 0)
+	 * message length is aligned with NLMSG_ALIGN()
+	 */
+	/*
+	 * followed by TLVs defined in enum nlmsgerr_attrs
+	 * if NETLINK_EXT_ACK was set
+	 */
+};
+
+/**
+ * enum nlmsgerr_attrs - nlmsgerr attributes
+ * @NLMSGERR_ATTR_UNUSED: unused
+ * @NLMSGERR_ATTR_MSG: error message string (string)
+ * @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original
+ *	 message, counting from the beginning of the header (u32)
+ * @NLMSGERR_ATTR_COOKIE: arbitrary subsystem specific cookie to
+ *	be used - in the success case - to identify a created
+ *	object or operation or similar (binary)
+ * @__NLMSGERR_ATTR_MAX: number of attributes
+ * @NLMSGERR_ATTR_MAX: highest attribute number
+ */
+enum nlmsgerr_attrs {
+	NLMSGERR_ATTR_UNUSED,
+	NLMSGERR_ATTR_MSG,
+	NLMSGERR_ATTR_OFFS,
+	NLMSGERR_ATTR_COOKIE,
+
+	__NLMSGERR_ATTR_MAX,
+	NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1
+};
+
+#define NETLINK_ADD_MEMBERSHIP		1
+#define NETLINK_DROP_MEMBERSHIP		2
+#define NETLINK_PKTINFO			3
+#define NETLINK_BROADCAST_ERROR		4
+#define NETLINK_NO_ENOBUFS		5
+#define NETLINK_RX_RING			6
+#define NETLINK_TX_RING			7
+#define NETLINK_LISTEN_ALL_NSID		8
+#define NETLINK_LIST_MEMBERSHIPS	9
+#define NETLINK_CAP_ACK			10
+#define NETLINK_EXT_ACK			11
+#define NETLINK_GET_STRICT_CHK		12
+
+struct nl_pktinfo {
+	__u32	group;
+};
+
+struct nl_mmap_req {
+	unsigned int	nm_block_size;
+	unsigned int	nm_block_nr;
+	unsigned int	nm_frame_size;
+	unsigned int	nm_frame_nr;
+};
+
+struct nl_mmap_hdr {
+	unsigned int	nm_status;
+	unsigned int	nm_len;
+	__u32		nm_group;
+	/* credentials */
+	__u32		nm_pid;
+	__u32		nm_uid;
+	__u32		nm_gid;
+};
+
+enum nl_mmap_status {
+	NL_MMAP_STATUS_UNUSED,
+	NL_MMAP_STATUS_RESERVED,
+	NL_MMAP_STATUS_VALID,
+	NL_MMAP_STATUS_COPY,
+	NL_MMAP_STATUS_SKIP,
+};
+
+#define NL_MMAP_MSG_ALIGNMENT		NLMSG_ALIGNTO
+#define NL_MMAP_MSG_ALIGN(sz)		__ALIGN_KERNEL(sz, NL_MMAP_MSG_ALIGNMENT)
+#define NL_MMAP_HDRLEN			NL_MMAP_MSG_ALIGN(sizeof(struct nl_mmap_hdr))
+
+#define NET_MAJOR 36		/* Major 36 is reserved for networking 						*/
+
+enum {
+	NETLINK_UNCONNECTED = 0,
+	NETLINK_CONNECTED,
+};
+
+/*
+ *  <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ * |        Header       | Pad |     Payload       | Pad |
+ * |   (struct nlattr)   | ing |                   | ing |
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ *  <-------------- nlattr->nla_len -------------->
+ */
+
+struct nlattr {
+	__u16           nla_len;
+	__u16           nla_type;
+};
+
+/*
+ * nla_type (16 bits)
+ * +---+---+-------------------------------+
+ * | N | O | Attribute Type                |
+ * +---+---+-------------------------------+
+ * N := Carries nested attributes
+ * O := Payload stored in network byte order
+ *
+ * Note: The N and O flag are mutually exclusive.
+ */
+#define NLA_F_NESTED		(1 << 15)
+#define NLA_F_NET_BYTEORDER	(1 << 14)
+#define NLA_TYPE_MASK		~(NLA_F_NESTED | NLA_F_NET_BYTEORDER)
+
+#define NLA_ALIGNTO		4
+#define NLA_ALIGN(len)		(((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
+#define NLA_HDRLEN		((int) NLA_ALIGN(sizeof(struct nlattr)))
+
+/* Generic 32 bitflags attribute content sent to the kernel.
+ *
+ * The value is a bitmap that defines the values being set
+ * The selector is a bitmask that defines which value is legit
+ *
+ * Examples:
+ *  value = 0x0, and selector = 0x1
+ *  implies we are selecting bit 1 and we want to set its value to 0.
+ *
+ *  value = 0x2, and selector = 0x2
+ *  implies we are selecting bit 2 and we want to set its value to 1.
+ *
+ */
+struct nla_bitfield32 {
+	__u32 value;
+	__u32 selector;
+};
+
+#endif /* __LINUX_NETLINK_H */
diff --git a/include/uapi/linux/netlink_diag.h b/include/uapi/linux/netlink_diag.h
new file mode 100644
index 0000000..4cd0657
--- /dev/null
+++ b/include/uapi/linux/netlink_diag.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __NETLINK_DIAG_H__
+#define __NETLINK_DIAG_H__
+
+#include <linux/types.h>
+
+struct netlink_diag_req {
+	__u8	sdiag_family;
+	__u8	sdiag_protocol;
+	__u16	pad;
+	__u32	ndiag_ino;
+	__u32	ndiag_show;
+	__u32	ndiag_cookie[2];
+};
+
+struct netlink_diag_msg {
+	__u8	ndiag_family;
+	__u8	ndiag_type;
+	__u8	ndiag_protocol;
+	__u8	ndiag_state;
+
+	__u32	ndiag_portid;
+	__u32	ndiag_dst_portid;
+	__u32	ndiag_dst_group;
+	__u32	ndiag_ino;
+	__u32	ndiag_cookie[2];
+};
+
+struct netlink_diag_ring {
+	__u32	ndr_block_size;
+	__u32	ndr_block_nr;
+	__u32	ndr_frame_size;
+	__u32	ndr_frame_nr;
+};
+
+enum {
+	/* NETLINK_DIAG_NONE, standard nl API requires this attribute!  */
+	NETLINK_DIAG_MEMINFO,
+	NETLINK_DIAG_GROUPS,
+	NETLINK_DIAG_RX_RING,
+	NETLINK_DIAG_TX_RING,
+	NETLINK_DIAG_FLAGS,
+
+	__NETLINK_DIAG_MAX,
+};
+
+#define NETLINK_DIAG_MAX (__NETLINK_DIAG_MAX - 1)
+
+#define NDIAG_PROTO_ALL		((__u8) ~0)
+
+#define NDIAG_SHOW_MEMINFO	0x00000001 /* show memory info of a socket */
+#define NDIAG_SHOW_GROUPS	0x00000002 /* show groups of a netlink socket */
+/* deprecated since 4.6 */
+#define NDIAG_SHOW_RING_CFG	0x00000004 /* show ring configuration */
+#define NDIAG_SHOW_FLAGS	0x00000008 /* show flags of a netlink socket */
+
+/* flags */
+#define NDIAG_FLAG_CB_RUNNING		0x00000001
+#define NDIAG_FLAG_PKTINFO		0x00000002
+#define NDIAG_FLAG_BROADCAST_ERROR	0x00000004
+#define NDIAG_FLAG_NO_ENOBUFS		0x00000008
+#define NDIAG_FLAG_LISTEN_ALL_NSID	0x00000010
+#define NDIAG_FLAG_CAP_ACK		0x00000020
+
+#endif
diff --git a/include/uapi/linux/nexthop.h b/include/uapi/linux/nexthop.h
new file mode 100644
index 0000000..b56c5b8
--- /dev/null
+++ b/include/uapi/linux/nexthop.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_NEXTHOP_H
+#define _LINUX_NEXTHOP_H
+
+#include <linux/types.h>
+
+struct nhmsg {
+	unsigned char	nh_family;
+	unsigned char	nh_scope;     /* return only */
+	unsigned char	nh_protocol;  /* Routing protocol that installed nh */
+	unsigned char	resvd;
+	unsigned int	nh_flags;     /* RTNH_F flags */
+};
+
+/* entry in a nexthop group */
+struct nexthop_grp {
+	__u32	id;	  /* nexthop id - must exist */
+	__u8	weight;   /* weight of this nexthop */
+	__u8	resvd1;
+	__u16	resvd2;
+};
+
+enum {
+	NEXTHOP_GRP_TYPE_MPATH,  /* default type if not specified */
+	__NEXTHOP_GRP_TYPE_MAX,
+};
+
+#define NEXTHOP_GRP_TYPE_MAX (__NEXTHOP_GRP_TYPE_MAX - 1)
+
+enum {
+	NHA_UNSPEC,
+	NHA_ID,		/* u32; id for nexthop. id == 0 means auto-assign */
+
+	NHA_GROUP,	/* array of nexthop_grp */
+	NHA_GROUP_TYPE,	/* u16 one of NEXTHOP_GRP_TYPE */
+	/* if NHA_GROUP attribute is added, no other attributes can be set */
+
+	NHA_BLACKHOLE,	/* flag; nexthop used to blackhole packets */
+	/* if NHA_BLACKHOLE is added, OIF, GATEWAY, ENCAP can not be set */
+
+	NHA_OIF,	/* u32; nexthop device */
+	NHA_GATEWAY,	/* be32 (IPv4) or in6_addr (IPv6) gw address */
+	NHA_ENCAP_TYPE, /* u16; lwt encap type */
+	NHA_ENCAP,	/* lwt encap data */
+
+	/* NHA_OIF can be appended to dump request to return only
+	 * nexthops using given device
+	 */
+	NHA_GROUPS,	/* flag; only return nexthop groups in dump */
+	NHA_MASTER,	/* u32;  only return nexthops with given master dev */
+
+	__NHA_MAX,
+};
+
+#define NHA_MAX	(__NHA_MAX - 1)
+#endif
diff --git a/include/uapi/linux/packet_diag.h b/include/uapi/linux/packet_diag.h
new file mode 100644
index 0000000..349ddf0
--- /dev/null
+++ b/include/uapi/linux/packet_diag.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __PACKET_DIAG_H__
+#define __PACKET_DIAG_H__
+
+#include <linux/types.h>
+
+struct packet_diag_req {
+	__u8	sdiag_family;
+	__u8	sdiag_protocol;
+	__u16	pad;
+	__u32	pdiag_ino;
+	__u32	pdiag_show;
+	__u32	pdiag_cookie[2];
+};
+
+#define PACKET_SHOW_INFO	0x00000001 /* Basic packet_sk information */
+#define PACKET_SHOW_MCLIST	0x00000002 /* A set of packet_diag_mclist-s */
+#define PACKET_SHOW_RING_CFG	0x00000004 /* Rings configuration parameters */
+#define PACKET_SHOW_FANOUT	0x00000008
+#define PACKET_SHOW_MEMINFO	0x00000010
+#define PACKET_SHOW_FILTER	0x00000020
+
+struct packet_diag_msg {
+	__u8	pdiag_family;
+	__u8	pdiag_type;
+	__u16	pdiag_num;
+
+	__u32	pdiag_ino;
+	__u32	pdiag_cookie[2];
+};
+
+enum {
+	/* PACKET_DIAG_NONE, standard nl API requires this attribute!  */
+	PACKET_DIAG_INFO,
+	PACKET_DIAG_MCLIST,
+	PACKET_DIAG_RX_RING,
+	PACKET_DIAG_TX_RING,
+	PACKET_DIAG_FANOUT,
+	PACKET_DIAG_UID,
+	PACKET_DIAG_MEMINFO,
+	PACKET_DIAG_FILTER,
+
+	__PACKET_DIAG_MAX,
+};
+
+#define PACKET_DIAG_MAX (__PACKET_DIAG_MAX - 1)
+
+struct packet_diag_info {
+	__u32	pdi_index;
+	__u32	pdi_version;
+	__u32	pdi_reserve;
+	__u32	pdi_copy_thresh;
+	__u32	pdi_tstamp;
+	__u32	pdi_flags;
+
+#define PDI_RUNNING	0x1
+#define PDI_AUXDATA	0x2
+#define PDI_ORIGDEV	0x4
+#define PDI_VNETHDR	0x8
+#define PDI_LOSS	0x10
+};
+
+struct packet_diag_mclist {
+	__u32	pdmc_index;
+	__u32	pdmc_count;
+	__u16	pdmc_type;
+	__u16	pdmc_alen;
+	__u8	pdmc_addr[32]; /* MAX_ADDR_LEN */
+};
+
+struct packet_diag_ring {
+	__u32	pdr_block_size;
+	__u32	pdr_block_nr;
+	__u32	pdr_frame_size;
+	__u32	pdr_frame_nr;
+	__u32	pdr_retire_tmo;
+	__u32	pdr_sizeof_priv;
+	__u32	pdr_features;
+};
+
+#endif
diff --git a/include/uapi/linux/param.h b/include/uapi/linux/param.h
new file mode 100644
index 0000000..94e0c57
--- /dev/null
+++ b/include/uapi/linux/param.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_PARAM_H
+#define _LINUX_PARAM_H
+
+#include <asm/param.h>
+
+#endif
diff --git a/include/uapi/linux/pfkeyv2.h b/include/uapi/linux/pfkeyv2.h
new file mode 100644
index 0000000..d65b117
--- /dev/null
+++ b/include/uapi/linux/pfkeyv2.h
@@ -0,0 +1,384 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* PF_KEY user interface, this is defined by rfc2367 so
+ * do not make arbitrary modifications or else this header
+ * file will not be compliant.
+ */
+
+#ifndef _LINUX_PFKEY2_H
+#define _LINUX_PFKEY2_H
+
+#include <linux/types.h>
+
+#define PF_KEY_V2		2
+#define PFKEYV2_REVISION	199806L
+
+struct sadb_msg {
+	__u8		sadb_msg_version;
+	__u8		sadb_msg_type;
+	__u8		sadb_msg_errno;
+	__u8		sadb_msg_satype;
+	__u16	sadb_msg_len;
+	__u16	sadb_msg_reserved;
+	__u32	sadb_msg_seq;
+	__u32	sadb_msg_pid;
+} __attribute__((packed));
+/* sizeof(struct sadb_msg) == 16 */
+
+struct sadb_ext {
+	__u16	sadb_ext_len;
+	__u16	sadb_ext_type;
+} __attribute__((packed));
+/* sizeof(struct sadb_ext) == 4 */
+
+struct sadb_sa {
+	__u16	sadb_sa_len;
+	__u16	sadb_sa_exttype;
+	__be32		sadb_sa_spi;
+	__u8		sadb_sa_replay;
+	__u8		sadb_sa_state;
+	__u8		sadb_sa_auth;
+	__u8		sadb_sa_encrypt;
+	__u32	sadb_sa_flags;
+} __attribute__((packed));
+/* sizeof(struct sadb_sa) == 16 */
+
+struct sadb_lifetime {
+	__u16	sadb_lifetime_len;
+	__u16	sadb_lifetime_exttype;
+	__u32	sadb_lifetime_allocations;
+	__u64	sadb_lifetime_bytes;
+	__u64	sadb_lifetime_addtime;
+	__u64	sadb_lifetime_usetime;
+} __attribute__((packed));
+/* sizeof(struct sadb_lifetime) == 32 */
+
+struct sadb_address {
+	__u16	sadb_address_len;
+	__u16	sadb_address_exttype;
+	__u8		sadb_address_proto;
+	__u8		sadb_address_prefixlen;
+	__u16	sadb_address_reserved;
+} __attribute__((packed));
+/* sizeof(struct sadb_address) == 8 */
+
+struct sadb_key {
+	__u16	sadb_key_len;
+	__u16	sadb_key_exttype;
+	__u16	sadb_key_bits;
+	__u16	sadb_key_reserved;
+} __attribute__((packed));
+/* sizeof(struct sadb_key) == 8 */
+
+struct sadb_ident {
+	__u16	sadb_ident_len;
+	__u16	sadb_ident_exttype;
+	__u16	sadb_ident_type;
+	__u16	sadb_ident_reserved;
+	__u64	sadb_ident_id;
+} __attribute__((packed));
+/* sizeof(struct sadb_ident) == 16 */
+
+struct sadb_sens {
+	__u16	sadb_sens_len;
+	__u16	sadb_sens_exttype;
+	__u32	sadb_sens_dpd;
+	__u8		sadb_sens_sens_level;
+	__u8		sadb_sens_sens_len;
+	__u8		sadb_sens_integ_level;
+	__u8		sadb_sens_integ_len;
+	__u32	sadb_sens_reserved;
+} __attribute__((packed));
+/* sizeof(struct sadb_sens) == 16 */
+
+/* followed by:
+	__u64	sadb_sens_bitmap[sens_len];
+	__u64	sadb_integ_bitmap[integ_len];  */
+
+struct sadb_prop {
+	__u16	sadb_prop_len;
+	__u16	sadb_prop_exttype;
+	__u8		sadb_prop_replay;
+	__u8		sadb_prop_reserved[3];
+} __attribute__((packed));
+/* sizeof(struct sadb_prop) == 8 */
+
+/* followed by:
+	struct sadb_comb sadb_combs[(sadb_prop_len +
+		sizeof(__u64) - sizeof(struct sadb_prop)) /
+		sizeof(struct sadb_comb)]; */
+
+struct sadb_comb {
+	__u8		sadb_comb_auth;
+	__u8		sadb_comb_encrypt;
+	__u16	sadb_comb_flags;
+	__u16	sadb_comb_auth_minbits;
+	__u16	sadb_comb_auth_maxbits;
+	__u16	sadb_comb_encrypt_minbits;
+	__u16	sadb_comb_encrypt_maxbits;
+	__u32	sadb_comb_reserved;
+	__u32	sadb_comb_soft_allocations;
+	__u32	sadb_comb_hard_allocations;
+	__u64	sadb_comb_soft_bytes;
+	__u64	sadb_comb_hard_bytes;
+	__u64	sadb_comb_soft_addtime;
+	__u64	sadb_comb_hard_addtime;
+	__u64	sadb_comb_soft_usetime;
+	__u64	sadb_comb_hard_usetime;
+} __attribute__((packed));
+/* sizeof(struct sadb_comb) == 72 */
+
+struct sadb_supported {
+	__u16	sadb_supported_len;
+	__u16	sadb_supported_exttype;
+	__u32	sadb_supported_reserved;
+} __attribute__((packed));
+/* sizeof(struct sadb_supported) == 8 */
+
+/* followed by:
+	struct sadb_alg sadb_algs[(sadb_supported_len +
+		sizeof(__u64) - sizeof(struct sadb_supported)) /
+		sizeof(struct sadb_alg)]; */
+
+struct sadb_alg {
+	__u8		sadb_alg_id;
+	__u8		sadb_alg_ivlen;
+	__u16	sadb_alg_minbits;
+	__u16	sadb_alg_maxbits;
+	__u16	sadb_alg_reserved;
+} __attribute__((packed));
+/* sizeof(struct sadb_alg) == 8 */
+
+struct sadb_spirange {
+	__u16	sadb_spirange_len;
+	__u16	sadb_spirange_exttype;
+	__u32	sadb_spirange_min;
+	__u32	sadb_spirange_max;
+	__u32	sadb_spirange_reserved;
+} __attribute__((packed));
+/* sizeof(struct sadb_spirange) == 16 */
+
+struct sadb_x_kmprivate {
+	__u16	sadb_x_kmprivate_len;
+	__u16	sadb_x_kmprivate_exttype;
+	__u32	sadb_x_kmprivate_reserved;
+} __attribute__((packed));
+/* sizeof(struct sadb_x_kmprivate) == 8 */
+
+struct sadb_x_sa2 {
+	__u16	sadb_x_sa2_len;
+	__u16	sadb_x_sa2_exttype;
+	__u8		sadb_x_sa2_mode;
+	__u8		sadb_x_sa2_reserved1;
+	__u16	sadb_x_sa2_reserved2;
+	__u32	sadb_x_sa2_sequence;
+	__u32	sadb_x_sa2_reqid;
+} __attribute__((packed));
+/* sizeof(struct sadb_x_sa2) == 16 */
+
+struct sadb_x_policy {
+	__u16	sadb_x_policy_len;
+	__u16	sadb_x_policy_exttype;
+	__u16	sadb_x_policy_type;
+	__u8		sadb_x_policy_dir;
+	__u8		sadb_x_policy_reserved;
+	__u32	sadb_x_policy_id;
+	__u32	sadb_x_policy_priority;
+} __attribute__((packed));
+/* sizeof(struct sadb_x_policy) == 16 */
+
+struct sadb_x_ipsecrequest {
+	__u16	sadb_x_ipsecrequest_len;
+	__u16	sadb_x_ipsecrequest_proto;
+	__u8		sadb_x_ipsecrequest_mode;
+	__u8		sadb_x_ipsecrequest_level;
+	__u16	sadb_x_ipsecrequest_reserved1;
+	__u32	sadb_x_ipsecrequest_reqid;
+	__u32	sadb_x_ipsecrequest_reserved2;
+} __attribute__((packed));
+/* sizeof(struct sadb_x_ipsecrequest) == 16 */
+
+/* This defines the TYPE of Nat Traversal in use.  Currently only one
+ * type of NAT-T is supported, draft-ietf-ipsec-udp-encaps-06
+ */
+struct sadb_x_nat_t_type {
+	__u16	sadb_x_nat_t_type_len;
+	__u16	sadb_x_nat_t_type_exttype;
+	__u8		sadb_x_nat_t_type_type;
+	__u8		sadb_x_nat_t_type_reserved[3];
+} __attribute__((packed));
+/* sizeof(struct sadb_x_nat_t_type) == 8 */
+
+/* Pass a NAT Traversal port (Source or Dest port) */
+struct sadb_x_nat_t_port {
+	__u16	sadb_x_nat_t_port_len;
+	__u16	sadb_x_nat_t_port_exttype;
+	__be16		sadb_x_nat_t_port_port;
+	__u16	sadb_x_nat_t_port_reserved;
+} __attribute__((packed));
+/* sizeof(struct sadb_x_nat_t_port) == 8 */
+
+/* Generic LSM security context */
+struct sadb_x_sec_ctx {
+	__u16	sadb_x_sec_len;
+	__u16	sadb_x_sec_exttype;
+	__u8		sadb_x_ctx_alg;  /* LSMs: e.g., selinux == 1 */
+	__u8		sadb_x_ctx_doi;
+	__u16	sadb_x_ctx_len;
+} __attribute__((packed));
+/* sizeof(struct sadb_sec_ctx) = 8 */
+
+/* Used by MIGRATE to pass addresses IKE will use to perform
+ * negotiation with the peer */
+struct sadb_x_kmaddress {
+	__u16	sadb_x_kmaddress_len;
+	__u16	sadb_x_kmaddress_exttype;
+	__u32	sadb_x_kmaddress_reserved;
+} __attribute__((packed));
+/* sizeof(struct sadb_x_kmaddress) == 8 */
+
+/* To specify the SA dump filter */
+struct sadb_x_filter {
+	__u16	sadb_x_filter_len;
+	__u16	sadb_x_filter_exttype;
+	__u32	sadb_x_filter_saddr[4];
+	__u32	sadb_x_filter_daddr[4];
+	__u16	sadb_x_filter_family;
+	__u8	sadb_x_filter_splen;
+	__u8	sadb_x_filter_dplen;
+} __attribute__((packed));
+/* sizeof(struct sadb_x_filter) == 40 */
+
+/* Message types */
+#define SADB_RESERVED		0
+#define SADB_GETSPI		1
+#define SADB_UPDATE		2
+#define SADB_ADD		3
+#define SADB_DELETE		4
+#define SADB_GET		5
+#define SADB_ACQUIRE		6
+#define SADB_REGISTER		7
+#define SADB_EXPIRE		8
+#define SADB_FLUSH		9
+#define SADB_DUMP		10
+#define SADB_X_PROMISC		11
+#define SADB_X_PCHANGE		12
+#define SADB_X_SPDUPDATE	13
+#define SADB_X_SPDADD		14
+#define SADB_X_SPDDELETE	15
+#define SADB_X_SPDGET		16
+#define SADB_X_SPDACQUIRE	17
+#define SADB_X_SPDDUMP		18
+#define SADB_X_SPDFLUSH		19
+#define SADB_X_SPDSETIDX	20
+#define SADB_X_SPDEXPIRE	21
+#define SADB_X_SPDDELETE2	22
+#define SADB_X_NAT_T_NEW_MAPPING	23
+#define SADB_X_MIGRATE		24
+#define SADB_MAX		24
+
+/* Security Association flags */
+#define SADB_SAFLAGS_PFS	1
+#define SADB_SAFLAGS_NOPMTUDISC	0x20000000
+#define SADB_SAFLAGS_DECAP_DSCP	0x40000000
+#define SADB_SAFLAGS_NOECN	0x80000000
+
+/* Security Association states */
+#define SADB_SASTATE_LARVAL	0
+#define SADB_SASTATE_MATURE	1
+#define SADB_SASTATE_DYING	2
+#define SADB_SASTATE_DEAD	3
+#define SADB_SASTATE_MAX	3
+
+/* Security Association types */
+#define SADB_SATYPE_UNSPEC	0
+#define SADB_SATYPE_AH		2
+#define SADB_SATYPE_ESP		3
+#define SADB_SATYPE_RSVP	5
+#define SADB_SATYPE_OSPFV2	6
+#define SADB_SATYPE_RIPV2	7
+#define SADB_SATYPE_MIP		8
+#define SADB_X_SATYPE_IPCOMP	9
+#define SADB_SATYPE_MAX		9
+
+/* Authentication algorithms */
+#define SADB_AALG_NONE			0
+#define SADB_AALG_MD5HMAC		2
+#define SADB_AALG_SHA1HMAC		3
+#define SADB_X_AALG_SHA2_256HMAC	5
+#define SADB_X_AALG_SHA2_384HMAC	6
+#define SADB_X_AALG_SHA2_512HMAC	7
+#define SADB_X_AALG_RIPEMD160HMAC	8
+#define SADB_X_AALG_AES_XCBC_MAC	9
+#define SADB_X_AALG_NULL		251	/* kame */
+#define SADB_AALG_MAX			251
+
+/* Encryption algorithms */
+#define SADB_EALG_NONE			0
+#define SADB_EALG_DESCBC		2
+#define SADB_EALG_3DESCBC		3
+#define SADB_X_EALG_CASTCBC		6
+#define SADB_X_EALG_BLOWFISHCBC		7
+#define SADB_EALG_NULL			11
+#define SADB_X_EALG_AESCBC		12
+#define SADB_X_EALG_AESCTR		13
+#define SADB_X_EALG_AES_CCM_ICV8	14
+#define SADB_X_EALG_AES_CCM_ICV12	15
+#define SADB_X_EALG_AES_CCM_ICV16	16
+#define SADB_X_EALG_AES_GCM_ICV8	18
+#define SADB_X_EALG_AES_GCM_ICV12	19
+#define SADB_X_EALG_AES_GCM_ICV16	20
+#define SADB_X_EALG_CAMELLIACBC		22
+#define SADB_X_EALG_NULL_AES_GMAC	23
+#define SADB_EALG_MAX                   253 /* last EALG */
+/* private allocations should use 249-255 (RFC2407) */
+#define SADB_X_EALG_SERPENTCBC  252     /* draft-ietf-ipsec-ciph-aes-cbc-00 */
+#define SADB_X_EALG_TWOFISHCBC  253     /* draft-ietf-ipsec-ciph-aes-cbc-00 */
+
+/* Compression algorithms */
+#define SADB_X_CALG_NONE		0
+#define SADB_X_CALG_OUI			1
+#define SADB_X_CALG_DEFLATE		2
+#define SADB_X_CALG_LZS			3
+#define SADB_X_CALG_LZJH		4
+#define SADB_X_CALG_MAX			4
+
+/* Extension Header values */
+#define SADB_EXT_RESERVED		0
+#define SADB_EXT_SA			1
+#define SADB_EXT_LIFETIME_CURRENT	2
+#define SADB_EXT_LIFETIME_HARD		3
+#define SADB_EXT_LIFETIME_SOFT		4
+#define SADB_EXT_ADDRESS_SRC		5
+#define SADB_EXT_ADDRESS_DST		6
+#define SADB_EXT_ADDRESS_PROXY		7
+#define SADB_EXT_KEY_AUTH		8
+#define SADB_EXT_KEY_ENCRYPT		9
+#define SADB_EXT_IDENTITY_SRC		10
+#define SADB_EXT_IDENTITY_DST		11
+#define SADB_EXT_SENSITIVITY		12
+#define SADB_EXT_PROPOSAL		13
+#define SADB_EXT_SUPPORTED_AUTH		14
+#define SADB_EXT_SUPPORTED_ENCRYPT	15
+#define SADB_EXT_SPIRANGE		16
+#define SADB_X_EXT_KMPRIVATE		17
+#define SADB_X_EXT_POLICY		18
+#define SADB_X_EXT_SA2			19
+/* The next four entries are for setting up NAT Traversal */
+#define SADB_X_EXT_NAT_T_TYPE		20
+#define SADB_X_EXT_NAT_T_SPORT		21
+#define SADB_X_EXT_NAT_T_DPORT		22
+#define SADB_X_EXT_NAT_T_OA		23
+#define SADB_X_EXT_SEC_CTX		24
+/* Used with MIGRATE to pass @ to IKE for negotiation */
+#define SADB_X_EXT_KMADDRESS		25
+#define SADB_X_EXT_FILTER		26
+#define SADB_EXT_MAX			26
+
+/* Identity Extension values */
+#define SADB_IDENTTYPE_RESERVED	0
+#define SADB_IDENTTYPE_PREFIX	1
+#define SADB_IDENTTYPE_FQDN	2
+#define SADB_IDENTTYPE_USERFQDN	3
+#define SADB_IDENTTYPE_MAX	3
+
+#endif /* !(_LINUX_PFKEY2_H) */
diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h
new file mode 100644
index 0000000..a6aa466
--- /dev/null
+++ b/include/uapi/linux/pkt_cls.h
@@ -0,0 +1,690 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_PKT_CLS_H
+#define __LINUX_PKT_CLS_H
+
+#include <linux/types.h>
+#include <linux/pkt_sched.h>
+
+#define TC_COOKIE_MAX_SIZE 16
+
+/* Action attributes */
+enum {
+	TCA_ACT_UNSPEC,
+	TCA_ACT_KIND,
+	TCA_ACT_OPTIONS,
+	TCA_ACT_INDEX,
+	TCA_ACT_STATS,
+	TCA_ACT_PAD,
+	TCA_ACT_COOKIE,
+	__TCA_ACT_MAX
+};
+
+#define TCA_ACT_MAX __TCA_ACT_MAX
+#define TCA_OLD_COMPAT (TCA_ACT_MAX+1)
+#define TCA_ACT_MAX_PRIO 32
+#define TCA_ACT_BIND	1
+#define TCA_ACT_NOBIND	0
+#define TCA_ACT_UNBIND	1
+#define TCA_ACT_NOUNBIND	0
+#define TCA_ACT_REPLACE		1
+#define TCA_ACT_NOREPLACE	0
+
+#define TC_ACT_UNSPEC	(-1)
+#define TC_ACT_OK		0
+#define TC_ACT_RECLASSIFY	1
+#define TC_ACT_SHOT		2
+#define TC_ACT_PIPE		3
+#define TC_ACT_STOLEN		4
+#define TC_ACT_QUEUED		5
+#define TC_ACT_REPEAT		6
+#define TC_ACT_REDIRECT		7
+#define TC_ACT_TRAP		8 /* For hw path, this means "trap to cpu"
+				   * and don't further process the frame
+				   * in hardware. For sw path, this is
+				   * equivalent of TC_ACT_STOLEN - drop
+				   * the skb and act like everything
+				   * is alright.
+				   */
+#define TC_ACT_VALUE_MAX	TC_ACT_TRAP
+
+/* There is a special kind of actions called "extended actions",
+ * which need a value parameter. These have a local opcode located in
+ * the highest nibble, starting from 1. The rest of the bits
+ * are used to carry the value. These two parts together make
+ * a combined opcode.
+ */
+#define __TC_ACT_EXT_SHIFT 28
+#define __TC_ACT_EXT(local) ((local) << __TC_ACT_EXT_SHIFT)
+#define TC_ACT_EXT_VAL_MASK ((1 << __TC_ACT_EXT_SHIFT) - 1)
+#define TC_ACT_EXT_OPCODE(combined) ((combined) & (~TC_ACT_EXT_VAL_MASK))
+#define TC_ACT_EXT_CMP(combined, opcode) (TC_ACT_EXT_OPCODE(combined) == opcode)
+
+#define TC_ACT_JUMP __TC_ACT_EXT(1)
+#define TC_ACT_GOTO_CHAIN __TC_ACT_EXT(2)
+#define TC_ACT_EXT_OPCODE_MAX	TC_ACT_GOTO_CHAIN
+
+/* These macros are put here for binary compatibility with userspace apps that
+ * make use of them. For kernel code and new userspace apps, use the TCA_ID_*
+ * versions.
+ */
+#define TCA_ACT_GACT 5
+#define TCA_ACT_IPT 6
+#define TCA_ACT_PEDIT 7
+#define TCA_ACT_MIRRED 8
+#define TCA_ACT_NAT 9
+#define TCA_ACT_XT 10
+#define TCA_ACT_SKBEDIT 11
+#define TCA_ACT_VLAN 12
+#define TCA_ACT_BPF 13
+#define TCA_ACT_CONNMARK 14
+#define TCA_ACT_SKBMOD 15
+#define TCA_ACT_CSUM 16
+#define TCA_ACT_TUNNEL_KEY 17
+#define TCA_ACT_SIMP 22
+#define TCA_ACT_IFE 25
+#define TCA_ACT_SAMPLE 26
+
+/* Action type identifiers*/
+enum tca_id {
+	TCA_ID_UNSPEC = 0,
+	TCA_ID_POLICE = 1,
+	TCA_ID_GACT = TCA_ACT_GACT,
+	TCA_ID_IPT = TCA_ACT_IPT,
+	TCA_ID_PEDIT = TCA_ACT_PEDIT,
+	TCA_ID_MIRRED = TCA_ACT_MIRRED,
+	TCA_ID_NAT = TCA_ACT_NAT,
+	TCA_ID_XT = TCA_ACT_XT,
+	TCA_ID_SKBEDIT = TCA_ACT_SKBEDIT,
+	TCA_ID_VLAN = TCA_ACT_VLAN,
+	TCA_ID_BPF = TCA_ACT_BPF,
+	TCA_ID_CONNMARK = TCA_ACT_CONNMARK,
+	TCA_ID_SKBMOD = TCA_ACT_SKBMOD,
+	TCA_ID_CSUM = TCA_ACT_CSUM,
+	TCA_ID_TUNNEL_KEY = TCA_ACT_TUNNEL_KEY,
+	TCA_ID_SIMP = TCA_ACT_SIMP,
+	TCA_ID_IFE = TCA_ACT_IFE,
+	TCA_ID_SAMPLE = TCA_ACT_SAMPLE,
+	TCA_ID_CTINFO,
+	TCA_ID_MPLS,
+	TCA_ID_CT,
+	/* other actions go here */
+	__TCA_ID_MAX = 255
+};
+
+#define TCA_ID_MAX __TCA_ID_MAX
+
+struct tc_police {
+	__u32			index;
+	int			action;
+#define TC_POLICE_UNSPEC	TC_ACT_UNSPEC
+#define TC_POLICE_OK		TC_ACT_OK
+#define TC_POLICE_RECLASSIFY	TC_ACT_RECLASSIFY
+#define TC_POLICE_SHOT		TC_ACT_SHOT
+#define TC_POLICE_PIPE		TC_ACT_PIPE
+
+	__u32			limit;
+	__u32			burst;
+	__u32			mtu;
+	struct tc_ratespec	rate;
+	struct tc_ratespec	peakrate;
+	int			refcnt;
+	int			bindcnt;
+	__u32			capab;
+};
+
+struct tcf_t {
+	__u64   install;
+	__u64   lastuse;
+	__u64   expires;
+	__u64   firstuse;
+};
+
+struct tc_cnt {
+	int                   refcnt;
+	int                   bindcnt;
+};
+
+#define tc_gen \
+	__u32                 index; \
+	__u32                 capab; \
+	int                   action; \
+	int                   refcnt; \
+	int                   bindcnt
+
+enum {
+	TCA_POLICE_UNSPEC,
+	TCA_POLICE_TBF,
+	TCA_POLICE_RATE,
+	TCA_POLICE_PEAKRATE,
+	TCA_POLICE_AVRATE,
+	TCA_POLICE_RESULT,
+	TCA_POLICE_TM,
+	TCA_POLICE_PAD,
+	TCA_POLICE_RATE64,
+	TCA_POLICE_PEAKRATE64,
+	__TCA_POLICE_MAX
+#define TCA_POLICE_RESULT TCA_POLICE_RESULT
+};
+
+#define TCA_POLICE_MAX (__TCA_POLICE_MAX - 1)
+
+/* tca flags definitions */
+#define TCA_CLS_FLAGS_SKIP_HW	(1 << 0) /* don't offload filter to HW */
+#define TCA_CLS_FLAGS_SKIP_SW	(1 << 1) /* don't use filter in SW */
+#define TCA_CLS_FLAGS_IN_HW	(1 << 2) /* filter is offloaded to HW */
+#define TCA_CLS_FLAGS_NOT_IN_HW (1 << 3) /* filter isn't offloaded to HW */
+#define TCA_CLS_FLAGS_VERBOSE	(1 << 4) /* verbose logging */
+
+/* U32 filters */
+
+#define TC_U32_HTID(h) ((h)&0xFFF00000)
+#define TC_U32_USERHTID(h) (TC_U32_HTID(h)>>20)
+#define TC_U32_HASH(h) (((h)>>12)&0xFF)
+#define TC_U32_NODE(h) ((h)&0xFFF)
+#define TC_U32_KEY(h) ((h)&0xFFFFF)
+#define TC_U32_UNSPEC	0
+#define TC_U32_ROOT	(0xFFF00000)
+
+enum {
+	TCA_U32_UNSPEC,
+	TCA_U32_CLASSID,
+	TCA_U32_HASH,
+	TCA_U32_LINK,
+	TCA_U32_DIVISOR,
+	TCA_U32_SEL,
+	TCA_U32_POLICE,
+	TCA_U32_ACT,
+	TCA_U32_INDEV,
+	TCA_U32_PCNT,
+	TCA_U32_MARK,
+	TCA_U32_FLAGS,
+	TCA_U32_PAD,
+	__TCA_U32_MAX
+};
+
+#define TCA_U32_MAX (__TCA_U32_MAX - 1)
+
+struct tc_u32_key {
+	__be32		mask;
+	__be32		val;
+	int		off;
+	int		offmask;
+};
+
+struct tc_u32_sel {
+	unsigned char		flags;
+	unsigned char		offshift;
+	unsigned char		nkeys;
+
+	__be16			offmask;
+	__u16			off;
+	short			offoff;
+
+	short			hoff;
+	__be32			hmask;
+	struct tc_u32_key	keys[0];
+};
+
+struct tc_u32_mark {
+	__u32		val;
+	__u32		mask;
+	__u32		success;
+};
+
+struct tc_u32_pcnt {
+	__u64 rcnt;
+	__u64 rhit;
+	__u64 kcnts[0];
+};
+
+/* Flags */
+
+#define TC_U32_TERMINAL		1
+#define TC_U32_OFFSET		2
+#define TC_U32_VAROFFSET	4
+#define TC_U32_EAT		8
+
+#define TC_U32_MAXDEPTH 8
+
+
+/* RSVP filter */
+
+enum {
+	TCA_RSVP_UNSPEC,
+	TCA_RSVP_CLASSID,
+	TCA_RSVP_DST,
+	TCA_RSVP_SRC,
+	TCA_RSVP_PINFO,
+	TCA_RSVP_POLICE,
+	TCA_RSVP_ACT,
+	__TCA_RSVP_MAX
+};
+
+#define TCA_RSVP_MAX (__TCA_RSVP_MAX - 1 )
+
+struct tc_rsvp_gpi {
+	__u32	key;
+	__u32	mask;
+	int	offset;
+};
+
+struct tc_rsvp_pinfo {
+	struct tc_rsvp_gpi dpi;
+	struct tc_rsvp_gpi spi;
+	__u8	protocol;
+	__u8	tunnelid;
+	__u8	tunnelhdr;
+	__u8	pad;
+};
+
+/* ROUTE filter */
+
+enum {
+	TCA_ROUTE4_UNSPEC,
+	TCA_ROUTE4_CLASSID,
+	TCA_ROUTE4_TO,
+	TCA_ROUTE4_FROM,
+	TCA_ROUTE4_IIF,
+	TCA_ROUTE4_POLICE,
+	TCA_ROUTE4_ACT,
+	__TCA_ROUTE4_MAX
+};
+
+#define TCA_ROUTE4_MAX (__TCA_ROUTE4_MAX - 1)
+
+
+/* FW filter */
+
+enum {
+	TCA_FW_UNSPEC,
+	TCA_FW_CLASSID,
+	TCA_FW_POLICE,
+	TCA_FW_INDEV,
+	TCA_FW_ACT, /* used by CONFIG_NET_CLS_ACT */
+	TCA_FW_MASK,
+	__TCA_FW_MAX
+};
+
+#define TCA_FW_MAX (__TCA_FW_MAX - 1)
+
+/* TC index filter */
+
+enum {
+	TCA_TCINDEX_UNSPEC,
+	TCA_TCINDEX_HASH,
+	TCA_TCINDEX_MASK,
+	TCA_TCINDEX_SHIFT,
+	TCA_TCINDEX_FALL_THROUGH,
+	TCA_TCINDEX_CLASSID,
+	TCA_TCINDEX_POLICE,
+	TCA_TCINDEX_ACT,
+	__TCA_TCINDEX_MAX
+};
+
+#define TCA_TCINDEX_MAX     (__TCA_TCINDEX_MAX - 1)
+
+/* Flow filter */
+
+enum {
+	FLOW_KEY_SRC,
+	FLOW_KEY_DST,
+	FLOW_KEY_PROTO,
+	FLOW_KEY_PROTO_SRC,
+	FLOW_KEY_PROTO_DST,
+	FLOW_KEY_IIF,
+	FLOW_KEY_PRIORITY,
+	FLOW_KEY_MARK,
+	FLOW_KEY_NFCT,
+	FLOW_KEY_NFCT_SRC,
+	FLOW_KEY_NFCT_DST,
+	FLOW_KEY_NFCT_PROTO_SRC,
+	FLOW_KEY_NFCT_PROTO_DST,
+	FLOW_KEY_RTCLASSID,
+	FLOW_KEY_SKUID,
+	FLOW_KEY_SKGID,
+	FLOW_KEY_VLAN_TAG,
+	FLOW_KEY_RXHASH,
+	__FLOW_KEY_MAX,
+};
+
+#define FLOW_KEY_MAX	(__FLOW_KEY_MAX - 1)
+
+enum {
+	FLOW_MODE_MAP,
+	FLOW_MODE_HASH,
+};
+
+enum {
+	TCA_FLOW_UNSPEC,
+	TCA_FLOW_KEYS,
+	TCA_FLOW_MODE,
+	TCA_FLOW_BASECLASS,
+	TCA_FLOW_RSHIFT,
+	TCA_FLOW_ADDEND,
+	TCA_FLOW_MASK,
+	TCA_FLOW_XOR,
+	TCA_FLOW_DIVISOR,
+	TCA_FLOW_ACT,
+	TCA_FLOW_POLICE,
+	TCA_FLOW_EMATCHES,
+	TCA_FLOW_PERTURB,
+	__TCA_FLOW_MAX
+};
+
+#define TCA_FLOW_MAX	(__TCA_FLOW_MAX - 1)
+
+/* Basic filter */
+
+struct tc_basic_pcnt {
+	__u64 rcnt;
+	__u64 rhit;
+};
+
+enum {
+	TCA_BASIC_UNSPEC,
+	TCA_BASIC_CLASSID,
+	TCA_BASIC_EMATCHES,
+	TCA_BASIC_ACT,
+	TCA_BASIC_POLICE,
+	TCA_BASIC_PCNT,
+	TCA_BASIC_PAD,
+	__TCA_BASIC_MAX
+};
+
+#define TCA_BASIC_MAX (__TCA_BASIC_MAX - 1)
+
+
+/* Cgroup classifier */
+
+enum {
+	TCA_CGROUP_UNSPEC,
+	TCA_CGROUP_ACT,
+	TCA_CGROUP_POLICE,
+	TCA_CGROUP_EMATCHES,
+	__TCA_CGROUP_MAX,
+};
+
+#define TCA_CGROUP_MAX (__TCA_CGROUP_MAX - 1)
+
+/* BPF classifier */
+
+#define TCA_BPF_FLAG_ACT_DIRECT		(1 << 0)
+
+enum {
+	TCA_BPF_UNSPEC,
+	TCA_BPF_ACT,
+	TCA_BPF_POLICE,
+	TCA_BPF_CLASSID,
+	TCA_BPF_OPS_LEN,
+	TCA_BPF_OPS,
+	TCA_BPF_FD,
+	TCA_BPF_NAME,
+	TCA_BPF_FLAGS,
+	TCA_BPF_FLAGS_GEN,
+	TCA_BPF_TAG,
+	TCA_BPF_ID,
+	__TCA_BPF_MAX,
+};
+
+#define TCA_BPF_MAX (__TCA_BPF_MAX - 1)
+
+/* Flower classifier */
+
+enum {
+	TCA_FLOWER_UNSPEC,
+	TCA_FLOWER_CLASSID,
+	TCA_FLOWER_INDEV,
+	TCA_FLOWER_ACT,
+	TCA_FLOWER_KEY_ETH_DST,		/* ETH_ALEN */
+	TCA_FLOWER_KEY_ETH_DST_MASK,	/* ETH_ALEN */
+	TCA_FLOWER_KEY_ETH_SRC,		/* ETH_ALEN */
+	TCA_FLOWER_KEY_ETH_SRC_MASK,	/* ETH_ALEN */
+	TCA_FLOWER_KEY_ETH_TYPE,	/* be16 */
+	TCA_FLOWER_KEY_IP_PROTO,	/* u8 */
+	TCA_FLOWER_KEY_IPV4_SRC,	/* be32 */
+	TCA_FLOWER_KEY_IPV4_SRC_MASK,	/* be32 */
+	TCA_FLOWER_KEY_IPV4_DST,	/* be32 */
+	TCA_FLOWER_KEY_IPV4_DST_MASK,	/* be32 */
+	TCA_FLOWER_KEY_IPV6_SRC,	/* struct in6_addr */
+	TCA_FLOWER_KEY_IPV6_SRC_MASK,	/* struct in6_addr */
+	TCA_FLOWER_KEY_IPV6_DST,	/* struct in6_addr */
+	TCA_FLOWER_KEY_IPV6_DST_MASK,	/* struct in6_addr */
+	TCA_FLOWER_KEY_TCP_SRC,		/* be16 */
+	TCA_FLOWER_KEY_TCP_DST,		/* be16 */
+	TCA_FLOWER_KEY_UDP_SRC,		/* be16 */
+	TCA_FLOWER_KEY_UDP_DST,		/* be16 */
+
+	TCA_FLOWER_FLAGS,
+	TCA_FLOWER_KEY_VLAN_ID,		/* be16 */
+	TCA_FLOWER_KEY_VLAN_PRIO,	/* u8   */
+	TCA_FLOWER_KEY_VLAN_ETH_TYPE,	/* be16 */
+
+	TCA_FLOWER_KEY_ENC_KEY_ID,	/* be32 */
+	TCA_FLOWER_KEY_ENC_IPV4_SRC,	/* be32 */
+	TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK,/* be32 */
+	TCA_FLOWER_KEY_ENC_IPV4_DST,	/* be32 */
+	TCA_FLOWER_KEY_ENC_IPV4_DST_MASK,/* be32 */
+	TCA_FLOWER_KEY_ENC_IPV6_SRC,	/* struct in6_addr */
+	TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK,/* struct in6_addr */
+	TCA_FLOWER_KEY_ENC_IPV6_DST,	/* struct in6_addr */
+	TCA_FLOWER_KEY_ENC_IPV6_DST_MASK,/* struct in6_addr */
+
+	TCA_FLOWER_KEY_TCP_SRC_MASK,	/* be16 */
+	TCA_FLOWER_KEY_TCP_DST_MASK,	/* be16 */
+	TCA_FLOWER_KEY_UDP_SRC_MASK,	/* be16 */
+	TCA_FLOWER_KEY_UDP_DST_MASK,	/* be16 */
+	TCA_FLOWER_KEY_SCTP_SRC_MASK,	/* be16 */
+	TCA_FLOWER_KEY_SCTP_DST_MASK,	/* be16 */
+
+	TCA_FLOWER_KEY_SCTP_SRC,	/* be16 */
+	TCA_FLOWER_KEY_SCTP_DST,	/* be16 */
+
+	TCA_FLOWER_KEY_ENC_UDP_SRC_PORT,	/* be16 */
+	TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK,	/* be16 */
+	TCA_FLOWER_KEY_ENC_UDP_DST_PORT,	/* be16 */
+	TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK,	/* be16 */
+
+	TCA_FLOWER_KEY_FLAGS,		/* be32 */
+	TCA_FLOWER_KEY_FLAGS_MASK,	/* be32 */
+
+	TCA_FLOWER_KEY_ICMPV4_CODE,	/* u8 */
+	TCA_FLOWER_KEY_ICMPV4_CODE_MASK,/* u8 */
+	TCA_FLOWER_KEY_ICMPV4_TYPE,	/* u8 */
+	TCA_FLOWER_KEY_ICMPV4_TYPE_MASK,/* u8 */
+	TCA_FLOWER_KEY_ICMPV6_CODE,	/* u8 */
+	TCA_FLOWER_KEY_ICMPV6_CODE_MASK,/* u8 */
+	TCA_FLOWER_KEY_ICMPV6_TYPE,	/* u8 */
+	TCA_FLOWER_KEY_ICMPV6_TYPE_MASK,/* u8 */
+
+	TCA_FLOWER_KEY_ARP_SIP,		/* be32 */
+	TCA_FLOWER_KEY_ARP_SIP_MASK,	/* be32 */
+	TCA_FLOWER_KEY_ARP_TIP,		/* be32 */
+	TCA_FLOWER_KEY_ARP_TIP_MASK,	/* be32 */
+	TCA_FLOWER_KEY_ARP_OP,		/* u8 */
+	TCA_FLOWER_KEY_ARP_OP_MASK,	/* u8 */
+	TCA_FLOWER_KEY_ARP_SHA,		/* ETH_ALEN */
+	TCA_FLOWER_KEY_ARP_SHA_MASK,	/* ETH_ALEN */
+	TCA_FLOWER_KEY_ARP_THA,		/* ETH_ALEN */
+	TCA_FLOWER_KEY_ARP_THA_MASK,	/* ETH_ALEN */
+
+	TCA_FLOWER_KEY_MPLS_TTL,	/* u8 - 8 bits */
+	TCA_FLOWER_KEY_MPLS_BOS,	/* u8 - 1 bit */
+	TCA_FLOWER_KEY_MPLS_TC,		/* u8 - 3 bits */
+	TCA_FLOWER_KEY_MPLS_LABEL,	/* be32 - 20 bits */
+
+	TCA_FLOWER_KEY_TCP_FLAGS,	/* be16 */
+	TCA_FLOWER_KEY_TCP_FLAGS_MASK,	/* be16 */
+
+	TCA_FLOWER_KEY_IP_TOS,		/* u8 */
+	TCA_FLOWER_KEY_IP_TOS_MASK,	/* u8 */
+	TCA_FLOWER_KEY_IP_TTL,		/* u8 */
+	TCA_FLOWER_KEY_IP_TTL_MASK,	/* u8 */
+
+	TCA_FLOWER_KEY_CVLAN_ID,	/* be16 */
+	TCA_FLOWER_KEY_CVLAN_PRIO,	/* u8   */
+	TCA_FLOWER_KEY_CVLAN_ETH_TYPE,	/* be16 */
+
+	TCA_FLOWER_KEY_ENC_IP_TOS,	/* u8 */
+	TCA_FLOWER_KEY_ENC_IP_TOS_MASK,	/* u8 */
+	TCA_FLOWER_KEY_ENC_IP_TTL,	/* u8 */
+	TCA_FLOWER_KEY_ENC_IP_TTL_MASK,	/* u8 */
+
+	TCA_FLOWER_KEY_ENC_OPTS,
+	TCA_FLOWER_KEY_ENC_OPTS_MASK,
+
+	TCA_FLOWER_IN_HW_COUNT,
+
+	TCA_FLOWER_KEY_PORT_SRC_MIN,	/* be16 */
+	TCA_FLOWER_KEY_PORT_SRC_MAX,	/* be16 */
+	TCA_FLOWER_KEY_PORT_DST_MIN,	/* be16 */
+	TCA_FLOWER_KEY_PORT_DST_MAX,	/* be16 */
+
+	TCA_FLOWER_KEY_CT_STATE,	/* u16 */
+	TCA_FLOWER_KEY_CT_STATE_MASK,	/* u16 */
+	TCA_FLOWER_KEY_CT_ZONE,		/* u16 */
+	TCA_FLOWER_KEY_CT_ZONE_MASK,	/* u16 */
+	TCA_FLOWER_KEY_CT_MARK,		/* u32 */
+	TCA_FLOWER_KEY_CT_MARK_MASK,	/* u32 */
+	TCA_FLOWER_KEY_CT_LABELS,	/* u128 */
+	TCA_FLOWER_KEY_CT_LABELS_MASK,	/* u128 */
+
+	__TCA_FLOWER_MAX,
+};
+
+#define TCA_FLOWER_MAX (__TCA_FLOWER_MAX - 1)
+
+enum {
+	TCA_FLOWER_KEY_CT_FLAGS_NEW = 1 << 0, /* Beginning of a new connection. */
+	TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED = 1 << 1, /* Part of an existing connection. */
+	TCA_FLOWER_KEY_CT_FLAGS_RELATED = 1 << 2, /* Related to an established connection. */
+	TCA_FLOWER_KEY_CT_FLAGS_TRACKED = 1 << 3, /* Conntrack has occurred. */
+};
+
+enum {
+	TCA_FLOWER_KEY_ENC_OPTS_UNSPEC,
+	TCA_FLOWER_KEY_ENC_OPTS_GENEVE, /* Nested
+					 * TCA_FLOWER_KEY_ENC_OPT_GENEVE_
+					 * attributes
+					 */
+	__TCA_FLOWER_KEY_ENC_OPTS_MAX,
+};
+
+#define TCA_FLOWER_KEY_ENC_OPTS_MAX (__TCA_FLOWER_KEY_ENC_OPTS_MAX - 1)
+
+enum {
+	TCA_FLOWER_KEY_ENC_OPT_GENEVE_UNSPEC,
+	TCA_FLOWER_KEY_ENC_OPT_GENEVE_CLASS,            /* u16 */
+	TCA_FLOWER_KEY_ENC_OPT_GENEVE_TYPE,             /* u8 */
+	TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA,             /* 4 to 128 bytes */
+
+	__TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX,
+};
+
+#define TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX \
+		(__TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX - 1)
+
+enum {
+	TCA_FLOWER_KEY_FLAGS_IS_FRAGMENT = (1 << 0),
+	TCA_FLOWER_KEY_FLAGS_FRAG_IS_FIRST = (1 << 1),
+};
+
+#define TCA_FLOWER_MASK_FLAGS_RANGE	(1 << 0) /* Range-based match */
+
+/* Match-all classifier */
+
+struct tc_matchall_pcnt {
+	__u64 rhit;
+};
+
+enum {
+	TCA_MATCHALL_UNSPEC,
+	TCA_MATCHALL_CLASSID,
+	TCA_MATCHALL_ACT,
+	TCA_MATCHALL_FLAGS,
+	TCA_MATCHALL_PCNT,
+	TCA_MATCHALL_PAD,
+	__TCA_MATCHALL_MAX,
+};
+
+#define TCA_MATCHALL_MAX (__TCA_MATCHALL_MAX - 1)
+
+/* Extended Matches */
+
+struct tcf_ematch_tree_hdr {
+	__u16		nmatches;
+	__u16		progid;
+};
+
+enum {
+	TCA_EMATCH_TREE_UNSPEC,
+	TCA_EMATCH_TREE_HDR,
+	TCA_EMATCH_TREE_LIST,
+	__TCA_EMATCH_TREE_MAX
+};
+#define TCA_EMATCH_TREE_MAX (__TCA_EMATCH_TREE_MAX - 1)
+
+struct tcf_ematch_hdr {
+	__u16		matchid;
+	__u16		kind;
+	__u16		flags;
+	__u16		pad; /* currently unused */
+};
+
+/*  0                   1
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 
+ * +-----------------------+-+-+---+
+ * |         Unused        |S|I| R |
+ * +-----------------------+-+-+---+
+ *
+ * R(2) ::= relation to next ematch
+ *          where: 0 0 END (last ematch)
+ *                 0 1 AND
+ *                 1 0 OR
+ *                 1 1 Unused (invalid)
+ * I(1) ::= invert result
+ * S(1) ::= simple payload
+ */
+#define TCF_EM_REL_END	0
+#define TCF_EM_REL_AND	(1<<0)
+#define TCF_EM_REL_OR	(1<<1)
+#define TCF_EM_INVERT	(1<<2)
+#define TCF_EM_SIMPLE	(1<<3)
+
+#define TCF_EM_REL_MASK	3
+#define TCF_EM_REL_VALID(v) (((v) & TCF_EM_REL_MASK) != TCF_EM_REL_MASK)
+
+enum {
+	TCF_LAYER_LINK,
+	TCF_LAYER_NETWORK,
+	TCF_LAYER_TRANSPORT,
+	__TCF_LAYER_MAX
+};
+#define TCF_LAYER_MAX (__TCF_LAYER_MAX - 1)
+
+/* Ematch type assignments
+ *   1..32767		Reserved for ematches inside kernel tree
+ *   32768..65535	Free to use, not reliable
+ */
+#define	TCF_EM_CONTAINER	0
+#define	TCF_EM_CMP		1
+#define	TCF_EM_NBYTE		2
+#define	TCF_EM_U32		3
+#define	TCF_EM_META		4
+#define	TCF_EM_TEXT		5
+#define	TCF_EM_VLAN		6
+#define	TCF_EM_CANID		7
+#define	TCF_EM_IPSET		8
+#define	TCF_EM_IPT		9
+#define	TCF_EM_MAX		9
+
+enum {
+	TCF_EM_PROG_TC
+};
+
+enum {
+	TCF_EM_OPND_EQ,
+	TCF_EM_OPND_GT,
+	TCF_EM_OPND_LT
+};
+
+#endif
diff --git a/include/uapi/linux/pkt_sched.h b/include/uapi/linux/pkt_sched.h
new file mode 100644
index 0000000..12b5189
--- /dev/null
+++ b/include/uapi/linux/pkt_sched.h
@@ -0,0 +1,1416 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_PKT_SCHED_H
+#define __LINUX_PKT_SCHED_H
+
+#include <linux/const.h>
+#include <linux/types.h>
+
+/* Logical priority bands not depending on specific packet scheduler.
+   Every scheduler will map them to real traffic classes, if it has
+   no more precise mechanism to classify packets.
+
+   These numbers have no special meaning, though their coincidence
+   with obsolete IPv6 values is not occasional :-). New IPv6 drafts
+   preferred full anarchy inspired by diffserv group.
+
+   Note: TC_PRIO_BESTEFFORT does not mean that it is the most unhappy
+   class, actually, as rule it will be handled with more care than
+   filler or even bulk.
+ */
+
+#define TC_PRIO_BESTEFFORT		0
+#define TC_PRIO_FILLER			1
+#define TC_PRIO_BULK			2
+#define TC_PRIO_INTERACTIVE_BULK	4
+#define TC_PRIO_INTERACTIVE		6
+#define TC_PRIO_CONTROL			7
+
+#define TC_PRIO_MAX			15
+
+/* Generic queue statistics, available for all the elements.
+   Particular schedulers may have also their private records.
+ */
+
+struct tc_stats {
+	__u64	bytes;			/* Number of enqueued bytes */
+	__u32	packets;		/* Number of enqueued packets	*/
+	__u32	drops;			/* Packets dropped because of lack of resources */
+	__u32	overlimits;		/* Number of throttle events when this
+					 * flow goes out of allocated bandwidth */
+	__u32	bps;			/* Current flow byte rate */
+	__u32	pps;			/* Current flow packet rate */
+	__u32	qlen;
+	__u32	backlog;
+};
+
+struct tc_estimator {
+	signed char	interval;
+	unsigned char	ewma_log;
+};
+
+/* "Handles"
+   ---------
+
+    All the traffic control objects have 32bit identifiers, or "handles".
+
+    They can be considered as opaque numbers from user API viewpoint,
+    but actually they always consist of two fields: major and
+    minor numbers, which are interpreted by kernel specially,
+    that may be used by applications, though not recommended.
+
+    F.e. qdisc handles always have minor number equal to zero,
+    classes (or flows) have major equal to parent qdisc major, and
+    minor uniquely identifying class inside qdisc.
+
+    Macros to manipulate handles:
+ */
+
+#define TC_H_MAJ_MASK (0xFFFF0000U)
+#define TC_H_MIN_MASK (0x0000FFFFU)
+#define TC_H_MAJ(h) ((h)&TC_H_MAJ_MASK)
+#define TC_H_MIN(h) ((h)&TC_H_MIN_MASK)
+#define TC_H_MAKE(maj,min) (((maj)&TC_H_MAJ_MASK)|((min)&TC_H_MIN_MASK))
+
+#define TC_H_UNSPEC	(0U)
+#define TC_H_ROOT	(0xFFFFFFFFU)
+#define TC_H_INGRESS    (0xFFFFFFF1U)
+#define TC_H_CLSACT	TC_H_INGRESS
+
+#define TC_H_MIN_PRIORITY	0xFFE0U
+#define TC_H_MIN_INGRESS	0xFFF2U
+#define TC_H_MIN_EGRESS		0xFFF3U
+
+/* Need to corrospond to iproute2 tc/tc_core.h "enum link_layer" */
+enum tc_link_layer {
+	TC_LINKLAYER_UNAWARE, /* Indicate unaware old iproute2 util */
+	TC_LINKLAYER_ETHERNET,
+	TC_LINKLAYER_ATM,
+};
+#define TC_LINKLAYER_MASK 0x0F /* limit use to lower 4 bits */
+
+struct tc_ratespec {
+	unsigned char	cell_log;
+	__u8		linklayer; /* lower 4 bits */
+	unsigned short	overhead;
+	short		cell_align;
+	unsigned short	mpu;
+	__u32		rate;
+};
+
+#define TC_RTAB_SIZE	1024
+
+struct tc_sizespec {
+	unsigned char	cell_log;
+	unsigned char	size_log;
+	short		cell_align;
+	int		overhead;
+	unsigned int	linklayer;
+	unsigned int	mpu;
+	unsigned int	mtu;
+	unsigned int	tsize;
+};
+
+enum {
+	TCA_STAB_UNSPEC,
+	TCA_STAB_BASE,
+	TCA_STAB_DATA,
+	__TCA_STAB_MAX
+};
+
+#define TCA_STAB_MAX (__TCA_STAB_MAX - 1)
+
+/* NSSFIFO section */
+
+enum {
+	TCA_NSSFIFO_UNSPEC,
+	TCA_NSSFIFO_PARMS,
+	__TCA_NSSFIFO_MAX
+};
+
+#define TCA_NSSFIFO_MAX	(__TCA_NSSFIFO_MAX - 1)
+
+struct tc_nssfifo_qopt {
+	__u32	limit;		/* Queue length: bytes for bfifo, packets for pfifo */
+	__u8	set_default;	/* Sets qdisc to be the default qdisc for enqueue */
+};
+
+/* NSSWRED section */
+
+enum {
+	TCA_NSSWRED_UNSPEC,
+	TCA_NSSWRED_PARMS,
+	__TCA_NSSWRED_MAX
+};
+
+#define TCA_NSSWRED_MAX (__TCA_NSSWRED_MAX - 1)
+#define NSSWRED_CLASS_MAX 6
+struct tc_red_alg_parameter {
+	__u32	min;			/* qlen_avg < min: all pkts are enqueued */
+	__u32	max;			/* qlen_avg > max: all pkts are dropped */
+	__u32	probability;		/* Drop probability at qlen_avg = max */
+	__u32	exp_weight_factor;	/* exp_weight_factor for calculate qlen_avg */
+};
+
+struct tc_nsswred_traffic_class {
+	__u32 limit;			/* Queue length */
+	__u32 weight_mode_value;	/* Weight mode value */
+	struct tc_red_alg_parameter rap;/* Parameters for RED alg */
+};
+
+/*
+ * Weight modes for WRED
+ */
+enum tc_nsswred_weight_modes {
+	TC_NSSWRED_WEIGHT_MODE_DSCP = 0,	/* Weight mode is DSCP */
+	TC_NSSWRED_WEIGHT_MODES,		/* Must be last */
+};
+typedef enum tc_nsswred_weight_modes tc_nsswred_weight_mode_t;
+
+struct tc_nsswred_qopt {
+	__u32	limit;				/* Queue length */
+	tc_nsswred_weight_mode_t weight_mode;	/* Weight mode */
+	__u32	traffic_classes;		/* How many traffic classes: DPs */
+	__u32	def_traffic_class;		/* Default traffic if no match: def_DP */
+	__u32	traffic_id;			/* The traffic id to be configured */
+	__u32	weight_mode_value;		/* Weight mode value */
+	struct tc_red_alg_parameter rap;	/* RED algorithm parameters */
+	struct tc_nsswred_traffic_class tntc[NSSWRED_CLASS_MAX];
+						/* Traffic settings for dumpping */
+	__u8	ecn;				/* Setting ECN bit or dropping */
+	__u8	set_default;			/* Sets qdisc to be the default for enqueue */
+};
+
+/* NSSCODEL section */
+
+enum {
+	TCA_NSSCODEL_UNSPEC,
+	TCA_NSSCODEL_PARMS,
+	__TCA_NSSCODEL_MAX
+};
+
+#define TCA_NSSCODEL_MAX	(__TCA_NSSCODEL_MAX - 1)
+
+struct tc_nsscodel_qopt {
+	__u32	target;		/* Acceptable queueing delay */
+	__u32	limit;		/* Maximum number of packets that can be held in the queue */
+	__u32	interval;	/* Monitoring interval */
+	__u8	set_default;	/* Sets qdisc to be the default qdisc for enqueue */
+};
+
+struct tc_nsscodel_xstats {
+	__u32 peak_queue_delay;	/* Peak delay experienced by a dequeued packet */
+	__u32 peak_drop_delay;	/* Peak delay experienced by a dropped packet */
+};
+
+/* NSSTBL section */
+
+enum {
+	TCA_NSSTBL_UNSPEC,
+	TCA_NSSTBL_PARMS,
+	__TCA_NSSTBL_MAX
+};
+
+#define TCA_NSSTBL_MAX	(__TCA_NSSTBL_MAX - 1)
+
+struct tc_nsstbl_qopt {
+	__u32	burst;		/* Maximum burst size */
+	__u32	rate;		/* Limiting rate of TBF */
+	__u32	peakrate;	/* Maximum rate at which TBF is allowed to send */
+	__u32	mtu;		/* Max size of packet, or minumim burst size */
+};
+
+/* NSSPRIO section */
+
+#define TCA_NSSPRIO_MAX_BANDS 256
+
+enum {
+	TCA_NSSPRIO_UNSPEC,
+	TCA_NSSPRIO_PARMS,
+	__TCA_NSSPRIO_MAX
+};
+
+#define TCA_NSSPRIO_MAX	(__TCA_NSSPRIO_MAX - 1)
+
+struct tc_nssprio_qopt {
+	int	bands;				/* Number of bands */
+};
+
+/* NSSBF section */
+
+enum {
+	TCA_NSSBF_UNSPEC,
+	TCA_NSSBF_CLASS_PARMS,
+	TCA_NSSBF_QDISC_PARMS,
+	__TCA_NSSBF_MAX
+};
+
+#define TCA_NSSBF_MAX	(__TCA_NSSBF_MAX - 1)
+
+struct tc_nssbf_class_qopt {
+	__u32	burst;		/* Maximum burst size */
+	__u32	rate;		/* Allowed bandwidth for this class */
+	__u32	mtu;		/* MTU of the associated interface */
+	__u32	quantum;	/* Quamtum allocation for DRR */
+};
+
+struct tc_nssbf_qopt {
+	__u16	defcls;		/* Default class value */
+};
+
+/* NSSWRR section */
+
+enum {
+	TCA_NSSWRR_UNSPEC,
+	TCA_NSSWRR_CLASS_PARMS,
+	__TCA_NSSWRR_MAX
+};
+
+#define TCA_NSSWRR_MAX	(__TCA_NSSWRR_MAX - 1)
+
+struct tc_nsswrr_class_qopt {
+	__u32	quantum;	/* Weight associated to this class */
+};
+
+/* NSSWFQ section */
+
+enum {
+	TCA_NSSWFQ_UNSPEC,
+	TCA_NSSWFQ_CLASS_PARMS,
+	__TCA_NSSWFQ_MAX
+};
+
+#define TCA_NSSWFQ_MAX	(__TCA_NSSWFQ_MAX - 1)
+
+struct tc_nsswfq_class_qopt {
+	__u32	quantum;	/* Weight associated to this class */
+};
+
+/* NSSHTB section */
+
+enum {
+	TCA_NSSHTB_UNSPEC,
+	TCA_NSSHTB_CLASS_PARMS,
+	TCA_NSSHTB_QDISC_PARMS,
+	__TCA_NSSHTB_MAX
+};
+
+#define TCA_NSSHTB_MAX	(__TCA_NSSHTB_MAX - 1)
+
+struct tc_nsshtb_class_qopt {
+	__u32	burst;		/* Allowed burst size */
+	__u32	rate;		/* Allowed bandwidth for this class */
+	__u32	cburst;		/* Maximum burst size */
+	__u32	crate;		/* Maximum bandwidth for this class */
+	__u32	quantum;	/* Quantum allocation for DRR */
+	__u32	priority;	/* Priority value associated with this class */
+	__u32	overhead;	/* Overhead in bytes per packet */
+};
+
+struct tc_nsshtb_qopt {
+	__u32	r2q;		/* Rate to quantum ratio */
+};
+
+/* NSSBLACKHOLE section */
+
+enum {
+	TCA_NSSBLACKHOLE_UNSPEC,
+	TCA_NSSBLACKHOLE_PARMS,
+	__TCA_NSSBLACKHOLE_MAX
+};
+
+#define TCA_NSSBLACKHOLE_MAX	(__TCA_NSSBLACKHOLE_MAX - 1)
+
+struct tc_nssblackhole_qopt {
+	__u8	set_default;	/* Sets qdisc to be the default qdisc for enqueue */
+};
+
+/* FIFO section */
+
+struct tc_fifo_qopt {
+	__u32	limit;	/* Queue length: bytes for bfifo, packets for pfifo */
+};
+
+/* SKBPRIO section */
+
+/*
+ * Priorities go from zero to (SKBPRIO_MAX_PRIORITY - 1).
+ * SKBPRIO_MAX_PRIORITY should be at least 64 in order for skbprio to be able
+ * to map one to one the DS field of IPV4 and IPV6 headers.
+ * Memory allocation grows linearly with SKBPRIO_MAX_PRIORITY.
+ */
+
+#define SKBPRIO_MAX_PRIORITY 64
+
+struct tc_skbprio_qopt {
+	__u32	limit;		/* Queue length in packets. */
+};
+
+/* PRIO section */
+
+#define TCQ_PRIO_BANDS	16
+#define TCQ_MIN_PRIO_BANDS 2
+
+struct tc_prio_qopt {
+	int	bands;			/* Number of bands */
+	__u8	priomap[TC_PRIO_MAX+1];	/* Map: logical priority -> PRIO band */
+};
+
+/* MULTIQ section */
+
+struct tc_multiq_qopt {
+	__u16	bands;			/* Number of bands */
+	__u16	max_bands;		/* Maximum number of queues */
+};
+
+/* PLUG section */
+
+#define TCQ_PLUG_BUFFER                0
+#define TCQ_PLUG_RELEASE_ONE           1
+#define TCQ_PLUG_RELEASE_INDEFINITE    2
+#define TCQ_PLUG_LIMIT                 3
+
+struct tc_plug_qopt {
+	/* TCQ_PLUG_BUFFER: Inset a plug into the queue and
+	 *  buffer any incoming packets
+	 * TCQ_PLUG_RELEASE_ONE: Dequeue packets from queue head
+	 *   to beginning of the next plug.
+	 * TCQ_PLUG_RELEASE_INDEFINITE: Dequeue all packets from queue.
+	 *   Stop buffering packets until the next TCQ_PLUG_BUFFER
+	 *   command is received (just act as a pass-thru queue).
+	 * TCQ_PLUG_LIMIT: Increase/decrease queue size
+	 */
+	int             action;
+	__u32           limit;
+};
+
+/* TBF section */
+
+struct tc_tbf_qopt {
+	struct tc_ratespec rate;
+	struct tc_ratespec peakrate;
+	__u32		limit;
+	__u32		buffer;
+	__u32		mtu;
+};
+
+enum {
+	TCA_TBF_UNSPEC,
+	TCA_TBF_PARMS,
+	TCA_TBF_RTAB,
+	TCA_TBF_PTAB,
+	TCA_TBF_RATE64,
+	TCA_TBF_PRATE64,
+	TCA_TBF_BURST,
+	TCA_TBF_PBURST,
+	TCA_TBF_PAD,
+	__TCA_TBF_MAX,
+};
+
+#define TCA_TBF_MAX (__TCA_TBF_MAX - 1)
+
+
+/* TEQL section */
+
+/* TEQL does not require any parameters */
+
+/* SFQ section */
+
+struct tc_sfq_qopt {
+	unsigned	quantum;	/* Bytes per round allocated to flow */
+	int		perturb_period;	/* Period of hash perturbation */
+	__u32		limit;		/* Maximal packets in queue */
+	unsigned	divisor;	/* Hash divisor  */
+	unsigned	flows;		/* Maximal number of flows  */
+};
+
+struct tc_sfqred_stats {
+	__u32           prob_drop;      /* Early drops, below max threshold */
+	__u32           forced_drop;	/* Early drops, after max threshold */
+	__u32           prob_mark;      /* Marked packets, below max threshold */
+	__u32           forced_mark;    /* Marked packets, after max threshold */
+	__u32           prob_mark_head; /* Marked packets, below max threshold */
+	__u32           forced_mark_head;/* Marked packets, after max threshold */
+};
+
+struct tc_sfq_qopt_v1 {
+	struct tc_sfq_qopt v0;
+	unsigned int	depth;		/* max number of packets per flow */
+	unsigned int	headdrop;
+/* SFQRED parameters */
+	__u32		limit;		/* HARD maximal flow queue length (bytes) */
+	__u32		qth_min;	/* Min average length threshold (bytes) */
+	__u32		qth_max;	/* Max average length threshold (bytes) */
+	unsigned char   Wlog;		/* log(W)		*/
+	unsigned char   Plog;		/* log(P_max/(qth_max-qth_min))	*/
+	unsigned char   Scell_log;	/* cell size for idle damping */
+	unsigned char	flags;
+	__u32		max_P;		/* probability, high resolution */
+/* SFQRED stats */
+	struct tc_sfqred_stats stats;
+};
+
+
+struct tc_sfq_xstats {
+	__s32		allot;
+};
+
+/* RED section */
+
+enum {
+	TCA_RED_UNSPEC,
+	TCA_RED_PARMS,
+	TCA_RED_STAB,
+	TCA_RED_MAX_P,
+	__TCA_RED_MAX,
+};
+
+#define TCA_RED_MAX (__TCA_RED_MAX - 1)
+
+struct tc_red_qopt {
+	__u32		limit;		/* HARD maximal queue length (bytes)	*/
+	__u32		qth_min;	/* Min average length threshold (bytes) */
+	__u32		qth_max;	/* Max average length threshold (bytes) */
+	unsigned char   Wlog;		/* log(W)		*/
+	unsigned char   Plog;		/* log(P_max/(qth_max-qth_min))	*/
+	unsigned char   Scell_log;	/* cell size for idle damping */
+	unsigned char	flags;
+#define TC_RED_ECN		1
+#define TC_RED_HARDDROP		2
+#define TC_RED_ADAPTATIVE	4
+};
+
+struct tc_red_xstats {
+	__u32           early;          /* Early drops */
+	__u32           pdrop;          /* Drops due to queue limits */
+	__u32           other;          /* Drops due to drop() calls */
+	__u32           marked;         /* Marked packets */
+};
+
+/* GRED section */
+
+#define MAX_DPs 16
+
+enum {
+       TCA_GRED_UNSPEC,
+       TCA_GRED_PARMS,
+       TCA_GRED_STAB,
+       TCA_GRED_DPS,
+       TCA_GRED_MAX_P,
+       TCA_GRED_LIMIT,
+       TCA_GRED_VQ_LIST,	/* nested TCA_GRED_VQ_ENTRY */
+       __TCA_GRED_MAX,
+};
+
+#define TCA_GRED_MAX (__TCA_GRED_MAX - 1)
+
+enum {
+	TCA_GRED_VQ_ENTRY_UNSPEC,
+	TCA_GRED_VQ_ENTRY,	/* nested TCA_GRED_VQ_* */
+	__TCA_GRED_VQ_ENTRY_MAX,
+};
+#define TCA_GRED_VQ_ENTRY_MAX (__TCA_GRED_VQ_ENTRY_MAX - 1)
+
+enum {
+	TCA_GRED_VQ_UNSPEC,
+	TCA_GRED_VQ_PAD,
+	TCA_GRED_VQ_DP,			/* u32 */
+	TCA_GRED_VQ_STAT_BYTES,		/* u64 */
+	TCA_GRED_VQ_STAT_PACKETS,	/* u32 */
+	TCA_GRED_VQ_STAT_BACKLOG,	/* u32 */
+	TCA_GRED_VQ_STAT_PROB_DROP,	/* u32 */
+	TCA_GRED_VQ_STAT_PROB_MARK,	/* u32 */
+	TCA_GRED_VQ_STAT_FORCED_DROP,	/* u32 */
+	TCA_GRED_VQ_STAT_FORCED_MARK,	/* u32 */
+	TCA_GRED_VQ_STAT_PDROP,		/* u32 */
+	TCA_GRED_VQ_STAT_OTHER,		/* u32 */
+	TCA_GRED_VQ_FLAGS,		/* u32 */
+	__TCA_GRED_VQ_MAX
+};
+
+#define TCA_GRED_VQ_MAX (__TCA_GRED_VQ_MAX - 1)
+
+struct tc_gred_qopt {
+	__u32		limit;        /* HARD maximal queue length (bytes)    */
+	__u32		qth_min;      /* Min average length threshold (bytes) */
+	__u32		qth_max;      /* Max average length threshold (bytes) */
+	__u32		DP;           /* up to 2^32 DPs */
+	__u32		backlog;
+	__u32		qave;
+	__u32		forced;
+	__u32		early;
+	__u32		other;
+	__u32		pdrop;
+	__u8		Wlog;         /* log(W)               */
+	__u8		Plog;         /* log(P_max/(qth_max-qth_min)) */
+	__u8		Scell_log;    /* cell size for idle damping */
+	__u8		prio;         /* prio of this VQ */
+	__u32		packets;
+	__u32		bytesin;
+};
+
+/* gred setup */
+struct tc_gred_sopt {
+	__u32		DPs;
+	__u32		def_DP;
+	__u8		grio;
+	__u8		flags;
+	__u16		pad1;
+};
+
+/* CHOKe section */
+
+enum {
+	TCA_CHOKE_UNSPEC,
+	TCA_CHOKE_PARMS,
+	TCA_CHOKE_STAB,
+	TCA_CHOKE_MAX_P,
+	__TCA_CHOKE_MAX,
+};
+
+#define TCA_CHOKE_MAX (__TCA_CHOKE_MAX - 1)
+
+struct tc_choke_qopt {
+	__u32		limit;		/* Hard queue length (packets)	*/
+	__u32		qth_min;	/* Min average threshold (packets) */
+	__u32		qth_max;	/* Max average threshold (packets) */
+	unsigned char   Wlog;		/* log(W)		*/
+	unsigned char   Plog;		/* log(P_max/(qth_max-qth_min))	*/
+	unsigned char   Scell_log;	/* cell size for idle damping */
+	unsigned char	flags;		/* see RED flags */
+};
+
+struct tc_choke_xstats {
+	__u32		early;          /* Early drops */
+	__u32		pdrop;          /* Drops due to queue limits */
+	__u32		other;          /* Drops due to drop() calls */
+	__u32		marked;         /* Marked packets */
+	__u32		matched;	/* Drops due to flow match */
+};
+
+/* HTB section */
+#define TC_HTB_NUMPRIO		8
+#define TC_HTB_MAXDEPTH		8
+#define TC_HTB_PROTOVER		3 /* the same as HTB and TC's major */
+
+struct tc_htb_opt {
+	struct tc_ratespec 	rate;
+	struct tc_ratespec 	ceil;
+	__u32	buffer;
+	__u32	cbuffer;
+	__u32	quantum;
+	__u32	level;		/* out only */
+	__u32	prio;
+};
+struct tc_htb_glob {
+	__u32 version;		/* to match HTB/TC */
+    	__u32 rate2quantum;	/* bps->quantum divisor */
+    	__u32 defcls;		/* default class number */
+	__u32 debug;		/* debug flags */
+
+	/* stats */
+	__u32 direct_pkts; /* count of non shaped packets */
+};
+enum {
+	TCA_HTB_UNSPEC,
+	TCA_HTB_PARMS,
+	TCA_HTB_INIT,
+	TCA_HTB_CTAB,
+	TCA_HTB_RTAB,
+	TCA_HTB_DIRECT_QLEN,
+	TCA_HTB_RATE64,
+	TCA_HTB_CEIL64,
+	TCA_HTB_PAD,
+	__TCA_HTB_MAX,
+};
+
+#define TCA_HTB_MAX (__TCA_HTB_MAX - 1)
+
+struct tc_htb_xstats {
+	__u32 lends;
+	__u32 borrows;
+	__u32 giants;	/* unused since 'Make HTB scheduler work with TSO.' */
+	__s32 tokens;
+	__s32 ctokens;
+};
+
+/* HFSC section */
+
+struct tc_hfsc_qopt {
+	__u16	defcls;		/* default class */
+};
+
+struct tc_service_curve {
+	__u32	m1;		/* slope of the first segment in bps */
+	__u32	d;		/* x-projection of the first segment in us */
+	__u32	m2;		/* slope of the second segment in bps */
+};
+
+struct tc_hfsc_stats {
+	__u64	work;		/* total work done */
+	__u64	rtwork;		/* work done by real-time criteria */
+	__u32	period;		/* current period */
+	__u32	level;		/* class level in hierarchy */
+};
+
+enum {
+	TCA_HFSC_UNSPEC,
+	TCA_HFSC_RSC,
+	TCA_HFSC_FSC,
+	TCA_HFSC_USC,
+	__TCA_HFSC_MAX,
+};
+
+#define TCA_HFSC_MAX (__TCA_HFSC_MAX - 1)
+
+
+/* CBQ section */
+
+#define TC_CBQ_MAXPRIO		8
+#define TC_CBQ_MAXLEVEL		8
+#define TC_CBQ_DEF_EWMA		5
+
+struct tc_cbq_lssopt {
+	unsigned char	change;
+	unsigned char	flags;
+#define TCF_CBQ_LSS_BOUNDED	1
+#define TCF_CBQ_LSS_ISOLATED	2
+	unsigned char  	ewma_log;
+	unsigned char  	level;
+#define TCF_CBQ_LSS_FLAGS	1
+#define TCF_CBQ_LSS_EWMA	2
+#define TCF_CBQ_LSS_MAXIDLE	4
+#define TCF_CBQ_LSS_MINIDLE	8
+#define TCF_CBQ_LSS_OFFTIME	0x10
+#define TCF_CBQ_LSS_AVPKT	0x20
+	__u32		maxidle;
+	__u32		minidle;
+	__u32		offtime;
+	__u32		avpkt;
+};
+
+struct tc_cbq_wrropt {
+	unsigned char	flags;
+	unsigned char	priority;
+	unsigned char	cpriority;
+	unsigned char	__reserved;
+	__u32		allot;
+	__u32		weight;
+};
+
+struct tc_cbq_ovl {
+	unsigned char	strategy;
+#define	TC_CBQ_OVL_CLASSIC	0
+#define	TC_CBQ_OVL_DELAY	1
+#define	TC_CBQ_OVL_LOWPRIO	2
+#define	TC_CBQ_OVL_DROP		3
+#define	TC_CBQ_OVL_RCLASSIC	4
+	unsigned char	priority2;
+	__u16		pad;
+	__u32		penalty;
+};
+
+struct tc_cbq_police {
+	unsigned char	police;
+	unsigned char	__res1;
+	unsigned short	__res2;
+};
+
+struct tc_cbq_fopt {
+	__u32		split;
+	__u32		defmap;
+	__u32		defchange;
+};
+
+struct tc_cbq_xstats {
+	__u32		borrows;
+	__u32		overactions;
+	__s32		avgidle;
+	__s32		undertime;
+};
+
+enum {
+	TCA_CBQ_UNSPEC,
+	TCA_CBQ_LSSOPT,
+	TCA_CBQ_WRROPT,
+	TCA_CBQ_FOPT,
+	TCA_CBQ_OVL_STRATEGY,
+	TCA_CBQ_RATE,
+	TCA_CBQ_RTAB,
+	TCA_CBQ_POLICE,
+	__TCA_CBQ_MAX,
+};
+
+#define TCA_CBQ_MAX	(__TCA_CBQ_MAX - 1)
+
+/* ARL section */
+
+struct tc_arl_xstats {
+	__u32	max_bw;		/* The maximum bw measured */
+	__u32	min_rate;	/* The lowest base rate */
+	__u32	current_rate;	/* The current rate */
+	__u32	latency;	/* The current latency */
+	__u32	base_rate;	/* The base rate */
+	__u32	current_bw;	/* The current bw measured */
+	__u32	state;		/* The current state */
+};
+
+enum {
+	TCA_ARL_UNSPEC,
+	TCA_ARL_BUFFER,
+	TCA_ARL_MIN_RATE,
+	TCA_ARL_MAX_BW,
+	TCA_ARL_LIMIT,
+	TCA_ARL_MAX_LATENCY,
+	TCA_ARL_LATENCY_HYSTERESIS,
+	TCA_ARL_PAD,
+	TCA_ARL_MODE,
+	TCA_ARL_CODEL_TARGET,
+	__TCA_ARL_MAX,
+};
+#define TCA_ARL_MAX (__TCA_ARL_MAX - 1)
+
+/* dsmark section */
+
+enum {
+	TCA_DSMARK_UNSPEC,
+	TCA_DSMARK_INDICES,
+	TCA_DSMARK_DEFAULT_INDEX,
+	TCA_DSMARK_SET_TC_INDEX,
+	TCA_DSMARK_MASK,
+	TCA_DSMARK_VALUE,
+	__TCA_DSMARK_MAX,
+};
+
+#define TCA_DSMARK_MAX (__TCA_DSMARK_MAX - 1)
+
+/* ATM  section */
+
+enum {
+	TCA_ATM_UNSPEC,
+	TCA_ATM_FD,		/* file/socket descriptor */
+	TCA_ATM_PTR,		/* pointer to descriptor - later */
+	TCA_ATM_HDR,		/* LL header */
+	TCA_ATM_EXCESS,		/* excess traffic class (0 for CLP)  */
+	TCA_ATM_ADDR,		/* PVC address (for output only) */
+	TCA_ATM_STATE,		/* VC state (ATM_VS_*; for output only) */
+	__TCA_ATM_MAX,
+};
+
+#define TCA_ATM_MAX	(__TCA_ATM_MAX - 1)
+
+/* Network emulator */
+
+enum {
+	TCA_NETEM_UNSPEC,
+	TCA_NETEM_CORR,
+	TCA_NETEM_DELAY_DIST,
+	TCA_NETEM_REORDER,
+	TCA_NETEM_CORRUPT,
+	TCA_NETEM_LOSS,
+	TCA_NETEM_RATE,
+	TCA_NETEM_ECN,
+	TCA_NETEM_RATE64,
+	TCA_NETEM_PAD,
+	TCA_NETEM_LATENCY64,
+	TCA_NETEM_JITTER64,
+	TCA_NETEM_SLOT,
+	TCA_NETEM_SLOT_DIST,
+	__TCA_NETEM_MAX,
+};
+
+#define TCA_NETEM_MAX (__TCA_NETEM_MAX - 1)
+
+struct tc_netem_qopt {
+	__u32	latency;	/* added delay (us) */
+	__u32   limit;		/* fifo limit (packets) */
+	__u32	loss;		/* random packet loss (0=none ~0=100%) */
+	__u32	gap;		/* re-ordering gap (0 for none) */
+	__u32   duplicate;	/* random packet dup  (0=none ~0=100%) */
+	__u32	jitter;		/* random jitter in latency (us) */
+};
+
+struct tc_netem_corr {
+	__u32	delay_corr;	/* delay correlation */
+	__u32	loss_corr;	/* packet loss correlation */
+	__u32	dup_corr;	/* duplicate correlation  */
+};
+
+struct tc_netem_reorder {
+	__u32	probability;
+	__u32	correlation;
+};
+
+struct tc_netem_corrupt {
+	__u32	probability;
+	__u32	correlation;
+};
+
+struct tc_netem_rate {
+	__u32	rate;	/* byte/s */
+	__s32	packet_overhead;
+	__u32	cell_size;
+	__s32	cell_overhead;
+};
+
+struct tc_netem_slot {
+	__s64   min_delay; /* nsec */
+	__s64   max_delay;
+	__s32   max_packets;
+	__s32   max_bytes;
+	__s64	dist_delay; /* nsec */
+	__s64	dist_jitter; /* nsec */
+};
+
+enum {
+	NETEM_LOSS_UNSPEC,
+	NETEM_LOSS_GI,		/* General Intuitive - 4 state model */
+	NETEM_LOSS_GE,		/* Gilbert Elliot models */
+	__NETEM_LOSS_MAX
+};
+#define NETEM_LOSS_MAX (__NETEM_LOSS_MAX - 1)
+
+/* State transition probabilities for 4 state model */
+struct tc_netem_gimodel {
+	__u32	p13;
+	__u32	p31;
+	__u32	p32;
+	__u32	p14;
+	__u32	p23;
+};
+
+/* Gilbert-Elliot models */
+struct tc_netem_gemodel {
+	__u32 p;
+	__u32 r;
+	__u32 h;
+	__u32 k1;
+};
+
+#define NETEM_DIST_SCALE	8192
+#define NETEM_DIST_MAX		16384
+
+/* DRR */
+
+enum {
+	TCA_DRR_UNSPEC,
+	TCA_DRR_QUANTUM,
+	__TCA_DRR_MAX
+};
+
+#define TCA_DRR_MAX	(__TCA_DRR_MAX - 1)
+
+struct tc_drr_stats {
+	__u32	deficit;
+};
+
+/* MQPRIO */
+#define TC_QOPT_BITMASK 15
+#define TC_QOPT_MAX_QUEUE 16
+
+enum {
+	TC_MQPRIO_HW_OFFLOAD_NONE,	/* no offload requested */
+	TC_MQPRIO_HW_OFFLOAD_TCS,	/* offload TCs, no queue counts */
+	__TC_MQPRIO_HW_OFFLOAD_MAX
+};
+
+#define TC_MQPRIO_HW_OFFLOAD_MAX (__TC_MQPRIO_HW_OFFLOAD_MAX - 1)
+
+enum {
+	TC_MQPRIO_MODE_DCB,
+	TC_MQPRIO_MODE_CHANNEL,
+	__TC_MQPRIO_MODE_MAX
+};
+
+#define __TC_MQPRIO_MODE_MAX (__TC_MQPRIO_MODE_MAX - 1)
+
+enum {
+	TC_MQPRIO_SHAPER_DCB,
+	TC_MQPRIO_SHAPER_BW_RATE,	/* Add new shapers below */
+	__TC_MQPRIO_SHAPER_MAX
+};
+
+#define __TC_MQPRIO_SHAPER_MAX (__TC_MQPRIO_SHAPER_MAX - 1)
+
+struct tc_mqprio_qopt {
+	__u8	num_tc;
+	__u8	prio_tc_map[TC_QOPT_BITMASK + 1];
+	__u8	hw;
+	__u16	count[TC_QOPT_MAX_QUEUE];
+	__u16	offset[TC_QOPT_MAX_QUEUE];
+};
+
+#define TC_MQPRIO_F_MODE		0x1
+#define TC_MQPRIO_F_SHAPER		0x2
+#define TC_MQPRIO_F_MIN_RATE		0x4
+#define TC_MQPRIO_F_MAX_RATE		0x8
+
+enum {
+	TCA_MQPRIO_UNSPEC,
+	TCA_MQPRIO_MODE,
+	TCA_MQPRIO_SHAPER,
+	TCA_MQPRIO_MIN_RATE64,
+	TCA_MQPRIO_MAX_RATE64,
+	__TCA_MQPRIO_MAX,
+};
+
+#define TCA_MQPRIO_MAX (__TCA_MQPRIO_MAX - 1)
+
+/* SFB */
+
+enum {
+	TCA_SFB_UNSPEC,
+	TCA_SFB_PARMS,
+	__TCA_SFB_MAX,
+};
+
+#define TCA_SFB_MAX (__TCA_SFB_MAX - 1)
+
+/*
+ * Note: increment, decrement are Q0.16 fixed-point values.
+ */
+struct tc_sfb_qopt {
+	__u32 rehash_interval;	/* delay between hash move, in ms */
+	__u32 warmup_time;	/* double buffering warmup time in ms (warmup_time < rehash_interval) */
+	__u32 max;		/* max len of qlen_min */
+	__u32 bin_size;		/* maximum queue length per bin */
+	__u32 increment;	/* probability increment, (d1 in Blue) */
+	__u32 decrement;	/* probability decrement, (d2 in Blue) */
+	__u32 limit;		/* max SFB queue length */
+	__u32 penalty_rate;	/* inelastic flows are rate limited to 'rate' pps */
+	__u32 penalty_burst;
+};
+
+struct tc_sfb_xstats {
+	__u32 earlydrop;
+	__u32 penaltydrop;
+	__u32 bucketdrop;
+	__u32 queuedrop;
+	__u32 childdrop; /* drops in child qdisc */
+	__u32 marked;
+	__u32 maxqlen;
+	__u32 maxprob;
+	__u32 avgprob;
+};
+
+#define SFB_MAX_PROB 0xFFFF
+
+/* QFQ */
+enum {
+	TCA_QFQ_UNSPEC,
+	TCA_QFQ_WEIGHT,
+	TCA_QFQ_LMAX,
+	__TCA_QFQ_MAX
+};
+
+#define TCA_QFQ_MAX	(__TCA_QFQ_MAX - 1)
+
+struct tc_qfq_stats {
+	__u32 weight;
+	__u32 lmax;
+};
+
+/* CODEL */
+
+enum {
+	TCA_CODEL_UNSPEC,
+	TCA_CODEL_TARGET,
+	TCA_CODEL_LIMIT,
+	TCA_CODEL_INTERVAL,
+	TCA_CODEL_ECN,
+	TCA_CODEL_CE_THRESHOLD,
+	__TCA_CODEL_MAX
+};
+
+#define TCA_CODEL_MAX	(__TCA_CODEL_MAX - 1)
+
+struct tc_codel_xstats {
+	__u32	maxpacket; /* largest packet we've seen so far */
+	__u32	count;	   /* how many drops we've done since the last time we
+			    * entered dropping state
+			    */
+	__u32	lastcount; /* count at entry to dropping state */
+	__u32	ldelay;    /* in-queue delay seen by most recently dequeued packet */
+	__s32	drop_next; /* time to drop next packet */
+	__u32	drop_overlimit; /* number of time max qdisc packet limit was hit */
+	__u32	ecn_mark;  /* number of packets we ECN marked instead of dropped */
+	__u32	dropping;  /* are we in dropping state ? */
+	__u32	ce_mark;   /* number of CE marked packets because of ce_threshold */
+};
+
+/* FQ_CODEL */
+
+enum {
+	TCA_FQ_CODEL_UNSPEC,
+	TCA_FQ_CODEL_TARGET,
+	TCA_FQ_CODEL_LIMIT,
+	TCA_FQ_CODEL_INTERVAL,
+	TCA_FQ_CODEL_ECN,
+	TCA_FQ_CODEL_FLOWS,
+	TCA_FQ_CODEL_QUANTUM,
+	TCA_FQ_CODEL_CE_THRESHOLD,
+	TCA_FQ_CODEL_DROP_BATCH_SIZE,
+	TCA_FQ_CODEL_MEMORY_LIMIT,
+	__TCA_FQ_CODEL_MAX
+};
+
+#define TCA_FQ_CODEL_MAX	(__TCA_FQ_CODEL_MAX - 1)
+
+enum {
+	TCA_FQ_CODEL_XSTATS_QDISC,
+	TCA_FQ_CODEL_XSTATS_CLASS,
+};
+
+struct tc_fq_codel_qd_stats {
+	__u32	maxpacket;	/* largest packet we've seen so far */
+	__u32	drop_overlimit; /* number of time max qdisc
+				 * packet limit was hit
+				 */
+	__u32	ecn_mark;	/* number of packets we ECN marked
+				 * instead of being dropped
+				 */
+	__u32	new_flow_count; /* number of time packets
+				 * created a 'new flow'
+				 */
+	__u32	new_flows_len;	/* count of flows in new list */
+	__u32	old_flows_len;	/* count of flows in old list */
+	__u32	ce_mark;	/* packets above ce_threshold */
+	__u32	memory_usage;	/* in bytes */
+	__u32	drop_overmemory;
+};
+
+struct tc_fq_codel_cl_stats {
+	__s32	deficit;
+	__u32	ldelay;		/* in-queue delay seen by most recently
+				 * dequeued packet
+				 */
+	__u32	count;
+	__u32	lastcount;
+	__u32	dropping;
+	__s32	drop_next;
+};
+
+struct tc_fq_codel_xstats {
+	__u32	type;
+	union {
+		struct tc_fq_codel_qd_stats qdisc_stats;
+		struct tc_fq_codel_cl_stats class_stats;
+	};
+};
+
+/* FQ */
+
+enum {
+	TCA_FQ_UNSPEC,
+
+	TCA_FQ_PLIMIT,		/* limit of total number of packets in queue */
+
+	TCA_FQ_FLOW_PLIMIT,	/* limit of packets per flow */
+
+	TCA_FQ_QUANTUM,		/* RR quantum */
+
+	TCA_FQ_INITIAL_QUANTUM,		/* RR quantum for new flow */
+
+	TCA_FQ_RATE_ENABLE,	/* enable/disable rate limiting */
+
+	TCA_FQ_FLOW_DEFAULT_RATE,/* obsolete, do not use */
+
+	TCA_FQ_FLOW_MAX_RATE,	/* per flow max rate */
+
+	TCA_FQ_BUCKETS_LOG,	/* log2(number of buckets) */
+
+	TCA_FQ_FLOW_REFILL_DELAY,	/* flow credit refill delay in usec */
+
+	TCA_FQ_ORPHAN_MASK,	/* mask applied to orphaned skb hashes */
+
+	TCA_FQ_LOW_RATE_THRESHOLD, /* per packet delay under this rate */
+
+	TCA_FQ_CE_THRESHOLD,	/* DCTCP-like CE-marking threshold */
+
+	__TCA_FQ_MAX
+};
+
+#define TCA_FQ_MAX	(__TCA_FQ_MAX - 1)
+
+struct tc_fq_qd_stats {
+	__u64	gc_flows;
+	__u64	highprio_packets;
+	__u64	tcp_retrans;
+	__u64	throttled;
+	__u64	flows_plimit;
+	__u64	pkts_too_long;
+	__u64	allocation_errors;
+	__s64	time_next_delayed_flow;
+	__u32	flows;
+	__u32	inactive_flows;
+	__u32	throttled_flows;
+	__u32	unthrottle_latency_ns;
+	__u64	ce_mark;		/* packets above ce_threshold */
+};
+
+/* Heavy-Hitter Filter */
+
+enum {
+	TCA_HHF_UNSPEC,
+	TCA_HHF_BACKLOG_LIMIT,
+	TCA_HHF_QUANTUM,
+	TCA_HHF_HH_FLOWS_LIMIT,
+	TCA_HHF_RESET_TIMEOUT,
+	TCA_HHF_ADMIT_BYTES,
+	TCA_HHF_EVICT_TIMEOUT,
+	TCA_HHF_NON_HH_WEIGHT,
+	__TCA_HHF_MAX
+};
+
+#define TCA_HHF_MAX	(__TCA_HHF_MAX - 1)
+
+struct tc_hhf_xstats {
+	__u32	drop_overlimit; /* number of times max qdisc packet limit
+				 * was hit
+				 */
+	__u32	hh_overlimit;   /* number of times max heavy-hitters was hit */
+	__u32	hh_tot_count;   /* number of captured heavy-hitters so far */
+	__u32	hh_cur_count;   /* number of current heavy-hitters */
+};
+
+/* PIE */
+enum {
+	TCA_PIE_UNSPEC,
+	TCA_PIE_TARGET,
+	TCA_PIE_LIMIT,
+	TCA_PIE_TUPDATE,
+	TCA_PIE_ALPHA,
+	TCA_PIE_BETA,
+	TCA_PIE_ECN,
+	TCA_PIE_BYTEMODE,
+	__TCA_PIE_MAX
+};
+#define TCA_PIE_MAX   (__TCA_PIE_MAX - 1)
+
+struct tc_pie_xstats {
+	__u64 prob;             /* current probability */
+	__u32 delay;            /* current delay in ms */
+	__u32 avg_dq_rate;      /* current average dq_rate in bits/pie_time */
+	__u32 packets_in;       /* total number of packets enqueued */
+	__u32 dropped;          /* packets dropped due to pie_action */
+	__u32 overlimit;        /* dropped due to lack of space in queue */
+	__u32 maxq;             /* maximum queue size */
+	__u32 ecn_mark;         /* packets marked with ecn*/
+};
+
+/* CBS */
+struct tc_cbs_qopt {
+	__u8 offload;
+	__u8 _pad[3];
+	__s32 hicredit;
+	__s32 locredit;
+	__s32 idleslope;
+	__s32 sendslope;
+};
+
+enum {
+	TCA_CBS_UNSPEC,
+	TCA_CBS_PARMS,
+	__TCA_CBS_MAX,
+};
+
+#define TCA_CBS_MAX (__TCA_CBS_MAX - 1)
+
+
+/* ETF */
+struct tc_etf_qopt {
+	__s32 delta;
+	__s32 clockid;
+	__u32 flags;
+#define TC_ETF_DEADLINE_MODE_ON	_BITUL(0)
+#define TC_ETF_OFFLOAD_ON	_BITUL(1)
+#define TC_ETF_SKIP_SOCK_CHECK	_BITUL(2)
+};
+
+enum {
+	TCA_ETF_UNSPEC,
+	TCA_ETF_PARMS,
+	__TCA_ETF_MAX,
+};
+
+#define TCA_ETF_MAX (__TCA_ETF_MAX - 1)
+
+
+/* CAKE */
+enum {
+	TCA_CAKE_UNSPEC,
+	TCA_CAKE_PAD,
+	TCA_CAKE_BASE_RATE64,
+	TCA_CAKE_DIFFSERV_MODE,
+	TCA_CAKE_ATM,
+	TCA_CAKE_FLOW_MODE,
+	TCA_CAKE_OVERHEAD,
+	TCA_CAKE_RTT,
+	TCA_CAKE_TARGET,
+	TCA_CAKE_AUTORATE,
+	TCA_CAKE_MEMORY,
+	TCA_CAKE_NAT,
+	TCA_CAKE_RAW,
+	TCA_CAKE_WASH,
+	TCA_CAKE_MPU,
+	TCA_CAKE_INGRESS,
+	TCA_CAKE_ACK_FILTER,
+	TCA_CAKE_SPLIT_GSO,
+	TCA_CAKE_FWMARK,
+	__TCA_CAKE_MAX
+};
+#define TCA_CAKE_MAX	(__TCA_CAKE_MAX - 1)
+
+enum {
+	__TCA_CAKE_STATS_INVALID,
+	TCA_CAKE_STATS_PAD,
+	TCA_CAKE_STATS_CAPACITY_ESTIMATE64,
+	TCA_CAKE_STATS_MEMORY_LIMIT,
+	TCA_CAKE_STATS_MEMORY_USED,
+	TCA_CAKE_STATS_AVG_NETOFF,
+	TCA_CAKE_STATS_MIN_NETLEN,
+	TCA_CAKE_STATS_MAX_NETLEN,
+	TCA_CAKE_STATS_MIN_ADJLEN,
+	TCA_CAKE_STATS_MAX_ADJLEN,
+	TCA_CAKE_STATS_TIN_STATS,
+	TCA_CAKE_STATS_DEFICIT,
+	TCA_CAKE_STATS_COBALT_COUNT,
+	TCA_CAKE_STATS_DROPPING,
+	TCA_CAKE_STATS_DROP_NEXT_US,
+	TCA_CAKE_STATS_P_DROP,
+	TCA_CAKE_STATS_BLUE_TIMER_US,
+	__TCA_CAKE_STATS_MAX
+};
+#define TCA_CAKE_STATS_MAX (__TCA_CAKE_STATS_MAX - 1)
+
+enum {
+	__TCA_CAKE_TIN_STATS_INVALID,
+	TCA_CAKE_TIN_STATS_PAD,
+	TCA_CAKE_TIN_STATS_SENT_PACKETS,
+	TCA_CAKE_TIN_STATS_SENT_BYTES64,
+	TCA_CAKE_TIN_STATS_DROPPED_PACKETS,
+	TCA_CAKE_TIN_STATS_DROPPED_BYTES64,
+	TCA_CAKE_TIN_STATS_ACKS_DROPPED_PACKETS,
+	TCA_CAKE_TIN_STATS_ACKS_DROPPED_BYTES64,
+	TCA_CAKE_TIN_STATS_ECN_MARKED_PACKETS,
+	TCA_CAKE_TIN_STATS_ECN_MARKED_BYTES64,
+	TCA_CAKE_TIN_STATS_BACKLOG_PACKETS,
+	TCA_CAKE_TIN_STATS_BACKLOG_BYTES,
+	TCA_CAKE_TIN_STATS_THRESHOLD_RATE64,
+	TCA_CAKE_TIN_STATS_TARGET_US,
+	TCA_CAKE_TIN_STATS_INTERVAL_US,
+	TCA_CAKE_TIN_STATS_WAY_INDIRECT_HITS,
+	TCA_CAKE_TIN_STATS_WAY_MISSES,
+	TCA_CAKE_TIN_STATS_WAY_COLLISIONS,
+	TCA_CAKE_TIN_STATS_PEAK_DELAY_US,
+	TCA_CAKE_TIN_STATS_AVG_DELAY_US,
+	TCA_CAKE_TIN_STATS_BASE_DELAY_US,
+	TCA_CAKE_TIN_STATS_SPARSE_FLOWS,
+	TCA_CAKE_TIN_STATS_BULK_FLOWS,
+	TCA_CAKE_TIN_STATS_UNRESPONSIVE_FLOWS,
+	TCA_CAKE_TIN_STATS_MAX_SKBLEN,
+	TCA_CAKE_TIN_STATS_FLOW_QUANTUM,
+	__TCA_CAKE_TIN_STATS_MAX
+};
+#define TCA_CAKE_TIN_STATS_MAX (__TCA_CAKE_TIN_STATS_MAX - 1)
+#define TC_CAKE_MAX_TINS (8)
+
+enum {
+	CAKE_FLOW_NONE = 0,
+	CAKE_FLOW_SRC_IP,
+	CAKE_FLOW_DST_IP,
+	CAKE_FLOW_HOSTS,    /* = CAKE_FLOW_SRC_IP | CAKE_FLOW_DST_IP */
+	CAKE_FLOW_FLOWS,
+	CAKE_FLOW_DUAL_SRC, /* = CAKE_FLOW_SRC_IP | CAKE_FLOW_FLOWS */
+	CAKE_FLOW_DUAL_DST, /* = CAKE_FLOW_DST_IP | CAKE_FLOW_FLOWS */
+	CAKE_FLOW_TRIPLE,   /* = CAKE_FLOW_HOSTS  | CAKE_FLOW_FLOWS */
+	CAKE_FLOW_MAX,
+};
+
+enum {
+	CAKE_DIFFSERV_DIFFSERV3 = 0,
+	CAKE_DIFFSERV_DIFFSERV4,
+	CAKE_DIFFSERV_DIFFSERV8,
+	CAKE_DIFFSERV_BESTEFFORT,
+	CAKE_DIFFSERV_PRECEDENCE,
+	CAKE_DIFFSERV_MAX
+};
+
+enum {
+	CAKE_ACK_NONE = 0,
+	CAKE_ACK_FILTER,
+	CAKE_ACK_AGGRESSIVE,
+	CAKE_ACK_MAX
+};
+
+enum {
+	CAKE_ATM_NONE = 0,
+	CAKE_ATM_ATM,
+	CAKE_ATM_PTM,
+	CAKE_ATM_MAX
+};
+
+
+/* TAPRIO */
+enum {
+	TC_TAPRIO_CMD_SET_GATES = 0x00,
+	TC_TAPRIO_CMD_SET_AND_HOLD = 0x01,
+	TC_TAPRIO_CMD_SET_AND_RELEASE = 0x02,
+};
+
+enum {
+	TCA_TAPRIO_SCHED_ENTRY_UNSPEC,
+	TCA_TAPRIO_SCHED_ENTRY_INDEX, /* u32 */
+	TCA_TAPRIO_SCHED_ENTRY_CMD, /* u8 */
+	TCA_TAPRIO_SCHED_ENTRY_GATE_MASK, /* u32 */
+	TCA_TAPRIO_SCHED_ENTRY_INTERVAL, /* u32 */
+	__TCA_TAPRIO_SCHED_ENTRY_MAX,
+};
+#define TCA_TAPRIO_SCHED_ENTRY_MAX (__TCA_TAPRIO_SCHED_ENTRY_MAX - 1)
+
+/* The format for schedule entry list is:
+ * [TCA_TAPRIO_SCHED_ENTRY_LIST]
+ *   [TCA_TAPRIO_SCHED_ENTRY]
+ *     [TCA_TAPRIO_SCHED_ENTRY_CMD]
+ *     [TCA_TAPRIO_SCHED_ENTRY_GATES]
+ *     [TCA_TAPRIO_SCHED_ENTRY_INTERVAL]
+ */
+enum {
+	TCA_TAPRIO_SCHED_UNSPEC,
+	TCA_TAPRIO_SCHED_ENTRY,
+	__TCA_TAPRIO_SCHED_MAX,
+};
+
+#define TCA_TAPRIO_SCHED_MAX (__TCA_TAPRIO_SCHED_MAX - 1)
+
+/* The format for the admin sched (dump only):
+ * [TCA_TAPRIO_SCHED_ADMIN_SCHED]
+ *   [TCA_TAPRIO_ATTR_SCHED_BASE_TIME]
+ *   [TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST]
+ *     [TCA_TAPRIO_ATTR_SCHED_ENTRY]
+ *       [TCA_TAPRIO_ATTR_SCHED_ENTRY_CMD]
+ *       [TCA_TAPRIO_ATTR_SCHED_ENTRY_GATES]
+ *       [TCA_TAPRIO_ATTR_SCHED_ENTRY_INTERVAL]
+ */
+
+#define TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST	BIT(0)
+#define TCA_TAPRIO_ATTR_FLAG_FULL_OFFLOAD	BIT(1)
+
+enum {
+	TCA_TAPRIO_ATTR_UNSPEC,
+	TCA_TAPRIO_ATTR_PRIOMAP, /* struct tc_mqprio_qopt */
+	TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST, /* nested of entry */
+	TCA_TAPRIO_ATTR_SCHED_BASE_TIME, /* s64 */
+	TCA_TAPRIO_ATTR_SCHED_SINGLE_ENTRY, /* single entry */
+	TCA_TAPRIO_ATTR_SCHED_CLOCKID, /* s32 */
+	TCA_TAPRIO_PAD,
+	TCA_TAPRIO_ATTR_ADMIN_SCHED, /* The admin sched, only used in dump */
+	TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME, /* s64 */
+	TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION, /* s64 */
+	TCA_TAPRIO_ATTR_FLAGS, /* u32 */
+	TCA_TAPRIO_ATTR_TXTIME_DELAY, /* u32 */
+	__TCA_TAPRIO_ATTR_MAX,
+};
+
+#define TCA_TAPRIO_ATTR_MAX (__TCA_TAPRIO_ATTR_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/posix_types.h b/include/uapi/linux/posix_types.h
new file mode 100644
index 0000000..9a7a740
--- /dev/null
+++ b/include/uapi/linux/posix_types.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_POSIX_TYPES_H
+#define _LINUX_POSIX_TYPES_H
+
+#include <linux/stddef.h>
+
+/*
+ * This allows for 1024 file descriptors: if NR_OPEN is ever grown
+ * beyond that you'll have to change this too. But 1024 fd's seem to be
+ * enough even for such "real" unices like OSF/1, so hopefully this is
+ * one limit that doesn't have to be changed [again].
+ *
+ * Note that POSIX wants the FD_CLEAR(fd,fdsetp) defines to be in
+ * <sys/time.h> (and thus <linux/time.h>) - but this is a more logical
+ * place for them. Solved by having dummy defines in <sys/time.h>.
+ */
+
+/*
+ * This macro may have been defined in <gnu/types.h>. But we always
+ * use the one here.
+ */
+#undef __FD_SETSIZE
+#define __FD_SETSIZE	1024
+
+typedef struct {
+	unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
+} __kernel_fd_set;
+
+/* Type of a signal handler.  */
+typedef void (*__kernel_sighandler_t)(int);
+
+/* Type of a SYSV IPC key.  */
+typedef int __kernel_key_t;
+typedef int __kernel_mqd_t;
+
+#include <asm/posix_types.h>
+
+#endif /* _LINUX_POSIX_TYPES_H */
diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
new file mode 100644
index 0000000..358e83e
--- /dev/null
+++ b/include/uapi/linux/rtnetlink.h
@@ -0,0 +1,759 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_RTNETLINK_H
+#define __LINUX_RTNETLINK_H
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <linux/if_link.h>
+#include <linux/if_addr.h>
+#include <linux/neighbour.h>
+
+/* rtnetlink families. Values up to 127 are reserved for real address
+ * families, values above 128 may be used arbitrarily.
+ */
+#define RTNL_FAMILY_IPMR		128
+#define RTNL_FAMILY_IP6MR		129
+#define RTNL_FAMILY_MAX			129
+
+/****
+ *		Routing/neighbour discovery messages.
+ ****/
+
+/* Types of messages */
+
+enum {
+	RTM_BASE	= 16,
+#define RTM_BASE	RTM_BASE
+
+	RTM_NEWLINK	= 16,
+#define RTM_NEWLINK	RTM_NEWLINK
+	RTM_DELLINK,
+#define RTM_DELLINK	RTM_DELLINK
+	RTM_GETLINK,
+#define RTM_GETLINK	RTM_GETLINK
+	RTM_SETLINK,
+#define RTM_SETLINK	RTM_SETLINK
+
+	RTM_NEWADDR	= 20,
+#define RTM_NEWADDR	RTM_NEWADDR
+	RTM_DELADDR,
+#define RTM_DELADDR	RTM_DELADDR
+	RTM_GETADDR,
+#define RTM_GETADDR	RTM_GETADDR
+
+	RTM_NEWROUTE	= 24,
+#define RTM_NEWROUTE	RTM_NEWROUTE
+	RTM_DELROUTE,
+#define RTM_DELROUTE	RTM_DELROUTE
+	RTM_GETROUTE,
+#define RTM_GETROUTE	RTM_GETROUTE
+
+	RTM_NEWNEIGH	= 28,
+#define RTM_NEWNEIGH	RTM_NEWNEIGH
+	RTM_DELNEIGH,
+#define RTM_DELNEIGH	RTM_DELNEIGH
+	RTM_GETNEIGH,
+#define RTM_GETNEIGH	RTM_GETNEIGH
+
+	RTM_NEWRULE	= 32,
+#define RTM_NEWRULE	RTM_NEWRULE
+	RTM_DELRULE,
+#define RTM_DELRULE	RTM_DELRULE
+	RTM_GETRULE,
+#define RTM_GETRULE	RTM_GETRULE
+
+	RTM_NEWQDISC	= 36,
+#define RTM_NEWQDISC	RTM_NEWQDISC
+	RTM_DELQDISC,
+#define RTM_DELQDISC	RTM_DELQDISC
+	RTM_GETQDISC,
+#define RTM_GETQDISC	RTM_GETQDISC
+
+	RTM_NEWTCLASS	= 40,
+#define RTM_NEWTCLASS	RTM_NEWTCLASS
+	RTM_DELTCLASS,
+#define RTM_DELTCLASS	RTM_DELTCLASS
+	RTM_GETTCLASS,
+#define RTM_GETTCLASS	RTM_GETTCLASS
+
+	RTM_NEWTFILTER	= 44,
+#define RTM_NEWTFILTER	RTM_NEWTFILTER
+	RTM_DELTFILTER,
+#define RTM_DELTFILTER	RTM_DELTFILTER
+	RTM_GETTFILTER,
+#define RTM_GETTFILTER	RTM_GETTFILTER
+
+	RTM_NEWACTION	= 48,
+#define RTM_NEWACTION   RTM_NEWACTION
+	RTM_DELACTION,
+#define RTM_DELACTION   RTM_DELACTION
+	RTM_GETACTION,
+#define RTM_GETACTION   RTM_GETACTION
+
+	RTM_NEWPREFIX	= 52,
+#define RTM_NEWPREFIX	RTM_NEWPREFIX
+
+	RTM_GETMULTICAST = 58,
+#define RTM_GETMULTICAST RTM_GETMULTICAST
+
+	RTM_GETANYCAST	= 62,
+#define RTM_GETANYCAST	RTM_GETANYCAST
+
+	RTM_NEWNEIGHTBL	= 64,
+#define RTM_NEWNEIGHTBL	RTM_NEWNEIGHTBL
+	RTM_GETNEIGHTBL	= 66,
+#define RTM_GETNEIGHTBL	RTM_GETNEIGHTBL
+	RTM_SETNEIGHTBL,
+#define RTM_SETNEIGHTBL	RTM_SETNEIGHTBL
+
+	RTM_NEWNDUSEROPT = 68,
+#define RTM_NEWNDUSEROPT RTM_NEWNDUSEROPT
+
+	RTM_NEWADDRLABEL = 72,
+#define RTM_NEWADDRLABEL RTM_NEWADDRLABEL
+	RTM_DELADDRLABEL,
+#define RTM_DELADDRLABEL RTM_DELADDRLABEL
+	RTM_GETADDRLABEL,
+#define RTM_GETADDRLABEL RTM_GETADDRLABEL
+
+	RTM_GETDCB = 78,
+#define RTM_GETDCB RTM_GETDCB
+	RTM_SETDCB,
+#define RTM_SETDCB RTM_SETDCB
+
+	RTM_NEWNETCONF = 80,
+#define RTM_NEWNETCONF RTM_NEWNETCONF
+	RTM_DELNETCONF,
+#define RTM_DELNETCONF RTM_DELNETCONF
+	RTM_GETNETCONF = 82,
+#define RTM_GETNETCONF RTM_GETNETCONF
+
+	RTM_NEWMDB = 84,
+#define RTM_NEWMDB RTM_NEWMDB
+	RTM_DELMDB = 85,
+#define RTM_DELMDB RTM_DELMDB
+	RTM_GETMDB = 86,
+#define RTM_GETMDB RTM_GETMDB
+
+	RTM_NEWNSID = 88,
+#define RTM_NEWNSID RTM_NEWNSID
+	RTM_DELNSID = 89,
+#define RTM_DELNSID RTM_DELNSID
+	RTM_GETNSID = 90,
+#define RTM_GETNSID RTM_GETNSID
+
+	RTM_NEWSTATS = 92,
+#define RTM_NEWSTATS RTM_NEWSTATS
+	RTM_GETSTATS = 94,
+#define RTM_GETSTATS RTM_GETSTATS
+
+	RTM_NEWCACHEREPORT = 96,
+#define RTM_NEWCACHEREPORT RTM_NEWCACHEREPORT
+
+	RTM_NEWCHAIN = 100,
+#define RTM_NEWCHAIN RTM_NEWCHAIN
+	RTM_DELCHAIN,
+#define RTM_DELCHAIN RTM_DELCHAIN
+	RTM_GETCHAIN,
+#define RTM_GETCHAIN RTM_GETCHAIN
+
+	RTM_NEWNEXTHOP = 104,
+#define RTM_NEWNEXTHOP	RTM_NEWNEXTHOP
+	RTM_DELNEXTHOP,
+#define RTM_DELNEXTHOP	RTM_DELNEXTHOP
+	RTM_GETNEXTHOP,
+#define RTM_GETNEXTHOP	RTM_GETNEXTHOP
+
+	__RTM_MAX,
+#define RTM_MAX		(((__RTM_MAX + 3) & ~3) - 1)
+};
+
+#define RTM_NR_MSGTYPES	(RTM_MAX + 1 - RTM_BASE)
+#define RTM_NR_FAMILIES	(RTM_NR_MSGTYPES >> 2)
+#define RTM_FAM(cmd)	(((cmd) - RTM_BASE) >> 2)
+
+/* 
+   Generic structure for encapsulation of optional route information.
+   It is reminiscent of sockaddr, but with sa_family replaced
+   with attribute type.
+ */
+
+struct rtattr {
+	unsigned short	rta_len;
+	unsigned short	rta_type;
+};
+
+/* Macros to handle rtattributes */
+
+#define RTA_ALIGNTO	4U
+#define RTA_ALIGN(len) ( ((len)+RTA_ALIGNTO-1) & ~(RTA_ALIGNTO-1) )
+#define RTA_OK(rta,len) ((len) >= (int)sizeof(struct rtattr) && \
+			 (rta)->rta_len >= sizeof(struct rtattr) && \
+			 (rta)->rta_len <= (len))
+#define RTA_NEXT(rta,attrlen)	((attrlen) -= RTA_ALIGN((rta)->rta_len), \
+				 (struct rtattr*)(((char*)(rta)) + RTA_ALIGN((rta)->rta_len)))
+#define RTA_LENGTH(len)	(RTA_ALIGN(sizeof(struct rtattr)) + (len))
+#define RTA_SPACE(len)	RTA_ALIGN(RTA_LENGTH(len))
+#define RTA_DATA(rta)   ((void*)(((char*)(rta)) + RTA_LENGTH(0)))
+#define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0))
+
+
+
+
+/******************************************************************************
+ *		Definitions used in routing table administration.
+ ****/
+
+struct rtmsg {
+	unsigned char		rtm_family;
+	unsigned char		rtm_dst_len;
+	unsigned char		rtm_src_len;
+	unsigned char		rtm_tos;
+
+	unsigned char		rtm_table;	/* Routing table id */
+	unsigned char		rtm_protocol;	/* Routing protocol; see below	*/
+	unsigned char		rtm_scope;	/* See below */	
+	unsigned char		rtm_type;	/* See below	*/
+
+	unsigned		rtm_flags;
+};
+
+/* rtm_type */
+
+enum {
+	RTN_UNSPEC,
+	RTN_UNICAST,		/* Gateway or direct route	*/
+	RTN_LOCAL,		/* Accept locally		*/
+	RTN_BROADCAST,		/* Accept locally as broadcast,
+				   send as broadcast */
+	RTN_ANYCAST,		/* Accept locally as broadcast,
+				   but send as unicast */
+	RTN_MULTICAST,		/* Multicast route		*/
+	RTN_BLACKHOLE,		/* Drop				*/
+	RTN_UNREACHABLE,	/* Destination is unreachable   */
+	RTN_PROHIBIT,		/* Administratively prohibited	*/
+	RTN_THROW,		/* Not in this table		*/
+	RTN_NAT,		/* Translate this address	*/
+	RTN_XRESOLVE,		/* Use external resolver	*/
+	__RTN_MAX
+};
+
+#define RTN_MAX (__RTN_MAX - 1)
+
+
+/* rtm_protocol */
+
+#define RTPROT_UNSPEC	0
+#define RTPROT_REDIRECT	1	/* Route installed by ICMP redirects;
+				   not used by current IPv4 */
+#define RTPROT_KERNEL	2	/* Route installed by kernel		*/
+#define RTPROT_BOOT	3	/* Route installed during boot		*/
+#define RTPROT_STATIC	4	/* Route installed by administrator	*/
+
+/* Values of protocol >= RTPROT_STATIC are not interpreted by kernel;
+   they are just passed from user and back as is.
+   It will be used by hypothetical multiple routing daemons.
+   Note that protocol values should be standardized in order to
+   avoid conflicts.
+ */
+
+#define RTPROT_GATED	8	/* Apparently, GateD */
+#define RTPROT_RA	9	/* RDISC/ND router advertisements */
+#define RTPROT_MRT	10	/* Merit MRT */
+#define RTPROT_ZEBRA	11	/* Zebra */
+#define RTPROT_BIRD	12	/* BIRD */
+#define RTPROT_DNROUTED	13	/* DECnet routing daemon */
+#define RTPROT_XORP	14	/* XORP */
+#define RTPROT_NTK	15	/* Netsukuku */
+#define RTPROT_DHCP	16      /* DHCP client */
+#define RTPROT_MROUTED	17      /* Multicast daemon */
+#define RTPROT_BABEL	42      /* Babel daemon */
+#define RTPROT_BGP	186     /* BGP Routes */
+#define RTPROT_ISIS	187     /* ISIS Routes */
+#define RTPROT_OSPF	188     /* OSPF Routes */
+#define RTPROT_RIP	189     /* RIP Routes */
+#define RTPROT_EIGRP	192     /* EIGRP Routes */
+
+/* rtm_scope
+
+   Really it is not scope, but sort of distance to the destination.
+   NOWHERE are reserved for not existing destinations, HOST is our
+   local addresses, LINK are destinations, located on directly attached
+   link and UNIVERSE is everywhere in the Universe.
+
+   Intermediate values are also possible f.e. interior routes
+   could be assigned a value between UNIVERSE and LINK.
+*/
+
+enum rt_scope_t {
+	RT_SCOPE_UNIVERSE=0,
+/* User defined values  */
+	RT_SCOPE_SITE=200,
+	RT_SCOPE_LINK=253,
+	RT_SCOPE_HOST=254,
+	RT_SCOPE_NOWHERE=255
+};
+
+/* rtm_flags */
+
+#define RTM_F_NOTIFY		0x100	/* Notify user of route change	*/
+#define RTM_F_CLONED		0x200	/* This route is cloned		*/
+#define RTM_F_EQUALIZE		0x400	/* Multipath equalizer: NI	*/
+#define RTM_F_PREFIX		0x800	/* Prefix addresses		*/
+#define RTM_F_LOOKUP_TABLE	0x1000	/* set rtm_table to FIB lookup result */
+#define RTM_F_FIB_MATCH	        0x2000	/* return full fib lookup match */
+
+/* Reserved table identifiers */
+
+enum rt_class_t {
+	RT_TABLE_UNSPEC=0,
+/* User defined values */
+	RT_TABLE_COMPAT=252,
+	RT_TABLE_DEFAULT=253,
+	RT_TABLE_MAIN=254,
+	RT_TABLE_LOCAL=255,
+	RT_TABLE_MAX=0xFFFFFFFF
+};
+
+
+/* Routing message attributes */
+
+enum rtattr_type_t {
+	RTA_UNSPEC,
+	RTA_DST,
+	RTA_SRC,
+	RTA_IIF,
+	RTA_OIF,
+	RTA_GATEWAY,
+	RTA_PRIORITY,
+	RTA_PREFSRC,
+	RTA_METRICS,
+	RTA_MULTIPATH,
+	RTA_PROTOINFO, /* no longer used */
+	RTA_FLOW,
+	RTA_CACHEINFO,
+	RTA_SESSION, /* no longer used */
+	RTA_MP_ALGO, /* no longer used */
+	RTA_TABLE,
+	RTA_MARK,
+	RTA_MFC_STATS,
+	RTA_VIA,
+	RTA_NEWDST,
+	RTA_PREF,
+	RTA_ENCAP_TYPE,
+	RTA_ENCAP,
+	RTA_EXPIRES,
+	RTA_PAD,
+	RTA_UID,
+	RTA_TTL_PROPAGATE,
+	RTA_IP_PROTO,
+	RTA_SPORT,
+	RTA_DPORT,
+	RTA_NH_ID,
+	__RTA_MAX
+};
+
+#define RTA_MAX (__RTA_MAX - 1)
+
+#define RTM_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))))
+#define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg))
+
+/* RTM_MULTIPATH --- array of struct rtnexthop.
+ *
+ * "struct rtnexthop" describes all necessary nexthop information,
+ * i.e. parameters of path to a destination via this nexthop.
+ *
+ * At the moment it is impossible to set different prefsrc, mtu, window
+ * and rtt for different paths from multipath.
+ */
+
+struct rtnexthop {
+	unsigned short		rtnh_len;
+	unsigned char		rtnh_flags;
+	unsigned char		rtnh_hops;
+	int			rtnh_ifindex;
+};
+
+/* rtnh_flags */
+
+#define RTNH_F_DEAD		1	/* Nexthop is dead (used by multipath)	*/
+#define RTNH_F_PERVASIVE	2	/* Do recursive gateway lookup	*/
+#define RTNH_F_ONLINK		4	/* Gateway is forced on link	*/
+#define RTNH_F_OFFLOAD		8	/* offloaded route */
+#define RTNH_F_LINKDOWN		16	/* carrier-down on nexthop */
+#define RTNH_F_UNRESOLVED	32	/* The entry is unresolved (ipmr) */
+
+#define RTNH_COMPARE_MASK	(RTNH_F_DEAD | RTNH_F_LINKDOWN | RTNH_F_OFFLOAD)
+
+/* Macros to handle hexthops */
+
+#define RTNH_ALIGNTO	4
+#define RTNH_ALIGN(len) ( ((len)+RTNH_ALIGNTO-1) & ~(RTNH_ALIGNTO-1) )
+#define RTNH_OK(rtnh,len) ((rtnh)->rtnh_len >= sizeof(struct rtnexthop) && \
+			   ((int)(rtnh)->rtnh_len) <= (len))
+#define RTNH_NEXT(rtnh)	((struct rtnexthop*)(((char*)(rtnh)) + RTNH_ALIGN((rtnh)->rtnh_len)))
+#define RTNH_LENGTH(len) (RTNH_ALIGN(sizeof(struct rtnexthop)) + (len))
+#define RTNH_SPACE(len)	RTNH_ALIGN(RTNH_LENGTH(len))
+#define RTNH_DATA(rtnh)   ((struct rtattr*)(((char*)(rtnh)) + RTNH_LENGTH(0)))
+
+/* RTA_VIA */
+struct rtvia {
+	__kernel_sa_family_t	rtvia_family;
+	__u8			rtvia_addr[0];
+};
+
+/* RTM_CACHEINFO */
+
+struct rta_cacheinfo {
+	__u32	rta_clntref;
+	__u32	rta_lastuse;
+	__s32	rta_expires;
+	__u32	rta_error;
+	__u32	rta_used;
+
+#define RTNETLINK_HAVE_PEERINFO 1
+	__u32	rta_id;
+	__u32	rta_ts;
+	__u32	rta_tsage;
+};
+
+/* RTM_METRICS --- array of struct rtattr with types of RTAX_* */
+
+enum {
+	RTAX_UNSPEC,
+#define RTAX_UNSPEC RTAX_UNSPEC
+	RTAX_LOCK,
+#define RTAX_LOCK RTAX_LOCK
+	RTAX_MTU,
+#define RTAX_MTU RTAX_MTU
+	RTAX_WINDOW,
+#define RTAX_WINDOW RTAX_WINDOW
+	RTAX_RTT,
+#define RTAX_RTT RTAX_RTT
+	RTAX_RTTVAR,
+#define RTAX_RTTVAR RTAX_RTTVAR
+	RTAX_SSTHRESH,
+#define RTAX_SSTHRESH RTAX_SSTHRESH
+	RTAX_CWND,
+#define RTAX_CWND RTAX_CWND
+	RTAX_ADVMSS,
+#define RTAX_ADVMSS RTAX_ADVMSS
+	RTAX_REORDERING,
+#define RTAX_REORDERING RTAX_REORDERING
+	RTAX_HOPLIMIT,
+#define RTAX_HOPLIMIT RTAX_HOPLIMIT
+	RTAX_INITCWND,
+#define RTAX_INITCWND RTAX_INITCWND
+	RTAX_FEATURES,
+#define RTAX_FEATURES RTAX_FEATURES
+	RTAX_RTO_MIN,
+#define RTAX_RTO_MIN RTAX_RTO_MIN
+	RTAX_INITRWND,
+#define RTAX_INITRWND RTAX_INITRWND
+	RTAX_QUICKACK,
+#define RTAX_QUICKACK RTAX_QUICKACK
+	RTAX_CC_ALGO,
+#define RTAX_CC_ALGO RTAX_CC_ALGO
+	RTAX_FASTOPEN_NO_COOKIE,
+#define RTAX_FASTOPEN_NO_COOKIE RTAX_FASTOPEN_NO_COOKIE
+	__RTAX_MAX
+};
+
+#define RTAX_MAX (__RTAX_MAX - 1)
+
+#define RTAX_FEATURE_ECN	(1 << 0)
+#define RTAX_FEATURE_SACK	(1 << 1)
+#define RTAX_FEATURE_TIMESTAMP	(1 << 2)
+#define RTAX_FEATURE_ALLFRAG	(1 << 3)
+
+#define RTAX_FEATURE_MASK	(RTAX_FEATURE_ECN | RTAX_FEATURE_SACK | \
+				 RTAX_FEATURE_TIMESTAMP | RTAX_FEATURE_ALLFRAG)
+
+struct rta_session {
+	__u8	proto;
+	__u8	pad1;
+	__u16	pad2;
+
+	union {
+		struct {
+			__u16	sport;
+			__u16	dport;
+		} ports;
+
+		struct {
+			__u8	type;
+			__u8	code;
+			__u16	ident;
+		} icmpt;
+
+		__u32		spi;
+	} u;
+};
+
+struct rta_mfc_stats {
+	__u64	mfcs_packets;
+	__u64	mfcs_bytes;
+	__u64	mfcs_wrong_if;
+};
+
+/****
+ *		General form of address family dependent message.
+ ****/
+
+struct rtgenmsg {
+	unsigned char		rtgen_family;
+};
+
+/*****************************************************************
+ *		Link layer specific messages.
+ ****/
+
+/* struct ifinfomsg
+ * passes link level specific information, not dependent
+ * on network protocol.
+ */
+
+struct ifinfomsg {
+	unsigned char	ifi_family;
+	unsigned char	__ifi_pad;
+	unsigned short	ifi_type;		/* ARPHRD_* */
+	int		ifi_index;		/* Link index	*/
+	unsigned	ifi_flags;		/* IFF_* flags	*/
+	unsigned	ifi_change;		/* IFF_* change mask */
+};
+
+/********************************************************************
+ *		prefix information 
+ ****/
+
+struct prefixmsg {
+	unsigned char	prefix_family;
+	unsigned char	prefix_pad1;
+	unsigned short	prefix_pad2;
+	int		prefix_ifindex;
+	unsigned char	prefix_type;
+	unsigned char	prefix_len;
+	unsigned char	prefix_flags;
+	unsigned char	prefix_pad3;
+};
+
+enum 
+{
+	PREFIX_UNSPEC,
+	PREFIX_ADDRESS,
+	PREFIX_CACHEINFO,
+	__PREFIX_MAX
+};
+
+#define PREFIX_MAX	(__PREFIX_MAX - 1)
+
+struct prefix_cacheinfo {
+	__u32	preferred_time;
+	__u32	valid_time;
+};
+
+
+/*****************************************************************
+ *		Traffic control messages.
+ ****/
+
+struct tcmsg {
+	unsigned char	tcm_family;
+	unsigned char	tcm__pad1;
+	unsigned short	tcm__pad2;
+	int		tcm_ifindex;
+	__u32		tcm_handle;
+	__u32		tcm_parent;
+/* tcm_block_index is used instead of tcm_parent
+ * in case tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK
+ */
+#define tcm_block_index tcm_parent
+	__u32		tcm_info;
+};
+
+/* For manipulation of filters in shared block, tcm_ifindex is set to
+ * TCM_IFINDEX_MAGIC_BLOCK, and tcm_parent is aliased to tcm_block_index
+ * which is the block index.
+ */
+#define TCM_IFINDEX_MAGIC_BLOCK (0xFFFFFFFFU)
+
+enum {
+	TCA_UNSPEC,
+	TCA_KIND,
+	TCA_OPTIONS,
+	TCA_STATS,
+	TCA_XSTATS,
+	TCA_RATE,
+	TCA_FCNT,
+	TCA_STATS2,
+	TCA_STAB,
+	TCA_PAD,
+	TCA_DUMP_INVISIBLE,
+	TCA_CHAIN,
+	TCA_HW_OFFLOAD,
+	TCA_INGRESS_BLOCK,
+	TCA_EGRESS_BLOCK,
+	__TCA_MAX
+};
+
+#define TCA_MAX (__TCA_MAX - 1)
+
+#define TCA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcmsg))))
+#define TCA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcmsg))
+
+/********************************************************************
+ *		Neighbor Discovery userland options
+ ****/
+
+struct nduseroptmsg {
+	unsigned char	nduseropt_family;
+	unsigned char	nduseropt_pad1;
+	unsigned short	nduseropt_opts_len;	/* Total length of options */
+	int		nduseropt_ifindex;
+	__u8		nduseropt_icmp_type;
+	__u8		nduseropt_icmp_code;
+	unsigned short	nduseropt_pad2;
+	unsigned int	nduseropt_pad3;
+	/* Followed by one or more ND options */
+};
+
+enum {
+	NDUSEROPT_UNSPEC,
+	NDUSEROPT_SRCADDR,
+	__NDUSEROPT_MAX
+};
+
+#define NDUSEROPT_MAX	(__NDUSEROPT_MAX - 1)
+
+/* RTnetlink multicast groups - backwards compatibility for userspace */
+#define RTMGRP_LINK		1
+#define RTMGRP_NOTIFY		2
+#define RTMGRP_NEIGH		4
+#define RTMGRP_TC		8
+
+#define RTMGRP_IPV4_IFADDR	0x10
+#define RTMGRP_IPV4_MROUTE	0x20
+#define RTMGRP_IPV4_ROUTE	0x40
+#define RTMGRP_IPV4_RULE	0x80
+
+#define RTMGRP_IPV6_IFADDR	0x100
+#define RTMGRP_IPV6_MROUTE	0x200
+#define RTMGRP_IPV6_ROUTE	0x400
+#define RTMGRP_IPV6_IFINFO	0x800
+
+#define RTMGRP_DECnet_IFADDR    0x1000
+#define RTMGRP_DECnet_ROUTE     0x4000
+
+#define RTMGRP_IPV6_PREFIX	0x20000
+
+/* RTnetlink multicast groups */
+enum rtnetlink_groups {
+	RTNLGRP_NONE,
+#define RTNLGRP_NONE		RTNLGRP_NONE
+	RTNLGRP_LINK,
+#define RTNLGRP_LINK		RTNLGRP_LINK
+	RTNLGRP_NOTIFY,
+#define RTNLGRP_NOTIFY		RTNLGRP_NOTIFY
+	RTNLGRP_NEIGH,
+#define RTNLGRP_NEIGH		RTNLGRP_NEIGH
+	RTNLGRP_TC,
+#define RTNLGRP_TC		RTNLGRP_TC
+	RTNLGRP_IPV4_IFADDR,
+#define RTNLGRP_IPV4_IFADDR	RTNLGRP_IPV4_IFADDR
+	RTNLGRP_IPV4_MROUTE,
+#define	RTNLGRP_IPV4_MROUTE	RTNLGRP_IPV4_MROUTE
+	RTNLGRP_IPV4_ROUTE,
+#define RTNLGRP_IPV4_ROUTE	RTNLGRP_IPV4_ROUTE
+	RTNLGRP_IPV4_RULE,
+#define RTNLGRP_IPV4_RULE	RTNLGRP_IPV4_RULE
+	RTNLGRP_IPV6_IFADDR,
+#define RTNLGRP_IPV6_IFADDR	RTNLGRP_IPV6_IFADDR
+	RTNLGRP_IPV6_MROUTE,
+#define RTNLGRP_IPV6_MROUTE	RTNLGRP_IPV6_MROUTE
+	RTNLGRP_IPV6_ROUTE,
+#define RTNLGRP_IPV6_ROUTE	RTNLGRP_IPV6_ROUTE
+	RTNLGRP_IPV6_IFINFO,
+#define RTNLGRP_IPV6_IFINFO	RTNLGRP_IPV6_IFINFO
+	RTNLGRP_DECnet_IFADDR,
+#define RTNLGRP_DECnet_IFADDR	RTNLGRP_DECnet_IFADDR
+	RTNLGRP_NOP2,
+	RTNLGRP_DECnet_ROUTE,
+#define RTNLGRP_DECnet_ROUTE	RTNLGRP_DECnet_ROUTE
+	RTNLGRP_DECnet_RULE,
+#define RTNLGRP_DECnet_RULE	RTNLGRP_DECnet_RULE
+	RTNLGRP_NOP4,
+	RTNLGRP_IPV6_PREFIX,
+#define RTNLGRP_IPV6_PREFIX	RTNLGRP_IPV6_PREFIX
+	RTNLGRP_IPV6_RULE,
+#define RTNLGRP_IPV6_RULE	RTNLGRP_IPV6_RULE
+	RTNLGRP_ND_USEROPT,
+#define RTNLGRP_ND_USEROPT	RTNLGRP_ND_USEROPT
+	RTNLGRP_PHONET_IFADDR,
+#define RTNLGRP_PHONET_IFADDR	RTNLGRP_PHONET_IFADDR
+	RTNLGRP_PHONET_ROUTE,
+#define RTNLGRP_PHONET_ROUTE	RTNLGRP_PHONET_ROUTE
+	RTNLGRP_DCB,
+#define RTNLGRP_DCB		RTNLGRP_DCB
+	RTNLGRP_IPV4_NETCONF,
+#define RTNLGRP_IPV4_NETCONF	RTNLGRP_IPV4_NETCONF
+	RTNLGRP_IPV6_NETCONF,
+#define RTNLGRP_IPV6_NETCONF	RTNLGRP_IPV6_NETCONF
+	RTNLGRP_MDB,
+#define RTNLGRP_MDB		RTNLGRP_MDB
+	RTNLGRP_MPLS_ROUTE,
+#define RTNLGRP_MPLS_ROUTE	RTNLGRP_MPLS_ROUTE
+	RTNLGRP_NSID,
+#define RTNLGRP_NSID		RTNLGRP_NSID
+	RTNLGRP_MPLS_NETCONF,
+#define RTNLGRP_MPLS_NETCONF	RTNLGRP_MPLS_NETCONF
+	RTNLGRP_IPV4_MROUTE_R,
+#define RTNLGRP_IPV4_MROUTE_R	RTNLGRP_IPV4_MROUTE_R
+	RTNLGRP_IPV6_MROUTE_R,
+#define RTNLGRP_IPV6_MROUTE_R	RTNLGRP_IPV6_MROUTE_R
+	RTNLGRP_NEXTHOP,
+#define RTNLGRP_NEXTHOP		RTNLGRP_NEXTHOP
+	__RTNLGRP_MAX
+};
+#define RTNLGRP_MAX	(__RTNLGRP_MAX - 1)
+
+/* TC action piece */
+struct tcamsg {
+	unsigned char	tca_family;
+	unsigned char	tca__pad1;
+	unsigned short	tca__pad2;
+};
+
+enum {
+	TCA_ROOT_UNSPEC,
+	TCA_ROOT_TAB,
+#define TCA_ACT_TAB TCA_ROOT_TAB
+#define TCAA_MAX TCA_ROOT_TAB
+	TCA_ROOT_FLAGS,
+	TCA_ROOT_COUNT,
+	TCA_ROOT_TIME_DELTA, /* in msecs */
+	__TCA_ROOT_MAX,
+#define	TCA_ROOT_MAX (__TCA_ROOT_MAX - 1)
+};
+
+#define TA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcamsg))))
+#define TA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcamsg))
+/* tcamsg flags stored in attribute TCA_ROOT_FLAGS
+ *
+ * TCA_FLAG_LARGE_DUMP_ON user->kernel to request for larger than TCA_ACT_MAX_PRIO
+ * actions in a dump. All dump responses will contain the number of actions
+ * being dumped stored in for user app's consumption in TCA_ROOT_COUNT
+ *
+ */
+#define TCA_FLAG_LARGE_DUMP_ON		(1 << 0)
+
+/* New extended info filters for IFLA_EXT_MASK */
+#define RTEXT_FILTER_VF		(1 << 0)
+#define RTEXT_FILTER_BRVLAN	(1 << 1)
+#define RTEXT_FILTER_BRVLAN_COMPRESSED	(1 << 2)
+#define	RTEXT_FILTER_SKIP_STATS	(1 << 3)
+
+/* End of information exported to user level */
+
+
+
+#endif /* __LINUX_RTNETLINK_H */
diff --git a/include/uapi/linux/sctp.h b/include/uapi/linux/sctp.h
new file mode 100644
index 0000000..0d4c150
--- /dev/null
+++ b/include/uapi/linux/sctp.h
@@ -0,0 +1,1174 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/* SCTP kernel implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2002 Intel Corp.
+ *
+ * This file is part of the SCTP kernel implementation
+ *
+ * This header represents the structures and constants needed to support
+ * the SCTP Extension to the Sockets API.
+ *
+ * This SCTP implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This SCTP implementation 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 GNU CC; see the file COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ *    lksctp developers <linux-sctp@vger.kernel.org>
+ *
+ * Or submit a bug report through the following website:
+ *    http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ *    La Monte H.P. Yarroll    <piggy@acm.org>
+ *    R. Stewart               <randall@sctp.chicago.il.us>
+ *    K. Morneau               <kmorneau@cisco.com>
+ *    Q. Xie                   <qxie1@email.mot.com>
+ *    Karl Knutson             <karl@athena.chicago.il.us>
+ *    Jon Grimm                <jgrimm@us.ibm.com>
+ *    Daisy Chang              <daisyc@us.ibm.com>
+ *    Ryan Layer               <rmlayer@us.ibm.com>
+ *    Ardelle Fan              <ardelle.fan@intel.com>
+ *    Sridhar Samudrala        <sri@us.ibm.com>
+ *    Inaky Perez-Gonzalez     <inaky.gonzalez@intel.com>
+ *    Vlad Yasevich            <vladislav.yasevich@hp.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#ifndef _SCTP_H
+#define _SCTP_H
+
+#include <linux/types.h>
+#include <linux/socket.h>
+
+typedef __s32 sctp_assoc_t;
+
+#define SCTP_FUTURE_ASSOC	0
+#define SCTP_CURRENT_ASSOC	1
+#define SCTP_ALL_ASSOC		2
+
+/* The following symbols come from the Sockets API Extensions for
+ * SCTP <draft-ietf-tsvwg-sctpsocket-07.txt>.
+ */
+#define SCTP_RTOINFO	0
+#define SCTP_ASSOCINFO  1
+#define SCTP_INITMSG	2
+#define SCTP_NODELAY	3		/* Get/set nodelay option. */
+#define SCTP_AUTOCLOSE	4
+#define SCTP_SET_PEER_PRIMARY_ADDR 5
+#define SCTP_PRIMARY_ADDR	6
+#define SCTP_ADAPTATION_LAYER	7
+#define SCTP_DISABLE_FRAGMENTS	8
+#define SCTP_PEER_ADDR_PARAMS	9
+#define SCTP_DEFAULT_SEND_PARAM	10
+#define SCTP_EVENTS	11
+#define SCTP_I_WANT_MAPPED_V4_ADDR 12	/* Turn on/off mapped v4 addresses  */
+#define SCTP_MAXSEG	13		/* Get/set maximum fragment. */
+#define SCTP_STATUS	14
+#define SCTP_GET_PEER_ADDR_INFO	15
+#define SCTP_DELAYED_ACK_TIME	16
+#define SCTP_DELAYED_ACK SCTP_DELAYED_ACK_TIME
+#define SCTP_DELAYED_SACK SCTP_DELAYED_ACK_TIME
+#define SCTP_CONTEXT	17
+#define SCTP_FRAGMENT_INTERLEAVE	18
+#define SCTP_PARTIAL_DELIVERY_POINT	19 /* Set/Get partial delivery point */
+#define SCTP_MAX_BURST	20		/* Set/Get max burst */
+#define SCTP_AUTH_CHUNK	21	/* Set only: add a chunk type to authenticate */
+#define SCTP_HMAC_IDENT	22
+#define SCTP_AUTH_KEY	23
+#define SCTP_AUTH_ACTIVE_KEY	24
+#define SCTP_AUTH_DELETE_KEY	25
+#define SCTP_PEER_AUTH_CHUNKS	26	/* Read only */
+#define SCTP_LOCAL_AUTH_CHUNKS	27	/* Read only */
+#define SCTP_GET_ASSOC_NUMBER	28	/* Read only */
+#define SCTP_GET_ASSOC_ID_LIST	29	/* Read only */
+#define SCTP_AUTO_ASCONF       30
+#define SCTP_PEER_ADDR_THLDS	31
+#define SCTP_RECVRCVINFO	32
+#define SCTP_RECVNXTINFO	33
+#define SCTP_DEFAULT_SNDINFO	34
+#define SCTP_AUTH_DEACTIVATE_KEY	35
+#define SCTP_REUSE_PORT		36
+
+/* Internal Socket Options. Some of the sctp library functions are
+ * implemented using these socket options.
+ */
+#define SCTP_SOCKOPT_BINDX_ADD	100	/* BINDX requests for adding addrs */
+#define SCTP_SOCKOPT_BINDX_REM	101	/* BINDX requests for removing addrs. */
+#define SCTP_SOCKOPT_PEELOFF	102	/* peel off association. */
+/* Options 104-106 are deprecated and removed. Do not use this space */
+#define SCTP_SOCKOPT_CONNECTX_OLD	107	/* CONNECTX old requests. */
+#define SCTP_GET_PEER_ADDRS	108		/* Get all peer address. */
+#define SCTP_GET_LOCAL_ADDRS	109		/* Get all local address. */
+#define SCTP_SOCKOPT_CONNECTX	110		/* CONNECTX requests. */
+#define SCTP_SOCKOPT_CONNECTX3	111	/* CONNECTX requests (updated) */
+#define SCTP_GET_ASSOC_STATS	112	/* Read only */
+#define SCTP_PR_SUPPORTED	113
+#define SCTP_DEFAULT_PRINFO	114
+#define SCTP_PR_ASSOC_STATUS	115
+#define SCTP_PR_STREAM_STATUS	116
+#define SCTP_RECONFIG_SUPPORTED	117
+#define SCTP_ENABLE_STREAM_RESET	118
+#define SCTP_RESET_STREAMS	119
+#define SCTP_RESET_ASSOC	120
+#define SCTP_ADD_STREAMS	121
+#define SCTP_SOCKOPT_PEELOFF_FLAGS 122
+#define SCTP_STREAM_SCHEDULER	123
+#define SCTP_STREAM_SCHEDULER_VALUE	124
+#define SCTP_INTERLEAVING_SUPPORTED	125
+#define SCTP_SENDMSG_CONNECT	126
+#define SCTP_EVENT	127
+#define SCTP_ASCONF_SUPPORTED	128
+#define SCTP_AUTH_SUPPORTED	129
+#define SCTP_ECN_SUPPORTED	130
+
+/* PR-SCTP policies */
+#define SCTP_PR_SCTP_NONE	0x0000
+#define SCTP_PR_SCTP_TTL	0x0010
+#define SCTP_PR_SCTP_RTX	0x0020
+#define SCTP_PR_SCTP_PRIO	0x0030
+#define SCTP_PR_SCTP_MAX	SCTP_PR_SCTP_PRIO
+#define SCTP_PR_SCTP_MASK	0x0030
+
+#define __SCTP_PR_INDEX(x)	((x >> 4) - 1)
+#define SCTP_PR_INDEX(x)	__SCTP_PR_INDEX(SCTP_PR_SCTP_ ## x)
+
+#define SCTP_PR_POLICY(x)	((x) & SCTP_PR_SCTP_MASK)
+#define SCTP_PR_SET_POLICY(flags, x)	\
+	do {				\
+		flags &= ~SCTP_PR_SCTP_MASK;	\
+		flags |= x;		\
+	} while (0)
+
+#define SCTP_PR_TTL_ENABLED(x)	(SCTP_PR_POLICY(x) == SCTP_PR_SCTP_TTL)
+#define SCTP_PR_RTX_ENABLED(x)	(SCTP_PR_POLICY(x) == SCTP_PR_SCTP_RTX)
+#define SCTP_PR_PRIO_ENABLED(x)	(SCTP_PR_POLICY(x) == SCTP_PR_SCTP_PRIO)
+
+/* For enable stream reset */
+#define SCTP_ENABLE_RESET_STREAM_REQ	0x01
+#define SCTP_ENABLE_RESET_ASSOC_REQ	0x02
+#define SCTP_ENABLE_CHANGE_ASSOC_REQ	0x04
+#define SCTP_ENABLE_STRRESET_MASK	0x07
+
+#define SCTP_STREAM_RESET_INCOMING	0x01
+#define SCTP_STREAM_RESET_OUTGOING	0x02
+
+/* These are bit fields for msghdr->msg_flags.  See section 5.1.  */
+/* On user space Linux, these live in <bits/socket.h> as an enum.  */
+enum sctp_msg_flags {
+	MSG_NOTIFICATION = 0x8000,
+#define MSG_NOTIFICATION MSG_NOTIFICATION
+};
+
+/* 5.3.1 SCTP Initiation Structure (SCTP_INIT)
+ *
+ *   This cmsghdr structure provides information for initializing new
+ *   SCTP associations with sendmsg().  The SCTP_INITMSG socket option
+ *   uses this same data structure.  This structure is not used for
+ *   recvmsg().
+ *
+ *   cmsg_level    cmsg_type      cmsg_data[]
+ *   ------------  ------------   ----------------------
+ *   IPPROTO_SCTP  SCTP_INIT      struct sctp_initmsg
+ */
+struct sctp_initmsg {
+	__u16 sinit_num_ostreams;
+	__u16 sinit_max_instreams;
+	__u16 sinit_max_attempts;
+	__u16 sinit_max_init_timeo;
+};
+
+/* 5.3.2 SCTP Header Information Structure (SCTP_SNDRCV)
+ *
+ *   This cmsghdr structure specifies SCTP options for sendmsg() and
+ *   describes SCTP header information about a received message through
+ *   recvmsg().
+ *
+ *   cmsg_level    cmsg_type      cmsg_data[]
+ *   ------------  ------------   ----------------------
+ *   IPPROTO_SCTP  SCTP_SNDRCV    struct sctp_sndrcvinfo
+ */
+struct sctp_sndrcvinfo {
+	__u16 sinfo_stream;
+	__u16 sinfo_ssn;
+	__u16 sinfo_flags;
+	__u32 sinfo_ppid;
+	__u32 sinfo_context;
+	__u32 sinfo_timetolive;
+	__u32 sinfo_tsn;
+	__u32 sinfo_cumtsn;
+	sctp_assoc_t sinfo_assoc_id;
+};
+
+/* 5.3.4 SCTP Send Information Structure (SCTP_SNDINFO)
+ *
+ *   This cmsghdr structure specifies SCTP options for sendmsg().
+ *
+ *   cmsg_level    cmsg_type      cmsg_data[]
+ *   ------------  ------------   -------------------
+ *   IPPROTO_SCTP  SCTP_SNDINFO   struct sctp_sndinfo
+ */
+struct sctp_sndinfo {
+	__u16 snd_sid;
+	__u16 snd_flags;
+	__u32 snd_ppid;
+	__u32 snd_context;
+	sctp_assoc_t snd_assoc_id;
+};
+
+/* 5.3.5 SCTP Receive Information Structure (SCTP_RCVINFO)
+ *
+ *   This cmsghdr structure describes SCTP receive information
+ *   about a received message through recvmsg().
+ *
+ *   cmsg_level    cmsg_type      cmsg_data[]
+ *   ------------  ------------   -------------------
+ *   IPPROTO_SCTP  SCTP_RCVINFO   struct sctp_rcvinfo
+ */
+struct sctp_rcvinfo {
+	__u16 rcv_sid;
+	__u16 rcv_ssn;
+	__u16 rcv_flags;
+	__u32 rcv_ppid;
+	__u32 rcv_tsn;
+	__u32 rcv_cumtsn;
+	__u32 rcv_context;
+	sctp_assoc_t rcv_assoc_id;
+};
+
+/* 5.3.6 SCTP Next Receive Information Structure (SCTP_NXTINFO)
+ *
+ *   This cmsghdr structure describes SCTP receive information
+ *   of the next message that will be delivered through recvmsg()
+ *   if this information is already available when delivering
+ *   the current message.
+ *
+ *   cmsg_level    cmsg_type      cmsg_data[]
+ *   ------------  ------------   -------------------
+ *   IPPROTO_SCTP  SCTP_NXTINFO   struct sctp_nxtinfo
+ */
+struct sctp_nxtinfo {
+	__u16 nxt_sid;
+	__u16 nxt_flags;
+	__u32 nxt_ppid;
+	__u32 nxt_length;
+	sctp_assoc_t nxt_assoc_id;
+};
+
+/* 5.3.7 SCTP PR-SCTP Information Structure (SCTP_PRINFO)
+ *
+ *   This cmsghdr structure specifies SCTP options for sendmsg().
+ *
+ *   cmsg_level    cmsg_type      cmsg_data[]
+ *   ------------  ------------   -------------------
+ *   IPPROTO_SCTP  SCTP_PRINFO    struct sctp_prinfo
+ */
+struct sctp_prinfo {
+	__u16 pr_policy;
+	__u32 pr_value;
+};
+
+/* 5.3.8 SCTP AUTH Information Structure (SCTP_AUTHINFO)
+ *
+ *   This cmsghdr structure specifies SCTP options for sendmsg().
+ *
+ *   cmsg_level    cmsg_type      cmsg_data[]
+ *   ------------  ------------   -------------------
+ *   IPPROTO_SCTP  SCTP_AUTHINFO  struct sctp_authinfo
+ */
+struct sctp_authinfo {
+	__u16 auth_keynumber;
+};
+
+/*
+ *  sinfo_flags: 16 bits (unsigned integer)
+ *
+ *   This field may contain any of the following flags and is composed of
+ *   a bitwise OR of these values.
+ */
+enum sctp_sinfo_flags {
+	SCTP_UNORDERED		= (1 << 0), /* Send/receive message unordered. */
+	SCTP_ADDR_OVER		= (1 << 1), /* Override the primary destination. */
+	SCTP_ABORT		= (1 << 2), /* Send an ABORT message to the peer. */
+	SCTP_SACK_IMMEDIATELY	= (1 << 3), /* SACK should be sent without delay. */
+	/* 2 bits here have been used by SCTP_PR_SCTP_MASK */
+	SCTP_SENDALL		= (1 << 6),
+	SCTP_PR_SCTP_ALL	= (1 << 7),
+	SCTP_NOTIFICATION	= MSG_NOTIFICATION, /* Next message is not user msg but notification. */
+	SCTP_EOF		= MSG_FIN,  /* Initiate graceful shutdown process. */
+};
+
+typedef union {
+	__u8   			raw;
+	struct sctp_initmsg	init;
+	struct sctp_sndrcvinfo	sndrcv;
+} sctp_cmsg_data_t;
+
+/* These are cmsg_types.  */
+typedef enum sctp_cmsg_type {
+	SCTP_INIT,		/* 5.2.1 SCTP Initiation Structure */
+#define SCTP_INIT	SCTP_INIT
+	SCTP_SNDRCV,		/* 5.2.2 SCTP Header Information Structure */
+#define SCTP_SNDRCV	SCTP_SNDRCV
+	SCTP_SNDINFO,		/* 5.3.4 SCTP Send Information Structure */
+#define SCTP_SNDINFO	SCTP_SNDINFO
+	SCTP_RCVINFO,		/* 5.3.5 SCTP Receive Information Structure */
+#define SCTP_RCVINFO	SCTP_RCVINFO
+	SCTP_NXTINFO,		/* 5.3.6 SCTP Next Receive Information Structure */
+#define SCTP_NXTINFO	SCTP_NXTINFO
+	SCTP_PRINFO,		/* 5.3.7 SCTP PR-SCTP Information Structure */
+#define SCTP_PRINFO	SCTP_PRINFO
+	SCTP_AUTHINFO,		/* 5.3.8 SCTP AUTH Information Structure */
+#define SCTP_AUTHINFO	SCTP_AUTHINFO
+	SCTP_DSTADDRV4,		/* 5.3.9 SCTP Destination IPv4 Address Structure */
+#define SCTP_DSTADDRV4	SCTP_DSTADDRV4
+	SCTP_DSTADDRV6,		/* 5.3.10 SCTP Destination IPv6 Address Structure */
+#define SCTP_DSTADDRV6	SCTP_DSTADDRV6
+} sctp_cmsg_t;
+
+/*
+ * 5.3.1.1 SCTP_ASSOC_CHANGE
+ *
+ *   Communication notifications inform the ULP that an SCTP association
+ *   has either begun or ended. The identifier for a new association is
+ *   provided by this notificaion. The notification information has the
+ *   following format:
+ *
+ */
+struct sctp_assoc_change {
+	__u16 sac_type;
+	__u16 sac_flags;
+	__u32 sac_length;
+	__u16 sac_state;
+	__u16 sac_error;
+	__u16 sac_outbound_streams;
+	__u16 sac_inbound_streams;
+	sctp_assoc_t sac_assoc_id;
+	__u8 sac_info[0];
+};
+
+/*
+ *   sac_state: 32 bits (signed integer)
+ *
+ *   This field holds one of a number of values that communicate the
+ *   event that happened to the association.  They include:
+ *
+ *   Note:  The following state names deviate from the API draft as
+ *   the names clash too easily with other kernel symbols.
+ */
+enum sctp_sac_state {
+	SCTP_COMM_UP,
+	SCTP_COMM_LOST,
+	SCTP_RESTART,
+	SCTP_SHUTDOWN_COMP,
+	SCTP_CANT_STR_ASSOC,
+};
+
+/*
+ * 5.3.1.2 SCTP_PEER_ADDR_CHANGE
+ *
+ *   When a destination address on a multi-homed peer encounters a change
+ *   an interface details event is sent.  The information has the
+ *   following structure:
+ */
+struct sctp_paddr_change {
+	__u16 spc_type;
+	__u16 spc_flags;
+	__u32 spc_length;
+	struct sockaddr_storage spc_aaddr;
+	int spc_state;
+	int spc_error;
+	sctp_assoc_t spc_assoc_id;
+} __attribute__((packed, aligned(4)));
+
+/*
+ *    spc_state:  32 bits (signed integer)
+ *
+ *   This field holds one of a number of values that communicate the
+ *   event that happened to the address.  They include:
+ */
+enum sctp_spc_state {
+	SCTP_ADDR_AVAILABLE,
+	SCTP_ADDR_UNREACHABLE,
+	SCTP_ADDR_REMOVED,
+	SCTP_ADDR_ADDED,
+	SCTP_ADDR_MADE_PRIM,
+	SCTP_ADDR_CONFIRMED,
+};
+
+
+/*
+ * 5.3.1.3 SCTP_REMOTE_ERROR
+ *
+ *   A remote peer may send an Operational Error message to its peer.
+ *   This message indicates a variety of error conditions on an
+ *   association. The entire error TLV as it appears on the wire is
+ *   included in a SCTP_REMOTE_ERROR event.  Please refer to the SCTP
+ *   specification [SCTP] and any extensions for a list of possible
+ *   error formats. SCTP error TLVs have the format:
+ */
+struct sctp_remote_error {
+	__u16 sre_type;
+	__u16 sre_flags;
+	__u32 sre_length;
+	__be16 sre_error;
+	sctp_assoc_t sre_assoc_id;
+	__u8 sre_data[0];
+};
+
+
+/*
+ * 5.3.1.4 SCTP_SEND_FAILED
+ *
+ *   If SCTP cannot deliver a message it may return the message as a
+ *   notification.
+ */
+struct sctp_send_failed {
+	__u16 ssf_type;
+	__u16 ssf_flags;
+	__u32 ssf_length;
+	__u32 ssf_error;
+	struct sctp_sndrcvinfo ssf_info;
+	sctp_assoc_t ssf_assoc_id;
+	__u8 ssf_data[0];
+};
+
+/*
+ *   ssf_flags: 16 bits (unsigned integer)
+ *
+ *   The flag value will take one of the following values
+ *
+ *   SCTP_DATA_UNSENT  - Indicates that the data was never put on
+ *                       the wire.
+ *
+ *   SCTP_DATA_SENT    - Indicates that the data was put on the wire.
+ *                       Note that this does not necessarily mean that the
+ *                       data was (or was not) successfully delivered.
+ */
+enum sctp_ssf_flags {
+	SCTP_DATA_UNSENT,
+	SCTP_DATA_SENT,
+};
+
+/*
+ * 5.3.1.5 SCTP_SHUTDOWN_EVENT
+ *
+ *   When a peer sends a SHUTDOWN, SCTP delivers this notification to
+ *   inform the application that it should cease sending data.
+ */
+struct sctp_shutdown_event {
+	__u16 sse_type;
+	__u16 sse_flags;
+	__u32 sse_length;
+	sctp_assoc_t sse_assoc_id;
+};
+
+/*
+ * 5.3.1.6 SCTP_ADAPTATION_INDICATION
+ *
+ *   When a peer sends a Adaptation Layer Indication parameter , SCTP
+ *   delivers this notification to inform the application
+ *   that of the peers requested adaptation layer.
+ */
+struct sctp_adaptation_event {
+	__u16 sai_type;
+	__u16 sai_flags;
+	__u32 sai_length;
+	__u32 sai_adaptation_ind;
+	sctp_assoc_t sai_assoc_id;
+};
+
+/*
+ * 5.3.1.7 SCTP_PARTIAL_DELIVERY_EVENT
+ *
+ *   When a receiver is engaged in a partial delivery of a
+ *   message this notification will be used to indicate
+ *   various events.
+ */
+struct sctp_pdapi_event {
+	__u16 pdapi_type;
+	__u16 pdapi_flags;
+	__u32 pdapi_length;
+	__u32 pdapi_indication;
+	sctp_assoc_t pdapi_assoc_id;
+	__u32 pdapi_stream;
+	__u32 pdapi_seq;
+};
+
+enum { SCTP_PARTIAL_DELIVERY_ABORTED=0, };
+
+/*
+ * 5.3.1.8.  SCTP_AUTHENTICATION_EVENT
+ *
+ *  When a receiver is using authentication this message will provide
+ *  notifications regarding new keys being made active as well as errors.
+ */
+struct sctp_authkey_event {
+	__u16 auth_type;
+	__u16 auth_flags;
+	__u32 auth_length;
+	__u16 auth_keynumber;
+	__u16 auth_altkeynumber;
+	__u32 auth_indication;
+	sctp_assoc_t auth_assoc_id;
+};
+
+enum {
+	SCTP_AUTH_NEW_KEY,
+#define	SCTP_AUTH_NEWKEY	SCTP_AUTH_NEW_KEY /* compatible with before */
+	SCTP_AUTH_FREE_KEY,
+	SCTP_AUTH_NO_AUTH,
+};
+
+/*
+ * 6.1.9. SCTP_SENDER_DRY_EVENT
+ *
+ * When the SCTP stack has no more user data to send or retransmit, this
+ * notification is given to the user. Also, at the time when a user app
+ * subscribes to this event, if there is no data to be sent or
+ * retransmit, the stack will immediately send up this notification.
+ */
+struct sctp_sender_dry_event {
+	__u16 sender_dry_type;
+	__u16 sender_dry_flags;
+	__u32 sender_dry_length;
+	sctp_assoc_t sender_dry_assoc_id;
+};
+
+#define SCTP_STREAM_RESET_INCOMING_SSN	0x0001
+#define SCTP_STREAM_RESET_OUTGOING_SSN	0x0002
+#define SCTP_STREAM_RESET_DENIED	0x0004
+#define SCTP_STREAM_RESET_FAILED	0x0008
+struct sctp_stream_reset_event {
+	__u16 strreset_type;
+	__u16 strreset_flags;
+	__u32 strreset_length;
+	sctp_assoc_t strreset_assoc_id;
+	__u16 strreset_stream_list[];
+};
+
+#define SCTP_ASSOC_RESET_DENIED		0x0004
+#define SCTP_ASSOC_RESET_FAILED		0x0008
+struct sctp_assoc_reset_event {
+	__u16 assocreset_type;
+	__u16 assocreset_flags;
+	__u32 assocreset_length;
+	sctp_assoc_t assocreset_assoc_id;
+	__u32 assocreset_local_tsn;
+	__u32 assocreset_remote_tsn;
+};
+
+#define SCTP_ASSOC_CHANGE_DENIED	0x0004
+#define SCTP_ASSOC_CHANGE_FAILED	0x0008
+#define SCTP_STREAM_CHANGE_DENIED	SCTP_ASSOC_CHANGE_DENIED
+#define SCTP_STREAM_CHANGE_FAILED	SCTP_ASSOC_CHANGE_FAILED
+struct sctp_stream_change_event {
+	__u16 strchange_type;
+	__u16 strchange_flags;
+	__u32 strchange_length;
+	sctp_assoc_t strchange_assoc_id;
+	__u16 strchange_instrms;
+	__u16 strchange_outstrms;
+};
+
+/*
+ * Described in Section 7.3
+ *   Ancillary Data and Notification Interest Options
+ */
+struct sctp_event_subscribe {
+	__u8 sctp_data_io_event;
+	__u8 sctp_association_event;
+	__u8 sctp_address_event;
+	__u8 sctp_send_failure_event;
+	__u8 sctp_peer_error_event;
+	__u8 sctp_shutdown_event;
+	__u8 sctp_partial_delivery_event;
+	__u8 sctp_adaptation_layer_event;
+	__u8 sctp_authentication_event;
+	__u8 sctp_sender_dry_event;
+	__u8 sctp_stream_reset_event;
+	__u8 sctp_assoc_reset_event;
+	__u8 sctp_stream_change_event;
+};
+
+/*
+ * 5.3.1 SCTP Notification Structure
+ *
+ *   The notification structure is defined as the union of all
+ *   notification types.
+ *
+ */
+union sctp_notification {
+	struct {
+		__u16 sn_type;             /* Notification type. */
+		__u16 sn_flags;
+		__u32 sn_length;
+	} sn_header;
+	struct sctp_assoc_change sn_assoc_change;
+	struct sctp_paddr_change sn_paddr_change;
+	struct sctp_remote_error sn_remote_error;
+	struct sctp_send_failed sn_send_failed;
+	struct sctp_shutdown_event sn_shutdown_event;
+	struct sctp_adaptation_event sn_adaptation_event;
+	struct sctp_pdapi_event sn_pdapi_event;
+	struct sctp_authkey_event sn_authkey_event;
+	struct sctp_sender_dry_event sn_sender_dry_event;
+	struct sctp_stream_reset_event sn_strreset_event;
+	struct sctp_assoc_reset_event sn_assocreset_event;
+	struct sctp_stream_change_event sn_strchange_event;
+};
+
+/* Section 5.3.1
+ * All standard values for sn_type flags are greater than 2^15.
+ * Values from 2^15 and down are reserved.
+ */
+
+enum sctp_sn_type {
+	SCTP_SN_TYPE_BASE	= (1<<15),
+	SCTP_DATA_IO_EVENT	= SCTP_SN_TYPE_BASE,
+#define SCTP_DATA_IO_EVENT		SCTP_DATA_IO_EVENT
+	SCTP_ASSOC_CHANGE,
+#define SCTP_ASSOC_CHANGE		SCTP_ASSOC_CHANGE
+	SCTP_PEER_ADDR_CHANGE,
+#define SCTP_PEER_ADDR_CHANGE		SCTP_PEER_ADDR_CHANGE
+	SCTP_SEND_FAILED,
+#define SCTP_SEND_FAILED		SCTP_SEND_FAILED
+	SCTP_REMOTE_ERROR,
+#define SCTP_REMOTE_ERROR		SCTP_REMOTE_ERROR
+	SCTP_SHUTDOWN_EVENT,
+#define SCTP_SHUTDOWN_EVENT		SCTP_SHUTDOWN_EVENT
+	SCTP_PARTIAL_DELIVERY_EVENT,
+#define SCTP_PARTIAL_DELIVERY_EVENT	SCTP_PARTIAL_DELIVERY_EVENT
+	SCTP_ADAPTATION_INDICATION,
+#define SCTP_ADAPTATION_INDICATION	SCTP_ADAPTATION_INDICATION
+	SCTP_AUTHENTICATION_EVENT,
+#define SCTP_AUTHENTICATION_INDICATION	SCTP_AUTHENTICATION_EVENT
+	SCTP_SENDER_DRY_EVENT,
+#define SCTP_SENDER_DRY_EVENT		SCTP_SENDER_DRY_EVENT
+	SCTP_STREAM_RESET_EVENT,
+#define SCTP_STREAM_RESET_EVENT		SCTP_STREAM_RESET_EVENT
+	SCTP_ASSOC_RESET_EVENT,
+#define SCTP_ASSOC_RESET_EVENT		SCTP_ASSOC_RESET_EVENT
+	SCTP_STREAM_CHANGE_EVENT,
+#define SCTP_STREAM_CHANGE_EVENT	SCTP_STREAM_CHANGE_EVENT
+	SCTP_SN_TYPE_MAX	= SCTP_STREAM_CHANGE_EVENT,
+#define SCTP_SN_TYPE_MAX		SCTP_SN_TYPE_MAX
+};
+
+/* Notification error codes used to fill up the error fields in some
+ * notifications.
+ * SCTP_PEER_ADDRESS_CHAGE 	: spc_error
+ * SCTP_ASSOC_CHANGE		: sac_error
+ * These names should be potentially included in the draft 04 of the SCTP
+ * sockets API specification.
+ */
+typedef enum sctp_sn_error {
+	SCTP_FAILED_THRESHOLD,
+	SCTP_RECEIVED_SACK,
+	SCTP_HEARTBEAT_SUCCESS,
+	SCTP_RESPONSE_TO_USER_REQ,
+	SCTP_INTERNAL_ERROR,
+	SCTP_SHUTDOWN_GUARD_EXPIRES,
+	SCTP_PEER_FAULTY,
+} sctp_sn_error_t;
+
+/*
+ * 7.1.1 Retransmission Timeout Parameters (SCTP_RTOINFO)
+ *
+ *   The protocol parameters used to initialize and bound retransmission
+ *   timeout (RTO) are tunable.  See [SCTP] for more information on how
+ *   these parameters are used in RTO calculation.
+ */
+struct sctp_rtoinfo {
+	sctp_assoc_t	srto_assoc_id;
+	__u32		srto_initial;
+	__u32		srto_max;
+	__u32		srto_min;
+};
+
+/*
+ * 7.1.2 Association Parameters (SCTP_ASSOCINFO)
+ *
+ *   This option is used to both examine and set various association and
+ *   endpoint parameters.
+ */
+struct sctp_assocparams {
+	sctp_assoc_t	sasoc_assoc_id;
+	__u16		sasoc_asocmaxrxt;
+	__u16		sasoc_number_peer_destinations;
+	__u32		sasoc_peer_rwnd;
+	__u32		sasoc_local_rwnd;
+	__u32		sasoc_cookie_life;
+};
+
+/*
+ * 7.1.9 Set Peer Primary Address (SCTP_SET_PEER_PRIMARY_ADDR)
+ *
+ *  Requests that the peer mark the enclosed address as the association
+ *  primary. The enclosed address must be one of the association's
+ *  locally bound addresses. The following structure is used to make a
+ *   set primary request:
+ */
+struct sctp_setpeerprim {
+	sctp_assoc_t            sspp_assoc_id;
+	struct sockaddr_storage sspp_addr;
+} __attribute__((packed, aligned(4)));
+
+/*
+ * 7.1.10 Set Primary Address (SCTP_PRIMARY_ADDR)
+ *
+ *  Requests that the local SCTP stack use the enclosed peer address as
+ *  the association primary. The enclosed address must be one of the
+ *  association peer's addresses. The following structure is used to
+ *  make a set peer primary request:
+ */
+struct sctp_prim {
+	sctp_assoc_t            ssp_assoc_id;
+	struct sockaddr_storage ssp_addr;
+} __attribute__((packed, aligned(4)));
+
+/* For backward compatibility use, define the old name too */
+#define sctp_setprim	sctp_prim
+
+/*
+ * 7.1.11 Set Adaptation Layer Indicator (SCTP_ADAPTATION_LAYER)
+ *
+ * Requests that the local endpoint set the specified Adaptation Layer
+ * Indication parameter for all future INIT and INIT-ACK exchanges.
+ */
+struct sctp_setadaptation {
+	__u32	ssb_adaptation_ind;
+};
+
+/*
+ * 7.1.13 Peer Address Parameters  (SCTP_PEER_ADDR_PARAMS)
+ *
+ *   Applications can enable or disable heartbeats for any peer address
+ *   of an association, modify an address's heartbeat interval, force a
+ *   heartbeat to be sent immediately, and adjust the address's maximum
+ *   number of retransmissions sent before an address is considered
+ *   unreachable. The following structure is used to access and modify an
+ *   address's parameters:
+ */
+enum  sctp_spp_flags {
+	SPP_HB_ENABLE = 1<<0,		/*Enable heartbeats*/
+	SPP_HB_DISABLE = 1<<1,		/*Disable heartbeats*/
+	SPP_HB = SPP_HB_ENABLE | SPP_HB_DISABLE,
+	SPP_HB_DEMAND = 1<<2,		/*Send heartbeat immediately*/
+	SPP_PMTUD_ENABLE = 1<<3,	/*Enable PMTU discovery*/
+	SPP_PMTUD_DISABLE = 1<<4,	/*Disable PMTU discovery*/
+	SPP_PMTUD = SPP_PMTUD_ENABLE | SPP_PMTUD_DISABLE,
+	SPP_SACKDELAY_ENABLE = 1<<5,	/*Enable SACK*/
+	SPP_SACKDELAY_DISABLE = 1<<6,	/*Disable SACK*/
+	SPP_SACKDELAY = SPP_SACKDELAY_ENABLE | SPP_SACKDELAY_DISABLE,
+	SPP_HB_TIME_IS_ZERO = 1<<7,	/* Set HB delay to 0 */
+	SPP_IPV6_FLOWLABEL = 1<<8,
+	SPP_DSCP = 1<<9,
+};
+
+struct sctp_paddrparams {
+	sctp_assoc_t		spp_assoc_id;
+	struct sockaddr_storage	spp_address;
+	__u32			spp_hbinterval;
+	__u16			spp_pathmaxrxt;
+	__u32			spp_pathmtu;
+	__u32			spp_sackdelay;
+	__u32			spp_flags;
+	__u32			spp_ipv6_flowlabel;
+	__u8			spp_dscp;
+} __attribute__((packed, aligned(4)));
+
+/*
+ * 7.1.18.  Add a chunk that must be authenticated (SCTP_AUTH_CHUNK)
+ *
+ * This set option adds a chunk type that the user is requesting to be
+ * received only in an authenticated way.  Changes to the list of chunks
+ * will only effect future associations on the socket.
+ */
+struct sctp_authchunk {
+	__u8		sauth_chunk;
+};
+
+/*
+ * 7.1.19.  Get or set the list of supported HMAC Identifiers (SCTP_HMAC_IDENT)
+ *
+ * This option gets or sets the list of HMAC algorithms that the local
+ * endpoint requires the peer to use.
+ */
+/* This here is only used by user space as is. It might not be a good idea
+ * to export/reveal the whole structure with reserved fields etc.
+ */
+enum {
+	SCTP_AUTH_HMAC_ID_SHA1 = 1,
+	SCTP_AUTH_HMAC_ID_SHA256 = 3,
+};
+
+struct sctp_hmacalgo {
+	__u32		shmac_num_idents;
+	__u16		shmac_idents[];
+};
+
+/* Sadly, user and kernel space have different names for
+ * this structure member, so this is to not break anything.
+ */
+#define shmac_number_of_idents	shmac_num_idents
+
+/*
+ * 7.1.20.  Set a shared key (SCTP_AUTH_KEY)
+ *
+ * This option will set a shared secret key which is used to build an
+ * association shared key.
+ */
+struct sctp_authkey {
+	sctp_assoc_t	sca_assoc_id;
+	__u16		sca_keynumber;
+	__u16		sca_keylength;
+	__u8		sca_key[];
+};
+
+/*
+ * 7.1.21.  Get or set the active shared key (SCTP_AUTH_ACTIVE_KEY)
+ *
+ * This option will get or set the active shared key to be used to build
+ * the association shared key.
+ */
+
+struct sctp_authkeyid {
+	sctp_assoc_t	scact_assoc_id;
+	__u16		scact_keynumber;
+};
+
+
+/*
+ * 7.1.23.  Get or set delayed ack timer (SCTP_DELAYED_SACK)
+ *
+ * This option will effect the way delayed acks are performed.  This
+ * option allows you to get or set the delayed ack time, in
+ * milliseconds.  It also allows changing the delayed ack frequency.
+ * Changing the frequency to 1 disables the delayed sack algorithm.  If
+ * the assoc_id is 0, then this sets or gets the endpoints default
+ * values.  If the assoc_id field is non-zero, then the set or get
+ * effects the specified association for the one to many model (the
+ * assoc_id field is ignored by the one to one model).  Note that if
+ * sack_delay or sack_freq are 0 when setting this option, then the
+ * current values will remain unchanged.
+ */
+struct sctp_sack_info {
+	sctp_assoc_t	sack_assoc_id;
+	uint32_t	sack_delay;
+	uint32_t	sack_freq;
+};
+
+struct sctp_assoc_value {
+    sctp_assoc_t            assoc_id;
+    uint32_t                assoc_value;
+};
+
+struct sctp_stream_value {
+	sctp_assoc_t assoc_id;
+	uint16_t stream_id;
+	uint16_t stream_value;
+};
+
+/*
+ * 7.2.2 Peer Address Information
+ *
+ *   Applications can retrieve information about a specific peer address
+ *   of an association, including its reachability state, congestion
+ *   window, and retransmission timer values.  This information is
+ *   read-only. The following structure is used to access this
+ *   information:
+ */
+struct sctp_paddrinfo {
+	sctp_assoc_t		spinfo_assoc_id;
+	struct sockaddr_storage	spinfo_address;
+	__s32			spinfo_state;
+	__u32			spinfo_cwnd;
+	__u32			spinfo_srtt;
+	__u32			spinfo_rto;
+	__u32			spinfo_mtu;
+} __attribute__((packed, aligned(4)));
+
+/* Peer addresses's state. */
+/* UNKNOWN: Peer address passed by the upper layer in sendmsg or connect[x]
+ * calls.
+ * UNCONFIRMED: Peer address received in INIT/INIT-ACK address parameters.
+ *              Not yet confirmed by a heartbeat and not available for data
+ *		transfers.
+ * ACTIVE : Peer address confirmed, active and available for data transfers.
+ * INACTIVE: Peer address inactive and not available for data transfers.
+ */
+enum sctp_spinfo_state {
+	SCTP_INACTIVE,
+	SCTP_PF,
+	SCTP_ACTIVE,
+	SCTP_UNCONFIRMED,
+	SCTP_UNKNOWN = 0xffff  /* Value used for transport state unknown */
+};
+
+/*
+ * 7.2.1 Association Status (SCTP_STATUS)
+ *
+ *   Applications can retrieve current status information about an
+ *   association, including association state, peer receiver window size,
+ *   number of unacked data chunks, and number of data chunks pending
+ *   receipt.  This information is read-only.  The following structure is
+ *   used to access this information:
+ */
+struct sctp_status {
+	sctp_assoc_t		sstat_assoc_id;
+	__s32			sstat_state;
+	__u32			sstat_rwnd;
+	__u16			sstat_unackdata;
+	__u16			sstat_penddata;
+	__u16			sstat_instrms;
+	__u16			sstat_outstrms;
+	__u32			sstat_fragmentation_point;
+	struct sctp_paddrinfo	sstat_primary;
+};
+
+/*
+ * 7.2.3.  Get the list of chunks the peer requires to be authenticated
+ *         (SCTP_PEER_AUTH_CHUNKS)
+ *
+ * This option gets a list of chunks for a specified association that
+ * the peer requires to be received authenticated only.
+ */
+struct sctp_authchunks {
+	sctp_assoc_t	gauth_assoc_id;
+	__u32		gauth_number_of_chunks;
+	uint8_t		gauth_chunks[];
+};
+
+/* The broken spelling has been released already in lksctp-tools header,
+ * so don't break anyone, now that it's fixed.
+ */
+#define guth_number_of_chunks	gauth_number_of_chunks
+
+/* Association states.  */
+enum sctp_sstat_state {
+	SCTP_EMPTY                = 0,
+	SCTP_CLOSED               = 1,
+	SCTP_COOKIE_WAIT          = 2,
+	SCTP_COOKIE_ECHOED        = 3,
+	SCTP_ESTABLISHED          = 4,
+	SCTP_SHUTDOWN_PENDING     = 5,
+	SCTP_SHUTDOWN_SENT        = 6,
+	SCTP_SHUTDOWN_RECEIVED    = 7,
+	SCTP_SHUTDOWN_ACK_SENT    = 8,
+};
+
+/*
+ * 8.2.6. Get the Current Identifiers of Associations
+ *        (SCTP_GET_ASSOC_ID_LIST)
+ *
+ * This option gets the current list of SCTP association identifiers of
+ * the SCTP associations handled by a one-to-many style socket.
+ */
+struct sctp_assoc_ids {
+	__u32		gaids_number_of_ids;
+	sctp_assoc_t	gaids_assoc_id[];
+};
+
+/*
+ * 8.3, 8.5 get all peer/local addresses in an association.
+ * This parameter struct is used by SCTP_GET_PEER_ADDRS and
+ * SCTP_GET_LOCAL_ADDRS socket options used internally to implement
+ * sctp_getpaddrs() and sctp_getladdrs() API.
+ */
+struct sctp_getaddrs_old {
+	sctp_assoc_t            assoc_id;
+	int			addr_num;
+	struct sockaddr		*addrs;
+};
+
+struct sctp_getaddrs {
+	sctp_assoc_t		assoc_id; /*input*/
+	__u32			addr_num; /*output*/
+	__u8			addrs[0]; /*output, variable size*/
+};
+
+/* A socket user request obtained via SCTP_GET_ASSOC_STATS that retrieves
+ * association stats. All stats are counts except sas_maxrto and
+ * sas_obs_rto_ipaddr. maxrto is the max observed rto + transport since
+ * the last call. Will return 0 when RTO was not update since last call
+ */
+struct sctp_assoc_stats {
+	sctp_assoc_t	sas_assoc_id;    /* Input */
+					 /* Transport of observed max RTO */
+	struct sockaddr_storage sas_obs_rto_ipaddr;
+	__u64		sas_maxrto;      /* Maximum Observed RTO for period */
+	__u64		sas_isacks;	 /* SACKs received */
+	__u64		sas_osacks;	 /* SACKs sent */
+	__u64		sas_opackets;	 /* Packets sent */
+	__u64		sas_ipackets;	 /* Packets received */
+	__u64		sas_rtxchunks;   /* Retransmitted Chunks */
+	__u64		sas_outofseqtsns;/* TSN received > next expected */
+	__u64		sas_idupchunks;  /* Dups received (ordered+unordered) */
+	__u64		sas_gapcnt;      /* Gap Acknowledgements Received */
+	__u64		sas_ouodchunks;  /* Unordered data chunks sent */
+	__u64		sas_iuodchunks;  /* Unordered data chunks received */
+	__u64		sas_oodchunks;	 /* Ordered data chunks sent */
+	__u64		sas_iodchunks;	 /* Ordered data chunks received */
+	__u64		sas_octrlchunks; /* Control chunks sent */
+	__u64		sas_ictrlchunks; /* Control chunks received */
+};
+
+/*
+ * 8.1 sctp_bindx()
+ *
+ * The flags parameter is formed from the bitwise OR of zero or more of the
+ * following currently defined flags:
+ */
+#define SCTP_BINDX_ADD_ADDR 0x01
+#define SCTP_BINDX_REM_ADDR 0x02
+
+/* This is the structure that is passed as an argument(optval) to
+ * getsockopt(SCTP_SOCKOPT_PEELOFF).
+ */
+typedef struct {
+	sctp_assoc_t associd;
+	int sd;
+} sctp_peeloff_arg_t;
+
+typedef struct {
+	sctp_peeloff_arg_t p_arg;
+	unsigned flags;
+} sctp_peeloff_flags_arg_t;
+
+/*
+ *  Peer Address Thresholds socket option
+ */
+struct sctp_paddrthlds {
+	sctp_assoc_t spt_assoc_id;
+	struct sockaddr_storage spt_address;
+	__u16 spt_pathmaxrxt;
+	__u16 spt_pathpfthld;
+};
+
+/*
+ * Socket Option for Getting the Association/Stream-Specific PR-SCTP Status
+ */
+struct sctp_prstatus {
+	sctp_assoc_t sprstat_assoc_id;
+	__u16 sprstat_sid;
+	__u16 sprstat_policy;
+	__u64 sprstat_abandoned_unsent;
+	__u64 sprstat_abandoned_sent;
+};
+
+struct sctp_default_prinfo {
+	sctp_assoc_t pr_assoc_id;
+	__u32 pr_value;
+	__u16 pr_policy;
+};
+
+struct sctp_info {
+	__u32	sctpi_tag;
+	__u32	sctpi_state;
+	__u32	sctpi_rwnd;
+	__u16	sctpi_unackdata;
+	__u16	sctpi_penddata;
+	__u16	sctpi_instrms;
+	__u16	sctpi_outstrms;
+	__u32	sctpi_fragmentation_point;
+	__u32	sctpi_inqueue;
+	__u32	sctpi_outqueue;
+	__u32	sctpi_overall_error;
+	__u32	sctpi_max_burst;
+	__u32	sctpi_maxseg;
+	__u32	sctpi_peer_rwnd;
+	__u32	sctpi_peer_tag;
+	__u8	sctpi_peer_capable;
+	__u8	sctpi_peer_sack;
+	__u16	__reserved1;
+
+	/* assoc status info */
+	__u64	sctpi_isacks;
+	__u64	sctpi_osacks;
+	__u64	sctpi_opackets;
+	__u64	sctpi_ipackets;
+	__u64	sctpi_rtxchunks;
+	__u64	sctpi_outofseqtsns;
+	__u64	sctpi_idupchunks;
+	__u64	sctpi_gapcnt;
+	__u64	sctpi_ouodchunks;
+	__u64	sctpi_iuodchunks;
+	__u64	sctpi_oodchunks;
+	__u64	sctpi_iodchunks;
+	__u64	sctpi_octrlchunks;
+	__u64	sctpi_ictrlchunks;
+
+	/* primary transport info */
+	struct sockaddr_storage	sctpi_p_address;
+	__s32	sctpi_p_state;
+	__u32	sctpi_p_cwnd;
+	__u32	sctpi_p_srtt;
+	__u32	sctpi_p_rto;
+	__u32	sctpi_p_hbinterval;
+	__u32	sctpi_p_pathmaxrxt;
+	__u32	sctpi_p_sackdelay;
+	__u32	sctpi_p_sackfreq;
+	__u32	sctpi_p_ssthresh;
+	__u32	sctpi_p_partial_bytes_acked;
+	__u32	sctpi_p_flight_size;
+	__u16	sctpi_p_error;
+	__u16	__reserved2;
+
+	/* sctp sock info */
+	__u32	sctpi_s_autoclose;
+	__u32	sctpi_s_adaptation_ind;
+	__u32	sctpi_s_pd_point;
+	__u8	sctpi_s_nodelay;
+	__u8	sctpi_s_disable_fragments;
+	__u8	sctpi_s_v4mapped;
+	__u8	sctpi_s_frag_interleave;
+	__u32	sctpi_s_type;
+	__u32	__reserved3;
+};
+
+struct sctp_reset_streams {
+	sctp_assoc_t srs_assoc_id;
+	uint16_t srs_flags;
+	uint16_t srs_number_streams;	/* 0 == ALL */
+	uint16_t srs_stream_list[];	/* list if srs_num_streams is not 0 */
+};
+
+struct sctp_add_streams {
+	sctp_assoc_t sas_assoc_id;
+	uint16_t sas_instrms;
+	uint16_t sas_outstrms;
+};
+
+struct sctp_event {
+	sctp_assoc_t se_assoc_id;
+	uint16_t se_type;
+	uint8_t se_on;
+};
+
+/* SCTP Stream schedulers */
+enum sctp_sched_type {
+	SCTP_SS_FCFS,
+	SCTP_SS_DEFAULT = SCTP_SS_FCFS,
+	SCTP_SS_PRIO,
+	SCTP_SS_RR,
+	SCTP_SS_MAX = SCTP_SS_RR
+};
+
+#endif /* _SCTP_H */
diff --git a/include/uapi/linux/seg6.h b/include/uapi/linux/seg6.h
new file mode 100644
index 0000000..329163e
--- /dev/null
+++ b/include/uapi/linux/seg6.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ *  SR-IPv6 implementation
+ *
+ *  Author:
+ *  David Lebrun <david.lebrun@uclouvain.be>
+ *
+ *
+ *  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.
+ */
+
+#ifndef _LINUX_SEG6_H
+#define _LINUX_SEG6_H
+
+#include <linux/types.h>
+#include <linux/in6.h>		/* For struct in6_addr. */
+
+/*
+ * SRH
+ */
+struct ipv6_sr_hdr {
+	__u8	nexthdr;
+	__u8	hdrlen;
+	__u8	type;
+	__u8	segments_left;
+	__u8	first_segment; /* Represents the last_entry field of SRH */
+	__u8	flags;
+	__u16	tag;
+
+	struct in6_addr segments[0];
+};
+
+#define SR6_FLAG1_PROTECTED	(1 << 6)
+#define SR6_FLAG1_OAM		(1 << 5)
+#define SR6_FLAG1_ALERT		(1 << 4)
+#define SR6_FLAG1_HMAC		(1 << 3)
+
+#define SR6_TLV_INGRESS		1
+#define SR6_TLV_EGRESS		2
+#define SR6_TLV_OPAQUE		3
+#define SR6_TLV_PADDING		4
+#define SR6_TLV_HMAC		5
+
+#define sr_has_hmac(srh) ((srh)->flags & SR6_FLAG1_HMAC)
+
+struct sr6_tlv {
+	__u8 type;
+	__u8 len;
+	__u8 data[0];
+};
+
+#endif
diff --git a/include/uapi/linux/seg6_genl.h b/include/uapi/linux/seg6_genl.h
new file mode 100644
index 0000000..0c23052
--- /dev/null
+++ b/include/uapi/linux/seg6_genl.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_SEG6_GENL_H
+#define _LINUX_SEG6_GENL_H
+
+#define SEG6_GENL_NAME		"SEG6"
+#define SEG6_GENL_VERSION	0x1
+
+enum {
+	SEG6_ATTR_UNSPEC,
+	SEG6_ATTR_DST,
+	SEG6_ATTR_DSTLEN,
+	SEG6_ATTR_HMACKEYID,
+	SEG6_ATTR_SECRET,
+	SEG6_ATTR_SECRETLEN,
+	SEG6_ATTR_ALGID,
+	SEG6_ATTR_HMACINFO,
+	__SEG6_ATTR_MAX,
+};
+
+#define SEG6_ATTR_MAX (__SEG6_ATTR_MAX - 1)
+
+enum {
+	SEG6_CMD_UNSPEC,
+	SEG6_CMD_SETHMAC,
+	SEG6_CMD_DUMPHMAC,
+	SEG6_CMD_SET_TUNSRC,
+	SEG6_CMD_GET_TUNSRC,
+	__SEG6_CMD_MAX,
+};
+
+#define SEG6_CMD_MAX (__SEG6_CMD_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/seg6_hmac.h b/include/uapi/linux/seg6_hmac.h
new file mode 100644
index 0000000..3fb3412
--- /dev/null
+++ b/include/uapi/linux/seg6_hmac.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_SEG6_HMAC_H
+#define _LINUX_SEG6_HMAC_H
+
+#include <linux/types.h>
+#include <linux/seg6.h>
+
+#define SEG6_HMAC_SECRET_LEN	64
+#define SEG6_HMAC_FIELD_LEN	32
+
+struct sr6_tlv_hmac {
+	struct sr6_tlv tlvhdr;
+	__u16 reserved;
+	__be32 hmackeyid;
+	__u8 hmac[SEG6_HMAC_FIELD_LEN];
+};
+
+enum {
+	SEG6_HMAC_ALGO_SHA1 = 1,
+	SEG6_HMAC_ALGO_SHA256 = 2,
+};
+
+#endif
diff --git a/include/uapi/linux/seg6_iptunnel.h b/include/uapi/linux/seg6_iptunnel.h
new file mode 100644
index 0000000..3004e98
--- /dev/null
+++ b/include/uapi/linux/seg6_iptunnel.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ *  SR-IPv6 implementation
+ *
+ *  Author:
+ *  David Lebrun <david.lebrun@uclouvain.be>
+ *
+ *
+ *  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.
+ */
+
+#ifndef _LINUX_SEG6_IPTUNNEL_H
+#define _LINUX_SEG6_IPTUNNEL_H
+
+#include <linux/seg6.h>		/* For struct ipv6_sr_hdr. */
+
+enum {
+	SEG6_IPTUNNEL_UNSPEC,
+	SEG6_IPTUNNEL_SRH,
+	__SEG6_IPTUNNEL_MAX,
+};
+#define SEG6_IPTUNNEL_MAX (__SEG6_IPTUNNEL_MAX - 1)
+
+struct seg6_iptunnel_encap {
+	int mode;
+	struct ipv6_sr_hdr srh[0];
+};
+
+#define SEG6_IPTUN_ENCAP_SIZE(x) ((sizeof(*x)) + (((x)->srh->hdrlen + 1) << 3))
+
+enum {
+	SEG6_IPTUN_MODE_INLINE,
+	SEG6_IPTUN_MODE_ENCAP,
+	SEG6_IPTUN_MODE_L2ENCAP,
+};
+
+
+#endif
diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
new file mode 100644
index 0000000..5312de8
--- /dev/null
+++ b/include/uapi/linux/seg6_local.h
@@ -0,0 +1,80 @@
+/*
+ *  SR-IPv6 implementation
+ *
+ *  Author:
+ *  David Lebrun <david.lebrun@uclouvain.be>
+ *
+ *
+ *  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.
+ */
+
+#ifndef _LINUX_SEG6_LOCAL_H
+#define _LINUX_SEG6_LOCAL_H
+
+#include <linux/seg6.h>
+
+enum {
+	SEG6_LOCAL_UNSPEC,
+	SEG6_LOCAL_ACTION,
+	SEG6_LOCAL_SRH,
+	SEG6_LOCAL_TABLE,
+	SEG6_LOCAL_NH4,
+	SEG6_LOCAL_NH6,
+	SEG6_LOCAL_IIF,
+	SEG6_LOCAL_OIF,
+	SEG6_LOCAL_BPF,
+	__SEG6_LOCAL_MAX,
+};
+#define SEG6_LOCAL_MAX (__SEG6_LOCAL_MAX - 1)
+
+enum {
+	SEG6_LOCAL_ACTION_UNSPEC	= 0,
+	/* node segment */
+	SEG6_LOCAL_ACTION_END		= 1,
+	/* adjacency segment (IPv6 cross-connect) */
+	SEG6_LOCAL_ACTION_END_X		= 2,
+	/* lookup of next seg NH in table */
+	SEG6_LOCAL_ACTION_END_T		= 3,
+	/* decap and L2 cross-connect */
+	SEG6_LOCAL_ACTION_END_DX2	= 4,
+	/* decap and IPv6 cross-connect */
+	SEG6_LOCAL_ACTION_END_DX6	= 5,
+	/* decap and IPv4 cross-connect */
+	SEG6_LOCAL_ACTION_END_DX4	= 6,
+	/* decap and lookup of DA in v6 table */
+	SEG6_LOCAL_ACTION_END_DT6	= 7,
+	/* decap and lookup of DA in v4 table */
+	SEG6_LOCAL_ACTION_END_DT4	= 8,
+	/* binding segment with insertion */
+	SEG6_LOCAL_ACTION_END_B6	= 9,
+	/* binding segment with encapsulation */
+	SEG6_LOCAL_ACTION_END_B6_ENCAP	= 10,
+	/* binding segment with MPLS encap */
+	SEG6_LOCAL_ACTION_END_BM	= 11,
+	/* lookup last seg in table */
+	SEG6_LOCAL_ACTION_END_S		= 12,
+	/* forward to SR-unaware VNF with static proxy */
+	SEG6_LOCAL_ACTION_END_AS	= 13,
+	/* forward to SR-unaware VNF with masquerading */
+	SEG6_LOCAL_ACTION_END_AM	= 14,
+	/* custom BPF action */
+	SEG6_LOCAL_ACTION_END_BPF	= 15,
+
+	__SEG6_LOCAL_ACTION_MAX,
+};
+
+#define SEG6_LOCAL_ACTION_MAX (__SEG6_LOCAL_ACTION_MAX - 1)
+
+enum {
+	SEG6_LOCAL_BPF_PROG_UNSPEC,
+	SEG6_LOCAL_BPF_PROG,
+	SEG6_LOCAL_BPF_PROG_NAME,
+	__SEG6_LOCAL_BPF_PROG_MAX,
+};
+
+#define SEG6_LOCAL_BPF_PROG_MAX (__SEG6_LOCAL_BPF_PROG_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/snmp.h b/include/uapi/linux/snmp.h
new file mode 100644
index 0000000..549a31c
--- /dev/null
+++ b/include/uapi/linux/snmp.h
@@ -0,0 +1,326 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Definitions for MIBs
+ *
+ * Author: Hideaki YOSHIFUJI <yoshfuji@linux-ipv6.org>
+ */
+
+#ifndef _LINUX_SNMP_H
+#define _LINUX_SNMP_H
+
+/* ipstats mib definitions */
+/*
+ * RFC 1213:  MIB-II
+ * RFC 2011 (updates 1213):  SNMPv2-MIB-IP
+ * RFC 2863:  Interfaces Group MIB
+ * RFC 2465:  IPv6 MIB: General Group
+ * draft-ietf-ipv6-rfc2011-update-10.txt: MIB for IP: IP Statistics Tables
+ */
+enum
+{
+	IPSTATS_MIB_NUM = 0,
+/* frequently written fields in fast path, kept in same cache line */
+	IPSTATS_MIB_INPKTS,			/* InReceives */
+	IPSTATS_MIB_INOCTETS,			/* InOctets */
+	IPSTATS_MIB_INDELIVERS,			/* InDelivers */
+	IPSTATS_MIB_OUTFORWDATAGRAMS,		/* OutForwDatagrams */
+	IPSTATS_MIB_OUTPKTS,			/* OutRequests */
+	IPSTATS_MIB_OUTOCTETS,			/* OutOctets */
+/* other fields */
+	IPSTATS_MIB_INHDRERRORS,		/* InHdrErrors */
+	IPSTATS_MIB_INTOOBIGERRORS,		/* InTooBigErrors */
+	IPSTATS_MIB_INNOROUTES,			/* InNoRoutes */
+	IPSTATS_MIB_INADDRERRORS,		/* InAddrErrors */
+	IPSTATS_MIB_INUNKNOWNPROTOS,		/* InUnknownProtos */
+	IPSTATS_MIB_INTRUNCATEDPKTS,		/* InTruncatedPkts */
+	IPSTATS_MIB_INDISCARDS,			/* InDiscards */
+	IPSTATS_MIB_OUTDISCARDS,		/* OutDiscards */
+	IPSTATS_MIB_OUTNOROUTES,		/* OutNoRoutes */
+	IPSTATS_MIB_REASMTIMEOUT,		/* ReasmTimeout */
+	IPSTATS_MIB_REASMREQDS,			/* ReasmReqds */
+	IPSTATS_MIB_REASMOKS,			/* ReasmOKs */
+	IPSTATS_MIB_REASMFAILS,			/* ReasmFails */
+	IPSTATS_MIB_FRAGOKS,			/* FragOKs */
+	IPSTATS_MIB_FRAGFAILS,			/* FragFails */
+	IPSTATS_MIB_FRAGCREATES,		/* FragCreates */
+	IPSTATS_MIB_INMCASTPKTS,		/* InMcastPkts */
+	IPSTATS_MIB_OUTMCASTPKTS,		/* OutMcastPkts */
+	IPSTATS_MIB_INBCASTPKTS,		/* InBcastPkts */
+	IPSTATS_MIB_OUTBCASTPKTS,		/* OutBcastPkts */
+	IPSTATS_MIB_INMCASTOCTETS,		/* InMcastOctets */
+	IPSTATS_MIB_OUTMCASTOCTETS,		/* OutMcastOctets */
+	IPSTATS_MIB_INBCASTOCTETS,		/* InBcastOctets */
+	IPSTATS_MIB_OUTBCASTOCTETS,		/* OutBcastOctets */
+	IPSTATS_MIB_CSUMERRORS,			/* InCsumErrors */
+	IPSTATS_MIB_NOECTPKTS,			/* InNoECTPkts */
+	IPSTATS_MIB_ECT1PKTS,			/* InECT1Pkts */
+	IPSTATS_MIB_ECT0PKTS,			/* InECT0Pkts */
+	IPSTATS_MIB_CEPKTS,			/* InCEPkts */
+	IPSTATS_MIB_REASM_OVERLAPS,		/* ReasmOverlaps */
+	__IPSTATS_MIB_MAX
+};
+
+/* icmp mib definitions */
+/*
+ * RFC 1213:  MIB-II ICMP Group
+ * RFC 2011 (updates 1213):  SNMPv2 MIB for IP: ICMP group
+ */
+enum
+{
+	ICMP_MIB_NUM = 0,
+	ICMP_MIB_INMSGS,			/* InMsgs */
+	ICMP_MIB_INERRORS,			/* InErrors */
+	ICMP_MIB_INDESTUNREACHS,		/* InDestUnreachs */
+	ICMP_MIB_INTIMEEXCDS,			/* InTimeExcds */
+	ICMP_MIB_INPARMPROBS,			/* InParmProbs */
+	ICMP_MIB_INSRCQUENCHS,			/* InSrcQuenchs */
+	ICMP_MIB_INREDIRECTS,			/* InRedirects */
+	ICMP_MIB_INECHOS,			/* InEchos */
+	ICMP_MIB_INECHOREPS,			/* InEchoReps */
+	ICMP_MIB_INTIMESTAMPS,			/* InTimestamps */
+	ICMP_MIB_INTIMESTAMPREPS,		/* InTimestampReps */
+	ICMP_MIB_INADDRMASKS,			/* InAddrMasks */
+	ICMP_MIB_INADDRMASKREPS,		/* InAddrMaskReps */
+	ICMP_MIB_OUTMSGS,			/* OutMsgs */
+	ICMP_MIB_OUTERRORS,			/* OutErrors */
+	ICMP_MIB_OUTDESTUNREACHS,		/* OutDestUnreachs */
+	ICMP_MIB_OUTTIMEEXCDS,			/* OutTimeExcds */
+	ICMP_MIB_OUTPARMPROBS,			/* OutParmProbs */
+	ICMP_MIB_OUTSRCQUENCHS,			/* OutSrcQuenchs */
+	ICMP_MIB_OUTREDIRECTS,			/* OutRedirects */
+	ICMP_MIB_OUTECHOS,			/* OutEchos */
+	ICMP_MIB_OUTECHOREPS,			/* OutEchoReps */
+	ICMP_MIB_OUTTIMESTAMPS,			/* OutTimestamps */
+	ICMP_MIB_OUTTIMESTAMPREPS,		/* OutTimestampReps */
+	ICMP_MIB_OUTADDRMASKS,			/* OutAddrMasks */
+	ICMP_MIB_OUTADDRMASKREPS,		/* OutAddrMaskReps */
+	ICMP_MIB_CSUMERRORS,			/* InCsumErrors */
+	__ICMP_MIB_MAX
+};
+
+#define __ICMPMSG_MIB_MAX 512	/* Out+In for all 8-bit ICMP types */
+
+/* icmp6 mib definitions */
+/*
+ * RFC 2466:  ICMPv6-MIB
+ */
+enum
+{
+	ICMP6_MIB_NUM = 0,
+	ICMP6_MIB_INMSGS,			/* InMsgs */
+	ICMP6_MIB_INERRORS,			/* InErrors */
+	ICMP6_MIB_OUTMSGS,			/* OutMsgs */
+	ICMP6_MIB_OUTERRORS,			/* OutErrors */
+	ICMP6_MIB_CSUMERRORS,			/* InCsumErrors */
+	__ICMP6_MIB_MAX
+};
+
+#define __ICMP6MSG_MIB_MAX 512 /* Out+In for all 8-bit ICMPv6 types */
+
+/* tcp mib definitions */
+/*
+ * RFC 1213:  MIB-II TCP group
+ * RFC 2012 (updates 1213):  SNMPv2-MIB-TCP
+ */
+enum
+{
+	TCP_MIB_NUM = 0,
+	TCP_MIB_RTOALGORITHM,			/* RtoAlgorithm */
+	TCP_MIB_RTOMIN,				/* RtoMin */
+	TCP_MIB_RTOMAX,				/* RtoMax */
+	TCP_MIB_MAXCONN,			/* MaxConn */
+	TCP_MIB_ACTIVEOPENS,			/* ActiveOpens */
+	TCP_MIB_PASSIVEOPENS,			/* PassiveOpens */
+	TCP_MIB_ATTEMPTFAILS,			/* AttemptFails */
+	TCP_MIB_ESTABRESETS,			/* EstabResets */
+	TCP_MIB_CURRESTAB,			/* CurrEstab */
+	TCP_MIB_INSEGS,				/* InSegs */
+	TCP_MIB_OUTSEGS,			/* OutSegs */
+	TCP_MIB_RETRANSSEGS,			/* RetransSegs */
+	TCP_MIB_INERRS,				/* InErrs */
+	TCP_MIB_OUTRSTS,			/* OutRsts */
+	TCP_MIB_CSUMERRORS,			/* InCsumErrors */
+	__TCP_MIB_MAX
+};
+
+/* udp mib definitions */
+/*
+ * RFC 1213:  MIB-II UDP group
+ * RFC 2013 (updates 1213):  SNMPv2-MIB-UDP
+ */
+enum
+{
+	UDP_MIB_NUM = 0,
+	UDP_MIB_INDATAGRAMS,			/* InDatagrams */
+	UDP_MIB_NOPORTS,			/* NoPorts */
+	UDP_MIB_INERRORS,			/* InErrors */
+	UDP_MIB_OUTDATAGRAMS,			/* OutDatagrams */
+	UDP_MIB_RCVBUFERRORS,			/* RcvbufErrors */
+	UDP_MIB_SNDBUFERRORS,			/* SndbufErrors */
+	UDP_MIB_CSUMERRORS,			/* InCsumErrors */
+	UDP_MIB_IGNOREDMULTI,			/* IgnoredMulti */
+	__UDP_MIB_MAX
+};
+
+/* linux mib definitions */
+enum
+{
+	LINUX_MIB_NUM = 0,
+	LINUX_MIB_SYNCOOKIESSENT,		/* SyncookiesSent */
+	LINUX_MIB_SYNCOOKIESRECV,		/* SyncookiesRecv */
+	LINUX_MIB_SYNCOOKIESFAILED,		/* SyncookiesFailed */
+	LINUX_MIB_EMBRYONICRSTS,		/* EmbryonicRsts */
+	LINUX_MIB_PRUNECALLED,			/* PruneCalled */
+	LINUX_MIB_RCVPRUNED,			/* RcvPruned */
+	LINUX_MIB_OFOPRUNED,			/* OfoPruned */
+	LINUX_MIB_OUTOFWINDOWICMPS,		/* OutOfWindowIcmps */
+	LINUX_MIB_LOCKDROPPEDICMPS,		/* LockDroppedIcmps */
+	LINUX_MIB_ARPFILTER,			/* ArpFilter */
+	LINUX_MIB_TIMEWAITED,			/* TimeWaited */
+	LINUX_MIB_TIMEWAITRECYCLED,		/* TimeWaitRecycled */
+	LINUX_MIB_TIMEWAITKILLED,		/* TimeWaitKilled */
+	LINUX_MIB_PAWSACTIVEREJECTED,		/* PAWSActiveRejected */
+	LINUX_MIB_PAWSESTABREJECTED,		/* PAWSEstabRejected */
+	LINUX_MIB_DELAYEDACKS,			/* DelayedACKs */
+	LINUX_MIB_DELAYEDACKLOCKED,		/* DelayedACKLocked */
+	LINUX_MIB_DELAYEDACKLOST,		/* DelayedACKLost */
+	LINUX_MIB_LISTENOVERFLOWS,		/* ListenOverflows */
+	LINUX_MIB_LISTENDROPS,			/* ListenDrops */
+	LINUX_MIB_TCPHPHITS,			/* TCPHPHits */
+	LINUX_MIB_TCPPUREACKS,			/* TCPPureAcks */
+	LINUX_MIB_TCPHPACKS,			/* TCPHPAcks */
+	LINUX_MIB_TCPRENORECOVERY,		/* TCPRenoRecovery */
+	LINUX_MIB_TCPSACKRECOVERY,		/* TCPSackRecovery */
+	LINUX_MIB_TCPSACKRENEGING,		/* TCPSACKReneging */
+	LINUX_MIB_TCPSACKREORDER,		/* TCPSACKReorder */
+	LINUX_MIB_TCPRENOREORDER,		/* TCPRenoReorder */
+	LINUX_MIB_TCPTSREORDER,			/* TCPTSReorder */
+	LINUX_MIB_TCPFULLUNDO,			/* TCPFullUndo */
+	LINUX_MIB_TCPPARTIALUNDO,		/* TCPPartialUndo */
+	LINUX_MIB_TCPDSACKUNDO,			/* TCPDSACKUndo */
+	LINUX_MIB_TCPLOSSUNDO,			/* TCPLossUndo */
+	LINUX_MIB_TCPLOSTRETRANSMIT,		/* TCPLostRetransmit */
+	LINUX_MIB_TCPRENOFAILURES,		/* TCPRenoFailures */
+	LINUX_MIB_TCPSACKFAILURES,		/* TCPSackFailures */
+	LINUX_MIB_TCPLOSSFAILURES,		/* TCPLossFailures */
+	LINUX_MIB_TCPFASTRETRANS,		/* TCPFastRetrans */
+	LINUX_MIB_TCPSLOWSTARTRETRANS,		/* TCPSlowStartRetrans */
+	LINUX_MIB_TCPTIMEOUTS,			/* TCPTimeouts */
+	LINUX_MIB_TCPLOSSPROBES,		/* TCPLossProbes */
+	LINUX_MIB_TCPLOSSPROBERECOVERY,		/* TCPLossProbeRecovery */
+	LINUX_MIB_TCPRENORECOVERYFAIL,		/* TCPRenoRecoveryFail */
+	LINUX_MIB_TCPSACKRECOVERYFAIL,		/* TCPSackRecoveryFail */
+	LINUX_MIB_TCPRCVCOLLAPSED,		/* TCPRcvCollapsed */
+	LINUX_MIB_TCPDSACKOLDSENT,		/* TCPDSACKOldSent */
+	LINUX_MIB_TCPDSACKOFOSENT,		/* TCPDSACKOfoSent */
+	LINUX_MIB_TCPDSACKRECV,			/* TCPDSACKRecv */
+	LINUX_MIB_TCPDSACKOFORECV,		/* TCPDSACKOfoRecv */
+	LINUX_MIB_TCPABORTONDATA,		/* TCPAbortOnData */
+	LINUX_MIB_TCPABORTONCLOSE,		/* TCPAbortOnClose */
+	LINUX_MIB_TCPABORTONMEMORY,		/* TCPAbortOnMemory */
+	LINUX_MIB_TCPABORTONTIMEOUT,		/* TCPAbortOnTimeout */
+	LINUX_MIB_TCPABORTONLINGER,		/* TCPAbortOnLinger */
+	LINUX_MIB_TCPABORTFAILED,		/* TCPAbortFailed */
+	LINUX_MIB_TCPMEMORYPRESSURES,		/* TCPMemoryPressures */
+	LINUX_MIB_TCPMEMORYPRESSURESCHRONO,	/* TCPMemoryPressuresChrono */
+	LINUX_MIB_TCPSACKDISCARD,		/* TCPSACKDiscard */
+	LINUX_MIB_TCPDSACKIGNOREDOLD,		/* TCPSACKIgnoredOld */
+	LINUX_MIB_TCPDSACKIGNOREDNOUNDO,	/* TCPSACKIgnoredNoUndo */
+	LINUX_MIB_TCPSPURIOUSRTOS,		/* TCPSpuriousRTOs */
+	LINUX_MIB_TCPMD5NOTFOUND,		/* TCPMD5NotFound */
+	LINUX_MIB_TCPMD5UNEXPECTED,		/* TCPMD5Unexpected */
+	LINUX_MIB_TCPMD5FAILURE,		/* TCPMD5Failure */
+	LINUX_MIB_SACKSHIFTED,
+	LINUX_MIB_SACKMERGED,
+	LINUX_MIB_SACKSHIFTFALLBACK,
+	LINUX_MIB_TCPBACKLOGDROP,
+	LINUX_MIB_PFMEMALLOCDROP,
+	LINUX_MIB_TCPMINTTLDROP, /* RFC 5082 */
+	LINUX_MIB_TCPDEFERACCEPTDROP,
+	LINUX_MIB_IPRPFILTER, /* IP Reverse Path Filter (rp_filter) */
+	LINUX_MIB_TCPTIMEWAITOVERFLOW,		/* TCPTimeWaitOverflow */
+	LINUX_MIB_TCPREQQFULLDOCOOKIES,		/* TCPReqQFullDoCookies */
+	LINUX_MIB_TCPREQQFULLDROP,		/* TCPReqQFullDrop */
+	LINUX_MIB_TCPRETRANSFAIL,		/* TCPRetransFail */
+	LINUX_MIB_TCPRCVCOALESCE,		/* TCPRcvCoalesce */
+	LINUX_MIB_TCPBACKLOGCOALESCE,		/* TCPBacklogCoalesce */
+	LINUX_MIB_TCPOFOQUEUE,			/* TCPOFOQueue */
+	LINUX_MIB_TCPOFODROP,			/* TCPOFODrop */
+	LINUX_MIB_TCPOFOMERGE,			/* TCPOFOMerge */
+	LINUX_MIB_TCPCHALLENGEACK,		/* TCPChallengeACK */
+	LINUX_MIB_TCPSYNCHALLENGE,		/* TCPSYNChallenge */
+	LINUX_MIB_TCPFASTOPENACTIVE,		/* TCPFastOpenActive */
+	LINUX_MIB_TCPFASTOPENACTIVEFAIL,	/* TCPFastOpenActiveFail */
+	LINUX_MIB_TCPFASTOPENPASSIVE,		/* TCPFastOpenPassive*/
+	LINUX_MIB_TCPFASTOPENPASSIVEFAIL,	/* TCPFastOpenPassiveFail */
+	LINUX_MIB_TCPFASTOPENLISTENOVERFLOW,	/* TCPFastOpenListenOverflow */
+	LINUX_MIB_TCPFASTOPENCOOKIEREQD,	/* TCPFastOpenCookieReqd */
+	LINUX_MIB_TCPFASTOPENBLACKHOLE,		/* TCPFastOpenBlackholeDetect */
+	LINUX_MIB_TCPSPURIOUS_RTX_HOSTQUEUES, /* TCPSpuriousRtxHostQueues */
+	LINUX_MIB_BUSYPOLLRXPACKETS,		/* BusyPollRxPackets */
+	LINUX_MIB_TCPAUTOCORKING,		/* TCPAutoCorking */
+	LINUX_MIB_TCPFROMZEROWINDOWADV,		/* TCPFromZeroWindowAdv */
+	LINUX_MIB_TCPTOZEROWINDOWADV,		/* TCPToZeroWindowAdv */
+	LINUX_MIB_TCPWANTZEROWINDOWADV,		/* TCPWantZeroWindowAdv */
+	LINUX_MIB_TCPSYNRETRANS,		/* TCPSynRetrans */
+	LINUX_MIB_TCPORIGDATASENT,		/* TCPOrigDataSent */
+	LINUX_MIB_TCPHYSTARTTRAINDETECT,	/* TCPHystartTrainDetect */
+	LINUX_MIB_TCPHYSTARTTRAINCWND,		/* TCPHystartTrainCwnd */
+	LINUX_MIB_TCPHYSTARTDELAYDETECT,	/* TCPHystartDelayDetect */
+	LINUX_MIB_TCPHYSTARTDELAYCWND,		/* TCPHystartDelayCwnd */
+	LINUX_MIB_TCPACKSKIPPEDSYNRECV,		/* TCPACKSkippedSynRecv */
+	LINUX_MIB_TCPACKSKIPPEDPAWS,		/* TCPACKSkippedPAWS */
+	LINUX_MIB_TCPACKSKIPPEDSEQ,		/* TCPACKSkippedSeq */
+	LINUX_MIB_TCPACKSKIPPEDFINWAIT2,	/* TCPACKSkippedFinWait2 */
+	LINUX_MIB_TCPACKSKIPPEDTIMEWAIT,	/* TCPACKSkippedTimeWait */
+	LINUX_MIB_TCPACKSKIPPEDCHALLENGE,	/* TCPACKSkippedChallenge */
+	LINUX_MIB_TCPWINPROBE,			/* TCPWinProbe */
+	LINUX_MIB_TCPKEEPALIVE,			/* TCPKeepAlive */
+	LINUX_MIB_TCPMTUPFAIL,			/* TCPMTUPFail */
+	LINUX_MIB_TCPMTUPSUCCESS,		/* TCPMTUPSuccess */
+	LINUX_MIB_TCPDELIVERED,			/* TCPDelivered */
+	LINUX_MIB_TCPDELIVEREDCE,		/* TCPDeliveredCE */
+	LINUX_MIB_TCPACKCOMPRESSED,		/* TCPAckCompressed */
+	LINUX_MIB_TCPZEROWINDOWDROP,		/* TCPZeroWindowDrop */
+	LINUX_MIB_TCPRCVQDROP,			/* TCPRcvQDrop */
+	LINUX_MIB_TCPWQUEUETOOBIG,		/* TCPWqueueTooBig */
+	LINUX_MIB_TCPFASTOPENPASSIVEALTKEY,	/* TCPFastOpenPassiveAltKey */
+	__LINUX_MIB_MAX
+};
+
+/* linux Xfrm mib definitions */
+enum
+{
+	LINUX_MIB_XFRMNUM = 0,
+	LINUX_MIB_XFRMINERROR,			/* XfrmInError */
+	LINUX_MIB_XFRMINBUFFERERROR,		/* XfrmInBufferError */
+	LINUX_MIB_XFRMINHDRERROR,		/* XfrmInHdrError */
+	LINUX_MIB_XFRMINNOSTATES,		/* XfrmInNoStates */
+	LINUX_MIB_XFRMINSTATEPROTOERROR,	/* XfrmInStateProtoError */
+	LINUX_MIB_XFRMINSTATEMODEERROR,		/* XfrmInStateModeError */
+	LINUX_MIB_XFRMINSTATESEQERROR,		/* XfrmInStateSeqError */
+	LINUX_MIB_XFRMINSTATEEXPIRED,		/* XfrmInStateExpired */
+	LINUX_MIB_XFRMINSTATEMISMATCH,		/* XfrmInStateMismatch */
+	LINUX_MIB_XFRMINSTATEINVALID,		/* XfrmInStateInvalid */
+	LINUX_MIB_XFRMINTMPLMISMATCH,		/* XfrmInTmplMismatch */
+	LINUX_MIB_XFRMINNOPOLS,			/* XfrmInNoPols */
+	LINUX_MIB_XFRMINPOLBLOCK,		/* XfrmInPolBlock */
+	LINUX_MIB_XFRMINPOLERROR,		/* XfrmInPolError */
+	LINUX_MIB_XFRMOUTERROR,			/* XfrmOutError */
+	LINUX_MIB_XFRMOUTBUNDLEGENERROR,	/* XfrmOutBundleGenError */
+	LINUX_MIB_XFRMOUTBUNDLECHECKERROR,	/* XfrmOutBundleCheckError */
+	LINUX_MIB_XFRMOUTNOSTATES,		/* XfrmOutNoStates */
+	LINUX_MIB_XFRMOUTSTATEPROTOERROR,	/* XfrmOutStateProtoError */
+	LINUX_MIB_XFRMOUTSTATEMODEERROR,	/* XfrmOutStateModeError */
+	LINUX_MIB_XFRMOUTSTATESEQERROR,		/* XfrmOutStateSeqError */
+	LINUX_MIB_XFRMOUTSTATEEXPIRED,		/* XfrmOutStateExpired */
+	LINUX_MIB_XFRMOUTPOLBLOCK,		/* XfrmOutPolBlock */
+	LINUX_MIB_XFRMOUTPOLDEAD,		/* XfrmOutPolDead */
+	LINUX_MIB_XFRMOUTPOLERROR,		/* XfrmOutPolError */
+	LINUX_MIB_XFRMFWDHDRERROR,		/* XfrmFwdHdrError*/
+	LINUX_MIB_XFRMOUTSTATEINVALID,		/* XfrmOutStateInvalid */
+	LINUX_MIB_XFRMACQUIREERROR,		/* XfrmAcquireError */
+	__LINUX_MIB_XFRMMAX
+};
+
+#endif	/* _LINUX_SNMP_H */
diff --git a/include/uapi/linux/sock_diag.h b/include/uapi/linux/sock_diag.h
new file mode 100644
index 0000000..a69cf20
--- /dev/null
+++ b/include/uapi/linux/sock_diag.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __SOCK_DIAG_H__
+#define __SOCK_DIAG_H__
+
+#include <linux/types.h>
+
+#define SOCK_DIAG_BY_FAMILY 20
+#define SOCK_DESTROY 21
+
+struct sock_diag_req {
+	__u8	sdiag_family;
+	__u8	sdiag_protocol;
+};
+
+enum {
+	SK_MEMINFO_RMEM_ALLOC,
+	SK_MEMINFO_RCVBUF,
+	SK_MEMINFO_WMEM_ALLOC,
+	SK_MEMINFO_SNDBUF,
+	SK_MEMINFO_FWD_ALLOC,
+	SK_MEMINFO_WMEM_QUEUED,
+	SK_MEMINFO_OPTMEM,
+	SK_MEMINFO_BACKLOG,
+	SK_MEMINFO_DROPS,
+
+	SK_MEMINFO_VARS,
+};
+
+enum sknetlink_groups {
+	SKNLGRP_NONE,
+	SKNLGRP_INET_TCP_DESTROY,
+	SKNLGRP_INET_UDP_DESTROY,
+	SKNLGRP_INET6_TCP_DESTROY,
+	SKNLGRP_INET6_UDP_DESTROY,
+	__SKNLGRP_MAX,
+};
+#define SKNLGRP_MAX	(__SKNLGRP_MAX - 1)
+
+#endif /* __SOCK_DIAG_H__ */
diff --git a/include/uapi/linux/socket.h b/include/uapi/linux/socket.h
new file mode 100644
index 0000000..debcf26
--- /dev/null
+++ b/include/uapi/linux/socket.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_SOCKET_H
+#define _LINUX_SOCKET_H
+
+/*
+ * Desired design of maximum size and alignment (see RFC2553)
+ */
+#define _K_SS_MAXSIZE	128	/* Implementation specific max size */
+
+typedef unsigned short __kernel_sa_family_t;
+
+/*
+ * The definition uses anonymous union and struct in order to control the
+ * default alignment.
+ */
+struct __kernel_sockaddr_storage {
+	union {
+		struct {
+			__kernel_sa_family_t	ss_family; /* address family */
+			/* Following field(s) are implementation specific */
+			char __data[_K_SS_MAXSIZE - sizeof(unsigned short)];
+				/* space to achieve desired size, */
+				/* _SS_MAXSIZE value minus size of ss_family */
+		};
+		void *__align; /* implementation specific desired alignment */
+	};
+};
+
+#endif /* _LINUX_SOCKET_H */
diff --git a/include/uapi/linux/sockios.h b/include/uapi/linux/sockios.h
new file mode 100644
index 0000000..7d1bccb
--- /dev/null
+++ b/include/uapi/linux/sockios.h
@@ -0,0 +1,174 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Definitions of the socket-level I/O control calls.
+ *
+ * Version:	@(#)sockios.h	1.0.2	03/09/93
+ *
+ * Authors:	Ross Biro
+ *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ */
+#ifndef _LINUX_SOCKIOS_H
+#define _LINUX_SOCKIOS_H
+
+#include <asm/bitsperlong.h>
+#include <asm/sockios.h>
+
+/* Linux-specific socket ioctls */
+#define SIOCINQ		FIONREAD
+#define SIOCOUTQ	TIOCOUTQ        /* output queue size (not sent + not acked) */
+
+#define SOCK_IOC_TYPE	0x89
+
+/*
+ * the timeval/timespec data structure layout is defined by libc,
+ * so we need to cover both possible versions on 32-bit.
+ */
+/* Get stamp (timeval) */
+#define SIOCGSTAMP_NEW	 _IOR(SOCK_IOC_TYPE, 0x06, long long[2])
+/* Get stamp (timespec) */
+#define SIOCGSTAMPNS_NEW _IOR(SOCK_IOC_TYPE, 0x07, long long[2])
+
+#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
+/* on 64-bit and x32, avoid the ?: operator */
+#define SIOCGSTAMP	SIOCGSTAMP_OLD
+#define SIOCGSTAMPNS	SIOCGSTAMPNS_OLD
+#else
+#define SIOCGSTAMP	((sizeof(struct timeval))  == 8 ? \
+			 SIOCGSTAMP_OLD   : SIOCGSTAMP_NEW)
+#define SIOCGSTAMPNS	((sizeof(struct timespec)) == 8 ? \
+			 SIOCGSTAMPNS_OLD : SIOCGSTAMPNS_NEW)
+#endif
+
+/* Routing table calls. */
+#define SIOCADDRT	0x890B		/* add routing table entry	*/
+#define SIOCDELRT	0x890C		/* delete routing table entry	*/
+#define SIOCRTMSG	0x890D		/* unused			*/
+
+/* Socket configuration controls. */
+#define SIOCGIFNAME	0x8910		/* get iface name		*/
+#define SIOCSIFLINK	0x8911		/* set iface channel		*/
+#define SIOCGIFCONF	0x8912		/* get iface list		*/
+#define SIOCGIFFLAGS	0x8913		/* get flags			*/
+#define SIOCSIFFLAGS	0x8914		/* set flags			*/
+#define SIOCGIFADDR	0x8915		/* get PA address		*/
+#define SIOCSIFADDR	0x8916		/* set PA address		*/
+#define SIOCGIFDSTADDR	0x8917		/* get remote PA address	*/
+#define SIOCSIFDSTADDR	0x8918		/* set remote PA address	*/
+#define SIOCGIFBRDADDR	0x8919		/* get broadcast PA address	*/
+#define SIOCSIFBRDADDR	0x891a		/* set broadcast PA address	*/
+#define SIOCGIFNETMASK	0x891b		/* get network PA mask		*/
+#define SIOCSIFNETMASK	0x891c		/* set network PA mask		*/
+#define SIOCGIFMETRIC	0x891d		/* get metric			*/
+#define SIOCSIFMETRIC	0x891e		/* set metric			*/
+#define SIOCGIFMEM	0x891f		/* get memory address (BSD)	*/
+#define SIOCSIFMEM	0x8920		/* set memory address (BSD)	*/
+#define SIOCGIFMTU	0x8921		/* get MTU size			*/
+#define SIOCSIFMTU	0x8922		/* set MTU size			*/
+#define SIOCSIFNAME	0x8923		/* set interface name */
+#define	SIOCSIFHWADDR	0x8924		/* set hardware address 	*/
+#define SIOCGIFENCAP	0x8925		/* get/set encapsulations       */
+#define SIOCSIFENCAP	0x8926		
+#define SIOCGIFHWADDR	0x8927		/* Get hardware address		*/
+#define SIOCGIFSLAVE	0x8929		/* Driver slaving support	*/
+#define SIOCSIFSLAVE	0x8930
+#define SIOCADDMULTI	0x8931		/* Multicast address lists	*/
+#define SIOCDELMULTI	0x8932
+#define SIOCGIFINDEX	0x8933		/* name -> if_index mapping	*/
+#define SIOGIFINDEX	SIOCGIFINDEX	/* misprint compatibility :-)	*/
+#define SIOCSIFPFLAGS	0x8934		/* set/get extended flags set	*/
+#define SIOCGIFPFLAGS	0x8935
+#define SIOCDIFADDR	0x8936		/* delete PA address		*/
+#define	SIOCSIFHWBROADCAST	0x8937	/* set hardware broadcast addr	*/
+#define SIOCGIFCOUNT	0x8938		/* get number of devices */
+
+#define SIOCGIFBR	0x8940		/* Bridging support		*/
+#define SIOCSIFBR	0x8941		/* Set bridging options 	*/
+
+#define SIOCGIFTXQLEN	0x8942		/* Get the tx queue length	*/
+#define SIOCSIFTXQLEN	0x8943		/* Set the tx queue length 	*/
+
+/* SIOCGIFDIVERT was:	0x8944		Frame diversion support */
+/* SIOCSIFDIVERT was:	0x8945		Set frame diversion options */
+
+#define SIOCETHTOOL	0x8946		/* Ethtool interface		*/
+
+#define SIOCGMIIPHY	0x8947		/* Get address of MII PHY in use. */
+#define SIOCGMIIREG	0x8948		/* Read MII PHY register.	*/
+#define SIOCSMIIREG	0x8949		/* Write MII PHY register.	*/
+
+#define SIOCWANDEV	0x894A		/* get/set netdev parameters	*/
+
+#define SIOCOUTQNSD	0x894B		/* output queue size (not sent only) */
+#define SIOCGSKNS	0x894C		/* get socket network namespace */
+
+/* ARP cache control calls. */
+		    /*  0x8950 - 0x8952  * obsolete calls, don't re-use */
+#define SIOCDARP	0x8953		/* delete ARP table entry	*/
+#define SIOCGARP	0x8954		/* get ARP table entry		*/
+#define SIOCSARP	0x8955		/* set ARP table entry		*/
+
+/* RARP cache control calls. */
+#define SIOCDRARP	0x8960		/* delete RARP table entry	*/
+#define SIOCGRARP	0x8961		/* get RARP table entry		*/
+#define SIOCSRARP	0x8962		/* set RARP table entry		*/
+
+/* Driver configuration calls */
+
+#define SIOCGIFMAP	0x8970		/* Get device parameters	*/
+#define SIOCSIFMAP	0x8971		/* Set device parameters	*/
+
+/* DLCI configuration calls */
+
+#define SIOCADDDLCI	0x8980		/* Create new DLCI device	*/
+#define SIOCDELDLCI	0x8981		/* Delete DLCI device		*/
+
+#define SIOCGIFVLAN	0x8982		/* 802.1Q VLAN support		*/
+#define SIOCSIFVLAN	0x8983		/* Set 802.1Q VLAN options 	*/
+
+/* bonding calls */
+
+#define SIOCBONDENSLAVE	0x8990		/* enslave a device to the bond */
+#define SIOCBONDRELEASE 0x8991		/* release a slave from the bond*/
+#define SIOCBONDSETHWADDR      0x8992	/* set the hw addr of the bond  */
+#define SIOCBONDSLAVEINFOQUERY 0x8993   /* rtn info about slave state   */
+#define SIOCBONDINFOQUERY      0x8994	/* rtn info about bond state    */
+#define SIOCBONDCHANGEACTIVE   0x8995   /* update to a new active slave */
+			
+/* bridge calls */
+#define SIOCBRADDBR     0x89a0		/* create new bridge device     */
+#define SIOCBRDELBR     0x89a1		/* remove bridge device         */
+#define SIOCBRADDIF	0x89a2		/* add interface to bridge      */
+#define SIOCBRDELIF	0x89a3		/* remove interface from bridge */
+
+/* hardware time stamping: parameters in linux/net_tstamp.h */
+#define SIOCSHWTSTAMP	0x89b0		/* set and get config		*/
+#define SIOCGHWTSTAMP	0x89b1		/* get config			*/
+
+/* Device private ioctl calls */
+
+/*
+ *	These 16 ioctls are available to devices via the do_ioctl() device
+ *	vector. Each device should include this file and redefine these names
+ *	as their own. Because these are device dependent it is a good idea
+ *	_NOT_ to issue them to random objects and hope.
+ *
+ *	THESE IOCTLS ARE _DEPRECATED_ AND WILL DISAPPEAR IN 2.5.X -DaveM
+ */
+ 
+#define SIOCDEVPRIVATE	0x89F0	/* to 89FF */
+
+/*
+ *	These 16 ioctl calls are protocol private
+ */
+ 
+#define SIOCPROTOPRIVATE 0x89E0 /* to 89EF */
+#endif	/* _LINUX_SOCKIOS_H */
diff --git a/include/uapi/linux/stddef.h b/include/uapi/linux/stddef.h
new file mode 100644
index 0000000..23e025f
--- /dev/null
+++ b/include/uapi/linux/stddef.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+
+#ifndef __always_inline
+#define __always_inline __inline__
+#endif
diff --git a/include/uapi/linux/sysinfo.h b/include/uapi/linux/sysinfo.h
new file mode 100644
index 0000000..435d5c2
--- /dev/null
+++ b/include/uapi/linux/sysinfo.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_SYSINFO_H
+#define _LINUX_SYSINFO_H
+
+#include <linux/types.h>
+
+#define SI_LOAD_SHIFT	16
+struct sysinfo {
+	__kernel_long_t uptime;		/* Seconds since boot */
+	__kernel_ulong_t loads[3];	/* 1, 5, and 15 minute load averages */
+	__kernel_ulong_t totalram;	/* Total usable main memory size */
+	__kernel_ulong_t freeram;	/* Available memory size */
+	__kernel_ulong_t sharedram;	/* Amount of shared memory */
+	__kernel_ulong_t bufferram;	/* Memory used by buffers */
+	__kernel_ulong_t totalswap;	/* Total swap space size */
+	__kernel_ulong_t freeswap;	/* swap space still available */
+	__u16 procs;		   	/* Number of current processes */
+	__u16 pad;		   	/* Explicit padding for m68k */
+	__kernel_ulong_t totalhigh;	/* Total high memory size */
+	__kernel_ulong_t freehigh;	/* Available high memory size */
+	__u32 mem_unit;			/* Memory unit size in bytes */
+	char _f[20-2*sizeof(__kernel_ulong_t)-sizeof(__u32)];	/* Padding: libc5 uses this.. */
+};
+
+#endif /* _LINUX_SYSINFO_H */
diff --git a/include/uapi/linux/tc_act/tc_bpf.h b/include/uapi/linux/tc_act/tc_bpf.h
new file mode 100644
index 0000000..653c4f9
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_bpf.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2015 Jiri Pirko <jiri@resnulli.us>
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_TC_BPF_H
+#define __LINUX_TC_BPF_H
+
+#include <linux/pkt_cls.h>
+
+struct tc_act_bpf {
+	tc_gen;
+};
+
+enum {
+	TCA_ACT_BPF_UNSPEC,
+	TCA_ACT_BPF_TM,
+	TCA_ACT_BPF_PARMS,
+	TCA_ACT_BPF_OPS_LEN,
+	TCA_ACT_BPF_OPS,
+	TCA_ACT_BPF_FD,
+	TCA_ACT_BPF_NAME,
+	TCA_ACT_BPF_PAD,
+	TCA_ACT_BPF_TAG,
+	TCA_ACT_BPF_ID,
+	__TCA_ACT_BPF_MAX,
+};
+#define TCA_ACT_BPF_MAX (__TCA_ACT_BPF_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_connmark.h b/include/uapi/linux/tc_act/tc_connmark.h
new file mode 100644
index 0000000..9f8f6f7
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_connmark.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __UAPI_TC_CONNMARK_H
+#define __UAPI_TC_CONNMARK_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+struct tc_connmark {
+	tc_gen;
+	__u16 zone;
+};
+
+enum {
+	TCA_CONNMARK_UNSPEC,
+	TCA_CONNMARK_PARMS,
+	TCA_CONNMARK_TM,
+	TCA_CONNMARK_PAD,
+	__TCA_CONNMARK_MAX
+};
+#define TCA_CONNMARK_MAX (__TCA_CONNMARK_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_csum.h b/include/uapi/linux/tc_act/tc_csum.h
new file mode 100644
index 0000000..94b2044
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_csum.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_CSUM_H
+#define __LINUX_TC_CSUM_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+enum {
+	TCA_CSUM_UNSPEC,
+	TCA_CSUM_PARMS,
+	TCA_CSUM_TM,
+	TCA_CSUM_PAD,
+	__TCA_CSUM_MAX
+};
+#define TCA_CSUM_MAX (__TCA_CSUM_MAX - 1)
+
+enum {
+	TCA_CSUM_UPDATE_FLAG_IPV4HDR = 1,
+	TCA_CSUM_UPDATE_FLAG_ICMP    = 2,
+	TCA_CSUM_UPDATE_FLAG_IGMP    = 4,
+	TCA_CSUM_UPDATE_FLAG_TCP     = 8,
+	TCA_CSUM_UPDATE_FLAG_UDP     = 16,
+	TCA_CSUM_UPDATE_FLAG_UDPLITE = 32,
+	TCA_CSUM_UPDATE_FLAG_SCTP    = 64,
+};
+
+struct tc_csum {
+	tc_gen;
+
+	__u32 update_flags;
+};
+
+#endif /* __LINUX_TC_CSUM_H */
diff --git a/include/uapi/linux/tc_act/tc_ct.h b/include/uapi/linux/tc_act/tc_ct.h
new file mode 100644
index 0000000..5fb1d7a
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_ct.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __UAPI_TC_CT_H
+#define __UAPI_TC_CT_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+enum {
+	TCA_CT_UNSPEC,
+	TCA_CT_PARMS,
+	TCA_CT_TM,
+	TCA_CT_ACTION,		/* u16 */
+	TCA_CT_ZONE,		/* u16 */
+	TCA_CT_MARK,		/* u32 */
+	TCA_CT_MARK_MASK,	/* u32 */
+	TCA_CT_LABELS,		/* u128 */
+	TCA_CT_LABELS_MASK,	/* u128 */
+	TCA_CT_NAT_IPV4_MIN,	/* be32 */
+	TCA_CT_NAT_IPV4_MAX,	/* be32 */
+	TCA_CT_NAT_IPV6_MIN,	/* struct in6_addr */
+	TCA_CT_NAT_IPV6_MAX,	/* struct in6_addr */
+	TCA_CT_NAT_PORT_MIN,	/* be16 */
+	TCA_CT_NAT_PORT_MAX,	/* be16 */
+	TCA_CT_PAD,
+	__TCA_CT_MAX
+};
+
+#define TCA_CT_MAX (__TCA_CT_MAX - 1)
+
+#define TCA_CT_ACT_COMMIT	(1 << 0)
+#define TCA_CT_ACT_FORCE	(1 << 1)
+#define TCA_CT_ACT_CLEAR	(1 << 2)
+#define TCA_CT_ACT_NAT		(1 << 3)
+#define TCA_CT_ACT_NAT_SRC	(1 << 4)
+#define TCA_CT_ACT_NAT_DST	(1 << 5)
+
+struct tc_ct {
+	tc_gen;
+};
+
+#endif /* __UAPI_TC_CT_H */
diff --git a/include/uapi/linux/tc_act/tc_ctinfo.h b/include/uapi/linux/tc_act/tc_ctinfo.h
new file mode 100644
index 0000000..f5f26d9
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_ctinfo.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __UAPI_TC_CTINFO_H
+#define __UAPI_TC_CTINFO_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+struct tc_ctinfo {
+	tc_gen;
+};
+
+enum {
+	TCA_CTINFO_UNSPEC,
+	TCA_CTINFO_PAD,
+	TCA_CTINFO_TM,
+	TCA_CTINFO_ACT,
+	TCA_CTINFO_ZONE,
+	TCA_CTINFO_PARMS_DSCP_MASK,
+	TCA_CTINFO_PARMS_DSCP_STATEMASK,
+	TCA_CTINFO_PARMS_CPMARK_MASK,
+	TCA_CTINFO_STATS_DSCP_SET,
+	TCA_CTINFO_STATS_DSCP_ERROR,
+	TCA_CTINFO_STATS_CPMARK_SET,
+	__TCA_CTINFO_MAX
+};
+
+#define TCA_CTINFO_MAX (__TCA_CTINFO_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_defact.h b/include/uapi/linux/tc_act/tc_defact.h
new file mode 100644
index 0000000..e3ecd8b
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_defact.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_DEF_H
+#define __LINUX_TC_DEF_H
+
+#include <linux/pkt_cls.h>
+
+struct tc_defact {
+	tc_gen;
+};
+
+enum {
+	TCA_DEF_UNSPEC,
+	TCA_DEF_TM,
+	TCA_DEF_PARMS,
+	TCA_DEF_DATA,
+	TCA_DEF_PAD,
+	__TCA_DEF_MAX
+};
+#define TCA_DEF_MAX (__TCA_DEF_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_gact.h b/include/uapi/linux/tc_act/tc_gact.h
new file mode 100644
index 0000000..37e5392
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_gact.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_GACT_H
+#define __LINUX_TC_GACT_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+struct tc_gact {
+	tc_gen;
+
+};
+
+struct tc_gact_p {
+#define PGACT_NONE              0
+#define PGACT_NETRAND           1
+#define PGACT_DETERM            2
+#define MAX_RAND                (PGACT_DETERM + 1 )
+	__u16                 ptype;
+	__u16                 pval;
+	int                   paction;
+};
+ 
+enum {
+	TCA_GACT_UNSPEC,
+	TCA_GACT_TM,
+	TCA_GACT_PARMS,
+	TCA_GACT_PROB,
+	TCA_GACT_PAD,
+	__TCA_GACT_MAX
+};
+#define TCA_GACT_MAX (__TCA_GACT_MAX - 1)
+ 
+#endif
diff --git a/include/uapi/linux/tc_act/tc_ife.h b/include/uapi/linux/tc_act/tc_ife.h
new file mode 100644
index 0000000..8c401f1
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_ife.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __UAPI_TC_IFE_H
+#define __UAPI_TC_IFE_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+#include <linux/ife.h>
+
+/* Flag bits for now just encoding/decoding; mutually exclusive */
+#define IFE_ENCODE 1
+#define IFE_DECODE 0
+
+struct tc_ife {
+	tc_gen;
+	__u16 flags;
+};
+
+/*XXX: We need to encode the total number of bytes consumed */
+enum {
+	TCA_IFE_UNSPEC,
+	TCA_IFE_PARMS,
+	TCA_IFE_TM,
+	TCA_IFE_DMAC,
+	TCA_IFE_SMAC,
+	TCA_IFE_TYPE,
+	TCA_IFE_METALST,
+	TCA_IFE_PAD,
+	__TCA_IFE_MAX
+};
+#define TCA_IFE_MAX (__TCA_IFE_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_ipt.h b/include/uapi/linux/tc_act/tc_ipt.h
new file mode 100644
index 0000000..c48d7da
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_ipt.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_IPT_H
+#define __LINUX_TC_IPT_H
+
+#include <linux/pkt_cls.h>
+
+enum {
+	TCA_IPT_UNSPEC,
+	TCA_IPT_TABLE,
+	TCA_IPT_HOOK,
+	TCA_IPT_INDEX,
+	TCA_IPT_CNT,
+	TCA_IPT_TM,
+	TCA_IPT_TARG,
+	TCA_IPT_PAD,
+	__TCA_IPT_MAX
+};
+#define TCA_IPT_MAX (__TCA_IPT_MAX - 1)
+                                                                                
+#endif
diff --git a/include/uapi/linux/tc_act/tc_mirred.h b/include/uapi/linux/tc_act/tc_mirred.h
new file mode 100644
index 0000000..2500a00
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_mirred.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_MIR_H
+#define __LINUX_TC_MIR_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+#define TCA_EGRESS_REDIR 1  /* packet redirect to EGRESS*/
+#define TCA_EGRESS_MIRROR 2 /* mirror packet to EGRESS */
+#define TCA_INGRESS_REDIR 3  /* packet redirect to INGRESS*/
+#define TCA_INGRESS_MIRROR 4 /* mirror packet to INGRESS */
+
+struct tc_mirred {
+	tc_gen;
+	int                     eaction;   /* one of IN/EGRESS_MIRROR/REDIR */
+	__u32                   ifindex;  /* ifindex of egress port */
+};
+
+enum {
+	TCA_MIRRED_UNSPEC,
+	TCA_MIRRED_TM,
+	TCA_MIRRED_PARMS,
+	TCA_MIRRED_PAD,
+	__TCA_MIRRED_MAX
+};
+#define TCA_MIRRED_MAX (__TCA_MIRRED_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_mpls.h b/include/uapi/linux/tc_act/tc_mpls.h
new file mode 100644
index 0000000..9360e95
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_mpls.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* Copyright (C) 2019 Netronome Systems, Inc. */
+
+#ifndef __LINUX_TC_MPLS_H
+#define __LINUX_TC_MPLS_H
+
+#include <linux/pkt_cls.h>
+
+#define TCA_MPLS_ACT_POP	1
+#define TCA_MPLS_ACT_PUSH	2
+#define TCA_MPLS_ACT_MODIFY	3
+#define TCA_MPLS_ACT_DEC_TTL	4
+
+struct tc_mpls {
+	tc_gen;		/* generic TC action fields. */
+	int m_action;	/* action of type TCA_MPLS_ACT_*. */
+};
+
+enum {
+	TCA_MPLS_UNSPEC,
+	TCA_MPLS_TM,	/* struct tcf_t; time values associated with action. */
+	TCA_MPLS_PARMS,	/* struct tc_mpls; action type and general TC fields. */
+	TCA_MPLS_PAD,
+	TCA_MPLS_PROTO,	/* be16; eth_type of pushed or next (for pop) header. */
+	TCA_MPLS_LABEL,	/* u32; MPLS label. Lower 20 bits are used. */
+	TCA_MPLS_TC,	/* u8; MPLS TC field. Lower 3 bits are used. */
+	TCA_MPLS_TTL,	/* u8; MPLS TTL field. Must not be 0. */
+	TCA_MPLS_BOS,	/* u8; MPLS BOS field. Either 1 or 0. */
+	__TCA_MPLS_MAX,
+};
+#define TCA_MPLS_MAX (__TCA_MPLS_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_nat.h b/include/uapi/linux/tc_act/tc_nat.h
new file mode 100644
index 0000000..21399c2
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_nat.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_NAT_H
+#define __LINUX_TC_NAT_H
+
+#include <linux/pkt_cls.h>
+#include <linux/types.h>
+
+enum {
+	TCA_NAT_UNSPEC,
+	TCA_NAT_PARMS,
+	TCA_NAT_TM,
+	TCA_NAT_PAD,
+	__TCA_NAT_MAX
+};
+#define TCA_NAT_MAX (__TCA_NAT_MAX - 1)
+
+#define TCA_NAT_FLAG_EGRESS 1
+
+struct tc_nat {
+	tc_gen;
+	__be32 old_addr;
+	__be32 new_addr;
+	__be32 mask;
+	__u32 flags;
+};
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_pedit.h b/include/uapi/linux/tc_act/tc_pedit.h
new file mode 100644
index 0000000..f3e61b0
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_pedit.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_PED_H
+#define __LINUX_TC_PED_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+enum {
+	TCA_PEDIT_UNSPEC,
+	TCA_PEDIT_TM,
+	TCA_PEDIT_PARMS,
+	TCA_PEDIT_PAD,
+	TCA_PEDIT_PARMS_EX,
+	TCA_PEDIT_KEYS_EX,
+	TCA_PEDIT_KEY_EX,
+	__TCA_PEDIT_MAX
+};
+
+#define TCA_PEDIT_MAX (__TCA_PEDIT_MAX - 1)
+
+enum {
+	TCA_PEDIT_KEY_EX_HTYPE = 1,
+	TCA_PEDIT_KEY_EX_CMD = 2,
+	__TCA_PEDIT_KEY_EX_MAX
+};
+
+#define TCA_PEDIT_KEY_EX_MAX (__TCA_PEDIT_KEY_EX_MAX - 1)
+
+ /* TCA_PEDIT_KEY_EX_HDR_TYPE_NETWROK is a special case for legacy users. It
+  * means no specific header type - offset is relative to the network layer
+  */
+enum pedit_header_type {
+	TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK = 0,
+	TCA_PEDIT_KEY_EX_HDR_TYPE_ETH = 1,
+	TCA_PEDIT_KEY_EX_HDR_TYPE_IP4 = 2,
+	TCA_PEDIT_KEY_EX_HDR_TYPE_IP6 = 3,
+	TCA_PEDIT_KEY_EX_HDR_TYPE_TCP = 4,
+	TCA_PEDIT_KEY_EX_HDR_TYPE_UDP = 5,
+	__PEDIT_HDR_TYPE_MAX,
+};
+
+#define TCA_PEDIT_HDR_TYPE_MAX (__PEDIT_HDR_TYPE_MAX - 1)
+
+enum pedit_cmd {
+	TCA_PEDIT_KEY_EX_CMD_SET = 0,
+	TCA_PEDIT_KEY_EX_CMD_ADD = 1,
+	__PEDIT_CMD_MAX,
+};
+
+#define TCA_PEDIT_CMD_MAX (__PEDIT_CMD_MAX - 1)
+
+struct tc_pedit_key {
+	__u32           mask;  /* AND */
+	__u32           val;   /*XOR */
+	__u32           off;  /*offset */
+	__u32           at;
+	__u32           offmask;
+	__u32           shift;
+};
+
+struct tc_pedit_sel {
+	tc_gen;
+	unsigned char           nkeys;
+	unsigned char           flags;
+	struct tc_pedit_key     keys[0];
+};
+
+#define tc_pedit tc_pedit_sel
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_sample.h b/include/uapi/linux/tc_act/tc_sample.h
new file mode 100644
index 0000000..fee1bcc
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_sample.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_SAMPLE_H
+#define __LINUX_TC_SAMPLE_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+#include <linux/if_ether.h>
+
+struct tc_sample {
+	tc_gen;
+};
+
+enum {
+	TCA_SAMPLE_UNSPEC,
+	TCA_SAMPLE_TM,
+	TCA_SAMPLE_PARMS,
+	TCA_SAMPLE_RATE,
+	TCA_SAMPLE_TRUNC_SIZE,
+	TCA_SAMPLE_PSAMPLE_GROUP,
+	TCA_SAMPLE_PAD,
+	__TCA_SAMPLE_MAX
+};
+#define TCA_SAMPLE_MAX (__TCA_SAMPLE_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_skbedit.h b/include/uapi/linux/tc_act/tc_skbedit.h
new file mode 100644
index 0000000..800e933
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_skbedit.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2008, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * Author: Alexander Duyck <alexander.h.duyck@intel.com>
+ */
+
+#ifndef __LINUX_TC_SKBEDIT_H
+#define __LINUX_TC_SKBEDIT_H
+
+#include <linux/pkt_cls.h>
+
+#define SKBEDIT_F_PRIORITY		0x1
+#define SKBEDIT_F_QUEUE_MAPPING		0x2
+#define SKBEDIT_F_MARK			0x4
+#define SKBEDIT_F_PTYPE			0x8
+#define SKBEDIT_F_MASK			0x10
+#define SKBEDIT_F_INHERITDSFIELD	0x20
+
+struct tc_skbedit {
+	tc_gen;
+};
+
+enum {
+	TCA_SKBEDIT_UNSPEC,
+	TCA_SKBEDIT_TM,
+	TCA_SKBEDIT_PARMS,
+	TCA_SKBEDIT_PRIORITY,
+	TCA_SKBEDIT_QUEUE_MAPPING,
+	TCA_SKBEDIT_MARK,
+	TCA_SKBEDIT_PAD,
+	TCA_SKBEDIT_PTYPE,
+	TCA_SKBEDIT_MASK,
+	TCA_SKBEDIT_FLAGS,
+	__TCA_SKBEDIT_MAX
+};
+#define TCA_SKBEDIT_MAX (__TCA_SKBEDIT_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_skbmod.h b/include/uapi/linux/tc_act/tc_skbmod.h
new file mode 100644
index 0000000..c525b35
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_skbmod.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2016, Jamal Hadi Salim
+ *
+ * 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.
+*/
+
+#ifndef __LINUX_TC_SKBMOD_H
+#define __LINUX_TC_SKBMOD_H
+
+#include <linux/pkt_cls.h>
+
+#define SKBMOD_F_DMAC	0x1
+#define SKBMOD_F_SMAC	0x2
+#define SKBMOD_F_ETYPE	0x4
+#define SKBMOD_F_SWAPMAC 0x8
+
+struct tc_skbmod {
+	tc_gen;
+	__u64 flags;
+};
+
+enum {
+	TCA_SKBMOD_UNSPEC,
+	TCA_SKBMOD_TM,
+	TCA_SKBMOD_PARMS,
+	TCA_SKBMOD_DMAC,
+	TCA_SKBMOD_SMAC,
+	TCA_SKBMOD_ETYPE,
+	TCA_SKBMOD_PAD,
+	__TCA_SKBMOD_MAX
+};
+#define TCA_SKBMOD_MAX (__TCA_SKBMOD_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_tunnel_key.h b/include/uapi/linux/tc_act/tc_tunnel_key.h
new file mode 100644
index 0000000..41c8b46
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_tunnel_key.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2016, Amir Vadai <amir@vadai.me>
+ * Copyright (c) 2016, Mellanox Technologies. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_TC_TUNNEL_KEY_H
+#define __LINUX_TC_TUNNEL_KEY_H
+
+#include <linux/pkt_cls.h>
+
+#define TCA_TUNNEL_KEY_ACT_SET	    1
+#define TCA_TUNNEL_KEY_ACT_RELEASE  2
+
+struct tc_tunnel_key {
+	tc_gen;
+	int t_action;
+};
+
+enum {
+	TCA_TUNNEL_KEY_UNSPEC,
+	TCA_TUNNEL_KEY_TM,
+	TCA_TUNNEL_KEY_PARMS,
+	TCA_TUNNEL_KEY_ENC_IPV4_SRC,	/* be32 */
+	TCA_TUNNEL_KEY_ENC_IPV4_DST,	/* be32 */
+	TCA_TUNNEL_KEY_ENC_IPV6_SRC,	/* struct in6_addr */
+	TCA_TUNNEL_KEY_ENC_IPV6_DST,	/* struct in6_addr */
+	TCA_TUNNEL_KEY_ENC_KEY_ID,	/* be64 */
+	TCA_TUNNEL_KEY_PAD,
+	TCA_TUNNEL_KEY_ENC_DST_PORT,	/* be16 */
+	TCA_TUNNEL_KEY_NO_CSUM,		/* u8 */
+	TCA_TUNNEL_KEY_ENC_OPTS,	/* Nested TCA_TUNNEL_KEY_ENC_OPTS_
+					 * attributes
+					 */
+	TCA_TUNNEL_KEY_ENC_TOS,		/* u8 */
+	TCA_TUNNEL_KEY_ENC_TTL,		/* u8 */
+	__TCA_TUNNEL_KEY_MAX,
+};
+
+#define TCA_TUNNEL_KEY_MAX (__TCA_TUNNEL_KEY_MAX - 1)
+
+enum {
+	TCA_TUNNEL_KEY_ENC_OPTS_UNSPEC,
+	TCA_TUNNEL_KEY_ENC_OPTS_GENEVE,		/* Nested
+						 * TCA_TUNNEL_KEY_ENC_OPTS_
+						 * attributes
+						 */
+	__TCA_TUNNEL_KEY_ENC_OPTS_MAX,
+};
+
+#define TCA_TUNNEL_KEY_ENC_OPTS_MAX (__TCA_TUNNEL_KEY_ENC_OPTS_MAX - 1)
+
+enum {
+	TCA_TUNNEL_KEY_ENC_OPT_GENEVE_UNSPEC,
+	TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS,		/* be16 */
+	TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE,		/* u8 */
+	TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA,		/* 4 to 128 bytes */
+
+	__TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX,
+};
+
+#define TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX \
+	(__TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_act/tc_vlan.h b/include/uapi/linux/tc_act/tc_vlan.h
new file mode 100644
index 0000000..168995b
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_vlan.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us>
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_TC_VLAN_H
+#define __LINUX_TC_VLAN_H
+
+#include <linux/pkt_cls.h>
+
+#define TCA_VLAN_ACT_POP	1
+#define TCA_VLAN_ACT_PUSH	2
+#define TCA_VLAN_ACT_MODIFY	3
+
+struct tc_vlan {
+	tc_gen;
+	int v_action;
+};
+
+enum {
+	TCA_VLAN_UNSPEC,
+	TCA_VLAN_TM,
+	TCA_VLAN_PARMS,
+	TCA_VLAN_PUSH_VLAN_ID,
+	TCA_VLAN_PUSH_VLAN_PROTOCOL,
+	TCA_VLAN_PAD,
+	TCA_VLAN_PUSH_VLAN_PRIORITY,
+	__TCA_VLAN_MAX,
+};
+#define TCA_VLAN_MAX (__TCA_VLAN_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_ematch/tc_em_cmp.h b/include/uapi/linux/tc_ematch/tc_em_cmp.h
new file mode 100644
index 0000000..2549d9d
--- /dev/null
+++ b/include/uapi/linux/tc_ematch/tc_em_cmp.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_EM_CMP_H
+#define __LINUX_TC_EM_CMP_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+struct tcf_em_cmp {
+	__u32		val;
+	__u32		mask;
+	__u16		off;
+	__u8		align:4;
+	__u8		flags:4;
+	__u8		layer:4;
+	__u8		opnd:4;
+};
+
+enum {
+	TCF_EM_ALIGN_U8  = 1,
+	TCF_EM_ALIGN_U16 = 2,
+	TCF_EM_ALIGN_U32 = 4
+};
+
+#define TCF_EM_CMP_TRANS	1
+
+#endif
diff --git a/include/uapi/linux/tc_ematch/tc_em_ipt.h b/include/uapi/linux/tc_ematch/tc_em_ipt.h
new file mode 100644
index 0000000..49a6553
--- /dev/null
+++ b/include/uapi/linux/tc_ematch/tc_em_ipt.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_EM_IPT_H
+#define __LINUX_TC_EM_IPT_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+enum {
+	TCA_EM_IPT_UNSPEC,
+	TCA_EM_IPT_HOOK,
+	TCA_EM_IPT_MATCH_NAME,
+	TCA_EM_IPT_MATCH_REVISION,
+	TCA_EM_IPT_NFPROTO,
+	TCA_EM_IPT_MATCH_DATA,
+	__TCA_EM_IPT_MAX
+};
+
+#define TCA_EM_IPT_MAX (__TCA_EM_IPT_MAX - 1)
+
+#endif
diff --git a/include/uapi/linux/tc_ematch/tc_em_meta.h b/include/uapi/linux/tc_ematch/tc_em_meta.h
new file mode 100644
index 0000000..cf30b5b
--- /dev/null
+++ b/include/uapi/linux/tc_ematch/tc_em_meta.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_EM_META_H
+#define __LINUX_TC_EM_META_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+enum {
+	TCA_EM_META_UNSPEC,
+	TCA_EM_META_HDR,
+	TCA_EM_META_LVALUE,
+	TCA_EM_META_RVALUE,
+	__TCA_EM_META_MAX
+};
+#define TCA_EM_META_MAX (__TCA_EM_META_MAX - 1)
+
+struct tcf_meta_val {
+	__u16			kind;
+	__u8			shift;
+	__u8			op;
+};
+
+#define TCF_META_TYPE_MASK	(0xf << 12)
+#define TCF_META_TYPE(kind)	(((kind) & TCF_META_TYPE_MASK) >> 12)
+#define TCF_META_ID_MASK	0x7ff
+#define TCF_META_ID(kind)	((kind) & TCF_META_ID_MASK)
+
+enum {
+	TCF_META_TYPE_VAR,
+	TCF_META_TYPE_INT,
+	__TCF_META_TYPE_MAX
+};
+#define TCF_META_TYPE_MAX (__TCF_META_TYPE_MAX - 1)
+
+enum {
+	TCF_META_ID_VALUE,
+	TCF_META_ID_RANDOM,
+	TCF_META_ID_LOADAVG_0,
+	TCF_META_ID_LOADAVG_1,
+	TCF_META_ID_LOADAVG_2,
+	TCF_META_ID_DEV,
+	TCF_META_ID_PRIORITY,
+	TCF_META_ID_PROTOCOL,
+	TCF_META_ID_PKTTYPE,
+	TCF_META_ID_PKTLEN,
+	TCF_META_ID_DATALEN,
+	TCF_META_ID_MACLEN,
+	TCF_META_ID_NFMARK,
+	TCF_META_ID_TCINDEX,
+	TCF_META_ID_RTCLASSID,
+	TCF_META_ID_RTIIF,
+	TCF_META_ID_SK_FAMILY,
+	TCF_META_ID_SK_STATE,
+	TCF_META_ID_SK_REUSE,
+	TCF_META_ID_SK_BOUND_IF,
+	TCF_META_ID_SK_REFCNT,
+	TCF_META_ID_SK_SHUTDOWN,
+	TCF_META_ID_SK_PROTO,
+	TCF_META_ID_SK_TYPE,
+	TCF_META_ID_SK_RCVBUF,
+	TCF_META_ID_SK_RMEM_ALLOC,
+	TCF_META_ID_SK_WMEM_ALLOC,
+	TCF_META_ID_SK_OMEM_ALLOC,
+	TCF_META_ID_SK_WMEM_QUEUED,
+	TCF_META_ID_SK_RCV_QLEN,
+	TCF_META_ID_SK_SND_QLEN,
+ 	TCF_META_ID_SK_ERR_QLEN,
+	TCF_META_ID_SK_FORWARD_ALLOCS,
+	TCF_META_ID_SK_SNDBUF,
+ 	TCF_META_ID_SK_ALLOCS,
+	__TCF_META_ID_SK_ROUTE_CAPS,	/* unimplemented but in ABI already */
+ 	TCF_META_ID_SK_HASH,
+ 	TCF_META_ID_SK_LINGERTIME,
+ 	TCF_META_ID_SK_ACK_BACKLOG,
+ 	TCF_META_ID_SK_MAX_ACK_BACKLOG,
+ 	TCF_META_ID_SK_PRIO,
+ 	TCF_META_ID_SK_RCVLOWAT,
+ 	TCF_META_ID_SK_RCVTIMEO,
+ 	TCF_META_ID_SK_SNDTIMEO,
+ 	TCF_META_ID_SK_SENDMSG_OFF,
+ 	TCF_META_ID_SK_WRITE_PENDING,
+	TCF_META_ID_VLAN_TAG,
+	TCF_META_ID_RXHASH,
+	__TCF_META_ID_MAX
+};
+#define TCF_META_ID_MAX (__TCF_META_ID_MAX - 1)
+
+struct tcf_meta_hdr {
+	struct tcf_meta_val	left;
+	struct tcf_meta_val	right;
+};
+
+#endif
diff --git a/include/uapi/linux/tc_ematch/tc_em_nbyte.h b/include/uapi/linux/tc_ematch/tc_em_nbyte.h
new file mode 100644
index 0000000..c76333f
--- /dev/null
+++ b/include/uapi/linux/tc_ematch/tc_em_nbyte.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_TC_EM_NBYTE_H
+#define __LINUX_TC_EM_NBYTE_H
+
+#include <linux/types.h>
+#include <linux/pkt_cls.h>
+
+struct tcf_em_nbyte {
+	__u16		off;
+	__u16		len:12;
+	__u8		layer:4;
+};
+
+#endif
diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
new file mode 100644
index 0000000..d08acf4
--- /dev/null
+++ b/include/uapi/linux/tcp.h
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * INET		An implementation of the TCP/IP protocol suite for the LINUX
+ *		operating system.  INET is implemented using the  BSD Socket
+ *		interface as the means of communication with the user level.
+ *
+ *		Definitions for the TCP protocol.
+ *
+ * Version:	@(#)tcp.h	1.0.2	04/28/93
+ *
+ * Author:	Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ */
+#ifndef _LINUX_TCP_H
+#define _LINUX_TCP_H
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+#include <linux/socket.h>
+
+struct tcphdr {
+	__be16	source;
+	__be16	dest;
+	__be32	seq;
+	__be32	ack_seq;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	__u16	res1:4,
+		doff:4,
+		fin:1,
+		syn:1,
+		rst:1,
+		psh:1,
+		ack:1,
+		urg:1,
+		ece:1,
+		cwr:1;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+	__u16	doff:4,
+		res1:4,
+		cwr:1,
+		ece:1,
+		urg:1,
+		ack:1,
+		psh:1,
+		rst:1,
+		syn:1,
+		fin:1;
+#else
+#error	"Adjust your <asm/byteorder.h> defines"
+#endif	
+	__be16	window;
+	__sum16	check;
+	__be16	urg_ptr;
+};
+
+/*
+ *	The union cast uses a gcc extension to avoid aliasing problems
+ *  (union is compatible to any of its members)
+ *  This means this part of the code is -fstrict-aliasing safe now.
+ */
+union tcp_word_hdr { 
+	struct tcphdr hdr;
+	__be32 		  words[5];
+}; 
+
+#define tcp_flag_word(tp) ( ((union tcp_word_hdr *)(tp))->words [3]) 
+
+enum { 
+	TCP_FLAG_CWR = __constant_cpu_to_be32(0x00800000),
+	TCP_FLAG_ECE = __constant_cpu_to_be32(0x00400000),
+	TCP_FLAG_URG = __constant_cpu_to_be32(0x00200000),
+	TCP_FLAG_ACK = __constant_cpu_to_be32(0x00100000),
+	TCP_FLAG_PSH = __constant_cpu_to_be32(0x00080000),
+	TCP_FLAG_RST = __constant_cpu_to_be32(0x00040000),
+	TCP_FLAG_SYN = __constant_cpu_to_be32(0x00020000),
+	TCP_FLAG_FIN = __constant_cpu_to_be32(0x00010000),
+	TCP_RESERVED_BITS = __constant_cpu_to_be32(0x0F000000),
+	TCP_DATA_OFFSET = __constant_cpu_to_be32(0xF0000000)
+}; 
+
+/*
+ * TCP general constants
+ */
+#define TCP_MSS_DEFAULT		 536U	/* IPv4 (RFC1122, RFC2581) */
+#define TCP_MSS_DESIRED		1220U	/* IPv6 (tunneled), EDNS0 (RFC3226) */
+
+/* TCP socket options */
+#define TCP_NODELAY		1	/* Turn off Nagle's algorithm. */
+#define TCP_MAXSEG		2	/* Limit MSS */
+#define TCP_CORK		3	/* Never send partially complete segments */
+#define TCP_KEEPIDLE		4	/* Start keeplives after this period */
+#define TCP_KEEPINTVL		5	/* Interval between keepalives */
+#define TCP_KEEPCNT		6	/* Number of keepalives before death */
+#define TCP_SYNCNT		7	/* Number of SYN retransmits */
+#define TCP_LINGER2		8	/* Life time of orphaned FIN-WAIT-2 state */
+#define TCP_DEFER_ACCEPT	9	/* Wake up listener only when data arrive */
+#define TCP_WINDOW_CLAMP	10	/* Bound advertised window */
+#define TCP_INFO		11	/* Information about this connection. */
+#define TCP_QUICKACK		12	/* Block/reenable quick acks */
+#define TCP_CONGESTION		13	/* Congestion control algorithm */
+#define TCP_MD5SIG		14	/* TCP MD5 Signature (RFC2385) */
+#define TCP_THIN_LINEAR_TIMEOUTS 16      /* Use linear timeouts for thin streams*/
+#define TCP_THIN_DUPACK         17      /* Fast retrans. after 1 dupack */
+#define TCP_USER_TIMEOUT	18	/* How long for loss retry before timeout */
+#define TCP_REPAIR		19	/* TCP sock is under repair right now */
+#define TCP_REPAIR_QUEUE	20
+#define TCP_QUEUE_SEQ		21
+#define TCP_REPAIR_OPTIONS	22
+#define TCP_FASTOPEN		23	/* Enable FastOpen on listeners */
+#define TCP_TIMESTAMP		24
+#define TCP_NOTSENT_LOWAT	25	/* limit number of unsent bytes in write queue */
+#define TCP_CC_INFO		26	/* Get Congestion Control (optional) info */
+#define TCP_SAVE_SYN		27	/* Record SYN headers for new connections */
+#define TCP_SAVED_SYN		28	/* Get SYN headers recorded for connection */
+#define TCP_REPAIR_WINDOW	29	/* Get/set window parameters */
+#define TCP_FASTOPEN_CONNECT	30	/* Attempt FastOpen with connect */
+#define TCP_ULP			31	/* Attach a ULP to a TCP connection */
+#define TCP_MD5SIG_EXT		32	/* TCP MD5 Signature with extensions */
+#define TCP_FASTOPEN_KEY	33	/* Set the key for Fast Open (cookie) */
+#define TCP_FASTOPEN_NO_COOKIE	34	/* Enable TFO without a TFO cookie */
+#define TCP_ZEROCOPY_RECEIVE	35
+#define TCP_INQ			36	/* Notify bytes available to read as a cmsg on read */
+
+#define TCP_CM_INQ		TCP_INQ
+
+#define TCP_TX_DELAY		37	/* delay outgoing packets by XX usec */
+
+
+#define TCP_REPAIR_ON		1
+#define TCP_REPAIR_OFF		0
+#define TCP_REPAIR_OFF_NO_WP	-1	/* Turn off without window probes */
+
+struct tcp_repair_opt {
+	__u32	opt_code;
+	__u32	opt_val;
+};
+
+struct tcp_repair_window {
+	__u32	snd_wl1;
+	__u32	snd_wnd;
+	__u32	max_window;
+
+	__u32	rcv_wnd;
+	__u32	rcv_wup;
+};
+
+enum {
+	TCP_NO_QUEUE,
+	TCP_RECV_QUEUE,
+	TCP_SEND_QUEUE,
+	TCP_QUEUES_NR,
+};
+
+/* for TCP_INFO socket option */
+#define TCPI_OPT_TIMESTAMPS	1
+#define TCPI_OPT_SACK		2
+#define TCPI_OPT_WSCALE		4
+#define TCPI_OPT_ECN		8 /* ECN was negociated at TCP session init */
+#define TCPI_OPT_ECN_SEEN	16 /* we received at least one packet with ECT */
+#define TCPI_OPT_SYN_DATA	32 /* SYN-ACK acked data in SYN sent or rcvd */
+
+/*
+ * Sender's congestion state indicating normal or abnormal situations
+ * in the last round of packets sent. The state is driven by the ACK
+ * information and timer events.
+ */
+enum tcp_ca_state {
+	/*
+	 * Nothing bad has been observed recently.
+	 * No apparent reordering, packet loss, or ECN marks.
+	 */
+	TCP_CA_Open = 0,
+#define TCPF_CA_Open	(1<<TCP_CA_Open)
+	/*
+	 * The sender enters disordered state when it has received DUPACKs or
+	 * SACKs in the last round of packets sent. This could be due to packet
+	 * loss or reordering but needs further information to confirm packets
+	 * have been lost.
+	 */
+	TCP_CA_Disorder = 1,
+#define TCPF_CA_Disorder (1<<TCP_CA_Disorder)
+	/*
+	 * The sender enters Congestion Window Reduction (CWR) state when it
+	 * has received ACKs with ECN-ECE marks, or has experienced congestion
+	 * or packet discard on the sender host (e.g. qdisc).
+	 */
+	TCP_CA_CWR = 2,
+#define TCPF_CA_CWR	(1<<TCP_CA_CWR)
+	/*
+	 * The sender is in fast recovery and retransmitting lost packets,
+	 * typically triggered by ACK events.
+	 */
+	TCP_CA_Recovery = 3,
+#define TCPF_CA_Recovery (1<<TCP_CA_Recovery)
+	/*
+	 * The sender is in loss recovery triggered by retransmission timeout.
+	 */
+	TCP_CA_Loss = 4
+#define TCPF_CA_Loss	(1<<TCP_CA_Loss)
+};
+
+struct tcp_info {
+	__u8	tcpi_state;
+	__u8	tcpi_ca_state;
+	__u8	tcpi_retransmits;
+	__u8	tcpi_probes;
+	__u8	tcpi_backoff;
+	__u8	tcpi_options;
+	__u8	tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
+	__u8	tcpi_delivery_rate_app_limited:1;
+
+	__u32	tcpi_rto;
+	__u32	tcpi_ato;
+	__u32	tcpi_snd_mss;
+	__u32	tcpi_rcv_mss;
+
+	__u32	tcpi_unacked;
+	__u32	tcpi_sacked;
+	__u32	tcpi_lost;
+	__u32	tcpi_retrans;
+	__u32	tcpi_fackets;
+
+	/* Times. */
+	__u32	tcpi_last_data_sent;
+	__u32	tcpi_last_ack_sent;     /* Not remembered, sorry. */
+	__u32	tcpi_last_data_recv;
+	__u32	tcpi_last_ack_recv;
+
+	/* Metrics. */
+	__u32	tcpi_pmtu;
+	__u32	tcpi_rcv_ssthresh;
+	__u32	tcpi_rtt;
+	__u32	tcpi_rttvar;
+	__u32	tcpi_snd_ssthresh;
+	__u32	tcpi_snd_cwnd;
+	__u32	tcpi_advmss;
+	__u32	tcpi_reordering;
+
+	__u32	tcpi_rcv_rtt;
+	__u32	tcpi_rcv_space;
+
+	__u32	tcpi_total_retrans;
+
+	__u64	tcpi_pacing_rate;
+	__u64	tcpi_max_pacing_rate;
+	__u64	tcpi_bytes_acked;    /* RFC4898 tcpEStatsAppHCThruOctetsAcked */
+	__u64	tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */
+	__u32	tcpi_segs_out;	     /* RFC4898 tcpEStatsPerfSegsOut */
+	__u32	tcpi_segs_in;	     /* RFC4898 tcpEStatsPerfSegsIn */
+
+	__u32	tcpi_notsent_bytes;
+	__u32	tcpi_min_rtt;
+	__u32	tcpi_data_segs_in;	/* RFC4898 tcpEStatsDataSegsIn */
+	__u32	tcpi_data_segs_out;	/* RFC4898 tcpEStatsDataSegsOut */
+
+	__u64   tcpi_delivery_rate;
+
+	__u64	tcpi_busy_time;      /* Time (usec) busy sending data */
+	__u64	tcpi_rwnd_limited;   /* Time (usec) limited by receive window */
+	__u64	tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */
+
+	__u32	tcpi_delivered;
+	__u32	tcpi_delivered_ce;
+
+	__u64	tcpi_bytes_sent;     /* RFC4898 tcpEStatsPerfHCDataOctetsOut */
+	__u64	tcpi_bytes_retrans;  /* RFC4898 tcpEStatsPerfOctetsRetrans */
+	__u32	tcpi_dsack_dups;     /* RFC4898 tcpEStatsStackDSACKDups */
+	__u32	tcpi_reord_seen;     /* reordering events seen */
+
+	__u32	tcpi_rcv_ooopack;    /* Out-of-order packets received */
+
+	__u32	tcpi_snd_wnd;	     /* peer's advertised receive window after
+				      * scaling (bytes)
+				      */
+};
+
+/* netlink attributes types for SCM_TIMESTAMPING_OPT_STATS */
+enum {
+	TCP_NLA_PAD,
+	TCP_NLA_BUSY,		/* Time (usec) busy sending data */
+	TCP_NLA_RWND_LIMITED,	/* Time (usec) limited by receive window */
+	TCP_NLA_SNDBUF_LIMITED,	/* Time (usec) limited by send buffer */
+	TCP_NLA_DATA_SEGS_OUT,	/* Data pkts sent including retransmission */
+	TCP_NLA_TOTAL_RETRANS,	/* Data pkts retransmitted */
+	TCP_NLA_PACING_RATE,    /* Pacing rate in bytes per second */
+	TCP_NLA_DELIVERY_RATE,  /* Delivery rate in bytes per second */
+	TCP_NLA_SND_CWND,       /* Sending congestion window */
+	TCP_NLA_REORDERING,     /* Reordering metric */
+	TCP_NLA_MIN_RTT,        /* minimum RTT */
+	TCP_NLA_RECUR_RETRANS,  /* Recurring retransmits for the current pkt */
+	TCP_NLA_DELIVERY_RATE_APP_LMT, /* delivery rate application limited ? */
+	TCP_NLA_SNDQ_SIZE,	/* Data (bytes) pending in send queue */
+	TCP_NLA_CA_STATE,	/* ca_state of socket */
+	TCP_NLA_SND_SSTHRESH,	/* Slow start size threshold */
+	TCP_NLA_DELIVERED,	/* Data pkts delivered incl. out-of-order */
+	TCP_NLA_DELIVERED_CE,	/* Like above but only ones w/ CE marks */
+	TCP_NLA_BYTES_SENT,	/* Data bytes sent including retransmission */
+	TCP_NLA_BYTES_RETRANS,	/* Data bytes retransmitted */
+	TCP_NLA_DSACK_DUPS,	/* DSACK blocks received */
+	TCP_NLA_REORD_SEEN,	/* reordering events seen */
+	TCP_NLA_SRTT,		/* smoothed RTT in usecs */
+};
+
+/* for TCP_MD5SIG socket option */
+#define TCP_MD5SIG_MAXKEYLEN	80
+
+/* tcp_md5sig extension flags for TCP_MD5SIG_EXT */
+#define TCP_MD5SIG_FLAG_PREFIX		1	/* address prefix length */
+
+struct tcp_md5sig {
+	struct __kernel_sockaddr_storage tcpm_addr;	/* address associated */
+	__u8	tcpm_flags;				/* extension flags */
+	__u8	tcpm_prefixlen;				/* address prefix */
+	__u16	tcpm_keylen;				/* key length */
+	__u32	__tcpm_pad;				/* zero */
+	__u8	tcpm_key[TCP_MD5SIG_MAXKEYLEN];		/* key (binary) */
+};
+
+/* INET_DIAG_MD5SIG */
+struct tcp_diag_md5sig {
+	__u8	tcpm_family;
+	__u8	tcpm_prefixlen;
+	__u16	tcpm_keylen;
+	__be32	tcpm_addr[4];
+	__u8	tcpm_key[TCP_MD5SIG_MAXKEYLEN];
+};
+
+/* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */
+
+struct tcp_zerocopy_receive {
+	__u64 address;		/* in: address of mapping */
+	__u32 length;		/* in/out: number of bytes to map/mapped */
+	__u32 recv_skip_hint;	/* out: amount of bytes to skip */
+};
+#endif /* _LINUX_TCP_H */
diff --git a/include/uapi/linux/tcp_metrics.h b/include/uapi/linux/tcp_metrics.h
new file mode 100644
index 0000000..7cb4a17
--- /dev/null
+++ b/include/uapi/linux/tcp_metrics.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* tcp_metrics.h - TCP Metrics Interface */
+
+#ifndef _LINUX_TCP_METRICS_H
+#define _LINUX_TCP_METRICS_H
+
+#include <linux/types.h>
+
+/* NETLINK_GENERIC related info
+ */
+#define TCP_METRICS_GENL_NAME		"tcp_metrics"
+#define TCP_METRICS_GENL_VERSION	0x1
+
+enum tcp_metric_index {
+	TCP_METRIC_RTT,		/* in ms units */
+	TCP_METRIC_RTTVAR,	/* in ms units */
+	TCP_METRIC_SSTHRESH,
+	TCP_METRIC_CWND,
+	TCP_METRIC_REORDERING,
+
+	TCP_METRIC_RTT_US,	/* in usec units */
+	TCP_METRIC_RTTVAR_US,	/* in usec units */
+
+	/* Always last.  */
+	__TCP_METRIC_MAX,
+};
+
+#define TCP_METRIC_MAX	(__TCP_METRIC_MAX - 1)
+
+enum {
+	TCP_METRICS_ATTR_UNSPEC,
+	TCP_METRICS_ATTR_ADDR_IPV4,		/* u32 */
+	TCP_METRICS_ATTR_ADDR_IPV6,		/* binary */
+	TCP_METRICS_ATTR_AGE,			/* msecs */
+	TCP_METRICS_ATTR_TW_TSVAL,		/* u32, raw, rcv tsval */
+	TCP_METRICS_ATTR_TW_TS_STAMP,		/* s32, sec age */
+	TCP_METRICS_ATTR_VALS,			/* nested +1, u32 */
+	TCP_METRICS_ATTR_FOPEN_MSS,		/* u16 */
+	TCP_METRICS_ATTR_FOPEN_SYN_DROPS,	/* u16, count of drops */
+	TCP_METRICS_ATTR_FOPEN_SYN_DROP_TS,	/* msecs age */
+	TCP_METRICS_ATTR_FOPEN_COOKIE,		/* binary */
+	TCP_METRICS_ATTR_SADDR_IPV4,		/* u32 */
+	TCP_METRICS_ATTR_SADDR_IPV6,		/* binary */
+	TCP_METRICS_ATTR_PAD,
+
+	__TCP_METRICS_ATTR_MAX,
+};
+
+#define TCP_METRICS_ATTR_MAX	(__TCP_METRICS_ATTR_MAX - 1)
+
+enum {
+	TCP_METRICS_CMD_UNSPEC,
+	TCP_METRICS_CMD_GET,
+	TCP_METRICS_CMD_DEL,
+
+	__TCP_METRICS_CMD_MAX,
+};
+
+#define TCP_METRICS_CMD_MAX	(__TCP_METRICS_CMD_MAX - 1)
+
+#endif /* _LINUX_TCP_METRICS_H */
diff --git a/include/uapi/linux/tipc.h b/include/uapi/linux/tipc.h
new file mode 100644
index 0000000..e16cb4e
--- /dev/null
+++ b/include/uapi/linux/tipc.h
@@ -0,0 +1,291 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * include/uapi/linux/tipc.h: Header for TIPC socket interface
+ *
+ * Copyright (c) 2003-2006, 2015-2016 Ericsson AB
+ * Copyright (c) 2005, 2010-2011, Wind River Systems
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the names of the copyright holders nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _LINUX_TIPC_H_
+#define _LINUX_TIPC_H_
+
+#include <linux/types.h>
+#include <linux/sockios.h>
+
+/*
+ * TIPC addressing primitives
+ */
+
+struct tipc_socket_addr {
+	__u32 ref;
+	__u32 node;
+};
+
+struct tipc_service_addr {
+	__u32 type;
+	__u32 instance;
+};
+
+struct tipc_service_range {
+	__u32 type;
+	__u32 lower;
+	__u32 upper;
+};
+
+/*
+ * Application-accessible service types
+ */
+
+#define TIPC_NODE_STATE		0	/* node state service type */
+#define TIPC_TOP_SRV		1	/* topology server service type */
+#define TIPC_LINK_STATE		2	/* link state service type */
+#define TIPC_RESERVED_TYPES	64	/* lowest user-allowed service type */
+
+/*
+ * Publication scopes when binding service / service range
+ */
+enum tipc_scope {
+	TIPC_CLUSTER_SCOPE = 2, /* 0 can also be used */
+	TIPC_NODE_SCOPE    = 3
+};
+
+/*
+ * Limiting values for messages
+ */
+
+#define TIPC_MAX_USER_MSG_SIZE	66000U
+
+/*
+ * Message importance levels
+ */
+
+#define TIPC_LOW_IMPORTANCE		0
+#define TIPC_MEDIUM_IMPORTANCE		1
+#define TIPC_HIGH_IMPORTANCE		2
+#define TIPC_CRITICAL_IMPORTANCE	3
+
+/*
+ * Msg rejection/connection shutdown reasons
+ */
+
+#define TIPC_OK			0
+#define TIPC_ERR_NO_NAME	1
+#define TIPC_ERR_NO_PORT	2
+#define TIPC_ERR_NO_NODE	3
+#define TIPC_ERR_OVERLOAD	4
+#define TIPC_CONN_SHUTDOWN	5
+
+/*
+ * TIPC topology subscription service definitions
+ */
+
+#define TIPC_SUB_PORTS          0x01    /* filter: evt at each match */
+#define TIPC_SUB_SERVICE        0x02    /* filter: evt at first up/last down */
+#define TIPC_SUB_CANCEL         0x04    /* filter: cancel a subscription */
+
+#define TIPC_WAIT_FOREVER	(~0)	/* timeout for permanent subscription */
+
+struct tipc_subscr {
+	struct tipc_service_range seq;	/* range of interest */
+	__u32 timeout;			/* subscription duration (in ms) */
+	__u32 filter;			/* bitmask of filter options */
+	char usr_handle[8];		/* available for subscriber use */
+};
+
+#define TIPC_PUBLISHED		1	/* publication event */
+#define TIPC_WITHDRAWN		2	/* withdrawal event */
+#define TIPC_SUBSCR_TIMEOUT	3	/* subscription timeout event */
+
+struct tipc_event {
+	__u32 event;			/* event type */
+	__u32 found_lower;		/* matching range */
+	__u32 found_upper;		/*    "      "    */
+	struct tipc_socket_addr port;	/* associated socket */
+	struct tipc_subscr s;		/* associated subscription */
+};
+
+/*
+ * Socket API
+ */
+
+#ifndef AF_TIPC
+#define AF_TIPC		30
+#endif
+
+#ifndef PF_TIPC
+#define PF_TIPC		AF_TIPC
+#endif
+
+#ifndef SOL_TIPC
+#define SOL_TIPC	271
+#endif
+
+#define TIPC_ADDR_MCAST         1
+#define TIPC_SERVICE_RANGE      1
+#define TIPC_SERVICE_ADDR       2
+#define TIPC_SOCKET_ADDR        3
+
+struct sockaddr_tipc {
+	unsigned short family;
+	unsigned char  addrtype;
+	signed   char  scope;
+	union {
+		struct tipc_socket_addr id;
+		struct tipc_service_range nameseq;
+		struct {
+			struct tipc_service_addr name;
+			__u32 domain;
+		} name;
+	} addr;
+};
+
+/*
+ * Ancillary data objects supported by recvmsg()
+ */
+
+#define TIPC_ERRINFO	1	/* error info */
+#define TIPC_RETDATA	2	/* returned data */
+#define TIPC_DESTNAME	3	/* destination name */
+
+/*
+ * TIPC-specific socket option names
+ */
+
+#define TIPC_IMPORTANCE		127	/* Default: TIPC_LOW_IMPORTANCE */
+#define TIPC_SRC_DROPPABLE	128	/* Default: based on socket type */
+#define TIPC_DEST_DROPPABLE	129	/* Default: based on socket type */
+#define TIPC_CONN_TIMEOUT	130	/* Default: 8000 (ms)  */
+#define TIPC_NODE_RECVQ_DEPTH	131	/* Default: none (read only) */
+#define TIPC_SOCK_RECVQ_DEPTH	132	/* Default: none (read only) */
+#define TIPC_MCAST_BROADCAST    133     /* Default: TIPC selects. No arg */
+#define TIPC_MCAST_REPLICAST    134     /* Default: TIPC selects. No arg */
+#define TIPC_GROUP_JOIN         135     /* Takes struct tipc_group_req* */
+#define TIPC_GROUP_LEAVE        136     /* No argument */
+#define TIPC_SOCK_RECVQ_USED    137     /* Default: none (read only) */
+
+/*
+ * Flag values
+ */
+#define TIPC_GROUP_LOOPBACK     0x1  /* Receive copy of sent msg when match */
+#define TIPC_GROUP_MEMBER_EVTS  0x2  /* Receive membership events in socket */
+
+struct tipc_group_req {
+	__u32 type;      /* group id */
+	__u32 instance;  /* member id */
+	__u32 scope;     /* cluster/node */
+	__u32 flags;
+};
+
+/*
+ * Maximum sizes of TIPC bearer-related names (including terminating NULL)
+ * The string formatting for each name element is:
+ * media: media
+ * interface: media:interface name
+ * link: node:interface-node:interface
+ */
+#define TIPC_NODEID_LEN         16
+#define TIPC_MAX_MEDIA_NAME	16
+#define TIPC_MAX_IF_NAME	16
+#define TIPC_MAX_BEARER_NAME	32
+#define TIPC_MAX_LINK_NAME	68
+
+#define SIOCGETLINKNAME        SIOCPROTOPRIVATE
+#define SIOCGETNODEID          (SIOCPROTOPRIVATE + 1)
+
+struct tipc_sioc_ln_req {
+	__u32 peer;
+	__u32 bearer_id;
+	char linkname[TIPC_MAX_LINK_NAME];
+};
+
+struct tipc_sioc_nodeid_req {
+	__u32 peer;
+	char node_id[TIPC_NODEID_LEN];
+};
+
+/* The macros and functions below are deprecated:
+ */
+
+#define TIPC_CFG_SRV		0
+#define TIPC_ZONE_SCOPE         1
+
+#define TIPC_ADDR_NAMESEQ	1
+#define TIPC_ADDR_NAME		2
+#define TIPC_ADDR_ID		3
+
+#define TIPC_NODE_BITS          12
+#define TIPC_CLUSTER_BITS       12
+#define TIPC_ZONE_BITS          8
+
+#define TIPC_NODE_OFFSET        0
+#define TIPC_CLUSTER_OFFSET     TIPC_NODE_BITS
+#define TIPC_ZONE_OFFSET        (TIPC_CLUSTER_OFFSET + TIPC_CLUSTER_BITS)
+
+#define TIPC_NODE_SIZE          ((1UL << TIPC_NODE_BITS) - 1)
+#define TIPC_CLUSTER_SIZE       ((1UL << TIPC_CLUSTER_BITS) - 1)
+#define TIPC_ZONE_SIZE          ((1UL << TIPC_ZONE_BITS) - 1)
+
+#define TIPC_NODE_MASK		(TIPC_NODE_SIZE << TIPC_NODE_OFFSET)
+#define TIPC_CLUSTER_MASK	(TIPC_CLUSTER_SIZE << TIPC_CLUSTER_OFFSET)
+#define TIPC_ZONE_MASK		(TIPC_ZONE_SIZE << TIPC_ZONE_OFFSET)
+
+#define TIPC_ZONE_CLUSTER_MASK (TIPC_ZONE_MASK | TIPC_CLUSTER_MASK)
+
+#define tipc_portid tipc_socket_addr
+#define tipc_name tipc_service_addr
+#define tipc_name_seq tipc_service_range
+
+static __inline__ __u32 tipc_addr(unsigned int zone,
+			      unsigned int cluster,
+			      unsigned int node)
+{
+	return (zone << TIPC_ZONE_OFFSET) |
+		(cluster << TIPC_CLUSTER_OFFSET) |
+		node;
+}
+
+static __inline__ unsigned int tipc_zone(__u32 addr)
+{
+	return addr >> TIPC_ZONE_OFFSET;
+}
+
+static __inline__ unsigned int tipc_cluster(__u32 addr)
+{
+	return (addr & TIPC_CLUSTER_MASK) >> TIPC_CLUSTER_OFFSET;
+}
+
+static __inline__ unsigned int tipc_node(__u32 addr)
+{
+	return addr & TIPC_NODE_MASK;
+}
+
+#endif
diff --git a/include/uapi/linux/tipc_netlink.h b/include/uapi/linux/tipc_netlink.h
new file mode 100644
index 0000000..efb958f
--- /dev/null
+++ b/include/uapi/linux/tipc_netlink.h
@@ -0,0 +1,333 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Copyright (c) 2014, Ericsson AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the names of the copyright holders nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _LINUX_TIPC_NETLINK_H_
+#define _LINUX_TIPC_NETLINK_H_
+
+#define TIPC_GENL_V2_NAME      "TIPCv2"
+#define TIPC_GENL_V2_VERSION   0x1
+
+/* Netlink commands */
+enum {
+	TIPC_NL_UNSPEC,
+	TIPC_NL_LEGACY,
+	TIPC_NL_BEARER_DISABLE,
+	TIPC_NL_BEARER_ENABLE,
+	TIPC_NL_BEARER_GET,
+	TIPC_NL_BEARER_SET,
+	TIPC_NL_SOCK_GET,
+	TIPC_NL_PUBL_GET,
+	TIPC_NL_LINK_GET,
+	TIPC_NL_LINK_SET,
+	TIPC_NL_LINK_RESET_STATS,
+	TIPC_NL_MEDIA_GET,
+	TIPC_NL_MEDIA_SET,
+	TIPC_NL_NODE_GET,
+	TIPC_NL_NET_GET,
+	TIPC_NL_NET_SET,
+	TIPC_NL_NAME_TABLE_GET,
+	TIPC_NL_MON_SET,
+	TIPC_NL_MON_GET,
+	TIPC_NL_MON_PEER_GET,
+	TIPC_NL_PEER_REMOVE,
+	TIPC_NL_BEARER_ADD,
+	TIPC_NL_UDP_GET_REMOTEIP,
+
+	__TIPC_NL_CMD_MAX,
+	TIPC_NL_CMD_MAX = __TIPC_NL_CMD_MAX - 1
+};
+
+/* Top level netlink attributes */
+enum {
+	TIPC_NLA_UNSPEC,
+	TIPC_NLA_BEARER,		/* nest */
+	TIPC_NLA_SOCK,			/* nest */
+	TIPC_NLA_PUBL,			/* nest */
+	TIPC_NLA_LINK,			/* nest */
+	TIPC_NLA_MEDIA,			/* nest */
+	TIPC_NLA_NODE,			/* nest */
+	TIPC_NLA_NET,			/* nest */
+	TIPC_NLA_NAME_TABLE,		/* nest */
+	TIPC_NLA_MON,			/* nest */
+	TIPC_NLA_MON_PEER,		/* nest */
+
+	__TIPC_NLA_MAX,
+	TIPC_NLA_MAX = __TIPC_NLA_MAX - 1
+};
+
+/* Bearer info */
+enum {
+	TIPC_NLA_BEARER_UNSPEC,
+	TIPC_NLA_BEARER_NAME,		/* string */
+	TIPC_NLA_BEARER_PROP,		/* nest */
+	TIPC_NLA_BEARER_DOMAIN,		/* u32 */
+	TIPC_NLA_BEARER_UDP_OPTS,	/* nest */
+
+	__TIPC_NLA_BEARER_MAX,
+	TIPC_NLA_BEARER_MAX = __TIPC_NLA_BEARER_MAX - 1
+};
+
+enum {
+	TIPC_NLA_UDP_UNSPEC,
+	TIPC_NLA_UDP_LOCAL,		/* sockaddr_storage */
+	TIPC_NLA_UDP_REMOTE,		/* sockaddr_storage */
+	TIPC_NLA_UDP_MULTI_REMOTEIP,	/* flag */
+
+	__TIPC_NLA_UDP_MAX,
+	TIPC_NLA_UDP_MAX = __TIPC_NLA_UDP_MAX - 1
+};
+/* Socket info */
+enum {
+	TIPC_NLA_SOCK_UNSPEC,
+	TIPC_NLA_SOCK_ADDR,		/* u32 */
+	TIPC_NLA_SOCK_REF,		/* u32 */
+	TIPC_NLA_SOCK_CON,		/* nest */
+	TIPC_NLA_SOCK_HAS_PUBL,		/* flag */
+	TIPC_NLA_SOCK_STAT,		/* nest */
+	TIPC_NLA_SOCK_TYPE,		/* u32 */
+	TIPC_NLA_SOCK_INO,		/* u32 */
+	TIPC_NLA_SOCK_UID,		/* u32 */
+	TIPC_NLA_SOCK_TIPC_STATE,	/* u32 */
+	TIPC_NLA_SOCK_COOKIE,		/* u64 */
+	TIPC_NLA_SOCK_PAD,		/* flag */
+	TIPC_NLA_SOCK_GROUP,		/* nest */
+
+	__TIPC_NLA_SOCK_MAX,
+	TIPC_NLA_SOCK_MAX = __TIPC_NLA_SOCK_MAX - 1
+};
+
+/* Link info */
+enum {
+	TIPC_NLA_LINK_UNSPEC,
+	TIPC_NLA_LINK_NAME,		/* string */
+	TIPC_NLA_LINK_DEST,		/* u32 */
+	TIPC_NLA_LINK_MTU,		/* u32 */
+	TIPC_NLA_LINK_BROADCAST,	/* flag */
+	TIPC_NLA_LINK_UP,		/* flag */
+	TIPC_NLA_LINK_ACTIVE,		/* flag */
+	TIPC_NLA_LINK_PROP,		/* nest */
+	TIPC_NLA_LINK_STATS,		/* nest */
+	TIPC_NLA_LINK_RX,		/* u32 */
+	TIPC_NLA_LINK_TX,		/* u32 */
+
+	__TIPC_NLA_LINK_MAX,
+	TIPC_NLA_LINK_MAX = __TIPC_NLA_LINK_MAX - 1
+};
+
+/* Media info */
+enum {
+	TIPC_NLA_MEDIA_UNSPEC,
+	TIPC_NLA_MEDIA_NAME,		/* string */
+	TIPC_NLA_MEDIA_PROP,		/* nest */
+
+	__TIPC_NLA_MEDIA_MAX,
+	TIPC_NLA_MEDIA_MAX = __TIPC_NLA_MEDIA_MAX - 1
+};
+
+/* Node info */
+enum {
+	TIPC_NLA_NODE_UNSPEC,
+	TIPC_NLA_NODE_ADDR,		/* u32 */
+	TIPC_NLA_NODE_UP,		/* flag */
+
+	__TIPC_NLA_NODE_MAX,
+	TIPC_NLA_NODE_MAX = __TIPC_NLA_NODE_MAX - 1
+};
+
+/* Net info */
+enum {
+	TIPC_NLA_NET_UNSPEC,
+	TIPC_NLA_NET_ID,		/* u32 */
+	TIPC_NLA_NET_ADDR,		/* u32 */
+	TIPC_NLA_NET_NODEID,		/* u64 */
+	TIPC_NLA_NET_NODEID_W1,		/* u64 */
+
+	__TIPC_NLA_NET_MAX,
+	TIPC_NLA_NET_MAX = __TIPC_NLA_NET_MAX - 1
+};
+
+/* Name table info */
+enum {
+	TIPC_NLA_NAME_TABLE_UNSPEC,
+	TIPC_NLA_NAME_TABLE_PUBL,	/* nest */
+
+	__TIPC_NLA_NAME_TABLE_MAX,
+	TIPC_NLA_NAME_TABLE_MAX = __TIPC_NLA_NAME_TABLE_MAX - 1
+};
+
+/* Monitor info */
+enum {
+	TIPC_NLA_MON_UNSPEC,
+	TIPC_NLA_MON_ACTIVATION_THRESHOLD,	/* u32 */
+	TIPC_NLA_MON_REF,			/* u32 */
+	TIPC_NLA_MON_ACTIVE,			/* flag */
+	TIPC_NLA_MON_BEARER_NAME,		/* string */
+	TIPC_NLA_MON_PEERCNT,			/* u32 */
+	TIPC_NLA_MON_LISTGEN,			/* u32 */
+
+	__TIPC_NLA_MON_MAX,
+	TIPC_NLA_MON_MAX = __TIPC_NLA_MON_MAX - 1
+};
+
+/* Publication info */
+enum {
+	TIPC_NLA_PUBL_UNSPEC,
+
+	TIPC_NLA_PUBL_TYPE,		/* u32 */
+	TIPC_NLA_PUBL_LOWER,		/* u32 */
+	TIPC_NLA_PUBL_UPPER,		/* u32 */
+	TIPC_NLA_PUBL_SCOPE,		/* u32 */
+	TIPC_NLA_PUBL_NODE,		/* u32 */
+	TIPC_NLA_PUBL_REF,		/* u32 */
+	TIPC_NLA_PUBL_KEY,		/* u32 */
+
+	__TIPC_NLA_PUBL_MAX,
+	TIPC_NLA_PUBL_MAX = __TIPC_NLA_PUBL_MAX - 1
+};
+
+/* Monitor peer info */
+enum {
+	TIPC_NLA_MON_PEER_UNSPEC,
+
+	TIPC_NLA_MON_PEER_ADDR,			/* u32 */
+	TIPC_NLA_MON_PEER_DOMGEN,		/* u32 */
+	TIPC_NLA_MON_PEER_APPLIED,		/* u32 */
+	TIPC_NLA_MON_PEER_UPMAP,		/* u64 */
+	TIPC_NLA_MON_PEER_MEMBERS,		/* tlv */
+	TIPC_NLA_MON_PEER_UP,			/* flag */
+	TIPC_NLA_MON_PEER_HEAD,			/* flag */
+	TIPC_NLA_MON_PEER_LOCAL,		/* flag */
+	TIPC_NLA_MON_PEER_PAD,			/* flag */
+
+	__TIPC_NLA_MON_PEER_MAX,
+	TIPC_NLA_MON_PEER_MAX = __TIPC_NLA_MON_PEER_MAX - 1
+};
+
+/* Nest, socket group info */
+enum {
+	TIPC_NLA_SOCK_GROUP_ID,			/* u32 */
+	TIPC_NLA_SOCK_GROUP_OPEN,		/* flag */
+	TIPC_NLA_SOCK_GROUP_NODE_SCOPE,		/* flag */
+	TIPC_NLA_SOCK_GROUP_CLUSTER_SCOPE,	/* flag */
+	TIPC_NLA_SOCK_GROUP_INSTANCE,		/* u32 */
+	TIPC_NLA_SOCK_GROUP_BC_SEND_NEXT,	/* u32 */
+
+	__TIPC_NLA_SOCK_GROUP_MAX,
+	TIPC_NLA_SOCK_GROUP_MAX = __TIPC_NLA_SOCK_GROUP_MAX - 1
+};
+
+/* Nest, connection info */
+enum {
+	TIPC_NLA_CON_UNSPEC,
+
+	TIPC_NLA_CON_FLAG,		/* flag */
+	TIPC_NLA_CON_NODE,		/* u32 */
+	TIPC_NLA_CON_SOCK,		/* u32 */
+	TIPC_NLA_CON_TYPE,		/* u32 */
+	TIPC_NLA_CON_INST,		/* u32 */
+
+	__TIPC_NLA_CON_MAX,
+	TIPC_NLA_CON_MAX = __TIPC_NLA_CON_MAX - 1
+};
+
+/* Nest, socket statistics info */
+enum {
+	TIPC_NLA_SOCK_STAT_RCVQ,	/* u32 */
+	TIPC_NLA_SOCK_STAT_SENDQ,	/* u32 */
+	TIPC_NLA_SOCK_STAT_LINK_CONG,	/* flag */
+	TIPC_NLA_SOCK_STAT_CONN_CONG,	/* flag */
+	TIPC_NLA_SOCK_STAT_DROP,	/* u32 */
+
+	__TIPC_NLA_SOCK_STAT_MAX,
+	TIPC_NLA_SOCK_STAT_MAX = __TIPC_NLA_SOCK_STAT_MAX - 1
+};
+
+/* Nest, link propreties. Valid for link, media and bearer */
+enum {
+	TIPC_NLA_PROP_UNSPEC,
+
+	TIPC_NLA_PROP_PRIO,		/* u32 */
+	TIPC_NLA_PROP_TOL,		/* u32 */
+	TIPC_NLA_PROP_WIN,		/* u32 */
+	TIPC_NLA_PROP_MTU,		/* u32 */
+	TIPC_NLA_PROP_BROADCAST,	/* u32 */
+	TIPC_NLA_PROP_BROADCAST_RATIO,	/* u32 */
+
+	__TIPC_NLA_PROP_MAX,
+	TIPC_NLA_PROP_MAX = __TIPC_NLA_PROP_MAX - 1
+};
+
+/* Nest, statistics info */
+enum {
+	TIPC_NLA_STATS_UNSPEC,
+
+	TIPC_NLA_STATS_RX_INFO,		/* u32 */
+	TIPC_NLA_STATS_RX_FRAGMENTS,	/* u32 */
+	TIPC_NLA_STATS_RX_FRAGMENTED,	/* u32 */
+	TIPC_NLA_STATS_RX_BUNDLES,	/* u32 */
+	TIPC_NLA_STATS_RX_BUNDLED,	/* u32 */
+	TIPC_NLA_STATS_TX_INFO,		/* u32 */
+	TIPC_NLA_STATS_TX_FRAGMENTS,	/* u32 */
+	TIPC_NLA_STATS_TX_FRAGMENTED,	/* u32 */
+	TIPC_NLA_STATS_TX_BUNDLES,	/* u32 */
+	TIPC_NLA_STATS_TX_BUNDLED,	/* u32 */
+	TIPC_NLA_STATS_MSG_PROF_TOT,	/* u32 */
+	TIPC_NLA_STATS_MSG_LEN_CNT,	/* u32 */
+	TIPC_NLA_STATS_MSG_LEN_TOT,	/* u32 */
+	TIPC_NLA_STATS_MSG_LEN_P0,	/* u32 */
+	TIPC_NLA_STATS_MSG_LEN_P1,	/* u32 */
+	TIPC_NLA_STATS_MSG_LEN_P2,	/* u32 */
+	TIPC_NLA_STATS_MSG_LEN_P3,	/* u32 */
+	TIPC_NLA_STATS_MSG_LEN_P4,	/* u32 */
+	TIPC_NLA_STATS_MSG_LEN_P5,	/* u32 */
+	TIPC_NLA_STATS_MSG_LEN_P6,	/* u32 */
+	TIPC_NLA_STATS_RX_STATES,	/* u32 */
+	TIPC_NLA_STATS_RX_PROBES,	/* u32 */
+	TIPC_NLA_STATS_RX_NACKS,	/* u32 */
+	TIPC_NLA_STATS_RX_DEFERRED,	/* u32 */
+	TIPC_NLA_STATS_TX_STATES,	/* u32 */
+	TIPC_NLA_STATS_TX_PROBES,	/* u32 */
+	TIPC_NLA_STATS_TX_NACKS,	/* u32 */
+	TIPC_NLA_STATS_TX_ACKS,		/* u32 */
+	TIPC_NLA_STATS_RETRANSMITTED,	/* u32 */
+	TIPC_NLA_STATS_DUPLICATES,	/* u32 */
+	TIPC_NLA_STATS_LINK_CONGS,	/* u32 */
+	TIPC_NLA_STATS_MAX_QUEUE,	/* u32 */
+	TIPC_NLA_STATS_AVG_QUEUE,	/* u32 */
+
+	__TIPC_NLA_STATS_MAX,
+	TIPC_NLA_STATS_MAX = __TIPC_NLA_STATS_MAX - 1
+};
+
+#endif
diff --git a/include/uapi/linux/tipc_sockets_diag.h b/include/uapi/linux/tipc_sockets_diag.h
new file mode 100644
index 0000000..21b766e
--- /dev/null
+++ b/include/uapi/linux/tipc_sockets_diag.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* AF_TIPC sock_diag interface for querying open sockets */
+
+#ifndef __TIPC_SOCKETS_DIAG_H__
+#define __TIPC_SOCKETS_DIAG_H__
+
+#include <linux/types.h>
+#include <linux/sock_diag.h>
+
+/* Request */
+struct tipc_sock_diag_req {
+	__u8	sdiag_family;	/* must be AF_TIPC */
+	__u8	sdiag_protocol;	/* must be 0 */
+	__u16	pad;		/* must be 0 */
+	__u32	tidiag_states;	/* query*/
+};
+#endif /* __TIPC_SOCKETS_DIAG_H__ */
diff --git a/include/uapi/linux/types.h b/include/uapi/linux/types.h
new file mode 100644
index 0000000..999cb0f
--- /dev/null
+++ b/include/uapi/linux/types.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_TYPES_H
+#define _LINUX_TYPES_H
+
+#include <asm/types.h>
+
+#ifndef __ASSEMBLY__
+
+#include <linux/posix_types.h>
+
+
+/*
+ * Below are truly Linux-specific types that should never collide with
+ * any application/library that wants linux/types.h.
+ */
+
+#ifdef __CHECKER__
+#define __bitwise__ __attribute__((bitwise))
+#else
+#define __bitwise__
+#endif
+#define __bitwise __bitwise__
+
+typedef __u16 __bitwise __le16;
+typedef __u16 __bitwise __be16;
+typedef __u32 __bitwise __le32;
+typedef __u32 __bitwise __be32;
+typedef __u64 __bitwise __le64;
+typedef __u64 __bitwise __be64;
+
+typedef __u16 __bitwise __sum16;
+typedef __u32 __bitwise __wsum;
+
+/*
+ * aligned_u64 should be used in defining kernel<->userspace ABIs to avoid
+ * common 32/64-bit compat problems.
+ * 64-bit values align to 4-byte boundaries on x86_32 (and possibly other
+ * architectures) and to 8-byte boundaries on 64-bit architectures.  The new
+ * aligned_64 type enforces 8-byte alignment so that structs containing
+ * aligned_64 values have the same alignment on 32-bit and 64-bit architectures.
+ * No conversions are necessary between 32-bit user-space and a 64-bit kernel.
+ */
+#define __aligned_u64 __u64 __attribute__((aligned(8)))
+#define __aligned_be64 __be64 __attribute__((aligned(8)))
+#define __aligned_le64 __le64 __attribute__((aligned(8)))
+
+typedef unsigned __bitwise __poll_t;
+
+#endif /*  __ASSEMBLY__ */
+#endif /* _LINUX_TYPES_H */
diff --git a/include/uapi/linux/unix_diag.h b/include/uapi/linux/unix_diag.h
new file mode 100644
index 0000000..a198857
--- /dev/null
+++ b/include/uapi/linux/unix_diag.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __UNIX_DIAG_H__
+#define __UNIX_DIAG_H__
+
+#include <linux/types.h>
+
+struct unix_diag_req {
+	__u8	sdiag_family;
+	__u8	sdiag_protocol;
+	__u16	pad;
+	__u32	udiag_states;
+	__u32	udiag_ino;
+	__u32	udiag_show;
+	__u32	udiag_cookie[2];
+};
+
+#define UDIAG_SHOW_NAME		0x00000001	/* show name (not path) */
+#define UDIAG_SHOW_VFS		0x00000002	/* show VFS inode info */
+#define UDIAG_SHOW_PEER		0x00000004	/* show peer socket info */
+#define UDIAG_SHOW_ICONS	0x00000008	/* show pending connections */
+#define UDIAG_SHOW_RQLEN	0x00000010	/* show skb receive queue len */
+#define UDIAG_SHOW_MEMINFO	0x00000020	/* show memory info of a socket */
+#define UDIAG_SHOW_UID		0x00000040	/* show socket's UID */
+
+struct unix_diag_msg {
+	__u8	udiag_family;
+	__u8	udiag_type;
+	__u8	udiag_state;
+	__u8	pad;
+
+	__u32	udiag_ino;
+	__u32	udiag_cookie[2];
+};
+
+enum {
+	/* UNIX_DIAG_NONE, standard nl API requires this attribute!  */
+	UNIX_DIAG_NAME,
+	UNIX_DIAG_VFS,
+	UNIX_DIAG_PEER,
+	UNIX_DIAG_ICONS,
+	UNIX_DIAG_RQLEN,
+	UNIX_DIAG_MEMINFO,
+	UNIX_DIAG_SHUTDOWN,
+	UNIX_DIAG_UID,
+
+	__UNIX_DIAG_MAX,
+};
+
+#define UNIX_DIAG_MAX (__UNIX_DIAG_MAX - 1)
+
+struct unix_diag_vfs {
+	__u32	udiag_vfs_ino;
+	__u32	udiag_vfs_dev;
+};
+
+struct unix_diag_rqlen {
+	__u32	udiag_rqueue;
+	__u32	udiag_wqueue;
+};
+
+#endif
diff --git a/include/uapi/linux/veth.h b/include/uapi/linux/veth.h
new file mode 100644
index 0000000..52b58e5
--- /dev/null
+++ b/include/uapi/linux/veth.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __NET_VETH_H_
+#define __NET_VETH_H_
+
+enum {
+	VETH_INFO_UNSPEC,
+	VETH_INFO_PEER,
+
+	__VETH_INFO_MAX
+#define VETH_INFO_MAX	(__VETH_INFO_MAX - 1)
+};
+
+#endif
diff --git a/include/uapi/linux/vm_sockets_diag.h b/include/uapi/linux/vm_sockets_diag.h
new file mode 100644
index 0000000..6da42f9
--- /dev/null
+++ b/include/uapi/linux/vm_sockets_diag.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* AF_VSOCK sock_diag(7) interface for querying open sockets */
+
+#ifndef __VM_SOCKETS_DIAG_H__
+#define __VM_SOCKETS_DIAG_H__
+
+#include <linux/types.h>
+
+/* Request */
+struct vsock_diag_req {
+	__u8	sdiag_family;	/* must be AF_VSOCK */
+	__u8	sdiag_protocol;	/* must be 0 */
+	__u16	pad;		/* must be 0 */
+	__u32	vdiag_states;	/* query bitmap (e.g. 1 << TCP_LISTEN) */
+	__u32	vdiag_ino;	/* must be 0 (reserved) */
+	__u32	vdiag_show;	/* must be 0 (reserved) */
+	__u32	vdiag_cookie[2];
+};
+
+/* Response */
+struct vsock_diag_msg {
+	__u8	vdiag_family;	/* AF_VSOCK */
+	__u8	vdiag_type;	/* SOCK_STREAM or SOCK_DGRAM */
+	__u8	vdiag_state;	/* sk_state (e.g. TCP_LISTEN) */
+	__u8	vdiag_shutdown; /* local RCV_SHUTDOWN | SEND_SHUTDOWN */
+	__u32   vdiag_src_cid;
+	__u32   vdiag_src_port;
+	__u32   vdiag_dst_cid;
+	__u32   vdiag_dst_port;
+	__u32	vdiag_ino;
+	__u32	vdiag_cookie[2];
+};
+
+#endif /* __VM_SOCKETS_DIAG_H__ */
diff --git a/include/uapi/linux/xdp_diag.h b/include/uapi/linux/xdp_diag.h
new file mode 100644
index 0000000..78b2591
--- /dev/null
+++ b/include/uapi/linux/xdp_diag.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * xdp_diag: interface for query/monitor XDP sockets
+ * Copyright(c) 2019 Intel Corporation.
+ */
+
+#ifndef _LINUX_XDP_DIAG_H
+#define _LINUX_XDP_DIAG_H
+
+#include <linux/types.h>
+
+struct xdp_diag_req {
+	__u8	sdiag_family;
+	__u8	sdiag_protocol;
+	__u16	pad;
+	__u32	xdiag_ino;
+	__u32	xdiag_show;
+	__u32	xdiag_cookie[2];
+};
+
+struct xdp_diag_msg {
+	__u8	xdiag_family;
+	__u8	xdiag_type;
+	__u16	pad;
+	__u32	xdiag_ino;
+	__u32	xdiag_cookie[2];
+};
+
+#define XDP_SHOW_INFO		(1 << 0) /* Basic information */
+#define XDP_SHOW_RING_CFG	(1 << 1)
+#define XDP_SHOW_UMEM		(1 << 2)
+#define XDP_SHOW_MEMINFO	(1 << 3)
+
+enum {
+	XDP_DIAG_NONE,
+	XDP_DIAG_INFO,
+	XDP_DIAG_UID,
+	XDP_DIAG_RX_RING,
+	XDP_DIAG_TX_RING,
+	XDP_DIAG_UMEM,
+	XDP_DIAG_UMEM_FILL_RING,
+	XDP_DIAG_UMEM_COMPLETION_RING,
+	XDP_DIAG_MEMINFO,
+	__XDP_DIAG_MAX,
+};
+
+#define XDP_DIAG_MAX (__XDP_DIAG_MAX - 1)
+
+struct xdp_diag_info {
+	__u32	ifindex;
+	__u32	queue_id;
+};
+
+struct xdp_diag_ring {
+	__u32	entries; /*num descs */
+};
+
+#define XDP_DU_F_ZEROCOPY (1 << 0)
+
+struct xdp_diag_umem {
+	__u64	size;
+	__u32	id;
+	__u32	num_pages;
+	__u32	chunk_size;
+	__u32	headroom;
+	__u32	ifindex;
+	__u32	queue_id;
+	__u32	flags;
+	__u32	refs;
+};
+
+#endif /* _LINUX_XDP_DIAG_H */
diff --git a/include/uapi/linux/xfrm.h b/include/uapi/linux/xfrm.h
new file mode 100644
index 0000000..5cdda9d
--- /dev/null
+++ b/include/uapi/linux/xfrm.h
@@ -0,0 +1,540 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_XFRM_H
+#define _LINUX_XFRM_H
+
+#include <linux/in6.h>
+#include <linux/types.h>
+
+/* All of the structures in this file may not change size as they are
+ * passed into the kernel from userspace via netlink sockets.
+ */
+
+/* Structure to encapsulate addresses. I do not want to use
+ * "standard" structure. My apologies.
+ */
+typedef union {
+	__be32		a4;
+	__be32		a6[4];
+	struct in6_addr	in6;
+} xfrm_address_t;
+
+/* Ident of a specific xfrm_state. It is used on input to lookup
+ * the state by (spi,daddr,ah/esp) or to store information about
+ * spi, protocol and tunnel address on output.
+ */
+struct xfrm_id {
+	xfrm_address_t	daddr;
+	__be32		spi;
+	__u8		proto;
+};
+
+struct xfrm_sec_ctx {
+	__u8	ctx_doi;
+	__u8	ctx_alg;
+	__u16	ctx_len;
+	__u32	ctx_sid;
+	char	ctx_str[0];
+};
+
+/* Security Context Domains of Interpretation */
+#define XFRM_SC_DOI_RESERVED 0
+#define XFRM_SC_DOI_LSM 1
+
+/* Security Context Algorithms */
+#define XFRM_SC_ALG_RESERVED 0
+#define XFRM_SC_ALG_SELINUX 1
+
+/* Selector, used as selector both on policy rules (SPD) and SAs. */
+
+struct xfrm_selector {
+	xfrm_address_t	daddr;
+	xfrm_address_t	saddr;
+	__be16	dport;
+	__be16	dport_mask;
+	__be16	sport;
+	__be16	sport_mask;
+	__u16	family;
+	__u8	prefixlen_d;
+	__u8	prefixlen_s;
+	__u8	proto;
+	int	ifindex;
+	__kernel_uid32_t	user;
+};
+
+#define XFRM_INF (~(__u64)0)
+
+struct xfrm_lifetime_cfg {
+	__u64	soft_byte_limit;
+	__u64	hard_byte_limit;
+	__u64	soft_packet_limit;
+	__u64	hard_packet_limit;
+	__u64	soft_add_expires_seconds;
+	__u64	hard_add_expires_seconds;
+	__u64	soft_use_expires_seconds;
+	__u64	hard_use_expires_seconds;
+};
+
+struct xfrm_lifetime_cur {
+	__u64	bytes;
+	__u64	packets;
+	__u64	add_time;
+	__u64	use_time;
+};
+
+struct xfrm_replay_state {
+	__u32	oseq;
+	__u32	seq;
+	__u32	bitmap;
+};
+
+#define XFRMA_REPLAY_ESN_MAX	4096
+
+struct xfrm_replay_state_esn {
+	unsigned int	bmp_len;
+	__u32		oseq;
+	__u32		seq;
+	__u32		oseq_hi;
+	__u32		seq_hi;
+	__u32		replay_window;
+	__u32		bmp[0];
+};
+
+struct xfrm_algo {
+	char		alg_name[64];
+	unsigned int	alg_key_len;    /* in bits */
+	char		alg_key[0];
+};
+
+struct xfrm_algo_auth {
+	char		alg_name[64];
+	unsigned int	alg_key_len;    /* in bits */
+	unsigned int	alg_trunc_len;  /* in bits */
+	char		alg_key[0];
+};
+
+struct xfrm_algo_aead {
+	char		alg_name[64];
+	unsigned int	alg_key_len;	/* in bits */
+	unsigned int	alg_icv_len;	/* in bits */
+	char		alg_key[0];
+};
+
+struct xfrm_stats {
+	__u32	replay_window;
+	__u32	replay;
+	__u32	integrity_failed;
+};
+
+enum {
+	XFRM_POLICY_TYPE_MAIN	= 0,
+	XFRM_POLICY_TYPE_SUB	= 1,
+	XFRM_POLICY_TYPE_MAX	= 2,
+	XFRM_POLICY_TYPE_ANY	= 255
+};
+
+enum {
+	XFRM_POLICY_IN	= 0,
+	XFRM_POLICY_OUT	= 1,
+	XFRM_POLICY_FWD	= 2,
+	XFRM_POLICY_MASK = 3,
+	XFRM_POLICY_MAX	= 3
+};
+
+enum {
+	XFRM_SHARE_ANY,		/* No limitations */
+	XFRM_SHARE_SESSION,	/* For this session only */
+	XFRM_SHARE_USER,	/* For this user only */
+	XFRM_SHARE_UNIQUE	/* Use once */
+};
+
+#define XFRM_MODE_TRANSPORT 0
+#define XFRM_MODE_TUNNEL 1
+#define XFRM_MODE_ROUTEOPTIMIZATION 2
+#define XFRM_MODE_IN_TRIGGER 3
+#define XFRM_MODE_BEET 4
+#define XFRM_MODE_MAX 5
+
+/* Netlink configuration messages.  */
+enum {
+	XFRM_MSG_BASE = 0x10,
+
+	XFRM_MSG_NEWSA = 0x10,
+#define XFRM_MSG_NEWSA XFRM_MSG_NEWSA
+	XFRM_MSG_DELSA,
+#define XFRM_MSG_DELSA XFRM_MSG_DELSA
+	XFRM_MSG_GETSA,
+#define XFRM_MSG_GETSA XFRM_MSG_GETSA
+
+	XFRM_MSG_NEWPOLICY,
+#define XFRM_MSG_NEWPOLICY XFRM_MSG_NEWPOLICY
+	XFRM_MSG_DELPOLICY,
+#define XFRM_MSG_DELPOLICY XFRM_MSG_DELPOLICY
+	XFRM_MSG_GETPOLICY,
+#define XFRM_MSG_GETPOLICY XFRM_MSG_GETPOLICY
+
+	XFRM_MSG_ALLOCSPI,
+#define XFRM_MSG_ALLOCSPI XFRM_MSG_ALLOCSPI
+	XFRM_MSG_ACQUIRE,
+#define XFRM_MSG_ACQUIRE XFRM_MSG_ACQUIRE
+	XFRM_MSG_EXPIRE,
+#define XFRM_MSG_EXPIRE XFRM_MSG_EXPIRE
+
+	XFRM_MSG_UPDPOLICY,
+#define XFRM_MSG_UPDPOLICY XFRM_MSG_UPDPOLICY
+	XFRM_MSG_UPDSA,
+#define XFRM_MSG_UPDSA XFRM_MSG_UPDSA
+
+	XFRM_MSG_POLEXPIRE,
+#define XFRM_MSG_POLEXPIRE XFRM_MSG_POLEXPIRE
+
+	XFRM_MSG_FLUSHSA,
+#define XFRM_MSG_FLUSHSA XFRM_MSG_FLUSHSA
+	XFRM_MSG_FLUSHPOLICY,
+#define XFRM_MSG_FLUSHPOLICY XFRM_MSG_FLUSHPOLICY
+
+	XFRM_MSG_NEWAE,
+#define XFRM_MSG_NEWAE XFRM_MSG_NEWAE
+	XFRM_MSG_GETAE,
+#define XFRM_MSG_GETAE XFRM_MSG_GETAE
+
+	XFRM_MSG_REPORT,
+#define XFRM_MSG_REPORT XFRM_MSG_REPORT
+
+	XFRM_MSG_MIGRATE,
+#define XFRM_MSG_MIGRATE XFRM_MSG_MIGRATE
+
+	XFRM_MSG_NEWSADINFO,
+#define XFRM_MSG_NEWSADINFO XFRM_MSG_NEWSADINFO
+	XFRM_MSG_GETSADINFO,
+#define XFRM_MSG_GETSADINFO XFRM_MSG_GETSADINFO
+
+	XFRM_MSG_NEWSPDINFO,
+#define XFRM_MSG_NEWSPDINFO XFRM_MSG_NEWSPDINFO
+	XFRM_MSG_GETSPDINFO,
+#define XFRM_MSG_GETSPDINFO XFRM_MSG_GETSPDINFO
+
+	XFRM_MSG_MAPPING,
+#define XFRM_MSG_MAPPING XFRM_MSG_MAPPING
+	__XFRM_MSG_MAX
+};
+#define XFRM_MSG_MAX (__XFRM_MSG_MAX - 1)
+
+#define XFRM_NR_MSGTYPES (XFRM_MSG_MAX + 1 - XFRM_MSG_BASE)
+
+/*
+ * Generic LSM security context for comunicating to user space
+ * NOTE: Same format as sadb_x_sec_ctx
+ */
+struct xfrm_user_sec_ctx {
+	__u16			len;
+	__u16			exttype;
+	__u8			ctx_alg;  /* LSMs: e.g., selinux == 1 */
+	__u8			ctx_doi;
+	__u16			ctx_len;
+};
+
+struct xfrm_user_tmpl {
+	struct xfrm_id		id;
+	__u16			family;
+	xfrm_address_t		saddr;
+	__u32			reqid;
+	__u8			mode;
+	__u8			share;
+	__u8			optional;
+	__u32			aalgos;
+	__u32			ealgos;
+	__u32			calgos;
+};
+
+struct xfrm_encap_tmpl {
+	__u16		encap_type;
+	__be16		encap_sport;
+	__be16		encap_dport;
+	xfrm_address_t	encap_oa;
+};
+
+/* AEVENT flags  */
+enum xfrm_ae_ftype_t {
+	XFRM_AE_UNSPEC,
+	XFRM_AE_RTHR=1,	/* replay threshold*/
+	XFRM_AE_RVAL=2, /* replay value */
+	XFRM_AE_LVAL=4, /* lifetime value */
+	XFRM_AE_ETHR=8, /* expiry timer threshold */
+	XFRM_AE_CR=16, /* Event cause is replay update */
+	XFRM_AE_CE=32, /* Event cause is timer expiry */
+	XFRM_AE_CU=64, /* Event cause is policy update */
+	__XFRM_AE_MAX
+
+#define XFRM_AE_MAX (__XFRM_AE_MAX - 1)
+};
+
+struct xfrm_userpolicy_type {
+	__u8		type;
+	__u16		reserved1;
+	__u8		reserved2;
+};
+
+/* Netlink message attributes.  */
+enum xfrm_attr_type_t {
+	XFRMA_UNSPEC,
+	XFRMA_ALG_AUTH,		/* struct xfrm_algo */
+	XFRMA_ALG_CRYPT,	/* struct xfrm_algo */
+	XFRMA_ALG_COMP,		/* struct xfrm_algo */
+	XFRMA_ENCAP,		/* struct xfrm_algo + struct xfrm_encap_tmpl */
+	XFRMA_TMPL,		/* 1 or more struct xfrm_user_tmpl */
+	XFRMA_SA,		/* struct xfrm_usersa_info  */
+	XFRMA_POLICY,		/*struct xfrm_userpolicy_info */
+	XFRMA_SEC_CTX,		/* struct xfrm_sec_ctx */
+	XFRMA_LTIME_VAL,
+	XFRMA_REPLAY_VAL,
+	XFRMA_REPLAY_THRESH,
+	XFRMA_ETIMER_THRESH,
+	XFRMA_SRCADDR,		/* xfrm_address_t */
+	XFRMA_COADDR,		/* xfrm_address_t */
+	XFRMA_LASTUSED,		/* unsigned long  */
+	XFRMA_POLICY_TYPE,	/* struct xfrm_userpolicy_type */
+	XFRMA_MIGRATE,
+	XFRMA_ALG_AEAD,		/* struct xfrm_algo_aead */
+	XFRMA_KMADDRESS,        /* struct xfrm_user_kmaddress */
+	XFRMA_ALG_AUTH_TRUNC,	/* struct xfrm_algo_auth */
+	XFRMA_MARK,		/* struct xfrm_mark */
+	XFRMA_TFCPAD,		/* __u32 */
+	XFRMA_REPLAY_ESN_VAL,	/* struct xfrm_replay_state_esn */
+	XFRMA_SA_EXTRA_FLAGS,	/* __u32 */
+	XFRMA_PROTO,		/* __u8 */
+	XFRMA_ADDRESS_FILTER,	/* struct xfrm_address_filter */
+	XFRMA_PAD,
+	XFRMA_OFFLOAD_DEV,	/* struct xfrm_state_offload */
+	XFRMA_SET_MARK,		/* __u32 */
+	XFRMA_SET_MARK_MASK,	/* __u32 */
+	XFRMA_IF_ID,		/* __u32 */
+	__XFRMA_MAX
+
+#define XFRMA_OUTPUT_MARK XFRMA_SET_MARK	/* Compatibility */
+#define XFRMA_MAX (__XFRMA_MAX - 1)
+};
+
+struct xfrm_mark {
+	__u32           v; /* value */
+	__u32           m; /* mask */
+};
+
+enum xfrm_sadattr_type_t {
+	XFRMA_SAD_UNSPEC,
+	XFRMA_SAD_CNT,
+	XFRMA_SAD_HINFO,
+	__XFRMA_SAD_MAX
+
+#define XFRMA_SAD_MAX (__XFRMA_SAD_MAX - 1)
+};
+
+struct xfrmu_sadhinfo {
+	__u32 sadhcnt; /* current hash bkts */
+	__u32 sadhmcnt; /* max allowed hash bkts */
+};
+
+enum xfrm_spdattr_type_t {
+	XFRMA_SPD_UNSPEC,
+	XFRMA_SPD_INFO,
+	XFRMA_SPD_HINFO,
+	XFRMA_SPD_IPV4_HTHRESH,
+	XFRMA_SPD_IPV6_HTHRESH,
+	__XFRMA_SPD_MAX
+
+#define XFRMA_SPD_MAX (__XFRMA_SPD_MAX - 1)
+};
+
+struct xfrmu_spdinfo {
+	__u32 incnt;
+	__u32 outcnt;
+	__u32 fwdcnt;
+	__u32 inscnt;
+	__u32 outscnt;
+	__u32 fwdscnt;
+};
+
+struct xfrmu_spdhinfo {
+	__u32 spdhcnt;
+	__u32 spdhmcnt;
+};
+
+struct xfrmu_spdhthresh {
+	__u8 lbits;
+	__u8 rbits;
+};
+
+struct xfrm_usersa_info {
+	struct xfrm_selector		sel;
+	struct xfrm_id			id;
+	xfrm_address_t			saddr;
+	struct xfrm_lifetime_cfg	lft;
+	struct xfrm_lifetime_cur	curlft;
+	struct xfrm_stats		stats;
+	__u32				seq;
+	__u32				reqid;
+	__u16				family;
+	__u8				mode;		/* XFRM_MODE_xxx */
+	__u8				replay_window;
+	__u8				flags;
+#define XFRM_STATE_NOECN	1
+#define XFRM_STATE_DECAP_DSCP	2
+#define XFRM_STATE_NOPMTUDISC	4
+#define XFRM_STATE_WILDRECV	8
+#define XFRM_STATE_ICMP		16
+#define XFRM_STATE_AF_UNSPEC	32
+#define XFRM_STATE_ALIGN4	64
+#define XFRM_STATE_ESN		128
+};
+
+#define XFRM_SA_XFLAG_DONT_ENCAP_DSCP	1
+
+struct xfrm_usersa_id {
+	xfrm_address_t			daddr;
+	__be32				spi;
+	__u16				family;
+	__u8				proto;
+};
+
+struct xfrm_aevent_id {
+	struct xfrm_usersa_id		sa_id;
+	xfrm_address_t			saddr;
+	__u32				flags;
+	__u32				reqid;
+};
+
+struct xfrm_userspi_info {
+	struct xfrm_usersa_info		info;
+	__u32				min;
+	__u32				max;
+};
+
+struct xfrm_userpolicy_info {
+	struct xfrm_selector		sel;
+	struct xfrm_lifetime_cfg	lft;
+	struct xfrm_lifetime_cur	curlft;
+	__u32				priority;
+	__u32				index;
+	__u8				dir;
+	__u8				action;
+#define XFRM_POLICY_ALLOW	0
+#define XFRM_POLICY_BLOCK	1
+	__u8				flags;
+#define XFRM_POLICY_LOCALOK	1	/* Allow user to override global policy */
+	/* Automatically expand selector to include matching ICMP payloads. */
+#define XFRM_POLICY_ICMP	2
+	__u8				share;
+};
+
+struct xfrm_userpolicy_id {
+	struct xfrm_selector		sel;
+	__u32				index;
+	__u8				dir;
+};
+
+struct xfrm_user_acquire {
+	struct xfrm_id			id;
+	xfrm_address_t			saddr;
+	struct xfrm_selector		sel;
+	struct xfrm_userpolicy_info	policy;
+	__u32				aalgos;
+	__u32				ealgos;
+	__u32				calgos;
+	__u32				seq;
+};
+
+struct xfrm_user_expire {
+	struct xfrm_usersa_info		state;
+	__u8				hard;
+};
+
+struct xfrm_user_polexpire {
+	struct xfrm_userpolicy_info	pol;
+	__u8				hard;
+};
+
+struct xfrm_usersa_flush {
+	__u8				proto;
+};
+
+struct xfrm_user_report {
+	__u8				proto;
+	struct xfrm_selector		sel;
+};
+
+/* Used by MIGRATE to pass addresses IKE should use to perform
+ * SA negotiation with the peer */
+struct xfrm_user_kmaddress {
+	xfrm_address_t                  local;
+	xfrm_address_t                  remote;
+	__u32				reserved;
+	__u16				family;
+};
+
+struct xfrm_user_migrate {
+	xfrm_address_t			old_daddr;
+	xfrm_address_t			old_saddr;
+	xfrm_address_t			new_daddr;
+	xfrm_address_t			new_saddr;
+	__u8				proto;
+	__u8				mode;
+	__u16				reserved;
+	__u32				reqid;
+	__u16				old_family;
+	__u16				new_family;
+};
+
+struct xfrm_user_mapping {
+	struct xfrm_usersa_id		id;
+	__u32				reqid;
+	xfrm_address_t			old_saddr;
+	xfrm_address_t			new_saddr;
+	__be16				old_sport;
+	__be16				new_sport;
+};
+
+struct xfrm_address_filter {
+	xfrm_address_t			saddr;
+	xfrm_address_t			daddr;
+	__u16				family;
+	__u8				splen;
+	__u8				dplen;
+};
+
+struct xfrm_user_offload {
+	int				ifindex;
+	__u8				flags;
+};
+#define XFRM_OFFLOAD_IPV6	1
+#define XFRM_OFFLOAD_INBOUND	2
+
+/* backwards compatibility for userspace */
+#define XFRMGRP_ACQUIRE		1
+#define XFRMGRP_EXPIRE		2
+#define XFRMGRP_SA		4
+#define XFRMGRP_POLICY		8
+#define XFRMGRP_REPORT		0x20
+
+enum xfrm_nlgroups {
+	XFRMNLGRP_NONE,
+#define XFRMNLGRP_NONE		XFRMNLGRP_NONE
+	XFRMNLGRP_ACQUIRE,
+#define XFRMNLGRP_ACQUIRE	XFRMNLGRP_ACQUIRE
+	XFRMNLGRP_EXPIRE,
+#define XFRMNLGRP_EXPIRE	XFRMNLGRP_EXPIRE
+	XFRMNLGRP_SA,
+#define XFRMNLGRP_SA		XFRMNLGRP_SA
+	XFRMNLGRP_POLICY,
+#define XFRMNLGRP_POLICY	XFRMNLGRP_POLICY
+	XFRMNLGRP_AEVENTS,
+#define XFRMNLGRP_AEVENTS	XFRMNLGRP_AEVENTS
+	XFRMNLGRP_REPORT,
+#define XFRMNLGRP_REPORT	XFRMNLGRP_REPORT
+	XFRMNLGRP_MIGRATE,
+#define XFRMNLGRP_MIGRATE	XFRMNLGRP_MIGRATE
+	XFRMNLGRP_MAPPING,
+#define XFRMNLGRP_MAPPING	XFRMNLGRP_MAPPING
+	__XFRMNLGRP_MAX
+};
+#define XFRMNLGRP_MAX	(__XFRMNLGRP_MAX - 1)
+
+#endif /* _LINUX_XFRM_H */
diff --git a/include/utils.h b/include/utils.h
new file mode 100644
index 0000000..794d360
--- /dev/null
+++ b/include/utils.h
@@ -0,0 +1,322 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __UTILS_H__
+#define __UTILS_H__ 1
+
+#include <sys/types.h>
+#include <asm/types.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <time.h>
+
+#ifdef HAVE_LIBBSD
+#include <bsd/string.h>
+#endif
+
+#include "libnetlink.h"
+#include "ll_map.h"
+#include "rtm_map.h"
+#include "json_print.h"
+
+extern int preferred_family;
+extern int human_readable;
+extern int use_iec;
+extern int show_stats;
+extern int show_details;
+extern int show_raw;
+extern int resolve_hosts;
+extern int oneline;
+extern int brief;
+extern int json;
+extern int pretty;
+extern int timestamp;
+extern int timestamp_short;
+extern const char * _SL_;
+extern int max_flush_loops;
+extern int batch_mode;
+extern int numeric;
+extern bool do_all;
+
+#ifndef CONFDIR
+#define CONFDIR		"/etc/iproute2"
+#endif
+
+#define SPRINT_BSIZE 64
+#define SPRINT_BUF(x)	char x[SPRINT_BSIZE]
+
+void incomplete_command(void) __attribute__((noreturn));
+
+#define NEXT_ARG() do { argv++; if (--argc <= 0) incomplete_command(); } while(0)
+#define NEXT_ARG_OK() (argc - 1 > 0)
+#define NEXT_ARG_FWD() do { argv++; argc--; } while(0)
+#define PREV_ARG() do { argv--; argc++; } while(0)
+
+#define TIME_UNITS_PER_SEC	1000000
+#define NSEC_PER_USEC 1000
+#define NSEC_PER_MSEC 1000000
+#define NSEC_PER_SEC 1000000000LL
+
+typedef struct
+{
+	__u16 flags;
+	__u16 bytelen;
+	__s16 bitlen;
+	/* These next two fields match rtvia */
+	__u16 family;
+	__u32 data[64];
+} inet_prefix;
+
+enum {
+	PREFIXLEN_SPECIFIED	= (1 << 0),
+	ADDRTYPE_INET		= (1 << 1),
+	ADDRTYPE_UNSPEC		= (1 << 2),
+	ADDRTYPE_MULTI		= (1 << 3),
+
+	ADDRTYPE_INET_UNSPEC	= ADDRTYPE_INET | ADDRTYPE_UNSPEC,
+	ADDRTYPE_INET_MULTI	= ADDRTYPE_INET | ADDRTYPE_MULTI
+};
+
+static inline void inet_prefix_reset(inet_prefix *p)
+{
+	p->flags = 0;
+}
+
+static inline bool is_addrtype_inet(const inet_prefix *p)
+{
+	return p->flags & ADDRTYPE_INET;
+}
+
+static inline bool is_addrtype_inet_unspec(const inet_prefix *p)
+{
+	return (p->flags & ADDRTYPE_INET_UNSPEC) == ADDRTYPE_INET_UNSPEC;
+}
+
+static inline bool is_addrtype_inet_multi(const inet_prefix *p)
+{
+	return (p->flags & ADDRTYPE_INET_MULTI) == ADDRTYPE_INET_MULTI;
+}
+
+static inline bool is_addrtype_inet_not_unspec(const inet_prefix *p)
+{
+	return (p->flags & ADDRTYPE_INET_UNSPEC) == ADDRTYPE_INET;
+}
+
+static inline bool is_addrtype_inet_not_multi(const inet_prefix *p)
+{
+	return (p->flags & ADDRTYPE_INET_MULTI) == ADDRTYPE_INET;
+}
+
+#define DN_MAXADDL 20
+#ifndef AF_DECnet
+#define AF_DECnet 12
+#endif
+
+struct dn_naddr
+{
+        unsigned short          a_len;
+        unsigned char a_addr[DN_MAXADDL];
+};
+
+#ifndef AF_MPLS
+# define AF_MPLS 28
+#endif
+#ifndef IPPROTO_MPLS
+#define IPPROTO_MPLS	137
+#endif
+
+#ifndef CLOCK_TAI
+# define CLOCK_TAI 11
+#endif
+
+#ifndef AF_XDP
+# define AF_XDP 44
+# if AF_MAX < 45
+#  undef AF_MAX
+#  define AF_MAX 45
+# endif
+#endif
+
+__u32 get_addr32(const char *name);
+int get_addr_1(inet_prefix *dst, const char *arg, int family);
+int get_prefix_1(inet_prefix *dst, char *arg, int family);
+int get_addr(inet_prefix *dst, const char *arg, int family);
+int get_prefix(inet_prefix *dst, char *arg, int family);
+int mask2bits(__u32 netmask);
+int get_addr_rta(inet_prefix *dst, const struct rtattr *rta, int family);
+int get_addr_ila(__u64 *val, const char *arg);
+
+int read_prop(const char *dev, char *prop, long *value);
+int get_hex(char c);
+int get_integer(int *val, const char *arg, int base);
+int get_unsigned(unsigned *val, const char *arg, int base);
+int get_time_rtt(unsigned *val, const char *arg, int *raw);
+#define get_byte get_u8
+#define get_ushort get_u16
+#define get_short get_s16
+int get_s64(__s64 *val, const char *arg, int base);
+int get_u64(__u64 *val, const char *arg, int base);
+int get_u32(__u32 *val, const char *arg, int base);
+int get_s32(__s32 *val, const char *arg, int base);
+int get_u16(__u16 *val, const char *arg, int base);
+int get_u8(__u8 *val, const char *arg, int base);
+int get_be64(__be64 *val, const char *arg, int base);
+int get_be32(__be32 *val, const char *arg, int base);
+int get_be16(__be16 *val, const char *arg, int base);
+int get_addr64(__u64 *ap, const char *cp);
+
+int hex2mem(const char *buf, uint8_t *mem, int count);
+char *hexstring_n2a(const __u8 *str, int len, char *buf, int blen);
+__u8 *hexstring_a2n(const char *str, __u8 *buf, int blen, unsigned int *len);
+#define ADDR64_BUF_SIZE sizeof("xxxx:xxxx:xxxx:xxxx")
+int addr64_n2a(__u64 addr, char *buff, size_t len);
+
+int af_bit_len(int af);
+
+const char *format_host_r(int af, int len, const void *addr,
+			       char *buf, int buflen);
+#define format_host_rta_r(af, rta, buf, buflen)	\
+	format_host_r(af, RTA_PAYLOAD(rta), RTA_DATA(rta), \
+		      buf, buflen)
+
+const char *format_host(int af, int lne, const void *addr);
+#define format_host_rta(af, rta) \
+	format_host(af, RTA_PAYLOAD(rta), RTA_DATA(rta))
+const char *rt_addr_n2a_r(int af, int len, const void *addr,
+			       char *buf, int buflen);
+const char *rt_addr_n2a(int af, int len, const void *addr);
+#define rt_addr_n2a_rta(af, rta) \
+	rt_addr_n2a(af, RTA_PAYLOAD(rta), RTA_DATA(rta))
+
+int read_family(const char *name);
+const char *family_name(int family);
+
+void missarg(const char *) __attribute__((noreturn));
+void invarg(const char *, const char *) __attribute__((noreturn));
+void duparg(const char *, const char *) __attribute__((noreturn));
+void duparg2(const char *, const char *) __attribute__((noreturn));
+int nodev(const char *dev);
+int check_ifname(const char *);
+int get_ifname(char *, const char *);
+const char *get_ifname_rta(int ifindex, const struct rtattr *rta);
+bool matches(const char *prefix, const char *string);
+int inet_addr_match(const inet_prefix *a, const inet_prefix *b, int bits);
+int inet_addr_match_rta(const inet_prefix *m, const struct rtattr *rta);
+
+const char *mpls_ntop(int af, const void *addr, char *str, size_t len);
+int mpls_pton(int af, const char *src, void *addr, size_t alen);
+
+extern int __iproute2_hz_internal;
+int __get_hz(void);
+
+static __inline__ int get_hz(void)
+{
+	if (__iproute2_hz_internal == 0)
+		__iproute2_hz_internal = __get_hz();
+	return __iproute2_hz_internal;
+}
+
+extern int __iproute2_user_hz_internal;
+int __get_user_hz(void);
+
+static __inline__ int get_user_hz(void)
+{
+	if (__iproute2_user_hz_internal == 0)
+		__iproute2_user_hz_internal = __get_user_hz();
+	return __iproute2_user_hz_internal;
+}
+
+static inline __u32 nl_mgrp(__u32 group)
+{
+	if (group > 31 ) {
+		fprintf(stderr, "Use setsockopt for this group %d\n", group);
+		exit(-1);
+	}
+	return group ? (1 << (group - 1)) : 0;
+}
+
+/* courtesy of bridge-utils */
+static inline unsigned long __tv_to_jiffies(const struct timeval *tv)
+{
+	unsigned long long jif;
+
+	jif = 1000000ULL * tv->tv_sec + tv->tv_usec;
+
+	return jif/10000;
+}
+
+static inline void __jiffies_to_tv(struct timeval *tv, unsigned long jiffies)
+{
+	unsigned long long tvusec;
+
+	tvusec = 10000ULL*jiffies;
+	tv->tv_sec = tvusec/1000000;
+	tv->tv_usec = tvusec - 1000000 * tv->tv_sec;
+}
+
+void print_escape_buf(const __u8 *buf, size_t len, const char *escape);
+
+int print_timestamp(FILE *fp);
+void print_nlmsg_timestamp(FILE *fp, const struct nlmsghdr *n);
+
+unsigned int print_name_and_link(const char *fmt,
+				 const char *name, struct rtattr *tb[]);
+
+#define BIT(nr)                 (1UL << (nr))
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+#define BUILD_BUG_ON(cond) ((void)sizeof(char[1 - 2 * !!(cond)]))
+
+#ifndef offsetof
+# define offsetof(type, member) ((size_t) &((type *)0)->member)
+#endif
+
+#ifndef min
+# define min(x, y) ({			\
+	typeof(x) _min1 = (x);		\
+	typeof(y) _min2 = (y);		\
+	(void) (&_min1 == &_min2);	\
+	_min1 < _min2 ? _min1 : _min2; })
+#endif
+
+#ifndef __check_format_string
+# define __check_format_string(pos_str, pos_args) \
+	__attribute__ ((format (printf, (pos_str), (pos_args))))
+#endif
+
+#define _textify(x)	#x
+#define textify(x)	_textify(x)
+
+#define htonll(x) ((1==htonl(1)) ? (x) : ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
+#define ntohll(x) ((1==ntohl(1)) ? (x) : ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
+
+extern int cmdlineno;
+ssize_t getcmdline(char **line, size_t *len, FILE *in);
+int makeargs(char *line, char *argv[], int maxargs);
+
+char *int_to_str(int val, char *buf);
+int get_guid(__u64 *guid, const char *arg);
+int get_real_family(int rtm_type, int rtm_family);
+
+int cmd_exec(const char *cmd, char **argv, bool do_fork,
+	     int (*setup)(void *), void *arg);
+int make_path(const char *path, mode_t mode);
+char *find_cgroup2_mount(void);
+int get_command_name(const char *pid, char *comm, size_t len);
+
+int get_rtnl_link_stats_rta(struct rtnl_link_stats64 *stats64,
+			    struct rtattr *tb[]);
+
+#ifdef NEED_STRLCPY
+size_t strlcpy(char *dst, const char *src, size_t size);
+size_t strlcat(char *dst, const char *src, size_t size);
+#endif
+
+void drop_cap(void);
+
+int get_time(unsigned int *time, const char *str);
+int get_time64(__s64 *time, const char *str);
+char *sprint_time(__u32 time, char *buf);
+char *sprint_time64(__s64 time, char *buf);
+
+#endif /* __UTILS_H__ */
diff --git a/include/xt-internal.h b/include/xt-internal.h
new file mode 100644
index 0000000..89c73e4
--- /dev/null
+++ b/include/xt-internal.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _XTABLES_INTERNAL_H
+#define _XTABLES_INTERNAL_H 1
+
+#ifndef XT_LIB_DIR
+#	define XT_LIB_DIR "/lib/xtables"
+#endif
+
+/* protocol family dependent informations */
+struct afinfo {
+	/* protocol family */
+	int family;
+
+	/* prefix of library name (ex "libipt_" */
+	char *libprefix;
+
+	/* used by setsockopt (ex IPPROTO_IP */
+	int ipproto;
+
+	/* kernel module (ex "ip_tables" */
+	char *kmod;
+
+	/* optname to check revision support of match */
+	int so_rev_match;
+
+	/* optname to check revision support of match */
+	int so_rev_target;
+};
+
+enum xt_tryload {
+	DONT_LOAD,
+	DURING_LOAD,
+	TRY_LOAD,
+	LOAD_MUST_SUCCEED
+};
+
+struct xtables_rule_match {
+	struct xtables_rule_match *next;
+	struct xtables_match *match;
+	/* Multiple matches of the same type: the ones before
+	   the current one are completed from parsing point of view */
+	unsigned int completed;
+};
+
+extern char *lib_dir;
+
+extern void *fw_calloc(size_t count, size_t size);
+extern void *fw_malloc(size_t size);
+
+extern const char *modprobe_program;
+extern int xtables_insmod(const char *modname, const char *modprobe, int quiet);
+extern int load_xtables_ko(const char *modprobe, int quiet);
+
+/* This is decleared in ip[6]tables.c */
+extern struct afinfo afinfo;
+
+/* Keeping track of external matches and targets: linked lists.  */
+extern struct xtables_match *xtables_matches;
+extern struct xtables_target *xtables_targets;
+
+extern struct xtables_match *find_match(const char *name, enum xt_tryload,
+					struct xtables_rule_match **match);
+extern struct xtables_target *find_target(const char *name, enum xt_tryload);
+
+extern void _init(void);
+
+#endif /* _XTABLES_INTERNAL_H */
diff --git a/include/xtables.h b/include/xtables.h
new file mode 100644
index 0000000..583619f
--- /dev/null
+++ b/include/xtables.h
@@ -0,0 +1,598 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _XTABLES_H
+#define _XTABLES_H
+
+/*
+ * Changing any structs/functions may incur a needed change
+ * in libxtables_vcurrent/vage too.
+ */
+
+#include <sys/socket.h> /* PF_* */
+#include <sys/types.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <linux/types.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/x_tables.h>
+
+#ifndef IPPROTO_SCTP
+#define IPPROTO_SCTP 132
+#endif
+#ifndef IPPROTO_DCCP
+#define IPPROTO_DCCP 33
+#endif
+#ifndef IPPROTO_MH
+#	define IPPROTO_MH 135
+#endif
+#ifndef IPPROTO_UDPLITE
+#define IPPROTO_UDPLITE	136
+#endif
+
+#include <xtables-version.h>
+
+struct in_addr;
+
+/*
+ * .size is here so that there is a somewhat reasonable check
+ * against the chosen .type.
+ */
+#define XTOPT_POINTER(stype, member) \
+	.ptroff = offsetof(stype, member), \
+	.size = sizeof(((stype *)NULL)->member)
+#define XTOPT_TABLEEND {.name = NULL}
+
+/**
+ * Select the format the input has to conform to, as well as the target type
+ * (area pointed to with XTOPT_POINTER). Note that the storing is not always
+ * uniform. @cb->val will be populated with as much as there is space, i.e.
+ * exactly 2 items for ranges, but the target area can receive more values
+ * (e.g. in case of ranges), or less values (e.g. %XTTYPE_HOSTMASK).
+ *
+ * %XTTYPE_NONE:	option takes no argument
+ * %XTTYPE_UINT*:	standard integer
+ * %XTTYPE_UINT*RC:	colon-separated range of standard integers
+ * %XTTYPE_DOUBLE:	double-precision floating point number
+ * %XTTYPE_STRING:	arbitrary string
+ * %XTTYPE_TOSMASK:	8-bit TOS value with optional mask
+ * %XTTYPE_MARKMASK32:	32-bit mark with optional mask
+ * %XTTYPE_SYSLOGLEVEL:	syslog level by name or number
+ * %XTTYPE_HOST:	one host or address (ptr: union nf_inet_addr)
+ * %XTTYPE_HOSTMASK:	one host or address, with an optional prefix length
+ * 			(ptr: union nf_inet_addr; only host portion is stored)
+ * %XTTYPE_PROTOCOL:	protocol number/name from /etc/protocols (ptr: uint8_t)
+ * %XTTYPE_PORT:	16-bit port name or number (supports %XTOPT_NBO)
+ * %XTTYPE_PORTRC:	colon-separated port range (names acceptable),
+ * 			(supports %XTOPT_NBO)
+ * %XTTYPE_PLEN:	prefix length
+ * %XTTYPE_PLENMASK:	prefix length (ptr: union nf_inet_addr)
+ * %XTTYPE_ETHERMAC:	Ethernet MAC address in hex form
+ */
+enum xt_option_type {
+	XTTYPE_NONE,
+	XTTYPE_UINT8,
+	XTTYPE_UINT16,
+	XTTYPE_UINT32,
+	XTTYPE_UINT64,
+	XTTYPE_UINT8RC,
+	XTTYPE_UINT16RC,
+	XTTYPE_UINT32RC,
+	XTTYPE_UINT64RC,
+	XTTYPE_DOUBLE,
+	XTTYPE_STRING,
+	XTTYPE_TOSMASK,
+	XTTYPE_MARKMASK32,
+	XTTYPE_SYSLOGLEVEL,
+	XTTYPE_HOST,
+	XTTYPE_HOSTMASK,
+	XTTYPE_PROTOCOL,
+	XTTYPE_PORT,
+	XTTYPE_PORTRC,
+	XTTYPE_PLEN,
+	XTTYPE_PLENMASK,
+	XTTYPE_ETHERMAC,
+};
+
+/**
+ * %XTOPT_INVERT:	option is invertible (usable with !)
+ * %XTOPT_MAND:		option is mandatory
+ * %XTOPT_MULTI:	option may be specified multiple times
+ * %XTOPT_PUT:		store value into memory at @ptroff
+ * %XTOPT_NBO:		store value in network-byte order
+ * 			(only certain XTTYPEs recognize this)
+ */
+enum xt_option_flags {
+	XTOPT_INVERT = 1 << 0,
+	XTOPT_MAND   = 1 << 1,
+	XTOPT_MULTI  = 1 << 2,
+	XTOPT_PUT    = 1 << 3,
+	XTOPT_NBO    = 1 << 4,
+};
+
+/**
+ * @name:	name of option
+ * @type:	type of input and validation method, see %XTTYPE_*
+ * @id:		unique number (within extension) for option, 0-31
+ * @excl:	bitmask of flags that cannot be used with this option
+ * @also:	bitmask of flags that must be used with this option
+ * @flags:	bitmask of option flags, see %XTOPT_*
+ * @ptroff:	offset into private structure for member
+ * @size:	size of the item pointed to by @ptroff; this is a safeguard
+ * @min:	lowest allowed value (for singular integral types)
+ * @max:	highest allowed value (for singular integral types)
+ */
+struct xt_option_entry {
+	const char *name;
+	enum xt_option_type type;
+	unsigned int id, excl, also, flags;
+	unsigned int ptroff;
+	size_t size;
+	unsigned int min, max;
+};
+
+/**
+ * @arg:	input from command line
+ * @ext_name:	name of extension currently being processed
+ * @entry:	current option being processed
+ * @data:	per-extension kernel data block
+ * @xflags:	options of the extension that have been used
+ * @invert:	whether option was used with !
+ * @nvals:	number of results in uXX_multi
+ * @val:	parsed result
+ * @udata:	per-extension private scratch area
+ * 		(cf. xtables_{match,target}->udata_size)
+ */
+struct xt_option_call {
+	const char *arg, *ext_name;
+	const struct xt_option_entry *entry;
+	void *data;
+	unsigned int xflags;
+	bool invert;
+	uint8_t nvals;
+	union {
+		uint8_t u8, u8_range[2], syslog_level, protocol;
+		uint16_t u16, u16_range[2], port, port_range[2];
+		uint32_t u32, u32_range[2];
+		uint64_t u64, u64_range[2];
+		double dbl;
+		struct {
+			union nf_inet_addr haddr, hmask;
+			uint8_t hlen;
+		};
+		struct {
+			uint8_t tos_value, tos_mask;
+		};
+		struct {
+			uint32_t mark, mask;
+		};
+		uint8_t ethermac[6];
+	} val;
+	/* Wished for a world where the ones below were gone: */
+	union {
+		struct xt_entry_match **match;
+		struct xt_entry_target **target;
+	};
+	void *xt_entry;
+	void *udata;
+};
+
+/**
+ * @ext_name:	name of extension currently being processed
+ * @data:	per-extension (kernel) data block
+ * @udata:	per-extension private scratch area
+ * 		(cf. xtables_{match,target}->udata_size)
+ * @xflags:	options of the extension that have been used
+ */
+struct xt_fcheck_call {
+	const char *ext_name;
+	void *data, *udata;
+	unsigned int xflags;
+};
+
+/**
+ * A "linear"/linked-list based name<->id map, for files similar to
+ * /etc/iproute2/.
+ */
+struct xtables_lmap {
+	char *name;
+	int id;
+	struct xtables_lmap *next;
+};
+
+enum xtables_ext_flags {
+	XTABLES_EXT_ALIAS = 1 << 0,
+};
+
+#if XTABLES_VERSION_CODE >= 12
+struct xt_xlate;
+
+struct xt_xlate_mt_params {
+	const void			*ip;
+	const struct xt_entry_match	*match;
+	int				numeric;
+	bool				escape_quotes;
+};
+
+struct xt_xlate_tg_params {
+	const void			*ip;
+	const struct xt_entry_target	*target;
+	int				numeric;
+	bool				escape_quotes;
+};
+#endif
+
+/* Include file for additions: new matches and targets. */
+struct xtables_match
+{
+	/*
+	 * ABI/API version this module requires. Must be first member,
+	 * as the rest of this struct may be subject to ABI changes.
+	 */
+	const char *version;
+
+	struct xtables_match *next;
+
+	const char *name;
+	const char *real_name;
+
+	/* Revision of match (0 by default). */
+	uint8_t revision;
+
+	/* Extension flags */
+	uint8_t ext_flags;
+
+	uint16_t family;
+
+	/* Size of match data. */
+	size_t size;
+
+	/* Size of match data relevant for userspace comparison purposes */
+	size_t userspacesize;
+
+	/* Function which prints out usage message. */
+	void (*help)(void);
+
+	/* Initialize the match. */
+	void (*init)(struct xt_entry_match *m);
+
+	/* Function which parses command options; returns true if it
+           ate an option */
+	/* entry is struct ipt_entry for example */
+	int (*parse)(int c, char **argv, int invert, unsigned int *flags,
+		     const void *entry,
+		     struct xt_entry_match **match);
+
+	/* Final check; exit if not ok. */
+	void (*final_check)(unsigned int flags);
+
+	/* Prints out the match iff non-NULL: put space at end */
+	/* ip is struct ipt_ip * for example */
+	void (*print)(const void *ip,
+		      const struct xt_entry_match *match, int numeric);
+
+	/* Saves the match info in parsable form to stdout. */
+	/* ip is struct ipt_ip * for example */
+	void (*save)(const void *ip, const struct xt_entry_match *match);
+
+	/* Print match name or alias */
+	const char *(*alias)(const struct xt_entry_match *match);
+
+	/* Pointer to list of extra command-line options */
+	const struct option *extra_opts;
+
+	/* New parser */
+	void (*x6_parse)(struct xt_option_call *);
+	void (*x6_fcheck)(struct xt_fcheck_call *);
+	const struct xt_option_entry *x6_options;
+
+#if XTABLES_VERSION_CODE >= 12
+	/* Translate iptables to nft */
+	int (*xlate)(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params);
+#endif
+
+	/* Size of per-extension instance extra "global" scratch space */
+	size_t udata_size;
+
+	/* Ignore these men behind the curtain: */
+	void *udata;
+	unsigned int option_offset;
+	struct xt_entry_match *m;
+	unsigned int mflags;
+	unsigned int loaded; /* simulate loading so options are merged properly */
+};
+
+struct xtables_target
+{
+	/*
+	 * ABI/API version this module requires. Must be first member,
+	 * as the rest of this struct may be subject to ABI changes.
+	 */
+	const char *version;
+
+	struct xtables_target *next;
+
+
+	const char *name;
+
+	/* Real target behind this, if any. */
+	const char *real_name;
+
+	/* Revision of target (0 by default). */
+	uint8_t revision;
+
+	/* Extension flags */
+	uint8_t ext_flags;
+
+	uint16_t family;
+
+
+	/* Size of target data. */
+	size_t size;
+
+	/* Size of target data relevant for userspace comparison purposes */
+	size_t userspacesize;
+
+	/* Function which prints out usage message. */
+	void (*help)(void);
+
+	/* Initialize the target. */
+	void (*init)(struct xt_entry_target *t);
+
+	/* Function which parses command options; returns true if it
+           ate an option */
+	/* entry is struct ipt_entry for example */
+	int (*parse)(int c, char **argv, int invert, unsigned int *flags,
+		     const void *entry,
+		     struct xt_entry_target **targetinfo);
+
+	/* Final check; exit if not ok. */
+	void (*final_check)(unsigned int flags);
+
+	/* Prints out the target iff non-NULL: put space at end */
+	void (*print)(const void *ip,
+		      const struct xt_entry_target *target, int numeric);
+
+	/* Saves the targinfo in parsable form to stdout. */
+	void (*save)(const void *ip,
+		     const struct xt_entry_target *target);
+
+	/* Print target name or alias */
+	const char *(*alias)(const struct xt_entry_target *target);
+
+	/* Pointer to list of extra command-line options */
+	const struct option *extra_opts;
+
+	/* New parser */
+	void (*x6_parse)(struct xt_option_call *);
+	void (*x6_fcheck)(struct xt_fcheck_call *);
+	const struct xt_option_entry *x6_options;
+
+#if XTABLES_VERSION_CODE >= 12
+	/* Translate iptables to nft */
+	int (*xlate)(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params);
+#endif
+
+	size_t udata_size;
+
+	/* Ignore these men behind the curtain: */
+	void *udata;
+	unsigned int option_offset;
+	struct xt_entry_target *t;
+	unsigned int tflags;
+	unsigned int used;
+	unsigned int loaded; /* simulate loading so options are merged properly */
+};
+
+struct xtables_rule_match {
+	struct xtables_rule_match *next;
+	struct xtables_match *match;
+	/* Multiple matches of the same type: the ones before
+	   the current one are completed from parsing point of view */
+	bool completed;
+};
+
+/**
+ * struct xtables_pprot -
+ *
+ * A few hardcoded protocols for 'all' and in case the user has no
+ * /etc/protocols.
+ */
+struct xtables_pprot {
+	const char *name;
+	uint8_t num;
+};
+
+enum xtables_tryload {
+	XTF_DONT_LOAD,
+	XTF_DURING_LOAD,
+	XTF_TRY_LOAD,
+	XTF_LOAD_MUST_SUCCEED,
+};
+
+enum xtables_exittype {
+	OTHER_PROBLEM = 1,
+	PARAMETER_PROBLEM,
+	VERSION_PROBLEM,
+	RESOURCE_PROBLEM,
+	XTF_ONLY_ONCE,
+	XTF_NO_INVERT,
+	XTF_BAD_VALUE,
+	XTF_ONE_ACTION,
+};
+
+struct xtables_globals
+{
+	unsigned int option_offset;
+	const char *program_name, *program_version;
+	struct option *orig_opts;
+	struct option *opts;
+	void (*exit_err)(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+	int (*compat_rev)(const char *name, uint8_t rev, int opt);
+};
+
+#define XT_GETOPT_TABLEEND {.name = NULL, .has_arg = false}
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const char *xtables_modprobe_program;
+extern struct xtables_match *xtables_matches;
+extern struct xtables_target *xtables_targets;
+
+extern void xtables_init(void);
+extern void xtables_set_nfproto(uint8_t);
+extern void *xtables_calloc(size_t, size_t);
+extern void *xtables_malloc(size_t);
+extern void *xtables_realloc(void *, size_t);
+
+extern int xtables_insmod(const char *, const char *, bool);
+extern int xtables_load_ko(const char *, bool);
+extern int xtables_set_params(struct xtables_globals *xtp);
+extern void xtables_free_opts(int reset_offset);
+extern struct option *xtables_merge_options(struct option *origopts,
+	struct option *oldopts, const struct option *newopts,
+	unsigned int *option_offset);
+
+extern int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto);
+extern struct xtables_match *xtables_find_match(const char *name,
+	enum xtables_tryload, struct xtables_rule_match **match);
+extern struct xtables_target *xtables_find_target(const char *name,
+	enum xtables_tryload);
+extern int xtables_compatible_revision(const char *name, uint8_t revision,
+				       int opt);
+
+extern void xtables_rule_matches_free(struct xtables_rule_match **matches);
+
+/* Your shared library should call one of these. */
+extern void xtables_register_match(struct xtables_match *me);
+extern void xtables_register_matches(struct xtables_match *, unsigned int);
+extern void xtables_register_target(struct xtables_target *me);
+extern void xtables_register_targets(struct xtables_target *, unsigned int);
+
+extern bool xtables_strtoul(const char *, char **, uintmax_t *,
+	uintmax_t, uintmax_t);
+extern bool xtables_strtoui(const char *, char **, unsigned int *,
+	unsigned int, unsigned int);
+extern int xtables_service_to_port(const char *name, const char *proto);
+extern uint16_t xtables_parse_port(const char *port, const char *proto);
+extern void
+xtables_parse_interface(const char *arg, char *vianame, unsigned char *mask);
+
+/* this is a special 64bit data type that is 8-byte aligned */
+#define aligned_u64 uint64_t __attribute__((aligned(8)))
+
+extern struct xtables_globals *xt_params;
+#define xtables_error (xt_params->exit_err)
+
+extern void xtables_param_act(unsigned int, const char *, ...);
+
+extern const char *xtables_ipaddr_to_numeric(const struct in_addr *);
+extern const char *xtables_ipaddr_to_anyname(const struct in_addr *);
+extern const char *xtables_ipmask_to_numeric(const struct in_addr *);
+extern struct in_addr *xtables_numeric_to_ipaddr(const char *);
+extern struct in_addr *xtables_numeric_to_ipmask(const char *);
+extern int xtables_ipmask_to_cidr(const struct in_addr *);
+extern void xtables_ipparse_any(const char *, struct in_addr **,
+	struct in_addr *, unsigned int *);
+extern void xtables_ipparse_multiple(const char *, struct in_addr **,
+	struct in_addr **, unsigned int *);
+
+extern struct in6_addr *xtables_numeric_to_ip6addr(const char *);
+extern const char *xtables_ip6addr_to_numeric(const struct in6_addr *);
+extern const char *xtables_ip6addr_to_anyname(const struct in6_addr *);
+extern const char *xtables_ip6mask_to_numeric(const struct in6_addr *);
+extern int xtables_ip6mask_to_cidr(const struct in6_addr *);
+extern void xtables_ip6parse_any(const char *, struct in6_addr **,
+	struct in6_addr *, unsigned int *);
+extern void xtables_ip6parse_multiple(const char *, struct in6_addr **,
+	struct in6_addr **, unsigned int *);
+
+/**
+ * Print the specified value to standard output, quoting dangerous
+ * characters if required.
+ */
+extern void xtables_save_string(const char *value);
+
+#define FMT_NUMERIC		0x0001
+#define FMT_NOCOUNTS		0x0002
+#define FMT_KILOMEGAGIGA	0x0004
+#define FMT_OPTIONS		0x0008
+#define FMT_NOTABLE		0x0010
+#define FMT_NOTARGET		0x0020
+#define FMT_VIA			0x0040
+#define FMT_NONEWLINE		0x0080
+#define FMT_LINENUMBERS		0x0100
+
+#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \
+                        | FMT_NUMERIC | FMT_NOTABLE)
+#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab))
+
+extern void xtables_print_num(uint64_t number, unsigned int format);
+
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+#	ifdef _INIT
+#		undef _init
+#		define _init _INIT
+#	endif
+	extern void init_extensions(void);
+	extern void init_extensions4(void);
+	extern void init_extensions6(void);
+#else
+#	define _init __attribute__((constructor)) _INIT
+#endif
+
+extern const struct xtables_pprot xtables_chain_protos[];
+extern uint16_t xtables_parse_protocol(const char *s);
+
+/* kernel revision handling */
+extern int kernel_version;
+extern void get_kernel_version(void);
+#define LINUX_VERSION(x,y,z)	(0x10000*(x) + 0x100*(y) + z)
+#define LINUX_VERSION_MAJOR(x)	(((x)>>16) & 0xFF)
+#define LINUX_VERSION_MINOR(x)	(((x)>> 8) & 0xFF)
+#define LINUX_VERSION_PATCH(x)	( (x)      & 0xFF)
+
+/* xtoptions.c */
+extern void xtables_option_metavalidate(const char *,
+					const struct xt_option_entry *);
+extern struct option *xtables_options_xfrm(struct option *, struct option *,
+					   const struct xt_option_entry *,
+					   unsigned int *);
+extern void xtables_option_parse(struct xt_option_call *);
+extern void xtables_option_tpcall(unsigned int, char **, bool,
+				  struct xtables_target *, void *);
+extern void xtables_option_mpcall(unsigned int, char **, bool,
+				  struct xtables_match *, void *);
+extern void xtables_option_tfcall(struct xtables_target *);
+extern void xtables_option_mfcall(struct xtables_match *);
+extern void xtables_options_fcheck(const char *, unsigned int,
+				   const struct xt_option_entry *);
+
+extern struct xtables_lmap *xtables_lmap_init(const char *);
+extern void xtables_lmap_free(struct xtables_lmap *);
+extern int xtables_lmap_name2id(const struct xtables_lmap *, const char *);
+extern const char *xtables_lmap_id2name(const struct xtables_lmap *, int);
+
+#ifdef XTABLES_INTERNAL
+
+/* Shipped modules rely on this... */
+
+#	ifndef ARRAY_SIZE
+#		define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+#	endif
+
+extern void _init(void);
+
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* _XTABLES_H */
diff --git a/ip/.gitignore b/ip/.gitignore
new file mode 100644
index 0000000..b95b232
--- /dev/null
+++ b/ip/.gitignore
@@ -0,0 +1,2 @@
+ip
+rtmon
diff --git a/ip/Android.mk b/ip/Android.mk
new file mode 100644
index 0000000..8e6724b
--- /dev/null
+++ b/ip/Android.mk
@@ -0,0 +1,130 @@
+LOCAL_PATH := $(call my-dir)
+
+IPROUTE2_USE_CONFIG ?= no
+
+etc_iproute2_dir := etc/iproute2
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := ip.c ipaddress.c ipaddrlabel.c iproute.c iprule.c ipnetns.c \
+        rtm_map.c iptunnel.c ip6tunnel.c tunnel.c ipneigh.c ipntable.c iplink.c \
+        ipmaddr.c ipmonitor.c ipmroute.c ipprefix.c iptuntap.c iptoken.c \
+        ipxfrm.c xfrm_state.c xfrm_policy.c xfrm_monitor.c iplink_dummy.c \
+        iplink_vlan.c link_veth.c link_gre.c iplink_can.c iplink_xdp.c \
+        iplink_macvlan.c ipl2tp.c link_vti.c link_vti6.c link_xfrm.c \
+        iplink_vxlan.c tcp_metrics.c iplink_ipoib.c ipnetconf.c link_ip6tnl.c \
+        link_iptnl.c link_gre6.c iplink_bond.c iplink_bond_slave.c iplink_hsr.c \
+        iplink_bridge.c iplink_bridge_slave.c ipfou.c iplink_ipvlan.c \
+        iplink_geneve.c iplink_vrf.c iproute_lwtunnel.c ipmacsec.c ipila.c \
+        ipvrf.c iplink_xstats.c ipseg6.c iplink_netdevsim.c iplink_rmnet.c \
+        ipnexthop.c
+
+LOCAL_MODULE := ip
+
+ifeq ($(USE_IP_USER_BUILD),1)
+LOCAL_MODULE_TAGS := user eng
+else
+LOCAL_MODULE_TAGS := eng
+endif
+
+LOCAL_STATIC_LIBRARIES += libiprouteutil libnetlink
+
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/uapi
+
+LOCAL_CFLAGS := \
+    -O2 -g \
+    -W -Wall \
+    -Wno-implicit-function-declaration \
+    -Wno-missing-field-initializers \
+    -Wno-pointer-arith \
+    -Wno-sign-compare \
+    -Wno-unused-parameter \
+    -Werror \
+    -D_GNU_SOURCE
+
+ifeq ($(BUILD_EUREKA),true)
+LOCAL_CFLAGS += -DHAVE_SETNS
+endif
+
+LOCAL_LDFLAGS := -Wl,-export-dynamic -Wl,--no-gc-sections
+
+ifeq ($(IPROUTE2_USE_CONFIG), yes)
+LOCAL_REQUIRED_MODULES += ematch_map group rt_dsfield rt_protos rt_realms rt_scopes rt_tables
+endif
+
+LOCAL_REQUIRED_MODULES += rtmon
+
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := rtmon.c
+LOCAL_MODULE := rtmon
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/uapi
+LOCAL_STATIC_LIBRARIES += libiprouteutil libnetlink
+LOCAL_CFLAGS := \
+    -O2 -g \
+    -W -Wall \
+    -Wno-implicit-function-declaration \
+    -Wno-missing-field-initializers \
+    -Wno-pointer-arith \
+    -Wno-sign-compare \
+    -Wno-unused-parameter \
+    -Werror \
+    -D_GNU_SOURCE
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := ematch_map
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT)/$(etc_iproute2_dir)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := ../$(etc_iproute2_dir)/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := group
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT)/$(etc_iproute2_dir)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := ../$(etc_iproute2_dir)/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := rt_dsfield
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT)/$(etc_iproute2_dir)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := ../$(etc_iproute2_dir)/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := rt_protos
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT)/$(etc_iproute2_dir)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := ../$(etc_iproute2_dir)/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := rt_realms
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT)/$(etc_iproute2_dir)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := ../$(etc_iproute2_dir)/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := rt_scopes
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT)/$(etc_iproute2_dir)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := ../$(etc_iproute2_dir)/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := rt_tables
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT)/$(etc_iproute2_dir)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := ../$(etc_iproute2_dir)/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
diff --git a/ip/MODULE_LICENSE_GPL b/ip/MODULE_LICENSE_GPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ip/MODULE_LICENSE_GPL
diff --git a/ip/Makefile b/ip/Makefile
new file mode 100644
index 0000000..5ab78d7
--- /dev/null
+++ b/ip/Makefile
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: GPL-2.0
+IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \
+    rtm_map.o iptunnel.o ip6tunnel.o tunnel.o ipneigh.o ipntable.o iplink.o \
+    ipmaddr.o ipmonitor.o ipmroute.o ipprefix.o iptuntap.o iptoken.o \
+    ipxfrm.o xfrm_state.o xfrm_policy.o xfrm_monitor.o iplink_dummy.o \
+    iplink_ifb.o iplink_nlmon.o iplink_team.o iplink_vcan.o iplink_vxcan.o \
+    iplink_vlan.o link_veth.o link_gre.o iplink_can.o iplink_xdp.o \
+    iplink_macvlan.o ipl2tp.o link_vti.o link_vti6.o link_xfrm.o \
+    iplink_vxlan.o tcp_metrics.o iplink_ipoib.o ipnetconf.o link_ip6tnl.o \
+    link_iptnl.o link_gre6.o iplink_bond.o iplink_bond_slave.o iplink_hsr.o \
+    iplink_bridge.o iplink_bridge_slave.o ipfou.o iplink_ipvlan.o \
+    iplink_geneve.o iplink_vrf.o iproute_lwtunnel.o ipmacsec.o ipila.o \
+    ipvrf.o iplink_xstats.o ipseg6.o iplink_netdevsim.o iplink_rmnet.o \
+    ipnexthop.o
+
+RTMONOBJ=rtmon.o
+
+include ../config.mk
+
+ALLOBJ=$(IPOBJ) $(RTMONOBJ)
+SCRIPTS=ifcfg rtpr routel routef
+TARGETS=ip rtmon
+
+all: $(TARGETS) $(SCRIPTS)
+
+ip: $(IPOBJ) $(LIBNETLINK)
+	$(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+rtmon: $(RTMONOBJ)
+	$(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+	install -m 0755 $(TARGETS) $(DESTDIR)$(SBINDIR)
+	install -m 0755 $(SCRIPTS) $(DESTDIR)$(SBINDIR)
+
+clean:
+	rm -f $(ALLOBJ) $(TARGETS)
+
+SHARED_LIBS ?= y
+ifeq ($(SHARED_LIBS),y)
+
+LDLIBS += -ldl
+LDFLAGS += -Wl,-export-dynamic
+
+else
+
+ip: static-syms.o
+static-syms.o: static-syms.h
+static-syms.h: $(wildcard *.c)
+	files="$^" ; \
+	for s in `grep -B 3 '\<dlsym' $$files | sed -n '/snprintf/{s:.*"\([^"]*\)".*:\1:;s:%s::;p}'` ; do \
+		sed -n '/'$$s'[^ ]* =/{s:.* \([^ ]*'$$s'[^ ]*\) .*:extern char \1[] __attribute__((weak)); if (!strcmp(sym, "\1")) return \1;:;p}' $$files ; \
+	done > $@
+
+endif
diff --git a/ip/NOTICE b/ip/NOTICE
new file mode 100644
index 0000000..3912109
--- /dev/null
+++ b/ip/NOTICE
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, 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 Library 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 St, 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 Library General
+Public License instead of this License.
diff --git a/ip/ifcfg b/ip/ifcfg
new file mode 100644
index 0000000..5b34dec
--- /dev/null
+++ b/ip/ifcfg
@@ -0,0 +1,150 @@
+#! /bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+CheckForwarding () {
+  local sbase fwd
+  sbase=/proc/sys/net/ipv4/conf
+  fwd=0
+  if [ -d $sbase ]; then
+    for dir in $sbase/*/forwarding; do
+      fwd=$(( fwd + $(cat "$dir") ))
+    done
+  else
+    fwd=2
+  fi
+  return $fwd
+}
+
+RestartRDISC () {
+  killall -HUP rdisc || rdisc -fs
+}
+
+ABCMaskLen () {
+  local class;
+
+  class=${1%%.*}
+  if [ "$1" = "" -o $class -eq 0 -o $class -ge 224 ]; then return 0
+  elif [ $class -ge 224 ]; then return 0
+  elif [ $class -ge 192 ]; then return 24
+  elif [ $class -ge 128 ]; then return 16
+  else return 8; fi
+}
+
+label="label $1"
+ldev="$1"
+dev=${1%:*}
+if [ "$dev" = "" -o "$1" = "help" ]; then
+  echo "Usage: ifcfg DEV [[add|del [ADDR[/LEN]] [PEER] | stop]" 1>&2
+  echo "       add - add new address" 1>&2
+  echo "       del - delete address" 1>&2
+  echo "       stop - completely disable IP" 1>&2
+  exit 1
+fi
+shift
+
+CheckForwarding
+fwd=$?
+if [ $fwd -ne 0 ]; then
+  echo "Forwarding is ON or its state is unknown ($fwd). OK, No RDISC." 1>&2
+fi
+
+
+deleting=0
+case "$1" in
+add) shift ;;
+stop)
+  if [ "$ldev" != "$dev" ]; then
+    echo "Cannot stop alias $ldev" 1>&2
+    exit 1;
+  fi
+  ip -4 addr flush dev $dev $label || exit 1
+  if [ $fwd -eq 0 ]; then RestartRDISC; fi
+  exit 0 ;;
+del*)
+  deleting=1; shift ;;
+*)
+esac
+
+ipaddr=
+pfxlen=
+if [ "$1" != "" ]; then
+  ipaddr=${1%/*}
+  if [ "$1" != "$ipaddr" ]; then
+    pfxlen=${1#*/}
+  fi
+  if [ "$ipaddr" = "" ]; then
+    echo "$1 is bad IP address." 1>&2
+    exit 1
+  fi
+fi
+shift
+
+peer=$1
+if [ "$peer" != "" ]; then
+  if [ "$pfxlen" != "" -a "$pfxlen" != "32" ]; then
+    echo "Peer address with non-trivial netmask." 1>&2
+    exit 1
+  fi
+  pfx="$ipaddr peer $peer"
+else
+  if [ "$ipaddr" = "" ]; then
+    echo "Missing IP address argument." 1>&2
+    exit 1
+  fi
+  if [ "$pfxlen" = "" ]; then
+    ABCMaskLen $ipaddr
+    pfxlen=$?
+  fi
+  pfx="$ipaddr/$pfxlen"
+fi
+
+if [ "$ldev" = "$dev" -a "$ipaddr" != "" ]; then
+  label=
+fi
+
+if [ $deleting -ne 0 ]; then
+  ip addr del $pfx dev $dev $label || exit 1
+  if [ $fwd -eq 0 ]; then RestartRDISC; fi
+  exit 0
+fi
+
+
+if ! ip link set up dev $dev ; then
+  echo "Error: cannot enable interface $dev." 1>&2
+  exit 1
+fi
+if [ "$ipaddr" = "" ]; then exit 0; fi
+
+if ! arping -q -c 2 -w 3 -D -I $dev $ipaddr ; then
+  echo "Error: some host already uses address $ipaddr on $dev." 1>&2
+  exit 1
+fi
+
+if ! ip address add $pfx brd + dev $dev $label; then
+  echo "Error: failed to add $pfx on $dev." 1>&2
+  exit 1
+fi
+
+arping -q -A -c 1 -I $dev $ipaddr
+noarp=$?
+( sleep 2 ;
+  arping -q -U -c 1 -I $dev $ipaddr ) >/dev/null 2>&1 </dev/null &
+
+ip route add unreachable 224.0.0.0/24 >/dev/null 2>&1
+ip route add unreachable 255.255.255.255 >/dev/null 2>&1
+if [ "`ip link ls $dev | grep -c MULTICAST`" -ge 1 ]; then
+  ip route add 224.0.0.0/4 dev $dev scope global >/dev/null 2>&1
+fi
+
+if [ $fwd -eq 0 ]; then
+  if [ $noarp -eq 0 ]; then
+    ip ro append default dev $dev metric 30000 scope global
+  elif [ "$peer" != "" ]; then
+    if ping -q -c 2 -w 4 $peer ; then
+      ip ro append default via $peer dev $dev metric 30001
+    fi
+  fi
+  RestartRDISC
+fi
+
+exit 0
diff --git a/ip/ila_common.h b/ip/ila_common.h
new file mode 100644
index 0000000..f99c267
--- /dev/null
+++ b/ip/ila_common.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ILA_COMMON_H_
+#define _ILA_COMMON_H_
+
+#include <linux/ila.h>
+#include <string.h>
+
+static inline char *ila_csum_mode2name(__u8 csum_mode)
+{
+	switch (csum_mode) {
+	case ILA_CSUM_ADJUST_TRANSPORT:
+		return "adj-transport";
+	case ILA_CSUM_NEUTRAL_MAP:
+		return "neutral-map";
+	case ILA_CSUM_NO_ACTION:
+		return "no-action";
+	case ILA_CSUM_NEUTRAL_MAP_AUTO:
+		return "neutral-map-auto";
+	default:
+		return "unknown";
+	}
+}
+
+static inline int ila_csum_name2mode(char *name)
+{
+	if (strcmp(name, "adj-transport") == 0)
+		return ILA_CSUM_ADJUST_TRANSPORT;
+	else if (strcmp(name, "neutral-map") == 0)
+		return ILA_CSUM_NEUTRAL_MAP;
+	else if (strcmp(name, "neutral-map-auto") == 0)
+		return ILA_CSUM_NEUTRAL_MAP_AUTO;
+	else if (strcmp(name, "no-action") == 0)
+		return ILA_CSUM_NO_ACTION;
+	else if (strcmp(name, "neutral-map-auto") == 0)
+		return ILA_CSUM_NEUTRAL_MAP_AUTO;
+	else
+		return -1;
+}
+
+static inline char *ila_ident_type2name(__u8 ident_type)
+{
+	switch (ident_type) {
+	case ILA_ATYPE_IID:
+		return "iid";
+	case ILA_ATYPE_LUID:
+		return "luid";
+	case ILA_ATYPE_VIRT_V4:
+		return "virt-v4";
+	case ILA_ATYPE_VIRT_UNI_V6:
+		return "virt-uni-v6";
+	case ILA_ATYPE_VIRT_MULTI_V6:
+		return "virt-multi-v6";
+	case ILA_ATYPE_NONLOCAL_ADDR:
+		return "nonlocal-addr";
+	case ILA_ATYPE_USE_FORMAT:
+		return "use-format";
+	default:
+		return "unknown";
+	}
+}
+
+static inline int ila_ident_name2type(char *name)
+{
+	if (!strcmp(name, "luid"))
+		return ILA_ATYPE_LUID;
+	else if (!strcmp(name, "use-format"))
+		return ILA_ATYPE_USE_FORMAT;
+#if 0 /* No kernel support for configuring these yet */
+	else if (!strcmp(name, "iid"))
+		return ILA_ATYPE_IID;
+	else if (!strcmp(name, "virt-v4"))
+		return ILA_ATYPE_VIRT_V4;
+	else if (!strcmp(name, "virt-uni-v6"))
+		return ILA_ATYPE_VIRT_UNI_V6;
+	else if (!strcmp(name, "virt-multi-v6"))
+		return ILA_ATYPE_VIRT_MULTI_V6;
+	else if (!strcmp(name, "nonlocal-addr"))
+		return ILA_ATYPE_NONLOCAL_ADDR;
+#endif
+	else
+		return -1;
+}
+
+static inline char *ila_hook_type2name(__u8 hook_type)
+{
+	switch (hook_type) {
+	case ILA_HOOK_ROUTE_OUTPUT:
+		return "output";
+	case ILA_HOOK_ROUTE_INPUT:
+		return "input";
+	default:
+		return "unknown";
+	}
+}
+
+static inline int ila_hook_name2type(char *name)
+{
+	if (!strcmp(name, "output"))
+		return ILA_HOOK_ROUTE_OUTPUT;
+	else if (!strcmp(name, "input"))
+		return ILA_HOOK_ROUTE_INPUT;
+	else
+		return -1;
+}
+
+#endif /* _ILA_COMMON_H_ */
diff --git a/ip/ip.c b/ip/ip.c
new file mode 100644
index 0000000..fed26f8
--- /dev/null
+++ b/ip/ip.c
@@ -0,0 +1,326 @@
+/*
+ * ip.c		"ip" utility frontend.
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <errno.h>
+
+#include "SNAPSHOT.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "namespace.h"
+#include "color.h"
+
+int preferred_family = AF_UNSPEC;
+int human_readable;
+int use_iec;
+int show_stats;
+int show_details;
+int oneline;
+int brief;
+int json;
+int timestamp;
+int force;
+int max_flush_loops = 10;
+int batch_mode;
+int numeric;
+bool do_all;
+
+struct rtnl_handle rth = { .fd = -1 };
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }\n"
+		"       ip [ -force ] -batch filename\n"
+		"where  OBJECT := { link | address | addrlabel | route | rule | neigh | ntable |\n"
+		"                   tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm |\n"
+		"                   netns | l2tp | fou | macsec | tcp_metrics | token | netconf | ila |\n"
+		"                   vrf | sr | nexthop }\n"
+		"       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n"
+		"                    -h[uman-readable] | -iec | -j[son] | -p[retty] |\n"
+		"                    -f[amily] { inet | inet6 | mpls | bridge | link } |\n"
+		"                    -4 | -6 | -I | -D | -M | -B | -0 |\n"
+		"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] |\n"
+		"                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |\n"
+		"                    -rc[vbuf] [size] | -n[etns] name | -N[umeric] | -a[ll] |\n"
+		"                    -c[olor]}\n");
+	exit(-1);
+}
+
+static int do_help(int argc, char **argv)
+{
+	usage();
+	return 0;
+}
+
+static const struct cmd {
+	const char *cmd;
+	int (*func)(int argc, char **argv);
+} cmds[] = {
+	{ "address",	do_ipaddr },
+	{ "addrlabel",	do_ipaddrlabel },
+	{ "maddress",	do_multiaddr },
+	{ "route",	do_iproute },
+	{ "rule",	do_iprule },
+	{ "neighbor",	do_ipneigh },
+	{ "neighbour",	do_ipneigh },
+	{ "ntable",	do_ipntable },
+	{ "ntbl",	do_ipntable },
+	{ "link",	do_iplink },
+	{ "l2tp",	do_ipl2tp },
+	{ "fou",	do_ipfou },
+	{ "ila",	do_ipila },
+	{ "macsec",	do_ipmacsec },
+	{ "tunnel",	do_iptunnel },
+	{ "tunl",	do_iptunnel },
+	{ "tuntap",	do_iptuntap },
+	{ "tap",	do_iptuntap },
+	{ "token",	do_iptoken },
+	{ "tcpmetrics",	do_tcp_metrics },
+	{ "tcp_metrics", do_tcp_metrics },
+	{ "monitor",	do_ipmonitor },
+	{ "xfrm",	do_xfrm },
+	{ "mroute",	do_multiroute },
+	{ "mrule",	do_multirule },
+	{ "netns",	do_netns },
+	{ "netconf",	do_ipnetconf },
+	{ "vrf",	do_ipvrf},
+	{ "sr",		do_seg6 },
+	{ "nexthop",	do_ipnh },
+	{ "help",	do_help },
+	{ 0 }
+};
+
+static int do_cmd(const char *argv0, int argc, char **argv)
+{
+	const struct cmd *c;
+
+	for (c = cmds; c->cmd; ++c) {
+		if (matches(argv0, c->cmd) == 0)
+			return -(c->func(argc-1, argv+1));
+	}
+
+	fprintf(stderr, "Object \"%s\" is unknown, try \"ip help\".\n", argv0);
+	return EXIT_FAILURE;
+}
+
+static int batch(const char *name)
+{
+	char *line = NULL;
+	size_t len = 0;
+	int ret = EXIT_SUCCESS;
+	int orig_family = preferred_family;
+
+	batch_mode = 1;
+
+	if (name && strcmp(name, "-") != 0) {
+		if (freopen(name, "r", stdin) == NULL) {
+			fprintf(stderr,
+				"Cannot open file \"%s\" for reading: %s\n",
+				name, strerror(errno));
+			return EXIT_FAILURE;
+		}
+	}
+
+	if (rtnl_open(&rth, 0) < 0) {
+		fprintf(stderr, "Cannot open rtnetlink\n");
+		return EXIT_FAILURE;
+	}
+
+	cmdlineno = 0;
+	while (getcmdline(&line, &len, stdin) != -1) {
+		char *largv[100];
+		int largc;
+
+		preferred_family = orig_family;
+
+		largc = makeargs(line, largv, 100);
+		if (largc == 0)
+			continue;	/* blank line */
+
+		if (do_cmd(largv[0], largc, largv)) {
+			fprintf(stderr, "Command failed %s:%d\n",
+				name, cmdlineno);
+			ret = EXIT_FAILURE;
+			if (!force)
+				break;
+		}
+	}
+	if (line)
+		free(line);
+
+	rtnl_close(&rth);
+	return ret;
+}
+
+
+int main(int argc, char **argv)
+{
+	char *basename;
+	char *batch_file = NULL;
+	int color = 0;
+
+	/* to run vrf exec without root, capabilities might be set, drop them
+	 * if not needed as the first thing.
+	 * execv will drop them for the child command.
+	 * vrf exec requires:
+	 * - cap_dac_override to create the cgroup subdir in /sys
+	 * - cap_sys_admin to load the BPF program
+	 * - cap_net_admin to set the socket into the cgroup
+	 */
+	if (argc < 3 || strcmp(argv[1], "vrf") != 0 ||
+			strcmp(argv[2], "exec") != 0)
+		drop_cap();
+
+	basename = strrchr(argv[0], '/');
+	if (basename == NULL)
+		basename = argv[0];
+	else
+		basename++;
+
+	while (argc > 1) {
+		char *opt = argv[1];
+
+		if (strcmp(opt, "--") == 0) {
+			argc--; argv++;
+			break;
+		}
+		if (opt[0] != '-')
+			break;
+		if (opt[1] == '-')
+			opt++;
+		if (matches(opt, "-loops") == 0) {
+			argc--;
+			argv++;
+			if (argc <= 1)
+				usage();
+			max_flush_loops = atoi(argv[1]);
+		} else if (matches(opt, "-family") == 0) {
+			argc--;
+			argv++;
+			if (argc <= 1)
+				usage();
+			if (strcmp(argv[1], "help") == 0)
+				usage();
+			else
+				preferred_family = read_family(argv[1]);
+			if (preferred_family == AF_UNSPEC)
+				invarg("invalid protocol family", argv[1]);
+		} else if (strcmp(opt, "-4") == 0) {
+			preferred_family = AF_INET;
+		} else if (strcmp(opt, "-6") == 0) {
+			preferred_family = AF_INET6;
+		} else if (strcmp(opt, "-0") == 0) {
+			preferred_family = AF_PACKET;
+		} else if (strcmp(opt, "-D") == 0) {
+			preferred_family = AF_DECnet;
+		} else if (strcmp(opt, "-M") == 0) {
+			preferred_family = AF_MPLS;
+		} else if (strcmp(opt, "-B") == 0) {
+			preferred_family = AF_BRIDGE;
+		} else if (matches(opt, "-human") == 0 ||
+			   matches(opt, "-human-readable") == 0) {
+			++human_readable;
+		} else if (matches(opt, "-iec") == 0) {
+			++use_iec;
+		} else if (matches(opt, "-stats") == 0 ||
+			   matches(opt, "-statistics") == 0) {
+			++show_stats;
+		} else if (matches(opt, "-details") == 0) {
+			++show_details;
+		} else if (matches(opt, "-resolve") == 0) {
+			++resolve_hosts;
+		} else if (matches(opt, "-oneline") == 0) {
+			++oneline;
+		} else if (matches(opt, "-timestamp") == 0) {
+			++timestamp;
+		} else if (matches(opt, "-tshort") == 0) {
+			++timestamp;
+			++timestamp_short;
+		} else if (matches(opt, "-Version") == 0) {
+			printf("ip utility, iproute2-ss%s\n", SNAPSHOT);
+			exit(0);
+		} else if (matches(opt, "-force") == 0) {
+			++force;
+		} else if (matches(opt, "-batch") == 0) {
+			argc--;
+			argv++;
+			if (argc <= 1)
+				usage();
+			batch_file = argv[1];
+		} else if (matches(opt, "-brief") == 0) {
+			++brief;
+		} else if (matches(opt, "-json") == 0) {
+			++json;
+		} else if (matches(opt, "-pretty") == 0) {
+			++pretty;
+		} else if (matches(opt, "-rcvbuf") == 0) {
+			unsigned int size;
+
+			argc--;
+			argv++;
+			if (argc <= 1)
+				usage();
+			if (get_unsigned(&size, argv[1], 0)) {
+				fprintf(stderr, "Invalid rcvbuf size '%s'\n",
+					argv[1]);
+				exit(-1);
+			}
+			rcvbuf = size;
+		} else if (matches_color(opt, &color)) {
+		} else if (matches(opt, "-help") == 0) {
+			usage();
+		} else if (matches(opt, "-netns") == 0) {
+			NEXT_ARG();
+			if (netns_switch(argv[1]))
+				exit(-1);
+		} else if (matches(opt, "-Numeric") == 0) {
+			++numeric;
+		} else if (matches(opt, "-all") == 0) {
+			do_all = true;
+		} else {
+			fprintf(stderr,
+				"Option \"%s\" is unknown, try \"ip -help\".\n",
+				opt);
+			exit(-1);
+		}
+		argc--;	argv++;
+	}
+
+	_SL_ = oneline ? "\\" : "\n";
+
+	check_enable_color(color, json);
+
+	if (batch_file)
+		return batch(batch_file);
+
+	if (rtnl_open(&rth, 0) < 0)
+		exit(1);
+
+	rtnl_set_strict_dump(&rth);
+
+	if (strlen(basename) > 2)
+		return do_cmd(basename+2, argc, argv);
+
+	if (argc > 1)
+		return do_cmd(argv[1], argc-1, argv+1);
+
+	rtnl_close(&rth);
+	usage();
+}
diff --git a/ip/ip6tunnel.c b/ip/ip6tunnel.c
new file mode 100644
index 0000000..5399f91
--- /dev/null
+++ b/ip/ip6tunnel.c
@@ -0,0 +1,450 @@
+/*
+ * 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 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, see <http://www.gnu.org/licenses>.
+ */
+/*
+ * Author:
+ *	Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/ip.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+
+#include "utils.h"
+#include "tunnel.h"
+#include "ip_common.h"
+
+#define IP6_FLOWINFO_TCLASS	htonl(0x0FF00000)
+#define IP6_FLOWINFO_FLOWLABEL	htonl(0x000FFFFF)
+
+#define DEFAULT_TNL_HOP_LIMIT	(64)
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip -f inet6 tunnel { add | change | del | show } [ NAME ]\n"
+		"          [ mode { ip6ip6 | ipip6 | ip6gre | vti6 | any } ]\n"
+		"          [ remote ADDR local ADDR ] [ dev PHYS_DEV ]\n"
+		"          [ encaplimit ELIM ]\n"
+		"          [ hoplimit TTL ] [ tclass TCLASS ] [ flowlabel FLOWLABEL ]\n"
+		"          [ dscp inherit ]\n"
+		"          [ [no]allow-localremote ]\n"
+		"          [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n"
+		"\n"
+		"Where: NAME      := STRING\n"
+		"       ADDR      := IPV6_ADDRESS\n"
+		"       ELIM      := { none | 0..255 }(default=%d)\n"
+		"       TTL       := 0..255 (default=%d)\n"
+		"       TCLASS    := { 0x0..0xff | inherit }\n"
+		"       FLOWLABEL := { 0x0..0xfffff | inherit }\n"
+		"       KEY       := { DOTTED_QUAD | NUMBER }\n",
+		IPV6_DEFAULT_TNL_ENCAP_LIMIT,
+		DEFAULT_TNL_HOP_LIMIT);
+	exit(-1);
+}
+
+static void print_tunnel(const void *t)
+{
+	const struct ip6_tnl_parm2 *p = t;
+	SPRINT_BUF(b1);
+
+	/* Do not use format_host() for local addr,
+	 * symbolic name will not be useful.
+	 */
+	open_json_object(NULL);
+	print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname", "%s: ", p->name);
+	snprintf(b1, sizeof(b1), "%s/ipv6", tnl_strproto(p->proto));
+	print_string(PRINT_ANY, "mode", "%s ", b1);
+	print_string(PRINT_FP, NULL, "%s", "remote ");
+	print_color_string(PRINT_ANY, COLOR_INET6, "remote", "%s ",
+			   format_host_r(AF_INET6, 16, &p->raddr, b1, sizeof(b1)));
+	print_string(PRINT_FP, NULL, "%s", "local ");
+	print_color_string(PRINT_ANY, COLOR_INET6, "local", "%s",
+			   rt_addr_n2a_r(AF_INET6, 16, &p->laddr, b1, sizeof(b1)));
+
+	if (p->link) {
+		const char *n = ll_index_to_name(p->link);
+
+		if (n)
+			print_string(PRINT_ANY, "link", " dev %s", n);
+	}
+
+	if (p->flags & IP6_TNL_F_IGN_ENCAP_LIMIT)
+		print_null(PRINT_ANY, "ip6_tnl_f_ign_encap_limit",
+			   " encaplimit none", NULL);
+	else
+		print_uint(PRINT_ANY, "encap_limit", " encaplimit %u",
+			   p->encap_limit);
+
+	if (p->hop_limit)
+		print_uint(PRINT_ANY, "hoplimit", " hoplimit %u", p->hop_limit);
+	else
+		print_string(PRINT_FP, "hoplimit", " hoplimit %s", "inherit");
+
+	if (p->flags & IP6_TNL_F_USE_ORIG_TCLASS) {
+		print_null(PRINT_ANY, "ip6_tnl_f_use_orig_tclass",
+			   " tclass inherit", NULL);
+	} else {
+		__u32 val = ntohl(p->flowinfo & IP6_FLOWINFO_TCLASS);
+
+		snprintf(b1, sizeof(b1), "0x%02x", (__u8)(val >> 20));
+		print_string(PRINT_ANY, "tclass", " tclass %s", b1);
+	}
+
+	if (p->flags & IP6_TNL_F_USE_ORIG_FLOWLABEL) {
+		print_null(PRINT_ANY, "ip6_tnl_f_use_orig_flowlabel",
+			   " flowlabel inherit", NULL);
+	} else {
+		__u32 val = ntohl(p->flowinfo & IP6_FLOWINFO_FLOWLABEL);
+
+		snprintf(b1, sizeof(b1), "0x%05x", val);
+		print_string(PRINT_ANY, "flowlabel", " flowlabel %s", b1);
+	}
+
+	snprintf(b1, sizeof(b1), "0x%08x", ntohl(p->flowinfo));
+	print_string(PRINT_ANY, "flowinfo", " (flowinfo %s)", b1);
+
+	if (p->flags & IP6_TNL_F_RCV_DSCP_COPY)
+		print_null(PRINT_ANY, "ip6_tnl_f_rcv_dscp_copy",
+			   " dscp inherit", NULL);
+
+	if (p->flags & IP6_TNL_F_ALLOW_LOCAL_REMOTE)
+		print_null(PRINT_ANY, "ip6_tnl_f_allow_local_remote",
+			   " allow-localremote", NULL);
+
+	tnl_print_gre_flags(p->proto, p->i_flags, p->o_flags,
+			    p->i_key, p->o_key);
+
+	close_json_object();
+}
+
+static int parse_args(int argc, char **argv, int cmd, struct ip6_tnl_parm2 *p)
+{
+	int count = 0;
+	const char *medium = NULL;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "ipv6/ipv6") == 0 ||
+			    strcmp(*argv, "ip6ip6") == 0)
+				p->proto = IPPROTO_IPV6;
+			else if (strcmp(*argv, "vti6") == 0) {
+				p->proto = IPPROTO_IPV6;
+				p->i_flags |= VTI_ISVTI;
+			} else if (strcmp(*argv, "ip/ipv6") == 0 ||
+				 strcmp(*argv, "ipv4/ipv6") == 0 ||
+				 strcmp(*argv, "ipip6") == 0 ||
+				 strcmp(*argv, "ip4ip6") == 0)
+				p->proto = IPPROTO_IPIP;
+			else if (strcmp(*argv, "ip6gre") == 0 ||
+				 strcmp(*argv, "gre/ipv6") == 0)
+				p->proto = IPPROTO_GRE;
+			else if (strcmp(*argv, "any/ipv6") == 0 ||
+				 strcmp(*argv, "any") == 0)
+				p->proto = 0;
+			else {
+				fprintf(stderr, "Unknown tunnel mode \"%s\"\n", *argv);
+				exit(-1);
+			}
+		} else if (strcmp(*argv, "remote") == 0) {
+			inet_prefix raddr;
+
+			NEXT_ARG();
+			get_addr(&raddr, *argv, AF_INET6);
+			memcpy(&p->raddr, &raddr.data, sizeof(p->raddr));
+		} else if (strcmp(*argv, "local") == 0) {
+			inet_prefix laddr;
+
+			NEXT_ARG();
+			get_addr(&laddr, *argv, AF_INET6);
+			memcpy(&p->laddr, &laddr.data, sizeof(p->laddr));
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			medium = *argv;
+		} else if (strcmp(*argv, "encaplimit") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "none") == 0) {
+				p->flags |= IP6_TNL_F_IGN_ENCAP_LIMIT;
+			} else {
+				__u8 uval;
+
+				if (get_u8(&uval, *argv, 0) < -1)
+					invarg("invalid ELIM", *argv);
+				p->encap_limit = uval;
+				p->flags &= ~IP6_TNL_F_IGN_ENCAP_LIMIT;
+			}
+		} else if (strcmp(*argv, "hoplimit") == 0 ||
+			   strcmp(*argv, "ttl") == 0 ||
+			   strcmp(*argv, "hlim") == 0) {
+			__u8 uval;
+
+			NEXT_ARG();
+			if (get_u8(&uval, *argv, 0))
+				invarg("invalid TTL", *argv);
+			p->hop_limit = uval;
+		} else if (strcmp(*argv, "tclass") == 0 ||
+			   strcmp(*argv, "tc") == 0 ||
+			   strcmp(*argv, "tos") == 0 ||
+			   matches(*argv, "dsfield") == 0) {
+			__u8 uval;
+
+			NEXT_ARG();
+			p->flowinfo &= ~IP6_FLOWINFO_TCLASS;
+			if (strcmp(*argv, "inherit") == 0)
+				p->flags |= IP6_TNL_F_USE_ORIG_TCLASS;
+			else {
+				if (get_u8(&uval, *argv, 16))
+					invarg("invalid TClass", *argv);
+				p->flowinfo |= htonl((__u32)uval << 20) & IP6_FLOWINFO_TCLASS;
+				p->flags &= ~IP6_TNL_F_USE_ORIG_TCLASS;
+			}
+		} else if (strcmp(*argv, "flowlabel") == 0 ||
+			   strcmp(*argv, "fl") == 0) {
+			__u32 uval;
+
+			NEXT_ARG();
+			p->flowinfo &= ~IP6_FLOWINFO_FLOWLABEL;
+			if (strcmp(*argv, "inherit") == 0)
+				p->flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL;
+			else {
+				if (get_u32(&uval, *argv, 16))
+					invarg("invalid Flowlabel", *argv);
+				if (uval > 0xFFFFF)
+					invarg("invalid Flowlabel", *argv);
+				p->flowinfo |= htonl(uval) & IP6_FLOWINFO_FLOWLABEL;
+				p->flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL;
+			}
+		} else if (strcmp(*argv, "dscp") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0)
+				invarg("not inherit", *argv);
+			p->flags |= IP6_TNL_F_RCV_DSCP_COPY;
+		} else if (strcmp(*argv, "allow-localremote") == 0) {
+			p->flags |= IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+		} else if (strcmp(*argv, "noallow-localremote") == 0) {
+			p->flags &= ~IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+		} else if (strcmp(*argv, "key") == 0) {
+			NEXT_ARG();
+			p->i_flags |= GRE_KEY;
+			p->o_flags |= GRE_KEY;
+			p->i_key = p->o_key = tnl_parse_key("key", *argv);
+		} else if (strcmp(*argv, "ikey") == 0) {
+			NEXT_ARG();
+			p->i_flags |= GRE_KEY;
+			p->i_key = tnl_parse_key("ikey", *argv);
+		} else if (strcmp(*argv, "okey") == 0) {
+			NEXT_ARG();
+			p->o_flags |= GRE_KEY;
+			p->o_key = tnl_parse_key("okey", *argv);
+		} else if (strcmp(*argv, "seq") == 0) {
+			p->i_flags |= GRE_SEQ;
+			p->o_flags |= GRE_SEQ;
+		} else if (strcmp(*argv, "iseq") == 0) {
+			p->i_flags |= GRE_SEQ;
+		} else if (strcmp(*argv, "oseq") == 0) {
+			p->o_flags |= GRE_SEQ;
+		} else if (strcmp(*argv, "csum") == 0) {
+			p->i_flags |= GRE_CSUM;
+			p->o_flags |= GRE_CSUM;
+		} else if (strcmp(*argv, "icsum") == 0) {
+			p->i_flags |= GRE_CSUM;
+		} else if (strcmp(*argv, "ocsum") == 0) {
+			p->o_flags |= GRE_CSUM;
+		} else {
+			if (strcmp(*argv, "name") == 0) {
+				NEXT_ARG();
+			} else if (matches(*argv, "help") == 0)
+				usage();
+			if (p->name[0])
+				duparg2("name", *argv);
+			if (get_ifname(p->name, *argv))
+				invarg("\"name\" not a valid ifname", *argv);
+			if (cmd == SIOCCHGTUNNEL && count == 0) {
+				struct ip6_tnl_parm2 old_p = {};
+
+				if (tnl_get_ioctl(*argv, &old_p))
+					return -1;
+				*p = old_p;
+			}
+		}
+		count++;
+		argc--; argv++;
+	}
+	if (medium) {
+		p->link = ll_name_to_index(medium);
+		if (!p->link)
+			return nodev(medium);
+	}
+	return 0;
+}
+
+static void ip6_tnl_parm_init(struct ip6_tnl_parm2 *p, int apply_default)
+{
+	memset(p, 0, sizeof(*p));
+	p->proto = IPPROTO_IPV6;
+	if (apply_default) {
+		p->hop_limit = DEFAULT_TNL_HOP_LIMIT;
+		p->encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
+	}
+}
+
+static void ip6_tnl_parm_initialize(const struct tnl_print_nlmsg_info *info)
+{
+	const struct ifinfomsg *ifi = info->ifi;
+	const struct ip6_tnl_parm2 *p1 = info->p1;
+	struct ip6_tnl_parm2 *p2 = info->p2;
+
+	ip6_tnl_parm_init(p2, 0);
+	if (ifi->ifi_type == ARPHRD_IP6GRE)
+		p2->proto = IPPROTO_GRE;
+	p2->link = ifi->ifi_index;
+	strcpy(p2->name, p1->name);
+}
+
+static bool ip6_tnl_parm_match(const struct tnl_print_nlmsg_info *info)
+{
+	const struct ip6_tnl_parm2 *p1 = info->p1;
+	const struct ip6_tnl_parm2 *p2 = info->p2;
+
+	return ((!p1->link || p1->link == p2->link) &&
+		(!p1->name[0] || strcmp(p1->name, p2->name) == 0) &&
+		(IN6_IS_ADDR_UNSPECIFIED(&p1->laddr) ||
+		 IN6_ARE_ADDR_EQUAL(&p1->laddr, &p2->laddr)) &&
+		(IN6_IS_ADDR_UNSPECIFIED(&p1->raddr) ||
+		 IN6_ARE_ADDR_EQUAL(&p1->raddr, &p2->raddr)) &&
+		(!p1->proto || !p2->proto || p1->proto == p2->proto) &&
+		(!p1->encap_limit || p1->encap_limit == p2->encap_limit) &&
+		(!p1->hop_limit || p1->hop_limit == p2->hop_limit) &&
+		(!(p1->flowinfo & IP6_FLOWINFO_TCLASS) ||
+		 !((p1->flowinfo ^ p2->flowinfo) & IP6_FLOWINFO_TCLASS)) &&
+		(!(p1->flowinfo & IP6_FLOWINFO_FLOWLABEL) ||
+		 !((p1->flowinfo ^ p2->flowinfo) & IP6_FLOWINFO_FLOWLABEL)) &&
+		(!p1->flags || (p1->flags & p2->flags)));
+}
+
+static int do_show(int argc, char **argv)
+{
+	struct ip6_tnl_parm2 p, p1;
+
+	ip6_tnl_parm_init(&p, 0);
+	p.proto = 0;  /* default to any */
+
+	if (parse_args(argc, argv, SIOCGETTUNNEL, &p) < 0)
+		return -1;
+
+	if (!p.name[0] || show_stats) {
+		struct tnl_print_nlmsg_info info = {
+			.p1    = &p,
+			.p2    = &p1,
+			.init  = ip6_tnl_parm_initialize,
+			.match = ip6_tnl_parm_match,
+			.print = print_tunnel,
+		};
+
+		return do_tunnels_list(&info);
+	}
+
+	if (tnl_get_ioctl(p.name, &p))
+		return -1;
+
+	print_tunnel(&p);
+	return 0;
+}
+
+static int do_add(int cmd, int argc, char **argv)
+{
+	struct ip6_tnl_parm2 p;
+	const char *basedev = "ip6tnl0";
+
+	ip6_tnl_parm_init(&p, 1);
+
+	if (parse_args(argc, argv, cmd, &p) < 0)
+		return -1;
+
+	if (!*p.name)
+		fprintf(stderr, "Tunnel interface name not specified\n");
+
+	if (p.proto == IPPROTO_GRE)
+		basedev = "ip6gre0";
+	else if (p.i_flags & VTI_ISVTI)
+		basedev = "ip6_vti0";
+
+	return tnl_add_ioctl(cmd, basedev, p.name, &p);
+}
+
+static int do_del(int argc, char **argv)
+{
+	struct ip6_tnl_parm2 p;
+	const char *basedev = "ip6tnl0";
+
+	ip6_tnl_parm_init(&p, 1);
+
+	if (parse_args(argc, argv, SIOCDELTUNNEL, &p) < 0)
+		return -1;
+
+	if (p.proto == IPPROTO_GRE)
+		basedev = "ip6gre0";
+	else if (p.i_flags & VTI_ISVTI)
+		basedev = "ip6_vti0";
+
+	return tnl_del_ioctl(basedev, p.name, &p);
+}
+
+int do_ip6tunnel(int argc, char **argv)
+{
+	switch (preferred_family) {
+	case AF_UNSPEC:
+		preferred_family = AF_INET6;
+		break;
+	case AF_INET6:
+		break;
+	default:
+		fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
+		exit(-1);
+	}
+
+	if (argc > 0) {
+		if (matches(*argv, "add") == 0)
+			return do_add(SIOCADDTUNNEL, argc - 1, argv + 1);
+		if (matches(*argv, "change") == 0)
+			return do_add(SIOCCHGTUNNEL, argc - 1, argv + 1);
+		if (matches(*argv, "delete") == 0)
+			return do_del(argc - 1, argv + 1);
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return do_show(argc - 1, argv + 1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return do_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip -f inet6 tunnel help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ip_common.h b/ip/ip_common.h
new file mode 100644
index 0000000..879287e
--- /dev/null
+++ b/ip/ip_common.h
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _IP_COMMON_H_
+#define _IP_COMMON_H_
+
+#include <stdbool.h>
+
+#include "json_print.h"
+
+struct link_filter {
+	int ifindex;
+	int family;
+	int oneline;
+	int showqueue;
+	inet_prefix pfx;
+	int scope, scopemask;
+	int flags, flagmask;
+	int up;
+	char *label;
+	int flushed;
+	char *flushb;
+	int flushp;
+	int flushe;
+	int group;
+	int master;
+	char *kind;
+	char *slave_kind;
+	int target_nsid;
+};
+
+int get_operstate(const char *name);
+int print_linkinfo(struct nlmsghdr *n, void *arg);
+int print_addrinfo(struct nlmsghdr *n, void *arg);
+int print_addrlabel(struct nlmsghdr *n, void *arg);
+int print_neigh(struct nlmsghdr *n, void *arg);
+int ipaddr_list_link(int argc, char **argv);
+void ipaddr_get_vf_rate(int, int *, int *, const char *);
+void iplink_usage(void) __attribute__((noreturn));
+
+void iproute_reset_filter(int ifindex);
+void ipmroute_reset_filter(int ifindex);
+void ipaddr_reset_filter(int oneline, int ifindex);
+void ipneigh_reset_filter(int ifindex);
+void ipnetconf_reset_filter(int ifindex);
+
+int print_route(struct nlmsghdr *n, void *arg);
+int print_mroute(struct nlmsghdr *n, void *arg);
+int print_prefix(struct nlmsghdr *n, void *arg);
+int print_rule(struct nlmsghdr *n, void *arg);
+int print_netconf(struct rtnl_ctrl_data *ctrl,
+		  struct nlmsghdr *n, void *arg);
+int print_nexthop(struct nlmsghdr *n, void *arg);
+void netns_map_init(void);
+void netns_nsid_socket_init(void);
+int print_nsid(struct nlmsghdr *n, void *arg);
+char *get_name_from_nsid(int nsid);
+int get_netnsid_from_name(const char *name);
+int set_netnsid_from_name(const char *name, int nsid);
+int do_ipaddr(int argc, char **argv);
+int do_ipaddrlabel(int argc, char **argv);
+int do_iproute(int argc, char **argv);
+int do_iprule(int argc, char **argv);
+int do_ipneigh(int argc, char **argv);
+int do_ipntable(int argc, char **argv);
+int do_iptunnel(int argc, char **argv);
+int do_ip6tunnel(int argc, char **argv);
+int do_iptuntap(int argc, char **argv);
+int do_iplink(int argc, char **argv);
+int do_ipmacsec(int argc, char **argv);
+int do_ipmonitor(int argc, char **argv);
+int do_multiaddr(int argc, char **argv);
+int do_multiroute(int argc, char **argv);
+int do_multirule(int argc, char **argv);
+int do_netns(int argc, char **argv);
+int do_xfrm(int argc, char **argv);
+int do_ipl2tp(int argc, char **argv);
+int do_ipfou(int argc, char **argv);
+int do_ipila(int argc, char **argv);
+int do_tcp_metrics(int argc, char **argv);
+int do_ipnetconf(int argc, char **argv);
+int do_iptoken(int argc, char **argv);
+int do_ipvrf(int argc, char **argv);
+void vrf_reset(void);
+int netns_identify_pid(const char *pidstr, char *name, int len);
+int do_seg6(int argc, char **argv);
+int do_ipnh(int argc, char **argv);
+
+int iplink_get(char *name, __u32 filt_mask);
+int iplink_ifla_xstats(int argc, char **argv);
+
+int ip_link_list(req_filter_fn_t filter_fn, struct nlmsg_chain *linfo);
+void free_nlmsg_chain(struct nlmsg_chain *info);
+
+static inline int rtm_get_table(struct rtmsg *r, struct rtattr **tb)
+{
+	__u32 table = r->rtm_table;
+
+	if (tb[RTA_TABLE])
+		table = rta_getattr_u32(tb[RTA_TABLE]);
+	return table;
+}
+
+extern struct rtnl_handle rth;
+
+struct iplink_req {
+	struct nlmsghdr		n;
+	struct ifinfomsg	i;
+	char			buf[1024];
+};
+
+struct link_util {
+	struct link_util	*next;
+	const char		*id;
+	int			maxattr;
+	int			(*parse_opt)(struct link_util *, int, char **,
+					     struct nlmsghdr *);
+	void			(*print_opt)(struct link_util *, FILE *,
+					     struct rtattr *[]);
+	void			(*print_xstats)(struct link_util *, FILE *,
+						struct rtattr *);
+	void			(*print_help)(struct link_util *, int, char **,
+					      FILE *);
+	int			(*parse_ifla_xstats)(struct link_util *,
+						     int, char **);
+	int			(*print_ifla_xstats)(struct nlmsghdr *, void *);
+};
+
+struct link_util *get_link_kind(const char *kind);
+
+int iplink_parse(int argc, char **argv, struct iplink_req *req, char **type);
+
+/* iplink_bridge.c */
+void br_dump_bridge_id(const struct ifla_bridge_id *id, char *buf, size_t len);
+int bridge_parse_xstats(struct link_util *lu, int argc, char **argv);
+int bridge_print_xstats(struct nlmsghdr *n, void *arg);
+
+int bond_parse_xstats(struct link_util *lu, int argc, char **argv);
+int bond_print_xstats(struct nlmsghdr *n, void *arg);
+
+/* iproute_lwtunnel.c */
+int lwt_parse_encap(struct rtattr *rta, size_t len, int *argcp, char ***argvp,
+		    int encap_attr, int encap_type_attr);
+void lwt_print_encap(FILE *fp, struct rtattr *encap_type, struct rtattr *encap);
+
+/* iplink_xdp.c */
+int xdp_parse(int *argc, char ***argv, struct iplink_req *req, const char *ifname,
+	      bool generic, bool drv, bool offload);
+void xdp_dump(FILE *fp, struct rtattr *tb, bool link, bool details);
+
+/* iplink_vrf.c */
+__u32 ipvrf_get_table(const char *name);
+int name_is_vrf(const char *name);
+
+#ifndef	INFINITY_LIFE_TIME
+#define     INFINITY_LIFE_TIME      0xFFFFFFFFU
+#endif
+
+#ifndef LABEL_MAX_MASK
+#define     LABEL_MAX_MASK          0xFFFFFU
+#endif
+
+void print_num(FILE *fp, unsigned int width, uint64_t count);
+void print_rt_flags(FILE *fp, unsigned int flags);
+void print_rta_if(FILE *fp, const struct rtattr *rta, const char *prefix);
+void print_rta_gateway(FILE *fp, unsigned char family,
+		       const struct rtattr *rta);
+#endif /* _IP_COMMON_H_ */
diff --git a/ip/ipaddress.c b/ip/ipaddress.c
new file mode 100644
index 0000000..0c65f34
--- /dev/null
+++ b/ip/ipaddress.c
@@ -0,0 +1,2435 @@
+/*
+ * ipaddress.c		"ip address".
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <fnmatch.h>
+
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/if_infiniband.h>
+#include <linux/sockios.h>
+#include <linux/net_namespace.h>
+
+#include "utils.h"
+#include "rt_names.h"
+#include "utils.h"
+#include "ll_map.h"
+#include "ip_common.h"
+#include "color.h"
+
+enum {
+	IPADD_LIST,
+	IPADD_FLUSH,
+	IPADD_SAVE,
+};
+
+static struct link_filter filter;
+static int do_link;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	if (do_link)
+		iplink_usage();
+
+	fprintf(stderr,
+		"Usage: ip address {add|change|replace} IFADDR dev IFNAME [ LIFETIME ]\n"
+		"                                                      [ CONFFLAG-LIST ]\n"
+		"       ip address del IFADDR dev IFNAME [mngtmpaddr]\n"
+		"       ip address {save|flush} [ dev IFNAME ] [ scope SCOPE-ID ]\n"
+		"                            [ to PREFIX ] [ FLAG-LIST ] [ label LABEL ] [up]\n"
+		"       ip address [ show [ dev IFNAME ] [ scope SCOPE-ID ] [ master DEVICE ]\n"
+		"                         [ type TYPE ] [ to PREFIX ] [ FLAG-LIST ]\n"
+		"                         [ label LABEL ] [up] [ vrf NAME ] ]\n"
+		"       ip address {showdump|restore}\n"
+		"IFADDR := PREFIX | ADDR peer PREFIX\n"
+		"          [ broadcast ADDR ] [ anycast ADDR ]\n"
+		"          [ label IFNAME ] [ scope SCOPE-ID ] [ metric METRIC ]\n"
+		"SCOPE-ID := [ host | link | global | NUMBER ]\n"
+		"FLAG-LIST := [ FLAG-LIST ] FLAG\n"
+		"FLAG  := [ permanent | dynamic | secondary | primary |\n"
+		"           [-]tentative | [-]deprecated | [-]dadfailed | temporary |\n"
+		"           CONFFLAG-LIST ]\n"
+		"CONFFLAG-LIST := [ CONFFLAG-LIST ] CONFFLAG\n"
+		"CONFFLAG  := [ home | nodad | optimistic | mngtmpaddr | noprefixroute | autojoin ]\n"
+		"LIFETIME := [ valid_lft LFT ] [ preferred_lft LFT ]\n"
+		"LFT := forever | SECONDS\n"
+		"TYPE := { vlan | veth | vcan | vxcan | dummy | ifb | macvlan | macvtap |\n"
+		"          bridge | bond | ipoib | ip6tnl | ipip | sit | vxlan | lowpan |\n"
+		"          gre | gretap | erspan | ip6gre | ip6gretap | ip6erspan | vti |\n"
+		"          nlmon | can | bond_slave | ipvlan | geneve | bridge_slave |\n"
+		"          hsr | macsec | netdevsim }\n");
+
+	exit(-1);
+}
+
+static void print_link_flags(FILE *fp, unsigned int flags, unsigned int mdown)
+{
+	open_json_array(PRINT_ANY, is_json_context() ? "flags" : "<");
+	if (flags & IFF_UP && !(flags & IFF_RUNNING))
+		print_string(PRINT_ANY, NULL,
+			     flags ? "%s," : "%s", "NO-CARRIER");
+	flags &= ~IFF_RUNNING;
+#define _PF(f) if (flags&IFF_##f) {					\
+		flags &= ~IFF_##f ;					\
+		print_string(PRINT_ANY, NULL, flags ? "%s," : "%s", #f); }
+	_PF(LOOPBACK);
+	_PF(BROADCAST);
+	_PF(POINTOPOINT);
+	_PF(MULTICAST);
+	_PF(NOARP);
+	_PF(ALLMULTI);
+	_PF(PROMISC);
+	_PF(MASTER);
+	_PF(SLAVE);
+	_PF(DEBUG);
+	_PF(DYNAMIC);
+	_PF(AUTOMEDIA);
+	_PF(PORTSEL);
+	_PF(NOTRAILERS);
+	_PF(UP);
+	_PF(LOWER_UP);
+	_PF(DORMANT);
+	_PF(ECHO);
+#undef _PF
+	if (flags)
+		print_hex(PRINT_ANY, NULL, "%x", flags);
+	if (mdown)
+		print_string(PRINT_ANY, NULL, ",%s", "M-DOWN");
+	close_json_array(PRINT_ANY, "> ");
+}
+
+static const char *oper_states[] = {
+	"UNKNOWN", "NOTPRESENT", "DOWN", "LOWERLAYERDOWN",
+	"TESTING", "DORMANT",	 "UP"
+};
+
+static void print_operstate(FILE *f, __u8 state)
+{
+	if (state >= ARRAY_SIZE(oper_states)) {
+		if (is_json_context())
+			print_uint(PRINT_JSON, "operstate_index", NULL, state);
+		else
+			print_0xhex(PRINT_FP, NULL, "state %#llx", state);
+	} else if (brief) {
+		print_color_string(PRINT_ANY,
+				   oper_state_color(state),
+				   "operstate",
+				   "%-14s ",
+				   oper_states[state]);
+	} else {
+		if (is_json_context())
+			print_string(PRINT_JSON,
+				     "operstate",
+				     NULL, oper_states[state]);
+		else {
+			fprintf(f, "state ");
+			color_fprintf(f, oper_state_color(state),
+				      "%s ", oper_states[state]);
+		}
+	}
+}
+
+int get_operstate(const char *name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(oper_states); i++)
+		if (strcasecmp(name, oper_states[i]) == 0)
+			return i;
+	return -1;
+}
+
+static void print_queuelen(FILE *f, struct rtattr *tb[IFLA_MAX + 1])
+{
+	int qlen;
+
+	if (tb[IFLA_TXQLEN])
+		qlen = rta_getattr_u32(tb[IFLA_TXQLEN]);
+	else {
+		struct ifreq ifr = {};
+		int s = socket(AF_INET, SOCK_STREAM, 0);
+
+		if (s < 0)
+			return;
+
+		strcpy(ifr.ifr_name, rta_getattr_str(tb[IFLA_IFNAME]));
+		if (ioctl(s, SIOCGIFTXQLEN, &ifr) < 0) {
+			fprintf(stderr,
+				"ioctl(SIOCGIFTXQLEN) failed: %s\n",
+				strerror(errno));
+			close(s);
+			return;
+		}
+		close(s);
+		qlen = ifr.ifr_qlen;
+	}
+	if (qlen)
+		print_int(PRINT_ANY, "txqlen", "qlen %d", qlen);
+}
+
+static const char *link_modes[] = {
+	"DEFAULT", "DORMANT"
+};
+
+static void print_linkmode(FILE *f, struct rtattr *tb)
+{
+	unsigned int mode = rta_getattr_u8(tb);
+
+	if (mode >= ARRAY_SIZE(link_modes))
+		print_int(PRINT_ANY,
+			  "linkmode_index",
+			  "mode %d ",
+			  mode);
+	else
+		print_string(PRINT_ANY,
+			     "linkmode",
+			     "mode %s "
+			     , link_modes[mode]);
+}
+
+static char *parse_link_kind(struct rtattr *tb, bool slave)
+{
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	int attr = slave ? IFLA_INFO_SLAVE_KIND : IFLA_INFO_KIND;
+
+	parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb);
+
+	if (linkinfo[attr])
+		return RTA_DATA(linkinfo[attr]);
+
+	return "";
+}
+
+static int match_link_kind(struct rtattr **tb, const char *kind, bool slave)
+{
+	if (!tb[IFLA_LINKINFO])
+		return -1;
+
+	return strcmp(parse_link_kind(tb[IFLA_LINKINFO], slave), kind);
+}
+
+static void print_linktype(FILE *fp, struct rtattr *tb)
+{
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct link_util *lu;
+	struct link_util *slave_lu;
+	char slave[32];
+
+	parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb);
+	open_json_object("linkinfo");
+
+	if (linkinfo[IFLA_INFO_KIND]) {
+		const char *kind
+			= rta_getattr_str(linkinfo[IFLA_INFO_KIND]);
+
+		print_nl();
+		print_string(PRINT_ANY, "info_kind", "    %s ", kind);
+
+		lu = get_link_kind(kind);
+		if (lu && lu->print_opt) {
+			struct rtattr *attr[lu->maxattr+1], **data = NULL;
+
+			if (linkinfo[IFLA_INFO_DATA]) {
+				parse_rtattr_nested(attr, lu->maxattr,
+						    linkinfo[IFLA_INFO_DATA]);
+				data = attr;
+			}
+			open_json_object("info_data");
+			lu->print_opt(lu, fp, data);
+			close_json_object();
+
+			if (linkinfo[IFLA_INFO_XSTATS] && show_stats &&
+			    lu->print_xstats) {
+				open_json_object("info_xstats");
+				lu->print_xstats(lu, fp, linkinfo[IFLA_INFO_XSTATS]);
+				close_json_object();
+			}
+		}
+	}
+
+	if (linkinfo[IFLA_INFO_SLAVE_KIND]) {
+		const char *slave_kind
+			= rta_getattr_str(linkinfo[IFLA_INFO_SLAVE_KIND]);
+
+		print_nl();
+		print_string(PRINT_ANY,
+			     "info_slave_kind",
+			     "    %s_slave ",
+			     slave_kind);
+
+		snprintf(slave, sizeof(slave), "%s_slave", slave_kind);
+
+		slave_lu = get_link_kind(slave);
+		if (slave_lu && slave_lu->print_opt) {
+			struct rtattr *attr[slave_lu->maxattr+1], **data = NULL;
+
+			if (linkinfo[IFLA_INFO_SLAVE_DATA]) {
+				parse_rtattr_nested(attr, slave_lu->maxattr,
+						    linkinfo[IFLA_INFO_SLAVE_DATA]);
+				data = attr;
+			}
+			open_json_object("info_slave_data");
+			slave_lu->print_opt(slave_lu, fp, data);
+			close_json_object();
+		}
+	}
+	close_json_object();
+}
+
+static void print_af_spec(FILE *fp, struct rtattr *af_spec_attr)
+{
+	struct rtattr *inet6_attr;
+	struct rtattr *tb[IFLA_INET6_MAX + 1];
+
+	inet6_attr = parse_rtattr_one_nested(AF_INET6, af_spec_attr);
+	if (!inet6_attr)
+		return;
+
+	parse_rtattr_nested(tb, IFLA_INET6_MAX, inet6_attr);
+
+	if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
+		__u8 mode = rta_getattr_u8(tb[IFLA_INET6_ADDR_GEN_MODE]);
+		SPRINT_BUF(b1);
+
+		switch (mode) {
+		case IN6_ADDR_GEN_MODE_EUI64:
+			print_string(PRINT_ANY,
+				     "inet6_addr_gen_mode",
+				     "addrgenmode %s ",
+				     "eui64");
+			break;
+		case IN6_ADDR_GEN_MODE_NONE:
+			print_string(PRINT_ANY,
+				     "inet6_addr_gen_mode",
+				     "addrgenmode %s ",
+				     "none");
+			break;
+		case IN6_ADDR_GEN_MODE_STABLE_PRIVACY:
+			print_string(PRINT_ANY,
+				     "inet6_addr_gen_mode",
+				     "addrgenmode %s ",
+				     "stable_secret");
+			break;
+		case IN6_ADDR_GEN_MODE_RANDOM:
+			print_string(PRINT_ANY,
+				     "inet6_addr_gen_mode",
+				     "addrgenmode %s ",
+				     "random");
+			break;
+		default:
+			snprintf(b1, sizeof(b1), "%#.2hhx", mode);
+			print_string(PRINT_ANY,
+				     "inet6_addr_gen_mode",
+				     "addrgenmode %s ",
+				     b1);
+			break;
+		}
+	}
+}
+
+static void print_vf_stats64(FILE *fp, struct rtattr *vfstats);
+
+static void print_vfinfo(FILE *fp, struct ifinfomsg *ifi, struct rtattr *vfinfo)
+{
+	struct ifla_vf_mac *vf_mac;
+	struct ifla_vf_broadcast *vf_broadcast;
+	struct ifla_vf_tx_rate *vf_tx_rate;
+	struct rtattr *vf[IFLA_VF_MAX + 1] = {};
+
+	SPRINT_BUF(b1);
+
+	if (vfinfo->rta_type != IFLA_VF_INFO) {
+		fprintf(stderr, "BUG: rta type is %d\n", vfinfo->rta_type);
+		return;
+	}
+
+	parse_rtattr_nested(vf, IFLA_VF_MAX, vfinfo);
+
+	vf_mac = RTA_DATA(vf[IFLA_VF_MAC]);
+	vf_broadcast = RTA_DATA(vf[IFLA_VF_BROADCAST]);
+	vf_tx_rate = RTA_DATA(vf[IFLA_VF_TX_RATE]);
+
+	print_string(PRINT_FP, NULL, "%s    ", _SL_);
+	print_int(PRINT_ANY, "vf", "vf %d ", vf_mac->vf);
+
+	print_string(PRINT_ANY,
+		     "link_type",
+		     "    link/%s ",
+		     ll_type_n2a(ifi->ifi_type, b1, sizeof(b1)));
+
+	print_color_string(PRINT_ANY, COLOR_MAC,
+			   "address", "%s",
+			   ll_addr_n2a((unsigned char *) &vf_mac->mac,
+				       ifi->ifi_type == ARPHRD_ETHER ?
+				       ETH_ALEN : INFINIBAND_ALEN,
+				       ifi->ifi_type,
+				       b1, sizeof(b1)));
+
+	if (vf[IFLA_VF_BROADCAST]) {
+		if (ifi->ifi_flags&IFF_POINTOPOINT) {
+			print_string(PRINT_FP, NULL, " peer ", NULL);
+			print_bool(PRINT_JSON,
+				   "link_pointtopoint", NULL, true);
+		} else
+			print_string(PRINT_FP, NULL, " brd ", NULL);
+
+		print_color_string(PRINT_ANY, COLOR_MAC,
+				   "broadcast", "%s",
+				   ll_addr_n2a((unsigned char *) &vf_broadcast->broadcast,
+					       ifi->ifi_type == ARPHRD_ETHER ?
+					       ETH_ALEN : INFINIBAND_ALEN,
+					       ifi->ifi_type,
+					       b1, sizeof(b1)));
+	}
+
+	if (vf[IFLA_VF_VLAN_LIST]) {
+		struct rtattr *i, *vfvlanlist = vf[IFLA_VF_VLAN_LIST];
+		int rem = RTA_PAYLOAD(vfvlanlist);
+
+		open_json_array(PRINT_JSON, "vlan_list");
+		for (i = RTA_DATA(vfvlanlist);
+		     RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+			struct ifla_vf_vlan_info *vf_vlan_info = RTA_DATA(i);
+			SPRINT_BUF(b2);
+
+			open_json_object(NULL);
+			if (vf_vlan_info->vlan)
+				print_int(PRINT_ANY,
+					  "vlan",
+					  ", vlan %d",
+					  vf_vlan_info->vlan);
+			if (vf_vlan_info->qos)
+				print_int(PRINT_ANY,
+					  "qos",
+					  ", qos %d",
+					  vf_vlan_info->qos);
+			if (vf_vlan_info->vlan_proto &&
+			    vf_vlan_info->vlan_proto != htons(ETH_P_8021Q))
+				print_string(PRINT_ANY,
+					     "protocol",
+					     ", vlan protocol %s",
+					     ll_proto_n2a(
+						     vf_vlan_info->vlan_proto,
+						     b2, sizeof(b2)));
+			close_json_object();
+		}
+		close_json_array(PRINT_JSON, NULL);
+	} else {
+		struct ifla_vf_vlan *vf_vlan = RTA_DATA(vf[IFLA_VF_VLAN]);
+
+		if (vf_vlan->vlan)
+			print_int(PRINT_ANY,
+				  "vlan",
+				  ", vlan %d",
+				  vf_vlan->vlan);
+		if (vf_vlan->qos)
+			print_int(PRINT_ANY, "qos", ", qos %d", vf_vlan->qos);
+	}
+
+	if (vf_tx_rate->rate)
+		print_uint(PRINT_ANY,
+			   "tx_rate",
+			   ", tx rate %u (Mbps)",
+			   vf_tx_rate->rate);
+
+	if (vf[IFLA_VF_RATE]) {
+		struct ifla_vf_rate *vf_rate = RTA_DATA(vf[IFLA_VF_RATE]);
+		int max_tx = vf_rate->max_tx_rate;
+		int min_tx = vf_rate->min_tx_rate;
+
+		if (is_json_context()) {
+			open_json_object("rate");
+			print_uint(PRINT_JSON, "max_tx", NULL, max_tx);
+			print_uint(PRINT_ANY, "min_tx", NULL, min_tx);
+			close_json_object();
+		} else {
+			if (max_tx)
+				fprintf(fp, ", max_tx_rate %uMbps", max_tx);
+			if (min_tx)
+				fprintf(fp, ", min_tx_rate %uMbps", min_tx);
+		}
+	}
+
+	if (vf[IFLA_VF_SPOOFCHK]) {
+		struct ifla_vf_spoofchk *vf_spoofchk =
+			RTA_DATA(vf[IFLA_VF_SPOOFCHK]);
+
+		if (vf_spoofchk->setting != -1)
+			print_bool(PRINT_ANY,
+				   "spoofchk",
+				   vf_spoofchk->setting ?
+				   ", spoof checking on" : ", spoof checking off",
+				   vf_spoofchk->setting);
+	}
+
+	if (vf[IFLA_VF_LINK_STATE]) {
+		struct ifla_vf_link_state *vf_linkstate =
+			RTA_DATA(vf[IFLA_VF_LINK_STATE]);
+
+		if (vf_linkstate->link_state == IFLA_VF_LINK_STATE_AUTO)
+			print_string(PRINT_ANY,
+				     "link_state",
+				     ", link-state %s",
+				     "auto");
+		else if (vf_linkstate->link_state == IFLA_VF_LINK_STATE_ENABLE)
+			print_string(PRINT_ANY,
+				     "link_state",
+				     ", link-state %s",
+				     "enable");
+		else
+			print_string(PRINT_ANY,
+				     "link_state",
+				     ", link-state %s",
+				     "disable");
+	}
+
+	if (vf[IFLA_VF_TRUST]) {
+		struct ifla_vf_trust *vf_trust = RTA_DATA(vf[IFLA_VF_TRUST]);
+
+		if (vf_trust->setting != -1)
+			print_bool(PRINT_ANY,
+				   "trust",
+				   vf_trust->setting ? ", trust on" : ", trust off",
+				   vf_trust->setting);
+	}
+
+	if (vf[IFLA_VF_RSS_QUERY_EN]) {
+		struct ifla_vf_rss_query_en *rss_query =
+			RTA_DATA(vf[IFLA_VF_RSS_QUERY_EN]);
+
+		if (rss_query->setting != -1)
+			print_bool(PRINT_ANY,
+				   "query_rss_en",
+				   rss_query->setting ? ", query_rss on"
+				   : ", query_rss off",
+				   rss_query->setting);
+	}
+
+	if (vf[IFLA_VF_STATS] && show_stats)
+		print_vf_stats64(fp, vf[IFLA_VF_STATS]);
+}
+
+void print_num(FILE *fp, unsigned int width, uint64_t count)
+{
+	const char *prefix = "kMGTPE";
+	const unsigned int base = use_iec ? 1024 : 1000;
+	uint64_t powi = 1;
+	uint16_t powj = 1;
+	uint8_t precision = 2;
+	char buf[64];
+
+	if (!human_readable || count < base) {
+		fprintf(fp, "%-*"PRIu64" ", width, count);
+		return;
+	}
+
+	/* increase value by a factor of 1000/1024 and print
+	 * if result is something a human can read
+	 */
+	for (;;) {
+		powi *= base;
+		if (count / base < powi)
+			break;
+
+		if (!prefix[1])
+			break;
+		++prefix;
+	}
+
+	/* try to guess a good number of digits for precision */
+	for (; precision > 0; precision--) {
+		powj *= 10;
+		if (count / powi < powj)
+			break;
+	}
+
+	snprintf(buf, sizeof(buf), "%.*f%c%s", precision,
+		 (double) count / powi, *prefix, use_iec ? "i" : "");
+
+	fprintf(fp, "%-*s ", width, buf);
+}
+
+static void print_vf_stats64(FILE *fp, struct rtattr *vfstats)
+{
+	struct rtattr *vf[IFLA_VF_STATS_MAX + 1];
+
+	if (vfstats->rta_type != IFLA_VF_STATS) {
+		fprintf(stderr, "BUG: rta type is %d\n", vfstats->rta_type);
+		return;
+	}
+
+	parse_rtattr_nested(vf, IFLA_VF_STATS_MAX, vfstats);
+
+	if (is_json_context()) {
+		open_json_object("stats");
+
+		/* RX stats */
+		open_json_object("rx");
+		print_u64(PRINT_JSON, "bytes", NULL,
+			   rta_getattr_u64(vf[IFLA_VF_STATS_RX_BYTES]));
+		print_u64(PRINT_JSON, "packets", NULL,
+			   rta_getattr_u64(vf[IFLA_VF_STATS_RX_PACKETS]));
+		print_u64(PRINT_JSON, "multicast", NULL,
+			   rta_getattr_u64(vf[IFLA_VF_STATS_MULTICAST]));
+		print_u64(PRINT_JSON, "broadcast", NULL,
+			   rta_getattr_u64(vf[IFLA_VF_STATS_BROADCAST]));
+		if (vf[IFLA_VF_STATS_RX_DROPPED])
+			print_u64(PRINT_JSON, "dropped", NULL,
+				  rta_getattr_u64(vf[IFLA_VF_STATS_RX_DROPPED]));
+		close_json_object();
+
+		/* TX stats */
+		open_json_object("tx");
+		print_u64(PRINT_JSON, "tx_bytes", NULL,
+			   rta_getattr_u64(vf[IFLA_VF_STATS_TX_BYTES]));
+		print_u64(PRINT_JSON, "tx_packets", NULL,
+			   rta_getattr_u64(vf[IFLA_VF_STATS_TX_PACKETS]));
+		if (vf[IFLA_VF_STATS_TX_DROPPED])
+			print_u64(PRINT_JSON, "dropped", NULL,
+				  rta_getattr_u64(vf[IFLA_VF_STATS_TX_DROPPED]));
+		close_json_object();
+		close_json_object();
+	} else {
+		/* RX stats */
+		fprintf(fp, "%s", _SL_);
+		fprintf(fp, "    RX: bytes  packets  mcast   bcast ");
+		if (vf[IFLA_VF_STATS_RX_DROPPED])
+			fprintf(fp, "  dropped ");
+		fprintf(fp, "%s", _SL_);
+		fprintf(fp, "    ");
+
+		print_num(fp, 10, rta_getattr_u64(vf[IFLA_VF_STATS_RX_BYTES]));
+		print_num(fp, 8, rta_getattr_u64(vf[IFLA_VF_STATS_RX_PACKETS]));
+		print_num(fp, 7, rta_getattr_u64(vf[IFLA_VF_STATS_MULTICAST]));
+		print_num(fp, 7, rta_getattr_u64(vf[IFLA_VF_STATS_BROADCAST]));
+		if (vf[IFLA_VF_STATS_RX_DROPPED])
+			print_num(fp, 8, rta_getattr_u64(vf[IFLA_VF_STATS_RX_DROPPED]));
+
+		/* TX stats */
+		fprintf(fp, "%s", _SL_);
+		fprintf(fp, "    TX: bytes  packets ");
+		if (vf[IFLA_VF_STATS_TX_DROPPED])
+			fprintf(fp, "  dropped ");
+		fprintf(fp, "%s", _SL_);
+		fprintf(fp, "    ");
+
+		print_num(fp, 10, rta_getattr_u64(vf[IFLA_VF_STATS_TX_BYTES]));
+		print_num(fp, 8, rta_getattr_u64(vf[IFLA_VF_STATS_TX_PACKETS]));
+		if (vf[IFLA_VF_STATS_TX_DROPPED])
+			print_num(fp, 8, rta_getattr_u64(vf[IFLA_VF_STATS_TX_DROPPED]));
+	}
+}
+
+static void __print_link_stats(FILE *fp, struct rtattr *tb[])
+{
+	const struct rtattr *carrier_changes = tb[IFLA_CARRIER_CHANGES];
+	struct rtnl_link_stats64 _s, *s = &_s;
+	int ret;
+
+	ret = get_rtnl_link_stats_rta(s, tb);
+	if (ret < 0)
+		return;
+
+	if (is_json_context()) {
+		open_json_object((ret == sizeof(*s)) ? "stats64" : "stats");
+
+		/* RX stats */
+		open_json_object("rx");
+		print_u64(PRINT_JSON, "bytes", NULL, s->rx_bytes);
+		print_u64(PRINT_JSON, "packets", NULL, s->rx_packets);
+		print_u64(PRINT_JSON, "errors", NULL, s->rx_errors);
+		print_u64(PRINT_JSON, "dropped", NULL, s->rx_dropped);
+		print_u64(PRINT_JSON, "over_errors", NULL, s->rx_over_errors);
+		print_u64(PRINT_JSON, "multicast", NULL, s->multicast);
+		if (s->rx_compressed)
+			print_u64(PRINT_JSON,
+				   "compressed", NULL, s->rx_compressed);
+
+		/* RX error stats */
+		if (show_stats > 1) {
+			print_u64(PRINT_JSON,
+				   "length_errors",
+				   NULL, s->rx_length_errors);
+			print_u64(PRINT_JSON,
+				   "crc_errors",
+				   NULL, s->rx_crc_errors);
+			print_u64(PRINT_JSON,
+				   "frame_errors",
+				   NULL, s->rx_frame_errors);
+			print_u64(PRINT_JSON,
+				   "fifo_errors",
+				   NULL, s->rx_fifo_errors);
+			print_u64(PRINT_JSON,
+				   "missed_errors",
+				   NULL, s->rx_missed_errors);
+			if (s->rx_nohandler)
+				print_u64(PRINT_JSON,
+					   "nohandler", NULL, s->rx_nohandler);
+		}
+		close_json_object();
+
+		/* TX stats */
+		open_json_object("tx");
+		print_u64(PRINT_JSON, "bytes", NULL, s->tx_bytes);
+		print_u64(PRINT_JSON, "packets", NULL, s->tx_packets);
+		print_u64(PRINT_JSON, "errors", NULL, s->tx_errors);
+		print_u64(PRINT_JSON, "dropped", NULL, s->tx_dropped);
+		print_u64(PRINT_JSON,
+			   "carrier_errors",
+			   NULL, s->tx_carrier_errors);
+		print_u64(PRINT_JSON, "collisions", NULL, s->collisions);
+		if (s->tx_compressed)
+			print_u64(PRINT_JSON,
+				   "compressed", NULL, s->tx_compressed);
+
+		/* TX error stats */
+		if (show_stats > 1) {
+			print_u64(PRINT_JSON,
+				   "aborted_errors",
+				   NULL, s->tx_aborted_errors);
+			print_u64(PRINT_JSON,
+				   "fifo_errors",
+				   NULL, s->tx_fifo_errors);
+			print_u64(PRINT_JSON,
+				   "window_errors",
+				   NULL, s->tx_window_errors);
+			print_u64(PRINT_JSON,
+				   "heartbeat_errors",
+				   NULL, s->tx_heartbeat_errors);
+			if (carrier_changes)
+				print_u64(PRINT_JSON, "carrier_changes", NULL,
+					   rta_getattr_u32(carrier_changes));
+		}
+
+		close_json_object();
+		close_json_object();
+	} else {
+		/* RX stats */
+		fprintf(fp, "    RX: bytes  packets  errors  dropped overrun mcast   %s%s",
+			s->rx_compressed ? "compressed" : "", _SL_);
+
+		fprintf(fp, "    ");
+		print_num(fp, 10, s->rx_bytes);
+		print_num(fp, 8, s->rx_packets);
+		print_num(fp, 7, s->rx_errors);
+		print_num(fp, 7, s->rx_dropped);
+		print_num(fp, 7, s->rx_over_errors);
+		print_num(fp, 7, s->multicast);
+		if (s->rx_compressed)
+			print_num(fp, 7, s->rx_compressed);
+
+		/* RX error stats */
+		if (show_stats > 1) {
+			fprintf(fp, "%s", _SL_);
+			fprintf(fp, "    RX errors: length   crc     frame   fifo    missed%s%s",
+				s->rx_nohandler ? "   nohandler" : "", _SL_);
+			fprintf(fp, "               ");
+			print_num(fp, 8, s->rx_length_errors);
+			print_num(fp, 7, s->rx_crc_errors);
+			print_num(fp, 7, s->rx_frame_errors);
+			print_num(fp, 7, s->rx_fifo_errors);
+			print_num(fp, 7, s->rx_missed_errors);
+			if (s->rx_nohandler)
+				print_num(fp, 7, s->rx_nohandler);
+		}
+		fprintf(fp, "%s", _SL_);
+
+		/* TX stats */
+		fprintf(fp, "    TX: bytes  packets  errors  dropped carrier collsns %s%s",
+			s->tx_compressed ? "compressed" : "", _SL_);
+
+		fprintf(fp, "    ");
+		print_num(fp, 10, s->tx_bytes);
+		print_num(fp, 8, s->tx_packets);
+		print_num(fp, 7, s->tx_errors);
+		print_num(fp, 7, s->tx_dropped);
+		print_num(fp, 7, s->tx_carrier_errors);
+		print_num(fp, 7, s->collisions);
+		if (s->tx_compressed)
+			print_num(fp, 7, s->tx_compressed);
+
+		/* TX error stats */
+		if (show_stats > 1) {
+			fprintf(fp, "%s", _SL_);
+			fprintf(fp, "    TX errors: aborted  fifo   window heartbeat");
+			if (carrier_changes)
+				fprintf(fp, " transns");
+			fprintf(fp, "%s", _SL_);
+
+			fprintf(fp, "               ");
+			print_num(fp, 8, s->tx_aborted_errors);
+			print_num(fp, 7, s->tx_fifo_errors);
+			print_num(fp, 7, s->tx_window_errors);
+			print_num(fp, 7, s->tx_heartbeat_errors);
+			if (carrier_changes)
+				print_num(fp, 7,
+					  rta_getattr_u32(carrier_changes));
+		}
+	}
+}
+
+static void print_link_stats(FILE *fp, struct nlmsghdr *n)
+{
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_MAX+1];
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi),
+		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi)));
+	__print_link_stats(fp, tb);
+	print_nl();
+}
+
+static int print_linkinfo_brief(FILE *fp, const char *name,
+				const struct ifinfomsg *ifi,
+				struct rtattr *tb[])
+{
+	unsigned int m_flag = 0;
+
+	m_flag = print_name_and_link("%-16s ", name, tb);
+
+	if (tb[IFLA_OPERSTATE])
+		print_operstate(fp, rta_getattr_u8(tb[IFLA_OPERSTATE]));
+
+	if (filter.family == AF_PACKET) {
+		SPRINT_BUF(b1);
+
+		if (tb[IFLA_ADDRESS]) {
+			print_color_string(PRINT_ANY, COLOR_MAC,
+					   "address", "%s ",
+					   ll_addr_n2a(
+						   RTA_DATA(tb[IFLA_ADDRESS]),
+						   RTA_PAYLOAD(tb[IFLA_ADDRESS]),
+						   ifi->ifi_type,
+						   b1, sizeof(b1)));
+		}
+	}
+
+	if (filter.family == AF_PACKET) {
+		print_link_flags(fp, ifi->ifi_flags, m_flag);
+		print_string(PRINT_FP, NULL, "%s", "\n");
+	}
+
+	fflush(fp);
+	return 0;
+}
+
+static const char *link_events[] = {
+	[IFLA_EVENT_NONE] = "NONE",
+	[IFLA_EVENT_REBOOT] = "REBOOT",
+	[IFLA_EVENT_FEATURES] = "FEATURE CHANGE",
+	[IFLA_EVENT_BONDING_FAILOVER] = "BONDING FAILOVER",
+	[IFLA_EVENT_NOTIFY_PEERS] = "NOTIFY PEERS",
+	[IFLA_EVENT_IGMP_RESEND] = "RESEND IGMP",
+	[IFLA_EVENT_BONDING_OPTIONS] = "BONDING OPTION"
+};
+
+static void print_link_event(FILE *f, __u32 event)
+{
+	if (event >= ARRAY_SIZE(link_events))
+		print_int(PRINT_ANY, "event", "event %d ", event);
+	else {
+		if (event)
+			print_string(PRINT_ANY,
+				     "event", "event %s ",
+				     link_events[event]);
+	}
+}
+
+int print_linkinfo(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_MAX+1];
+	int len = n->nlmsg_len;
+	const char *name;
+	unsigned int m_flag = 0;
+	SPRINT_BUF(b1);
+
+	if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+		return 0;
+
+	len -= NLMSG_LENGTH(sizeof(*ifi));
+	if (len < 0)
+		return -1;
+
+	if (filter.ifindex && ifi->ifi_index != filter.ifindex)
+		return -1;
+	if (filter.up && !(ifi->ifi_flags&IFF_UP))
+		return -1;
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+	name = get_ifname_rta(ifi->ifi_index, tb[IFLA_IFNAME]);
+	if (!name)
+		return -1;
+
+	if (filter.label)
+		return 0;
+
+	if (tb[IFLA_GROUP]) {
+		int group = rta_getattr_u32(tb[IFLA_GROUP]);
+
+		if (filter.group != -1 && group != filter.group)
+			return -1;
+	}
+
+	if (tb[IFLA_MASTER]) {
+		int master = rta_getattr_u32(tb[IFLA_MASTER]);
+
+		if (filter.master > 0 && master != filter.master)
+			return -1;
+	} else if (filter.master > 0)
+		return -1;
+
+	if (filter.kind && match_link_kind(tb, filter.kind, 0))
+		return -1;
+
+	if (filter.slave_kind && match_link_kind(tb, filter.slave_kind, 1))
+		return -1;
+
+	if (n->nlmsg_type == RTM_DELLINK)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	if (brief)
+		return print_linkinfo_brief(fp, name, ifi, tb);
+
+	print_int(PRINT_ANY, "ifindex", "%d: ", ifi->ifi_index);
+
+	m_flag = print_name_and_link("%s: ", name, tb);
+	print_link_flags(fp, ifi->ifi_flags, m_flag);
+
+	if (tb[IFLA_MTU])
+		print_int(PRINT_ANY,
+			  "mtu", "mtu %u ",
+			  rta_getattr_u32(tb[IFLA_MTU]));
+	if (tb[IFLA_XDP])
+		xdp_dump(fp, tb[IFLA_XDP], do_link, false);
+	if (tb[IFLA_QDISC])
+		print_string(PRINT_ANY,
+			     "qdisc",
+			     "qdisc %s ",
+			     rta_getattr_str(tb[IFLA_QDISC]));
+	if (tb[IFLA_MASTER]) {
+		int master = rta_getattr_u32(tb[IFLA_MASTER]);
+
+		print_string(PRINT_ANY,
+			     "master", "master %s ",
+			     ll_index_to_name(master));
+	}
+
+	if (tb[IFLA_OPERSTATE])
+		print_operstate(fp, rta_getattr_u8(tb[IFLA_OPERSTATE]));
+
+	if (do_link && tb[IFLA_LINKMODE])
+		print_linkmode(fp, tb[IFLA_LINKMODE]);
+
+	if (tb[IFLA_GROUP]) {
+		int group = rta_getattr_u32(tb[IFLA_GROUP]);
+
+		print_string(PRINT_ANY,
+			     "group",
+			     "group %s ",
+			     rtnl_group_n2a(group, b1, sizeof(b1)));
+	}
+
+	if (filter.showqueue)
+		print_queuelen(fp, tb);
+
+	if (tb[IFLA_EVENT])
+		print_link_event(fp, rta_getattr_u32(tb[IFLA_EVENT]));
+
+	if (!filter.family || filter.family == AF_PACKET || show_details) {
+		print_nl();
+		print_string(PRINT_ANY,
+			     "link_type",
+			     "    link/%s ",
+			     ll_type_n2a(ifi->ifi_type, b1, sizeof(b1)));
+		if (tb[IFLA_ADDRESS]) {
+			print_color_string(PRINT_ANY,
+					   COLOR_MAC,
+					   "address",
+					   "%s",
+					   ll_addr_n2a(RTA_DATA(tb[IFLA_ADDRESS]),
+						       RTA_PAYLOAD(tb[IFLA_ADDRESS]),
+						       ifi->ifi_type,
+						       b1, sizeof(b1)));
+		}
+		if (tb[IFLA_BROADCAST]) {
+			if (ifi->ifi_flags&IFF_POINTOPOINT) {
+				print_string(PRINT_FP, NULL, " peer ", NULL);
+				print_bool(PRINT_JSON,
+					   "link_pointtopoint", NULL, true);
+			} else {
+				print_string(PRINT_FP, NULL, " brd ", NULL);
+			}
+			print_color_string(PRINT_ANY,
+					   COLOR_MAC,
+					   "broadcast",
+					   "%s",
+					   ll_addr_n2a(RTA_DATA(tb[IFLA_BROADCAST]),
+						       RTA_PAYLOAD(tb[IFLA_BROADCAST]),
+						       ifi->ifi_type,
+						       b1, sizeof(b1)));
+		}
+	}
+
+	if (tb[IFLA_LINK_NETNSID]) {
+		int id = rta_getattr_u32(tb[IFLA_LINK_NETNSID]);
+
+		if (is_json_context()) {
+			print_int(PRINT_JSON, "link_netnsid", NULL, id);
+		} else {
+			if (id >= 0) {
+				char *name = get_name_from_nsid(id);
+
+				if (name)
+					print_string(PRINT_FP, NULL,
+						     " link-netns %s", name);
+				else
+					print_int(PRINT_FP, NULL,
+						  " link-netnsid %d", id);
+			} else
+				print_string(PRINT_FP, NULL,
+					     " link-netnsid %s", "unknown");
+		}
+	}
+
+	if (tb[IFLA_NEW_NETNSID]) {
+		int id = rta_getattr_u32(tb[IFLA_NEW_NETNSID]);
+		char *name = get_name_from_nsid(id);
+
+		if (name)
+			print_string(PRINT_FP, NULL, " new-netns %s", name);
+		else
+			print_int(PRINT_FP, NULL, " new-netnsid %d", id);
+	}
+	if (tb[IFLA_NEW_IFINDEX]) {
+		int id = rta_getattr_u32(tb[IFLA_NEW_IFINDEX]);
+
+		print_int(PRINT_FP, NULL, " new-ifindex %d", id);
+	}
+
+	if (tb[IFLA_PROTO_DOWN]) {
+		if (rta_getattr_u8(tb[IFLA_PROTO_DOWN]))
+			print_bool(PRINT_ANY,
+				   "proto_down", " protodown on ", true);
+	}
+
+	if (show_details) {
+		if (tb[IFLA_PROMISCUITY])
+			print_uint(PRINT_ANY,
+				   "promiscuity",
+				   " promiscuity %u ",
+				   rta_getattr_u32(tb[IFLA_PROMISCUITY]));
+
+		if (tb[IFLA_MIN_MTU])
+			print_uint(PRINT_ANY,
+				   "min_mtu", "minmtu %u ",
+				   rta_getattr_u32(tb[IFLA_MIN_MTU]));
+
+		if (tb[IFLA_MAX_MTU])
+			print_uint(PRINT_ANY,
+				   "max_mtu", "maxmtu %u ",
+				   rta_getattr_u32(tb[IFLA_MAX_MTU]));
+
+		if (tb[IFLA_LINKINFO])
+			print_linktype(fp, tb[IFLA_LINKINFO]);
+
+		if (do_link && tb[IFLA_AF_SPEC])
+			print_af_spec(fp, tb[IFLA_AF_SPEC]);
+
+		if (tb[IFLA_NUM_TX_QUEUES])
+			print_uint(PRINT_ANY,
+				   "num_tx_queues",
+				   "numtxqueues %u ",
+				   rta_getattr_u32(tb[IFLA_NUM_TX_QUEUES]));
+
+		if (tb[IFLA_NUM_RX_QUEUES])
+			print_uint(PRINT_ANY,
+				   "num_rx_queues",
+				   "numrxqueues %u ",
+				   rta_getattr_u32(tb[IFLA_NUM_RX_QUEUES]));
+
+		if (tb[IFLA_GSO_MAX_SIZE])
+			print_uint(PRINT_ANY,
+				   "gso_max_size",
+				   "gso_max_size %u ",
+				   rta_getattr_u32(tb[IFLA_GSO_MAX_SIZE]));
+
+		if (tb[IFLA_GSO_MAX_SEGS])
+			print_uint(PRINT_ANY,
+				   "gso_max_segs",
+				   "gso_max_segs %u ",
+				   rta_getattr_u32(tb[IFLA_GSO_MAX_SEGS]));
+
+		if (tb[IFLA_PHYS_PORT_NAME])
+			print_string(PRINT_ANY,
+				     "phys_port_name",
+				     "portname %s ",
+				     rta_getattr_str(tb[IFLA_PHYS_PORT_NAME]));
+
+		if (tb[IFLA_PHYS_PORT_ID]) {
+			print_string(PRINT_ANY,
+				     "phys_port_id",
+				     "portid %s ",
+				     hexstring_n2a(
+					     RTA_DATA(tb[IFLA_PHYS_PORT_ID]),
+					     RTA_PAYLOAD(tb[IFLA_PHYS_PORT_ID]),
+					     b1, sizeof(b1)));
+		}
+
+		if (tb[IFLA_PHYS_SWITCH_ID]) {
+			print_string(PRINT_ANY,
+				     "phys_switch_id",
+				     "switchid %s ",
+				     hexstring_n2a(RTA_DATA(tb[IFLA_PHYS_SWITCH_ID]),
+						   RTA_PAYLOAD(tb[IFLA_PHYS_SWITCH_ID]),
+						   b1, sizeof(b1)));
+		}
+	}
+
+	if ((do_link || show_details) && tb[IFLA_IFALIAS]) {
+		print_string(PRINT_FP, NULL, "%s    ", _SL_);
+		print_string(PRINT_ANY,
+			     "ifalias",
+			     "alias %s",
+			     rta_getattr_str(tb[IFLA_IFALIAS]));
+	}
+
+	if ((do_link || show_details) && tb[IFLA_XDP])
+		xdp_dump(fp, tb[IFLA_XDP], true, true);
+
+	if (do_link && show_stats) {
+		print_nl();
+		__print_link_stats(fp, tb);
+	}
+
+	if ((do_link || show_details) && tb[IFLA_VFINFO_LIST] && tb[IFLA_NUM_VF]) {
+		struct rtattr *i, *vflist = tb[IFLA_VFINFO_LIST];
+		int rem = RTA_PAYLOAD(vflist);
+
+		open_json_array(PRINT_JSON, "vfinfo_list");
+		for (i = RTA_DATA(vflist); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+			open_json_object(NULL);
+			print_vfinfo(fp, ifi, i);
+			close_json_object();
+		}
+		close_json_array(PRINT_JSON, NULL);
+	}
+
+	print_string(PRINT_FP, NULL, "%s", "\n");
+	fflush(fp);
+	return 1;
+}
+
+static int flush_update(void)
+{
+
+	/*
+	 * Note that the kernel may delete multiple addresses for one
+	 * delete request (e.g. if ipv4 address promotion is disabled).
+	 * Since a flush operation is really a series of delete requests
+	 * its possible that we may request an address delete that has
+	 * already been done by the kernel. Therefore, ignore EADDRNOTAVAIL
+	 * errors returned from a flush request
+	 */
+	if ((rtnl_send_check(&rth, filter.flushb, filter.flushp) < 0) &&
+	    (errno != EADDRNOTAVAIL)) {
+		perror("Failed to send flush request");
+		return -1;
+	}
+	filter.flushp = 0;
+	return 0;
+}
+
+static int set_lifetime(unsigned int *lifetime, char *argv)
+{
+	if (strcmp(argv, "forever") == 0)
+		*lifetime = INFINITY_LIFE_TIME;
+	else if (get_u32(lifetime, argv, 0))
+		return -1;
+
+	return 0;
+}
+
+static unsigned int get_ifa_flags(struct ifaddrmsg *ifa,
+				  struct rtattr *ifa_flags_attr)
+{
+	return ifa_flags_attr ? rta_getattr_u32(ifa_flags_attr) :
+		ifa->ifa_flags;
+}
+
+/* Mapping from argument to address flag mask */
+static const struct {
+	const char *name;
+	unsigned long value;
+} ifa_flag_names[] = {
+	{ "secondary",		IFA_F_SECONDARY },
+	{ "temporary",		IFA_F_SECONDARY },
+	{ "nodad",		IFA_F_NODAD },
+	{ "optimistic",		IFA_F_OPTIMISTIC },
+	{ "dadfailed",		IFA_F_DADFAILED },
+	{ "home",		IFA_F_HOMEADDRESS },
+	{ "deprecated",		IFA_F_DEPRECATED },
+	{ "tentative",		IFA_F_TENTATIVE },
+	{ "permanent",		IFA_F_PERMANENT },
+	{ "mngtmpaddr",		IFA_F_MANAGETEMPADDR },
+	{ "noprefixroute",	IFA_F_NOPREFIXROUTE },
+	{ "autojoin",		IFA_F_MCAUTOJOIN },
+	{ "stable-privacy",	IFA_F_STABLE_PRIVACY },
+};
+
+static void print_ifa_flags(FILE *fp, const struct ifaddrmsg *ifa,
+			    unsigned int flags)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(ifa_flag_names); i++) {
+		unsigned long mask = ifa_flag_names[i].value;
+
+		if (mask == IFA_F_PERMANENT) {
+			if (!(flags & mask))
+				print_bool(PRINT_ANY,
+					   "dynamic", "dynamic ", true);
+		} else if (flags & mask) {
+			if (mask == IFA_F_SECONDARY &&
+			    ifa->ifa_family == AF_INET6) {
+				print_bool(PRINT_ANY,
+					   "temporary", "temporary ", true);
+			} else {
+				print_string(PRINT_FP, NULL,
+					     "%s ", ifa_flag_names[i].name);
+				print_bool(PRINT_JSON,
+					   ifa_flag_names[i].name, NULL, true);
+			}
+		}
+
+		flags &= ~mask;
+	}
+
+	if (flags) {
+		if (is_json_context()) {
+			SPRINT_BUF(b1);
+
+			snprintf(b1, sizeof(b1), "%02x", flags);
+			print_string(PRINT_JSON, "ifa_flags", NULL, b1);
+		} else {
+			fprintf(fp, "flags %02x ", flags);
+		}
+	}
+
+}
+
+static int get_filter(const char *arg)
+{
+	bool inv = false;
+	unsigned int i;
+
+	if (arg[0] == '-') {
+		inv = true;
+		arg++;
+	}
+
+	/* Special cases */
+	if (strcmp(arg, "dynamic") == 0) {
+		inv = !inv;
+		arg = "permanent";
+	} else if (strcmp(arg, "primary") == 0) {
+		inv = !inv;
+		arg = "secondary";
+	}
+
+	for (i = 0; i < ARRAY_SIZE(ifa_flag_names); i++) {
+		if (strcmp(arg, ifa_flag_names[i].name))
+			continue;
+
+		if (inv)
+			filter.flags &= ~ifa_flag_names[i].value;
+		else
+			filter.flags |= ifa_flag_names[i].value;
+		filter.flagmask |= ifa_flag_names[i].value;
+		return 0;
+	}
+	return -1;
+}
+
+static int ifa_label_match_rta(int ifindex, const struct rtattr *rta)
+{
+	const char *label;
+
+	if (!filter.label)
+		return 0;
+
+	if (rta)
+		label = RTA_DATA(rta);
+	else
+		label = ll_index_to_name(ifindex);
+
+	return fnmatch(filter.label, label, 0);
+}
+
+int print_addrinfo(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = arg;
+	struct ifaddrmsg *ifa = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	unsigned int ifa_flags;
+	struct rtattr *rta_tb[IFA_MAX+1];
+
+	SPRINT_BUF(b1);
+
+	if (n->nlmsg_type != RTM_NEWADDR && n->nlmsg_type != RTM_DELADDR)
+		return 0;
+	len -= NLMSG_LENGTH(sizeof(*ifa));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (filter.flushb && n->nlmsg_type != RTM_NEWADDR)
+		return 0;
+
+	parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa),
+		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
+
+	ifa_flags = get_ifa_flags(ifa, rta_tb[IFA_FLAGS]);
+
+	if (!rta_tb[IFA_LOCAL])
+		rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS];
+	if (!rta_tb[IFA_ADDRESS])
+		rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
+
+	if (filter.ifindex && filter.ifindex != ifa->ifa_index)
+		return 0;
+	if ((filter.scope^ifa->ifa_scope)&filter.scopemask)
+		return 0;
+	if ((filter.flags ^ ifa_flags) & filter.flagmask)
+		return 0;
+
+	if (filter.family && filter.family != ifa->ifa_family)
+		return 0;
+
+	if (ifa_label_match_rta(ifa->ifa_index, rta_tb[IFA_LABEL]))
+		return 0;
+
+	if (inet_addr_match_rta(&filter.pfx, rta_tb[IFA_LOCAL]))
+		return 0;
+
+	if (filter.flushb) {
+		struct nlmsghdr *fn;
+
+		if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) {
+			if (flush_update())
+				return -1;
+		}
+		fn = (struct nlmsghdr *)(filter.flushb + NLMSG_ALIGN(filter.flushp));
+		memcpy(fn, n, n->nlmsg_len);
+		fn->nlmsg_type = RTM_DELADDR;
+		fn->nlmsg_flags = NLM_F_REQUEST;
+		fn->nlmsg_seq = ++rth.seq;
+		filter.flushp = (((char *)fn) + n->nlmsg_len) - filter.flushb;
+		filter.flushed++;
+		if (show_stats < 2)
+			return 0;
+	}
+
+	if (n->nlmsg_type == RTM_DELADDR)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	if (!brief) {
+		const char *name;
+
+		if (filter.oneline || filter.flushb) {
+			const char *dev = ll_index_to_name(ifa->ifa_index);
+
+			if (is_json_context()) {
+				print_int(PRINT_JSON,
+					  "index", NULL, ifa->ifa_index);
+				print_string(PRINT_JSON, "dev", NULL, dev);
+			} else {
+				fprintf(fp, "%u: %s", ifa->ifa_index, dev);
+			}
+		}
+
+		name = family_name(ifa->ifa_family);
+		if (*name != '?') {
+			print_string(PRINT_ANY, "family", "    %s ", name);
+		} else {
+			print_int(PRINT_ANY, "family_index", "    family %d ",
+				  ifa->ifa_family);
+		}
+	}
+
+	if (rta_tb[IFA_LOCAL]) {
+		print_color_string(PRINT_ANY,
+				   ifa_family_color(ifa->ifa_family),
+				   "local", "%s",
+				   format_host_rta(ifa->ifa_family,
+						   rta_tb[IFA_LOCAL]));
+		if (rta_tb[IFA_ADDRESS] &&
+		    memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]),
+			   RTA_DATA(rta_tb[IFA_LOCAL]),
+			   ifa->ifa_family == AF_INET ? 4 : 16)) {
+			print_string(PRINT_FP, NULL, " %s ", "peer");
+			print_color_string(PRINT_ANY,
+					   ifa_family_color(ifa->ifa_family),
+					   "address",
+					   "%s",
+					   format_host_rta(ifa->ifa_family,
+							   rta_tb[IFA_ADDRESS]));
+		}
+		print_int(PRINT_ANY, "prefixlen", "/%d ", ifa->ifa_prefixlen);
+
+		if (rta_tb[IFA_RT_PRIORITY])
+			print_uint(PRINT_ANY, "metric", "metric %u ",
+				   rta_getattr_u32(rta_tb[IFA_RT_PRIORITY]));
+	}
+
+	if (brief)
+		goto brief_exit;
+
+	if (rta_tb[IFA_BROADCAST]) {
+		print_string(PRINT_FP, NULL, "%s ", "brd");
+		print_color_string(PRINT_ANY,
+				   ifa_family_color(ifa->ifa_family),
+				   "broadcast",
+				   "%s ",
+				   format_host_rta(ifa->ifa_family,
+						   rta_tb[IFA_BROADCAST]));
+	}
+
+	if (rta_tb[IFA_ANYCAST]) {
+		print_string(PRINT_FP, NULL, "%s ", "any");
+		print_color_string(PRINT_ANY,
+				   ifa_family_color(ifa->ifa_family),
+				   "anycast",
+				   "%s ",
+				   format_host_rta(ifa->ifa_family,
+						   rta_tb[IFA_ANYCAST]));
+	}
+
+	print_string(PRINT_ANY,
+		     "scope",
+		     "scope %s ",
+		     rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1)));
+
+	print_ifa_flags(fp, ifa, ifa_flags);
+
+	if (rta_tb[IFA_LABEL])
+		print_string(PRINT_ANY,
+			     "label",
+			     "%s",
+			     rta_getattr_str(rta_tb[IFA_LABEL]));
+
+	if (rta_tb[IFA_CACHEINFO]) {
+		struct ifa_cacheinfo *ci = RTA_DATA(rta_tb[IFA_CACHEINFO]);
+
+		print_nl();
+		print_string(PRINT_FP, NULL, "       valid_lft ", NULL);
+
+		if (ci->ifa_valid == INFINITY_LIFE_TIME) {
+			print_uint(PRINT_JSON,
+				   "valid_life_time",
+				   NULL, INFINITY_LIFE_TIME);
+			print_string(PRINT_FP, NULL, "%s", "forever");
+		} else {
+			print_uint(PRINT_ANY,
+				   "valid_life_time", "%usec", ci->ifa_valid);
+		}
+
+		print_string(PRINT_FP, NULL, " preferred_lft ", NULL);
+		if (ci->ifa_prefered == INFINITY_LIFE_TIME) {
+			print_uint(PRINT_JSON,
+				   "preferred_life_time",
+				   NULL, INFINITY_LIFE_TIME);
+			print_string(PRINT_FP, NULL, "%s", "forever");
+		} else {
+			if (ifa_flags & IFA_F_DEPRECATED)
+				print_int(PRINT_ANY,
+					  "preferred_life_time",
+					  "%dsec",
+					  ci->ifa_prefered);
+			else
+				print_uint(PRINT_ANY,
+					   "preferred_life_time",
+					   "%usec",
+					   ci->ifa_prefered);
+		}
+	}
+	print_string(PRINT_FP, NULL, "%s", "\n");
+brief_exit:
+	fflush(fp);
+	return 0;
+}
+
+static int print_selected_addrinfo(struct ifinfomsg *ifi,
+				   struct nlmsg_list *ainfo, FILE *fp)
+{
+	open_json_array(PRINT_JSON, "addr_info");
+	for ( ; ainfo ;  ainfo = ainfo->next) {
+		struct nlmsghdr *n = &ainfo->h;
+		struct ifaddrmsg *ifa = NLMSG_DATA(n);
+
+		if (n->nlmsg_type != RTM_NEWADDR)
+			continue;
+
+		if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifa)))
+			return -1;
+
+		if (ifa->ifa_index != ifi->ifi_index ||
+		    (filter.family && filter.family != ifa->ifa_family))
+			continue;
+
+		if (filter.up && !(ifi->ifi_flags&IFF_UP))
+			continue;
+
+		open_json_object(NULL);
+		print_addrinfo(n, fp);
+		close_json_object();
+	}
+	close_json_array(PRINT_JSON, NULL);
+
+	if (brief) {
+		print_string(PRINT_FP, NULL, "%s", "\n");
+		fflush(fp);
+	}
+	return 0;
+}
+
+
+static int store_nlmsg(struct nlmsghdr *n, void *arg)
+{
+	struct nlmsg_chain *lchain = (struct nlmsg_chain *)arg;
+	struct nlmsg_list *h;
+
+	h = malloc(n->nlmsg_len+sizeof(void *));
+	if (h == NULL)
+		return -1;
+
+	memcpy(&h->h, n, n->nlmsg_len);
+	h->next = NULL;
+
+	if (lchain->tail)
+		lchain->tail->next = h;
+	else
+		lchain->head = h;
+	lchain->tail = h;
+
+	ll_remember_index(n, NULL);
+	return 0;
+}
+
+static __u32 ipadd_dump_magic = 0x47361222;
+
+static int ipadd_save_prep(void)
+{
+	int ret;
+
+	if (isatty(STDOUT_FILENO)) {
+		fprintf(stderr, "Not sending a binary stream to stdout\n");
+		return -1;
+	}
+
+	ret = write(STDOUT_FILENO, &ipadd_dump_magic, sizeof(ipadd_dump_magic));
+	if (ret != sizeof(ipadd_dump_magic)) {
+		fprintf(stderr, "Can't write magic to dump file\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int ipadd_dump_check_magic(void)
+{
+	int ret;
+	__u32 magic = 0;
+
+	if (isatty(STDIN_FILENO)) {
+		fprintf(stderr, "Can't restore address dump from a terminal\n");
+		return -1;
+	}
+
+	ret = fread(&magic, sizeof(magic), 1, stdin);
+	if (magic != ipadd_dump_magic) {
+		fprintf(stderr, "Magic mismatch (%d elems, %x magic)\n", ret, magic);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int save_nlmsg(struct nlmsghdr *n, void *arg)
+{
+	int ret;
+
+	ret = write(STDOUT_FILENO, n, n->nlmsg_len);
+	if ((ret > 0) && (ret != n->nlmsg_len)) {
+		fprintf(stderr, "Short write while saving nlmsg\n");
+		ret = -EIO;
+	}
+
+	return ret == n->nlmsg_len ? 0 : ret;
+}
+
+static int show_handler(struct rtnl_ctrl_data *ctrl,
+			struct nlmsghdr *n, void *arg)
+{
+	struct ifaddrmsg *ifa = NLMSG_DATA(n);
+
+	open_json_object(NULL);
+	print_int(PRINT_ANY, "index", "if%d:\n", ifa->ifa_index);
+	print_addrinfo(n, stdout);
+	close_json_object();
+	return 0;
+}
+
+static int ipaddr_showdump(void)
+{
+	int err;
+
+	if (ipadd_dump_check_magic())
+		exit(-1);
+
+	new_json_obj(json);
+	open_json_object(NULL);
+	open_json_array(PRINT_JSON, "addr_info");
+
+	err = rtnl_from_file(stdin, &show_handler, NULL);
+
+	close_json_array(PRINT_JSON, NULL);
+	close_json_object();
+	delete_json_obj();
+
+	exit(err);
+}
+
+static int restore_handler(struct rtnl_ctrl_data *ctrl,
+			   struct nlmsghdr *n, void *arg)
+{
+	int ret;
+
+	n->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+
+	ll_init_map(&rth);
+
+	ret = rtnl_talk(&rth, n, NULL);
+	if ((ret < 0) && (errno == EEXIST))
+		ret = 0;
+
+	return ret;
+}
+
+static int ipaddr_restore(void)
+{
+	if (ipadd_dump_check_magic())
+		exit(-1);
+
+	exit(rtnl_from_file(stdin, &restore_handler, NULL));
+}
+
+void free_nlmsg_chain(struct nlmsg_chain *info)
+{
+	struct nlmsg_list *l, *n;
+
+	for (l = info->head; l; l = n) {
+		n = l->next;
+		free(l);
+	}
+}
+
+static void ipaddr_filter(struct nlmsg_chain *linfo, struct nlmsg_chain *ainfo)
+{
+	struct nlmsg_list *l, **lp;
+
+	lp = &linfo->head;
+	while ((l = *lp) != NULL) {
+		int ok = 0;
+		int missing_net_address = 1;
+		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+		struct nlmsg_list *a;
+
+		for (a = ainfo->head; a; a = a->next) {
+			struct nlmsghdr *n = &a->h;
+			struct ifaddrmsg *ifa = NLMSG_DATA(n);
+			struct rtattr *tb[IFA_MAX + 1];
+			unsigned int ifa_flags;
+
+			if (ifa->ifa_index != ifi->ifi_index)
+				continue;
+			missing_net_address = 0;
+			if (filter.family && filter.family != ifa->ifa_family)
+				continue;
+			if ((filter.scope^ifa->ifa_scope)&filter.scopemask)
+				continue;
+
+			parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(n));
+			ifa_flags = get_ifa_flags(ifa, tb[IFA_FLAGS]);
+
+			if ((filter.flags ^ ifa_flags) & filter.flagmask)
+				continue;
+
+			if (ifa_label_match_rta(ifa->ifa_index, tb[IFA_LABEL]))
+				continue;
+
+			if (!tb[IFA_LOCAL])
+				tb[IFA_LOCAL] = tb[IFA_ADDRESS];
+			if (inet_addr_match_rta(&filter.pfx, tb[IFA_LOCAL]))
+				continue;
+
+			ok = 1;
+			break;
+		}
+		if (missing_net_address &&
+		    (filter.family == AF_UNSPEC || filter.family == AF_PACKET))
+			ok = 1;
+		if (!ok) {
+			*lp = l->next;
+			free(l);
+		} else
+			lp = &l->next;
+	}
+}
+
+static int ipaddr_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+	struct ifaddrmsg *ifa = NLMSG_DATA(nlh);
+
+	ifa->ifa_index = filter.ifindex;
+
+	return 0;
+}
+
+static int ipaddr_flush(void)
+{
+	int round = 0;
+	char flushb[4096-512];
+
+	filter.flushb = flushb;
+	filter.flushp = 0;
+	filter.flushe = sizeof(flushb);
+
+	while ((max_flush_loops == 0) || (round < max_flush_loops)) {
+		if (rtnl_addrdump_req(&rth, filter.family,
+				      ipaddr_dump_filter) < 0) {
+			perror("Cannot send dump request");
+			exit(1);
+		}
+		filter.flushed = 0;
+		if (rtnl_dump_filter_nc(&rth, print_addrinfo,
+					stdout, NLM_F_DUMP_INTR) < 0) {
+			fprintf(stderr, "Flush terminated\n");
+			exit(1);
+		}
+		if (filter.flushed == 0) {
+ flush_done:
+			if (show_stats) {
+				if (round == 0)
+					printf("Nothing to flush.\n");
+				else
+					printf("*** Flush is complete after %d round%s ***\n", round, round > 1?"s":"");
+			}
+			fflush(stdout);
+			return 0;
+		}
+		round++;
+		if (flush_update() < 0)
+			return 1;
+
+		if (show_stats) {
+			printf("\n*** Round %d, deleting %d addresses ***\n", round, filter.flushed);
+			fflush(stdout);
+		}
+
+		/* If we are flushing, and specifying primary, then we
+		 * want to flush only a single round.  Otherwise, we'll
+		 * start flushing secondaries that were promoted to
+		 * primaries.
+		 */
+		if (!(filter.flags & IFA_F_SECONDARY) && (filter.flagmask & IFA_F_SECONDARY))
+			goto flush_done;
+	}
+	fprintf(stderr, "*** Flush remains incomplete after %d rounds. ***\n", max_flush_loops);
+	fflush(stderr);
+	return 1;
+}
+
+static int iplink_filter_req(struct nlmsghdr *nlh, int reqlen)
+{
+	int err;
+
+	err = addattr32(nlh, reqlen, IFLA_EXT_MASK, RTEXT_FILTER_VF);
+	if (err)
+		return err;
+
+	if (filter.master) {
+		err = addattr32(nlh, reqlen, IFLA_MASTER, filter.master);
+		if (err)
+			return err;
+	}
+
+	if (filter.kind) {
+		struct rtattr *linkinfo;
+
+		linkinfo = addattr_nest(nlh, reqlen, IFLA_LINKINFO);
+
+		err = addattr_l(nlh, reqlen, IFLA_INFO_KIND, filter.kind,
+				strlen(filter.kind));
+		if (err)
+			return err;
+
+		addattr_nest_end(nlh, linkinfo);
+	}
+
+	return 0;
+}
+
+static int ipaddr_link_get(int index, struct nlmsg_chain *linfo)
+{
+	struct iplink_req req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETLINK,
+		.i.ifi_family = filter.family,
+		.i.ifi_index = index,
+	};
+	__u32 filt_mask = RTEXT_FILTER_VF;
+	struct nlmsghdr *answer;
+
+	if (!show_stats)
+		filt_mask |= RTEXT_FILTER_SKIP_STATS;
+
+	addattr32(&req.n, sizeof(req), IFLA_EXT_MASK, filt_mask);
+
+	if (rtnl_talk(&rth, &req.n, &answer) < 0) {
+		perror("Cannot send link request");
+		return 1;
+	}
+
+	if (store_nlmsg(answer, linfo) < 0) {
+		fprintf(stderr, "Failed to process link information\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+/* fills in linfo with link data and optionally ainfo with address info
+ * caller can walk lists as desired and must call free_nlmsg_chain for
+ * both when done
+ */
+int ip_link_list(req_filter_fn_t filter_fn, struct nlmsg_chain *linfo)
+{
+	if (rtnl_linkdump_req_filter_fn(&rth, preferred_family,
+					filter_fn) < 0) {
+		perror("Cannot send dump request");
+		return 1;
+	}
+
+	if (rtnl_dump_filter(&rth, store_nlmsg, linfo) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static int ip_addr_list(struct nlmsg_chain *ainfo)
+{
+	if (rtnl_addrdump_req(&rth, filter.family, ipaddr_dump_filter) < 0) {
+		perror("Cannot send dump request");
+		return 1;
+	}
+
+	if (rtnl_dump_filter(&rth, store_nlmsg, ainfo) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
+{
+	struct nlmsg_chain linfo = { NULL, NULL};
+	struct nlmsg_chain _ainfo = { NULL, NULL}, *ainfo = &_ainfo;
+	struct nlmsg_list *l;
+	char *filter_dev = NULL;
+	int no_link = 0;
+
+	ipaddr_reset_filter(oneline, 0);
+	filter.showqueue = 1;
+	filter.family = preferred_family;
+
+	if (action == IPADD_FLUSH) {
+		if (argc <= 0) {
+			fprintf(stderr, "Flush requires arguments.\n");
+
+			return -1;
+		}
+		if (filter.family == AF_PACKET) {
+			fprintf(stderr, "Cannot flush link addresses.\n");
+			return -1;
+		}
+	}
+
+	while (argc > 0) {
+		if (strcmp(*argv, "to") == 0) {
+			NEXT_ARG();
+			if (get_prefix(&filter.pfx, *argv, filter.family))
+				invarg("invalid \"to\"\n", *argv);
+			if (filter.family == AF_UNSPEC)
+				filter.family = filter.pfx.family;
+		} else if (strcmp(*argv, "scope") == 0) {
+			unsigned int scope = 0;
+
+			NEXT_ARG();
+			filter.scopemask = -1;
+			if (rtnl_rtscope_a2n(&scope, *argv)) {
+				if (strcmp(*argv, "all") != 0)
+					invarg("invalid \"scope\"\n", *argv);
+				scope = RT_SCOPE_NOWHERE;
+				filter.scopemask = 0;
+			}
+			filter.scope = scope;
+		} else if (strcmp(*argv, "up") == 0) {
+			filter.up = 1;
+		} else if (get_filter(*argv) == 0) {
+
+		} else if (strcmp(*argv, "label") == 0) {
+			NEXT_ARG();
+			filter.label = *argv;
+		} else if (strcmp(*argv, "group") == 0) {
+			NEXT_ARG();
+			if (rtnl_group_a2n(&filter.group, *argv))
+				invarg("Invalid \"group\" value\n", *argv);
+		} else if (strcmp(*argv, "master") == 0) {
+			int ifindex;
+
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Device does not exist\n", *argv);
+			filter.master = ifindex;
+		} else if (strcmp(*argv, "vrf") == 0) {
+			int ifindex;
+
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Not a valid VRF name\n", *argv);
+			if (!name_is_vrf(*argv))
+				invarg("Not a valid VRF name\n", *argv);
+			filter.master = ifindex;
+		} else if (strcmp(*argv, "type") == 0) {
+			int soff;
+
+			NEXT_ARG();
+			soff = strlen(*argv) - strlen("_slave");
+			if (!strcmp(*argv + soff, "_slave")) {
+				(*argv)[soff] = '\0';
+				filter.slave_kind = *argv;
+			} else {
+				filter.kind = *argv;
+			}
+		} else {
+			if (strcmp(*argv, "dev") == 0)
+				NEXT_ARG();
+			else if (matches(*argv, "help") == 0)
+				usage();
+			if (filter_dev)
+				duparg2("dev", *argv);
+			filter_dev = *argv;
+		}
+		argv++; argc--;
+	}
+
+	if (filter_dev) {
+		filter.ifindex = ll_name_to_index(filter_dev);
+		if (filter.ifindex <= 0) {
+			fprintf(stderr, "Device \"%s\" does not exist.\n", filter_dev);
+			return -1;
+		}
+	}
+
+	if (action == IPADD_FLUSH)
+		return ipaddr_flush();
+
+	if (action == IPADD_SAVE) {
+		if (ipadd_save_prep())
+			exit(1);
+
+		if (rtnl_addrdump_req(&rth, preferred_family,
+				      ipaddr_dump_filter) < 0) {
+			perror("Cannot send dump request");
+			exit(1);
+		}
+
+		if (rtnl_dump_filter(&rth, save_nlmsg, stdout) < 0) {
+			fprintf(stderr, "Save terminated\n");
+			exit(1);
+		}
+
+		exit(0);
+	}
+
+	/*
+	 * Initialize a json_writer and open an array object
+	 * if -json was specified.
+	 */
+	new_json_obj(json);
+
+	/*
+	 * If only filter_dev present and none of the other
+	 * link filters are present, use RTM_GETLINK to get
+	 * the link device
+	 */
+	if (filter_dev && filter.group == -1 && do_link == 1) {
+		if (iplink_get(filter_dev, RTEXT_FILTER_VF) < 0) {
+			perror("Cannot send link get request");
+			delete_json_obj();
+			exit(1);
+		}
+		delete_json_obj();
+		goto out;
+	}
+
+	if (filter.ifindex) {
+		if (ipaddr_link_get(filter.ifindex, &linfo) != 0)
+			goto out;
+	} else {
+		if (ip_link_list(iplink_filter_req, &linfo) != 0)
+			goto out;
+	}
+
+	if (filter.family != AF_PACKET) {
+		if (filter.oneline)
+			no_link = 1;
+
+		if (ip_addr_list(ainfo) != 0)
+			goto out;
+
+		ipaddr_filter(&linfo, ainfo);
+	}
+
+	for (l = linfo.head; l; l = l->next) {
+		struct nlmsghdr *n = &l->h;
+		struct ifinfomsg *ifi = NLMSG_DATA(n);
+		int res = 0;
+
+		open_json_object(NULL);
+		if (brief || !no_link)
+			res = print_linkinfo(n, stdout);
+		if (res >= 0 && filter.family != AF_PACKET)
+			print_selected_addrinfo(ifi, ainfo->head, stdout);
+		if (res > 0 && !do_link && show_stats)
+			print_link_stats(stdout, n);
+		close_json_object();
+	}
+	fflush(stdout);
+
+out:
+	free_nlmsg_chain(ainfo);
+	free_nlmsg_chain(&linfo);
+	delete_json_obj();
+	return 0;
+}
+
+static void
+ipaddr_loop_each_vf(struct rtattr *tb[], int vfnum, int *min, int *max)
+{
+	struct rtattr *vflist = tb[IFLA_VFINFO_LIST];
+	struct rtattr *i, *vf[IFLA_VF_MAX+1];
+	struct ifla_vf_rate *vf_rate;
+	int rem;
+
+	rem = RTA_PAYLOAD(vflist);
+
+	for (i = RTA_DATA(vflist); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		parse_rtattr_nested(vf, IFLA_VF_MAX, i);
+
+		if (!vf[IFLA_VF_RATE]) {
+			fprintf(stderr, "VF min/max rate API not supported\n");
+			exit(1);
+		}
+
+		vf_rate = RTA_DATA(vf[IFLA_VF_RATE]);
+		if (vf_rate->vf == vfnum) {
+			*min = vf_rate->min_tx_rate;
+			*max = vf_rate->max_tx_rate;
+			return;
+		}
+	}
+	fprintf(stderr, "Cannot find VF %d\n", vfnum);
+	exit(1);
+}
+
+void ipaddr_get_vf_rate(int vfnum, int *min, int *max, const char *dev)
+{
+	struct nlmsg_chain linfo = { NULL, NULL};
+	struct rtattr *tb[IFLA_MAX+1];
+	struct ifinfomsg *ifi;
+	struct nlmsg_list *l;
+	struct nlmsghdr *n;
+	int idx, len;
+
+	idx = ll_name_to_index(dev);
+	if (idx == 0) {
+		fprintf(stderr, "Device %s does not exist\n", dev);
+		exit(1);
+	}
+
+	if (rtnl_linkdump_req(&rth, AF_UNSPEC) < 0) {
+		perror("Cannot send dump request");
+		exit(1);
+	}
+	if (rtnl_dump_filter(&rth, store_nlmsg, &linfo) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	for (l = linfo.head; l; l = l->next) {
+		n = &l->h;
+		ifi = NLMSG_DATA(n);
+
+		len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0 || (idx && idx != ifi->ifi_index))
+			continue;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+		if ((tb[IFLA_VFINFO_LIST] && tb[IFLA_NUM_VF])) {
+			ipaddr_loop_each_vf(tb, vfnum, min, max);
+			return;
+		}
+	}
+}
+
+int ipaddr_list_link(int argc, char **argv)
+{
+	preferred_family = AF_PACKET;
+	do_link = 1;
+	return ipaddr_list_flush_or_save(argc, argv, IPADD_LIST);
+}
+
+void ipaddr_reset_filter(int oneline, int ifindex)
+{
+	memset(&filter, 0, sizeof(filter));
+	filter.oneline = oneline;
+	filter.ifindex = ifindex;
+	filter.group = -1;
+}
+
+static int default_scope(inet_prefix *lcl)
+{
+	if (lcl->family == AF_INET) {
+		if (lcl->bytelen >= 1 && *(__u8 *)&lcl->data == 127)
+			return RT_SCOPE_HOST;
+	}
+	return 0;
+}
+
+static bool ipaddr_is_multicast(inet_prefix *a)
+{
+	if (a->family == AF_INET)
+		return IN_MULTICAST(ntohl(a->data[0]));
+	else if (a->family == AF_INET6)
+		return IN6_IS_ADDR_MULTICAST(a->data);
+	else
+		return false;
+}
+
+static bool is_valid_label(const char *dev, const char *label)
+{
+	size_t len = strlen(dev);
+
+	if (strncmp(label, dev, len) != 0)
+		return false;
+
+	return label[len] == '\0' || label[len] == ':';
+}
+
+static int ipaddr_modify(int cmd, int flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct ifaddrmsg	ifa;
+		char			buf[256];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST | flags,
+		.n.nlmsg_type = cmd,
+		.ifa.ifa_family = preferred_family,
+	};
+	char  *d = NULL;
+	char  *l = NULL;
+	char  *lcl_arg = NULL;
+	char  *valid_lftp = NULL;
+	char  *preferred_lftp = NULL;
+	inet_prefix lcl = {};
+	inet_prefix peer;
+	int local_len = 0;
+	int peer_len = 0;
+	int brd_len = 0;
+	int any_len = 0;
+	int scoped = 0;
+	__u32 preferred_lft = INFINITY_LIFE_TIME;
+	__u32 valid_lft = INFINITY_LIFE_TIME;
+	unsigned int ifa_flags = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "peer") == 0 ||
+		    strcmp(*argv, "remote") == 0) {
+			NEXT_ARG();
+
+			if (peer_len)
+				duparg("peer", *argv);
+			get_prefix(&peer, *argv, req.ifa.ifa_family);
+			peer_len = peer.bytelen;
+			if (req.ifa.ifa_family == AF_UNSPEC)
+				req.ifa.ifa_family = peer.family;
+			addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &peer.data, peer.bytelen);
+			req.ifa.ifa_prefixlen = peer.bitlen;
+		} else if (matches(*argv, "broadcast") == 0 ||
+			   strcmp(*argv, "brd") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			if (brd_len)
+				duparg("broadcast", *argv);
+			if (strcmp(*argv, "+") == 0)
+				brd_len = -1;
+			else if (strcmp(*argv, "-") == 0)
+				brd_len = -2;
+			else {
+				get_addr(&addr, *argv, req.ifa.ifa_family);
+				if (req.ifa.ifa_family == AF_UNSPEC)
+					req.ifa.ifa_family = addr.family;
+				addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &addr.data, addr.bytelen);
+				brd_len = addr.bytelen;
+			}
+		} else if (strcmp(*argv, "anycast") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			if (any_len)
+				duparg("anycast", *argv);
+			get_addr(&addr, *argv, req.ifa.ifa_family);
+			if (req.ifa.ifa_family == AF_UNSPEC)
+				req.ifa.ifa_family = addr.family;
+			addattr_l(&req.n, sizeof(req), IFA_ANYCAST, &addr.data, addr.bytelen);
+			any_len = addr.bytelen;
+		} else if (strcmp(*argv, "scope") == 0) {
+			unsigned int scope = 0;
+
+			NEXT_ARG();
+			if (rtnl_rtscope_a2n(&scope, *argv))
+				invarg("invalid scope value.", *argv);
+			req.ifa.ifa_scope = scope;
+			scoped = 1;
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (strcmp(*argv, "label") == 0) {
+			NEXT_ARG();
+			l = *argv;
+			addattr_l(&req.n, sizeof(req), IFA_LABEL, l, strlen(l)+1);
+		} else if (matches(*argv, "metric") == 0 ||
+			   matches(*argv, "priority") == 0 ||
+			   matches(*argv, "preference") == 0) {
+			__u32 metric;
+
+			NEXT_ARG();
+			if (get_u32(&metric, *argv, 0))
+				invarg("\"metric\" value is invalid\n", *argv);
+			addattr32(&req.n, sizeof(req), IFA_RT_PRIORITY, metric);
+		} else if (matches(*argv, "valid_lft") == 0) {
+			if (valid_lftp)
+				duparg("valid_lft", *argv);
+			NEXT_ARG();
+			valid_lftp = *argv;
+			if (set_lifetime(&valid_lft, *argv))
+				invarg("valid_lft value", *argv);
+		} else if (matches(*argv, "preferred_lft") == 0) {
+			if (preferred_lftp)
+				duparg("preferred_lft", *argv);
+			NEXT_ARG();
+			preferred_lftp = *argv;
+			if (set_lifetime(&preferred_lft, *argv))
+				invarg("preferred_lft value", *argv);
+		} else if (strcmp(*argv, "home") == 0) {
+			if (req.ifa.ifa_family == AF_INET6)
+				ifa_flags |= IFA_F_HOMEADDRESS;
+			else
+				fprintf(stderr, "Warning: home option can be set only for IPv6 addresses\n");
+		} else if (strcmp(*argv, "optimistic") == 0) {
+			if (req.ifa.ifa_family == AF_INET6)
+				ifa_flags |= IFA_F_OPTIMISTIC;
+			else
+				fprintf(stderr, "Warning: optimistic option can be set only for IPv6 addresses\n");
+		} else if (strcmp(*argv, "nodad") == 0) {
+			if (req.ifa.ifa_family == AF_INET6)
+				ifa_flags |= IFA_F_NODAD;
+			else
+				fprintf(stderr, "Warning: nodad option can be set only for IPv6 addresses\n");
+		} else if (strcmp(*argv, "mngtmpaddr") == 0) {
+			if (req.ifa.ifa_family == AF_INET6)
+				ifa_flags |= IFA_F_MANAGETEMPADDR;
+			else
+				fprintf(stderr, "Warning: mngtmpaddr option can be set only for IPv6 addresses\n");
+		} else if (strcmp(*argv, "noprefixroute") == 0) {
+			ifa_flags |= IFA_F_NOPREFIXROUTE;
+		} else if (strcmp(*argv, "autojoin") == 0) {
+			ifa_flags |= IFA_F_MCAUTOJOIN;
+		} else {
+			if (strcmp(*argv, "local") == 0)
+				NEXT_ARG();
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (local_len)
+				duparg2("local", *argv);
+			lcl_arg = *argv;
+			get_prefix(&lcl, *argv, req.ifa.ifa_family);
+			if (req.ifa.ifa_family == AF_UNSPEC)
+				req.ifa.ifa_family = lcl.family;
+			addattr_l(&req.n, sizeof(req), IFA_LOCAL, &lcl.data, lcl.bytelen);
+			local_len = lcl.bytelen;
+		}
+		argc--; argv++;
+	}
+	if (ifa_flags <= 0xff)
+		req.ifa.ifa_flags = ifa_flags;
+	else
+		addattr32(&req.n, sizeof(req), IFA_FLAGS, ifa_flags);
+
+	if (d == NULL) {
+		fprintf(stderr, "Not enough information: \"dev\" argument is required.\n");
+		return -1;
+	}
+	if (l && !is_valid_label(d, l)) {
+		fprintf(stderr,
+			"\"label\" (%s) must match \"dev\" (%s) or be prefixed by \"dev\" with a colon.\n",
+			l, d);
+		return -1;
+	}
+
+	if (peer_len == 0 && local_len) {
+		if (cmd == RTM_DELADDR && lcl.family == AF_INET && !(lcl.flags & PREFIXLEN_SPECIFIED)) {
+			fprintf(stderr,
+			    "Warning: Executing wildcard deletion to stay compatible with old scripts.\n"
+			    "         Explicitly specify the prefix length (%s/%d) to avoid this warning.\n"
+			    "         This special behaviour is likely to disappear in further releases,\n"
+			    "         fix your scripts!\n", lcl_arg, local_len*8);
+		} else {
+			peer = lcl;
+			addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &lcl.data, lcl.bytelen);
+		}
+	}
+	if (req.ifa.ifa_prefixlen == 0)
+		req.ifa.ifa_prefixlen = lcl.bitlen;
+
+	if (brd_len < 0 && cmd != RTM_DELADDR) {
+		inet_prefix brd;
+		int i;
+
+		if (req.ifa.ifa_family != AF_INET) {
+			fprintf(stderr, "Broadcast can be set only for IPv4 addresses\n");
+			return -1;
+		}
+		brd = peer;
+		if (brd.bitlen <= 30) {
+			for (i = 31; i >= brd.bitlen; i--) {
+				if (brd_len == -1)
+					brd.data[0] |= htonl(1<<(31-i));
+				else
+					brd.data[0] &= ~htonl(1<<(31-i));
+			}
+			addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &brd.data, brd.bytelen);
+			brd_len = brd.bytelen;
+		}
+	}
+	if (!scoped && cmd != RTM_DELADDR)
+		req.ifa.ifa_scope = default_scope(&lcl);
+
+	req.ifa.ifa_index = ll_name_to_index(d);
+	if (!req.ifa.ifa_index)
+		return nodev(d);
+
+	if (valid_lftp || preferred_lftp) {
+		struct ifa_cacheinfo cinfo = {};
+
+		if (!valid_lft) {
+			fprintf(stderr, "valid_lft is zero\n");
+			return -1;
+		}
+		if (valid_lft < preferred_lft) {
+			fprintf(stderr, "preferred_lft is greater than valid_lft\n");
+			return -1;
+		}
+
+		cinfo.ifa_prefered = preferred_lft;
+		cinfo.ifa_valid = valid_lft;
+		addattr_l(&req.n, sizeof(req), IFA_CACHEINFO, &cinfo,
+			  sizeof(cinfo));
+	}
+
+	if ((ifa_flags & IFA_F_MCAUTOJOIN) && !ipaddr_is_multicast(&lcl)) {
+		fprintf(stderr, "autojoin needs multicast address\n");
+		return -1;
+	}
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+int do_ipaddr(int argc, char **argv)
+{
+	if (argc < 1)
+		return ipaddr_list_flush_or_save(0, NULL, IPADD_LIST);
+	if (matches(*argv, "add") == 0)
+		return ipaddr_modify(RTM_NEWADDR, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+	if (matches(*argv, "change") == 0 ||
+		strcmp(*argv, "chg") == 0)
+		return ipaddr_modify(RTM_NEWADDR, NLM_F_REPLACE, argc-1, argv+1);
+	if (matches(*argv, "replace") == 0)
+		return ipaddr_modify(RTM_NEWADDR, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return ipaddr_modify(RTM_DELADDR, 0, argc-1, argv+1);
+	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+	    || matches(*argv, "lst") == 0)
+		return ipaddr_list_flush_or_save(argc-1, argv+1, IPADD_LIST);
+	if (matches(*argv, "flush") == 0)
+		return ipaddr_list_flush_or_save(argc-1, argv+1, IPADD_FLUSH);
+	if (matches(*argv, "save") == 0)
+		return ipaddr_list_flush_or_save(argc-1, argv+1, IPADD_SAVE);
+	if (matches(*argv, "showdump") == 0)
+		return ipaddr_showdump();
+	if (matches(*argv, "restore") == 0)
+		return ipaddr_restore();
+	if (matches(*argv, "help") == 0)
+		usage();
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip address help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ipaddrlabel.c b/ip/ipaddrlabel.c
new file mode 100644
index 0000000..beb08da
--- /dev/null
+++ b/ip/ipaddrlabel.c
@@ -0,0 +1,273 @@
+/*
+ * ipaddrlabel.c	"ip addrlabel"
+ *
+ * Copyright (C)2007 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 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, see <http://www.gnu.org/licenses>.
+ *
+ *
+ * Based on iprule.c.
+ *
+ * Authors:	YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <linux/types.h>
+#include <linux/if_addrlabel.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+#define IFAL_RTA(r)	((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrlblmsg))))
+#define IFAL_PAYLOAD(n)	NLMSG_PAYLOAD(n, sizeof(struct ifaddrlblmsg))
+
+extern struct rtnl_handle rth;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip addrlabel { add | del } prefix PREFIX [ dev DEV ] [ label LABEL ]\n"
+		"       ip addrlabel [ list | flush | help ]\n");
+	exit(-1);
+}
+
+int print_addrlabel(struct nlmsghdr *n, void *arg)
+{
+	struct ifaddrlblmsg *ifal = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[IFAL_MAX+1];
+
+	if (n->nlmsg_type != RTM_NEWADDRLABEL && n->nlmsg_type != RTM_DELADDRLABEL)
+		return 0;
+
+	len -= NLMSG_LENGTH(sizeof(*ifal));
+	if (len < 0)
+		return -1;
+
+	parse_rtattr(tb, IFAL_MAX, IFAL_RTA(ifal), len);
+
+	open_json_object(NULL);
+	if (n->nlmsg_type == RTM_DELADDRLABEL)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	if (tb[IFAL_ADDRESS]) {
+		const char *host
+			= format_host_rta(ifal->ifal_family,
+					  tb[IFAL_ADDRESS]);
+
+		print_string(PRINT_FP, NULL, "prefix ", NULL);
+		print_color_string(PRINT_ANY,
+				   ifa_family_color(ifal->ifal_family),
+				   "address", "%s", host);
+
+		print_uint(PRINT_ANY, "prefixlen", "/%u ",
+			   ifal->ifal_prefixlen);
+	}
+
+	if (ifal->ifal_index) {
+		print_string(PRINT_FP, NULL, "dev ", NULL);
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "ifname", "%s ",
+				   ll_index_to_name(ifal->ifal_index));
+	}
+
+	if (tb[IFAL_LABEL] && RTA_PAYLOAD(tb[IFAL_LABEL]) == sizeof(uint32_t)) {
+		uint32_t label = rta_getattr_u32(tb[IFAL_LABEL]);
+
+		print_uint(PRINT_ANY,
+			   "label", "label %u ", label);
+	}
+	print_string(PRINT_FP, NULL, "\n", "");
+	close_json_object();
+
+	return 0;
+}
+
+static int ipaddrlabel_list(int argc, char **argv)
+{
+	int af = preferred_family;
+
+	if (af == AF_UNSPEC)
+		af = AF_INET6;
+
+	if (argc > 0) {
+		fprintf(stderr, "\"ip addrlabel show\" does not take any arguments.\n");
+		return -1;
+	}
+
+	if (rtnl_addrlbldump_req(&rth, af) < 0) {
+		perror("Cannot send dump request");
+		return 1;
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&rth, print_addrlabel, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+	delete_json_obj();
+
+	return 0;
+}
+
+
+static int ipaddrlabel_modify(int cmd, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct ifaddrlblmsg	ifal;
+		char			buf[1024];
+	} req = {
+		.n.nlmsg_type = cmd,
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrlblmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.ifal.ifal_family = preferred_family,
+	};
+
+	inet_prefix prefix = {};
+	uint32_t label = 0xffffffffUL;
+	char *p = NULL;
+	char *l = NULL;
+
+	if (cmd == RTM_NEWADDRLABEL) {
+		req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_EXCL;
+	}
+
+	while (argc > 0) {
+		if (strcmp(*argv, "prefix") == 0) {
+			NEXT_ARG();
+			p = *argv;
+			get_prefix(&prefix, *argv, preferred_family);
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if ((req.ifal.ifal_index = ll_name_to_index(*argv)) == 0)
+				invarg("dev is invalid\n", *argv);
+		} else if (strcmp(*argv, "label") == 0) {
+			NEXT_ARG();
+			l = *argv;
+			if (get_u32(&label, *argv, 0) || label == 0xffffffffUL)
+				invarg("label is invalid\n", *argv);
+		}
+		argc--;
+		argv++;
+	}
+	if (p == NULL) {
+		fprintf(stderr, "Not enough information: \"prefix\" argument is required.\n");
+		return -1;
+	}
+	if (l == NULL) {
+		fprintf(stderr, "Not enough information: \"label\" argument is required.\n");
+		return -1;
+	}
+	addattr32(&req.n, sizeof(req), IFAL_LABEL, label);
+	addattr_l(&req.n, sizeof(req), IFAL_ADDRESS, &prefix.data, prefix.bytelen);
+	req.ifal.ifal_prefixlen = prefix.bitlen;
+
+	if (req.ifal.ifal_family == AF_UNSPEC)
+		req.ifal.ifal_family = AF_INET6;
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+
+static int flush_addrlabel(struct nlmsghdr *n, void *arg)
+{
+	struct rtnl_handle rth2;
+	struct rtmsg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[IFAL_MAX+1];
+
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0)
+		return -1;
+
+	parse_rtattr(tb, IFAL_MAX, RTM_RTA(r), len);
+
+	if (tb[IFAL_ADDRESS]) {
+		n->nlmsg_type = RTM_DELADDRLABEL;
+		n->nlmsg_flags = NLM_F_REQUEST;
+
+		if (rtnl_open(&rth2, 0) < 0)
+			return -1;
+
+		if (rtnl_talk(&rth2, n, NULL) < 0)
+			return -2;
+
+		rtnl_close(&rth2);
+	}
+
+	return 0;
+}
+
+static int ipaddrlabel_flush(int argc, char **argv)
+{
+	int af = preferred_family;
+
+	if (af == AF_UNSPEC)
+		af = AF_INET6;
+
+	if (argc > 0) {
+		fprintf(stderr, "\"ip addrlabel flush\" does not allow extra arguments\n");
+		return -1;
+	}
+
+	if (rtnl_addrlbldump_req(&rth, af) < 0) {
+		perror("Cannot send dump request");
+		return -1;
+	}
+
+	if (rtnl_dump_filter(&rth, flush_addrlabel, NULL) < 0) {
+		fprintf(stderr, "Flush terminated\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+int do_ipaddrlabel(int argc, char **argv)
+{
+	if (argc < 1) {
+		return ipaddrlabel_list(0, NULL);
+	} else if (matches(argv[0], "list") == 0 ||
+		   matches(argv[0], "lst") == 0 ||
+		   matches(argv[0], "show") == 0) {
+		return ipaddrlabel_list(argc-1, argv+1);
+	} else if (matches(argv[0], "add") == 0) {
+		return ipaddrlabel_modify(RTM_NEWADDRLABEL, argc-1, argv+1);
+	} else if (matches(argv[0], "delete") == 0) {
+		return ipaddrlabel_modify(RTM_DELADDRLABEL, argc-1, argv+1);
+	} else if (matches(argv[0], "flush") == 0) {
+		return ipaddrlabel_flush(argc-1, argv+1);
+	} else if (matches(argv[0], "help") == 0)
+		usage();
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip addrlabel help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ipfou.c b/ip/ipfou.c
new file mode 100644
index 0000000..ea126b0
--- /dev/null
+++ b/ip/ipfou.c
@@ -0,0 +1,350 @@
+/*
+ * ipfou.c	FOU (foo over UDP) support
+ *
+ *              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.
+ *
+ * Authors:	Tom Herbert <therbert@google.com>
+ */
+
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/fou.h>
+#include <linux/genetlink.h>
+#include <linux/ip.h>
+#include <arpa/inet.h>
+
+#include "libgenl.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip fou add port PORT { ipproto PROTO  | gue } [ -6 ]\n"
+		"		   [ local IFADDR ] [ peer IFADDR ]\n"
+		"		   [ peer_port PORT ] [ dev IFNAME ]\n"
+		"       ip fou del port PORT [ -6 ] [ local IFADDR ]\n"
+		"		   [ peer IFADDR ] [ peer_port PORT ]\n"
+		"		   [ dev IFNAME ]\n"
+		"       ip fou show\n"
+		"\n"
+		"Where: PROTO { ipproto-name | 1..255 }\n"
+		"       PORT { 1..65535 }\n"
+		"       IFADDR { addr }\n");
+
+	exit(-1);
+}
+
+/* netlink socket */
+static struct rtnl_handle genl_rth = { .fd = -1 };
+static int genl_family = -1;
+
+#define FOU_REQUEST(_req, _bufsiz, _cmd, _flags)	\
+	GENL_REQUEST(_req, _bufsiz, genl_family, 0,	\
+		     FOU_GENL_VERSION, _cmd, _flags)
+
+static int fou_parse_opt(int argc, char **argv, struct nlmsghdr *n,
+			 bool adding)
+{
+	const char *local = NULL, *peer = NULL;
+	__u16 port, peer_port = 0;
+	__u8 family = AF_INET;
+	bool gue_set = false;
+	int ipproto_set = 0;
+	__u8 ipproto, type;
+	int port_set = 0;
+	int index = 0;
+
+	while (argc > 0) {
+		if (!matches(*argv, "port")) {
+			NEXT_ARG();
+
+			if (get_be16(&port, *argv, 0) || port == 0)
+				invarg("invalid port", *argv);
+			port_set = 1;
+		} else if (!matches(*argv, "ipproto")) {
+			struct protoent *servptr;
+
+			NEXT_ARG();
+
+			servptr = getprotobyname(*argv);
+			if (servptr)
+				ipproto = servptr->p_proto;
+			else if (get_u8(&ipproto, *argv, 0) || ipproto == 0)
+				invarg("invalid ipproto", *argv);
+			ipproto_set = 1;
+		} else if (!matches(*argv, "gue")) {
+			gue_set = true;
+		} else if (!matches(*argv, "-6")) {
+			family = AF_INET6;
+		} else if (!matches(*argv, "local")) {
+			NEXT_ARG();
+
+			local = *argv;
+		} else if (!matches(*argv, "peer")) {
+			NEXT_ARG();
+
+			peer = *argv;
+		} else if (!matches(*argv, "peer_port")) {
+			NEXT_ARG();
+
+			if (get_be16(&peer_port, *argv, 0) || peer_port == 0)
+				invarg("invalid peer port", *argv);
+		} else if (!matches(*argv, "dev")) {
+			const char *ifname;
+
+			NEXT_ARG();
+
+			ifname = *argv;
+
+			if (check_ifname(ifname)) {
+				fprintf(stderr, "fou: invalid device name\n");
+				exit(EXIT_FAILURE);
+			}
+
+			index = ll_name_to_index(ifname);
+
+			if (!index) {
+				fprintf(stderr, "fou: unknown device name\n");
+				exit(EXIT_FAILURE);
+			}
+		} else {
+			fprintf(stderr
+				, "fou: unknown command \"%s\"?\n", *argv);
+			usage();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (!port_set) {
+		fprintf(stderr, "fou: missing port\n");
+		return -1;
+	}
+
+	if (!ipproto_set && !gue_set && adding) {
+		fprintf(stderr, "fou: must set ipproto or gue\n");
+		return -1;
+	}
+
+	if (ipproto_set && gue_set) {
+		fprintf(stderr, "fou: cannot set ipproto and gue\n");
+		return -1;
+	}
+
+	if ((peer_port && !peer) || (peer && !peer_port)) {
+		fprintf(stderr, "fou: both peer and peer port must be set\n");
+		return -1;
+	}
+
+	type = gue_set ? FOU_ENCAP_GUE : FOU_ENCAP_DIRECT;
+
+	addattr16(n, 1024, FOU_ATTR_PORT, port);
+	addattr8(n, 1024, FOU_ATTR_TYPE, type);
+	addattr8(n, 1024, FOU_ATTR_AF, family);
+
+	if (ipproto_set)
+		addattr8(n, 1024, FOU_ATTR_IPPROTO, ipproto);
+
+	if (local) {
+		inet_prefix local_addr;
+		__u8 attr_type = family == AF_INET ? FOU_ATTR_LOCAL_V4 :
+						     FOU_ATTR_LOCAL_V6;
+
+		if (get_addr(&local_addr, local, family)) {
+			fprintf(stderr, "fou: parsing local address failed\n");
+			exit(EXIT_FAILURE);
+		}
+		addattr_l(n, 1024, attr_type, &local_addr.data,
+			  local_addr.bytelen);
+	}
+
+	if (peer) {
+		inet_prefix peer_addr;
+		__u8 attr_type = family == AF_INET ? FOU_ATTR_PEER_V4 :
+						     FOU_ATTR_PEER_V6;
+
+		if (get_addr(&peer_addr, peer, family)) {
+			fprintf(stderr, "fou: parsing peer address failed\n");
+			exit(EXIT_FAILURE);
+		}
+		addattr_l(n, 1024, attr_type, &peer_addr.data,
+			  peer_addr.bytelen);
+
+		if (peer_port)
+			addattr16(n, 1024, FOU_ATTR_PEER_PORT, peer_port);
+	}
+
+	if (index)
+		addattr32(n, 1024, FOU_ATTR_IFINDEX, index);
+
+	return 0;
+}
+
+static int do_add(int argc, char **argv)
+{
+	FOU_REQUEST(req, 1024, FOU_CMD_ADD, NLM_F_REQUEST);
+
+	fou_parse_opt(argc, argv, &req.n, true);
+
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static int do_del(int argc, char **argv)
+{
+	FOU_REQUEST(req, 1024, FOU_CMD_DEL, NLM_F_REQUEST);
+
+	fou_parse_opt(argc, argv, &req.n, false);
+
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static int print_fou_mapping(struct nlmsghdr *n, void *arg)
+{
+	__u8 family = AF_INET, local_attr_type, peer_attr_type, byte_len;
+	struct rtattr *tb[FOU_ATTR_MAX + 1];
+	__u8 empty_buf[16] = {0};
+	struct genlmsghdr *ghdr;
+	int len = n->nlmsg_len;
+
+	if (n->nlmsg_type != genl_family)
+		return 0;
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+	if (len < 0)
+		return -1;
+
+	ghdr = NLMSG_DATA(n);
+	parse_rtattr(tb, FOU_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
+
+	open_json_object(NULL);
+	if (tb[FOU_ATTR_PORT])
+		print_uint(PRINT_ANY, "port", "port %u",
+			   ntohs(rta_getattr_u16(tb[FOU_ATTR_PORT])));
+
+	if (tb[FOU_ATTR_TYPE] &&
+	    rta_getattr_u8(tb[FOU_ATTR_TYPE]) == FOU_ENCAP_GUE)
+		print_null(PRINT_ANY, "gue", " gue", NULL);
+	else if (tb[FOU_ATTR_IPPROTO])
+		print_uint(PRINT_ANY, "ipproto",
+			   " ipproto %u", rta_getattr_u8(tb[FOU_ATTR_IPPROTO]));
+
+	if (tb[FOU_ATTR_AF]) {
+		family = rta_getattr_u8(tb[FOU_ATTR_AF]);
+
+		print_string(PRINT_JSON, "family", NULL,
+			     family_name(family));
+
+		if (family == AF_INET6)
+			print_string(PRINT_FP, NULL,
+				     " -6", NULL);
+	}
+
+	local_attr_type = family == AF_INET ? FOU_ATTR_LOCAL_V4 :
+					      FOU_ATTR_LOCAL_V6;
+	peer_attr_type = family == AF_INET ? FOU_ATTR_PEER_V4 :
+					     FOU_ATTR_PEER_V6;
+	byte_len = af_bit_len(family) / 8;
+
+	if (tb[local_attr_type] && memcmp(RTA_DATA(tb[local_attr_type]),
+					  empty_buf, byte_len)) {
+		print_string(PRINT_ANY, "local", " local %s",
+			     format_host_rta(family, tb[local_attr_type]));
+	}
+
+	if (tb[peer_attr_type] && memcmp(RTA_DATA(tb[peer_attr_type]),
+					 empty_buf, byte_len)) {
+		print_string(PRINT_ANY, "peer", " peer %s",
+			     format_host_rta(family, tb[peer_attr_type]));
+	}
+
+	if (tb[FOU_ATTR_PEER_PORT]) {
+		__u16 p_port = ntohs(rta_getattr_u16(tb[FOU_ATTR_PEER_PORT]));
+
+		if (p_port)
+			print_uint(PRINT_ANY, "peer_port", " peer_port %u",
+				   p_port);
+
+	}
+
+	if (tb[FOU_ATTR_IFINDEX]) {
+		int index = rta_getattr_s32(tb[FOU_ATTR_IFINDEX]);
+
+		if (index) {
+			const char *ifname;
+
+			ifname = ll_index_to_name(index);
+
+			if (ifname)
+				print_string(PRINT_ANY, "dev", " dev %s",
+					     ifname);
+		}
+	}
+
+	print_string(PRINT_FP, NULL, "\n", NULL);
+	close_json_object();
+
+	return 0;
+}
+
+static int do_show(int argc, char **argv)
+{
+	FOU_REQUEST(req, 4096, FOU_CMD_GET, NLM_F_REQUEST | NLM_F_DUMP);
+
+	if (argc > 0) {
+		fprintf(stderr,
+			"\"ip fou show\" does not take any arguments.\n");
+		return -1;
+	}
+
+	if (rtnl_send(&genl_rth, &req.n, req.n.nlmsg_len) < 0) {
+		perror("Cannot send show request");
+		exit(1);
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&genl_rth, print_fou_mapping, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+	delete_json_obj();
+	fflush(stdout);
+
+	return 0;
+}
+
+int do_ipfou(int argc, char **argv)
+{
+	if (argc < 1)
+		usage();
+
+	if (matches(*argv, "help") == 0)
+		usage();
+
+	if (genl_init_handle(&genl_rth, FOU_GENL_NAME, &genl_family))
+		exit(1);
+
+	if (matches(*argv, "add") == 0)
+		return do_add(argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return do_del(argc-1, argv+1);
+	if (matches(*argv, "show") == 0)
+		return do_show(argc-1, argv+1);
+
+	fprintf(stderr,
+		"Command \"%s\" is unknown, try \"ip fou help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ipila.c b/ip/ipila.c
new file mode 100644
index 0000000..739ee4e
--- /dev/null
+++ b/ip/ipila.c
@@ -0,0 +1,307 @@
+/*
+ * ipila.c	ILA (Identifier Locator Addressing) support
+ *
+ *              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.
+ *
+ * Authors:	Tom Herbert <tom@herbertland.com>
+ */
+
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/ila.h>
+#include <linux/genetlink.h>
+#include <linux/ip.h>
+#include <arpa/inet.h>
+
+#include "libgenl.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "ila_common.h"
+#include "json_print.h"
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip ila add loc_match LOCATOR_MATCH loc LOCATOR [ dev DEV ] OPTIONS\n"
+		"       ip ila del loc_match LOCATOR_MATCH [ loc LOCATOR ] [ dev DEV ]\n"
+		"       ip ila list\n"
+		"OPTIONS := [ csum-mode { adj-transport | neutral-map | neutral-map-auto | no-action } ]\n"
+		"           [ ident-type { luid | use-format } ]\n");
+
+	exit(-1);
+}
+
+/* netlink socket */
+static struct rtnl_handle genl_rth = { .fd = -1 };
+static int genl_family = -1;
+
+#define ILA_REQUEST(_req, _bufsiz, _cmd, _flags)	\
+	GENL_REQUEST(_req, _bufsiz, genl_family, 0,	\
+		     ILA_GENL_VERSION, _cmd, _flags)
+
+#define ILA_RTA(g) ((struct rtattr *)(((char *)(g)) +	\
+	NLMSG_ALIGN(sizeof(struct genlmsghdr))))
+
+static void print_addr64(__u64 addr, char *buff, size_t len)
+{
+	__u16 *words = (__u16 *)&addr;
+	__u16 v;
+	int i, ret;
+	size_t written = 0;
+	char *sep = ":";
+
+	for (i = 0; i < 4; i++) {
+		v = ntohs(words[i]);
+
+		if (i == 3)
+			sep = "";
+
+		ret = snprintf(&buff[written], len - written, "%x%s", v, sep);
+		written += ret;
+	}
+}
+
+static void print_ila_locid(const char *tag, int attr, struct rtattr *tb[])
+{
+	char abuf[256];
+
+	if (tb[attr])
+		print_addr64(rta_getattr_u64(tb[attr]),
+			     abuf, sizeof(abuf));
+	else
+		snprintf(abuf, sizeof(abuf), "-");
+
+	/* 20 = sizeof("xxxx:xxxx:xxxx:xxxx") */
+	print_string(PRINT_ANY, tag, "%-20s", abuf);
+}
+
+static int print_ila_mapping(struct nlmsghdr *n, void *arg)
+{
+	struct genlmsghdr *ghdr;
+	struct rtattr *tb[ILA_ATTR_MAX + 1];
+	int len = n->nlmsg_len;
+
+	if (n->nlmsg_type != genl_family)
+		return 0;
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+	if (len < 0)
+		return -1;
+
+	ghdr = NLMSG_DATA(n);
+	parse_rtattr(tb, ILA_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
+
+	open_json_object(NULL);
+	print_ila_locid("locator_match", ILA_ATTR_LOCATOR_MATCH, tb);
+	print_ila_locid("locator", ILA_ATTR_LOCATOR, tb);
+
+	if (tb[ILA_ATTR_IFINDEX]) {
+		__u32 ifindex
+			= rta_getattr_u32(tb[ILA_ATTR_IFINDEX]);
+
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "interface", "%-16s",
+				   ll_index_to_name(ifindex));
+	} else {
+		print_string(PRINT_FP, NULL, "%-10s ", "-");
+	}
+
+	if (tb[ILA_ATTR_CSUM_MODE]) {
+		__u8 csum = rta_getattr_u8(tb[ILA_ATTR_CSUM_MODE]);
+
+		print_string(PRINT_ANY, "csum_mode", "%s",
+			     ila_csum_mode2name(csum));
+	} else
+		print_string(PRINT_FP, NULL, "%-10s ", "-");
+
+	if (tb[ILA_ATTR_IDENT_TYPE])
+		print_string(PRINT_ANY, "ident_type", "%s",
+			ila_ident_type2name(rta_getattr_u8(
+						tb[ILA_ATTR_IDENT_TYPE])));
+	else
+		print_string(PRINT_FP, NULL, "%s", "-");
+
+	print_nl();
+	close_json_object();
+
+	return 0;
+}
+
+#define NLMSG_BUF_SIZE 4096
+
+static int do_list(int argc, char **argv)
+{
+	ILA_REQUEST(req, 1024, ILA_CMD_GET, NLM_F_REQUEST | NLM_F_DUMP);
+
+	if (argc > 0) {
+		fprintf(stderr, "\"ip ila show\" does not take "
+			"any arguments.\n");
+		return -1;
+	}
+
+	if (rtnl_send(&genl_rth, (void *)&req, req.n.nlmsg_len) < 0) {
+		perror("Cannot send dump request");
+		exit(1);
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&genl_rth, print_ila_mapping, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+	delete_json_obj();
+	fflush(stdout);
+
+	return 0;
+}
+
+static int ila_parse_opt(int argc, char **argv, struct nlmsghdr *n,
+			 bool adding)
+{
+	__u64 locator = 0;
+	__u64 locator_match = 0;
+	int ifindex = 0;
+	int csum_mode = 0;
+	int ident_type = 0;
+	bool loc_set = false;
+	bool loc_match_set = false;
+	bool ifindex_set = false;
+	bool csum_mode_set = false;
+	bool ident_type_set = false;
+
+	while (argc > 0) {
+		if (!matches(*argv, "loc")) {
+			NEXT_ARG();
+
+			if (get_addr64(&locator, *argv) < 0) {
+				fprintf(stderr, "Bad locator: %s\n", *argv);
+				return -1;
+			}
+			loc_set = true;
+		} else if (!matches(*argv, "loc_match")) {
+			NEXT_ARG();
+
+			if (get_addr64(&locator_match, *argv) < 0) {
+				fprintf(stderr, "Bad locator to match: %s\n",
+					*argv);
+				return -1;
+			}
+			loc_match_set = true;
+		} else if (!matches(*argv, "csum-mode")) {
+			NEXT_ARG();
+
+			csum_mode = ila_csum_name2mode(*argv);
+			if (csum_mode < 0) {
+				fprintf(stderr, "Bad csum-mode: %s\n",
+					*argv);
+				return -1;
+			}
+			csum_mode_set = true;
+		} else if (!matches(*argv, "ident-type")) {
+			NEXT_ARG();
+
+			ident_type = ila_ident_name2type(*argv);
+			if (ident_type < 0) {
+				fprintf(stderr, "Bad ident-type: %s\n",
+					*argv);
+				return -1;
+			}
+			ident_type_set = true;
+		} else if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+
+			ifindex = ll_name_to_index(*argv);
+			if (ifindex == 0) {
+				fprintf(stderr, "No such interface: %s\n",
+					*argv);
+				return -1;
+			}
+			ifindex_set = true;
+		} else {
+			usage();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (adding) {
+		if (!loc_set) {
+			fprintf(stderr, "ila: missing locator\n");
+			return -1;
+		}
+		if (!loc_match_set) {
+			fprintf(stderr, "ila: missing locator0match\n");
+			return -1;
+		}
+	}
+
+	if (loc_match_set)
+		addattr64(n, 1024, ILA_ATTR_LOCATOR_MATCH, locator_match);
+
+	if (loc_set)
+		addattr64(n, 1024, ILA_ATTR_LOCATOR, locator);
+
+	if (ifindex_set)
+		addattr32(n, 1024, ILA_ATTR_IFINDEX, ifindex);
+
+	if (csum_mode_set)
+		addattr8(n, 1024, ILA_ATTR_CSUM_MODE, csum_mode);
+
+	if (ident_type_set)
+		addattr8(n, 1024, ILA_ATTR_IDENT_TYPE, ident_type);
+
+	return 0;
+}
+
+static int do_add(int argc, char **argv)
+{
+	ILA_REQUEST(req, 1024, ILA_CMD_ADD, NLM_F_REQUEST);
+
+	ila_parse_opt(argc, argv, &req.n, true);
+
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static int do_del(int argc, char **argv)
+{
+	ILA_REQUEST(req, 1024, ILA_CMD_DEL, NLM_F_REQUEST);
+
+	ila_parse_opt(argc, argv, &req.n, false);
+
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+int do_ipila(int argc, char **argv)
+{
+	if (argc < 1)
+		usage();
+
+	if (matches(*argv, "help") == 0)
+		usage();
+
+	if (genl_init_handle(&genl_rth, ILA_GENL_NAME, &genl_family))
+		exit(1);
+
+	if (matches(*argv, "add") == 0)
+		return do_add(argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return do_del(argc-1, argv+1);
+	if (matches(*argv, "list") == 0)
+		return do_list(argc-1, argv+1);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip ila help\".\n",
+		*argv);
+	exit(-1);
+}
diff --git a/ip/ipl2tp.c b/ip/ipl2tp.c
new file mode 100644
index 0000000..77bc324
--- /dev/null
+++ b/ip/ipl2tp.c
@@ -0,0 +1,850 @@
+/*
+ * ipl2tp.c	       "ip l2tp"
+ *
+ *		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.
+ *
+ * Original Author:	James Chapman <jchapman@katalix.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/ip.h>
+
+#include <linux/genetlink.h>
+#include <linux/l2tp.h>
+#include "libgenl.h"
+
+#include "utils.h"
+#include "ip_common.h"
+
+enum {
+	L2TP_ADD,
+	L2TP_CHG,
+	L2TP_DEL,
+	L2TP_GET
+};
+
+struct l2tp_parm {
+	uint32_t tunnel_id;
+	uint32_t peer_tunnel_id;
+	uint32_t session_id;
+	uint32_t peer_session_id;
+	enum l2tp_encap_type encap;
+	uint16_t local_udp_port;
+	uint16_t peer_udp_port;
+	int cookie_len;
+	uint8_t cookie[8];
+	int peer_cookie_len;
+	uint8_t peer_cookie[8];
+	inet_prefix local_ip;
+	inet_prefix peer_ip;
+
+	uint16_t pw_type;
+	unsigned int udp6_csum_tx:1;
+	unsigned int udp6_csum_rx:1;
+	unsigned int udp_csum:1;
+	unsigned int recv_seq:1;
+	unsigned int send_seq:1;
+	unsigned int tunnel:1;
+	unsigned int session:1;
+	int reorder_timeout;
+	const char *ifname;
+	uint8_t l2spec_type;
+	uint8_t l2spec_len;
+};
+
+struct l2tp_stats {
+	uint64_t data_rx_packets;
+	uint64_t data_rx_bytes;
+	uint64_t data_rx_errors;
+	uint64_t data_rx_oos_packets;
+	uint64_t data_rx_oos_discards;
+	uint64_t data_tx_packets;
+	uint64_t data_tx_bytes;
+	uint64_t data_tx_errors;
+};
+
+struct l2tp_data {
+	struct l2tp_parm config;
+	struct l2tp_stats stats;
+};
+
+/* netlink socket */
+static struct rtnl_handle genl_rth;
+static int genl_family = -1;
+
+/*****************************************************************************
+ * Netlink actions
+ *****************************************************************************/
+
+static int create_tunnel(struct l2tp_parm *p)
+{
+	uint32_t local_attr = L2TP_ATTR_IP_SADDR;
+	uint32_t peer_attr = L2TP_ATTR_IP_DADDR;
+
+	GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_TUNNEL_CREATE, NLM_F_REQUEST | NLM_F_ACK);
+
+	addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id);
+	addattr32(&req.n, 1024, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id);
+	addattr8(&req.n, 1024, L2TP_ATTR_PROTO_VERSION, 3);
+	addattr16(&req.n, 1024, L2TP_ATTR_ENCAP_TYPE, p->encap);
+
+	if (p->local_ip.family == AF_INET6)
+		local_attr = L2TP_ATTR_IP6_SADDR;
+	addattr_l(&req.n, 1024, local_attr, &p->local_ip.data,
+		  p->local_ip.bytelen);
+
+	if (p->peer_ip.family == AF_INET6)
+		peer_attr = L2TP_ATTR_IP6_DADDR;
+	addattr_l(&req.n, 1024, peer_attr, &p->peer_ip.data,
+		  p->peer_ip.bytelen);
+
+	if (p->encap == L2TP_ENCAPTYPE_UDP) {
+		addattr16(&req.n, 1024, L2TP_ATTR_UDP_SPORT, p->local_udp_port);
+		addattr16(&req.n, 1024, L2TP_ATTR_UDP_DPORT, p->peer_udp_port);
+		if (p->udp_csum)
+			addattr8(&req.n, 1024, L2TP_ATTR_UDP_CSUM, 1);
+		if (!p->udp6_csum_tx)
+			addattr(&req.n, 1024, L2TP_ATTR_UDP_ZERO_CSUM6_TX);
+		if (!p->udp6_csum_rx)
+			addattr(&req.n, 1024, L2TP_ATTR_UDP_ZERO_CSUM6_RX);
+	}
+
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static int delete_tunnel(struct l2tp_parm *p)
+{
+	GENL_REQUEST(req, 128, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_TUNNEL_DELETE, NLM_F_REQUEST | NLM_F_ACK);
+
+	addattr32(&req.n, 128, L2TP_ATTR_CONN_ID, p->tunnel_id);
+
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static int create_session(struct l2tp_parm *p)
+{
+	GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_SESSION_CREATE, NLM_F_REQUEST | NLM_F_ACK);
+
+	addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id);
+	addattr32(&req.n, 1024, L2TP_ATTR_PEER_CONN_ID, p->peer_tunnel_id);
+	addattr32(&req.n, 1024, L2TP_ATTR_SESSION_ID, p->session_id);
+	addattr32(&req.n, 1024, L2TP_ATTR_PEER_SESSION_ID, p->peer_session_id);
+	addattr16(&req.n, 1024, L2TP_ATTR_PW_TYPE, p->pw_type);
+	addattr8(&req.n, 1024, L2TP_ATTR_L2SPEC_TYPE, p->l2spec_type);
+	addattr8(&req.n, 1024, L2TP_ATTR_L2SPEC_LEN, p->l2spec_len);
+
+	if (p->recv_seq)
+		addattr8(&req.n, 1024, L2TP_ATTR_RECV_SEQ, 1);
+	if (p->send_seq)
+		addattr8(&req.n, 1024, L2TP_ATTR_SEND_SEQ, 1);
+	if (p->reorder_timeout)
+		addattr64(&req.n, 1024, L2TP_ATTR_RECV_TIMEOUT,
+					  p->reorder_timeout);
+	if (p->cookie_len)
+		addattr_l(&req.n, 1024, L2TP_ATTR_COOKIE,
+			  p->cookie, p->cookie_len);
+	if (p->peer_cookie_len)
+		addattr_l(&req.n, 1024, L2TP_ATTR_PEER_COOKIE,
+			  p->peer_cookie,  p->peer_cookie_len);
+	if (p->ifname)
+		addattrstrz(&req.n, 1024, L2TP_ATTR_IFNAME, p->ifname);
+
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static int delete_session(struct l2tp_parm *p)
+{
+	GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_SESSION_DELETE, NLM_F_REQUEST | NLM_F_ACK);
+
+	addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->tunnel_id);
+	addattr32(&req.n, 1024, L2TP_ATTR_SESSION_ID, p->session_id);
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static void print_cookie(const char *name, const char *fmt,
+			 const uint8_t *cookie, int len)
+{
+	char abuf[32];
+	size_t n;
+
+	n = snprintf(abuf, sizeof(abuf),
+		     "%02x%02x%02x%02x",
+		     cookie[0], cookie[1], cookie[2], cookie[3]);
+	if (len == 8)
+		snprintf(abuf + n, sizeof(abuf) - n,
+			 "%02x%02x%02x%02x",
+			 cookie[4], cookie[5],
+			 cookie[6], cookie[7]);
+
+	print_string(PRINT_ANY, name, fmt, abuf);
+}
+
+static void print_tunnel(const struct l2tp_data *data)
+{
+	const struct l2tp_parm *p = &data->config;
+	char buf[INET6_ADDRSTRLEN];
+
+	open_json_object(NULL);
+	print_uint(PRINT_ANY, "tunnel_id", "Tunnel %u,", p->tunnel_id);
+	print_string(PRINT_ANY, "encap", " encap %s",
+		     p->encap == L2TP_ENCAPTYPE_UDP ? "UDP" :
+		     p->encap == L2TP_ENCAPTYPE_IP ? "IP" : "??");
+	print_nl();
+
+	print_string(PRINT_ANY, "local", "  From %s ",
+		     inet_ntop(p->local_ip.family, p->local_ip.data,
+			       buf, sizeof(buf)));
+	print_string(PRINT_ANY, "peer", "to %s",
+		     inet_ntop(p->peer_ip.family, p->peer_ip.data,
+			       buf, sizeof(buf)));
+	print_nl();
+
+	print_uint(PRINT_ANY, "peer_tunnel", "  Peer tunnel %u",
+		   p->peer_tunnel_id);
+	print_nl();
+
+	if (p->encap == L2TP_ENCAPTYPE_UDP) {
+		print_string(PRINT_FP, NULL,
+			     "  UDP source / dest ports:", NULL);
+
+		print_hu(PRINT_ANY, "local_port", " %hu",
+			   p->local_udp_port);
+		print_hu(PRINT_ANY, "peer_port", "/%hu",
+			   p->peer_udp_port);
+		print_nl();
+
+		switch (p->local_ip.family) {
+		case AF_INET:
+			print_bool(PRINT_JSON, "checksum",
+				   NULL, p->udp_csum);
+			print_string(PRINT_FP, NULL,
+				     "  UDP checksum: %s\n",
+				     p->udp_csum ? "enabled" : "disabled");
+			break;
+		case AF_INET6:
+			if (is_json_context()) {
+				print_bool(PRINT_JSON, "checksum_tx",
+					   NULL, p->udp6_csum_tx);
+
+				print_bool(PRINT_JSON, "checksum_rx",
+					   NULL, p->udp6_csum_tx);
+			} else {
+				printf("  UDP checksum: %s%s%s%s\n",
+				       p->udp6_csum_tx && p->udp6_csum_rx
+				       ? "enabled" : "",
+				       p->udp6_csum_tx && !p->udp6_csum_rx
+				       ? "tx" : "",
+				       !p->udp6_csum_tx && p->udp6_csum_rx
+				       ? "rx" : "",
+				       !p->udp6_csum_tx && !p->udp6_csum_rx
+				       ? "disabled" : "");
+			}
+			break;
+		}
+	}
+	close_json_object();
+}
+
+static void print_session(struct l2tp_data *data)
+{
+	struct l2tp_parm *p = &data->config;
+
+	open_json_object(NULL);
+
+	print_uint(PRINT_ANY, "session_id", "Session %u", p->session_id);
+	print_uint(PRINT_ANY, "tunnel_id",  " in tunnel %u", p->tunnel_id);
+	print_nl();
+
+	print_uint(PRINT_ANY, "peer_session_id",
+		     "  Peer session %u,", p->peer_session_id);
+	print_uint(PRINT_ANY, "peer_tunnel_id",
+		     " tunnel %u",  p->peer_tunnel_id);
+	print_nl();
+
+	if (p->ifname != NULL) {
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "interface", "  interface name: %s" , p->ifname);
+		print_nl();
+	}
+
+	/* Show offsets only for plain console output (for legacy scripts) */
+	print_uint(PRINT_FP, "offset", "  offset %u,", 0);
+	print_uint(PRINT_FP, "peer_offset", " peer offset %u\n", 0);
+
+	if (p->cookie_len > 0)
+		print_cookie("cookie", "  cookie %s",
+			     p->cookie, p->cookie_len);
+
+	if (p->peer_cookie_len > 0)
+		print_cookie("peer_cookie", "  peer cookie %s",
+			     p->peer_cookie, p->peer_cookie_len);
+
+	if (p->reorder_timeout != 0)
+		print_uint(PRINT_ANY, "reorder_timeout",
+			   "  reorder timeout: %u", p->reorder_timeout);
+
+
+	if (p->send_seq || p->recv_seq) {
+		print_string(PRINT_FP, NULL, "%s  sequence numbering:", _SL_);
+
+		if (p->send_seq)
+			print_null(PRINT_ANY, "send_seq", " send", NULL);
+		if (p->recv_seq)
+			print_null(PRINT_ANY, "recv_seq", " recv", NULL);
+
+	}
+	print_string(PRINT_FP, NULL, "\n", NULL);
+	close_json_object();
+}
+
+static int get_response(struct nlmsghdr *n, void *arg)
+{
+	struct genlmsghdr *ghdr;
+	struct l2tp_data *data = arg;
+	struct l2tp_parm *p = &data->config;
+	struct rtattr *attrs[L2TP_ATTR_MAX + 1];
+	struct rtattr *nla_stats, *rta;
+	int len;
+
+	/* Validate message and parse attributes */
+	if (n->nlmsg_type == NLMSG_ERROR)
+		return -EBADMSG;
+
+	ghdr = NLMSG_DATA(n);
+	len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*ghdr));
+	if (len < 0)
+		return -1;
+
+	parse_rtattr(attrs, L2TP_ATTR_MAX, (void *)ghdr + GENL_HDRLEN, len);
+
+	if (attrs[L2TP_ATTR_PW_TYPE])
+		p->pw_type = rta_getattr_u16(attrs[L2TP_ATTR_PW_TYPE]);
+	if (attrs[L2TP_ATTR_ENCAP_TYPE])
+		p->encap = rta_getattr_u16(attrs[L2TP_ATTR_ENCAP_TYPE]);
+	if (attrs[L2TP_ATTR_CONN_ID])
+		p->tunnel_id = rta_getattr_u32(attrs[L2TP_ATTR_CONN_ID]);
+	if (attrs[L2TP_ATTR_PEER_CONN_ID])
+		p->peer_tunnel_id = rta_getattr_u32(attrs[L2TP_ATTR_PEER_CONN_ID]);
+	if (attrs[L2TP_ATTR_SESSION_ID])
+		p->session_id = rta_getattr_u32(attrs[L2TP_ATTR_SESSION_ID]);
+	if (attrs[L2TP_ATTR_PEER_SESSION_ID])
+		p->peer_session_id = rta_getattr_u32(attrs[L2TP_ATTR_PEER_SESSION_ID]);
+	if (attrs[L2TP_ATTR_L2SPEC_TYPE])
+		p->l2spec_type = rta_getattr_u8(attrs[L2TP_ATTR_L2SPEC_TYPE]);
+	if (attrs[L2TP_ATTR_L2SPEC_LEN])
+		p->l2spec_len = rta_getattr_u8(attrs[L2TP_ATTR_L2SPEC_LEN]);
+
+	if (attrs[L2TP_ATTR_UDP_CSUM])
+		p->udp_csum = !!rta_getattr_u8(attrs[L2TP_ATTR_UDP_CSUM]);
+
+	p->udp6_csum_tx = !attrs[L2TP_ATTR_UDP_ZERO_CSUM6_TX];
+	p->udp6_csum_rx = !attrs[L2TP_ATTR_UDP_ZERO_CSUM6_RX];
+
+	if (attrs[L2TP_ATTR_COOKIE])
+		memcpy(p->cookie, RTA_DATA(attrs[L2TP_ATTR_COOKIE]),
+		       p->cookie_len = RTA_PAYLOAD(attrs[L2TP_ATTR_COOKIE]));
+
+	if (attrs[L2TP_ATTR_PEER_COOKIE])
+		memcpy(p->peer_cookie, RTA_DATA(attrs[L2TP_ATTR_PEER_COOKIE]),
+		       p->peer_cookie_len = RTA_PAYLOAD(attrs[L2TP_ATTR_PEER_COOKIE]));
+
+	if (attrs[L2TP_ATTR_RECV_SEQ])
+		p->recv_seq = !!rta_getattr_u8(attrs[L2TP_ATTR_RECV_SEQ]);
+	if (attrs[L2TP_ATTR_SEND_SEQ])
+		p->send_seq = !!rta_getattr_u8(attrs[L2TP_ATTR_SEND_SEQ]);
+
+	if (attrs[L2TP_ATTR_RECV_TIMEOUT])
+		p->reorder_timeout = rta_getattr_u64(attrs[L2TP_ATTR_RECV_TIMEOUT]);
+
+	rta = attrs[L2TP_ATTR_IP_SADDR];
+	p->local_ip.family = AF_INET;
+	if (!rta) {
+		rta = attrs[L2TP_ATTR_IP6_SADDR];
+		p->local_ip.family = AF_INET6;
+	}
+	if (rta && get_addr_rta(&p->local_ip, rta, p->local_ip.family))
+		return -1;
+
+	rta = attrs[L2TP_ATTR_IP_DADDR];
+	p->peer_ip.family = AF_INET;
+	if (!rta) {
+		rta = attrs[L2TP_ATTR_IP6_DADDR];
+		p->peer_ip.family = AF_INET6;
+	}
+	if (rta && get_addr_rta(&p->peer_ip, rta, p->peer_ip.family))
+		return -1;
+
+	if (attrs[L2TP_ATTR_UDP_SPORT])
+		p->local_udp_port = rta_getattr_u16(attrs[L2TP_ATTR_UDP_SPORT]);
+	if (attrs[L2TP_ATTR_UDP_DPORT])
+		p->peer_udp_port = rta_getattr_u16(attrs[L2TP_ATTR_UDP_DPORT]);
+	if (attrs[L2TP_ATTR_IFNAME])
+		p->ifname = rta_getattr_str(attrs[L2TP_ATTR_IFNAME]);
+
+	nla_stats = attrs[L2TP_ATTR_STATS];
+	if (nla_stats) {
+		struct rtattr *tb[L2TP_ATTR_STATS_MAX + 1];
+
+		parse_rtattr_nested(tb, L2TP_ATTR_STATS_MAX, nla_stats);
+
+		if (tb[L2TP_ATTR_TX_PACKETS])
+			data->stats.data_tx_packets = rta_getattr_u64(tb[L2TP_ATTR_TX_PACKETS]);
+		if (tb[L2TP_ATTR_TX_BYTES])
+			data->stats.data_tx_bytes = rta_getattr_u64(tb[L2TP_ATTR_TX_BYTES]);
+		if (tb[L2TP_ATTR_TX_ERRORS])
+			data->stats.data_tx_errors = rta_getattr_u64(tb[L2TP_ATTR_TX_ERRORS]);
+		if (tb[L2TP_ATTR_RX_PACKETS])
+			data->stats.data_rx_packets = rta_getattr_u64(tb[L2TP_ATTR_RX_PACKETS]);
+		if (tb[L2TP_ATTR_RX_BYTES])
+			data->stats.data_rx_bytes = rta_getattr_u64(tb[L2TP_ATTR_RX_BYTES]);
+		if (tb[L2TP_ATTR_RX_ERRORS])
+			data->stats.data_rx_errors = rta_getattr_u64(tb[L2TP_ATTR_RX_ERRORS]);
+		if (tb[L2TP_ATTR_RX_SEQ_DISCARDS])
+			data->stats.data_rx_oos_discards = rta_getattr_u64(tb[L2TP_ATTR_RX_SEQ_DISCARDS]);
+		if (tb[L2TP_ATTR_RX_OOS_PACKETS])
+			data->stats.data_rx_oos_packets = rta_getattr_u64(tb[L2TP_ATTR_RX_OOS_PACKETS]);
+	}
+
+	return 0;
+}
+
+static int session_nlmsg(struct nlmsghdr *n, void *arg)
+{
+	int ret = get_response(n, arg);
+
+	if (ret == 0)
+		print_session(arg);
+
+	return ret;
+}
+
+static int get_session(struct l2tp_data *p)
+{
+	GENL_REQUEST(req, 128, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_SESSION_GET,
+		     NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST);
+
+	req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq;
+
+	if (p->config.tunnel_id && p->config.session_id) {
+		addattr32(&req.n, 128, L2TP_ATTR_CONN_ID, p->config.tunnel_id);
+		addattr32(&req.n, 128, L2TP_ATTR_SESSION_ID,
+			  p->config.session_id);
+	}
+
+	if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0)
+		return -2;
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&genl_rth, session_nlmsg, p) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	delete_json_obj();
+	fflush(stdout);
+
+	return 0;
+}
+
+static int tunnel_nlmsg(struct nlmsghdr *n, void *arg)
+{
+	int ret = get_response(n, arg);
+
+	if (ret == 0)
+		print_tunnel(arg);
+
+	return ret;
+}
+
+static int get_tunnel(struct l2tp_data *p)
+{
+	GENL_REQUEST(req, 1024, genl_family, 0, L2TP_GENL_VERSION,
+		     L2TP_CMD_TUNNEL_GET,
+		     NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST);
+
+	req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq;
+
+	if (p->config.tunnel_id)
+		addattr32(&req.n, 1024, L2TP_ATTR_CONN_ID, p->config.tunnel_id);
+
+	if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0)
+		return -2;
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&genl_rth, tunnel_nlmsg, p) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	delete_json_obj();
+	fflush(stdout);
+
+	return 0;
+}
+
+/*****************************************************************************
+ * Command parser
+ *****************************************************************************/
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ip l2tp add tunnel\n"
+		"          remote ADDR local ADDR\n"
+		"          tunnel_id ID peer_tunnel_id ID\n"
+		"          [ encap { ip | udp } ]\n"
+		"          [ udp_sport PORT ] [ udp_dport PORT ]\n"
+		"          [ udp_csum { on | off } ]\n"
+		"          [ udp6_csum_tx { on | off } ]\n"
+		"          [ udp6_csum_rx { on | off } ]\n"
+		"Usage: ip l2tp add session [ name NAME ]\n"
+		"          tunnel_id ID\n"
+		"          session_id ID peer_session_id ID\n"
+		"          [ cookie HEXSTR ] [ peer_cookie HEXSTR ]\n"
+		"          [ seq { none | send | recv | both } ]\n"
+		"          [ l2spec_type L2SPEC ]\n"
+		"       ip l2tp del tunnel tunnel_id ID\n"
+		"       ip l2tp del session tunnel_id ID session_id ID\n"
+		"       ip l2tp show tunnel [ tunnel_id ID ]\n"
+		"       ip l2tp show session [ tunnel_id ID ] [ session_id ID ]\n"
+		"\n"
+		"Where: NAME   := STRING\n"
+		"       ADDR   := { IP_ADDRESS | any }\n"
+		"       PORT   := { 0..65535 }\n"
+		"       ID     := { 1..4294967295 }\n"
+		"       HEXSTR := { 8 or 16 hex digits (4 / 8 bytes) }\n"
+		"       L2SPEC := { none | default }\n");
+
+	exit(-1);
+}
+
+static int parse_args(int argc, char **argv, int cmd, struct l2tp_parm *p)
+{
+	memset(p, 0, sizeof(*p));
+
+	if (argc == 0)
+		usage();
+
+	/* Defaults */
+	p->l2spec_type = L2TP_L2SPECTYPE_DEFAULT;
+	p->l2spec_len = 4;
+	p->udp6_csum_rx = 1;
+	p->udp6_csum_tx = 1;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "encap") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "ip") == 0) {
+				p->encap = L2TP_ENCAPTYPE_IP;
+			} else if (strcmp(*argv, "udp") == 0) {
+				p->encap = L2TP_ENCAPTYPE_UDP;
+			} else {
+				fprintf(stderr, "Unknown tunnel encapsulation \"%s\"\n", *argv);
+				exit(-1);
+			}
+		} else if (strcmp(*argv, "name") == 0) {
+			NEXT_ARG();
+			if (check_ifname(*argv))
+				invarg("\"name\" not a valid ifname", *argv);
+			p->ifname = *argv;
+		} else if (strcmp(*argv, "remote") == 0) {
+			NEXT_ARG();
+			if (get_addr(&p->peer_ip, *argv, AF_UNSPEC))
+				invarg("invalid remote address\n", *argv);
+		} else if (strcmp(*argv, "local") == 0) {
+			NEXT_ARG();
+			if (get_addr(&p->local_ip, *argv, AF_UNSPEC))
+				invarg("invalid local address\n", *argv);
+		} else if ((strcmp(*argv, "tunnel_id") == 0) ||
+			   (strcmp(*argv, "tid") == 0)) {
+			__u32 uval;
+
+			NEXT_ARG();
+			if (get_u32(&uval, *argv, 0))
+				invarg("invalid ID\n", *argv);
+			p->tunnel_id = uval;
+		} else if ((strcmp(*argv, "peer_tunnel_id") == 0) ||
+			   (strcmp(*argv, "ptid") == 0)) {
+			__u32 uval;
+
+			NEXT_ARG();
+			if (get_u32(&uval, *argv, 0))
+				invarg("invalid ID\n", *argv);
+			p->peer_tunnel_id = uval;
+		} else if ((strcmp(*argv, "session_id") == 0) ||
+			   (strcmp(*argv, "sid") == 0)) {
+			__u32 uval;
+
+			NEXT_ARG();
+			if (get_u32(&uval, *argv, 0))
+				invarg("invalid ID\n", *argv);
+			p->session_id = uval;
+		} else if ((strcmp(*argv, "peer_session_id") == 0) ||
+			   (strcmp(*argv, "psid") == 0)) {
+			__u32 uval;
+
+			NEXT_ARG();
+			if (get_u32(&uval, *argv, 0))
+				invarg("invalid ID\n", *argv);
+			p->peer_session_id = uval;
+		} else if (strcmp(*argv, "udp_sport") == 0) {
+			__u16 uval;
+
+			NEXT_ARG();
+			if (get_u16(&uval, *argv, 0))
+				invarg("invalid port\n", *argv);
+			p->local_udp_port = uval;
+		} else if (strcmp(*argv, "udp_dport") == 0) {
+			__u16 uval;
+
+			NEXT_ARG();
+			if (get_u16(&uval, *argv, 0))
+				invarg("invalid port\n", *argv);
+			p->peer_udp_port = uval;
+		} else if (strcmp(*argv, "udp_csum") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "on") == 0)
+				p->udp_csum = 1;
+			else if (strcmp(*argv, "off") == 0)
+				p->udp_csum = 0;
+			else
+				invarg("invalid option for udp_csum\n", *argv);
+		} else if (strcmp(*argv, "udp6_csum_rx") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "on") == 0)
+				p->udp6_csum_rx = 1;
+			else if (strcmp(*argv, "off") == 0)
+				p->udp6_csum_rx = 0;
+			else
+				invarg("invalid option for udp6_csum_rx\n"
+						, *argv);
+		} else if (strcmp(*argv, "udp6_csum_tx") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "on") == 0)
+				p->udp6_csum_tx = 1;
+			else if (strcmp(*argv, "off") == 0)
+				p->udp6_csum_tx = 0;
+			else
+				invarg("invalid option for udp6_csum_tx\n"
+						, *argv);
+		} else if (strcmp(*argv, "offset") == 0) {
+			fprintf(stderr, "Ignoring option \"offset\"\n");
+			NEXT_ARG();
+		} else if (strcmp(*argv, "peer_offset") == 0) {
+			fprintf(stderr, "Ignoring option \"peer_offset\"\n");
+			NEXT_ARG();
+		} else if (strcmp(*argv, "cookie") == 0) {
+			int slen;
+
+			NEXT_ARG();
+			slen = strlen(*argv);
+			if ((slen != 8) && (slen != 16))
+				invarg("cookie must be either 8 or 16 hex digits\n", *argv);
+
+			p->cookie_len = slen / 2;
+			if (hex2mem(*argv, p->cookie, p->cookie_len) < 0)
+				invarg("cookie must be a hex string\n", *argv);
+		} else if (strcmp(*argv, "peer_cookie") == 0) {
+			int slen;
+
+			NEXT_ARG();
+			slen = strlen(*argv);
+			if ((slen != 8) && (slen != 16))
+				invarg("cookie must be either 8 or 16 hex digits\n", *argv);
+
+			p->peer_cookie_len = slen / 2;
+			if (hex2mem(*argv, p->peer_cookie, p->peer_cookie_len) < 0)
+				invarg("cookie must be a hex string\n", *argv);
+		} else if (strcmp(*argv, "l2spec_type") == 0) {
+			NEXT_ARG();
+			if (strcasecmp(*argv, "default") == 0) {
+				p->l2spec_type = L2TP_L2SPECTYPE_DEFAULT;
+				p->l2spec_len = 4;
+			} else if (strcasecmp(*argv, "none") == 0) {
+				p->l2spec_type = L2TP_L2SPECTYPE_NONE;
+				p->l2spec_len = 0;
+			} else {
+				fprintf(stderr,
+					"Unknown layer2specific header type \"%s\"\n",
+					*argv);
+				exit(-1);
+			}
+		} else if (strcmp(*argv, "seq") == 0) {
+			NEXT_ARG();
+			if (strcasecmp(*argv, "both") == 0) {
+				p->recv_seq = 1;
+				p->send_seq = 1;
+			} else if (strcasecmp(*argv, "recv") == 0) {
+				p->recv_seq = 1;
+			} else if (strcasecmp(*argv, "send") == 0) {
+				p->send_seq = 1;
+			} else if (strcasecmp(*argv, "none") == 0) {
+				p->recv_seq = 0;
+				p->send_seq = 0;
+			} else {
+				fprintf(stderr,
+					"Unknown seq value \"%s\"\n", *argv);
+				exit(-1);
+			}
+		} else if (strcmp(*argv, "tunnel") == 0) {
+			p->tunnel = 1;
+		} else if (strcmp(*argv, "session") == 0) {
+			p->session = 1;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			fprintf(stderr, "Unknown command: %s\n", *argv);
+			usage();
+		}
+
+		argc--; argv++;
+	}
+
+	return 0;
+}
+
+
+static int do_add(int argc, char **argv)
+{
+	struct l2tp_parm p;
+	int ret = 0;
+
+	if (parse_args(argc, argv, L2TP_ADD, &p) < 0)
+		return -1;
+
+	if (!p.tunnel && !p.session)
+		missarg("tunnel or session");
+
+	if (p.tunnel_id == 0)
+		missarg("tunnel_id");
+
+	/* session_id and peer_session_id must be provided for sessions */
+	if ((p.session) && (p.peer_session_id == 0))
+		missarg("peer_session_id");
+	if ((p.session) && (p.session_id == 0))
+		missarg("session_id");
+
+	/* peer_tunnel_id is needed for tunnels */
+	if ((p.tunnel) && (p.peer_tunnel_id == 0))
+		missarg("peer_tunnel_id");
+
+	if (p.tunnel) {
+		if (p.local_ip.family == AF_UNSPEC)
+			missarg("local");
+
+		if (p.peer_ip.family == AF_UNSPEC)
+			missarg("remote");
+
+		if (p.encap == L2TP_ENCAPTYPE_UDP) {
+			if (p.local_udp_port == 0)
+				missarg("udp_sport");
+			if (p.peer_udp_port == 0)
+				missarg("udp_dport");
+		}
+
+		ret = create_tunnel(&p);
+	}
+
+	if (p.session) {
+		/* Only ethernet pseudowires supported */
+		p.pw_type = L2TP_PWTYPE_ETH;
+
+		ret = create_session(&p);
+	}
+
+	return ret;
+}
+
+static int do_del(int argc, char **argv)
+{
+	struct l2tp_parm p;
+
+	if (parse_args(argc, argv, L2TP_DEL, &p) < 0)
+		return -1;
+
+	if (!p.tunnel && !p.session)
+		missarg("tunnel or session");
+
+	if ((p.tunnel) && (p.tunnel_id == 0))
+		missarg("tunnel_id");
+	if ((p.session) && (p.session_id == 0))
+		missarg("session_id");
+
+	if (p.session_id)
+		return delete_session(&p);
+	else
+		return delete_tunnel(&p);
+
+	return -1;
+}
+
+static int do_show(int argc, char **argv)
+{
+	struct l2tp_data data;
+	struct l2tp_parm *p = &data.config;
+
+	if (parse_args(argc, argv, L2TP_GET, p) < 0)
+		return -1;
+
+	if (!p->tunnel && !p->session)
+		missarg("tunnel or session");
+
+	if (p->session)
+		get_session(&data);
+	else
+		get_tunnel(&data);
+
+	return 0;
+}
+
+int do_ipl2tp(int argc, char **argv)
+{
+	if (argc < 1 || !matches(*argv, "help"))
+		usage();
+
+	if (genl_init_handle(&genl_rth, L2TP_GENL_NAME, &genl_family))
+		exit(1);
+
+	if (matches(*argv, "add") == 0)
+		return do_add(argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return do_del(argc-1, argv+1);
+	if (matches(*argv, "show") == 0 ||
+	    matches(*argv, "lst") == 0 ||
+	    matches(*argv, "list") == 0)
+		return do_show(argc-1, argv+1);
+
+	fprintf(stderr,
+		"Command \"%s\" is unknown, try \"ip l2tp help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/iplink.c b/ip/iplink.c
new file mode 100644
index 0000000..212a088
--- /dev/null
+++ b/ip/iplink.c
@@ -0,0 +1,1685 @@
+/*
+ * iplink.c		"ip link".
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <linux/if.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#include <linux/sockios.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <stdbool.h>
+#include <linux/mpls.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "namespace.h"
+
+#define IPLINK_IOCTL_COMPAT	1
+#ifndef LIBDIR
+#define LIBDIR "/usr/lib"
+#endif
+
+#ifndef GSO_MAX_SIZE
+#define GSO_MAX_SIZE		65536
+#endif
+#ifndef GSO_MAX_SEGS
+#define GSO_MAX_SEGS		65535
+#endif
+
+
+static void usage(void) __attribute__((noreturn));
+static int iplink_have_newlink(void);
+
+void iplink_usage(void)
+{
+	if (iplink_have_newlink()) {
+		fprintf(stderr,
+			"Usage: ip link add [link DEV] [ name ] NAME\n"
+			"		    [ txqueuelen PACKETS ]\n"
+			"		    [ address LLADDR ]\n"
+			"		    [ broadcast LLADDR ]\n"
+			"		    [ mtu MTU ] [index IDX ]\n"
+			"		    [ numtxqueues QUEUE_COUNT ]\n"
+			"		    [ numrxqueues QUEUE_COUNT ]\n"
+			"		    type TYPE [ ARGS ]\n"
+			"\n"
+			"	ip link delete { DEVICE | dev DEVICE | group DEVGROUP } type TYPE [ ARGS ]\n"
+			"\n"
+			"	ip link set { DEVICE | dev DEVICE | group DEVGROUP }\n"
+			"			[ { up | down } ]\n"
+			"			[ type TYPE ARGS ]\n");
+	} else
+		fprintf(stderr,
+			"Usage: ip link set DEVICE [ { up | down } ]\n");
+
+	fprintf(stderr,
+		"		[ arp { on | off } ]\n"
+		"		[ dynamic { on | off } ]\n"
+		"		[ multicast { on | off } ]\n"
+		"		[ allmulticast { on | off } ]\n"
+		"		[ promisc { on | off } ]\n"
+		"		[ trailers { on | off } ]\n"
+		"		[ carrier { on | off } ]\n"
+		"		[ txqueuelen PACKETS ]\n"
+		"		[ name NEWNAME ]\n"
+		"		[ address LLADDR ]\n"
+		"		[ broadcast LLADDR ]\n"
+		"		[ mtu MTU ]\n"
+		"		[ netns { PID | NAME } ]\n"
+		"		[ link-netns NAME | link-netnsid ID ]\n"
+		"			[ alias NAME ]\n"
+		"			[ vf NUM [ mac LLADDR ]\n"
+		"				 [ vlan VLANID [ qos VLAN-QOS ] [ proto VLAN-PROTO ] ]\n"
+		"				 [ rate TXRATE ]\n"
+		"				 [ max_tx_rate TXRATE ]\n"
+		"				 [ min_tx_rate TXRATE ]\n"
+		"				 [ spoofchk { on | off} ]\n"
+		"				 [ query_rss { on | off} ]\n"
+		"				 [ state { auto | enable | disable} ] ]\n"
+		"				 [ trust { on | off} ] ]\n"
+		"				 [ node_guid { eui64 } ]\n"
+		"				 [ port_guid { eui64 } ]\n"
+		"			[ { xdp | xdpgeneric | xdpdrv | xdpoffload } { off |\n"
+		"				  object FILE [ section NAME ] [ verbose ] |\n"
+		"				  pinned FILE } ]\n"
+		"			[ master DEVICE ][ vrf NAME ]\n"
+		"			[ nomaster ]\n"
+		"			[ addrgenmode { eui64 | none | stable_secret | random } ]\n"
+		"			[ protodown { on | off } ]\n"
+		"			[ gso_max_size BYTES ] | [ gso_max_segs PACKETS ]\n"
+		"\n"
+		"	ip link show [ DEVICE | group GROUP ] [up] [master DEV] [vrf NAME] [type TYPE]\n"
+		"\n"
+		"	ip link xstats type TYPE [ ARGS ]\n"
+		"\n"
+		"	ip link afstats [ dev DEVICE ]\n");
+
+	if (iplink_have_newlink()) {
+		fprintf(stderr,
+			"\n"
+			"	ip link help [ TYPE ]\n"
+			"\n"
+			"TYPE := { vlan | veth | vcan | vxcan | dummy | ifb | macvlan | macvtap |\n"
+			"	   bridge | bond | team | ipoib | ip6tnl | ipip | sit | vxlan |\n"
+			"	   gre | gretap | erspan | ip6gre | ip6gretap | ip6erspan |\n"
+			"	   vti | nlmon | team_slave | bond_slave | bridge_slave |\n"
+			"	   ipvlan | ipvtap | geneve | vrf | macsec | netdevsim | rmnet |\n"
+			"	   xfrm }\n");
+	}
+	exit(-1);
+}
+
+static void usage(void)
+{
+	iplink_usage();
+}
+
+static int on_off(const char *msg, const char *realval)
+{
+	fprintf(stderr,
+		"Error: argument of \"%s\" must be \"on\" or \"off\", not \"%s\"\n",
+		msg, realval);
+	return -1;
+}
+
+static void *BODY;		/* cached dlopen(NULL) handle */
+static struct link_util *linkutil_list;
+
+struct link_util *get_link_kind(const char *id)
+{
+	void *dlh;
+	char buf[256];
+	struct link_util *l;
+
+	for (l = linkutil_list; l; l = l->next)
+		if (strcmp(l->id, id) == 0)
+			return l;
+
+	snprintf(buf, sizeof(buf), LIBDIR "/ip/link_%s.so", id);
+	dlh = dlopen(buf, RTLD_LAZY);
+	if (dlh == NULL) {
+		/* look in current binary, only open once */
+		dlh = BODY;
+		if (dlh == NULL) {
+			dlh = BODY = dlopen(NULL, RTLD_LAZY);
+			if (dlh == NULL)
+				return NULL;
+		}
+	}
+
+	snprintf(buf, sizeof(buf), "%s_link_util", id);
+	l = dlsym(dlh, buf);
+	if (l == NULL)
+		return NULL;
+
+	l->next = linkutil_list;
+	linkutil_list = l;
+	return l;
+}
+
+static int get_link_mode(const char *mode)
+{
+	if (strcasecmp(mode, "default") == 0)
+		return IF_LINK_MODE_DEFAULT;
+	if (strcasecmp(mode, "dormant") == 0)
+		return IF_LINK_MODE_DORMANT;
+	return -1;
+}
+
+static int get_addr_gen_mode(const char *mode)
+{
+	if (strcasecmp(mode, "eui64") == 0)
+		return IN6_ADDR_GEN_MODE_EUI64;
+	if (strcasecmp(mode, "none") == 0)
+		return IN6_ADDR_GEN_MODE_NONE;
+	if (strcasecmp(mode, "stable_secret") == 0)
+		return IN6_ADDR_GEN_MODE_STABLE_PRIVACY;
+	if (strcasecmp(mode, "random") == 0)
+		return IN6_ADDR_GEN_MODE_RANDOM;
+	return -1;
+}
+
+#if IPLINK_IOCTL_COMPAT
+static int have_rtnl_newlink = -1;
+
+static int accept_msg(struct rtnl_ctrl_data *ctrl,
+		      struct nlmsghdr *n, void *arg)
+{
+	struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(n);
+
+	if (n->nlmsg_type == NLMSG_ERROR &&
+	    (err->error == -EOPNOTSUPP || err->error == -EINVAL))
+		have_rtnl_newlink = 0;
+	else
+		have_rtnl_newlink = 1;
+	return -1;
+}
+
+static int iplink_have_newlink(void)
+{
+	struct {
+		struct nlmsghdr		n;
+		struct ifinfomsg	i;
+		char			buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+		.n.nlmsg_type = RTM_NEWLINK,
+		.i.ifi_family = AF_UNSPEC,
+	};
+
+	if (have_rtnl_newlink < 0) {
+		if (rtnl_send(&rth, &req.n, req.n.nlmsg_len) < 0) {
+			perror("request send failed");
+			exit(1);
+		}
+		rtnl_listen(&rth, accept_msg, NULL);
+	}
+	return have_rtnl_newlink;
+}
+#else /* IPLINK_IOCTL_COMPAT */
+static int iplink_have_newlink(void)
+{
+	return 1;
+}
+#endif /* ! IPLINK_IOCTL_COMPAT */
+
+static int nl_get_ll_addr_len(const char *ifname)
+{
+	int len;
+	int dev_index = ll_name_to_index(ifname);
+	struct iplink_req req = {
+		.n = {
+			.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+			.nlmsg_type = RTM_GETLINK,
+			.nlmsg_flags = NLM_F_REQUEST
+		},
+		.i = {
+			.ifi_family = preferred_family,
+			.ifi_index = dev_index,
+		}
+	};
+	struct nlmsghdr *answer;
+	struct rtattr *tb[IFLA_MAX+1];
+
+	if (dev_index == 0)
+		return -1;
+
+	if (rtnl_talk(&rth, &req.n, &answer) < 0)
+		return -1;
+
+	len = answer->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg));
+	if (len < 0) {
+		free(answer);
+		return -1;
+	}
+
+	parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)),
+			   len, NLA_F_NESTED);
+	if (!tb[IFLA_ADDRESS]) {
+		free(answer);
+		return -1;
+	}
+
+	len = RTA_PAYLOAD(tb[IFLA_ADDRESS]);
+	free(answer);
+	return len;
+}
+
+static void iplink_parse_vf_vlan_info(int vf, int *argcp, char ***argvp,
+				      struct ifla_vf_vlan_info *ivvip)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	unsigned int vci;
+
+	NEXT_ARG();
+	if (get_unsigned(&vci, *argv, 0) || vci > 4095)
+		invarg("Invalid \"vlan\" value\n", *argv);
+
+	ivvip->vlan = vci;
+	ivvip->vf = vf;
+	ivvip->qos = 0;
+	ivvip->vlan_proto = htons(ETH_P_8021Q);
+	if (NEXT_ARG_OK()) {
+		NEXT_ARG();
+		if (matches(*argv, "qos") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&ivvip->qos, *argv, 0))
+				invarg("Invalid \"qos\" value\n", *argv);
+		} else {
+			/* rewind arg */
+			PREV_ARG();
+		}
+	}
+	if (NEXT_ARG_OK()) {
+		NEXT_ARG();
+		if (matches(*argv, "proto") == 0) {
+			NEXT_ARG();
+			if (ll_proto_a2n(&ivvip->vlan_proto, *argv))
+				invarg("protocol is invalid\n", *argv);
+			if (ivvip->vlan_proto != htons(ETH_P_8021AD) &&
+			    ivvip->vlan_proto != htons(ETH_P_8021Q)) {
+				SPRINT_BUF(b1);
+				SPRINT_BUF(b2);
+				char msg[64 + sizeof(b1) + sizeof(b2)];
+
+				sprintf(msg,
+					"Invalid \"vlan protocol\" value - supported %s, %s\n",
+					ll_proto_n2a(htons(ETH_P_8021Q),
+					     b1, sizeof(b1)),
+					ll_proto_n2a(htons(ETH_P_8021AD),
+					     b2, sizeof(b2)));
+				invarg(msg, *argv);
+			}
+		} else {
+			/* rewind arg */
+			PREV_ARG();
+		}
+	}
+
+	*argcp = argc;
+	*argvp = argv;
+}
+
+static int iplink_parse_vf(int vf, int *argcp, char ***argvp,
+			   struct iplink_req *req, const char *dev)
+{
+	char new_rate_api = 0, count = 0, override_legacy_rate = 0;
+	struct ifla_vf_rate tivt;
+	int len, argc = *argcp;
+	char **argv = *argvp;
+	struct rtattr *vfinfo;
+
+	tivt.min_tx_rate = -1;
+	tivt.max_tx_rate = -1;
+
+	vfinfo = addattr_nest(&req->n, sizeof(*req), IFLA_VF_INFO);
+
+	while (NEXT_ARG_OK()) {
+		NEXT_ARG();
+		count++;
+		if (!matches(*argv, "max_tx_rate")) {
+			/* new API in use */
+			new_rate_api = 1;
+			/* override legacy rate */
+			override_legacy_rate = 1;
+		} else if (!matches(*argv, "min_tx_rate")) {
+			/* new API in use */
+			new_rate_api = 1;
+		}
+	}
+
+	while (count--) {
+		/* rewind arg */
+		PREV_ARG();
+	}
+
+	while (NEXT_ARG_OK()) {
+		NEXT_ARG();
+		if (matches(*argv, "mac") == 0) {
+			struct ifla_vf_mac ivm = { 0 };
+			int halen = nl_get_ll_addr_len(dev);
+
+			NEXT_ARG();
+			ivm.vf = vf;
+			len = ll_addr_a2n((char *)ivm.mac, 32, *argv);
+			if (len < 0)
+				return -1;
+			if (halen > 0 && len != halen) {
+				fprintf(stderr,
+					"Invalid address length %d - must be %d bytes\n",
+					len, halen);
+				return -1;
+			}
+			addattr_l(&req->n, sizeof(*req), IFLA_VF_MAC,
+				  &ivm, sizeof(ivm));
+		} else if (matches(*argv, "vlan") == 0) {
+			struct ifla_vf_vlan_info ivvi;
+
+			iplink_parse_vf_vlan_info(vf, &argc, &argv, &ivvi);
+			/* support the old interface in case of older kernel*/
+			if (ivvi.vlan_proto == htons(ETH_P_8021Q)) {
+				struct ifla_vf_vlan ivv;
+
+				ivv.vf = ivvi.vf;
+				ivv.vlan = ivvi.vlan;
+				ivv.qos = ivvi.qos;
+				addattr_l(&req->n, sizeof(*req),
+					  IFLA_VF_VLAN, &ivv, sizeof(ivv));
+			} else {
+				struct rtattr *vfvlanlist;
+
+				vfvlanlist = addattr_nest(&req->n, sizeof(*req),
+							  IFLA_VF_VLAN_LIST);
+				addattr_l(&req->n, sizeof(*req),
+					  IFLA_VF_VLAN_INFO, &ivvi,
+					  sizeof(ivvi));
+
+				while (NEXT_ARG_OK()) {
+					NEXT_ARG();
+					if (matches(*argv, "vlan") != 0) {
+						PREV_ARG();
+						break;
+					}
+					iplink_parse_vf_vlan_info(vf, &argc,
+								  &argv, &ivvi);
+					addattr_l(&req->n, sizeof(*req),
+						  IFLA_VF_VLAN_INFO, &ivvi,
+						  sizeof(ivvi));
+				}
+				addattr_nest_end(&req->n, vfvlanlist);
+			}
+		} else if (matches(*argv, "rate") == 0) {
+			struct ifla_vf_tx_rate ivt;
+
+			NEXT_ARG();
+			if (get_unsigned(&ivt.rate, *argv, 0))
+				invarg("Invalid \"rate\" value\n", *argv);
+
+			ivt.vf = vf;
+			if (!new_rate_api)
+				addattr_l(&req->n, sizeof(*req),
+					  IFLA_VF_TX_RATE, &ivt, sizeof(ivt));
+			else if (!override_legacy_rate)
+				tivt.max_tx_rate = ivt.rate;
+
+		} else if (matches(*argv, "max_tx_rate") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&tivt.max_tx_rate, *argv, 0))
+				invarg("Invalid \"max tx rate\" value\n",
+				       *argv);
+			tivt.vf = vf;
+
+		} else if (matches(*argv, "min_tx_rate") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&tivt.min_tx_rate, *argv, 0))
+				invarg("Invalid \"min tx rate\" value\n",
+				       *argv);
+			tivt.vf = vf;
+
+		} else if (matches(*argv, "spoofchk") == 0) {
+			struct ifla_vf_spoofchk ivs;
+
+			NEXT_ARG();
+			if (matches(*argv, "on") == 0)
+				ivs.setting = 1;
+			else if (matches(*argv, "off") == 0)
+				ivs.setting = 0;
+			else
+				return on_off("spoofchk", *argv);
+			ivs.vf = vf;
+			addattr_l(&req->n, sizeof(*req), IFLA_VF_SPOOFCHK,
+				  &ivs, sizeof(ivs));
+
+		} else if (matches(*argv, "query_rss") == 0) {
+			struct ifla_vf_rss_query_en ivs;
+
+			NEXT_ARG();
+			if (matches(*argv, "on") == 0)
+				ivs.setting = 1;
+			else if (matches(*argv, "off") == 0)
+				ivs.setting = 0;
+			else
+				return on_off("query_rss", *argv);
+			ivs.vf = vf;
+			addattr_l(&req->n, sizeof(*req), IFLA_VF_RSS_QUERY_EN,
+				  &ivs, sizeof(ivs));
+
+		} else if (matches(*argv, "trust") == 0) {
+			struct ifla_vf_trust ivt;
+
+			NEXT_ARG();
+			if (matches(*argv, "on") == 0)
+				ivt.setting = 1;
+			else if (matches(*argv, "off") == 0)
+				ivt.setting = 0;
+			else
+				invarg("Invalid \"trust\" value\n", *argv);
+			ivt.vf = vf;
+			addattr_l(&req->n, sizeof(*req), IFLA_VF_TRUST,
+				  &ivt, sizeof(ivt));
+
+		} else if (matches(*argv, "state") == 0) {
+			struct ifla_vf_link_state ivl;
+
+			NEXT_ARG();
+			if (matches(*argv, "auto") == 0)
+				ivl.link_state = IFLA_VF_LINK_STATE_AUTO;
+			else if (matches(*argv, "enable") == 0)
+				ivl.link_state = IFLA_VF_LINK_STATE_ENABLE;
+			else if (matches(*argv, "disable") == 0)
+				ivl.link_state = IFLA_VF_LINK_STATE_DISABLE;
+			else
+				invarg("Invalid \"state\" value\n", *argv);
+			ivl.vf = vf;
+			addattr_l(&req->n, sizeof(*req), IFLA_VF_LINK_STATE,
+				  &ivl, sizeof(ivl));
+		} else if (matches(*argv, "node_guid") == 0) {
+			struct ifla_vf_guid ivg;
+
+			NEXT_ARG();
+			ivg.vf = vf;
+			if (get_guid(&ivg.guid, *argv)) {
+				invarg("Invalid GUID format\n", *argv);
+				return -1;
+			}
+			addattr_l(&req->n, sizeof(*req), IFLA_VF_IB_NODE_GUID,
+				  &ivg, sizeof(ivg));
+		} else if (matches(*argv, "port_guid") == 0) {
+			struct ifla_vf_guid ivg;
+
+			NEXT_ARG();
+			ivg.vf = vf;
+			if (get_guid(&ivg.guid, *argv)) {
+				invarg("Invalid GUID format\n", *argv);
+				return -1;
+			}
+			addattr_l(&req->n, sizeof(*req), IFLA_VF_IB_PORT_GUID,
+				  &ivg, sizeof(ivg));
+		} else {
+			/* rewind arg */
+			PREV_ARG();
+			break;
+		}
+	}
+
+	if (new_rate_api) {
+		int tmin, tmax;
+
+		if (tivt.min_tx_rate == -1 || tivt.max_tx_rate == -1) {
+			ipaddr_get_vf_rate(tivt.vf, &tmin, &tmax, dev);
+			if (tivt.min_tx_rate == -1)
+				tivt.min_tx_rate = tmin;
+			if (tivt.max_tx_rate == -1)
+				tivt.max_tx_rate = tmax;
+		}
+
+		if (tivt.max_tx_rate && tivt.min_tx_rate > tivt.max_tx_rate) {
+			fprintf(stderr,
+				"Invalid min_tx_rate %d - must be <= max_tx_rate %d\n",
+				tivt.min_tx_rate, tivt.max_tx_rate);
+			return -1;
+		}
+
+		addattr_l(&req->n, sizeof(*req), IFLA_VF_RATE, &tivt,
+			  sizeof(tivt));
+	}
+
+	if (argc == *argcp)
+		incomplete_command();
+
+	addattr_nest_end(&req->n, vfinfo);
+
+	*argcp = argc;
+	*argvp = argv;
+	return 0;
+}
+
+int iplink_parse(int argc, char **argv, struct iplink_req *req, char **type)
+{
+	char *name = NULL;
+	char *dev = NULL;
+	char *link = NULL;
+	int ret, len;
+	char abuf[32];
+	int qlen = -1;
+	int mtu = -1;
+	int netns = -1;
+	int vf = -1;
+	int numtxqueues = -1;
+	int numrxqueues = -1;
+	int link_netnsid = -1;
+	int index = 0;
+	int group = -1;
+	int addr_len = 0;
+
+	ret = argc;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "up") == 0) {
+			req->i.ifi_change |= IFF_UP;
+			req->i.ifi_flags |= IFF_UP;
+		} else if (strcmp(*argv, "down") == 0) {
+			req->i.ifi_change |= IFF_UP;
+			req->i.ifi_flags &= ~IFF_UP;
+		} else if (strcmp(*argv, "name") == 0) {
+			NEXT_ARG();
+			if (name)
+				duparg("name", *argv);
+			if (check_ifname(*argv))
+				invarg("\"name\" not a valid ifname", *argv);
+			name = *argv;
+			if (!dev)
+				dev = name;
+		} else if (strcmp(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (index)
+				duparg("index", *argv);
+			index = atoi(*argv);
+			if (index <= 0)
+				invarg("Invalid \"index\" value", *argv);
+		} else if (matches(*argv, "link") == 0) {
+			NEXT_ARG();
+			link = *argv;
+		} else if (matches(*argv, "address") == 0) {
+			NEXT_ARG();
+			addr_len = ll_addr_a2n(abuf, sizeof(abuf), *argv);
+			if (addr_len < 0)
+				return -1;
+			addattr_l(&req->n, sizeof(*req),
+				  IFLA_ADDRESS, abuf, addr_len);
+		} else if (matches(*argv, "broadcast") == 0 ||
+			   strcmp(*argv, "brd") == 0) {
+			NEXT_ARG();
+			len = ll_addr_a2n(abuf, sizeof(abuf), *argv);
+			if (len < 0)
+				return -1;
+			addattr_l(&req->n, sizeof(*req),
+				  IFLA_BROADCAST, abuf, len);
+		} else if (matches(*argv, "txqueuelen") == 0 ||
+			   strcmp(*argv, "qlen") == 0 ||
+			   matches(*argv, "txqlen") == 0) {
+			NEXT_ARG();
+			if (qlen != -1)
+				duparg("txqueuelen", *argv);
+			if (get_integer(&qlen,  *argv, 0))
+				invarg("Invalid \"txqueuelen\" value\n", *argv);
+			addattr_l(&req->n, sizeof(*req),
+				  IFLA_TXQLEN, &qlen, 4);
+		} else if (strcmp(*argv, "mtu") == 0) {
+			NEXT_ARG();
+			if (mtu != -1)
+				duparg("mtu", *argv);
+			if (get_integer(&mtu, *argv, 0))
+				invarg("Invalid \"mtu\" value\n", *argv);
+			addattr_l(&req->n, sizeof(*req), IFLA_MTU, &mtu, 4);
+		} else if (strcmp(*argv, "xdpgeneric") == 0 ||
+			   strcmp(*argv, "xdpdrv") == 0 ||
+			   strcmp(*argv, "xdpoffload") == 0 ||
+			   strcmp(*argv, "xdp") == 0) {
+			bool generic = strcmp(*argv, "xdpgeneric") == 0;
+			bool drv = strcmp(*argv, "xdpdrv") == 0;
+			bool offload = strcmp(*argv, "xdpoffload") == 0;
+
+			NEXT_ARG();
+			if (xdp_parse(&argc, &argv, req, dev,
+				      generic, drv, offload))
+				exit(-1);
+
+			if (offload && name == dev)
+				dev = NULL;
+		} else if (strcmp(*argv, "netns") == 0) {
+			NEXT_ARG();
+			if (netns != -1)
+				duparg("netns", *argv);
+			netns = netns_get_fd(*argv);
+			if (netns >= 0)
+				addattr_l(&req->n, sizeof(*req), IFLA_NET_NS_FD,
+					  &netns, 4);
+			else if (get_integer(&netns, *argv, 0) == 0)
+				addattr_l(&req->n, sizeof(*req),
+					  IFLA_NET_NS_PID, &netns, 4);
+			else
+				invarg("Invalid \"netns\" value\n", *argv);
+		} else if (strcmp(*argv, "multicast") == 0) {
+			NEXT_ARG();
+			req->i.ifi_change |= IFF_MULTICAST;
+
+			if (strcmp(*argv, "on") == 0)
+				req->i.ifi_flags |= IFF_MULTICAST;
+			else if (strcmp(*argv, "off") == 0)
+				req->i.ifi_flags &= ~IFF_MULTICAST;
+			else
+				return on_off("multicast", *argv);
+		} else if (strcmp(*argv, "allmulticast") == 0) {
+			NEXT_ARG();
+			req->i.ifi_change |= IFF_ALLMULTI;
+
+			if (strcmp(*argv, "on") == 0)
+				req->i.ifi_flags |= IFF_ALLMULTI;
+			else if (strcmp(*argv, "off") == 0)
+				req->i.ifi_flags &= ~IFF_ALLMULTI;
+			else
+				return on_off("allmulticast", *argv);
+		} else if (strcmp(*argv, "promisc") == 0) {
+			NEXT_ARG();
+			req->i.ifi_change |= IFF_PROMISC;
+
+			if (strcmp(*argv, "on") == 0)
+				req->i.ifi_flags |= IFF_PROMISC;
+			else if (strcmp(*argv, "off") == 0)
+				req->i.ifi_flags &= ~IFF_PROMISC;
+			else
+				return on_off("promisc", *argv);
+		} else if (strcmp(*argv, "trailers") == 0) {
+			NEXT_ARG();
+			req->i.ifi_change |= IFF_NOTRAILERS;
+
+			if (strcmp(*argv, "off") == 0)
+				req->i.ifi_flags |= IFF_NOTRAILERS;
+			else if (strcmp(*argv, "on") == 0)
+				req->i.ifi_flags &= ~IFF_NOTRAILERS;
+			else
+				return on_off("trailers", *argv);
+		} else if (strcmp(*argv, "arp") == 0) {
+			NEXT_ARG();
+			req->i.ifi_change |= IFF_NOARP;
+
+			if (strcmp(*argv, "on") == 0)
+				req->i.ifi_flags &= ~IFF_NOARP;
+			else if (strcmp(*argv, "off") == 0)
+				req->i.ifi_flags |= IFF_NOARP;
+			else
+				return on_off("arp", *argv);
+		} else if (strcmp(*argv, "carrier") == 0) {
+			int carrier;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "on") == 0)
+				carrier = 1;
+			else if (strcmp(*argv, "off") == 0)
+				carrier = 0;
+			else
+				return on_off("carrier", *argv);
+
+			addattr8(&req->n, sizeof(*req), IFLA_CARRIER, carrier);
+		} else if (strcmp(*argv, "vf") == 0) {
+			struct rtattr *vflist;
+
+			NEXT_ARG();
+			if (get_integer(&vf,  *argv, 0))
+				invarg("Invalid \"vf\" value\n", *argv);
+
+			vflist = addattr_nest(&req->n, sizeof(*req),
+					      IFLA_VFINFO_LIST);
+			if (!dev)
+				missarg("dev");
+
+			len = iplink_parse_vf(vf, &argc, &argv, req, dev);
+			if (len < 0)
+				return -1;
+			addattr_nest_end(&req->n, vflist);
+
+			if (name == dev)
+				dev = NULL;
+		} else if (matches(*argv, "master") == 0) {
+			int ifindex;
+
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Device does not exist\n", *argv);
+			addattr_l(&req->n, sizeof(*req), IFLA_MASTER,
+				  &ifindex, 4);
+		} else if (strcmp(*argv, "vrf") == 0) {
+			int ifindex;
+
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Not a valid VRF name\n", *argv);
+			if (!name_is_vrf(*argv))
+				invarg("Not a valid VRF name\n", *argv);
+			addattr_l(&req->n, sizeof(*req), IFLA_MASTER,
+				  &ifindex, sizeof(ifindex));
+		} else if (matches(*argv, "nomaster") == 0) {
+			int ifindex = 0;
+
+			addattr_l(&req->n, sizeof(*req), IFLA_MASTER,
+				  &ifindex, 4);
+		} else if (matches(*argv, "dynamic") == 0) {
+			NEXT_ARG();
+			req->i.ifi_change |= IFF_DYNAMIC;
+
+			if (strcmp(*argv, "on") == 0)
+				req->i.ifi_flags |= IFF_DYNAMIC;
+			else if (strcmp(*argv, "off") == 0)
+				req->i.ifi_flags &= ~IFF_DYNAMIC;
+			else
+				return on_off("dynamic", *argv);
+		} else if (matches(*argv, "type") == 0) {
+			NEXT_ARG();
+			*type = *argv;
+			argc--; argv++;
+			break;
+		} else if (matches(*argv, "alias") == 0) {
+			NEXT_ARG();
+			len = strlen(*argv);
+			if (len >= IFALIASZ)
+				invarg("alias too long\n", *argv);
+			addattr_l(&req->n, sizeof(*req), IFLA_IFALIAS,
+				  *argv, len);
+		} else if (strcmp(*argv, "group") == 0) {
+			NEXT_ARG();
+			if (group != -1)
+				duparg("group", *argv);
+			if (rtnl_group_a2n(&group, *argv))
+				invarg("Invalid \"group\" value\n", *argv);
+			addattr32(&req->n, sizeof(*req), IFLA_GROUP, group);
+		} else if (strcmp(*argv, "mode") == 0) {
+			int mode;
+
+			NEXT_ARG();
+			mode = get_link_mode(*argv);
+			if (mode < 0)
+				invarg("Invalid link mode\n", *argv);
+			addattr8(&req->n, sizeof(*req), IFLA_LINKMODE, mode);
+		} else if (strcmp(*argv, "state") == 0) {
+			int state;
+
+			NEXT_ARG();
+			state = get_operstate(*argv);
+			if (state < 0)
+				invarg("Invalid operstate\n", *argv);
+
+			addattr8(&req->n, sizeof(*req), IFLA_OPERSTATE, state);
+		} else if (matches(*argv, "numtxqueues") == 0) {
+			NEXT_ARG();
+			if (numtxqueues != -1)
+				duparg("numtxqueues", *argv);
+			if (get_integer(&numtxqueues, *argv, 0))
+				invarg("Invalid \"numtxqueues\" value\n",
+				       *argv);
+			addattr_l(&req->n, sizeof(*req), IFLA_NUM_TX_QUEUES,
+				  &numtxqueues, 4);
+		} else if (matches(*argv, "numrxqueues") == 0) {
+			NEXT_ARG();
+			if (numrxqueues != -1)
+				duparg("numrxqueues", *argv);
+			if (get_integer(&numrxqueues, *argv, 0))
+				invarg("Invalid \"numrxqueues\" value\n",
+				       *argv);
+			addattr_l(&req->n, sizeof(*req), IFLA_NUM_RX_QUEUES,
+				  &numrxqueues, 4);
+		} else if (matches(*argv, "addrgenmode") == 0) {
+			struct rtattr *afs, *afs6;
+			int mode;
+
+			NEXT_ARG();
+			mode = get_addr_gen_mode(*argv);
+			if (mode < 0)
+				invarg("Invalid address generation mode\n",
+				       *argv);
+			afs = addattr_nest(&req->n, sizeof(*req), IFLA_AF_SPEC);
+			afs6 = addattr_nest(&req->n, sizeof(*req), AF_INET6);
+			addattr8(&req->n, sizeof(*req),
+				 IFLA_INET6_ADDR_GEN_MODE, mode);
+			addattr_nest_end(&req->n, afs6);
+			addattr_nest_end(&req->n, afs);
+		} else if (matches(*argv, "link-netns") == 0) {
+			NEXT_ARG();
+			if (link_netnsid != -1)
+				duparg("link-netns/link-netnsid", *argv);
+			link_netnsid = get_netnsid_from_name(*argv);
+			/* No nsid? Try to assign one. */
+			if (link_netnsid < 0)
+				set_netnsid_from_name(*argv, -1);
+			link_netnsid = get_netnsid_from_name(*argv);
+			if (link_netnsid < 0)
+				invarg("Invalid \"link-netns\" value\n",
+				       *argv);
+			addattr32(&req->n, sizeof(*req), IFLA_LINK_NETNSID,
+				  link_netnsid);
+		} else if (matches(*argv, "link-netnsid") == 0) {
+			NEXT_ARG();
+			if (link_netnsid != -1)
+				duparg("link-netns/link-netnsid", *argv);
+			if (get_integer(&link_netnsid, *argv, 0))
+				invarg("Invalid \"link-netnsid\" value\n",
+				       *argv);
+			addattr32(&req->n, sizeof(*req), IFLA_LINK_NETNSID,
+				  link_netnsid);
+		} else if (strcmp(*argv, "protodown") == 0) {
+			unsigned int proto_down;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "on") == 0)
+				proto_down = 1;
+			else if (strcmp(*argv, "off") == 0)
+				proto_down = 0;
+			else
+				return on_off("protodown", *argv);
+			addattr8(&req->n, sizeof(*req), IFLA_PROTO_DOWN,
+				 proto_down);
+		} else if (strcmp(*argv, "gso_max_size") == 0) {
+			unsigned int max_size;
+
+			NEXT_ARG();
+			if (get_unsigned(&max_size, *argv, 0) ||
+			    max_size > GSO_MAX_SIZE)
+				invarg("Invalid \"gso_max_size\" value\n",
+				       *argv);
+			addattr32(&req->n, sizeof(*req),
+				  IFLA_GSO_MAX_SIZE, max_size);
+		} else if (strcmp(*argv, "gso_max_segs") == 0) {
+			unsigned int max_segs;
+
+			NEXT_ARG();
+			if (get_unsigned(&max_segs, *argv, 0) ||
+			    max_segs > GSO_MAX_SEGS)
+				invarg("Invalid \"gso_max_segs\" value\n",
+				       *argv);
+			addattr32(&req->n, sizeof(*req),
+				  IFLA_GSO_MAX_SEGS, max_segs);
+		} else {
+			if (matches(*argv, "help") == 0)
+				usage();
+
+			if (strcmp(*argv, "dev") == 0)
+				NEXT_ARG();
+			if (dev != name)
+				duparg2("dev", *argv);
+			if (check_ifname(*argv))
+				invarg("\"dev\" not a valid ifname", *argv);
+			dev = *argv;
+		}
+		argc--; argv++;
+	}
+
+	ret -= argc;
+
+	/* Allow "ip link add dev" and "ip link add name" */
+	if (!name)
+		name = dev;
+	else if (!dev)
+		dev = name;
+	else if (!strcmp(name, dev))
+		name = dev;
+
+	if (dev && addr_len &&
+	    !(req->n.nlmsg_flags & NLM_F_CREATE)) {
+		int halen = nl_get_ll_addr_len(dev);
+
+		if (halen >= 0 && halen != addr_len) {
+			fprintf(stderr,
+				"Invalid address length %d - must be %d bytes\n",
+				addr_len, halen);
+			return -1;
+		}
+	}
+
+	if (!(req->n.nlmsg_flags & NLM_F_CREATE) && index) {
+		fprintf(stderr,
+			"index can be used only when creating devices.\n");
+		exit(-1);
+	}
+
+	if (group != -1) {
+		if (!dev) {
+			if (argc) {
+				fprintf(stderr,
+					"Garbage instead of arguments \"%s ...\". Try \"ip link help\".\n",
+					*argv);
+				exit(-1);
+			}
+			if (req->n.nlmsg_flags & NLM_F_CREATE) {
+				fprintf(stderr,
+					"group cannot be used when creating devices.\n");
+				exit(-1);
+			}
+
+			*type = NULL;
+			return ret;
+		}
+	}
+
+	if (!(req->n.nlmsg_flags & NLM_F_CREATE)) {
+		if (!dev) {
+			fprintf(stderr,
+				"Not enough information: \"dev\" argument is required.\n");
+			exit(-1);
+		}
+
+		req->i.ifi_index = ll_name_to_index(dev);
+		if (!req->i.ifi_index)
+			return nodev(dev);
+
+		/* Not renaming to the same name */
+		if (name == dev)
+			name = NULL;
+	} else {
+		if (name != dev) {
+			fprintf(stderr,
+				"both \"name\" and \"dev\" cannot be used when creating devices.\n");
+			exit(-1);
+		}
+
+		if (link) {
+			int ifindex;
+
+			ifindex = ll_name_to_index(link);
+			if (!ifindex)
+				return nodev(link);
+			addattr32(&req->n, sizeof(*req), IFLA_LINK, ifindex);
+		}
+
+		req->i.ifi_index = index;
+	}
+
+	if (name) {
+		addattr_l(&req->n, sizeof(*req),
+			  IFLA_IFNAME, name, strlen(name) + 1);
+	}
+
+	return ret;
+}
+
+static int iplink_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+	char *type = NULL;
+	struct iplink_req req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST | flags,
+		.n.nlmsg_type = cmd,
+		.i.ifi_family = preferred_family,
+	};
+	int ret;
+
+	ret = iplink_parse(argc, argv, &req, &type);
+	if (ret < 0)
+		return ret;
+
+	if (type) {
+		struct link_util *lu;
+		struct rtattr *linkinfo;
+		char *ulinep = strchr(type, '_');
+		int iflatype;
+
+		linkinfo = addattr_nest(&req.n, sizeof(req), IFLA_LINKINFO);
+		addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, type,
+			 strlen(type));
+
+		lu = get_link_kind(type);
+		if (ulinep && !strcmp(ulinep, "_slave"))
+			iflatype = IFLA_INFO_SLAVE_DATA;
+		else
+			iflatype = IFLA_INFO_DATA;
+
+		argc -= ret;
+		argv += ret;
+
+		if (lu && argc) {
+			struct rtattr *data;
+
+			data = addattr_nest(&req.n, sizeof(req), iflatype);
+
+			if (lu->parse_opt &&
+			    lu->parse_opt(lu, argc, argv, &req.n))
+				return -1;
+
+			addattr_nest_end(&req.n, data);
+		} else if (argc) {
+			if (matches(*argv, "help") == 0)
+				usage();
+			fprintf(stderr,
+				"Garbage instead of arguments \"%s ...\". Try \"ip link help\".\n",
+				*argv);
+			return -1;
+		}
+		addattr_nest_end(&req.n, linkinfo);
+	} else if (flags & NLM_F_CREATE) {
+		fprintf(stderr,
+			"Not enough information: \"type\" argument is required\n");
+		return -1;
+	}
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -2;
+
+	/* remove device from cache; next use can refresh with new data */
+	ll_drop_by_index(req.i.ifi_index);
+
+	return 0;
+}
+
+int iplink_get(char *name, __u32 filt_mask)
+{
+	struct iplink_req req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETLINK,
+		.i.ifi_family = preferred_family,
+	};
+	struct nlmsghdr *answer;
+
+	if (name) {
+		addattr_l(&req.n, sizeof(req),
+			  IFLA_IFNAME, name, strlen(name) + 1);
+	}
+	addattr32(&req.n, sizeof(req), IFLA_EXT_MASK, filt_mask);
+
+	if (rtnl_talk(&rth, &req.n, &answer) < 0)
+		return -2;
+
+	open_json_object(NULL);
+	print_linkinfo(answer, stdout);
+	close_json_object();
+
+	free(answer);
+	return 0;
+}
+
+#if IPLINK_IOCTL_COMPAT
+static int get_ctl_fd(void)
+{
+	int s_errno;
+	int fd;
+
+	fd = socket(PF_INET, SOCK_DGRAM, 0);
+	if (fd >= 0)
+		return fd;
+	s_errno = errno;
+	fd = socket(PF_PACKET, SOCK_DGRAM, 0);
+	if (fd >= 0)
+		return fd;
+	fd = socket(PF_INET6, SOCK_DGRAM, 0);
+	if (fd >= 0)
+		return fd;
+	errno = s_errno;
+	perror("Cannot create control socket");
+	return -1;
+}
+
+static int do_chflags(const char *dev, __u32 flags, __u32 mask)
+{
+	struct ifreq ifr;
+	int fd;
+	int err;
+
+	strlcpy(ifr.ifr_name, dev, IFNAMSIZ);
+	fd = get_ctl_fd();
+	if (fd < 0)
+		return -1;
+	err = ioctl(fd, SIOCGIFFLAGS, &ifr);
+	if (err) {
+		perror("SIOCGIFFLAGS");
+		close(fd);
+		return -1;
+	}
+	if ((ifr.ifr_flags^flags)&mask) {
+		ifr.ifr_flags &= ~mask;
+		ifr.ifr_flags |= mask&flags;
+		err = ioctl(fd, SIOCSIFFLAGS, &ifr);
+		if (err)
+			perror("SIOCSIFFLAGS");
+	}
+	close(fd);
+	return err;
+}
+
+static int do_changename(const char *dev, const char *newdev)
+{
+	struct ifreq ifr;
+	int fd;
+	int err;
+
+	strlcpy(ifr.ifr_name, dev, IFNAMSIZ);
+	strlcpy(ifr.ifr_newname, newdev, IFNAMSIZ);
+	fd = get_ctl_fd();
+	if (fd < 0)
+		return -1;
+	err = ioctl(fd, SIOCSIFNAME, &ifr);
+	if (err) {
+		perror("SIOCSIFNAME");
+		close(fd);
+		return -1;
+	}
+	close(fd);
+	return err;
+}
+
+static int set_qlen(const char *dev, int qlen)
+{
+	struct ifreq ifr = { .ifr_qlen = qlen };
+	int s;
+
+	s = get_ctl_fd();
+	if (s < 0)
+		return -1;
+
+	strlcpy(ifr.ifr_name, dev, IFNAMSIZ);
+	if (ioctl(s, SIOCSIFTXQLEN, &ifr) < 0) {
+		perror("SIOCSIFXQLEN");
+		close(s);
+		return -1;
+	}
+	close(s);
+
+	return 0;
+}
+
+static int set_mtu(const char *dev, int mtu)
+{
+	struct ifreq ifr = { .ifr_mtu = mtu };
+	int s;
+
+	s = get_ctl_fd();
+	if (s < 0)
+		return -1;
+
+	strlcpy(ifr.ifr_name, dev, IFNAMSIZ);
+	if (ioctl(s, SIOCSIFMTU, &ifr) < 0) {
+		perror("SIOCSIFMTU");
+		close(s);
+		return -1;
+	}
+	close(s);
+
+	return 0;
+}
+
+static int get_address(const char *dev, int *htype)
+{
+	struct ifreq ifr = {};
+	struct sockaddr_ll me = {
+		.sll_family = AF_PACKET,
+		.sll_protocol = htons(ETH_P_LOOP),
+	};
+	socklen_t alen;
+	int s;
+
+	s = socket(PF_PACKET, SOCK_DGRAM, 0);
+	if (s < 0) {
+		perror("socket(PF_PACKET)");
+		return -1;
+	}
+
+	strlcpy(ifr.ifr_name, dev, IFNAMSIZ);
+	if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
+		perror("SIOCGIFINDEX");
+		close(s);
+		return -1;
+	}
+
+	me.sll_ifindex = ifr.ifr_ifindex;
+	if (bind(s, (struct sockaddr *)&me, sizeof(me)) == -1) {
+		perror("bind");
+		close(s);
+		return -1;
+	}
+
+	alen = sizeof(me);
+	if (getsockname(s, (struct sockaddr *)&me, &alen) == -1) {
+		perror("getsockname");
+		close(s);
+		return -1;
+	}
+	close(s);
+	*htype = me.sll_hatype;
+	return me.sll_halen;
+}
+
+static int parse_address(const char *dev, int hatype, int halen,
+		char *lla, struct ifreq *ifr)
+{
+	int alen;
+
+	memset(ifr, 0, sizeof(*ifr));
+	strlcpy(ifr->ifr_name, dev, IFNAMSIZ);
+	ifr->ifr_hwaddr.sa_family = hatype;
+	alen = ll_addr_a2n(ifr->ifr_hwaddr.sa_data, 14, lla);
+	if (alen < 0)
+		return -1;
+	if (alen != halen) {
+		fprintf(stderr,
+			"Wrong address (%s) length: expected %d bytes\n",
+			lla, halen);
+		return -1;
+	}
+	return 0;
+}
+
+static int set_address(struct ifreq *ifr, int brd)
+{
+	int s;
+
+	s = get_ctl_fd();
+	if (s < 0)
+		return -1;
+	if (ioctl(s, brd?SIOCSIFHWBROADCAST:SIOCSIFHWADDR, ifr) < 0) {
+		perror(brd?"SIOCSIFHWBROADCAST":"SIOCSIFHWADDR");
+		close(s);
+		return -1;
+	}
+	close(s);
+	return 0;
+}
+
+static int do_set(int argc, char **argv)
+{
+	char *dev = NULL;
+	__u32 mask = 0;
+	__u32 flags = 0;
+	int qlen = -1;
+	int mtu = -1;
+	char *newaddr = NULL;
+	char *newbrd = NULL;
+	struct ifreq ifr0, ifr1;
+	char *newname = NULL;
+	int htype, halen;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "up") == 0) {
+			mask |= IFF_UP;
+			flags |= IFF_UP;
+		} else if (strcmp(*argv, "down") == 0) {
+			mask |= IFF_UP;
+			flags &= ~IFF_UP;
+		} else if (strcmp(*argv, "name") == 0) {
+			NEXT_ARG();
+			if (check_ifname(*argv))
+				invarg("\"name\" not a valid ifname", *argv);
+			newname = *argv;
+		} else if (matches(*argv, "address") == 0) {
+			NEXT_ARG();
+			newaddr = *argv;
+		} else if (matches(*argv, "broadcast") == 0 ||
+			   strcmp(*argv, "brd") == 0) {
+			NEXT_ARG();
+			newbrd = *argv;
+		} else if (matches(*argv, "txqueuelen") == 0 ||
+			   strcmp(*argv, "qlen") == 0 ||
+			   matches(*argv, "txqlen") == 0) {
+			NEXT_ARG();
+			if (qlen != -1)
+				duparg("txqueuelen", *argv);
+			if (get_integer(&qlen,  *argv, 0))
+				invarg("Invalid \"txqueuelen\" value\n", *argv);
+		} else if (strcmp(*argv, "mtu") == 0) {
+			NEXT_ARG();
+			if (mtu != -1)
+				duparg("mtu", *argv);
+			if (get_integer(&mtu, *argv, 0))
+				invarg("Invalid \"mtu\" value\n", *argv);
+		} else if (strcmp(*argv, "multicast") == 0) {
+			NEXT_ARG();
+			mask |= IFF_MULTICAST;
+
+			if (strcmp(*argv, "on") == 0)
+				flags |= IFF_MULTICAST;
+			else if (strcmp(*argv, "off") == 0)
+				flags &= ~IFF_MULTICAST;
+			else
+				return on_off("multicast", *argv);
+		} else if (strcmp(*argv, "allmulticast") == 0) {
+			NEXT_ARG();
+			mask |= IFF_ALLMULTI;
+
+			if (strcmp(*argv, "on") == 0)
+				flags |= IFF_ALLMULTI;
+			else if (strcmp(*argv, "off") == 0)
+				flags &= ~IFF_ALLMULTI;
+			else
+				return on_off("allmulticast", *argv);
+		} else if (strcmp(*argv, "promisc") == 0) {
+			NEXT_ARG();
+			mask |= IFF_PROMISC;
+
+			if (strcmp(*argv, "on") == 0)
+				flags |= IFF_PROMISC;
+			else if (strcmp(*argv, "off") == 0)
+				flags &= ~IFF_PROMISC;
+			else
+				return on_off("promisc", *argv);
+		} else if (strcmp(*argv, "trailers") == 0) {
+			NEXT_ARG();
+			mask |= IFF_NOTRAILERS;
+
+			if (strcmp(*argv, "off") == 0)
+				flags |= IFF_NOTRAILERS;
+			else if (strcmp(*argv, "on") == 0)
+				flags &= ~IFF_NOTRAILERS;
+			else
+				return on_off("trailers", *argv);
+		} else if (strcmp(*argv, "arp") == 0) {
+			NEXT_ARG();
+			mask |= IFF_NOARP;
+
+			if (strcmp(*argv, "on") == 0)
+				flags &= ~IFF_NOARP;
+			else if (strcmp(*argv, "off") == 0)
+				flags |= IFF_NOARP;
+			else
+				return on_off("arp", *argv);
+		} else if (matches(*argv, "dynamic") == 0) {
+			NEXT_ARG();
+			mask |= IFF_DYNAMIC;
+
+			if (strcmp(*argv, "on") == 0)
+				flags |= IFF_DYNAMIC;
+			else if (strcmp(*argv, "off") == 0)
+				flags &= ~IFF_DYNAMIC;
+			else
+				return on_off("dynamic", *argv);
+		} else {
+			if (strcmp(*argv, "dev") == 0)
+				NEXT_ARG();
+			else if (matches(*argv, "help") == 0)
+				usage();
+
+			if (dev)
+				duparg2("dev", *argv);
+			if (check_ifname(*argv))
+				invarg("\"dev\" not a valid ifname", *argv);
+			dev = *argv;
+		}
+		argc--; argv++;
+	}
+
+	if (!dev) {
+		fprintf(stderr,
+			"Not enough of information: \"dev\" argument is required.\n");
+		exit(-1);
+	}
+
+	if (newaddr || newbrd) {
+		halen = get_address(dev, &htype);
+		if (halen < 0)
+			return -1;
+		if (newaddr) {
+			if (parse_address(dev, htype, halen,
+					  newaddr, &ifr0) < 0)
+				return -1;
+		}
+		if (newbrd) {
+			if (parse_address(dev, htype, halen,
+					  newbrd, &ifr1) < 0)
+				return -1;
+		}
+	}
+
+	if (newname && strcmp(dev, newname)) {
+		if (do_changename(dev, newname) < 0)
+			return -1;
+		dev = newname;
+	}
+	if (qlen != -1) {
+		if (set_qlen(dev, qlen) < 0)
+			return -1;
+	}
+	if (mtu != -1) {
+		if (set_mtu(dev, mtu) < 0)
+			return -1;
+	}
+	if (newaddr || newbrd) {
+		if (newbrd) {
+			if (set_address(&ifr1, 1) < 0)
+				return -1;
+		}
+		if (newaddr) {
+			if (set_address(&ifr0, 0) < 0)
+				return -1;
+		}
+	}
+	if (mask)
+		return do_chflags(dev, flags, mask);
+	return 0;
+}
+#endif /* IPLINK_IOCTL_COMPAT */
+
+static void print_mpls_stats(FILE *fp, struct rtattr *attr)
+{
+	struct rtattr *mrtb[MPLS_STATS_MAX+1];
+	struct mpls_link_stats *stats;
+
+	parse_rtattr(mrtb, MPLS_STATS_MAX, RTA_DATA(attr),
+		     RTA_PAYLOAD(attr));
+	if (!mrtb[MPLS_STATS_LINK])
+		return;
+
+	stats = RTA_DATA(mrtb[MPLS_STATS_LINK]);
+
+	fprintf(fp, "    mpls:\n");
+	fprintf(fp, "        RX: bytes  packets  errors  dropped  noroute\n");
+	fprintf(fp, "        ");
+	print_num(fp, 10, stats->rx_bytes);
+	print_num(fp, 8, stats->rx_packets);
+	print_num(fp, 7, stats->rx_errors);
+	print_num(fp, 8, stats->rx_dropped);
+	print_num(fp, 7, stats->rx_noroute);
+	fprintf(fp, "\n");
+	fprintf(fp, "        TX: bytes  packets  errors  dropped\n");
+	fprintf(fp, "        ");
+	print_num(fp, 10, stats->tx_bytes);
+	print_num(fp, 8, stats->tx_packets);
+	print_num(fp, 7, stats->tx_errors);
+	print_num(fp, 7, stats->tx_dropped);
+	fprintf(fp, "\n");
+}
+
+static void print_af_stats_attr(FILE *fp, int ifindex, struct rtattr *attr)
+{
+	bool if_printed = false;
+	struct rtattr *i;
+	int rem;
+
+	rem = RTA_PAYLOAD(attr);
+	for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		if (preferred_family != AF_UNSPEC &&
+		    i->rta_type != preferred_family)
+			continue;
+
+		if (!if_printed) {
+			fprintf(fp, "%u: %s\n", ifindex,
+				ll_index_to_name(ifindex));
+			if_printed = true;
+		}
+
+		switch (i->rta_type) {
+		case AF_MPLS:
+			print_mpls_stats(fp, i);
+			break;
+		default:
+			fprintf(fp, "    unknown af(%d)\n", i->rta_type);
+			break;
+		}
+	}
+}
+
+struct af_stats_ctx {
+	FILE *fp;
+	int ifindex;
+};
+
+static int print_af_stats(struct nlmsghdr *n, void *arg)
+{
+	struct if_stats_msg *ifsm = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_STATS_MAX+1];
+	int len = n->nlmsg_len;
+	struct af_stats_ctx *ctx = arg;
+	FILE *fp = ctx->fp;
+
+	len -= NLMSG_LENGTH(sizeof(*ifsm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (ctx->ifindex && ifsm->ifindex != ctx->ifindex)
+		return 0;
+
+	parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len);
+
+	if (tb[IFLA_STATS_AF_SPEC])
+		print_af_stats_attr(fp, ifsm->ifindex, tb[IFLA_STATS_AF_SPEC]);
+
+	fflush(fp);
+	return 0;
+}
+
+static int iplink_afstats(int argc, char **argv)
+{
+	__u32 filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_AF_SPEC);
+	const char *filter_dev = NULL;
+	struct af_stats_ctx ctx = {
+		.fp = stdout,
+		.ifindex = 0,
+	};
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (filter_dev)
+				duparg2("dev", *argv);
+			filter_dev = *argv;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			fprintf(stderr,
+				"Command \"%s\" is unknown, try \"ip link help\".\n",
+				*argv);
+			exit(-1);
+		}
+
+		argv++; argc--;
+	}
+
+	if (filter_dev) {
+		ctx.ifindex = ll_name_to_index(filter_dev);
+		if (ctx.ifindex <= 0) {
+			fprintf(stderr,
+				"Device \"%s\" does not exist.\n",
+				filter_dev);
+			return -1;
+		}
+	}
+
+	if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) {
+		perror("Cannont send dump request");
+		return 1;
+	}
+
+	if (rtnl_dump_filter(&rth, print_af_stats, &ctx) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static void do_help(int argc, char **argv)
+{
+	struct link_util *lu = NULL;
+
+	if (argc <= 0) {
+		usage();
+		return;
+	}
+
+	lu = get_link_kind(*argv);
+	if (lu && lu->print_help)
+		lu->print_help(lu, argc-1, argv+1, stdout);
+	else
+		usage();
+}
+
+int do_iplink(int argc, char **argv)
+{
+	if (argc < 1)
+		return ipaddr_list_link(0, NULL);
+
+	if (iplink_have_newlink()) {
+		if (matches(*argv, "add") == 0)
+			return iplink_modify(RTM_NEWLINK,
+					     NLM_F_CREATE|NLM_F_EXCL,
+					     argc-1, argv+1);
+		if (matches(*argv, "set") == 0 ||
+		    matches(*argv, "change") == 0)
+			return iplink_modify(RTM_NEWLINK, 0,
+					     argc-1, argv+1);
+		if (matches(*argv, "replace") == 0)
+			return iplink_modify(RTM_NEWLINK,
+					     NLM_F_CREATE|NLM_F_REPLACE,
+					     argc-1, argv+1);
+		if (matches(*argv, "delete") == 0)
+			return iplink_modify(RTM_DELLINK, 0,
+					     argc-1, argv+1);
+	} else {
+#if IPLINK_IOCTL_COMPAT
+		if (matches(*argv, "set") == 0)
+			return do_set(argc-1, argv+1);
+#endif
+	}
+
+	if (matches(*argv, "show") == 0 ||
+	    matches(*argv, "lst") == 0 ||
+	    matches(*argv, "list") == 0)
+		return ipaddr_list_link(argc-1, argv+1);
+
+	if (matches(*argv, "xstats") == 0)
+		return iplink_ifla_xstats(argc-1, argv+1);
+
+	if (matches(*argv, "afstats") == 0) {
+		iplink_afstats(argc-1, argv+1);
+		return 0;
+	}
+
+	if (matches(*argv, "help") == 0) {
+		do_help(argc-1, argv+1);
+		return 0;
+	}
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip link help\".\n",
+		*argv);
+	exit(-1);
+}
diff --git a/ip/iplink_bond.c b/ip/iplink_bond.c
new file mode 100644
index 0000000..585b6be
--- /dev/null
+++ b/ip/iplink_bond.c
@@ -0,0 +1,831 @@
+/*
+ * iplink_bond.c	Bonding device support
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@resnulli.us>
+ *              Scott Feldman <sfeldma@cumulusnetworks.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_bonding.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+#define BOND_MAX_ARP_TARGETS    16
+
+static unsigned int xstats_print_attr;
+static int filter_index;
+
+static const char *mode_tbl[] = {
+	"balance-rr",
+	"active-backup",
+	"balance-xor",
+	"broadcast",
+	"802.3ad",
+	"balance-tlb",
+	"balance-alb",
+	NULL,
+};
+
+static const char *arp_validate_tbl[] = {
+	"none",
+	"active",
+	"backup",
+	"all",
+	NULL,
+};
+
+static const char *arp_all_targets_tbl[] = {
+	"any",
+	"all",
+	NULL,
+};
+
+static const char *primary_reselect_tbl[] = {
+	"always",
+	"better",
+	"failure",
+	NULL,
+};
+
+static const char *fail_over_mac_tbl[] = {
+	"none",
+	"active",
+	"follow",
+	NULL,
+};
+
+static const char *xmit_hash_policy_tbl[] = {
+	"layer2",
+	"layer3+4",
+	"layer2+3",
+	"encap2+3",
+	"encap3+4",
+	NULL,
+};
+
+static const char *lacp_rate_tbl[] = {
+	"slow",
+	"fast",
+	NULL,
+};
+
+static const char *ad_select_tbl[] = {
+	"stable",
+	"bandwidth",
+	"count",
+	NULL,
+};
+
+static const char *get_name(const char **tbl, int index)
+{
+	int i;
+
+	for (i = 0; tbl[i]; i++)
+		if (i == index)
+			return tbl[i];
+
+	return "UNKNOWN";
+}
+
+static int get_index(const char **tbl, char *name)
+{
+	int i, index;
+
+	/* check for integer index passed in instead of name */
+	if (get_integer(&index, name, 10) == 0)
+		for (i = 0; tbl[i]; i++)
+			if (i == index)
+				return i;
+
+	for (i = 0; tbl[i]; i++)
+		if (strcmp(tbl[i], name) == 0)
+			return i;
+
+	return -1;
+}
+
+static void print_explain(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... bond [ mode BONDMODE ] [ active_slave SLAVE_DEV ]\n"
+		"                [ clear_active_slave ] [ miimon MIIMON ]\n"
+		"                [ updelay UPDELAY ] [ downdelay DOWNDELAY ]\n"
+		"                [ peer_notify_delay DELAY ]\n"
+		"                [ use_carrier USE_CARRIER ]\n"
+		"                [ arp_interval ARP_INTERVAL ]\n"
+		"                [ arp_validate ARP_VALIDATE ]\n"
+		"                [ arp_all_targets ARP_ALL_TARGETS ]\n"
+		"                [ arp_ip_target [ ARP_IP_TARGET, ... ] ]\n"
+		"                [ primary SLAVE_DEV ]\n"
+		"                [ primary_reselect PRIMARY_RESELECT ]\n"
+		"                [ fail_over_mac FAIL_OVER_MAC ]\n"
+		"                [ xmit_hash_policy XMIT_HASH_POLICY ]\n"
+		"                [ resend_igmp RESEND_IGMP ]\n"
+		"                [ num_grat_arp|num_unsol_na NUM_GRAT_ARP|NUM_UNSOL_NA ]\n"
+		"                [ all_slaves_active ALL_SLAVES_ACTIVE ]\n"
+		"                [ min_links MIN_LINKS ]\n"
+		"                [ lp_interval LP_INTERVAL ]\n"
+		"                [ packets_per_slave PACKETS_PER_SLAVE ]\n"
+		"                [ tlb_dynamic_lb TLB_DYNAMIC_LB ]\n"
+		"                [ lacp_rate LACP_RATE ]\n"
+		"                [ ad_select AD_SELECT ]\n"
+		"                [ ad_user_port_key PORTKEY ]\n"
+		"                [ ad_actor_sys_prio SYSPRIO ]\n"
+		"                [ ad_actor_system LLADDR ]\n"
+		"\n"
+		"BONDMODE := balance-rr|active-backup|balance-xor|broadcast|802.3ad|balance-tlb|balance-alb\n"
+		"ARP_VALIDATE := none|active|backup|all\n"
+		"ARP_ALL_TARGETS := any|all\n"
+		"PRIMARY_RESELECT := always|better|failure\n"
+		"FAIL_OVER_MAC := none|active|follow\n"
+		"XMIT_HASH_POLICY := layer2|layer2+3|layer3+4|encap2+3|encap3+4\n"
+		"LACP_RATE := slow|fast\n"
+		"AD_SELECT := stable|bandwidth|count\n"
+	);
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static int bond_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	__u8 mode, use_carrier, primary_reselect, fail_over_mac;
+	__u8 xmit_hash_policy, num_peer_notif, all_slaves_active;
+	__u8 lacp_rate, ad_select, tlb_dynamic_lb;
+	__u16 ad_user_port_key, ad_actor_sys_prio;
+	__u32 miimon, updelay, downdelay, peer_notify_delay, arp_interval, arp_validate;
+	__u32 arp_all_targets, resend_igmp, min_links, lp_interval;
+	__u32 packets_per_slave;
+	unsigned int ifindex;
+
+	while (argc > 0) {
+		if (matches(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (get_index(mode_tbl, *argv) < 0)
+				invarg("invalid mode", *argv);
+			mode = get_index(mode_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_MODE, mode);
+		} else if (matches(*argv, "active_slave") == 0) {
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				return nodev(*argv);
+			addattr32(n, 1024, IFLA_BOND_ACTIVE_SLAVE, ifindex);
+		} else if (matches(*argv, "clear_active_slave") == 0) {
+			addattr32(n, 1024, IFLA_BOND_ACTIVE_SLAVE, 0);
+		} else if (matches(*argv, "miimon") == 0) {
+			NEXT_ARG();
+			if (get_u32(&miimon, *argv, 0))
+				invarg("invalid miimon", *argv);
+			addattr32(n, 1024, IFLA_BOND_MIIMON, miimon);
+		} else if (matches(*argv, "updelay") == 0) {
+			NEXT_ARG();
+			if (get_u32(&updelay, *argv, 0))
+				invarg("invalid updelay", *argv);
+			addattr32(n, 1024, IFLA_BOND_UPDELAY, updelay);
+		} else if (matches(*argv, "downdelay") == 0) {
+			NEXT_ARG();
+			if (get_u32(&downdelay, *argv, 0))
+				invarg("invalid downdelay", *argv);
+			addattr32(n, 1024, IFLA_BOND_DOWNDELAY, downdelay);
+		} else if (matches(*argv, "peer_notify_delay") == 0) {
+			NEXT_ARG();
+			if (get_u32(&peer_notify_delay, *argv, 0))
+				invarg("invalid peer_notify_delay", *argv);
+			addattr32(n, 1024, IFLA_BOND_PEER_NOTIF_DELAY, peer_notify_delay);
+		} else if (matches(*argv, "use_carrier") == 0) {
+			NEXT_ARG();
+			if (get_u8(&use_carrier, *argv, 0))
+				invarg("invalid use_carrier", *argv);
+			addattr8(n, 1024, IFLA_BOND_USE_CARRIER, use_carrier);
+		} else if (matches(*argv, "arp_interval") == 0) {
+			NEXT_ARG();
+			if (get_u32(&arp_interval, *argv, 0))
+				invarg("invalid arp_interval", *argv);
+			addattr32(n, 1024, IFLA_BOND_ARP_INTERVAL, arp_interval);
+		} else if (matches(*argv, "arp_ip_target") == 0) {
+			struct rtattr *nest = addattr_nest(n, 1024,
+				IFLA_BOND_ARP_IP_TARGET);
+			if (NEXT_ARG_OK()) {
+				NEXT_ARG();
+				char *targets = strdupa(*argv);
+				char *target = strtok(targets, ",");
+				int i;
+
+				for (i = 0; target && i < BOND_MAX_ARP_TARGETS; i++) {
+					__u32 addr = get_addr32(target);
+
+					addattr32(n, 1024, i, addr);
+					target = strtok(NULL, ",");
+				}
+				addattr_nest_end(n, nest);
+			}
+			addattr_nest_end(n, nest);
+		} else if (matches(*argv, "arp_validate") == 0) {
+			NEXT_ARG();
+			if (get_index(arp_validate_tbl, *argv) < 0)
+				invarg("invalid arp_validate", *argv);
+			arp_validate = get_index(arp_validate_tbl, *argv);
+			addattr32(n, 1024, IFLA_BOND_ARP_VALIDATE, arp_validate);
+		} else if (matches(*argv, "arp_all_targets") == 0) {
+			NEXT_ARG();
+			if (get_index(arp_all_targets_tbl, *argv) < 0)
+				invarg("invalid arp_all_targets", *argv);
+			arp_all_targets = get_index(arp_all_targets_tbl, *argv);
+			addattr32(n, 1024, IFLA_BOND_ARP_ALL_TARGETS, arp_all_targets);
+		} else if (matches(*argv, "primary") == 0) {
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				return nodev(*argv);
+			addattr32(n, 1024, IFLA_BOND_PRIMARY, ifindex);
+		} else if (matches(*argv, "primary_reselect") == 0) {
+			NEXT_ARG();
+			if (get_index(primary_reselect_tbl, *argv) < 0)
+				invarg("invalid primary_reselect", *argv);
+			primary_reselect = get_index(primary_reselect_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_PRIMARY_RESELECT,
+				 primary_reselect);
+		} else if (matches(*argv, "fail_over_mac") == 0) {
+			NEXT_ARG();
+			if (get_index(fail_over_mac_tbl, *argv) < 0)
+				invarg("invalid fail_over_mac", *argv);
+			fail_over_mac = get_index(fail_over_mac_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_FAIL_OVER_MAC,
+				 fail_over_mac);
+		} else if (matches(*argv, "xmit_hash_policy") == 0) {
+			NEXT_ARG();
+			if (get_index(xmit_hash_policy_tbl, *argv) < 0)
+				invarg("invalid xmit_hash_policy", *argv);
+
+			xmit_hash_policy = get_index(xmit_hash_policy_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_XMIT_HASH_POLICY,
+				 xmit_hash_policy);
+		} else if (matches(*argv, "resend_igmp") == 0) {
+			NEXT_ARG();
+			if (get_u32(&resend_igmp, *argv, 0))
+				invarg("invalid resend_igmp", *argv);
+
+			addattr32(n, 1024, IFLA_BOND_RESEND_IGMP, resend_igmp);
+		} else if (matches(*argv, "num_grat_arp") == 0 ||
+			   matches(*argv, "num_unsol_na") == 0) {
+			NEXT_ARG();
+			if (get_u8(&num_peer_notif, *argv, 0))
+				invarg("invalid num_grat_arp|num_unsol_na",
+				       *argv);
+
+			addattr8(n, 1024, IFLA_BOND_NUM_PEER_NOTIF,
+				 num_peer_notif);
+		} else if (matches(*argv, "all_slaves_active") == 0) {
+			NEXT_ARG();
+			if (get_u8(&all_slaves_active, *argv, 0))
+				invarg("invalid all_slaves_active", *argv);
+
+			addattr8(n, 1024, IFLA_BOND_ALL_SLAVES_ACTIVE,
+				 all_slaves_active);
+		} else if (matches(*argv, "min_links") == 0) {
+			NEXT_ARG();
+			if (get_u32(&min_links, *argv, 0))
+				invarg("invalid min_links", *argv);
+
+			addattr32(n, 1024, IFLA_BOND_MIN_LINKS, min_links);
+		} else if (matches(*argv, "lp_interval") == 0) {
+			NEXT_ARG();
+			if (get_u32(&lp_interval, *argv, 0))
+				invarg("invalid lp_interval", *argv);
+
+			addattr32(n, 1024, IFLA_BOND_LP_INTERVAL, lp_interval);
+		} else if (matches(*argv, "packets_per_slave") == 0) {
+			NEXT_ARG();
+			if (get_u32(&packets_per_slave, *argv, 0))
+				invarg("invalid packets_per_slave", *argv);
+
+			addattr32(n, 1024, IFLA_BOND_PACKETS_PER_SLAVE,
+				  packets_per_slave);
+		} else if (matches(*argv, "lacp_rate") == 0) {
+			NEXT_ARG();
+			if (get_index(lacp_rate_tbl, *argv) < 0)
+				invarg("invalid lacp_rate", *argv);
+
+			lacp_rate = get_index(lacp_rate_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_AD_LACP_RATE, lacp_rate);
+		} else if (matches(*argv, "ad_select") == 0) {
+			NEXT_ARG();
+			if (get_index(ad_select_tbl, *argv) < 0)
+				invarg("invalid ad_select", *argv);
+
+			ad_select = get_index(ad_select_tbl, *argv);
+			addattr8(n, 1024, IFLA_BOND_AD_SELECT, ad_select);
+		} else if (matches(*argv, "ad_user_port_key") == 0) {
+			NEXT_ARG();
+			if (get_u16(&ad_user_port_key, *argv, 0))
+				invarg("invalid ad_user_port_key", *argv);
+
+			addattr16(n, 1024, IFLA_BOND_AD_USER_PORT_KEY,
+				  ad_user_port_key);
+		} else if (matches(*argv, "ad_actor_sys_prio") == 0) {
+			NEXT_ARG();
+			if (get_u16(&ad_actor_sys_prio, *argv, 0))
+				invarg("invalid ad_actor_sys_prio", *argv);
+
+			addattr16(n, 1024, IFLA_BOND_AD_ACTOR_SYS_PRIO,
+				  ad_actor_sys_prio);
+		} else if (matches(*argv, "ad_actor_system") == 0) {
+			int len;
+			char abuf[32];
+
+			NEXT_ARG();
+			len = ll_addr_a2n(abuf, sizeof(abuf), *argv);
+			if (len < 0)
+				return -1;
+			addattr_l(n, 1024, IFLA_BOND_AD_ACTOR_SYSTEM,
+				  abuf, len);
+		} else if (matches(*argv, "tlb_dynamic_lb") == 0) {
+			NEXT_ARG();
+			if (get_u8(&tlb_dynamic_lb, *argv, 0)) {
+				invarg("invalid tlb_dynamic_lb", *argv);
+				return -1;
+			}
+			addattr8(n, 1024, IFLA_BOND_TLB_DYNAMIC_LB,
+				 tlb_dynamic_lb);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "bond: unknown command \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void bond_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_BOND_MODE]) {
+		const char *mode = get_name(mode_tbl,
+					    rta_getattr_u8(tb[IFLA_BOND_MODE]));
+		print_string(PRINT_ANY, "mode", "mode %s ", mode);
+	}
+
+	if (tb[IFLA_BOND_ACTIVE_SLAVE]) {
+		unsigned int ifindex =
+			rta_getattr_u32(tb[IFLA_BOND_ACTIVE_SLAVE]);
+
+		if (ifindex) {
+			print_string(PRINT_ANY,
+				     "active_slave",
+				     "active_slave %s ",
+				     ll_index_to_name(ifindex));
+		}
+	}
+
+	if (tb[IFLA_BOND_MIIMON])
+		print_uint(PRINT_ANY,
+			   "miimon",
+			   "miimon %u ",
+			   rta_getattr_u32(tb[IFLA_BOND_MIIMON]));
+
+	if (tb[IFLA_BOND_UPDELAY])
+		print_uint(PRINT_ANY,
+			   "updelay",
+			   "updelay %u ",
+			   rta_getattr_u32(tb[IFLA_BOND_UPDELAY]));
+
+	if (tb[IFLA_BOND_DOWNDELAY])
+		print_uint(PRINT_ANY,
+			   "downdelay",
+			   "downdelay %u ",
+			   rta_getattr_u32(tb[IFLA_BOND_DOWNDELAY]));
+
+	if (tb[IFLA_BOND_PEER_NOTIF_DELAY])
+		print_uint(PRINT_ANY,
+			   "peer_notify_delay",
+			   "peer_notify_delay %u ",
+			   rta_getattr_u32(tb[IFLA_BOND_PEER_NOTIF_DELAY]));
+
+	if (tb[IFLA_BOND_USE_CARRIER])
+		print_uint(PRINT_ANY,
+			   "use_carrier",
+			   "use_carrier %u ",
+			   rta_getattr_u8(tb[IFLA_BOND_USE_CARRIER]));
+
+	if (tb[IFLA_BOND_ARP_INTERVAL])
+		print_uint(PRINT_ANY,
+			   "arp_interval",
+			   "arp_interval %u ",
+			   rta_getattr_u32(tb[IFLA_BOND_ARP_INTERVAL]));
+
+	if (tb[IFLA_BOND_ARP_IP_TARGET]) {
+		struct rtattr *iptb[BOND_MAX_ARP_TARGETS + 1];
+		int i;
+
+		parse_rtattr_nested(iptb, BOND_MAX_ARP_TARGETS,
+				    tb[IFLA_BOND_ARP_IP_TARGET]);
+
+		if (iptb[0]) {
+			open_json_array(PRINT_JSON, "arp_ip_target");
+			print_string(PRINT_FP, NULL, "arp_ip_target ", NULL);
+		}
+
+		for (i = 0; i < BOND_MAX_ARP_TARGETS; i++) {
+			if (iptb[i])
+				print_string(PRINT_ANY,
+					     NULL,
+					     "%s",
+					     rt_addr_n2a_rta(AF_INET, iptb[i]));
+			if (!is_json_context()
+			    && i < BOND_MAX_ARP_TARGETS-1
+			    && iptb[i+1])
+				fprintf(f, ",");
+		}
+
+		if (iptb[0]) {
+			print_string(PRINT_FP, NULL, " ", NULL);
+			close_json_array(PRINT_JSON, NULL);
+		}
+	}
+
+	if (tb[IFLA_BOND_ARP_VALIDATE]) {
+		__u32 arp_v = rta_getattr_u32(tb[IFLA_BOND_ARP_VALIDATE]);
+		const char *arp_validate = get_name(arp_validate_tbl, arp_v);
+
+		if (!arp_v && is_json_context())
+			print_null(PRINT_JSON, "arp_validate", NULL, NULL);
+		else
+			print_string(PRINT_ANY,
+				     "arp_validate",
+				     "arp_validate %s ",
+				     arp_validate);
+	}
+
+	if (tb[IFLA_BOND_ARP_ALL_TARGETS]) {
+		const char *arp_all_targets = get_name(arp_all_targets_tbl,
+						       rta_getattr_u32(tb[IFLA_BOND_ARP_ALL_TARGETS]));
+		print_string(PRINT_ANY,
+			     "arp_all_targets",
+			     "arp_all_targets %s ",
+			     arp_all_targets);
+	}
+
+	if (tb[IFLA_BOND_PRIMARY]) {
+		unsigned int ifindex = rta_getattr_u32(tb[IFLA_BOND_PRIMARY]);
+
+		if (ifindex) {
+			print_string(PRINT_ANY,
+				     "primary",
+				     "primary %s ",
+				     ll_index_to_name(ifindex));
+		}
+	}
+
+	if (tb[IFLA_BOND_PRIMARY_RESELECT]) {
+		const char *primary_reselect = get_name(primary_reselect_tbl,
+							rta_getattr_u8(tb[IFLA_BOND_PRIMARY_RESELECT]));
+		print_string(PRINT_ANY,
+			     "primary_reselect",
+			     "primary_reselect %s ",
+			     primary_reselect);
+	}
+
+	if (tb[IFLA_BOND_FAIL_OVER_MAC]) {
+		const char *fail_over_mac = get_name(fail_over_mac_tbl,
+						     rta_getattr_u8(tb[IFLA_BOND_FAIL_OVER_MAC]));
+		print_string(PRINT_ANY,
+			     "fail_over_mac",
+			     "fail_over_mac %s ",
+			     fail_over_mac);
+	}
+
+	if (tb[IFLA_BOND_XMIT_HASH_POLICY]) {
+		const char *xmit_hash_policy = get_name(xmit_hash_policy_tbl,
+							rta_getattr_u8(tb[IFLA_BOND_XMIT_HASH_POLICY]));
+		print_string(PRINT_ANY,
+			     "xmit_hash_policy",
+			     "xmit_hash_policy %s ",
+			     xmit_hash_policy);
+	}
+
+	if (tb[IFLA_BOND_RESEND_IGMP])
+		print_uint(PRINT_ANY,
+			   "resend_igmp",
+			   "resend_igmp %u ",
+			   rta_getattr_u32(tb[IFLA_BOND_RESEND_IGMP]));
+
+	if (tb[IFLA_BOND_NUM_PEER_NOTIF])
+		print_uint(PRINT_ANY,
+			   "num_peer_notif",
+			   "num_grat_arp %u ",
+			   rta_getattr_u8(tb[IFLA_BOND_NUM_PEER_NOTIF]));
+
+	if (tb[IFLA_BOND_ALL_SLAVES_ACTIVE])
+		print_uint(PRINT_ANY,
+			   "all_slaves_active",
+			   "all_slaves_active %u ",
+			   rta_getattr_u8(tb[IFLA_BOND_ALL_SLAVES_ACTIVE]));
+
+	if (tb[IFLA_BOND_MIN_LINKS])
+		print_uint(PRINT_ANY,
+			   "min_links",
+			   "min_links %u ",
+			   rta_getattr_u32(tb[IFLA_BOND_MIN_LINKS]));
+
+	if (tb[IFLA_BOND_LP_INTERVAL])
+		print_uint(PRINT_ANY,
+			   "lp_interval",
+			   "lp_interval %u ",
+			   rta_getattr_u32(tb[IFLA_BOND_LP_INTERVAL]));
+
+	if (tb[IFLA_BOND_PACKETS_PER_SLAVE])
+		print_uint(PRINT_ANY,
+			   "packets_per_slave",
+			   "packets_per_slave %u ",
+			   rta_getattr_u32(tb[IFLA_BOND_PACKETS_PER_SLAVE]));
+
+	if (tb[IFLA_BOND_AD_LACP_RATE]) {
+		const char *lacp_rate = get_name(lacp_rate_tbl,
+						 rta_getattr_u8(tb[IFLA_BOND_AD_LACP_RATE]));
+		print_string(PRINT_ANY,
+			     "ad_lacp_rate",
+			     "lacp_rate %s ",
+			     lacp_rate);
+	}
+
+	if (tb[IFLA_BOND_AD_SELECT]) {
+		const char *ad_select = get_name(ad_select_tbl,
+						 rta_getattr_u8(tb[IFLA_BOND_AD_SELECT]));
+		print_string(PRINT_ANY,
+			     "ad_select",
+			     "ad_select %s ",
+			     ad_select);
+	}
+
+	if (tb[IFLA_BOND_AD_INFO]) {
+		struct rtattr *adtb[IFLA_BOND_AD_INFO_MAX + 1];
+
+		parse_rtattr_nested(adtb, IFLA_BOND_AD_INFO_MAX,
+				    tb[IFLA_BOND_AD_INFO]);
+
+		open_json_object("ad_info");
+
+		if (adtb[IFLA_BOND_AD_INFO_AGGREGATOR])
+			print_int(PRINT_ANY,
+				  "aggregator",
+				  "ad_aggregator %d ",
+				  rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_AGGREGATOR]));
+
+		if (adtb[IFLA_BOND_AD_INFO_NUM_PORTS])
+			print_int(PRINT_ANY,
+				  "num_ports",
+				  "ad_num_ports %d ",
+				  rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_NUM_PORTS]));
+
+		if (adtb[IFLA_BOND_AD_INFO_ACTOR_KEY])
+			print_int(PRINT_ANY,
+				  "actor_key",
+				  "ad_actor_key %d ",
+				  rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_ACTOR_KEY]));
+
+		if (adtb[IFLA_BOND_AD_INFO_PARTNER_KEY])
+			print_int(PRINT_ANY,
+				  "partner_key",
+				  "ad_partner_key %d ",
+				  rta_getattr_u16(adtb[IFLA_BOND_AD_INFO_PARTNER_KEY]));
+
+		if (adtb[IFLA_BOND_AD_INFO_PARTNER_MAC]) {
+			unsigned char *p =
+				RTA_DATA(adtb[IFLA_BOND_AD_INFO_PARTNER_MAC]);
+			SPRINT_BUF(b);
+			print_string(PRINT_ANY,
+				     "partner_mac",
+				     "ad_partner_mac %s ",
+				     ll_addr_n2a(p, ETH_ALEN, 0, b, sizeof(b)));
+		}
+
+		close_json_object();
+	}
+
+	if (tb[IFLA_BOND_AD_ACTOR_SYS_PRIO]) {
+		print_uint(PRINT_ANY,
+			   "ad_actor_sys_prio",
+			   "ad_actor_sys_prio %u ",
+			   rta_getattr_u16(tb[IFLA_BOND_AD_ACTOR_SYS_PRIO]));
+	}
+
+	if (tb[IFLA_BOND_AD_USER_PORT_KEY]) {
+		print_uint(PRINT_ANY,
+			   "ad_user_port_key",
+			   "ad_user_port_key %u ",
+			   rta_getattr_u16(tb[IFLA_BOND_AD_USER_PORT_KEY]));
+	}
+
+	if (tb[IFLA_BOND_AD_ACTOR_SYSTEM]) {
+		/* We assume the l2 address is an Ethernet MAC address */
+		SPRINT_BUF(b1);
+
+		print_string(PRINT_ANY,
+			     "ad_actor_system",
+			     "ad_actor_system %s ",
+			     ll_addr_n2a(RTA_DATA(tb[IFLA_BOND_AD_ACTOR_SYSTEM]),
+					 RTA_PAYLOAD(tb[IFLA_BOND_AD_ACTOR_SYSTEM]),
+					 1 /*ARPHDR_ETHER*/, b1, sizeof(b1)));
+	}
+
+	if (tb[IFLA_BOND_TLB_DYNAMIC_LB]) {
+		print_uint(PRINT_ANY,
+			   "tlb_dynamic_lb",
+			   "tlb_dynamic_lb %u ",
+			   rta_getattr_u8(tb[IFLA_BOND_TLB_DYNAMIC_LB]));
+	}
+}
+
+static void bond_print_help(struct link_util *lu, int argc, char **argv,
+			    FILE *f)
+{
+	print_explain(f);
+}
+
+static void bond_print_xstats_help(struct link_util *lu, FILE *f)
+{
+	fprintf(f, "Usage: ... %s [ 802.3ad ] [ dev DEVICE ]\n", lu->id);
+}
+
+static void bond_print_3ad_stats(struct rtattr *lacpattr)
+{
+	struct rtattr *lacptb[BOND_3AD_STAT_MAX+1];
+	__u64 val;
+
+	parse_rtattr(lacptb, BOND_3AD_STAT_MAX, RTA_DATA(lacpattr),
+		     RTA_PAYLOAD(lacpattr));
+	open_json_object("802.3ad");
+	if (lacptb[BOND_3AD_STAT_LACPDU_RX]) {
+		print_string(PRINT_FP, NULL, "%-16s    ", "");
+		print_u64(PRINT_ANY, "lacpdu_rx", "LACPDU Rx %llu\n",
+			  rta_getattr_u64(lacptb[BOND_3AD_STAT_LACPDU_RX]));
+	}
+	if (lacptb[BOND_3AD_STAT_LACPDU_TX]) {
+		print_string(PRINT_FP, NULL, "%-16s    ", "");
+		print_u64(PRINT_ANY, "lacpdu_tx", "LACPDU Tx %llu\n",
+			  rta_getattr_u64(lacptb[BOND_3AD_STAT_LACPDU_TX]));
+	}
+	if (lacptb[BOND_3AD_STAT_LACPDU_UNKNOWN_RX]) {
+		print_string(PRINT_FP, NULL, "%-16s    ", "");
+		val = rta_getattr_u64(lacptb[BOND_3AD_STAT_LACPDU_UNKNOWN_RX]);
+		print_u64(PRINT_ANY,
+			  "lacpdu_unknown_rx",
+			  "LACPDU Unknown type Rx %llu\n",
+			  val);
+	}
+	if (lacptb[BOND_3AD_STAT_LACPDU_ILLEGAL_RX]) {
+		print_string(PRINT_FP, NULL, "%-16s    ", "");
+		val = rta_getattr_u64(lacptb[BOND_3AD_STAT_LACPDU_ILLEGAL_RX]);
+		print_u64(PRINT_ANY,
+			  "lacpdu_illegal_rx",
+			  "LACPDU Illegal Rx %llu\n",
+			  val);
+	}
+	if (lacptb[BOND_3AD_STAT_MARKER_RX]) {
+		print_string(PRINT_FP, NULL, "%-16s    ", "");
+		print_u64(PRINT_ANY, "marker_rx", "Marker Rx %llu\n",
+			  rta_getattr_u64(lacptb[BOND_3AD_STAT_MARKER_RX]));
+	}
+	if (lacptb[BOND_3AD_STAT_MARKER_TX]) {
+		print_string(PRINT_FP, NULL, "%-16s    ", "");
+		print_u64(PRINT_ANY, "marker_tx", "Marker Tx %llu\n",
+			  rta_getattr_u64(lacptb[BOND_3AD_STAT_MARKER_TX]));
+	}
+	if (lacptb[BOND_3AD_STAT_MARKER_RESP_RX]) {
+		print_string(PRINT_FP, NULL, "%-16s    ", "");
+		val = rta_getattr_u64(lacptb[BOND_3AD_STAT_MARKER_RESP_RX]);
+		print_u64(PRINT_ANY,
+			  "marker_response_rx",
+			  "Marker response Rx %llu\n",
+			  val);
+	}
+	if (lacptb[BOND_3AD_STAT_MARKER_RESP_TX]) {
+		print_string(PRINT_FP, NULL, "%-16s    ", "");
+		val = rta_getattr_u64(lacptb[BOND_3AD_STAT_MARKER_RESP_TX]);
+		print_u64(PRINT_ANY,
+			  "marker_response_tx",
+			  "Marker response Tx %llu\n",
+			  val);
+	}
+	if (lacptb[BOND_3AD_STAT_MARKER_UNKNOWN_RX]) {
+		print_string(PRINT_FP, NULL, "%-16s    ", "");
+		val = rta_getattr_u64(lacptb[BOND_3AD_STAT_MARKER_UNKNOWN_RX]);
+		print_u64(PRINT_ANY,
+			  "marker_unknown_rx",
+			  "Marker unknown type Rx %llu\n",
+			  val);
+	}
+	close_json_object();
+}
+
+static void bond_print_stats_attr(struct rtattr *attr, int ifindex)
+{
+	struct rtattr *bondtb[LINK_XSTATS_TYPE_MAX+1];
+	struct rtattr *i, *list;
+	const char *ifname = "";
+	int rem;
+
+	parse_rtattr(bondtb, LINK_XSTATS_TYPE_MAX+1, RTA_DATA(attr),
+	RTA_PAYLOAD(attr));
+	if (!bondtb[LINK_XSTATS_TYPE_BOND])
+		return;
+
+	list = bondtb[LINK_XSTATS_TYPE_BOND];
+	rem = RTA_PAYLOAD(list);
+	open_json_object(NULL);
+	ifname = ll_index_to_name(ifindex);
+	print_string(PRINT_ANY, "ifname", "%-16s\n", ifname);
+	for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		if (xstats_print_attr && i->rta_type != xstats_print_attr)
+			continue;
+
+		switch (i->rta_type) {
+		case BOND_XSTATS_3AD:
+			bond_print_3ad_stats(i);
+			break;
+		}
+		break;
+	}
+	close_json_object();
+}
+
+int bond_print_xstats(struct nlmsghdr *n, void *arg)
+{
+	struct if_stats_msg *ifsm = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_STATS_MAX+1];
+	int len = n->nlmsg_len;
+
+	len -= NLMSG_LENGTH(sizeof(*ifsm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+	if (filter_index && filter_index != ifsm->ifindex)
+		return 0;
+
+	parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len);
+	if (tb[IFLA_STATS_LINK_XSTATS])
+		bond_print_stats_attr(tb[IFLA_STATS_LINK_XSTATS],
+				      ifsm->ifindex);
+
+	if (tb[IFLA_STATS_LINK_XSTATS_SLAVE])
+		bond_print_stats_attr(tb[IFLA_STATS_LINK_XSTATS_SLAVE],
+				      ifsm->ifindex);
+
+	return 0;
+}
+
+int bond_parse_xstats(struct link_util *lu, int argc, char **argv)
+{
+	while (argc > 0) {
+		if (strcmp(*argv, "lacp") == 0 ||
+		    strcmp(*argv, "802.3ad") == 0) {
+			xstats_print_attr = BOND_XSTATS_3AD;
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			filter_index = ll_name_to_index(*argv);
+			if (!filter_index)
+				return nodev(*argv);
+		} else if (strcmp(*argv, "help") == 0) {
+			bond_print_xstats_help(lu, stdout);
+			exit(0);
+		} else {
+			invarg("unknown attribute", *argv);
+		}
+		argc--; argv++;
+	}
+
+	return 0;
+}
+
+
+struct link_util bond_link_util = {
+	.id		= "bond",
+	.maxattr	= IFLA_BOND_MAX,
+	.parse_opt	= bond_parse_opt,
+	.print_opt	= bond_print_opt,
+	.print_help	= bond_print_help,
+	.parse_ifla_xstats = bond_parse_xstats,
+	.print_ifla_xstats = bond_print_xstats,
+};
diff --git a/ip/iplink_bond_slave.c b/ip/iplink_bond_slave.c
new file mode 100644
index 0000000..4eaf72b
--- /dev/null
+++ b/ip/iplink_bond_slave.c
@@ -0,0 +1,161 @@
+/*
+ * iplink_bond_slave.c	Bonding slave device support
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <linux/if_bonding.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+	fprintf(f, "Usage: ... bond_slave [ queue_id ID ]\n");
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static const char *slave_states[] = {
+	[BOND_STATE_ACTIVE] = "ACTIVE",
+	[BOND_STATE_BACKUP] = "BACKUP",
+};
+
+static void print_slave_state(FILE *f, struct rtattr *tb)
+{
+	unsigned int state = rta_getattr_u8(tb);
+
+	if (state >= ARRAY_SIZE(slave_states))
+		print_int(PRINT_ANY, "state_index", "state %d ", state);
+	else
+		print_string(PRINT_ANY,
+			     "state",
+			     "state %s ",
+			     slave_states[state]);
+}
+
+static const char *slave_mii_status[] = {
+	[BOND_LINK_UP] = "UP",
+	[BOND_LINK_FAIL] = "GOING_DOWN",
+	[BOND_LINK_DOWN] = "DOWN",
+	[BOND_LINK_BACK] = "GOING_BACK",
+};
+
+static void print_slave_mii_status(FILE *f, struct rtattr *tb)
+{
+	unsigned int status = rta_getattr_u8(tb);
+
+	if (status >= ARRAY_SIZE(slave_mii_status))
+		print_int(PRINT_ANY,
+			  "mii_status_index",
+			  "mii_status %d ",
+			  status);
+	else
+		print_string(PRINT_ANY,
+			     "mii_status",
+			     "mii_status %s ",
+			     slave_mii_status[status]);
+}
+
+static void bond_slave_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	SPRINT_BUF(b1);
+	if (!tb)
+		return;
+
+	if (tb[IFLA_BOND_SLAVE_STATE])
+		print_slave_state(f, tb[IFLA_BOND_SLAVE_STATE]);
+
+	if (tb[IFLA_BOND_SLAVE_MII_STATUS])
+		print_slave_mii_status(f, tb[IFLA_BOND_SLAVE_MII_STATUS]);
+
+	if (tb[IFLA_BOND_SLAVE_LINK_FAILURE_COUNT])
+		print_int(PRINT_ANY,
+			  "link_failure_count",
+			  "link_failure_count %d ",
+			  rta_getattr_u32(tb[IFLA_BOND_SLAVE_LINK_FAILURE_COUNT]));
+
+	if (tb[IFLA_BOND_SLAVE_PERM_HWADDR])
+		print_string(PRINT_ANY,
+			     "perm_hwaddr",
+			     "perm_hwaddr %s ",
+			     ll_addr_n2a(RTA_DATA(tb[IFLA_BOND_SLAVE_PERM_HWADDR]),
+					 RTA_PAYLOAD(tb[IFLA_BOND_SLAVE_PERM_HWADDR]),
+					 0, b1, sizeof(b1)));
+
+	if (tb[IFLA_BOND_SLAVE_QUEUE_ID])
+		print_int(PRINT_ANY,
+			  "queue_id",
+			  "queue_id %d ",
+			  rta_getattr_u16(tb[IFLA_BOND_SLAVE_QUEUE_ID]));
+
+	if (tb[IFLA_BOND_SLAVE_AD_AGGREGATOR_ID])
+		print_int(PRINT_ANY,
+			  "ad_aggregator_id",
+			  "ad_aggregator_id %d ",
+			  rta_getattr_u16(tb[IFLA_BOND_SLAVE_AD_AGGREGATOR_ID]));
+
+	if (tb[IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE])
+		print_int(PRINT_ANY,
+			  "ad_actor_oper_port_state",
+			  "ad_actor_oper_port_state %d ",
+			  rta_getattr_u8(tb[IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE]));
+
+	if (tb[IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE])
+		print_int(PRINT_ANY,
+			  "ad_partner_oper_port_state",
+			  "ad_partner_oper_port_state %d ",
+			  rta_getattr_u16(tb[IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE]));
+}
+
+static int bond_slave_parse_opt(struct link_util *lu, int argc, char **argv,
+				struct nlmsghdr *n)
+{
+	__u16 queue_id;
+
+	while (argc > 0) {
+		if (matches(*argv, "queue_id") == 0) {
+			NEXT_ARG();
+			if (get_u16(&queue_id, *argv, 0))
+				invarg("queue_id is invalid", *argv);
+			addattr16(n, 1024, IFLA_BOND_SLAVE_QUEUE_ID, queue_id);
+		} else {
+			if (matches(*argv, "help") != 0)
+				fprintf(stderr,
+					"bond_slave: unknown option \"%s\"?\n",
+					*argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void bond_slave_print_help(struct link_util *lu, int argc, char **argv,
+				  FILE *f)
+{
+	print_explain(f);
+}
+
+struct link_util bond_slave_link_util = {
+	.id		= "bond_slave",
+	.maxattr	= IFLA_BOND_SLAVE_MAX,
+	.print_opt	= bond_slave_print_opt,
+	.parse_opt	= bond_slave_parse_opt,
+	.print_help	= bond_slave_print_help,
+	.parse_ifla_xstats = bond_parse_xstats,
+	.print_ifla_xstats = bond_print_xstats,
+};
diff --git a/ip/iplink_bridge.c b/ip/iplink_bridge.c
new file mode 100644
index 0000000..06f736d
--- /dev/null
+++ b/ip/iplink_bridge.c
@@ -0,0 +1,871 @@
+/*
+ * iplink_bridge.c	Bridge device support
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <netinet/ether.h>
+#include <linux/if_link.h>
+#include <linux/if_bridge.h>
+#include <net/if.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static unsigned int xstats_print_attr;
+static int filter_index;
+
+static void print_explain(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... bridge [ fdb_flush ]\n"
+		"		  [ forward_delay FORWARD_DELAY ]\n"
+		"		  [ hello_time HELLO_TIME ]\n"
+		"		  [ max_age MAX_AGE ]\n"
+		"		  [ ageing_time AGEING_TIME ]\n"
+		"		  [ stp_state STP_STATE ]\n"
+		"		  [ priority PRIORITY ]\n"
+		"		  [ group_fwd_mask MASK ]\n"
+		"		  [ group_address ADDRESS ]\n"
+		"		  [ vlan_filtering VLAN_FILTERING ]\n"
+		"		  [ vlan_protocol VLAN_PROTOCOL ]\n"
+		"		  [ vlan_default_pvid VLAN_DEFAULT_PVID ]\n"
+		"		  [ vlan_stats_enabled VLAN_STATS_ENABLED ]\n"
+		"		  [ vlan_stats_per_port VLAN_STATS_PER_PORT ]\n"
+		"		  [ mcast_snooping MULTICAST_SNOOPING ]\n"
+		"		  [ mcast_router MULTICAST_ROUTER ]\n"
+		"		  [ mcast_query_use_ifaddr MCAST_QUERY_USE_IFADDR ]\n"
+		"		  [ mcast_querier MULTICAST_QUERIER ]\n"
+		"		  [ mcast_hash_elasticity HASH_ELASTICITY ]\n"
+		"		  [ mcast_hash_max HASH_MAX ]\n"
+		"		  [ mcast_last_member_count LAST_MEMBER_COUNT ]\n"
+		"		  [ mcast_startup_query_count STARTUP_QUERY_COUNT ]\n"
+		"		  [ mcast_last_member_interval LAST_MEMBER_INTERVAL ]\n"
+		"		  [ mcast_membership_interval MEMBERSHIP_INTERVAL ]\n"
+		"		  [ mcast_querier_interval QUERIER_INTERVAL ]\n"
+		"		  [ mcast_query_interval QUERY_INTERVAL ]\n"
+		"		  [ mcast_query_response_interval QUERY_RESPONSE_INTERVAL ]\n"
+		"		  [ mcast_startup_query_interval STARTUP_QUERY_INTERVAL ]\n"
+		"		  [ mcast_stats_enabled MCAST_STATS_ENABLED ]\n"
+		"		  [ mcast_igmp_version IGMP_VERSION ]\n"
+		"		  [ mcast_mld_version MLD_VERSION ]\n"
+		"		  [ nf_call_iptables NF_CALL_IPTABLES ]\n"
+		"		  [ nf_call_ip6tables NF_CALL_IP6TABLES ]\n"
+		"		  [ nf_call_arptables NF_CALL_ARPTABLES ]\n"
+		"\n"
+		"Where: VLAN_PROTOCOL := { 802.1Q | 802.1ad }\n"
+	);
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+void br_dump_bridge_id(const struct ifla_bridge_id *id, char *buf, size_t len)
+{
+	char eaddr[32];
+
+	ether_ntoa_r((const struct ether_addr *)id->addr, eaddr);
+	snprintf(buf, len, "%.2x%.2x.%s", id->prio[0], id->prio[1], eaddr);
+}
+
+static int bridge_parse_opt(struct link_util *lu, int argc, char **argv,
+			    struct nlmsghdr *n)
+{
+	__u32 val;
+
+	while (argc > 0) {
+		if (matches(*argv, "forward_delay") == 0) {
+			NEXT_ARG();
+			if (get_u32(&val, *argv, 0))
+				invarg("invalid forward_delay", *argv);
+
+			addattr32(n, 1024, IFLA_BR_FORWARD_DELAY, val);
+		} else if (matches(*argv, "hello_time") == 0) {
+			NEXT_ARG();
+			if (get_u32(&val, *argv, 0))
+				invarg("invalid hello_time", *argv);
+
+			addattr32(n, 1024, IFLA_BR_HELLO_TIME, val);
+		} else if (matches(*argv, "max_age") == 0) {
+			NEXT_ARG();
+			if (get_u32(&val, *argv, 0))
+				invarg("invalid max_age", *argv);
+
+			addattr32(n, 1024, IFLA_BR_MAX_AGE, val);
+		} else if (matches(*argv, "ageing_time") == 0) {
+			NEXT_ARG();
+			if (get_u32(&val, *argv, 0))
+				invarg("invalid ageing_time", *argv);
+
+			addattr32(n, 1024, IFLA_BR_AGEING_TIME, val);
+		} else if (matches(*argv, "stp_state") == 0) {
+			NEXT_ARG();
+			if (get_u32(&val, *argv, 0))
+				invarg("invalid stp_state", *argv);
+
+			addattr32(n, 1024, IFLA_BR_STP_STATE, val);
+		} else if (matches(*argv, "priority") == 0) {
+			__u16 prio;
+
+			NEXT_ARG();
+			if (get_u16(&prio, *argv, 0))
+				invarg("invalid priority", *argv);
+
+			addattr16(n, 1024, IFLA_BR_PRIORITY, prio);
+		} else if (matches(*argv, "vlan_filtering") == 0) {
+			__u8 vlan_filter;
+
+			NEXT_ARG();
+			if (get_u8(&vlan_filter, *argv, 0))
+				invarg("invalid vlan_filtering", *argv);
+
+			addattr8(n, 1024, IFLA_BR_VLAN_FILTERING, vlan_filter);
+		} else if (matches(*argv, "vlan_protocol") == 0) {
+			__u16 vlan_proto;
+
+			NEXT_ARG();
+			if (ll_proto_a2n(&vlan_proto, *argv))
+				invarg("invalid vlan_protocol", *argv);
+
+			addattr16(n, 1024, IFLA_BR_VLAN_PROTOCOL, vlan_proto);
+		} else if (matches(*argv, "group_fwd_mask") == 0) {
+			__u16 fwd_mask;
+
+			NEXT_ARG();
+			if (get_u16(&fwd_mask, *argv, 0))
+				invarg("invalid group_fwd_mask", *argv);
+
+			addattr16(n, 1024, IFLA_BR_GROUP_FWD_MASK, fwd_mask);
+		} else if (matches(*argv, "group_address") == 0) {
+			char llabuf[32];
+			int len;
+
+			NEXT_ARG();
+			len = ll_addr_a2n(llabuf, sizeof(llabuf), *argv);
+			if (len < 0)
+				return -1;
+			addattr_l(n, 1024, IFLA_BR_GROUP_ADDR, llabuf, len);
+		} else if (matches(*argv, "fdb_flush") == 0) {
+			addattr(n, 1024, IFLA_BR_FDB_FLUSH);
+		} else if (matches(*argv, "vlan_default_pvid") == 0) {
+			__u16 default_pvid;
+
+			NEXT_ARG();
+			if (get_u16(&default_pvid, *argv, 0))
+				invarg("invalid vlan_default_pvid", *argv);
+
+			addattr16(n, 1024, IFLA_BR_VLAN_DEFAULT_PVID,
+				  default_pvid);
+		} else if (matches(*argv, "vlan_stats_enabled") == 0) {
+			__u8 vlan_stats_enabled;
+
+			NEXT_ARG();
+			if (get_u8(&vlan_stats_enabled, *argv, 0))
+				invarg("invalid vlan_stats_enabled", *argv);
+			addattr8(n, 1024, IFLA_BR_VLAN_STATS_ENABLED,
+				  vlan_stats_enabled);
+		} else if (matches(*argv, "vlan_stats_per_port") == 0) {
+			__u8 vlan_stats_per_port;
+
+			NEXT_ARG();
+			if (get_u8(&vlan_stats_per_port, *argv, 0))
+				invarg("invalid vlan_stats_per_port", *argv);
+			addattr8(n, 1024, IFLA_BR_VLAN_STATS_PER_PORT,
+				 vlan_stats_per_port);
+		} else if (matches(*argv, "mcast_router") == 0) {
+			__u8 mcast_router;
+
+			NEXT_ARG();
+			if (get_u8(&mcast_router, *argv, 0))
+				invarg("invalid mcast_router", *argv);
+
+			addattr8(n, 1024, IFLA_BR_MCAST_ROUTER, mcast_router);
+		} else if (matches(*argv, "mcast_snooping") == 0) {
+			__u8 mcast_snoop;
+
+			NEXT_ARG();
+			if (get_u8(&mcast_snoop, *argv, 0))
+				invarg("invalid mcast_snooping", *argv);
+
+			addattr8(n, 1024, IFLA_BR_MCAST_SNOOPING, mcast_snoop);
+		} else if (matches(*argv, "mcast_query_use_ifaddr") == 0) {
+			__u8 mcast_qui;
+
+			NEXT_ARG();
+			if (get_u8(&mcast_qui, *argv, 0))
+				invarg("invalid mcast_query_use_ifaddr",
+				       *argv);
+
+			addattr8(n, 1024, IFLA_BR_MCAST_QUERY_USE_IFADDR,
+				 mcast_qui);
+		} else if (matches(*argv, "mcast_querier") == 0) {
+			__u8 mcast_querier;
+
+			NEXT_ARG();
+			if (get_u8(&mcast_querier, *argv, 0))
+				invarg("invalid mcast_querier", *argv);
+
+			addattr8(n, 1024, IFLA_BR_MCAST_QUERIER, mcast_querier);
+		} else if (matches(*argv, "mcast_hash_elasticity") == 0) {
+			__u32 mcast_hash_el;
+
+			NEXT_ARG();
+			if (get_u32(&mcast_hash_el, *argv, 0))
+				invarg("invalid mcast_hash_elasticity",
+				       *argv);
+
+			addattr32(n, 1024, IFLA_BR_MCAST_HASH_ELASTICITY,
+				  mcast_hash_el);
+		} else if (matches(*argv, "mcast_hash_max") == 0) {
+			__u32 mcast_hash_max;
+
+			NEXT_ARG();
+			if (get_u32(&mcast_hash_max, *argv, 0))
+				invarg("invalid mcast_hash_max", *argv);
+
+			addattr32(n, 1024, IFLA_BR_MCAST_HASH_MAX,
+				  mcast_hash_max);
+		} else if (matches(*argv, "mcast_last_member_count") == 0) {
+			__u32 mcast_lmc;
+
+			NEXT_ARG();
+			if (get_u32(&mcast_lmc, *argv, 0))
+				invarg("invalid mcast_last_member_count",
+				       *argv);
+
+			addattr32(n, 1024, IFLA_BR_MCAST_LAST_MEMBER_CNT,
+				  mcast_lmc);
+		} else if (matches(*argv, "mcast_startup_query_count") == 0) {
+			__u32 mcast_sqc;
+
+			NEXT_ARG();
+			if (get_u32(&mcast_sqc, *argv, 0))
+				invarg("invalid mcast_startup_query_count",
+				       *argv);
+
+			addattr32(n, 1024, IFLA_BR_MCAST_STARTUP_QUERY_CNT,
+				  mcast_sqc);
+		} else if (matches(*argv, "mcast_last_member_interval") == 0) {
+			__u64 mcast_last_member_intvl;
+
+			NEXT_ARG();
+			if (get_u64(&mcast_last_member_intvl, *argv, 0))
+				invarg("invalid mcast_last_member_interval",
+				       *argv);
+
+			addattr64(n, 1024, IFLA_BR_MCAST_LAST_MEMBER_INTVL,
+				  mcast_last_member_intvl);
+		} else if (matches(*argv, "mcast_membership_interval") == 0) {
+			__u64 mcast_membership_intvl;
+
+			NEXT_ARG();
+			if (get_u64(&mcast_membership_intvl, *argv, 0))
+				invarg("invalid mcast_membership_interval",
+				       *argv);
+
+			addattr64(n, 1024, IFLA_BR_MCAST_MEMBERSHIP_INTVL,
+				  mcast_membership_intvl);
+		} else if (matches(*argv, "mcast_querier_interval") == 0) {
+			__u64 mcast_querier_intvl;
+
+			NEXT_ARG();
+			if (get_u64(&mcast_querier_intvl, *argv, 0))
+				invarg("invalid mcast_querier_interval",
+				       *argv);
+
+			addattr64(n, 1024, IFLA_BR_MCAST_QUERIER_INTVL,
+				  mcast_querier_intvl);
+		} else if (matches(*argv, "mcast_query_interval") == 0) {
+			__u64 mcast_query_intvl;
+
+			NEXT_ARG();
+			if (get_u64(&mcast_query_intvl, *argv, 0))
+				invarg("invalid mcast_query_interval",
+				       *argv);
+
+			addattr64(n, 1024, IFLA_BR_MCAST_QUERY_INTVL,
+				  mcast_query_intvl);
+		} else if (!matches(*argv, "mcast_query_response_interval")) {
+			__u64 mcast_query_resp_intvl;
+
+			NEXT_ARG();
+			if (get_u64(&mcast_query_resp_intvl, *argv, 0))
+				invarg("invalid mcast_query_response_interval",
+				       *argv);
+
+			addattr64(n, 1024, IFLA_BR_MCAST_QUERY_RESPONSE_INTVL,
+				  mcast_query_resp_intvl);
+		} else if (!matches(*argv, "mcast_startup_query_interval")) {
+			__u64 mcast_startup_query_intvl;
+
+			NEXT_ARG();
+			if (get_u64(&mcast_startup_query_intvl, *argv, 0))
+				invarg("invalid mcast_startup_query_interval",
+				       *argv);
+
+			addattr64(n, 1024, IFLA_BR_MCAST_STARTUP_QUERY_INTVL,
+				  mcast_startup_query_intvl);
+		} else if (matches(*argv, "mcast_stats_enabled") == 0) {
+			__u8 mcast_stats_enabled;
+
+			NEXT_ARG();
+			if (get_u8(&mcast_stats_enabled, *argv, 0))
+				invarg("invalid mcast_stats_enabled", *argv);
+			addattr8(n, 1024, IFLA_BR_MCAST_STATS_ENABLED,
+				  mcast_stats_enabled);
+		} else if (matches(*argv, "mcast_igmp_version") == 0) {
+			__u8 igmp_version;
+
+			NEXT_ARG();
+			if (get_u8(&igmp_version, *argv, 0))
+				invarg("invalid mcast_igmp_version", *argv);
+			addattr8(n, 1024, IFLA_BR_MCAST_IGMP_VERSION,
+				  igmp_version);
+		} else if (matches(*argv, "mcast_mld_version") == 0) {
+			__u8 mld_version;
+
+			NEXT_ARG();
+			if (get_u8(&mld_version, *argv, 0))
+				invarg("invalid mcast_mld_version", *argv);
+			addattr8(n, 1024, IFLA_BR_MCAST_MLD_VERSION,
+				  mld_version);
+		} else if (matches(*argv, "nf_call_iptables") == 0) {
+			__u8 nf_call_ipt;
+
+			NEXT_ARG();
+			if (get_u8(&nf_call_ipt, *argv, 0))
+				invarg("invalid nf_call_iptables", *argv);
+
+			addattr8(n, 1024, IFLA_BR_NF_CALL_IPTABLES,
+				 nf_call_ipt);
+		} else if (matches(*argv, "nf_call_ip6tables") == 0) {
+			__u8 nf_call_ip6t;
+
+			NEXT_ARG();
+			if (get_u8(&nf_call_ip6t, *argv, 0))
+				invarg("invalid nf_call_ip6tables", *argv);
+
+			addattr8(n, 1024, IFLA_BR_NF_CALL_IP6TABLES,
+				 nf_call_ip6t);
+		} else if (matches(*argv, "nf_call_arptables") == 0) {
+			__u8 nf_call_arpt;
+
+			NEXT_ARG();
+			if (get_u8(&nf_call_arpt, *argv, 0))
+				invarg("invalid nf_call_arptables", *argv);
+
+			addattr8(n, 1024, IFLA_BR_NF_CALL_ARPTABLES,
+				 nf_call_arpt);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "bridge: unknown command \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void _bridge_print_timer(FILE *f,
+				const char *attr,
+				struct rtattr *timer)
+{
+	struct timeval tv;
+
+	__jiffies_to_tv(&tv, rta_getattr_u64(timer));
+	if (is_json_context()) {
+		json_writer_t *jw = get_json_writer();
+
+		jsonw_name(jw, attr);
+		jsonw_printf(jw, "%i.%.2i",
+			     (int)tv.tv_sec,
+			     (int)tv.tv_usec / 10000);
+	} else {
+		fprintf(f, "%s %4i.%.2i ", attr, (int)tv.tv_sec,
+			(int)tv.tv_usec / 10000);
+	}
+}
+
+static void bridge_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_BR_FORWARD_DELAY])
+		print_uint(PRINT_ANY,
+			   "forward_delay",
+			   "forward_delay %u ",
+			   rta_getattr_u32(tb[IFLA_BR_FORWARD_DELAY]));
+
+	if (tb[IFLA_BR_HELLO_TIME])
+		print_uint(PRINT_ANY,
+			   "hello_time",
+			   "hello_time %u ",
+			   rta_getattr_u32(tb[IFLA_BR_HELLO_TIME]));
+
+	if (tb[IFLA_BR_MAX_AGE])
+		print_uint(PRINT_ANY,
+			   "max_age",
+			   "max_age %u ",
+			   rta_getattr_u32(tb[IFLA_BR_MAX_AGE]));
+
+	if (tb[IFLA_BR_AGEING_TIME])
+		print_uint(PRINT_ANY,
+			   "ageing_time",
+			   "ageing_time %u ",
+			   rta_getattr_u32(tb[IFLA_BR_AGEING_TIME]));
+
+	if (tb[IFLA_BR_STP_STATE])
+		print_uint(PRINT_ANY,
+			   "stp_state",
+			   "stp_state %u ",
+			   rta_getattr_u32(tb[IFLA_BR_STP_STATE]));
+
+	if (tb[IFLA_BR_PRIORITY])
+		print_uint(PRINT_ANY,
+			   "priority",
+			   "priority %u ",
+			   rta_getattr_u16(tb[IFLA_BR_PRIORITY]));
+
+	if (tb[IFLA_BR_VLAN_FILTERING])
+		print_uint(PRINT_ANY,
+			   "vlan_filtering",
+			   "vlan_filtering %u ",
+			   rta_getattr_u8(tb[IFLA_BR_VLAN_FILTERING]));
+
+	if (tb[IFLA_BR_VLAN_PROTOCOL]) {
+		SPRINT_BUF(b1);
+
+		print_string(PRINT_ANY,
+			     "vlan_protocol",
+			     "vlan_protocol %s ",
+			     ll_proto_n2a(rta_getattr_u16(tb[IFLA_BR_VLAN_PROTOCOL]),
+					  b1, sizeof(b1)));
+	}
+
+	if (tb[IFLA_BR_BRIDGE_ID]) {
+		char bridge_id[32];
+
+		br_dump_bridge_id(RTA_DATA(tb[IFLA_BR_BRIDGE_ID]), bridge_id,
+				  sizeof(bridge_id));
+		print_string(PRINT_ANY,
+			     "bridge_id",
+			     "bridge_id %s ",
+			     bridge_id);
+	}
+
+	if (tb[IFLA_BR_ROOT_ID]) {
+		char root_id[32];
+
+		br_dump_bridge_id(RTA_DATA(tb[IFLA_BR_BRIDGE_ID]), root_id,
+				  sizeof(root_id));
+		print_string(PRINT_ANY,
+			     "root_id",
+			     "designated_root %s ",
+			     root_id);
+	}
+
+	if (tb[IFLA_BR_ROOT_PORT])
+		print_uint(PRINT_ANY,
+			   "root_port",
+			   "root_port %u ",
+			   rta_getattr_u16(tb[IFLA_BR_ROOT_PORT]));
+
+	if (tb[IFLA_BR_ROOT_PATH_COST])
+		print_uint(PRINT_ANY,
+			   "root_path_cost",
+			   "root_path_cost %u ",
+			   rta_getattr_u32(tb[IFLA_BR_ROOT_PATH_COST]));
+
+	if (tb[IFLA_BR_TOPOLOGY_CHANGE])
+		print_uint(PRINT_ANY,
+			   "topology_change",
+			   "topology_change %u ",
+			   rta_getattr_u8(tb[IFLA_BR_TOPOLOGY_CHANGE]));
+
+	if (tb[IFLA_BR_TOPOLOGY_CHANGE_DETECTED])
+		print_uint(PRINT_ANY,
+			   "topology_change_detected",
+			   "topology_change_detected %u ",
+			   rta_getattr_u8(tb[IFLA_BR_TOPOLOGY_CHANGE_DETECTED]));
+
+	if (tb[IFLA_BR_HELLO_TIMER])
+		_bridge_print_timer(f, "hello_timer", tb[IFLA_BR_HELLO_TIMER]);
+
+	if (tb[IFLA_BR_TCN_TIMER])
+		_bridge_print_timer(f, "tcn_timer", tb[IFLA_BR_TCN_TIMER]);
+
+	if (tb[IFLA_BR_TOPOLOGY_CHANGE_TIMER])
+		_bridge_print_timer(f, "topology_change_timer",
+				    tb[IFLA_BR_TOPOLOGY_CHANGE_TIMER]);
+
+	if (tb[IFLA_BR_GC_TIMER])
+		_bridge_print_timer(f, "gc_timer", tb[IFLA_BR_GC_TIMER]);
+
+	if (tb[IFLA_BR_VLAN_DEFAULT_PVID])
+		print_uint(PRINT_ANY,
+			   "vlan_default_pvid",
+			   "vlan_default_pvid %u ",
+			   rta_getattr_u16(tb[IFLA_BR_VLAN_DEFAULT_PVID]));
+
+	if (tb[IFLA_BR_VLAN_STATS_ENABLED])
+		print_uint(PRINT_ANY,
+			   "vlan_stats_enabled",
+			   "vlan_stats_enabled %u ",
+			   rta_getattr_u8(tb[IFLA_BR_VLAN_STATS_ENABLED]));
+
+	if (tb[IFLA_BR_VLAN_STATS_PER_PORT])
+		print_uint(PRINT_ANY,
+			   "vlan_stats_per_port",
+			   "vlan_stats_per_port %u ",
+			   rta_getattr_u8(tb[IFLA_BR_VLAN_STATS_PER_PORT]));
+
+	if (tb[IFLA_BR_GROUP_FWD_MASK])
+		print_0xhex(PRINT_ANY,
+			    "group_fwd_mask",
+			    "group_fwd_mask %#llx ",
+			    rta_getattr_u16(tb[IFLA_BR_GROUP_FWD_MASK]));
+
+	if (tb[IFLA_BR_GROUP_ADDR]) {
+		SPRINT_BUF(mac);
+
+		print_string(PRINT_ANY,
+			     "group_addr",
+			     "group_address %s ",
+			     ll_addr_n2a(RTA_DATA(tb[IFLA_BR_GROUP_ADDR]),
+					 RTA_PAYLOAD(tb[IFLA_BR_GROUP_ADDR]),
+					 1 /*ARPHDR_ETHER*/, mac, sizeof(mac)));
+	}
+
+	if (tb[IFLA_BR_MCAST_SNOOPING])
+		print_uint(PRINT_ANY,
+			   "mcast_snooping",
+			   "mcast_snooping %u ",
+			   rta_getattr_u8(tb[IFLA_BR_MCAST_SNOOPING]));
+
+	if (tb[IFLA_BR_MCAST_ROUTER])
+		print_uint(PRINT_ANY,
+			   "mcast_router",
+			   "mcast_router %u ",
+			   rta_getattr_u8(tb[IFLA_BR_MCAST_ROUTER]));
+
+	if (tb[IFLA_BR_MCAST_QUERY_USE_IFADDR])
+		print_uint(PRINT_ANY,
+			   "mcast_query_use_ifaddr",
+			   "mcast_query_use_ifaddr %u ",
+			   rta_getattr_u8(tb[IFLA_BR_MCAST_QUERY_USE_IFADDR]));
+
+	if (tb[IFLA_BR_MCAST_QUERIER])
+		print_uint(PRINT_ANY,
+			   "mcast_querier",
+			   "mcast_querier %u ",
+			   rta_getattr_u8(tb[IFLA_BR_MCAST_QUERIER]));
+
+	if (tb[IFLA_BR_MCAST_HASH_ELASTICITY])
+		print_uint(PRINT_ANY,
+			   "mcast_hash_elasticity",
+			   "mcast_hash_elasticity %u ",
+			   rta_getattr_u32(tb[IFLA_BR_MCAST_HASH_ELASTICITY]));
+
+	if (tb[IFLA_BR_MCAST_HASH_MAX])
+		print_uint(PRINT_ANY,
+			   "mcast_hash_max",
+			   "mcast_hash_max %u ",
+			   rta_getattr_u32(tb[IFLA_BR_MCAST_HASH_MAX]));
+
+	if (tb[IFLA_BR_MCAST_LAST_MEMBER_CNT])
+		print_uint(PRINT_ANY,
+			   "mcast_last_member_cnt",
+			   "mcast_last_member_count %u ",
+			   rta_getattr_u32(tb[IFLA_BR_MCAST_LAST_MEMBER_CNT]));
+
+	if (tb[IFLA_BR_MCAST_STARTUP_QUERY_CNT])
+		print_uint(PRINT_ANY,
+			   "mcast_startup_query_cnt",
+			   "mcast_startup_query_count %u ",
+			   rta_getattr_u32(tb[IFLA_BR_MCAST_STARTUP_QUERY_CNT]));
+
+	if (tb[IFLA_BR_MCAST_LAST_MEMBER_INTVL])
+		print_lluint(PRINT_ANY,
+			     "mcast_last_member_intvl",
+			     "mcast_last_member_interval %llu ",
+			     rta_getattr_u64(tb[IFLA_BR_MCAST_LAST_MEMBER_INTVL]));
+
+	if (tb[IFLA_BR_MCAST_MEMBERSHIP_INTVL])
+		print_lluint(PRINT_ANY,
+			     "mcast_membership_intvl",
+			     "mcast_membership_interval %llu ",
+			     rta_getattr_u64(tb[IFLA_BR_MCAST_MEMBERSHIP_INTVL]));
+
+	if (tb[IFLA_BR_MCAST_QUERIER_INTVL])
+		print_lluint(PRINT_ANY,
+			     "mcast_querier_intvl",
+			     "mcast_querier_interval %llu ",
+			     rta_getattr_u64(tb[IFLA_BR_MCAST_QUERIER_INTVL]));
+
+	if (tb[IFLA_BR_MCAST_QUERY_INTVL])
+		print_lluint(PRINT_ANY,
+			     "mcast_query_intvl",
+			     "mcast_query_interval %llu ",
+			     rta_getattr_u64(tb[IFLA_BR_MCAST_QUERY_INTVL]));
+
+	if (tb[IFLA_BR_MCAST_QUERY_RESPONSE_INTVL])
+		print_lluint(PRINT_ANY,
+			     "mcast_query_response_intvl",
+			     "mcast_query_response_interval %llu ",
+			     rta_getattr_u64(tb[IFLA_BR_MCAST_QUERY_RESPONSE_INTVL]));
+
+	if (tb[IFLA_BR_MCAST_STARTUP_QUERY_INTVL])
+		print_lluint(PRINT_ANY,
+			     "mcast_startup_query_intvl",
+			     "mcast_startup_query_interval %llu ",
+			     rta_getattr_u64(tb[IFLA_BR_MCAST_STARTUP_QUERY_INTVL]));
+
+	if (tb[IFLA_BR_MCAST_STATS_ENABLED])
+		print_uint(PRINT_ANY,
+			   "mcast_stats_enabled",
+			   "mcast_stats_enabled %u ",
+			   rta_getattr_u8(tb[IFLA_BR_MCAST_STATS_ENABLED]));
+
+	if (tb[IFLA_BR_MCAST_IGMP_VERSION])
+		print_uint(PRINT_ANY,
+			   "mcast_igmp_version",
+			   "mcast_igmp_version %u ",
+			   rta_getattr_u8(tb[IFLA_BR_MCAST_IGMP_VERSION]));
+
+	if (tb[IFLA_BR_MCAST_MLD_VERSION])
+		print_uint(PRINT_ANY,
+			   "mcast_mld_version",
+			   "mcast_mld_version %u ",
+			   rta_getattr_u8(tb[IFLA_BR_MCAST_MLD_VERSION]));
+
+	if (tb[IFLA_BR_NF_CALL_IPTABLES])
+		print_uint(PRINT_ANY,
+			   "nf_call_iptables",
+			   "nf_call_iptables %u ",
+			   rta_getattr_u8(tb[IFLA_BR_NF_CALL_IPTABLES]));
+
+	if (tb[IFLA_BR_NF_CALL_IP6TABLES])
+		print_uint(PRINT_ANY,
+			   "nf_call_ip6tables",
+			   "nf_call_ip6tables %u ",
+			   rta_getattr_u8(tb[IFLA_BR_NF_CALL_IP6TABLES]));
+
+	if (tb[IFLA_BR_NF_CALL_ARPTABLES])
+		print_uint(PRINT_ANY,
+			   "nf_call_arptables",
+			   "nf_call_arptables %u ",
+			   rta_getattr_u8(tb[IFLA_BR_NF_CALL_ARPTABLES]));
+}
+
+static void bridge_print_help(struct link_util *lu, int argc, char **argv,
+			      FILE *f)
+{
+	print_explain(f);
+}
+
+static void bridge_print_xstats_help(struct link_util *lu, FILE *f)
+{
+	fprintf(f, "Usage: ... %s [ igmp ] [ dev DEVICE ]\n", lu->id);
+}
+
+static void bridge_print_stats_attr(struct rtattr *attr, int ifindex)
+{
+	struct rtattr *brtb[LINK_XSTATS_TYPE_MAX+1];
+	struct br_mcast_stats *mstats;
+	struct rtattr *i, *list;
+	const char *ifname = "";
+	int rem;
+
+	parse_rtattr(brtb, LINK_XSTATS_TYPE_MAX, RTA_DATA(attr),
+	RTA_PAYLOAD(attr));
+	if (!brtb[LINK_XSTATS_TYPE_BRIDGE])
+		return;
+
+	list = brtb[LINK_XSTATS_TYPE_BRIDGE];
+	rem = RTA_PAYLOAD(list);
+	open_json_object(NULL);
+	ifname = ll_index_to_name(ifindex);
+	print_string(PRINT_ANY, "ifname", "%-16s\n", ifname);
+	for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		if (xstats_print_attr && i->rta_type != xstats_print_attr)
+			continue;
+		switch (i->rta_type) {
+		case BRIDGE_XSTATS_MCAST:
+			mstats = RTA_DATA(i);
+			open_json_object("multicast");
+			open_json_object("igmp_queries");
+			print_string(PRINT_FP, NULL,
+				     "%-16s    IGMP queries:\n", "");
+			print_string(PRINT_FP, NULL, "%-16s      ", "");
+			print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ",
+				  mstats->igmp_v1queries[BR_MCAST_DIR_RX]);
+			print_u64(PRINT_ANY, "rx_v2", "v2 %llu ",
+				  mstats->igmp_v2queries[BR_MCAST_DIR_RX]);
+			print_u64(PRINT_ANY, "rx_v3", "v3 %llu\n",
+				  mstats->igmp_v3queries[BR_MCAST_DIR_RX]);
+			print_string(PRINT_FP, NULL, "%-16s      ", "");
+			print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ",
+				  mstats->igmp_v1queries[BR_MCAST_DIR_TX]);
+			print_u64(PRINT_ANY, "tx_v2", "v2 %llu ",
+				  mstats->igmp_v2queries[BR_MCAST_DIR_TX]);
+			print_u64(PRINT_ANY, "tx_v3", "v3 %llu\n",
+				  mstats->igmp_v3queries[BR_MCAST_DIR_TX]);
+			close_json_object();
+
+			open_json_object("igmp_reports");
+			print_string(PRINT_FP, NULL,
+				     "%-16s    IGMP reports:\n", "");
+			print_string(PRINT_FP, NULL, "%-16s      ", "");
+			print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ",
+				  mstats->igmp_v1reports[BR_MCAST_DIR_RX]);
+			print_u64(PRINT_ANY, "rx_v2", "v2 %llu ",
+				  mstats->igmp_v2reports[BR_MCAST_DIR_RX]);
+			print_u64(PRINT_ANY, "rx_v3", "v3 %llu\n",
+				  mstats->igmp_v3reports[BR_MCAST_DIR_RX]);
+			print_string(PRINT_FP, NULL, "%-16s      ", "");
+			print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ",
+				  mstats->igmp_v1reports[BR_MCAST_DIR_TX]);
+			print_u64(PRINT_ANY, "tx_v2", "v2 %llu",
+				  mstats->igmp_v2reports[BR_MCAST_DIR_TX]);
+			print_u64(PRINT_ANY, "tx_v3", "v3 %llu\n",
+				  mstats->igmp_v3reports[BR_MCAST_DIR_TX]);
+			close_json_object();
+
+			open_json_object("igmp_leaves");
+			print_string(PRINT_FP, NULL,
+				     "%-16s    IGMP leaves: ", "");
+			print_u64(PRINT_ANY, "rx", "RX: %llu ",
+				  mstats->igmp_leaves[BR_MCAST_DIR_RX]);
+			print_u64(PRINT_ANY, "tx", "TX: %llu\n",
+				  mstats->igmp_leaves[BR_MCAST_DIR_TX]);
+			close_json_object();
+
+			print_string(PRINT_FP, NULL,
+				     "%-16s    IGMP parse errors: ", "");
+			print_u64(PRINT_ANY, "igmp_parse_errors", "%llu\n",
+				  mstats->igmp_parse_errors);
+
+			open_json_object("mld_queries");
+			print_string(PRINT_FP, NULL,
+				     "%-16s    MLD queries:\n", "");
+			print_string(PRINT_FP, NULL, "%-16s      ", "");
+			print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ",
+				  mstats->mld_v1queries[BR_MCAST_DIR_RX]);
+			print_u64(PRINT_ANY, "rx_v2", "v2 %llu\n",
+				  mstats->mld_v2queries[BR_MCAST_DIR_RX]);
+			print_string(PRINT_FP, NULL, "%-16s      ", "");
+			print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ",
+				  mstats->mld_v1queries[BR_MCAST_DIR_TX]);
+			print_u64(PRINT_ANY, "tx_v2", "v2 %llu\n",
+				  mstats->mld_v2queries[BR_MCAST_DIR_TX]);
+			close_json_object();
+
+			open_json_object("mld_reports");
+			print_string(PRINT_FP, NULL,
+				     "%-16s    MLD reports:\n", "");
+			print_string(PRINT_FP, NULL, "%-16s      ", "");
+			print_u64(PRINT_ANY, "rx_v1", "RX: v1 %llu ",
+				  mstats->mld_v1reports[BR_MCAST_DIR_RX]);
+			print_u64(PRINT_ANY, "rx_v2", "v2 %llu\n",
+				  mstats->mld_v2reports[BR_MCAST_DIR_RX]);
+			print_string(PRINT_FP, NULL, "%-16s      ", "");
+			print_u64(PRINT_ANY, "tx_v1", "TX: v1 %llu ",
+				  mstats->mld_v1reports[BR_MCAST_DIR_TX]);
+			print_u64(PRINT_ANY, "tx_v2", "v2 %llu\n",
+				  mstats->mld_v2reports[BR_MCAST_DIR_TX]);
+			close_json_object();
+
+			open_json_object("mld_leaves");
+			print_string(PRINT_FP, NULL,
+				     "%-16s    MLD leaves: ", "");
+			print_u64(PRINT_ANY, "rx", "RX: %llu ",
+				  mstats->mld_leaves[BR_MCAST_DIR_RX]);
+			print_u64(PRINT_ANY, "tx", "TX: %llu\n",
+				  mstats->mld_leaves[BR_MCAST_DIR_TX]);
+			close_json_object();
+
+			print_string(PRINT_FP, NULL,
+				     "%-16s    MLD parse errors: ", "");
+			print_u64(PRINT_ANY, "mld_parse_errors", "%llu\n",
+				  mstats->mld_parse_errors);
+			close_json_object();
+			break;
+		}
+	}
+	close_json_object();
+}
+
+int bridge_print_xstats(struct nlmsghdr *n, void *arg)
+{
+	struct if_stats_msg *ifsm = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_STATS_MAX+1];
+	int len = n->nlmsg_len;
+
+	len -= NLMSG_LENGTH(sizeof(*ifsm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+	if (filter_index && filter_index != ifsm->ifindex)
+		return 0;
+
+	parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len);
+	if (tb[IFLA_STATS_LINK_XSTATS])
+		bridge_print_stats_attr(tb[IFLA_STATS_LINK_XSTATS],
+					ifsm->ifindex);
+
+	if (tb[IFLA_STATS_LINK_XSTATS_SLAVE])
+		bridge_print_stats_attr(tb[IFLA_STATS_LINK_XSTATS_SLAVE],
+					ifsm->ifindex);
+
+	return 0;
+}
+
+int bridge_parse_xstats(struct link_util *lu, int argc, char **argv)
+{
+	while (argc > 0) {
+		if (strcmp(*argv, "igmp") == 0 || strcmp(*argv, "mcast") == 0) {
+			xstats_print_attr = BRIDGE_XSTATS_MCAST;
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			filter_index = ll_name_to_index(*argv);
+			if (!filter_index)
+				return nodev(*argv);
+		} else if (strcmp(*argv, "help") == 0) {
+			bridge_print_xstats_help(lu, stdout);
+			exit(0);
+		} else {
+			invarg("unknown attribute", *argv);
+		}
+		argc--; argv++;
+	}
+
+	return 0;
+}
+
+struct link_util bridge_link_util = {
+	.id		= "bridge",
+	.maxattr	= IFLA_BR_MAX,
+	.parse_opt	= bridge_parse_opt,
+	.print_opt	= bridge_print_opt,
+	.print_help     = bridge_print_help,
+	.parse_ifla_xstats = bridge_parse_xstats,
+	.print_ifla_xstats = bridge_print_xstats,
+};
diff --git a/ip/iplink_bridge_slave.c b/ip/iplink_bridge_slave.c
new file mode 100644
index 0000000..79a1d2f
--- /dev/null
+++ b/ip/iplink_bridge_slave.c
@@ -0,0 +1,447 @@
+/*
+ * iplink_bridge_slave.c	Bridge slave device support
+ *
+ *              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.
+ *
+ * Authors:     Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <linux/if_link.h>
+#include <linux/if_bridge.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... bridge_slave [ fdb_flush ]\n"
+		"			[ state STATE ]\n"
+		"			[ priority PRIO ]\n"
+		"			[ cost COST ]\n"
+		"			[ guard {on | off} ]\n"
+		"			[ hairpin {on | off} ]\n"
+		"			[ fastleave {on | off} ]\n"
+		"			[ root_block {on | off} ]\n"
+		"			[ learning {on | off} ]\n"
+		"			[ flood {on | off} ]\n"
+		"			[ proxy_arp {on | off} ]\n"
+		"			[ proxy_arp_wifi {on | off} ]\n"
+		"			[ mcast_router MULTICAST_ROUTER ]\n"
+		"			[ mcast_fast_leave {on | off} ]\n"
+		"			[ mcast_flood {on | off} ]\n"
+		"			[ mcast_to_unicast {on | off} ]\n"
+		"			[ group_fwd_mask MASK ]\n"
+		"			[ neigh_suppress {on | off} ]\n"
+		"			[ vlan_tunnel {on | off} ]\n"
+		"			[ isolated {on | off} ]\n"
+		"			[ backup_port DEVICE ] [ nobackup_port ]\n"
+	);
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static const char *port_states[] = {
+	[BR_STATE_DISABLED] = "disabled",
+	[BR_STATE_LISTENING] = "listening",
+	[BR_STATE_LEARNING] = "learning",
+	[BR_STATE_FORWARDING] = "forwarding",
+	[BR_STATE_BLOCKING] = "blocking",
+};
+
+static const char *fwd_mask_tbl[16] = {
+	[0]	= "stp",
+	[2]	= "lacp",
+	[14]	= "lldp"
+};
+
+static void print_portstate(FILE *f, __u8 state)
+{
+	if (state <= BR_STATE_BLOCKING)
+		print_string(PRINT_ANY,
+			     "state",
+			     "state %s ",
+			     port_states[state]);
+	else
+		print_int(PRINT_ANY, "state_index", "state (%d) ", state);
+}
+
+static void _print_onoff(FILE *f, char *json_flag, char *flag, __u8 val)
+{
+	if (is_json_context())
+		print_bool(PRINT_JSON, flag, NULL, val);
+	else
+		fprintf(f, "%s %s ", flag, val ? "on" : "off");
+}
+
+static void _print_timer(FILE *f, const char *attr, struct rtattr *timer)
+{
+	struct timeval tv;
+
+	__jiffies_to_tv(&tv, rta_getattr_u64(timer));
+	if (is_json_context()) {
+		json_writer_t *jw = get_json_writer();
+
+		jsonw_name(jw, attr);
+		jsonw_printf(jw, "%i.%.2i",
+			     (int)tv.tv_sec, (int)tv.tv_usec / 10000);
+	} else {
+		fprintf(f, "%s %4i.%.2i ", attr, (int)tv.tv_sec,
+			(int)tv.tv_usec / 10000);
+	}
+}
+
+static void _bitmask2str(__u16 bitmask, char *dst, size_t dst_size,
+			 const char **tbl)
+{
+	int len, i;
+
+	for (i = 0, len = 0; bitmask; i++, bitmask >>= 1) {
+		if (bitmask & 0x1) {
+			if (tbl[i])
+				len += snprintf(dst + len, dst_size - len, "%s,",
+						tbl[i]);
+			else
+				len += snprintf(dst + len, dst_size - len, "0x%x,",
+						(1 << i));
+		}
+	}
+
+	if (!len)
+		snprintf(dst, dst_size, "0x0");
+	else
+		dst[len - 1] = 0;
+}
+
+static void bridge_slave_print_opt(struct link_util *lu, FILE *f,
+				   struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_BRPORT_STATE])
+		print_portstate(f, rta_getattr_u8(tb[IFLA_BRPORT_STATE]));
+
+	if (tb[IFLA_BRPORT_PRIORITY])
+		print_int(PRINT_ANY,
+			  "priority",
+			  "priority %d ",
+			  rta_getattr_u16(tb[IFLA_BRPORT_PRIORITY]));
+
+	if (tb[IFLA_BRPORT_COST])
+		print_int(PRINT_ANY,
+			  "cost",
+			  "cost %d ",
+			  rta_getattr_u32(tb[IFLA_BRPORT_COST]));
+
+	if (tb[IFLA_BRPORT_MODE])
+		_print_onoff(f, "mode", "hairpin",
+			     rta_getattr_u8(tb[IFLA_BRPORT_MODE]));
+
+	if (tb[IFLA_BRPORT_GUARD])
+		_print_onoff(f, "guard", "guard",
+			     rta_getattr_u8(tb[IFLA_BRPORT_GUARD]));
+
+	if (tb[IFLA_BRPORT_PROTECT])
+		_print_onoff(f, "protect", "root_block",
+			     rta_getattr_u8(tb[IFLA_BRPORT_PROTECT]));
+
+	if (tb[IFLA_BRPORT_FAST_LEAVE])
+		_print_onoff(f, "fast_leave", "fastleave",
+			     rta_getattr_u8(tb[IFLA_BRPORT_FAST_LEAVE]));
+
+	if (tb[IFLA_BRPORT_LEARNING])
+		_print_onoff(f, "learning", "learning",
+			     rta_getattr_u8(tb[IFLA_BRPORT_LEARNING]));
+
+	if (tb[IFLA_BRPORT_UNICAST_FLOOD])
+		_print_onoff(f, "unicast_flood", "flood",
+			     rta_getattr_u8(tb[IFLA_BRPORT_UNICAST_FLOOD]));
+
+	if (tb[IFLA_BRPORT_ID])
+		print_0xhex(PRINT_ANY, "id", "port_id %#llx ",
+			    rta_getattr_u16(tb[IFLA_BRPORT_ID]));
+
+	if (tb[IFLA_BRPORT_NO])
+		print_0xhex(PRINT_ANY, "no", "port_no %#llx ",
+			   rta_getattr_u16(tb[IFLA_BRPORT_NO]));
+
+	if (tb[IFLA_BRPORT_DESIGNATED_PORT])
+		print_uint(PRINT_ANY,
+			   "designated_port",
+			   "designated_port %u ",
+			   rta_getattr_u16(tb[IFLA_BRPORT_DESIGNATED_PORT]));
+
+	if (tb[IFLA_BRPORT_DESIGNATED_COST])
+		print_uint(PRINT_ANY,
+			   "designated_cost",
+			   "designated_cost %u ",
+			   rta_getattr_u16(tb[IFLA_BRPORT_DESIGNATED_COST]));
+
+	if (tb[IFLA_BRPORT_BRIDGE_ID]) {
+		char bridge_id[32];
+
+		br_dump_bridge_id(RTA_DATA(tb[IFLA_BRPORT_BRIDGE_ID]),
+				  bridge_id, sizeof(bridge_id));
+		print_string(PRINT_ANY,
+			     "bridge_id",
+			     "designated_bridge %s ",
+			     bridge_id);
+	}
+
+	if (tb[IFLA_BRPORT_ROOT_ID]) {
+		char root_id[32];
+
+		br_dump_bridge_id(RTA_DATA(tb[IFLA_BRPORT_ROOT_ID]),
+				  root_id, sizeof(root_id));
+		print_string(PRINT_ANY,
+			     "root_id",
+			     "designated_root %s ", root_id);
+	}
+
+	if (tb[IFLA_BRPORT_HOLD_TIMER])
+		_print_timer(f, "hold_timer", tb[IFLA_BRPORT_HOLD_TIMER]);
+
+	if (tb[IFLA_BRPORT_MESSAGE_AGE_TIMER])
+		_print_timer(f, "message_age_timer",
+			     tb[IFLA_BRPORT_MESSAGE_AGE_TIMER]);
+
+	if (tb[IFLA_BRPORT_FORWARD_DELAY_TIMER])
+		_print_timer(f, "forward_delay_timer",
+			     tb[IFLA_BRPORT_FORWARD_DELAY_TIMER]);
+
+	if (tb[IFLA_BRPORT_TOPOLOGY_CHANGE_ACK])
+		print_uint(PRINT_ANY,
+			   "topology_change_ack",
+			   "topology_change_ack %u ",
+			   rta_getattr_u8(tb[IFLA_BRPORT_TOPOLOGY_CHANGE_ACK]));
+
+	if (tb[IFLA_BRPORT_CONFIG_PENDING])
+		print_uint(PRINT_ANY,
+			   "config_pending",
+			   "config_pending %u ",
+			   rta_getattr_u8(tb[IFLA_BRPORT_CONFIG_PENDING]));
+
+	if (tb[IFLA_BRPORT_PROXYARP])
+		_print_onoff(f, "proxyarp", "proxy_arp",
+			     rta_getattr_u8(tb[IFLA_BRPORT_PROXYARP]));
+
+	if (tb[IFLA_BRPORT_PROXYARP_WIFI])
+		_print_onoff(f, "proxyarp_wifi", "proxy_arp_wifi",
+			     rta_getattr_u8(tb[IFLA_BRPORT_PROXYARP_WIFI]));
+
+	if (tb[IFLA_BRPORT_MULTICAST_ROUTER])
+		print_uint(PRINT_ANY,
+			   "multicast_router",
+			   "mcast_router %u ",
+			   rta_getattr_u8(tb[IFLA_BRPORT_MULTICAST_ROUTER]));
+
+	if (tb[IFLA_BRPORT_FAST_LEAVE])
+		// not printing any json here because
+		// we already printed fast_leave before
+		print_string(PRINT_FP,
+			     NULL,
+			     "mcast_fast_leave %s ",
+			     rta_getattr_u8(tb[IFLA_BRPORT_FAST_LEAVE]) ? "on" : "off");
+
+	if (tb[IFLA_BRPORT_MCAST_FLOOD])
+		_print_onoff(f, "mcast_flood", "mcast_flood",
+			     rta_getattr_u8(tb[IFLA_BRPORT_MCAST_FLOOD]));
+
+	if (tb[IFLA_BRPORT_MCAST_TO_UCAST])
+		_print_onoff(f, "mcast_to_unicast", "mcast_to_unicast",
+			     rta_getattr_u8(tb[IFLA_BRPORT_MCAST_TO_UCAST]));
+
+	if (tb[IFLA_BRPORT_NEIGH_SUPPRESS])
+		_print_onoff(f, "neigh_suppress", "neigh_suppress",
+			     rta_getattr_u8(tb[IFLA_BRPORT_NEIGH_SUPPRESS]));
+
+	if (tb[IFLA_BRPORT_GROUP_FWD_MASK]) {
+		char convbuf[256];
+		__u16 fwd_mask;
+
+		fwd_mask = rta_getattr_u16(tb[IFLA_BRPORT_GROUP_FWD_MASK]);
+		print_0xhex(PRINT_ANY, "group_fwd_mask",
+			    "group_fwd_mask %#llx ", fwd_mask);
+		_bitmask2str(fwd_mask, convbuf, sizeof(convbuf), fwd_mask_tbl);
+		print_string(PRINT_ANY, "group_fwd_mask_str",
+			     "group_fwd_mask_str %s ", convbuf);
+	}
+
+	if (tb[IFLA_BRPORT_VLAN_TUNNEL])
+		_print_onoff(f, "vlan_tunnel", "vlan_tunnel",
+			     rta_getattr_u8(tb[IFLA_BRPORT_VLAN_TUNNEL]));
+
+	if (tb[IFLA_BRPORT_ISOLATED])
+		_print_onoff(f, "isolated", "isolated",
+			     rta_getattr_u8(tb[IFLA_BRPORT_ISOLATED]));
+
+	if (tb[IFLA_BRPORT_BACKUP_PORT]) {
+		int backup_p = rta_getattr_u32(tb[IFLA_BRPORT_BACKUP_PORT]);
+
+		print_string(PRINT_ANY, "backup_port", "backup_port %s ",
+			     ll_index_to_name(backup_p));
+	}
+}
+
+static void bridge_slave_parse_on_off(char *arg_name, char *arg_val,
+				      struct nlmsghdr *n, int type)
+{
+	__u8 val;
+
+	if (strcmp(arg_val, "on") == 0)
+		val = 1;
+	else if (strcmp(arg_val, "off") == 0)
+		val = 0;
+	else
+		invarg("should be \"on\" or \"off\"", arg_name);
+
+	addattr8(n, 1024, type, val);
+}
+
+static int bridge_slave_parse_opt(struct link_util *lu, int argc, char **argv,
+				  struct nlmsghdr *n)
+{
+	__u8 state;
+	__u16 priority;
+	__u32 cost;
+
+	while (argc > 0) {
+		if (matches(*argv, "fdb_flush") == 0) {
+			addattr(n, 1024, IFLA_BRPORT_FLUSH);
+		} else if (matches(*argv, "state") == 0) {
+			NEXT_ARG();
+			if (get_u8(&state, *argv, 0))
+				invarg("state is invalid", *argv);
+			addattr8(n, 1024, IFLA_BRPORT_STATE, state);
+		} else if (matches(*argv, "priority") == 0) {
+			NEXT_ARG();
+			if (get_u16(&priority, *argv, 0))
+				invarg("priority is invalid", *argv);
+			addattr16(n, 1024, IFLA_BRPORT_PRIORITY, priority);
+		} else if (matches(*argv, "cost") == 0) {
+			NEXT_ARG();
+			if (get_u32(&cost, *argv, 0))
+				invarg("cost is invalid", *argv);
+			addattr32(n, 1024, IFLA_BRPORT_COST, cost);
+		} else if (matches(*argv, "hairpin") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("hairpin", *argv, n,
+						  IFLA_BRPORT_MODE);
+		} else if (matches(*argv, "guard") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("guard", *argv, n,
+						  IFLA_BRPORT_GUARD);
+		} else if (matches(*argv, "root_block") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("root_block", *argv, n,
+						  IFLA_BRPORT_PROTECT);
+		} else if (matches(*argv, "fastleave") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("fastleave", *argv, n,
+						  IFLA_BRPORT_FAST_LEAVE);
+		} else if (matches(*argv, "learning") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("learning", *argv, n,
+						  IFLA_BRPORT_LEARNING);
+		} else if (matches(*argv, "flood") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("flood", *argv, n,
+						  IFLA_BRPORT_UNICAST_FLOOD);
+		} else if (matches(*argv, "mcast_flood") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("mcast_flood", *argv, n,
+						  IFLA_BRPORT_MCAST_FLOOD);
+		} else if (matches(*argv, "mcast_to_unicast") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("mcast_to_unicast", *argv, n,
+						  IFLA_BRPORT_MCAST_TO_UCAST);
+		} else if (matches(*argv, "proxy_arp") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("proxy_arp", *argv, n,
+						  IFLA_BRPORT_PROXYARP);
+		} else if (matches(*argv, "proxy_arp_wifi") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("proxy_arp_wifi", *argv, n,
+						  IFLA_BRPORT_PROXYARP_WIFI);
+		} else if (matches(*argv, "mcast_router") == 0) {
+			__u8 mcast_router;
+
+			NEXT_ARG();
+			if (get_u8(&mcast_router, *argv, 0))
+				invarg("invalid mcast_router", *argv);
+			addattr8(n, 1024, IFLA_BRPORT_MULTICAST_ROUTER,
+				 mcast_router);
+		} else if (matches(*argv, "mcast_fast_leave") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("mcast_fast_leave", *argv, n,
+						  IFLA_BRPORT_FAST_LEAVE);
+		} else if (matches(*argv, "neigh_suppress") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("neigh_suppress", *argv, n,
+						  IFLA_BRPORT_NEIGH_SUPPRESS);
+		} else if (matches(*argv, "group_fwd_mask") == 0) {
+			__u16 mask;
+
+			NEXT_ARG();
+			if (get_u16(&mask, *argv, 0))
+				invarg("invalid group_fwd_mask", *argv);
+			addattr16(n, 1024, IFLA_BRPORT_GROUP_FWD_MASK, mask);
+		} else if (matches(*argv, "vlan_tunnel") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("vlan_tunnel", *argv, n,
+						  IFLA_BRPORT_VLAN_TUNNEL);
+		} else if (matches(*argv, "isolated") == 0) {
+			NEXT_ARG();
+			bridge_slave_parse_on_off("isolated", *argv, n,
+						  IFLA_BRPORT_ISOLATED);
+		} else if (matches(*argv, "backup_port") == 0) {
+			int ifindex;
+
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Device does not exist\n", *argv);
+			addattr32(n, 1024, IFLA_BRPORT_BACKUP_PORT, ifindex);
+		} else if (matches(*argv, "nobackup_port") == 0) {
+			addattr32(n, 1024, IFLA_BRPORT_BACKUP_PORT, 0);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "bridge_slave: unknown option \"%s\"?\n",
+				*argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void bridge_slave_print_help(struct link_util *lu, int argc, char **argv,
+		FILE *f)
+{
+	print_explain(f);
+}
+
+struct link_util bridge_slave_link_util = {
+	.id		= "bridge_slave",
+	.maxattr	= IFLA_BRPORT_MAX,
+	.print_opt	= bridge_slave_print_opt,
+	.parse_opt	= bridge_slave_parse_opt,
+	.print_help     = bridge_slave_print_help,
+	.parse_ifla_xstats = bridge_parse_xstats,
+	.print_ifla_xstats = bridge_print_xstats,
+};
diff --git a/ip/iplink_can.c b/ip/iplink_can.c
new file mode 100644
index 0000000..735ab94
--- /dev/null
+++ b/ip/iplink_can.c
@@ -0,0 +1,598 @@
+/*
+ * iplink_can.c	CAN device support
+ *
+ *		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.
+ *
+ * Authors:	Wolfgang Grandegger <wg@grandegger.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/can/netlink.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_usage(FILE *f)
+{
+	fprintf(f,
+		"Usage: ip link set DEVICE type can\n"
+		"\t[ bitrate BITRATE [ sample-point SAMPLE-POINT] ] |\n"
+		"\t[ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1\n \t  phase-seg2 PHASE-SEG2 [ sjw SJW ] ]\n"
+		"\n"
+		"\t[ dbitrate BITRATE [ dsample-point SAMPLE-POINT] ] |\n"
+		"\t[ dtq TQ dprop-seg PROP_SEG dphase-seg1 PHASE-SEG1\n \t  dphase-seg2 PHASE-SEG2 [ dsjw SJW ] ]\n"
+		"\n"
+		"\t[ loopback { on | off } ]\n"
+		"\t[ listen-only { on | off } ]\n"
+		"\t[ triple-sampling { on | off } ]\n"
+		"\t[ one-shot { on | off } ]\n"
+		"\t[ berr-reporting { on | off } ]\n"
+		"\t[ fd { on | off } ]\n"
+		"\t[ fd-non-iso { on | off } ]\n"
+		"\t[ presume-ack { on | off } ]\n"
+		"\n"
+		"\t[ restart-ms TIME-MS ]\n"
+		"\t[ restart ]\n"
+		"\n"
+		"\t[ termination { 0..65535 } ]\n"
+		"\n"
+		"\tWhere: BITRATE	:= { 1..1000000 }\n"
+		"\t	  SAMPLE-POINT	:= { 0.000..0.999 }\n"
+		"\t	  TQ		:= { NUMBER }\n"
+		"\t	  PROP-SEG	:= { 1..8 }\n"
+		"\t	  PHASE-SEG1	:= { 1..8 }\n"
+		"\t	  PHASE-SEG2	:= { 1..8 }\n"
+		"\t	  SJW		:= { 1..4 }\n"
+		"\t	  RESTART-MS	:= { 0 | NUMBER }\n"
+		);
+}
+
+static void usage(void)
+{
+	print_usage(stderr);
+}
+
+static int get_float(float *val, const char *arg)
+{
+	float res;
+	char *ptr;
+
+	if (!arg || !*arg)
+		return -1;
+	res = strtof(arg, &ptr);
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+	*val = res;
+	return 0;
+}
+
+static void set_ctrlmode(char *name, char *arg,
+			 struct can_ctrlmode *cm, __u32 flags)
+{
+	if (strcmp(arg, "on") == 0) {
+		cm->flags |= flags;
+	} else if (strcmp(arg, "off") != 0) {
+		fprintf(stderr,
+			"Error: argument of \"%s\" must be \"on\" or \"off\", not \"%s\"\n",
+			name, arg);
+		exit(-1);
+	}
+	cm->mask |= flags;
+}
+
+static void print_ctrlmode(FILE *f, __u32 cm)
+{
+	open_json_array(PRINT_ANY, is_json_context() ? "ctrlmode" : "<");
+#define _PF(cmflag, cmname)						\
+	if (cm & cmflag) {						\
+		cm &= ~cmflag;						\
+		print_string(PRINT_ANY, NULL, cm ? "%s," : "%s", cmname); \
+	}
+	_PF(CAN_CTRLMODE_LOOPBACK, "LOOPBACK");
+	_PF(CAN_CTRLMODE_LISTENONLY, "LISTEN-ONLY");
+	_PF(CAN_CTRLMODE_3_SAMPLES, "TRIPLE-SAMPLING");
+	_PF(CAN_CTRLMODE_ONE_SHOT, "ONE-SHOT");
+	_PF(CAN_CTRLMODE_BERR_REPORTING, "BERR-REPORTING");
+	_PF(CAN_CTRLMODE_FD, "FD");
+	_PF(CAN_CTRLMODE_FD_NON_ISO, "FD-NON-ISO");
+	_PF(CAN_CTRLMODE_PRESUME_ACK, "PRESUME-ACK");
+#undef _PF
+	if (cm)
+		print_hex(PRINT_ANY, NULL, "%x", cm);
+	close_json_array(PRINT_ANY, "> ");
+}
+
+static int can_parse_opt(struct link_util *lu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	struct can_bittiming bt = {}, dbt = {};
+	struct can_ctrlmode cm = {0, 0};
+
+	while (argc > 0) {
+		if (matches(*argv, "bitrate") == 0) {
+			NEXT_ARG();
+			if (get_u32(&bt.bitrate, *argv, 0))
+				invarg("invalid \"bitrate\" value\n", *argv);
+		} else if (matches(*argv, "sample-point") == 0) {
+			float sp;
+
+			NEXT_ARG();
+			if (get_float(&sp, *argv))
+				invarg("invalid \"sample-point\" value\n",
+				       *argv);
+			bt.sample_point = (__u32)(sp * 1000);
+		} else if (matches(*argv, "tq") == 0) {
+			NEXT_ARG();
+			if (get_u32(&bt.tq, *argv, 0))
+				invarg("invalid \"tq\" value\n", *argv);
+		} else if (matches(*argv, "prop-seg") == 0) {
+			NEXT_ARG();
+			if (get_u32(&bt.prop_seg, *argv, 0))
+				invarg("invalid \"prop-seg\" value\n", *argv);
+		} else if (matches(*argv, "phase-seg1") == 0) {
+			NEXT_ARG();
+			if (get_u32(&bt.phase_seg1, *argv, 0))
+				invarg("invalid \"phase-seg1\" value\n", *argv);
+		} else if (matches(*argv, "phase-seg2") == 0) {
+			NEXT_ARG();
+			if (get_u32(&bt.phase_seg2, *argv, 0))
+				invarg("invalid \"phase-seg2\" value\n", *argv);
+		} else if (matches(*argv, "sjw") == 0) {
+			NEXT_ARG();
+			if (get_u32(&bt.sjw, *argv, 0))
+				invarg("invalid \"sjw\" value\n", *argv);
+		} else if (matches(*argv, "dbitrate") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.bitrate, *argv, 0))
+				invarg("invalid \"dbitrate\" value\n", *argv);
+		} else if (matches(*argv, "dsample-point") == 0) {
+			float sp;
+
+			NEXT_ARG();
+			if (get_float(&sp, *argv))
+				invarg("invalid \"dsample-point\" value\n", *argv);
+			dbt.sample_point = (__u32)(sp * 1000);
+		} else if (matches(*argv, "dtq") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.tq, *argv, 0))
+				invarg("invalid \"dtq\" value\n", *argv);
+		} else if (matches(*argv, "dprop-seg") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.prop_seg, *argv, 0))
+				invarg("invalid \"dprop-seg\" value\n", *argv);
+		} else if (matches(*argv, "dphase-seg1") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.phase_seg1, *argv, 0))
+				invarg("invalid \"dphase-seg1\" value\n", *argv);
+		} else if (matches(*argv, "dphase-seg2") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.phase_seg2, *argv, 0))
+				invarg("invalid \"dphase-seg2\" value\n", *argv);
+		} else if (matches(*argv, "dsjw") == 0) {
+			NEXT_ARG();
+			if (get_u32(&dbt.sjw, *argv, 0))
+				invarg("invalid \"dsjw\" value\n", *argv);
+		} else if (matches(*argv, "loopback") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("loopback", *argv, &cm,
+				     CAN_CTRLMODE_LOOPBACK);
+		} else if (matches(*argv, "listen-only") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("listen-only", *argv, &cm,
+				     CAN_CTRLMODE_LISTENONLY);
+		} else if (matches(*argv, "triple-sampling") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("triple-sampling", *argv, &cm,
+				     CAN_CTRLMODE_3_SAMPLES);
+		} else if (matches(*argv, "one-shot") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("one-shot", *argv, &cm,
+				     CAN_CTRLMODE_ONE_SHOT);
+		} else if (matches(*argv, "berr-reporting") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("berr-reporting", *argv, &cm,
+				     CAN_CTRLMODE_BERR_REPORTING);
+		} else if (matches(*argv, "fd") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("fd", *argv, &cm,
+				     CAN_CTRLMODE_FD);
+		} else if (matches(*argv, "fd-non-iso") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("fd-non-iso", *argv, &cm,
+				     CAN_CTRLMODE_FD_NON_ISO);
+		} else if (matches(*argv, "presume-ack") == 0) {
+			NEXT_ARG();
+			set_ctrlmode("presume-ack", *argv, &cm,
+				     CAN_CTRLMODE_PRESUME_ACK);
+		} else if (matches(*argv, "restart") == 0) {
+			__u32 val = 1;
+
+			addattr32(n, 1024, IFLA_CAN_RESTART, val);
+		} else if (matches(*argv, "restart-ms") == 0) {
+			__u32 val;
+
+			NEXT_ARG();
+			if (get_u32(&val, *argv, 0))
+				invarg("invalid \"restart-ms\" value\n", *argv);
+			addattr32(n, 1024, IFLA_CAN_RESTART_MS, val);
+		} else if (matches(*argv, "termination") == 0) {
+			__u16 val;
+
+			NEXT_ARG();
+			if (get_u16(&val, *argv, 0))
+				invarg("invalid \"termination\" value\n",
+				       *argv);
+			addattr16(n, 1024, IFLA_CAN_TERMINATION, val);
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+			return -1;
+		} else {
+			fprintf(stderr, "can: unknown option \"%s\"\n", *argv);
+			usage();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (bt.bitrate || bt.tq)
+		addattr_l(n, 1024, IFLA_CAN_BITTIMING, &bt, sizeof(bt));
+	if (dbt.bitrate || dbt.tq)
+		addattr_l(n, 1024, IFLA_CAN_DATA_BITTIMING, &dbt, sizeof(dbt));
+	if (cm.mask)
+		addattr_l(n, 1024, IFLA_CAN_CTRLMODE, &cm, sizeof(cm));
+
+	return 0;
+}
+
+static const char *can_state_names[CAN_STATE_MAX] = {
+	[CAN_STATE_ERROR_ACTIVE] = "ERROR-ACTIVE",
+	[CAN_STATE_ERROR_WARNING] = "ERROR-WARNING",
+	[CAN_STATE_ERROR_PASSIVE] = "ERROR-PASSIVE",
+	[CAN_STATE_BUS_OFF] = "BUS-OFF",
+	[CAN_STATE_STOPPED] = "STOPPED",
+	[CAN_STATE_SLEEPING] = "SLEEPING"
+};
+
+static void can_print_json_timing_min_max(const char *attr, int min, int max)
+{
+	open_json_object(attr);
+	print_int(PRINT_JSON, "min", NULL, min);
+	print_int(PRINT_JSON, "max", NULL, max);
+	close_json_object();
+}
+
+static void can_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_CAN_CTRLMODE]) {
+		struct can_ctrlmode *cm = RTA_DATA(tb[IFLA_CAN_CTRLMODE]);
+
+		if (cm->flags)
+			print_ctrlmode(f, cm->flags);
+	}
+
+	if (tb[IFLA_CAN_STATE]) {
+		uint32_t state = rta_getattr_u32(tb[IFLA_CAN_STATE]);
+
+		print_string(PRINT_ANY, "state", "state %s ", state < CAN_STATE_MAX ?
+			can_state_names[state] : "UNKNOWN");
+	}
+
+	if (tb[IFLA_CAN_BERR_COUNTER]) {
+		struct can_berr_counter *bc =
+			RTA_DATA(tb[IFLA_CAN_BERR_COUNTER]);
+
+		if (is_json_context()) {
+			open_json_object("berr_counter");
+			print_int(PRINT_JSON, "tx", NULL, bc->txerr);
+			print_int(PRINT_JSON, "rx", NULL, bc->rxerr);
+			close_json_object();
+		} else {
+			fprintf(f, "(berr-counter tx %d rx %d) ",
+				bc->txerr, bc->rxerr);
+		}
+	}
+
+	if (tb[IFLA_CAN_RESTART_MS]) {
+		__u32 *restart_ms = RTA_DATA(tb[IFLA_CAN_RESTART_MS]);
+
+		print_int(PRINT_ANY,
+			  "restart_ms",
+			  "restart-ms %d ",
+			  *restart_ms);
+	}
+
+	/* bittiming is irrelevant if fixed bitrate is defined */
+	if (tb[IFLA_CAN_BITTIMING] && !tb[IFLA_CAN_BITRATE_CONST]) {
+		struct can_bittiming *bt = RTA_DATA(tb[IFLA_CAN_BITTIMING]);
+
+		if (is_json_context()) {
+			json_writer_t *jw;
+
+			open_json_object("bittiming");
+			print_int(PRINT_ANY, "bitrate", NULL, bt->bitrate);
+			jw = get_json_writer();
+			jsonw_name(jw, "sample_point");
+			jsonw_printf(jw, "%.3f",
+				     (float) bt->sample_point / 1000);
+			print_int(PRINT_ANY, "tq", NULL, bt->tq);
+			print_int(PRINT_ANY, "prop_seg", NULL, bt->prop_seg);
+			print_int(PRINT_ANY, "phase_seg1",
+				  NULL, bt->phase_seg1);
+			print_int(PRINT_ANY, "phase_seg2",
+				  NULL, bt->phase_seg2);
+			print_int(PRINT_ANY, "sjw", NULL, bt->sjw);
+			close_json_object();
+		} else {
+			fprintf(f, "\n	  bitrate %d sample-point %.3f ",
+				bt->bitrate, (float) bt->sample_point / 1000.);
+			fprintf(f,
+				"\n	  tq %d prop-seg %d phase-seg1 %d phase-seg2 %d sjw %d",
+				bt->tq, bt->prop_seg,
+				bt->phase_seg1, bt->phase_seg2,
+				bt->sjw);
+		}
+	}
+
+	/* bittiming const is irrelevant if fixed bitrate is defined */
+	if (tb[IFLA_CAN_BITTIMING_CONST] && !tb[IFLA_CAN_BITRATE_CONST]) {
+		struct can_bittiming_const *btc =
+			RTA_DATA(tb[IFLA_CAN_BITTIMING_CONST]);
+
+		if (is_json_context()) {
+			open_json_object("bittiming_const");
+			print_string(PRINT_JSON, "name", NULL, btc->name);
+			can_print_json_timing_min_max("tseg1",
+						      btc->tseg1_min,
+						      btc->tseg1_max);
+			can_print_json_timing_min_max("tseg2",
+						      btc->tseg2_min,
+						      btc->tseg2_max);
+			can_print_json_timing_min_max("sjw", 1, btc->sjw_max);
+			can_print_json_timing_min_max("brp",
+						      btc->brp_min,
+						      btc->brp_max);
+			print_int(PRINT_JSON, "brp_inc", NULL, btc->brp_inc);
+			close_json_object();
+		} else {
+			fprintf(f, "\n	  %s: tseg1 %d..%d tseg2 %d..%d "
+				"sjw 1..%d brp %d..%d brp-inc %d",
+				btc->name, btc->tseg1_min, btc->tseg1_max,
+				btc->tseg2_min, btc->tseg2_max, btc->sjw_max,
+				btc->brp_min, btc->brp_max, btc->brp_inc);
+		}
+	}
+
+	if (tb[IFLA_CAN_BITRATE_CONST]) {
+		__u32 *bitrate_const = RTA_DATA(tb[IFLA_CAN_BITRATE_CONST]);
+		int bitrate_cnt = RTA_PAYLOAD(tb[IFLA_CAN_BITRATE_CONST]) /
+			sizeof(*bitrate_const);
+		int i;
+		__u32 bitrate = 0;
+
+		if (tb[IFLA_CAN_BITTIMING]) {
+			struct can_bittiming *bt =
+				RTA_DATA(tb[IFLA_CAN_BITTIMING]);
+			bitrate = bt->bitrate;
+		}
+
+		if (is_json_context()) {
+			print_uint(PRINT_JSON,
+				   "bittiming_bitrate",
+				   NULL, bitrate);
+			open_json_array(PRINT_JSON, "bitrate_const");
+			for (i = 0; i < bitrate_cnt; ++i)
+				print_uint(PRINT_JSON, NULL, NULL,
+					   bitrate_const[i]);
+			close_json_array(PRINT_JSON, NULL);
+		} else {
+			fprintf(f, "\n	  bitrate %u", bitrate);
+			fprintf(f, "\n	     [");
+
+			for (i = 0; i < bitrate_cnt - 1; ++i) {
+				/* This will keep lines below 80 signs */
+				if (!(i % 6) && i)
+					fprintf(f, "\n	      ");
+
+				fprintf(f, "%8u, ", bitrate_const[i]);
+			}
+
+			if (!(i % 6) && i)
+				fprintf(f, "\n	      ");
+			fprintf(f, "%8u ]", bitrate_const[i]);
+		}
+	}
+
+	/* data bittiming is irrelevant if fixed bitrate is defined */
+	if (tb[IFLA_CAN_DATA_BITTIMING] && !tb[IFLA_CAN_DATA_BITRATE_CONST]) {
+		struct can_bittiming *dbt =
+			RTA_DATA(tb[IFLA_CAN_DATA_BITTIMING]);
+
+		if (is_json_context()) {
+			json_writer_t *jw;
+
+			open_json_object("data_bittiming");
+			print_int(PRINT_JSON, "bitrate", NULL, dbt->bitrate);
+			jw = get_json_writer();
+			jsonw_name(jw, "sample_point");
+			jsonw_printf(jw, "%.3f",
+				     (float) dbt->sample_point / 1000.);
+			print_int(PRINT_JSON, "tq", NULL, dbt->tq);
+			print_int(PRINT_JSON, "prop_seg", NULL, dbt->prop_seg);
+			print_int(PRINT_JSON, "phase_seg1",
+				  NULL, dbt->phase_seg1);
+			print_int(PRINT_JSON, "phase_seg2",
+				  NULL, dbt->phase_seg2);
+			print_int(PRINT_JSON, "sjw", NULL, dbt->sjw);
+			close_json_object();
+		} else {
+			fprintf(f, "\n	  dbitrate %d dsample-point %.3f ",
+				dbt->bitrate,
+				(float) dbt->sample_point / 1000.);
+			fprintf(f, "\n	  dtq %d dprop-seg %d dphase-seg1 %d "
+				"dphase-seg2 %d dsjw %d",
+				dbt->tq, dbt->prop_seg, dbt->phase_seg1,
+				dbt->phase_seg2, dbt->sjw);
+		}
+	}
+
+	/* data bittiming const is irrelevant if fixed bitrate is defined */
+	if (tb[IFLA_CAN_DATA_BITTIMING_CONST] &&
+	    !tb[IFLA_CAN_DATA_BITRATE_CONST]) {
+		struct can_bittiming_const *dbtc =
+			RTA_DATA(tb[IFLA_CAN_DATA_BITTIMING_CONST]);
+
+		if (is_json_context()) {
+			open_json_object("data_bittiming_const");
+			print_string(PRINT_JSON, "name", NULL, dbtc->name);
+			can_print_json_timing_min_max("tseg1",
+						      dbtc->tseg1_min,
+						      dbtc->tseg1_max);
+			can_print_json_timing_min_max("tseg2",
+						      dbtc->tseg2_min,
+						      dbtc->tseg2_max);
+			can_print_json_timing_min_max("sjw", 1, dbtc->sjw_max);
+			can_print_json_timing_min_max("brp",
+						      dbtc->brp_min,
+						      dbtc->brp_max);
+
+			print_int(PRINT_JSON, "brp_inc", NULL, dbtc->brp_inc);
+			close_json_object();
+		} else {
+			fprintf(f, "\n	  %s: dtseg1 %d..%d dtseg2 %d..%d "
+				"dsjw 1..%d dbrp %d..%d dbrp-inc %d",
+				dbtc->name, dbtc->tseg1_min, dbtc->tseg1_max,
+				dbtc->tseg2_min, dbtc->tseg2_max, dbtc->sjw_max,
+				dbtc->brp_min, dbtc->brp_max, dbtc->brp_inc);
+		}
+	}
+
+	if (tb[IFLA_CAN_DATA_BITRATE_CONST]) {
+		__u32 *dbitrate_const =
+			RTA_DATA(tb[IFLA_CAN_DATA_BITRATE_CONST]);
+		int dbitrate_cnt =
+			RTA_PAYLOAD(tb[IFLA_CAN_DATA_BITRATE_CONST]) /
+			sizeof(*dbitrate_const);
+		int i;
+		__u32 dbitrate = 0;
+
+		if (tb[IFLA_CAN_DATA_BITTIMING]) {
+			struct can_bittiming *dbt =
+				RTA_DATA(tb[IFLA_CAN_DATA_BITTIMING]);
+			dbitrate = dbt->bitrate;
+		}
+
+		if (is_json_context()) {
+			print_uint(PRINT_JSON, "data_bittiming_bitrate",
+				   NULL, dbitrate);
+			open_json_array(PRINT_JSON, "data_bitrate_const");
+			for (i = 0; i < dbitrate_cnt; ++i)
+				print_uint(PRINT_JSON, NULL, NULL,
+					   dbitrate_const[i]);
+			close_json_array(PRINT_JSON, NULL);
+		} else {
+			fprintf(f, "\n	  dbitrate %u", dbitrate);
+			fprintf(f, "\n	     [");
+
+			for (i = 0; i < dbitrate_cnt - 1; ++i) {
+				/* This will keep lines below 80 signs */
+				if (!(i % 6) && i)
+					fprintf(f, "\n	      ");
+
+				fprintf(f, "%8u, ", dbitrate_const[i]);
+			}
+
+			if (!(i % 6) && i)
+				fprintf(f, "\n	      ");
+			fprintf(f, "%8u ]", dbitrate_const[i]);
+		}
+	}
+
+	if (tb[IFLA_CAN_TERMINATION_CONST] && tb[IFLA_CAN_TERMINATION]) {
+		__u16 *trm = RTA_DATA(tb[IFLA_CAN_TERMINATION]);
+		__u16 *trm_const = RTA_DATA(tb[IFLA_CAN_TERMINATION_CONST]);
+		int trm_cnt = RTA_PAYLOAD(tb[IFLA_CAN_TERMINATION_CONST]) /
+			sizeof(*trm_const);
+		int i;
+
+		if (is_json_context()) {
+			print_hu(PRINT_JSON, "termination", NULL, *trm);
+			open_json_array(PRINT_JSON, "termination_const");
+			for (i = 0; i < trm_cnt; ++i)
+				print_hu(PRINT_JSON, NULL, NULL, trm_const[i]);
+			close_json_array(PRINT_JSON, NULL);
+		} else {
+			fprintf(f, "\n	  termination %hu [ ", *trm);
+
+			for (i = 0; i < trm_cnt - 1; ++i)
+				fprintf(f, "%hu, ", trm_const[i]);
+
+			fprintf(f, "%hu ]", trm_const[i]);
+		}
+	}
+
+	if (tb[IFLA_CAN_CLOCK]) {
+		struct can_clock *clock = RTA_DATA(tb[IFLA_CAN_CLOCK]);
+
+		print_int(PRINT_ANY,
+			  "clock",
+			  "\n	  clock %d ",
+			  clock->freq);
+	}
+
+}
+
+static void can_print_xstats(struct link_util *lu,
+			     FILE *f, struct rtattr *xstats)
+{
+	struct can_device_stats *stats;
+
+	if (xstats && RTA_PAYLOAD(xstats) == sizeof(*stats)) {
+		stats = RTA_DATA(xstats);
+
+		if (is_json_context()) {
+			print_int(PRINT_JSON, "restarts",
+				  NULL, stats->restarts);
+			print_int(PRINT_JSON, "bus_error",
+				  NULL, stats->bus_error);
+			print_int(PRINT_JSON, "arbitration_lost",
+				  NULL, stats->arbitration_lost);
+			print_int(PRINT_JSON, "error_warning",
+				  NULL, stats->error_warning);
+			print_int(PRINT_JSON, "error_passive",
+				  NULL, stats->error_passive);
+			print_int(PRINT_JSON, "bus_off", NULL, stats->bus_off);
+		} else {
+			fprintf(f, "\n	  re-started bus-errors arbit-lost "
+				"error-warn error-pass bus-off");
+			fprintf(f, "\n	  %-10d %-10d %-10d %-10d %-10d %-10d",
+				stats->restarts, stats->bus_error,
+				stats->arbitration_lost, stats->error_warning,
+				stats->error_passive, stats->bus_off);
+		}
+	}
+}
+
+static void can_print_help(struct link_util *lu, int argc, char **argv,
+			   FILE *f)
+{
+	print_usage(f);
+}
+
+struct link_util can_link_util = {
+	.id		= "can",
+	.maxattr	= IFLA_CAN_MAX,
+	.parse_opt	= can_parse_opt,
+	.print_opt	= can_print_opt,
+	.print_xstats	= can_print_xstats,
+	.print_help	= can_print_help,
+};
diff --git a/ip/iplink_dummy.c b/ip/iplink_dummy.c
new file mode 100644
index 0000000..cba2295
--- /dev/null
+++ b/ip/iplink_dummy.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void dummy_print_help(struct link_util *lu,
+			    int argc, char **argv, FILE *f)
+{
+	fprintf(f, "Usage: ... dummy\n");
+}
+
+struct link_util dummy_link_util = {
+	.id		= "dummy",
+	.print_help	= dummy_print_help,
+};
diff --git a/ip/iplink_geneve.c b/ip/iplink_geneve.c
new file mode 100644
index 0000000..9299236
--- /dev/null
+++ b/ip/iplink_geneve.c
@@ -0,0 +1,386 @@
+/*
+ * iplink_geneve.c	GENEVE device support
+ *
+ *              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.
+ *
+ * Authors:     John W. Linville <linville@tuxdriver.com>
+ */
+
+#include <stdio.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+#define GENEVE_ATTRSET(attrs, type) (((attrs) & (1L << (type))) != 0)
+
+static void print_explain(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... geneve id VNI\n"
+		"		remote ADDR\n"
+		"		[ ttl TTL ]\n"
+		"		[ tos TOS ]\n"
+		"		[ df DF ]\n"
+		"		[ flowlabel LABEL ]\n"
+		"		[ dstport PORT ]\n"
+		"		[ [no]external ]\n"
+		"		[ [no]udpcsum ]\n"
+		"		[ [no]udp6zerocsumtx ]\n"
+		"		[ [no]udp6zerocsumrx ]\n"
+		"\n"
+		"Where:	VNI   := 0-16777215\n"
+		"	ADDR  := IP_ADDRESS\n"
+		"	TOS   := { NUMBER | inherit }\n"
+		"	TTL   := { 1..255 | auto | inherit }\n"
+		"	DF    := { unset | set | inherit }\n"
+		"	LABEL := 0-1048575\n"
+	);
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static void check_duparg(__u64 *attrs, int type, const char *key,
+			 const char *argv)
+{
+	if (!GENEVE_ATTRSET(*attrs, type)) {
+		*attrs |= (1L << type);
+		return;
+	}
+	duparg2(key, argv);
+}
+
+static int geneve_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	inet_prefix daddr;
+	__u32 vni = 0;
+	__u32 label = 0;
+	__u8 ttl = 0;
+	__u8 tos = 0;
+	__u16 dstport = 0;
+	bool metadata = 0;
+	__u8 udpcsum = 0;
+	__u8 udp6zerocsumtx = 0;
+	__u8 udp6zerocsumrx = 0;
+	__u64 attrs = 0;
+	bool set_op = (n->nlmsg_type == RTM_NEWLINK &&
+		       !(n->nlmsg_flags & NLM_F_CREATE));
+
+	inet_prefix_reset(&daddr);
+
+	while (argc > 0) {
+		if (!matches(*argv, "id") ||
+		    !matches(*argv, "vni")) {
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_GENEVE_ID, "id", *argv);
+			if (get_u32(&vni, *argv, 0) ||
+			    vni >= 1u << 24)
+				invarg("invalid id", *argv);
+		} else if (!matches(*argv, "remote")) {
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_GENEVE_REMOTE, "remote",
+				     *argv);
+			get_addr(&daddr, *argv, AF_UNSPEC);
+			if (!is_addrtype_inet_not_multi(&daddr))
+				invarg("invalid remote address", *argv);
+		} else if (!matches(*argv, "ttl") ||
+			   !matches(*argv, "hoplimit")) {
+			unsigned int uval;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_GENEVE_TTL, "ttl", *argv);
+			if (strcmp(*argv, "inherit") == 0) {
+				addattr8(n, 1024, IFLA_GENEVE_TTL_INHERIT, 1);
+			} else if (strcmp(*argv, "auto") != 0) {
+				if (get_unsigned(&uval, *argv, 0))
+					invarg("invalid TTL", *argv);
+				if (uval > 255)
+					invarg("TTL must be <= 255", *argv);
+				ttl = uval;
+			}
+		} else if (!matches(*argv, "tos") ||
+			   !matches(*argv, "dsfield")) {
+			__u32 uval;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_GENEVE_TOS, "tos", *argv);
+			if (strcmp(*argv, "inherit") != 0) {
+				if (rtnl_dsfield_a2n(&uval, *argv))
+					invarg("bad TOS value", *argv);
+				tos = uval;
+			} else
+				tos = 1;
+		} else if (!matches(*argv, "df")) {
+			enum ifla_geneve_df df;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_GENEVE_DF, "df", *argv);
+			if (strcmp(*argv, "unset") == 0)
+				df = GENEVE_DF_UNSET;
+			else if (strcmp(*argv, "set") == 0)
+				df = GENEVE_DF_SET;
+			else if (strcmp(*argv, "inherit") == 0)
+				df = GENEVE_DF_INHERIT;
+			else
+				invarg("DF must be 'unset', 'set' or 'inherit'",
+				       *argv);
+
+			addattr8(n, 1024, IFLA_GENEVE_DF, df);
+		} else if (!matches(*argv, "label") ||
+			   !matches(*argv, "flowlabel")) {
+			__u32 uval;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_GENEVE_LABEL, "flowlabel",
+				     *argv);
+			if (get_u32(&uval, *argv, 0) ||
+			    (uval & ~LABEL_MAX_MASK))
+				invarg("invalid flowlabel", *argv);
+			label = htonl(uval);
+		} else if (!matches(*argv, "dstport")) {
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_GENEVE_PORT, "dstport",
+				     *argv);
+			if (get_u16(&dstport, *argv, 0))
+				invarg("dstport", *argv);
+		} else if (!matches(*argv, "external")) {
+			check_duparg(&attrs, IFLA_GENEVE_COLLECT_METADATA,
+				     *argv, *argv);
+			metadata = true;
+		} else if (!matches(*argv, "noexternal")) {
+			check_duparg(&attrs, IFLA_GENEVE_COLLECT_METADATA,
+				     *argv, *argv);
+			metadata = false;
+		} else if (!matches(*argv, "udpcsum")) {
+			check_duparg(&attrs, IFLA_GENEVE_UDP_CSUM, *argv,
+				     *argv);
+			udpcsum = 1;
+		} else if (!matches(*argv, "noudpcsum")) {
+			check_duparg(&attrs, IFLA_GENEVE_UDP_CSUM, *argv,
+				     *argv);
+			udpcsum = 0;
+		} else if (!matches(*argv, "udp6zerocsumtx")) {
+			check_duparg(&attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_TX,
+				     *argv, *argv);
+			udp6zerocsumtx = 1;
+		} else if (!matches(*argv, "noudp6zerocsumtx")) {
+			check_duparg(&attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_TX,
+				     *argv, *argv);
+			udp6zerocsumtx = 0;
+		} else if (!matches(*argv, "udp6zerocsumrx")) {
+			check_duparg(&attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_RX,
+				     *argv, *argv);
+			udp6zerocsumrx = 1;
+		} else if (!matches(*argv, "noudp6zerocsumrx")) {
+			check_duparg(&attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_RX,
+				     *argv, *argv);
+			udp6zerocsumrx = 0;
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "geneve: unknown command \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (metadata && GENEVE_ATTRSET(attrs, IFLA_GENEVE_ID)) {
+		fprintf(stderr, "geneve: both 'external' and vni cannot be specified\n");
+		return -1;
+	}
+
+	if (!metadata) {
+		/* parameter checking make sense only for full geneve tunnels */
+		if (!GENEVE_ATTRSET(attrs, IFLA_GENEVE_ID)) {
+			fprintf(stderr, "geneve: missing virtual network identifier\n");
+			return -1;
+		}
+
+		/* If we are modifying the geneve device, then we only need the
+		 * ID (VNI) to identify the geneve device, and we do not need
+		 * the remote IP.
+		 */
+		if (!set_op && !is_addrtype_inet(&daddr)) {
+			fprintf(stderr, "geneve: remote link partner not specified\n");
+			return -1;
+		}
+	}
+
+	addattr32(n, 1024, IFLA_GENEVE_ID, vni);
+	if (is_addrtype_inet(&daddr)) {
+		int type = (daddr.family == AF_INET) ? IFLA_GENEVE_REMOTE :
+						       IFLA_GENEVE_REMOTE6;
+		addattr_l(n, 1024, type, daddr.data, daddr.bytelen);
+	}
+	if (!set_op || GENEVE_ATTRSET(attrs, IFLA_GENEVE_LABEL))
+		addattr32(n, 1024, IFLA_GENEVE_LABEL, label);
+	if (!set_op || GENEVE_ATTRSET(attrs, IFLA_GENEVE_TTL))
+		addattr8(n, 1024, IFLA_GENEVE_TTL, ttl);
+	if (!set_op || GENEVE_ATTRSET(attrs, IFLA_GENEVE_TOS))
+		addattr8(n, 1024, IFLA_GENEVE_TOS, tos);
+	if (dstport)
+		addattr16(n, 1024, IFLA_GENEVE_PORT, htons(dstport));
+	if (metadata)
+		addattr(n, 1024, IFLA_GENEVE_COLLECT_METADATA);
+	if (GENEVE_ATTRSET(attrs, IFLA_GENEVE_UDP_CSUM))
+		addattr8(n, 1024, IFLA_GENEVE_UDP_CSUM, udpcsum);
+	if (GENEVE_ATTRSET(attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_TX))
+		addattr8(n, 1024, IFLA_GENEVE_UDP_ZERO_CSUM6_TX, udp6zerocsumtx);
+	if (GENEVE_ATTRSET(attrs, IFLA_GENEVE_UDP_ZERO_CSUM6_RX))
+		addattr8(n, 1024, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, udp6zerocsumrx);
+
+	return 0;
+}
+
+static void geneve_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	__u32 vni;
+	__u8 ttl = 0;
+	__u8 tos = 0;
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_GENEVE_COLLECT_METADATA]) {
+		print_bool(PRINT_ANY, "external", "external ", true);
+		return;
+	}
+
+	if (!tb[IFLA_GENEVE_ID] ||
+	    RTA_PAYLOAD(tb[IFLA_GENEVE_ID]) < sizeof(__u32))
+		return;
+
+	vni = rta_getattr_u32(tb[IFLA_GENEVE_ID]);
+	print_uint(PRINT_ANY, "id", "id %u ", vni);
+
+	if (tb[IFLA_GENEVE_REMOTE]) {
+		__be32 addr = rta_getattr_u32(tb[IFLA_GENEVE_REMOTE]);
+
+		if (addr)
+			print_string(PRINT_ANY,
+				     "remote",
+				     "remote %s ",
+				     format_host(AF_INET, 4, &addr));
+	} else if (tb[IFLA_GENEVE_REMOTE6]) {
+		struct in6_addr addr;
+
+		memcpy(&addr, RTA_DATA(tb[IFLA_GENEVE_REMOTE6]), sizeof(struct in6_addr));
+		if (!IN6_IS_ADDR_UNSPECIFIED(&addr)) {
+			if (!IN6_IS_ADDR_MULTICAST(&addr))
+				print_string(PRINT_ANY,
+					     "remote6",
+					     "remote %s ",
+					     format_host(AF_INET6,
+							 sizeof(struct in6_addr),
+							 &addr));
+		}
+	}
+
+	if (tb[IFLA_GENEVE_TTL_INHERIT] &&
+	    rta_getattr_u8(tb[IFLA_GENEVE_TTL_INHERIT])) {
+		print_string(PRINT_FP, NULL, "ttl %s ", "inherit");
+	} else if (tb[IFLA_GENEVE_TTL]) {
+		ttl = rta_getattr_u8(tb[IFLA_GENEVE_TTL]);
+		if (is_json_context() || ttl)
+			print_uint(PRINT_ANY, "ttl", "ttl %u ", ttl);
+		else
+			print_string(PRINT_FP, NULL, "ttl %s ", "auto");
+	}
+
+	if (tb[IFLA_GENEVE_TOS])
+		tos = rta_getattr_u8(tb[IFLA_GENEVE_TOS]);
+	if (tos) {
+		if (is_json_context() || tos != 1)
+			print_0xhex(PRINT_ANY, "tos", "tos %#llx ", tos);
+		else
+			print_string(PRINT_FP, NULL, "tos %s ", "inherit");
+	}
+
+	if (tb[IFLA_GENEVE_DF]) {
+		enum ifla_geneve_df df = rta_getattr_u8(tb[IFLA_GENEVE_DF]);
+
+		if (df == GENEVE_DF_UNSET)
+			print_string(PRINT_JSON, "df", "df %s ", "unset");
+		else if (df == GENEVE_DF_SET)
+			print_string(PRINT_ANY, "df", "df %s ", "set");
+		else if (df == GENEVE_DF_INHERIT)
+			print_string(PRINT_ANY, "df", "df %s ", "inherit");
+	}
+
+	if (tb[IFLA_GENEVE_LABEL]) {
+		__u32 label = rta_getattr_u32(tb[IFLA_GENEVE_LABEL]);
+
+		if (label)
+			print_0xhex(PRINT_ANY,
+				    "label", "flowlabel %#llx ",
+				    ntohl(label));
+	}
+
+	if (tb[IFLA_GENEVE_PORT])
+		print_uint(PRINT_ANY,
+			   "port",
+			   "dstport %u ",
+			   rta_getattr_be16(tb[IFLA_GENEVE_PORT]));
+
+	if (tb[IFLA_GENEVE_UDP_CSUM]) {
+		if (is_json_context()) {
+			print_bool(PRINT_JSON,
+				   "udp_csum",
+				   NULL,
+				   rta_getattr_u8(tb[IFLA_GENEVE_UDP_CSUM]));
+		} else {
+			if (!rta_getattr_u8(tb[IFLA_GENEVE_UDP_CSUM]))
+				fputs("no", f);
+			fputs("udpcsum ", f);
+		}
+	}
+
+	if (tb[IFLA_GENEVE_UDP_ZERO_CSUM6_TX]) {
+		if (is_json_context()) {
+			print_bool(PRINT_JSON,
+				   "udp_zero_csum6_tx",
+				   NULL,
+				   rta_getattr_u8(tb[IFLA_GENEVE_UDP_ZERO_CSUM6_TX]));
+		} else {
+			if (!rta_getattr_u8(tb[IFLA_GENEVE_UDP_ZERO_CSUM6_TX]))
+				fputs("no", f);
+			fputs("udp6zerocsumtx ", f);
+		}
+	}
+
+	if (tb[IFLA_GENEVE_UDP_ZERO_CSUM6_RX]) {
+		if (is_json_context()) {
+			print_bool(PRINT_JSON,
+				   "udp_zero_csum6_rx",
+				   NULL,
+				   rta_getattr_u8(tb[IFLA_GENEVE_UDP_ZERO_CSUM6_RX]));
+		} else {
+			if (!rta_getattr_u8(tb[IFLA_GENEVE_UDP_ZERO_CSUM6_RX]))
+				fputs("no", f);
+			fputs("udp6zerocsumrx ", f);
+		}
+	}
+}
+
+static void geneve_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_explain(f);
+}
+
+struct link_util geneve_link_util = {
+	.id		= "geneve",
+	.maxattr	= IFLA_GENEVE_MAX,
+	.parse_opt	= geneve_parse_opt,
+	.print_opt	= geneve_print_opt,
+	.print_help	= geneve_print_help,
+};
diff --git a/ip/iplink_hsr.c b/ip/iplink_hsr.c
new file mode 100644
index 0000000..7d9167d
--- /dev/null
+++ b/ip/iplink_hsr.c
@@ -0,0 +1,157 @@
+/*
+ * iplink_hsr.c	HSR device support
+ *
+ *		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.
+ *
+ * Authors:	Arvid Brodin <arvid.brodin@alten.se>
+ *
+ *		Based on iplink_vlan.c by Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>  /* Needed by linux/if.h for some reason */
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_usage(FILE *f)
+{
+	fprintf(f,
+		"Usage:\tip link add name NAME type hsr slave1 SLAVE1-IF slave2 SLAVE2-IF\n"
+		"\t[ supervision ADDR-BYTE ] [version VERSION]\n"
+		"\n"
+		"NAME\n"
+		"	name of new hsr device (e.g. hsr0)\n"
+		"SLAVE1-IF, SLAVE2-IF\n"
+		"	the two slave devices bound to the HSR device\n"
+		"ADDR-BYTE\n"
+		"	0-255; the last byte of the multicast address used for HSR supervision\n"
+		"	frames (default = 0)\n"
+		"VERSION\n"
+		"	0,1; the protocol version to be used. (default = 0)\n");
+}
+
+static void usage(void)
+{
+	print_usage(stderr);
+}
+
+static int hsr_parse_opt(struct link_util *lu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	int ifindex;
+	unsigned char multicast_spec;
+	unsigned char protocol_version;
+
+	while (argc > 0) {
+		if (matches(*argv, "supervision") == 0) {
+			NEXT_ARG();
+			if (get_u8(&multicast_spec, *argv, 0))
+				invarg("ADDR-BYTE is invalid", *argv);
+			addattr_l(n, 1024, IFLA_HSR_MULTICAST_SPEC,
+				  &multicast_spec, 1);
+		} else if (matches(*argv, "version") == 0) {
+			NEXT_ARG();
+			if (!(get_u8(&protocol_version, *argv, 0) == 0 ||
+			      get_u8(&protocol_version, *argv, 0) == 1))
+				invarg("version is invalid", *argv);
+			addattr_l(n, 1024, IFLA_HSR_VERSION,
+				  &protocol_version, 1);
+		} else if (matches(*argv, "slave1") == 0) {
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (ifindex == 0)
+				invarg("No such interface", *argv);
+			addattr_l(n, 1024, IFLA_HSR_SLAVE1, &ifindex, 4);
+		} else if (matches(*argv, "slave2") == 0) {
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (ifindex == 0)
+				invarg("No such interface", *argv);
+			addattr_l(n, 1024, IFLA_HSR_SLAVE2, &ifindex, 4);
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+			return -1;
+		} else {
+			fprintf(stderr, "hsr: what is \"%s\"?\n", *argv);
+			usage();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void hsr_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	SPRINT_BUF(b1);
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_HSR_SLAVE1] &&
+	    RTA_PAYLOAD(tb[IFLA_HSR_SLAVE1]) < sizeof(__u32))
+		return;
+	if (tb[IFLA_HSR_SLAVE2] &&
+	    RTA_PAYLOAD(tb[IFLA_HSR_SLAVE2]) < sizeof(__u32))
+		return;
+	if (tb[IFLA_HSR_SEQ_NR] &&
+	    RTA_PAYLOAD(tb[IFLA_HSR_SEQ_NR]) < sizeof(__u16))
+		return;
+	if (tb[IFLA_HSR_SUPERVISION_ADDR] &&
+	    RTA_PAYLOAD(tb[IFLA_HSR_SUPERVISION_ADDR]) < ETH_ALEN)
+		return;
+
+	if (tb[IFLA_HSR_SLAVE1])
+		print_string(PRINT_ANY,
+			     "slave1",
+			     "slave1 %s ",
+			     ll_index_to_name(rta_getattr_u32(tb[IFLA_HSR_SLAVE1])));
+	else
+		print_null(PRINT_ANY, "slave1", "slave1 %s ", "<none>");
+
+	if (tb[IFLA_HSR_SLAVE2])
+		print_string(PRINT_ANY,
+			     "slave2",
+			     "slave2 %s ",
+			     ll_index_to_name(rta_getattr_u32(tb[IFLA_HSR_SLAVE2])));
+	else
+		print_null(PRINT_ANY, "slave2", "slave2 %s ", "<none>");
+
+	if (tb[IFLA_HSR_SEQ_NR])
+		print_int(PRINT_ANY,
+			  "seq_nr",
+			  "sequence %d ",
+			  rta_getattr_u16(tb[IFLA_HSR_SEQ_NR]));
+
+	if (tb[IFLA_HSR_SUPERVISION_ADDR])
+		print_string(PRINT_ANY,
+			     "supervision_addr",
+			     "supervision %s ",
+			     ll_addr_n2a(RTA_DATA(tb[IFLA_HSR_SUPERVISION_ADDR]),
+					 RTA_PAYLOAD(tb[IFLA_HSR_SUPERVISION_ADDR]),
+					 ARPHRD_VOID,
+					 b1, sizeof(b1)));
+}
+
+static void hsr_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
+}
+
+struct link_util hsr_link_util = {
+	.id		= "hsr",
+	.maxattr	= IFLA_HSR_MAX,
+	.parse_opt	= hsr_parse_opt,
+	.print_opt	= hsr_print_opt,
+	.print_help	= hsr_print_help,
+};
diff --git a/ip/iplink_ifb.c b/ip/iplink_ifb.c
new file mode 100644
index 0000000..a2a7301
--- /dev/null
+++ b/ip/iplink_ifb.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void ifb_print_help(struct link_util *lu,
+			    int argc, char **argv, FILE *f)
+{
+	fprintf(f, "Usage: ... ifb\n");
+}
+
+struct link_util ifb_link_util = {
+	.id		= "ifb",
+	.print_help	= ifb_print_help,
+};
diff --git a/ip/iplink_ipoib.c b/ip/iplink_ipoib.c
new file mode 100644
index 0000000..05dba35
--- /dev/null
+++ b/ip/iplink_ipoib.c
@@ -0,0 +1,145 @@
+/*
+ * iplink_ipoib.c	IPoIB device support
+ *
+ *              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.
+ *
+ * Authors:     Or Gerlitz <ogerlitz@mellanox.com>
+ *		copied iflink_vlan.c authored by Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_link.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... ipoib [ pkey PKEY ]\n"
+		"		 [ mode {datagram | connected} ]\n"
+		"		 [ umcast {0|1} ]\n"
+		"\n"
+		"PKEY  := 0x8001-0xffff\n"
+	);
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static int mode_arg(void)
+{
+	fprintf(stderr, "Error: argument of \"mode\" must be \"datagram\"or \"connected\"\n");
+	return -1;
+}
+
+static int ipoib_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	__u16 pkey, mode, umcast;
+
+	while (argc > 0) {
+		if (matches(*argv, "pkey") == 0) {
+			NEXT_ARG();
+			if (get_u16(&pkey, *argv, 0))
+				invarg("pkey is invalid", *argv);
+			addattr_l(n, 1024, IFLA_IPOIB_PKEY, &pkey, 2);
+		} else if (matches(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "datagram") == 0)
+				mode = IPOIB_MODE_DATAGRAM;
+			else if (strcmp(*argv, "connected") == 0)
+				mode = IPOIB_MODE_CONNECTED;
+			else
+				return mode_arg();
+			addattr_l(n, 1024, IFLA_IPOIB_MODE, &mode, 2);
+		} else if (matches(*argv, "umcast") == 0) {
+			NEXT_ARG();
+			if (get_u16(&umcast, *argv, 0))
+				invarg("umcast is invalid", *argv);
+			addattr_l(n, 1024, IFLA_IPOIB_UMCAST, &umcast, 2);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "ipoib: unknown option \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void ipoib_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	__u16 mode;
+
+	if (!tb)
+		return;
+
+	if (!tb[IFLA_IPOIB_PKEY] ||
+	    RTA_PAYLOAD(tb[IFLA_IPOIB_PKEY]) < sizeof(__u16))
+		return;
+
+	__u16 pkey = rta_getattr_u16(tb[IFLA_IPOIB_PKEY]);
+
+	if (is_json_context()) {
+		SPRINT_BUF(b1);
+
+		snprintf(b1, sizeof(b1), "%#.4x", pkey);
+		print_string(PRINT_JSON, "key", NULL, b1);
+	} else {
+		fprintf(f, "pkey  %#.4x ", pkey);
+	}
+
+	if (!tb[IFLA_IPOIB_MODE] ||
+	    RTA_PAYLOAD(tb[IFLA_IPOIB_MODE]) < sizeof(__u16))
+		return;
+
+	mode = rta_getattr_u16(tb[IFLA_IPOIB_MODE]);
+
+	const char *mode_str =
+		mode == IPOIB_MODE_DATAGRAM ? "datagram" :
+		mode == IPOIB_MODE_CONNECTED ? "connected" : "unknown";
+
+	print_string(PRINT_ANY, "mode", "mode  %s ", mode_str);
+
+	if (!tb[IFLA_IPOIB_UMCAST] ||
+	    RTA_PAYLOAD(tb[IFLA_IPOIB_UMCAST]) < sizeof(__u16))
+		return;
+
+	__u16 umcast = rta_getattr_u16(tb[IFLA_IPOIB_UMCAST]);
+
+	if (is_json_context()) {
+		SPRINT_BUF(b1);
+
+		snprintf(b1, sizeof(b1), "%.4x", umcast);
+		print_string(PRINT_JSON, "umcast", NULL, b1);
+	} else {
+		fprintf(f, "umcast  %.4x ", umcast);
+	}
+}
+
+static void ipoib_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_explain(f);
+}
+
+struct link_util ipoib_link_util = {
+	.id		= "ipoib",
+	.maxattr	= IFLA_IPOIB_MAX,
+	.parse_opt	= ipoib_parse_opt,
+	.print_opt	= ipoib_print_opt,
+	.print_help	= ipoib_print_help,
+};
diff --git a/ip/iplink_ipvlan.c b/ip/iplink_ipvlan.c
new file mode 100644
index 0000000..baae767
--- /dev/null
+++ b/ip/iplink_ipvlan.c
@@ -0,0 +1,133 @@
+/* iplink_ipvlan.c	IPVLAN/IPVTAP device support
+ *
+ *              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.
+ *
+ * Authors:     Mahesh Bandewar <maheshb@google.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <linux/if_link.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(struct link_util *lu, FILE *f)
+{
+	fprintf(f,
+		"Usage: ... %s [ mode MODE ] [ FLAGS ]\n"
+		"\n"
+		"MODE: l3 | l3s | l2\n"
+		"FLAGS: bridge | private | vepa\n"
+		"(first values are the defaults if nothing is specified).\n",
+		lu->id);
+}
+
+static int ipvlan_parse_opt(struct link_util *lu, int argc, char **argv,
+			    struct nlmsghdr *n)
+{
+	__u16 flags = 0;
+	bool mflag_given = false;
+
+	while (argc > 0) {
+		if (matches(*argv, "mode") == 0) {
+			__u16 mode = 0;
+
+			NEXT_ARG();
+
+			if (strcmp(*argv, "l2") == 0)
+				mode = IPVLAN_MODE_L2;
+			else if (strcmp(*argv, "l3") == 0)
+				mode = IPVLAN_MODE_L3;
+			else if (strcmp(*argv, "l3s") == 0)
+				mode = IPVLAN_MODE_L3S;
+			else {
+				fprintf(stderr, "Error: argument of \"mode\" must be either \"l2\", \"l3\" or \"l3s\"\n");
+				return -1;
+			}
+			addattr16(n, 1024, IFLA_IPVLAN_MODE, mode);
+		} else if (matches(*argv, "private") == 0 && !mflag_given) {
+			flags |= IPVLAN_F_PRIVATE;
+			mflag_given = true;
+		} else if (matches(*argv, "vepa") == 0 && !mflag_given) {
+			flags |= IPVLAN_F_VEPA;
+			mflag_given = true;
+		} else if (matches(*argv, "bridge") == 0 && !mflag_given) {
+			mflag_given = true;
+		} else if (matches(*argv, "help") == 0) {
+			print_explain(lu, stderr);
+			return -1;
+		} else {
+			fprintf(stderr, "%s: unknown option \"%s\"?\n",
+				lu->id, *argv);
+			print_explain(lu, stderr);
+			return -1;
+		}
+		argc--;
+		argv++;
+	}
+	addattr16(n, 1024, IFLA_IPVLAN_FLAGS, flags);
+
+	return 0;
+}
+
+static void ipvlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_IPVLAN_MODE]) {
+		if (RTA_PAYLOAD(tb[IFLA_IPVLAN_MODE]) == sizeof(__u16)) {
+			__u16 mode = rta_getattr_u16(tb[IFLA_IPVLAN_MODE]);
+			const char *mode_str = mode == IPVLAN_MODE_L2 ? "l2" :
+				mode == IPVLAN_MODE_L3 ? "l3" :
+				mode == IPVLAN_MODE_L3S ? "l3s" : "unknown";
+
+			print_string(PRINT_ANY, "mode", " mode %s ", mode_str);
+		}
+	}
+	if (tb[IFLA_IPVLAN_FLAGS]) {
+		if (RTA_PAYLOAD(tb[IFLA_IPVLAN_FLAGS]) == sizeof(__u16)) {
+			__u16 flags = rta_getattr_u16(tb[IFLA_IPVLAN_FLAGS]);
+
+			if (flags & IPVLAN_F_PRIVATE)
+				print_bool(PRINT_ANY, "private", "private ",
+					   true);
+			else if (flags & IPVLAN_F_VEPA)
+				print_bool(PRINT_ANY, "vepa", "vepa ",
+					   true);
+			else
+				print_bool(PRINT_ANY, "bridge", "bridge ",
+					   true);
+		}
+	}
+}
+
+static void ipvlan_print_help(struct link_util *lu, int argc, char **argv,
+			      FILE *f)
+{
+	print_explain(lu, f);
+}
+
+struct link_util ipvlan_link_util = {
+	.id		= "ipvlan",
+	.maxattr	= IFLA_IPVLAN_MAX,
+	.parse_opt	= ipvlan_parse_opt,
+	.print_opt	= ipvlan_print_opt,
+	.print_help	= ipvlan_print_help,
+};
+
+struct link_util ipvtap_link_util = {
+	.id		= "ipvtap",
+	.maxattr	= IFLA_IPVLAN_MAX,
+	.parse_opt	= ipvlan_parse_opt,
+	.print_opt	= ipvlan_print_opt,
+	.print_help	= ipvlan_print_help,
+};
diff --git a/ip/iplink_macvlan.c b/ip/iplink_macvlan.c
new file mode 100644
index 0000000..b966a61
--- /dev/null
+++ b/ip/iplink_macvlan.c
@@ -0,0 +1,274 @@
+/*
+ * iplink_macvlan.c	macvlan/macvtap device support
+ *
+ *              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.
+ *
+ * Authors:     Patrick McHardy <kaber@trash.net>
+ *		Arnd Bergmann <arnd@arndb.de>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <linux/if_link.h>
+#include <linux/if_ether.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+#define pfx_err(lu, ...) {               \
+	fprintf(stderr, "%s: ", lu->id); \
+	fprintf(stderr, __VA_ARGS__);    \
+	fprintf(stderr, "\n");           \
+}
+
+static void print_explain(struct link_util *lu, FILE *f)
+{
+	fprintf(f,
+		"Usage: ... %s mode MODE [flag MODE_FLAG] MODE_OPTS\n"
+		"\n"
+		"MODE: private | vepa | bridge | passthru | source\n"
+		"MODE_FLAG: null | nopromisc\n"
+		"MODE_OPTS: for mode \"source\":\n"
+		"\tmacaddr { { add | del } <macaddr> | set [ <macaddr> [ <macaddr>  ... ] ] | flush }\n",
+		lu->id
+	);
+}
+
+static void explain(struct link_util *lu)
+{
+	print_explain(lu, stderr);
+}
+
+
+static int mode_arg(const char *arg)
+{
+	fprintf(stderr,
+		"Error: argument of \"mode\" must be \"private\", \"vepa\", \"bridge\", \"passthru\" or \"source\", not \"%s\"\n",
+		arg);
+	return -1;
+}
+
+static int flag_arg(const char *arg)
+{
+	fprintf(stderr,
+		"Error: argument of \"flag\" must be \"nopromisc\" or \"null\", not \"%s\"\n",
+		arg);
+	return -1;
+}
+
+static int macvlan_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	__u32 mode = 0;
+	__u16 flags = 0;
+	__u32 mac_mode = 0;
+	int has_flags = 0;
+	char mac[ETH_ALEN];
+	struct rtattr *nmac;
+
+	while (argc > 0) {
+		if (matches(*argv, "mode") == 0) {
+			NEXT_ARG();
+
+			if (strcmp(*argv, "private") == 0)
+				mode = MACVLAN_MODE_PRIVATE;
+			else if (strcmp(*argv, "vepa") == 0)
+				mode = MACVLAN_MODE_VEPA;
+			else if (strcmp(*argv, "bridge") == 0)
+				mode = MACVLAN_MODE_BRIDGE;
+			else if (strcmp(*argv, "passthru") == 0)
+				mode = MACVLAN_MODE_PASSTHRU;
+			else if (strcmp(*argv, "source") == 0)
+				mode = MACVLAN_MODE_SOURCE;
+			else
+				return mode_arg(*argv);
+		} else if (matches(*argv, "flag") == 0) {
+			NEXT_ARG();
+
+			if (strcmp(*argv, "nopromisc") == 0)
+				flags |= MACVLAN_FLAG_NOPROMISC;
+			else if (strcmp(*argv, "null") == 0)
+				flags |= 0;
+			else
+				return flag_arg(*argv);
+
+			has_flags = 1;
+
+		} else if (matches(*argv, "macaddr") == 0) {
+			NEXT_ARG();
+
+			if (strcmp(*argv, "add") == 0) {
+				mac_mode = MACVLAN_MACADDR_ADD;
+			} else if (strcmp(*argv, "del") == 0) {
+				mac_mode = MACVLAN_MACADDR_DEL;
+			} else if (strcmp(*argv, "set") == 0) {
+				mac_mode = MACVLAN_MACADDR_SET;
+			} else if (strcmp(*argv, "flush") == 0) {
+				mac_mode = MACVLAN_MACADDR_FLUSH;
+			} else {
+				explain(lu);
+				return -1;
+			}
+
+			addattr32(n, 1024, IFLA_MACVLAN_MACADDR_MODE, mac_mode);
+
+			if (mac_mode == MACVLAN_MACADDR_ADD ||
+			    mac_mode == MACVLAN_MACADDR_DEL) {
+				NEXT_ARG();
+
+				if (ll_addr_a2n(mac, sizeof(mac),
+						*argv) != ETH_ALEN)
+					return -1;
+
+				addattr_l(n, 1024, IFLA_MACVLAN_MACADDR, &mac,
+					  ETH_ALEN);
+			}
+
+			if (mac_mode == MACVLAN_MACADDR_SET) {
+				nmac = addattr_nest(n, 1024,
+						    IFLA_MACVLAN_MACADDR_DATA);
+				while (NEXT_ARG_OK()) {
+					NEXT_ARG_FWD();
+
+					if (ll_addr_a2n(mac, sizeof(mac),
+							*argv) != ETH_ALEN) {
+						PREV_ARG();
+						break;
+					}
+
+					addattr_l(n, 1024, IFLA_MACVLAN_MACADDR,
+						  &mac, ETH_ALEN);
+				}
+				addattr_nest_end(n, nmac);
+			}
+		} else if (matches(*argv, "nopromisc") == 0) {
+			flags |= MACVLAN_FLAG_NOPROMISC;
+			has_flags = 1;
+		} else if (matches(*argv, "help") == 0) {
+			explain(lu);
+			return -1;
+		} else {
+			pfx_err(lu, "unknown option \"%s\"?", *argv);
+			explain(lu);
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (mode)
+		addattr32(n, 1024, IFLA_MACVLAN_MODE, mode);
+
+	if (has_flags) {
+		if (flags & MACVLAN_FLAG_NOPROMISC &&
+		    mode != MACVLAN_MODE_PASSTHRU) {
+			pfx_err(lu, "nopromisc flag only valid in passthru mode");
+			explain(lu);
+			return -1;
+		}
+		addattr16(n, 1024, IFLA_MACVLAN_FLAGS, flags);
+	}
+	return 0;
+}
+
+static void macvlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	__u32 mode;
+	__u16 flags;
+	__u32 count;
+	unsigned char *addr;
+	int len;
+	struct rtattr *rta;
+
+	if (!tb)
+		return;
+
+	if (!tb[IFLA_MACVLAN_MODE] ||
+	    RTA_PAYLOAD(tb[IFLA_MACVLAN_MODE]) < sizeof(__u32))
+		return;
+
+	mode = rta_getattr_u32(tb[IFLA_MACVLAN_MODE]);
+	print_string(PRINT_ANY,
+		     "mode",
+		     "mode %s ",
+		     mode == MACVLAN_MODE_PRIVATE ? "private"
+		     : mode == MACVLAN_MODE_VEPA    ? "vepa"
+		     : mode == MACVLAN_MODE_BRIDGE  ? "bridge"
+		     : mode == MACVLAN_MODE_PASSTHRU  ? "passthru"
+		     : mode == MACVLAN_MODE_SOURCE  ? "source"
+		     :				 "unknown");
+
+	if (!tb[IFLA_MACVLAN_FLAGS] ||
+	    RTA_PAYLOAD(tb[IFLA_MACVLAN_FLAGS]) < sizeof(__u16))
+		flags = 0;
+	else
+		flags = rta_getattr_u16(tb[IFLA_MACVLAN_FLAGS]);
+
+	if (flags & MACVLAN_FLAG_NOPROMISC)
+		print_bool(PRINT_ANY, "nopromisc", "nopromisc ", true);
+
+	/* in source mode, there are more options to print */
+
+	if (mode != MACVLAN_MODE_SOURCE)
+		return;
+
+	if (!tb[IFLA_MACVLAN_MACADDR_COUNT] ||
+	    RTA_PAYLOAD(tb[IFLA_MACVLAN_MACADDR_COUNT]) < sizeof(__u32))
+		return;
+
+	count = rta_getattr_u32(tb[IFLA_MACVLAN_MACADDR_COUNT]);
+	print_int(PRINT_ANY, "macaddr_count", "remotes (%d) ", count);
+
+	if (!tb[IFLA_MACVLAN_MACADDR_DATA])
+		return;
+
+	rta = RTA_DATA(tb[IFLA_MACVLAN_MACADDR_DATA]);
+	len = RTA_PAYLOAD(tb[IFLA_MACVLAN_MACADDR_DATA]);
+
+	open_json_array(PRINT_JSON, "macaddr_data");
+	for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+		if (rta->rta_type != IFLA_MACVLAN_MACADDR ||
+		    RTA_PAYLOAD(rta) < 6)
+			continue;
+		addr = RTA_DATA(rta);
+		if (is_json_context()) {
+			SPRINT_BUF(b1);
+
+			snprintf(b1, sizeof(b1),
+				 "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", addr[0],
+				 addr[1], addr[2], addr[3], addr[4], addr[5]);
+			print_string(PRINT_JSON, NULL, NULL, b1);
+		} else {
+			fprintf(f, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x ", addr[0],
+				addr[1], addr[2], addr[3], addr[4], addr[5]);
+		}
+	}
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static void macvlan_print_help(struct link_util *lu, int argc, char **argv,
+			       FILE *f)
+{
+	print_explain(lu, f);
+}
+
+struct link_util macvlan_link_util = {
+	.id		= "macvlan",
+	.maxattr	= IFLA_MACVLAN_MAX,
+	.parse_opt	= macvlan_parse_opt,
+	.print_opt	= macvlan_print_opt,
+	.print_help	= macvlan_print_help,
+};
+
+struct link_util macvtap_link_util = {
+	.id		= "macvtap",
+	.maxattr	= IFLA_MACVLAN_MAX,
+	.parse_opt	= macvlan_parse_opt,
+	.print_opt	= macvlan_print_opt,
+	.print_help	= macvlan_print_help,
+};
diff --git a/ip/iplink_netdevsim.c b/ip/iplink_netdevsim.c
new file mode 100644
index 0000000..3448608
--- /dev/null
+++ b/ip/iplink_netdevsim.c
@@ -0,0 +1,16 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void netdevsim_print_help(struct link_util *lu,
+				 int argc, char **argv, FILE *f)
+{
+	fprintf(f, "Usage: ... netdevsim\n");
+}
+
+struct link_util netdevsim_link_util = {
+	.id		= "netdevsim",
+	.print_help	= netdevsim_print_help,
+};
diff --git a/ip/iplink_nlmon.c b/ip/iplink_nlmon.c
new file mode 100644
index 0000000..6ffb910
--- /dev/null
+++ b/ip/iplink_nlmon.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void nlmon_print_help(struct link_util *lu,
+			    int argc, char **argv, FILE *f)
+{
+	fprintf(f, "Usage: ... nlmon\n");
+}
+
+struct link_util nlmon_link_util = {
+	.id		= "nlmon",
+	.print_help	= nlmon_print_help,
+};
diff --git a/ip/iplink_rmnet.c b/ip/iplink_rmnet.c
new file mode 100644
index 0000000..1d16440
--- /dev/null
+++ b/ip/iplink_rmnet.c
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * iplink_rmnet.c	RMNET device support
+ *
+ * Authors:     Daniele Palmas <dnlplm@gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... rmnet mux_id MUXID\n"
+		"\n"
+		"MUXID := 1-254\n"
+	);
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static int rmnet_parse_opt(struct link_util *lu, int argc, char **argv,
+			   struct nlmsghdr *n)
+{
+	__u16 mux_id;
+
+	while (argc > 0) {
+		if (matches(*argv, "mux_id") == 0) {
+			NEXT_ARG();
+			if (get_u16(&mux_id, *argv, 0))
+				invarg("mux_id is invalid", *argv);
+			addattr16(n, 1024, IFLA_RMNET_MUX_ID, mux_id);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "rmnet: unknown command \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void rmnet_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (!tb[IFLA_RMNET_MUX_ID] ||
+	    RTA_PAYLOAD(tb[IFLA_RMNET_MUX_ID]) < sizeof(__u16))
+		return;
+
+	print_uint(PRINT_ANY,
+		   "mux_id",
+		   "mux_id %u ",
+		   rta_getattr_u16(tb[IFLA_RMNET_MUX_ID]));
+}
+
+static void rmnet_print_help(struct link_util *lu, int argc, char **argv,
+			     FILE *f)
+{
+	print_explain(f);
+}
+
+struct link_util rmnet_link_util = {
+	.id		= "rmnet",
+	.maxattr	= IFLA_RMNET_MAX,
+	.parse_opt	= rmnet_parse_opt,
+	.print_opt	= rmnet_print_opt,
+	.print_help	= rmnet_print_help,
+};
diff --git a/ip/iplink_team.c b/ip/iplink_team.c
new file mode 100644
index 0000000..58f955a
--- /dev/null
+++ b/ip/iplink_team.c
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void team_print_help(struct link_util *lu,
+			    int argc, char **argv, FILE *f)
+{
+	fprintf(f, "Usage: ... team\n");
+}
+
+static void team_slave_print_help(struct link_util *lu,
+				  int argc, char **argv, FILE *f)
+{
+	fprintf(f, "Usage: ... team_slave\n");
+}
+
+struct link_util team_link_util = {
+	.id		= "team",
+	.print_help	= team_print_help,
+}, team_slave_link_util = {
+	.id		= "team_slave",
+	.print_help	= team_slave_print_help,
+};
diff --git a/ip/iplink_vcan.c b/ip/iplink_vcan.c
new file mode 100644
index 0000000..74a1505
--- /dev/null
+++ b/ip/iplink_vcan.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void vcan_print_help(struct link_util *lu,
+			    int argc, char **argv, FILE *f)
+{
+	fprintf(f, "Usage: ... vcan\n");
+}
+
+struct link_util vcan_link_util = {
+	.id		= "vcan",
+	.print_help	= vcan_print_help,
+};
diff --git a/ip/iplink_vlan.c b/ip/iplink_vlan.c
new file mode 100644
index 0000000..0dfb4a8
--- /dev/null
+++ b/ip/iplink_vlan.c
@@ -0,0 +1,285 @@
+/*
+ * iplink_vlan.c	VLAN device support
+ *
+ *              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.
+ *
+ * Authors:     Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_vlan.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... vlan id VLANID\n"
+		"		[ protocol VLANPROTO ]\n"
+		"		[ reorder_hdr { on | off } ]\n"
+		"		[ gvrp { on | off } ]\n"
+		"		[ mvrp { on | off } ]\n"
+		"		[ loose_binding { on | off } ]\n"
+		"		[ bridge_binding { on | off } ]\n"
+		"		[ ingress-qos-map QOS-MAP ]\n"
+		"		[ egress-qos-map QOS-MAP ]\n"
+		"\n"
+		"VLANID := 0-4095\n"
+		"VLANPROTO: [ 802.1Q | 802.1ad ]\n"
+		"QOS-MAP := [ QOS-MAP ] QOS-MAPPING\n"
+		"QOS-MAPPING := FROM:TO\n"
+	);
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static int on_off(const char *msg, const char *arg)
+{
+	fprintf(stderr, "Error: argument of \"%s\" must be \"on\" or \"off\", not \"%s\"\n", msg, arg);
+	return -1;
+}
+
+static int vlan_parse_qos_map(int *argcp, char ***argvp, struct nlmsghdr *n,
+			      int attrtype)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	struct ifla_vlan_qos_mapping m;
+	struct rtattr *tail;
+
+	tail = addattr_nest(n, 1024, attrtype);
+
+	while (argc > 0) {
+		char *colon = strchr(*argv, ':');
+
+		if (!colon)
+			break;
+		*colon = '\0';
+
+		if (get_u32(&m.from, *argv, 0))
+			return 1;
+		if (get_u32(&m.to, colon + 1, 0))
+			return 1;
+		argc--, argv++;
+
+		addattr_l(n, 1024, IFLA_VLAN_QOS_MAPPING, &m, sizeof(m));
+	}
+
+	addattr_nest_end(n, tail);
+
+	*argcp = argc;
+	*argvp = argv;
+	return 0;
+}
+
+static int vlan_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	struct ifla_vlan_flags flags = { 0 };
+	__u16 id, proto;
+
+	while (argc > 0) {
+		if (matches(*argv, "protocol") == 0) {
+			NEXT_ARG();
+			if (ll_proto_a2n(&proto, *argv))
+				invarg("protocol is invalid", *argv);
+			addattr_l(n, 1024, IFLA_VLAN_PROTOCOL, &proto, 2);
+		} else if (matches(*argv, "id") == 0) {
+			NEXT_ARG();
+			if (get_u16(&id, *argv, 0))
+				invarg("id is invalid", *argv);
+			addattr_l(n, 1024, IFLA_VLAN_ID, &id, 2);
+		} else if (matches(*argv, "reorder_hdr") == 0) {
+			NEXT_ARG();
+			flags.mask |= VLAN_FLAG_REORDER_HDR;
+			if (strcmp(*argv, "on") == 0)
+				flags.flags |= VLAN_FLAG_REORDER_HDR;
+			else if (strcmp(*argv, "off") == 0)
+				flags.flags &= ~VLAN_FLAG_REORDER_HDR;
+			else
+				return on_off("reorder_hdr", *argv);
+		} else if (matches(*argv, "gvrp") == 0) {
+			NEXT_ARG();
+			flags.mask |= VLAN_FLAG_GVRP;
+			if (strcmp(*argv, "on") == 0)
+				flags.flags |= VLAN_FLAG_GVRP;
+			else if (strcmp(*argv, "off") == 0)
+				flags.flags &= ~VLAN_FLAG_GVRP;
+			else
+				return on_off("gvrp", *argv);
+		} else if (matches(*argv, "mvrp") == 0) {
+			NEXT_ARG();
+			flags.mask |= VLAN_FLAG_MVRP;
+			if (strcmp(*argv, "on") == 0)
+				flags.flags |= VLAN_FLAG_MVRP;
+			else if (strcmp(*argv, "off") == 0)
+				flags.flags &= ~VLAN_FLAG_MVRP;
+			else
+				return on_off("mvrp", *argv);
+		} else if (matches(*argv, "loose_binding") == 0) {
+			NEXT_ARG();
+			flags.mask |= VLAN_FLAG_LOOSE_BINDING;
+			if (strcmp(*argv, "on") == 0)
+				flags.flags |= VLAN_FLAG_LOOSE_BINDING;
+			else if (strcmp(*argv, "off") == 0)
+				flags.flags &= ~VLAN_FLAG_LOOSE_BINDING;
+			else
+				return on_off("loose_binding", *argv);
+		} else if (matches(*argv, "bridge_binding") == 0) {
+			NEXT_ARG();
+			flags.mask |= VLAN_FLAG_BRIDGE_BINDING;
+			if (strcmp(*argv, "on") == 0)
+				flags.flags |= VLAN_FLAG_BRIDGE_BINDING;
+			else if (strcmp(*argv, "off") == 0)
+				flags.flags &= ~VLAN_FLAG_BRIDGE_BINDING;
+			else
+				return on_off("bridge_binding", *argv);
+		} else if (matches(*argv, "ingress-qos-map") == 0) {
+			NEXT_ARG();
+			if (vlan_parse_qos_map(&argc, &argv, n,
+					       IFLA_VLAN_INGRESS_QOS))
+				invarg("invalid ingress-qos-map", *argv);
+			continue;
+		} else if (matches(*argv, "egress-qos-map") == 0) {
+			NEXT_ARG();
+			if (vlan_parse_qos_map(&argc, &argv, n,
+					       IFLA_VLAN_EGRESS_QOS))
+				invarg("invalid egress-qos-map", *argv);
+			continue;
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "vlan: unknown command \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (flags.mask)
+		addattr_l(n, 1024, IFLA_VLAN_FLAGS, &flags, sizeof(flags));
+
+	return 0;
+}
+
+static void vlan_print_map(FILE *f,
+			   const char *name_json,
+			   const char *name_fp,
+			   struct rtattr *attr)
+{
+	struct ifla_vlan_qos_mapping *m;
+	struct rtattr *i;
+	int rem;
+
+	open_json_array(PRINT_JSON, name_json);
+	print_string(PRINT_FP, NULL, "\n      %s { ", name_fp);
+
+	rem = RTA_PAYLOAD(attr);
+	for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+		m = RTA_DATA(i);
+
+		if (is_json_context()) {
+			open_json_object(NULL);
+			print_uint(PRINT_JSON, "from", NULL, m->from);
+			print_uint(PRINT_JSON, "to", NULL, m->to);
+			close_json_object();
+		} else {
+			fprintf(f, "%u:%u ", m->from, m->to);
+		}
+	}
+
+	close_json_array(PRINT_JSON, NULL);
+	print_string(PRINT_FP, NULL, "%s ", "}");
+}
+
+static void vlan_print_flags(FILE *fp, __u32 flags)
+{
+	open_json_array(PRINT_ANY, is_json_context() ? "flags" : "<");
+#define _PF(f)	if (flags & VLAN_FLAG_##f) {				\
+		flags &= ~VLAN_FLAG_##f;				\
+		print_string(PRINT_ANY, NULL, flags ? "%s," : "%s", #f); \
+	}
+	_PF(REORDER_HDR);
+	_PF(GVRP);
+	_PF(MVRP);
+	_PF(LOOSE_BINDING);
+	_PF(BRIDGE_BINDING);
+#undef _PF
+	if (flags)
+		print_hex(PRINT_ANY, NULL, "%x", flags);
+	close_json_array(PRINT_ANY, "> ");
+}
+
+static void vlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	struct ifla_vlan_flags *flags;
+
+	SPRINT_BUF(b1);
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_VLAN_PROTOCOL] &&
+	    RTA_PAYLOAD(tb[IFLA_VLAN_PROTOCOL]) < sizeof(__u16))
+		return;
+	if (!tb[IFLA_VLAN_ID] ||
+	    RTA_PAYLOAD(tb[IFLA_VLAN_ID]) < sizeof(__u16))
+		return;
+
+	if (tb[IFLA_VLAN_PROTOCOL])
+		print_string(PRINT_ANY,
+			     "protocol",
+			     "protocol %s ",
+			     ll_proto_n2a(
+				     rta_getattr_u16(tb[IFLA_VLAN_PROTOCOL]),
+				     b1, sizeof(b1)));
+	else
+		print_string(PRINT_ANY, "protocol", "protocol %s ", "802.1q");
+
+	print_uint(PRINT_ANY,
+		   "id",
+		   "id %u ",
+		   rta_getattr_u16(tb[IFLA_VLAN_ID]));
+
+	if (tb[IFLA_VLAN_FLAGS]) {
+		if (RTA_PAYLOAD(tb[IFLA_VLAN_FLAGS]) < sizeof(*flags))
+			return;
+		flags = RTA_DATA(tb[IFLA_VLAN_FLAGS]);
+		vlan_print_flags(f, flags->flags);
+	}
+	if (tb[IFLA_VLAN_INGRESS_QOS])
+		vlan_print_map(f,
+			       "ingress_qos",
+			       "ingress-qos-map",
+			       tb[IFLA_VLAN_INGRESS_QOS]);
+	if (tb[IFLA_VLAN_EGRESS_QOS])
+		vlan_print_map(f,
+			       "egress_qos",
+			       "egress-qos-map",
+			       tb[IFLA_VLAN_EGRESS_QOS]);
+}
+
+static void vlan_print_help(struct link_util *lu, int argc, char **argv,
+			    FILE *f)
+{
+	print_explain(f);
+}
+
+struct link_util vlan_link_util = {
+	.id		= "vlan",
+	.maxattr	= IFLA_VLAN_MAX,
+	.parse_opt	= vlan_parse_opt,
+	.print_opt	= vlan_print_opt,
+	.print_help	= vlan_print_help,
+};
diff --git a/ip/iplink_vrf.c b/ip/iplink_vrf.c
new file mode 100644
index 0000000..5d20f29
--- /dev/null
+++ b/ip/iplink_vrf.c
@@ -0,0 +1,226 @@
+/* iplink_vrf.c	VRF device support
+ *
+ *              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.
+ *
+ * Authors:     Shrijeet Mukherjee <shm@cumulusnetworks.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <linux/if_link.h>
+#include <errno.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static void vrf_explain(FILE *f)
+{
+	fprintf(f, "Usage: ... vrf table TABLEID\n");
+}
+
+static void explain(void)
+{
+	vrf_explain(stderr);
+}
+
+static int vrf_parse_opt(struct link_util *lu, int argc, char **argv,
+			    struct nlmsghdr *n)
+{
+	while (argc > 0) {
+		if (matches(*argv, "table") == 0) {
+			__u32 table;
+
+			NEXT_ARG();
+
+			if (rtnl_rttable_a2n(&table, *argv))
+				invarg("invalid table ID\n", *argv);
+			addattr32(n, 1024, IFLA_VRF_TABLE, table);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "vrf: unknown option \"%s\"?\n",
+				*argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	return 0;
+}
+
+static void vrf_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_VRF_TABLE])
+		print_uint(PRINT_ANY,
+			   "table",
+			   "table %u ",
+			   rta_getattr_u32(tb[IFLA_VRF_TABLE]));
+}
+
+static void vrf_slave_print_opt(struct link_util *lu, FILE *f,
+				struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_VRF_PORT_TABLE]) {
+		print_uint(PRINT_ANY,
+			   "table",
+			   "table %u ",
+			   rta_getattr_u32(tb[IFLA_VRF_PORT_TABLE]));
+	}
+}
+
+static void vrf_print_help(struct link_util *lu, int argc, char **argv,
+			   FILE *f)
+{
+	vrf_explain(f);
+}
+
+struct link_util vrf_link_util = {
+	.id		= "vrf",
+	.maxattr	= IFLA_VRF_MAX,
+	.parse_opt	= vrf_parse_opt,
+	.print_opt	= vrf_print_opt,
+	.print_help	= vrf_print_help,
+};
+
+struct link_util vrf_slave_link_util = {
+	.id             = "vrf_slave",
+	.maxattr        = IFLA_VRF_PORT_MAX,
+	.print_opt	= vrf_slave_print_opt,
+};
+
+/* returns table id if name is a VRF device */
+__u32 ipvrf_get_table(const char *name)
+{
+	struct {
+		struct nlmsghdr		n;
+		struct ifinfomsg	i;
+		char			buf[1024];
+	} req = {
+		.n = {
+			.nlmsg_len   = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+			.nlmsg_flags = NLM_F_REQUEST,
+			.nlmsg_type  = RTM_GETLINK,
+		},
+		.i = {
+			.ifi_family  = preferred_family,
+		},
+	};
+	struct nlmsghdr *answer;
+	struct rtattr *tb[IFLA_MAX+1];
+	struct rtattr *li[IFLA_INFO_MAX+1];
+	struct rtattr *vrf_attr[IFLA_VRF_MAX + 1];
+	struct ifinfomsg *ifi;
+	__u32 tb_id = 0;
+	int len;
+
+	addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name, strlen(name) + 1);
+
+	if (rtnl_talk_suppress_rtnl_errmsg(&rth, &req.n, &answer) < 0) {
+		/* special case "default" vrf to be the main table */
+		if (errno == ENODEV && !strcmp(name, "default"))
+			if (rtnl_rttable_a2n(&tb_id, "main"))
+				fprintf(stderr,
+					"BUG: RTTable \"main\" not found.\n");
+
+		return tb_id;
+	}
+
+	ifi = NLMSG_DATA(answer);
+	len = answer->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
+	if (len < 0) {
+		fprintf(stderr, "BUG: Invalid response to link query.\n");
+		goto out;
+	}
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+	if (!tb[IFLA_LINKINFO])
+		goto out;
+
+	parse_rtattr_nested(li, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+	if (!li[IFLA_INFO_KIND] || !li[IFLA_INFO_DATA])
+		goto out;
+
+	if (strcmp(RTA_DATA(li[IFLA_INFO_KIND]), "vrf"))
+		goto out;
+
+	parse_rtattr_nested(vrf_attr, IFLA_VRF_MAX, li[IFLA_INFO_DATA]);
+	if (vrf_attr[IFLA_VRF_TABLE])
+		tb_id = rta_getattr_u32(vrf_attr[IFLA_VRF_TABLE]);
+
+	if (!tb_id)
+		fprintf(stderr, "BUG: VRF %s is missing table id\n", name);
+
+out:
+	free(answer);
+	return tb_id;
+}
+
+int name_is_vrf(const char *name)
+{
+	struct {
+		struct nlmsghdr		n;
+		struct ifinfomsg	i;
+		char			buf[1024];
+	} req = {
+		.n = {
+			.nlmsg_len   = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+			.nlmsg_flags = NLM_F_REQUEST,
+			.nlmsg_type  = RTM_GETLINK,
+		},
+		.i = {
+			.ifi_family  = preferred_family,
+		},
+	};
+	struct nlmsghdr *answer;
+	struct rtattr *tb[IFLA_MAX+1];
+	struct rtattr *li[IFLA_INFO_MAX+1];
+	struct ifinfomsg *ifi;
+	int ifindex = 0;
+	int len;
+
+	addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name, strlen(name) + 1);
+
+	if (rtnl_talk_suppress_rtnl_errmsg(&rth, &req.n, &answer) < 0)
+		return 0;
+
+	ifi = NLMSG_DATA(answer);
+	len = answer->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
+	if (len < 0) {
+		fprintf(stderr, "BUG: Invalid response to link query.\n");
+		goto out;
+	}
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+	if (!tb[IFLA_LINKINFO])
+		goto out;
+
+	parse_rtattr_nested(li, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+	if (!li[IFLA_INFO_KIND])
+		goto out;
+
+	if (strcmp(RTA_DATA(li[IFLA_INFO_KIND]), "vrf"))
+		goto out;
+
+	ifindex = ifi->ifi_index;
+out:
+	free(answer);
+	return ifindex;
+}
diff --git a/ip/iplink_vxcan.c b/ip/iplink_vxcan.c
new file mode 100644
index 0000000..8b08c9a
--- /dev/null
+++ b/ip/iplink_vxcan.c
@@ -0,0 +1,88 @@
+/*
+ * iplink_vxcan.c	vxcan device support (Virtual CAN Tunnel)
+ *
+ *		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.
+ *
+ * Author:	Oliver Hartkopp <socketcan@hartkopp.net>
+ * Based on:	link_veth.c from Pavel Emelianov <xemul@openvz.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/can/vxcan.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_usage(FILE *f)
+{
+	printf("Usage: ip link <options> type vxcan [peer <options>]\n"
+	       "To get <options> type 'ip link add help'\n");
+}
+
+static void usage(void)
+{
+	print_usage(stderr);
+}
+
+static int vxcan_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	char *type = NULL;
+	int err;
+	struct rtattr *data;
+	struct ifinfomsg *ifm, *peer_ifm;
+	unsigned int ifi_flags, ifi_change, ifi_index;
+
+	if (strcmp(argv[0], "peer") != 0) {
+		usage();
+		return -1;
+	}
+
+	ifm = NLMSG_DATA(n);
+	ifi_flags = ifm->ifi_flags;
+	ifi_change = ifm->ifi_change;
+	ifi_index = ifm->ifi_index;
+	ifm->ifi_flags = 0;
+	ifm->ifi_change = 0;
+	ifm->ifi_index = 0;
+
+	data = addattr_nest(n, 1024, VXCAN_INFO_PEER);
+
+	n->nlmsg_len += sizeof(struct ifinfomsg);
+
+	err = iplink_parse(argc - 1, argv + 1, (struct iplink_req *)n, &type);
+	if (err < 0)
+		return err;
+
+	if (type)
+		duparg("type", argv[err]);
+
+	peer_ifm = RTA_DATA(data);
+	peer_ifm->ifi_index = ifm->ifi_index;
+	peer_ifm->ifi_flags = ifm->ifi_flags;
+	peer_ifm->ifi_change = ifm->ifi_change;
+	ifm->ifi_flags = ifi_flags;
+	ifm->ifi_change = ifi_change;
+	ifm->ifi_index = ifi_index;
+
+	addattr_nest_end(n, data);
+	return argc - 1 - err;
+}
+
+static void vxcan_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
+}
+
+struct link_util vxcan_link_util = {
+	.id = "vxcan",
+	.parse_opt = vxcan_parse_opt,
+	.print_help = vxcan_print_help,
+};
diff --git a/ip/iplink_vxlan.c b/ip/iplink_vxlan.c
new file mode 100644
index 0000000..bae9d99
--- /dev/null
+++ b/ip/iplink_vxlan.c
@@ -0,0 +1,657 @@
+/*
+ * iplink_vxlan.c	VXLAN device support
+ *
+ *              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.
+ *
+ * Authors:     Stephen Hemminger <shemminger@vyatta.com
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/ip.h>
+#include <linux/if_link.h>
+#include <arpa/inet.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+#define VXLAN_ATTRSET(attrs, type) (((attrs) & (1L << (type))) != 0)
+
+static void print_explain(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... vxlan id VNI\n"
+		"		[ { group | remote } IP_ADDRESS ]\n"
+		"		[ local ADDR ]\n"
+		"		[ ttl TTL ]\n"
+		"		[ tos TOS ]\n"
+		"		[ df DF ]\n"
+		"		[ flowlabel LABEL ]\n"
+		"		[ dev PHYS_DEV ]\n"
+		"		[ dstport PORT ]\n"
+		"		[ srcport MIN MAX ]\n"
+		"		[ [no]learning ]\n"
+		"		[ [no]proxy ]\n"
+		"		[ [no]rsc ]\n"
+		"		[ [no]l2miss ]\n"
+		"		[ [no]l3miss ]\n"
+		"		[ ageing SECONDS ]\n"
+		"		[ maxaddress NUMBER ]\n"
+		"		[ [no]udpcsum ]\n"
+		"		[ [no]udp6zerocsumtx ]\n"
+		"		[ [no]udp6zerocsumrx ]\n"
+		"		[ [no]remcsumtx ] [ [no]remcsumrx ]\n"
+		"		[ [no]external ] [ gbp ] [ gpe ]\n"
+		"\n"
+		"Where:	VNI	:= 0-16777215\n"
+		"	ADDR	:= { IP_ADDRESS | any }\n"
+		"	TOS	:= { NUMBER | inherit }\n"
+		"	TTL	:= { 1..255 | auto | inherit }\n"
+		"	DF	:= { unset | set | inherit }\n"
+		"	LABEL := 0-1048575\n"
+	);
+}
+
+static void explain(void)
+{
+	print_explain(stderr);
+}
+
+static void check_duparg(__u64 *attrs, int type, const char *key,
+			 const char *argv)
+{
+	if (!VXLAN_ATTRSET(*attrs, type)) {
+		*attrs |= (1L << type);
+		return;
+	}
+	duparg2(key, argv);
+}
+
+static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	inet_prefix saddr, daddr;
+	__u32 vni = 0;
+	__u8 learning = 1;
+	__u16 dstport = 0;
+	__u8 metadata = 0;
+	__u64 attrs = 0;
+	bool set_op = (n->nlmsg_type == RTM_NEWLINK &&
+		       !(n->nlmsg_flags & NLM_F_CREATE));
+	bool selected_family = false;
+
+	saddr.family = daddr.family = AF_UNSPEC;
+
+	inet_prefix_reset(&saddr);
+	inet_prefix_reset(&daddr);
+
+	while (argc > 0) {
+		if (!matches(*argv, "id") ||
+		    !matches(*argv, "vni")) {
+			/* We will add ID attribute outside of the loop since we
+			 * need to consider metadata information as well.
+			 */
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_ID, "id", *argv);
+			if (get_u32(&vni, *argv, 0) ||
+			    vni >= 1u << 24)
+				invarg("invalid id", *argv);
+		} else if (!matches(*argv, "group")) {
+			if (is_addrtype_inet_not_multi(&daddr)) {
+				fprintf(stderr, "vxlan: both group and remote");
+				fprintf(stderr, " cannot be specified\n");
+				return -1;
+			}
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_GROUP, "group", *argv);
+			get_addr(&daddr, *argv, saddr.family);
+			if (!is_addrtype_inet_multi(&daddr))
+				invarg("invalid group address", *argv);
+		} else if (!matches(*argv, "remote")) {
+			if (is_addrtype_inet_multi(&daddr)) {
+				fprintf(stderr, "vxlan: both group and remote");
+				fprintf(stderr, " cannot be specified\n");
+				return -1;
+			}
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_GROUP, "remote", *argv);
+			get_addr(&daddr, *argv, saddr.family);
+			if (!is_addrtype_inet_not_multi(&daddr))
+				invarg("invalid remote address", *argv);
+		} else if (!matches(*argv, "local")) {
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_LOCAL, "local", *argv);
+			get_addr(&saddr, *argv, daddr.family);
+			if (!is_addrtype_inet_not_multi(&saddr))
+				invarg("invalid local address", *argv);
+		} else if (!matches(*argv, "dev")) {
+			unsigned int link;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_LINK, "dev", *argv);
+			link = ll_name_to_index(*argv);
+			if (!link)
+				exit(nodev(*argv));
+			addattr32(n, 1024, IFLA_VXLAN_LINK, link);
+		} else if (!matches(*argv, "ttl") ||
+			   !matches(*argv, "hoplimit")) {
+			unsigned int uval;
+			__u8 ttl = 0;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_TTL, "ttl", *argv);
+			if (strcmp(*argv, "inherit") == 0) {
+				addattr(n, 1024, IFLA_VXLAN_TTL_INHERIT);
+			} else if (strcmp(*argv, "auto") == 0) {
+				addattr8(n, 1024, IFLA_VXLAN_TTL, ttl);
+			} else {
+				if (get_unsigned(&uval, *argv, 0))
+					invarg("invalid TTL", *argv);
+				if (uval > 255)
+					invarg("TTL must be <= 255", *argv);
+				ttl = uval;
+				addattr8(n, 1024, IFLA_VXLAN_TTL, ttl);
+			}
+		} else if (!matches(*argv, "tos") ||
+			   !matches(*argv, "dsfield")) {
+			__u32 uval;
+			__u8 tos;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_TOS, "tos", *argv);
+			if (strcmp(*argv, "inherit") != 0) {
+				if (rtnl_dsfield_a2n(&uval, *argv))
+					invarg("bad TOS value", *argv);
+				tos = uval;
+			} else
+				tos = 1;
+			addattr8(n, 1024, IFLA_VXLAN_TOS, tos);
+		} else if (!matches(*argv, "df")) {
+			enum ifla_vxlan_df df;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_DF, "df", *argv);
+			if (strcmp(*argv, "unset") == 0)
+				df = VXLAN_DF_UNSET;
+			else if (strcmp(*argv, "set") == 0)
+				df = VXLAN_DF_SET;
+			else if (strcmp(*argv, "inherit") == 0)
+				df = VXLAN_DF_INHERIT;
+			else
+				invarg("DF must be 'unset', 'set' or 'inherit'",
+				       *argv);
+
+			addattr8(n, 1024, IFLA_VXLAN_DF, df);
+		} else if (!matches(*argv, "label") ||
+			   !matches(*argv, "flowlabel")) {
+			__u32 uval;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_LABEL, "flowlabel",
+				     *argv);
+			if (get_u32(&uval, *argv, 0) ||
+			    (uval & ~LABEL_MAX_MASK))
+				invarg("invalid flowlabel", *argv);
+			addattr32(n, 1024, IFLA_VXLAN_LABEL, htonl(uval));
+		} else if (!matches(*argv, "ageing")) {
+			__u32 age;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_AGEING, "ageing",
+				     *argv);
+			if (strcmp(*argv, "none") == 0)
+				age = 0;
+			else if (get_u32(&age, *argv, 0))
+				invarg("ageing timer", *argv);
+			addattr32(n, 1024, IFLA_VXLAN_AGEING, age);
+		} else if (!matches(*argv, "maxaddress")) {
+			__u32 maxaddr;
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_LIMIT,
+				     "maxaddress", *argv);
+			if (strcmp(*argv, "unlimited") == 0)
+				maxaddr = 0;
+			else if (get_u32(&maxaddr, *argv, 0))
+				invarg("max addresses", *argv);
+			addattr32(n, 1024, IFLA_VXLAN_LIMIT, maxaddr);
+		} else if (!matches(*argv, "port") ||
+			   !matches(*argv, "srcport")) {
+			struct ifla_vxlan_port_range range = { 0, 0 };
+
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_PORT_RANGE, "srcport",
+				     *argv);
+			if (get_be16(&range.low, *argv, 0))
+				invarg("min port", *argv);
+			NEXT_ARG();
+			if (get_be16(&range.high, *argv, 0))
+				invarg("max port", *argv);
+			if (range.low || range.high) {
+				addattr_l(n, 1024, IFLA_VXLAN_PORT_RANGE,
+					  &range, sizeof(range));
+			}
+		} else if (!matches(*argv, "dstport")) {
+			NEXT_ARG();
+			check_duparg(&attrs, IFLA_VXLAN_PORT, "dstport", *argv);
+			if (get_u16(&dstport, *argv, 0))
+				invarg("dst port", *argv);
+		} else if (!matches(*argv, "nolearning")) {
+			check_duparg(&attrs, IFLA_VXLAN_LEARNING, *argv, *argv);
+			learning = 0;
+		} else if (!matches(*argv, "learning")) {
+			check_duparg(&attrs, IFLA_VXLAN_LEARNING, *argv, *argv);
+			learning = 1;
+		} else if (!matches(*argv, "noproxy")) {
+			check_duparg(&attrs, IFLA_VXLAN_PROXY, *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_PROXY, 0);
+		} else if (!matches(*argv, "proxy")) {
+			check_duparg(&attrs, IFLA_VXLAN_PROXY, *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_PROXY, 1);
+		} else if (!matches(*argv, "norsc")) {
+			check_duparg(&attrs, IFLA_VXLAN_RSC, *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_RSC, 0);
+		} else if (!matches(*argv, "rsc")) {
+			check_duparg(&attrs, IFLA_VXLAN_RSC, *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_RSC, 1);
+		} else if (!matches(*argv, "nol2miss")) {
+			check_duparg(&attrs, IFLA_VXLAN_L2MISS, *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_L2MISS, 0);
+		} else if (!matches(*argv, "l2miss")) {
+			check_duparg(&attrs, IFLA_VXLAN_L2MISS, *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_L2MISS, 1);
+		} else if (!matches(*argv, "nol3miss")) {
+			check_duparg(&attrs, IFLA_VXLAN_L3MISS, *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_L3MISS, 0);
+		} else if (!matches(*argv, "l3miss")) {
+			check_duparg(&attrs, IFLA_VXLAN_L3MISS, *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_L3MISS, 1);
+		} else if (!matches(*argv, "udpcsum")) {
+			check_duparg(&attrs, IFLA_VXLAN_UDP_CSUM, *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_UDP_CSUM, 1);
+		} else if (!matches(*argv, "noudpcsum")) {
+			check_duparg(&attrs, IFLA_VXLAN_UDP_CSUM, *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_UDP_CSUM, 0);
+		} else if (!matches(*argv, "udp6zerocsumtx")) {
+			check_duparg(&attrs, IFLA_VXLAN_UDP_ZERO_CSUM6_TX,
+				     *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_TX, 1);
+		} else if (!matches(*argv, "noudp6zerocsumtx")) {
+			check_duparg(&attrs, IFLA_VXLAN_UDP_ZERO_CSUM6_TX,
+				     *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_TX, 0);
+		} else if (!matches(*argv, "udp6zerocsumrx")) {
+			check_duparg(&attrs, IFLA_VXLAN_UDP_ZERO_CSUM6_RX,
+				     *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_RX, 1);
+		} else if (!matches(*argv, "noudp6zerocsumrx")) {
+			check_duparg(&attrs, IFLA_VXLAN_UDP_ZERO_CSUM6_RX,
+				     *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_RX, 0);
+		} else if (!matches(*argv, "remcsumtx")) {
+			check_duparg(&attrs, IFLA_VXLAN_REMCSUM_TX,
+				     *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_REMCSUM_TX, 1);
+		} else if (!matches(*argv, "noremcsumtx")) {
+			check_duparg(&attrs, IFLA_VXLAN_REMCSUM_TX,
+				     *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_REMCSUM_TX, 0);
+		} else if (!matches(*argv, "remcsumrx")) {
+			check_duparg(&attrs, IFLA_VXLAN_REMCSUM_RX,
+				     *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_REMCSUM_RX, 1);
+		} else if (!matches(*argv, "noremcsumrx")) {
+			check_duparg(&attrs, IFLA_VXLAN_REMCSUM_RX,
+				     *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_REMCSUM_RX, 0);
+		} else if (!matches(*argv, "external")) {
+			check_duparg(&attrs, IFLA_VXLAN_COLLECT_METADATA,
+				     *argv, *argv);
+			metadata = 1;
+			learning = 0;
+			/* we will add LEARNING attribute outside of the loop */
+			addattr8(n, 1024, IFLA_VXLAN_COLLECT_METADATA,
+				 metadata);
+		} else if (!matches(*argv, "noexternal")) {
+			check_duparg(&attrs, IFLA_VXLAN_COLLECT_METADATA,
+				     *argv, *argv);
+			metadata = 0;
+			addattr8(n, 1024, IFLA_VXLAN_COLLECT_METADATA,
+				 metadata);
+		} else if (!matches(*argv, "gbp")) {
+			check_duparg(&attrs, IFLA_VXLAN_GBP, *argv, *argv);
+			addattr_l(n, 1024, IFLA_VXLAN_GBP, NULL, 0);
+		} else if (!matches(*argv, "gpe")) {
+			check_duparg(&attrs, IFLA_VXLAN_GPE, *argv, *argv);
+			addattr_l(n, 1024, IFLA_VXLAN_GPE, NULL, 0);
+		} else if (matches(*argv, "help") == 0) {
+			explain();
+			return -1;
+		} else {
+			fprintf(stderr, "vxlan: unknown command \"%s\"?\n", *argv);
+			explain();
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (metadata && VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID)) {
+		fprintf(stderr, "vxlan: both 'external' and vni cannot be specified\n");
+		return -1;
+	}
+
+	if (!metadata && !VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID) && !set_op) {
+		fprintf(stderr, "vxlan: missing virtual network identifier\n");
+		return -1;
+	}
+
+	if (is_addrtype_inet_multi(&daddr) &&
+	    !VXLAN_ATTRSET(attrs, IFLA_VXLAN_LINK)) {
+		fprintf(stderr, "vxlan: 'group' requires 'dev' to be specified\n");
+		return -1;
+	}
+
+	if (!VXLAN_ATTRSET(attrs, IFLA_VXLAN_PORT) &&
+	    VXLAN_ATTRSET(attrs, IFLA_VXLAN_GPE)) {
+		dstport = 4790;
+	} else if (!VXLAN_ATTRSET(attrs, IFLA_VXLAN_PORT) && !set_op) {
+		fprintf(stderr, "vxlan: destination port not specified\n"
+			"Will use Linux kernel default (non-standard value)\n");
+		fprintf(stderr,
+			"Use 'dstport 4789' to get the IANA assigned value\n"
+			"Use 'dstport 0' to get default and quiet this message\n");
+	}
+
+	if (VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID))
+		addattr32(n, 1024, IFLA_VXLAN_ID, vni);
+
+	if (is_addrtype_inet(&saddr)) {
+		int type = (saddr.family == AF_INET) ? IFLA_VXLAN_LOCAL
+						     : IFLA_VXLAN_LOCAL6;
+		addattr_l(n, 1024, type, saddr.data, saddr.bytelen);
+		selected_family = true;
+	}
+
+	if (is_addrtype_inet(&daddr)) {
+		int type = (daddr.family == AF_INET) ? IFLA_VXLAN_GROUP
+						     : IFLA_VXLAN_GROUP6;
+		addattr_l(n, 1024, type, daddr.data, daddr.bytelen);
+		selected_family = true;
+	}
+
+	if (!selected_family) {
+		if (preferred_family == AF_INET) {
+			get_addr(&daddr, "default", AF_INET);
+			addattr_l(n, 1024, IFLA_VXLAN_GROUP,
+				  daddr.data, daddr.bytelen);
+		} else if (preferred_family == AF_INET6) {
+			get_addr(&daddr, "default", AF_INET6);
+			addattr_l(n, 1024, IFLA_VXLAN_GROUP6,
+				  daddr.data, daddr.bytelen);
+		}
+	}
+
+	if (!set_op || VXLAN_ATTRSET(attrs, IFLA_VXLAN_LEARNING))
+		addattr8(n, 1024, IFLA_VXLAN_LEARNING, learning);
+
+	if (dstport)
+		addattr16(n, 1024, IFLA_VXLAN_PORT, htons(dstport));
+
+	return 0;
+}
+
+static void vxlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	__u32 vni;
+	__u8 ttl = 0;
+	__u8 tos = 0;
+	__u32 maxaddr;
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_VXLAN_COLLECT_METADATA] &&
+	    rta_getattr_u8(tb[IFLA_VXLAN_COLLECT_METADATA])) {
+		print_bool(PRINT_ANY, "external", "external ", true);
+		return;
+	}
+
+	if (!tb[IFLA_VXLAN_ID] ||
+	    RTA_PAYLOAD(tb[IFLA_VXLAN_ID]) < sizeof(__u32))
+		return;
+
+	vni = rta_getattr_u32(tb[IFLA_VXLAN_ID]);
+	print_uint(PRINT_ANY, "id", "id %u ", vni);
+
+	if (tb[IFLA_VXLAN_GROUP]) {
+		__be32 addr = rta_getattr_u32(tb[IFLA_VXLAN_GROUP]);
+
+		if (addr) {
+			if (IN_MULTICAST(ntohl(addr)))
+				print_string(PRINT_ANY,
+					     "group",
+					     "group %s ",
+					     format_host(AF_INET, 4, &addr));
+			else
+				print_string(PRINT_ANY,
+					     "remote",
+					     "remote %s ",
+					     format_host(AF_INET, 4, &addr));
+		}
+	} else if (tb[IFLA_VXLAN_GROUP6]) {
+		struct in6_addr addr;
+
+		memcpy(&addr, RTA_DATA(tb[IFLA_VXLAN_GROUP6]), sizeof(struct in6_addr));
+		if (!IN6_IS_ADDR_UNSPECIFIED(&addr)) {
+			if (IN6_IS_ADDR_MULTICAST(&addr))
+				print_string(PRINT_ANY,
+					     "group6",
+					     "group %s ",
+					     format_host(AF_INET6,
+							 sizeof(struct in6_addr),
+							 &addr));
+			else
+				print_string(PRINT_ANY,
+					     "remote6",
+					     "remote %s ",
+					     format_host(AF_INET6,
+							 sizeof(struct in6_addr),
+							 &addr));
+		}
+	}
+
+	if (tb[IFLA_VXLAN_LOCAL]) {
+		__be32 addr = rta_getattr_u32(tb[IFLA_VXLAN_LOCAL]);
+
+		if (addr)
+			print_string(PRINT_ANY,
+				     "local",
+				     "local %s ",
+				     format_host(AF_INET, 4, &addr));
+	} else if (tb[IFLA_VXLAN_LOCAL6]) {
+		struct in6_addr addr;
+
+		memcpy(&addr, RTA_DATA(tb[IFLA_VXLAN_LOCAL6]), sizeof(struct in6_addr));
+		if (!IN6_IS_ADDR_UNSPECIFIED(&addr))
+			print_string(PRINT_ANY,
+				     "local6",
+				     "local %s ",
+				     format_host(AF_INET6,
+						 sizeof(struct in6_addr),
+						 &addr));
+	}
+
+	if (tb[IFLA_VXLAN_LINK]) {
+		unsigned int link = rta_getattr_u32(tb[IFLA_VXLAN_LINK]);
+
+		if (link) {
+			print_string(PRINT_ANY, "link", "dev %s ",
+				     ll_index_to_name(link));
+		}
+	}
+
+	if (tb[IFLA_VXLAN_PORT_RANGE]) {
+		const struct ifla_vxlan_port_range *r
+			= RTA_DATA(tb[IFLA_VXLAN_PORT_RANGE]);
+		if (is_json_context()) {
+			open_json_object("port_range");
+			print_uint(PRINT_JSON, "low", NULL, ntohs(r->low));
+			print_uint(PRINT_JSON, "high", NULL, ntohs(r->high));
+			close_json_object();
+		} else {
+			fprintf(f, "srcport %u %u ",
+				ntohs(r->low), ntohs(r->high));
+		}
+	}
+
+	if (tb[IFLA_VXLAN_PORT])
+		print_uint(PRINT_ANY,
+			   "port",
+			   "dstport %u ",
+			   rta_getattr_be16(tb[IFLA_VXLAN_PORT]));
+
+	if (tb[IFLA_VXLAN_LEARNING]) {
+		__u8 learning = rta_getattr_u8(tb[IFLA_VXLAN_LEARNING]);
+
+		print_bool(PRINT_JSON, "learning", NULL, learning);
+		if (!learning)
+			print_bool(PRINT_FP, NULL, "nolearning ", true);
+	}
+
+	if (tb[IFLA_VXLAN_PROXY] && rta_getattr_u8(tb[IFLA_VXLAN_PROXY]))
+		print_bool(PRINT_ANY, "proxy", "proxy ", true);
+
+	if (tb[IFLA_VXLAN_RSC] && rta_getattr_u8(tb[IFLA_VXLAN_RSC]))
+		print_bool(PRINT_ANY, "rsc", "rsc ", true);
+
+	if (tb[IFLA_VXLAN_L2MISS] && rta_getattr_u8(tb[IFLA_VXLAN_L2MISS]))
+		print_bool(PRINT_ANY, "l2miss", "l2miss ", true);
+
+	if (tb[IFLA_VXLAN_L3MISS] && rta_getattr_u8(tb[IFLA_VXLAN_L3MISS]))
+		print_bool(PRINT_ANY, "l3miss", "l3miss ", true);
+
+	if (tb[IFLA_VXLAN_TOS])
+		tos = rta_getattr_u8(tb[IFLA_VXLAN_TOS]);
+	if (tos) {
+		if (is_json_context() || tos != 1)
+			print_0xhex(PRINT_ANY, "tos", "tos %#llx ", tos);
+		else
+			print_string(PRINT_FP, NULL, "tos %s ", "inherit");
+	}
+
+	if (tb[IFLA_VXLAN_TTL_INHERIT] &&
+	    rta_getattr_u8(tb[IFLA_VXLAN_TTL_INHERIT])) {
+		print_string(PRINT_FP, NULL, "ttl %s ", "inherit");
+	} else if (tb[IFLA_VXLAN_TTL]) {
+		ttl = rta_getattr_u8(tb[IFLA_VXLAN_TTL]);
+		if (is_json_context() || ttl)
+			print_uint(PRINT_ANY, "ttl", "ttl %u ", ttl);
+		else
+			print_string(PRINT_FP, NULL, "ttl %s ", "auto");
+	}
+
+	if (tb[IFLA_VXLAN_DF]) {
+		enum ifla_vxlan_df df = rta_getattr_u8(tb[IFLA_VXLAN_DF]);
+
+		if (df == VXLAN_DF_UNSET)
+			print_string(PRINT_JSON, "df", "df %s ", "unset");
+		else if (df == VXLAN_DF_SET)
+			print_string(PRINT_ANY, "df", "df %s ", "set");
+		else if (df == VXLAN_DF_INHERIT)
+			print_string(PRINT_ANY, "df", "df %s ", "inherit");
+	}
+
+	if (tb[IFLA_VXLAN_LABEL]) {
+		__u32 label = rta_getattr_u32(tb[IFLA_VXLAN_LABEL]);
+
+		if (label)
+			print_0xhex(PRINT_ANY, "label",
+				    "flowlabel %#llx ", ntohl(label));
+	}
+
+	if (tb[IFLA_VXLAN_AGEING]) {
+		__u32 age = rta_getattr_u32(tb[IFLA_VXLAN_AGEING]);
+
+		if (age == 0)
+			print_uint(PRINT_ANY, "ageing", "ageing none ", 0);
+		else
+			print_uint(PRINT_ANY, "ageing", "ageing %u ", age);
+	}
+
+	if (tb[IFLA_VXLAN_LIMIT] &&
+	    ((maxaddr = rta_getattr_u32(tb[IFLA_VXLAN_LIMIT])) != 0))
+		print_uint(PRINT_ANY, "limit", "maxaddr %u ", maxaddr);
+
+	if (tb[IFLA_VXLAN_UDP_CSUM]) {
+		__u8 udp_csum = rta_getattr_u8(tb[IFLA_VXLAN_UDP_CSUM]);
+
+		if (is_json_context()) {
+			print_bool(PRINT_ANY, "udp_csum", NULL, udp_csum);
+		} else {
+			if (!udp_csum)
+				fputs("no", f);
+			fputs("udpcsum ", f);
+		}
+	}
+
+	if (tb[IFLA_VXLAN_UDP_ZERO_CSUM6_TX]) {
+		__u8 csum6 = rta_getattr_u8(tb[IFLA_VXLAN_UDP_ZERO_CSUM6_TX]);
+
+		if (is_json_context()) {
+			print_bool(PRINT_ANY,
+				   "udp_zero_csum6_tx", NULL, csum6);
+		} else {
+			if (!csum6)
+				fputs("no", f);
+			fputs("udp6zerocsumtx ", f);
+		}
+	}
+
+	if (tb[IFLA_VXLAN_UDP_ZERO_CSUM6_RX]) {
+		__u8 csum6 = rta_getattr_u8(tb[IFLA_VXLAN_UDP_ZERO_CSUM6_RX]);
+
+		if (is_json_context()) {
+			print_bool(PRINT_ANY,
+				   "udp_zero_csum6_rx",
+				   NULL,
+				   csum6);
+		} else {
+			if (!csum6)
+				fputs("no", f);
+			fputs("udp6zerocsumrx ", f);
+		}
+	}
+
+	if (tb[IFLA_VXLAN_REMCSUM_TX] &&
+	    rta_getattr_u8(tb[IFLA_VXLAN_REMCSUM_TX]))
+		print_bool(PRINT_ANY, "remcsum_tx", "remcsumtx ", true);
+
+	if (tb[IFLA_VXLAN_REMCSUM_RX] &&
+	    rta_getattr_u8(tb[IFLA_VXLAN_REMCSUM_RX]))
+		print_bool(PRINT_ANY, "remcsum_rx", "remcsumrx ", true);
+
+	if (tb[IFLA_VXLAN_GBP])
+		print_bool(PRINT_ANY, "gbp", "gbp ", true);
+	if (tb[IFLA_VXLAN_GPE])
+		print_bool(PRINT_ANY, "gpe", "gpe ", true);
+}
+
+static void vxlan_print_help(struct link_util *lu, int argc, char **argv,
+			     FILE *f)
+{
+	print_explain(f);
+}
+
+struct link_util vxlan_link_util = {
+	.id		= "vxlan",
+	.maxattr	= IFLA_VXLAN_MAX,
+	.parse_opt	= vxlan_parse_opt,
+	.print_opt	= vxlan_print_opt,
+	.print_help	= vxlan_print_help,
+};
diff --git a/ip/iplink_xdp.c b/ip/iplink_xdp.c
new file mode 100644
index 0000000..4a490bc
--- /dev/null
+++ b/ip/iplink_xdp.c
@@ -0,0 +1,199 @@
+/*
+ * iplink_xdp.c XDP program loader
+ *
+ *              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.
+ *
+ * Authors:     Daniel Borkmann <daniel@iogearbox.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <linux/bpf.h>
+
+#include "bpf_util.h"
+#include "utils.h"
+#include "ip_common.h"
+
+extern int force;
+
+struct xdp_req {
+	struct iplink_req *req;
+	__u32 flags;
+};
+
+static void xdp_ebpf_cb(void *raw, int fd, const char *annotation)
+{
+	struct xdp_req *xdp = raw;
+	struct iplink_req *req = xdp->req;
+	struct rtattr *xdp_attr;
+
+	xdp_attr = addattr_nest(&req->n, sizeof(*req), IFLA_XDP);
+	addattr32(&req->n, sizeof(*req), IFLA_XDP_FD, fd);
+	if (xdp->flags)
+		addattr32(&req->n, sizeof(*req), IFLA_XDP_FLAGS, xdp->flags);
+	addattr_nest_end(&req->n, xdp_attr);
+}
+
+static const struct bpf_cfg_ops bpf_cb_ops = {
+	.ebpf_cb = xdp_ebpf_cb,
+};
+
+static int xdp_delete(struct xdp_req *xdp)
+{
+	xdp_ebpf_cb(xdp, -1, NULL);
+	return 0;
+}
+
+int xdp_parse(int *argc, char ***argv, struct iplink_req *req,
+	      const char *ifname, bool generic, bool drv, bool offload)
+{
+	struct bpf_cfg_in cfg = {
+		.type = BPF_PROG_TYPE_XDP,
+		.argc = *argc,
+		.argv = *argv,
+	};
+	struct xdp_req xdp = {
+		.req = req,
+	};
+
+	if (offload) {
+		int ifindex = ll_name_to_index(ifname);
+
+		if (!ifindex)
+			incomplete_command();
+		cfg.ifindex = ifindex;
+	}
+
+	if (!force)
+		xdp.flags |= XDP_FLAGS_UPDATE_IF_NOEXIST;
+	if (generic)
+		xdp.flags |= XDP_FLAGS_SKB_MODE;
+	if (drv)
+		xdp.flags |= XDP_FLAGS_DRV_MODE;
+	if (offload)
+		xdp.flags |= XDP_FLAGS_HW_MODE;
+
+	if (*argc == 1) {
+		if (strcmp(**argv, "none") == 0 ||
+		    strcmp(**argv, "off") == 0)
+			return xdp_delete(&xdp);
+	}
+
+	if (bpf_parse_and_load_common(&cfg, &bpf_cb_ops, &xdp))
+		return -1;
+
+	*argc = cfg.argc;
+	*argv = cfg.argv;
+	return 0;
+}
+
+static void xdp_dump_json_one(struct rtattr *tb[IFLA_XDP_MAX + 1], __u32 attr,
+			      __u8 mode)
+{
+	if (!tb[attr])
+		return;
+
+	open_json_object(NULL);
+	print_uint(PRINT_JSON, "mode", NULL, mode);
+	bpf_dump_prog_info(NULL, rta_getattr_u32(tb[attr]));
+	close_json_object();
+}
+
+static void xdp_dump_json(struct rtattr *tb[IFLA_XDP_MAX + 1])
+{
+	__u32 prog_id = 0;
+	__u8 mode;
+
+	mode = rta_getattr_u8(tb[IFLA_XDP_ATTACHED]);
+	if (tb[IFLA_XDP_PROG_ID])
+		prog_id = rta_getattr_u32(tb[IFLA_XDP_PROG_ID]);
+
+	open_json_object("xdp");
+	print_uint(PRINT_JSON, "mode", NULL, mode);
+	if (prog_id)
+		bpf_dump_prog_info(NULL, prog_id);
+
+	open_json_array(PRINT_JSON, "attached");
+	if (tb[IFLA_XDP_SKB_PROG_ID] ||
+	    tb[IFLA_XDP_DRV_PROG_ID] ||
+	    tb[IFLA_XDP_HW_PROG_ID]) {
+		xdp_dump_json_one(tb, IFLA_XDP_SKB_PROG_ID, XDP_ATTACHED_SKB);
+		xdp_dump_json_one(tb, IFLA_XDP_DRV_PROG_ID, XDP_ATTACHED_DRV);
+		xdp_dump_json_one(tb, IFLA_XDP_HW_PROG_ID, XDP_ATTACHED_HW);
+	} else if (tb[IFLA_XDP_PROG_ID]) {
+		/* Older kernel - use IFLA_XDP_PROG_ID */
+		xdp_dump_json_one(tb, IFLA_XDP_PROG_ID, mode);
+	}
+	close_json_array(PRINT_JSON, NULL);
+
+	close_json_object();
+}
+
+static void xdp_dump_prog_one(FILE *fp, struct rtattr *tb[IFLA_XDP_MAX + 1],
+			      __u32 attr, bool link, bool details,
+			      const char *pfx)
+{
+	__u32 prog_id;
+
+	if (!tb[attr])
+		return;
+
+	prog_id = rta_getattr_u32(tb[attr]);
+	if (!details) {
+		if (prog_id && !link && attr == IFLA_XDP_PROG_ID)
+			fprintf(fp, "/id:%u", prog_id);
+		return;
+	}
+
+	if (prog_id) {
+		fprintf(fp, "%s    prog/xdp%s ", _SL_, pfx);
+		bpf_dump_prog_info(fp, prog_id);
+	}
+}
+
+void xdp_dump(FILE *fp, struct rtattr *xdp, bool link, bool details)
+{
+	struct rtattr *tb[IFLA_XDP_MAX + 1];
+	__u8 mode;
+
+	parse_rtattr_nested(tb, IFLA_XDP_MAX, xdp);
+
+	if (!tb[IFLA_XDP_ATTACHED])
+		return;
+
+	mode = rta_getattr_u8(tb[IFLA_XDP_ATTACHED]);
+	if (mode == XDP_ATTACHED_NONE)
+		return;
+	else if (is_json_context())
+		return details ? (void)0 : xdp_dump_json(tb);
+	else if (details && link)
+		/* don't print mode */;
+	else if (mode == XDP_ATTACHED_DRV)
+		fprintf(fp, "xdp");
+	else if (mode == XDP_ATTACHED_SKB)
+		fprintf(fp, "xdpgeneric");
+	else if (mode == XDP_ATTACHED_HW)
+		fprintf(fp, "xdpoffload");
+	else if (mode == XDP_ATTACHED_MULTI)
+		fprintf(fp, "xdpmulti");
+	else
+		fprintf(fp, "xdp[%u]", mode);
+
+	xdp_dump_prog_one(fp, tb, IFLA_XDP_PROG_ID, link, details, "");
+
+	if (mode == XDP_ATTACHED_MULTI) {
+		xdp_dump_prog_one(fp, tb, IFLA_XDP_SKB_PROG_ID, link, details,
+				  "generic");
+		xdp_dump_prog_one(fp, tb, IFLA_XDP_DRV_PROG_ID, link, details,
+				  "drv");
+		xdp_dump_prog_one(fp, tb, IFLA_XDP_HW_PROG_ID, link, details,
+				  "offload");
+	}
+
+	if (!details || !link)
+		fprintf(fp, " ");
+}
diff --git a/ip/iplink_xstats.c b/ip/iplink_xstats.c
new file mode 100644
index 0000000..c64e688
--- /dev/null
+++ b/ip/iplink_xstats.c
@@ -0,0 +1,82 @@
+/*
+ * iplink_stats.c       Extended statistics commands
+ *
+ *              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.
+ *
+ * Authors:     Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/if_link.h>
+#include <netinet/ether.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_explain(FILE *f)
+{
+	fprintf(f, "Usage: ... xstats type TYPE [ ARGS ]\n");
+}
+
+int iplink_ifla_xstats(int argc, char **argv)
+{
+	struct link_util *lu = NULL;
+	__u32 filt_mask;
+
+	if (!argc) {
+		fprintf(stderr, "xstats: missing argument\n");
+		return -1;
+	}
+
+	if (matches(*argv, "type") == 0) {
+		NEXT_ARG();
+		lu = get_link_kind(*argv);
+		if (!lu)
+			invarg("invalid type", *argv);
+	} else if (matches(*argv, "help") == 0) {
+		print_explain(stdout);
+		return 0;
+	} else {
+		invarg("unknown argument", *argv);
+	}
+
+	if (!lu) {
+		print_explain(stderr);
+		return -1;
+	}
+
+	if (!lu->print_ifla_xstats) {
+		fprintf(stderr, "xstats: link type %s doesn't support xstats\n",
+			lu->id);
+		return -1;
+	}
+
+	if (lu->parse_ifla_xstats &&
+	    lu->parse_ifla_xstats(lu, argc-1, argv+1))
+		return -1;
+
+	if (strstr(lu->id, "_slave"))
+		filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS_SLAVE);
+	else
+		filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS);
+
+	if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) {
+		perror("Cannont send dump request");
+		return -1;
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&rth, lu->print_ifla_xstats, stdout) < 0) {
+		delete_json_obj();
+		fprintf(stderr, "Dump terminated\n");
+		return -1;
+	}
+	delete_json_obj();
+
+	return 0;
+}
diff --git a/ip/ipmacsec.c b/ip/ipmacsec.c
new file mode 100644
index 0000000..ad6ad7d
--- /dev/null
+++ b/ip/ipmacsec.c
@@ -0,0 +1,1381 @@
+/*
+ * ipmacsec.c		"ip macsec".
+ *
+ *		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.
+ *
+ * Authors:	Sabrina Dubroca <sd@queasysnail.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/genetlink.h>
+#include <linux/if_ether.h>
+#include <linux/if_macsec.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "ll_map.h"
+#include "libgenl.h"
+
+static const char * const values_on_off[] = { "off", "on" };
+
+static const char * const validate_str[] = {
+	[MACSEC_VALIDATE_DISABLED] = "disabled",
+	[MACSEC_VALIDATE_CHECK] = "check",
+	[MACSEC_VALIDATE_STRICT] = "strict",
+};
+
+struct sci {
+	__u64 sci;
+	__u16 port;
+	char abuf[6];
+};
+
+struct sa_desc {
+	__u8 an;
+	__u32 pn;
+	__u8 key_id[MACSEC_KEYID_LEN];
+	__u32 key_len;
+	__u8 key[MACSEC_MAX_KEY_LEN];
+	__u8 active;
+};
+
+struct cipher_args {
+	__u64 id;
+	__u8 icv_len;
+};
+
+struct txsc_desc {
+	int ifindex;
+	__u64 sci;
+	__be16 port;
+	struct cipher_args cipher;
+	__u32 window;
+	enum macsec_validation_type validate;
+	__u8 encoding_sa;
+};
+
+struct rxsc_desc {
+	int ifindex;
+	__u64 sci;
+	__u8 active;
+};
+
+#define MACSEC_BUFLEN 1024
+
+
+/* netlink socket */
+static struct rtnl_handle genl_rth;
+static int genl_family = -1;
+
+#define MACSEC_GENL_REQ(_req, _bufsiz, _cmd, _flags) \
+	GENL_REQUEST(_req, _bufsiz, genl_family, 0, MACSEC_GENL_VERSION, \
+		     _cmd, _flags)
+
+
+static void ipmacsec_usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip macsec add DEV tx sa { 0..3 } [ OPTS ] key ID KEY\n"
+		"       ip macsec set DEV tx sa { 0..3 } [ OPTS ]\n"
+		"       ip macsec del DEV tx sa { 0..3 }\n"
+		"       ip macsec add DEV rx SCI [ on | off ]\n"
+		"       ip macsec set DEV rx SCI [ on | off ]\n"
+		"       ip macsec del DEV rx SCI\n"
+		"       ip macsec add DEV rx SCI sa { 0..3 } [ OPTS ] key ID KEY\n"
+		"       ip macsec set DEV rx SCI sa { 0..3 } [ OPTS ]\n"
+		"       ip macsec del DEV rx SCI sa { 0..3 }\n"
+		"       ip macsec show\n"
+		"       ip macsec show DEV\n"
+		"where  OPTS := [ pn <u32> ] [ on | off ]\n"
+		"       ID   := 128-bit hex string\n"
+		"       KEY  := 128-bit or 256-bit hex string\n"
+		"       SCI  := { sci <u64> | port { 1..2^16-1 } address <lladdr> }\n");
+
+	exit(-1);
+}
+
+static int one_of(const char *msg, const char *realval, const char * const *list,
+		  size_t len, int *index)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		if (matches(realval, list[i]) == 0) {
+			*index = i;
+			return 0;
+		}
+	}
+
+	fprintf(stderr, "Error: argument of \"%s\" must be one of ", msg);
+	for (i = 0; i < len; i++)
+		fprintf(stderr, "\"%s\", ", list[i]);
+	fprintf(stderr, "not \"%s\"\n", realval);
+	return -1;
+}
+
+static int get_an(__u8 *val, const char *arg)
+{
+	int ret = get_u8(val, arg, 0);
+
+	if (ret)
+		return ret;
+
+	if (*val > 3)
+		return -1;
+
+	return 0;
+}
+
+static int get_sci(__u64 *sci, const char *arg)
+{
+	return get_be64(sci, arg, 16);
+}
+
+static int get_port(__be16 *port, const char *arg)
+{
+	return get_be16(port, arg, 0);
+}
+
+#define _STR(a) #a
+#define STR(a) _STR(a)
+
+static void get_icvlen(__u8 *icvlen, char *arg)
+{
+	int ret = get_u8(icvlen, arg, 10);
+
+	if (ret)
+		invarg("expected ICV length", arg);
+
+	if (*icvlen < MACSEC_MIN_ICV_LEN || *icvlen > MACSEC_STD_ICV_LEN)
+		invarg("ICV length must be in the range {"
+		       STR(MACSEC_MIN_ICV_LEN) ".." STR(MACSEC_STD_ICV_LEN)
+		       "}", arg);
+}
+
+static bool get_sa(int *argcp, char ***argvp, __u8 *an)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int ret;
+
+	if (argc <= 0 || strcmp(*argv, "sa") != 0)
+		return false;
+
+	NEXT_ARG();
+	ret = get_an(an, *argv);
+	if (ret)
+		invarg("expected an { 0..3 }", *argv);
+	argc--; argv++;
+
+	*argvp = argv;
+	*argcp = argc;
+	return true;
+}
+
+static int parse_sa_args(int *argcp, char ***argvp, struct sa_desc *sa)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int ret;
+	bool active_set = false;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "pn") == 0) {
+			if (sa->pn != 0)
+				duparg2("pn", "pn");
+			NEXT_ARG();
+			ret = get_u32(&sa->pn, *argv, 0);
+			if (ret)
+				invarg("expected pn", *argv);
+			if (sa->pn == 0)
+				invarg("expected pn != 0", *argv);
+		} else if (strcmp(*argv, "key") == 0) {
+			unsigned int len;
+
+			NEXT_ARG();
+			if (!hexstring_a2n(*argv, sa->key_id, MACSEC_KEYID_LEN,
+					   &len))
+				invarg("expected key id", *argv);
+			NEXT_ARG();
+			if (!hexstring_a2n(*argv, sa->key, MACSEC_MAX_KEY_LEN,
+					   &sa->key_len))
+				invarg("expected key", *argv);
+		} else if (strcmp(*argv, "on") == 0) {
+			if (active_set)
+				duparg2("on/off", "on");
+			sa->active = true;
+			active_set = true;
+		} else if (strcmp(*argv, "off") == 0) {
+			if (active_set)
+				duparg2("on/off", "off");
+			sa->active = false;
+			active_set = true;
+		} else {
+			fprintf(stderr, "macsec: unknown command \"%s\"?\n",
+				*argv);
+			ipmacsec_usage();
+		}
+
+		argv++; argc--;
+	}
+
+	*argvp = argv;
+	*argcp = argc;
+	return 0;
+}
+
+static __u64 make_sci(char *addr, __be16 port)
+{
+	__u64 sci;
+
+	memcpy(&sci, addr, ETH_ALEN);
+	memcpy(((char *)&sci) + ETH_ALEN, &port, sizeof(port));
+
+	return sci;
+}
+
+static bool sci_complete(bool sci, bool port, bool addr, bool port_only)
+{
+	return sci || (port && (addr || port_only));
+}
+
+static int get_sci_portaddr(struct sci *sci, int *argcp, char ***argvp,
+			    bool port_only, bool optional)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int ret;
+	bool p = false, a = false, s = false;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "sci") == 0) {
+			if (p)
+				invarg("expected address", *argv);
+			if (a)
+				invarg("expected port", *argv);
+			NEXT_ARG();
+			ret = get_sci(&sci->sci, *argv);
+			if (ret)
+				invarg("expected sci", *argv);
+			s = true;
+		} else if (strcmp(*argv, "port") == 0) {
+			NEXT_ARG();
+			ret = get_port(&sci->port, *argv);
+			if (ret)
+				invarg("expected port", *argv);
+			if (sci->port == 0)
+				invarg("expected port != 0", *argv);
+			p = true;
+		} else if (strcmp(*argv, "address") == 0) {
+			NEXT_ARG();
+			ret = ll_addr_a2n(sci->abuf, sizeof(sci->abuf), *argv);
+			if (ret < 0)
+				invarg("expected lladdr", *argv);
+			a = true;
+		} else if (optional) {
+			break;
+		} else {
+			invarg("expected sci, port, or address", *argv);
+		}
+
+		argv++; argc--;
+
+		if (sci_complete(s, p, a, port_only))
+			break;
+	}
+
+	if (!optional && !sci_complete(s, p, a, port_only))
+		return -1;
+
+	if (p && a)
+		sci->sci = make_sci(sci->abuf, sci->port);
+
+	*argvp = argv;
+	*argcp = argc;
+
+	return p || a || s;
+}
+
+static bool parse_rxsci(int *argcp, char ***argvp, struct rxsc_desc *rxsc,
+			struct sa_desc *rxsa)
+{
+	struct sci sci = { 0 };
+
+	if (*argcp == 0 ||
+	    get_sci_portaddr(&sci, argcp, argvp, false, false) < 0) {
+		fprintf(stderr, "expected sci\n");
+		ipmacsec_usage();
+	}
+
+	rxsc->sci = sci.sci;
+
+	return get_sa(argcp, argvp, &rxsa->an);
+}
+
+static int parse_rxsci_args(int *argcp, char ***argvp, struct rxsc_desc *rxsc)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	bool active_set = false;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "on") == 0) {
+			if (active_set)
+				duparg2("on/off", "on");
+			rxsc->active = true;
+			active_set = true;
+		} else if (strcmp(*argv, "off") == 0) {
+			if (active_set)
+				duparg2("on/off", "off");
+			rxsc->active = false;
+			active_set = true;
+		} else {
+			fprintf(stderr, "macsec: unknown command \"%s\"?\n",
+				*argv);
+			ipmacsec_usage();
+		}
+
+		argv++; argc--;
+	}
+
+	*argvp = argv;
+	*argcp = argc;
+	return 0;
+}
+
+enum cmd {
+	CMD_ADD,
+	CMD_DEL,
+	CMD_UPD,
+	__CMD_MAX
+};
+
+static const enum macsec_nl_commands macsec_commands[__CMD_MAX][2][2] = {
+	[CMD_ADD] = {
+		[0] = {-1, MACSEC_CMD_ADD_RXSC},
+		[1] = {MACSEC_CMD_ADD_TXSA, MACSEC_CMD_ADD_RXSA},
+	},
+	[CMD_UPD] = {
+		[0] = {-1, MACSEC_CMD_UPD_RXSC},
+		[1] = {MACSEC_CMD_UPD_TXSA, MACSEC_CMD_UPD_RXSA},
+	},
+	[CMD_DEL] = {
+		[0] = {-1, MACSEC_CMD_DEL_RXSC},
+		[1] = {MACSEC_CMD_DEL_TXSA, MACSEC_CMD_DEL_RXSA},
+	},
+};
+
+static int do_modify_nl(enum cmd c, enum macsec_nl_commands cmd, int ifindex,
+			struct rxsc_desc *rxsc, struct sa_desc *sa)
+{
+	struct rtattr *attr_sa;
+
+	MACSEC_GENL_REQ(req, MACSEC_BUFLEN, cmd, NLM_F_REQUEST);
+
+	addattr32(&req.n, MACSEC_BUFLEN, MACSEC_ATTR_IFINDEX, ifindex);
+	if (rxsc) {
+		struct rtattr *attr_rxsc;
+
+		attr_rxsc = addattr_nest(&req.n, MACSEC_BUFLEN,
+					 MACSEC_ATTR_RXSC_CONFIG);
+		addattr64(&req.n, MACSEC_BUFLEN,
+			  MACSEC_RXSC_ATTR_SCI, rxsc->sci);
+		if (c != CMD_DEL && rxsc->active != 0xff)
+			addattr8(&req.n, MACSEC_BUFLEN,
+				 MACSEC_RXSC_ATTR_ACTIVE, rxsc->active);
+
+		addattr_nest_end(&req.n, attr_rxsc);
+	}
+
+	if (sa->an == 0xff)
+		goto talk;
+
+	attr_sa = addattr_nest(&req.n, MACSEC_BUFLEN, MACSEC_ATTR_SA_CONFIG);
+
+	addattr8(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_AN, sa->an);
+
+	if (c != CMD_DEL) {
+		if (sa->pn)
+			addattr32(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_PN,
+				  sa->pn);
+
+		if (sa->key_len) {
+			addattr_l(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_KEYID,
+				  sa->key_id, MACSEC_KEYID_LEN);
+			addattr_l(&req.n, MACSEC_BUFLEN, MACSEC_SA_ATTR_KEY,
+				  sa->key, sa->key_len);
+		}
+
+		if (sa->active != 0xff) {
+			addattr8(&req.n, MACSEC_BUFLEN,
+				 MACSEC_SA_ATTR_ACTIVE, sa->active);
+		}
+	}
+
+	addattr_nest_end(&req.n, attr_sa);
+
+talk:
+	if (rtnl_talk(&genl_rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static bool check_sa_args(enum cmd c, struct sa_desc *sa)
+{
+	if (c == CMD_ADD) {
+		if (!sa->key_len) {
+			fprintf(stderr, "cannot create SA without key\n");
+			return -1;
+		}
+
+		if (sa->pn == 0) {
+			fprintf(stderr, "must specify a packet number != 0\n");
+			return -1;
+		}
+	} else if (c == CMD_UPD) {
+		if (sa->key_len) {
+			fprintf(stderr, "cannot change key on SA\n");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int do_modify_txsa(enum cmd c, int argc, char **argv, int ifindex)
+{
+	struct sa_desc txsa = {0};
+	enum macsec_nl_commands cmd;
+
+	txsa.an = 0xff;
+	txsa.active = 0xff;
+
+	if (argc == 0 || !get_sa(&argc, &argv, &txsa.an))
+		ipmacsec_usage();
+
+	if (c == CMD_DEL)
+		goto modify;
+
+	if (parse_sa_args(&argc, &argv, &txsa))
+		return -1;
+
+	if (check_sa_args(c, &txsa))
+		return -1;
+
+modify:
+	cmd = macsec_commands[c][1][0];
+	return do_modify_nl(c, cmd, ifindex, NULL, &txsa);
+}
+
+static int do_modify_rxsci(enum cmd c, int argc, char **argv, int ifindex)
+{
+	struct rxsc_desc rxsc = {0};
+	struct sa_desc rxsa = {0};
+	bool sa_set;
+	enum macsec_nl_commands cmd;
+
+	rxsc.ifindex = ifindex;
+	rxsc.active = 0xff;
+	rxsa.an = 0xff;
+	rxsa.active = 0xff;
+
+	sa_set = parse_rxsci(&argc, &argv, &rxsc, &rxsa);
+
+	if (c == CMD_DEL)
+		goto modify;
+
+	if (sa_set && (parse_sa_args(&argc, &argv, &rxsa) ||
+		       check_sa_args(c, &rxsa)))
+		return -1;
+	if (!sa_set && parse_rxsci_args(&argc, &argv, &rxsc))
+		return -1;
+
+modify:
+	cmd = macsec_commands[c][sa_set][1];
+	return do_modify_nl(c, cmd, rxsc.ifindex, &rxsc, &rxsa);
+}
+
+static int do_modify(enum cmd c, int argc, char **argv)
+{
+	int ifindex;
+
+	if (argc == 0)
+		ipmacsec_usage();
+
+	ifindex = ll_name_to_index(*argv);
+	if (!ifindex) {
+		fprintf(stderr, "Device \"%s\" does not exist.\n", *argv);
+		return -1;
+	}
+	argc--; argv++;
+
+	if (argc == 0)
+		ipmacsec_usage();
+
+	if (strcmp(*argv, "tx") == 0)
+		return do_modify_txsa(c, argc-1, argv+1, ifindex);
+	if (strcmp(*argv, "rx") == 0)
+		return do_modify_rxsci(c, argc-1, argv+1, ifindex);
+
+	ipmacsec_usage();
+	return -1;
+}
+
+/* dump/show */
+static struct {
+	int ifindex;
+	__u64 sci;
+} filter;
+
+static int validate_dump(struct rtattr **attrs)
+{
+	return attrs[MACSEC_ATTR_IFINDEX] && attrs[MACSEC_ATTR_SECY] &&
+	       attrs[MACSEC_ATTR_TXSA_LIST] && attrs[MACSEC_ATTR_RXSC_LIST] &&
+	       attrs[MACSEC_ATTR_TXSC_STATS] && attrs[MACSEC_ATTR_SECY_STATS];
+
+}
+
+static int validate_secy_dump(struct rtattr **attrs)
+{
+	return attrs[MACSEC_SECY_ATTR_SCI] &&
+	       attrs[MACSEC_SECY_ATTR_ENCODING_SA] &&
+	       attrs[MACSEC_SECY_ATTR_CIPHER_SUITE] &&
+	       attrs[MACSEC_SECY_ATTR_ICV_LEN] &&
+	       attrs[MACSEC_SECY_ATTR_PROTECT] &&
+	       attrs[MACSEC_SECY_ATTR_REPLAY] &&
+	       attrs[MACSEC_SECY_ATTR_OPER] &&
+	       attrs[MACSEC_SECY_ATTR_VALIDATE] &&
+	       attrs[MACSEC_SECY_ATTR_ENCRYPT] &&
+	       attrs[MACSEC_SECY_ATTR_INC_SCI] &&
+	       attrs[MACSEC_SECY_ATTR_ES] &&
+	       attrs[MACSEC_SECY_ATTR_SCB];
+}
+
+static void print_flag(struct rtattr *attrs[], const char *desc,
+		       int field)
+{
+	__u8 flag;
+
+	if (!attrs[field])
+		return;
+
+	flag = rta_getattr_u8(attrs[field]);
+	if (is_json_context())
+		print_bool(PRINT_JSON, desc, NULL, flag);
+	else {
+		print_string(PRINT_FP, NULL, "%s ", desc);
+		print_string(PRINT_FP, NULL, "%s ",
+			     flag ? "on" : "off");
+	}
+}
+
+static void print_key(struct rtattr *key)
+{
+	SPRINT_BUF(keyid);
+
+	print_string(PRINT_ANY, "key", " key %s\n",
+		     hexstring_n2a(RTA_DATA(key), RTA_PAYLOAD(key),
+				   keyid, sizeof(keyid)));
+}
+
+#define CIPHER_NAME_GCM_AES_128 "GCM-AES-128"
+#define CIPHER_NAME_GCM_AES_256 "GCM-AES-256"
+#define DEFAULT_CIPHER_NAME CIPHER_NAME_GCM_AES_128
+
+static const char *cs_id_to_name(__u64 cid)
+{
+	switch (cid) {
+	case MACSEC_DEFAULT_CIPHER_ID:
+		return DEFAULT_CIPHER_NAME;
+	case MACSEC_CIPHER_ID_GCM_AES_128:
+	     /* MACSEC_DEFAULT_CIPHER_ALT: */
+		return CIPHER_NAME_GCM_AES_128;
+	case MACSEC_CIPHER_ID_GCM_AES_256:
+		return CIPHER_NAME_GCM_AES_256;
+	default:
+		return "(unknown)";
+	}
+}
+
+static void print_attrs(struct rtattr *attrs[])
+{
+	print_flag(attrs, "protect", MACSEC_SECY_ATTR_PROTECT);
+
+	if (attrs[MACSEC_SECY_ATTR_VALIDATE]) {
+		__u8 val = rta_getattr_u8(attrs[MACSEC_SECY_ATTR_VALIDATE]);
+
+		print_string(PRINT_ANY, "validate",
+			     "validate %s ", validate_str[val]);
+	}
+
+	print_flag(attrs, "sc", MACSEC_RXSC_ATTR_ACTIVE);
+	print_flag(attrs, "sa", MACSEC_SA_ATTR_ACTIVE);
+	print_flag(attrs, "encrypt", MACSEC_SECY_ATTR_ENCRYPT);
+	print_flag(attrs, "send_sci", MACSEC_SECY_ATTR_INC_SCI);
+	print_flag(attrs, "end_station", MACSEC_SECY_ATTR_ES);
+	print_flag(attrs, "scb", MACSEC_SECY_ATTR_SCB);
+	print_flag(attrs, "replay", MACSEC_SECY_ATTR_REPLAY);
+
+	if (attrs[MACSEC_SECY_ATTR_WINDOW]) {
+		__u32 win = rta_getattr_u32(attrs[MACSEC_SECY_ATTR_WINDOW]);
+
+		print_uint(PRINT_ANY, "window", "window %u ", win);
+	}
+
+	if (attrs[MACSEC_SECY_ATTR_CIPHER_SUITE]) {
+		__u64 cid = rta_getattr_u64(attrs[MACSEC_SECY_ATTR_CIPHER_SUITE]);
+
+		print_nl();
+		print_string(PRINT_ANY, "cipher_suite",
+			     "    cipher suite: %s,", cs_id_to_name(cid));
+	}
+
+	if (attrs[MACSEC_SECY_ATTR_ICV_LEN]) {
+		__u8 icv_len = rta_getattr_u8(attrs[MACSEC_SECY_ATTR_ICV_LEN]);
+
+		print_uint(PRINT_ANY, "icv_length",
+		     " using ICV length %u\n", icv_len);
+	}
+}
+
+static __u64 getattr_u64(const struct rtattr *stat)
+{
+	size_t len = RTA_PAYLOAD(stat);
+
+	switch (len) {
+	case sizeof(__u64):
+		return rta_getattr_u64(stat);
+	case sizeof(__u32):
+		return rta_getattr_u32(stat);
+	case sizeof(__u16):
+		return rta_getattr_u16(stat);
+	case sizeof(__u8):
+		return rta_getattr_u8(stat);
+	default:
+		fprintf(stderr, "invalid attribute length %zu\n",
+			len);
+		exit(-1);
+	}
+}
+
+static void print_fp_stats(const char *prefix,
+			   const char *names[], unsigned int num,
+			   struct rtattr *stats[])
+{
+	unsigned int i;
+	int pad;
+
+	printf("%sstats:", prefix);
+
+	for (i = 1; i < num; i++) {
+		if (!names[i])
+			continue;
+		printf(" %s", names[i]);
+	}
+
+	printf("\n%s      ", prefix);
+
+	for (i = 1; i < num; i++) {
+		if (!names[i])
+			continue;
+
+		pad = strlen(names[i]) + 1;
+		if (stats[i])
+			printf("%*llu", pad, getattr_u64(stats[i]));
+		else
+			printf("%*c", pad, '-');
+	}
+	printf("\n");
+}
+
+static void print_json_stats(const char *names[], unsigned int num,
+			     struct rtattr *stats[])
+{
+	unsigned int i;
+
+	for (i = 1; i < num; i++) {
+		if (!names[i] || !stats[i])
+			continue;
+
+		print_u64(PRINT_JSON, names[i],
+			   NULL, getattr_u64(stats[i]));
+	}
+}
+
+static void print_stats(const char *prefix,
+			const char *names[], unsigned int num,
+			struct rtattr *stats[])
+{
+
+	if (is_json_context())
+		print_json_stats(names, num, stats);
+	else
+		print_fp_stats(prefix, names, num, stats);
+}
+
+static const char *txsc_stats_names[NUM_MACSEC_TXSC_STATS_ATTR] = {
+	[MACSEC_TXSC_STATS_ATTR_OUT_PKTS_PROTECTED] = "OutPktsProtected",
+	[MACSEC_TXSC_STATS_ATTR_OUT_PKTS_ENCRYPTED] = "OutPktsEncrypted",
+	[MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_PROTECTED] = "OutOctetsProtected",
+	[MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_ENCRYPTED] = "OutOctetsEncrypted",
+};
+
+static void print_txsc_stats(const char *prefix, struct rtattr *attr)
+{
+	struct rtattr *stats[MACSEC_TXSC_STATS_ATTR_MAX + 1];
+
+	if (!attr || show_stats == 0)
+		return;
+
+	parse_rtattr_nested(stats, MACSEC_TXSC_STATS_ATTR_MAX, attr);
+
+	print_stats(prefix, txsc_stats_names, NUM_MACSEC_TXSC_STATS_ATTR,
+		    stats);
+}
+
+static const char *secy_stats_names[NUM_MACSEC_SECY_STATS_ATTR] = {
+	[MACSEC_SECY_STATS_ATTR_OUT_PKTS_UNTAGGED] = "OutPktsUntagged",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_UNTAGGED] = "InPktsUntagged",
+	[MACSEC_SECY_STATS_ATTR_OUT_PKTS_TOO_LONG] = "OutPktsTooLong",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_TAG] = "InPktsNoTag",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_BAD_TAG] = "InPktsBadTag",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_UNKNOWN_SCI] = "InPktsUnknownSCI",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_SCI] = "InPktsNoSCI",
+	[MACSEC_SECY_STATS_ATTR_IN_PKTS_OVERRUN] = "InPktsOverrun",
+};
+
+static void print_secy_stats(const char *prefix, struct rtattr *attr)
+{
+	struct rtattr *stats[MACSEC_SECY_STATS_ATTR_MAX + 1];
+
+	if (!attr || show_stats == 0)
+		return;
+
+	parse_rtattr_nested(stats, MACSEC_SECY_STATS_ATTR_MAX, attr);
+
+	print_stats(prefix, secy_stats_names,
+		    NUM_MACSEC_SECY_STATS_ATTR, stats);
+}
+
+static const char *rxsa_stats_names[NUM_MACSEC_SA_STATS_ATTR] = {
+	[MACSEC_SA_STATS_ATTR_IN_PKTS_OK] = "InPktsOK",
+	[MACSEC_SA_STATS_ATTR_IN_PKTS_INVALID] = "InPktsInvalid",
+	[MACSEC_SA_STATS_ATTR_IN_PKTS_NOT_VALID] = "InPktsNotValid",
+	[MACSEC_SA_STATS_ATTR_IN_PKTS_NOT_USING_SA] = "InPktsNotUsingSA",
+	[MACSEC_SA_STATS_ATTR_IN_PKTS_UNUSED_SA] = "InPktsUnusedSA",
+};
+
+static void print_rxsa_stats(const char *prefix, struct rtattr *attr)
+{
+	struct rtattr *stats[MACSEC_SA_STATS_ATTR_MAX + 1];
+
+	if (!attr || show_stats == 0)
+		return;
+
+	parse_rtattr_nested(stats, MACSEC_SA_STATS_ATTR_MAX, attr);
+
+	print_stats(prefix, rxsa_stats_names, NUM_MACSEC_SA_STATS_ATTR, stats);
+}
+
+static const char *txsa_stats_names[NUM_MACSEC_SA_STATS_ATTR] = {
+	[MACSEC_SA_STATS_ATTR_OUT_PKTS_PROTECTED] = "OutPktsProtected",
+	[MACSEC_SA_STATS_ATTR_OUT_PKTS_ENCRYPTED] = "OutPktsEncrypted",
+};
+
+static void print_txsa_stats(const char *prefix, struct rtattr *attr)
+{
+	struct rtattr *stats[MACSEC_SA_STATS_ATTR_MAX + 1];
+
+	if (!attr || show_stats == 0)
+		return;
+
+	parse_rtattr_nested(stats, MACSEC_SA_STATS_ATTR_MAX, attr);
+
+	print_stats(prefix, txsa_stats_names, NUM_MACSEC_SA_STATS_ATTR, stats);
+}
+
+static void print_tx_sc(const char *prefix, __u64 sci, __u8 encoding_sa,
+			struct rtattr *txsc_stats, struct rtattr *secy_stats,
+			struct rtattr *sa)
+{
+	struct rtattr *sa_attr[MACSEC_SA_ATTR_MAX + 1];
+	struct rtattr *a;
+	int rem;
+
+	print_string(PRINT_FP, NULL, "%s", prefix);
+	print_0xhex(PRINT_ANY, "sci",
+		    "TXSC: %016llx", ntohll(sci));
+	print_uint(PRINT_ANY, "encoding_sa",
+		   " on SA %d\n", encoding_sa);
+
+	print_secy_stats(prefix, secy_stats);
+	print_txsc_stats(prefix, txsc_stats);
+
+	open_json_array(PRINT_JSON, "sa_list");
+	rem = RTA_PAYLOAD(sa);
+	for (a = RTA_DATA(sa); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) {
+		bool state;
+
+		open_json_object(NULL);
+		parse_rtattr_nested(sa_attr, MACSEC_SA_ATTR_MAX, a);
+		state = rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_ACTIVE]);
+
+		print_string(PRINT_FP, NULL, "%s", prefix);
+		print_string(PRINT_FP, NULL, "%s", prefix);
+		print_uint(PRINT_ANY, "an", "%d:",
+			   rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_AN]));
+		print_uint(PRINT_ANY, "pn", " PN %u,",
+			   rta_getattr_u32(sa_attr[MACSEC_SA_ATTR_PN]));
+
+		print_bool(PRINT_JSON, "active", NULL, state);
+		print_string(PRINT_FP, NULL,
+			     " state %s,", state ? "on" : "off");
+		print_key(sa_attr[MACSEC_SA_ATTR_KEYID]);
+
+		print_txsa_stats(prefix, sa_attr[MACSEC_SA_ATTR_STATS]);
+		close_json_object();
+	}
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static const char *rxsc_stats_names[NUM_MACSEC_RXSC_STATS_ATTR] = {
+	[MACSEC_RXSC_STATS_ATTR_IN_OCTETS_VALIDATED] = "InOctetsValidated",
+	[MACSEC_RXSC_STATS_ATTR_IN_OCTETS_DECRYPTED] = "InOctetsDecrypted",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNCHECKED] = "InPktsUnchecked",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_DELAYED] = "InPktsDelayed",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_OK] = "InPktsOK",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_INVALID] = "InPktsInvalid",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_LATE] = "InPktsLate",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_VALID] = "InPktsNotValid",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_USING_SA] = "InPktsNotUsingSA",
+	[MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNUSED_SA] = "InPktsUnusedSA",
+};
+
+static void print_rxsc_stats(const char *prefix, struct rtattr *attr)
+{
+	struct rtattr *stats[MACSEC_RXSC_STATS_ATTR_MAX + 1];
+
+	if (!attr || show_stats == 0)
+		return;
+
+	parse_rtattr_nested(stats, MACSEC_RXSC_STATS_ATTR_MAX, attr);
+
+	print_stats(prefix, rxsc_stats_names,
+		    NUM_MACSEC_RXSC_STATS_ATTR, stats);
+}
+
+static void print_rx_sc(const char *prefix, __be64 sci, __u8 active,
+			struct rtattr *rxsc_stats, struct rtattr *sa)
+{
+	struct rtattr *sa_attr[MACSEC_SA_ATTR_MAX + 1];
+	struct rtattr *a;
+	int rem;
+
+	print_string(PRINT_FP, NULL, "%s", prefix);
+	print_0xhex(PRINT_ANY, "sci",
+		    "RXSC: %016llx,", ntohll(sci));
+	print_bool(PRINT_JSON, "active", NULL, active);
+	print_string(PRINT_FP, NULL,
+		     " state %s\n", active ? "on" : "off");
+	print_rxsc_stats(prefix, rxsc_stats);
+
+	open_json_array(PRINT_JSON, "sa_list");
+	rem = RTA_PAYLOAD(sa);
+	for (a = RTA_DATA(sa); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) {
+		bool state;
+
+		open_json_object(NULL);
+		parse_rtattr_nested(sa_attr, MACSEC_SA_ATTR_MAX, a);
+		state = rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_ACTIVE]);
+
+		print_string(PRINT_FP, NULL, "%s", prefix);
+		print_string(PRINT_FP, NULL, "%s", prefix);
+		print_uint(PRINT_ANY, "an", "%u:",
+			   rta_getattr_u8(sa_attr[MACSEC_SA_ATTR_AN]));
+		print_uint(PRINT_ANY, "pn", " PN %u,",
+			   rta_getattr_u32(sa_attr[MACSEC_SA_ATTR_PN]));
+
+		print_bool(PRINT_JSON, "active", NULL, state);
+		print_string(PRINT_FP, NULL, " state %s,",
+			     state ? "on" : "off");
+
+		print_key(sa_attr[MACSEC_SA_ATTR_KEYID]);
+
+		print_rxsa_stats(prefix, sa_attr[MACSEC_SA_ATTR_STATS]);
+		close_json_object();
+	}
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static void print_rxsc_list(struct rtattr *sc)
+{
+	int rem = RTA_PAYLOAD(sc);
+	struct rtattr *c;
+
+	open_json_array(PRINT_JSON, "rx_sc");
+	for (c = RTA_DATA(sc); RTA_OK(c, rem); c = RTA_NEXT(c, rem)) {
+		struct rtattr *sc_attr[MACSEC_RXSC_ATTR_MAX + 1];
+
+		open_json_object(NULL);
+
+		parse_rtattr_nested(sc_attr, MACSEC_RXSC_ATTR_MAX, c);
+		print_rx_sc("    ",
+			    rta_getattr_u64(sc_attr[MACSEC_RXSC_ATTR_SCI]),
+			    rta_getattr_u32(sc_attr[MACSEC_RXSC_ATTR_ACTIVE]),
+			    sc_attr[MACSEC_RXSC_ATTR_STATS],
+			    sc_attr[MACSEC_RXSC_ATTR_SA_LIST]);
+		close_json_object();
+	}
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static int process(struct nlmsghdr *n, void *arg)
+{
+	struct genlmsghdr *ghdr;
+	struct rtattr *attrs[MACSEC_ATTR_MAX + 1];
+	struct rtattr *attrs_secy[MACSEC_SECY_ATTR_MAX + 1];
+	int len = n->nlmsg_len;
+	int ifindex;
+	__u64 sci;
+	__u8 encoding_sa;
+
+	if (n->nlmsg_type != genl_family)
+		return -1;
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+	if (len < 0)
+		return -1;
+
+	ghdr = NLMSG_DATA(n);
+	if (ghdr->cmd != MACSEC_CMD_GET_TXSC)
+		return 0;
+
+	parse_rtattr(attrs, MACSEC_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
+	if (!validate_dump(attrs)) {
+		fprintf(stderr, "incomplete dump message\n");
+		return -1;
+	}
+
+	ifindex = rta_getattr_u32(attrs[MACSEC_ATTR_IFINDEX]);
+	parse_rtattr_nested(attrs_secy, MACSEC_SECY_ATTR_MAX,
+			    attrs[MACSEC_ATTR_SECY]);
+
+	if (!validate_secy_dump(attrs_secy)) {
+		fprintf(stderr, "incomplete dump message\n");
+		return -1;
+	}
+
+	sci = rta_getattr_u64(attrs_secy[MACSEC_SECY_ATTR_SCI]);
+	encoding_sa = rta_getattr_u8(attrs_secy[MACSEC_SECY_ATTR_ENCODING_SA]);
+
+	if (filter.ifindex && ifindex != filter.ifindex)
+		return 0;
+
+	if (filter.sci && sci != filter.sci)
+		return 0;
+
+	open_json_object(NULL);
+	print_uint(PRINT_ANY, "ifindex", "%u: ", ifindex);
+	print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname",
+			   "%s: ", ll_index_to_name(ifindex));
+
+	print_attrs(attrs_secy);
+
+	print_tx_sc("    ", sci, encoding_sa,
+		    attrs[MACSEC_ATTR_TXSC_STATS],
+		    attrs[MACSEC_ATTR_SECY_STATS],
+		    attrs[MACSEC_ATTR_TXSA_LIST]);
+
+	if (attrs[MACSEC_ATTR_RXSC_LIST])
+		print_rxsc_list(attrs[MACSEC_ATTR_RXSC_LIST]);
+
+	close_json_object();
+
+	return 0;
+}
+
+static int do_dump(int ifindex)
+{
+	MACSEC_GENL_REQ(req, MACSEC_BUFLEN, MACSEC_CMD_GET_TXSC,
+			NLM_F_REQUEST | NLM_F_DUMP);
+
+	memset(&filter, 0, sizeof(filter));
+	filter.ifindex = ifindex;
+
+	req.n.nlmsg_seq = genl_rth.dump = ++genl_rth.seq;
+	if (rtnl_send(&genl_rth, &req, req.n.nlmsg_len) < 0) {
+		perror("Failed to send dump request");
+		exit(1);
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&genl_rth, process, stdout) < 0) {
+		delete_json_obj();
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	delete_json_obj();
+
+	return 0;
+}
+
+static int do_show(int argc, char **argv)
+{
+	int ifindex;
+
+	if (argc == 0)
+		return do_dump(0);
+
+	ifindex = ll_name_to_index(*argv);
+	if (ifindex == 0) {
+		fprintf(stderr, "Device \"%s\" does not exist.\n", *argv);
+		return -1;
+	}
+
+	argc--, argv++;
+	if (argc == 0)
+		return do_dump(ifindex);
+
+	ipmacsec_usage();
+	return -1;
+}
+
+int do_ipmacsec(int argc, char **argv)
+{
+	if (argc < 1)
+		ipmacsec_usage();
+
+	if (matches(*argv, "help") == 0)
+		ipmacsec_usage();
+
+	if (genl_init_handle(&genl_rth, MACSEC_GENL_NAME, &genl_family))
+		exit(1);
+
+	if (matches(*argv, "show") == 0)
+		return do_show(argc-1, argv+1);
+
+	if (matches(*argv, "add") == 0)
+		return do_modify(CMD_ADD, argc-1, argv+1);
+	if (matches(*argv, "set") == 0)
+		return do_modify(CMD_UPD, argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return do_modify(CMD_DEL, argc-1, argv+1);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip macsec help\".\n",
+		*argv);
+	exit(-1);
+}
+
+/* device creation */
+static void macsec_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_MACSEC_SCI]) {
+		if (is_json_context()) {
+			SPRINT_BUF(b1);
+
+			snprintf(b1, sizeof(b1), "%016llx",
+				 ntohll(rta_getattr_u64(tb[IFLA_MACSEC_SCI])));
+			print_string(PRINT_JSON, "sci", NULL, b1);
+		} else {
+			fprintf(f, "sci %016llx ",
+				ntohll(rta_getattr_u64(tb[IFLA_MACSEC_SCI])));
+		}
+	}
+
+	print_flag(tb, "protect", IFLA_MACSEC_PROTECT);
+
+	if (tb[IFLA_MACSEC_CIPHER_SUITE]) {
+		__u64 csid
+			= rta_getattr_u64(tb[IFLA_MACSEC_CIPHER_SUITE]);
+
+		print_string(PRINT_ANY,
+			     "cipher_suite",
+			     "cipher %s ",
+			     cs_id_to_name(csid));
+	}
+
+	if (tb[IFLA_MACSEC_ICV_LEN]) {
+		if (is_json_context()) {
+			char b2[4];
+
+			snprintf(b2, sizeof(b2), "%hhu",
+				 rta_getattr_u8(tb[IFLA_MACSEC_ICV_LEN]));
+			print_uint(PRINT_JSON, "icv_len", NULL, atoi(b2));
+		} else {
+			fprintf(f, "icvlen %hhu ",
+				rta_getattr_u8(tb[IFLA_MACSEC_ICV_LEN]));
+		}
+	}
+
+	if (tb[IFLA_MACSEC_ENCODING_SA]) {
+		if (is_json_context()) {
+			char b2[4];
+
+			snprintf(b2, sizeof(b2), "%hhu",
+				 rta_getattr_u8(tb[IFLA_MACSEC_ENCODING_SA]));
+			print_uint(PRINT_JSON, "encoding_sa", NULL, atoi(b2));
+		} else {
+			fprintf(f, "encodingsa %hhu ",
+				rta_getattr_u8(tb[IFLA_MACSEC_ENCODING_SA]));
+		}
+	}
+
+	if (tb[IFLA_MACSEC_VALIDATION]) {
+		__u8 val = rta_getattr_u8(tb[IFLA_MACSEC_VALIDATION]);
+
+		print_string(PRINT_ANY,
+			     "validation",
+			     "validate %s ",
+			     validate_str[val]);
+	}
+
+	const char *inc_sci, *es, *replay;
+
+	if (is_json_context()) {
+		inc_sci = "inc_sci";
+		replay = "replay_protect";
+		es = "es";
+	} else {
+		inc_sci = "send_sci";
+		es = "end_station";
+		replay = "replay";
+	}
+
+	print_flag(tb, "encrypt", IFLA_MACSEC_ENCRYPT);
+	print_flag(tb, inc_sci, IFLA_MACSEC_INC_SCI);
+	print_flag(tb, es, IFLA_MACSEC_ES);
+	print_flag(tb, "scb", IFLA_MACSEC_SCB);
+	print_flag(tb, replay, IFLA_MACSEC_REPLAY_PROTECT);
+
+	if (tb[IFLA_MACSEC_WINDOW])
+		print_int(PRINT_ANY,
+			  "window",
+			  "window %d ",
+			  rta_getattr_u32(tb[IFLA_MACSEC_WINDOW]));
+}
+
+static bool check_txsc_flags(bool es, bool scb, bool sci)
+{
+	if (sci && (es || scb))
+		return false;
+	if (es && scb)
+		return false;
+	return true;
+}
+
+static void usage(FILE *f)
+{
+	fprintf(f,
+		"Usage: ... macsec [ [ address <lladdr> ] port { 1..2^16-1 } | sci <u64> ]\n"
+		"                  [ cipher { default | gcm-aes-128 | gcm-aes-256 } ]\n"
+		"                  [ icvlen { 8..16 } ]\n"
+		"                  [ encrypt { on | off } ]\n"
+		"                  [ send_sci { on | off } ]\n"
+		"                  [ end_station { on | off } ]\n"
+		"                  [ scb { on | off } ]\n"
+		"                  [ protect { on | off } ]\n"
+		"                  [ replay { on | off} window { 0..2^32-1 } ]\n"
+		"                  [ validate { strict | check | disabled } ]\n"
+		"                  [ encodingsa { 0..3 } ]\n"
+		);
+}
+
+static int macsec_parse_opt(struct link_util *lu, int argc, char **argv,
+			    struct nlmsghdr *n)
+{
+	int ret;
+	__u8 encoding_sa = 0xff;
+	__u32 window = -1;
+	struct cipher_args cipher = {0};
+	enum macsec_validation_type validate;
+	bool es = false, scb = false, send_sci = false;
+	int replay_protect = -1;
+	struct sci sci = { 0 };
+
+	ret = get_sci_portaddr(&sci, &argc, &argv, true, true);
+	if (ret < 0) {
+		fprintf(stderr, "expected sci\n");
+		return -1;
+	}
+
+	if (ret > 0) {
+		if (sci.sci)
+			addattr_l(n, MACSEC_BUFLEN, IFLA_MACSEC_SCI,
+				  &sci.sci, sizeof(sci.sci));
+		else
+			addattr_l(n, MACSEC_BUFLEN, IFLA_MACSEC_PORT,
+				  &sci.port, sizeof(sci.port));
+	}
+
+	while (argc > 0) {
+		if (strcmp(*argv, "cipher") == 0) {
+			NEXT_ARG();
+			if (cipher.id)
+				duparg("cipher", *argv);
+			if (strcmp(*argv, "default") == 0)
+				cipher.id = MACSEC_DEFAULT_CIPHER_ID;
+			else if (strcmp(*argv, "gcm-aes-128") == 0 ||
+			         strcmp(*argv, "GCM-AES-128") == 0)
+				cipher.id = MACSEC_CIPHER_ID_GCM_AES_128;
+			else if (strcmp(*argv, "gcm-aes-256") == 0 ||
+			         strcmp(*argv, "GCM-AES-256") == 0)
+				cipher.id = MACSEC_CIPHER_ID_GCM_AES_256;
+			else
+				invarg("expected: default, gcm-aes-128 or"
+				       " gcm-aes-256", *argv);
+		} else if (strcmp(*argv, "icvlen") == 0) {
+			NEXT_ARG();
+			if (cipher.icv_len)
+				duparg("icvlen", *argv);
+			get_icvlen(&cipher.icv_len, *argv);
+		} else if (strcmp(*argv, "encrypt") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("encrypt", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_ENCRYPT, i);
+		} else if (strcmp(*argv, "send_sci") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("send_sci", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			send_sci = i;
+			addattr8(n, MACSEC_BUFLEN,
+				 IFLA_MACSEC_INC_SCI, send_sci);
+		} else if (strcmp(*argv, "end_station") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("end_station", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			es = i;
+			addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_ES, es);
+		} else if (strcmp(*argv, "scb") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("scb", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			scb = i;
+			addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_SCB, scb);
+		} else if (strcmp(*argv, "protect") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("protect", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_PROTECT, i);
+		} else if (strcmp(*argv, "replay") == 0) {
+			NEXT_ARG();
+			int i;
+
+			ret = one_of("replay", *argv, values_on_off,
+				     ARRAY_SIZE(values_on_off), &i);
+			if (ret != 0)
+				return ret;
+			replay_protect = !!i;
+		} else if (strcmp(*argv, "window") == 0) {
+			NEXT_ARG();
+			ret = get_u32(&window, *argv, 0);
+			if (ret)
+				invarg("expected replay window size", *argv);
+		} else if (strcmp(*argv, "validate") == 0) {
+			NEXT_ARG();
+			ret = one_of("validate", *argv,
+				     validate_str, ARRAY_SIZE(validate_str),
+				     (int *)&validate);
+			if (ret != 0)
+				return ret;
+			addattr8(n, MACSEC_BUFLEN,
+				 IFLA_MACSEC_VALIDATION, validate);
+		} else if (strcmp(*argv, "encodingsa") == 0) {
+			if (encoding_sa != 0xff)
+				duparg2("encodingsa", "encodingsa");
+			NEXT_ARG();
+			ret = get_an(&encoding_sa, *argv);
+			if (ret)
+				invarg("expected an { 0..3 }", *argv);
+		} else {
+			fprintf(stderr, "macsec: unknown command \"%s\"?\n",
+				*argv);
+			usage(stderr);
+			return -1;
+		}
+
+		argv++; argc--;
+	}
+
+	if (!check_txsc_flags(es, scb, send_sci)) {
+		fprintf(stderr,
+			"invalid combination of send_sci/end_station/scb\n");
+		return -1;
+	}
+
+	if (window != -1 && replay_protect == -1) {
+		fprintf(stderr,
+			"replay window set, but replay protection not enabled. did you mean 'replay on window %u'?\n",
+			window);
+		return -1;
+	} else if (window == -1 && replay_protect == 1) {
+		fprintf(stderr,
+			"replay protection enabled, but no window set. did you mean 'replay on window VALUE'?\n");
+		return -1;
+	}
+
+	if (cipher.id)
+		addattr_l(n, MACSEC_BUFLEN, IFLA_MACSEC_CIPHER_SUITE,
+			  &cipher.id, sizeof(cipher.id));
+	if (cipher.icv_len)
+		addattr_l(n, MACSEC_BUFLEN, IFLA_MACSEC_ICV_LEN,
+			  &cipher.icv_len, sizeof(cipher.icv_len));
+
+	if (replay_protect != -1) {
+		addattr32(n, MACSEC_BUFLEN, IFLA_MACSEC_WINDOW, window);
+		addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_REPLAY_PROTECT,
+			 replay_protect);
+	}
+
+	if (encoding_sa != 0xff) {
+		addattr_l(n, MACSEC_BUFLEN, IFLA_MACSEC_ENCODING_SA,
+			  &encoding_sa, sizeof(encoding_sa));
+	}
+
+	return 0;
+}
+
+static void macsec_print_help(struct link_util *lu, int argc, char **argv,
+			      FILE *f)
+{
+	usage(f);
+}
+
+struct link_util macsec_link_util = {
+	.id = "macsec",
+	.maxattr = IFLA_MACSEC_MAX,
+	.parse_opt = macsec_parse_opt,
+	.print_help = macsec_print_help,
+	.print_opt = macsec_print_opt,
+};
diff --git a/ip/ipmaddr.c b/ip/ipmaddr.c
new file mode 100644
index 0000000..3400e05
--- /dev/null
+++ b/ip/ipmaddr.c
@@ -0,0 +1,369 @@
+/*
+ * ipmaddr.c		"ip maddress".
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <linux/netdevice.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/sockios.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+static struct {
+	char *dev;
+	int  family;
+} filter;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip maddr [ add | del ] MULTIADDR dev STRING\n"
+		"       ip maddr show [ dev STRING ]\n");
+	exit(-1);
+}
+
+static int parse_hex(char *str, unsigned char *addr, size_t size)
+{
+	int len = 0;
+
+	while (*str && (len < 2 * size)) {
+		int tmp;
+
+		if (str[1] == 0)
+			return -1;
+		if (sscanf(str, "%02x", &tmp) != 1)
+			return -1;
+		addr[len] = tmp;
+		len++;
+		str += 2;
+	}
+	return len;
+}
+
+struct ma_info {
+	struct ma_info *next;
+	int		index;
+	int		users;
+	char		*features;
+	char		name[IFNAMSIZ];
+	inet_prefix	addr;
+};
+
+static void maddr_ins(struct ma_info **lst, struct ma_info *m)
+{
+	struct ma_info *mp;
+
+	for (; (mp = *lst) != NULL; lst = &mp->next) {
+		if (mp->index > m->index)
+			break;
+	}
+	m->next = *lst;
+	*lst = m;
+}
+
+static void read_dev_mcast(struct ma_info **result_p)
+{
+	char buf[256];
+	FILE *fp = fopen("/proc/net/dev_mcast", "r");
+
+	if (!fp)
+		return;
+
+	while (fgets(buf, sizeof(buf), fp)) {
+		char hexa[256];
+		struct ma_info m = { .addr.family = AF_PACKET };
+		int len;
+		int st;
+
+		sscanf(buf, "%d%s%d%d%s", &m.index, m.name, &m.users, &st,
+		       hexa);
+		if (filter.dev && strcmp(filter.dev, m.name))
+			continue;
+
+		len = parse_hex(hexa, (unsigned char *)&m.addr.data, sizeof(m.addr.data));
+		if (len >= 0) {
+			struct ma_info *ma = malloc(sizeof(m));
+
+			memcpy(ma, &m, sizeof(m));
+			ma->addr.bytelen = len;
+			ma->addr.bitlen = len<<3;
+			if (st)
+				ma->features = "static";
+			maddr_ins(result_p, ma);
+		}
+	}
+	fclose(fp);
+}
+
+static void read_igmp(struct ma_info **result_p)
+{
+	struct ma_info m = {
+		.addr.family = AF_INET,
+		.addr.bitlen = 32,
+		.addr.bytelen = 4,
+	};
+	char buf[256];
+	FILE *fp = fopen("/proc/net/igmp", "r");
+
+	if (!fp)
+		return;
+	if (!fgets(buf, sizeof(buf), fp)) {
+		fclose(fp);
+		return;
+	}
+
+	while (fgets(buf, sizeof(buf), fp)) {
+		struct ma_info *ma;
+
+		if (buf[0] != '\t') {
+			size_t len;
+
+			sscanf(buf, "%d%s", &m.index, m.name);
+			len = strlen(m.name);
+			if (m.name[len - 1] == ':')
+				m.name[len - 1] = '\0';
+			continue;
+		}
+
+		if (filter.dev && strcmp(filter.dev, m.name))
+			continue;
+
+		sscanf(buf, "%08x%d", (__u32 *)&m.addr.data, &m.users);
+
+		ma = malloc(sizeof(m));
+		memcpy(ma, &m, sizeof(m));
+		maddr_ins(result_p, ma);
+	}
+	fclose(fp);
+}
+
+
+static void read_igmp6(struct ma_info **result_p)
+{
+	char buf[256];
+	FILE *fp = fopen("/proc/net/igmp6", "r");
+
+	if (!fp)
+		return;
+
+	while (fgets(buf, sizeof(buf), fp)) {
+		char hexa[256];
+		struct ma_info m = { .addr.family = AF_INET6 };
+		int len;
+
+		sscanf(buf, "%d%s%s%d", &m.index, m.name, hexa, &m.users);
+
+		if (filter.dev && strcmp(filter.dev, m.name))
+			continue;
+
+		len = parse_hex(hexa, (unsigned char *)&m.addr.data, sizeof(m.addr.data));
+		if (len >= 0) {
+			struct ma_info *ma = malloc(sizeof(m));
+
+			memcpy(ma, &m, sizeof(m));
+
+			ma->addr.bytelen = len;
+			ma->addr.bitlen = len<<3;
+			maddr_ins(result_p, ma);
+		}
+	}
+	fclose(fp);
+}
+
+static void print_maddr(FILE *fp, struct ma_info *list)
+{
+	print_string(PRINT_FP, NULL, "\t", NULL);
+
+	open_json_object(NULL);
+	if (list->addr.family == AF_PACKET) {
+		SPRINT_BUF(b1);
+
+		print_string(PRINT_FP, NULL, "link  ", NULL);
+		print_color_string(PRINT_ANY, COLOR_MAC, "link", "%s",
+				   ll_addr_n2a((void *)list->addr.data, list->addr.bytelen,
+					       0, b1, sizeof(b1)));
+	} else {
+		print_string(PRINT_ANY, "family", "%-5s ",
+			     family_name(list->addr.family));
+		print_color_string(PRINT_ANY, ifa_family_color(list->addr.family),
+				   "address", "%s",
+				   format_host(list->addr.family,
+					       -1, list->addr.data));
+	}
+
+	if (list->users != 1)
+		print_uint(PRINT_ANY, "users", " users %u", list->users);
+
+	if (list->features)
+		print_string(PRINT_ANY, "features", " %s", list->features);
+
+	print_string(PRINT_FP, NULL, "\n", NULL);
+	close_json_object();
+}
+
+static void print_mlist(FILE *fp, struct ma_info *list)
+{
+	int cur_index = 0;
+
+	new_json_obj(json);
+	for (; list; list = list->next) {
+
+		if (list->index != cur_index || oneline) {
+			if (cur_index) {
+				close_json_array(PRINT_JSON, NULL);
+				close_json_object();
+			}
+			open_json_object(NULL);
+
+			print_uint(PRINT_ANY, "ifindex", "%d:", list->index);
+			print_color_string(PRINT_ANY, COLOR_IFNAME,
+					   "ifname", "\t%s", list->name);
+			print_nl();
+			cur_index = list->index;
+
+			open_json_array(PRINT_JSON, "maddr");
+		}
+
+		print_maddr(fp, list);
+	}
+	if (cur_index) {
+		close_json_array(PRINT_JSON, NULL);
+		close_json_object();
+	}
+
+	delete_json_obj();
+}
+
+static int multiaddr_list(int argc, char **argv)
+{
+	struct ma_info *list = NULL;
+
+	if (!filter.family)
+		filter.family = preferred_family;
+
+	while (argc > 0) {
+		if (1) {
+			if (strcmp(*argv, "dev") == 0) {
+				NEXT_ARG();
+			} else if (matches(*argv, "help") == 0)
+				usage();
+			if (filter.dev)
+				duparg2("dev", *argv);
+			filter.dev = *argv;
+		}
+		argv++; argc--;
+	}
+
+	if (!filter.family || filter.family == AF_PACKET)
+		read_dev_mcast(&list);
+	if (!filter.family || filter.family == AF_INET)
+		read_igmp(&list);
+	if (!filter.family || filter.family == AF_INET6)
+		read_igmp6(&list);
+	print_mlist(stdout, list);
+	return 0;
+}
+
+static int multiaddr_modify(int cmd, int argc, char **argv)
+{
+	struct ifreq ifr = {};
+	int family;
+	int fd;
+
+	if (cmd == RTM_NEWADDR)
+		cmd = SIOCADDMULTI;
+	else
+		cmd = SIOCDELMULTI;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (ifr.ifr_name[0])
+				duparg("dev", *argv);
+			if (get_ifname(ifr.ifr_name, *argv))
+				invarg("\"dev\" not a valid ifname", *argv);
+		} else {
+			if (matches(*argv, "address") == 0) {
+				NEXT_ARG();
+			}
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (ifr.ifr_hwaddr.sa_data[0])
+				duparg("address", *argv);
+			if (ll_addr_a2n(ifr.ifr_hwaddr.sa_data,
+					14, *argv) < 0) {
+				fprintf(stderr, "Error: \"%s\" is not a legal ll address.\n", *argv);
+				exit(1);
+			}
+		}
+		argc--; argv++;
+	}
+	if (ifr.ifr_name[0] == 0) {
+		fprintf(stderr, "Not enough information: \"dev\" is required.\n");
+		exit(-1);
+	}
+
+	switch (preferred_family) {
+	case AF_INET6:
+	case AF_PACKET:
+	case AF_INET:
+		family = preferred_family;
+		break;
+	default:
+		family = AF_INET;
+	}
+
+	fd = socket(family, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		perror("Cannot create socket");
+		exit(1);
+	}
+	if (ioctl(fd, cmd, (char *)&ifr) != 0) {
+		perror("ioctl");
+		exit(1);
+	}
+	close(fd);
+
+	exit(0);
+}
+
+
+int do_multiaddr(int argc, char **argv)
+{
+	if (argc < 1)
+		return multiaddr_list(0, NULL);
+	if (matches(*argv, "add") == 0)
+		return multiaddr_modify(RTM_NEWADDR, argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return multiaddr_modify(RTM_DELADDR, argc-1, argv+1);
+	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+	    || matches(*argv, "lst") == 0)
+		return multiaddr_list(argc-1, argv+1);
+	if (matches(*argv, "help") == 0)
+		usage();
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip maddr help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c
new file mode 100644
index 0000000..685be52
--- /dev/null
+++ b/ip/ipmonitor.c
@@ -0,0 +1,355 @@
+/*
+ * ipmonitor.c		"ip monitor".
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <time.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void usage(void) __attribute__((noreturn));
+static int prefix_banner;
+int listen_all_nsid;
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip monitor [ all | LISTofOBJECTS ] [ FILE ] [ label ] [all-nsid] [dev DEVICE]\n"
+		"LISTofOBJECTS := link | address | route | mroute | prefix |\n"
+		"		 neigh | netconf | rule | nsid\n"
+		"FILE := file FILENAME\n");
+	exit(-1);
+}
+
+static void print_headers(FILE *fp, char *label, struct rtnl_ctrl_data *ctrl)
+{
+	if (timestamp)
+		print_timestamp(fp);
+
+	if (listen_all_nsid) {
+		if (ctrl == NULL || ctrl->nsid < 0)
+			fprintf(fp, "[nsid current]");
+		else
+			fprintf(fp, "[nsid %d]", ctrl->nsid);
+	}
+
+	if (prefix_banner)
+		fprintf(fp, "%s", label);
+}
+
+static int accept_msg(struct rtnl_ctrl_data *ctrl,
+		      struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+
+	switch (n->nlmsg_type) {
+	case RTM_NEWROUTE:
+	case RTM_DELROUTE: {
+		struct rtmsg *r = NLMSG_DATA(n);
+		int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
+
+		if (len < 0) {
+			fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+			return -1;
+		}
+
+		if (r->rtm_flags & RTM_F_CLONED)
+			return 0;
+
+		if (r->rtm_family == RTNL_FAMILY_IPMR ||
+		    r->rtm_family == RTNL_FAMILY_IP6MR) {
+			print_headers(fp, "[MROUTE]", ctrl);
+			print_mroute(n, arg);
+			return 0;
+		} else {
+			print_headers(fp, "[ROUTE]", ctrl);
+			print_route(n, arg);
+			return 0;
+		}
+	}
+
+	case RTM_NEWNEXTHOP:
+	case RTM_DELNEXTHOP:
+		print_headers(fp, "[NEXTHOP]", ctrl);
+		print_nexthop(n, arg);
+		return 0;
+
+	case RTM_NEWLINK:
+	case RTM_DELLINK:
+		ll_remember_index(n, NULL);
+		print_headers(fp, "[LINK]", ctrl);
+		print_linkinfo(n, arg);
+		return 0;
+
+	case RTM_NEWADDR:
+	case RTM_DELADDR:
+		print_headers(fp, "[ADDR]", ctrl);
+		print_addrinfo(n, arg);
+		return 0;
+
+	case RTM_NEWADDRLABEL:
+	case RTM_DELADDRLABEL:
+		print_headers(fp, "[ADDRLABEL]", ctrl);
+		print_addrlabel(n, arg);
+		return 0;
+
+	case RTM_NEWNEIGH:
+	case RTM_DELNEIGH:
+	case RTM_GETNEIGH:
+		if (preferred_family) {
+			struct ndmsg *r = NLMSG_DATA(n);
+
+			if (r->ndm_family != preferred_family)
+				return 0;
+		}
+
+		print_headers(fp, "[NEIGH]", ctrl);
+		print_neigh(n, arg);
+		return 0;
+
+	case RTM_NEWPREFIX:
+		print_headers(fp, "[PREFIX]", ctrl);
+		print_prefix(n, arg);
+		return 0;
+
+	case RTM_NEWRULE:
+	case RTM_DELRULE:
+		print_headers(fp, "[RULE]", ctrl);
+		print_rule(n, arg);
+		return 0;
+
+	case NLMSG_TSTAMP:
+		print_nlmsg_timestamp(fp, n);
+		return 0;
+
+	case RTM_NEWNETCONF:
+	case RTM_DELNETCONF:
+		print_headers(fp, "[NETCONF]", ctrl);
+		print_netconf(ctrl, n, arg);
+		return 0;
+
+	case RTM_DELNSID:
+	case RTM_NEWNSID:
+		print_headers(fp, "[NSID]", ctrl);
+		print_nsid(n, arg);
+		return 0;
+
+	case NLMSG_ERROR:
+	case NLMSG_NOOP:
+	case NLMSG_DONE:
+		break;	/* ignore */
+
+	default:
+		fprintf(stderr,
+			"Unknown message: type=0x%08x(%d) flags=0x%08x(%d) len=0x%08x(%d)\n",
+			n->nlmsg_type, n->nlmsg_type,
+			n->nlmsg_flags, n->nlmsg_flags, n->nlmsg_len,
+			n->nlmsg_len);
+	}
+	return 0;
+}
+
+int do_ipmonitor(int argc, char **argv)
+{
+	int lnexthop = 0, nh_set = 1;
+	char *file = NULL;
+	unsigned int groups = 0;
+	int llink = 0;
+	int laddr = 0;
+	int lroute = 0;
+	int lmroute = 0;
+	int lprefix = 0;
+	int lneigh = 0;
+	int lnetconf = 0;
+	int lrule = 0;
+	int lnsid = 0;
+	int ifindex = 0;
+
+	groups |= nl_mgrp(RTNLGRP_LINK);
+	groups |= nl_mgrp(RTNLGRP_IPV4_IFADDR);
+	groups |= nl_mgrp(RTNLGRP_IPV6_IFADDR);
+	groups |= nl_mgrp(RTNLGRP_IPV4_ROUTE);
+	groups |= nl_mgrp(RTNLGRP_IPV6_ROUTE);
+	groups |= nl_mgrp(RTNLGRP_MPLS_ROUTE);
+	groups |= nl_mgrp(RTNLGRP_IPV4_MROUTE);
+	groups |= nl_mgrp(RTNLGRP_IPV6_MROUTE);
+	groups |= nl_mgrp(RTNLGRP_IPV6_PREFIX);
+	groups |= nl_mgrp(RTNLGRP_NEIGH);
+	groups |= nl_mgrp(RTNLGRP_IPV4_NETCONF);
+	groups |= nl_mgrp(RTNLGRP_IPV6_NETCONF);
+	groups |= nl_mgrp(RTNLGRP_IPV4_RULE);
+	groups |= nl_mgrp(RTNLGRP_IPV6_RULE);
+	groups |= nl_mgrp(RTNLGRP_NSID);
+	groups |= nl_mgrp(RTNLGRP_MPLS_NETCONF);
+
+	rtnl_close(&rth);
+
+	while (argc > 0) {
+		if (matches(*argv, "file") == 0) {
+			NEXT_ARG();
+			file = *argv;
+		} else if (matches(*argv, "label") == 0) {
+			prefix_banner = 1;
+		} else if (matches(*argv, "link") == 0) {
+			llink = 1;
+			groups = 0;
+			nh_set = 0;
+		} else if (matches(*argv, "address") == 0) {
+			laddr = 1;
+			groups = 0;
+			nh_set = 0;
+		} else if (matches(*argv, "route") == 0) {
+			lroute = 1;
+			groups = 0;
+			nh_set = 0;
+		} else if (matches(*argv, "mroute") == 0) {
+			lmroute = 1;
+			groups = 0;
+			nh_set = 0;
+		} else if (matches(*argv, "prefix") == 0) {
+			lprefix = 1;
+			groups = 0;
+			nh_set = 0;
+		} else if (matches(*argv, "neigh") == 0) {
+			lneigh = 1;
+			groups = 0;
+			nh_set = 0;
+		} else if (matches(*argv, "netconf") == 0) {
+			lnetconf = 1;
+			groups = 0;
+			nh_set = 0;
+		} else if (matches(*argv, "rule") == 0) {
+			lrule = 1;
+			groups = 0;
+			nh_set = 0;
+		} else if (matches(*argv, "nsid") == 0) {
+			lnsid = 1;
+			groups = 0;
+			nh_set = 0;
+		} else if (matches(*argv, "nexthop") == 0) {
+			lnexthop = 1;
+			groups = 0;
+		} else if (strcmp(*argv, "all") == 0) {
+			prefix_banner = 1;
+		} else if (matches(*argv, "all-nsid") == 0) {
+			listen_all_nsid = 1;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Device does not exist\n", *argv);
+		} else {
+			fprintf(stderr, "Argument \"%s\" is unknown, try \"ip monitor help\".\n", *argv);
+			exit(-1);
+		}
+		argc--;	argv++;
+	}
+
+	ipaddr_reset_filter(1, ifindex);
+	iproute_reset_filter(ifindex);
+	ipmroute_reset_filter(ifindex);
+	ipneigh_reset_filter(ifindex);
+	ipnetconf_reset_filter(ifindex);
+
+	if (llink)
+		groups |= nl_mgrp(RTNLGRP_LINK);
+	if (laddr) {
+		if (!preferred_family || preferred_family == AF_INET)
+			groups |= nl_mgrp(RTNLGRP_IPV4_IFADDR);
+		if (!preferred_family || preferred_family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_IFADDR);
+	}
+	if (lroute) {
+		if (!preferred_family || preferred_family == AF_INET)
+			groups |= nl_mgrp(RTNLGRP_IPV4_ROUTE);
+		if (!preferred_family || preferred_family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_ROUTE);
+		if (!preferred_family || preferred_family == AF_MPLS)
+			groups |= nl_mgrp(RTNLGRP_MPLS_ROUTE);
+	}
+	if (lmroute) {
+		if (!preferred_family || preferred_family == AF_INET)
+			groups |= nl_mgrp(RTNLGRP_IPV4_MROUTE);
+		if (!preferred_family || preferred_family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_MROUTE);
+	}
+	if (lprefix) {
+		if (!preferred_family || preferred_family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_PREFIX);
+	}
+	if (lneigh) {
+		groups |= nl_mgrp(RTNLGRP_NEIGH);
+	}
+	if (lnetconf) {
+		if (!preferred_family || preferred_family == AF_INET)
+			groups |= nl_mgrp(RTNLGRP_IPV4_NETCONF);
+		if (!preferred_family || preferred_family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_NETCONF);
+		if (!preferred_family || preferred_family == AF_MPLS)
+			groups |= nl_mgrp(RTNLGRP_MPLS_NETCONF);
+	}
+	if (lrule) {
+		if (!preferred_family || preferred_family == AF_INET)
+			groups |= nl_mgrp(RTNLGRP_IPV4_RULE);
+		if (!preferred_family || preferred_family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_RULE);
+	}
+	if (lnsid) {
+		groups |= nl_mgrp(RTNLGRP_NSID);
+	}
+	if (nh_set)
+		lnexthop = 1;
+
+	if (file) {
+		FILE *fp;
+		int err;
+
+		fp = fopen(file, "r");
+		if (fp == NULL) {
+			perror("Cannot fopen");
+			exit(-1);
+		}
+		err = rtnl_from_file(fp, accept_msg, stdout);
+		fclose(fp);
+		return err;
+	}
+
+	if (rtnl_open(&rth, groups) < 0)
+		exit(1);
+
+	if (lnexthop && rtnl_add_nl_group(&rth, RTNLGRP_NEXTHOP) < 0) {
+		fprintf(stderr, "Failed to add nexthop group to list\n");
+		exit(1);
+	}
+
+	if (listen_all_nsid && rtnl_listen_all_nsid(&rth) < 0)
+		exit(1);
+
+	ll_init_map(&rth);
+	netns_nsid_socket_init();
+	netns_map_init();
+
+	if (rtnl_listen(&rth, accept_msg, stdout) < 0)
+		exit(2);
+
+	return 0;
+}
diff --git a/ip/ipmroute.c b/ip/ipmroute.c
new file mode 100644
index 0000000..656ea0d
--- /dev/null
+++ b/ip/ipmroute.c
@@ -0,0 +1,340 @@
+/*
+ * ipmroute.c		"ip mroute".
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <linux/netdevice.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/sockios.h>
+
+#include <rt_names.h>
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip mroute show [ [ to ] PREFIX ] [ from PREFIX ] [ iif DEVICE ]\n"
+	"			[ table TABLE_ID ]\n"
+	"TABLE_ID := [ local | main | default | all | NUMBER ]\n"
+#if 0
+	"Usage: ip mroute [ add | del ] DESTINATION from SOURCE [ iif DEVICE ] [ oif DEVICE ]\n"
+#endif
+	);
+	exit(-1);
+}
+
+static struct rtfilter {
+	int tb;
+	int af;
+	int iif;
+	inet_prefix mdst;
+	inet_prefix msrc;
+} filter;
+
+int print_mroute(struct nlmsghdr *n, void *arg)
+{
+	struct rtmsg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[RTA_MAX+1];
+	FILE *fp = arg;
+	const char *src, *dst;
+	SPRINT_BUF(b1);
+	SPRINT_BUF(b2);
+	__u32 table;
+	int iif = 0;
+	int family;
+
+	if ((n->nlmsg_type != RTM_NEWROUTE &&
+	     n->nlmsg_type != RTM_DELROUTE)) {
+		fprintf(stderr, "Not a multicast route: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (r->rtm_type != RTN_MULTICAST) {
+		fprintf(stderr,
+			"Non multicast route received, kernel does support IP multicast?\n");
+		return -1;
+	}
+
+	parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+	table = rtm_get_table(r, tb);
+
+	if (filter.tb > 0 && filter.tb != table)
+		return 0;
+
+	if (tb[RTA_IIF])
+		iif = rta_getattr_u32(tb[RTA_IIF]);
+	if (filter.iif && filter.iif != iif)
+		return 0;
+
+	if (filter.af && filter.af != r->rtm_family)
+		return 0;
+
+	if (inet_addr_match_rta(&filter.mdst, tb[RTA_DST]))
+		return 0;
+
+	if (inet_addr_match_rta(&filter.msrc, tb[RTA_SRC]))
+		return 0;
+
+	family = get_real_family(r->rtm_type, r->rtm_family);
+
+	open_json_object(NULL);
+	if (n->nlmsg_type == RTM_DELROUTE)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	if (tb[RTA_SRC])
+		src = rt_addr_n2a_r(family, RTA_PAYLOAD(tb[RTA_SRC]),
+				    RTA_DATA(tb[RTA_SRC]), b1, sizeof(b1));
+	else
+		src = "unknown";
+
+	if (tb[RTA_DST])
+		dst = rt_addr_n2a_r(family, RTA_PAYLOAD(tb[RTA_DST]),
+				    RTA_DATA(tb[RTA_DST]), b2, sizeof(b2));
+	else
+		dst = "unknown";
+
+	if (is_json_context()) {
+		print_string(PRINT_JSON, "src", NULL, src);
+		print_string(PRINT_JSON, "dst", NULL, dst);
+	} else {
+		char obuf[256];
+
+		snprintf(obuf, sizeof(obuf), "(%s,%s)", src, dst);
+		print_string(PRINT_FP, NULL,
+			     "%-32s Iif: ", obuf);
+	}
+
+	if (iif)
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "iif", "%-10s ", ll_index_to_name(iif));
+	else
+		print_string(PRINT_ANY,"iif", "%s ", "unresolved");
+
+	if (tb[RTA_MULTIPATH]) {
+		struct rtnexthop *nh = RTA_DATA(tb[RTA_MULTIPATH]);
+		int first = 1;
+
+		open_json_array(PRINT_JSON, "multipath");
+		len = RTA_PAYLOAD(tb[RTA_MULTIPATH]);
+
+		for (;;) {
+			if (len < sizeof(*nh))
+				break;
+			if (nh->rtnh_len > len)
+				break;
+
+			open_json_object(NULL);
+			if (first) {
+				print_string(PRINT_FP, NULL, "Oifs: ", NULL);
+				first = 0;
+			}
+
+			print_color_string(PRINT_ANY, COLOR_IFNAME,
+					   "oif", "%s", ll_index_to_name(nh->rtnh_ifindex));
+
+			if (nh->rtnh_hops > 1)
+				print_uint(PRINT_ANY,
+					   "ttl", "(ttl %u) ", nh->rtnh_hops);
+			else
+				print_string(PRINT_FP, NULL, " ", NULL);
+
+			close_json_object();
+			len -= NLMSG_ALIGN(nh->rtnh_len);
+			nh = RTNH_NEXT(nh);
+		}
+		close_json_array(PRINT_JSON, NULL);
+	}
+
+	print_string(PRINT_ANY, "state", " State: %s",
+		     (r->rtm_flags & RTNH_F_UNRESOLVED) ? "unresolved" : "resolved");
+
+	if (r->rtm_flags & RTNH_F_OFFLOAD)
+		print_null(PRINT_ANY, "offload", " offload", NULL);
+
+	if (show_stats && tb[RTA_MFC_STATS]) {
+		struct rta_mfc_stats *mfcs = RTA_DATA(tb[RTA_MFC_STATS]);
+
+		print_nl();
+		print_u64(PRINT_ANY, "packets", "  %"PRIu64" packets,",
+			   mfcs->mfcs_packets);
+		print_u64(PRINT_ANY, "bytes", " %"PRIu64" bytes", mfcs->mfcs_bytes);
+
+		if (mfcs->mfcs_wrong_if)
+			print_u64(PRINT_ANY, "wrong_if",
+				   ", %"PRIu64" arrived on wrong iif.",
+				   mfcs->mfcs_wrong_if);
+	}
+
+	if (show_stats && tb[RTA_EXPIRES]) {
+		struct timeval tv;
+		double age;
+
+		__jiffies_to_tv(&tv, rta_getattr_u64(tb[RTA_EXPIRES]));
+		age = tv.tv_sec;
+		age += tv.tv_usec / 1000000.;
+		print_float(PRINT_ANY, "expires",
+			    ", Age %.2f", age);
+	}
+
+	if (table && (table != RT_TABLE_MAIN || show_details > 0) && !filter.tb)
+		print_string(PRINT_ANY, "table", " Table: %s",
+			     rtnl_rttable_n2a(table, b1, sizeof(b1)));
+
+	print_string(PRINT_FP, NULL, "\n", NULL);
+	close_json_object();
+	fflush(fp);
+	return 0;
+}
+
+void ipmroute_reset_filter(int ifindex)
+{
+	memset(&filter, 0, sizeof(filter));
+	filter.mdst.bitlen = -1;
+	filter.msrc.bitlen = -1;
+	filter.iif = ifindex;
+}
+
+static int iproute_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+	int err;
+
+	if (filter.tb) {
+		err = addattr32(nlh, reqlen, RTA_TABLE, filter.tb);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int mroute_list(int argc, char **argv)
+{
+	char *id = NULL;
+	int family = preferred_family;
+
+	ipmroute_reset_filter(0);
+	if (family == AF_INET || family == AF_UNSPEC) {
+		family = RTNL_FAMILY_IPMR;
+		filter.af = RTNL_FAMILY_IPMR;
+		filter.tb = RT_TABLE_DEFAULT;  /* for backward compatibility */
+	} else if (family == AF_INET6) {
+		family = RTNL_FAMILY_IP6MR;
+		filter.af = RTNL_FAMILY_IP6MR;
+	} else {
+		/* family does not have multicast routing */
+		return 0;
+	}
+
+	filter.msrc.family = filter.mdst.family = family;
+
+	while (argc > 0) {
+		if (matches(*argv, "table") == 0) {
+			__u32 tid;
+
+			NEXT_ARG();
+			if (rtnl_rttable_a2n(&tid, *argv)) {
+				if (strcmp(*argv, "all") == 0) {
+					filter.tb = 0;
+				} else if (strcmp(*argv, "help") == 0) {
+					usage();
+				} else {
+					invarg("table id value is invalid\n", *argv);
+				}
+			} else
+				filter.tb = tid;
+		} else if (strcmp(*argv, "iif") == 0) {
+			NEXT_ARG();
+			id = *argv;
+		} else if (matches(*argv, "from") == 0) {
+			NEXT_ARG();
+			if (get_prefix(&filter.msrc, *argv, family))
+				invarg("from value is invalid\n", *argv);
+		} else {
+			if (strcmp(*argv, "to") == 0) {
+				NEXT_ARG();
+			}
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (get_prefix(&filter.mdst, *argv, family))
+				invarg("to value is invalid\n", *argv);
+		}
+		argc--; argv++;
+	}
+
+	ll_init_map(&rth);
+
+	if (id)  {
+		int idx;
+
+		idx = ll_name_to_index(id);
+		if (!idx)
+			return nodev(id);
+		filter.iif = idx;
+	}
+
+	if (rtnl_routedump_req(&rth, filter.af, iproute_dump_filter) < 0) {
+		perror("Cannot send dump request");
+		return 1;
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&rth, print_mroute, stdout) < 0) {
+		delete_json_obj();
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	delete_json_obj();
+
+	return 0;
+}
+
+int do_multiroute(int argc, char **argv)
+{
+	if (argc < 1)
+		return mroute_list(0, NULL);
+#if 0
+	if (matches(*argv, "add") == 0)
+		return mroute_modify(RTM_NEWADDR, argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return mroute_modify(RTM_DELADDR, argc-1, argv+1);
+	if (matches(*argv, "get") == 0)
+		return mroute_get(argc-1, argv+1);
+#endif
+	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+	    || matches(*argv, "lst") == 0)
+		return mroute_list(argc-1, argv+1);
+	if (matches(*argv, "help") == 0)
+		usage();
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip mroute help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ipneigh.c b/ip/ipneigh.c
new file mode 100644
index 0000000..a3869c8
--- /dev/null
+++ b/ip/ipneigh.c
@@ -0,0 +1,631 @@
+/*
+ * ipneigh.c		"ip neigh".
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+#define NUD_VALID	(NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE|NUD_PROBE|NUD_STALE|NUD_DELAY)
+#define MAX_ROUNDS	10
+
+static struct
+{
+	int family;
+	int index;
+	int state;
+	int unused_only;
+	inet_prefix pfx;
+	int flushed;
+	char *flushb;
+	int flushp;
+	int flushe;
+	int master;
+	int protocol;
+	__u8 ndm_flags;
+} filter;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip neigh { add | del | change | replace }\n"
+		"		{ ADDR [ lladdr LLADDR ] [ nud STATE ] | proxy ADDR } [ dev DEV ]\n"
+		"		[ router ] [ extern_learn ] [ protocol PROTO ]\n"
+		"\n"
+		"	ip neigh { show | flush } [ proxy ] [ to PREFIX ] [ dev DEV ] [ nud STATE ]\n"
+		"				  [ vrf NAME ]\n"
+		"\n"
+		"STATE := { permanent | noarp | stale | reachable | none |\n"
+		"           incomplete | delay | probe | failed }\n");
+	exit(-1);
+}
+
+static int nud_state_a2n(unsigned int *state, const char *arg)
+{
+	if (matches(arg, "permanent") == 0)
+		*state = NUD_PERMANENT;
+	else if (matches(arg, "reachable") == 0)
+		*state = NUD_REACHABLE;
+	else if (strcmp(arg, "noarp") == 0)
+		*state = NUD_NOARP;
+	else if (strcmp(arg, "none") == 0)
+		*state = NUD_NONE;
+	else if (strcmp(arg, "stale") == 0)
+		*state = NUD_STALE;
+	else if (strcmp(arg, "incomplete") == 0)
+		*state = NUD_INCOMPLETE;
+	else if (strcmp(arg, "delay") == 0)
+		*state = NUD_DELAY;
+	else if (strcmp(arg, "probe") == 0)
+		*state = NUD_PROBE;
+	else if (matches(arg, "failed") == 0)
+		*state = NUD_FAILED;
+	else {
+		if (get_unsigned(state, arg, 0))
+			return -1;
+		if (*state >= 0x100 || (*state&((*state)-1)))
+			return -1;
+	}
+	return 0;
+}
+
+static int flush_update(void)
+{
+	if (rtnl_send_check(&rth, filter.flushb, filter.flushp) < 0) {
+		perror("Failed to send flush request");
+		return -1;
+	}
+	filter.flushp = 0;
+	return 0;
+}
+
+
+static int ipneigh_modify(int cmd, int flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct ndmsg		ndm;
+		char			buf[256];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST | flags,
+		.n.nlmsg_type = cmd,
+		.ndm.ndm_family = preferred_family,
+		.ndm.ndm_state = NUD_PERMANENT,
+	};
+	char  *dev = NULL;
+	int dst_ok = 0;
+	int dev_ok = 0;
+	int lladdr_ok = 0;
+	char *lla = NULL;
+	inet_prefix dst;
+
+	while (argc > 0) {
+		if (matches(*argv, "lladdr") == 0) {
+			NEXT_ARG();
+			if (lladdr_ok)
+				duparg("lladdr", *argv);
+			lla = *argv;
+			lladdr_ok = 1;
+		} else if (strcmp(*argv, "nud") == 0) {
+			unsigned int state;
+
+			NEXT_ARG();
+			if (nud_state_a2n(&state, *argv))
+				invarg("nud state is bad", *argv);
+			req.ndm.ndm_state = state;
+		} else if (matches(*argv, "proxy") == 0) {
+			NEXT_ARG();
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (dst_ok)
+				duparg("address", *argv);
+			get_addr(&dst, *argv, preferred_family);
+			dst_ok = 1;
+			dev_ok = 1;
+			req.ndm.ndm_flags |= NTF_PROXY;
+		} else if (strcmp(*argv, "router") == 0) {
+			req.ndm.ndm_flags |= NTF_ROUTER;
+		} else if (matches(*argv, "extern_learn") == 0) {
+			req.ndm.ndm_flags |= NTF_EXT_LEARNED;
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			dev = *argv;
+			dev_ok = 1;
+		} else if (matches(*argv, "protocol") == 0) {
+			__u32 proto;
+
+			NEXT_ARG();
+			if (rtnl_rtprot_a2n(&proto, *argv))
+				invarg("\"protocol\" value is invalid\n", *argv);
+			if (addattr8(&req.n, sizeof(req), NDA_PROTOCOL, proto))
+				return -1;
+		} else {
+			if (strcmp(*argv, "to") == 0) {
+				NEXT_ARG();
+			}
+			if (matches(*argv, "help") == 0) {
+				NEXT_ARG();
+			}
+			if (dst_ok)
+				duparg2("to", *argv);
+			get_addr(&dst, *argv, preferred_family);
+			dst_ok = 1;
+		}
+		argc--; argv++;
+	}
+	if (!dev_ok || !dst_ok || dst.family == AF_UNSPEC) {
+		fprintf(stderr, "Device and destination are required arguments.\n");
+		exit(-1);
+	}
+	req.ndm.ndm_family = dst.family;
+	if (addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen) < 0)
+		return -1;
+
+	if (lla && strcmp(lla, "null")) {
+		char llabuf[20];
+		int l;
+
+		l = ll_addr_a2n(llabuf, sizeof(llabuf), lla);
+		if (l < 0)
+			return -1;
+
+		if (addattr_l(&req.n, sizeof(req), NDA_LLADDR, llabuf, l) < 0)
+			return -1;
+	}
+
+	ll_init_map(&rth);
+
+	if (dev) {
+		req.ndm.ndm_ifindex = ll_name_to_index(dev);
+		if (!req.ndm.ndm_ifindex)
+			return nodev(dev);
+	}
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		exit(2);
+
+	return 0;
+}
+
+static void print_cacheinfo(const struct nda_cacheinfo *ci)
+{
+	static int hz;
+
+	if (!hz)
+		hz = get_user_hz();
+
+	if (ci->ndm_refcnt)
+		print_uint(PRINT_ANY, "refcnt",
+				" ref %u", ci->ndm_refcnt);
+
+	print_uint(PRINT_ANY, "used", " used %u", ci->ndm_used / hz);
+	print_uint(PRINT_ANY, "confirmed", "/%u", ci->ndm_confirmed / hz);
+	print_uint(PRINT_ANY, "updated", "/%u", ci->ndm_updated / hz);
+}
+
+static void print_neigh_state(unsigned int nud)
+{
+
+	open_json_array(PRINT_JSON,
+			is_json_context() ?  "state" : "");
+
+#define PRINT_FLAG(f)						\
+	if (nud & NUD_##f) {					\
+		nud &= ~NUD_##f;				\
+		print_string(PRINT_ANY, NULL, " %s", #f);	\
+	}
+
+	PRINT_FLAG(INCOMPLETE);
+	PRINT_FLAG(REACHABLE);
+	PRINT_FLAG(STALE);
+	PRINT_FLAG(DELAY);
+	PRINT_FLAG(PROBE);
+	PRINT_FLAG(FAILED);
+	PRINT_FLAG(NOARP);
+	PRINT_FLAG(PERMANENT);
+#undef PRINT_FLAG
+
+	close_json_array(PRINT_JSON, NULL);
+}
+
+int print_neigh(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct ndmsg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[NDA_MAX+1];
+	static int logit = 1;
+	__u8 protocol = 0;
+
+	if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH &&
+	    n->nlmsg_type != RTM_GETNEIGH) {
+		fprintf(stderr, "Not RTM_NEWNEIGH: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+
+		return 0;
+	}
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (filter.flushb && n->nlmsg_type != RTM_NEWNEIGH)
+		return 0;
+
+	if (filter.family && filter.family != r->ndm_family)
+		return 0;
+	if (filter.index && filter.index != r->ndm_ifindex)
+		return 0;
+	if (!(filter.state&r->ndm_state) &&
+	    !(r->ndm_flags & NTF_PROXY) &&
+	    !(r->ndm_flags & NTF_EXT_LEARNED) &&
+	    (r->ndm_state || !(filter.state&0x100)) &&
+	    (r->ndm_family != AF_DECnet))
+		return 0;
+
+	if (filter.master && !(n->nlmsg_flags & NLM_F_DUMP_FILTERED)) {
+		if (logit) {
+			logit = 0;
+			fprintf(fp,
+				"\nWARNING: Kernel does not support filtering by master device\n\n");
+		}
+	}
+
+	parse_rtattr(tb, NDA_MAX, NDA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+	if (inet_addr_match_rta(&filter.pfx, tb[NDA_DST]))
+		return 0;
+
+	if (tb[NDA_PROTOCOL])
+		protocol = rta_getattr_u8(tb[NDA_PROTOCOL]);
+
+	if (filter.protocol && filter.protocol != protocol)
+		return 0;
+
+	if (filter.unused_only && tb[NDA_CACHEINFO]) {
+		struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]);
+
+		if (ci->ndm_refcnt)
+			return 0;
+	}
+
+	if (filter.flushb) {
+		struct nlmsghdr *fn;
+
+		if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) {
+			if (flush_update())
+				return -1;
+		}
+		fn = (struct nlmsghdr *)(filter.flushb + NLMSG_ALIGN(filter.flushp));
+		memcpy(fn, n, n->nlmsg_len);
+		fn->nlmsg_type = RTM_DELNEIGH;
+		fn->nlmsg_flags = NLM_F_REQUEST;
+		fn->nlmsg_seq = ++rth.seq;
+		filter.flushp = (((char *)fn) + n->nlmsg_len) - filter.flushb;
+		filter.flushed++;
+		if (show_stats < 2)
+			return 0;
+	}
+
+	open_json_object(NULL);
+	if (n->nlmsg_type == RTM_DELNEIGH)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+	else if (n->nlmsg_type == RTM_GETNEIGH)
+		print_null(PRINT_ANY, "miss", "%s ", "miss");
+
+	if (tb[NDA_DST]) {
+		const char *dst;
+		int family = r->ndm_family;
+
+		if (family == AF_BRIDGE) {
+			if (RTA_PAYLOAD(tb[NDA_DST]) == sizeof(struct in6_addr))
+				family = AF_INET6;
+			else
+				family = AF_INET;
+		}
+
+		dst = format_host_rta(family, tb[NDA_DST]);
+		print_color_string(PRINT_ANY,
+				   ifa_family_color(family),
+				   "dst", "%s ", dst);
+	}
+
+	if (!filter.index && r->ndm_ifindex) {
+		if (!is_json_context())
+			fprintf(fp, "dev ");
+
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "dev", "%s ",
+				   ll_index_to_name(r->ndm_ifindex));
+	}
+
+	if (tb[NDA_LLADDR]) {
+		const char *lladdr;
+		SPRINT_BUF(b1);
+
+		lladdr = ll_addr_n2a(RTA_DATA(tb[NDA_LLADDR]),
+				     RTA_PAYLOAD(tb[NDA_LLADDR]),
+				     ll_index_to_type(r->ndm_ifindex),
+				     b1, sizeof(b1));
+
+		if (!is_json_context())
+			fprintf(fp, "lladdr ");
+
+		print_color_string(PRINT_ANY, COLOR_MAC,
+				   "lladdr", "%s", lladdr);
+	}
+
+	if (r->ndm_flags & NTF_ROUTER)
+		print_null(PRINT_ANY, "router", " %s", "router");
+
+	if (r->ndm_flags & NTF_PROXY)
+		print_null(PRINT_ANY, "proxy", " %s", "proxy");
+
+	if (r->ndm_flags & NTF_EXT_LEARNED)
+		print_null(PRINT_ANY, "extern_learn", " %s ", "extern_learn");
+
+	if (r->ndm_flags & NTF_OFFLOADED)
+		print_null(PRINT_ANY, "offload", " %s", "offload");
+
+	if (show_stats) {
+		if (tb[NDA_CACHEINFO])
+			print_cacheinfo(RTA_DATA(tb[NDA_CACHEINFO]));
+
+		if (tb[NDA_PROBES])
+			print_uint(PRINT_ANY, "probes", " probes %u",
+				   rta_getattr_u32(tb[NDA_PROBES]));
+	}
+
+	if (r->ndm_state)
+		print_neigh_state(r->ndm_state);
+
+	if (protocol) {
+		SPRINT_BUF(b1);
+
+		print_string(PRINT_ANY, "protocol", " proto %s ",
+			     rtnl_rtprot_n2a(protocol, b1, sizeof(b1)));
+	}
+
+	print_string(PRINT_FP, NULL, "\n", "");
+	close_json_object();
+	fflush(stdout);
+
+	return 0;
+}
+
+void ipneigh_reset_filter(int ifindex)
+{
+	memset(&filter, 0, sizeof(filter));
+	filter.state = ~0;
+	filter.index = ifindex;
+}
+
+static int ipneigh_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+	struct ndmsg *ndm = NLMSG_DATA(nlh);
+	int err;
+
+	ndm->ndm_flags = filter.ndm_flags;
+
+	if (filter.index) {
+		err = addattr32(nlh, reqlen, NDA_IFINDEX, filter.index);
+		if (err)
+			return err;
+	}
+	if (filter.master) {
+		err = addattr32(nlh, reqlen, NDA_MASTER, filter.master);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int do_show_or_flush(int argc, char **argv, int flush)
+{
+	char *filter_dev = NULL;
+	int state_given = 0;
+
+	ipneigh_reset_filter(0);
+
+	if (!filter.family)
+		filter.family = preferred_family;
+
+	if (flush) {
+		if (argc <= 0) {
+			fprintf(stderr, "Flush requires arguments.\n");
+			return -1;
+		}
+		filter.state = ~(NUD_PERMANENT|NUD_NOARP);
+	} else
+		filter.state = 0xFF & ~NUD_NOARP;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (filter_dev)
+				duparg("dev", *argv);
+			filter_dev = *argv;
+		} else if (strcmp(*argv, "master") == 0) {
+			int ifindex;
+
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Device does not exist\n", *argv);
+			filter.master = ifindex;
+		} else if (strcmp(*argv, "vrf") == 0) {
+			int ifindex;
+
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Not a valid VRF name\n", *argv);
+			if (!name_is_vrf(*argv))
+				invarg("Not a valid VRF name\n", *argv);
+			filter.master = ifindex;
+		} else if (strcmp(*argv, "unused") == 0) {
+			filter.unused_only = 1;
+		} else if (strcmp(*argv, "nud") == 0) {
+			unsigned int state;
+
+			NEXT_ARG();
+			if (!state_given) {
+				state_given = 1;
+				filter.state = 0;
+			}
+			if (nud_state_a2n(&state, *argv)) {
+				if (strcmp(*argv, "all") != 0)
+					invarg("nud state is bad", *argv);
+				state = ~0;
+				if (flush)
+					state &= ~NUD_NOARP;
+			}
+			if (state == 0)
+				state = 0x100;
+			filter.state |= state;
+		} else if (strcmp(*argv, "proxy") == 0) {
+			filter.ndm_flags = NTF_PROXY;
+		} else if (matches(*argv, "protocol") == 0) {
+			__u32 prot;
+
+			NEXT_ARG();
+			if (rtnl_rtprot_a2n(&prot, *argv)) {
+				if (strcmp(*argv, "all"))
+					invarg("invalid \"protocol\"\n", *argv);
+				prot = 0;
+			}
+			filter.protocol = prot;
+		} else {
+			if (strcmp(*argv, "to") == 0) {
+				NEXT_ARG();
+			}
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (get_prefix(&filter.pfx, *argv, filter.family))
+				invarg("to value is invalid\n", *argv);
+			if (filter.family == AF_UNSPEC)
+				filter.family = filter.pfx.family;
+		}
+		argc--; argv++;
+	}
+
+	ll_init_map(&rth);
+
+	if (filter_dev) {
+		filter.index = ll_name_to_index(filter_dev);
+		if (!filter.index)
+			return nodev(filter_dev);
+	}
+
+	if (flush) {
+		int round = 0;
+		char flushb[4096-512];
+
+		filter.flushb = flushb;
+		filter.flushp = 0;
+		filter.flushe = sizeof(flushb);
+
+		while (round < MAX_ROUNDS) {
+			if (rtnl_neighdump_req(&rth, filter.family,
+					       ipneigh_dump_filter) < 0) {
+				perror("Cannot send dump request");
+				exit(1);
+			}
+			filter.flushed = 0;
+			if (rtnl_dump_filter(&rth, print_neigh, stdout) < 0) {
+				fprintf(stderr, "Flush terminated\n");
+				exit(1);
+			}
+			if (filter.flushed == 0) {
+				if (show_stats) {
+					if (round == 0)
+						printf("Nothing to flush.\n");
+					else
+						printf("*** Flush is complete after %d round%s ***\n", round, round > 1?"s":"");
+				}
+				fflush(stdout);
+				return 0;
+			}
+			round++;
+			if (flush_update() < 0)
+				exit(1);
+			if (show_stats) {
+				printf("\n*** Round %d, deleting %d entries ***\n", round, filter.flushed);
+				fflush(stdout);
+			}
+			filter.state &= ~NUD_FAILED;
+		}
+		printf("*** Flush not complete bailing out after %d rounds\n",
+			MAX_ROUNDS);
+		return 1;
+	}
+
+	if (rtnl_neighdump_req(&rth, filter.family, ipneigh_dump_filter) < 0) {
+		perror("Cannot send dump request");
+		exit(1);
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&rth, print_neigh, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	delete_json_obj();
+
+	return 0;
+}
+
+int do_ipneigh(int argc, char **argv)
+{
+	if (argc > 0) {
+		if (matches(*argv, "add") == 0)
+			return ipneigh_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+		if (matches(*argv, "change") == 0 ||
+		    strcmp(*argv, "chg") == 0)
+			return ipneigh_modify(RTM_NEWNEIGH, NLM_F_REPLACE, argc-1, argv+1);
+		if (matches(*argv, "replace") == 0)
+			return ipneigh_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
+		if (matches(*argv, "delete") == 0)
+			return ipneigh_modify(RTM_DELNEIGH, 0, argc-1, argv+1);
+		if (matches(*argv, "get") == 0) {
+			fprintf(stderr, "Sorry, \"neigh get\" is not implemented :-(\n");
+			return -1;
+		}
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return do_show_or_flush(argc-1, argv+1, 0);
+		if (matches(*argv, "flush") == 0)
+			return do_show_or_flush(argc-1, argv+1, 1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return do_show_or_flush(0, NULL, 0);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip neigh help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ipnetconf.c b/ip/ipnetconf.c
new file mode 100644
index 0000000..0e946ca
--- /dev/null
+++ b/ip/ipnetconf.c
@@ -0,0 +1,256 @@
+/*
+ * ipnetconf.c		"ip netconf".
+ *
+ *		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.
+ *
+ * Authors:	Nicolas Dichtel, <nicolas.dichtel@6wind.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static struct {
+	int family;
+	int ifindex;
+} filter;
+
+static const char * const rp_filter_names[] = {
+	"off", "strict", "loose"
+};
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ip netconf show [ dev STRING ]\n");
+	exit(-1);
+}
+
+static void print_onoff(FILE *fp, const char *flag, __u32 val)
+{
+	if (is_json_context())
+		print_bool(PRINT_JSON, flag, NULL, val);
+	else
+		fprintf(fp, "%s %s ", flag, val ? "on" : "off");
+}
+
+static struct rtattr *netconf_rta(struct netconfmsg *ncm)
+{
+	return (struct rtattr *)((char *)ncm
+				 + NLMSG_ALIGN(sizeof(struct netconfmsg)));
+}
+
+int print_netconf(struct rtnl_ctrl_data *ctrl, struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct netconfmsg *ncm = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[NETCONFA_MAX+1];
+	int ifindex = 0;
+
+	if (n->nlmsg_type == NLMSG_ERROR)
+		return -1;
+
+	if (n->nlmsg_type != RTM_NEWNETCONF &&
+	    n->nlmsg_type != RTM_DELNETCONF) {
+		fprintf(stderr, "Not a netconf message: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+
+		return -1;
+	}
+	len -= NLMSG_SPACE(sizeof(*ncm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (filter.family && filter.family != ncm->ncm_family)
+		return 0;
+
+	parse_rtattr(tb, NETCONFA_MAX, netconf_rta(ncm),
+		     NLMSG_PAYLOAD(n, sizeof(*ncm)));
+
+	if (tb[NETCONFA_IFINDEX])
+		ifindex = rta_getattr_u32(tb[NETCONFA_IFINDEX]);
+
+	if (filter.ifindex && filter.ifindex != ifindex)
+		return 0;
+
+	open_json_object(NULL);
+	if (n->nlmsg_type == RTM_DELNETCONF)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	print_string(PRINT_ANY, "family",
+		     "%s ", family_name(ncm->ncm_family));
+
+	if (tb[NETCONFA_IFINDEX]) {
+		const char *dev;
+
+		switch (ifindex) {
+		case NETCONFA_IFINDEX_ALL:
+			dev = "all";
+			break;
+		case NETCONFA_IFINDEX_DEFAULT:
+			dev = "default";
+			break;
+		default:
+			dev = ll_index_to_name(ifindex);
+			break;
+		}
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "interface", "%s ", dev);
+	}
+
+	if (tb[NETCONFA_FORWARDING])
+		print_onoff(fp, "forwarding",
+				rta_getattr_u32(tb[NETCONFA_FORWARDING]));
+
+	if (tb[NETCONFA_RP_FILTER]) {
+		__u32 rp_filter = rta_getattr_u32(tb[NETCONFA_RP_FILTER]);
+
+		if (rp_filter < ARRAY_SIZE(rp_filter_names))
+			print_string(PRINT_ANY, "rp_filter",
+				     "rp_filter %s ",
+				     rp_filter_names[rp_filter]);
+		else
+			print_uint(PRINT_ANY, "rp_filter",
+				   "rp_filter %u ", rp_filter);
+	}
+
+	if (tb[NETCONFA_MC_FORWARDING])
+		print_onoff(fp, "mc_forwarding",
+				rta_getattr_u32(tb[NETCONFA_MC_FORWARDING]));
+
+	if (tb[NETCONFA_PROXY_NEIGH])
+		print_onoff(fp, "proxy_neigh",
+				rta_getattr_u32(tb[NETCONFA_PROXY_NEIGH]));
+
+	if (tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN])
+		print_onoff(fp, "ignore_routes_with_linkdown",
+		     rta_getattr_u32(tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN]));
+
+	if (tb[NETCONFA_INPUT])
+		print_onoff(fp, "input", rta_getattr_u32(tb[NETCONFA_INPUT]));
+
+	close_json_object();
+	print_string(PRINT_FP, NULL, "\n", NULL);
+	fflush(fp);
+	return 0;
+}
+
+static int print_netconf2(struct nlmsghdr *n, void *arg)
+{
+	return print_netconf(NULL, n, arg);
+}
+
+void ipnetconf_reset_filter(int ifindex)
+{
+	memset(&filter, 0, sizeof(filter));
+	filter.ifindex = ifindex;
+}
+
+static int do_show(int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr		n;
+		struct netconfmsg	ncm;
+		char			buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct netconfmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+		.n.nlmsg_type = RTM_GETNETCONF,
+	};
+
+	ipnetconf_reset_filter(0);
+	filter.family = preferred_family;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			filter.ifindex = ll_name_to_index(*argv);
+			if (filter.ifindex <= 0) {
+				fprintf(stderr,
+					"Device \"%s\" does not exist.\n",
+					*argv);
+				return -1;
+			}
+		}
+		argv++; argc--;
+	}
+
+	ll_init_map(&rth);
+
+	if (filter.ifindex && filter.family != AF_UNSPEC) {
+		req.ncm.ncm_family = filter.family;
+		addattr_l(&req.n, sizeof(req), NETCONFA_IFINDEX,
+			  &filter.ifindex, sizeof(filter.ifindex));
+
+		if (rtnl_send(&rth, &req.n, req.n.nlmsg_len) < 0) {
+			perror("Can not send request");
+			exit(1);
+		}
+		rtnl_listen(&rth, print_netconf, stdout);
+	} else {
+		rth.flags = RTNL_HANDLE_F_SUPPRESS_NLERR;
+dump:
+		if (rtnl_netconfdump_req(&rth, filter.family) < 0) {
+			perror("Cannot send dump request");
+			exit(1);
+		}
+
+		new_json_obj(json);
+		if (rtnl_dump_filter(&rth, print_netconf2, stdout) < 0) {
+			/* kernel does not support netconf dump on AF_UNSPEC;
+			 * fall back to requesting by family
+			 */
+			if (errno == EOPNOTSUPP &&
+			    filter.family == AF_UNSPEC) {
+				filter.family = AF_INET;
+				goto dump;
+			}
+			perror("RTNETLINK answers");
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+		delete_json_obj();
+		if (preferred_family == AF_UNSPEC && filter.family == AF_INET) {
+			preferred_family = AF_INET6;
+			filter.family = AF_INET6;
+			goto dump;
+		}
+	}
+	return 0;
+}
+
+int do_ipnetconf(int argc, char **argv)
+{
+	if (argc > 0) {
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return do_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return do_show(0, NULL);
+
+	fprintf(stderr,
+		"Command \"%s\" is unknown, try \"ip netconf help\".\n",
+		*argv);
+	exit(-1);
+}
diff --git a/ip/ipnetns.c b/ip/ipnetns.c
new file mode 100644
index 0000000..fc58a04
--- /dev/null
+++ b/ip/ipnetns.c
@@ -0,0 +1,1042 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#define _ATFILE_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/inotify.h>
+#include <sys/mount.h>
+#include <sys/syscall.h>
+#include <stdio.h>
+#include <string.h>
+#include <sched.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <linux/limits.h>
+
+#include <linux/net_namespace.h>
+
+#include "utils.h"
+#include "list.h"
+#include "ip_common.h"
+#include "namespace.h"
+#include "json_print.h"
+
+static int usage(void)
+{
+	fprintf(stderr,
+		"Usage:	ip netns list\n"
+		"	ip netns add NAME\n"
+		"	ip netns attach NAME PID\n"
+		"	ip netns set NAME NETNSID\n"
+		"	ip [-all] netns delete [NAME]\n"
+		"	ip netns identify [PID]\n"
+		"	ip netns pids NAME\n"
+		"	ip [-all] netns exec [NAME] cmd ...\n"
+		"	ip netns monitor\n"
+		"	ip netns list-id [target-nsid POSITIVE-INT] [nsid POSITIVE-INT]\n"
+		"NETNSID := auto | POSITIVE-INT\n");
+	exit(-1);
+}
+
+/* This socket is used to get nsid */
+static struct rtnl_handle rtnsh = { .fd = -1 };
+
+static int have_rtnl_getnsid = -1;
+static int saved_netns = -1;
+static struct link_filter filter;
+
+static int ipnetns_accept_msg(struct rtnl_ctrl_data *ctrl,
+			      struct nlmsghdr *n, void *arg)
+{
+	struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(n);
+
+	if (n->nlmsg_type == NLMSG_ERROR &&
+	    (err->error == -EOPNOTSUPP || err->error == -EINVAL))
+		have_rtnl_getnsid = 0;
+	else
+		have_rtnl_getnsid = 1;
+	return -1;
+}
+
+static int ipnetns_have_nsid(void)
+{
+	struct {
+		struct nlmsghdr n;
+		struct rtgenmsg g;
+		char            buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETNSID,
+		.g.rtgen_family = AF_UNSPEC,
+	};
+	int fd;
+
+	if (have_rtnl_getnsid < 0) {
+		fd = open("/proc/self/ns/net", O_RDONLY);
+		if (fd < 0) {
+			have_rtnl_getnsid = 0;
+			return 0;
+		}
+
+		addattr32(&req.n, 1024, NETNSA_FD, fd);
+
+		if (rtnl_send(&rth, &req.n, req.n.nlmsg_len) < 0) {
+			perror("request send failed");
+			exit(1);
+		}
+		rtnl_listen(&rth, ipnetns_accept_msg, NULL);
+		close(fd);
+	}
+
+	return have_rtnl_getnsid;
+}
+
+int get_netnsid_from_name(const char *name)
+{
+	struct {
+		struct nlmsghdr n;
+		struct rtgenmsg g;
+		char            buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETNSID,
+		.g.rtgen_family = AF_UNSPEC,
+	};
+	struct nlmsghdr *answer;
+	struct rtattr *tb[NETNSA_MAX + 1];
+	struct rtgenmsg *rthdr;
+	int len, fd, ret = -1;
+
+	netns_nsid_socket_init();
+
+	fd = netns_get_fd(name);
+	if (fd < 0)
+		return fd;
+
+	addattr32(&req.n, 1024, NETNSA_FD, fd);
+	if (rtnl_talk(&rtnsh, &req.n, &answer) < 0) {
+		close(fd);
+		return -2;
+	}
+	close(fd);
+
+	/* Validate message and parse attributes */
+	if (answer->nlmsg_type == NLMSG_ERROR)
+		goto out;
+
+	rthdr = NLMSG_DATA(answer);
+	len = answer->nlmsg_len - NLMSG_SPACE(sizeof(*rthdr));
+	if (len < 0)
+		goto out;
+
+	parse_rtattr(tb, NETNSA_MAX, NETNS_RTA(rthdr), len);
+
+	if (tb[NETNSA_NSID]) {
+		ret = rta_getattr_u32(tb[NETNSA_NSID]);
+	}
+
+out:
+	free(answer);
+	return ret;
+}
+
+struct nsid_cache {
+	struct hlist_node	nsid_hash;
+	struct hlist_node	name_hash;
+	int			nsid;
+	char			name[0];
+};
+
+#define NSIDMAP_SIZE		128
+#define NSID_HASH_NSID(nsid)	(nsid & (NSIDMAP_SIZE - 1))
+#define NSID_HASH_NAME(name)	(namehash(name) & (NSIDMAP_SIZE - 1))
+
+static struct hlist_head	nsid_head[NSIDMAP_SIZE];
+static struct hlist_head	name_head[NSIDMAP_SIZE];
+
+static struct nsid_cache *netns_map_get_by_nsid(int nsid)
+{
+	uint32_t h = NSID_HASH_NSID(nsid);
+	struct hlist_node *n;
+
+	hlist_for_each(n, &nsid_head[h]) {
+		struct nsid_cache *c = container_of(n, struct nsid_cache,
+						    nsid_hash);
+		if (c->nsid == nsid)
+			return c;
+	}
+
+	return NULL;
+}
+
+char *get_name_from_nsid(int nsid)
+{
+	struct nsid_cache *c;
+
+	netns_nsid_socket_init();
+	netns_map_init();
+
+	c = netns_map_get_by_nsid(nsid);
+	if (c)
+		return c->name;
+
+	return NULL;
+}
+
+static int netns_map_add(int nsid, const char *name)
+{
+	struct nsid_cache *c;
+	uint32_t h;
+
+	if (netns_map_get_by_nsid(nsid) != NULL)
+		return -EEXIST;
+
+	c = malloc(sizeof(*c) + strlen(name) + 1);
+	if (c == NULL) {
+		perror("malloc");
+		return -ENOMEM;
+	}
+	c->nsid = nsid;
+	strcpy(c->name, name);
+
+	h = NSID_HASH_NSID(nsid);
+	hlist_add_head(&c->nsid_hash, &nsid_head[h]);
+
+	h = NSID_HASH_NAME(name);
+	hlist_add_head(&c->name_hash, &name_head[h]);
+
+	return 0;
+}
+
+static void netns_map_del(struct nsid_cache *c)
+{
+	hlist_del(&c->name_hash);
+	hlist_del(&c->nsid_hash);
+	free(c);
+}
+
+void netns_nsid_socket_init(void)
+{
+	if (rtnsh.fd > -1 || !ipnetns_have_nsid())
+		return;
+
+	if (rtnl_open(&rtnsh, 0) < 0) {
+		fprintf(stderr, "Cannot open rtnetlink\n");
+		exit(1);
+	}
+
+}
+
+void netns_map_init(void)
+{
+	static int initialized;
+	struct dirent *entry;
+	DIR *dir;
+	int nsid;
+
+	if (initialized || !ipnetns_have_nsid())
+		return;
+
+	dir = opendir(NETNS_RUN_DIR);
+	if (!dir)
+		return;
+
+	while ((entry = readdir(dir)) != NULL) {
+		if (strcmp(entry->d_name, ".") == 0)
+			continue;
+		if (strcmp(entry->d_name, "..") == 0)
+			continue;
+		nsid = get_netnsid_from_name(entry->d_name);
+
+		if (nsid >= 0)
+			netns_map_add(nsid, entry->d_name);
+	}
+	closedir(dir);
+	initialized = 1;
+}
+
+static int netns_get_name(int nsid, char *name)
+{
+	struct dirent *entry;
+	DIR *dir;
+	int id;
+
+	dir = opendir(NETNS_RUN_DIR);
+	if (!dir)
+		return -ENOENT;
+
+	while ((entry = readdir(dir)) != NULL) {
+		if (strcmp(entry->d_name, ".") == 0)
+			continue;
+		if (strcmp(entry->d_name, "..") == 0)
+			continue;
+		id = get_netnsid_from_name(entry->d_name);
+
+		if (nsid == id) {
+			strcpy(name, entry->d_name);
+			closedir(dir);
+			return 0;
+		}
+	}
+	closedir(dir);
+	return -ENOENT;
+}
+
+int print_nsid(struct nlmsghdr *n, void *arg)
+{
+	struct rtgenmsg *rthdr = NLMSG_DATA(n);
+	struct rtattr *tb[NETNSA_MAX+1];
+	int len = n->nlmsg_len;
+	FILE *fp = (FILE *)arg;
+	struct nsid_cache *c;
+	char name[NAME_MAX];
+	int nsid, current;
+
+	if (n->nlmsg_type != RTM_NEWNSID && n->nlmsg_type != RTM_DELNSID)
+		return 0;
+
+	len -= NLMSG_SPACE(sizeof(*rthdr));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d in %s\n", len,
+			__func__);
+		return -1;
+	}
+
+	parse_rtattr(tb, NETNSA_MAX, NETNS_RTA(rthdr), len);
+	if (tb[NETNSA_NSID] == NULL) {
+		fprintf(stderr, "BUG: NETNSA_NSID is missing %s\n", __func__);
+		return -1;
+	}
+
+	open_json_object(NULL);
+	if (n->nlmsg_type == RTM_DELNSID)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	nsid = rta_getattr_u32(tb[NETNSA_NSID]);
+	if (nsid < 0)
+		print_string(PRINT_ANY, "nsid", "nsid %s ", "not-assigned");
+	else
+		print_uint(PRINT_ANY, "nsid", "nsid %u ", nsid);
+
+	if (tb[NETNSA_CURRENT_NSID]) {
+		current = rta_getattr_u32(tb[NETNSA_CURRENT_NSID]);
+		if (current < 0)
+			print_string(PRINT_ANY, "current-nsid",
+				     "current-nsid %s ", "not-assigned");
+		else
+			print_uint(PRINT_ANY, "current-nsid",
+				   "current-nsid %u ", current);
+	}
+
+	c = netns_map_get_by_nsid(tb[NETNSA_CURRENT_NSID] ? current : nsid);
+	if (c != NULL) {
+		print_string(PRINT_ANY, "name",
+			     "(iproute2 netns name: %s)", c->name);
+		netns_map_del(c);
+	}
+
+	/* During 'ip monitor nsid', no chance to have new nsid in cache. */
+	if (c == NULL && n->nlmsg_type == RTM_NEWNSID)
+		if (netns_get_name(nsid, name) == 0) {
+			print_string(PRINT_ANY, "name",
+				     "(iproute2 netns name: %s)", name);
+			netns_map_add(nsid, name);
+		}
+
+	print_string(PRINT_FP, NULL, "\n", NULL);
+	close_json_object();
+	fflush(fp);
+	return 0;
+}
+
+static int get_netnsid_from_netnsid(int nsid)
+{
+	struct {
+		struct nlmsghdr n;
+		struct rtgenmsg g;
+		char            buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(NLMSG_ALIGN(sizeof(struct rtgenmsg))),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETNSID,
+		.g.rtgen_family = AF_UNSPEC,
+	};
+	struct nlmsghdr *answer;
+	int err;
+
+	netns_nsid_socket_init();
+
+	err = addattr32(&req.n, sizeof(req), NETNSA_NSID, nsid);
+	if (err)
+		return err;
+
+	if (filter.target_nsid >= 0) {
+		err = addattr32(&req.n, sizeof(req), NETNSA_TARGET_NSID,
+				filter.target_nsid);
+		if (err)
+			return err;
+	}
+
+	if (rtnl_talk(&rtnsh, &req.n, &answer) < 0)
+		return -2;
+
+	/* Validate message and parse attributes */
+	if (answer->nlmsg_type == NLMSG_ERROR)
+		goto err_out;
+
+	new_json_obj(json);
+	err = print_nsid(answer, stdout);
+	delete_json_obj();
+err_out:
+	free(answer);
+	return err;
+}
+
+static int netns_filter_req(struct nlmsghdr *nlh, int reqlen)
+{
+	int err;
+
+	if (filter.target_nsid >= 0) {
+		err = addattr32(nlh, reqlen, NETNSA_TARGET_NSID,
+				filter.target_nsid);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int netns_list_id(int argc, char **argv)
+{
+	int nsid = -1;
+
+	if (!ipnetns_have_nsid()) {
+		fprintf(stderr,
+			"RTM_GETNSID is not supported by the kernel.\n");
+		return -ENOTSUP;
+	}
+
+	filter.target_nsid = -1;
+	while (argc > 0) {
+		if (strcmp(*argv, "target-nsid") == 0) {
+			if (filter.target_nsid >= 0)
+				duparg("target-nsid", *argv);
+			NEXT_ARG();
+
+			if (get_integer(&filter.target_nsid, *argv, 0))
+				invarg("\"target-nsid\" value is invalid\n",
+				       *argv);
+			else if (filter.target_nsid < 0)
+				invarg("\"target-nsid\" value should be >= 0\n",
+				       argv[1]);
+		} else if (strcmp(*argv, "nsid") == 0) {
+			if (nsid >= 0)
+				duparg("nsid", *argv);
+			NEXT_ARG();
+
+			if (get_integer(&nsid, *argv, 0))
+				invarg("\"nsid\" value is invalid\n", *argv);
+			else if (nsid < 0)
+				invarg("\"nsid\" value should be >= 0\n",
+				       argv[1]);
+		} else
+			usage();
+		argc--; argv++;
+	}
+
+	if (nsid >= 0)
+		return get_netnsid_from_netnsid(nsid);
+
+	if (rtnl_nsiddump_req_filter_fn(&rth, AF_UNSPEC,
+					netns_filter_req) < 0) {
+		perror("Cannot send dump request");
+		exit(1);
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&rth, print_nsid, stdout) < 0) {
+		delete_json_obj();
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	delete_json_obj();
+	return 0;
+}
+
+static int netns_list(int argc, char **argv)
+{
+	struct dirent *entry;
+	DIR *dir;
+	int id;
+
+	dir = opendir(NETNS_RUN_DIR);
+	if (!dir)
+		return 0;
+
+	new_json_obj(json);
+	while ((entry = readdir(dir)) != NULL) {
+		if (strcmp(entry->d_name, ".") == 0)
+			continue;
+		if (strcmp(entry->d_name, "..") == 0)
+			continue;
+
+		open_json_object(NULL);
+		print_string(PRINT_ANY, "name",
+			     "%s", entry->d_name);
+		if (ipnetns_have_nsid()) {
+			id = get_netnsid_from_name(entry->d_name);
+			if (id >= 0)
+				print_uint(PRINT_ANY, "id",
+					   " (id: %d)", id);
+		}
+		print_string(PRINT_FP, NULL, "\n", NULL);
+		close_json_object();
+	}
+	closedir(dir);
+	delete_json_obj();
+	return 0;
+}
+
+static int do_switch(void *arg)
+{
+	char *netns = arg;
+
+	/* we just changed namespaces. clear any vrf association
+	 * with prior namespace before exec'ing command
+	 */
+	vrf_reset();
+
+	return netns_switch(netns);
+}
+
+static int on_netns_exec(char *nsname, void *arg)
+{
+	char **argv = arg;
+
+	printf("\nnetns: %s\n", nsname);
+	cmd_exec(argv[0], argv, true, do_switch, nsname);
+	return 0;
+}
+
+static int netns_exec(int argc, char **argv)
+{
+	/* Setup the proper environment for apps that are not netns
+	 * aware, and execute a program in that environment.
+	 */
+	if (argc < 1 && !do_all) {
+		fprintf(stderr, "No netns name specified\n");
+		return -1;
+	}
+	if ((argc < 2 && !do_all) || (argc < 1 && do_all)) {
+		fprintf(stderr, "No command specified\n");
+		return -1;
+	}
+
+	if (do_all)
+		return netns_foreach(on_netns_exec, argv);
+
+	/* ip must return the status of the child,
+	 * but do_cmd() will add a minus to this,
+	 * so let's add another one here to cancel it.
+	 */
+	return -cmd_exec(argv[1], argv + 1, !!batch_mode, do_switch, argv[0]);
+}
+
+static int is_pid(const char *str)
+{
+	int ch;
+
+	for (; (ch = *str); str++) {
+		if (!isdigit(ch))
+			return 0;
+	}
+	return 1;
+}
+
+static int netns_pids(int argc, char **argv)
+{
+	const char *name;
+	char net_path[PATH_MAX];
+	int netns;
+	struct stat netst;
+	DIR *dir;
+	struct dirent *entry;
+
+	if (argc < 1) {
+		fprintf(stderr, "No netns name specified\n");
+		return -1;
+	}
+	if (argc > 1) {
+		fprintf(stderr, "extra arguments specified\n");
+		return -1;
+	}
+
+	name = argv[0];
+	snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name);
+	netns = open(net_path, O_RDONLY);
+	if (netns < 0) {
+		fprintf(stderr, "Cannot open network namespace: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	if (fstat(netns, &netst) < 0) {
+		fprintf(stderr, "Stat of netns failed: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	dir = opendir("/proc/");
+	if (!dir) {
+		fprintf(stderr, "Open of /proc failed: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	while ((entry = readdir(dir))) {
+		char pid_net_path[PATH_MAX];
+		struct stat st;
+
+		if (!is_pid(entry->d_name))
+			continue;
+		snprintf(pid_net_path, sizeof(pid_net_path), "/proc/%s/ns/net",
+			entry->d_name);
+		if (stat(pid_net_path, &st) != 0)
+			continue;
+		if ((st.st_dev == netst.st_dev) &&
+		    (st.st_ino == netst.st_ino)) {
+			printf("%s\n", entry->d_name);
+		}
+	}
+	closedir(dir);
+	return 0;
+
+}
+
+int netns_identify_pid(const char *pidstr, char *name, int len)
+{
+	char net_path[PATH_MAX];
+	int netns;
+	struct stat netst;
+	DIR *dir;
+	struct dirent *entry;
+
+	name[0] = '\0';
+
+	snprintf(net_path, sizeof(net_path), "/proc/%s/ns/net", pidstr);
+	netns = open(net_path, O_RDONLY);
+	if (netns < 0) {
+		fprintf(stderr, "Cannot open network namespace: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	if (fstat(netns, &netst) < 0) {
+		fprintf(stderr, "Stat of netns failed: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	dir = opendir(NETNS_RUN_DIR);
+	if (!dir) {
+		/* Succeed treat a missing directory as an empty directory */
+		if (errno == ENOENT)
+			return 0;
+
+		fprintf(stderr, "Failed to open directory %s:%s\n",
+			NETNS_RUN_DIR, strerror(errno));
+		return -1;
+	}
+
+	while ((entry = readdir(dir))) {
+		char name_path[PATH_MAX];
+		struct stat st;
+
+		if (strcmp(entry->d_name, ".") == 0)
+			continue;
+		if (strcmp(entry->d_name, "..") == 0)
+			continue;
+
+		snprintf(name_path, sizeof(name_path), "%s/%s",	NETNS_RUN_DIR,
+			entry->d_name);
+
+		if (stat(name_path, &st) != 0)
+			continue;
+
+		if ((st.st_dev == netst.st_dev) &&
+		    (st.st_ino == netst.st_ino)) {
+			strlcpy(name, entry->d_name, len);
+		}
+	}
+	closedir(dir);
+	return 0;
+
+}
+
+static int netns_identify(int argc, char **argv)
+{
+	const char *pidstr;
+	char name[256];
+	int rc;
+
+	if (argc < 1) {
+		pidstr = "self";
+	} else if (argc > 1) {
+		fprintf(stderr, "extra arguments specified\n");
+		return -1;
+	} else {
+		pidstr = argv[0];
+		if (!is_pid(pidstr)) {
+			fprintf(stderr, "Specified string '%s' is not a pid\n",
+					pidstr);
+			return -1;
+		}
+	}
+
+	rc = netns_identify_pid(pidstr, name, sizeof(name));
+	if (!rc)
+		printf("%s\n", name);
+
+	return rc;
+}
+
+static int on_netns_del(char *nsname, void *arg)
+{
+	char netns_path[PATH_MAX];
+
+	snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, nsname);
+	umount2(netns_path, MNT_DETACH);
+	if (unlink(netns_path) < 0) {
+		fprintf(stderr, "Cannot remove namespace file \"%s\": %s\n",
+			netns_path, strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+
+static int netns_delete(int argc, char **argv)
+{
+	if (argc < 1 && !do_all) {
+		fprintf(stderr, "No netns name specified\n");
+		return -1;
+	}
+
+	if (do_all)
+		return netns_foreach(on_netns_del, NULL);
+
+	return on_netns_del(argv[0], NULL);
+}
+
+static int create_netns_dir(void)
+{
+	/* Create the base netns directory if it doesn't exist */
+	if (mkdir(NETNS_RUN_DIR, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) {
+		if (errno != EEXIST) {
+			fprintf(stderr, "mkdir %s failed: %s\n",
+				NETNS_RUN_DIR, strerror(errno));
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/* Obtain a FD for the current namespace, so we can reenter it later */
+static void netns_save(void)
+{
+	if (saved_netns != -1)
+		return;
+
+	saved_netns = open("/proc/self/ns/net", O_RDONLY | O_CLOEXEC);
+	if (saved_netns == -1) {
+		perror("Cannot open init namespace");
+		exit(1);
+	}
+}
+
+static void netns_restore(void)
+{
+	if (saved_netns == -1)
+		return;
+
+	if (setns(saved_netns, CLONE_NEWNET)) {
+		perror("setns");
+		exit(1);
+	}
+
+	close(saved_netns);
+	saved_netns = -1;
+}
+
+static int netns_add(int argc, char **argv, bool create)
+{
+	/* This function creates a new network namespace and
+	 * a new mount namespace and bind them into a well known
+	 * location in the filesystem based on the name provided.
+	 *
+	 * If create is true, a new namespace will be created,
+	 * otherwise an existing one will be attached to the file.
+	 *
+	 * The mount namespace is created so that any necessary
+	 * userspace tweaks like remounting /sys, or bind mounting
+	 * a new /etc/resolv.conf can be shared between users.
+	 */
+	char netns_path[PATH_MAX], proc_path[PATH_MAX];
+	const char *name;
+	pid_t pid;
+	int fd;
+	int made_netns_run_dir_mount = 0;
+
+	if (create) {
+		if (argc < 1) {
+			fprintf(stderr, "No netns name specified\n");
+			return -1;
+		}
+	} else {
+		if (argc < 2) {
+			fprintf(stderr, "No netns name and PID specified\n");
+			return -1;
+		}
+
+		if (get_s32(&pid, argv[1], 0) || !pid) {
+			fprintf(stderr, "Invalid PID: %s\n", argv[1]);
+			return -1;
+		}
+	}
+	name = argv[0];
+
+	snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name);
+
+	if (create_netns_dir())
+		return -1;
+
+	/* Make it possible for network namespace mounts to propagate between
+	 * mount namespaces.  This makes it likely that a unmounting a network
+	 * namespace file in one namespace will unmount the network namespace
+	 * file in all namespaces allowing the network namespace to be freed
+	 * sooner.
+	 */
+	while (mount("", NETNS_RUN_DIR, "none", MS_SHARED | MS_REC, NULL)) {
+		/* Fail unless we need to make the mount point */
+		if (errno != EINVAL || made_netns_run_dir_mount) {
+			fprintf(stderr, "mount --make-shared %s failed: %s\n",
+				NETNS_RUN_DIR, strerror(errno));
+			return -1;
+		}
+
+		/* Upgrade NETNS_RUN_DIR to a mount point */
+		if (mount(NETNS_RUN_DIR, NETNS_RUN_DIR, "none", MS_BIND | MS_REC, NULL)) {
+			fprintf(stderr, "mount --bind %s %s failed: %s\n",
+				NETNS_RUN_DIR, NETNS_RUN_DIR, strerror(errno));
+			return -1;
+		}
+		made_netns_run_dir_mount = 1;
+	}
+
+	/* Create the filesystem state */
+	fd = open(netns_path, O_RDONLY|O_CREAT|O_EXCL, 0);
+	if (fd < 0) {
+		fprintf(stderr, "Cannot create namespace file \"%s\": %s\n",
+			netns_path, strerror(errno));
+		return -1;
+	}
+	close(fd);
+
+	if (create) {
+		netns_save();
+		if (unshare(CLONE_NEWNET) < 0) {
+			fprintf(stderr, "Failed to create a new network namespace \"%s\": %s\n",
+				name, strerror(errno));
+			goto out_delete;
+		}
+
+		strcpy(proc_path, "/proc/self/ns/net");
+	} else {
+		snprintf(proc_path, sizeof(proc_path), "/proc/%d/ns/net", pid);
+	}
+
+	/* Bind the netns last so I can watch for it */
+	if (mount(proc_path, netns_path, "none", MS_BIND, NULL) < 0) {
+		fprintf(stderr, "Bind %s -> %s failed: %s\n",
+			proc_path, netns_path, strerror(errno));
+		goto out_delete;
+	}
+	netns_restore();
+
+	return 0;
+out_delete:
+	if (create) {
+		netns_restore();
+		netns_delete(argc, argv);
+	} else if (unlink(netns_path) < 0) {
+		fprintf(stderr, "Cannot remove namespace file \"%s\": %s\n",
+			netns_path, strerror(errno));
+	}
+	return -1;
+}
+
+int set_netnsid_from_name(const char *name, int nsid)
+{
+	struct {
+		struct nlmsghdr n;
+		struct rtgenmsg g;
+		char            buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_NEWNSID,
+		.g.rtgen_family = AF_UNSPEC,
+	};
+	int fd, err = 0;
+
+	netns_nsid_socket_init();
+
+	fd = netns_get_fd(name);
+	if (fd < 0)
+		return fd;
+
+	addattr32(&req.n, 1024, NETNSA_FD, fd);
+	addattr32(&req.n, 1024, NETNSA_NSID, nsid);
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		err = -2;
+
+	close(fd);
+	return err;
+}
+
+static int netns_set(int argc, char **argv)
+{
+	char netns_path[PATH_MAX];
+	const char *name;
+	int netns, nsid;
+
+	if (argc < 1) {
+		fprintf(stderr, "No netns name specified\n");
+		return -1;
+	}
+	if (argc < 2) {
+		fprintf(stderr, "No nsid specified\n");
+		return -1;
+	}
+	name = argv[0];
+	/* If a negative nsid is specified the kernel will select the nsid. */
+	if (strcmp(argv[1], "auto") == 0)
+		nsid = -1;
+	else if (get_integer(&nsid, argv[1], 0))
+		invarg("Invalid \"netnsid\" value\n", argv[1]);
+	else if (nsid < 0)
+		invarg("\"netnsid\" value should be >= 0\n", argv[1]);
+
+	snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name);
+	netns = open(netns_path, O_RDONLY | O_CLOEXEC);
+	if (netns < 0) {
+		fprintf(stderr, "Cannot open network namespace \"%s\": %s\n",
+			name, strerror(errno));
+		return -1;
+	}
+
+	return set_netnsid_from_name(name, nsid);
+}
+
+static int netns_monitor(int argc, char **argv)
+{
+	char buf[4096];
+	struct inotify_event *event;
+	int fd;
+
+	fd = inotify_init();
+	if (fd < 0) {
+		fprintf(stderr, "inotify_init failed: %s\n",
+			strerror(errno));
+		return -1;
+	}
+
+	if (create_netns_dir())
+		return -1;
+
+	if (inotify_add_watch(fd, NETNS_RUN_DIR, IN_CREATE | IN_DELETE) < 0) {
+		fprintf(stderr, "inotify_add_watch failed: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	for (;;) {
+		ssize_t len = read(fd, buf, sizeof(buf));
+
+		if (len < 0) {
+			fprintf(stderr, "read failed: %s\n",
+				strerror(errno));
+			return -1;
+		}
+		for (event = (struct inotify_event *)buf;
+		     (char *)event < &buf[len];
+		     event = (struct inotify_event *)((char *)event + sizeof(*event) + event->len)) {
+			if (event->mask & IN_CREATE)
+				printf("add %s\n", event->name);
+			if (event->mask & IN_DELETE)
+				printf("delete %s\n", event->name);
+		}
+	}
+	return 0;
+}
+
+static int invalid_name(const char *name)
+{
+	return !*name || strlen(name) > NAME_MAX ||
+		strchr(name, '/') || !strcmp(name, ".") || !strcmp(name, "..");
+}
+
+int do_netns(int argc, char **argv)
+{
+	netns_nsid_socket_init();
+
+	if (argc < 1) {
+		netns_map_init();
+		return netns_list(0, NULL);
+	}
+
+	if (!do_all && argc > 1 && invalid_name(argv[1])) {
+		fprintf(stderr, "Invalid netns name \"%s\"\n", argv[1]);
+		exit(-1);
+	}
+
+	if ((matches(*argv, "list") == 0) || (matches(*argv, "show") == 0) ||
+	    (matches(*argv, "lst") == 0)) {
+		netns_map_init();
+		return netns_list(argc-1, argv+1);
+	}
+
+	if ((matches(*argv, "list-id") == 0)) {
+		netns_map_init();
+		return netns_list_id(argc-1, argv+1);
+	}
+
+	if (matches(*argv, "help") == 0)
+		return usage();
+
+	if (matches(*argv, "add") == 0)
+		return netns_add(argc-1, argv+1, true);
+
+	if (matches(*argv, "set") == 0)
+		return netns_set(argc-1, argv+1);
+
+	if (matches(*argv, "delete") == 0)
+		return netns_delete(argc-1, argv+1);
+
+	if (matches(*argv, "identify") == 0)
+		return netns_identify(argc-1, argv+1);
+
+	if (matches(*argv, "pids") == 0)
+		return netns_pids(argc-1, argv+1);
+
+	if (matches(*argv, "exec") == 0)
+		return netns_exec(argc-1, argv+1);
+
+	if (matches(*argv, "monitor") == 0)
+		return netns_monitor(argc-1, argv+1);
+
+	if (matches(*argv, "attach") == 0)
+		return netns_add(argc-1, argv+1, false);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip netns help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ipnexthop.c b/ip/ipnexthop.c
new file mode 100644
index 0000000..9f860c8
--- /dev/null
+++ b/ip/ipnexthop.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ip nexthop
+ *
+ * Copyright (c) 2017-19 David Ahern <dsahern@gmail.com>
+ */
+
+#include <linux/nexthop.h>
+#include <stdio.h>
+#include <string.h>
+#include <rt_names.h>
+#include <errno.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static struct {
+	unsigned int flushed;
+	unsigned int groups;
+	unsigned int ifindex;
+	unsigned int master;
+	unsigned int proto;
+} filter;
+
+enum {
+	IPNH_LIST,
+	IPNH_FLUSH,
+};
+
+#define RTM_NHA(h)  ((struct rtattr *)(((char *)(h)) + \
+			NLMSG_ALIGN(sizeof(struct nhmsg))))
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip nexthop { list | flush } [ protocol ID ] SELECTOR\n"
+		"       ip nexthop { add | replace } id ID NH [ protocol ID ]\n"
+		"       ip nexthop { get| del } id ID\n"
+		"SELECTOR := [ id ID ] [ dev DEV ] [ vrf NAME ] [ master DEV ]\n"
+		"            [ groups ]\n"
+		"NH := { blackhole | [ via ADDRESS ] [ dev DEV ] [ onlink ]\n"
+		"      [ encap ENCAPTYPE ENCAPHDR ] | group GROUP ] }\n"
+		"GROUP := [ id[,weight]>/<id[,weight]>/... ]\n"
+		"ENCAPTYPE := [ mpls ]\n"
+		"ENCAPHDR := [ MPLSLABEL ]\n");
+	exit(-1);
+}
+
+static int nh_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+	int err;
+
+	if (filter.ifindex) {
+		err = addattr32(nlh, reqlen, NHA_OIF, filter.ifindex);
+		if (err)
+			return err;
+	}
+
+	if (filter.groups) {
+		addattr_l(nlh, reqlen, NHA_GROUPS, NULL, 0);
+		if (err)
+			return err;
+	}
+
+	if (filter.master) {
+		addattr32(nlh, reqlen, NHA_MASTER, filter.master);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static struct rtnl_handle rth_del = { .fd = -1 };
+
+static int delete_nexthop(__u32 id)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct nhmsg	nhm;
+		char		buf[64];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_DELNEXTHOP,
+		.nhm.nh_family = AF_UNSPEC,
+	};
+
+	req.n.nlmsg_seq = ++rth_del.seq;
+
+	addattr32(&req.n, sizeof(req), NHA_ID, id);
+
+	if (rtnl_talk(&rth_del, &req.n, NULL) < 0)
+		return -1;
+	return 0;
+}
+
+static int flush_nexthop(struct nlmsghdr *nlh, void *arg)
+{
+	struct nhmsg *nhm = NLMSG_DATA(nlh);
+	struct rtattr *tb[NHA_MAX+1];
+	__u32 id = 0;
+	int len;
+
+	len = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*nhm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (filter.proto && nhm->nh_protocol != filter.proto)
+		return 0;
+
+	parse_rtattr(tb, NHA_MAX, RTM_NHA(nhm), len);
+	if (tb[NHA_ID])
+		id = rta_getattr_u32(tb[NHA_ID]);
+
+	if (id && !delete_nexthop(id))
+		filter.flushed++;
+
+	return 0;
+}
+
+static int ipnh_flush(unsigned int all)
+{
+	int rc = -2;
+
+	if (all) {
+		filter.groups = 1;
+		filter.ifindex = 0;
+		filter.master = 0;
+	}
+
+	if (rtnl_open(&rth_del, 0) < 0) {
+		fprintf(stderr, "Cannot open rtnetlink\n");
+		return EXIT_FAILURE;
+	}
+again:
+	if (rtnl_nexthopdump_req(&rth, preferred_family, nh_dump_filter) < 0) {
+		perror("Cannot send dump request");
+		goto out;
+	}
+
+	if (rtnl_dump_filter(&rth, flush_nexthop, stdout) < 0) {
+		fprintf(stderr, "Dump terminated. Failed to flush nexthops\n");
+		goto out;
+	}
+
+	/* if deleting all, then remove groups first */
+	if (all && filter.groups) {
+		filter.groups = 0;
+		goto again;
+	}
+
+	rc = 0;
+out:
+	rtnl_close(&rth_del);
+	if (!filter.flushed)
+		printf("Nothing to flush\n");
+	else
+		printf("Flushed %d nexthops\n", filter.flushed);
+
+	return rc;
+}
+
+static void print_nh_group(FILE *fp, const struct rtattr *grps_attr)
+{
+	struct nexthop_grp *nhg = RTA_DATA(grps_attr);
+	int num = RTA_PAYLOAD(grps_attr) / sizeof(*nhg);
+	int i;
+
+	if (!num || num * sizeof(*nhg) != RTA_PAYLOAD(grps_attr)) {
+		fprintf(fp, "<invalid nexthop group>");
+		return;
+	}
+
+	open_json_array(PRINT_JSON, "group");
+	print_string(PRINT_FP, NULL, "%s", "group ");
+	for (i = 0; i < num; ++i) {
+		open_json_object(NULL);
+
+		if (i)
+			print_string(PRINT_FP, NULL, "%s", "/");
+
+		print_uint(PRINT_ANY, "id", "%u", nhg[i].id);
+		if (nhg[i].weight)
+			print_uint(PRINT_ANY, "weight", ",%u", nhg[i].weight + 1);
+
+		close_json_object();
+	}
+	print_string(PRINT_FP, NULL, "%s", " ");
+	close_json_array(PRINT_JSON, NULL);
+}
+
+int print_nexthop(struct nlmsghdr *n, void *arg)
+{
+	struct nhmsg *nhm = NLMSG_DATA(n);
+	struct rtattr *tb[NHA_MAX+1];
+	FILE *fp = (FILE *)arg;
+	int len;
+
+	SPRINT_BUF(b1);
+
+	if (n->nlmsg_type != RTM_DELNEXTHOP &&
+	    n->nlmsg_type != RTM_NEWNEXTHOP) {
+		fprintf(stderr, "Not a nexthop: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return -1;
+	}
+
+	len = n->nlmsg_len - NLMSG_SPACE(sizeof(*nhm));
+	if (len < 0) {
+		close_json_object();
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (filter.proto && filter.proto != nhm->nh_protocol)
+		return 0;
+
+	parse_rtattr(tb, NHA_MAX, RTM_NHA(nhm), len);
+
+	open_json_object(NULL);
+
+	if (n->nlmsg_type == RTM_DELROUTE)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	if (tb[NHA_ID])
+		print_uint(PRINT_ANY, "id", "id %u ",
+			   rta_getattr_u32(tb[NHA_ID]));
+
+	if (tb[NHA_GROUP])
+		print_nh_group(fp, tb[NHA_GROUP]);
+
+	if (tb[NHA_ENCAP])
+		lwt_print_encap(fp, tb[NHA_ENCAP_TYPE], tb[NHA_ENCAP]);
+
+	if (tb[NHA_GATEWAY])
+		print_rta_gateway(fp, nhm->nh_family, tb[NHA_GATEWAY]);
+
+	if (tb[NHA_OIF])
+		print_rta_if(fp, tb[NHA_OIF], "dev");
+
+	if (nhm->nh_scope != RT_SCOPE_UNIVERSE || show_details > 0) {
+		print_string(PRINT_ANY, "scope", "scope %s ",
+			     rtnl_rtscope_n2a(nhm->nh_scope, b1, sizeof(b1)));
+	}
+
+	if (tb[NHA_BLACKHOLE])
+		print_null(PRINT_ANY, "blackhole", "blackhole ", NULL);
+
+	if (nhm->nh_protocol != RTPROT_UNSPEC || show_details > 0) {
+		print_string(PRINT_ANY, "protocol", "proto %s ",
+			     rtnl_rtprot_n2a(nhm->nh_protocol, b1, sizeof(b1)));
+	}
+
+	if (tb[NHA_OIF])
+		print_rt_flags(fp, nhm->nh_flags);
+
+	print_string(PRINT_FP, NULL, "%s", "\n");
+	close_json_object();
+	fflush(fp);
+
+	return 0;
+}
+
+static int add_nh_group_attr(struct nlmsghdr *n, int maxlen, char *argv)
+{
+	struct nexthop_grp *grps;
+	int count = 0, i;
+	char *sep, *wsep;
+
+	if (*argv != '\0')
+		count = 1;
+
+	/* separator is '/' */
+	sep = strchr(argv, '/');
+	while (sep) {
+		count++;
+		sep = strchr(sep + 1, '/');
+	}
+
+	if (count == 0)
+		return -1;
+
+	grps = calloc(count, sizeof(*grps));
+	if (!grps)
+		return -1;
+
+	for (i = 0; i < count; ++i) {
+		sep = strchr(argv, '/');
+		if (sep)
+			*sep = '\0';
+
+		wsep = strchr(argv, ',');
+		if (wsep)
+			*wsep = '\0';
+
+		if (get_unsigned(&grps[i].id, argv, 0))
+			return -1;
+		if (wsep) {
+			unsigned int w;
+
+			wsep++;
+			if (get_unsigned(&w, wsep, 0) || w == 0 || w > 256)
+				invarg("\"weight\" is invalid\n", wsep);
+			grps[i].weight = w - 1;
+		}
+
+		if (!sep)
+			break;
+
+		argv = sep + 1;
+	}
+
+	return addattr_l(n, maxlen, NHA_GROUP, grps, count * sizeof(*grps));
+}
+
+static int ipnh_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct nhmsg	nhm;
+		char		buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST | flags,
+		.n.nlmsg_type = cmd,
+		.nhm.nh_family = preferred_family,
+	};
+	__u32 nh_flags = 0;
+
+	while (argc > 0) {
+		if (!strcmp(*argv, "id")) {
+			__u32 id;
+
+			NEXT_ARG();
+			if (get_unsigned(&id, *argv, 0))
+				invarg("invalid id value", *argv);
+			addattr32(&req.n, sizeof(req), NHA_ID, id);
+		} else if (!strcmp(*argv, "dev")) {
+			int ifindex;
+
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				invarg("Device does not exist\n", *argv);
+			addattr32(&req.n, sizeof(req), NHA_OIF, ifindex);
+			if (req.nhm.nh_family == AF_UNSPEC)
+				req.nhm.nh_family = AF_INET;
+		} else if (strcmp(*argv, "via") == 0) {
+			inet_prefix addr;
+			int family;
+
+			NEXT_ARG();
+			family = read_family(*argv);
+			if (family == AF_UNSPEC)
+				family = req.nhm.nh_family;
+			else
+				NEXT_ARG();
+			get_addr(&addr, *argv, family);
+			if (req.nhm.nh_family == AF_UNSPEC)
+				req.nhm.nh_family = addr.family;
+			else if (req.nhm.nh_family != addr.family)
+				invarg("address family mismatch\n", *argv);
+			addattr_l(&req.n, sizeof(req), NHA_GATEWAY,
+				  &addr.data, addr.bytelen);
+		} else if (strcmp(*argv, "encap") == 0) {
+			char buf[1024];
+			struct rtattr *rta = (void *)buf;
+
+			rta->rta_type = NHA_ENCAP;
+			rta->rta_len = RTA_LENGTH(0);
+
+			lwt_parse_encap(rta, sizeof(buf), &argc, &argv,
+					NHA_ENCAP, NHA_ENCAP_TYPE);
+
+			if (rta->rta_len > RTA_LENGTH(0)) {
+				addraw_l(&req.n, 1024, RTA_DATA(rta),
+					 RTA_PAYLOAD(rta));
+			}
+		} else if (!strcmp(*argv, "blackhole")) {
+			addattr_l(&req.n, sizeof(req), NHA_BLACKHOLE, NULL, 0);
+			if (req.nhm.nh_family == AF_UNSPEC)
+				req.nhm.nh_family = AF_INET;
+		} else if (!strcmp(*argv, "onlink")) {
+			nh_flags |= RTNH_F_ONLINK;
+		} else if (!strcmp(*argv, "group")) {
+			NEXT_ARG();
+
+			if (add_nh_group_attr(&req.n, sizeof(req), *argv))
+				invarg("\"group\" value is invalid\n", *argv);
+		} else if (matches(*argv, "protocol") == 0) {
+			__u32 prot;
+
+			NEXT_ARG();
+			if (rtnl_rtprot_a2n(&prot, *argv))
+				invarg("\"protocol\" value is invalid\n", *argv);
+			req.nhm.nh_protocol = prot;
+		} else if (strcmp(*argv, "help") == 0) {
+			usage();
+		} else {
+			invarg("", *argv);
+		}
+		argc--; argv++;
+	}
+
+	req.nhm.nh_flags = nh_flags;
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static int ipnh_get_id(__u32 id)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct nhmsg	nhm;
+		char		buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type  = RTM_GETNEXTHOP,
+		.nhm.nh_family = preferred_family,
+	};
+	struct nlmsghdr *answer;
+
+	addattr32(&req.n, sizeof(req), NHA_ID, id);
+
+	if (rtnl_talk(&rth, &req.n, &answer) < 0)
+		return -2;
+
+	new_json_obj(json);
+
+	if (print_nexthop(answer, (void *)stdout) < 0) {
+		free(answer);
+		return -1;
+	}
+
+	delete_json_obj();
+	fflush(stdout);
+
+	free(answer);
+
+	return 0;
+}
+
+static int ipnh_list_flush(int argc, char **argv, int action)
+{
+	unsigned int all = (argc == 0);
+
+	while (argc > 0) {
+		if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			filter.ifindex = ll_name_to_index(*argv);
+			if (!filter.ifindex)
+				invarg("Device does not exist\n", *argv);
+		} else if (!matches(*argv, "groups")) {
+			filter.groups = 1;
+		} else if (!matches(*argv, "master")) {
+			NEXT_ARG();
+			filter.master = ll_name_to_index(*argv);
+			if (!filter.master)
+				invarg("Device does not exist\n", *argv);
+		} else if (matches(*argv, "vrf") == 0) {
+			NEXT_ARG();
+			if (!name_is_vrf(*argv))
+				invarg("Invalid VRF\n", *argv);
+			filter.master = ll_name_to_index(*argv);
+			if (!filter.master)
+				invarg("VRF does not exist\n", *argv);
+		} else if (!strcmp(*argv, "id")) {
+			__u32 id;
+
+			NEXT_ARG();
+			if (get_unsigned(&id, *argv, 0))
+				invarg("invalid id value", *argv);
+			return ipnh_get_id(id);
+		} else if (!matches(*argv, "protocol")) {
+			__u32 proto;
+
+			NEXT_ARG();
+			if (get_unsigned(&proto, *argv, 0))
+				invarg("invalid protocol value", *argv);
+			filter.proto = proto;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			invarg("", *argv);
+		}
+		argc--; argv++;
+	}
+
+	if (action == IPNH_FLUSH)
+		return ipnh_flush(all);
+
+	if (rtnl_nexthopdump_req(&rth, preferred_family, nh_dump_filter) < 0) {
+		perror("Cannot send dump request");
+		return -2;
+	}
+
+	new_json_obj(json);
+
+	if (rtnl_dump_filter(&rth, print_nexthop, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return -2;
+	}
+
+	delete_json_obj();
+	fflush(stdout);
+
+	return 0;
+}
+
+static int ipnh_get(int argc, char **argv)
+{
+	__u32 id = 0;
+
+	while (argc > 0) {
+		if (!strcmp(*argv, "id")) {
+			NEXT_ARG();
+			if (get_unsigned(&id, *argv, 0))
+				invarg("invalid id value", *argv);
+		} else  {
+			usage();
+		}
+		argc--; argv++;
+	}
+
+	if (!id) {
+		usage();
+		return -1;
+	}
+
+	return ipnh_get_id(id);
+}
+
+int do_ipnh(int argc, char **argv)
+{
+	if (argc < 1)
+		return ipnh_list_flush(0, NULL, IPNH_LIST);
+
+	if (!matches(*argv, "add"))
+		return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_CREATE|NLM_F_EXCL,
+				   argc-1, argv+1);
+	if (!matches(*argv, "replace"))
+		return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_CREATE|NLM_F_REPLACE,
+				   argc-1, argv+1);
+	if (!matches(*argv, "delete"))
+		return ipnh_modify(RTM_DELNEXTHOP, 0, argc-1, argv+1);
+
+	if (!matches(*argv, "list") ||
+	    !matches(*argv, "show") ||
+	    !matches(*argv, "lst"))
+		return ipnh_list_flush(argc-1, argv+1, IPNH_LIST);
+
+	if (!matches(*argv, "get"))
+		return ipnh_get(argc-1, argv+1);
+
+	if (!matches(*argv, "flush"))
+		return ipnh_list_flush(argc-1, argv+1, IPNH_FLUSH);
+
+	if (!matches(*argv, "help"))
+		usage();
+
+	fprintf(stderr,
+		"Command \"%s\" is unknown, try \"ip nexthop help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ipntable.c b/ip/ipntable.c
new file mode 100644
index 0000000..ddee490
--- /dev/null
+++ b/ip/ipntable.c
@@ -0,0 +1,696 @@
+/*
+ * 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 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, see <http://www.gnu.org/licenses>.
+ */
+/*
+ * based on ipneigh.c
+ */
+/*
+ * Authors:
+ *	Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <time.h>
+
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+static struct
+{
+	int family;
+	int index;
+#define NONE_DEV	(-1)
+	const char *name;
+} filter;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip ntable change name NAME [ dev DEV ]\n"
+		"	 [ thresh1 VAL ] [ thresh2 VAL ] [ thresh3 VAL ] [ gc_int MSEC ]\n"
+		"	 [ PARMS ]\n"
+		"Usage: ip ntable show [ dev DEV ] [ name NAME ]\n"
+
+		"PARMS := [ base_reachable MSEC ] [ retrans MSEC ] [ gc_stale MSEC ]\n"
+		"	 [ delay_probe MSEC ] [ queue LEN ]\n"
+		"	 [ app_probes VAL ] [ ucast_probes VAL ] [ mcast_probes VAL ]\n"
+		"	 [ anycast_delay MSEC ] [ proxy_delay MSEC ] [ proxy_queue LEN ]\n"
+		"	 [ locktime MSEC ]\n"
+		);
+
+	exit(-1);
+}
+
+static int ipntable_modify(int cmd, int flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct ndtmsg		ndtm;
+		char			buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndtmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST | flags,
+		.n.nlmsg_type = cmd,
+		.ndtm.ndtm_family = preferred_family,
+	};
+	char *namep = NULL;
+	char *threshsp = NULL;
+	char *gc_intp = NULL;
+	char parms_buf[1024] = {};
+	struct rtattr *parms_rta = (struct rtattr *)parms_buf;
+	int parms_change = 0;
+
+	parms_rta->rta_type = NDTA_PARMS;
+	parms_rta->rta_len = RTA_LENGTH(0);
+
+	while (argc > 0) {
+		if (strcmp(*argv, "name") == 0) {
+			int len;
+
+			NEXT_ARG();
+			if (namep)
+				duparg("NAME", *argv);
+
+			namep = *argv;
+			len = strlen(namep) + 1;
+			addattr_l(&req.n, sizeof(req), NDTA_NAME, namep, len);
+		} else if (strcmp(*argv, "thresh1") == 0) {
+			__u32 thresh1;
+
+			NEXT_ARG();
+			threshsp = *argv;
+
+			if (get_u32(&thresh1, *argv, 0))
+				invarg("\"thresh1\" value is invalid", *argv);
+
+			addattr32(&req.n, sizeof(req), NDTA_THRESH1, thresh1);
+		} else if (strcmp(*argv, "thresh2") == 0) {
+			__u32 thresh2;
+
+			NEXT_ARG();
+			threshsp = *argv;
+
+			if (get_u32(&thresh2, *argv, 0))
+				invarg("\"thresh2\" value is invalid", *argv);
+
+			addattr32(&req.n, sizeof(req), NDTA_THRESH2, thresh2);
+		} else if (strcmp(*argv, "thresh3") == 0) {
+			__u32 thresh3;
+
+			NEXT_ARG();
+			threshsp = *argv;
+
+			if (get_u32(&thresh3, *argv, 0))
+				invarg("\"thresh3\" value is invalid", *argv);
+
+			addattr32(&req.n, sizeof(req), NDTA_THRESH3, thresh3);
+		} else if (strcmp(*argv, "gc_int") == 0) {
+			__u64 gc_int;
+
+			NEXT_ARG();
+			gc_intp = *argv;
+
+			if (get_u64(&gc_int, *argv, 0))
+				invarg("\"gc_int\" value is invalid", *argv);
+
+			addattr_l(&req.n, sizeof(req), NDTA_GC_INTERVAL,
+				  &gc_int, sizeof(gc_int));
+		} else if (strcmp(*argv, "dev") == 0) {
+			__u32 ifindex;
+
+			NEXT_ARG();
+			ifindex = ll_name_to_index(*argv);
+			if (!ifindex)
+				return nodev(*argv);
+
+			rta_addattr32(parms_rta, sizeof(parms_buf),
+				      NDTPA_IFINDEX, ifindex);
+		} else if (strcmp(*argv, "base_reachable") == 0) {
+			__u64 breachable;
+
+			NEXT_ARG();
+
+			if (get_u64(&breachable, *argv, 0))
+				invarg("\"base_reachable\" value is invalid", *argv);
+
+			rta_addattr_l(parms_rta, sizeof(parms_buf),
+				      NDTPA_BASE_REACHABLE_TIME,
+				      &breachable, sizeof(breachable));
+			parms_change = 1;
+		} else if (strcmp(*argv, "retrans") == 0) {
+			__u64 retrans;
+
+			NEXT_ARG();
+
+			if (get_u64(&retrans, *argv, 0))
+				invarg("\"retrans\" value is invalid", *argv);
+
+			rta_addattr_l(parms_rta, sizeof(parms_buf),
+				      NDTPA_RETRANS_TIME,
+				      &retrans, sizeof(retrans));
+			parms_change = 1;
+		} else if (strcmp(*argv, "gc_stale") == 0) {
+			__u64 gc_stale;
+
+			NEXT_ARG();
+
+			if (get_u64(&gc_stale, *argv, 0))
+				invarg("\"gc_stale\" value is invalid", *argv);
+
+			rta_addattr_l(parms_rta, sizeof(parms_buf),
+				      NDTPA_GC_STALETIME,
+				      &gc_stale, sizeof(gc_stale));
+			parms_change = 1;
+		} else if (strcmp(*argv, "delay_probe") == 0) {
+			__u64 delay_probe;
+
+			NEXT_ARG();
+
+			if (get_u64(&delay_probe, *argv, 0))
+				invarg("\"delay_probe\" value is invalid", *argv);
+
+			rta_addattr_l(parms_rta, sizeof(parms_buf),
+				      NDTPA_DELAY_PROBE_TIME,
+				      &delay_probe, sizeof(delay_probe));
+			parms_change = 1;
+		} else if (strcmp(*argv, "queue") == 0) {
+			__u32 queue;
+
+			NEXT_ARG();
+
+			if (get_u32(&queue, *argv, 0))
+				invarg("\"queue\" value is invalid", *argv);
+
+			rta_addattr32(parms_rta, sizeof(parms_buf),
+				      NDTPA_QUEUE_LEN, queue);
+			parms_change = 1;
+		} else if (strcmp(*argv, "app_probes") == 0) {
+			__u32 aprobe;
+
+			NEXT_ARG();
+
+			if (get_u32(&aprobe, *argv, 0))
+				invarg("\"app_probes\" value is invalid", *argv);
+
+			rta_addattr32(parms_rta, sizeof(parms_buf),
+				      NDTPA_APP_PROBES, aprobe);
+			parms_change = 1;
+		} else if (strcmp(*argv, "ucast_probes") == 0) {
+			__u32 uprobe;
+
+			NEXT_ARG();
+
+			if (get_u32(&uprobe, *argv, 0))
+				invarg("\"ucast_probes\" value is invalid", *argv);
+
+			rta_addattr32(parms_rta, sizeof(parms_buf),
+				      NDTPA_UCAST_PROBES, uprobe);
+			parms_change = 1;
+		} else if (strcmp(*argv, "mcast_probes") == 0) {
+			__u32 mprobe;
+
+			NEXT_ARG();
+
+			if (get_u32(&mprobe, *argv, 0))
+				invarg("\"mcast_probes\" value is invalid", *argv);
+
+			rta_addattr32(parms_rta, sizeof(parms_buf),
+				      NDTPA_MCAST_PROBES, mprobe);
+			parms_change = 1;
+		} else if (strcmp(*argv, "anycast_delay") == 0) {
+			__u64 anycast_delay;
+
+			NEXT_ARG();
+
+			if (get_u64(&anycast_delay, *argv, 0))
+				invarg("\"anycast_delay\" value is invalid", *argv);
+
+			rta_addattr_l(parms_rta, sizeof(parms_buf),
+				      NDTPA_ANYCAST_DELAY,
+				      &anycast_delay, sizeof(anycast_delay));
+			parms_change = 1;
+		} else if (strcmp(*argv, "proxy_delay") == 0) {
+			__u64 proxy_delay;
+
+			NEXT_ARG();
+
+			if (get_u64(&proxy_delay, *argv, 0))
+				invarg("\"proxy_delay\" value is invalid", *argv);
+
+			rta_addattr_l(parms_rta, sizeof(parms_buf),
+				      NDTPA_PROXY_DELAY,
+				      &proxy_delay, sizeof(proxy_delay));
+			parms_change = 1;
+		} else if (strcmp(*argv, "proxy_queue") == 0) {
+			__u32 pqueue;
+
+			NEXT_ARG();
+
+			if (get_u32(&pqueue, *argv, 0))
+				invarg("\"proxy_queue\" value is invalid", *argv);
+
+			rta_addattr32(parms_rta, sizeof(parms_buf),
+				      NDTPA_PROXY_QLEN, pqueue);
+			parms_change = 1;
+		} else if (strcmp(*argv, "locktime") == 0) {
+			__u64 locktime;
+
+			NEXT_ARG();
+
+			if (get_u64(&locktime, *argv, 0))
+				invarg("\"locktime\" value is invalid", *argv);
+
+			rta_addattr_l(parms_rta, sizeof(parms_buf),
+				      NDTPA_LOCKTIME,
+				      &locktime, sizeof(locktime));
+			parms_change = 1;
+		} else {
+			invarg("unknown", *argv);
+		}
+
+		argc--; argv++;
+	}
+
+	if (!namep)
+		missarg("NAME");
+	if (!threshsp && !gc_intp && !parms_change) {
+		fprintf(stderr, "Not enough information: changeable attributes required.\n");
+		exit(-1);
+	}
+
+	if (parms_rta->rta_len > RTA_LENGTH(0)) {
+		addattr_l(&req.n, sizeof(req), NDTA_PARMS, RTA_DATA(parms_rta),
+			  RTA_PAYLOAD(parms_rta));
+	}
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		exit(2);
+
+	return 0;
+}
+
+static const char *ntable_strtime_delta(__u32 msec)
+{
+	static char str[32];
+	struct timeval now = {};
+	time_t t;
+	struct tm *tp;
+
+	if (msec == 0)
+		goto error;
+
+	if (gettimeofday(&now, NULL) < 0) {
+		perror("gettimeofday");
+		goto error;
+	}
+
+	t = now.tv_sec - (msec / 1000);
+	tp = localtime(&t);
+	if (!tp)
+		goto error;
+
+	strftime(str, sizeof(str), "%Y-%m-%d %T", tp);
+
+	return str;
+ error:
+	strcpy(str, "(error)");
+	return str;
+}
+
+static void print_ndtconfig(const struct ndt_config *ndtc)
+{
+
+	print_uint(PRINT_ANY, "key_length",
+		   "    config key_len %u ", ndtc->ndtc_key_len);
+	print_uint(PRINT_ANY, "entry_size",
+		   "entry_size %u ", ndtc->ndtc_entry_size);
+	print_uint(PRINT_ANY, "entries", "entries %u ", ndtc->ndtc_entries);
+
+	print_nl();
+
+	print_string(PRINT_ANY, "last_flush",
+		     "        last_flush %s ",
+		     ntable_strtime_delta(ndtc->ndtc_last_flush));
+	print_string(PRINT_ANY, "last_rand",
+		     "last_rand %s ",
+		     ntable_strtime_delta(ndtc->ndtc_last_rand));
+
+	print_nl();
+
+	print_uint(PRINT_ANY, "hash_rnd",
+		   "        hash_rnd %u ", ndtc->ndtc_hash_rnd);
+	print_0xhex(PRINT_ANY, "hash_mask",
+		    "hash_mask %08llx ", ndtc->ndtc_hash_mask);
+
+	print_uint(PRINT_ANY, "hash_chain_gc",
+		   "hash_chain_gc %u ", ndtc->ndtc_hash_chain_gc);
+	print_uint(PRINT_ANY, "proxy_qlen",
+		   "proxy_qlen %u ", ndtc->ndtc_proxy_qlen);
+
+	print_nl();
+}
+
+static void print_ndtparams(struct rtattr *tpb[])
+{
+
+	if (tpb[NDTPA_IFINDEX]) {
+		__u32 ifindex = rta_getattr_u32(tpb[NDTPA_IFINDEX]);
+
+		print_string(PRINT_FP, NULL, "    dev ", NULL);
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "dev", "%s ", ll_index_to_name(ifindex));
+		print_nl();
+	}
+
+	print_string(PRINT_FP, NULL, "    ", NULL);
+	if (tpb[NDTPA_REFCNT]) {
+		__u32 refcnt = rta_getattr_u32(tpb[NDTPA_REFCNT]);
+
+		print_uint(PRINT_ANY, "refcnt", "refcnt %u ", refcnt);
+	}
+
+	if (tpb[NDTPA_REACHABLE_TIME]) {
+		__u64 reachable = rta_getattr_u64(tpb[NDTPA_REACHABLE_TIME]);
+
+		print_u64(PRINT_ANY, "reachable",
+			   "reachable %llu ", reachable);
+	}
+
+	if (tpb[NDTPA_BASE_REACHABLE_TIME]) {
+		__u64 breachable
+			= rta_getattr_u64(tpb[NDTPA_BASE_REACHABLE_TIME]);
+
+		print_u64(PRINT_ANY, "base_reachable",
+			   "base_reachable %llu ", breachable);
+	}
+
+	if (tpb[NDTPA_RETRANS_TIME]) {
+		__u64 retrans = rta_getattr_u64(tpb[NDTPA_RETRANS_TIME]);
+
+		print_u64(PRINT_ANY, "retrans", "retrans %llu ", retrans);
+	}
+
+	print_string(PRINT_FP, NULL, "%s    ", _SL_);
+
+	if (tpb[NDTPA_GC_STALETIME]) {
+		__u64 gc_stale = rta_getattr_u64(tpb[NDTPA_GC_STALETIME]);
+
+		print_u64(PRINT_ANY, "gc_stale", "gc_stale %llu ", gc_stale);
+	}
+
+	if (tpb[NDTPA_DELAY_PROBE_TIME]) {
+		__u64 delay_probe
+			= rta_getattr_u64(tpb[NDTPA_DELAY_PROBE_TIME]);
+
+		print_u64(PRINT_ANY, "delay_probe",
+			   "delay_probe %llu ", delay_probe);
+	}
+
+	if (tpb[NDTPA_QUEUE_LEN]) {
+		__u32 queue = rta_getattr_u32(tpb[NDTPA_QUEUE_LEN]);
+
+		print_uint(PRINT_ANY, "queue", "queue %u ", queue);
+	}
+
+	print_string(PRINT_FP, NULL, "%s    ", _SL_);
+
+	if (tpb[NDTPA_APP_PROBES]) {
+		__u32 aprobe = rta_getattr_u32(tpb[NDTPA_APP_PROBES]);
+
+		print_uint(PRINT_ANY, "app_probes", "app_probes %u ", aprobe);
+	}
+
+	if (tpb[NDTPA_UCAST_PROBES]) {
+		__u32 uprobe = rta_getattr_u32(tpb[NDTPA_UCAST_PROBES]);
+
+		print_uint(PRINT_ANY, "ucast_probes",
+			   "ucast_probes %u ", uprobe);
+	}
+
+	if (tpb[NDTPA_MCAST_PROBES]) {
+		__u32 mprobe = rta_getattr_u32(tpb[NDTPA_MCAST_PROBES]);
+
+		print_uint(PRINT_ANY, "mcast_probes",
+			   "mcast_probes %u ", mprobe);
+	}
+
+	print_string(PRINT_FP, NULL, "%s    ", _SL_);
+
+	if (tpb[NDTPA_ANYCAST_DELAY]) {
+		__u64 anycast_delay = rta_getattr_u64(tpb[NDTPA_ANYCAST_DELAY]);
+
+		print_u64(PRINT_ANY, "anycast_delay",
+			   "anycast_delay %llu ", anycast_delay);
+	}
+
+	if (tpb[NDTPA_PROXY_DELAY]) {
+		__u64 proxy_delay = rta_getattr_u64(tpb[NDTPA_PROXY_DELAY]);
+
+		print_u64(PRINT_ANY, "proxy_delay",
+			   "proxy_delay %llu ", proxy_delay);
+	}
+
+	if (tpb[NDTPA_PROXY_QLEN]) {
+		__u32 pqueue = rta_getattr_u32(tpb[NDTPA_PROXY_QLEN]);
+
+		print_uint(PRINT_ANY, "proxy_queue", "proxy_queue %u ", pqueue);
+	}
+
+	if (tpb[NDTPA_LOCKTIME]) {
+		__u64 locktime = rta_getattr_u64(tpb[NDTPA_LOCKTIME]);
+
+		print_u64(PRINT_ANY, "locktime", "locktime %llu ", locktime);
+	}
+
+	print_nl();
+}
+
+static void print_ndtstats(const struct ndt_stats *ndts)
+{
+
+	print_string(PRINT_FP, NULL, "    stats ", NULL);
+
+	print_u64(PRINT_ANY, "allocs", "allocs %llu ", ndts->ndts_allocs);
+	print_u64(PRINT_ANY, "destroys", "destroys %llu ",
+		   ndts->ndts_destroys);
+	print_u64(PRINT_ANY, "hash_grows", "hash_grows %llu ",
+		   ndts->ndts_hash_grows);
+
+	print_string(PRINT_FP, NULL, "%s    ", _SL_);
+
+	print_u64(PRINT_ANY, "res_failed", "res_failed %llu ",
+		   ndts->ndts_res_failed);
+	print_u64(PRINT_ANY, "lookups", "lookups %llu ", ndts->ndts_lookups);
+	print_u64(PRINT_ANY, "hits", "hits %llu ", ndts->ndts_hits);
+
+	print_string(PRINT_FP, NULL, "%s    ", _SL_);
+
+	print_u64(PRINT_ANY, "rcv_probes_mcast", "rcv_probes_mcast %llu ",
+		   ndts->ndts_rcv_probes_mcast);
+	print_u64(PRINT_ANY, "rcv_probes_ucast", "rcv_probes_ucast %llu ",
+		   ndts->ndts_rcv_probes_ucast);
+
+	print_string(PRINT_FP, NULL, "%s    ", _SL_);
+
+	print_u64(PRINT_ANY, "periodic_gc_runs", "periodic_gc_runs %llu ",
+		   ndts->ndts_periodic_gc_runs);
+	print_u64(PRINT_ANY, "forced_gc_runs", "forced_gc_runs %llu ",
+		   ndts->ndts_forced_gc_runs);
+
+	print_nl();
+}
+
+static int print_ntable(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct ndtmsg *ndtm = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[NDTA_MAX+1];
+	struct rtattr *tpb[NDTPA_MAX+1];
+	int ret;
+
+	if (n->nlmsg_type != RTM_NEWNEIGHTBL) {
+		fprintf(stderr, "Not NEIGHTBL: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+	len -= NLMSG_LENGTH(sizeof(*ndtm));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (preferred_family && preferred_family != ndtm->ndtm_family)
+		return 0;
+
+	parse_rtattr(tb, NDTA_MAX, NDTA_RTA(ndtm),
+		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*ndtm)));
+
+	if (tb[NDTA_NAME]) {
+		const char *name = rta_getattr_str(tb[NDTA_NAME]);
+
+		if (filter.name && strcmp(filter.name, name))
+			return 0;
+	}
+
+	if (tb[NDTA_PARMS]) {
+		parse_rtattr(tpb, NDTPA_MAX, RTA_DATA(tb[NDTA_PARMS]),
+			     RTA_PAYLOAD(tb[NDTA_PARMS]));
+
+		if (tpb[NDTPA_IFINDEX]) {
+			__u32 ifindex = rta_getattr_u32(tpb[NDTPA_IFINDEX]);
+
+			if (filter.index && filter.index != ifindex)
+				return 0;
+		} else {
+			if (filter.index && filter.index != NONE_DEV)
+				return 0;
+		}
+	}
+
+	open_json_object(NULL);
+	print_string(PRINT_ANY, "family",
+		     "%s ", family_name(ndtm->ndtm_family));
+
+	if (tb[NDTA_NAME]) {
+		const char *name = rta_getattr_str(tb[NDTA_NAME]);
+
+		print_string(PRINT_ANY, "name", "%s ", name);
+	}
+
+	print_nl();
+
+	ret = (tb[NDTA_THRESH1] || tb[NDTA_THRESH2] || tb[NDTA_THRESH3] ||
+	       tb[NDTA_GC_INTERVAL]);
+	if (ret)
+		print_string(PRINT_FP, NULL, "    ", NULL);
+
+	if (tb[NDTA_THRESH1]) {
+		__u32 thresh1 = rta_getattr_u32(tb[NDTA_THRESH1]);
+
+		print_uint(PRINT_ANY, "thresh1", "thresh1 %u ", thresh1);
+	}
+
+	if (tb[NDTA_THRESH2]) {
+		__u32 thresh2 = rta_getattr_u32(tb[NDTA_THRESH2]);
+
+		print_uint(PRINT_ANY, "thresh2", "thresh2 %u ", thresh2);
+	}
+
+	if (tb[NDTA_THRESH3]) {
+		__u32 thresh3 = rta_getattr_u32(tb[NDTA_THRESH3]);
+
+		print_uint(PRINT_ANY, "thresh3", "thresh3 %u ", thresh3);
+	}
+
+	if (tb[NDTA_GC_INTERVAL]) {
+		__u64 gc_int = rta_getattr_u64(tb[NDTA_GC_INTERVAL]);
+
+		print_u64(PRINT_ANY, "gc_interval", "gc_int %llu ", gc_int);
+	}
+
+	if (ret)
+		print_nl();
+
+	if (tb[NDTA_CONFIG] && show_stats)
+		print_ndtconfig(RTA_DATA(tb[NDTA_CONFIG]));
+
+	if (tb[NDTA_PARMS])
+		print_ndtparams(tpb);
+
+	if (tb[NDTA_STATS] && show_stats)
+		print_ndtstats(RTA_DATA(tb[NDTA_STATS]));
+
+	print_string(PRINT_FP, NULL, "\n", "");
+	close_json_object();
+	fflush(fp);
+
+	return 0;
+}
+
+static void ipntable_reset_filter(void)
+{
+	memset(&filter, 0, sizeof(filter));
+}
+
+static int ipntable_show(int argc, char **argv)
+{
+	ipntable_reset_filter();
+
+	filter.family = preferred_family;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+
+			if (strcmp("none", *argv) == 0)
+				filter.index = NONE_DEV;
+			else if ((filter.index = ll_name_to_index(*argv)) == 0)
+				invarg("\"DEV\" is invalid", *argv);
+		} else if (strcmp(*argv, "name") == 0) {
+			NEXT_ARG();
+
+			filter.name = *argv;
+		} else
+			invarg("unknown", *argv);
+
+		argc--; argv++;
+	}
+
+	if (rtnl_neightbldump_req(&rth, preferred_family) < 0) {
+		perror("Cannot send dump request");
+		exit(1);
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&rth, print_ntable, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+	delete_json_obj();
+
+	return 0;
+}
+
+int do_ipntable(int argc, char **argv)
+{
+	ll_init_map(&rth);
+
+	if (argc > 0) {
+		if (matches(*argv, "change") == 0 ||
+		    matches(*argv, "chg") == 0)
+			return ipntable_modify(RTM_SETNEIGHTBL,
+					       NLM_F_REPLACE,
+					       argc-1, argv+1);
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return ipntable_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return ipntable_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip ntable help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/ipprefix.c b/ip/ipprefix.c
new file mode 100644
index 0000000..466af20
--- /dev/null
+++ b/ip/ipprefix.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C)2005 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 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, see <http://www.gnu.org/licenses>.
+ */
+/*
+ * based on ip.c, iproute.c
+ */
+/*
+ * Authors:
+ *	Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/icmp6.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+/* prefix flags; see kernel's net/ipv6/addrconf.c and include/net/if_inet6.h */
+#define IF_PREFIX_ONLINK	0x01
+#define IF_PREFIX_AUTOCONF	0x02
+
+int print_prefix(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct prefixmsg *prefix = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[RTA_MAX+1];
+	int family = preferred_family;
+
+	if (n->nlmsg_type != RTM_NEWPREFIX) {
+		fprintf(stderr, "Not a prefix: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(sizeof(*prefix));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (family == AF_UNSPEC)
+		family = AF_INET6;
+	if (family != AF_INET6)
+		return 0;
+
+	if (prefix->prefix_family != AF_INET6) {
+		fprintf(stderr, "incorrect protocol family: %d\n", prefix->prefix_family);
+		return 0;
+	}
+	if (prefix->prefix_type != ND_OPT_PREFIX_INFORMATION) {
+		fprintf(stderr, "wrong ND type %d\n", prefix->prefix_type);
+		return 0;
+	}
+
+	parse_rtattr(tb, RTA_MAX, RTM_RTA(prefix), len);
+
+	if (tb[PREFIX_ADDRESS]) {
+		fprintf(fp, "prefix %s/%u",
+		        rt_addr_n2a_rta(family, tb[PREFIX_ADDRESS]),
+			prefix->prefix_len);
+	}
+	fprintf(fp, "dev %s ", ll_index_to_name(prefix->prefix_ifindex));
+
+	if (prefix->prefix_flags & IF_PREFIX_ONLINK)
+		fprintf(fp, "onlink ");
+	if (prefix->prefix_flags & IF_PREFIX_AUTOCONF)
+		fprintf(fp, "autoconf ");
+
+	if (tb[PREFIX_CACHEINFO]) {
+		const struct prefix_cacheinfo *pc
+			 = RTA_DATA(tb[PREFIX_CACHEINFO]);
+
+		fprintf(fp, "valid %u ", pc->valid_time);
+		fprintf(fp, "preferred %u ", pc->preferred_time);
+	}
+
+	fprintf(fp, "\n");
+	fflush(fp);
+
+	return 0;
+}
diff --git a/ip/iproute.c b/ip/iproute.c
new file mode 100644
index 0000000..32bb52d
--- /dev/null
+++ b/ip/iproute.c
@@ -0,0 +1,2333 @@
+/*
+ * iproute.c		"ip route".
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <linux/in_route.h>
+#include <linux/icmpv6.h>
+#include <errno.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+#ifndef RTAX_RTTVAR
+#define RTAX_RTTVAR RTAX_HOPS
+#endif
+
+enum list_action {
+	IPROUTE_LIST,
+	IPROUTE_FLUSH,
+	IPROUTE_SAVE,
+};
+static const char *mx_names[RTAX_MAX+1] = {
+	[RTAX_MTU]			= "mtu",
+	[RTAX_WINDOW]			= "window",
+	[RTAX_RTT]			= "rtt",
+	[RTAX_RTTVAR]			= "rttvar",
+	[RTAX_SSTHRESH]			= "ssthresh",
+	[RTAX_CWND]			= "cwnd",
+	[RTAX_ADVMSS]			= "advmss",
+	[RTAX_REORDERING]		= "reordering",
+	[RTAX_HOPLIMIT]			= "hoplimit",
+	[RTAX_INITCWND]			= "initcwnd",
+	[RTAX_FEATURES]			= "features",
+	[RTAX_RTO_MIN]			= "rto_min",
+	[RTAX_INITRWND]			= "initrwnd",
+	[RTAX_QUICKACK]			= "quickack",
+	[RTAX_CC_ALGO]			= "congctl",
+	[RTAX_FASTOPEN_NO_COOKIE]	= "fastopen_no_cookie"
+};
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip route { list | flush } SELECTOR\n"
+		"       ip route save SELECTOR\n"
+		"       ip route restore\n"
+		"       ip route showdump\n"
+		"       ip route get [ ROUTE_GET_FLAGS ] ADDRESS\n"
+		"                            [ from ADDRESS iif STRING ]\n"
+		"                            [ oif STRING ] [ tos TOS ]\n"
+		"                            [ mark NUMBER ] [ vrf NAME ]\n"
+		"                            [ uid NUMBER ] [ ipproto PROTOCOL ]\n"
+		"                            [ sport NUMBER ] [ dport NUMBER ]\n"
+		"       ip route { add | del | change | append | replace } ROUTE\n"
+		"SELECTOR := [ root PREFIX ] [ match PREFIX ] [ exact PREFIX ]\n"
+		"            [ table TABLE_ID ] [ vrf NAME ] [ proto RTPROTO ]\n"
+		"            [ type TYPE ] [ scope SCOPE ]\n"
+		"ROUTE := NODE_SPEC [ INFO_SPEC ]\n"
+		"NODE_SPEC := [ TYPE ] PREFIX [ tos TOS ]\n"
+		"             [ table TABLE_ID ] [ proto RTPROTO ]\n"
+		"             [ scope SCOPE ] [ metric METRIC ]\n"
+		"             [ ttl-propagate { enabled | disabled } ]\n"
+		"INFO_SPEC := { NH | nhid ID } OPTIONS FLAGS [ nexthop NH ]...\n"
+		"NH := [ encap ENCAPTYPE ENCAPHDR ] [ via [ FAMILY ] ADDRESS ]\n"
+		"	    [ dev STRING ] [ weight NUMBER ] NHFLAGS\n"
+		"FAMILY := [ inet | inet6 | mpls | bridge | link ]\n"
+		"OPTIONS := FLAGS [ mtu NUMBER ] [ advmss NUMBER ] [ as [ to ] ADDRESS ]\n"
+		"           [ rtt TIME ] [ rttvar TIME ] [ reordering NUMBER ]\n"
+		"           [ window NUMBER ] [ cwnd NUMBER ] [ initcwnd NUMBER ]\n"
+		"           [ ssthresh NUMBER ] [ realms REALM ] [ src ADDRESS ]\n"
+		"           [ rto_min TIME ] [ hoplimit NUMBER ] [ initrwnd NUMBER ]\n"
+		"           [ features FEATURES ] [ quickack BOOL ] [ congctl NAME ]\n"
+		"           [ pref PREF ] [ expires TIME ] [ fastopen_no_cookie BOOL ]\n"
+		"TYPE := { unicast | local | broadcast | multicast | throw |\n"
+		"          unreachable | prohibit | blackhole | nat }\n"
+		"TABLE_ID := [ local | main | default | all | NUMBER ]\n"
+		"SCOPE := [ host | link | global | NUMBER ]\n"
+		"NHFLAGS := [ onlink | pervasive ]\n"
+		"RTPROTO := [ kernel | boot | static | NUMBER ]\n"
+		"PREF := [ low | medium | high ]\n"
+		"TIME := NUMBER[s|ms]\n"
+		"BOOL := [1|0]\n"
+		"FEATURES := ecn\n"
+		"ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local ]\n"
+		"ENCAPHDR := [ MPLSLABEL | SEG6HDR ]\n"
+		"SEG6HDR := [ mode SEGMODE ] segs ADDR1,ADDRi,ADDRn [hmac HMACKEYID] [cleanup]\n"
+		"SEGMODE := [ encap | inline ]\n"
+		"ROUTE_GET_FLAGS := [ fibmatch ]\n");
+	exit(-1);
+}
+
+
+static struct
+{
+	unsigned int tb;
+	int cloned;
+	int flushed;
+	char *flushb;
+	int flushp;
+	int flushe;
+	int protocol, protocolmask;
+	int scope, scopemask;
+	__u64 typemask;
+	int tos, tosmask;
+	int iif, iifmask;
+	int oif, oifmask;
+	int mark, markmask;
+	int realm, realmmask;
+	__u32 metric, metricmask;
+	inet_prefix rprefsrc;
+	inet_prefix rvia;
+	inet_prefix rdst;
+	inet_prefix mdst;
+	inet_prefix rsrc;
+	inet_prefix msrc;
+} filter;
+
+static int flush_update(void)
+{
+	if (rtnl_send_check(&rth, filter.flushb, filter.flushp) < 0) {
+		perror("Failed to send flush request");
+		return -2;
+	}
+	filter.flushp = 0;
+	return 0;
+}
+
+static int filter_nlmsg(struct nlmsghdr *n, struct rtattr **tb, int host_len)
+{
+	struct rtmsg *r = NLMSG_DATA(n);
+	inet_prefix dst = { .family = r->rtm_family };
+	inet_prefix src = { .family = r->rtm_family };
+	inet_prefix via = { .family = r->rtm_family };
+	inet_prefix prefsrc = { .family = r->rtm_family };
+	__u32 table;
+	static int ip6_multiple_tables;
+
+	table = rtm_get_table(r, tb);
+
+	if (preferred_family != AF_UNSPEC && r->rtm_family != preferred_family)
+		return 0;
+
+	if (r->rtm_family == AF_INET6 && table != RT_TABLE_MAIN)
+		ip6_multiple_tables = 1;
+
+	if (filter.cloned == !(r->rtm_flags & RTM_F_CLONED))
+		return 0;
+
+	if (r->rtm_family == AF_INET6 && !ip6_multiple_tables) {
+		if (filter.tb) {
+			if (filter.tb == RT_TABLE_LOCAL) {
+				if (r->rtm_type != RTN_LOCAL)
+					return 0;
+			} else if (filter.tb == RT_TABLE_MAIN) {
+				if (r->rtm_type == RTN_LOCAL)
+					return 0;
+			} else {
+				return 0;
+			}
+		}
+	} else {
+		if (filter.tb > 0 && filter.tb != table)
+			return 0;
+	}
+	if ((filter.protocol^r->rtm_protocol)&filter.protocolmask)
+		return 0;
+	if ((filter.scope^r->rtm_scope)&filter.scopemask)
+		return 0;
+
+	if (filter.typemask && !(filter.typemask & (1 << r->rtm_type)))
+		return 0;
+	if ((filter.tos^r->rtm_tos)&filter.tosmask)
+		return 0;
+	if (filter.rdst.family) {
+		if (r->rtm_family != filter.rdst.family ||
+		    filter.rdst.bitlen > r->rtm_dst_len)
+			return 0;
+	} else if (filter.rdst.flags & PREFIXLEN_SPECIFIED) {
+		if (filter.rdst.bitlen > r->rtm_dst_len)
+			return 0;
+	}
+	if (filter.mdst.family) {
+		if (r->rtm_family != filter.mdst.family ||
+		    (filter.mdst.bitlen >= 0 &&
+		     filter.mdst.bitlen < r->rtm_dst_len))
+			return 0;
+	} else if (filter.mdst.flags & PREFIXLEN_SPECIFIED) {
+		if (filter.mdst.bitlen >= 0 &&
+		    filter.mdst.bitlen < r->rtm_dst_len)
+			return 0;
+	}
+	if (filter.rsrc.family) {
+		if (r->rtm_family != filter.rsrc.family ||
+		    filter.rsrc.bitlen > r->rtm_src_len)
+			return 0;
+	} else if (filter.rsrc.flags & PREFIXLEN_SPECIFIED) {
+		if (filter.rsrc.bitlen > r->rtm_src_len)
+			return 0;
+	}
+	if (filter.msrc.family) {
+		if (r->rtm_family != filter.msrc.family ||
+		    (filter.msrc.bitlen >= 0 &&
+		     filter.msrc.bitlen < r->rtm_src_len))
+			return 0;
+	} else if (filter.msrc.flags & PREFIXLEN_SPECIFIED) {
+		if (filter.msrc.bitlen >= 0 &&
+		    filter.msrc.bitlen < r->rtm_src_len)
+			return 0;
+	}
+	if (filter.rvia.family) {
+		int family = r->rtm_family;
+
+		if (tb[RTA_VIA]) {
+			struct rtvia *via = RTA_DATA(tb[RTA_VIA]);
+
+			family = via->rtvia_family;
+		}
+		if (family != filter.rvia.family)
+			return 0;
+	}
+	if (filter.rprefsrc.family && r->rtm_family != filter.rprefsrc.family)
+		return 0;
+
+	if (tb[RTA_DST])
+		memcpy(&dst.data, RTA_DATA(tb[RTA_DST]), (r->rtm_dst_len+7)/8);
+	if (filter.rsrc.family || filter.msrc.family ||
+	    filter.rsrc.flags & PREFIXLEN_SPECIFIED ||
+	    filter.msrc.flags & PREFIXLEN_SPECIFIED) {
+		if (tb[RTA_SRC])
+			memcpy(&src.data, RTA_DATA(tb[RTA_SRC]), (r->rtm_src_len+7)/8);
+	}
+	if (filter.rvia.bitlen > 0) {
+		if (tb[RTA_GATEWAY])
+			memcpy(&via.data, RTA_DATA(tb[RTA_GATEWAY]), host_len/8);
+		if (tb[RTA_VIA]) {
+			size_t len = RTA_PAYLOAD(tb[RTA_VIA]) - 2;
+			struct rtvia *rtvia = RTA_DATA(tb[RTA_VIA]);
+
+			via.family = rtvia->rtvia_family;
+			memcpy(&via.data, rtvia->rtvia_addr, len);
+		}
+	}
+	if (filter.rprefsrc.bitlen > 0) {
+		if (tb[RTA_PREFSRC])
+			memcpy(&prefsrc.data, RTA_DATA(tb[RTA_PREFSRC]), host_len/8);
+	}
+
+	if ((filter.rdst.family || filter.rdst.flags & PREFIXLEN_SPECIFIED) &&
+	    inet_addr_match(&dst, &filter.rdst, filter.rdst.bitlen))
+		return 0;
+	if ((filter.mdst.family || filter.mdst.flags & PREFIXLEN_SPECIFIED) &&
+	    inet_addr_match(&dst, &filter.mdst, r->rtm_dst_len))
+		return 0;
+
+	if ((filter.rsrc.family || filter.rsrc.flags & PREFIXLEN_SPECIFIED) &&
+	    inet_addr_match(&src, &filter.rsrc, filter.rsrc.bitlen))
+		return 0;
+	if ((filter.msrc.family || filter.msrc.flags & PREFIXLEN_SPECIFIED) &&
+	    filter.msrc.bitlen >= 0 &&
+	    inet_addr_match(&src, &filter.msrc, r->rtm_src_len))
+		return 0;
+
+	if (filter.rvia.family && inet_addr_match(&via, &filter.rvia, filter.rvia.bitlen))
+		return 0;
+	if (filter.rprefsrc.family && inet_addr_match(&prefsrc, &filter.rprefsrc, filter.rprefsrc.bitlen))
+		return 0;
+	if (filter.realmmask) {
+		__u32 realms = 0;
+
+		if (tb[RTA_FLOW])
+			realms = rta_getattr_u32(tb[RTA_FLOW]);
+		if ((realms^filter.realm)&filter.realmmask)
+			return 0;
+	}
+	if (filter.iifmask) {
+		int iif = 0;
+
+		if (tb[RTA_IIF])
+			iif = rta_getattr_u32(tb[RTA_IIF]);
+		if ((iif^filter.iif)&filter.iifmask)
+			return 0;
+	}
+	if (filter.oifmask) {
+		int oif = 0;
+
+		if (tb[RTA_OIF])
+			oif = rta_getattr_u32(tb[RTA_OIF]);
+		if ((oif^filter.oif)&filter.oifmask)
+			return 0;
+	}
+	if (filter.markmask) {
+		int mark = 0;
+
+		if (tb[RTA_MARK])
+			mark = rta_getattr_u32(tb[RTA_MARK]);
+		if ((mark ^ filter.mark) & filter.markmask)
+			return 0;
+	}
+	if (filter.metricmask) {
+		__u32 metric = 0;
+
+		if (tb[RTA_PRIORITY])
+			metric = rta_getattr_u32(tb[RTA_PRIORITY]);
+		if ((metric ^ filter.metric) & filter.metricmask)
+			return 0;
+	}
+	if (filter.flushb &&
+	    r->rtm_family == AF_INET6 &&
+	    r->rtm_dst_len == 0 &&
+	    r->rtm_type == RTN_UNREACHABLE &&
+	    tb[RTA_PRIORITY] &&
+	    rta_getattr_u32(tb[RTA_PRIORITY]) == -1)
+		return 0;
+
+	return 1;
+}
+
+static void print_rtax_features(FILE *fp, unsigned int features)
+{
+	unsigned int of = features;
+
+	if (features & RTAX_FEATURE_ECN) {
+		print_null(PRINT_ANY, "ecn", "ecn ", NULL);
+		features &= ~RTAX_FEATURE_ECN;
+	}
+
+	if (features)
+		print_0xhex(PRINT_ANY,
+			    "features", "%#llx ", of);
+}
+
+void print_rt_flags(FILE *fp, unsigned int flags)
+{
+	open_json_array(PRINT_JSON,
+			is_json_context() ?  "flags" : "");
+
+	if (flags & RTNH_F_DEAD)
+		print_string(PRINT_ANY, NULL, "%s ", "dead");
+	if (flags & RTNH_F_ONLINK)
+		print_string(PRINT_ANY, NULL, "%s ", "onlink");
+	if (flags & RTNH_F_PERVASIVE)
+		print_string(PRINT_ANY, NULL, "%s ", "pervasive");
+	if (flags & RTNH_F_OFFLOAD)
+		print_string(PRINT_ANY, NULL, "%s ", "offload");
+	if (flags & RTM_F_NOTIFY)
+		print_string(PRINT_ANY, NULL, "%s ", "notify");
+	if (flags & RTNH_F_LINKDOWN)
+		print_string(PRINT_ANY, NULL, "%s ", "linkdown");
+	if (flags & RTNH_F_UNRESOLVED)
+		print_string(PRINT_ANY, NULL, "%s ", "unresolved");
+
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static void print_rt_pref(FILE *fp, unsigned int pref)
+{
+
+	switch (pref) {
+	case ICMPV6_ROUTER_PREF_LOW:
+		print_string(PRINT_ANY,
+			     "pref", "pref %s", "low");
+		break;
+	case ICMPV6_ROUTER_PREF_MEDIUM:
+		print_string(PRINT_ANY,
+			     "pref", "pref %s", "medium");
+		break;
+	case ICMPV6_ROUTER_PREF_HIGH:
+		print_string(PRINT_ANY,
+			     "pref", "pref %s", "high");
+		break;
+	default:
+		print_uint(PRINT_ANY,
+			   "pref", "%u", pref);
+	}
+}
+
+void print_rta_if(FILE *fp, const struct rtattr *rta, const char *prefix)
+{
+	const char *ifname = ll_index_to_name(rta_getattr_u32(rta));
+
+	if (is_json_context())
+		print_string(PRINT_JSON, prefix, NULL, ifname);
+	else {
+		fprintf(fp, "%s ", prefix);
+		color_fprintf(fp, COLOR_IFNAME, "%s ", ifname);
+	}
+}
+
+static void print_cache_flags(FILE *fp, __u32 flags)
+{
+	json_writer_t *jw = get_json_writer();
+	flags &= ~0xFFFF;
+
+	if (jw) {
+		jsonw_name(jw, "cache");
+		jsonw_start_array(jw);
+	} else {
+		fprintf(fp, "%s    cache ", _SL_);
+		if (flags == 0)
+			return;
+		putc('<', fp);
+	}
+
+#define PRTFL(fl, flname)						\
+	if (flags & RTCF_##fl) {					\
+		flags &= ~RTCF_##fl;					\
+		if (jw)							\
+			jsonw_string(jw, flname);			\
+		else							\
+			fprintf(fp, "%s%s", flname, flags ? "," : "> "); \
+	}
+
+	PRTFL(LOCAL, "local");
+	PRTFL(REJECT, "reject");
+	PRTFL(MULTICAST, "mc");
+	PRTFL(BROADCAST, "brd");
+	PRTFL(DNAT, "dst-nat");
+	PRTFL(SNAT, "src-nat");
+	PRTFL(MASQ, "masq");
+	PRTFL(DIRECTDST, "dst-direct");
+	PRTFL(DIRECTSRC, "src-direct");
+	PRTFL(REDIRECTED, "redirected");
+	PRTFL(DOREDIRECT, "redirect");
+	PRTFL(FAST, "fastroute");
+	PRTFL(NOTIFY, "notify");
+	PRTFL(TPROXY, "proxy");
+#undef PRTFL
+
+	if (flags)
+		print_hex(PRINT_ANY, "flags", "%x>", flags);
+
+	if (jw)
+		jsonw_end_array(jw);
+}
+
+static void print_rta_cacheinfo(FILE *fp, const struct rta_cacheinfo *ci)
+{
+	static int hz;
+
+	if (!hz)
+		hz = get_user_hz();
+
+	if (ci->rta_expires != 0)
+		print_int(PRINT_ANY, "expires",
+			   "expires %dsec ", ci->rta_expires/hz);
+	if (ci->rta_error != 0)
+		print_uint(PRINT_ANY, "error",
+			   "error %u ", ci->rta_error);
+
+	if (show_stats) {
+		if (ci->rta_clntref)
+			print_uint(PRINT_ANY, "users",
+				   "users %u ", ci->rta_clntref);
+		if (ci->rta_used != 0)
+			print_uint(PRINT_ANY, "used",
+				   "used %u ", ci->rta_used);
+		if (ci->rta_lastuse != 0)
+			print_uint(PRINT_ANY, "age",
+				   "age %usec ", ci->rta_lastuse/hz);
+	}
+	if (ci->rta_id)
+		print_0xhex(PRINT_ANY, "ipid",
+			    "ipid 0x%04llx ", ci->rta_id);
+	if (ci->rta_ts || ci->rta_tsage) {
+		print_0xhex(PRINT_ANY, "ts",
+			    "ts 0x%llx", ci->rta_ts);
+		print_uint(PRINT_ANY, "tsage",
+			   "tsage %usec ", ci->rta_tsage);
+	}
+}
+
+static void print_rta_flow(FILE *fp, const struct rtattr *rta)
+{
+	__u32 to = rta_getattr_u32(rta);
+	__u32 from = to >> 16;
+	SPRINT_BUF(b1);
+
+	to &= 0xFFFF;
+	if (is_json_context()) {
+		open_json_object("flow");
+
+		if (from)
+			print_string(PRINT_JSON, "from", NULL,
+				     rtnl_rtrealm_n2a(from, b1, sizeof(b1)));
+		print_string(PRINT_JSON, "to", NULL,
+			     rtnl_rtrealm_n2a(to, b1, sizeof(b1)));
+		close_json_object();
+	} else {
+		fprintf(fp, "realm%s ", from ? "s" : "");
+
+		if (from)
+			print_string(PRINT_FP, NULL, "%s/",
+				     rtnl_rtrealm_n2a(from, b1, sizeof(b1)));
+		print_string(PRINT_FP, NULL, "%s ",
+			     rtnl_rtrealm_n2a(to, b1, sizeof(b1)));
+	}
+}
+
+static void print_rta_newdst(FILE *fp, const struct rtmsg *r,
+			     const struct rtattr *rta)
+{
+	const char *newdst = format_host_rta(r->rtm_family, rta);
+
+	if (is_json_context())
+		print_string(PRINT_JSON, "to", NULL, newdst);
+	else {
+		fprintf(fp, "as to ");
+		print_color_string(PRINT_FP,
+				   ifa_family_color(r->rtm_family),
+				   NULL, "%s ", newdst);
+	}
+}
+
+void print_rta_gateway(FILE *fp, unsigned char family, const struct rtattr *rta)
+{
+	const char *gateway = format_host_rta(family, rta);
+
+	if (is_json_context())
+		print_string(PRINT_JSON, "gateway", NULL, gateway);
+	else {
+		fprintf(fp, "via ");
+		print_color_string(PRINT_FP,
+				   ifa_family_color(family),
+				   NULL, "%s ", gateway);
+	}
+}
+
+static void print_rta_via(FILE *fp, const struct rtattr *rta)
+{
+	size_t len = RTA_PAYLOAD(rta) - 2;
+	const struct rtvia *via = RTA_DATA(rta);
+
+	if (is_json_context()) {
+		open_json_object("via");
+		print_string(PRINT_JSON, "family", NULL,
+			     family_name(via->rtvia_family));
+		print_string(PRINT_JSON, "host", NULL,
+			     format_host(via->rtvia_family, len,
+					 via->rtvia_addr));
+		close_json_object();
+	} else {
+		print_string(PRINT_FP, NULL, "via %s ",
+			     family_name(via->rtvia_family));
+		print_color_string(PRINT_FP,
+				   ifa_family_color(via->rtvia_family),
+				   NULL, "%s ",
+				   format_host(via->rtvia_family,
+					       len, via->rtvia_addr));
+	}
+}
+
+static void print_rta_metrics(FILE *fp, const struct rtattr *rta)
+{
+	struct rtattr *mxrta[RTAX_MAX+1];
+	unsigned int mxlock = 0;
+	int i;
+
+	open_json_array(PRINT_JSON, "metrics");
+	open_json_object(NULL);
+
+	parse_rtattr(mxrta, RTAX_MAX, RTA_DATA(rta), RTA_PAYLOAD(rta));
+
+	if (mxrta[RTAX_LOCK])
+		mxlock = rta_getattr_u32(mxrta[RTAX_LOCK]);
+
+	for (i = 2; i <= RTAX_MAX; i++) {
+		__u32 val = 0U;
+
+		if (mxrta[i] == NULL && !(mxlock & (1 << i)))
+			continue;
+
+		if (mxrta[i] != NULL && i != RTAX_CC_ALGO)
+			val = rta_getattr_u32(mxrta[i]);
+
+		if (i == RTAX_HOPLIMIT && (int)val == -1)
+			continue;
+
+		if (!is_json_context()) {
+			if (i < sizeof(mx_names)/sizeof(char *) && mx_names[i])
+				fprintf(fp, "%s ", mx_names[i]);
+			else
+				fprintf(fp, "metric %d ", i);
+
+			if (mxlock & (1<<i))
+				fprintf(fp, "lock ");
+		}
+
+		switch (i) {
+		case RTAX_FEATURES:
+			print_rtax_features(fp, val);
+			break;
+		default:
+			print_uint(PRINT_ANY, mx_names[i], "%u ", val);
+			break;
+
+		case RTAX_RTT:
+		case RTAX_RTTVAR:
+		case RTAX_RTO_MIN:
+			if (i == RTAX_RTT)
+				val /= 8;
+			else if (i == RTAX_RTTVAR)
+				val /= 4;
+
+			if (is_json_context())
+				print_uint(PRINT_JSON, mx_names[i],
+					   NULL, val);
+			else {
+				if (val >= 1000)
+					fprintf(fp, "%gs ", val/1e3);
+				else
+					fprintf(fp, "%ums ", val);
+			}
+			break;
+		case RTAX_CC_ALGO:
+			print_string(PRINT_ANY, "congestion",
+				     "%s ", rta_getattr_str(mxrta[i]));
+			break;
+		}
+	}
+
+	close_json_object();
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static void print_rta_multipath(FILE *fp, const struct rtmsg *r,
+				struct rtattr *rta)
+{
+	const struct rtnexthop *nh = RTA_DATA(rta);
+	int len = RTA_PAYLOAD(rta);
+	int first = 1;
+
+	open_json_array(PRINT_JSON, "nexthops");
+
+	while (len >= sizeof(*nh)) {
+		struct rtattr *tb[RTA_MAX + 1];
+
+		if (nh->rtnh_len > len)
+			break;
+
+		open_json_object(NULL);
+
+		if ((r->rtm_flags & RTM_F_CLONED) &&
+		    r->rtm_type == RTN_MULTICAST) {
+			if (first) {
+				print_string(PRINT_FP, NULL, "Oifs: ", NULL);
+				first = 0;
+			} else {
+				print_string(PRINT_FP, NULL, " ", NULL);
+			}
+		} else
+			print_string(PRINT_FP, NULL, "%s\tnexthop ", _SL_);
+
+		if (nh->rtnh_len > sizeof(*nh)) {
+			parse_rtattr(tb, RTA_MAX, RTNH_DATA(nh),
+				     nh->rtnh_len - sizeof(*nh));
+
+			if (tb[RTA_ENCAP])
+				lwt_print_encap(fp,
+						tb[RTA_ENCAP_TYPE],
+						tb[RTA_ENCAP]);
+			if (tb[RTA_NEWDST])
+				print_rta_newdst(fp, r, tb[RTA_NEWDST]);
+			if (tb[RTA_GATEWAY])
+				print_rta_gateway(fp, r->rtm_family,
+						  tb[RTA_GATEWAY]);
+			if (tb[RTA_VIA])
+				print_rta_via(fp, tb[RTA_VIA]);
+			if (tb[RTA_FLOW])
+				print_rta_flow(fp, tb[RTA_FLOW]);
+		}
+
+		if ((r->rtm_flags & RTM_F_CLONED) &&
+		    r->rtm_type == RTN_MULTICAST) {
+			print_string(PRINT_ANY, "dev",
+				     "%s", ll_index_to_name(nh->rtnh_ifindex));
+
+			if (nh->rtnh_hops != 1)
+				print_int(PRINT_ANY, "ttl", "(ttl>%d)", nh->rtnh_hops);
+
+			print_string(PRINT_FP, NULL, " ", NULL);
+		} else {
+			print_string(PRINT_ANY, "dev",
+				     "dev %s ", ll_index_to_name(nh->rtnh_ifindex));
+
+			if (r->rtm_family != AF_MPLS)
+				print_int(PRINT_ANY, "weight",
+					  "weight %d ", nh->rtnh_hops + 1);
+		}
+
+		print_rt_flags(fp, nh->rtnh_flags);
+
+		len -= NLMSG_ALIGN(nh->rtnh_len);
+		nh = RTNH_NEXT(nh);
+
+		close_json_object();
+	}
+	close_json_array(PRINT_JSON, NULL);
+}
+
+int print_route(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct rtmsg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[RTA_MAX+1];
+	int family, color, host_len;
+	__u32 table;
+	int ret;
+
+	SPRINT_BUF(b1);
+
+	if (n->nlmsg_type != RTM_NEWROUTE && n->nlmsg_type != RTM_DELROUTE) {
+		fprintf(stderr, "Not a route: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return -1;
+	}
+	if (filter.flushb && n->nlmsg_type != RTM_NEWROUTE)
+		return 0;
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	host_len = af_bit_len(r->rtm_family);
+
+	parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+	table = rtm_get_table(r, tb);
+
+	if (!filter_nlmsg(n, tb, host_len))
+		return 0;
+
+	if (filter.flushb) {
+		struct nlmsghdr *fn;
+
+		if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) {
+			ret = flush_update();
+			if (ret < 0)
+				return ret;
+		}
+		fn = (struct nlmsghdr *)(filter.flushb + NLMSG_ALIGN(filter.flushp));
+		memcpy(fn, n, n->nlmsg_len);
+		fn->nlmsg_type = RTM_DELROUTE;
+		fn->nlmsg_flags = NLM_F_REQUEST;
+		fn->nlmsg_seq = ++rth.seq;
+		filter.flushp = (((char *)fn) + n->nlmsg_len) - filter.flushb;
+		filter.flushed++;
+		if (show_stats < 2)
+			return 0;
+	}
+
+	open_json_object(NULL);
+	if (n->nlmsg_type == RTM_DELROUTE)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	if ((r->rtm_type != RTN_UNICAST || show_details > 0) &&
+	    (!filter.typemask || (filter.typemask & (1 << r->rtm_type))))
+		print_string(PRINT_ANY, "type", "%s ",
+			     rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)));
+
+	color = COLOR_NONE;
+	if (tb[RTA_DST]) {
+		family = get_real_family(r->rtm_type, r->rtm_family);
+		color = ifa_family_color(family);
+
+		if (r->rtm_dst_len != host_len) {
+			snprintf(b1, sizeof(b1),
+				 "%s/%u", rt_addr_n2a_rta(family, tb[RTA_DST]),
+				 r->rtm_dst_len);
+		} else {
+			format_host_rta_r(family, tb[RTA_DST],
+					  b1, sizeof(b1));
+
+		}
+	} else if (r->rtm_dst_len) {
+		snprintf(b1, sizeof(b1), "0/%d ", r->rtm_dst_len);
+	} else {
+		strncpy(b1, "default", sizeof(b1));
+	}
+	print_color_string(PRINT_ANY, color,
+			   "dst", "%s ", b1);
+
+	if (tb[RTA_SRC]) {
+		family = get_real_family(r->rtm_type, r->rtm_family);
+		color = ifa_family_color(family);
+
+		if (r->rtm_src_len != host_len) {
+			snprintf(b1, sizeof(b1),
+				 "%s/%u",
+				 rt_addr_n2a_rta(family, tb[RTA_SRC]),
+				 r->rtm_src_len);
+		} else {
+			format_host_rta_r(family, tb[RTA_SRC],
+					  b1, sizeof(b1));
+		}
+		print_color_string(PRINT_ANY, color,
+				   "from", "from %s ", b1);
+	} else if (r->rtm_src_len) {
+		snprintf(b1, sizeof(b1), "0/%u", r->rtm_src_len);
+
+		print_string(PRINT_ANY, "src", "from %s ", b1);
+	}
+
+	if (tb[RTA_NH_ID])
+		print_uint(PRINT_ANY, "nhid", "nhid %u ",
+			   rta_getattr_u32(tb[RTA_NH_ID]));
+
+	if (tb[RTA_NEWDST])
+		print_rta_newdst(fp, r, tb[RTA_NEWDST]);
+
+	if (tb[RTA_ENCAP])
+		lwt_print_encap(fp, tb[RTA_ENCAP_TYPE], tb[RTA_ENCAP]);
+
+	if (r->rtm_tos && filter.tosmask != -1) {
+		print_string(PRINT_ANY, "tos", "tos %s ",
+			     rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1)));
+	}
+
+	if (tb[RTA_GATEWAY] && filter.rvia.bitlen != host_len)
+		print_rta_gateway(fp, r->rtm_family, tb[RTA_GATEWAY]);
+
+	if (tb[RTA_VIA])
+		print_rta_via(fp, tb[RTA_VIA]);
+
+	if (tb[RTA_OIF] && filter.oifmask != -1)
+		print_rta_if(fp, tb[RTA_OIF], "dev");
+
+	if (table && (table != RT_TABLE_MAIN || show_details > 0) && !filter.tb)
+		print_string(PRINT_ANY,
+			     "table", "table %s ",
+			     rtnl_rttable_n2a(table, b1, sizeof(b1)));
+
+	if (!(r->rtm_flags & RTM_F_CLONED)) {
+		if ((r->rtm_protocol != RTPROT_BOOT || show_details > 0) &&
+		    filter.protocolmask != -1)
+			print_string(PRINT_ANY,
+				     "protocol", "proto %s ",
+				     rtnl_rtprot_n2a(r->rtm_protocol,
+						     b1, sizeof(b1)));
+
+		if ((r->rtm_scope != RT_SCOPE_UNIVERSE || show_details > 0) &&
+		    filter.scopemask != -1)
+			print_string(PRINT_ANY,
+				     "scope", "scope %s ",
+				     rtnl_rtscope_n2a(r->rtm_scope,
+						      b1, sizeof(b1)));
+	}
+
+	if (tb[RTA_PREFSRC] && filter.rprefsrc.bitlen != host_len) {
+		const char *psrc
+			= rt_addr_n2a_rta(r->rtm_family, tb[RTA_PREFSRC]);
+
+		/* Do not use format_host(). It is our local addr
+		   and symbolic name will not be useful.
+		*/
+		if (is_json_context())
+			print_string(PRINT_JSON, "prefsrc", NULL, psrc);
+		else {
+			fprintf(fp, "src ");
+			print_color_string(PRINT_FP,
+					   ifa_family_color(r->rtm_family),
+					   NULL, "%s ", psrc);
+		}
+
+	}
+
+	if (tb[RTA_PRIORITY] && filter.metricmask != -1)
+		print_uint(PRINT_ANY, "metric", "metric %u ",
+			   rta_getattr_u32(tb[RTA_PRIORITY]));
+
+	print_rt_flags(fp, r->rtm_flags);
+
+	if (tb[RTA_MARK]) {
+		unsigned int mark = rta_getattr_u32(tb[RTA_MARK]);
+
+		if (mark) {
+			if (is_json_context())
+				print_uint(PRINT_JSON, "mark", NULL, mark);
+			else if (mark >= 16)
+				print_0xhex(PRINT_FP, NULL,
+					    "mark 0x%llx ", mark);
+			else
+				print_uint(PRINT_FP, NULL,
+					   "mark %u ", mark);
+		}
+	}
+
+	if (tb[RTA_FLOW] && filter.realmmask != ~0U)
+		print_rta_flow(fp, tb[RTA_FLOW]);
+
+	if (tb[RTA_UID])
+		print_uint(PRINT_ANY, "uid", "uid %u ",
+			   rta_getattr_u32(tb[RTA_UID]));
+
+	if (r->rtm_family == AF_INET) {
+		if (r->rtm_flags & RTM_F_CLONED)
+			print_cache_flags(fp, r->rtm_flags);
+
+		if (tb[RTA_CACHEINFO])
+			print_rta_cacheinfo(fp, RTA_DATA(tb[RTA_CACHEINFO]));
+	} else if (r->rtm_family == AF_INET6) {
+		if (tb[RTA_CACHEINFO])
+			print_rta_cacheinfo(fp, RTA_DATA(tb[RTA_CACHEINFO]));
+	}
+
+	if (tb[RTA_METRICS])
+		print_rta_metrics(fp, tb[RTA_METRICS]);
+
+	if (tb[RTA_IIF] && filter.iifmask != -1)
+		print_rta_if(fp, tb[RTA_IIF], "iif");
+
+	if (tb[RTA_MULTIPATH])
+		print_rta_multipath(fp, r, tb[RTA_MULTIPATH]);
+
+	if (tb[RTA_PREF])
+		print_rt_pref(fp, rta_getattr_u8(tb[RTA_PREF]));
+
+	if (tb[RTA_TTL_PROPAGATE]) {
+		bool propagate = rta_getattr_u8(tb[RTA_TTL_PROPAGATE]);
+
+		if (is_json_context())
+			print_bool(PRINT_JSON, "ttl-propogate", NULL,
+				   propagate);
+		else
+			print_string(PRINT_FP, NULL,
+				     "ttl-propogate %s",
+				     propagate ? "enabled" : "disabled");
+	}
+
+	print_string(PRINT_FP, NULL, "\n", NULL);
+	close_json_object();
+	fflush(fp);
+	return 0;
+}
+
+static int parse_one_nh(struct nlmsghdr *n, struct rtmsg *r,
+			struct rtattr *rta, size_t len, struct rtnexthop *rtnh,
+			int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+
+	while (++argv, --argc > 0) {
+		if (strcmp(*argv, "via") == 0) {
+			inet_prefix addr;
+			int family;
+
+			NEXT_ARG();
+			family = read_family(*argv);
+			if (family == AF_UNSPEC)
+				family = r->rtm_family;
+			else
+				NEXT_ARG();
+			get_addr(&addr, *argv, family);
+			if (r->rtm_family == AF_UNSPEC)
+				r->rtm_family = addr.family;
+			if (addr.family == r->rtm_family) {
+				if (rta_addattr_l(rta, len, RTA_GATEWAY,
+						  &addr.data, addr.bytelen))
+					return -1;
+				rtnh->rtnh_len += sizeof(struct rtattr)
+						  + addr.bytelen;
+			} else {
+				if (rta_addattr_l(rta, len, RTA_VIA,
+						  &addr.family, addr.bytelen + 2))
+					return -1;
+				rtnh->rtnh_len += RTA_SPACE(addr.bytelen + 2);
+			}
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			rtnh->rtnh_ifindex = ll_name_to_index(*argv);
+			if (!rtnh->rtnh_ifindex)
+				return nodev(*argv);
+		} else if (strcmp(*argv, "weight") == 0) {
+			unsigned int w;
+
+			NEXT_ARG();
+			if (get_unsigned(&w, *argv, 0) || w == 0 || w > 256)
+				invarg("\"weight\" is invalid\n", *argv);
+			rtnh->rtnh_hops = w - 1;
+		} else if (strcmp(*argv, "onlink") == 0) {
+			rtnh->rtnh_flags |= RTNH_F_ONLINK;
+		} else if (matches(*argv, "realms") == 0) {
+			__u32 realm;
+
+			NEXT_ARG();
+			if (get_rt_realms_or_raw(&realm, *argv))
+				invarg("\"realm\" value is invalid\n", *argv);
+			if (rta_addattr32(rta, len, RTA_FLOW, realm))
+				return -1;
+			rtnh->rtnh_len += sizeof(struct rtattr) + 4;
+		} else if (strcmp(*argv, "encap") == 0) {
+			int old_len = rta->rta_len;
+
+			if (lwt_parse_encap(rta, len, &argc, &argv,
+					    RTA_ENCAP, RTA_ENCAP_TYPE))
+				return -1;
+			rtnh->rtnh_len += rta->rta_len - old_len;
+		} else if (strcmp(*argv, "as") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "to") == 0)
+				NEXT_ARG();
+			get_addr(&addr, *argv, r->rtm_family);
+			if (rta_addattr_l(rta, len, RTA_NEWDST,
+					  &addr.data, addr.bytelen))
+				return -1;
+			rtnh->rtnh_len += sizeof(struct rtattr) + addr.bytelen;
+		} else
+			break;
+	}
+	*argcp = argc;
+	*argvp = argv;
+	return 0;
+}
+
+static int parse_nexthops(struct nlmsghdr *n, struct rtmsg *r,
+			  int argc, char **argv)
+{
+	char buf[4096];
+	struct rtattr *rta = (void *)buf;
+	struct rtnexthop *rtnh;
+
+	rta->rta_type = RTA_MULTIPATH;
+	rta->rta_len = RTA_LENGTH(0);
+	rtnh = RTA_DATA(rta);
+
+	while (argc > 0) {
+		if (strcmp(*argv, "nexthop") != 0) {
+			fprintf(stderr, "Error: \"nexthop\" or end of line is expected instead of \"%s\"\n", *argv);
+			exit(-1);
+		}
+		if (argc <= 1) {
+			fprintf(stderr, "Error: unexpected end of line after \"nexthop\"\n");
+			exit(-1);
+		}
+		memset(rtnh, 0, sizeof(*rtnh));
+		rtnh->rtnh_len = sizeof(*rtnh);
+		rta->rta_len += rtnh->rtnh_len;
+		if (parse_one_nh(n, r, rta, 4096, rtnh, &argc, &argv)) {
+			fprintf(stderr, "Error: cannot parse nexthop\n");
+			exit(-1);
+		}
+		rtnh = RTNH_NEXT(rtnh);
+	}
+
+	if (rta->rta_len > RTA_LENGTH(0))
+		return addattr_l(n, 4096, RTA_MULTIPATH,
+				 RTA_DATA(rta), RTA_PAYLOAD(rta));
+	return 0;
+}
+
+static int iproute_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct rtmsg		r;
+		char			buf[4096];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST | flags,
+		.n.nlmsg_type = cmd,
+		.r.rtm_family = preferred_family,
+		.r.rtm_table = RT_TABLE_MAIN,
+		.r.rtm_scope = RT_SCOPE_NOWHERE,
+	};
+	char  mxbuf[256];
+	struct rtattr *mxrta = (void *)mxbuf;
+	unsigned int mxlock = 0;
+	char  *d = NULL;
+	int gw_ok = 0;
+	int dst_ok = 0;
+	int nhs_ok = 0;
+	int scope_ok = 0;
+	int table_ok = 0;
+	int raw = 0;
+	int type_ok = 0;
+	__u32 nhid = 0;
+
+	if (cmd != RTM_DELROUTE) {
+		req.r.rtm_protocol = RTPROT_BOOT;
+		req.r.rtm_scope = RT_SCOPE_UNIVERSE;
+		req.r.rtm_type = RTN_UNICAST;
+	}
+
+	mxrta->rta_type = RTA_METRICS;
+	mxrta->rta_len = RTA_LENGTH(0);
+
+	while (argc > 0) {
+		if (strcmp(*argv, "src") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			get_addr(&addr, *argv, req.r.rtm_family);
+			if (req.r.rtm_family == AF_UNSPEC)
+				req.r.rtm_family = addr.family;
+			addattr_l(&req.n, sizeof(req),
+				  RTA_PREFSRC, &addr.data, addr.bytelen);
+		} else if (strcmp(*argv, "as") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "to") == 0) {
+				NEXT_ARG();
+			}
+			get_addr(&addr, *argv, req.r.rtm_family);
+			if (req.r.rtm_family == AF_UNSPEC)
+				req.r.rtm_family = addr.family;
+			addattr_l(&req.n, sizeof(req),
+				  RTA_NEWDST, &addr.data, addr.bytelen);
+		} else if (strcmp(*argv, "via") == 0) {
+			inet_prefix addr;
+			int family;
+
+			if (gw_ok) {
+				invarg("use nexthop syntax to specify multiple via\n",
+				       *argv);
+			}
+			gw_ok = 1;
+			NEXT_ARG();
+			family = read_family(*argv);
+			if (family == AF_UNSPEC)
+				family = req.r.rtm_family;
+			else
+				NEXT_ARG();
+			get_addr(&addr, *argv, family);
+			if (req.r.rtm_family == AF_UNSPEC)
+				req.r.rtm_family = addr.family;
+			if (addr.family == req.r.rtm_family)
+				addattr_l(&req.n, sizeof(req), RTA_GATEWAY,
+					  &addr.data, addr.bytelen);
+			else
+				addattr_l(&req.n, sizeof(req), RTA_VIA,
+					  &addr.family, addr.bytelen+2);
+		} else if (strcmp(*argv, "from") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			get_prefix(&addr, *argv, req.r.rtm_family);
+			if (req.r.rtm_family == AF_UNSPEC)
+				req.r.rtm_family = addr.family;
+			if (addr.bytelen)
+				addattr_l(&req.n, sizeof(req), RTA_SRC, &addr.data, addr.bytelen);
+			req.r.rtm_src_len = addr.bitlen;
+		} else if (strcmp(*argv, "tos") == 0 ||
+			   matches(*argv, "dsfield") == 0) {
+			__u32 tos;
+
+			NEXT_ARG();
+			if (rtnl_dsfield_a2n(&tos, *argv))
+				invarg("\"tos\" value is invalid\n", *argv);
+			req.r.rtm_tos = tos;
+		} else if (strcmp(*argv, "expires") == 0) {
+			__u32 expires;
+
+			NEXT_ARG();
+			if (get_u32(&expires, *argv, 0))
+				invarg("\"expires\" value is invalid\n", *argv);
+			addattr32(&req.n, sizeof(req), RTA_EXPIRES, expires);
+		} else if (matches(*argv, "metric") == 0 ||
+			   matches(*argv, "priority") == 0 ||
+			   strcmp(*argv, "preference") == 0) {
+			__u32 metric;
+
+			NEXT_ARG();
+			if (get_u32(&metric, *argv, 0))
+				invarg("\"metric\" value is invalid\n", *argv);
+			addattr32(&req.n, sizeof(req), RTA_PRIORITY, metric);
+		} else if (strcmp(*argv, "scope") == 0) {
+			__u32 scope = 0;
+
+			NEXT_ARG();
+			if (rtnl_rtscope_a2n(&scope, *argv))
+				invarg("invalid \"scope\" value\n", *argv);
+			req.r.rtm_scope = scope;
+			scope_ok = 1;
+		} else if (strcmp(*argv, "mtu") == 0) {
+			unsigned int mtu;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_MTU);
+				NEXT_ARG();
+			}
+			if (get_unsigned(&mtu, *argv, 0))
+				invarg("\"mtu\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_MTU, mtu);
+		} else if (strcmp(*argv, "hoplimit") == 0) {
+			unsigned int hoplimit;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_HOPLIMIT);
+				NEXT_ARG();
+			}
+			if (get_unsigned(&hoplimit, *argv, 0) || hoplimit > 255)
+				invarg("\"hoplimit\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_HOPLIMIT, hoplimit);
+		} else if (strcmp(*argv, "advmss") == 0) {
+			unsigned int mss;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_ADVMSS);
+				NEXT_ARG();
+			}
+			if (get_unsigned(&mss, *argv, 0))
+				invarg("\"mss\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_ADVMSS, mss);
+		} else if (matches(*argv, "reordering") == 0) {
+			unsigned int reord;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_REORDERING);
+				NEXT_ARG();
+			}
+			if (get_unsigned(&reord, *argv, 0))
+				invarg("\"reordering\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_REORDERING, reord);
+		} else if (strcmp(*argv, "rtt") == 0) {
+			unsigned int rtt;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_RTT);
+				NEXT_ARG();
+			}
+			if (get_time_rtt(&rtt, *argv, &raw))
+				invarg("\"rtt\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_RTT,
+				(raw) ? rtt : rtt * 8);
+		} else if (strcmp(*argv, "rto_min") == 0) {
+			unsigned int rto_min;
+
+			NEXT_ARG();
+			mxlock |= (1<<RTAX_RTO_MIN);
+			if (get_time_rtt(&rto_min, *argv, &raw))
+				invarg("\"rto_min\" value is invalid\n",
+				       *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_RTO_MIN,
+				      rto_min);
+		} else if (matches(*argv, "window") == 0) {
+			unsigned int win;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_WINDOW);
+				NEXT_ARG();
+			}
+			if (get_unsigned(&win, *argv, 0))
+				invarg("\"window\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_WINDOW, win);
+		} else if (matches(*argv, "cwnd") == 0) {
+			unsigned int win;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_CWND);
+				NEXT_ARG();
+			}
+			if (get_unsigned(&win, *argv, 0))
+				invarg("\"cwnd\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_CWND, win);
+		} else if (matches(*argv, "initcwnd") == 0) {
+			unsigned int win;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_INITCWND);
+				NEXT_ARG();
+			}
+			if (get_unsigned(&win, *argv, 0))
+				invarg("\"initcwnd\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf),
+				      RTAX_INITCWND, win);
+		} else if (matches(*argv, "initrwnd") == 0) {
+			unsigned int win;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_INITRWND);
+				NEXT_ARG();
+			}
+			if (get_unsigned(&win, *argv, 0))
+				invarg("\"initrwnd\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf),
+				      RTAX_INITRWND, win);
+		} else if (matches(*argv, "features") == 0) {
+			unsigned int features = 0;
+
+			while (argc > 0) {
+				NEXT_ARG();
+
+				if (strcmp(*argv, "ecn") == 0)
+					features |= RTAX_FEATURE_ECN;
+				else
+					invarg("\"features\" value not valid\n", *argv);
+				break;
+			}
+
+			rta_addattr32(mxrta, sizeof(mxbuf),
+				      RTAX_FEATURES, features);
+		} else if (matches(*argv, "quickack") == 0) {
+			unsigned int quickack;
+
+			NEXT_ARG();
+			if (get_unsigned(&quickack, *argv, 0))
+				invarg("\"quickack\" value is invalid\n", *argv);
+			if (quickack != 1 && quickack != 0)
+				invarg("\"quickack\" value should be 0 or 1\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf),
+				      RTAX_QUICKACK, quickack);
+		} else if (matches(*argv, "congctl") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= 1 << RTAX_CC_ALGO;
+				NEXT_ARG();
+			}
+			rta_addattr_l(mxrta, sizeof(mxbuf), RTAX_CC_ALGO, *argv,
+				      strlen(*argv));
+		} else if (matches(*argv, "rttvar") == 0) {
+			unsigned int win;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_RTTVAR);
+				NEXT_ARG();
+			}
+			if (get_time_rtt(&win, *argv, &raw))
+				invarg("\"rttvar\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_RTTVAR,
+				(raw) ? win : win * 4);
+		} else if (matches(*argv, "ssthresh") == 0) {
+			unsigned int win;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "lock") == 0) {
+				mxlock |= (1<<RTAX_SSTHRESH);
+				NEXT_ARG();
+			}
+			if (get_unsigned(&win, *argv, 0))
+				invarg("\"ssthresh\" value is invalid\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_SSTHRESH, win);
+		} else if (matches(*argv, "realms") == 0) {
+			__u32 realm;
+
+			NEXT_ARG();
+			if (get_rt_realms_or_raw(&realm, *argv))
+				invarg("\"realm\" value is invalid\n", *argv);
+			addattr32(&req.n, sizeof(req), RTA_FLOW, realm);
+		} else if (strcmp(*argv, "onlink") == 0) {
+			req.r.rtm_flags |= RTNH_F_ONLINK;
+		} else if (strcmp(*argv, "nexthop") == 0) {
+			nhs_ok = 1;
+			break;
+		} else if (!strcmp(*argv, "nhid")) {
+			NEXT_ARG();
+			if (get_u32(&nhid, *argv, 0))
+				invarg("\"id\" value is invalid\n", *argv);
+			addattr32(&req.n, sizeof(req), RTA_NH_ID, nhid);
+		} else if (matches(*argv, "protocol") == 0) {
+			__u32 prot;
+
+			NEXT_ARG();
+			if (rtnl_rtprot_a2n(&prot, *argv))
+				invarg("\"protocol\" value is invalid\n", *argv);
+			req.r.rtm_protocol = prot;
+		} else if (matches(*argv, "table") == 0) {
+			__u32 tid;
+
+			NEXT_ARG();
+			if (rtnl_rttable_a2n(&tid, *argv))
+				invarg("\"table\" value is invalid\n", *argv);
+			if (tid < 256)
+				req.r.rtm_table = tid;
+			else {
+				req.r.rtm_table = RT_TABLE_UNSPEC;
+				addattr32(&req.n, sizeof(req), RTA_TABLE, tid);
+			}
+			table_ok = 1;
+		} else if (matches(*argv, "vrf") == 0) {
+			__u32 tid;
+
+			NEXT_ARG();
+			tid = ipvrf_get_table(*argv);
+			if (tid == 0)
+				invarg("Invalid VRF\n", *argv);
+			if (tid < 256)
+				req.r.rtm_table = tid;
+			else {
+				req.r.rtm_table = RT_TABLE_UNSPEC;
+				addattr32(&req.n, sizeof(req), RTA_TABLE, tid);
+			}
+			table_ok = 1;
+		} else if (strcmp(*argv, "dev") == 0 ||
+			   strcmp(*argv, "oif") == 0) {
+			NEXT_ARG();
+			d = *argv;
+		} else if (matches(*argv, "pref") == 0) {
+			__u8 pref;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "low") == 0)
+				pref = ICMPV6_ROUTER_PREF_LOW;
+			else if (strcmp(*argv, "medium") == 0)
+				pref = ICMPV6_ROUTER_PREF_MEDIUM;
+			else if (strcmp(*argv, "high") == 0)
+				pref = ICMPV6_ROUTER_PREF_HIGH;
+			else if (get_u8(&pref, *argv, 0))
+				invarg("\"pref\" value is invalid\n", *argv);
+			addattr8(&req.n, sizeof(req), RTA_PREF, pref);
+		} else if (strcmp(*argv, "encap") == 0) {
+			char buf[1024];
+			struct rtattr *rta = (void *)buf;
+
+			rta->rta_type = RTA_ENCAP;
+			rta->rta_len = RTA_LENGTH(0);
+
+			lwt_parse_encap(rta, sizeof(buf), &argc, &argv,
+					RTA_ENCAP, RTA_ENCAP_TYPE);
+
+			if (rta->rta_len > RTA_LENGTH(0))
+				addraw_l(&req.n, 1024
+					 , RTA_DATA(rta), RTA_PAYLOAD(rta));
+		} else if (strcmp(*argv, "ttl-propagate") == 0) {
+			__u8 ttl_prop;
+
+			NEXT_ARG();
+			if (matches(*argv, "enabled") == 0)
+				ttl_prop = 1;
+			else if (matches(*argv, "disabled") == 0)
+				ttl_prop = 0;
+			else
+				invarg("\"ttl-propagate\" value is invalid\n",
+				       *argv);
+
+			addattr8(&req.n, sizeof(req), RTA_TTL_PROPAGATE,
+				 ttl_prop);
+		} else if (matches(*argv, "fastopen_no_cookie") == 0) {
+			unsigned int fastopen_no_cookie;
+
+			NEXT_ARG();
+			if (get_unsigned(&fastopen_no_cookie, *argv, 0))
+				invarg("\"fastopen_no_cookie\" value is invalid\n", *argv);
+			if (fastopen_no_cookie != 1 && fastopen_no_cookie != 0)
+				invarg("\"fastopen_no_cookie\" value should be 0 or 1\n", *argv);
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_FASTOPEN_NO_COOKIE, fastopen_no_cookie);
+		} else {
+			int type;
+			inet_prefix dst;
+
+			if (strcmp(*argv, "to") == 0) {
+				NEXT_ARG();
+			}
+			if ((**argv < '0' || **argv > '9') &&
+			    rtnl_rtntype_a2n(&type, *argv) == 0) {
+				NEXT_ARG();
+				req.r.rtm_type = type;
+				type_ok = 1;
+			}
+
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (dst_ok)
+				duparg2("to", *argv);
+			get_prefix(&dst, *argv, req.r.rtm_family);
+			if (req.r.rtm_family == AF_UNSPEC)
+				req.r.rtm_family = dst.family;
+			req.r.rtm_dst_len = dst.bitlen;
+			dst_ok = 1;
+			if (dst.bytelen)
+				addattr_l(&req.n, sizeof(req),
+					  RTA_DST, &dst.data, dst.bytelen);
+		}
+		argc--; argv++;
+	}
+
+	if (!dst_ok)
+		usage();
+
+	if (d) {
+		int idx = ll_name_to_index(d);
+
+		if (!idx)
+			return nodev(d);
+		addattr32(&req.n, sizeof(req), RTA_OIF, idx);
+	}
+
+	if (mxrta->rta_len > RTA_LENGTH(0)) {
+		if (mxlock)
+			rta_addattr32(mxrta, sizeof(mxbuf), RTAX_LOCK, mxlock);
+		addattr_l(&req.n, sizeof(req), RTA_METRICS, RTA_DATA(mxrta), RTA_PAYLOAD(mxrta));
+	}
+
+	if (nhs_ok && parse_nexthops(&req.n, &req.r, argc, argv))
+		return -1;
+
+	if (req.r.rtm_family == AF_UNSPEC)
+		req.r.rtm_family = AF_INET;
+
+	if (!table_ok) {
+		if (req.r.rtm_type == RTN_LOCAL ||
+		    req.r.rtm_type == RTN_BROADCAST ||
+		    req.r.rtm_type == RTN_NAT ||
+		    req.r.rtm_type == RTN_ANYCAST)
+			req.r.rtm_table = RT_TABLE_LOCAL;
+	}
+	if (!scope_ok) {
+		if (req.r.rtm_family == AF_INET6 ||
+		    req.r.rtm_family == AF_MPLS)
+			req.r.rtm_scope = RT_SCOPE_UNIVERSE;
+		else if (req.r.rtm_type == RTN_LOCAL ||
+			 req.r.rtm_type == RTN_NAT)
+			req.r.rtm_scope = RT_SCOPE_HOST;
+		else if (req.r.rtm_type == RTN_BROADCAST ||
+			 req.r.rtm_type == RTN_MULTICAST ||
+			 req.r.rtm_type == RTN_ANYCAST)
+			req.r.rtm_scope = RT_SCOPE_LINK;
+		else if (req.r.rtm_type == RTN_UNICAST ||
+			 req.r.rtm_type == RTN_UNSPEC) {
+			if (cmd == RTM_DELROUTE)
+				req.r.rtm_scope = RT_SCOPE_NOWHERE;
+			else if (!gw_ok && !nhs_ok && !nhid)
+				req.r.rtm_scope = RT_SCOPE_LINK;
+		}
+	}
+
+	if (!type_ok && req.r.rtm_family == AF_MPLS)
+		req.r.rtm_type = RTN_UNICAST;
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+static int iproute_flush_cache(void)
+{
+#define ROUTE_FLUSH_PATH "/proc/sys/net/ipv4/route/flush"
+
+	int len;
+	int flush_fd = open(ROUTE_FLUSH_PATH, O_WRONLY);
+	char *buffer = "-1";
+
+	if (flush_fd < 0) {
+		fprintf(stderr, "Cannot open \"%s\": %s\n",
+				ROUTE_FLUSH_PATH, strerror(errno));
+		return -1;
+	}
+
+	len = strlen(buffer);
+
+	if ((write(flush_fd, (void *)buffer, len)) < len) {
+		fprintf(stderr, "Cannot flush routing cache\n");
+		close(flush_fd);
+		return -1;
+	}
+	close(flush_fd);
+	return 0;
+}
+
+static __u32 route_dump_magic = 0x45311224;
+
+static int save_route(struct nlmsghdr *n, void *arg)
+{
+	int ret;
+	int len = n->nlmsg_len;
+	struct rtmsg *r = NLMSG_DATA(n);
+	struct rtattr *tb[RTA_MAX+1];
+	int host_len;
+
+	host_len = af_bit_len(r->rtm_family);
+	len -= NLMSG_LENGTH(sizeof(*r));
+	parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+	if (!filter_nlmsg(n, tb, host_len))
+		return 0;
+
+	ret = write(STDOUT_FILENO, n, n->nlmsg_len);
+	if ((ret > 0) && (ret != n->nlmsg_len)) {
+		fprintf(stderr, "Short write while saving nlmsg\n");
+		ret = -EIO;
+	}
+
+	return ret == n->nlmsg_len ? 0 : ret;
+}
+
+static int save_route_prep(void)
+{
+	int ret;
+
+	if (isatty(STDOUT_FILENO)) {
+		fprintf(stderr, "Not sending a binary stream to stdout\n");
+		return -1;
+	}
+
+	ret = write(STDOUT_FILENO, &route_dump_magic, sizeof(route_dump_magic));
+	if (ret != sizeof(route_dump_magic)) {
+		fprintf(stderr, "Can't write magic to dump file\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int iproute_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+	struct rtmsg *rtm = NLMSG_DATA(nlh);
+	int err;
+
+	rtm->rtm_protocol = filter.protocol;
+	if (filter.cloned)
+		rtm->rtm_flags |= RTM_F_CLONED;
+
+	if (filter.tb) {
+		err = addattr32(nlh, reqlen, RTA_TABLE, filter.tb);
+		if (err)
+			return err;
+	}
+
+	if (filter.oif) {
+		err = addattr32(nlh, reqlen, RTA_OIF, filter.oif);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int iproute_flush(int family, rtnl_filter_t filter_fn)
+{
+	time_t start = time(0);
+	char flushb[4096-512];
+	int round = 0;
+	int ret;
+
+	if (filter.cloned) {
+		if (family != AF_INET6) {
+			iproute_flush_cache();
+			if (show_stats)
+				printf("*** IPv4 routing cache is flushed.\n");
+		}
+		if (family == AF_INET)
+			return 0;
+	}
+
+	filter.flushb = flushb;
+	filter.flushp = 0;
+	filter.flushe = sizeof(flushb);
+
+	for (;;) {
+		if (rtnl_routedump_req(&rth, family, iproute_dump_filter) < 0) {
+			perror("Cannot send dump request");
+			return -2;
+		}
+		filter.flushed = 0;
+		if (rtnl_dump_filter(&rth, filter_fn, stdout) < 0) {
+			fprintf(stderr, "Flush terminated\n");
+			return -2;
+		}
+		if (filter.flushed == 0) {
+			if (show_stats) {
+				if (round == 0 &&
+				    (!filter.cloned || family == AF_INET6))
+					printf("Nothing to flush.\n");
+				else
+					printf("*** Flush is complete after %d round%s ***\n",
+					       round, round > 1 ? "s" : "");
+			}
+			fflush(stdout);
+			return 0;
+		}
+		round++;
+		ret = flush_update();
+		if (ret < 0)
+			return ret;
+
+		if (time(0) - start > 30) {
+			printf("\n*** Flush not completed after %ld seconds, %d entries remain ***\n",
+			       (long)(time(0) - start), filter.flushed);
+			return -1;
+		}
+
+		if (show_stats) {
+			printf("\n*** Round %d, deleting %d entries ***\n",
+			       round, filter.flushed);
+			fflush(stdout);
+		}
+	}
+}
+
+static int iproute_list_flush_or_save(int argc, char **argv, int action)
+{
+	int dump_family = preferred_family;
+	char *id = NULL;
+	char *od = NULL;
+	unsigned int mark = 0;
+	rtnl_filter_t filter_fn;
+
+	if (action == IPROUTE_SAVE) {
+		if (save_route_prep())
+			return -1;
+
+		filter_fn = save_route;
+	} else
+		filter_fn = print_route;
+
+	iproute_reset_filter(0);
+	filter.tb = RT_TABLE_MAIN;
+
+	if ((action == IPROUTE_FLUSH) && argc <= 0) {
+		fprintf(stderr, "\"ip route flush\" requires arguments.\n");
+		return -1;
+	}
+
+	while (argc > 0) {
+		if (matches(*argv, "table") == 0) {
+			__u32 tid;
+
+			NEXT_ARG();
+			if (rtnl_rttable_a2n(&tid, *argv)) {
+				if (strcmp(*argv, "all") == 0) {
+					filter.tb = 0;
+				} else if (strcmp(*argv, "cache") == 0) {
+					filter.cloned = 1;
+				} else if (strcmp(*argv, "help") == 0) {
+					usage();
+				} else {
+					invarg("table id value is invalid\n", *argv);
+				}
+			} else
+				filter.tb = tid;
+		} else if (matches(*argv, "vrf") == 0) {
+			__u32 tid;
+
+			NEXT_ARG();
+			tid = ipvrf_get_table(*argv);
+			if (tid == 0)
+				invarg("Invalid VRF\n", *argv);
+			filter.tb = tid;
+			filter.typemask = ~(1 << RTN_LOCAL | 1<<RTN_BROADCAST);
+		} else if (matches(*argv, "cached") == 0 ||
+			   matches(*argv, "cloned") == 0) {
+			filter.cloned = 1;
+		} else if (strcmp(*argv, "tos") == 0 ||
+			   matches(*argv, "dsfield") == 0) {
+			__u32 tos;
+
+			NEXT_ARG();
+			if (rtnl_dsfield_a2n(&tos, *argv))
+				invarg("TOS value is invalid\n", *argv);
+			filter.tos = tos;
+			filter.tosmask = -1;
+		} else if (matches(*argv, "protocol") == 0) {
+			__u32 prot = 0;
+
+			NEXT_ARG();
+			filter.protocolmask = -1;
+			if (rtnl_rtprot_a2n(&prot, *argv)) {
+				if (strcmp(*argv, "all") != 0)
+					invarg("invalid \"protocol\"\n", *argv);
+				prot = 0;
+				filter.protocolmask = 0;
+			}
+			filter.protocol = prot;
+		} else if (matches(*argv, "scope") == 0) {
+			__u32 scope = 0;
+
+			NEXT_ARG();
+			filter.scopemask = -1;
+			if (rtnl_rtscope_a2n(&scope, *argv)) {
+				if (strcmp(*argv, "all") != 0)
+					invarg("invalid \"scope\"\n", *argv);
+				scope = RT_SCOPE_NOWHERE;
+				filter.scopemask = 0;
+			}
+			filter.scope = scope;
+		} else if (matches(*argv, "type") == 0) {
+			int type;
+
+			NEXT_ARG();
+			if (rtnl_rtntype_a2n(&type, *argv))
+				invarg("node type value is invalid\n", *argv);
+			filter.typemask = (1<<type);
+		} else if (strcmp(*argv, "dev") == 0 ||
+			   strcmp(*argv, "oif") == 0) {
+			NEXT_ARG();
+			od = *argv;
+		} else if (strcmp(*argv, "iif") == 0) {
+			NEXT_ARG();
+			id = *argv;
+		} else if (strcmp(*argv, "mark") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&mark, *argv, 0))
+				invarg("invalid mark value", *argv);
+			filter.markmask = -1;
+		} else if (matches(*argv, "metric") == 0 ||
+			   matches(*argv, "priority") == 0 ||
+			   strcmp(*argv, "preference") == 0) {
+			__u32 metric;
+
+			NEXT_ARG();
+			if (get_u32(&metric, *argv, 0))
+				invarg("\"metric\" value is invalid\n", *argv);
+			filter.metric = metric;
+			filter.metricmask = -1;
+		} else if (strcmp(*argv, "via") == 0) {
+			int family;
+
+			NEXT_ARG();
+			family = read_family(*argv);
+			if (family == AF_UNSPEC)
+				family = dump_family;
+			else
+				NEXT_ARG();
+			get_prefix(&filter.rvia, *argv, family);
+		} else if (strcmp(*argv, "src") == 0) {
+			NEXT_ARG();
+			get_prefix(&filter.rprefsrc, *argv, dump_family);
+		} else if (matches(*argv, "realms") == 0) {
+			__u32 realm;
+
+			NEXT_ARG();
+			if (get_rt_realms_or_raw(&realm, *argv))
+				invarg("invalid realms\n", *argv);
+			filter.realm = realm;
+			filter.realmmask = ~0U;
+			if ((filter.realm&0xFFFF) == 0 &&
+			    (*argv)[strlen(*argv) - 1] == '/')
+				filter.realmmask &= ~0xFFFF;
+			if ((filter.realm&0xFFFF0000U) == 0 &&
+			    (strchr(*argv, '/') == NULL ||
+			     (*argv)[0] == '/'))
+				filter.realmmask &= ~0xFFFF0000U;
+		} else if (matches(*argv, "from") == 0) {
+			NEXT_ARG();
+			if (matches(*argv, "root") == 0) {
+				NEXT_ARG();
+				get_prefix(&filter.rsrc, *argv, dump_family);
+			} else if (matches(*argv, "match") == 0) {
+				NEXT_ARG();
+				get_prefix(&filter.msrc, *argv, dump_family);
+			} else {
+				if (matches(*argv, "exact") == 0) {
+					NEXT_ARG();
+				}
+				get_prefix(&filter.msrc, *argv, dump_family);
+				filter.rsrc = filter.msrc;
+			}
+		} else {
+			if (matches(*argv, "to") == 0) {
+				NEXT_ARG();
+			}
+			if (matches(*argv, "root") == 0) {
+				NEXT_ARG();
+				get_prefix(&filter.rdst, *argv, dump_family);
+			} else if (matches(*argv, "match") == 0) {
+				NEXT_ARG();
+				get_prefix(&filter.mdst, *argv, dump_family);
+			} else {
+				if (matches(*argv, "exact") == 0) {
+					NEXT_ARG();
+				}
+				get_prefix(&filter.mdst, *argv, dump_family);
+				filter.rdst = filter.mdst;
+			}
+		}
+		argc--; argv++;
+	}
+
+	if (dump_family == AF_UNSPEC && filter.tb)
+		dump_family = AF_INET;
+
+	if (id || od)  {
+		int idx;
+
+		if (id) {
+			idx = ll_name_to_index(id);
+			if (!idx)
+				return nodev(id);
+			filter.iif = idx;
+			filter.iifmask = -1;
+		}
+		if (od) {
+			idx = ll_name_to_index(od);
+			if (!idx)
+				return nodev(od);
+			filter.oif = idx;
+			filter.oifmask = -1;
+		}
+	}
+	filter.mark = mark;
+
+	if (action == IPROUTE_FLUSH)
+		return iproute_flush(dump_family, filter_fn);
+
+	if (rtnl_routedump_req(&rth, dump_family, iproute_dump_filter) < 0) {
+		perror("Cannot send dump request");
+		return -2;
+	}
+
+	new_json_obj(json);
+
+	if (rtnl_dump_filter(&rth, filter_fn, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return -2;
+	}
+
+	delete_json_obj();
+	fflush(stdout);
+	return 0;
+}
+
+
+static int iproute_get(int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr	n;
+		struct rtmsg		r;
+		char			buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETROUTE,
+		.r.rtm_family = preferred_family,
+	};
+	char  *idev = NULL;
+	char  *odev = NULL;
+	struct nlmsghdr *answer;
+	int connected = 0;
+	int fib_match = 0;
+	int from_ok = 0;
+	unsigned int mark = 0;
+	bool address_found = false;
+
+	iproute_reset_filter(0);
+	filter.cloned = 2;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "tos") == 0 ||
+		    matches(*argv, "dsfield") == 0) {
+			__u32 tos;
+
+			NEXT_ARG();
+			if (rtnl_dsfield_a2n(&tos, *argv))
+				invarg("TOS value is invalid\n", *argv);
+			req.r.rtm_tos = tos;
+		} else if (matches(*argv, "from") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			if (matches(*argv, "help") == 0)
+				usage();
+			from_ok = 1;
+			get_prefix(&addr, *argv, req.r.rtm_family);
+			if (req.r.rtm_family == AF_UNSPEC)
+				req.r.rtm_family = addr.family;
+			if (addr.bytelen)
+				addattr_l(&req.n, sizeof(req), RTA_SRC,
+					  &addr.data, addr.bytelen);
+			req.r.rtm_src_len = addr.bitlen;
+		} else if (matches(*argv, "iif") == 0) {
+			NEXT_ARG();
+			idev = *argv;
+		} else if (matches(*argv, "mark") == 0) {
+			NEXT_ARG();
+			if (get_unsigned(&mark, *argv, 0))
+				invarg("invalid mark value", *argv);
+		} else if (matches(*argv, "oif") == 0 ||
+			   strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			odev = *argv;
+		} else if (matches(*argv, "notify") == 0) {
+			req.r.rtm_flags |= RTM_F_NOTIFY;
+		} else if (matches(*argv, "connected") == 0) {
+			connected = 1;
+		} else if (matches(*argv, "vrf") == 0) {
+			NEXT_ARG();
+			if (!name_is_vrf(*argv))
+				invarg("Invalid VRF\n", *argv);
+			odev = *argv;
+		} else if (matches(*argv, "uid") == 0) {
+			uid_t uid;
+
+			NEXT_ARG();
+			if (get_unsigned(&uid, *argv, 0))
+				invarg("invalid UID\n", *argv);
+			addattr32(&req.n, sizeof(req), RTA_UID, uid);
+		} else if (matches(*argv, "fibmatch") == 0) {
+			fib_match = 1;
+		} else if (strcmp(*argv, "as") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "to") == 0)
+				NEXT_ARG();
+			get_addr(&addr, *argv, req.r.rtm_family);
+			if (req.r.rtm_family == AF_UNSPEC)
+				req.r.rtm_family = addr.family;
+			addattr_l(&req.n, sizeof(req), RTA_NEWDST,
+				  &addr.data, addr.bytelen);
+		} else if (matches(*argv, "sport") == 0) {
+			__be16 sport;
+
+			NEXT_ARG();
+			if (get_be16(&sport, *argv, 0))
+				invarg("invalid sport\n", *argv);
+			addattr16(&req.n, sizeof(req), RTA_SPORT, sport);
+		} else if (matches(*argv, "dport") == 0) {
+			__be16 dport;
+
+			NEXT_ARG();
+			if (get_be16(&dport, *argv, 0))
+				invarg("invalid dport\n", *argv);
+			addattr16(&req.n, sizeof(req), RTA_DPORT, dport);
+		} else if (matches(*argv, "ipproto") == 0) {
+			int ipproto;
+
+			NEXT_ARG();
+			ipproto = inet_proto_a2n(*argv);
+			if (ipproto < 0)
+				invarg("Invalid \"ipproto\" value\n",
+				       *argv);
+			addattr8(&req.n, sizeof(req), RTA_IP_PROTO, ipproto);
+		} else {
+			inet_prefix addr;
+
+			if (strcmp(*argv, "to") == 0) {
+				NEXT_ARG();
+			}
+			if (matches(*argv, "help") == 0)
+				usage();
+			get_prefix(&addr, *argv, req.r.rtm_family);
+			if (req.r.rtm_family == AF_UNSPEC)
+				req.r.rtm_family = addr.family;
+			if (addr.bytelen)
+				addattr_l(&req.n, sizeof(req),
+					  RTA_DST, &addr.data, addr.bytelen);
+			req.r.rtm_dst_len = addr.bitlen;
+			address_found = true;
+		}
+		argc--; argv++;
+	}
+
+	if (!address_found) {
+		fprintf(stderr, "need at least a destination address\n");
+		return -1;
+	}
+
+	if (idev || odev)  {
+		int idx;
+
+		if (idev) {
+			idx = ll_name_to_index(idev);
+			if (!idx)
+				return nodev(idev);
+			addattr32(&req.n, sizeof(req), RTA_IIF, idx);
+		}
+		if (odev) {
+			idx = ll_name_to_index(odev);
+			if (!idx)
+				return nodev(odev);
+			addattr32(&req.n, sizeof(req), RTA_OIF, idx);
+		}
+	}
+	if (mark)
+		addattr32(&req.n, sizeof(req), RTA_MARK, mark);
+
+	if (req.r.rtm_family == AF_UNSPEC)
+		req.r.rtm_family = AF_INET;
+
+	/* Only IPv4 supports the RTM_F_LOOKUP_TABLE flag */
+	if (req.r.rtm_family == AF_INET)
+		req.r.rtm_flags |= RTM_F_LOOKUP_TABLE;
+	if (fib_match)
+		req.r.rtm_flags |= RTM_F_FIB_MATCH;
+
+	if (rtnl_talk(&rth, &req.n, &answer) < 0)
+		return -2;
+
+	new_json_obj(json);
+
+	if (connected && !from_ok) {
+		struct rtmsg *r = NLMSG_DATA(answer);
+		int len = answer->nlmsg_len;
+		struct rtattr *tb[RTA_MAX+1];
+
+		if (print_route(answer, (void *)stdout) < 0) {
+			fprintf(stderr, "An error :-)\n");
+			free(answer);
+			return -1;
+		}
+
+		if (answer->nlmsg_type != RTM_NEWROUTE) {
+			fprintf(stderr, "Not a route?\n");
+			free(answer);
+			return -1;
+		}
+		len -= NLMSG_LENGTH(sizeof(*r));
+		if (len < 0) {
+			fprintf(stderr, "Wrong len %d\n", len);
+			free(answer);
+			return -1;
+		}
+
+		parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+		if (tb[RTA_PREFSRC]) {
+			tb[RTA_PREFSRC]->rta_type = RTA_SRC;
+			r->rtm_src_len = 8*RTA_PAYLOAD(tb[RTA_PREFSRC]);
+		} else if (!tb[RTA_SRC]) {
+			fprintf(stderr, "Failed to connect the route\n");
+			free(answer);
+			return -1;
+		}
+		if (!odev && tb[RTA_OIF])
+			tb[RTA_OIF]->rta_type = 0;
+		if (tb[RTA_GATEWAY])
+			tb[RTA_GATEWAY]->rta_type = 0;
+		if (tb[RTA_VIA])
+			tb[RTA_VIA]->rta_type = 0;
+		if (!idev && tb[RTA_IIF])
+			tb[RTA_IIF]->rta_type = 0;
+		req.n.nlmsg_flags = NLM_F_REQUEST;
+		req.n.nlmsg_type = RTM_GETROUTE;
+
+		delete_json_obj();
+		free(answer);
+		if (rtnl_talk(&rth, &req.n, &answer) < 0)
+			return -2;
+	}
+
+	if (print_route(answer, (void *)stdout) < 0) {
+		fprintf(stderr, "An error :-)\n");
+		free(answer);
+		return -1;
+	}
+
+	delete_json_obj();
+	free(answer);
+	return 0;
+}
+
+static int rtattr_cmp(const struct rtattr *rta1, const struct rtattr *rta2)
+{
+	if (!rta1 || !rta2 || rta1->rta_len != rta2->rta_len)
+		return 1;
+
+	return memcmp(RTA_DATA(rta1), RTA_DATA(rta2), RTA_PAYLOAD(rta1));
+}
+
+static int restore_handler(struct rtnl_ctrl_data *ctrl,
+			   struct nlmsghdr *n, void *arg)
+{
+	struct rtmsg *r = NLMSG_DATA(n);
+	struct rtattr *tb[RTA_MAX+1];
+	int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
+	int ret, prio = *(int *)arg;
+
+	parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+	/* Restore routes in correct order:
+	 * 0. ones for local addresses,
+	 * 1. ones for local networks,
+	 * 2. others (remote networks/hosts).
+	 */
+	if (!prio && !tb[RTA_GATEWAY] && (!tb[RTA_PREFSRC] ||
+	    !rtattr_cmp(tb[RTA_PREFSRC], tb[RTA_DST])))
+		goto restore;
+	else if (prio == 1 && !tb[RTA_GATEWAY] && tb[RTA_PREFSRC] &&
+		 rtattr_cmp(tb[RTA_PREFSRC], tb[RTA_DST]))
+		goto restore;
+	else if (prio == 2 && tb[RTA_GATEWAY])
+		goto restore;
+
+	return 0;
+
+restore:
+	n->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+
+	ll_init_map(&rth);
+
+	ret = rtnl_talk(&rth, n, NULL);
+	if ((ret < 0) && (errno == EEXIST))
+		ret = 0;
+
+	return ret;
+}
+
+static int route_dump_check_magic(void)
+{
+	int ret;
+	__u32 magic = 0;
+
+	if (isatty(STDIN_FILENO)) {
+		fprintf(stderr, "Can't restore route dump from a terminal\n");
+		return -1;
+	}
+
+	ret = fread(&magic, sizeof(magic), 1, stdin);
+	if (magic != route_dump_magic) {
+		fprintf(stderr, "Magic mismatch (%d elems, %x magic)\n", ret, magic);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int iproute_restore(void)
+{
+	int pos, prio;
+
+	if (route_dump_check_magic())
+		return -1;
+
+	pos = ftell(stdin);
+	if (pos == -1) {
+		perror("Failed to restore: ftell");
+		return -1;
+	}
+
+	for (prio = 0; prio < 3; prio++) {
+		int err;
+
+		err = rtnl_from_file(stdin, &restore_handler, &prio);
+		if (err)
+			return -2;
+
+		if (fseek(stdin, pos, SEEK_SET) == -1) {
+			perror("Failed to restore: fseek");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int show_handler(struct rtnl_ctrl_data *ctrl,
+			struct nlmsghdr *n, void *arg)
+{
+	print_route(n, stdout);
+	return 0;
+}
+
+static int iproute_showdump(void)
+{
+	if (route_dump_check_magic())
+		return -1;
+
+	if (rtnl_from_file(stdin, &show_handler, NULL))
+		return -2;
+
+	return 0;
+}
+
+void iproute_reset_filter(int ifindex)
+{
+	memset(&filter, 0, sizeof(filter));
+	filter.mdst.bitlen = -1;
+	filter.msrc.bitlen = -1;
+	filter.oif = ifindex;
+	if (filter.oif > 0)
+		filter.oifmask = -1;
+}
+
+int do_iproute(int argc, char **argv)
+{
+	if (argc < 1)
+		return iproute_list_flush_or_save(0, NULL, IPROUTE_LIST);
+
+	if (matches(*argv, "add") == 0)
+		return iproute_modify(RTM_NEWROUTE, NLM_F_CREATE|NLM_F_EXCL,
+				      argc-1, argv+1);
+	if (matches(*argv, "change") == 0 || strcmp(*argv, "chg") == 0)
+		return iproute_modify(RTM_NEWROUTE, NLM_F_REPLACE,
+				      argc-1, argv+1);
+	if (matches(*argv, "replace") == 0)
+		return iproute_modify(RTM_NEWROUTE, NLM_F_CREATE|NLM_F_REPLACE,
+				      argc-1, argv+1);
+	if (matches(*argv, "prepend") == 0)
+		return iproute_modify(RTM_NEWROUTE, NLM_F_CREATE,
+				      argc-1, argv+1);
+	if (matches(*argv, "append") == 0)
+		return iproute_modify(RTM_NEWROUTE, NLM_F_CREATE|NLM_F_APPEND,
+				      argc-1, argv+1);
+	if (matches(*argv, "test") == 0)
+		return iproute_modify(RTM_NEWROUTE, NLM_F_EXCL,
+				      argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return iproute_modify(RTM_DELROUTE, 0,
+				      argc-1, argv+1);
+	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+	    || matches(*argv, "lst") == 0)
+		return iproute_list_flush_or_save(argc-1, argv+1, IPROUTE_LIST);
+	if (matches(*argv, "get") == 0)
+		return iproute_get(argc-1, argv+1);
+	if (matches(*argv, "flush") == 0)
+		return iproute_list_flush_or_save(argc-1, argv+1, IPROUTE_FLUSH);
+	if (matches(*argv, "save") == 0)
+		return iproute_list_flush_or_save(argc-1, argv+1, IPROUTE_SAVE);
+	if (matches(*argv, "restore") == 0)
+		return iproute_restore();
+	if (matches(*argv, "showdump") == 0)
+		return iproute_showdump();
+	if (matches(*argv, "help") == 0)
+		usage();
+
+	fprintf(stderr,
+		"Command \"%s\" is unknown, try \"ip route help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
new file mode 100644
index 0000000..60f34a3
--- /dev/null
+++ b/ip/iproute_lwtunnel.c
@@ -0,0 +1,1174 @@
+/*
+ * iproute_lwtunnel.c
+ *
+ *		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.
+ *
+ * Authors:	Roopa Prabhu, <roopa@cumulusnetworks.com>
+ *		Thomas Graf <tgraf@suug.ch>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <linux/ila.h>
+#include <linux/lwtunnel.h>
+#include <linux/mpls_iptunnel.h>
+#include <errno.h>
+
+#include "rt_names.h"
+#include "bpf_util.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "ila_common.h"
+
+#include <linux/seg6.h>
+#include <linux/seg6_iptunnel.h>
+#include <linux/seg6_hmac.h>
+#include <linux/seg6_local.h>
+#include <linux/if_tunnel.h>
+
+static const char *format_encap_type(int type)
+{
+	switch (type) {
+	case LWTUNNEL_ENCAP_MPLS:
+		return "mpls";
+	case LWTUNNEL_ENCAP_IP:
+		return "ip";
+	case LWTUNNEL_ENCAP_IP6:
+		return "ip6";
+	case LWTUNNEL_ENCAP_ILA:
+		return "ila";
+	case LWTUNNEL_ENCAP_BPF:
+		return "bpf";
+	case LWTUNNEL_ENCAP_SEG6:
+		return "seg6";
+	case LWTUNNEL_ENCAP_SEG6_LOCAL:
+		return "seg6local";
+	default:
+		return "unknown";
+	}
+}
+
+static void encap_type_usage(void)
+{
+	int i;
+
+	fprintf(stderr, "Usage: ip route ... encap TYPE [ OPTIONS ] [...]\n");
+
+	for (i = 1; i <= LWTUNNEL_ENCAP_MAX; i++)
+		fprintf(stderr, "%s %s\n", format_encap_type(i),
+			i == 1 ? "TYPE := " : "      ");
+
+	exit(-1);
+}
+
+static int read_encap_type(const char *name)
+{
+	if (strcmp(name, "mpls") == 0)
+		return LWTUNNEL_ENCAP_MPLS;
+	else if (strcmp(name, "ip") == 0)
+		return LWTUNNEL_ENCAP_IP;
+	else if (strcmp(name, "ip6") == 0)
+		return LWTUNNEL_ENCAP_IP6;
+	else if (strcmp(name, "ila") == 0)
+		return LWTUNNEL_ENCAP_ILA;
+	else if (strcmp(name, "bpf") == 0)
+		return LWTUNNEL_ENCAP_BPF;
+	else if (strcmp(name, "seg6") == 0)
+		return LWTUNNEL_ENCAP_SEG6;
+	else if (strcmp(name, "seg6local") == 0)
+		return LWTUNNEL_ENCAP_SEG6_LOCAL;
+	else if (strcmp(name, "help") == 0)
+		encap_type_usage();
+
+	return LWTUNNEL_ENCAP_NONE;
+}
+
+static void print_srh(FILE *fp, struct ipv6_sr_hdr *srh)
+{
+	int i;
+
+	if (is_json_context())
+		open_json_array(PRINT_JSON, "segs");
+	else
+		fprintf(fp, "segs %d [ ", srh->first_segment + 1);
+
+	for (i = srh->first_segment; i >= 0; i--)
+		print_color_string(PRINT_ANY, COLOR_INET6,
+				   NULL, "%s ",
+				   rt_addr_n2a(AF_INET6, 16, &srh->segments[i]));
+
+	if (is_json_context())
+		close_json_array(PRINT_JSON, NULL);
+	else
+		fprintf(fp, "] ");
+
+	if (sr_has_hmac(srh)) {
+		unsigned int offset = ((srh->hdrlen + 1) << 3) - 40;
+		struct sr6_tlv_hmac *tlv;
+
+		tlv = (struct sr6_tlv_hmac *)((char *)srh + offset);
+		print_0xhex(PRINT_ANY, "hmac",
+			    "hmac %llX ", ntohl(tlv->hmackeyid));
+	}
+}
+
+static const char *seg6_mode_types[] = {
+	[SEG6_IPTUN_MODE_INLINE]	= "inline",
+	[SEG6_IPTUN_MODE_ENCAP]		= "encap",
+	[SEG6_IPTUN_MODE_L2ENCAP]	= "l2encap",
+};
+
+static const char *format_seg6mode_type(int mode)
+{
+	if (mode < 0 || mode > ARRAY_SIZE(seg6_mode_types))
+		return "<unknown>";
+
+	return seg6_mode_types[mode];
+}
+
+static int read_seg6mode_type(const char *mode)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(seg6_mode_types); i++) {
+		if (strcmp(mode, seg6_mode_types[i]) == 0)
+			return i;
+	}
+
+	return -1;
+}
+
+static void print_encap_seg6(FILE *fp, struct rtattr *encap)
+{
+	struct rtattr *tb[SEG6_IPTUNNEL_MAX+1];
+	struct seg6_iptunnel_encap *tuninfo;
+
+	parse_rtattr_nested(tb, SEG6_IPTUNNEL_MAX, encap);
+
+	if (!tb[SEG6_IPTUNNEL_SRH])
+		return;
+
+	tuninfo = RTA_DATA(tb[SEG6_IPTUNNEL_SRH]);
+	print_string(PRINT_ANY, "mode",
+		     "mode %s ", format_seg6mode_type(tuninfo->mode));
+
+	print_srh(fp, tuninfo->srh);
+}
+
+static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
+	[SEG6_LOCAL_ACTION_END]			= "End",
+	[SEG6_LOCAL_ACTION_END_X]		= "End.X",
+	[SEG6_LOCAL_ACTION_END_T]		= "End.T",
+	[SEG6_LOCAL_ACTION_END_DX2]		= "End.DX2",
+	[SEG6_LOCAL_ACTION_END_DX6]		= "End.DX6",
+	[SEG6_LOCAL_ACTION_END_DX4]		= "End.DX4",
+	[SEG6_LOCAL_ACTION_END_DT6]		= "End.DT6",
+	[SEG6_LOCAL_ACTION_END_DT4]		= "End.DT4",
+	[SEG6_LOCAL_ACTION_END_B6]		= "End.B6",
+	[SEG6_LOCAL_ACTION_END_B6_ENCAP]	= "End.B6.Encaps",
+	[SEG6_LOCAL_ACTION_END_BM]		= "End.BM",
+	[SEG6_LOCAL_ACTION_END_S]		= "End.S",
+	[SEG6_LOCAL_ACTION_END_AS]		= "End.AS",
+	[SEG6_LOCAL_ACTION_END_AM]		= "End.AM",
+	[SEG6_LOCAL_ACTION_END_BPF]		= "End.BPF",
+};
+
+static const char *format_action_type(int action)
+{
+	if (action < 0 || action > SEG6_LOCAL_ACTION_MAX)
+		return "<invalid>";
+
+	return seg6_action_names[action] ?: "<unknown>";
+}
+
+static int read_action_type(const char *name)
+{
+	int i;
+
+	for (i = 0; i < SEG6_LOCAL_ACTION_MAX + 1; i++) {
+		if (!seg6_action_names[i])
+			continue;
+
+		if (strcmp(seg6_action_names[i], name) == 0)
+			return i;
+	}
+
+	return SEG6_LOCAL_ACTION_UNSPEC;
+}
+
+static void print_encap_bpf_prog(FILE *fp, struct rtattr *encap,
+				 const char *str)
+{
+	struct rtattr *tb[LWT_BPF_PROG_MAX+1];
+	const char *progname = NULL;
+
+	parse_rtattr_nested(tb, LWT_BPF_PROG_MAX, encap);
+
+	if (tb[LWT_BPF_PROG_NAME])
+		progname = rta_getattr_str(tb[LWT_BPF_PROG_NAME]);
+
+	if (is_json_context())
+		print_string(PRINT_JSON, str, NULL,
+			     progname ? : "<unknown>");
+	else {
+		fprintf(fp, "%s ", str);
+		if (progname)
+			fprintf(fp, "%s ", progname);
+	}
+}
+
+static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
+{
+	struct rtattr *tb[SEG6_LOCAL_MAX + 1];
+	int action;
+
+	parse_rtattr_nested(tb, SEG6_LOCAL_MAX, encap);
+
+	if (!tb[SEG6_LOCAL_ACTION])
+		return;
+
+	action = rta_getattr_u32(tb[SEG6_LOCAL_ACTION]);
+
+	print_string(PRINT_ANY, "action",
+		     "action %s ", format_action_type(action));
+
+	if (tb[SEG6_LOCAL_SRH]) {
+		open_json_object("srh");
+		print_srh(fp, RTA_DATA(tb[SEG6_LOCAL_SRH]));
+		close_json_object();
+	}
+
+	if (tb[SEG6_LOCAL_TABLE])
+		print_uint(PRINT_ANY, "table",
+			   "table %u ", rta_getattr_u32(tb[SEG6_LOCAL_TABLE]));
+
+	if (tb[SEG6_LOCAL_NH4]) {
+		print_string(PRINT_ANY, "nh4",
+			     "nh4 %s ", rt_addr_n2a_rta(AF_INET, tb[SEG6_LOCAL_NH4]));
+	}
+
+	if (tb[SEG6_LOCAL_NH6]) {
+		print_string(PRINT_ANY, "nh6",
+			     "nh6 %s ", rt_addr_n2a_rta(AF_INET6, tb[SEG6_LOCAL_NH6]));
+	}
+
+	if (tb[SEG6_LOCAL_IIF]) {
+		int iif = rta_getattr_u32(tb[SEG6_LOCAL_IIF]);
+
+		print_string(PRINT_ANY, "iif",
+			     "iif %s ", ll_index_to_name(iif));
+	}
+
+	if (tb[SEG6_LOCAL_OIF]) {
+		int oif = rta_getattr_u32(tb[SEG6_LOCAL_OIF]);
+
+		print_string(PRINT_ANY, "oif",
+			     "oif %s ", ll_index_to_name(oif));
+	}
+
+	if (tb[SEG6_LOCAL_BPF])
+		print_encap_bpf_prog(fp, tb[SEG6_LOCAL_BPF], "endpoint");
+}
+
+static void print_encap_mpls(FILE *fp, struct rtattr *encap)
+{
+	struct rtattr *tb[MPLS_IPTUNNEL_MAX+1];
+
+	parse_rtattr_nested(tb, MPLS_IPTUNNEL_MAX, encap);
+
+	if (tb[MPLS_IPTUNNEL_DST])
+		print_string(PRINT_ANY, "dst", " %s ",
+			format_host_rta(AF_MPLS, tb[MPLS_IPTUNNEL_DST]));
+	if (tb[MPLS_IPTUNNEL_TTL])
+		print_uint(PRINT_ANY, "ttl", "ttl %u ",
+			rta_getattr_u8(tb[MPLS_IPTUNNEL_TTL]));
+}
+
+static void print_encap_ip(FILE *fp, struct rtattr *encap)
+{
+	struct rtattr *tb[LWTUNNEL_IP_MAX+1];
+	__u16 flags;
+
+	parse_rtattr_nested(tb, LWTUNNEL_IP_MAX, encap);
+
+	if (tb[LWTUNNEL_IP_ID])
+		print_u64(PRINT_ANY, "id", "id %llu ",
+			   ntohll(rta_getattr_u64(tb[LWTUNNEL_IP_ID])));
+
+	if (tb[LWTUNNEL_IP_SRC])
+		print_color_string(PRINT_ANY, COLOR_INET,
+				   "src", "src %s ",
+				   rt_addr_n2a_rta(AF_INET, tb[LWTUNNEL_IP_SRC]));
+
+	if (tb[LWTUNNEL_IP_DST])
+		print_color_string(PRINT_ANY, COLOR_INET,
+				   "dst", "dst %s ",
+				   rt_addr_n2a_rta(AF_INET, tb[LWTUNNEL_IP_DST]));
+
+	if (tb[LWTUNNEL_IP_TTL])
+		print_uint(PRINT_ANY, "ttl",
+			   "ttl %u ", rta_getattr_u8(tb[LWTUNNEL_IP_TTL]));
+
+	if (tb[LWTUNNEL_IP_TOS])
+		print_uint(PRINT_ANY, "tos",
+			   "tos %d ", rta_getattr_u8(tb[LWTUNNEL_IP_TOS]));
+
+	if (tb[LWTUNNEL_IP_FLAGS]) {
+		flags = rta_getattr_u16(tb[LWTUNNEL_IP_FLAGS]);
+		if (flags & TUNNEL_KEY)
+			print_bool(PRINT_ANY, "key", "key ", true);
+		if (flags & TUNNEL_CSUM)
+			print_bool(PRINT_ANY, "csum", "csum ", true);
+		if (flags & TUNNEL_SEQ)
+			print_bool(PRINT_ANY, "seq", "seq ", true);
+	}
+}
+
+static void print_encap_ila(FILE *fp, struct rtattr *encap)
+{
+	struct rtattr *tb[ILA_ATTR_MAX+1];
+
+	parse_rtattr_nested(tb, ILA_ATTR_MAX, encap);
+
+	if (tb[ILA_ATTR_LOCATOR]) {
+		char abuf[ADDR64_BUF_SIZE];
+
+		addr64_n2a(rta_getattr_u64(tb[ILA_ATTR_LOCATOR]),
+			   abuf, sizeof(abuf));
+		print_string(PRINT_ANY, "locator",
+			     " %s ", abuf);
+	}
+
+	if (tb[ILA_ATTR_CSUM_MODE])
+		print_string(PRINT_ANY, "csum_mode",
+			     " csum-mode %s ",
+			     ila_csum_mode2name(rta_getattr_u8(tb[ILA_ATTR_CSUM_MODE])));
+
+	if (tb[ILA_ATTR_IDENT_TYPE])
+		print_string(PRINT_ANY, "ident_type",
+			     " ident-type %s ",
+			     ila_ident_type2name(rta_getattr_u8(tb[ILA_ATTR_IDENT_TYPE])));
+
+	if (tb[ILA_ATTR_HOOK_TYPE])
+		print_string(PRINT_ANY, "hook_type",
+			     " hook-type %s ",
+			     ila_hook_type2name(rta_getattr_u8(tb[ILA_ATTR_HOOK_TYPE])));
+}
+
+static void print_encap_ip6(FILE *fp, struct rtattr *encap)
+{
+	struct rtattr *tb[LWTUNNEL_IP6_MAX+1];
+	__u16 flags;
+
+	parse_rtattr_nested(tb, LWTUNNEL_IP6_MAX, encap);
+
+	if (tb[LWTUNNEL_IP6_ID])
+		print_u64(PRINT_ANY, "id", "id %llu ",
+			    ntohll(rta_getattr_u64(tb[LWTUNNEL_IP6_ID])));
+
+	if (tb[LWTUNNEL_IP6_SRC])
+		print_color_string(PRINT_ANY, COLOR_INET6,
+				   "src", "src %s ",
+				   rt_addr_n2a_rta(AF_INET6, tb[LWTUNNEL_IP6_SRC]));
+
+	if (tb[LWTUNNEL_IP6_DST])
+		print_color_string(PRINT_ANY, COLOR_INET6,
+				   "dst", "dst %s ",
+				   rt_addr_n2a_rta(AF_INET6, tb[LWTUNNEL_IP6_DST]));
+
+	if (tb[LWTUNNEL_IP6_HOPLIMIT])
+		print_u64(PRINT_ANY, "hoplimit",
+			   "hoplimit %u ",
+			   rta_getattr_u8(tb[LWTUNNEL_IP6_HOPLIMIT]));
+
+	if (tb[LWTUNNEL_IP6_TC])
+		print_uint(PRINT_ANY, "tc",
+			   "tc %u ", rta_getattr_u8(tb[LWTUNNEL_IP6_TC]));
+
+	if (tb[LWTUNNEL_IP6_FLAGS]) {
+		flags = rta_getattr_u16(tb[LWTUNNEL_IP6_FLAGS]);
+		if (flags & TUNNEL_KEY)
+			print_bool(PRINT_ANY, "key", "key ", true);
+		if (flags & TUNNEL_CSUM)
+			print_bool(PRINT_ANY, "csum", "csum ", true);
+		if (flags & TUNNEL_SEQ)
+			print_bool(PRINT_ANY, "seq", "seq ", true);
+	}
+}
+
+static void print_encap_bpf(FILE *fp, struct rtattr *encap)
+{
+	struct rtattr *tb[LWT_BPF_MAX+1];
+
+	parse_rtattr_nested(tb, LWT_BPF_MAX, encap);
+
+	if (tb[LWT_BPF_IN])
+		print_encap_bpf_prog(fp, tb[LWT_BPF_IN], "in");
+	if (tb[LWT_BPF_OUT])
+		print_encap_bpf_prog(fp, tb[LWT_BPF_OUT], "out");
+	if (tb[LWT_BPF_XMIT])
+		print_encap_bpf_prog(fp, tb[LWT_BPF_XMIT], "xmit");
+	if (tb[LWT_BPF_XMIT_HEADROOM])
+		print_uint(PRINT_ANY, "headroom",
+			   " %u ", rta_getattr_u32(tb[LWT_BPF_XMIT_HEADROOM]));
+}
+
+void lwt_print_encap(FILE *fp, struct rtattr *encap_type,
+			  struct rtattr *encap)
+{
+	int et;
+
+	if (!encap_type)
+		return;
+
+	et = rta_getattr_u16(encap_type);
+
+	print_string(PRINT_ANY, "encap", " encap %s ", format_encap_type(et));
+
+	switch (et) {
+	case LWTUNNEL_ENCAP_MPLS:
+		print_encap_mpls(fp, encap);
+		break;
+	case LWTUNNEL_ENCAP_IP:
+		print_encap_ip(fp, encap);
+		break;
+	case LWTUNNEL_ENCAP_ILA:
+		print_encap_ila(fp, encap);
+		break;
+	case LWTUNNEL_ENCAP_IP6:
+		print_encap_ip6(fp, encap);
+		break;
+	case LWTUNNEL_ENCAP_BPF:
+		print_encap_bpf(fp, encap);
+		break;
+	case LWTUNNEL_ENCAP_SEG6:
+		print_encap_seg6(fp, encap);
+		break;
+	case LWTUNNEL_ENCAP_SEG6_LOCAL:
+		print_encap_seg6local(fp, encap);
+		break;
+	}
+}
+
+static struct ipv6_sr_hdr *parse_srh(char *segbuf, int hmac, bool encap)
+{
+	struct ipv6_sr_hdr *srh;
+	int nsegs = 0;
+	int srhlen;
+	char *s;
+	int i;
+
+	s = segbuf;
+	for (i = 0; *s; *s++ == ',' ? i++ : *s);
+	nsegs = i + 1;
+
+	if (!encap)
+		nsegs++;
+
+	srhlen = 8 + 16*nsegs;
+
+	if (hmac)
+		srhlen += 40;
+
+	srh = malloc(srhlen);
+	memset(srh, 0, srhlen);
+
+	srh->hdrlen = (srhlen >> 3) - 1;
+	srh->type = 4;
+	srh->segments_left = nsegs - 1;
+	srh->first_segment = nsegs - 1;
+
+	if (hmac)
+		srh->flags |= SR6_FLAG1_HMAC;
+
+	i = srh->first_segment;
+	for (s = strtok(segbuf, ","); s; s = strtok(NULL, ",")) {
+		inet_prefix addr;
+
+		get_addr(&addr, s, AF_INET6);
+		memcpy(&srh->segments[i], addr.data, sizeof(struct in6_addr));
+		i--;
+	}
+
+	if (hmac) {
+		struct sr6_tlv_hmac *tlv;
+
+		tlv = (struct sr6_tlv_hmac *)((char *)srh + srhlen - 40);
+		tlv->tlvhdr.type = SR6_TLV_HMAC;
+		tlv->tlvhdr.len = 38;
+		tlv->hmackeyid = htonl(hmac);
+	}
+
+	return srh;
+}
+
+static int parse_encap_seg6(struct rtattr *rta, size_t len, int *argcp,
+			    char ***argvp)
+{
+	int mode_ok = 0, segs_ok = 0, hmac_ok = 0;
+	struct seg6_iptunnel_encap *tuninfo;
+	struct ipv6_sr_hdr *srh;
+	char **argv = *argvp;
+	char segbuf[1024] = "";
+	int argc = *argcp;
+	int encap = -1;
+	__u32 hmac = 0;
+	int ret = 0;
+	int srhlen;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (mode_ok++)
+				duparg2("mode", *argv);
+			encap = read_seg6mode_type(*argv);
+			if (encap < 0)
+				invarg("\"mode\" value is invalid\n", *argv);
+		} else if (strcmp(*argv, "segs") == 0) {
+			NEXT_ARG();
+			if (segs_ok++)
+				duparg2("segs", *argv);
+			if (encap == -1)
+				invarg("\"segs\" provided before \"mode\"\n",
+				       *argv);
+
+			strlcpy(segbuf, *argv, 1024);
+		} else if (strcmp(*argv, "hmac") == 0) {
+			NEXT_ARG();
+			if (hmac_ok++)
+				duparg2("hmac", *argv);
+			get_u32(&hmac, *argv, 0);
+		} else {
+			break;
+		}
+		argc--; argv++;
+	}
+
+	srh = parse_srh(segbuf, hmac, encap);
+	srhlen = (srh->hdrlen + 1) << 3;
+
+	tuninfo = malloc(sizeof(*tuninfo) + srhlen);
+	memset(tuninfo, 0, sizeof(*tuninfo) + srhlen);
+
+	tuninfo->mode = encap;
+
+	memcpy(tuninfo->srh, srh, srhlen);
+
+	if (rta_addattr_l(rta, len, SEG6_IPTUNNEL_SRH, tuninfo,
+			  sizeof(*tuninfo) + srhlen)) {
+		ret = -1;
+		goto out;
+	}
+
+	*argcp = argc + 1;
+	*argvp = argv - 1;
+
+out:
+	free(tuninfo);
+	free(srh);
+
+	return ret;
+}
+
+struct lwt_x {
+	struct rtattr *rta;
+	size_t len;
+};
+
+static void bpf_lwt_cb(void *lwt_ptr, int fd, const char *annotation)
+{
+	struct lwt_x *x = lwt_ptr;
+
+	rta_addattr32(x->rta, x->len, LWT_BPF_PROG_FD, fd);
+	rta_addattr_l(x->rta, x->len, LWT_BPF_PROG_NAME, annotation,
+		      strlen(annotation) + 1);
+}
+
+static const struct bpf_cfg_ops bpf_cb_ops = {
+	.ebpf_cb = bpf_lwt_cb,
+};
+
+static int lwt_parse_bpf(struct rtattr *rta, size_t len,
+			 int *argcp, char ***argvp,
+			 int attr, const enum bpf_prog_type bpf_type)
+{
+	struct bpf_cfg_in cfg = {
+		.type = bpf_type,
+		.argc = *argcp,
+		.argv = *argvp,
+	};
+	struct lwt_x x = {
+		.rta = rta,
+		.len = len,
+	};
+	struct rtattr *nest;
+	int err;
+
+	nest = rta_nest(rta, len, attr);
+	err = bpf_parse_and_load_common(&cfg, &bpf_cb_ops, &x);
+	if (err < 0) {
+		fprintf(stderr, "Failed to parse eBPF program: %s\n",
+			strerror(-err));
+		return -1;
+	}
+	rta_nest_end(rta, nest);
+
+	*argcp = cfg.argc;
+	*argvp = cfg.argv;
+
+	return 0;
+}
+
+static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
+				 char ***argvp)
+{
+	int segs_ok = 0, hmac_ok = 0, table_ok = 0, nh4_ok = 0, nh6_ok = 0;
+	int iif_ok = 0, oif_ok = 0, action_ok = 0, srh_ok = 0, bpf_ok = 0;
+	__u32 action = 0, table, iif, oif;
+	struct ipv6_sr_hdr *srh;
+	char **argv = *argvp;
+	int argc = *argcp;
+	char segbuf[1024];
+	inet_prefix addr;
+	__u32 hmac = 0;
+	int ret = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (action_ok++)
+				duparg2("action", *argv);
+			action = read_action_type(*argv);
+			if (!action)
+				invarg("\"action\" value is invalid\n", *argv);
+			ret = rta_addattr32(rta, len, SEG6_LOCAL_ACTION,
+					    action);
+		} else if (strcmp(*argv, "table") == 0) {
+			NEXT_ARG();
+			if (table_ok++)
+				duparg2("table", *argv);
+			get_u32(&table, *argv, 0);
+			ret = rta_addattr32(rta, len, SEG6_LOCAL_TABLE, table);
+		} else if (strcmp(*argv, "nh4") == 0) {
+			NEXT_ARG();
+			if (nh4_ok++)
+				duparg2("nh4", *argv);
+			get_addr(&addr, *argv, AF_INET);
+			ret = rta_addattr_l(rta, len, SEG6_LOCAL_NH4,
+					    &addr.data, addr.bytelen);
+		} else if (strcmp(*argv, "nh6") == 0) {
+			NEXT_ARG();
+			if (nh6_ok++)
+				duparg2("nh6", *argv);
+			get_addr(&addr, *argv, AF_INET6);
+			ret = rta_addattr_l(rta, len, SEG6_LOCAL_NH6,
+					    &addr.data, addr.bytelen);
+		} else if (strcmp(*argv, "iif") == 0) {
+			NEXT_ARG();
+			if (iif_ok++)
+				duparg2("iif", *argv);
+			iif = ll_name_to_index(*argv);
+			if (!iif)
+				exit(nodev(*argv));
+			ret = rta_addattr32(rta, len, SEG6_LOCAL_IIF, iif);
+		} else if (strcmp(*argv, "oif") == 0) {
+			NEXT_ARG();
+			if (oif_ok++)
+				duparg2("oif", *argv);
+			oif = ll_name_to_index(*argv);
+			if (!oif)
+				exit(nodev(*argv));
+			ret = rta_addattr32(rta, len, SEG6_LOCAL_OIF, oif);
+		} else if (strcmp(*argv, "srh") == 0) {
+			NEXT_ARG();
+			if (srh_ok++)
+				duparg2("srh", *argv);
+			if (strcmp(*argv, "segs") != 0)
+				invarg("missing \"segs\" attribute for srh\n",
+					*argv);
+			NEXT_ARG();
+			if (segs_ok++)
+				duparg2("segs", *argv);
+			strncpy(segbuf, *argv, 1024);
+			segbuf[1023] = 0;
+			if (!NEXT_ARG_OK())
+				break;
+			NEXT_ARG();
+			if (strcmp(*argv, "hmac") == 0) {
+				NEXT_ARG();
+				if (hmac_ok++)
+					duparg2("hmac", *argv);
+				get_u32(&hmac, *argv, 0);
+			} else {
+				continue;
+			}
+		} else if (strcmp(*argv, "endpoint") == 0) {
+			NEXT_ARG();
+			if (bpf_ok++)
+				duparg2("endpoint", *argv);
+
+			if (lwt_parse_bpf(rta, len, &argc, &argv, SEG6_LOCAL_BPF,
+			    BPF_PROG_TYPE_LWT_SEG6LOCAL) < 0)
+				exit(-1);
+		} else {
+			break;
+		}
+		if (ret)
+			return ret;
+		argc--; argv++;
+	}
+
+	if (!action) {
+		fprintf(stderr, "Missing action type\n");
+		exit(-1);
+	}
+
+	if (srh_ok) {
+		int srhlen;
+
+		srh = parse_srh(segbuf, hmac,
+				action == SEG6_LOCAL_ACTION_END_B6_ENCAP);
+		srhlen = (srh->hdrlen + 1) << 3;
+		ret = rta_addattr_l(rta, len, SEG6_LOCAL_SRH, srh, srhlen);
+		free(srh);
+	}
+
+	*argcp = argc + 1;
+	*argvp = argv - 1;
+
+	return ret;
+}
+
+static int parse_encap_mpls(struct rtattr *rta, size_t len,
+			    int *argcp, char ***argvp)
+{
+	inet_prefix addr;
+	int argc = *argcp;
+	char **argv = *argvp;
+	int ttl_ok = 0;
+
+	if (get_addr(&addr, *argv, AF_MPLS)) {
+		fprintf(stderr,
+			"Error: an inet address is expected rather than \"%s\".\n",
+			*argv);
+		exit(1);
+	}
+
+	if (rta_addattr_l(rta, len, MPLS_IPTUNNEL_DST,
+			  &addr.data, addr.bytelen))
+		return -1;
+
+	argc--;
+	argv++;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "ttl") == 0) {
+			__u8 ttl;
+
+			NEXT_ARG();
+			if (ttl_ok++)
+				duparg2("ttl", *argv);
+			if (get_u8(&ttl, *argv, 0))
+				invarg("\"ttl\" value is invalid\n", *argv);
+			if (rta_addattr8(rta, len, MPLS_IPTUNNEL_TTL, ttl))
+				return -1;
+		} else {
+			break;
+		}
+		argc--; argv++;
+	}
+
+	/* argv is currently the first unparsed argument,
+	 * but the lwt_parse_encap() caller will move to the next,
+	 * so step back
+	 */
+	*argcp = argc + 1;
+	*argvp = argv - 1;
+
+	return 0;
+}
+
+static int parse_encap_ip(struct rtattr *rta, size_t len,
+			  int *argcp, char ***argvp)
+{
+	int id_ok = 0, dst_ok = 0, src_ok = 0, tos_ok = 0, ttl_ok = 0;
+	int key_ok = 0, csum_ok = 0, seq_ok = 0;
+	char **argv = *argvp;
+	int argc = *argcp;
+	int ret = 0;
+	__u16 flags = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "id") == 0) {
+			__u64 id;
+
+			NEXT_ARG();
+			if (id_ok++)
+				duparg2("id", *argv);
+			if (get_be64(&id, *argv, 0))
+				invarg("\"id\" value is invalid\n", *argv);
+			ret = rta_addattr64(rta, len, LWTUNNEL_IP_ID, id);
+		} else if (strcmp(*argv, "dst") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			if (dst_ok++)
+				duparg2("dst", *argv);
+			get_addr(&addr, *argv, AF_INET);
+			ret = rta_addattr_l(rta, len, LWTUNNEL_IP_DST,
+					    &addr.data, addr.bytelen);
+		} else if (strcmp(*argv, "src") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			if (src_ok++)
+				duparg2("src", *argv);
+			get_addr(&addr, *argv, AF_INET);
+			ret = rta_addattr_l(rta, len, LWTUNNEL_IP_SRC,
+					    &addr.data, addr.bytelen);
+		} else if (strcmp(*argv, "tos") == 0) {
+			__u32 tos;
+
+			NEXT_ARG();
+			if (tos_ok++)
+				duparg2("tos", *argv);
+			if (rtnl_dsfield_a2n(&tos, *argv))
+				invarg("\"tos\" value is invalid\n", *argv);
+			ret = rta_addattr8(rta, len, LWTUNNEL_IP_TOS, tos);
+		} else if (strcmp(*argv, "ttl") == 0) {
+			__u8 ttl;
+
+			NEXT_ARG();
+			if (ttl_ok++)
+				duparg2("ttl", *argv);
+			if (get_u8(&ttl, *argv, 0))
+				invarg("\"ttl\" value is invalid\n", *argv);
+			ret = rta_addattr8(rta, len, LWTUNNEL_IP_TTL, ttl);
+		} else if (strcmp(*argv, "key") == 0) {
+			if (key_ok++)
+				duparg2("key", *argv);
+			flags |= TUNNEL_KEY;
+		} else if (strcmp(*argv, "csum") == 0) {
+			if (csum_ok++)
+				duparg2("csum", *argv);
+			flags |= TUNNEL_CSUM;
+		} else if (strcmp(*argv, "seq") == 0) {
+			if (seq_ok++)
+				duparg2("seq", *argv);
+			flags |= TUNNEL_SEQ;
+		} else {
+			break;
+		}
+		if (ret)
+			break;
+		argc--; argv++;
+	}
+
+	if (flags)
+		ret = rta_addattr16(rta, len,  LWTUNNEL_IP_FLAGS, flags);
+
+	/* argv is currently the first unparsed argument,
+	 * but the lwt_parse_encap() caller will move to the next,
+	 * so step back
+	 */
+	*argcp = argc + 1;
+	*argvp = argv - 1;
+
+	return ret;
+}
+
+static int parse_encap_ila(struct rtattr *rta, size_t len,
+			   int *argcp, char ***argvp)
+{
+	__u64 locator;
+	int argc = *argcp;
+	char **argv = *argvp;
+	int ret = 0;
+
+	if (get_addr64(&locator, *argv) < 0) {
+		fprintf(stderr, "Bad locator: %s\n", *argv);
+		exit(1);
+	}
+
+	argc--; argv++;
+
+	if (rta_addattr64(rta, len, ILA_ATTR_LOCATOR, locator))
+		return -1;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "csum-mode") == 0) {
+			int csum_mode;
+
+			NEXT_ARG();
+
+			csum_mode = ila_csum_name2mode(*argv);
+			if (csum_mode < 0)
+				invarg("\"csum-mode\" value is invalid\n",
+				       *argv);
+
+			ret = rta_addattr8(rta, len, ILA_ATTR_CSUM_MODE,
+					   (__u8)csum_mode);
+
+			argc--; argv++;
+		} else if (strcmp(*argv, "ident-type") == 0) {
+			int ident_type;
+
+			NEXT_ARG();
+
+			ident_type = ila_ident_name2type(*argv);
+			if (ident_type < 0)
+				invarg("\"ident-type\" value is invalid\n",
+				       *argv);
+
+			ret = rta_addattr8(rta, len, ILA_ATTR_IDENT_TYPE,
+					   (__u8)ident_type);
+
+			argc--; argv++;
+		} else if (strcmp(*argv, "hook-type") == 0) {
+			int hook_type;
+
+			NEXT_ARG();
+
+			hook_type = ila_hook_name2type(*argv);
+			if (hook_type < 0)
+				invarg("\"hook-type\" value is invalid\n",
+				       *argv);
+
+			ret = rta_addattr8(rta, len, ILA_ATTR_HOOK_TYPE,
+					   (__u8)hook_type);
+
+			argc--; argv++;
+		} else {
+			break;
+		}
+		if (ret)
+			break;
+	}
+
+	/* argv is currently the first unparsed argument,
+	 * but the lwt_parse_encap() caller will move to the next,
+	 * so step back
+	 */
+	*argcp = argc + 1;
+	*argvp = argv - 1;
+
+	return ret;
+}
+
+static int parse_encap_ip6(struct rtattr *rta, size_t len,
+			   int *argcp, char ***argvp)
+{
+	int id_ok = 0, dst_ok = 0, src_ok = 0, tos_ok = 0, ttl_ok = 0;
+	int key_ok = 0, csum_ok = 0, seq_ok = 0;
+	char **argv = *argvp;
+	int argc = *argcp;
+	int ret = 0;
+	__u16 flags = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "id") == 0) {
+			__u64 id;
+
+			NEXT_ARG();
+			if (id_ok++)
+				duparg2("id", *argv);
+			if (get_be64(&id, *argv, 0))
+				invarg("\"id\" value is invalid\n", *argv);
+			ret = rta_addattr64(rta, len, LWTUNNEL_IP6_ID, id);
+		} else if (strcmp(*argv, "dst") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			if (dst_ok++)
+				duparg2("dst", *argv);
+			get_addr(&addr, *argv, AF_INET6);
+			ret = rta_addattr_l(rta, len, LWTUNNEL_IP6_DST,
+					    &addr.data, addr.bytelen);
+		} else if (strcmp(*argv, "src") == 0) {
+			inet_prefix addr;
+
+			NEXT_ARG();
+			if (src_ok++)
+				duparg2("src", *argv);
+			get_addr(&addr, *argv, AF_INET6);
+			ret = rta_addattr_l(rta, len, LWTUNNEL_IP6_SRC,
+					    &addr.data, addr.bytelen);
+		} else if (strcmp(*argv, "tc") == 0) {
+			__u32 tc;
+
+			NEXT_ARG();
+			if (tos_ok++)
+				duparg2("tc", *argv);
+			if (rtnl_dsfield_a2n(&tc, *argv))
+				invarg("\"tc\" value is invalid\n", *argv);
+			ret = rta_addattr8(rta, len, LWTUNNEL_IP6_TC, tc);
+		} else if (strcmp(*argv, "hoplimit") == 0) {
+			__u8 hoplimit;
+
+			NEXT_ARG();
+			if (ttl_ok++)
+				duparg2("hoplimit", *argv);
+			if (get_u8(&hoplimit, *argv, 0))
+				invarg("\"hoplimit\" value is invalid\n",
+				       *argv);
+			ret = rta_addattr8(rta, len, LWTUNNEL_IP6_HOPLIMIT,
+					   hoplimit);
+		} else if (strcmp(*argv, "key") == 0) {
+			if (key_ok++)
+				duparg2("key", *argv);
+			flags |= TUNNEL_KEY;
+		} else if (strcmp(*argv, "csum") == 0) {
+			if (csum_ok++)
+				duparg2("csum", *argv);
+			flags |= TUNNEL_CSUM;
+		} else if (strcmp(*argv, "seq") == 0) {
+			if (seq_ok++)
+				duparg2("seq", *argv);
+			flags |= TUNNEL_SEQ;
+		} else {
+			break;
+		}
+		if (ret)
+			break;
+		argc--; argv++;
+	}
+
+	if (flags)
+		ret = rta_addattr16(rta, len,  LWTUNNEL_IP6_FLAGS, flags);
+
+	/* argv is currently the first unparsed argument,
+	 * but the lwt_parse_encap() caller will move to the next,
+	 * so step back
+	 */
+	*argcp = argc + 1;
+	*argvp = argv - 1;
+
+	return ret;
+}
+
+static void lwt_bpf_usage(void)
+{
+	fprintf(stderr, "Usage: ip route ... encap bpf [ in BPF ] [ out BPF ] [ xmit BPF ] [...]\n");
+	fprintf(stderr, "BPF := obj FILE [ section NAME ] [ verbose ]\n");
+	exit(-1);
+}
+
+static int parse_encap_bpf(struct rtattr *rta, size_t len, int *argcp,
+			   char ***argvp)
+{
+	char **argv = *argvp;
+	int argc = *argcp;
+	int headroom_set = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "in") == 0) {
+			NEXT_ARG();
+			if (lwt_parse_bpf(rta, len, &argc, &argv, LWT_BPF_IN,
+					  BPF_PROG_TYPE_LWT_IN) < 0)
+				return -1;
+		} else if (strcmp(*argv, "out") == 0) {
+			NEXT_ARG();
+			if (lwt_parse_bpf(rta, len, &argc, &argv, LWT_BPF_OUT,
+					  BPF_PROG_TYPE_LWT_OUT) < 0)
+				return -1;
+		} else if (strcmp(*argv, "xmit") == 0) {
+			NEXT_ARG();
+			if (lwt_parse_bpf(rta, len, &argc, &argv, LWT_BPF_XMIT,
+					  BPF_PROG_TYPE_LWT_XMIT) < 0)
+				return -1;
+		} else if (strcmp(*argv, "headroom") == 0) {
+			unsigned int headroom;
+
+			NEXT_ARG();
+			if (get_unsigned(&headroom, *argv, 0) || headroom == 0)
+				invarg("headroom is invalid\n", *argv);
+			if (!headroom_set)
+				rta_addattr32(rta, len, LWT_BPF_XMIT_HEADROOM,
+					      headroom);
+			headroom_set = 1;
+		} else if (strcmp(*argv, "help") == 0) {
+			lwt_bpf_usage();
+		} else {
+			break;
+		}
+		NEXT_ARG_FWD();
+	}
+
+	/* argv is currently the first unparsed argument,
+	 * but the lwt_parse_encap() caller will move to the next,
+	 * so step back
+	 */
+	*argcp = argc + 1;
+	*argvp = argv - 1;
+
+	return 0;
+}
+
+int lwt_parse_encap(struct rtattr *rta, size_t len, int *argcp, char ***argvp,
+		    int encap_attr, int encap_type_attr)
+{
+	struct rtattr *nest;
+	int argc = *argcp;
+	char **argv = *argvp;
+	__u16 type;
+	int ret = 0;
+
+	NEXT_ARG();
+	type = read_encap_type(*argv);
+	if (!type)
+		invarg("\"encap type\" value is invalid\n", *argv);
+
+	NEXT_ARG();
+	if (argc <= 1) {
+		fprintf(stderr,
+			"Error: unexpected end of line after \"encap\"\n");
+		exit(-1);
+	}
+
+	nest = rta_nest(rta, len, encap_attr);
+	switch (type) {
+	case LWTUNNEL_ENCAP_MPLS:
+		ret = parse_encap_mpls(rta, len, &argc, &argv);
+		break;
+	case LWTUNNEL_ENCAP_IP:
+		ret = parse_encap_ip(rta, len, &argc, &argv);
+		break;
+	case LWTUNNEL_ENCAP_ILA:
+		ret = parse_encap_ila(rta, len, &argc, &argv);
+		break;
+	case LWTUNNEL_ENCAP_IP6:
+		ret = parse_encap_ip6(rta, len, &argc, &argv);
+		break;
+	case LWTUNNEL_ENCAP_BPF:
+		if (parse_encap_bpf(rta, len, &argc, &argv) < 0)
+			exit(-1);
+		break;
+	case LWTUNNEL_ENCAP_SEG6:
+		ret = parse_encap_seg6(rta, len, &argc, &argv);
+		break;
+	case LWTUNNEL_ENCAP_SEG6_LOCAL:
+		ret = parse_encap_seg6local(rta, len, &argc, &argv);
+		break;
+	default:
+		fprintf(stderr, "Error: unsupported encap type\n");
+		break;
+	}
+	if (ret)
+		return ret;
+
+	rta_nest_end(rta, nest);
+
+	ret = rta_addattr16(rta, len, encap_type_attr, type);
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return ret;
+}
diff --git a/ip/iprule.c b/ip/iprule.c
new file mode 100644
index 0000000..9f5d998
--- /dev/null
+++ b/ip/iprule.c
@@ -0,0 +1,1070 @@
+/*
+ * iprule.c		"ip rule".
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <linux/if.h>
+#include <linux/fib_rules.h>
+#include <errno.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+enum list_action {
+	IPRULE_LIST,
+	IPRULE_FLUSH,
+	IPRULE_SAVE,
+};
+
+extern struct rtnl_handle rth;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip rule { add | del } SELECTOR ACTION\n"
+		"       ip rule { flush | save | restore }\n"
+		"       ip rule [ list [ SELECTOR ]]\n"
+		"SELECTOR := [ not ] [ from PREFIX ] [ to PREFIX ] [ tos TOS ] [ fwmark FWMARK[/MASK] ]\n"
+		"            [ iif STRING ] [ oif STRING ] [ pref NUMBER ] [ l3mdev ]\n"
+		"            [ uidrange NUMBER-NUMBER ]\n"
+		"            [ ipproto PROTOCOL ]\n"
+		"            [ sport [ NUMBER | NUMBER-NUMBER ]\n"
+		"            [ dport [ NUMBER | NUMBER-NUMBER ] ]\n"
+		"ACTION := [ table TABLE_ID ]\n"
+		"          [ protocol PROTO ]\n"
+		"          [ nat ADDRESS ]\n"
+		"          [ realms [SRCREALM/]DSTREALM ]\n"
+		"          [ goto NUMBER ]\n"
+		"          SUPPRESSOR\n"
+		"SUPPRESSOR := [ suppress_prefixlength NUMBER ]\n"
+		"              [ suppress_ifgroup DEVGROUP ]\n"
+		"TABLE_ID := [ local | main | default | NUMBER ]\n");
+	exit(-1);
+}
+
+static struct
+{
+	int not;
+	int l3mdev;
+	int iifmask, oifmask, uidrange;
+	unsigned int tb;
+	unsigned int tos, tosmask;
+	unsigned int pref, prefmask;
+	unsigned int fwmark, fwmask;
+	uint64_t tun_id;
+	char iif[IFNAMSIZ];
+	char oif[IFNAMSIZ];
+	struct fib_rule_uid_range range;
+	inet_prefix src;
+	inet_prefix dst;
+	int protocol;
+	int protocolmask;
+	struct fib_rule_port_range sport;
+	struct fib_rule_port_range dport;
+	__u8 ipproto;
+} filter;
+
+static inline int frh_get_table(struct fib_rule_hdr *frh, struct rtattr **tb)
+{
+	__u32 table = frh->table;
+	if (tb[RTA_TABLE])
+		table = rta_getattr_u32(tb[RTA_TABLE]);
+	return table;
+}
+
+static bool filter_nlmsg(struct nlmsghdr *n, struct rtattr **tb, int host_len)
+{
+	struct fib_rule_hdr *frh = NLMSG_DATA(n);
+	__u32 table;
+
+	if (preferred_family != AF_UNSPEC && frh->family != preferred_family)
+		return false;
+
+	if (filter.prefmask &&
+	    filter.pref ^ (tb[FRA_PRIORITY] ? rta_getattr_u32(tb[FRA_PRIORITY]) : 0))
+		return false;
+	if (filter.not && !(frh->flags & FIB_RULE_INVERT))
+		return false;
+
+	if (filter.src.family) {
+		inet_prefix *f_src = &filter.src;
+
+		if (f_src->family != frh->family ||
+		    f_src->bitlen > frh->src_len)
+			return false;
+
+		if (inet_addr_match_rta(f_src, tb[FRA_SRC]))
+			return false;
+	}
+
+	if (filter.dst.family) {
+		inet_prefix *f_dst = &filter.dst;
+
+		if (f_dst->family != frh->family ||
+		    f_dst->bitlen > frh->dst_len)
+			return false;
+
+		if (inet_addr_match_rta(f_dst, tb[FRA_DST]))
+			return false;
+	}
+
+	if (filter.tosmask && filter.tos ^ frh->tos)
+		return false;
+
+	if (filter.fwmark) {
+		__u32 mark = 0;
+
+		if (tb[FRA_FWMARK])
+			mark = rta_getattr_u32(tb[FRA_FWMARK]);
+		if (filter.fwmark ^ mark)
+			return false;
+	}
+	if (filter.fwmask) {
+		__u32 mask = 0;
+
+		if (tb[FRA_FWMASK])
+			mask = rta_getattr_u32(tb[FRA_FWMASK]);
+		if (filter.fwmask ^ mask)
+			return false;
+	}
+
+	if (filter.iifmask) {
+		if (tb[FRA_IFNAME]) {
+			if (strcmp(filter.iif, rta_getattr_str(tb[FRA_IFNAME])) != 0)
+				return false;
+		} else {
+			return false;
+		}
+	}
+
+	if (filter.oifmask) {
+		if (tb[FRA_OIFNAME]) {
+			if (strcmp(filter.oif, rta_getattr_str(tb[FRA_OIFNAME])) != 0)
+				return false;
+		} else {
+			return false;
+		}
+	}
+
+	if (filter.l3mdev && !(tb[FRA_L3MDEV] && rta_getattr_u8(tb[FRA_L3MDEV])))
+		return false;
+
+	if (filter.uidrange) {
+		struct fib_rule_uid_range *r = RTA_DATA(tb[FRA_UID_RANGE]);
+
+		if (!tb[FRA_UID_RANGE] ||
+		    r->start != filter.range.start ||
+		    r->end != filter.range.end)
+			return false;
+	}
+
+	if (filter.ipproto) {
+		__u8 ipproto = 0;
+
+		if (tb[FRA_IP_PROTO])
+			ipproto = rta_getattr_u8(tb[FRA_IP_PROTO]);
+		if (filter.ipproto != ipproto)
+			return false;
+	}
+
+	if (filter.sport.start) {
+		const struct fib_rule_port_range *r;
+
+		if (!tb[FRA_SPORT_RANGE])
+			return false;
+
+		r = RTA_DATA(tb[FRA_SPORT_RANGE]);
+		if (r->start != filter.sport.start ||
+		    r->end != filter.sport.end)
+			return false;
+	}
+
+	if (filter.dport.start) {
+		const struct fib_rule_port_range *r;
+
+		if (!tb[FRA_DPORT_RANGE])
+			return false;
+
+		r = RTA_DATA(tb[FRA_DPORT_RANGE]);
+		if (r->start != filter.dport.start ||
+		    r->end != filter.dport.end)
+			return false;
+	}
+
+	if (filter.tun_id) {
+		__u64 tun_id = 0;
+
+		if (tb[FRA_TUN_ID]) {
+			tun_id = ntohll(rta_getattr_u64(tb[FRA_TUN_ID]));
+			if (filter.tun_id != tun_id)
+				return false;
+		} else {
+			return false;
+		}
+	}
+
+	table = frh_get_table(frh, tb);
+	if (filter.tb > 0 && filter.tb ^ table)
+		return false;
+
+	return true;
+}
+
+int print_rule(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = arg;
+	struct fib_rule_hdr *frh = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	int host_len = -1;
+	__u32 table, prio = 0;
+	struct rtattr *tb[FRA_MAX+1];
+	SPRINT_BUF(b1);
+
+	if (n->nlmsg_type != RTM_NEWRULE && n->nlmsg_type != RTM_DELRULE)
+		return 0;
+
+	len -= NLMSG_LENGTH(sizeof(*frh));
+	if (len < 0)
+		return -1;
+
+	parse_rtattr(tb, FRA_MAX, RTM_RTA(frh), len);
+
+	host_len = af_bit_len(frh->family);
+
+	if (!filter_nlmsg(n, tb, host_len))
+		return 0;
+
+	open_json_object(NULL);
+	if (n->nlmsg_type == RTM_DELRULE)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	if (tb[FRA_PRIORITY])
+		prio = rta_getattr_u32(tb[FRA_PRIORITY]);
+
+	print_uint(PRINT_ANY, "priority", "%u:\t", prio);
+
+	if (frh->flags & FIB_RULE_INVERT)
+		print_null(PRINT_ANY, "not", "not ", NULL);
+
+	if (tb[FRA_SRC]) {
+		const char *src = rt_addr_n2a_rta(frh->family, tb[FRA_SRC]);
+
+		print_string(PRINT_FP, NULL, "from ", NULL);
+		print_color_string(PRINT_ANY, ifa_family_color(frh->family),
+				   "src", "%s", src);
+		if (frh->src_len != host_len)
+			print_uint(PRINT_ANY, "srclen", "/%u", frh->src_len);
+	} else if (frh->src_len) {
+		print_string(PRINT_ANY, "src", "from %s", "0");
+		print_uint(PRINT_ANY, "srclen", "/%u", frh->src_len);
+	} else {
+		print_string(PRINT_ANY, "src", "from %s", "all");
+	}
+
+	if (tb[FRA_DST]) {
+		const char *dst = rt_addr_n2a_rta(frh->family, tb[FRA_DST]);
+
+		print_string(PRINT_FP, NULL, " to ", NULL);
+		print_color_string(PRINT_ANY, ifa_family_color(frh->family),
+				   "dst", "%s", dst);
+		if (frh->dst_len != host_len)
+			print_uint(PRINT_ANY, "dstlen", "/%u", frh->dst_len);
+	} else if (frh->dst_len) {
+		print_string(PRINT_ANY, "dst", " to %s", "0");
+		print_uint(PRINT_ANY, "dstlen", "/%u", frh->dst_len);
+	}
+
+	if (frh->tos) {
+		print_string(PRINT_ANY, "tos",
+			     " tos %s",
+			     rtnl_dsfield_n2a(frh->tos, b1, sizeof(b1)));
+	}
+
+	if (tb[FRA_FWMARK] || tb[FRA_FWMASK]) {
+		__u32 mark = 0, mask = 0;
+
+		if (tb[FRA_FWMARK])
+			mark = rta_getattr_u32(tb[FRA_FWMARK]);
+
+		if (tb[FRA_FWMASK] &&
+		    (mask = rta_getattr_u32(tb[FRA_FWMASK])) != 0xFFFFFFFF) {
+			print_0xhex(PRINT_ANY, "fwmark", " fwmark %#llx", mark);
+			print_0xhex(PRINT_ANY, "fwmask", "/%#llx", mask);
+		} else {
+			print_0xhex(PRINT_ANY, "fwmark", " fwmark %#llx", mark);
+		}
+	}
+
+	if (tb[FRA_IFNAME]) {
+		if (!is_json_context())
+			fprintf(fp, " iif ");
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "iif", "%s",
+				   rta_getattr_str(tb[FRA_IFNAME]));
+
+		if (frh->flags & FIB_RULE_IIF_DETACHED)
+			print_null(PRINT_ANY, "iif_detached", " [detached]",
+				   NULL);
+	}
+
+	if (tb[FRA_OIFNAME]) {
+		if (!is_json_context())
+			fprintf(fp, " oif ");
+
+		print_color_string(PRINT_ANY, COLOR_IFNAME, "oif", "%s",
+				   rta_getattr_str(tb[FRA_OIFNAME]));
+
+		if (frh->flags & FIB_RULE_OIF_DETACHED)
+			print_null(PRINT_ANY, "oif_detached", " [detached]",
+				   NULL);
+	}
+
+	if (tb[FRA_L3MDEV]) {
+		__u8 mdev = rta_getattr_u8(tb[FRA_L3MDEV]);
+
+		if (mdev)
+			print_null(PRINT_ANY, "l3mdev",
+				   " lookup [l3mdev-table]", NULL);
+	}
+
+	if (tb[FRA_UID_RANGE]) {
+		struct fib_rule_uid_range *r = RTA_DATA(tb[FRA_UID_RANGE]);
+
+		print_uint(PRINT_ANY, "uid_start", " uidrange %u", r->start);
+		print_uint(PRINT_ANY, "uid_end", "-%u", r->end);
+	}
+
+	if (tb[FRA_IP_PROTO]) {
+		SPRINT_BUF(pbuf);
+		print_string(PRINT_ANY, "ipproto", " ipproto %s",
+			     inet_proto_n2a(rta_getattr_u8(tb[FRA_IP_PROTO]),
+					    pbuf, sizeof(pbuf)));
+	}
+
+	if (tb[FRA_SPORT_RANGE]) {
+		struct fib_rule_port_range *r = RTA_DATA(tb[FRA_SPORT_RANGE]);
+
+		if (r->start == r->end) {
+			print_uint(PRINT_ANY, "sport", " sport %u", r->start);
+		} else {
+			print_uint(PRINT_ANY, "sport_start", " sport %u",
+				   r->start);
+			print_uint(PRINT_ANY, "sport_end", "-%u", r->end);
+		}
+	}
+
+	if (tb[FRA_DPORT_RANGE]) {
+		struct fib_rule_port_range *r = RTA_DATA(tb[FRA_DPORT_RANGE]);
+
+		if (r->start == r->end) {
+			print_uint(PRINT_ANY, "dport", " dport %u", r->start);
+		} else {
+			print_uint(PRINT_ANY, "dport_start", " dport %u",
+				   r->start);
+			print_uint(PRINT_ANY, "dport_end", "-%u", r->end);
+		}
+	}
+
+	if (tb[FRA_TUN_ID]) {
+		__u64 tun_id = ntohll(rta_getattr_u64(tb[FRA_TUN_ID]));
+
+		print_u64(PRINT_ANY, "tun_id", " tun_id %llu", tun_id);
+	}
+
+	table = frh_get_table(frh, tb);
+	if (table) {
+		print_string(PRINT_ANY, "table",
+			     " lookup %s",
+			     rtnl_rttable_n2a(table, b1, sizeof(b1)));
+
+		if (tb[FRA_SUPPRESS_PREFIXLEN]) {
+			int pl = rta_getattr_u32(tb[FRA_SUPPRESS_PREFIXLEN]);
+
+			if (pl != -1)
+				print_int(PRINT_ANY, "suppress_prefixlen",
+					  " suppress_prefixlength %d", pl);
+		}
+
+		if (tb[FRA_SUPPRESS_IFGROUP]) {
+			int group = rta_getattr_u32(tb[FRA_SUPPRESS_IFGROUP]);
+
+			if (group != -1) {
+				const char *grname
+					= rtnl_group_n2a(group, b1, sizeof(b1));
+
+				print_string(PRINT_ANY, "suppress_ifgroup",
+					     " suppress_ifgroup %s", grname);
+			}
+		}
+	}
+
+	if (tb[FRA_FLOW]) {
+		__u32 to = rta_getattr_u32(tb[FRA_FLOW]);
+		__u32 from = to>>16;
+
+		to &= 0xFFFF;
+		if (from)
+			print_string(PRINT_ANY,
+				     "flow_from", " realms %s/",
+				     rtnl_rtrealm_n2a(from, b1, sizeof(b1)));
+		else
+			print_string(PRINT_FP, NULL, " realms ", NULL);
+
+		print_string(PRINT_ANY, "flow_to", "%s",
+			     rtnl_rtrealm_n2a(to, b1, sizeof(b1)));
+	}
+
+	if (frh->action == RTN_NAT) {
+		if (tb[RTA_GATEWAY]) {
+			const char *gateway;
+
+			gateway = format_host_rta(frh->family, tb[RTA_GATEWAY]);
+
+			print_string(PRINT_ANY, "nat_gateway",
+				     " map-to %s", gateway);
+		} else {
+			print_null(PRINT_ANY, "masquerade", " masquerade", NULL);
+		}
+	} else if (frh->action == FR_ACT_GOTO) {
+		if (tb[FRA_GOTO])
+			print_uint(PRINT_ANY, "goto", " goto %u",
+				   rta_getattr_u32(tb[FRA_GOTO]));
+		else
+			print_string(PRINT_ANY, "goto", " goto %s", "none");
+
+		if (frh->flags & FIB_RULE_UNRESOLVED)
+			print_null(PRINT_ANY, "unresolved",
+				   " [unresolved]", NULL);
+	} else if (frh->action == FR_ACT_NOP) {
+		print_null(PRINT_ANY, "nop", " nop", NULL);
+	} else if (frh->action != FR_ACT_TO_TBL) {
+		print_string(PRINT_ANY, "action", " %s",
+			     rtnl_rtntype_n2a(frh->action, b1, sizeof(b1)));
+	}
+
+	if (tb[FRA_PROTOCOL]) {
+		__u8 protocol = rta_getattr_u8(tb[FRA_PROTOCOL]);
+
+		if ((protocol && protocol != RTPROT_KERNEL) || show_details > 0) {
+			print_string(PRINT_ANY, "protocol", " proto %s",
+				     rtnl_rtprot_n2a(protocol, b1, sizeof(b1)));
+		}
+	}
+	print_string(PRINT_FP, NULL, "\n", "");
+	close_json_object();
+	fflush(fp);
+	return 0;
+}
+
+static __u32 rule_dump_magic = 0x71706986;
+
+static int save_rule_prep(void)
+{
+	int ret;
+
+	if (isatty(STDOUT_FILENO)) {
+		fprintf(stderr, "Not sending a binary stream to stdout\n");
+		return -1;
+	}
+
+	ret = write(STDOUT_FILENO, &rule_dump_magic, sizeof(rule_dump_magic));
+	if (ret != sizeof(rule_dump_magic)) {
+		fprintf(stderr, "Can't write magic to dump file\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int save_rule(struct nlmsghdr *n, void *arg)
+{
+	int ret;
+
+	ret = write(STDOUT_FILENO, n, n->nlmsg_len);
+	if ((ret > 0) && (ret != n->nlmsg_len)) {
+		fprintf(stderr, "Short write while saving nlmsg\n");
+		ret = -EIO;
+	}
+
+	return ret == n->nlmsg_len ? 0 : ret;
+}
+
+static int flush_rule(struct nlmsghdr *n, void *arg)
+{
+	struct rtnl_handle rth2;
+	struct fib_rule_hdr *frh = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[FRA_MAX+1];
+	int host_len = -1;
+
+	len -= NLMSG_LENGTH(sizeof(*frh));
+	if (len < 0)
+		return -1;
+
+	parse_rtattr(tb, FRA_MAX, RTM_RTA(frh), len);
+
+	host_len = af_bit_len(frh->family);
+	if (!filter_nlmsg(n, tb, host_len))
+		return 0;
+
+	if (tb[FRA_PROTOCOL]) {
+		__u8 protocol = rta_getattr_u8(tb[FRA_PROTOCOL]);
+
+		if ((filter.protocol ^ protocol) & filter.protocolmask)
+			return 0;
+	}
+
+	if (tb[FRA_PRIORITY]) {
+		n->nlmsg_type = RTM_DELRULE;
+		n->nlmsg_flags = NLM_F_REQUEST;
+
+		if (rtnl_open(&rth2, 0) < 0)
+			return -1;
+
+		if (rtnl_talk(&rth2, n, NULL) < 0)
+			return -2;
+
+		rtnl_close(&rth2);
+	}
+
+	return 0;
+}
+
+static int iprule_list_flush_or_save(int argc, char **argv, int action)
+{
+	rtnl_filter_t filter_fn;
+	int af = preferred_family;
+
+	if (af == AF_UNSPEC)
+		af = AF_INET;
+
+	if (action == IPRULE_SAVE && argc > 0) {
+		fprintf(stderr, "\"ip rule save\" does not take any arguments.\n");
+		return -1;
+	}
+
+	switch (action) {
+	case IPRULE_SAVE:
+		if (save_rule_prep())
+			return -1;
+		filter_fn = save_rule;
+		break;
+	case IPRULE_FLUSH:
+		filter_fn = flush_rule;
+		break;
+	default:
+		filter_fn = print_rule;
+	}
+
+	memset(&filter, 0, sizeof(filter));
+
+	while (argc > 0) {
+		if (matches(*argv, "preference") == 0 ||
+		    matches(*argv, "order") == 0 ||
+		    matches(*argv, "priority") == 0) {
+			__u32 pref;
+
+			NEXT_ARG();
+			if (get_u32(&pref, *argv, 0))
+				invarg("preference value is invalid\n", *argv);
+			filter.pref = pref;
+			filter.prefmask = 1;
+		} else if (strcmp(*argv, "not") == 0) {
+			filter.not = 1;
+		} else if (strcmp(*argv, "tos") == 0) {
+			__u32 tos;
+
+			NEXT_ARG();
+			if (rtnl_dsfield_a2n(&tos, *argv))
+				invarg("TOS value is invalid\n", *argv);
+			filter.tos = tos;
+			filter.tosmask = 1;
+		} else if (strcmp(*argv, "fwmark") == 0) {
+			char *slash;
+			__u32 fwmark, fwmask;
+
+			NEXT_ARG();
+			slash = strchr(*argv, '/');
+			if (slash != NULL)
+				*slash = '\0';
+			if (get_u32(&fwmark, *argv, 0))
+				invarg("fwmark value is invalid\n", *argv);
+			filter.fwmark = fwmark;
+			if (slash) {
+				if (get_u32(&fwmask, slash+1, 0))
+					invarg("fwmask value is invalid\n",
+					       slash+1);
+				filter.fwmask = fwmask;
+			}
+		} else if (strcmp(*argv, "dev") == 0 ||
+			   strcmp(*argv, "iif") == 0) {
+			NEXT_ARG();
+			if (get_ifname(filter.iif, *argv))
+				invarg("\"iif\"/\"dev\" not a valid ifname", *argv);
+			filter.iifmask = 1;
+		} else if (strcmp(*argv, "oif") == 0) {
+			NEXT_ARG();
+			if (get_ifname(filter.oif, *argv))
+				invarg("\"oif\" not a valid ifname", *argv);
+			filter.oifmask = 1;
+		} else if (strcmp(*argv, "l3mdev") == 0) {
+			filter.l3mdev = 1;
+		} else if (strcmp(*argv, "uidrange") == 0) {
+			NEXT_ARG();
+			filter.uidrange = 1;
+			if (sscanf(*argv, "%u-%u",
+				   &filter.range.start,
+				   &filter.range.end) != 2)
+				invarg("invalid UID range\n", *argv);
+
+		} else if (matches(*argv, "tun_id") == 0) {
+			__u64 tun_id;
+
+			NEXT_ARG();
+			if (get_u64(&tun_id, *argv, 0))
+				invarg("\"tun_id\" value is invalid\n", *argv);
+			filter.tun_id = tun_id;
+		} else if (matches(*argv, "lookup") == 0 ||
+			   matches(*argv, "table") == 0) {
+			__u32 tid;
+
+			NEXT_ARG();
+			if (rtnl_rttable_a2n(&tid, *argv))
+				invarg("table id value is invalid\n", *argv);
+			filter.tb = tid;
+		} else if (matches(*argv, "from") == 0 ||
+			   matches(*argv, "src") == 0) {
+			NEXT_ARG();
+			if (get_prefix(&filter.src, *argv, af))
+				invarg("from value is invalid\n", *argv);
+		} else if (matches(*argv, "protocol") == 0) {
+			__u32 prot;
+			NEXT_ARG();
+			filter.protocolmask = -1;
+			if (rtnl_rtprot_a2n(&prot, *argv)) {
+				if (strcmp(*argv, "all") != 0)
+					invarg("invalid \"protocol\"\n", *argv);
+				prot = 0;
+				filter.protocolmask = 0;
+			}
+			filter.protocol = prot;
+		} else if (strcmp(*argv, "ipproto") == 0) {
+			int ipproto;
+
+			NEXT_ARG();
+			ipproto = inet_proto_a2n(*argv);
+			if (ipproto < 0)
+				invarg("Invalid \"ipproto\" value\n", *argv);
+			filter.ipproto = ipproto;
+		} else if (strcmp(*argv, "sport") == 0) {
+			struct fib_rule_port_range r;
+			int ret;
+
+			NEXT_ARG();
+			ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
+			if (ret == 1)
+				r.end = r.start;
+			else if (ret != 2)
+				invarg("invalid port range\n", *argv);
+			filter.sport = r;
+		} else if (strcmp(*argv, "dport") == 0) {
+			struct fib_rule_port_range r;
+			int ret;
+
+			NEXT_ARG();
+			ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
+			if (ret == 1)
+				r.end = r.start;
+			else if (ret != 2)
+				invarg("invalid dport range\n", *argv);
+			filter.dport = r;
+		} else{
+			if (matches(*argv, "dst") == 0 ||
+			    matches(*argv, "to") == 0) {
+				NEXT_ARG();
+			}
+			if (get_prefix(&filter.dst, *argv, af))
+				invarg("to value is invalid\n", *argv);
+		}
+		argc--; argv++;
+	}
+
+	if (rtnl_ruledump_req(&rth, af) < 0) {
+		perror("Cannot send dump request");
+		return 1;
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&rth, filter_fn, stdout) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+	delete_json_obj();
+
+	return 0;
+}
+
+static int rule_dump_check_magic(void)
+{
+	int ret;
+	__u32 magic = 0;
+
+	if (isatty(STDIN_FILENO)) {
+		fprintf(stderr, "Can't restore rule dump from a terminal\n");
+		return -1;
+	}
+
+	ret = fread(&magic, sizeof(magic), 1, stdin);
+	if (magic != rule_dump_magic) {
+		fprintf(stderr, "Magic mismatch (%d elems, %x magic)\n",
+			ret, magic);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int restore_handler(struct rtnl_ctrl_data *ctrl,
+			   struct nlmsghdr *n, void *arg)
+{
+	int ret;
+
+	n->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+
+	ll_init_map(&rth);
+
+	ret = rtnl_talk(&rth, n, NULL);
+	if ((ret < 0) && (errno == EEXIST))
+		ret = 0;
+
+	return ret;
+}
+
+
+static int iprule_restore(void)
+{
+	if (rule_dump_check_magic())
+		exit(-1);
+
+	exit(rtnl_from_file(stdin, &restore_handler, NULL));
+}
+
+static int iprule_modify(int cmd, int argc, char **argv)
+{
+	int l3mdev_rule = 0;
+	int table_ok = 0;
+	__u32 tid = 0;
+	struct {
+		struct nlmsghdr	n;
+		struct fib_rule_hdr	frh;
+		char			buf[1024];
+	} req = {
+		.n.nlmsg_type = cmd,
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct fib_rule_hdr)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.frh.family = preferred_family,
+		.frh.action = FR_ACT_UNSPEC,
+	};
+
+	if (cmd == RTM_NEWRULE) {
+		if (argc == 0) {
+			fprintf(stderr,
+				"\"ip rule add\" requires arguments.\n");
+			return -1;
+		}
+		req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_EXCL;
+		req.frh.action = FR_ACT_TO_TBL;
+	}
+
+	if (cmd == RTM_DELRULE && argc == 0) {
+		fprintf(stderr, "\"ip rule del\" requires arguments.\n");
+		return -1;
+	}
+
+	while (argc > 0) {
+		if (strcmp(*argv, "not") == 0) {
+			req.frh.flags |= FIB_RULE_INVERT;
+		} else if (strcmp(*argv, "from") == 0) {
+			inet_prefix dst;
+
+			NEXT_ARG();
+			get_prefix(&dst, *argv, req.frh.family);
+			req.frh.src_len = dst.bitlen;
+			addattr_l(&req.n, sizeof(req), FRA_SRC,
+				  &dst.data, dst.bytelen);
+		} else if (strcmp(*argv, "to") == 0) {
+			inet_prefix dst;
+
+			NEXT_ARG();
+			get_prefix(&dst, *argv, req.frh.family);
+			req.frh.dst_len = dst.bitlen;
+			addattr_l(&req.n, sizeof(req), FRA_DST,
+				  &dst.data, dst.bytelen);
+		} else if (matches(*argv, "preference") == 0 ||
+			   matches(*argv, "order") == 0 ||
+			   matches(*argv, "priority") == 0) {
+			__u32 pref;
+
+			NEXT_ARG();
+			if (get_u32(&pref, *argv, 0))
+				invarg("preference value is invalid\n", *argv);
+			addattr32(&req.n, sizeof(req), FRA_PRIORITY, pref);
+		} else if (strcmp(*argv, "tos") == 0 ||
+			   matches(*argv, "dsfield") == 0) {
+			__u32 tos;
+
+			NEXT_ARG();
+			if (rtnl_dsfield_a2n(&tos, *argv))
+				invarg("TOS value is invalid\n", *argv);
+			req.frh.tos = tos;
+		} else if (strcmp(*argv, "fwmark") == 0) {
+			char *slash;
+			__u32 fwmark, fwmask;
+
+			NEXT_ARG();
+
+			slash = strchr(*argv, '/');
+			if (slash != NULL)
+				*slash = '\0';
+			if (get_u32(&fwmark, *argv, 0))
+				invarg("fwmark value is invalid\n", *argv);
+			addattr32(&req.n, sizeof(req), FRA_FWMARK, fwmark);
+			if (slash) {
+				if (get_u32(&fwmask, slash+1, 0))
+					invarg("fwmask value is invalid\n",
+					       slash+1);
+				addattr32(&req.n, sizeof(req),
+					  FRA_FWMASK, fwmask);
+			}
+		} else if (matches(*argv, "realms") == 0) {
+			__u32 realm;
+
+			NEXT_ARG();
+			if (get_rt_realms_or_raw(&realm, *argv))
+				invarg("invalid realms\n", *argv);
+			addattr32(&req.n, sizeof(req), FRA_FLOW, realm);
+		} else if (matches(*argv, "protocol") == 0) {
+			__u32 proto;
+
+			NEXT_ARG();
+			if (rtnl_rtprot_a2n(&proto, *argv))
+				invarg("\"protocol\" value is invalid\n", *argv);
+			addattr8(&req.n, sizeof(req), FRA_PROTOCOL, proto);
+		} else if (matches(*argv, "tun_id") == 0) {
+			__u64 tun_id;
+
+			NEXT_ARG();
+			if (get_be64(&tun_id, *argv, 0))
+				invarg("\"tun_id\" value is invalid\n", *argv);
+			addattr64(&req.n, sizeof(req), FRA_TUN_ID, tun_id);
+		} else if (matches(*argv, "table") == 0 ||
+			   strcmp(*argv, "lookup") == 0) {
+			NEXT_ARG();
+			if (rtnl_rttable_a2n(&tid, *argv))
+				invarg("invalid table ID\n", *argv);
+			if (tid < 256)
+				req.frh.table = tid;
+			else {
+				req.frh.table = RT_TABLE_UNSPEC;
+				addattr32(&req.n, sizeof(req), FRA_TABLE, tid);
+			}
+			table_ok = 1;
+		} else if (matches(*argv, "suppress_prefixlength") == 0 ||
+			   strcmp(*argv, "sup_pl") == 0) {
+			int pl;
+
+			NEXT_ARG();
+			if (get_s32(&pl, *argv, 0) || pl < 0)
+				invarg("suppress_prefixlength value is invalid\n",
+				       *argv);
+			addattr32(&req.n, sizeof(req),
+				  FRA_SUPPRESS_PREFIXLEN, pl);
+		} else if (matches(*argv, "suppress_ifgroup") == 0 ||
+			   strcmp(*argv, "sup_group") == 0) {
+			NEXT_ARG();
+			int group;
+
+			if (rtnl_group_a2n(&group, *argv))
+				invarg("Invalid \"suppress_ifgroup\" value\n",
+				       *argv);
+			addattr32(&req.n, sizeof(req),
+				  FRA_SUPPRESS_IFGROUP, group);
+		} else if (strcmp(*argv, "dev") == 0 ||
+			   strcmp(*argv, "iif") == 0) {
+			NEXT_ARG();
+			if (check_ifname(*argv))
+				invarg("\"iif\"/\"dev\" not a valid ifname", *argv);
+			addattr_l(&req.n, sizeof(req), FRA_IFNAME,
+				  *argv, strlen(*argv)+1);
+		} else if (strcmp(*argv, "oif") == 0) {
+			NEXT_ARG();
+			if (check_ifname(*argv))
+				invarg("\"oif\" not a valid ifname", *argv);
+			addattr_l(&req.n, sizeof(req), FRA_OIFNAME,
+				  *argv, strlen(*argv)+1);
+		} else if (strcmp(*argv, "l3mdev") == 0) {
+			addattr8(&req.n, sizeof(req), FRA_L3MDEV, 1);
+			table_ok = 1;
+			l3mdev_rule = 1;
+		} else if (strcmp(*argv, "uidrange") == 0) {
+			struct fib_rule_uid_range r;
+
+			NEXT_ARG();
+			if (sscanf(*argv, "%u-%u", &r.start, &r.end) != 2)
+				invarg("invalid UID range\n", *argv);
+			addattr_l(&req.n, sizeof(req), FRA_UID_RANGE, &r,
+				  sizeof(r));
+		} else if (strcmp(*argv, "nat") == 0 ||
+			   matches(*argv, "map-to") == 0) {
+			NEXT_ARG();
+			fprintf(stderr, "Warning: route NAT is deprecated\n");
+			addattr32(&req.n, sizeof(req), RTA_GATEWAY,
+				  get_addr32(*argv));
+			req.frh.action = RTN_NAT;
+		} else if (strcmp(*argv, "ipproto") == 0) {
+			int ipproto;
+
+			NEXT_ARG();
+			ipproto = inet_proto_a2n(*argv);
+			if (ipproto < 0)
+				invarg("Invalid \"ipproto\" value\n",
+				       *argv);
+			addattr8(&req.n, sizeof(req), FRA_IP_PROTO, ipproto);
+		} else if (strcmp(*argv, "sport") == 0) {
+			struct fib_rule_port_range r;
+			int ret = 0;
+
+			NEXT_ARG();
+			ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
+			if (ret == 1)
+				r.end = r.start;
+			else if (ret != 2)
+				invarg("invalid port range\n", *argv);
+			addattr_l(&req.n, sizeof(req), FRA_SPORT_RANGE, &r,
+				  sizeof(r));
+		} else if (strcmp(*argv, "dport") == 0) {
+			struct fib_rule_port_range r;
+			int ret = 0;
+
+			NEXT_ARG();
+			ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
+			if (ret == 1)
+				r.end = r.start;
+			else if (ret != 2)
+				invarg("invalid dport range\n", *argv);
+			addattr_l(&req.n, sizeof(req), FRA_DPORT_RANGE, &r,
+				  sizeof(r));
+		} else {
+			int type;
+
+			if (strcmp(*argv, "type") == 0)
+				NEXT_ARG();
+
+			if (matches(*argv, "help") == 0)
+				usage();
+			else if (matches(*argv, "goto") == 0) {
+				__u32 target;
+
+				type = FR_ACT_GOTO;
+				NEXT_ARG();
+				if (get_u32(&target, *argv, 0))
+					invarg("invalid target\n", *argv);
+				addattr32(&req.n, sizeof(req),
+					  FRA_GOTO, target);
+			} else if (matches(*argv, "nop") == 0)
+				type = FR_ACT_NOP;
+			else if (rtnl_rtntype_a2n(&type, *argv))
+				invarg("Failed to parse rule type", *argv);
+			req.frh.action = type;
+			table_ok = 1;
+		}
+		argc--;
+		argv++;
+	}
+
+	if (l3mdev_rule && tid != 0) {
+		fprintf(stderr,
+			"table can not be specified for l3mdev rules\n");
+		return -EINVAL;
+	}
+
+	if (req.frh.family == AF_UNSPEC)
+		req.frh.family = AF_INET;
+
+	if (!table_ok && cmd == RTM_NEWRULE)
+		req.frh.table = RT_TABLE_MAIN;
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+int do_iprule(int argc, char **argv)
+{
+	if (argc < 1) {
+		return iprule_list_flush_or_save(0, NULL, IPRULE_LIST);
+	} else if (matches(argv[0], "list") == 0 ||
+		   matches(argv[0], "lst") == 0 ||
+		   matches(argv[0], "show") == 0) {
+		return iprule_list_flush_or_save(argc-1, argv+1, IPRULE_LIST);
+	} else if (matches(argv[0], "save") == 0) {
+		return iprule_list_flush_or_save(argc-1, argv+1, IPRULE_SAVE);
+	} else if (matches(argv[0], "restore") == 0) {
+		return iprule_restore();
+	} else if (matches(argv[0], "add") == 0) {
+		return iprule_modify(RTM_NEWRULE, argc-1, argv+1);
+	} else if (matches(argv[0], "delete") == 0) {
+		return iprule_modify(RTM_DELRULE, argc-1, argv+1);
+	} else if (matches(argv[0], "flush") == 0) {
+		return iprule_list_flush_or_save(argc-1, argv+1, IPRULE_FLUSH);
+	} else if (matches(argv[0], "help") == 0)
+		usage();
+
+	fprintf(stderr,
+		"Command \"%s\" is unknown, try \"ip rule help\".\n", *argv);
+	exit(-1);
+}
+
+int do_multirule(int argc, char **argv)
+{
+	switch (preferred_family) {
+	case AF_UNSPEC:
+	case AF_INET:
+		preferred_family = RTNL_FAMILY_IPMR;
+		break;
+	case AF_INET6:
+		preferred_family = RTNL_FAMILY_IP6MR;
+		break;
+	case RTNL_FAMILY_IPMR:
+	case RTNL_FAMILY_IP6MR:
+		break;
+	default:
+		fprintf(stderr,
+			"Multicast rules are only supported for IPv4/IPv6, was: %i\n",
+			preferred_family);
+		exit(-1);
+	}
+
+	return do_iprule(argc, argv);
+}
diff --git a/ip/ipseg6.c b/ip/ipseg6.c
new file mode 100644
index 0000000..56a7699
--- /dev/null
+++ b/ip/ipseg6.c
@@ -0,0 +1,253 @@
+/*
+ * seg6.c "ip sr/seg6"
+ *
+ *	  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: David Lebrun <david.lebrun@uclouvain.be>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+
+#include <linux/genetlink.h>
+#include <linux/seg6_genl.h>
+#include <linux/seg6_hmac.h>
+
+#include "utils.h"
+#include "ip_common.h"
+#include "libgenl.h"
+#include "json_print.h"
+
+#define HMAC_KEY_PROMPT "Enter secret for HMAC key ID (blank to delete): "
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip sr { COMMAND | help }\n"
+		"	   ip sr hmac show\n"
+		"	   ip sr hmac set KEYID ALGO\n"
+		"	   ip sr tunsrc show\n"
+		"	   ip sr tunsrc set ADDRESS\n"
+		"where  ALGO := { sha1 | sha256 }\n");
+	exit(-1);
+}
+
+static struct rtnl_handle grth = { .fd = -1 };
+static int genl_family = -1;
+
+#define SEG6_REQUEST(_req, _bufsiz, _cmd, _flags) \
+	GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
+				SEG6_GENL_VERSION, _cmd, _flags)
+
+static struct {
+	unsigned int cmd;
+	inet_prefix addr;
+	__u32 keyid;
+	const char *pass;
+	__u8 alg_id;
+} opts;
+
+static void print_dumphmac(struct rtattr *attrs[])
+{
+	char secret[64];
+	char *algstr;
+	__u8 slen = rta_getattr_u8(attrs[SEG6_ATTR_SECRETLEN]);
+	__u8 alg_id = rta_getattr_u8(attrs[SEG6_ATTR_ALGID]);
+
+	memset(secret, 0, 64);
+
+	if (slen > 63) {
+		fprintf(stderr, "HMAC secret length %d > 63, truncated\n", slen);
+		slen = 63;
+	}
+
+	memcpy(secret, RTA_DATA(attrs[SEG6_ATTR_SECRET]), slen);
+
+	switch (alg_id) {
+	case SEG6_HMAC_ALGO_SHA1:
+		algstr = "sha1";
+		break;
+	case SEG6_HMAC_ALGO_SHA256:
+		algstr = "sha256";
+		break;
+	default:
+		algstr = "<unknown>";
+	}
+
+	print_uint(PRINT_ANY, "hmac", "hmac %u ",
+		   rta_getattr_u32(attrs[SEG6_ATTR_HMACKEYID]));
+	print_string(PRINT_ANY, "algo", "algo %s ", algstr);
+	print_string(PRINT_ANY, "secret", "secret \"%s\"\n", secret);
+}
+
+static void print_tunsrc(struct rtattr *attrs[])
+{
+	const char *dst
+		= rt_addr_n2a(AF_INET6, 16,
+			      RTA_DATA(attrs[SEG6_ATTR_DST]));
+
+	print_string(PRINT_ANY, "tunsrc",
+		     "tunsrc addr %s\n", dst);
+}
+
+static int process_msg(struct nlmsghdr *n, void *arg)
+{
+	struct rtattr *attrs[SEG6_ATTR_MAX + 1];
+	struct genlmsghdr *ghdr;
+	int len = n->nlmsg_len;
+
+	if (n->nlmsg_type != genl_family)
+		return -1;
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+	if (len < 0)
+		return -1;
+
+	ghdr = NLMSG_DATA(n);
+
+	parse_rtattr(attrs, SEG6_ATTR_MAX, (void *)ghdr + GENL_HDRLEN, len);
+
+	open_json_object(NULL);
+	switch (ghdr->cmd) {
+	case SEG6_CMD_DUMPHMAC:
+		print_dumphmac(attrs);
+		break;
+
+	case SEG6_CMD_GET_TUNSRC:
+		print_tunsrc(attrs);
+		break;
+	}
+	close_json_object();
+
+	return 0;
+}
+
+static int seg6_do_cmd(void)
+{
+	SEG6_REQUEST(req, 1024, opts.cmd, NLM_F_REQUEST);
+	struct nlmsghdr *answer;
+	int repl = 0, dump = 0;
+
+	if (genl_family < 0) {
+		if (rtnl_open_byproto(&grth, 0, NETLINK_GENERIC) < 0) {
+			fprintf(stderr, "Cannot open generic netlink socket\n");
+			exit(1);
+		}
+		genl_family = genl_resolve_family(&grth, SEG6_GENL_NAME);
+		if (genl_family < 0)
+			exit(1);
+		req.n.nlmsg_type = genl_family;
+	}
+
+	switch (opts.cmd) {
+	case SEG6_CMD_SETHMAC:
+	{
+		addattr32(&req.n, sizeof(req), SEG6_ATTR_HMACKEYID, opts.keyid);
+		addattr8(&req.n, sizeof(req), SEG6_ATTR_SECRETLEN,
+			 strlen(opts.pass));
+		addattr8(&req.n, sizeof(req), SEG6_ATTR_ALGID, opts.alg_id);
+		if (strlen(opts.pass))
+			addattr_l(&req.n, sizeof(req), SEG6_ATTR_SECRET,
+				  opts.pass, strlen(opts.pass));
+		break;
+	}
+	case SEG6_CMD_SET_TUNSRC:
+		addattr_l(&req.n, sizeof(req), SEG6_ATTR_DST, opts.addr.data,
+			  sizeof(struct in6_addr));
+		break;
+	case SEG6_CMD_DUMPHMAC:
+		dump = 1;
+		break;
+	case SEG6_CMD_GET_TUNSRC:
+		repl = 1;
+		break;
+	}
+
+	if (!repl && !dump) {
+		if (rtnl_talk(&grth, &req.n, NULL) < 0)
+			return -1;
+	} else if (repl) {
+		if (rtnl_talk(&grth, &req.n, &answer) < 0)
+			return -2;
+		new_json_obj(json);
+		if (process_msg(answer, stdout) < 0) {
+			fprintf(stderr, "Error parsing reply\n");
+			exit(1);
+		}
+		delete_json_obj();
+		free(answer);
+	} else {
+		req.n.nlmsg_flags |= NLM_F_DUMP;
+		req.n.nlmsg_seq = grth.dump = ++grth.seq;
+		if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
+			perror("Failed to send dump request");
+			exit(1);
+		}
+
+		new_json_obj(json);
+		if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+		delete_json_obj();
+		fflush(stdout);
+	}
+
+	return 0;
+}
+
+int do_seg6(int argc, char **argv)
+{
+	if (argc < 1 || matches(*argv, "help") == 0)
+		usage();
+
+	memset(&opts, 0, sizeof(opts));
+
+	if (matches(*argv, "hmac") == 0) {
+		NEXT_ARG();
+		if (matches(*argv, "show") == 0) {
+			opts.cmd = SEG6_CMD_DUMPHMAC;
+		} else if (matches(*argv, "set") == 0) {
+			NEXT_ARG();
+			if (get_u32(&opts.keyid, *argv, 0) || opts.keyid == 0)
+				invarg("hmac KEYID value is invalid", *argv);
+			NEXT_ARG();
+			if (strcmp(*argv, "sha1") == 0) {
+				opts.alg_id = SEG6_HMAC_ALGO_SHA1;
+			} else if (strcmp(*argv, "sha256") == 0) {
+				opts.alg_id = SEG6_HMAC_ALGO_SHA256;
+			} else {
+				invarg("hmac ALGO value is invalid", *argv);
+			}
+			opts.cmd = SEG6_CMD_SETHMAC;
+			opts.pass = getpass(HMAC_KEY_PROMPT);
+		} else {
+			invarg("unknown", *argv);
+		}
+	} else if (matches(*argv, "tunsrc") == 0) {
+		NEXT_ARG();
+		if (matches(*argv, "show") == 0) {
+			opts.cmd = SEG6_CMD_GET_TUNSRC;
+		} else if (matches(*argv, "set") == 0) {
+			NEXT_ARG();
+			opts.cmd = SEG6_CMD_SET_TUNSRC;
+			get_addr(&opts.addr, *argv, AF_INET6);
+		} else {
+			invarg("unknown", *argv);
+		}
+	} else {
+		invarg("unknown", *argv);
+	}
+
+	return seg6_do_cmd();
+}
diff --git a/ip/iptoken.c b/ip/iptoken.c
new file mode 100644
index 0000000..9f35689
--- /dev/null
+++ b/ip/iptoken.c
@@ -0,0 +1,208 @@
+/*
+ * iptoken.c    "ip token"
+ *
+ *              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.
+ *
+ * Authors:     Daniel Borkmann, <borkmann@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <linux/types.h>
+#include <linux/if.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+extern struct rtnl_handle rth;
+
+struct rtnl_dump_args {
+	FILE *fp;
+	int ifindex;
+};
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ip token [ list | set | del | get ] [ TOKEN ] [ dev DEV ]\n");
+	exit(-1);
+}
+
+static int print_token(struct nlmsghdr *n, void *arg)
+{
+	struct rtnl_dump_args *args = arg;
+	FILE *fp = args->fp;
+	int ifindex = args->ifindex;
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *ltb[IFLA_INET6_MAX + 1];
+
+	if (n->nlmsg_type != RTM_NEWLINK)
+		return -1;
+
+	len -= NLMSG_LENGTH(sizeof(*ifi));
+	if (len < 0)
+		return -1;
+
+	if (ifi->ifi_family != AF_INET6)
+		return 0;
+	if (ifi->ifi_index == 0)
+		return 0;
+	if (ifindex > 0 && ifi->ifi_index != ifindex)
+		return 0;
+	if (ifi->ifi_flags & (IFF_LOOPBACK | IFF_NOARP))
+		return 0;
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+	if (!tb[IFLA_PROTINFO])
+		return -1;
+
+	parse_rtattr_nested(ltb, IFLA_INET6_MAX, tb[IFLA_PROTINFO]);
+	if (!ltb[IFLA_INET6_TOKEN]) {
+		fprintf(stderr, "Seems there's no support for IPv6 token!\n");
+		return -1;
+	}
+
+	open_json_object(NULL);
+	print_string(PRINT_FP, NULL, "token ", NULL);
+	print_color_string(PRINT_ANY,
+			   ifa_family_color(ifi->ifi_family),
+			   "token", "%s",
+			   format_host_rta(ifi->ifi_family, ltb[IFLA_INET6_TOKEN]));
+	print_string(PRINT_FP, NULL, " dev ", NULL);
+	print_color_string(PRINT_ANY, COLOR_IFNAME,
+			   "ifname", "%s\n",
+			   ll_index_to_name(ifi->ifi_index));
+	close_json_object();
+	fflush(fp);
+
+	return 0;
+}
+
+static int iptoken_list(int argc, char **argv)
+{
+	int af = AF_INET6;
+	struct rtnl_dump_args da = { .fp = stdout };
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if ((da.ifindex = ll_name_to_index(*argv)) == 0)
+				invarg("dev is invalid\n", *argv);
+			break;
+		}
+		argc--; argv++;
+	}
+
+	if (rtnl_linkdump_req(&rth, af) < 0) {
+		perror("Cannot send dump request");
+		return -1;
+	}
+
+	new_json_obj(json);
+	if (rtnl_dump_filter(&rth, print_token, &da) < 0) {
+		delete_json_obj();
+		fprintf(stderr, "Dump terminated\n");
+		return -1;
+	}
+	delete_json_obj();
+
+	return 0;
+}
+
+static int iptoken_set(int argc, char **argv, bool delete)
+{
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg ifi;
+		char buf[512];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_SETLINK,
+		.ifi.ifi_family = AF_INET6,
+	};
+	struct rtattr *afs, *afs6;
+	bool have_token = delete, have_dev = false;
+	inet_prefix addr = { .bytelen = 16, };
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (!have_dev) {
+				if ((req.ifi.ifi_index =
+				     ll_name_to_index(*argv)) == 0)
+					invarg("dev is invalid\n", *argv);
+				have_dev = true;
+			}
+		} else {
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (!have_token) {
+				get_prefix(&addr, *argv, req.ifi.ifi_family);
+				have_token = true;
+			}
+		}
+		argc--; argv++;
+	}
+
+	if (!have_token) {
+		fprintf(stderr, "Not enough information: token is required.\n");
+		return -1;
+	}
+	if (!have_dev) {
+		fprintf(stderr, "Not enough information: \"dev\" argument is required.\n");
+		return -1;
+	}
+
+	afs = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+	afs6 = addattr_nest(&req.n, sizeof(req), AF_INET6);
+	addattr_l(&req.n, sizeof(req), IFLA_INET6_TOKEN,
+		  &addr.data, addr.bytelen);
+	addattr_nest_end(&req.n, afs6);
+	addattr_nest_end(&req.n, afs);
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		return -2;
+
+	return 0;
+}
+
+int do_iptoken(int argc, char **argv)
+{
+	ll_init_map(&rth);
+
+	if (argc < 1) {
+		return iptoken_list(0, NULL);
+	} else if (matches(argv[0], "list") == 0 ||
+		   matches(argv[0], "lst") == 0 ||
+		   matches(argv[0], "show") == 0) {
+		return iptoken_list(argc - 1, argv + 1);
+	} else if (matches(argv[0], "set") == 0 ||
+		   matches(argv[0], "add") == 0) {
+		return iptoken_set(argc - 1, argv + 1, false);
+	} else if (matches(argv[0], "delete") == 0) {
+		return iptoken_set(argc - 1, argv + 1, true);
+	} else if (matches(argv[0], "get") == 0) {
+		return iptoken_list(argc - 1, argv + 1);
+	} else if (matches(argv[0], "help") == 0)
+		usage();
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip token help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/iptunnel.c b/ip/iptunnel.c
new file mode 100644
index 0000000..696f3b9
--- /dev/null
+++ b/ip/iptunnel.c
@@ -0,0 +1,596 @@
+/*
+ * iptunnel.c	       "ip tunnel"
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip tunnel { add | change | del | show | prl | 6rd } [ NAME ]\n"
+		"	 [ mode { ipip | gre | sit | isatap | vti } ] [ remote ADDR ] [ local ADDR ]\n"
+		"	 [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n"
+		"	 [ prl-default ADDR ] [ prl-nodefault ADDR ] [ prl-delete ADDR ]\n"
+		"	 [ 6rd-prefix ADDR ] [ 6rd-relay_prefix ADDR ] [ 6rd-reset ]\n"
+		"	 [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ]\n"
+		"\n"
+		"Where:	NAME := STRING\n"
+		"	ADDR := { IP_ADDRESS | any }\n"
+		"	TOS  := { STRING | 00..ff | inherit | inherit/STRING | inherit/00..ff }\n"
+		"	TTL  := { 1..255 | inherit }\n"
+		"	KEY  := { DOTTED_QUAD | NUMBER }\n");
+	exit(-1);
+}
+
+static void set_tunnel_proto(struct ip_tunnel_parm *p, int proto)
+{
+	if (p->iph.protocol && p->iph.protocol != proto) {
+		fprintf(stderr,
+			"You managed to ask for more than one tunnel mode.\n");
+		exit(-1);
+	}
+	p->iph.protocol = proto;
+}
+
+static int parse_args(int argc, char **argv, int cmd, struct ip_tunnel_parm *p)
+{
+	int count = 0;
+	const char *medium = NULL;
+	int isatap = 0;
+
+	memset(p, 0, sizeof(*p));
+	p->iph.version = 4;
+	p->iph.ihl = 5;
+#ifndef IP_DF
+#define IP_DF		0x4000		/* Flag: "Don't Fragment"	*/
+#endif
+	p->iph.frag_off = htons(IP_DF);
+
+	while (argc > 0) {
+		if (strcmp(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "ipip") == 0 ||
+			    strcmp(*argv, "ip/ip") == 0) {
+				set_tunnel_proto(p, IPPROTO_IPIP);
+			} else if (strcmp(*argv, "gre") == 0 ||
+				   strcmp(*argv, "gre/ip") == 0) {
+				set_tunnel_proto(p, IPPROTO_GRE);
+			} else if (strcmp(*argv, "sit") == 0 ||
+				   strcmp(*argv, "ipv6/ip") == 0) {
+				set_tunnel_proto(p, IPPROTO_IPV6);
+			} else if (strcmp(*argv, "isatap") == 0) {
+				set_tunnel_proto(p, IPPROTO_IPV6);
+				isatap++;
+			} else if (strcmp(*argv, "vti") == 0) {
+				set_tunnel_proto(p, IPPROTO_IPIP);
+				p->i_flags |= VTI_ISVTI;
+			} else {
+				fprintf(stderr,
+					"Unknown tunnel mode \"%s\"\n", *argv);
+				exit(-1);
+			}
+		} else if (strcmp(*argv, "key") == 0) {
+			NEXT_ARG();
+			p->i_flags |= GRE_KEY;
+			p->o_flags |= GRE_KEY;
+			p->i_key = p->o_key = tnl_parse_key("key", *argv);
+		} else if (strcmp(*argv, "ikey") == 0) {
+			NEXT_ARG();
+			p->i_flags |= GRE_KEY;
+			p->i_key = tnl_parse_key("ikey", *argv);
+		} else if (strcmp(*argv, "okey") == 0) {
+			NEXT_ARG();
+			p->o_flags |= GRE_KEY;
+			p->o_key = tnl_parse_key("okey", *argv);
+		} else if (strcmp(*argv, "seq") == 0) {
+			p->i_flags |= GRE_SEQ;
+			p->o_flags |= GRE_SEQ;
+		} else if (strcmp(*argv, "iseq") == 0) {
+			p->i_flags |= GRE_SEQ;
+		} else if (strcmp(*argv, "oseq") == 0) {
+			p->o_flags |= GRE_SEQ;
+		} else if (strcmp(*argv, "csum") == 0) {
+			p->i_flags |= GRE_CSUM;
+			p->o_flags |= GRE_CSUM;
+		} else if (strcmp(*argv, "icsum") == 0) {
+			p->i_flags |= GRE_CSUM;
+		} else if (strcmp(*argv, "ocsum") == 0) {
+			p->o_flags |= GRE_CSUM;
+		} else if (strcmp(*argv, "nopmtudisc") == 0) {
+			p->iph.frag_off = 0;
+		} else if (strcmp(*argv, "pmtudisc") == 0) {
+			p->iph.frag_off = htons(IP_DF);
+		} else if (strcmp(*argv, "remote") == 0) {
+			NEXT_ARG();
+			p->iph.daddr = get_addr32(*argv);
+		} else if (strcmp(*argv, "local") == 0) {
+			NEXT_ARG();
+			p->iph.saddr = get_addr32(*argv);
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			medium = *argv;
+		} else if (strcmp(*argv, "ttl") == 0 ||
+			   strcmp(*argv, "hoplimit") == 0 ||
+			   strcmp(*argv, "hlim") == 0) {
+			__u8 uval;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (get_u8(&uval, *argv, 0))
+					invarg("invalid TTL\n", *argv);
+				p->iph.ttl = uval;
+			}
+		} else if (strcmp(*argv, "tos") == 0 ||
+			   strcmp(*argv, "tclass") == 0 ||
+			   matches(*argv, "dsfield") == 0) {
+			char *dsfield;
+			__u32 uval;
+
+			NEXT_ARG();
+			dsfield = *argv;
+			strsep(&dsfield, "/");
+			if (strcmp(*argv, "inherit") != 0) {
+				dsfield = *argv;
+				p->iph.tos = 0;
+			} else
+				p->iph.tos = 1;
+			if (dsfield) {
+				if (rtnl_dsfield_a2n(&uval, dsfield))
+					invarg("bad TOS value", *argv);
+				p->iph.tos |= uval;
+			}
+		} else {
+			if (strcmp(*argv, "name") == 0)
+				NEXT_ARG();
+			else if (matches(*argv, "help") == 0)
+				usage();
+
+			if (p->name[0])
+				duparg2("name", *argv);
+			if (get_ifname(p->name, *argv))
+				invarg("\"name\" not a valid ifname", *argv);
+			if (cmd == SIOCCHGTUNNEL && count == 0) {
+				struct ip_tunnel_parm old_p = {};
+
+				if (tnl_get_ioctl(*argv, &old_p))
+					return -1;
+				*p = old_p;
+			}
+		}
+		count++;
+		argc--; argv++;
+	}
+
+
+	if (p->iph.protocol == 0) {
+		if (memcmp(p->name, "gre", 3) == 0)
+			p->iph.protocol = IPPROTO_GRE;
+		else if (memcmp(p->name, "ipip", 4) == 0)
+			p->iph.protocol = IPPROTO_IPIP;
+		else if (memcmp(p->name, "sit", 3) == 0)
+			p->iph.protocol = IPPROTO_IPV6;
+		else if (memcmp(p->name, "isatap", 6) == 0) {
+			p->iph.protocol = IPPROTO_IPV6;
+			isatap++;
+		} else if (memcmp(p->name, "vti", 3) == 0) {
+			p->iph.protocol = IPPROTO_IPIP;
+			p->i_flags |= VTI_ISVTI;
+		}
+	}
+
+	if ((p->i_flags & GRE_KEY) || (p->o_flags & GRE_KEY)) {
+		if (!(p->i_flags & VTI_ISVTI) &&
+		    (p->iph.protocol != IPPROTO_GRE)) {
+			fprintf(stderr, "Keys are not allowed with ipip and sit tunnels\n");
+			return -1;
+		}
+	}
+
+	if (medium) {
+		p->link = ll_name_to_index(medium);
+		if (!p->link)
+			return nodev(medium);
+	}
+
+	if (p->i_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) {
+		p->i_key = p->iph.daddr;
+		p->i_flags |= GRE_KEY;
+	}
+	if (p->o_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) {
+		p->o_key = p->iph.daddr;
+		p->o_flags |= GRE_KEY;
+	}
+	if (IN_MULTICAST(ntohl(p->iph.daddr)) && !p->iph.saddr) {
+		fprintf(stderr, "A broadcast tunnel requires a source address\n");
+		return -1;
+	}
+	if (isatap)
+		p->i_flags |= SIT_ISATAP;
+
+	return 0;
+}
+
+static const char *tnl_defname(const struct ip_tunnel_parm *p)
+{
+	switch (p->iph.protocol) {
+	case IPPROTO_IPIP:
+		if (p->i_flags & VTI_ISVTI)
+			return "ip_vti0";
+		else
+			return "tunl0";
+	case IPPROTO_GRE:
+		return "gre0";
+	case IPPROTO_IPV6:
+		return "sit0";
+	}
+	return NULL;
+}
+
+static int do_add(int cmd, int argc, char **argv)
+{
+	struct ip_tunnel_parm p;
+	const char *basedev;
+
+	if (parse_args(argc, argv, cmd, &p) < 0)
+		return -1;
+
+	if (p.iph.ttl && p.iph.frag_off == 0) {
+		fprintf(stderr, "ttl != 0 and nopmtudisc are incompatible\n");
+		return -1;
+	}
+
+	basedev = tnl_defname(&p);
+	if (!basedev) {
+		fprintf(stderr,
+			"cannot determine tunnel mode (ipip, gre, vti or sit)\n");
+		return -1;
+	}
+
+	return tnl_add_ioctl(cmd, basedev, p.name, &p);
+}
+
+static int do_del(int argc, char **argv)
+{
+	struct ip_tunnel_parm p;
+
+	if (parse_args(argc, argv, SIOCDELTUNNEL, &p) < 0)
+		return -1;
+
+	return tnl_del_ioctl(tnl_defname(&p) ? : p.name, p.name, &p);
+}
+
+static void print_tunnel(const void *t)
+{
+	const struct ip_tunnel_parm *p = t;
+	struct ip_tunnel_6rd ip6rd = {};
+	SPRINT_BUF(b1);
+
+	/* Do not use format_host() for local addr,
+	 * symbolic name will not be useful.
+	 */
+	open_json_object(NULL);
+	print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname", "%s: ", p->name);
+	snprintf(b1, sizeof(b1), "%s/ip", tnl_strproto(p->iph.protocol));
+	print_string(PRINT_ANY, "mode", "%s ", b1);
+	print_null(PRINT_FP, NULL, "remote ", NULL);
+	print_color_string(PRINT_ANY, COLOR_INET, "remote", "%s ",
+			   p->iph.daddr || is_json_context()
+				? format_host_r(AF_INET, 4, &p->iph.daddr, b1, sizeof(b1))
+				: "any");
+	print_null(PRINT_FP, NULL, "local ", NULL);
+	print_color_string(PRINT_ANY, COLOR_INET, "local", "%s",
+			   p->iph.saddr || is_json_context()
+				? rt_addr_n2a_r(AF_INET, 4, &p->iph.saddr, b1, sizeof(b1))
+				: "any");
+
+	if (p->iph.protocol == IPPROTO_IPV6 && (p->i_flags & SIT_ISATAP)) {
+		struct ip_tunnel_prl prl[16] = {};
+		int i;
+
+		prl[0].datalen = sizeof(prl) - sizeof(prl[0]);
+		prl[0].addr = htonl(INADDR_ANY);
+
+		if (!tnl_prl_ioctl(SIOCGETPRL, p->name, prl)) {
+			for (i = 1; i < ARRAY_SIZE(prl); i++) {
+				if (prl[i].addr == htonl(INADDR_ANY))
+					continue;
+				if (prl[i].flags & PRL_DEFAULT)
+					print_string(PRINT_ANY, "pdr",
+						     " pdr %s",
+						     format_host(AF_INET, 4, &prl[i].addr));
+				else
+					print_string(PRINT_ANY, "pr", " pr %s",
+						     format_host(AF_INET, 4, &prl[i].addr));
+			}
+		}
+	}
+
+	if (p->link) {
+		const char *n = ll_index_to_name(p->link);
+
+		if (n)
+			print_string(PRINT_ANY, "dev", " dev %s", n);
+	}
+
+	if (p->iph.ttl)
+		print_uint(PRINT_ANY, "ttl", " ttl %u", p->iph.ttl);
+	else
+		print_string(PRINT_FP, "ttl", " ttl %s", "inherit");
+
+	if (p->iph.tos) {
+		SPRINT_BUF(b2);
+
+		if (p->iph.tos != 1) {
+			if (!is_json_context() && p->iph.tos & 1)
+				snprintf(b2, sizeof(b2), "%s%s",
+					 p->iph.tos & 1 ? "inherit/" : "",
+					 rtnl_dsfield_n2a(p->iph.tos & ~1, b1, sizeof(b1)));
+			else
+				snprintf(b2, sizeof(b2), "%s",
+					 rtnl_dsfield_n2a(p->iph.tos, b1, sizeof(b1)));
+			print_string(PRINT_ANY, "tos", " tos %s", b2);
+		} else {
+			print_string(PRINT_FP, NULL, " tos %s", "inherit");
+		}
+	}
+
+	if (!(p->iph.frag_off & htons(IP_DF)))
+		print_null(PRINT_ANY, "nopmtudisc", " nopmtudisc", NULL);
+
+	if (p->iph.protocol == IPPROTO_IPV6 && !tnl_ioctl_get_6rd(p->name, &ip6rd) && ip6rd.prefixlen) {
+		print_string(PRINT_ANY, "6rd-prefix", " 6rd-prefix %s",
+			     inet_ntop(AF_INET6, &ip6rd.prefix, b1, sizeof(b1)));
+		print_uint(PRINT_ANY, "6rd-prefixlen", "/%u", ip6rd.prefixlen);
+		if (ip6rd.relay_prefix) {
+			print_string(PRINT_ANY, "6rd-relay_prefix",
+				     " 6rd-relay_prefix %s",
+				     format_host(AF_INET, 4, &ip6rd.relay_prefix));
+			print_uint(PRINT_ANY, "6rd-relay_prefixlen", "/%u",
+				   ip6rd.relay_prefixlen);
+		}
+	}
+
+	tnl_print_gre_flags(p->iph.protocol, p->i_flags, p->o_flags,
+			    p->i_key, p->o_key);
+
+	close_json_object();
+}
+
+
+static void ip_tunnel_parm_initialize(const struct tnl_print_nlmsg_info *info)
+{
+	struct ip_tunnel_parm *p2 = info->p2;
+
+	memset(p2, 0, sizeof(*p2));
+}
+
+static bool ip_tunnel_parm_match(const struct tnl_print_nlmsg_info *info)
+{
+	const struct ip_tunnel_parm *p1 = info->p1;
+	const struct ip_tunnel_parm *p2 = info->p2;
+
+	return ((!p1->link || p1->link == p2->link) &&
+		(!p1->name[0] || strcmp(p1->name, p2->name) == 0) &&
+		(!p1->iph.daddr || p1->iph.daddr == p2->iph.daddr) &&
+		(!p1->iph.saddr || p1->iph.saddr == p2->iph.saddr) &&
+		(!p1->i_key || p1->i_key == p2->i_key));
+}
+
+static int do_show(int argc, char **argv)
+{
+	struct ip_tunnel_parm p, p1;
+	const char *basedev;
+
+	if (parse_args(argc, argv, SIOCGETTUNNEL, &p) < 0)
+		return -1;
+
+	basedev = tnl_defname(&p);
+	if (!basedev) {
+		struct tnl_print_nlmsg_info info = {
+			.p1    = &p,
+			.p2    = &p1,
+			.init  = ip_tunnel_parm_initialize,
+			.match = ip_tunnel_parm_match,
+			.print = print_tunnel,
+		};
+
+		return do_tunnels_list(&info);
+	}
+
+	if (tnl_get_ioctl(p.name[0] ? p.name : basedev, &p))
+		return -1;
+
+	print_tunnel(&p);
+	fputc('\n', stdout);
+	return 0;
+}
+
+static int do_prl(int argc, char **argv)
+{
+	struct ip_tunnel_prl p = {};
+	int count = 0;
+	int cmd = 0;
+	const char *medium = NULL;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "prl-default") == 0) {
+			NEXT_ARG();
+			cmd = SIOCADDPRL;
+			p.addr = get_addr32(*argv);
+			p.flags |= PRL_DEFAULT;
+			count++;
+		} else if (strcmp(*argv, "prl-nodefault") == 0) {
+			NEXT_ARG();
+			cmd = SIOCADDPRL;
+			p.addr = get_addr32(*argv);
+			count++;
+		} else if (strcmp(*argv, "prl-delete") == 0) {
+			NEXT_ARG();
+			cmd = SIOCDELPRL;
+			p.addr = get_addr32(*argv);
+			count++;
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (check_ifname(*argv))
+				invarg("\"dev\" not a valid ifname", *argv);
+			medium = *argv;
+		} else {
+			fprintf(stderr,
+				"Invalid PRL parameter \"%s\"\n", *argv);
+			exit(-1);
+		}
+		if (count > 1) {
+			fprintf(stderr,
+				"One PRL entry at a time\n");
+			exit(-1);
+		}
+		argc--; argv++;
+	}
+	if (!medium) {
+		fprintf(stderr, "Must specify device\n");
+		exit(-1);
+	}
+
+	return tnl_prl_ioctl(cmd, medium, &p);
+}
+
+static int do_6rd(int argc, char **argv)
+{
+	struct ip_tunnel_6rd ip6rd = {};
+	int cmd = 0;
+	const char *medium = NULL;
+	inet_prefix prefix;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "6rd-prefix") == 0) {
+			NEXT_ARG();
+			if (get_prefix(&prefix, *argv, AF_INET6))
+				invarg("invalid 6rd_prefix\n", *argv);
+			cmd = SIOCADD6RD;
+			memcpy(&ip6rd.prefix, prefix.data, 16);
+			ip6rd.prefixlen = prefix.bitlen;
+		} else if (strcmp(*argv, "6rd-relay_prefix") == 0) {
+			NEXT_ARG();
+			if (get_prefix(&prefix, *argv, AF_INET))
+				invarg("invalid 6rd-relay_prefix\n", *argv);
+			cmd = SIOCADD6RD;
+			memcpy(&ip6rd.relay_prefix, prefix.data, 4);
+			ip6rd.relay_prefixlen = prefix.bitlen;
+		} else if (strcmp(*argv, "6rd-reset") == 0) {
+			cmd = SIOCDEL6RD;
+		} else if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (check_ifname(*argv))
+				invarg("\"dev\" not a valid ifname", *argv);
+			medium = *argv;
+		} else {
+			fprintf(stderr,
+				"Invalid 6RD parameter \"%s\"\n", *argv);
+			exit(-1);
+		}
+		argc--; argv++;
+	}
+	if (!medium) {
+		fprintf(stderr, "Must specify device\n");
+		exit(-1);
+	}
+
+	return tnl_6rd_ioctl(cmd, medium, &ip6rd);
+}
+
+static int tunnel_mode_is_ipv6(char *tunnel_mode)
+{
+	static const char * const ipv6_modes[] = {
+		"ipv6/ipv6", "ip6ip6",
+		"vti6",
+		"ip/ipv6", "ipv4/ipv6", "ipip6", "ip4ip6",
+		"ip6gre", "gre/ipv6",
+		"any/ipv6", "any"
+	};
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ipv6_modes); i++) {
+		if (strcmp(ipv6_modes[i], tunnel_mode) == 0)
+			return 1;
+	}
+	return 0;
+}
+
+int do_iptunnel(int argc, char **argv)
+{
+	int i;
+
+	for (i = 0; i < argc - 1; i++) {
+		if (strcmp(argv[i], "mode") == 0) {
+			if (tunnel_mode_is_ipv6(argv[i + 1]))
+				preferred_family = AF_INET6;
+			break;
+		}
+	}
+	switch (preferred_family) {
+	case AF_UNSPEC:
+		preferred_family = AF_INET;
+		break;
+	case AF_INET:
+		break;
+	/*
+	 * This is silly enough but we have no easy way to make it
+	 * protocol-independent because of unarranged structure between
+	 * IPv4 and IPv6.
+	 */
+	case AF_INET6:
+		return do_ip6tunnel(argc, argv);
+	default:
+		fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
+		exit(-1);
+	}
+
+	if (argc > 0) {
+		if (matches(*argv, "add") == 0)
+			return do_add(SIOCADDTUNNEL, argc - 1, argv + 1);
+		if (matches(*argv, "change") == 0)
+			return do_add(SIOCCHGTUNNEL, argc - 1, argv + 1);
+		if (matches(*argv, "delete") == 0)
+			return do_del(argc - 1, argv + 1);
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return do_show(argc - 1, argv + 1);
+		if (matches(*argv, "prl") == 0)
+			return do_prl(argc - 1, argv + 1);
+		if (matches(*argv, "6rd") == 0)
+			return do_6rd(argc - 1, argv + 1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return do_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip tunnel help\"\n", *argv);
+	exit(-1);
+}
diff --git a/ip/iptuntap.c b/ip/iptuntap.c
new file mode 100644
index 0000000..82e3849
--- /dev/null
+++ b/ip/iptuntap.c
@@ -0,0 +1,599 @@
+/*
+ * iptunnel.c	       "ip tuntap"
+ *
+ *		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.
+ *
+ * Authors:	David Woodhouse <David.Woodhouse@intel.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <linux/if_arp.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <glob.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+
+static const char drv_name[] = "tun";
+
+#define TUNDEV "/dev/net/tun"
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip tuntap { add | del | show | list | lst | help } [ dev PHYS_DEV ]\n"
+		"	[ mode { tun | tap } ] [ user USER ] [ group GROUP ]\n"
+		"	[ one_queue ] [ pi ] [ vnet_hdr ] [ multi_queue ] [ name NAME ]\n"
+		"\n"
+		"Where:	USER  := { STRING | NUMBER }\n"
+		"	GROUP := { STRING | NUMBER }\n");
+	exit(-1);
+}
+
+static int tap_add_ioctl(struct ifreq *ifr, uid_t uid, gid_t gid)
+{
+	int fd;
+	int ret = -1;
+
+#ifdef IFF_TUN_EXCL
+	ifr->ifr_flags |= IFF_TUN_EXCL;
+#endif
+
+	fd = open(TUNDEV, O_RDWR);
+	if (fd < 0) {
+		perror("open");
+		return -1;
+	}
+	if (ioctl(fd, TUNSETIFF, ifr)) {
+		perror("ioctl(TUNSETIFF)");
+		goto out;
+	}
+	if (uid != -1 && ioctl(fd, TUNSETOWNER, uid)) {
+		perror("ioctl(TUNSETOWNER)");
+		goto out;
+	}
+	if (gid != -1 && ioctl(fd, TUNSETGROUP, gid)) {
+		perror("ioctl(TUNSETGROUP)");
+		goto out;
+	}
+	if (ioctl(fd, TUNSETPERSIST, 1)) {
+		perror("ioctl(TUNSETPERSIST)");
+		goto out;
+	}
+	ret = 0;
+ out:
+	close(fd);
+	return ret;
+}
+
+static int tap_del_ioctl(struct ifreq *ifr)
+{
+	int fd = open(TUNDEV, O_RDWR);
+	int ret = -1;
+
+	if (fd < 0) {
+		perror("open");
+		return -1;
+	}
+	if (ioctl(fd, TUNSETIFF, ifr)) {
+		perror("ioctl(TUNSETIFF)");
+		goto out;
+	}
+	if (ioctl(fd, TUNSETPERSIST, 0)) {
+		perror("ioctl(TUNSETPERSIST)");
+		goto out;
+	}
+	ret = 0;
+ out:
+	close(fd);
+	return ret;
+
+}
+static int parse_args(int argc, char **argv,
+		      struct ifreq *ifr, uid_t *uid, gid_t *gid)
+{
+	int count = 0;
+
+	memset(ifr, 0, sizeof(*ifr));
+
+	ifr->ifr_flags |= IFF_NO_PI;
+
+	while (argc > 0) {
+		if (matches(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (matches(*argv, "tun") == 0) {
+				if (ifr->ifr_flags & IFF_TAP) {
+					fprintf(stderr, "You managed to ask for more than one tunnel mode.\n");
+					exit(-1);
+				}
+				ifr->ifr_flags |= IFF_TUN;
+			} else if (matches(*argv, "tap") == 0) {
+				if (ifr->ifr_flags & IFF_TUN) {
+					fprintf(stderr, "You managed to ask for more than one tunnel mode.\n");
+					exit(-1);
+				}
+				ifr->ifr_flags |= IFF_TAP;
+			} else {
+				fprintf(stderr, "Unknown tunnel mode \"%s\"\n", *argv);
+				exit(-1);
+			}
+		} else if (uid && matches(*argv, "user") == 0) {
+			char *end;
+			unsigned long user;
+
+			NEXT_ARG();
+			if (**argv && ((user = strtol(*argv, &end, 10)), !*end))
+				*uid = user;
+			else {
+				struct passwd *pw = getpwnam(*argv);
+
+				if (!pw) {
+					fprintf(stderr, "invalid user \"%s\"\n", *argv);
+					exit(-1);
+				}
+				*uid = pw->pw_uid;
+			}
+		} else if (gid && matches(*argv, "group") == 0) {
+			char *end;
+			unsigned long group;
+
+			NEXT_ARG();
+
+			if (**argv && ((group = strtol(*argv, &end, 10)), !*end))
+				*gid = group;
+			else {
+				struct group *gr = getgrnam(*argv);
+
+				if (!gr) {
+					fprintf(stderr, "invalid group \"%s\"\n", *argv);
+					exit(-1);
+				}
+				*gid = gr->gr_gid;
+			}
+		} else if (matches(*argv, "pi") == 0) {
+			ifr->ifr_flags &= ~IFF_NO_PI;
+		} else if (matches(*argv, "one_queue") == 0) {
+			ifr->ifr_flags |= IFF_ONE_QUEUE;
+		} else if (matches(*argv, "vnet_hdr") == 0) {
+			ifr->ifr_flags |= IFF_VNET_HDR;
+		} else if (matches(*argv, "multi_queue") == 0) {
+			ifr->ifr_flags |= IFF_MULTI_QUEUE;
+		} else if (matches(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (get_ifname(ifr->ifr_name, *argv))
+				invarg("\"dev\" not a valid ifname", *argv);
+		} else {
+			if (matches(*argv, "name") == 0) {
+				NEXT_ARG();
+			} else if (matches(*argv, "help") == 0)
+				usage();
+			if (ifr->ifr_name[0])
+				duparg2("name", *argv);
+			if (get_ifname(ifr->ifr_name, *argv))
+				invarg("\"name\" not a valid ifname", *argv);
+		}
+		count++;
+		argc--; argv++;
+	}
+
+	if (!(ifr->ifr_flags & TUN_TYPE_MASK)) {
+		fprintf(stderr, "You failed to specify a tunnel mode\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static int do_add(int argc, char **argv)
+{
+	struct ifreq ifr;
+	uid_t uid = -1;
+	gid_t gid = -1;
+
+	if (parse_args(argc, argv, &ifr, &uid, &gid) < 0)
+		return -1;
+
+	return tap_add_ioctl(&ifr, uid, gid);
+}
+
+static int do_del(int argc, char **argv)
+{
+	struct ifreq ifr;
+
+	if (parse_args(argc, argv, &ifr, NULL, NULL) < 0)
+		return -1;
+
+	return tap_del_ioctl(&ifr);
+}
+
+static void print_flags(long flags)
+{
+	open_json_array(PRINT_JSON, "flags");
+
+	if (flags & IFF_TUN)
+		print_string(PRINT_ANY, NULL, " %s", "tun");
+
+	if (flags & IFF_TAP)
+		print_string(PRINT_ANY, NULL, " %s", "tap");
+
+	if (!(flags & IFF_NO_PI))
+		print_string(PRINT_ANY, NULL, " %s", "pi");
+
+	if (flags & IFF_ONE_QUEUE)
+		print_string(PRINT_ANY, NULL, " %s", "one_queue");
+
+	if (flags & IFF_VNET_HDR)
+		print_string(PRINT_ANY, NULL, " %s", "vnet_hdr");
+
+	if (flags & IFF_PERSIST)
+		print_string(PRINT_ANY, NULL, " %s", "persist");
+
+	if (!(flags & IFF_NOFILTER))
+		print_string(PRINT_ANY, NULL, " %s", "filter");
+
+	flags &= ~(IFF_TUN | IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE |
+		   IFF_VNET_HDR | IFF_PERSIST | IFF_NOFILTER);
+	if (flags)
+		print_0xhex(PRINT_ANY, NULL, "%#llx", flags);
+
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static char *pid_name(pid_t pid)
+{
+	char *comm;
+	FILE *f;
+	int err;
+
+	err = asprintf(&comm, "/proc/%d/comm", pid);
+	if (err < 0)
+		return NULL;
+
+	f = fopen(comm, "r");
+	free(comm);
+	if (!f) {
+		perror("fopen");
+		return NULL;
+	}
+
+	if (fscanf(f, "%ms\n", &comm) != 1) {
+		perror("fscanf");
+		comm = NULL;
+	}
+
+
+	if (fclose(f))
+		perror("fclose");
+
+	return comm;
+}
+
+static void show_processes(const char *name)
+{
+	glob_t globbuf = { };
+	char **fd_path;
+	int err;
+
+	err = glob("/proc/[0-9]*/fd/[0-9]*", GLOB_NOSORT,
+		   NULL, &globbuf);
+	if (err)
+		return;
+
+	open_json_array(PRINT_JSON, "processes");
+
+	fd_path = globbuf.gl_pathv;
+	while (*fd_path) {
+		const char *dev_net_tun = "/dev/net/tun";
+		const size_t linkbuf_len = strlen(dev_net_tun) + 2;
+		char linkbuf[linkbuf_len], *fdinfo;
+		int pid, fd;
+		FILE *f;
+
+		if (sscanf(*fd_path, "/proc/%d/fd/%d", &pid, &fd) != 2)
+			goto next;
+
+		if (pid == getpid())
+			goto next;
+
+		err = readlink(*fd_path, linkbuf, linkbuf_len - 1);
+		if (err < 0) {
+			perror("readlink");
+			goto next;
+		}
+		linkbuf[err] = '\0';
+		if (strcmp(dev_net_tun, linkbuf))
+			goto next;
+
+		if (asprintf(&fdinfo, "/proc/%d/fdinfo/%d", pid, fd) < 0)
+			goto next;
+
+		f = fopen(fdinfo, "r");
+		free(fdinfo);
+		if (!f) {
+			perror("fopen");
+			goto next;
+		}
+
+		while (!feof(f)) {
+			char *key = NULL, *value = NULL;
+
+			err = fscanf(f, "%m[^:]: %ms\n", &key, &value);
+			if (err == EOF) {
+				if (ferror(f))
+					perror("fscanf");
+				break;
+			} else if (err == 2 &&
+				   !strcmp("iff", key) &&
+				   !strcmp(name, value)) {
+				char *pname = pid_name(pid);
+
+				print_string(PRINT_ANY, "name",
+					     "%s", pname ? : "<NULL>");
+
+				print_uint(PRINT_ANY, "pid",
+					   "(%d)", pid);
+				free(pname);
+			}
+
+			free(key);
+			free(value);
+		}
+		if (fclose(f))
+			perror("fclose");
+
+next:
+		++fd_path;
+	}
+	close_json_array(PRINT_JSON, NULL);
+
+	globfree(&globbuf);
+}
+
+static int tuntap_filter_req(struct nlmsghdr *nlh, int reqlen)
+{
+	struct rtattr *linkinfo;
+	int err;
+
+	linkinfo = addattr_nest(nlh, reqlen, IFLA_LINKINFO);
+
+	err = addattr_l(nlh, reqlen, IFLA_INFO_KIND,
+			drv_name, sizeof(drv_name) - 1);
+	if (err)
+		return err;
+
+	addattr_nest_end(nlh, linkinfo);
+
+	return 0;
+}
+
+static int print_tuntap(struct nlmsghdr *n, void *arg)
+{
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_MAX+1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	const char *name, *kind;
+	long flags, owner = -1, group = -1;
+
+	if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+		return 0;
+
+	if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifi)))
+		return -1;
+
+	switch (ifi->ifi_type) {
+	case ARPHRD_NONE:
+	case ARPHRD_ETHER:
+		break;
+	default:
+		return 0;
+	}
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n));
+
+	if (!tb[IFLA_IFNAME])
+		return 0;
+
+	if (!tb[IFLA_LINKINFO])
+		return 0;
+
+	parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+	if (!linkinfo[IFLA_INFO_KIND])
+		return 0;
+
+	kind = rta_getattr_str(linkinfo[IFLA_INFO_KIND]);
+	if (strcmp(kind, drv_name))
+		return 0;
+
+	name = rta_getattr_str(tb[IFLA_IFNAME]);
+
+	if (read_prop(name, "tun_flags", &flags))
+		return 0;
+	if (read_prop(name, "owner", &owner))
+		return 0;
+	if (read_prop(name, "group", &group))
+		return 0;
+
+	open_json_object(NULL);
+	print_color_string(PRINT_ANY, COLOR_IFNAME,
+			   "ifname", "%s:", name);
+	print_flags(flags);
+	if (owner != -1)
+		print_u64(PRINT_ANY, "user",
+			   " user %ld", owner);
+	if (group != -1)
+		print_u64(PRINT_ANY, "group",
+			   " group %ld", group);
+
+	if (show_details) {
+		print_string(PRINT_FP, NULL,
+			     "%s\tAttached to processes:", _SL_);
+		show_processes(name);
+	}
+	close_json_object();
+	print_string(PRINT_FP, NULL, "%s", "\n");
+
+	return 0;
+}
+
+static int do_show(int argc, char **argv)
+{
+	if (rtnl_linkdump_req_filter_fn(&rth, AF_UNSPEC,
+					tuntap_filter_req) < 0) {
+		perror("Cannot send dump request\n");
+		return -1;
+	}
+
+	new_json_obj(json);
+
+	if (rtnl_dump_filter(&rth, print_tuntap, NULL) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return -1;
+	}
+
+	delete_json_obj();
+	fflush(stdout);
+
+	return 0;
+}
+
+int do_iptuntap(int argc, char **argv)
+{
+	if (argc > 0) {
+		if (matches(*argv, "add") == 0)
+			return do_add(argc-1, argv+1);
+		if (matches(*argv, "delete") == 0)
+			return do_del(argc-1, argv+1);
+		if (matches(*argv, "show") == 0 ||
+		    matches(*argv, "lst") == 0 ||
+		    matches(*argv, "list") == 0)
+			return do_show(argc-1, argv+1);
+		if (matches(*argv, "help") == 0)
+			usage();
+	} else
+		return do_show(0, NULL);
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip tuntap help\".\n",
+		*argv);
+	exit(-1);
+}
+
+static void print_owner(FILE *f, uid_t uid)
+{
+	struct passwd *pw = getpwuid(uid);
+
+	if (pw)
+		print_string(PRINT_ANY, "user", "user %s ", pw->pw_name);
+	else
+		print_uint(PRINT_ANY, "user", "user %u ", uid);
+}
+
+static void print_group(FILE *f, gid_t gid)
+{
+	struct group *group = getgrgid(gid);
+
+	if (group)
+		print_string(PRINT_ANY, "group", "group %s ", group->gr_name);
+	else
+		print_uint(PRINT_ANY, "group", "group %u ", gid);
+}
+
+static void print_mq(FILE *f, struct rtattr *tb[])
+{
+	if (!tb[IFLA_TUN_MULTI_QUEUE] ||
+	    !rta_getattr_u8(tb[IFLA_TUN_MULTI_QUEUE])) {
+		if (is_json_context())
+			print_bool(PRINT_JSON, "multi_queue", NULL, false);
+		return;
+	}
+
+	print_bool(PRINT_ANY, "multi_queue", "multi_queue ", true);
+
+	if (tb[IFLA_TUN_NUM_QUEUES]) {
+		print_uint(PRINT_ANY, "numqueues", "numqueues %u ",
+			   rta_getattr_u32(tb[IFLA_TUN_NUM_QUEUES]));
+	}
+
+	if (tb[IFLA_TUN_NUM_DISABLED_QUEUES]) {
+		print_uint(PRINT_ANY, "numdisabled", "numdisabled %u ",
+			   rta_getattr_u32(tb[IFLA_TUN_NUM_DISABLED_QUEUES]));
+	}
+}
+
+static void print_onoff(FILE *f, const char *flag, __u8 val)
+{
+	if (is_json_context())
+		print_bool(PRINT_JSON, flag, NULL, !!val);
+	else
+		fprintf(f, "%s %s ", flag, val ? "on" : "off");
+}
+
+static void print_type(FILE *f, __u8 type)
+{
+	SPRINT_BUF(buf);
+	const char *str = buf;
+
+	if (type == IFF_TUN)
+		str = "tun";
+	else if (type == IFF_TAP)
+		str = "tap";
+	else
+		snprintf(buf, sizeof(buf), "UNKNOWN:%hhu", type);
+
+	print_string(PRINT_ANY, "type", "type %s ", str);
+}
+
+static void tun_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	if (!tb)
+		return;
+
+	if (tb[IFLA_TUN_TYPE])
+		print_type(f, rta_getattr_u8(tb[IFLA_TUN_TYPE]));
+
+	if (tb[IFLA_TUN_PI])
+		print_onoff(f, "pi", rta_getattr_u8(tb[IFLA_TUN_PI]));
+
+	if (tb[IFLA_TUN_VNET_HDR]) {
+		print_onoff(f, "vnet_hdr",
+			    rta_getattr_u8(tb[IFLA_TUN_VNET_HDR]));
+	}
+
+	print_mq(f, tb);
+
+	if (tb[IFLA_TUN_PERSIST])
+		print_onoff(f, "persist", rta_getattr_u8(tb[IFLA_TUN_PERSIST]));
+
+	if (tb[IFLA_TUN_OWNER])
+		print_owner(f, rta_getattr_u32(tb[IFLA_TUN_OWNER]));
+
+	if (tb[IFLA_TUN_GROUP])
+		print_group(f, rta_getattr_u32(tb[IFLA_TUN_GROUP]));
+}
+
+struct link_util tun_link_util = {
+	.id = "tun",
+	.maxattr = IFLA_TUN_MAX,
+	.print_opt = tun_print_opt,
+};
diff --git a/ip/ipvrf.c b/ip/ipvrf.c
new file mode 100644
index 0000000..43366f6
--- /dev/null
+++ b/ip/ipvrf.c
@@ -0,0 +1,643 @@
+/*
+ * ipvrf.c	"ip vrf"
+ *
+ *		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.
+ *
+ * Authors:	David Ahern <dsa@cumulusnetworks.com>
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <linux/bpf.h>
+#include <linux/if.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "bpf_util.h"
+
+#define CGRP_PROC_FILE  "/cgroup.procs"
+
+static struct link_filter vrf_filter;
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage:	ip vrf show [NAME] ...\n"
+		"	ip vrf exec [NAME] cmd ...\n"
+		"	ip vrf identify [PID]\n"
+		"	ip vrf pids [NAME]\n");
+
+	exit(-1);
+}
+
+/*
+ * parse process based cgroup file looking for PATH/vrf/NAME where
+ * NAME is the name of the vrf the process is associated with
+ */
+static int vrf_identify(pid_t pid, char *name, size_t len)
+{
+	char path[PATH_MAX];
+	char buf[4096];
+	char *vrf, *end;
+	FILE *fp;
+
+	snprintf(path, sizeof(path), "/proc/%d/cgroup", pid);
+	fp = fopen(path, "r");
+	if (!fp)
+		return -1;
+
+	memset(name, 0, len);
+
+	while (fgets(buf, sizeof(buf), fp)) {
+		/* want the controller-less cgroup */
+		if (strstr(buf, "::/") == NULL)
+			continue;
+
+		vrf = strstr(buf, "/vrf/");
+		if (vrf) {
+			vrf += 5;  /* skip past "/vrf/" */
+			end = strchr(vrf, '\n');
+			if (end)
+				*end = '\0';
+
+			strlcpy(name, vrf, len);
+			break;
+		}
+	}
+
+	fclose(fp);
+
+	return 0;
+}
+
+static int ipvrf_identify(int argc, char **argv)
+{
+	char vrf[32];
+	int rc;
+	unsigned int pid;
+
+	if (argc < 1)
+		pid = getpid();
+	else if (argc > 1)
+		invarg("Extra arguments specified\n", argv[1]);
+	else if (get_unsigned(&pid, argv[0], 10))
+		invarg("Invalid pid\n", argv[0]);
+
+	rc = vrf_identify(pid, vrf, sizeof(vrf));
+	if (!rc) {
+		if (vrf[0] != '\0')
+			printf("%s\n", vrf);
+	} else {
+		fprintf(stderr, "Failed to lookup vrf association: %s\n",
+			strerror(errno));
+	}
+
+	return rc;
+}
+
+/* read PATH/vrf/NAME/cgroup.procs file */
+static void read_cgroup_pids(const char *base_path, char *name)
+{
+	char path[PATH_MAX];
+	char buf[4096];
+	FILE *fp;
+
+	if (snprintf(path, sizeof(path), "%s/vrf/%s%s",
+		     base_path, name, CGRP_PROC_FILE) >= sizeof(path))
+		return;
+
+	fp = fopen(path, "r");
+	if (!fp)
+		return; /* no cgroup file, nothing to show */
+
+	/* dump contents (pids) of cgroup.procs */
+	while (fgets(buf, sizeof(buf), fp)) {
+		char *nl, comm[32];
+
+		nl = strchr(buf, '\n');
+		if (nl)
+			*nl = '\0';
+
+		if (get_command_name(buf, comm, sizeof(comm)))
+			strcpy(comm, "<terminated?>");
+
+		printf("%5s  %s\n", buf, comm);
+	}
+
+	fclose(fp);
+}
+
+/* recurse path looking for PATH[/NETNS]/vrf/NAME */
+static int recurse_dir(char *base_path, char *name, const char *netns)
+{
+	char path[PATH_MAX];
+	struct dirent *de;
+	struct stat fstat;
+	int rc;
+	DIR *d;
+
+	d = opendir(base_path);
+	if (!d)
+		return -1;
+
+	while ((de = readdir(d)) != NULL) {
+		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+			continue;
+
+		if (!strcmp(de->d_name, "vrf")) {
+			const char *pdir = strrchr(base_path, '/');
+
+			/* found a 'vrf' directory. if it is for the given
+			 * namespace then dump the cgroup pids
+			 */
+			if (*netns == '\0' ||
+			    (pdir && !strcmp(pdir+1, netns)))
+				read_cgroup_pids(base_path, name);
+
+			continue;
+		}
+
+		/* is this a subdir that needs to be walked */
+		if (snprintf(path, sizeof(path), "%s/%s",
+			     base_path, de->d_name) >= sizeof(path))
+			continue;
+
+		if (lstat(path, &fstat) < 0)
+			continue;
+
+		if (S_ISDIR(fstat.st_mode)) {
+			rc = recurse_dir(path, name, netns);
+			if (rc != 0)
+				goto out;
+		}
+	}
+
+	rc = 0;
+out:
+	closedir(d);
+
+	return rc;
+}
+
+static int ipvrf_get_netns(char *netns, int len)
+{
+	if (netns_identify_pid("self", netns, len-3)) {
+		fprintf(stderr, "Failed to get name of network namespace: %s\n",
+			strerror(errno));
+		return -1;
+	}
+
+	if (*netns != '\0')
+		strcat(netns, "-ns");
+
+	return 0;
+}
+
+static int ipvrf_pids(int argc, char **argv)
+{
+	char *mnt, *vrf;
+	char netns[256];
+	int ret = -1;
+
+	if (argc != 1) {
+		fprintf(stderr, "Invalid arguments\n");
+		return -1;
+	}
+
+	vrf = argv[0];
+	if (!name_is_vrf(vrf)) {
+		fprintf(stderr, "Invalid VRF name\n");
+		return -1;
+	}
+
+	mnt = find_cgroup2_mount();
+	if (!mnt)
+		return -1;
+
+	if (ipvrf_get_netns(netns, sizeof(netns)) < 0)
+		goto out;
+
+	ret = recurse_dir(mnt, vrf, netns);
+
+out:
+	free(mnt);
+
+	return ret;
+}
+
+/* load BPF program to set sk_bound_dev_if for sockets */
+static char bpf_log_buf[256*1024];
+
+static int prog_load(int idx)
+{
+	struct bpf_insn prog[] = {
+		BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
+		BPF_MOV64_IMM(BPF_REG_3, idx),
+		BPF_MOV64_IMM(BPF_REG_2,
+			      offsetof(struct bpf_sock, bound_dev_if)),
+		BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3,
+			    offsetof(struct bpf_sock, bound_dev_if)),
+		BPF_MOV64_IMM(BPF_REG_0, 1), /* r0 = verdict */
+		BPF_EXIT_INSN(),
+	};
+
+	return bpf_prog_load(BPF_PROG_TYPE_CGROUP_SOCK, prog, sizeof(prog),
+			     "GPL", bpf_log_buf, sizeof(bpf_log_buf));
+}
+
+static int vrf_configure_cgroup(const char *path, int ifindex)
+{
+	int rc = -1, cg_fd, prog_fd = -1;
+
+	cg_fd = open(path, O_DIRECTORY | O_RDONLY);
+	if (cg_fd < 0) {
+		fprintf(stderr,
+			"Failed to open cgroup path: '%s'\n",
+			strerror(errno));
+		goto out;
+	}
+
+	/*
+	 * Load bpf program into kernel and attach to cgroup to affect
+	 * socket creates
+	 */
+	prog_fd = prog_load(ifindex);
+	if (prog_fd < 0) {
+		fprintf(stderr, "Failed to load BPF prog: '%s'\n",
+			strerror(errno));
+
+		if (errno != EPERM) {
+			fprintf(stderr,
+				"Kernel compiled with CGROUP_BPF enabled?\n");
+		}
+		goto out;
+	}
+
+	if (bpf_prog_attach_fd(prog_fd, cg_fd, BPF_CGROUP_INET_SOCK_CREATE)) {
+		fprintf(stderr, "Failed to attach prog to cgroup: '%s'\n",
+			strerror(errno));
+		goto out;
+	}
+
+	rc = 0;
+out:
+	close(cg_fd);
+	close(prog_fd);
+
+	return rc;
+}
+
+/* get base path for controller-less cgroup for a process.
+ * path returned does not include /vrf/NAME if it exists
+ */
+static int vrf_path(char *vpath, size_t len)
+{
+	char path[PATH_MAX];
+	char buf[4096];
+	char *vrf;
+	FILE *fp;
+
+	snprintf(path, sizeof(path), "/proc/%d/cgroup", getpid());
+	fp = fopen(path, "r");
+	if (!fp)
+		return -1;
+
+	vpath[0] = '\0';
+
+	while (fgets(buf, sizeof(buf), fp)) {
+		char *start, *nl;
+
+		start = strstr(buf, "::/");
+		if (!start)
+			continue;
+
+		/* advance past '::' */
+		start += 2;
+
+		nl = strchr(start, '\n');
+		if (nl)
+			*nl = '\0';
+
+		vrf = strstr(start, "/vrf");
+		if (vrf)
+			*vrf = '\0';
+
+		strlcpy(vpath, start, len);
+
+		/* if vrf path is just / then return nothing */
+		if (!strcmp(vpath, "/"))
+			vpath[0] = '\0';
+
+		break;
+	}
+
+	fclose(fp);
+
+	return 0;
+}
+
+static int vrf_switch(const char *name)
+{
+	char path[PATH_MAX], *mnt, pid[16];
+	char vpath[PATH_MAX], netns[256];
+	int ifindex = 0;
+	int rc = -1, len, fd = -1;
+
+	if (strcmp(name, "default")) {
+		ifindex = name_is_vrf(name);
+		if (!ifindex) {
+			fprintf(stderr, "Invalid VRF name\n");
+			return -1;
+		}
+	}
+
+	mnt = find_cgroup2_mount();
+	if (!mnt)
+		return -1;
+
+	/* -1 on length to add '/' to the end */
+	if (ipvrf_get_netns(netns, sizeof(netns) - 1) < 0)
+		goto out;
+
+	if (vrf_path(vpath, sizeof(vpath)) < 0) {
+		fprintf(stderr, "Failed to get base cgroup path: %s\n",
+			strerror(errno));
+		goto out;
+	}
+
+	/* if path already ends in netns then don't add it again */
+	if (*netns != '\0') {
+		char *pdir = strrchr(vpath, '/');
+
+		if (!pdir)
+			pdir = vpath;
+		else
+			pdir++;
+
+		if (strcmp(pdir, netns) == 0)
+			*pdir = '\0';
+
+		strcat(netns, "/");
+	}
+
+	/* path to cgroup; make sure buffer has room to cat "/cgroup.procs"
+	 * to the end of the path
+	 */
+	len = snprintf(path, sizeof(path) - sizeof(CGRP_PROC_FILE),
+		       "%s%s/%svrf/%s",
+		       mnt, vpath, netns, ifindex ? name : "");
+	if (len > sizeof(path) - sizeof(CGRP_PROC_FILE)) {
+		fprintf(stderr, "Invalid path to cgroup2 mount\n");
+		goto out;
+	}
+
+	if (make_path(path, 0755)) {
+		fprintf(stderr, "Failed to setup vrf cgroup2 directory\n");
+		goto out;
+	}
+
+	if (ifindex && vrf_configure_cgroup(path, ifindex))
+		goto out;
+
+	/*
+	 * write pid to cgroup.procs making process part of cgroup
+	 */
+	strcat(path, CGRP_PROC_FILE);
+	fd = open(path, O_RDWR | O_APPEND);
+	if (fd < 0) {
+		fprintf(stderr, "Failed to open cgroups.procs file: %s.\n",
+			strerror(errno));
+		goto out;
+	}
+
+	snprintf(pid, sizeof(pid), "%d", getpid());
+	if (write(fd, pid, strlen(pid)) < 0) {
+		fprintf(stderr, "Failed to join cgroup\n");
+		goto out2;
+	}
+
+	rc = 0;
+out2:
+	close(fd);
+out:
+	free(mnt);
+
+	drop_cap();
+
+	return rc;
+}
+
+static int do_switch(void *arg)
+{
+	char *vrf = arg;
+
+	return vrf_switch(vrf);
+}
+
+static int ipvrf_exec(int argc, char **argv)
+{
+	if (argc < 1) {
+		fprintf(stderr, "No VRF name specified\n");
+		return -1;
+	}
+	if (argc < 2) {
+		fprintf(stderr, "No command specified\n");
+		return -1;
+	}
+
+	return -cmd_exec(argv[1], argv + 1, !!batch_mode, do_switch, argv[0]);
+}
+
+/* reset VRF association of current process to default VRF;
+ * used by netns_exec
+ */
+void vrf_reset(void)
+{
+	char vrf[32];
+
+	if (vrf_identify(getpid(), vrf, sizeof(vrf)) ||
+	    (vrf[0] == '\0'))
+		return;
+
+	vrf_switch("default");
+}
+
+static int ipvrf_filter_req(struct nlmsghdr *nlh, int reqlen)
+{
+	struct rtattr *linkinfo;
+	int err;
+
+	if (vrf_filter.kind) {
+		linkinfo = addattr_nest(nlh, reqlen, IFLA_LINKINFO);
+
+		err = addattr_l(nlh, reqlen, IFLA_INFO_KIND, vrf_filter.kind,
+				strlen(vrf_filter.kind));
+		if (err)
+			return err;
+
+		addattr_nest_end(nlh, linkinfo);
+	}
+
+	return 0;
+}
+
+/* input arg is linkinfo */
+static __u32 vrf_table_linkinfo(struct rtattr *li[])
+{
+	struct rtattr *attr[IFLA_VRF_MAX + 1];
+
+	if (li[IFLA_INFO_DATA]) {
+		parse_rtattr_nested(attr, IFLA_VRF_MAX, li[IFLA_INFO_DATA]);
+
+		if (attr[IFLA_VRF_TABLE])
+			return rta_getattr_u32(attr[IFLA_VRF_TABLE]);
+	}
+
+	return 0;
+}
+
+static int ipvrf_print(struct nlmsghdr *n)
+{
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_MAX+1];
+	struct rtattr *li[IFLA_INFO_MAX+1];
+	int len = n->nlmsg_len;
+	const char *name;
+	__u32 tb_id;
+
+	len -= NLMSG_LENGTH(sizeof(*ifi));
+	if (len < 0)
+		return 0;
+
+	if (vrf_filter.ifindex && vrf_filter.ifindex != ifi->ifi_index)
+		return 0;
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+	/* kernel does not support filter by master device */
+	if (tb[IFLA_MASTER]) {
+		int master = *(int *)RTA_DATA(tb[IFLA_MASTER]);
+
+		if (vrf_filter.master && master != vrf_filter.master)
+			return 0;
+	}
+
+	if (!tb[IFLA_IFNAME]) {
+		fprintf(stderr,
+			"BUG: device with ifindex %d has nil ifname\n",
+			ifi->ifi_index);
+		return 0;
+	}
+	name = rta_getattr_str(tb[IFLA_IFNAME]);
+
+	/* missing LINKINFO means not VRF. e.g., kernel does not
+	 * support filtering on kind, so userspace needs to handle
+	 */
+	if (!tb[IFLA_LINKINFO])
+		return 0;
+
+	parse_rtattr_nested(li, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+	if (!li[IFLA_INFO_KIND])
+		return 0;
+
+	if (strcmp(RTA_DATA(li[IFLA_INFO_KIND]), "vrf"))
+		return 0;
+
+	tb_id = vrf_table_linkinfo(li);
+	if (!tb_id) {
+		fprintf(stderr,
+			"BUG: VRF %s is missing table id\n", name);
+		return 0;
+	}
+
+	printf("%-16s %5u", name, tb_id);
+
+	printf("\n");
+	return 1;
+}
+
+static int ipvrf_show(int argc, char **argv)
+{
+	struct nlmsg_chain linfo = { NULL, NULL};
+	int rc = 0;
+
+	vrf_filter.kind = "vrf";
+
+	if (argc > 1)
+		usage();
+
+	if (argc == 1) {
+		__u32 tb_id;
+
+		tb_id = ipvrf_get_table(argv[0]);
+		if (!tb_id) {
+			fprintf(stderr, "Invalid VRF\n");
+			return 1;
+		}
+		printf("%s %u\n", argv[0], tb_id);
+		return 0;
+	}
+
+	if (ip_link_list(ipvrf_filter_req, &linfo) == 0) {
+		struct nlmsg_list *l;
+		unsigned nvrf = 0;
+		int n;
+
+		n = printf("%-16s  %5s\n", "Name", "Table");
+		printf("%.*s\n", n-1, "-----------------------");
+		for (l = linfo.head; l; l = l->next)
+			nvrf += ipvrf_print(&l->h);
+
+		if (!nvrf)
+			printf("No VRF has been configured\n");
+	} else
+		rc = 1;
+
+	free_nlmsg_chain(&linfo);
+
+	return rc;
+}
+
+int do_ipvrf(int argc, char **argv)
+{
+	if (argc == 0)
+		return ipvrf_show(0, NULL);
+
+	if (matches(*argv, "identify") == 0)
+		return ipvrf_identify(argc-1, argv+1);
+
+	if (matches(*argv, "pids") == 0)
+		return ipvrf_pids(argc-1, argv+1);
+
+	if (matches(*argv, "exec") == 0)
+		return ipvrf_exec(argc-1, argv+1);
+
+	if (matches(*argv, "show") == 0 ||
+	    matches(*argv, "lst") == 0 ||
+	    matches(*argv, "list") == 0)
+		return ipvrf_show(argc-1, argv+1);
+
+	if (matches(*argv, "help") == 0)
+		usage();
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip vrf help\".\n",
+		*argv);
+
+	exit(-1);
+}
diff --git a/ip/ipxfrm.c b/ip/ipxfrm.c
new file mode 100644
index 0000000..32f5609
--- /dev/null
+++ b/ip/ipxfrm.c
@@ -0,0 +1,1552 @@
+/* $USAGI: $ */
+
+/*
+ * Copyright (C)2004 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 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, see <http://www.gnu.org/licenses>.
+ */
+/*
+ * based on ip.c, iproute.c
+ */
+/*
+ * Authors:
+ *	Masahide NAKAMURA @USAGI
+ */
+
+#include <alloca.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <netdb.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include "utils.h"
+#include "xfrm.h"
+#include "ip_common.h"
+
+#define STRBUF_SIZE	(128)
+
+struct xfrm_filter filter;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip xfrm XFRM-OBJECT { COMMAND | help }\n"
+		"where  XFRM-OBJECT := state | policy | monitor\n");
+	exit(-1);
+}
+
+/* This is based on utils.c(inet_addr_match) */
+int xfrm_addr_match(xfrm_address_t *x1, xfrm_address_t *x2, int bits)
+{
+	__u32 *a1 = (__u32 *)x1;
+	__u32 *a2 = (__u32 *)x2;
+	int words = bits >> 0x05;
+
+	bits &= 0x1f;
+
+	if (words)
+		if (memcmp(a1, a2, words << 2))
+			return -1;
+
+	if (bits) {
+		__u32 w1, w2;
+		__u32 mask;
+
+		w1 = a1[words];
+		w2 = a2[words];
+
+		mask = htonl((0xffffffff) << (0x20 - bits));
+
+		if ((w1 ^ w2) & mask)
+			return 1;
+	}
+
+	return 0;
+}
+
+int xfrm_xfrmproto_is_ipsec(__u8 proto)
+{
+	return (proto ==  IPPROTO_ESP ||
+		proto ==  IPPROTO_AH  ||
+		proto ==  IPPROTO_COMP);
+}
+
+int xfrm_xfrmproto_is_ro(__u8 proto)
+{
+	return (proto ==  IPPROTO_ROUTING ||
+		proto ==  IPPROTO_DSTOPTS);
+}
+
+struct typeent {
+	const char *t_name;
+	int t_type;
+};
+
+static const struct typeent xfrmproto_types[] = {
+	{ "esp", IPPROTO_ESP }, { "ah", IPPROTO_AH }, { "comp", IPPROTO_COMP },
+	{ "route2", IPPROTO_ROUTING }, { "hao", IPPROTO_DSTOPTS },
+	{ "ipsec-any", IPSEC_PROTO_ANY },
+	{ NULL, -1 }
+};
+
+int xfrm_xfrmproto_getbyname(char *name)
+{
+	int i;
+
+	for (i = 0; ; i++) {
+		const struct typeent *t = &xfrmproto_types[i];
+
+		if (!t->t_name || t->t_type == -1)
+			break;
+
+		if (strcmp(t->t_name, name) == 0)
+			return t->t_type;
+	}
+
+	return -1;
+}
+
+const char *strxf_xfrmproto(__u8 proto)
+{
+	static char str[16];
+	int i;
+
+	for (i = 0; ; i++) {
+		const struct typeent *t = &xfrmproto_types[i];
+
+		if (!t->t_name || t->t_type == -1)
+			break;
+
+		if (t->t_type == proto)
+			return t->t_name;
+	}
+
+	sprintf(str, "%u", proto);
+	return str;
+}
+
+static const struct typeent algo_types[] = {
+	{ "enc", XFRMA_ALG_CRYPT }, { "auth", XFRMA_ALG_AUTH },
+	{ "comp", XFRMA_ALG_COMP }, { "aead", XFRMA_ALG_AEAD },
+	{ "auth-trunc", XFRMA_ALG_AUTH_TRUNC },
+	{ NULL, -1 }
+};
+
+int xfrm_algotype_getbyname(char *name)
+{
+	int i;
+
+	for (i = 0; ; i++) {
+		const struct typeent *t = &algo_types[i];
+
+		if (!t->t_name || t->t_type == -1)
+			break;
+
+		if (strcmp(t->t_name, name) == 0)
+			return t->t_type;
+	}
+
+	return -1;
+}
+
+const char *strxf_algotype(int type)
+{
+	static char str[32];
+	int i;
+
+	for (i = 0; ; i++) {
+		const struct typeent *t = &algo_types[i];
+
+		if (!t->t_name || t->t_type == -1)
+			break;
+
+		if (t->t_type == type)
+			return t->t_name;
+	}
+
+	sprintf(str, "%d", type);
+	return str;
+}
+
+static const char *strxf_mask8(__u8 mask)
+{
+	static char str[16];
+	const int sn = sizeof(mask) * 8 - 1;
+	__u8 b;
+	int i = 0;
+
+	for (b = (1 << sn); b > 0; b >>= 1)
+		str[i++] = ((b & mask) ? '1' : '0');
+	str[i] = '\0';
+
+	return str;
+}
+
+const char *strxf_mask32(__u32 mask)
+{
+	static char str[16];
+
+	sprintf(str, "%.8x", mask);
+
+	return str;
+}
+
+static const char *strxf_share(__u8 share)
+{
+	static char str[32];
+
+	switch (share) {
+	case XFRM_SHARE_ANY:
+		strcpy(str, "any");
+		break;
+	case XFRM_SHARE_SESSION:
+		strcpy(str, "session");
+		break;
+	case XFRM_SHARE_USER:
+		strcpy(str, "user");
+		break;
+	case XFRM_SHARE_UNIQUE:
+		strcpy(str, "unique");
+		break;
+	default:
+		sprintf(str, "%u", share);
+		break;
+	}
+
+	return str;
+}
+
+const char *strxf_proto(__u8 proto)
+{
+	static char buf[32];
+	struct protoent *pp;
+	const char *p;
+
+	pp = getprotobynumber(proto);
+	if (pp)
+		p = pp->p_name;
+	else {
+		sprintf(buf, "%u", proto);
+		p = buf;
+	}
+
+	return p;
+}
+
+const char *strxf_ptype(__u8 ptype)
+{
+	static char str[16];
+
+	switch (ptype) {
+	case XFRM_POLICY_TYPE_MAIN:
+		strcpy(str, "main");
+		break;
+	case XFRM_POLICY_TYPE_SUB:
+		strcpy(str, "sub");
+		break;
+	default:
+		sprintf(str, "%u", ptype);
+		break;
+	}
+
+	return str;
+}
+
+static void xfrm_id_info_print(xfrm_address_t *saddr, struct xfrm_id *id,
+			__u8 mode, __u32 reqid, __u16 family, int force_spi,
+			FILE *fp, const char *prefix, const char *title)
+{
+	if (title)
+		fputs(title, fp);
+
+	fprintf(fp, "src %s ", rt_addr_n2a(family, sizeof(*saddr), saddr));
+	fprintf(fp, "dst %s", rt_addr_n2a(family, sizeof(id->daddr), &id->daddr));
+	fprintf(fp, "%s", _SL_);
+
+	if (prefix)
+		fputs(prefix, fp);
+	fprintf(fp, "\t");
+
+	fprintf(fp, "proto %s ", strxf_xfrmproto(id->proto));
+
+	if (show_stats > 0 || force_spi || id->spi) {
+		__u32 spi = ntohl(id->spi);
+
+		fprintf(fp, "spi 0x%08x", spi);
+		if (show_stats > 0)
+			fprintf(fp, "(%u)", spi);
+		fprintf(fp, " ");
+	}
+
+	fprintf(fp, "reqid %u", reqid);
+	if (show_stats > 0)
+		fprintf(fp, "(0x%08x)", reqid);
+	fprintf(fp, " ");
+
+	fprintf(fp, "mode ");
+	switch (mode) {
+	case XFRM_MODE_TRANSPORT:
+		fprintf(fp, "transport");
+		break;
+	case XFRM_MODE_TUNNEL:
+		fprintf(fp, "tunnel");
+		break;
+	case XFRM_MODE_ROUTEOPTIMIZATION:
+		fprintf(fp, "ro");
+		break;
+	case XFRM_MODE_IN_TRIGGER:
+		fprintf(fp, "in_trigger");
+		break;
+	case XFRM_MODE_BEET:
+		fprintf(fp, "beet");
+		break;
+	default:
+		fprintf(fp, "%u", mode);
+		break;
+	}
+	fprintf(fp, "%s", _SL_);
+}
+
+static const char *strxf_limit(__u64 limit)
+{
+	static char str[32];
+
+	if (limit == XFRM_INF)
+		strcpy(str, "(INF)");
+	else
+		sprintf(str, "%llu", (unsigned long long) limit);
+
+	return str;
+}
+
+static void xfrm_stats_print(struct xfrm_stats *s, FILE *fp,
+			     const char *prefix)
+{
+	if (prefix)
+		fputs(prefix, fp);
+	fprintf(fp, "stats:%s", _SL_);
+
+	if (prefix)
+		fputs(prefix, fp);
+	fprintf(fp, "  replay-window %u replay %u failed %u%s",
+		s->replay_window, s->replay, s->integrity_failed, _SL_);
+}
+
+static const char *strxf_time(__u64 time)
+{
+	static char str[32];
+
+	if (time == 0)
+		strcpy(str, "-");
+	else {
+		time_t t;
+		struct tm *tp;
+
+		/* XXX: treat time in the same manner of kernel's
+		 * net/xfrm/xfrm_{user,state}.c
+		 */
+		t = (long)time;
+		tp = localtime(&t);
+
+		strftime(str, sizeof(str), "%Y-%m-%d %T", tp);
+	}
+
+	return str;
+}
+
+static void xfrm_lifetime_print(struct xfrm_lifetime_cfg *cfg,
+			 struct xfrm_lifetime_cur *cur,
+			 FILE *fp, const char *prefix)
+{
+	if (cfg) {
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "lifetime config:%s", _SL_);
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "  limit: soft %s(bytes),",
+			strxf_limit(cfg->soft_byte_limit));
+		fprintf(fp, " hard %s(bytes)%s",
+			strxf_limit(cfg->hard_byte_limit), _SL_);
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "  limit: soft %s(packets),",
+			strxf_limit(cfg->soft_packet_limit));
+		fprintf(fp, " hard %s(packets)%s",
+			strxf_limit(cfg->hard_packet_limit), _SL_);
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "  expire add: soft %llu(sec), hard %llu(sec)%s",
+			(unsigned long long) cfg->soft_add_expires_seconds,
+			(unsigned long long) cfg->hard_add_expires_seconds,
+			_SL_);
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "  expire use: soft %llu(sec), hard %llu(sec)%s",
+			(unsigned long long) cfg->soft_use_expires_seconds,
+			(unsigned long long) cfg->hard_use_expires_seconds,
+			_SL_);
+	}
+	if (cur) {
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "lifetime current:%s", _SL_);
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "  %llu(bytes), %llu(packets)%s",
+			(unsigned long long) cur->bytes,
+			(unsigned long long) cur->packets,
+			 _SL_);
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "  add %s ", strxf_time(cur->add_time));
+		fprintf(fp, "use %s%s", strxf_time(cur->use_time), _SL_);
+	}
+}
+
+void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
+			 FILE *fp, const char *prefix)
+{
+	__u16 f;
+
+	f = sel->family;
+	if (f == AF_UNSPEC)
+		f = family;
+	if (f == AF_UNSPEC)
+		f = preferred_family;
+
+	if (prefix)
+		fputs(prefix, fp);
+
+	fprintf(fp, "src %s/%u ",
+		rt_addr_n2a(f, sizeof(sel->saddr), &sel->saddr),
+		sel->prefixlen_s);
+
+	fprintf(fp, "dst %s/%u ",
+		rt_addr_n2a(f, sizeof(sel->daddr), &sel->daddr),
+		sel->prefixlen_d);
+
+	if (sel->proto)
+		fprintf(fp, "proto %s ", strxf_proto(sel->proto));
+	switch (sel->proto) {
+	case IPPROTO_TCP:
+	case IPPROTO_UDP:
+	case IPPROTO_SCTP:
+	case IPPROTO_DCCP:
+	default: /* XXX */
+		if (sel->sport_mask)
+			fprintf(fp, "sport %u ", ntohs(sel->sport));
+		if (sel->dport_mask)
+			fprintf(fp, "dport %u ", ntohs(sel->dport));
+		break;
+	case IPPROTO_ICMP:
+	case IPPROTO_ICMPV6:
+		/* type/code is stored at sport/dport in selector */
+		if (sel->sport_mask)
+			fprintf(fp, "type %u ", ntohs(sel->sport));
+		if (sel->dport_mask)
+			fprintf(fp, "code %u ", ntohs(sel->dport));
+		break;
+	case IPPROTO_GRE:
+		if (sel->sport_mask || sel->dport_mask)
+			fprintf(fp, "key %u ",
+				(((__u32)ntohs(sel->sport)) << 16) +
+				ntohs(sel->dport));
+		break;
+	case IPPROTO_MH:
+		if (sel->sport_mask)
+			fprintf(fp, "type %u ", ntohs(sel->sport));
+		if (sel->dport_mask) {
+			if (show_stats > 0)
+				fprintf(fp, "(dport) 0x%.4x ", sel->dport);
+		}
+		break;
+	}
+
+	if (sel->ifindex > 0)
+		fprintf(fp, "dev %s ", ll_index_to_name(sel->ifindex));
+
+	if (show_stats > 0)
+		fprintf(fp, "uid %u", sel->user);
+
+	fprintf(fp, "%s", _SL_);
+}
+
+static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
+			      FILE *fp, const char *prefix, int newline,
+			      bool nokeys)
+{
+	int keylen;
+	int i;
+
+	if (prefix)
+		fputs(prefix, fp);
+
+	fprintf(fp, "%s ", strxf_algotype(type));
+
+	if (len < sizeof(*algo)) {
+		fprintf(fp, "(ERROR truncated)");
+		goto fin;
+	}
+	len -= sizeof(*algo);
+
+	fprintf(fp, "%s ", algo->alg_name);
+
+	keylen = algo->alg_key_len / 8;
+	if (len < keylen) {
+		fprintf(fp, "(ERROR truncated)");
+		goto fin;
+	}
+
+	if (nokeys)
+		fprintf(fp, "<<Keys hidden>>");
+	else if (keylen > 0) {
+		fprintf(fp, "0x");
+		for (i = 0; i < keylen; i++)
+			fprintf(fp, "%.2x", (unsigned char)algo->alg_key[i]);
+
+		if (show_stats > 0)
+			fprintf(fp, " (%d bits)", algo->alg_key_len);
+	}
+
+ fin:
+	if (newline)
+		fprintf(fp, "%s", _SL_);
+}
+
+static inline void xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
+				   FILE *fp, const char *prefix, bool nokeys)
+{
+	return __xfrm_algo_print(algo, type, len, fp, prefix, 1, nokeys);
+}
+
+static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
+			    FILE *fp, const char *prefix, bool nokeys)
+{
+	struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
+
+	memcpy(base_algo->alg_name, algo->alg_name, sizeof(base_algo->alg_name));
+	base_algo->alg_key_len = algo->alg_key_len;
+	memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
+
+	__xfrm_algo_print(base_algo, XFRMA_ALG_AEAD, len, fp, prefix, 0,
+			  nokeys);
+
+	fprintf(fp, " %d", algo->alg_icv_len);
+
+	fprintf(fp, "%s", _SL_);
+}
+
+static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
+				  FILE *fp, const char *prefix, bool nokeys)
+{
+	struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
+
+	memcpy(base_algo->alg_name, algo->alg_name, sizeof(base_algo->alg_name));
+	base_algo->alg_key_len = algo->alg_key_len;
+	memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
+
+	__xfrm_algo_print(base_algo, XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0,
+			  nokeys);
+
+	fprintf(fp, " %d", algo->alg_trunc_len);
+
+	fprintf(fp, "%s", _SL_);
+}
+
+static void xfrm_tmpl_print(struct xfrm_user_tmpl *tmpls, int len,
+			    FILE *fp, const char *prefix)
+{
+	int ntmpls = len / sizeof(struct xfrm_user_tmpl);
+	int i;
+
+	if (ntmpls <= 0) {
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "(ERROR \"tmpl\" truncated)");
+		fprintf(fp, "%s", _SL_);
+		return;
+	}
+
+	for (i = 0; i < ntmpls; i++) {
+		struct xfrm_user_tmpl *tmpl = &tmpls[i];
+
+		if (prefix)
+			fputs(prefix, fp);
+
+		xfrm_id_info_print(&tmpl->saddr, &tmpl->id, tmpl->mode,
+				   tmpl->reqid, tmpl->family, 0, fp, prefix, "tmpl ");
+
+		if (show_stats > 0 || tmpl->optional) {
+			if (prefix)
+				fputs(prefix, fp);
+			fprintf(fp, "\t");
+			switch (tmpl->optional) {
+			case 0:
+				if (show_stats > 0)
+					fprintf(fp, "level required ");
+				break;
+			case 1:
+				fprintf(fp, "level use ");
+				break;
+			default:
+				fprintf(fp, "level %u ", tmpl->optional);
+				break;
+			}
+
+			if (show_stats > 0)
+				fprintf(fp, "share %s ", strxf_share(tmpl->share));
+
+			fprintf(fp, "%s", _SL_);
+		}
+
+		if (show_stats > 0) {
+			if (prefix)
+				fputs(prefix, fp);
+			fprintf(fp, "\t");
+			fprintf(fp, "%s-mask %s ",
+				strxf_algotype(XFRMA_ALG_CRYPT),
+				strxf_mask32(tmpl->ealgos));
+			fprintf(fp, "%s-mask %s ",
+				strxf_algotype(XFRMA_ALG_AUTH),
+				strxf_mask32(tmpl->aalgos));
+			fprintf(fp, "%s-mask %s",
+				strxf_algotype(XFRMA_ALG_COMP),
+				strxf_mask32(tmpl->calgos));
+
+			fprintf(fp, "%s", _SL_);
+		}
+	}
+}
+
+static void xfrm_output_mark_print(struct rtattr *tb[], FILE *fp)
+{
+	__u32 output_mark = rta_getattr_u32(tb[XFRMA_OUTPUT_MARK]);
+
+	fprintf(fp, "output-mark 0x%x", output_mark);
+}
+
+int xfrm_parse_mark(struct xfrm_mark *mark, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+
+	NEXT_ARG();
+	if (get_u32(&mark->v, *argv, 0)) {
+		invarg("MARK value is invalid\n", *argv);
+	}
+	if (argc > 1)
+		NEXT_ARG();
+	else { /* last entry on parse line */
+		mark->m = 0xffffffff;
+		goto done;
+	}
+
+	if (strcmp(*argv, "mask") == 0) {
+		NEXT_ARG();
+		if (get_u32(&mark->m, *argv, 0)) {
+			invarg("MASK value is invalid\n", *argv);
+		}
+	} else {
+		mark->m = 0xffffffff;
+		PREV_ARG();
+	}
+
+done:
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
+		      FILE *fp, const char *prefix, bool nokeys)
+{
+	if (tb[XFRMA_MARK]) {
+		struct rtattr *rta = tb[XFRMA_MARK];
+		struct xfrm_mark *m = RTA_DATA(rta);
+
+		fprintf(fp, "\tmark %#x/%#x ", m->v, m->m);
+
+		if (tb[XFRMA_OUTPUT_MARK])
+			xfrm_output_mark_print(tb, fp);
+		fprintf(fp, "%s", _SL_);
+	} else if (tb[XFRMA_OUTPUT_MARK]) {
+		fprintf(fp, "\t");
+
+		xfrm_output_mark_print(tb, fp);
+		fprintf(fp, "%s", _SL_);
+	}
+
+	if (tb[XFRMA_ALG_AUTH] && !tb[XFRMA_ALG_AUTH_TRUNC]) {
+		struct rtattr *rta = tb[XFRMA_ALG_AUTH];
+
+		xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_AUTH, RTA_PAYLOAD(rta),
+				fp, prefix, nokeys);
+	}
+
+	if (tb[XFRMA_ALG_AUTH_TRUNC]) {
+		struct rtattr *rta = tb[XFRMA_ALG_AUTH_TRUNC];
+
+		xfrm_auth_trunc_print(RTA_DATA(rta), RTA_PAYLOAD(rta), fp,
+				      prefix, nokeys);
+	}
+
+	if (tb[XFRMA_ALG_AEAD]) {
+		struct rtattr *rta = tb[XFRMA_ALG_AEAD];
+
+		xfrm_aead_print(RTA_DATA(rta), RTA_PAYLOAD(rta), fp, prefix,
+				nokeys);
+	}
+
+	if (tb[XFRMA_ALG_CRYPT]) {
+		struct rtattr *rta = tb[XFRMA_ALG_CRYPT];
+
+		xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_CRYPT,
+				RTA_PAYLOAD(rta), fp, prefix, nokeys);
+	}
+
+	if (tb[XFRMA_ALG_COMP]) {
+		struct rtattr *rta = tb[XFRMA_ALG_COMP];
+
+		xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_COMP, RTA_PAYLOAD(rta),
+				fp, prefix, nokeys);
+	}
+
+	if (tb[XFRMA_ENCAP]) {
+		struct xfrm_encap_tmpl *e;
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "encap ");
+
+		if (RTA_PAYLOAD(tb[XFRMA_ENCAP]) < sizeof(*e)) {
+			fprintf(fp, "(ERROR truncated)");
+			fprintf(fp, "%s", _SL_);
+			return;
+		}
+		e = RTA_DATA(tb[XFRMA_ENCAP]);
+
+		fprintf(fp, "type ");
+		switch (e->encap_type) {
+		case 1:
+			fprintf(fp, "espinudp-nonike ");
+			break;
+		case 2:
+			fprintf(fp, "espinudp ");
+			break;
+		default:
+			fprintf(fp, "%u ", e->encap_type);
+			break;
+		}
+		fprintf(fp, "sport %u ", ntohs(e->encap_sport));
+		fprintf(fp, "dport %u ", ntohs(e->encap_dport));
+
+		fprintf(fp, "addr %s",
+			rt_addr_n2a(family, sizeof(e->encap_oa), &e->encap_oa));
+		fprintf(fp, "%s", _SL_);
+	}
+
+	if (tb[XFRMA_TMPL]) {
+		struct rtattr *rta = tb[XFRMA_TMPL];
+
+		xfrm_tmpl_print(RTA_DATA(rta),
+				RTA_PAYLOAD(rta), fp, prefix);
+	}
+
+	if (tb[XFRMA_COADDR]) {
+		const xfrm_address_t *coa;
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "coa ");
+
+		coa = RTA_DATA(tb[XFRMA_COADDR]);
+		if (RTA_PAYLOAD(tb[XFRMA_COADDR]) < sizeof(*coa)) {
+			fprintf(fp, "(ERROR truncated)");
+			fprintf(fp, "%s", _SL_);
+			return;
+		}
+
+		fprintf(fp, "%s",
+			rt_addr_n2a(family, sizeof(*coa), coa));
+		fprintf(fp, "%s", _SL_);
+	}
+
+	if (tb[XFRMA_LASTUSED]) {
+		__u64 lastused;
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "lastused ");
+
+		if (RTA_PAYLOAD(tb[XFRMA_LASTUSED]) < sizeof(lastused)) {
+			fprintf(fp, "(ERROR truncated)");
+			fprintf(fp, "%s", _SL_);
+			return;
+		}
+
+		lastused = rta_getattr_u64(tb[XFRMA_LASTUSED]);
+
+		fprintf(fp, "%s", strxf_time(lastused));
+		fprintf(fp, "%s", _SL_);
+	}
+
+	if (tb[XFRMA_REPLAY_VAL]) {
+		struct xfrm_replay_state *replay;
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "anti-replay context: ");
+
+		if (RTA_PAYLOAD(tb[XFRMA_REPLAY_VAL]) < sizeof(*replay)) {
+			fprintf(fp, "(ERROR truncated)");
+			fprintf(fp, "%s", _SL_);
+			return;
+		}
+
+		replay = RTA_DATA(tb[XFRMA_REPLAY_VAL]);
+		fprintf(fp, "seq 0x%x, oseq 0x%x, bitmap 0x%08x",
+			replay->seq, replay->oseq, replay->bitmap);
+		fprintf(fp, "%s", _SL_);
+	}
+
+	if (tb[XFRMA_REPLAY_ESN_VAL]) {
+		struct xfrm_replay_state_esn *replay;
+		unsigned int i, j;
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "anti-replay esn context:");
+
+		if (RTA_PAYLOAD(tb[XFRMA_REPLAY_ESN_VAL]) < sizeof(*replay)) {
+			fprintf(fp, "(ERROR truncated)");
+			fprintf(fp, "%s", _SL_);
+			return;
+		}
+		fprintf(fp, "%s", _SL_);
+
+		replay = RTA_DATA(tb[XFRMA_REPLAY_ESN_VAL]);
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, " seq-hi 0x%x, seq 0x%x, oseq-hi 0x%0x, oseq 0x%0x",
+			replay->seq_hi, replay->seq, replay->oseq_hi,
+			replay->oseq);
+		fprintf(fp, "%s", _SL_);
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, " replay_window %u, bitmap-length %u",
+			replay->replay_window, replay->bmp_len);
+		for (i = replay->bmp_len, j = 0; i; i--) {
+			if (j++ % 8 == 0) {
+				fprintf(fp, "%s", _SL_);
+				if (prefix)
+					fputs(prefix, fp);
+				fprintf(fp, " ");
+			}
+			fprintf(fp, "%08x ", replay->bmp[i - 1]);
+		}
+		fprintf(fp, "%s", _SL_);
+	}
+	if (tb[XFRMA_OFFLOAD_DEV]) {
+		struct xfrm_user_offload *xuo;
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "crypto offload parameters: ");
+
+		if (RTA_PAYLOAD(tb[XFRMA_OFFLOAD_DEV]) < sizeof(*xuo)) {
+			fprintf(fp, "(ERROR truncated)");
+			fprintf(fp, "%s", _SL_);
+			return;
+		}
+
+		xuo = (struct xfrm_user_offload *)
+			RTA_DATA(tb[XFRMA_OFFLOAD_DEV]);
+		fprintf(fp, "dev %s dir %s", ll_index_to_name(xuo->ifindex),
+			(xuo->flags & XFRM_OFFLOAD_INBOUND) ? "in" : "out");
+		fprintf(fp, "%s", _SL_);
+	}
+	if (tb[XFRMA_IF_ID]) {
+		__u32 if_id = rta_getattr_u32(tb[XFRMA_IF_ID]);
+
+		if (prefix)
+			fputs(prefix, fp);
+		fprintf(fp, "if_id %#x", if_id);
+		fprintf(fp, "%s", _SL_);
+	}
+}
+
+static int xfrm_selector_iszero(struct xfrm_selector *s)
+{
+	struct xfrm_selector s0 = {};
+
+	return (memcmp(&s0, s, sizeof(s0)) == 0);
+}
+
+void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
+			    struct rtattr *tb[], FILE *fp, const char *prefix,
+			    const char *title, bool nokeys)
+{
+	char buf[STRBUF_SIZE] = {};
+	int force_spi = xfrm_xfrmproto_is_ipsec(xsinfo->id.proto);
+
+	xfrm_id_info_print(&xsinfo->saddr, &xsinfo->id, xsinfo->mode,
+			   xsinfo->reqid, xsinfo->family, force_spi, fp,
+			   prefix, title);
+
+	if (prefix)
+		strlcat(buf, prefix, sizeof(buf));
+	strlcat(buf, "\t", sizeof(buf));
+
+	fputs(buf, fp);
+	fprintf(fp, "replay-window %u ", xsinfo->replay_window);
+	if (show_stats > 0)
+		fprintf(fp, "seq 0x%08u ", xsinfo->seq);
+	if (show_stats > 0 || xsinfo->flags) {
+		__u8 flags = xsinfo->flags;
+
+		fprintf(fp, "flag ");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_NOECN, "noecn");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_DECAP_DSCP, "decap-dscp");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_NOPMTUDISC, "nopmtudisc");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_WILDRECV, "wildrecv");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_ICMP, "icmp");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_AF_UNSPEC, "af-unspec");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_ALIGN4, "align4");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_STATE_ESN, "esn");
+		if (flags)
+			fprintf(fp, "%x", flags);
+	}
+	if (show_stats > 0 && tb[XFRMA_SA_EXTRA_FLAGS]) {
+		__u32 extra_flags = rta_getattr_u32(tb[XFRMA_SA_EXTRA_FLAGS]);
+
+		fprintf(fp, "extra_flag ");
+		XFRM_FLAG_PRINT(fp, extra_flags,
+				XFRM_SA_XFLAG_DONT_ENCAP_DSCP,
+				"dont-encap-dscp");
+		if (extra_flags)
+			fprintf(fp, "%x", extra_flags);
+	}
+	if (show_stats > 0)
+		fprintf(fp, " (0x%s)", strxf_mask8(xsinfo->flags));
+	fprintf(fp, "%s", _SL_);
+
+	xfrm_xfrma_print(tb, xsinfo->family, fp, buf, nokeys);
+
+	if (!xfrm_selector_iszero(&xsinfo->sel)) {
+		char sbuf[STRBUF_SIZE];
+
+		memcpy(sbuf, buf, sizeof(sbuf));
+		strlcat(sbuf, "sel ", sizeof(sbuf));
+
+		xfrm_selector_print(&xsinfo->sel, xsinfo->family, fp, sbuf);
+	}
+
+	if (show_stats > 0) {
+		xfrm_lifetime_print(&xsinfo->lft, &xsinfo->curlft, fp, buf);
+		xfrm_stats_print(&xsinfo->stats, fp, buf);
+	}
+
+	if (tb[XFRMA_SEC_CTX]) {
+		struct xfrm_user_sec_ctx *sctx;
+
+		fprintf(fp, "\tsecurity context ");
+
+		if (RTA_PAYLOAD(tb[XFRMA_SEC_CTX]) < sizeof(*sctx))
+			fprintf(fp, "(ERROR truncated)");
+
+		sctx = RTA_DATA(tb[XFRMA_SEC_CTX]);
+
+		fprintf(fp, "%s %s", (char *)(sctx + 1), _SL_);
+	}
+
+}
+
+void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
+			    struct rtattr *tb[], FILE *fp, const char *prefix,
+			    const char *title)
+{
+	char buf[STRBUF_SIZE] = {};
+
+	xfrm_selector_print(&xpinfo->sel, preferred_family, fp, title);
+
+	if (tb[XFRMA_SEC_CTX]) {
+		struct xfrm_user_sec_ctx *sctx;
+
+		fprintf(fp, "\tsecurity context ");
+
+		if (RTA_PAYLOAD(tb[XFRMA_SEC_CTX]) < sizeof(*sctx))
+			fprintf(fp, "(ERROR truncated)");
+
+		sctx = RTA_DATA(tb[XFRMA_SEC_CTX]);
+
+		fprintf(fp, "%s ", (char *)(sctx + 1));
+		fprintf(fp, "%s", _SL_);
+	}
+
+	if (prefix)
+		strlcat(buf, prefix, sizeof(buf));
+	strlcat(buf, "\t", sizeof(buf));
+
+	fputs(buf, fp);
+	if (xpinfo->dir >= XFRM_POLICY_MAX) {
+		xpinfo->dir -= XFRM_POLICY_MAX;
+		fprintf(fp, "socket ");
+	} else
+		fprintf(fp, "dir ");
+
+	switch (xpinfo->dir) {
+	case XFRM_POLICY_IN:
+		fprintf(fp, "in");
+		break;
+	case XFRM_POLICY_OUT:
+		fprintf(fp, "out");
+		break;
+	case XFRM_POLICY_FWD:
+		fprintf(fp, "fwd");
+		break;
+	default:
+		fprintf(fp, "%u", xpinfo->dir);
+		break;
+	}
+	fprintf(fp, " ");
+
+	switch (xpinfo->action) {
+	case XFRM_POLICY_ALLOW:
+		if (show_stats > 0)
+			fprintf(fp, "action allow ");
+		break;
+	case XFRM_POLICY_BLOCK:
+		fprintf(fp, "action block ");
+		break;
+	default:
+		fprintf(fp, "action %u ", xpinfo->action);
+		break;
+	}
+
+	if (show_stats)
+		fprintf(fp, "index %u ", xpinfo->index);
+	fprintf(fp, "priority %u ", xpinfo->priority);
+
+	if (tb[XFRMA_POLICY_TYPE]) {
+		struct xfrm_userpolicy_type *upt;
+
+		fprintf(fp, "ptype ");
+
+		if (RTA_PAYLOAD(tb[XFRMA_POLICY_TYPE]) < sizeof(*upt))
+			fprintf(fp, "(ERROR truncated)");
+
+		upt = RTA_DATA(tb[XFRMA_POLICY_TYPE]);
+		fprintf(fp, "%s ", strxf_ptype(upt->type));
+	}
+
+	if (show_stats > 0)
+		fprintf(fp, "share %s ", strxf_share(xpinfo->share));
+
+	if (show_stats > 0 || xpinfo->flags) {
+		__u8 flags = xpinfo->flags;
+
+		fprintf(fp, "flag ");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_POLICY_LOCALOK, "localok");
+		XFRM_FLAG_PRINT(fp, flags, XFRM_POLICY_ICMP, "icmp");
+		if (flags)
+			fprintf(fp, "%x", flags);
+	}
+	if (show_stats > 0)
+		fprintf(fp, " (0x%s)", strxf_mask8(xpinfo->flags));
+	fprintf(fp, "%s", _SL_);
+
+	if (show_stats > 0)
+		xfrm_lifetime_print(&xpinfo->lft, &xpinfo->curlft, fp, buf);
+
+	xfrm_xfrma_print(tb, xpinfo->sel.family, fp, buf, false);
+}
+
+int xfrm_id_parse(xfrm_address_t *saddr, struct xfrm_id *id, __u16 *family,
+		  int loose, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	inet_prefix dst = {};
+	inet_prefix src = {};
+
+	while (1) {
+		if (strcmp(*argv, "src") == 0) {
+			NEXT_ARG();
+
+			get_prefix(&src, *argv, preferred_family);
+			if (src.family == AF_UNSPEC)
+				invarg("value after \"src\" has an unrecognized address family", *argv);
+			if (family)
+				*family = src.family;
+
+			memcpy(saddr, &src.data, sizeof(*saddr));
+
+			filter.id_src_mask = src.bitlen;
+
+		} else if (strcmp(*argv, "dst") == 0) {
+			NEXT_ARG();
+
+			get_prefix(&dst, *argv, preferred_family);
+			if (dst.family == AF_UNSPEC)
+				invarg("value after \"dst\" has an unrecognized address family", *argv);
+			if (family)
+				*family = dst.family;
+
+			memcpy(&id->daddr, &dst.data, sizeof(id->daddr));
+
+			filter.id_dst_mask = dst.bitlen;
+
+		} else if (strcmp(*argv, "proto") == 0) {
+			int ret;
+
+			NEXT_ARG();
+
+			ret = xfrm_xfrmproto_getbyname(*argv);
+			if (ret < 0)
+				invarg("XFRM-PROTO value is invalid", *argv);
+
+			id->proto = (__u8)ret;
+
+			filter.id_proto_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "spi") == 0) {
+			NEXT_ARG();
+			if (get_be32(&id->spi, *argv, 0))
+				invarg("SPI value is invalid", *argv);
+
+			filter.id_spi_mask = XFRM_FILTER_MASK_FULL;
+
+		} else {
+			PREV_ARG(); /* back track */
+			break;
+		}
+
+		if (!NEXT_ARG_OK())
+			break;
+		NEXT_ARG();
+	}
+
+	if (src.family && dst.family && (src.family != dst.family))
+		invarg("the same address family is required between values after \"src\" and \"dst\"", *argv);
+
+	if (id->spi && id->proto) {
+		if (xfrm_xfrmproto_is_ro(id->proto)) {
+			fprintf(stderr, "\"spi\" is invalid with XFRM-PROTO value \"%s\"\n",
+				strxf_xfrmproto(id->proto));
+			exit(1);
+		} else if (id->proto == IPPROTO_COMP && ntohl(id->spi) >= 0x10000) {
+			fprintf(stderr, "SPI value is too large with XFRM-PROTO value \"%s\"\n",
+				strxf_xfrmproto(id->proto));
+			exit(1);
+		}
+	}
+
+	if (loose == 0 && id->proto == 0)
+		missarg("XFRM-PROTO");
+	if (argc == *argcp)
+		missarg("ID");
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+int xfrm_mode_parse(__u8 *mode, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+
+	if (matches(*argv, "transport") == 0)
+		*mode = XFRM_MODE_TRANSPORT;
+	else if (matches(*argv, "tunnel") == 0)
+		*mode = XFRM_MODE_TUNNEL;
+	else if (matches(*argv, "ro") == 0)
+		*mode = XFRM_MODE_ROUTEOPTIMIZATION;
+	else if (matches(*argv, "in_trigger") == 0)
+		*mode = XFRM_MODE_IN_TRIGGER;
+	else if (matches(*argv, "beet") == 0)
+		*mode = XFRM_MODE_BEET;
+	else
+		invarg("MODE value is invalid", *argv);
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+int xfrm_encap_type_parse(__u16 *type, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+
+	if (strcmp(*argv, "espinudp-nonike") == 0)
+		*type = 1;
+	else if (strcmp(*argv, "espinudp") == 0)
+		*type = 2;
+	else
+		invarg("ENCAP-TYPE value is invalid", *argv);
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+/* NOTE: reqid is used by host-byte order */
+int xfrm_reqid_parse(__u32 *reqid, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+
+	if (get_u32(reqid, *argv, 0))
+		invarg("REQID value is invalid", *argv);
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static int xfrm_selector_upspec_parse(struct xfrm_selector *sel,
+				      int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	char *sportp = NULL;
+	char *dportp = NULL;
+	char *typep = NULL;
+	char *codep = NULL;
+	char *grekey = NULL;
+
+	while (1) {
+		if (strcmp(*argv, "proto") == 0) {
+			__u8 upspec;
+
+			NEXT_ARG();
+
+			if (strcmp(*argv, "any") == 0)
+				upspec = 0;
+			else {
+				struct protoent *pp;
+
+				pp = getprotobyname(*argv);
+				if (pp)
+					upspec = pp->p_proto;
+				else {
+					if (get_u8(&upspec, *argv, 0))
+						invarg("PROTO value is invalid", *argv);
+				}
+			}
+			sel->proto = upspec;
+
+			filter.upspec_proto_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "sport") == 0) {
+			sportp = *argv;
+
+			NEXT_ARG();
+
+			if (get_be16(&sel->sport, *argv, 0))
+				invarg("value after \"sport\" is invalid", *argv);
+			if (sel->sport)
+				sel->sport_mask = ~((__u16)0);
+
+			filter.upspec_sport_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "dport") == 0) {
+			dportp = *argv;
+
+			NEXT_ARG();
+
+			if (get_be16(&sel->dport, *argv, 0))
+				invarg("value after \"dport\" is invalid", *argv);
+			if (sel->dport)
+				sel->dport_mask = ~((__u16)0);
+
+			filter.upspec_dport_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "type") == 0) {
+			typep = *argv;
+
+			NEXT_ARG();
+
+			if (get_u16(&sel->sport, *argv, 0) ||
+			    (sel->sport & ~((__u16)0xff)))
+				invarg("value after \"type\" is invalid", *argv);
+			sel->sport = htons(sel->sport);
+			sel->sport_mask = ~((__u16)0);
+
+			filter.upspec_sport_mask = XFRM_FILTER_MASK_FULL;
+
+
+		} else if (strcmp(*argv, "code") == 0) {
+			codep = *argv;
+
+			NEXT_ARG();
+
+			if (get_u16(&sel->dport, *argv, 0) ||
+			    (sel->dport & ~((__u16)0xff)))
+				invarg("value after \"code\" is invalid", *argv);
+			sel->dport = htons(sel->dport);
+			sel->dport_mask = ~((__u16)0);
+
+			filter.upspec_dport_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "key") == 0) {
+			unsigned int uval;
+
+			grekey = *argv;
+
+			NEXT_ARG();
+
+			if (strchr(*argv, '.'))
+				uval = htonl(get_addr32(*argv));
+			else {
+				if (get_unsigned(&uval, *argv, 0) < 0) {
+					fprintf(stderr, "value after \"key\" is invalid\n");
+					exit(-1);
+				}
+			}
+
+			sel->sport = htons(uval >> 16);
+			sel->dport = htons(uval & 0xffff);
+			sel->sport_mask = ~((__u16)0);
+			sel->dport_mask = ~((__u16)0);
+
+			filter.upspec_dport_mask = XFRM_FILTER_MASK_FULL;
+
+		} else {
+			PREV_ARG(); /* back track */
+			break;
+		}
+
+		if (!NEXT_ARG_OK())
+			break;
+		NEXT_ARG();
+	}
+	if (argc == *argcp)
+		missarg("UPSPEC");
+	if (sportp || dportp) {
+		switch (sel->proto) {
+		case IPPROTO_TCP:
+		case IPPROTO_UDP:
+		case IPPROTO_SCTP:
+		case IPPROTO_DCCP:
+		case IPPROTO_IP: /* to allow shared SA for different protocols */
+			break;
+		default:
+			fprintf(stderr, "\"sport\" and \"dport\" are invalid with PROTO value \"%s\"\n", strxf_proto(sel->proto));
+			exit(1);
+		}
+	}
+	if (typep || codep) {
+		switch (sel->proto) {
+		case IPPROTO_ICMP:
+		case IPPROTO_ICMPV6:
+		case IPPROTO_MH:
+			break;
+		default:
+			fprintf(stderr, "\"type\" and \"code\" are invalid with PROTO value \"%s\"\n", strxf_proto(sel->proto));
+			exit(1);
+		}
+	}
+	if (grekey) {
+		switch (sel->proto) {
+		case IPPROTO_GRE:
+			break;
+		default:
+			fprintf(stderr, "\"key\" is invalid with PROTO value \"%s\"\n", strxf_proto(sel->proto));
+			exit(1);
+		}
+	}
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+int xfrm_selector_parse(struct xfrm_selector *sel, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	inet_prefix dst = {};
+	inet_prefix src = {};
+	char *upspecp = NULL;
+
+	while (1) {
+		if (strcmp(*argv, "src") == 0) {
+			NEXT_ARG();
+
+			get_prefix(&src, *argv, preferred_family);
+			if (src.family == AF_UNSPEC)
+				invarg("value after \"src\" has an unrecognized address family", *argv);
+			sel->family = src.family;
+
+			memcpy(&sel->saddr, &src.data, sizeof(sel->saddr));
+			sel->prefixlen_s = src.bitlen;
+
+			filter.sel_src_mask = src.bitlen;
+
+		} else if (strcmp(*argv, "dst") == 0) {
+			NEXT_ARG();
+
+			get_prefix(&dst, *argv, preferred_family);
+			if (dst.family == AF_UNSPEC)
+				invarg("value after \"dst\" has an unrecognized address family", *argv);
+			sel->family = dst.family;
+
+			memcpy(&sel->daddr, &dst.data, sizeof(sel->daddr));
+			sel->prefixlen_d = dst.bitlen;
+
+			filter.sel_dst_mask = dst.bitlen;
+
+		} else if (strcmp(*argv, "dev") == 0) {
+			int ifindex;
+
+			NEXT_ARG();
+
+			if (strcmp(*argv, "none") == 0)
+				ifindex = 0;
+			else {
+				ifindex = ll_name_to_index(*argv);
+				if (ifindex <= 0)
+					invarg("DEV value is invalid", *argv);
+			}
+			sel->ifindex = ifindex;
+
+			filter.sel_dev_mask = XFRM_FILTER_MASK_FULL;
+
+		} else {
+			if (upspecp) {
+				PREV_ARG(); /* back track */
+				break;
+			} else {
+				upspecp = *argv;
+				xfrm_selector_upspec_parse(sel, &argc, &argv);
+			}
+		}
+
+		if (!NEXT_ARG_OK())
+			break;
+
+		NEXT_ARG();
+	}
+
+	if (src.family && dst.family && (src.family != dst.family))
+		invarg("the same address family is required between values after \"src\" and \"dst\"", *argv);
+
+	if (argc == *argcp)
+		missarg("SELECTOR");
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+int xfrm_lifetime_cfg_parse(struct xfrm_lifetime_cfg *lft,
+			    int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int ret;
+
+	if (strcmp(*argv, "time-soft") == 0) {
+		NEXT_ARG();
+		ret = get_u64(&lft->soft_add_expires_seconds, *argv, 0);
+		if (ret)
+			invarg("value after \"time-soft\" is invalid", *argv);
+	} else if (strcmp(*argv, "time-hard") == 0) {
+		NEXT_ARG();
+		ret = get_u64(&lft->hard_add_expires_seconds, *argv, 0);
+		if (ret)
+			invarg("value after \"time-hard\" is invalid", *argv);
+	} else if (strcmp(*argv, "time-use-soft") == 0) {
+		NEXT_ARG();
+		ret = get_u64(&lft->soft_use_expires_seconds, *argv, 0);
+		if (ret)
+			invarg("value after \"time-use-soft\" is invalid", *argv);
+	} else if (strcmp(*argv, "time-use-hard") == 0) {
+		NEXT_ARG();
+		ret = get_u64(&lft->hard_use_expires_seconds, *argv, 0);
+		if (ret)
+			invarg("value after \"time-use-hard\" is invalid", *argv);
+	} else if (strcmp(*argv, "byte-soft") == 0) {
+		NEXT_ARG();
+		ret = get_u64(&lft->soft_byte_limit, *argv, 0);
+		if (ret)
+			invarg("value after \"byte-soft\" is invalid", *argv);
+	} else if (strcmp(*argv, "byte-hard") == 0) {
+		NEXT_ARG();
+		ret = get_u64(&lft->hard_byte_limit, *argv, 0);
+		if (ret)
+			invarg("value after \"byte-hard\" is invalid", *argv);
+	} else if (strcmp(*argv, "packet-soft") == 0) {
+		NEXT_ARG();
+		ret = get_u64(&lft->soft_packet_limit, *argv, 0);
+		if (ret)
+			invarg("value after \"packet-soft\" is invalid", *argv);
+	} else if (strcmp(*argv, "packet-hard") == 0) {
+		NEXT_ARG();
+		ret = get_u64(&lft->hard_packet_limit, *argv, 0);
+		if (ret)
+			invarg("value after \"packet-hard\" is invalid", *argv);
+	} else
+		invarg("LIMIT value is invalid", *argv);
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+int do_xfrm(int argc, char **argv)
+{
+	memset(&filter, 0, sizeof(filter));
+
+	if (argc < 1)
+		usage();
+
+	if (matches(*argv, "state") == 0 ||
+	    matches(*argv, "sa") == 0)
+		return do_xfrm_state(argc-1, argv+1);
+	else if (matches(*argv, "policy") == 0)
+		return do_xfrm_policy(argc-1, argv+1);
+	else if (matches(*argv, "monitor") == 0)
+		return do_xfrm_monitor(argc-1, argv+1);
+	else if (matches(*argv, "help") == 0) {
+		usage();
+		fprintf(stderr, "xfrm Object \"%s\" is unknown.\n", *argv);
+		exit(-1);
+	}
+	usage();
+}
diff --git a/ip/link_gre.c b/ip/link_gre.c
new file mode 100644
index 0000000..15beb73
--- /dev/null
+++ b/ip/link_gre.c
@@ -0,0 +1,571 @@
+/*
+ * link_gre.c	gre driver module
+ *
+ *		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.
+ *
+ * Authors:	Herbert Xu <herbert@gondor.apana.org.au>
+ *
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void gre_print_help(struct link_util *lu, int argc, char **argv, FILE *f)
+{
+	fprintf(f,
+		"Usage: ... %-9s	[ remote ADDR ]\n"
+		"			[ local ADDR ]\n"
+		"			[ [no][i|o]seq ]\n"
+		"			[ [i|o]key KEY | no[i|o]key ]\n"
+		"			[ [no][i|o]csum ]\n"
+		"			[ ttl TTL ]\n"
+		"			[ tos TOS ]\n"
+		"			[ [no]pmtudisc ]\n"
+		"			[ [no]ignore-df ]\n"
+		"			[ dev PHYS_DEV ]\n"
+		"			[ fwmark MARK ]\n"
+		"			[ external ]\n"
+		"			[ noencap ]\n"
+		"			[ encap { fou | gue | none } ]\n"
+		"			[ encap-sport PORT ]\n"
+		"			[ encap-dport PORT ]\n"
+		"			[ [no]encap-csum ]\n"
+		"			[ [no]encap-csum6 ]\n"
+		"			[ [no]encap-remcsum ]\n"
+		"			[ erspan_ver version ]\n"
+		"			[ erspan IDX ]\n"
+		"			[ erspan_dir { ingress | egress } ]\n"
+		"			[ erspan_hwid hwid ]\n"
+		"\n"
+		"Where:	ADDR := { IP_ADDRESS | any }\n"
+		"	TOS  := { NUMBER | inherit }\n"
+		"	TTL  := { 1..255 | inherit }\n"
+		"	KEY  := { DOTTED_QUAD | NUMBER }\n"
+		"	MARK := { 0x0..0xffffffff }\n",
+		lu->id);
+}
+
+static int gre_parse_opt(struct link_util *lu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETLINK,
+		.i.ifi_family = preferred_family,
+		.i.ifi_index = ifi->ifi_index,
+	};
+	struct nlmsghdr *answer;
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *greinfo[IFLA_GRE_MAX + 1];
+	int len;
+	__u16 iflags = 0;
+	__u16 oflags = 0;
+	__be32 ikey = 0;
+	__be32 okey = 0;
+	inet_prefix saddr, daddr;
+	__u8 pmtudisc = 1;
+	__u8 ignore_df = 0;
+	__u8 tos = 0;
+	__u8 ttl = 0;
+	__u32 link = 0;
+	__u16 encaptype = 0;
+	__u16 encapflags = 0;
+	__u16 encapsport = 0;
+	__u16 encapdport = 0;
+	__u8 metadata = 0;
+	__u32 fwmark = 0;
+	__u32 erspan_idx = 0;
+	__u8 erspan_ver = 0;
+	__u8 erspan_dir = 0;
+	__u16 erspan_hwid = 0;
+
+	inet_prefix_reset(&saddr);
+	inet_prefix_reset(&daddr);
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		const struct rtattr *rta;
+
+		if (rtnl_talk(&rth, &req.n, &answer) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = answer->nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(greinfo, IFLA_GRE_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		rta = greinfo[IFLA_GRE_LOCAL];
+		if (rta && get_addr_rta(&saddr, rta, AF_INET))
+			goto get_failed;
+
+		rta = greinfo[IFLA_GRE_REMOTE];
+		if (rta && get_addr_rta(&daddr, rta, AF_INET))
+			goto get_failed;
+
+		if (greinfo[IFLA_GRE_IKEY])
+			ikey = rta_getattr_u32(greinfo[IFLA_GRE_IKEY]);
+
+		if (greinfo[IFLA_GRE_OKEY])
+			okey = rta_getattr_u32(greinfo[IFLA_GRE_OKEY]);
+
+		if (greinfo[IFLA_GRE_IFLAGS])
+			iflags = rta_getattr_u16(greinfo[IFLA_GRE_IFLAGS]);
+
+		if (greinfo[IFLA_GRE_OFLAGS])
+			oflags = rta_getattr_u16(greinfo[IFLA_GRE_OFLAGS]);
+
+		if (greinfo[IFLA_GRE_PMTUDISC])
+			pmtudisc = rta_getattr_u8(
+				greinfo[IFLA_GRE_PMTUDISC]);
+
+		if (greinfo[IFLA_GRE_IGNORE_DF])
+			ignore_df =
+				!!rta_getattr_u8(greinfo[IFLA_GRE_IGNORE_DF]);
+
+		if (greinfo[IFLA_GRE_TOS])
+			tos = rta_getattr_u8(greinfo[IFLA_GRE_TOS]);
+
+		if (greinfo[IFLA_GRE_TTL])
+			ttl = rta_getattr_u8(greinfo[IFLA_GRE_TTL]);
+
+		if (greinfo[IFLA_GRE_LINK])
+			link = rta_getattr_u32(greinfo[IFLA_GRE_LINK]);
+
+		if (greinfo[IFLA_GRE_ENCAP_TYPE])
+			encaptype = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_TYPE]);
+
+		if (greinfo[IFLA_GRE_ENCAP_FLAGS])
+			encapflags = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_FLAGS]);
+
+		if (greinfo[IFLA_GRE_ENCAP_SPORT])
+			encapsport = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_SPORT]);
+
+		if (greinfo[IFLA_GRE_ENCAP_DPORT])
+			encapdport = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_DPORT]);
+
+		if (greinfo[IFLA_GRE_COLLECT_METADATA])
+			metadata = 1;
+
+		if (greinfo[IFLA_GRE_FWMARK])
+			fwmark = rta_getattr_u32(greinfo[IFLA_GRE_FWMARK]);
+
+		if (greinfo[IFLA_GRE_ERSPAN_INDEX])
+			erspan_idx = rta_getattr_u32(greinfo[IFLA_GRE_ERSPAN_INDEX]);
+
+		if (greinfo[IFLA_GRE_ERSPAN_VER])
+			erspan_ver = rta_getattr_u8(greinfo[IFLA_GRE_ERSPAN_VER]);
+
+		if (greinfo[IFLA_GRE_ERSPAN_DIR])
+			erspan_dir = rta_getattr_u8(greinfo[IFLA_GRE_ERSPAN_DIR]);
+
+		if (greinfo[IFLA_GRE_ERSPAN_HWID])
+			erspan_hwid = rta_getattr_u16(greinfo[IFLA_GRE_ERSPAN_HWID]);
+
+		free(answer);
+	}
+
+	while (argc > 0) {
+		if (!matches(*argv, "key")) {
+			NEXT_ARG();
+			iflags |= GRE_KEY;
+			oflags |= GRE_KEY;
+			ikey = okey = tnl_parse_key("key", *argv);
+		} else if (!matches(*argv, "nokey")) {
+			iflags &= ~GRE_KEY;
+			oflags &= ~GRE_KEY;
+			ikey = okey = 0;
+		} else if (!matches(*argv, "ikey")) {
+			NEXT_ARG();
+			iflags |= GRE_KEY;
+			ikey = tnl_parse_key("ikey", *argv);
+		} else if (!matches(*argv, "noikey")) {
+			iflags &= ~GRE_KEY;
+			ikey = 0;
+		} else if (!matches(*argv, "okey")) {
+			NEXT_ARG();
+			oflags |= GRE_KEY;
+			okey = tnl_parse_key("okey", *argv);
+		} else if (!matches(*argv, "nookey")) {
+			oflags &= ~GRE_KEY;
+			okey = 0;
+		} else if (!matches(*argv, "seq")) {
+			iflags |= GRE_SEQ;
+			oflags |= GRE_SEQ;
+		} else if (!matches(*argv, "noseq")) {
+			iflags &= ~GRE_SEQ;
+			oflags &= ~GRE_SEQ;
+		} else if (!matches(*argv, "iseq")) {
+			iflags |= GRE_SEQ;
+		} else if (!matches(*argv, "noiseq")) {
+			iflags &= ~GRE_SEQ;
+		} else if (!matches(*argv, "oseq")) {
+			oflags |= GRE_SEQ;
+		} else if (!matches(*argv, "nooseq")) {
+			oflags &= ~GRE_SEQ;
+		} else if (!matches(*argv, "csum")) {
+			iflags |= GRE_CSUM;
+			oflags |= GRE_CSUM;
+		} else if (!matches(*argv, "nocsum")) {
+			iflags &= ~GRE_CSUM;
+			oflags &= ~GRE_CSUM;
+		} else if (!matches(*argv, "icsum")) {
+			iflags |= GRE_CSUM;
+		} else if (!matches(*argv, "noicsum")) {
+			iflags &= ~GRE_CSUM;
+		} else if (!matches(*argv, "ocsum")) {
+			oflags |= GRE_CSUM;
+		} else if (!matches(*argv, "noocsum")) {
+			oflags &= ~GRE_CSUM;
+		} else if (!matches(*argv, "nopmtudisc")) {
+			pmtudisc = 0;
+		} else if (!matches(*argv, "pmtudisc")) {
+			pmtudisc = 1;
+		} else if (!matches(*argv, "remote")) {
+			NEXT_ARG();
+			get_addr(&daddr, *argv, AF_INET);
+		} else if (!matches(*argv, "local")) {
+			NEXT_ARG();
+			get_addr(&saddr, *argv, AF_INET);
+		} else if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			link = ll_name_to_index(*argv);
+			if (!link)
+				exit(nodev(*argv));
+		} else if (!matches(*argv, "ttl") ||
+			   !matches(*argv, "hoplimit") ||
+			   !matches(*argv, "hlim")) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (get_u8(&ttl, *argv, 0))
+					invarg("invalid TTL\n", *argv);
+			} else
+				ttl = 0;
+		} else if (!matches(*argv, "tos") ||
+			   !matches(*argv, "tclass") ||
+			   !matches(*argv, "dsfield")) {
+			__u32 uval;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (rtnl_dsfield_a2n(&uval, *argv))
+					invarg("bad TOS value", *argv);
+				tos = uval;
+			} else
+				tos = 1;
+		} else if (strcmp(*argv, "noencap") == 0) {
+			encaptype = TUNNEL_ENCAP_NONE;
+		} else if (strcmp(*argv, "encap") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "fou") == 0)
+				encaptype = TUNNEL_ENCAP_FOU;
+			else if (strcmp(*argv, "gue") == 0)
+				encaptype = TUNNEL_ENCAP_GUE;
+			else if (strcmp(*argv, "none") == 0)
+				encaptype = TUNNEL_ENCAP_NONE;
+			else
+				invarg("Invalid encap type.", *argv);
+		} else if (strcmp(*argv, "encap-sport") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "auto") == 0)
+				encapsport = 0;
+			else if (get_u16(&encapsport, *argv, 0))
+				invarg("Invalid source port.", *argv);
+		} else if (strcmp(*argv, "encap-dport") == 0) {
+			NEXT_ARG();
+			if (get_u16(&encapdport, *argv, 0))
+				invarg("Invalid destination port.", *argv);
+		} else if (strcmp(*argv, "encap-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "noencap-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "encap-udp6-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "noencap-udp6-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "encap-remcsum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "noencap-remcsum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "external") == 0) {
+			metadata = 1;
+		} else if (strcmp(*argv, "ignore-df") == 0) {
+			ignore_df = 1;
+		} else if (strcmp(*argv, "noignore-df") == 0) {
+			/*
+			 *only the lsb is significant, use 2 for presence
+			 */
+			ignore_df = 2;
+		} else if (strcmp(*argv, "fwmark") == 0) {
+			NEXT_ARG();
+			if (get_u32(&fwmark, *argv, 0))
+				invarg("invalid fwmark\n", *argv);
+		} else if (strcmp(*argv, "erspan") == 0) {
+			NEXT_ARG();
+			if (get_u32(&erspan_idx, *argv, 0))
+				invarg("invalid erspan index\n", *argv);
+			if (erspan_idx & ~((1<<20) - 1) || erspan_idx == 0)
+				invarg("erspan index must be > 0 and <= 20-bit\n", *argv);
+		} else if (strcmp(*argv, "erspan_ver") == 0) {
+			NEXT_ARG();
+			if (get_u8(&erspan_ver, *argv, 0))
+				invarg("invalid erspan version\n", *argv);
+			if (erspan_ver != 1 && erspan_ver != 2)
+				invarg("erspan version must be 1 or 2\n", *argv);
+		} else if (strcmp(*argv, "erspan_dir") == 0) {
+			NEXT_ARG();
+			if (matches(*argv, "ingress") == 0)
+				erspan_dir = 0;
+			else if (matches(*argv, "egress") == 0)
+				erspan_dir = 1;
+			else
+				invarg("Invalid erspan direction.", *argv);
+		} else if (strcmp(*argv, "erspan_hwid") == 0) {
+			NEXT_ARG();
+			if (get_u16(&erspan_hwid, *argv, 0))
+				invarg("invalid erspan hwid\n", *argv);
+		} else {
+			gre_print_help(lu, argc, argv, stderr);
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	if (is_addrtype_inet_multi(&daddr)) {
+		if (!ikey) {
+			ikey = daddr.data[0];
+			iflags |= GRE_KEY;
+		}
+		if (!okey) {
+			okey = daddr.data[0];
+			oflags |= GRE_KEY;
+		}
+		if (!is_addrtype_inet_not_unspec(&saddr)) {
+			fprintf(stderr,
+				"A broadcast tunnel requires a source address.\n");
+			return -1;
+		}
+	}
+
+	if (metadata) {
+		addattr_l(n, 1024, IFLA_GRE_COLLECT_METADATA, NULL, 0);
+		return 0;
+	}
+
+	addattr32(n, 1024, IFLA_GRE_IKEY, ikey);
+	addattr32(n, 1024, IFLA_GRE_OKEY, okey);
+	addattr_l(n, 1024, IFLA_GRE_IFLAGS, &iflags, 2);
+	addattr_l(n, 1024, IFLA_GRE_OFLAGS, &oflags, 2);
+	if (is_addrtype_inet_not_unspec(&saddr))
+		addattr_l(n, 1024, IFLA_GRE_LOCAL, saddr.data, saddr.bytelen);
+	if (is_addrtype_inet_not_unspec(&daddr))
+		addattr_l(n, 1024, IFLA_GRE_REMOTE, daddr.data, daddr.bytelen);
+	addattr_l(n, 1024, IFLA_GRE_PMTUDISC, &pmtudisc, 1);
+	if (ignore_df)
+		addattr8(n, 1024, IFLA_GRE_IGNORE_DF, ignore_df & 1);
+	addattr_l(n, 1024, IFLA_GRE_TOS, &tos, 1);
+	if (link)
+		addattr32(n, 1024, IFLA_GRE_LINK, link);
+	addattr_l(n, 1024, IFLA_GRE_TTL, &ttl, 1);
+	addattr32(n, 1024, IFLA_GRE_FWMARK, fwmark);
+	if (erspan_ver) {
+		addattr8(n, 1024, IFLA_GRE_ERSPAN_VER, erspan_ver);
+		if (erspan_ver == 1 && erspan_idx != 0) {
+			addattr32(n, 1024, IFLA_GRE_ERSPAN_INDEX, erspan_idx);
+		} else if (erspan_ver == 2) {
+			addattr8(n, 1024, IFLA_GRE_ERSPAN_DIR, erspan_dir);
+			addattr16(n, 1024, IFLA_GRE_ERSPAN_HWID, erspan_hwid);
+		}
+	}
+	addattr16(n, 1024, IFLA_GRE_ENCAP_TYPE, encaptype);
+	addattr16(n, 1024, IFLA_GRE_ENCAP_FLAGS, encapflags);
+	addattr16(n, 1024, IFLA_GRE_ENCAP_SPORT, htons(encapsport));
+	addattr16(n, 1024, IFLA_GRE_ENCAP_DPORT, htons(encapdport));
+
+	return 0;
+}
+
+static void gre_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s2[64];
+	__u16 iflags = 0;
+	__u16 oflags = 0;
+	__u8 ttl = 0;
+	__u8 tos = 0;
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_GRE_COLLECT_METADATA]) {
+		print_bool(PRINT_ANY, "external", "external ", true);
+		return;
+	}
+
+	tnl_print_endpoint("remote", tb[IFLA_GRE_REMOTE], AF_INET);
+	tnl_print_endpoint("local", tb[IFLA_GRE_LOCAL], AF_INET);
+
+	if (tb[IFLA_GRE_LINK]) {
+		__u32 link = rta_getattr_u32(tb[IFLA_GRE_LINK]);
+
+		if (link) {
+			print_string(PRINT_ANY, "link", "dev %s ",
+				     ll_index_to_name(link));
+		}
+	}
+
+	if (tb[IFLA_GRE_TTL])
+		ttl = rta_getattr_u8(tb[IFLA_GRE_TTL]);
+	if (is_json_context() || ttl)
+		print_uint(PRINT_ANY, "ttl", "ttl %u ", ttl);
+	else
+		print_string(PRINT_FP, NULL, "ttl %s ", "inherit");
+
+	if (tb[IFLA_GRE_TOS])
+		tos = rta_getattr_u8(tb[IFLA_GRE_TOS]);
+	if (tos) {
+		if (is_json_context() || tos != 1)
+			print_0xhex(PRINT_ANY, "tos", "tos %#llx ", tos);
+		else
+			print_string(PRINT_FP, NULL, "tos %s ", "inherit");
+	}
+
+	if (tb[IFLA_GRE_PMTUDISC]) {
+		if (!rta_getattr_u8(tb[IFLA_GRE_PMTUDISC]))
+			print_bool(PRINT_ANY, "pmtudisc", "nopmtudisc ", false);
+		else
+			print_bool(PRINT_JSON, "pmtudisc", NULL, true);
+	}
+
+	if (tb[IFLA_GRE_IGNORE_DF] && rta_getattr_u8(tb[IFLA_GRE_IGNORE_DF]))
+		print_bool(PRINT_ANY, "ignore_df", "ignore-df ", true);
+
+	if (tb[IFLA_GRE_IFLAGS])
+		iflags = rta_getattr_u16(tb[IFLA_GRE_IFLAGS]);
+
+	if (tb[IFLA_GRE_OFLAGS])
+		oflags = rta_getattr_u16(tb[IFLA_GRE_OFLAGS]);
+
+	if ((iflags & GRE_KEY) && tb[IFLA_GRE_IKEY]) {
+		inet_ntop(AF_INET, RTA_DATA(tb[IFLA_GRE_IKEY]), s2, sizeof(s2));
+		print_string(PRINT_ANY, "ikey", "ikey %s ", s2);
+	}
+
+	if ((oflags & GRE_KEY) && tb[IFLA_GRE_OKEY]) {
+		inet_ntop(AF_INET, RTA_DATA(tb[IFLA_GRE_OKEY]), s2, sizeof(s2));
+		print_string(PRINT_ANY, "okey", "okey %s ", s2);
+	}
+
+	if (iflags & GRE_SEQ)
+		print_bool(PRINT_ANY, "iseq", "iseq ", true);
+	if (oflags & GRE_SEQ)
+		print_bool(PRINT_ANY, "oseq", "oseq ", true);
+	if (iflags & GRE_CSUM)
+		print_bool(PRINT_ANY, "icsum", "icsum ", true);
+	if (oflags & GRE_CSUM)
+		print_bool(PRINT_ANY, "ocsum", "ocsum ", true);
+
+	if (tb[IFLA_GRE_FWMARK]) {
+		__u32 fwmark = rta_getattr_u32(tb[IFLA_GRE_FWMARK]);
+
+		if (fwmark) {
+			print_0xhex(PRINT_ANY,
+				    "fwmark", "fwmark %#llx ", fwmark);
+		}
+	}
+
+	if (tb[IFLA_GRE_ERSPAN_INDEX]) {
+		__u32 erspan_idx = rta_getattr_u32(tb[IFLA_GRE_ERSPAN_INDEX]);
+
+		print_uint(PRINT_ANY,
+			   "erspan_index", "erspan_index %u ", erspan_idx);
+	}
+
+	if (tb[IFLA_GRE_ERSPAN_VER]) {
+		__u8 erspan_ver = rta_getattr_u8(tb[IFLA_GRE_ERSPAN_VER]);
+
+		print_uint(PRINT_ANY,
+			   "erspan_ver", "erspan_ver %u ", erspan_ver);
+	}
+
+	if (tb[IFLA_GRE_ERSPAN_DIR]) {
+		__u8 erspan_dir = rta_getattr_u8(tb[IFLA_GRE_ERSPAN_DIR]);
+
+		if (erspan_dir == 0)
+			print_string(PRINT_ANY, "erspan_dir",
+				     "erspan_dir ingress ", NULL);
+		else
+			print_string(PRINT_ANY, "erspan_dir",
+				     "erspan_dir egress ", NULL);
+	}
+
+	if (tb[IFLA_GRE_ERSPAN_HWID]) {
+		__u16 erspan_hwid = rta_getattr_u16(tb[IFLA_GRE_ERSPAN_HWID]);
+
+		print_0xhex(PRINT_ANY,
+			    "erspan_hwid", "erspan_hwid %#llx ", erspan_hwid);
+	}
+
+	tnl_print_encap(tb,
+			IFLA_GRE_ENCAP_TYPE,
+			IFLA_GRE_ENCAP_FLAGS,
+			IFLA_GRE_ENCAP_SPORT,
+			IFLA_GRE_ENCAP_DPORT);
+}
+
+struct link_util gre_link_util = {
+	.id = "gre",
+	.maxattr = IFLA_GRE_MAX,
+	.parse_opt = gre_parse_opt,
+	.print_opt = gre_print_opt,
+	.print_help = gre_print_help,
+};
+
+struct link_util gretap_link_util = {
+	.id = "gretap",
+	.maxattr = IFLA_GRE_MAX,
+	.parse_opt = gre_parse_opt,
+	.print_opt = gre_print_opt,
+	.print_help = gre_print_help,
+};
+
+struct link_util erspan_link_util = {
+	.id = "erspan",
+	.maxattr = IFLA_GRE_MAX,
+	.parse_opt = gre_parse_opt,
+	.print_opt = gre_print_opt,
+	.print_help = gre_print_help,
+};
diff --git a/ip/link_gre6.c b/ip/link_gre6.c
new file mode 100644
index 0000000..9d1741b
--- /dev/null
+++ b/ip/link_gre6.c
@@ -0,0 +1,639 @@
+/*
+ * link_gre6.c	gre driver module
+ *
+ *		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.
+ *
+ * Authors:	Dmitry Kozlov <xeb@mail.ru>
+ *
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+#define IP6_FLOWINFO_TCLASS	htonl(0x0FF00000)
+#define IP6_FLOWINFO_FLOWLABEL	htonl(0x000FFFFF)
+
+#define DEFAULT_TNL_HOP_LIMIT	(64)
+
+static void gre_print_help(struct link_util *lu, int argc, char **argv, FILE *f)
+{
+	fprintf(f,
+		"Usage: ... %-9s	[ remote ADDR ]\n"
+		"			[ local ADDR ]\n"
+		"			[ [no][i|o]seq ]\n"
+		"			[ [i|o]key KEY | no[i|o]key ]\n"
+		"			[ [no][i|o]csum ]\n"
+		"			[ hoplimit TTL ]\n"
+		"			[ encaplimit ELIM ]\n"
+		"			[ tclass TCLASS ]\n"
+		"			[ flowlabel FLOWLABEL ]\n"
+		"			[ dscp inherit ]\n"
+		"			[ dev PHYS_DEV ]\n"
+		"			[ fwmark MARK ]\n"
+		"			[ [no]allow-localremote ]\n"
+		"			[ external ]\n"
+		"			[ noencap ]\n"
+		"			[ encap { fou | gue | none } ]\n"
+		"			[ encap-sport PORT ]\n"
+		"			[ encap-dport PORT ]\n"
+		"			[ [no]encap-csum ]\n"
+		"			[ [no]encap-csum6 ]\n"
+		"			[ [no]encap-remcsum ]\n"
+		"			[ erspan_ver version ]\n"
+		"			[ erspan IDX ]\n"
+		"			[ erspan_dir { ingress | egress } ]\n"
+		"			[ erspan_hwid hwid ]\n"
+		"\n"
+		"Where:	ADDR	  := IPV6_ADDRESS\n"
+		"	TTL	  := { 0..255 } (default=%d)\n"
+		"	KEY	  := { DOTTED_QUAD | NUMBER }\n"
+		"	ELIM	  := { none | 0..255 }(default=%d)\n"
+		"	TCLASS	  := { 0x0..0xff | inherit }\n"
+		"	FLOWLABEL := { 0x0..0xfffff | inherit }\n"
+		"	MARK	  := { 0x0..0xffffffff | inherit }\n",
+		lu->id,
+		DEFAULT_TNL_HOP_LIMIT, IPV6_DEFAULT_TNL_ENCAP_LIMIT);
+}
+
+static int gre_parse_opt(struct link_util *lu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETLINK,
+		.i.ifi_family = preferred_family,
+		.i.ifi_index = ifi->ifi_index,
+	};
+	struct nlmsghdr *answer;
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *greinfo[IFLA_GRE_MAX + 1];
+	int len;
+	__u16 iflags = 0;
+	__u16 oflags = 0;
+	__be32 ikey = 0;
+	__be32 okey = 0;
+	inet_prefix saddr, daddr;
+	__u8 hop_limit = DEFAULT_TNL_HOP_LIMIT;
+	__u8 encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
+	__u32 flowinfo = 0;
+	__u32 flags = 0;
+	__u32 link = 0;
+	__u16 encaptype = 0;
+	__u16 encapflags = TUNNEL_ENCAP_FLAG_CSUM6;
+	__u16 encapsport = 0;
+	__u16 encapdport = 0;
+	__u8 metadata = 0;
+	__u32 fwmark = 0;
+	__u32 erspan_idx = 0;
+	__u8 erspan_ver = 0;
+	__u8 erspan_dir = 0;
+	__u16 erspan_hwid = 0;
+
+	inet_prefix_reset(&saddr);
+	inet_prefix_reset(&daddr);
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		const struct rtattr *rta;
+
+		if (rtnl_talk(&rth, &req.n, &answer) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = answer->nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(greinfo, IFLA_GRE_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		rta = greinfo[IFLA_GRE_LOCAL];
+		if (rta && get_addr_rta(&saddr, rta, AF_INET6))
+			goto get_failed;
+
+		rta = greinfo[IFLA_GRE_REMOTE];
+		if (rta && get_addr_rta(&daddr, rta, AF_INET6))
+			goto get_failed;
+
+		if (greinfo[IFLA_GRE_IKEY])
+			ikey = rta_getattr_u32(greinfo[IFLA_GRE_IKEY]);
+
+		if (greinfo[IFLA_GRE_OKEY])
+			okey = rta_getattr_u32(greinfo[IFLA_GRE_OKEY]);
+
+		if (greinfo[IFLA_GRE_IFLAGS])
+			iflags = rta_getattr_u16(greinfo[IFLA_GRE_IFLAGS]);
+
+		if (greinfo[IFLA_GRE_OFLAGS])
+			oflags = rta_getattr_u16(greinfo[IFLA_GRE_OFLAGS]);
+
+		if (greinfo[IFLA_GRE_TTL])
+			hop_limit = rta_getattr_u8(greinfo[IFLA_GRE_TTL]);
+
+		if (greinfo[IFLA_GRE_LINK])
+			link = rta_getattr_u32(greinfo[IFLA_GRE_LINK]);
+
+		if (greinfo[IFLA_GRE_ENCAP_LIMIT])
+			encap_limit = rta_getattr_u8(greinfo[IFLA_GRE_ENCAP_LIMIT]);
+
+		if (greinfo[IFLA_GRE_FLOWINFO])
+			flowinfo = rta_getattr_u32(greinfo[IFLA_GRE_FLOWINFO]);
+
+		if (greinfo[IFLA_GRE_FLAGS])
+			flags = rta_getattr_u32(greinfo[IFLA_GRE_FLAGS]);
+
+		if (greinfo[IFLA_GRE_ENCAP_TYPE])
+			encaptype = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_TYPE]);
+
+		if (greinfo[IFLA_GRE_ENCAP_FLAGS])
+			encapflags = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_FLAGS]);
+
+		if (greinfo[IFLA_GRE_ENCAP_SPORT])
+			encapsport = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_SPORT]);
+
+		if (greinfo[IFLA_GRE_ENCAP_DPORT])
+			encapdport = rta_getattr_u16(greinfo[IFLA_GRE_ENCAP_DPORT]);
+
+		if (greinfo[IFLA_GRE_COLLECT_METADATA])
+			metadata = 1;
+
+		if (greinfo[IFLA_GRE_FWMARK])
+			fwmark = rta_getattr_u32(greinfo[IFLA_GRE_FWMARK]);
+
+		if (greinfo[IFLA_GRE_ERSPAN_INDEX])
+			erspan_idx = rta_getattr_u32(greinfo[IFLA_GRE_ERSPAN_INDEX]);
+
+		if (greinfo[IFLA_GRE_ERSPAN_VER])
+			erspan_ver = rta_getattr_u8(greinfo[IFLA_GRE_ERSPAN_VER]);
+
+		if (greinfo[IFLA_GRE_ERSPAN_DIR])
+			erspan_dir = rta_getattr_u8(greinfo[IFLA_GRE_ERSPAN_DIR]);
+
+		if (greinfo[IFLA_GRE_ERSPAN_HWID])
+			erspan_hwid = rta_getattr_u16(greinfo[IFLA_GRE_ERSPAN_HWID]);
+
+		free(answer);
+	}
+
+	while (argc > 0) {
+		if (!matches(*argv, "key")) {
+			NEXT_ARG();
+			iflags |= GRE_KEY;
+			oflags |= GRE_KEY;
+			ikey = okey = tnl_parse_key("key", *argv);
+		} else if (!matches(*argv, "nokey")) {
+			iflags &= ~GRE_KEY;
+			oflags &= ~GRE_KEY;
+			ikey = okey = 0;
+		} else if (!matches(*argv, "ikey")) {
+			NEXT_ARG();
+			iflags |= GRE_KEY;
+			ikey = tnl_parse_key("ikey", *argv);
+		} else if (!matches(*argv, "noikey")) {
+			iflags &= ~GRE_KEY;
+			ikey = 0;
+		} else if (!matches(*argv, "okey")) {
+			NEXT_ARG();
+			oflags |= GRE_KEY;
+			okey = tnl_parse_key("okey", *argv);
+		} else if (!matches(*argv, "nookey")) {
+			oflags &= ~GRE_KEY;
+			okey = 0;
+		} else if (!matches(*argv, "seq")) {
+			iflags |= GRE_SEQ;
+			oflags |= GRE_SEQ;
+		} else if (!matches(*argv, "noseq")) {
+			iflags &= ~GRE_SEQ;
+			oflags &= ~GRE_SEQ;
+		} else if (!matches(*argv, "iseq")) {
+			iflags |= GRE_SEQ;
+		} else if (!matches(*argv, "noiseq")) {
+			iflags &= ~GRE_SEQ;
+		} else if (!matches(*argv, "oseq")) {
+			oflags |= GRE_SEQ;
+		} else if (!matches(*argv, "nooseq")) {
+			oflags &= ~GRE_SEQ;
+		} else if (!matches(*argv, "csum")) {
+			iflags |= GRE_CSUM;
+			oflags |= GRE_CSUM;
+		} else if (!matches(*argv, "nocsum")) {
+			iflags &= ~GRE_CSUM;
+			oflags &= ~GRE_CSUM;
+		} else if (!matches(*argv, "icsum")) {
+			iflags |= GRE_CSUM;
+		} else if (!matches(*argv, "noicsum")) {
+			iflags &= ~GRE_CSUM;
+		} else if (!matches(*argv, "ocsum")) {
+			oflags |= GRE_CSUM;
+		} else if (!matches(*argv, "noocsum")) {
+			oflags &= ~GRE_CSUM;
+		} else if (!matches(*argv, "remote")) {
+			NEXT_ARG();
+			get_addr(&daddr, *argv, AF_INET6);
+		} else if (!matches(*argv, "local")) {
+			NEXT_ARG();
+			get_addr(&saddr, *argv, AF_INET6);
+		} else if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			link = ll_name_to_index(*argv);
+			if (!link)
+				exit(nodev(*argv));
+		} else if (!matches(*argv, "ttl") ||
+			   !matches(*argv, "hoplimit") ||
+			   !matches(*argv, "hlim")) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (get_u8(&hop_limit, *argv, 0))
+					invarg("invalid HLIM\n", *argv);
+			} else
+				hop_limit = 0;
+		} else if (!matches(*argv, "tos") ||
+			   !matches(*argv, "tclass") ||
+			   !matches(*argv, "dsfield")) {
+			__u8 uval;
+
+			NEXT_ARG();
+			flowinfo &= ~IP6_FLOWINFO_TCLASS;
+			if (strcmp(*argv, "inherit") == 0)
+				flags |= IP6_TNL_F_USE_ORIG_TCLASS;
+			else {
+				if (get_u8(&uval, *argv, 16))
+					invarg("invalid TClass", *argv);
+				flowinfo |= htonl((__u32)uval << 20) & IP6_FLOWINFO_TCLASS;
+				flags &= ~IP6_TNL_F_USE_ORIG_TCLASS;
+			}
+		} else if (strcmp(*argv, "flowlabel") == 0 ||
+			   strcmp(*argv, "fl") == 0) {
+			__u32 uval;
+
+			NEXT_ARG();
+			flowinfo &= ~IP6_FLOWINFO_FLOWLABEL;
+			if (strcmp(*argv, "inherit") == 0)
+				flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL;
+			else {
+				if (get_u32(&uval, *argv, 16))
+					invarg("invalid Flowlabel", *argv);
+				if (uval > 0xFFFFF)
+					invarg("invalid Flowlabel", *argv);
+				flowinfo |= htonl(uval) & IP6_FLOWINFO_FLOWLABEL;
+				flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL;
+			}
+		} else if (strcmp(*argv, "dscp") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0)
+				invarg("not inherit", *argv);
+			flags |= IP6_TNL_F_RCV_DSCP_COPY;
+		} else if (strcmp(*argv, "noencap") == 0) {
+			encaptype = TUNNEL_ENCAP_NONE;
+		} else if (strcmp(*argv, "encap") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "fou") == 0)
+				encaptype = TUNNEL_ENCAP_FOU;
+			else if (strcmp(*argv, "gue") == 0)
+				encaptype = TUNNEL_ENCAP_GUE;
+			else if (strcmp(*argv, "none") == 0)
+				encaptype = TUNNEL_ENCAP_NONE;
+			else
+				invarg("Invalid encap type.", *argv);
+		} else if (strcmp(*argv, "encap-sport") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "auto") == 0)
+				encapsport = 0;
+			else if (get_u16(&encapsport, *argv, 0))
+				invarg("Invalid source port.", *argv);
+		} else if (strcmp(*argv, "encap-dport") == 0) {
+			NEXT_ARG();
+			if (get_u16(&encapdport, *argv, 0))
+				invarg("Invalid destination port.", *argv);
+		} else if (strcmp(*argv, "encap-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "noencap-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "encap-udp6-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "noencap-udp6-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "encap-remcsum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "noencap-remcsum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "external") == 0) {
+			metadata = 1;
+		} else if (strcmp(*argv, "fwmark") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") == 0) {
+				flags |= IP6_TNL_F_USE_ORIG_FWMARK;
+				fwmark = 0;
+			} else {
+				if (get_u32(&fwmark, *argv, 0))
+					invarg("invalid fwmark\n", *argv);
+				flags &= ~IP6_TNL_F_USE_ORIG_FWMARK;
+			}
+		} else if (strcmp(*argv, "allow-localremote") == 0) {
+			flags |= IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+		} else if (strcmp(*argv, "noallow-localremote") == 0) {
+			flags &= ~IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+		} else if (strcmp(*argv, "encaplimit") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "none") == 0) {
+				flags |= IP6_TNL_F_IGN_ENCAP_LIMIT;
+			} else {
+				__u8 uval;
+
+				if (get_u8(&uval, *argv, 0))
+					invarg("invalid ELIM", *argv);
+				encap_limit = uval;
+				flags &= ~IP6_TNL_F_IGN_ENCAP_LIMIT;
+			}
+		} else if (strcmp(*argv, "erspan") == 0) {
+			NEXT_ARG();
+			if (get_u32(&erspan_idx, *argv, 0))
+				invarg("invalid erspan index\n", *argv);
+			if (erspan_idx & ~((1<<20) - 1) || erspan_idx == 0)
+				invarg("erspan index must be > 0 and <= 20-bit\n", *argv);
+		} else if (strcmp(*argv, "erspan_ver") == 0) {
+			NEXT_ARG();
+			if (get_u8(&erspan_ver, *argv, 0))
+				invarg("invalid erspan version\n", *argv);
+			if (erspan_ver != 1 && erspan_ver != 2)
+				invarg("erspan version must be 1 or 2\n", *argv);
+		} else if (strcmp(*argv, "erspan_dir") == 0) {
+			NEXT_ARG();
+			if (matches(*argv, "ingress") == 0)
+				erspan_dir = 0;
+			else if (matches(*argv, "egress") == 0)
+				erspan_dir = 1;
+			else
+				invarg("Invalid erspan direction.", *argv);
+		} else if (strcmp(*argv, "erspan_hwid") == 0) {
+			NEXT_ARG();
+			if (get_u16(&erspan_hwid, *argv, 0))
+				invarg("invalid erspan hwid\n", *argv);
+		} else {
+			gre_print_help(lu, argc, argv, stderr);
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	if (metadata) {
+		addattr_l(n, 1024, IFLA_GRE_COLLECT_METADATA, NULL, 0);
+		return 0;
+	}
+
+	addattr32(n, 1024, IFLA_GRE_IKEY, ikey);
+	addattr32(n, 1024, IFLA_GRE_OKEY, okey);
+	addattr_l(n, 1024, IFLA_GRE_IFLAGS, &iflags, 2);
+	addattr_l(n, 1024, IFLA_GRE_OFLAGS, &oflags, 2);
+	if (is_addrtype_inet_not_unspec(&saddr))
+		addattr_l(n, 1024, IFLA_GRE_LOCAL, saddr.data, saddr.bytelen);
+	if (is_addrtype_inet_not_unspec(&daddr))
+		addattr_l(n, 1024, IFLA_GRE_REMOTE, daddr.data, daddr.bytelen);
+	if (link)
+		addattr32(n, 1024, IFLA_GRE_LINK, link);
+	addattr_l(n, 1024, IFLA_GRE_TTL, &hop_limit, 1);
+	addattr_l(n, 1024, IFLA_GRE_ENCAP_LIMIT, &encap_limit, 1);
+	addattr_l(n, 1024, IFLA_GRE_FLOWINFO, &flowinfo, 4);
+	addattr32(n, 1024, IFLA_GRE_FLAGS, flags);
+	addattr32(n, 1024, IFLA_GRE_FWMARK, fwmark);
+	if (erspan_ver) {
+		addattr8(n, 1024, IFLA_GRE_ERSPAN_VER, erspan_ver);
+		if (erspan_ver == 1 && erspan_idx != 0) {
+			addattr32(n, 1024, IFLA_GRE_ERSPAN_INDEX, erspan_idx);
+		} else if (erspan_ver == 2) {
+			addattr8(n, 1024, IFLA_GRE_ERSPAN_DIR, erspan_dir);
+			addattr16(n, 1024, IFLA_GRE_ERSPAN_HWID, erspan_hwid);
+		}
+	}
+	addattr16(n, 1024, IFLA_GRE_ENCAP_TYPE, encaptype);
+	addattr16(n, 1024, IFLA_GRE_ENCAP_FLAGS, encapflags);
+	addattr16(n, 1024, IFLA_GRE_ENCAP_SPORT, htons(encapsport));
+	addattr16(n, 1024, IFLA_GRE_ENCAP_DPORT, htons(encapdport));
+
+	return 0;
+}
+
+static void gre_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s2[64];
+	__u16 iflags = 0;
+	__u16 oflags = 0;
+	__u32 flags = 0;
+	__u32 flowinfo = 0;
+	__u8 ttl = 0;
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_GRE_COLLECT_METADATA]) {
+		print_bool(PRINT_ANY, "external", "external ", true);
+		return;
+	}
+
+	if (tb[IFLA_GRE_FLAGS])
+		flags = rta_getattr_u32(tb[IFLA_GRE_FLAGS]);
+
+	if (tb[IFLA_GRE_FLOWINFO])
+		flowinfo = rta_getattr_u32(tb[IFLA_GRE_FLOWINFO]);
+
+	tnl_print_endpoint("remote", tb[IFLA_GRE_REMOTE], AF_INET6);
+	tnl_print_endpoint("local", tb[IFLA_GRE_LOCAL], AF_INET6);
+
+	if (tb[IFLA_GRE_LINK]) {
+		__u32 link = rta_getattr_u32(tb[IFLA_GRE_LINK]);
+
+		if (link) {
+			print_string(PRINT_ANY, "link", "dev %s ",
+				     ll_index_to_name(link));
+		}
+	}
+
+	if (tb[IFLA_GRE_TTL])
+		ttl = rta_getattr_u8(tb[IFLA_GRE_TTL]);
+	if (is_json_context() || ttl)
+		print_uint(PRINT_ANY, "ttl", "hoplimit %u ", ttl);
+	else
+		print_string(PRINT_FP, NULL, "hoplimit %s ", "inherit");
+
+	if (flags & IP6_TNL_F_IGN_ENCAP_LIMIT) {
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_ign_encap_limit",
+			   "encaplimit none ",
+			   true);
+	} else if (tb[IFLA_GRE_ENCAP_LIMIT]) {
+		__u8 val = rta_getattr_u8(tb[IFLA_GRE_ENCAP_LIMIT]);
+
+		print_uint(PRINT_ANY, "encap_limit", "encaplimit %u ", val);
+	}
+
+	if (flags & IP6_TNL_F_USE_ORIG_TCLASS) {
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_use_orig_tclass",
+			   "tclass inherit ",
+			   true);
+	} else if (tb[IFLA_GRE_FLOWINFO]) {
+		__u32 val = ntohl(flowinfo & IP6_FLOWINFO_TCLASS) >> 20;
+
+		snprintf(s2, sizeof(s2), "0x%02x", val);
+		print_string(PRINT_ANY, "tclass", "tclass %s ", s2);
+	}
+
+	if (flags & IP6_TNL_F_USE_ORIG_FLOWLABEL) {
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_use_orig_flowlabel",
+			   "flowlabel inherit ",
+			   true);
+	} else if (tb[IFLA_GRE_FLOWINFO]) {
+		__u32 val = ntohl(flowinfo & IP6_FLOWINFO_FLOWLABEL);
+
+		snprintf(s2, sizeof(s2), "0x%05x", val);
+		print_string(PRINT_ANY, "flowlabel", "flowlabel %s ", s2);
+	}
+
+	if (flags & IP6_TNL_F_RCV_DSCP_COPY)
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_rcv_dscp_copy",
+			   "dscp inherit ",
+			   true);
+
+	if (tb[IFLA_GRE_IFLAGS])
+		iflags = rta_getattr_u16(tb[IFLA_GRE_IFLAGS]);
+
+	if (tb[IFLA_GRE_OFLAGS])
+		oflags = rta_getattr_u16(tb[IFLA_GRE_OFLAGS]);
+
+	if ((iflags & GRE_KEY) && tb[IFLA_GRE_IKEY]) {
+		inet_ntop(AF_INET, RTA_DATA(tb[IFLA_GRE_IKEY]), s2, sizeof(s2));
+		print_string(PRINT_ANY, "ikey", "ikey %s ", s2);
+	}
+
+	if ((oflags & GRE_KEY) && tb[IFLA_GRE_OKEY]) {
+		inet_ntop(AF_INET, RTA_DATA(tb[IFLA_GRE_OKEY]), s2, sizeof(s2));
+		print_string(PRINT_ANY, "okey", "okey %s ", s2);
+	}
+
+	if (iflags & GRE_SEQ)
+		print_bool(PRINT_ANY, "iseq", "iseq ", true);
+	if (oflags & GRE_SEQ)
+		print_bool(PRINT_ANY, "oseq", "oseq ", true);
+	if (iflags & GRE_CSUM)
+		print_bool(PRINT_ANY, "icsum", "icsum ", true);
+	if (oflags & GRE_CSUM)
+		print_bool(PRINT_ANY, "ocsum", "ocsum ", true);
+
+	if (flags & IP6_TNL_F_ALLOW_LOCAL_REMOTE)
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_allow_local_remote",
+			   "allow-localremote ",
+			   true);
+
+	if (flags & IP6_TNL_F_USE_ORIG_FWMARK) {
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_use_orig_fwmark",
+			   "fwmark inherit ",
+			   true);
+	} else if (tb[IFLA_GRE_FWMARK]) {
+		__u32 fwmark = rta_getattr_u32(tb[IFLA_GRE_FWMARK]);
+
+		if (fwmark) {
+			print_0xhex(PRINT_ANY,
+				    "fwmark", "fwmark %#llx ", fwmark);
+		}
+	}
+
+	if (tb[IFLA_GRE_ERSPAN_INDEX]) {
+		__u32 erspan_idx = rta_getattr_u32(tb[IFLA_GRE_ERSPAN_INDEX]);
+
+		print_uint(PRINT_ANY,
+			   "erspan_index", "erspan_index %u ", erspan_idx);
+	}
+
+	if (tb[IFLA_GRE_ERSPAN_VER]) {
+		__u8 erspan_ver = rta_getattr_u8(tb[IFLA_GRE_ERSPAN_VER]);
+
+		print_uint(PRINT_ANY,
+			   "erspan_ver", "erspan_ver %u ", erspan_ver);
+	}
+
+	if (tb[IFLA_GRE_ERSPAN_DIR]) {
+		__u8 erspan_dir = rta_getattr_u8(tb[IFLA_GRE_ERSPAN_DIR]);
+
+		if (erspan_dir == 0)
+			print_string(PRINT_ANY, "erspan_dir",
+				     "erspan_dir ingress ", NULL);
+		else
+			print_string(PRINT_ANY, "erspan_dir",
+				     "erspan_dir egress ", NULL);
+	}
+
+	if (tb[IFLA_GRE_ERSPAN_HWID]) {
+		__u16 erspan_hwid = rta_getattr_u16(tb[IFLA_GRE_ERSPAN_HWID]);
+
+		print_0xhex(PRINT_ANY,
+			    "erspan_hwid", "erspan_hwid %#llx ", erspan_hwid);
+	}
+
+	tnl_print_encap(tb,
+			IFLA_GRE_ENCAP_TYPE,
+			IFLA_GRE_ENCAP_FLAGS,
+			IFLA_GRE_ENCAP_SPORT,
+			IFLA_GRE_ENCAP_DPORT);
+}
+
+struct link_util ip6gre_link_util = {
+	.id = "ip6gre",
+	.maxattr = IFLA_GRE_MAX,
+	.parse_opt = gre_parse_opt,
+	.print_opt = gre_print_opt,
+	.print_help = gre_print_help,
+};
+
+struct link_util ip6gretap_link_util = {
+	.id = "ip6gretap",
+	.maxattr = IFLA_GRE_MAX,
+	.parse_opt = gre_parse_opt,
+	.print_opt = gre_print_opt,
+	.print_help = gre_print_help,
+};
+
+struct link_util ip6erspan_link_util = {
+	.id = "ip6erspan",
+	.maxattr = IFLA_GRE_MAX,
+	.parse_opt = gre_parse_opt,
+	.print_opt = gre_print_opt,
+	.print_help = gre_print_help,
+};
diff --git a/ip/link_ip6tnl.c b/ip/link_ip6tnl.c
new file mode 100644
index 0000000..c7b49b0
--- /dev/null
+++ b/ip/link_ip6tnl.c
@@ -0,0 +1,466 @@
+/*
+ * link_ip6tnl.c	ip6tnl driver module
+ *
+ *		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.
+ *
+ * Authors:	Nicolas Dichtel <nicolas.dichtel@6wind.com>
+ *
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+#define IP6_FLOWINFO_TCLASS	htonl(0x0FF00000)
+#define IP6_FLOWINFO_FLOWLABEL	htonl(0x000FFFFF)
+
+#define DEFAULT_TNL_HOP_LIMIT	(64)
+
+static void ip6tunnel_print_help(struct link_util *lu, int argc, char **argv,
+				 FILE *f)
+{
+	fprintf(f,
+		"Usage: ... %-6s	[ remote ADDR ]\n"
+		"			[ local ADDR ]\n"
+		"			[ encaplimit ELIM ]\n"
+		"			[ hoplimit HLIM ]\n"
+		"			[ tclass TCLASS ]\n"
+		"			[ flowlabel FLOWLABEL ]\n"
+		"			[ dscp inherit ]\n"
+		"			[ [no]allow-localremote ]\n"
+		"			[ dev PHYS_DEV ]\n"
+		"			[ fwmark MARK ]\n"
+		"			[ external ]\n"
+		"			[ noencap ]\n"
+		"			[ encap { fou | gue | none } ]\n"
+		"			[ encap-sport PORT ]\n"
+		"			[ encap-dport PORT ]\n"
+		"			[ [no]encap-csum ]\n"
+		"			[ [no]encap-csum6 ]\n"
+		"			[ [no]encap-remcsum ]\n"
+		"			[ mode { ip6ip6 | ipip6 | any } ]\n"
+		"\n"
+		"Where:	ADDR	  := IPV6_ADDRESS\n"
+		"	ELIM	  := { none | 0..255 }(default=%d)\n"
+		"	HLIM	  := 0..255 (default=%d)\n"
+		"	TCLASS    := { 0x0..0xff | inherit }\n"
+		"	FLOWLABEL := { 0x0..0xfffff | inherit }\n"
+		"	MARK	  := { 0x0..0xffffffff | inherit }\n",
+		lu->id,
+		IPV6_DEFAULT_TNL_ENCAP_LIMIT, DEFAULT_TNL_HOP_LIMIT);
+}
+
+static int ip6tunnel_parse_opt(struct link_util *lu, int argc, char **argv,
+			       struct nlmsghdr *n)
+{
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETLINK,
+		.i.ifi_family = preferred_family,
+		.i.ifi_index = ifi->ifi_index,
+	};
+	struct nlmsghdr *answer;
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *iptuninfo[IFLA_IPTUN_MAX + 1];
+	int len;
+	inet_prefix saddr, daddr;
+	__u8 hop_limit = DEFAULT_TNL_HOP_LIMIT;
+	__u8 encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
+	__u32 flowinfo = 0;
+	__u32 flags = 0;
+	__u8 proto = 0;
+	__u32 link = 0;
+	__u16 encaptype = 0;
+	__u16 encapflags = TUNNEL_ENCAP_FLAG_CSUM6;
+	__u16 encapsport = 0;
+	__u16 encapdport = 0;
+	__u8 metadata = 0;
+	__u32 fwmark = 0;
+
+	inet_prefix_reset(&saddr);
+	inet_prefix_reset(&daddr);
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		const struct rtattr *rta;
+
+		if (rtnl_talk(&rth, &req.n, &answer) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = answer->nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(iptuninfo, IFLA_IPTUN_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		rta = iptuninfo[IFLA_IPTUN_LOCAL];
+		if (rta && get_addr_rta(&saddr, rta, AF_INET6))
+			goto get_failed;
+
+		rta = iptuninfo[IFLA_IPTUN_REMOTE];
+		if (rta && get_addr_rta(&daddr, rta, AF_INET6))
+			goto get_failed;
+
+		if (iptuninfo[IFLA_IPTUN_TTL])
+			hop_limit = rta_getattr_u8(iptuninfo[IFLA_IPTUN_TTL]);
+
+		if (iptuninfo[IFLA_IPTUN_ENCAP_LIMIT])
+			encap_limit = rta_getattr_u8(iptuninfo[IFLA_IPTUN_ENCAP_LIMIT]);
+
+		if (iptuninfo[IFLA_IPTUN_FLOWINFO])
+			flowinfo = rta_getattr_u32(iptuninfo[IFLA_IPTUN_FLOWINFO]);
+
+		if (iptuninfo[IFLA_IPTUN_FLAGS])
+			flags = rta_getattr_u32(iptuninfo[IFLA_IPTUN_FLAGS]);
+
+		if (iptuninfo[IFLA_IPTUN_LINK])
+			link = rta_getattr_u32(iptuninfo[IFLA_IPTUN_LINK]);
+
+		if (iptuninfo[IFLA_IPTUN_PROTO])
+			proto = rta_getattr_u8(iptuninfo[IFLA_IPTUN_PROTO]);
+		if (iptuninfo[IFLA_IPTUN_COLLECT_METADATA])
+			metadata = 1;
+
+		if (iptuninfo[IFLA_IPTUN_FWMARK])
+			fwmark = rta_getattr_u32(iptuninfo[IFLA_IPTUN_FWMARK]);
+
+		free(answer);
+	}
+
+	while (argc > 0) {
+		if (strcmp(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "ipv6/ipv6") == 0 ||
+			    strcmp(*argv, "ip6ip6") == 0)
+				proto = IPPROTO_IPV6;
+			else if (strcmp(*argv, "ip/ipv6") == 0 ||
+				 strcmp(*argv, "ipv4/ipv6") == 0 ||
+				 strcmp(*argv, "ipip6") == 0 ||
+				 strcmp(*argv, "ip4ip6") == 0)
+				proto = IPPROTO_IPIP;
+			else if (strcmp(*argv, "any/ipv6") == 0 ||
+				 strcmp(*argv, "any") == 0)
+				proto = 0;
+			else
+				invarg("Cannot guess tunnel mode.", *argv);
+		} else if (strcmp(*argv, "remote") == 0) {
+			NEXT_ARG();
+			get_addr(&daddr, *argv, AF_INET6);
+		} else if (strcmp(*argv, "local") == 0) {
+			NEXT_ARG();
+			get_addr(&saddr, *argv, AF_INET6);
+		} else if (matches(*argv, "dev") == 0) {
+			NEXT_ARG();
+			link = ll_name_to_index(*argv);
+			if (!link)
+				exit(nodev(*argv));
+		} else if (strcmp(*argv, "ttl") == 0 ||
+			   strcmp(*argv, "hoplimit") == 0 ||
+			   strcmp(*argv, "hlim") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (get_u8(&hop_limit, *argv, 0))
+					invarg("invalid HLIM\n", *argv);
+			} else
+				hop_limit = 0;
+		} else if (strcmp(*argv, "encaplimit") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "none") == 0) {
+				flags |= IP6_TNL_F_IGN_ENCAP_LIMIT;
+			} else {
+				__u8 uval;
+
+				if (get_u8(&uval, *argv, 0) < -1)
+					invarg("invalid ELIM", *argv);
+				encap_limit = uval;
+				flags &= ~IP6_TNL_F_IGN_ENCAP_LIMIT;
+			}
+		} else if (strcmp(*argv, "tos") == 0 ||
+			   strcmp(*argv, "tclass") == 0 ||
+			   strcmp(*argv, "tc") == 0 ||
+			   matches(*argv, "dsfield") == 0) {
+			__u8 uval;
+
+			NEXT_ARG();
+			flowinfo &= ~IP6_FLOWINFO_TCLASS;
+			if (strcmp(*argv, "inherit") == 0)
+				flags |= IP6_TNL_F_USE_ORIG_TCLASS;
+			else {
+				if (get_u8(&uval, *argv, 16))
+					invarg("invalid TClass", *argv);
+				flowinfo |= htonl((__u32)uval << 20) & IP6_FLOWINFO_TCLASS;
+				flags &= ~IP6_TNL_F_USE_ORIG_TCLASS;
+			}
+		} else if (strcmp(*argv, "flowlabel") == 0 ||
+			   strcmp(*argv, "fl") == 0) {
+			__u32 uval;
+
+			NEXT_ARG();
+			flowinfo &= ~IP6_FLOWINFO_FLOWLABEL;
+			if (strcmp(*argv, "inherit") == 0)
+				flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL;
+			else {
+				if (get_u32(&uval, *argv, 16))
+					invarg("invalid Flowlabel", *argv);
+				if (uval > 0xFFFFF)
+					invarg("invalid Flowlabel", *argv);
+				flowinfo |= htonl(uval) & IP6_FLOWINFO_FLOWLABEL;
+				flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL;
+			}
+		} else if (strcmp(*argv, "dscp") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0)
+				invarg("not inherit", *argv);
+			flags |= IP6_TNL_F_RCV_DSCP_COPY;
+		} else if (strcmp(*argv, "fwmark") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") == 0) {
+				flags |= IP6_TNL_F_USE_ORIG_FWMARK;
+				fwmark = 0;
+			} else {
+				if (get_u32(&fwmark, *argv, 0))
+					invarg("invalid fwmark\n", *argv);
+				flags &= ~IP6_TNL_F_USE_ORIG_FWMARK;
+			}
+		} else if (strcmp(*argv, "allow-localremote") == 0) {
+			flags |= IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+		} else if (strcmp(*argv, "noallow-localremote") == 0) {
+			flags &= ~IP6_TNL_F_ALLOW_LOCAL_REMOTE;
+		} else if (strcmp(*argv, "noencap") == 0) {
+			encaptype = TUNNEL_ENCAP_NONE;
+		} else if (strcmp(*argv, "encap") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "fou") == 0)
+				encaptype = TUNNEL_ENCAP_FOU;
+			else if (strcmp(*argv, "gue") == 0)
+				encaptype = TUNNEL_ENCAP_GUE;
+			else if (strcmp(*argv, "none") == 0)
+				encaptype = TUNNEL_ENCAP_NONE;
+			else
+				invarg("Invalid encap type.", *argv);
+		} else if (strcmp(*argv, "encap-sport") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "auto") == 0)
+				encapsport = 0;
+			else if (get_u16(&encapsport, *argv, 0))
+				invarg("Invalid source port.", *argv);
+		} else if (strcmp(*argv, "encap-dport") == 0) {
+			NEXT_ARG();
+			if (get_u16(&encapdport, *argv, 0))
+				invarg("Invalid destination port.", *argv);
+		} else if (strcmp(*argv, "encap-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "noencap-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "encap-udp6-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "noencap-udp6-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "encap-remcsum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "noencap-remcsum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "external") == 0) {
+			metadata = 1;
+		} else {
+			ip6tunnel_print_help(lu, argc, argv, stderr);
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	addattr8(n, 1024, IFLA_IPTUN_PROTO, proto);
+	if (metadata) {
+		addattr_l(n, 1024, IFLA_IPTUN_COLLECT_METADATA, NULL, 0);
+		return 0;
+	}
+
+	if (is_addrtype_inet_not_unspec(&saddr)) {
+		addattr_l(n, 1024, IFLA_IPTUN_LOCAL,
+			  saddr.data, saddr.bytelen);
+	}
+	if (is_addrtype_inet_not_unspec(&daddr)) {
+		addattr_l(n, 1024, IFLA_IPTUN_REMOTE,
+			  daddr.data, daddr.bytelen);
+	}
+	addattr8(n, 1024, IFLA_IPTUN_TTL, hop_limit);
+	addattr8(n, 1024, IFLA_IPTUN_ENCAP_LIMIT, encap_limit);
+	addattr32(n, 1024, IFLA_IPTUN_FLOWINFO, flowinfo);
+	addattr32(n, 1024, IFLA_IPTUN_FLAGS, flags);
+	addattr32(n, 1024, IFLA_IPTUN_LINK, link);
+	addattr32(n, 1024, IFLA_IPTUN_FWMARK, fwmark);
+
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_TYPE, encaptype);
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_FLAGS, encapflags);
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_SPORT, htons(encapsport));
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_DPORT, htons(encapdport));
+
+	return 0;
+}
+
+static void ip6tunnel_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s2[64];
+	__u32 flags = 0;
+	__u32 flowinfo = 0;
+	__u8 ttl = 0;
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_IPTUN_COLLECT_METADATA]) {
+		print_bool(PRINT_ANY, "external", "external ", true);
+		return;
+	}
+
+	if (tb[IFLA_IPTUN_FLAGS])
+		flags = rta_getattr_u32(tb[IFLA_IPTUN_FLAGS]);
+
+	if (tb[IFLA_IPTUN_FLOWINFO])
+		flowinfo = rta_getattr_u32(tb[IFLA_IPTUN_FLOWINFO]);
+
+	if (tb[IFLA_IPTUN_PROTO]) {
+		switch (rta_getattr_u8(tb[IFLA_IPTUN_PROTO])) {
+		case IPPROTO_IPIP:
+			print_string(PRINT_ANY, "proto", "%s ", "ipip6");
+			break;
+		case IPPROTO_IPV6:
+			print_string(PRINT_ANY, "proto", "%s ", "ip6ip6");
+			break;
+		case 0:
+			print_string(PRINT_ANY, "proto", "%s ", "any");
+			break;
+		}
+	}
+
+	tnl_print_endpoint("remote", tb[IFLA_IPTUN_REMOTE], AF_INET6);
+	tnl_print_endpoint("local", tb[IFLA_IPTUN_LOCAL], AF_INET6);
+
+	if (tb[IFLA_IPTUN_LINK]) {
+		__u32 link = rta_getattr_u32(tb[IFLA_IPTUN_LINK]);
+
+		if (link) {
+			print_string(PRINT_ANY, "link", "dev %s ",
+				     ll_index_to_name(link));
+		}
+	}
+
+	if (tb[IFLA_IPTUN_TTL])
+		ttl = rta_getattr_u8(tb[IFLA_IPTUN_TTL]);
+	if (is_json_context() || ttl)
+		print_uint(PRINT_ANY, "ttl", "hoplimit %u ", ttl);
+	else
+		print_string(PRINT_FP, NULL, "hoplimit %s ", "inherit");
+
+	if (flags & IP6_TNL_F_IGN_ENCAP_LIMIT) {
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_ign_encap_limit",
+			   "encaplimit none ",
+			   true);
+	} else if (tb[IFLA_IPTUN_ENCAP_LIMIT]) {
+		__u8 val = rta_getattr_u8(tb[IFLA_IPTUN_ENCAP_LIMIT]);
+
+		print_uint(PRINT_ANY, "encap_limit", "encaplimit %u ", val);
+	}
+
+	if (flags & IP6_TNL_F_USE_ORIG_TCLASS) {
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_use_orig_tclass",
+			   "tclass inherit ",
+			   true);
+	} else if (tb[IFLA_IPTUN_FLOWINFO]) {
+		__u32 val = ntohl(flowinfo & IP6_FLOWINFO_TCLASS) >> 20;
+
+		snprintf(s2, sizeof(s2), "0x%02x", val);
+		print_string(PRINT_ANY, "tclass", "tclass %s ", s2);
+	}
+
+	if (flags & IP6_TNL_F_USE_ORIG_FLOWLABEL) {
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_use_orig_flowlabel",
+			   "flowlabel inherit ",
+			   true);
+	} else if (tb[IFLA_IPTUN_FLOWINFO]) {
+		__u32 val = ntohl(flowinfo & IP6_FLOWINFO_FLOWLABEL);
+
+		snprintf(s2, sizeof(s2), "0x%05x", val);
+		print_string(PRINT_ANY, "flowlabel", "flowlabel %s ", s2);
+	}
+
+	if (flags & IP6_TNL_F_RCV_DSCP_COPY)
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_rcv_dscp_copy",
+			   "dscp inherit ",
+			   true);
+
+	if (flags & IP6_TNL_F_MIP6_DEV)
+		print_bool(PRINT_ANY, "ip6_tnl_f_mip6_dev", "mip6 ", true);
+
+	if (flags & IP6_TNL_F_ALLOW_LOCAL_REMOTE)
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_allow_local_remote",
+			   "allow-localremote ",
+			   true);
+
+	if (flags & IP6_TNL_F_USE_ORIG_FWMARK) {
+		print_bool(PRINT_ANY,
+			   "ip6_tnl_f_use_orig_fwmark",
+			   "fwmark inherit ",
+			   true);
+	} else if (tb[IFLA_IPTUN_FWMARK]) {
+		__u32 fwmark = rta_getattr_u32(tb[IFLA_IPTUN_FWMARK]);
+
+		if (fwmark) {
+			print_0xhex(PRINT_ANY,
+				    "fwmark", "fwmark %#llx ", fwmark);
+		}
+	}
+
+	tnl_print_encap(tb,
+			IFLA_IPTUN_ENCAP_TYPE,
+			IFLA_IPTUN_ENCAP_FLAGS,
+			IFLA_IPTUN_ENCAP_SPORT,
+			IFLA_IPTUN_ENCAP_DPORT);
+}
+
+struct link_util ip6tnl_link_util = {
+	.id = "ip6tnl",
+	.maxattr = IFLA_IPTUN_MAX,
+	.parse_opt = ip6tunnel_parse_opt,
+	.print_opt = ip6tunnel_print_opt,
+	.print_help = ip6tunnel_print_help,
+};
diff --git a/ip/link_iptnl.c b/ip/link_iptnl.c
new file mode 100644
index 0000000..636cdb2
--- /dev/null
+++ b/ip/link_iptnl.c
@@ -0,0 +1,497 @@
+/*
+ * link_iptnl.c	ipip and sit driver module
+ *
+ *		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.
+ *
+ * Authors:	Nicolas Dichtel <nicolas.dichtel@6wind.com>
+ *
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void iptunnel_print_help(struct link_util *lu, int argc, char **argv,
+				FILE *f)
+{
+	const char *mode;
+
+	if (strcmp(lu->id, "sit") == 0) {
+		mode =	"{ ip6ip | ipip | mplsip | any } ]\n"
+			"			[ isatap";
+	} else {
+		mode = "{ ipip | mplsip | any }";
+	}
+
+	fprintf(f,
+		"Usage: ... %-6s	[ remote ADDR ]\n"
+		"			[ local ADDR ]\n"
+		"			[ ttl TTL ]\n"
+		"			[ tos TOS ]\n"
+		"			[ [no]pmtudisc ]\n"
+		"			[ 6rd-prefix ADDR ]\n"
+		"			[ 6rd-relay_prefix ADDR ]\n"
+		"			[ 6rd-reset ]\n"
+		"			[ dev PHYS_DEV ]\n"
+		"			[ fwmark MARK ]\n"
+		"			[ external ]\n"
+		"			[ noencap ]\n"
+		"			[ encap { fou | gue | none } ]\n"
+		"			[ encap-sport PORT ]\n"
+		"			[ encap-dport PORT ]\n"
+		"			[ [no]encap-csum ]\n"
+		"			[ [no]encap-csum6 ]\n"
+		"			[ [no]encap-remcsum ]\n"
+		"			[ mode %s ]\n"
+		"\n"
+		"Where:	ADDR := { IP_ADDRESS | any }\n"
+		"	TOS  := { NUMBER | inherit }\n"
+		"	TTL  := { 1..255 | inherit }\n"
+		"	MARK := { 0x0..0xffffffff }\n",
+		lu->id, mode);
+}
+
+static int iptunnel_parse_opt(struct link_util *lu, int argc, char **argv,
+			      struct nlmsghdr *n)
+{
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETLINK,
+		.i.ifi_family = preferred_family,
+		.i.ifi_index = ifi->ifi_index,
+	};
+	struct nlmsghdr *answer;
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *iptuninfo[IFLA_IPTUN_MAX + 1];
+	int len;
+	inet_prefix saddr, daddr, ip6rdprefix, ip6rdrelayprefix;
+	__u8 pmtudisc = 1;
+	__u8 tos = 0;
+	__u16 iflags = 0;
+	__u8 ttl = 0;
+	__u8 proto = 0;
+	__u32 link = 0;
+	__u16 encaptype = 0;
+	__u16 encapflags = 0;
+	__u16 encapsport = 0;
+	__u16 encapdport = 0;
+	__u8 metadata = 0;
+	__u32 fwmark = 0;
+
+	inet_prefix_reset(&saddr);
+	inet_prefix_reset(&daddr);
+
+	inet_prefix_reset(&ip6rdprefix);
+	inet_prefix_reset(&ip6rdrelayprefix);
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		const struct rtattr *rta;
+
+		if (rtnl_talk(&rth, &req.n, &answer) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = answer->nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(iptuninfo, IFLA_IPTUN_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		rta = iptuninfo[IFLA_IPTUN_LOCAL];
+		if (rta && get_addr_rta(&saddr, rta, AF_INET))
+			goto get_failed;
+
+		rta = iptuninfo[IFLA_IPTUN_REMOTE];
+		if (rta && get_addr_rta(&daddr, rta, AF_INET))
+			goto get_failed;
+
+		rta = iptuninfo[IFLA_IPTUN_6RD_PREFIX];
+		if (rta && get_addr_rta(&ip6rdprefix, rta, AF_INET6))
+			goto get_failed;
+
+		rta = iptuninfo[IFLA_IPTUN_6RD_RELAY_PREFIX];
+		if (rta && get_addr_rta(&ip6rdrelayprefix, rta, AF_INET))
+			goto get_failed;
+
+		rta = iptuninfo[IFLA_IPTUN_6RD_PREFIXLEN];
+		ip6rdprefix.bitlen = rta ? rta_getattr_u16(rta) : 0;
+
+		rta = iptuninfo[IFLA_IPTUN_6RD_RELAY_PREFIXLEN];
+		ip6rdrelayprefix.bitlen = rta ? rta_getattr_u16(rta) : 0;
+
+		if (iptuninfo[IFLA_IPTUN_TTL])
+			ttl = rta_getattr_u8(iptuninfo[IFLA_IPTUN_TTL]);
+
+		if (iptuninfo[IFLA_IPTUN_PMTUDISC])
+			pmtudisc =
+				rta_getattr_u8(iptuninfo[IFLA_IPTUN_PMTUDISC]);
+
+		if (iptuninfo[IFLA_IPTUN_TOS])
+			tos = rta_getattr_u8(iptuninfo[IFLA_IPTUN_TOS]);
+
+		if (iptuninfo[IFLA_IPTUN_FLAGS])
+			iflags = rta_getattr_u16(iptuninfo[IFLA_IPTUN_FLAGS]);
+
+		if (iptuninfo[IFLA_IPTUN_LINK])
+			link = rta_getattr_u32(iptuninfo[IFLA_IPTUN_LINK]);
+
+		if (iptuninfo[IFLA_IPTUN_PROTO])
+			proto = rta_getattr_u8(iptuninfo[IFLA_IPTUN_PROTO]);
+
+		if (iptuninfo[IFLA_IPTUN_ENCAP_TYPE])
+			encaptype = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_TYPE]);
+		if (iptuninfo[IFLA_IPTUN_ENCAP_FLAGS])
+			encapflags = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_FLAGS]);
+		if (iptuninfo[IFLA_IPTUN_ENCAP_SPORT])
+			encapsport = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_SPORT]);
+		if (iptuninfo[IFLA_IPTUN_ENCAP_DPORT])
+			encapdport = rta_getattr_u16(iptuninfo[IFLA_IPTUN_ENCAP_DPORT]);
+
+		if (iptuninfo[IFLA_IPTUN_COLLECT_METADATA])
+			metadata = 1;
+
+		if (iptuninfo[IFLA_IPTUN_FWMARK])
+			fwmark = rta_getattr_u32(iptuninfo[IFLA_IPTUN_FWMARK]);
+
+		free(answer);
+	}
+
+	while (argc > 0) {
+		if (strcmp(*argv, "mode") == 0) {
+			NEXT_ARG();
+			if (strcmp(lu->id, "sit") == 0 &&
+			    (strcmp(*argv, "ipv6/ipv4") == 0 ||
+			     strcmp(*argv, "ip6ip") == 0))
+				proto = IPPROTO_IPV6;
+			else if (strcmp(*argv, "ipv4/ipv4") == 0 ||
+				 strcmp(*argv, "ipip") == 0 ||
+				 strcmp(*argv, "ip4ip4") == 0)
+				proto = IPPROTO_IPIP;
+			else if (strcmp(*argv, "mpls/ipv4") == 0 ||
+				   strcmp(*argv, "mplsip") == 0)
+				proto = IPPROTO_MPLS;
+			else if (strcmp(*argv, "any/ipv4") == 0 ||
+				 strcmp(*argv, "any") == 0)
+				proto = 0;
+			else
+				invarg("Cannot guess tunnel mode.", *argv);
+		} else if (strcmp(*argv, "remote") == 0) {
+			NEXT_ARG();
+			get_addr(&daddr, *argv, AF_INET);
+		} else if (strcmp(*argv, "local") == 0) {
+			NEXT_ARG();
+			get_addr(&saddr, *argv, AF_INET);
+		} else if (matches(*argv, "dev") == 0) {
+			NEXT_ARG();
+			link = ll_name_to_index(*argv);
+			if (!link)
+				exit(nodev(*argv));
+		} else if (strcmp(*argv, "ttl") == 0 ||
+			   strcmp(*argv, "hoplimit") == 0 ||
+			   strcmp(*argv, "hlim") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (get_u8(&ttl, *argv, 0))
+					invarg("invalid TTL\n", *argv);
+			} else
+				ttl = 0;
+		} else if (strcmp(*argv, "tos") == 0 ||
+			   strcmp(*argv, "tclass") == 0 ||
+			   strcmp(*argv, "tc") == 0 ||
+			   matches(*argv, "dsfield") == 0) {
+			__u32 uval;
+
+			NEXT_ARG();
+			if (strcmp(*argv, "inherit") != 0) {
+				if (rtnl_dsfield_a2n(&uval, *argv))
+					invarg("bad TOS value", *argv);
+				tos = uval;
+			} else
+				tos = 1;
+		} else if (strcmp(*argv, "nopmtudisc") == 0) {
+			pmtudisc = 0;
+		} else if (strcmp(*argv, "pmtudisc") == 0) {
+			pmtudisc = 1;
+		} else if (strcmp(lu->id, "sit") == 0 &&
+			   strcmp(*argv, "isatap") == 0) {
+			iflags |= SIT_ISATAP;
+		} else if (strcmp(*argv, "noencap") == 0) {
+			encaptype = TUNNEL_ENCAP_NONE;
+		} else if (strcmp(*argv, "encap") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "fou") == 0)
+				encaptype = TUNNEL_ENCAP_FOU;
+			else if (strcmp(*argv, "gue") == 0)
+				encaptype = TUNNEL_ENCAP_GUE;
+			else if (strcmp(*argv, "none") == 0)
+				encaptype = TUNNEL_ENCAP_NONE;
+			else
+				invarg("Invalid encap type.", *argv);
+		} else if (strcmp(*argv, "encap-sport") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "auto") == 0)
+				encapsport = 0;
+			else if (get_u16(&encapsport, *argv, 0))
+				invarg("Invalid source port.", *argv);
+		} else if (strcmp(*argv, "encap-dport") == 0) {
+			NEXT_ARG();
+			if (get_u16(&encapdport, *argv, 0))
+				invarg("Invalid destination port.", *argv);
+		} else if (strcmp(*argv, "encap-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "noencap-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM;
+		} else if (strcmp(*argv, "encap-udp6-csum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "noencap-udp6-csum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_CSUM6;
+		} else if (strcmp(*argv, "encap-remcsum") == 0) {
+			encapflags |= TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "noencap-remcsum") == 0) {
+			encapflags &= ~TUNNEL_ENCAP_FLAG_REMCSUM;
+		} else if (strcmp(*argv, "external") == 0) {
+			metadata = 1;
+		} else if (strcmp(*argv, "6rd-prefix") == 0) {
+			NEXT_ARG();
+			if (get_prefix(&ip6rdprefix, *argv, AF_INET6))
+				invarg("invalid 6rd_prefix\n", *argv);
+		} else if (strcmp(*argv, "6rd-relay_prefix") == 0) {
+			NEXT_ARG();
+			if (get_prefix(&ip6rdrelayprefix, *argv, AF_INET))
+				invarg("invalid 6rd-relay_prefix\n", *argv);
+		} else if (strcmp(*argv, "6rd-reset") == 0) {
+			get_prefix(&ip6rdprefix, "2002::/16", AF_INET6);
+			inet_prefix_reset(&ip6rdrelayprefix);
+		} else if (strcmp(*argv, "fwmark") == 0) {
+			NEXT_ARG();
+			if (get_u32(&fwmark, *argv, 0))
+				invarg("invalid fwmark\n", *argv);
+		} else {
+			iptunnel_print_help(lu, argc, argv, stderr);
+			return -1;
+		}
+		argc--, argv++;
+	}
+
+	if (ttl && pmtudisc == 0) {
+		fprintf(stderr, "ttl != 0 and nopmtudisc are incompatible\n");
+		exit(-1);
+	}
+
+	addattr8(n, 1024, IFLA_IPTUN_PROTO, proto);
+	if (metadata) {
+		addattr_l(n, 1024, IFLA_IPTUN_COLLECT_METADATA, NULL, 0);
+		return 0;
+	}
+
+	if (is_addrtype_inet_not_unspec(&saddr)) {
+		addattr_l(n, 1024, IFLA_IPTUN_LOCAL,
+			  saddr.data, saddr.bytelen);
+	}
+	if (is_addrtype_inet_not_unspec(&daddr)) {
+		addattr_l(n, 1024, IFLA_IPTUN_REMOTE,
+			  daddr.data, daddr.bytelen);
+	}
+	addattr8(n, 1024, IFLA_IPTUN_PMTUDISC, pmtudisc);
+	addattr8(n, 1024, IFLA_IPTUN_TOS, tos);
+	addattr8(n, 1024, IFLA_IPTUN_TTL, ttl);
+	addattr32(n, 1024, IFLA_IPTUN_LINK, link);
+	addattr32(n, 1024, IFLA_IPTUN_FWMARK, fwmark);
+
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_TYPE, encaptype);
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_FLAGS, encapflags);
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_SPORT, htons(encapsport));
+	addattr16(n, 1024, IFLA_IPTUN_ENCAP_DPORT, htons(encapdport));
+
+	if (strcmp(lu->id, "sit") == 0) {
+		addattr16(n, 1024, IFLA_IPTUN_FLAGS, iflags);
+		if (is_addrtype_inet(&ip6rdprefix)) {
+			addattr_l(n, 1024, IFLA_IPTUN_6RD_PREFIX,
+				  ip6rdprefix.data, ip6rdprefix.bytelen);
+			addattr16(n, 1024, IFLA_IPTUN_6RD_PREFIXLEN,
+				  ip6rdprefix.bitlen);
+		}
+		if (is_addrtype_inet(&ip6rdrelayprefix)) {
+			addattr32(n, 1024, IFLA_IPTUN_6RD_RELAY_PREFIX,
+				  ip6rdrelayprefix.data[0]);
+			addattr16(n, 1024, IFLA_IPTUN_6RD_RELAY_PREFIXLEN,
+				  ip6rdrelayprefix.bitlen);
+		}
+	}
+
+	return 0;
+}
+
+static void iptunnel_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s2[64];
+	__u16 prefixlen;
+	__u8 ttl = 0;
+	__u8 tos = 0;
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_IPTUN_COLLECT_METADATA]) {
+		print_bool(PRINT_ANY, "external", "external ", true);
+		return;
+	}
+
+	if (tb[IFLA_IPTUN_PROTO]) {
+		switch (rta_getattr_u8(tb[IFLA_IPTUN_PROTO])) {
+		case IPPROTO_IPIP:
+			print_string(PRINT_ANY, "proto", "%s ", "ipip");
+			break;
+		case IPPROTO_IPV6:
+			print_string(PRINT_ANY, "proto", "%s ", "ip6ip");
+			break;
+		case IPPROTO_MPLS:
+			print_string(PRINT_ANY, "proto", "%s ", "mplsip");
+			break;
+		case 0:
+			print_string(PRINT_ANY, "proto", "%s ", "any");
+			break;
+		}
+	}
+
+	tnl_print_endpoint("remote", tb[IFLA_IPTUN_REMOTE], AF_INET);
+	tnl_print_endpoint("local", tb[IFLA_IPTUN_LOCAL], AF_INET);
+
+	if (tb[IFLA_IPTUN_LINK]) {
+		__u32 link = rta_getattr_u32(tb[IFLA_IPTUN_LINK]);
+
+		if (link) {
+			print_string(PRINT_ANY, "link", "dev %s ",
+				     ll_index_to_name(link));
+		}
+	}
+
+	if (tb[IFLA_IPTUN_TTL])
+		ttl = rta_getattr_u8(tb[IFLA_IPTUN_TTL]);
+	if (is_json_context() || ttl)
+		print_uint(PRINT_ANY, "ttl", "ttl %u ", ttl);
+	else
+		print_string(PRINT_FP, NULL, "ttl %s ", "inherit");
+
+	if (tb[IFLA_IPTUN_TOS])
+		tos = rta_getattr_u8(tb[IFLA_IPTUN_TOS]);
+	if (tos) {
+		if (is_json_context() || tos != 1)
+			print_0xhex(PRINT_ANY, "tos", "tos %#llx ", tos);
+		else
+			print_string(PRINT_FP, NULL, "tos %s ", "inherit");
+	}
+
+	if (tb[IFLA_IPTUN_PMTUDISC] && rta_getattr_u8(tb[IFLA_IPTUN_PMTUDISC]))
+		print_bool(PRINT_ANY, "pmtudisc", "pmtudisc ", true);
+	else
+		print_bool(PRINT_ANY, "pmtudisc", "nopmtudisc ", false);
+
+	if (tb[IFLA_IPTUN_FLAGS]) {
+		__u16 iflags = rta_getattr_u16(tb[IFLA_IPTUN_FLAGS]);
+
+		if (iflags & SIT_ISATAP)
+			print_bool(PRINT_ANY, "isatap", "isatap ", true);
+	}
+
+	if (tb[IFLA_IPTUN_6RD_PREFIXLEN] &&
+	    (prefixlen = rta_getattr_u16(tb[IFLA_IPTUN_6RD_PREFIXLEN]))) {
+		__u16 relayprefixlen =
+			rta_getattr_u16(tb[IFLA_IPTUN_6RD_RELAY_PREFIXLEN]);
+		__u32 relayprefix =
+			rta_getattr_u32(tb[IFLA_IPTUN_6RD_RELAY_PREFIX]);
+
+		const char *prefix = inet_ntop(AF_INET6,
+					       RTA_DATA(tb[IFLA_IPTUN_6RD_PREFIX]),
+					       s2, sizeof(s2));
+
+		if (is_json_context()) {
+			print_string(PRINT_JSON, "prefix", NULL, prefix);
+			print_int(PRINT_JSON, "prefixlen", NULL, prefixlen);
+			if (relayprefix) {
+				print_string(PRINT_JSON,
+					     "relay_prefix",
+					     NULL,
+					     format_host(AF_INET,
+							 4,
+							 &relayprefix));
+				print_int(PRINT_JSON,
+					  "relay_prefixlen",
+					  NULL,
+					  relayprefixlen);
+			}
+		} else {
+			printf("6rd-prefix %s/%u ", prefix, prefixlen);
+			if (relayprefix) {
+				printf("6rd-relay_prefix %s/%u ",
+				       format_host(AF_INET, 4, &relayprefix),
+				       relayprefixlen);
+			}
+		}
+	}
+
+	if (tb[IFLA_IPTUN_FWMARK]) {
+		__u32 fwmark = rta_getattr_u32(tb[IFLA_IPTUN_FWMARK]);
+
+		if (fwmark) {
+			print_0xhex(PRINT_ANY,
+				    "fwmark", "fwmark %#llx ", fwmark);
+		}
+	}
+
+	tnl_print_encap(tb,
+			IFLA_IPTUN_ENCAP_TYPE,
+			IFLA_IPTUN_ENCAP_FLAGS,
+			IFLA_IPTUN_ENCAP_SPORT,
+			IFLA_IPTUN_ENCAP_DPORT);
+}
+
+struct link_util ipip_link_util = {
+	.id = "ipip",
+	.maxattr = IFLA_IPTUN_MAX,
+	.parse_opt = iptunnel_parse_opt,
+	.print_opt = iptunnel_print_opt,
+	.print_help = iptunnel_print_help,
+};
+
+struct link_util sit_link_util = {
+	.id = "sit",
+	.maxattr = IFLA_IPTUN_MAX,
+	.parse_opt = iptunnel_parse_opt,
+	.print_opt = iptunnel_print_opt,
+	.print_help = iptunnel_print_help,
+};
diff --git a/ip/link_veth.c b/ip/link_veth.c
new file mode 100644
index 0000000..33e8f2b
--- /dev/null
+++ b/ip/link_veth.c
@@ -0,0 +1,86 @@
+/*
+ * link_veth.c	veth driver module
+ *
+ *		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.
+ *
+ * Authors:	Pavel Emelianov <xemul@openvz.org>
+ *
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <linux/veth.h>
+
+#include "utils.h"
+#include "ip_common.h"
+
+static void print_usage(FILE *f)
+{
+	printf("Usage: ip link <options> type veth [peer <options>]\n"
+	       "To get <options> type 'ip link add help'\n");
+}
+
+static void usage(void)
+{
+	print_usage(stderr);
+}
+
+static int veth_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	char *type = NULL;
+	int err;
+	struct rtattr *data;
+	struct ifinfomsg *ifm, *peer_ifm;
+	unsigned int ifi_flags, ifi_change, ifi_index;
+
+	if (strcmp(argv[0], "peer") != 0) {
+		usage();
+		return -1;
+	}
+
+	ifm = NLMSG_DATA(n);
+	ifi_flags = ifm->ifi_flags;
+	ifi_change = ifm->ifi_change;
+	ifi_index = ifm->ifi_index;
+	ifm->ifi_flags = 0;
+	ifm->ifi_change = 0;
+	ifm->ifi_index = 0;
+
+	data = addattr_nest(n, 1024, VETH_INFO_PEER);
+
+	n->nlmsg_len += sizeof(struct ifinfomsg);
+
+	err = iplink_parse(argc - 1, argv + 1, (struct iplink_req *)n, &type);
+	if (err < 0)
+		return err;
+
+	if (type)
+		duparg("type", argv[err]);
+
+	peer_ifm = RTA_DATA(data);
+	peer_ifm->ifi_index = ifm->ifi_index;
+	peer_ifm->ifi_flags = ifm->ifi_flags;
+	peer_ifm->ifi_change = ifm->ifi_change;
+	ifm->ifi_flags = ifi_flags;
+	ifm->ifi_change = ifi_change;
+	ifm->ifi_index = ifi_index;
+
+	addattr_nest_end(n, data);
+	return argc - 1 - err;
+}
+
+static void veth_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
+}
+
+struct link_util veth_link_util = {
+	.id = "veth",
+	.parse_opt = veth_parse_opt,
+	.print_help = veth_print_help,
+};
diff --git a/ip/link_vti.c b/ip/link_vti.c
new file mode 100644
index 0000000..3a52ea8
--- /dev/null
+++ b/ip/link_vti.c
@@ -0,0 +1,216 @@
+/*
+ * link_vti.c	VTI driver module
+ *
+ *		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.
+ *
+ * Authors:	Herbert Xu <herbert@gondor.apana.org.au>
+ *          Saurabh Mohan <saurabh.mohan@vyatta.com> Modified link_gre.c for VTI
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void vti_print_help(struct link_util *lu, int argc, char **argv, FILE *f)
+{
+	fprintf(f,
+		"Usage: ... %-4s	[ remote ADDR ]\n"
+		"		[ local ADDR ]\n"
+		"		[ [i|o]key KEY ]\n"
+		"		[ dev PHYS_DEV ]\n"
+		"		[ fwmark MARK ]\n"
+		"\n"
+		"Where:	ADDR := { IP_ADDRESS }\n"
+		"	KEY  := { DOTTED_QUAD | NUMBER }\n"
+		"	MARK := { 0x0..0xffffffff }\n",
+		lu->id);
+}
+
+static int vti_parse_opt(struct link_util *lu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETLINK,
+		.i.ifi_family = preferred_family,
+		.i.ifi_index = ifi->ifi_index,
+	};
+	struct nlmsghdr *answer;
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *vtiinfo[IFLA_VTI_MAX + 1];
+	__be32 ikey = 0;
+	__be32 okey = 0;
+	inet_prefix saddr, daddr;
+	unsigned int link = 0;
+	__u32 fwmark = 0;
+	int len;
+
+	inet_prefix_reset(&saddr);
+	inet_prefix_reset(&daddr);
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		const struct rtattr *rta;
+
+		if (rtnl_talk(&rth, &req.n, &answer) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = answer->nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(vtiinfo, IFLA_VTI_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		rta = vtiinfo[IFLA_VTI_LOCAL];
+		if (rta && get_addr_rta(&saddr, rta, AF_INET))
+			goto get_failed;
+
+		rta = vtiinfo[IFLA_VTI_REMOTE];
+		if (rta && get_addr_rta(&daddr, rta, AF_INET))
+			goto get_failed;
+
+		if (vtiinfo[IFLA_VTI_IKEY])
+			ikey = rta_getattr_u32(vtiinfo[IFLA_VTI_IKEY]);
+
+		if (vtiinfo[IFLA_VTI_OKEY])
+			okey = rta_getattr_u32(vtiinfo[IFLA_VTI_OKEY]);
+
+		if (vtiinfo[IFLA_VTI_LINK])
+			link = rta_getattr_u8(vtiinfo[IFLA_VTI_LINK]);
+
+		if (vtiinfo[IFLA_VTI_FWMARK])
+			fwmark = rta_getattr_u32(vtiinfo[IFLA_VTI_FWMARK]);
+
+		free(answer);
+	}
+
+	while (argc > 0) {
+		if (!matches(*argv, "key")) {
+			NEXT_ARG();
+			ikey = okey = tnl_parse_key("key", *argv);
+		} else if (!matches(*argv, "ikey")) {
+			NEXT_ARG();
+			ikey = tnl_parse_key("ikey", *argv);
+		} else if (!matches(*argv, "okey")) {
+			NEXT_ARG();
+			okey = tnl_parse_key("okey", *argv);
+		} else if (!matches(*argv, "remote")) {
+			NEXT_ARG();
+			get_addr(&daddr, *argv, AF_INET);
+		} else if (!matches(*argv, "local")) {
+			NEXT_ARG();
+			get_addr(&saddr, *argv, AF_INET);
+		} else if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			link = ll_name_to_index(*argv);
+			if (!link)
+				exit(nodev(*argv));
+		} else if (strcmp(*argv, "fwmark") == 0) {
+			NEXT_ARG();
+			if (get_u32(&fwmark, *argv, 0))
+				invarg("invalid fwmark\n", *argv);
+		} else {
+			vti_print_help(lu, argc, argv, stderr);
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	addattr32(n, 1024, IFLA_VTI_IKEY, ikey);
+	addattr32(n, 1024, IFLA_VTI_OKEY, okey);
+	if (is_addrtype_inet_not_unspec(&saddr))
+		addattr_l(n, 1024, IFLA_VTI_LOCAL, saddr.data, saddr.bytelen);
+	if (is_addrtype_inet_not_unspec(&daddr))
+		addattr_l(n, 1024, IFLA_VTI_REMOTE, daddr.data, daddr.bytelen);
+	addattr32(n, 1024, IFLA_VTI_FWMARK, fwmark);
+	if (link)
+		addattr32(n, 1024, IFLA_VTI_LINK, link);
+
+	return 0;
+}
+
+static void vti_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s2[64];
+
+	if (!tb)
+		return;
+
+	tnl_print_endpoint("remote", tb[IFLA_VTI_REMOTE], AF_INET);
+	tnl_print_endpoint("local", tb[IFLA_VTI_LOCAL], AF_INET);
+
+	if (tb[IFLA_VTI_LINK]) {
+		__u32 link = rta_getattr_u32(tb[IFLA_VTI_LINK]);
+
+		if (link) {
+			print_string(PRINT_ANY, "link", "dev %s ",
+				     ll_index_to_name(link));
+		}
+	}
+
+	if (tb[IFLA_VTI_IKEY]) {
+		struct rtattr *rta = tb[IFLA_VTI_IKEY];
+		__u32 key = rta_getattr_u32(rta);
+
+		if (key && inet_ntop(AF_INET, RTA_DATA(rta), s2, sizeof(s2)))
+			print_string(PRINT_ANY, "ikey", "ikey %s ", s2);
+	}
+
+	if (tb[IFLA_VTI_OKEY]) {
+		struct rtattr *rta = tb[IFLA_VTI_OKEY];
+		__u32 key = rta_getattr_u32(rta);
+
+		if (key && inet_ntop(AF_INET, RTA_DATA(rta), s2, sizeof(s2)))
+			print_string(PRINT_ANY, "okey", "okey %s ", s2);
+	}
+
+	if (tb[IFLA_VTI_FWMARK]) {
+		__u32 fwmark = rta_getattr_u32(tb[IFLA_VTI_FWMARK]);
+
+		if (fwmark) {
+			print_0xhex(PRINT_ANY,
+				    "fwmark", "fwmark %#llx ", fwmark);
+		}
+	}
+}
+
+struct link_util vti_link_util = {
+	.id = "vti",
+	.maxattr = IFLA_VTI_MAX,
+	.parse_opt = vti_parse_opt,
+	.print_opt = vti_print_opt,
+	.print_help = vti_print_help,
+};
diff --git a/ip/link_vti6.c b/ip/link_vti6.c
new file mode 100644
index 0000000..0b080fa
--- /dev/null
+++ b/ip/link_vti6.c
@@ -0,0 +1,218 @@
+/*
+ * link_vti6.c	VTI driver module
+ *
+ *		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.
+ *
+ * Authors:	Herbert Xu <herbert@gondor.apana.org.au>
+ *		Saurabh Mohan <saurabh.mohan@vyatta.com> Modified link_gre.c for VTI
+ *		Steffen Klassert <steffen.klassert@secunet.com> Modified link_vti.c for IPv6
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void vti6_print_help(struct link_util *lu, int argc, char **argv,
+			    FILE *f)
+{
+	fprintf(f,
+		"Usage: ... %-4s	[ remote ADDR ]\n"
+		"		[ local ADDR ]\n"
+		"		[ [i|o]key KEY ]\n"
+		"		[ dev PHYS_DEV ]\n"
+		"		[ fwmark MARK ]\n"
+		"\n"
+		"Where:	ADDR := { IPV6_ADDRESS }\n"
+		"	KEY  := { DOTTED_QUAD | NUMBER }\n"
+		"	MARK := { 0x0..0xffffffff }\n",
+		lu->id);
+}
+
+static int vti6_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETLINK,
+		.i.ifi_family = preferred_family,
+		.i.ifi_index = ifi->ifi_index,
+	};
+	struct nlmsghdr *answer;
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *vtiinfo[IFLA_VTI_MAX + 1];
+	__be32 ikey = 0;
+	__be32 okey = 0;
+	inet_prefix saddr, daddr;
+	unsigned int link = 0;
+	__u32 fwmark = 0;
+	int len;
+
+	inet_prefix_reset(&saddr);
+	inet_prefix_reset(&daddr);
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		const struct rtattr *rta;
+
+		if (rtnl_talk(&rth, &req.n, &answer) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = answer->nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(NLMSG_DATA(answer)), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(vtiinfo, IFLA_VTI_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		rta = vtiinfo[IFLA_VTI_LOCAL];
+		if (rta && get_addr_rta(&saddr, rta, AF_INET6))
+			goto get_failed;
+
+		rta = vtiinfo[IFLA_VTI_REMOTE];
+		if (rta && get_addr_rta(&daddr, rta, AF_INET6))
+			goto get_failed;
+
+		if (vtiinfo[IFLA_VTI_IKEY])
+			ikey = rta_getattr_u32(vtiinfo[IFLA_VTI_IKEY]);
+
+		if (vtiinfo[IFLA_VTI_OKEY])
+			okey = rta_getattr_u32(vtiinfo[IFLA_VTI_OKEY]);
+
+		if (vtiinfo[IFLA_VTI_LINK])
+			link = rta_getattr_u8(vtiinfo[IFLA_VTI_LINK]);
+
+		if (vtiinfo[IFLA_VTI_FWMARK])
+			fwmark = rta_getattr_u32(vtiinfo[IFLA_VTI_FWMARK]);
+
+		free(answer);
+	}
+
+	while (argc > 0) {
+		if (!matches(*argv, "key")) {
+			NEXT_ARG();
+			ikey = okey = tnl_parse_key("key", *argv);
+		} else if (!matches(*argv, "ikey")) {
+			NEXT_ARG();
+			ikey = tnl_parse_key("ikey", *argv);
+		} else if (!matches(*argv, "okey")) {
+			NEXT_ARG();
+			okey = tnl_parse_key("okey", *argv);
+		} else if (!matches(*argv, "remote")) {
+			NEXT_ARG();
+			get_addr(&daddr, *argv, AF_INET6);
+		} else if (!matches(*argv, "local")) {
+			NEXT_ARG();
+			get_addr(&saddr, *argv, AF_INET6);
+		} else if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			link = ll_name_to_index(*argv);
+			if (!link)
+				exit(nodev(*argv));
+		} else if (strcmp(*argv, "fwmark") == 0) {
+			NEXT_ARG();
+			if (get_u32(&fwmark, *argv, 0))
+				invarg("invalid fwmark\n", *argv);
+		} else {
+			vti6_print_help(lu, argc, argv, stderr);
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	addattr32(n, 1024, IFLA_VTI_IKEY, ikey);
+	addattr32(n, 1024, IFLA_VTI_OKEY, okey);
+	if (is_addrtype_inet_not_unspec(&saddr))
+		addattr_l(n, 1024, IFLA_VTI_LOCAL, saddr.data, saddr.bytelen);
+	if (is_addrtype_inet_not_unspec(&daddr))
+		addattr_l(n, 1024, IFLA_VTI_REMOTE, daddr.data, daddr.bytelen);
+	addattr32(n, 1024, IFLA_VTI_FWMARK, fwmark);
+	if (link)
+		addattr32(n, 1024, IFLA_VTI_LINK, link);
+
+	return 0;
+}
+
+static void vti6_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s2[64];
+
+	if (!tb)
+		return;
+
+	tnl_print_endpoint("remote", tb[IFLA_VTI_REMOTE], AF_INET6);
+	tnl_print_endpoint("local", tb[IFLA_VTI_LOCAL], AF_INET6);
+
+	if (tb[IFLA_VTI_LINK]) {
+		__u32 link = rta_getattr_u32(tb[IFLA_VTI_LINK]);
+
+		if (link) {
+			print_string(PRINT_ANY, "link", "dev %s ",
+				     ll_index_to_name(link));
+		}
+	}
+
+	if (tb[IFLA_VTI_IKEY]) {
+		struct rtattr *rta = tb[IFLA_VTI_IKEY];
+		__u32 key = rta_getattr_u32(rta);
+
+		if (key && inet_ntop(AF_INET, RTA_DATA(rta), s2, sizeof(s2)))
+			print_string(PRINT_ANY, "ikey", "ikey %s ", s2);
+	}
+
+	if (tb[IFLA_VTI_OKEY]) {
+		struct rtattr *rta = tb[IFLA_VTI_OKEY];
+		__u32 key = rta_getattr_u32(rta);
+
+		if (key && inet_ntop(AF_INET, RTA_DATA(rta), s2, sizeof(s2)))
+			print_string(PRINT_ANY, "okey", "okey %s ", s2);
+	}
+
+	if (tb[IFLA_VTI_FWMARK]) {
+		__u32 fwmark = rta_getattr_u32(tb[IFLA_VTI_FWMARK]);
+
+		if (fwmark) {
+			print_0xhex(PRINT_ANY,
+				    "fwmark", "fwmark %#llx ", fwmark);
+		}
+	}
+}
+
+struct link_util vti6_link_util = {
+	.id = "vti6",
+	.maxattr = IFLA_VTI_MAX,
+	.parse_opt = vti6_parse_opt,
+	.print_opt = vti6_print_opt,
+	.print_help = vti6_print_help,
+};
diff --git a/ip/link_xfrm.c b/ip/link_xfrm.c
new file mode 100644
index 0000000..a28f308
--- /dev/null
+++ b/ip/link_xfrm.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * link_xfrm.c	Virtual XFRM Interface driver module
+ *
+ * Authors:	Matt Ellison <matt@arroyo.io>
+ */
+
+#include <string.h>
+#include <linux/if_link.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+static void xfrm_print_help(struct link_util *lu, int argc, char **argv,
+			    FILE *f)
+{
+	fprintf(f,
+		"Usage: ... %-4s dev [ PHYS_DEV ] [ if_id IF-ID ]\n"
+		"\n"
+		"Where: IF-ID := { 0x0..0xffffffff }\n",
+		lu->id);
+}
+
+static int xfrm_parse_opt(struct link_util *lu, int argc, char **argv,
+			  struct nlmsghdr *n)
+{
+	unsigned int link = 0;
+	__u32 if_id = 0;
+
+	while (argc > 0) {
+		if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			link = ll_name_to_index(*argv);
+			if (!link)
+				exit(nodev(*argv));
+		} else if (!matches(*argv, "if_id")) {
+			NEXT_ARG();
+			if (!get_u32(&if_id, *argv, 0))
+				addattr32(n, 1024, IFLA_XFRM_IF_ID, if_id);
+		} else {
+			xfrm_print_help(lu, argc, argv, stderr);
+			return -1;
+		}
+		argc--; argv++;
+	}
+
+	if (link)
+		addattr32(n, 1024, IFLA_XFRM_LINK, link);
+
+	return 0;
+}
+
+static void xfrm_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_XFRM_IF_ID]) {
+		__u32 id = rta_getattr_u32(tb[IFLA_XFRM_IF_ID]);
+
+		print_0xhex(PRINT_ANY, "if_id", "if_id %#llx ", id);
+
+	}
+
+}
+
+struct link_util xfrm_link_util = {
+	.id = "xfrm",
+	.maxattr = IFLA_XFRM_MAX,
+	.parse_opt = xfrm_parse_opt,
+	.print_opt = xfrm_print_opt,
+	.print_help = xfrm_print_help,
+};
diff --git a/ip/routef b/ip/routef
new file mode 100644
index 0000000..c251e7b
--- /dev/null
+++ b/ip/routef
@@ -0,0 +1,10 @@
+#! /bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+if [ -z "$*" ] ; then
+	exec ip -4 ro flush  scope global  type unicast
+else
+	echo "Usage: routef"
+	echo
+	echo "This script will flush the IPv4 routing table"
+fi
diff --git a/ip/routel b/ip/routel
new file mode 100644
index 0000000..7056886
--- /dev/null
+++ b/ip/routel
@@ -0,0 +1,72 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+#
+# Script created by: Stephen R. van den Berg <srb@cuci.nl>, 1999/04/18
+# Donated to the public domain.
+#
+# This script transforms the output of "ip" into more readable text.
+# "ip" is the Linux-advanced-routing configuration tool part of the
+# iproute package.
+#
+
+test "X-h" = "X$1" && echo "Usage: $0 [tablenr [raw ip args...]]" && exit 64
+
+test -z "$*" && set 0
+
+ip route list table "$@" |
+ while read network rest
+ do set xx $rest
+    shift
+    proto=""
+    via=""
+    dev=""
+    scope=""
+    src=""
+    table=""
+    case $network in
+       broadcast|local|unreachable) via=$network
+          network=$1
+          shift
+          ;;
+    esac
+    while test $# != 0
+    do
+       case "$1" in
+          proto|via|dev|scope|src|table)
+             key=$1
+             val=$2
+             eval "$key='$val'"
+             shift 2
+             ;;
+          dead|onlink|pervasive|offload|notify|linkdown|unresolved)
+             shift
+             ;;
+          *)
+             # avoid infinite loop on unknown keyword without value at line end
+             shift
+             shift
+             ;;
+       esac
+    done
+    echo "$network	$via	$src	$proto	$scope	$dev	$table"
+ done | awk -F '	' '
+BEGIN {
+   format="%15s%-3s %15s %15s %8s %8s%7s %s\n";
+   printf(format,"target","","gateway","source","proto","scope","dev","tbl");
+ }
+ { network=$1;
+   mask="";
+   if(match(network,"/"))
+    { mask=" "substr(network,RSTART+1);
+      network=substr(network,0,RSTART);
+    }
+   via=$2;
+   src=$3;
+   proto=$4;
+   scope=$5;
+   dev=$6;
+   table=$7;
+   printf(format,network,mask,via,src,proto,scope,dev,table);
+ }
+'
diff --git a/ip/rtm_map.c b/ip/rtm_map.c
new file mode 100644
index 0000000..8d8eafe
--- /dev/null
+++ b/ip/rtm_map.c
@@ -0,0 +1,129 @@
+/*
+ * rtm_map.c
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "rt_names.h"
+#include "utils.h"
+
+char *rtnl_rtntype_n2a(int id, char *buf, int len)
+{
+
+	if (numeric) {
+		snprintf(buf, len, "%d", id);
+		return buf;
+	}
+
+	switch (id) {
+	case RTN_UNSPEC:
+		return "none";
+	case RTN_UNICAST:
+		return "unicast";
+	case RTN_LOCAL:
+		return "local";
+	case RTN_BROADCAST:
+		return "broadcast";
+	case RTN_ANYCAST:
+		return "anycast";
+	case RTN_MULTICAST:
+		return "multicast";
+	case RTN_BLACKHOLE:
+		return "blackhole";
+	case RTN_UNREACHABLE:
+		return "unreachable";
+	case RTN_PROHIBIT:
+		return "prohibit";
+	case RTN_THROW:
+		return "throw";
+	case RTN_NAT:
+		return "nat";
+	case RTN_XRESOLVE:
+		return "xresolve";
+	default:
+		snprintf(buf, len, "%d", id);
+		return buf;
+	}
+}
+
+
+int rtnl_rtntype_a2n(int *id, char *arg)
+{
+	char *end;
+	unsigned long res;
+
+	if (strcmp(arg, "local") == 0)
+		res = RTN_LOCAL;
+	else if (strcmp(arg, "nat") == 0)
+		res = RTN_NAT;
+	else if (matches(arg, "broadcast") == 0 ||
+		 strcmp(arg, "brd") == 0)
+		res = RTN_BROADCAST;
+	else if (matches(arg, "anycast") == 0)
+		res = RTN_ANYCAST;
+	else if (matches(arg, "multicast") == 0)
+		res = RTN_MULTICAST;
+	else if (matches(arg, "prohibit") == 0)
+		res = RTN_PROHIBIT;
+	else if (matches(arg, "unreachable") == 0)
+		res = RTN_UNREACHABLE;
+	else if (matches(arg, "blackhole") == 0)
+		res = RTN_BLACKHOLE;
+	else if (matches(arg, "xresolve") == 0)
+		res = RTN_XRESOLVE;
+	else if (matches(arg, "unicast") == 0)
+		res = RTN_UNICAST;
+	else if (strcmp(arg, "throw") == 0)
+		res = RTN_THROW;
+	else {
+		res = strtoul(arg, &end, 0);
+		if (!end || end == arg || *end || res > 255)
+			return -1;
+	}
+	*id = res;
+	return 0;
+}
+
+static int get_rt_realms(__u32 *realms, char *arg)
+{
+	__u32 realm = 0;
+	char *p = strchr(arg, '/');
+
+	*realms = 0;
+	if (p) {
+		*p = 0;
+		if (rtnl_rtrealm_a2n(realms, arg)) {
+			*p = '/';
+			return -1;
+		}
+		*realms <<= 16;
+		*p = '/';
+		arg = p+1;
+	}
+	if (*arg && rtnl_rtrealm_a2n(&realm, arg))
+		return -1;
+	*realms |= realm;
+	return 0;
+}
+
+int get_rt_realms_or_raw(__u32 *realms, char *arg)
+{
+	if (!get_rt_realms(realms, arg))
+		return 0;
+
+	return get_unsigned(realms, arg, 0);
+}
diff --git a/ip/rtmon.c b/ip/rtmon.c
new file mode 100644
index 0000000..bccdded
--- /dev/null
+++ b/ip/rtmon.c
@@ -0,0 +1,184 @@
+/*
+ * rtmon.c		RTnetlink listener.
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <string.h>
+
+#include "SNAPSHOT.h"
+
+#include "utils.h"
+#include "libnetlink.h"
+
+static int init_phase = 1;
+
+static void write_stamp(FILE *fp)
+{
+	char buf[128];
+	struct nlmsghdr *n1 = (void *)buf;
+	struct timeval tv;
+
+	n1->nlmsg_type = NLMSG_TSTAMP;
+	n1->nlmsg_flags = 0;
+	n1->nlmsg_seq = 0;
+	n1->nlmsg_pid = 0;
+	n1->nlmsg_len = NLMSG_LENGTH(4*2);
+	gettimeofday(&tv, NULL);
+	((__u32 *)NLMSG_DATA(n1))[0] = tv.tv_sec;
+	((__u32 *)NLMSG_DATA(n1))[1] = tv.tv_usec;
+	fwrite((void *)n1, 1, NLMSG_ALIGN(n1->nlmsg_len), fp);
+}
+
+static int dump_msg(struct rtnl_ctrl_data *ctrl,
+		    struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+
+	if (!init_phase)
+		write_stamp(fp);
+	fwrite((void *)n, 1, NLMSG_ALIGN(n->nlmsg_len), fp);
+	fflush(fp);
+	return 0;
+}
+
+static int dump_msg2(struct nlmsghdr *n, void *arg)
+{
+	return dump_msg(NULL, n, arg);
+}
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: rtmon [ OPTIONS ] file FILE [ all | LISTofOBJECTS ]\n"
+		"OPTIONS := { -f[amily] { inet | inet6 | link | help } |\n"
+		"	     -4 | -6 | -0 | -V[ersion] }\n"
+		"LISTofOBJECTS := [ link ] [ address ] [ route ]\n");
+	exit(-1);
+}
+
+int
+main(int argc, char **argv)
+{
+	FILE *fp;
+	struct rtnl_handle rth;
+	int family = AF_UNSPEC;
+	unsigned int groups = ~0U;
+	int llink = 0;
+	int laddr = 0;
+	int lroute = 0;
+	char *file = NULL;
+
+	while (argc > 1) {
+		if (matches(argv[1], "-family") == 0) {
+			argc--;
+			argv++;
+			if (argc <= 1)
+				usage();
+			if (strcmp(argv[1], "inet") == 0)
+				family = AF_INET;
+			else if (strcmp(argv[1], "inet6") == 0)
+				family = AF_INET6;
+			else if (strcmp(argv[1], "link") == 0)
+				family = AF_INET6;
+			else if (strcmp(argv[1], "help") == 0)
+				usage();
+			else {
+				fprintf(stderr, "Protocol ID \"%s\" is unknown, try \"rtmon help\".\n", argv[1]);
+				exit(-1);
+			}
+		} else if (strcmp(argv[1], "-4") == 0) {
+			family = AF_INET;
+		} else if (strcmp(argv[1], "-6") == 0) {
+			family = AF_INET6;
+		} else if (strcmp(argv[1], "-0") == 0) {
+			family = AF_PACKET;
+		} else if (matches(argv[1], "-Version") == 0) {
+			printf("rtmon utility, iproute2-ss%s\n", SNAPSHOT);
+			exit(0);
+		} else if (matches(argv[1], "file") == 0) {
+			argc--;
+			argv++;
+			if (argc <= 1)
+				usage();
+			file = argv[1];
+		} else if (matches(argv[1], "link") == 0) {
+			llink = 1;
+			groups = 0;
+		} else if (matches(argv[1], "address") == 0) {
+			laddr = 1;
+			groups = 0;
+		} else if (matches(argv[1], "route") == 0) {
+			lroute = 1;
+			groups = 0;
+		} else if (strcmp(argv[1], "all") == 0) {
+			groups = ~0U;
+		} else if (matches(argv[1], "help") == 0) {
+			usage();
+		} else {
+			fprintf(stderr, "Argument \"%s\" is unknown, try \"rtmon help\".\n", argv[1]);
+			exit(-1);
+		}
+		argc--;	argv++;
+	}
+
+	if (file == NULL) {
+		fprintf(stderr, "Not enough information: argument \"file\" is required\n");
+		exit(-1);
+	}
+	if (llink)
+		groups |= nl_mgrp(RTNLGRP_LINK);
+	if (laddr) {
+		if (!family || family == AF_INET)
+			groups |= nl_mgrp(RTNLGRP_IPV4_IFADDR);
+		if (!family || family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_IFADDR);
+	}
+	if (lroute) {
+		if (!family || family == AF_INET)
+			groups |= nl_mgrp(RTNLGRP_IPV4_ROUTE);
+		if (!family || family == AF_INET6)
+			groups |= nl_mgrp(RTNLGRP_IPV6_ROUTE);
+	}
+
+	fp = fopen(file, "w");
+	if (fp == NULL) {
+		perror("Cannot fopen");
+		exit(-1);
+	}
+
+	if (rtnl_open(&rth, groups) < 0)
+		exit(1);
+
+	if (rtnl_linkdump_req(&rth, AF_UNSPEC) < 0) {
+		perror("Cannot send dump request");
+		exit(1);
+	}
+
+	write_stamp(fp);
+
+	if (rtnl_dump_filter(&rth, dump_msg2, fp) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return 1;
+	}
+
+	init_phase = 0;
+
+	if (rtnl_listen(&rth, dump_msg, (void *)fp) < 0)
+		exit(2);
+
+	exit(0);
+}
diff --git a/ip/rtpr b/ip/rtpr
new file mode 100644
index 0000000..7e48674
--- /dev/null
+++ b/ip/rtpr
@@ -0,0 +1,5 @@
+#! /bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+exec tr "[\\\\]" "[
+]"
diff --git a/ip/static-syms.c b/ip/static-syms.c
new file mode 100644
index 0000000..47c4092
--- /dev/null
+++ b/ip/static-syms.c
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This file creates a dummy version of dynamic loading
+ * for environments where dynamic linking
+ * is not used or available.
+ */
+
+#include <string.h>
+#include "dlfcn.h"
+
+void *_dlsym(const char *sym)
+{
+#include "static-syms.h"
+	return NULL;
+}
diff --git a/ip/tcp_metrics.c b/ip/tcp_metrics.c
new file mode 100644
index 0000000..acbd745
--- /dev/null
+++ b/ip/tcp_metrics.c
@@ -0,0 +1,542 @@
+/*
+ * tcp_metrics.c	"ip tcp_metrics/tcpmetrics"
+ *
+ *		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;
+ *
+ * Authors:	Julian Anastasov <ja@ssi.bg>, August 2012
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+
+#include <linux/genetlink.h>
+#include <linux/tcp_metrics.h>
+
+#include "utils.h"
+#include "ip_common.h"
+#include "libgenl.h"
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage:	ip tcp_metrics/tcpmetrics { COMMAND | help }\n"
+		"	ip tcp_metrics { show | flush } SELECTOR\n"
+		"	ip tcp_metrics delete [ address ] ADDRESS\n"
+		"SELECTOR := [ [ address ] PREFIX ]\n");
+	exit(-1);
+}
+
+/* netlink socket */
+static struct rtnl_handle grth = { .fd = -1 };
+static int genl_family = -1;
+static const double usec_per_sec = 1000000.;
+
+#define TCPM_REQUEST(_req, _bufsiz, _cmd, _flags) \
+	GENL_REQUEST(_req, _bufsiz, genl_family, 0, \
+		     TCP_METRICS_GENL_VERSION, _cmd, _flags)
+
+#define CMD_LIST	0x0001	/* list, lst, show		*/
+#define CMD_DEL		0x0002	/* delete, remove		*/
+#define CMD_FLUSH	0x0004	/* flush			*/
+
+static const struct {
+	const char *name;
+	int	code;
+} cmds[] = {
+	{	"list",		CMD_LIST	},
+	{	"lst",		CMD_LIST	},
+	{	"show",		CMD_LIST	},
+	{	"delete",	CMD_DEL		},
+	{	"remove",	CMD_DEL		},
+	{	"flush",	CMD_FLUSH	},
+};
+
+static const char *metric_name[TCP_METRIC_MAX + 1] = {
+	[TCP_METRIC_RTT]		= "rtt",
+	[TCP_METRIC_RTTVAR]		= "rttvar",
+	[TCP_METRIC_SSTHRESH]		= "ssthresh",
+	[TCP_METRIC_CWND]		= "cwnd",
+	[TCP_METRIC_REORDERING]		= "reordering",
+};
+
+static struct {
+	int flushed;
+	char *flushb;
+	int flushp;
+	int flushe;
+	int cmd;
+	inet_prefix daddr;
+	inet_prefix saddr;
+} f;
+
+static int flush_update(void)
+{
+	if (rtnl_send_check(&grth, f.flushb, f.flushp) < 0) {
+		perror("Failed to send flush request\n");
+		return -1;
+	}
+	f.flushp = 0;
+	return 0;
+}
+
+static void print_tcp_metrics(struct rtattr *a)
+{
+	struct rtattr *m[TCP_METRIC_MAX + 1 + 1];
+	unsigned long rtt = 0, rttvar = 0;
+	int i;
+
+	parse_rtattr_nested(m, TCP_METRIC_MAX + 1, a);
+
+	for (i = 0; i < TCP_METRIC_MAX + 1; i++) {
+		const char *name;
+		__u32 val;
+		SPRINT_BUF(b1);
+
+		a = m[i + 1];
+		if (!a)
+			continue;
+
+		val = rta_getattr_u32(a);
+
+		switch (i) {
+		case TCP_METRIC_RTT:
+			if (!rtt)
+				rtt = (val * 1000UL) >> 3;
+			continue;
+		case TCP_METRIC_RTTVAR:
+			if (!rttvar)
+				rttvar = (val * 1000UL) >> 2;
+			continue;
+		case TCP_METRIC_RTT_US:
+			rtt = val >> 3;
+			continue;
+
+		case TCP_METRIC_RTTVAR_US:
+			rttvar = val >> 2;
+			continue;
+
+		case TCP_METRIC_SSTHRESH:
+		case TCP_METRIC_CWND:
+		case TCP_METRIC_REORDERING:
+			name = metric_name[i];
+			break;
+
+		default:
+			snprintf(b1, sizeof(b1),
+				 " metric_%d ", i);
+			name = b1;
+		}
+
+
+		print_uint(PRINT_JSON, name, NULL, val);
+		print_string(PRINT_FP, NULL, " %s ", name);
+		print_uint(PRINT_FP, NULL, "%u", val);
+	}
+
+	if (rtt) {
+		print_float(PRINT_JSON, "rtt", NULL,
+			    (double)rtt / usec_per_sec);
+		print_u64(PRINT_FP, NULL,
+			   " rtt %luus", rtt);
+	}
+	if (rttvar) {
+		print_float(PRINT_JSON, "rttvar", NULL,
+			    (double) rttvar / usec_per_sec);
+		print_u64(PRINT_FP, NULL,
+			   " rttvar %luus", rttvar);
+	}
+}
+
+static int process_msg(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *) arg;
+	struct genlmsghdr *ghdr;
+	struct rtattr *attrs[TCP_METRICS_ATTR_MAX + 1], *a;
+	const char *h;
+	int len = n->nlmsg_len;
+	inet_prefix daddr, saddr;
+	int atype, stype;
+
+	if (n->nlmsg_type != genl_family)
+		return -1;
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+	if (len < 0)
+		return -1;
+
+	ghdr = NLMSG_DATA(n);
+	if (ghdr->cmd != TCP_METRICS_CMD_GET)
+		return 0;
+
+	parse_rtattr(attrs, TCP_METRICS_ATTR_MAX, (void *) ghdr + GENL_HDRLEN,
+		     len);
+
+	if (attrs[TCP_METRICS_ATTR_ADDR_IPV4]) {
+		if (f.daddr.family && f.daddr.family != AF_INET)
+			return 0;
+		a = attrs[TCP_METRICS_ATTR_ADDR_IPV4];
+		daddr.family = AF_INET;
+		atype = TCP_METRICS_ATTR_ADDR_IPV4;
+	} else if (attrs[TCP_METRICS_ATTR_ADDR_IPV6]) {
+		if (f.daddr.family && f.daddr.family != AF_INET6)
+			return 0;
+		a = attrs[TCP_METRICS_ATTR_ADDR_IPV6];
+		daddr.family = AF_INET6;
+		atype = TCP_METRICS_ATTR_ADDR_IPV6;
+	} else {
+		return 0;
+	}
+
+	if (get_addr_rta(&daddr, a, daddr.family))
+		return 0;
+
+	if (f.daddr.family && f.daddr.bitlen >= 0 &&
+	    inet_addr_match(&daddr, &f.daddr, f.daddr.bitlen))
+		return 0;
+
+	if (attrs[TCP_METRICS_ATTR_SADDR_IPV4]) {
+		if (f.saddr.family && f.saddr.family != AF_INET)
+			return 0;
+		a = attrs[TCP_METRICS_ATTR_SADDR_IPV4];
+		saddr.family = AF_INET;
+		stype = TCP_METRICS_ATTR_SADDR_IPV4;
+	} else if (attrs[TCP_METRICS_ATTR_SADDR_IPV6]) {
+		if (f.saddr.family && f.saddr.family != AF_INET6)
+			return 0;
+		a = attrs[TCP_METRICS_ATTR_SADDR_IPV6];
+		saddr.family = AF_INET6;
+		stype = TCP_METRICS_ATTR_SADDR_IPV6;
+	} else {
+		saddr.family = AF_UNSPEC;
+		stype = 0;
+	}
+
+	/* Only get/check for the source-address if the kernel supports it. */
+	if (saddr.family) {
+		if (get_addr_rta(&saddr, a, saddr.family))
+			return 0;
+
+		if (f.saddr.family && f.saddr.bitlen >= 0 &&
+		    inet_addr_match(&saddr, &f.saddr, f.saddr.bitlen))
+			return 0;
+	}
+
+	if (f.flushb) {
+		struct nlmsghdr *fn;
+
+		TCPM_REQUEST(req2, 128, TCP_METRICS_CMD_DEL, NLM_F_REQUEST);
+
+		addattr_l(&req2.n, sizeof(req2), atype, daddr.data,
+			  daddr.bytelen);
+		if (saddr.family)
+			addattr_l(&req2.n, sizeof(req2), stype, saddr.data,
+				  saddr.bytelen);
+
+		if (NLMSG_ALIGN(f.flushp) + req2.n.nlmsg_len > f.flushe) {
+			if (flush_update())
+				return -1;
+		}
+		fn = (struct nlmsghdr *) (f.flushb + NLMSG_ALIGN(f.flushp));
+		memcpy(fn, &req2.n, req2.n.nlmsg_len);
+		fn->nlmsg_seq = ++grth.seq;
+		f.flushp = (((char *) fn) + req2.n.nlmsg_len) - f.flushb;
+		f.flushed++;
+		if (show_stats < 2)
+			return 0;
+	}
+
+	open_json_object(NULL);
+	if (f.cmd & (CMD_DEL | CMD_FLUSH))
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	h = format_host(daddr.family, daddr.bytelen, daddr.data);
+	print_color_string(PRINT_ANY,
+			   ifa_family_color(daddr.family),
+			   "dst", "%s", h);
+
+	a = attrs[TCP_METRICS_ATTR_AGE];
+	if (a) {
+		__u64 val = rta_getattr_u64(a);
+		double age = val / 1000.;
+
+		print_float(PRINT_ANY, "age",
+			     " age %.03fsec", age);
+	}
+
+	a = attrs[TCP_METRICS_ATTR_TW_TS_STAMP];
+	if (a) {
+		__s32 val = (__s32) rta_getattr_u32(a);
+		__u32 tsval;
+		char tw_ts[64];
+
+		a = attrs[TCP_METRICS_ATTR_TW_TSVAL];
+		tsval = a ? rta_getattr_u32(a) : 0;
+		snprintf(tw_ts, sizeof(tw_ts),
+			 "%u/%d", tsval, val);
+		print_string(PRINT_ANY, "tw_ts_stamp",
+		     " tw_ts %s ago", tw_ts);
+	}
+
+	if (attrs[TCP_METRICS_ATTR_VALS])
+		print_tcp_metrics(attrs[TCP_METRICS_ATTR_VALS]);
+
+	a = attrs[TCP_METRICS_ATTR_FOPEN_MSS];
+	if (a) {
+		print_uint(PRINT_ANY, "fopen_miss", " fo_mss %u",
+			   rta_getattr_u16(a));
+	}
+
+	a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROPS];
+	if (a) {
+		__u16 syn_loss = rta_getattr_u16(a);
+		double ts;
+
+		a = attrs[TCP_METRICS_ATTR_FOPEN_SYN_DROP_TS];
+		ts = a ? rta_getattr_u64(a) : 0;
+
+		print_uint(PRINT_ANY, "fopen_syn_drops",
+			   " fo_syn_drops %u", syn_loss);
+		print_float(PRINT_ANY, "fopen_syn_drop_ts",
+			     "/%.03fusec ago",
+			     ts / 1000000.);
+	}
+
+	a = attrs[TCP_METRICS_ATTR_FOPEN_COOKIE];
+	if (a) {
+		char cookie[32 + 1];
+		unsigned char *ptr = RTA_DATA(a);
+		int i, max = RTA_PAYLOAD(a);
+
+		if (max > 16)
+			max = 16;
+		cookie[0] = 0;
+		for (i = 0; i < max; i++)
+			sprintf(cookie + i + i, "%02x", ptr[i]);
+
+		print_string(PRINT_ANY, "fo_cookie",
+			     " fo_cookie %s", cookie);
+	}
+
+	if (saddr.family) {
+		const char *src;
+
+		src = format_host(saddr.family, saddr.bytelen, saddr.data);
+		print_string(PRINT_ANY, "source",
+			     " source %s", src);
+	}
+
+	print_string(PRINT_FP, NULL, "\n", "");
+	close_json_object();
+	fflush(fp);
+	return 0;
+}
+
+static int tcpm_do_cmd(int cmd, int argc, char **argv)
+{
+	TCPM_REQUEST(req, 1024, TCP_METRICS_CMD_GET, NLM_F_REQUEST);
+	struct nlmsghdr *answer;
+	int atype = -1, stype = -1;
+	int ack;
+
+	memset(&f, 0, sizeof(f));
+	f.daddr.bitlen = -1;
+	f.daddr.family = preferred_family;
+	f.saddr.bitlen = -1;
+	f.saddr.family = preferred_family;
+
+	switch (preferred_family) {
+	case AF_UNSPEC:
+	case AF_INET:
+	case AF_INET6:
+		break;
+	default:
+		fprintf(stderr, "Unsupported protocol family: %d\n", preferred_family);
+		return -1;
+	}
+
+	for (; argc > 0; argc--, argv++) {
+		if (strcmp(*argv, "src") == 0 ||
+		    strcmp(*argv, "source") == 0) {
+			char *who = *argv;
+
+			NEXT_ARG();
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (f.saddr.bitlen >= 0)
+				duparg2(who, *argv);
+
+			get_prefix(&f.saddr, *argv, preferred_family);
+			if (f.saddr.bytelen && f.saddr.bytelen * 8 == f.saddr.bitlen) {
+				if (f.saddr.family == AF_INET)
+					stype = TCP_METRICS_ATTR_SADDR_IPV4;
+				else if (f.saddr.family == AF_INET6)
+					stype = TCP_METRICS_ATTR_SADDR_IPV6;
+			}
+
+			if (stype < 0) {
+				fprintf(stderr, "Error: a specific IP address is expected rather than \"%s\"\n",
+					*argv);
+				return -1;
+			}
+		} else {
+			char *who = "address";
+
+			if (strcmp(*argv, "addr") == 0 ||
+			    strcmp(*argv, "address") == 0) {
+				who = *argv;
+				NEXT_ARG();
+			}
+			if (matches(*argv, "help") == 0)
+				usage();
+			if (f.daddr.bitlen >= 0)
+				duparg2(who, *argv);
+
+			get_prefix(&f.daddr, *argv, preferred_family);
+			if (f.daddr.bytelen && f.daddr.bytelen * 8 == f.daddr.bitlen) {
+				if (f.daddr.family == AF_INET)
+					atype = TCP_METRICS_ATTR_ADDR_IPV4;
+				else if (f.daddr.family == AF_INET6)
+					atype = TCP_METRICS_ATTR_ADDR_IPV6;
+			}
+			if ((CMD_DEL & cmd) && atype < 0) {
+				fprintf(stderr, "Error: a specific IP address is expected rather than \"%s\"\n",
+					*argv);
+				return -1;
+			}
+		}
+		argc--; argv++;
+	}
+
+	if (cmd == CMD_DEL && atype < 0)
+		missarg("address");
+
+	/* flush for exact address ? Single del */
+	if (cmd == CMD_FLUSH && atype >= 0)
+		cmd = CMD_DEL;
+
+	/* flush for all addresses ? Single del without address */
+	if (cmd == CMD_FLUSH && f.daddr.bitlen <= 0 &&
+	    f.saddr.bitlen <= 0 && preferred_family == AF_UNSPEC) {
+		cmd = CMD_DEL;
+		req.g.cmd = TCP_METRICS_CMD_DEL;
+		ack = 1;
+	} else if (cmd == CMD_DEL) {
+		req.g.cmd = TCP_METRICS_CMD_DEL;
+		ack = 1;
+	} else {	/* CMD_FLUSH, CMD_LIST */
+		ack = 0;
+	}
+
+	if (genl_init_handle(&grth, TCP_METRICS_GENL_NAME, &genl_family))
+		exit(1);
+	req.n.nlmsg_type = genl_family;
+
+	if (!(cmd & CMD_FLUSH) && (atype >= 0 || (cmd & CMD_DEL))) {
+		if (ack)
+			req.n.nlmsg_flags |= NLM_F_ACK;
+		if (atype >= 0)
+			addattr_l(&req.n, sizeof(req), atype, &f.daddr.data,
+				  f.daddr.bytelen);
+		if (stype >= 0)
+			addattr_l(&req.n, sizeof(req), stype, &f.saddr.data,
+				  f.saddr.bytelen);
+	} else {
+		req.n.nlmsg_flags |= NLM_F_DUMP;
+	}
+
+	f.cmd = cmd;
+	if (cmd & CMD_FLUSH) {
+		int round = 0;
+		char flushb[4096-512];
+
+		f.flushb = flushb;
+		f.flushp = 0;
+		f.flushe = sizeof(flushb);
+
+		for (;;) {
+			req.n.nlmsg_seq = grth.dump = ++grth.seq;
+			if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
+				perror("Failed to send flush request");
+				exit(1);
+			}
+			f.flushed = 0;
+			if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
+				fprintf(stderr, "Flush terminated\n");
+				exit(1);
+			}
+			if (f.flushed == 0) {
+				if (round == 0) {
+					fprintf(stderr, "Nothing to flush.\n");
+				} else if (show_stats)
+					printf("*** Flush is complete after %d round%s ***\n",
+					       round, round > 1 ? "s" : "");
+				fflush(stdout);
+				return 0;
+			}
+			round++;
+			if (flush_update() < 0)
+				exit(1);
+			if (show_stats) {
+				printf("\n*** Round %d, deleting %d entries ***\n",
+				       round, f.flushed);
+				fflush(stdout);
+			}
+		}
+		return 0;
+	}
+
+	if (ack) {
+		if (rtnl_talk(&grth, &req.n, NULL) < 0)
+			return -2;
+	} else if (atype >= 0) {
+		if (rtnl_talk(&grth, &req.n, &answer) < 0)
+			return -2;
+		if (process_msg(answer, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+		free(answer);
+	} else {
+		req.n.nlmsg_seq = grth.dump = ++grth.seq;
+		if (rtnl_send(&grth, &req, req.n.nlmsg_len) < 0) {
+			perror("Failed to send dump request");
+			exit(1);
+		}
+
+		new_json_obj(json);
+		if (rtnl_dump_filter(&grth, process_msg, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+		delete_json_obj();
+	}
+	return 0;
+}
+
+int do_tcp_metrics(int argc, char **argv)
+{
+	int i;
+
+	if (argc < 1)
+		return tcpm_do_cmd(CMD_LIST, 0, NULL);
+	for (i = 0; i < ARRAY_SIZE(cmds); i++) {
+		if (matches(argv[0], cmds[i].name) == 0)
+			return tcpm_do_cmd(cmds[i].code, argc-1, argv+1);
+	}
+	if (matches(argv[0], "help") == 0)
+		usage();
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip tcp_metrics help\".\n",
+			*argv);
+	exit(-1);
+}
diff --git a/ip/tunnel.c b/ip/tunnel.c
new file mode 100644
index 0000000..88585cf
--- /dev/null
+++ b/ip/tunnel.c
@@ -0,0 +1,452 @@
+/*
+ * 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 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, see <http://www.gnu.org/licenses>.
+ */
+/*
+ * split from ip_tunnel.c
+ */
+/*
+ * Author:
+ *	Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <linux/if.h>
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/if_arp.h>
+
+#include "utils.h"
+#include "tunnel.h"
+#include "json_print.h"
+
+const char *tnl_strproto(__u8 proto)
+{
+	switch (proto) {
+	case IPPROTO_IPIP:
+		return "ip";
+	case IPPROTO_GRE:
+		return "gre";
+	case IPPROTO_IPV6:
+		return "ipv6";
+	case IPPROTO_ESP:
+		return "esp";
+	case IPPROTO_MPLS:
+		return "mpls";
+	case 0:
+		return "any";
+	default:
+		return "unknown";
+	}
+}
+
+int tnl_get_ioctl(const char *basedev, void *p)
+{
+	struct ifreq ifr;
+	int fd;
+	int err;
+
+	strlcpy(ifr.ifr_name, basedev, IFNAMSIZ);
+	ifr.ifr_ifru.ifru_data = (void *)p;
+
+	fd = socket(preferred_family, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		fprintf(stderr, "create socket failed: %s\n", strerror(errno));
+		return -1;
+	}
+
+	err = ioctl(fd, SIOCGETTUNNEL, &ifr);
+	if (err)
+		fprintf(stderr, "get tunnel \"%s\" failed: %s\n", basedev,
+			strerror(errno));
+
+	close(fd);
+	return err;
+}
+
+int tnl_add_ioctl(int cmd, const char *basedev, const char *name, void *p)
+{
+	struct ifreq ifr;
+	int fd;
+	int err;
+
+	if (cmd == SIOCCHGTUNNEL && name[0])
+		strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+	else
+		strlcpy(ifr.ifr_name, basedev, IFNAMSIZ);
+	ifr.ifr_ifru.ifru_data = p;
+
+	fd = socket(preferred_family, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		fprintf(stderr, "create socket failed: %s\n", strerror(errno));
+		return -1;
+	}
+
+	err = ioctl(fd, cmd, &ifr);
+	if (err)
+		fprintf(stderr, "add tunnel \"%s\" failed: %s\n", ifr.ifr_name,
+			strerror(errno));
+	close(fd);
+	return err;
+}
+
+int tnl_del_ioctl(const char *basedev, const char *name, void *p)
+{
+	struct ifreq ifr;
+	int fd;
+	int err;
+
+	if (name[0])
+		strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+	else
+		strlcpy(ifr.ifr_name, basedev, IFNAMSIZ);
+
+	ifr.ifr_ifru.ifru_data = p;
+
+	fd = socket(preferred_family, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		fprintf(stderr, "create socket failed: %s\n", strerror(errno));
+		return -1;
+	}
+
+	err = ioctl(fd, SIOCDELTUNNEL, &ifr);
+	if (err)
+		fprintf(stderr, "delete tunnel \"%s\" failed: %s\n",
+			ifr.ifr_name, strerror(errno));
+	close(fd);
+	return err;
+}
+
+static int tnl_gen_ioctl(int cmd, const char *name,
+			 void *p, int skiperr)
+{
+	struct ifreq ifr;
+	int fd;
+	int err;
+
+	strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+	ifr.ifr_ifru.ifru_data = p;
+
+	fd = socket(preferred_family, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		fprintf(stderr, "create socket failed: %s\n", strerror(errno));
+		return -1;
+	}
+
+	err = ioctl(fd, cmd, &ifr);
+	if (err && errno != skiperr)
+		fprintf(stderr, "%s: ioctl %x failed: %s\n", name,
+			cmd, strerror(errno));
+	close(fd);
+	return err;
+}
+
+int tnl_prl_ioctl(int cmd, const char *name, void *p)
+{
+	return tnl_gen_ioctl(cmd, name, p, -1);
+}
+
+int tnl_6rd_ioctl(int cmd, const char *name, void *p)
+{
+	return tnl_gen_ioctl(cmd, name, p, -1);
+}
+
+int tnl_ioctl_get_6rd(const char *name, void *p)
+{
+	return tnl_gen_ioctl(SIOCGET6RD, name, p, EINVAL);
+}
+
+__be32 tnl_parse_key(const char *name, const char *key)
+{
+	unsigned int uval;
+
+	if (strchr(key, '.'))
+		return get_addr32(key);
+
+	if (get_unsigned(&uval, key, 0) < 0) {
+		fprintf(stderr,
+			"invalid value for \"%s\": \"%s\"; it should be an unsigned integer\n",
+			name, key);
+		exit(-1);
+	}
+	return htonl(uval);
+}
+
+static const char *tnl_encap_str(const char *name, int enabled, int port)
+{
+	static const char ne[][sizeof("no")] = {
+		[0] = "no",
+		[1] = "",
+	};
+	static char buf[32];
+	char b1[16];
+	const char *val;
+
+	if (!port) {
+		val = "auto ";
+	} else if (port < 0) {
+		val = "";
+	} else {
+		snprintf(b1, sizeof(b1), "%u ", port - 1);
+		val = b1;
+	}
+
+	snprintf(buf, sizeof(buf), "%sencap-%s %s", ne[!!enabled], name, val);
+	return buf;
+}
+
+void tnl_print_encap(struct rtattr *tb[],
+		     int encap_type, int encap_flags,
+		     int encap_sport, int encap_dport)
+{
+	__u16 type, flags, sport, dport;
+
+	if (!tb[encap_type])
+		return;
+
+	type = rta_getattr_u16(tb[encap_type]);
+	if (type == TUNNEL_ENCAP_NONE)
+		return;
+
+	flags = rta_getattr_u16(tb[encap_flags]);
+	sport = rta_getattr_u16(tb[encap_sport]);
+	dport = rta_getattr_u16(tb[encap_dport]);
+
+	open_json_object("encap");
+	print_string(PRINT_FP, NULL, "encap ", NULL);
+
+	switch (type) {
+	case TUNNEL_ENCAP_FOU:
+		print_string(PRINT_ANY, "type", "%s ", "fou");
+		break;
+	case TUNNEL_ENCAP_GUE:
+		print_string(PRINT_ANY, "type", "%s ", "gue");
+		break;
+	default:
+		print_null(PRINT_ANY, "type", "%s ", "unknown");
+		break;
+	}
+
+	if (is_json_context()) {
+		print_uint(PRINT_JSON, "sport", NULL, ntohs(sport));
+		print_uint(PRINT_JSON, "dport", NULL, ntohs(dport));
+		print_bool(PRINT_JSON, "csum", NULL,
+			   flags & TUNNEL_ENCAP_FLAG_CSUM);
+		print_bool(PRINT_JSON, "csum6", NULL,
+			   flags & TUNNEL_ENCAP_FLAG_CSUM6);
+		print_bool(PRINT_JSON, "remcsum", NULL,
+			   flags & TUNNEL_ENCAP_FLAG_REMCSUM);
+		close_json_object();
+	} else {
+		int t;
+
+		t = sport ? ntohs(sport) + 1 : 0;
+		print_string(PRINT_FP, NULL, "%s",
+			     tnl_encap_str("sport", 1, t));
+
+		t = ntohs(dport) + 1;
+		print_string(PRINT_FP, NULL, "%s",
+			     tnl_encap_str("dport", 1, t));
+
+		t = flags & TUNNEL_ENCAP_FLAG_CSUM;
+		print_string(PRINT_FP, NULL, "%s",
+			     tnl_encap_str("csum", t, -1));
+
+		t = flags & TUNNEL_ENCAP_FLAG_CSUM6;
+		print_string(PRINT_FP, NULL, "%s",
+			     tnl_encap_str("csum6", t, -1));
+
+		t = flags & TUNNEL_ENCAP_FLAG_REMCSUM;
+		print_string(PRINT_FP, NULL, "%s",
+			     tnl_encap_str("remcsum", t, -1));
+	}
+}
+
+void tnl_print_endpoint(const char *name, const struct rtattr *rta, int family)
+{
+	const char *value;
+	inet_prefix dst;
+
+	if (!rta) {
+		value = "any";
+	} else if (get_addr_rta(&dst, rta, family)) {
+		value = "unknown";
+	} else if (dst.flags & ADDRTYPE_UNSPEC) {
+		value = "any";
+	} else {
+		value = format_host(family, dst.bytelen, dst.data);
+		if (!value)
+			value = "unknown";
+	}
+
+	if (is_json_context()) {
+		print_string(PRINT_JSON, name, NULL, value);
+	} else {
+		SPRINT_BUF(b1);
+
+		snprintf(b1, sizeof(b1), "%s %%s ", name);
+		print_string(PRINT_FP, NULL, b1, value);
+	}
+}
+
+void tnl_print_gre_flags(__u8 proto,
+			 __be16 i_flags, __be16 o_flags,
+			 __be32 i_key, __be32 o_key)
+{
+	if ((i_flags & GRE_KEY) && (o_flags & GRE_KEY) &&
+	    o_key == i_key) {
+		print_uint(PRINT_ANY, "key", " key %u", ntohl(i_key));
+	} else {
+		if (i_flags & GRE_KEY)
+			print_uint(PRINT_ANY, "ikey", " ikey %u", ntohl(i_key));
+		if (o_flags & GRE_KEY)
+			print_uint(PRINT_ANY, "okey", " okey %u", ntohl(o_key));
+	}
+
+	if (proto != IPPROTO_GRE)
+		return;
+
+	open_json_array(PRINT_JSON, "flags");
+	if (i_flags & GRE_SEQ) {
+		if (is_json_context())
+			print_string(PRINT_JSON, NULL, "%s", "rx_drop_ooseq");
+		else
+			printf("%s  Drop packets out of sequence.", _SL_);
+	}
+	if (i_flags & GRE_CSUM) {
+		if (is_json_context())
+			print_string(PRINT_JSON, NULL, "%s", "rx_csum");
+		else
+			printf("%s  Checksum in received packet is required.", _SL_);
+	}
+	if (o_flags & GRE_SEQ) {
+		if (is_json_context())
+			print_string(PRINT_JSON, NULL, "%s", "tx_seq");
+		else
+			printf("%s  Sequence packets on output.", _SL_);
+	}
+	if (o_flags & GRE_CSUM) {
+		if (is_json_context())
+			print_string(PRINT_JSON, NULL, "%s", "tx_csum");
+		else
+			printf("%s  Checksum output packets.", _SL_);
+	}
+	close_json_array(PRINT_JSON, NULL);
+}
+
+static void tnl_print_stats(const struct rtnl_link_stats64 *s)
+{
+	printf("%s", _SL_);
+	printf("RX: Packets    Bytes        Errors CsumErrs OutOfSeq Mcasts%s", _SL_);
+	printf("    %-10lld %-12lld %-6lld %-8lld %-8lld %-8lld%s",
+	       s->rx_packets, s->rx_bytes, s->rx_errors, s->rx_frame_errors,
+	       s->rx_fifo_errors, s->multicast, _SL_);
+	printf("TX: Packets    Bytes        Errors DeadLoop NoRoute  NoBufs%s", _SL_);
+	printf("    %-10lld %-12lld %-6lld %-8lld %-8lld %-6lld",
+	       s->tx_packets, s->tx_bytes, s->tx_errors, s->collisions,
+	       s->tx_carrier_errors, s->tx_dropped);
+}
+
+static int print_nlmsg_tunnel(struct nlmsghdr *n, void *arg)
+{
+	struct tnl_print_nlmsg_info *info = arg;
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_MAX+1];
+	const char *name, *n1;
+
+	if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+		return 0;
+
+	if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifi)))
+		return -1;
+
+	if (preferred_family == AF_INET) {
+		switch (ifi->ifi_type) {
+		case ARPHRD_TUNNEL:
+		case ARPHRD_IPGRE:
+		case ARPHRD_SIT:
+			break;
+		default:
+			return 0;
+		}
+	} else {
+		switch (ifi->ifi_type) {
+		case ARPHRD_TUNNEL6:
+		case ARPHRD_IP6GRE:
+			break;
+		default:
+			return 0;
+		}
+	}
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n));
+
+	if (!tb[IFLA_IFNAME])
+		return 0;
+
+	name = rta_getattr_str(tb[IFLA_IFNAME]);
+
+	/* Assume p1->name[IFNAMSIZ] is first field of structure */
+	n1 = info->p1;
+	if (n1[0] && strcmp(n1, name))
+		return 0;
+
+	info->ifi = ifi;
+	info->init(info);
+
+	/* TODO: parse netlink attributes */
+	if (tnl_get_ioctl(name, info->p2))
+		return 0;
+
+	if (!info->match(info))
+		return 0;
+
+	info->print(info->p2);
+	if (show_stats) {
+		struct rtnl_link_stats64 s;
+
+		if (get_rtnl_link_stats_rta(&s, tb) <= 0)
+			return -1;
+
+		tnl_print_stats(&s);
+	}
+	fputc('\n', stdout);
+
+	return 0;
+}
+
+int do_tunnels_list(struct tnl_print_nlmsg_info *info)
+{
+	new_json_obj(json);
+	if (rtnl_linkdump_req(&rth, preferred_family) < 0) {
+		perror("Cannot send dump request\n");
+		return -1;
+	}
+
+	if (rtnl_dump_filter(&rth, print_nlmsg_tunnel, info) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return -1;
+	}
+	delete_json_obj();
+
+	return 0;
+}
diff --git a/ip/tunnel.h b/ip/tunnel.h
new file mode 100644
index 0000000..604f8cb
--- /dev/null
+++ b/ip/tunnel.h
@@ -0,0 +1,62 @@
+/*
+ * 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 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, see <http://www.gnu.org/licenses>.
+ */
+/*
+ * Author:
+ *	Masahide NAKAMURA @USAGI
+ */
+#ifndef __TUNNEL_H__
+#define __TUNNEL_H__ 1
+
+#include <stdbool.h>
+#include <linux/types.h>
+
+struct rtattr;
+struct ifinfomsg;
+
+extern struct rtnl_handle rth;
+
+struct tnl_print_nlmsg_info {
+	const struct ifinfomsg *ifi;
+	const void *p1;
+	void *p2;
+
+	void (*init)(const struct tnl_print_nlmsg_info *info);
+	bool (*match)(const struct tnl_print_nlmsg_info *info);
+	void (*print)(const void *t);
+};
+
+int do_tunnels_list(struct tnl_print_nlmsg_info *info);
+
+const char *tnl_strproto(__u8 proto);
+
+int tnl_get_ioctl(const char *basedev, void *p);
+int tnl_add_ioctl(int cmd, const char *basedev, const char *name, void *p);
+int tnl_del_ioctl(const char *basedev, const char *name, void *p);
+int tnl_prl_ioctl(int cmd, const char *name, void *p);
+int tnl_6rd_ioctl(int cmd, const char *name, void *p);
+int tnl_ioctl_get_6rd(const char *name, void *p);
+__be32 tnl_parse_key(const char *name, const char *key);
+void tnl_print_encap(struct rtattr *tb[],
+		     int encap_type, int encap_flags,
+		     int encap_sport, int encap_dport);
+void tnl_print_endpoint(const char *name,
+			const struct rtattr *rta, int family);
+void tnl_print_gre_flags(__u8 proto,
+			 __be16 i_flags, __be16 o_flags,
+			 __be32 i_key, __be32 o_key);
+
+#endif
diff --git a/ip/xfrm.h b/ip/xfrm.h
new file mode 100644
index 0000000..9ba5ca6
--- /dev/null
+++ b/ip/xfrm.h
@@ -0,0 +1,145 @@
+/* $USAGI: $ */
+
+/*
+ * Copyright (C)2004 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 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, see <http://www.gnu.org/licenses>.
+ */
+/*
+ * Authors:
+ *	Masahide NAKAMURA @USAGI
+ */
+
+#ifndef __XFRM_H__
+#define __XFRM_H__ 1
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <linux/in.h>
+#include <linux/xfrm.h>
+#include <linux/ipsec.h>
+
+#ifndef IPPROTO_MH
+#define IPPROTO_MH              135
+#endif
+
+#define XFRMS_RTA(x)  ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_usersa_info))))
+#define XFRMS_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct xfrm_usersa_info))
+
+#define XFRMP_RTA(x)  ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_userpolicy_info))))
+#define XFRMP_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct xfrm_userpoilcy_info))
+
+#define XFRMSID_RTA(x)  ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_usersa_id))))
+#define XFRMSID_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct xfrm_usersa_id))
+
+#define XFRMPID_RTA(x)  ((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_userpolicy_id))))
+#define XFRMPID_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct xfrm_userpoilcy_id))
+
+#define XFRMACQ_RTA(x)	((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_user_acquire))))
+#define XFRMEXP_RTA(x)	((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_user_expire))))
+#define XFRMPEXP_RTA(x)	((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_user_polexpire))))
+
+#define XFRMREP_RTA(x)	((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(struct xfrm_user_report))))
+
+#define XFRMSAPD_RTA(x)	((struct rtattr*)(((char*)(x)) + NLMSG_ALIGN(sizeof(__u32))))
+#define XFRM_FLAG_PRINT(fp, flags, f, s) \
+	do { \
+		if (flags & f) { \
+			flags &= ~f; \
+			fprintf(fp, s "%s", (flags ? " " : "")); \
+		} \
+	} while(0)
+
+struct xfrm_buffer {
+	char *buf;
+	int size;
+	int offset;
+
+	int nlmsg_count;
+	struct rtnl_handle *rth;
+};
+
+struct xfrm_filter {
+	int use;
+
+	struct xfrm_usersa_info xsinfo;
+	__u8 id_src_mask;
+	__u8 id_dst_mask;
+	__u8 id_proto_mask;
+	__u32 id_spi_mask;
+	__u8 mode_mask;
+	__u32 reqid_mask;
+	__u8 state_flags_mask;
+
+	struct xfrm_userpolicy_info xpinfo;
+	__u8 dir_mask;
+	__u8 sel_src_mask;
+	__u8 sel_dst_mask;
+	__u32 sel_dev_mask;
+	__u8 upspec_proto_mask;
+	__u16 upspec_sport_mask;
+	__u16 upspec_dport_mask;
+	__u32 index_mask;
+	__u8 action_mask;
+	__u32 priority_mask;
+	__u8 policy_flags_mask;
+	__u8 filter_socket;
+
+	__u8 ptype;
+	__u8 ptype_mask;
+
+};
+#define XFRM_FILTER_MASK_FULL (~0)
+
+extern struct xfrm_filter filter;
+
+int xfrm_state_print(struct nlmsghdr *n, void *arg);
+int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg);
+int xfrm_policy_print(struct nlmsghdr *n, void *arg);
+int do_xfrm_state(int argc, char **argv);
+int do_xfrm_policy(int argc, char **argv);
+int do_xfrm_monitor(int argc, char **argv);
+
+int xfrm_addr_match(xfrm_address_t *x1, xfrm_address_t *x2, int bits);
+int xfrm_xfrmproto_is_ipsec(__u8 proto);
+int xfrm_xfrmproto_is_ro(__u8 proto);
+int xfrm_xfrmproto_getbyname(char *name);
+int xfrm_algotype_getbyname(char *name);
+int xfrm_parse_mark(struct xfrm_mark *mark, int *argcp, char ***argvp);
+const char *strxf_xfrmproto(__u8 proto);
+const char *strxf_algotype(int type);
+const char *strxf_mask32(__u32 mask);
+const char *strxf_proto(__u8 proto);
+const char *strxf_ptype(__u8 ptype);
+void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
+			 FILE *fp, const char *prefix);
+void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
+		      FILE *fp, const char *prefix, bool nokeys);
+void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
+			    struct rtattr *tb[], FILE *fp, const char *prefix,
+			   const char *title, bool nokeys);
+void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
+			    struct rtattr *tb[], FILE *fp, const char *prefix,
+			    const char *title);
+int xfrm_id_parse(xfrm_address_t *saddr, struct xfrm_id *id, __u16 *family,
+		  int loose, int *argcp, char ***argvp);
+int xfrm_mode_parse(__u8 *mode, int *argcp, char ***argvp);
+int xfrm_encap_type_parse(__u16 *type, int *argcp, char ***argvp);
+int xfrm_reqid_parse(__u32 *reqid, int *argcp, char ***argvp);
+int xfrm_selector_parse(struct xfrm_selector *sel, int *argcp, char ***argvp);
+int xfrm_lifetime_cfg_parse(struct xfrm_lifetime_cfg *lft,
+			    int *argcp, char ***argvp);
+int xfrm_sctx_parse(char *ctxstr, char *context,
+		    struct xfrm_user_sec_ctx *sctx);
+#endif
diff --git a/ip/xfrm_monitor.c b/ip/xfrm_monitor.c
new file mode 100644
index 0000000..e34b5fb
--- /dev/null
+++ b/ip/xfrm_monitor.c
@@ -0,0 +1,426 @@
+/* $USAGI: $ */
+
+/*
+ * Copyright (C)2005 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 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, see <http://www.gnu.org/licenses>.
+ */
+/*
+ * based on ipmonitor.c
+ */
+/*
+ * Authors:
+ *	Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/in.h>
+
+#include "utils.h"
+#include "xfrm.h"
+#include "ip_common.h"
+
+static void usage(void) __attribute__((noreturn));
+static int listen_all_nsid;
+static bool nokeys;
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip xfrm monitor [ nokeys ] [ all-nsid ] [ all | OBJECTS | help ]\n"
+		"OBJECTS := { acquire | expire | SA | aevent | policy | report }\n");
+	exit(-1);
+}
+
+static int xfrm_acquire_print(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct xfrm_user_acquire *xacq = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[XFRMA_MAX+1];
+	__u16 family;
+
+	len -= NLMSG_LENGTH(sizeof(*xacq));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	parse_rtattr(tb, XFRMA_MAX, XFRMACQ_RTA(xacq), len);
+
+	family = xacq->sel.family;
+	if (family == AF_UNSPEC)
+		family = xacq->policy.sel.family;
+	if (family == AF_UNSPEC)
+		family = preferred_family;
+
+	fprintf(fp, "acquire ");
+
+	fprintf(fp, "proto %s ", strxf_xfrmproto(xacq->id.proto));
+	if (show_stats > 0 || xacq->id.spi) {
+		__u32 spi = ntohl(xacq->id.spi);
+
+		fprintf(fp, "spi 0x%08x", spi);
+		if (show_stats > 0)
+			fprintf(fp, "(%u)", spi);
+		fprintf(fp, " ");
+	}
+	fprintf(fp, "%s", _SL_);
+
+	xfrm_selector_print(&xacq->sel, family, fp, "  sel ");
+
+	xfrm_policy_info_print(&xacq->policy, tb, fp, "    ", "  policy ");
+
+	if (show_stats > 0)
+		fprintf(fp, "  seq 0x%08u ", xacq->seq);
+	if (show_stats > 0) {
+		fprintf(fp, "%s-mask %s ",
+			strxf_algotype(XFRMA_ALG_CRYPT),
+			strxf_mask32(xacq->ealgos));
+		fprintf(fp, "%s-mask %s ",
+			strxf_algotype(XFRMA_ALG_AUTH),
+			strxf_mask32(xacq->aalgos));
+		fprintf(fp, "%s-mask %s",
+			strxf_algotype(XFRMA_ALG_COMP),
+			strxf_mask32(xacq->calgos));
+	}
+	fprintf(fp, "%s", _SL_);
+
+	if (oneline)
+		fprintf(fp, "\n");
+	fflush(fp);
+
+	return 0;
+}
+
+static int xfrm_state_flush_print(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct xfrm_usersa_flush *xsf = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	const char *str;
+
+	len -= NLMSG_SPACE(sizeof(*xsf));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	fprintf(fp, "Flushed state ");
+
+	str = strxf_xfrmproto(xsf->proto);
+	if (str)
+		fprintf(fp, "proto %s", str);
+	else
+		fprintf(fp, "proto %u", xsf->proto);
+	fprintf(fp, "%s", _SL_);
+
+	if (oneline)
+		fprintf(fp, "\n");
+	fflush(fp);
+
+	return 0;
+}
+
+static int xfrm_policy_flush_print(struct nlmsghdr *n, void *arg)
+{
+	struct rtattr *tb[XFRMA_MAX+1];
+	FILE *fp = (FILE *)arg;
+	int len = n->nlmsg_len;
+
+	len -= NLMSG_SPACE(0);
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	fprintf(fp, "Flushed policy ");
+
+	parse_rtattr(tb, XFRMA_MAX, NLMSG_DATA(n), len);
+
+	if (tb[XFRMA_POLICY_TYPE]) {
+		struct xfrm_userpolicy_type *upt;
+
+		fprintf(fp, "ptype ");
+
+		if (RTA_PAYLOAD(tb[XFRMA_POLICY_TYPE]) < sizeof(*upt))
+			fprintf(fp, "(ERROR truncated)");
+
+		upt = RTA_DATA(tb[XFRMA_POLICY_TYPE]);
+		fprintf(fp, "%s ", strxf_ptype(upt->type));
+	}
+
+	fprintf(fp, "%s", _SL_);
+
+	if (oneline)
+		fprintf(fp, "\n");
+	fflush(fp);
+
+	return 0;
+}
+
+static int xfrm_report_print(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct xfrm_user_report *xrep = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[XFRMA_MAX+1];
+	__u16 family;
+
+	len -= NLMSG_LENGTH(sizeof(*xrep));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	family = xrep->sel.family;
+	if (family == AF_UNSPEC)
+		family = preferred_family;
+
+	fprintf(fp, "report ");
+
+	fprintf(fp, "proto %s ", strxf_xfrmproto(xrep->proto));
+	fprintf(fp, "%s", _SL_);
+
+	xfrm_selector_print(&xrep->sel, family, fp, "  sel ");
+
+	parse_rtattr(tb, XFRMA_MAX, XFRMREP_RTA(xrep), len);
+
+	xfrm_xfrma_print(tb, family, fp, "  ", nokeys);
+
+	if (oneline)
+		fprintf(fp, "\n");
+
+	return 0;
+}
+
+static void xfrm_ae_flags_print(__u32 flags, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+
+	fprintf(fp, " (0x%x) ", flags);
+	if (!flags)
+		return;
+	if (flags & XFRM_AE_CR)
+		fprintf(fp, " replay update ");
+	if (flags & XFRM_AE_CE)
+		fprintf(fp, " timer expired ");
+	if (flags & XFRM_AE_CU)
+		fprintf(fp, " policy updated ");
+
+}
+
+static void xfrm_usersa_print(const struct xfrm_usersa_id *sa_id, __u32 reqid, FILE *fp)
+{
+	fprintf(fp, "dst %s ",
+		rt_addr_n2a(sa_id->family, sizeof(sa_id->daddr), &sa_id->daddr));
+
+	fprintf(fp, " reqid 0x%x", reqid);
+
+	fprintf(fp, " protocol %s ", strxf_proto(sa_id->proto));
+	fprintf(fp, " SPI 0x%x", ntohl(sa_id->spi));
+}
+
+static int xfrm_ae_print(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct xfrm_aevent_id *id = NLMSG_DATA(n);
+
+	fprintf(fp, "Async event ");
+	xfrm_ae_flags_print(id->flags, arg);
+	fprintf(fp, "\n\t");
+	fprintf(fp, "src %s ", rt_addr_n2a(id->sa_id.family,
+					   sizeof(id->saddr), &id->saddr));
+
+	xfrm_usersa_print(&id->sa_id, id->reqid, fp);
+
+	fprintf(fp, "\n");
+	fflush(fp);
+
+	return 0;
+}
+
+static void xfrm_print_addr(FILE *fp, int family, xfrm_address_t *a)
+{
+	fprintf(fp, "%s", rt_addr_n2a(family, sizeof(*a), a));
+}
+
+static int xfrm_mapping_print(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	struct xfrm_user_mapping *map = NLMSG_DATA(n);
+
+	fprintf(fp, "Mapping change ");
+	xfrm_print_addr(fp, map->id.family, &map->old_saddr);
+
+	fprintf(fp, ":%d -> ", ntohs(map->old_sport));
+	xfrm_print_addr(fp, map->id.family, &map->new_saddr);
+	fprintf(fp, ":%d\n\t", ntohs(map->new_sport));
+
+	xfrm_usersa_print(&map->id, map->reqid, fp);
+
+	fprintf(fp, "\n");
+	fflush(fp);
+	return 0;
+}
+
+static int xfrm_accept_msg(struct rtnl_ctrl_data *ctrl,
+			   struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+
+	if (timestamp)
+		print_timestamp(fp);
+
+	if (listen_all_nsid) {
+		if (ctrl == NULL || ctrl->nsid < 0)
+			fprintf(fp, "[nsid current]");
+		else
+			fprintf(fp, "[nsid %d]", ctrl->nsid);
+	}
+
+	switch (n->nlmsg_type) {
+	case XFRM_MSG_NEWSA:
+	case XFRM_MSG_DELSA:
+	case XFRM_MSG_UPDSA:
+	case XFRM_MSG_EXPIRE:
+		xfrm_state_print(n, arg);
+		return 0;
+	case XFRM_MSG_NEWPOLICY:
+	case XFRM_MSG_DELPOLICY:
+	case XFRM_MSG_UPDPOLICY:
+	case XFRM_MSG_POLEXPIRE:
+		xfrm_policy_print(n, arg);
+		return 0;
+	case XFRM_MSG_ACQUIRE:
+		xfrm_acquire_print(n, arg);
+		return 0;
+	case XFRM_MSG_FLUSHSA:
+		xfrm_state_flush_print(n, arg);
+		return 0;
+	case XFRM_MSG_FLUSHPOLICY:
+		xfrm_policy_flush_print(n, arg);
+		return 0;
+	case XFRM_MSG_REPORT:
+		xfrm_report_print(n, arg);
+		return 0;
+	case XFRM_MSG_NEWAE:
+		xfrm_ae_print(n, arg);
+		return 0;
+	case XFRM_MSG_MAPPING:
+		xfrm_mapping_print(n, arg);
+		return 0;
+	default:
+		break;
+	}
+
+	if (n->nlmsg_type != NLMSG_ERROR && n->nlmsg_type != NLMSG_NOOP &&
+	    n->nlmsg_type != NLMSG_DONE) {
+		fprintf(fp, "Unknown message: %08d 0x%08x 0x%08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+	}
+	return 0;
+}
+
+extern struct rtnl_handle rth;
+
+int do_xfrm_monitor(int argc, char **argv)
+{
+	char *file = NULL;
+	unsigned int groups = ~((unsigned)0); /* XXX */
+	int lacquire = 0;
+	int lexpire = 0;
+	int laevent = 0;
+	int lpolicy = 0;
+	int lsa = 0;
+	int lreport = 0;
+
+	rtnl_close(&rth);
+
+	while (argc > 0) {
+		if (matches(*argv, "file") == 0) {
+			NEXT_ARG();
+			file = *argv;
+		} else if (strcmp(*argv, "nokeys") == 0) {
+			nokeys = true;
+		} else if (strcmp(*argv, "all") == 0) {
+			/* fall out */
+		} else if (matches(*argv, "all-nsid") == 0) {
+			listen_all_nsid = 1;
+		} else if (matches(*argv, "acquire") == 0) {
+			lacquire = 1;
+			groups = 0;
+		} else if (matches(*argv, "expire") == 0) {
+			lexpire = 1;
+			groups = 0;
+		} else if (matches(*argv, "SA") == 0) {
+			lsa = 1;
+			groups = 0;
+		} else if (matches(*argv, "aevent") == 0) {
+			laevent = 1;
+			groups = 0;
+		} else if (matches(*argv, "policy") == 0) {
+			lpolicy = 1;
+			groups = 0;
+		} else if (matches(*argv, "report") == 0) {
+			lreport = 1;
+			groups = 0;
+		} else if (matches(*argv, "help") == 0) {
+			usage();
+		} else {
+			fprintf(stderr, "Argument \"%s\" is unknown, try \"ip xfrm monitor help\".\n", *argv);
+			exit(-1);
+		}
+		argc--;	argv++;
+	}
+
+	if (lacquire)
+		groups |= nl_mgrp(XFRMNLGRP_ACQUIRE);
+	if (lexpire)
+		groups |= nl_mgrp(XFRMNLGRP_EXPIRE);
+	if (lsa)
+		groups |= nl_mgrp(XFRMNLGRP_SA);
+	if (lpolicy)
+		groups |= nl_mgrp(XFRMNLGRP_POLICY);
+	if (laevent)
+		groups |= nl_mgrp(XFRMNLGRP_AEVENTS);
+	if (lreport)
+		groups |= nl_mgrp(XFRMNLGRP_REPORT);
+
+	if (file) {
+		FILE *fp;
+		int err;
+
+		fp = fopen(file, "r");
+		if (fp == NULL) {
+			perror("Cannot fopen");
+			exit(-1);
+		}
+		err = rtnl_from_file(fp, xfrm_accept_msg, stdout);
+		fclose(fp);
+		return err;
+	}
+
+	if (rtnl_open_byproto(&rth, groups, NETLINK_XFRM) < 0)
+		exit(1);
+	if (listen_all_nsid && rtnl_listen_all_nsid(&rth) < 0)
+		exit(1);
+
+	if (rtnl_listen(&rth, xfrm_accept_msg, (void *)stdout) < 0)
+		exit(2);
+
+	return 0;
+}
diff --git a/ip/xfrm_policy.c b/ip/xfrm_policy.c
new file mode 100644
index 0000000..7c0233c
--- /dev/null
+++ b/ip/xfrm_policy.c
@@ -0,0 +1,1204 @@
+/* $USAGI: $ */
+
+/*
+ * Copyright (C)2004 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 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, see <http://www.gnu.org/licenses>.
+ */
+/*
+ * based on iproute.c
+ */
+/*
+ * Authors:
+ *	Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include <linux/netlink.h>
+#include "utils.h"
+#include "xfrm.h"
+#include "ip_common.h"
+
+/* #define NLMSG_DELETEALL_BUF_SIZE (4096-512) */
+#define NLMSG_DELETEALL_BUF_SIZE 8192
+
+/*
+ * Receiving buffer defines:
+ * nlmsg
+ *   data = struct xfrm_userpolicy_info
+ *   rtattr
+ *     data = struct xfrm_user_tmpl[]
+ */
+#define NLMSG_BUF_SIZE 4096
+#define RTA_BUF_SIZE 2048
+#define XFRM_TMPLS_BUF_SIZE 1024
+#define CTX_BUF_SIZE 256
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip xfrm policy { add | update } SELECTOR dir DIR [ ctx CTX ]\n"
+		"	[ mark MARK [ mask MASK ] ] [ index INDEX ] [ ptype PTYPE ]\n"
+		"	[ action ACTION ] [ priority PRIORITY ] [ flag FLAG-LIST ]\n"
+		"	[ if_id IF_ID ] [ LIMIT-LIST ] [ TMPL-LIST ]\n"
+		"Usage: ip xfrm policy { delete | get } { SELECTOR | index INDEX } dir DIR\n"
+		"	[ ctx CTX ] [ mark MARK [ mask MASK ] ] [ ptype PTYPE ]\n"
+		"Usage: ip xfrm policy { deleteall | list } [ nosock ] [ SELECTOR ] [ dir DIR ]\n"
+		"	[ index INDEX ] [ ptype PTYPE ] [ action ACTION ] [ priority PRIORITY ]\n"
+		"	[ flag FLAG-LIST ]\n"
+		"Usage: ip xfrm policy flush [ ptype PTYPE ]\n"
+		"Usage: ip xfrm policy count\n"
+		"Usage: ip xfrm policy set [ hthresh4 LBITS RBITS ] [ hthresh6 LBITS RBITS ]\n"
+		"SELECTOR := [ src ADDR[/PLEN] ] [ dst ADDR[/PLEN] ] [ dev DEV ] [ UPSPEC ]\n"
+		"UPSPEC := proto { { ");
+	fprintf(stderr, "%s | %s | %s | %s } ",
+		strxf_proto(IPPROTO_TCP),
+		strxf_proto(IPPROTO_UDP),
+		strxf_proto(IPPROTO_SCTP),
+		strxf_proto(IPPROTO_DCCP));
+	fprintf(stderr,
+		"[ sport PORT ] [ dport PORT ] |\n"
+		"                  { %s | %s | %s } ",
+		strxf_proto(IPPROTO_ICMP),
+		strxf_proto(IPPROTO_ICMPV6),
+		strxf_proto(IPPROTO_MH));
+	fprintf(stderr,
+		"[ type NUMBER ] [ code NUMBER ] |\n"
+		"                  %s",
+		strxf_proto(IPPROTO_GRE));
+	fprintf(stderr,
+		" [ key { DOTTED-QUAD | NUMBER } ] | PROTO }\n"
+		"DIR := in | out | fwd\n"
+		"PTYPE := main | sub\n"
+		"ACTION := allow | block\n"
+		"FLAG-LIST := [ FLAG-LIST ] FLAG\n"
+		"FLAG := localok | icmp\n"
+		"LIMIT-LIST := [ LIMIT-LIST ] limit LIMIT\n"
+		"LIMIT := { time-soft | time-hard | time-use-soft | time-use-hard } SECONDS |\n"
+		"         { byte-soft | byte-hard } SIZE | { packet-soft | packet-hard } COUNT\n"
+		"TMPL-LIST := [ TMPL-LIST ] tmpl TMPL\n"
+		"TMPL := ID [ mode MODE ] [ reqid REQID ] [ level LEVEL ]\n"
+		"ID := [ src ADDR ] [ dst ADDR ] [ proto XFRM-PROTO ] [ spi SPI ]\n"
+		"XFRM-PROTO := ");
+	fprintf(stderr,
+		"%s | %s | %s | %s | %s\n",
+		strxf_xfrmproto(IPPROTO_ESP),
+		strxf_xfrmproto(IPPROTO_AH),
+		strxf_xfrmproto(IPPROTO_COMP),
+		strxf_xfrmproto(IPPROTO_ROUTING),
+		strxf_xfrmproto(IPPROTO_DSTOPTS));
+	fprintf(stderr,
+		"MODE := transport | tunnel | beet | ro | in_trigger\n"
+		"LEVEL := required | use\n");
+
+	exit(-1);
+}
+
+static int xfrm_policy_dir_parse(__u8 *dir, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+
+	if (strcmp(*argv, "in") == 0)
+		*dir = XFRM_POLICY_IN;
+	else if (strcmp(*argv, "out") == 0)
+		*dir = XFRM_POLICY_OUT;
+	else if (strcmp(*argv, "fwd") == 0)
+		*dir = XFRM_POLICY_FWD;
+	else
+		invarg("DIR value is invalid", *argv);
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static int xfrm_policy_ptype_parse(__u8 *ptype, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+
+	if (strcmp(*argv, "main") == 0)
+		*ptype = XFRM_POLICY_TYPE_MAIN;
+	else if (strcmp(*argv, "sub") == 0)
+		*ptype = XFRM_POLICY_TYPE_SUB;
+	else
+		invarg("PTYPE value is invalid", *argv);
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static int xfrm_policy_flag_parse(__u8 *flags, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int len = strlen(*argv);
+
+	if (len > 2 && strncmp(*argv, "0x", 2) == 0) {
+		__u8 val = 0;
+
+		if (get_u8(&val, *argv, 16))
+			invarg("FLAG value is invalid", *argv);
+		*flags = val;
+	} else {
+		while (1) {
+			if (strcmp(*argv, "localok") == 0)
+				*flags |= XFRM_POLICY_LOCALOK;
+			else if (strcmp(*argv, "icmp") == 0)
+				*flags |= XFRM_POLICY_ICMP;
+			else {
+				PREV_ARG(); /* back track */
+				break;
+			}
+
+			if (!NEXT_ARG_OK())
+				break;
+			NEXT_ARG();
+		}
+	}
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static int xfrm_tmpl_parse(struct xfrm_user_tmpl *tmpl,
+			   int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	char *idp = NULL;
+
+	while (1) {
+		if (strcmp(*argv, "mode") == 0) {
+			NEXT_ARG();
+			xfrm_mode_parse(&tmpl->mode,  &argc, &argv);
+		} else if (strcmp(*argv, "reqid") == 0) {
+			NEXT_ARG();
+			xfrm_reqid_parse(&tmpl->reqid, &argc, &argv);
+		} else if (strcmp(*argv, "level") == 0) {
+			NEXT_ARG();
+
+			if (strcmp(*argv, "required") == 0)
+				tmpl->optional = 0;
+			else if (strcmp(*argv, "use") == 0)
+				tmpl->optional = 1;
+			else
+				invarg("LEVEL value is invalid\n", *argv);
+
+		} else {
+			if (idp) {
+				PREV_ARG(); /* back track */
+				break;
+			}
+			idp = *argv;
+			preferred_family = AF_UNSPEC;
+			xfrm_id_parse(&tmpl->saddr, &tmpl->id, &tmpl->family,
+				      0, &argc, &argv);
+			preferred_family = tmpl->family;
+		}
+
+		if (!NEXT_ARG_OK())
+			break;
+
+		NEXT_ARG();
+	}
+	if (argc == *argcp)
+		missarg("TMPL");
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+int xfrm_sctx_parse(char *ctxstr, char *s,
+			   struct xfrm_user_sec_ctx *sctx)
+{
+	int slen;
+
+	slen = strlen(s) + 1;
+
+	sctx->exttype = XFRMA_SEC_CTX;
+	sctx->ctx_doi = 1;
+	sctx->ctx_alg = 1;
+	sctx->ctx_len = slen;
+	sctx->len = sizeof(struct xfrm_user_sec_ctx) + slen;
+	memcpy(ctxstr, s, slen);
+
+	return 0;
+}
+
+static int xfrm_policy_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	struct {
+		struct nlmsghdr			n;
+		struct xfrm_userpolicy_info	xpinfo;
+		char				buf[RTA_BUF_SIZE];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xpinfo)),
+		.n.nlmsg_flags = NLM_F_REQUEST | flags,
+		.n.nlmsg_type = cmd,
+		.xpinfo.sel.family = preferred_family,
+		.xpinfo.lft.soft_byte_limit = XFRM_INF,
+		.xpinfo.lft.hard_byte_limit = XFRM_INF,
+		.xpinfo.lft.soft_packet_limit = XFRM_INF,
+		.xpinfo.lft.hard_packet_limit = XFRM_INF,
+	};
+	char *dirp = NULL;
+	char *selp = NULL;
+	char *ptypep = NULL;
+	char *sctxp = NULL;
+	struct xfrm_userpolicy_type upt = {};
+	char tmpls_buf[XFRM_TMPLS_BUF_SIZE] = {};
+	int tmpls_len = 0;
+	struct xfrm_mark mark = {0, 0};
+	struct {
+		struct xfrm_user_sec_ctx sctx;
+		char	str[CTX_BUF_SIZE];
+	} ctx = {};
+	bool is_if_id_set = false;
+	__u32 if_id = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dir") == 0) {
+			if (dirp)
+				duparg("dir", *argv);
+			dirp = *argv;
+
+			NEXT_ARG();
+			xfrm_policy_dir_parse(&req.xpinfo.dir, &argc, &argv);
+		} else if (strcmp(*argv, "ctx") == 0) {
+			char *context;
+
+			if (sctxp)
+				duparg("ctx", *argv);
+			sctxp = *argv;
+			NEXT_ARG();
+			context = *argv;
+			xfrm_sctx_parse((char *)&ctx.str, context, &ctx.sctx);
+		} else if (strcmp(*argv, "mark") == 0) {
+			xfrm_parse_mark(&mark, &argc, &argv);
+		} else if (strcmp(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&req.xpinfo.index, *argv, 0))
+				invarg("INDEX value is invalid", *argv);
+		} else if (strcmp(*argv, "ptype") == 0) {
+			if (ptypep)
+				duparg("ptype", *argv);
+			ptypep = *argv;
+
+			NEXT_ARG();
+			xfrm_policy_ptype_parse(&upt.type, &argc, &argv);
+		} else if (strcmp(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "allow") == 0)
+				req.xpinfo.action = XFRM_POLICY_ALLOW;
+			else if (strcmp(*argv, "block") == 0)
+				req.xpinfo.action = XFRM_POLICY_BLOCK;
+			else
+				invarg("ACTION value is invalid\n", *argv);
+		} else if (strcmp(*argv, "priority") == 0) {
+			NEXT_ARG();
+			if (get_u32(&req.xpinfo.priority, *argv, 0))
+				invarg("PRIORITY value is invalid", *argv);
+		} else if (strcmp(*argv, "flag") == 0) {
+			NEXT_ARG();
+			xfrm_policy_flag_parse(&req.xpinfo.flags, &argc,
+					       &argv);
+		} else if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			xfrm_lifetime_cfg_parse(&req.xpinfo.lft, &argc, &argv);
+		} else if (strcmp(*argv, "tmpl") == 0) {
+			struct xfrm_user_tmpl *tmpl;
+
+			if (tmpls_len + sizeof(*tmpl) > sizeof(tmpls_buf)) {
+				fprintf(stderr, "Too many tmpls: buffer overflow\n");
+				exit(1);
+			}
+			tmpl = (struct xfrm_user_tmpl *)((char *)tmpls_buf + tmpls_len);
+
+			tmpl->family = preferred_family;
+			tmpl->aalgos = (~(__u32)0);
+			tmpl->ealgos = (~(__u32)0);
+			tmpl->calgos = (~(__u32)0);
+
+			NEXT_ARG();
+			xfrm_tmpl_parse(tmpl, &argc, &argv);
+
+			tmpls_len += sizeof(*tmpl);
+		} else if (strcmp(*argv, "if_id") == 0) {
+			NEXT_ARG();
+			if (get_u32(&if_id, *argv, 0))
+				invarg("IF_ID value is invalid", *argv);
+			is_if_id_set = true;
+		} else {
+			if (selp)
+				duparg("unknown", *argv);
+			selp = *argv;
+
+			xfrm_selector_parse(&req.xpinfo.sel, &argc, &argv);
+			if (preferred_family == AF_UNSPEC)
+				preferred_family = req.xpinfo.sel.family;
+		}
+
+		argc--; argv++;
+	}
+
+	if (!dirp) {
+		fprintf(stderr, "Not enough information: DIR is required.\n");
+		exit(1);
+	}
+
+	if (ptypep) {
+		addattr_l(&req.n, sizeof(req), XFRMA_POLICY_TYPE,
+			  (void *)&upt, sizeof(upt));
+	}
+
+	if (tmpls_len > 0) {
+		addattr_l(&req.n, sizeof(req), XFRMA_TMPL,
+			  (void *)tmpls_buf, tmpls_len);
+	}
+
+	if (mark.m) {
+		int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
+				  (void *)&mark, sizeof(mark));
+		if (r < 0) {
+			fprintf(stderr, "%s: XFRMA_MARK failed\n", __func__);
+			exit(1);
+		}
+	}
+
+	if (sctxp) {
+		addattr_l(&req.n, sizeof(req), XFRMA_SEC_CTX,
+			  (void *)&ctx, ctx.sctx.len);
+	}
+
+	if (is_if_id_set)
+		addattr32(&req.n, sizeof(req.buf), XFRMA_IF_ID, if_id);
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (req.xpinfo.sel.family == AF_UNSPEC)
+		req.xpinfo.sel.family = AF_INET;
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		exit(2);
+
+	rtnl_close(&rth);
+
+	return 0;
+}
+
+static int xfrm_policy_filter_match(struct xfrm_userpolicy_info *xpinfo,
+				    __u8 ptype)
+{
+	if (!filter.use)
+		return 1;
+
+	if (filter.xpinfo.sel.family != AF_UNSPEC &&
+	    filter.xpinfo.sel.family != xpinfo->sel.family)
+		return 0;
+
+	if ((xpinfo->dir^filter.xpinfo.dir)&filter.dir_mask)
+		return 0;
+
+	if (filter.filter_socket && (xpinfo->dir >= XFRM_POLICY_MAX))
+		return 0;
+
+	if ((ptype^filter.ptype)&filter.ptype_mask)
+		return 0;
+
+	if (filter.sel_src_mask) {
+		if (xfrm_addr_match(&xpinfo->sel.saddr, &filter.xpinfo.sel.saddr,
+				    filter.sel_src_mask))
+			return 0;
+	}
+
+	if (filter.sel_dst_mask) {
+		if (xfrm_addr_match(&xpinfo->sel.daddr, &filter.xpinfo.sel.daddr,
+				    filter.sel_dst_mask))
+			return 0;
+	}
+
+	if ((xpinfo->sel.ifindex^filter.xpinfo.sel.ifindex)&filter.sel_dev_mask)
+		return 0;
+
+	if ((xpinfo->sel.proto^filter.xpinfo.sel.proto)&filter.upspec_proto_mask)
+		return 0;
+
+	if (filter.upspec_sport_mask) {
+		if ((xpinfo->sel.sport^filter.xpinfo.sel.sport)&filter.upspec_sport_mask)
+			return 0;
+	}
+
+	if (filter.upspec_dport_mask) {
+		if ((xpinfo->sel.dport^filter.xpinfo.sel.dport)&filter.upspec_dport_mask)
+			return 0;
+	}
+
+	if ((xpinfo->index^filter.xpinfo.index)&filter.index_mask)
+		return 0;
+
+	if ((xpinfo->action^filter.xpinfo.action)&filter.action_mask)
+		return 0;
+
+	if ((xpinfo->priority^filter.xpinfo.priority)&filter.priority_mask)
+		return 0;
+
+	if (filter.policy_flags_mask)
+		if ((xpinfo->flags & filter.xpinfo.flags) == 0)
+			return 0;
+
+	return 1;
+}
+
+int xfrm_policy_print(struct nlmsghdr *n, void *arg)
+{
+	struct rtattr *tb[XFRMA_MAX+1];
+	struct rtattr *rta;
+	struct xfrm_userpolicy_info *xpinfo = NULL;
+	struct xfrm_user_polexpire *xpexp = NULL;
+	struct xfrm_userpolicy_id *xpid = NULL;
+	__u8 ptype = XFRM_POLICY_TYPE_MAIN;
+	FILE *fp = (FILE *)arg;
+	int len = n->nlmsg_len;
+
+	if (n->nlmsg_type != XFRM_MSG_NEWPOLICY &&
+	    n->nlmsg_type != XFRM_MSG_DELPOLICY &&
+	    n->nlmsg_type != XFRM_MSG_UPDPOLICY &&
+	    n->nlmsg_type != XFRM_MSG_POLEXPIRE) {
+		fprintf(stderr, "Not a policy: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+
+	if (n->nlmsg_type == XFRM_MSG_DELPOLICY)  {
+		xpid = NLMSG_DATA(n);
+		len -= NLMSG_SPACE(sizeof(*xpid));
+	} else if (n->nlmsg_type == XFRM_MSG_POLEXPIRE) {
+		xpexp = NLMSG_DATA(n);
+		xpinfo = &xpexp->pol;
+		len -= NLMSG_SPACE(sizeof(*xpexp));
+	} else {
+		xpexp = NULL;
+		xpinfo = NLMSG_DATA(n);
+		len -= NLMSG_SPACE(sizeof(*xpinfo));
+	}
+
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (n->nlmsg_type == XFRM_MSG_DELPOLICY)
+		rta = XFRMPID_RTA(xpid);
+	else if (n->nlmsg_type == XFRM_MSG_POLEXPIRE)
+		rta = XFRMPEXP_RTA(xpexp);
+	else
+		rta = XFRMP_RTA(xpinfo);
+
+	parse_rtattr(tb, XFRMA_MAX, rta, len);
+
+	if (tb[XFRMA_POLICY_TYPE]) {
+		struct xfrm_userpolicy_type *upt;
+
+		if (RTA_PAYLOAD(tb[XFRMA_POLICY_TYPE]) < sizeof(*upt)) {
+			fprintf(stderr, "too short XFRMA_POLICY_TYPE len\n");
+			return -1;
+		}
+		upt = RTA_DATA(tb[XFRMA_POLICY_TYPE]);
+		ptype = upt->type;
+	}
+
+	if (xpinfo && !xfrm_policy_filter_match(xpinfo, ptype))
+		return 0;
+
+	if (n->nlmsg_type == XFRM_MSG_DELPOLICY)
+		fprintf(fp, "Deleted ");
+	else if (n->nlmsg_type == XFRM_MSG_UPDPOLICY)
+		fprintf(fp, "Updated ");
+	else if (n->nlmsg_type == XFRM_MSG_POLEXPIRE)
+		fprintf(fp, "Expired ");
+
+	if (n->nlmsg_type == XFRM_MSG_DELPOLICY) {
+		/* xfrm_policy_id_print(); */
+		if (!tb[XFRMA_POLICY]) {
+			fprintf(stderr, "Buggy XFRM_MSG_DELPOLICY: no XFRMA_POLICY\n");
+			return -1;
+		}
+		if (RTA_PAYLOAD(tb[XFRMA_POLICY]) < sizeof(*xpinfo)) {
+			fprintf(stderr, "Buggy XFRM_MSG_DELPOLICY: too short XFRMA_POLICY len\n");
+			return -1;
+		}
+		xpinfo = RTA_DATA(tb[XFRMA_POLICY]);
+	}
+
+	xfrm_policy_info_print(xpinfo, tb, fp, NULL, NULL);
+
+	if (n->nlmsg_type == XFRM_MSG_POLEXPIRE) {
+		fprintf(fp, "\t");
+		fprintf(fp, "hard %u", xpexp->hard);
+		fprintf(fp, "%s", _SL_);
+	}
+
+	if (oneline)
+		fprintf(fp, "\n");
+	fflush(fp);
+
+	return 0;
+}
+
+static int xfrm_policy_get_or_delete(int argc, char **argv, int delete,
+				     struct nlmsghdr **answer)
+{
+	struct rtnl_handle rth;
+	struct {
+		struct nlmsghdr			n;
+		struct xfrm_userpolicy_id	xpid;
+		char				buf[RTA_BUF_SIZE];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xpid)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = delete ? XFRM_MSG_DELPOLICY
+				       : XFRM_MSG_GETPOLICY,
+	};
+	char *dirp = NULL;
+	char *selp = NULL;
+	char *indexp = NULL;
+	char *ptypep = NULL;
+	char *sctxp = NULL;
+	struct xfrm_userpolicy_type upt = {};
+	struct xfrm_mark mark = {0, 0};
+	struct {
+		struct xfrm_user_sec_ctx sctx;
+		char    str[CTX_BUF_SIZE];
+	} ctx = {};
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dir") == 0) {
+			if (dirp)
+				duparg("dir", *argv);
+			dirp = *argv;
+
+			NEXT_ARG();
+			xfrm_policy_dir_parse(&req.xpid.dir, &argc, &argv);
+
+		} else if (strcmp(*argv, "ctx") == 0) {
+			char *context;
+
+			if (sctxp)
+				duparg("ctx", *argv);
+			sctxp = *argv;
+			NEXT_ARG();
+			context = *argv;
+			xfrm_sctx_parse((char *)&ctx.str, context, &ctx.sctx);
+		} else if (strcmp(*argv, "mark") == 0) {
+			xfrm_parse_mark(&mark, &argc, &argv);
+		} else if (strcmp(*argv, "index") == 0) {
+			if (indexp)
+				duparg("index", *argv);
+			indexp = *argv;
+
+			NEXT_ARG();
+			if (get_u32(&req.xpid.index, *argv, 0))
+				invarg("INDEX value is invalid", *argv);
+
+		} else if (strcmp(*argv, "ptype") == 0) {
+			if (ptypep)
+				duparg("ptype", *argv);
+			ptypep = *argv;
+
+			NEXT_ARG();
+			xfrm_policy_ptype_parse(&upt.type, &argc, &argv);
+
+		} else {
+			if (selp)
+				invarg("unknown", *argv);
+			selp = *argv;
+
+			xfrm_selector_parse(&req.xpid.sel, &argc, &argv);
+			if (preferred_family == AF_UNSPEC)
+				preferred_family = req.xpid.sel.family;
+
+		}
+
+		argc--; argv++;
+	}
+
+	if (!dirp) {
+		fprintf(stderr, "Not enough information: DIR is required.\n");
+		exit(1);
+	}
+	if (ptypep) {
+		addattr_l(&req.n, sizeof(req), XFRMA_POLICY_TYPE,
+			  (void *)&upt, sizeof(upt));
+	}
+	if (!selp && !indexp) {
+		fprintf(stderr, "Not enough information: either SELECTOR or INDEX is required.\n");
+		exit(1);
+	}
+	if (selp && indexp)
+		duparg2("SELECTOR", "INDEX");
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (req.xpid.sel.family == AF_UNSPEC)
+		req.xpid.sel.family = AF_INET;
+
+	if (mark.m & mark.v) {
+		int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
+				  (void *)&mark, sizeof(mark));
+		if (r < 0) {
+			fprintf(stderr, "%s: XFRMA_MARK failed\n", __func__);
+			exit(1);
+		}
+	}
+
+	if (sctxp) {
+		addattr_l(&req.n, sizeof(req), XFRMA_SEC_CTX,
+			  (void *)&ctx, ctx.sctx.len);
+	}
+
+	if (rtnl_talk(&rth, &req.n, answer) < 0)
+		exit(2);
+
+	rtnl_close(&rth);
+
+	return 0;
+}
+
+static int xfrm_policy_delete(int argc, char **argv)
+{
+	return xfrm_policy_get_or_delete(argc, argv, 1, NULL);
+}
+
+static int xfrm_policy_get(int argc, char **argv)
+{
+	struct nlmsghdr *n = NULL;
+
+	xfrm_policy_get_or_delete(argc, argv, 0, &n);
+
+	if (xfrm_policy_print(n, (void *)stdout) < 0) {
+		fprintf(stderr, "An error :-)\n");
+		exit(1);
+	}
+
+	free(n);
+	return 0;
+}
+
+/*
+ * With an existing policy of nlmsg, make new nlmsg for deleting the policy
+ * and store it to buffer.
+ */
+static int xfrm_policy_keep(struct nlmsghdr *n, void *arg)
+{
+	struct xfrm_buffer *xb = (struct xfrm_buffer *)arg;
+	struct rtnl_handle *rth = xb->rth;
+	struct xfrm_userpolicy_info *xpinfo = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct rtattr *tb[XFRMA_MAX+1];
+	__u8 ptype = XFRM_POLICY_TYPE_MAIN;
+	struct nlmsghdr *new_n;
+	struct xfrm_userpolicy_id *xpid;
+
+	if (n->nlmsg_type != XFRM_MSG_NEWPOLICY) {
+		fprintf(stderr, "Not a policy: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(sizeof(*xpinfo));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	parse_rtattr(tb, XFRMA_MAX, XFRMP_RTA(xpinfo), len);
+
+	if (tb[XFRMA_POLICY_TYPE]) {
+		struct xfrm_userpolicy_type *upt;
+
+		if (RTA_PAYLOAD(tb[XFRMA_POLICY_TYPE]) < sizeof(*upt)) {
+			fprintf(stderr, "too short XFRMA_POLICY_TYPE len\n");
+			return -1;
+		}
+		upt = RTA_DATA(tb[XFRMA_POLICY_TYPE]);
+		ptype = upt->type;
+	}
+
+	if (!xfrm_policy_filter_match(xpinfo, ptype))
+		return 0;
+
+	/* can't delete socket policies */
+	if (xpinfo->dir >= XFRM_POLICY_MAX)
+		return 0;
+
+	if (xb->offset + NLMSG_LENGTH(sizeof(*xpid)) > xb->size)
+		return 0;
+
+	new_n = (struct nlmsghdr *)(xb->buf + xb->offset);
+	new_n->nlmsg_len = NLMSG_LENGTH(sizeof(*xpid));
+	new_n->nlmsg_flags = NLM_F_REQUEST;
+	new_n->nlmsg_type = XFRM_MSG_DELPOLICY;
+	new_n->nlmsg_seq = ++rth->seq;
+
+	xpid = NLMSG_DATA(new_n);
+	memcpy(&xpid->sel, &xpinfo->sel, sizeof(xpid->sel));
+	xpid->dir = xpinfo->dir;
+	xpid->index = xpinfo->index;
+
+	if (tb[XFRMA_MARK]) {
+		int r = addattr_l(new_n, xb->size, XFRMA_MARK,
+				(void *)RTA_DATA(tb[XFRMA_MARK]), tb[XFRMA_MARK]->rta_len);
+		if (r < 0) {
+			fprintf(stderr, "%s: XFRMA_MARK failed\n", __func__);
+			exit(1);
+		}
+	}
+
+	xb->offset += new_n->nlmsg_len;
+	xb->nlmsg_count++;
+
+	return 0;
+}
+
+static int xfrm_policy_list_or_deleteall(int argc, char **argv, int deleteall)
+{
+	char *selp = NULL;
+	struct rtnl_handle rth;
+
+	if (argc > 0 || preferred_family != AF_UNSPEC)
+		filter.use = 1;
+	filter.xpinfo.sel.family = preferred_family;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "dir") == 0) {
+			NEXT_ARG();
+			xfrm_policy_dir_parse(&filter.xpinfo.dir, &argc, &argv);
+
+			filter.dir_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "index") == 0) {
+			NEXT_ARG();
+			if (get_u32(&filter.xpinfo.index, *argv, 0))
+				invarg("INDEX value is invalid", *argv);
+
+			filter.index_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "ptype") == 0) {
+			NEXT_ARG();
+			xfrm_policy_ptype_parse(&filter.ptype, &argc, &argv);
+
+			filter.ptype_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "action") == 0) {
+			NEXT_ARG();
+			if (strcmp(*argv, "allow") == 0)
+				filter.xpinfo.action = XFRM_POLICY_ALLOW;
+			else if (strcmp(*argv, "block") == 0)
+				filter.xpinfo.action = XFRM_POLICY_BLOCK;
+			else
+				invarg("ACTION value is invalid\n", *argv);
+
+			filter.action_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "priority") == 0) {
+			NEXT_ARG();
+			if (get_u32(&filter.xpinfo.priority, *argv, 0))
+				invarg("PRIORITY value is invalid", *argv);
+
+			filter.priority_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "flag") == 0) {
+			NEXT_ARG();
+			xfrm_policy_flag_parse(&filter.xpinfo.flags, &argc,
+					       &argv);
+
+			filter.policy_flags_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "nosock") == 0) {
+			/* filter all socket-based policies */
+			filter.filter_socket = 1;
+		} else {
+			if (selp)
+				invarg("unknown", *argv);
+			selp = *argv;
+
+			xfrm_selector_parse(&filter.xpinfo.sel, &argc, &argv);
+			if (preferred_family == AF_UNSPEC)
+				preferred_family = filter.xpinfo.sel.family;
+
+		}
+
+		argc--; argv++;
+	}
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (deleteall) {
+		struct xfrm_buffer xb;
+		char buf[NLMSG_DELETEALL_BUF_SIZE];
+		int i;
+
+		xb.buf = buf;
+		xb.size = sizeof(buf);
+		xb.rth = &rth;
+
+		for (i = 0; ; i++) {
+			struct {
+				struct nlmsghdr n;
+				char buf[NLMSG_BUF_SIZE];
+			} req = {
+				.n.nlmsg_len = NLMSG_HDRLEN,
+				.n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+				.n.nlmsg_type = XFRM_MSG_GETPOLICY,
+				.n.nlmsg_seq = rth.dump = ++rth.seq,
+			};
+
+			xb.offset = 0;
+			xb.nlmsg_count = 0;
+
+			if (show_stats > 1)
+				fprintf(stderr, "Delete-all round = %d\n", i);
+
+			if (rtnl_send(&rth, (void *)&req, req.n.nlmsg_len) < 0) {
+				perror("Cannot send dump request");
+				exit(1);
+			}
+
+			if (rtnl_dump_filter(&rth, xfrm_policy_keep, &xb) < 0) {
+				fprintf(stderr, "Delete-all terminated\n");
+				exit(1);
+			}
+			if (xb.nlmsg_count == 0) {
+				if (show_stats > 1)
+					fprintf(stderr, "Delete-all completed\n");
+				break;
+			}
+
+			if (rtnl_send_check(&rth, xb.buf, xb.offset) < 0) {
+				perror("Failed to send delete-all request");
+				exit(1);
+			}
+			if (show_stats > 1)
+				fprintf(stderr, "Delete-all nlmsg count = %d\n", xb.nlmsg_count);
+
+			xb.offset = 0;
+			xb.nlmsg_count = 0;
+		}
+	} else {
+		struct {
+			struct nlmsghdr n;
+			char buf[NLMSG_BUF_SIZE];
+		} req = {
+			.n.nlmsg_len = NLMSG_HDRLEN,
+			.n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+			.n.nlmsg_type = XFRM_MSG_GETPOLICY,
+			.n.nlmsg_seq = rth.dump = ++rth.seq,
+		};
+
+		if (rtnl_send(&rth, (void *)&req, req.n.nlmsg_len) < 0) {
+			perror("Cannot send dump request");
+			exit(1);
+		}
+
+		if (rtnl_dump_filter(&rth, xfrm_policy_print, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+	}
+
+	rtnl_close(&rth);
+
+	exit(0);
+}
+
+static int print_spdinfo(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	__u32 *f = NLMSG_DATA(n);
+	struct rtattr *tb[XFRMA_SPD_MAX+1];
+	struct rtattr *rta;
+
+	int len = n->nlmsg_len;
+
+	len -= NLMSG_LENGTH(sizeof(__u32));
+	if (len < 0) {
+		fprintf(stderr, "SPDinfo: Wrong len %d\n", len);
+		return -1;
+	}
+
+	rta = XFRMSAPD_RTA(f);
+	parse_rtattr(tb, XFRMA_SPD_MAX, rta, len);
+
+	fprintf(fp, "\t SPD");
+	if (tb[XFRMA_SPD_INFO]) {
+		struct xfrmu_spdinfo *si;
+
+		if (RTA_PAYLOAD(tb[XFRMA_SPD_INFO]) < sizeof(*si)) {
+			fprintf(stderr, "SPDinfo: Wrong len %d\n", len);
+			return -1;
+		}
+		si = RTA_DATA(tb[XFRMA_SPD_INFO]);
+		fprintf(fp, " IN  %d", si->incnt);
+		fprintf(fp, " OUT %d", si->outcnt);
+		fprintf(fp, " FWD %d", si->fwdcnt);
+
+		if (show_stats) {
+			fprintf(fp, " (Sock:");
+			fprintf(fp, " IN %d", si->inscnt);
+			fprintf(fp, " OUT %d", si->outscnt);
+			fprintf(fp, " FWD %d", si->fwdscnt);
+			fprintf(fp, ")");
+		}
+
+		fprintf(fp, "%s", _SL_);
+	}
+	if (show_stats > 1) {
+		struct xfrmu_spdhinfo *sh;
+
+		if (tb[XFRMA_SPD_HINFO]) {
+			if (RTA_PAYLOAD(tb[XFRMA_SPD_HINFO]) < sizeof(*sh)) {
+				fprintf(stderr, "SPDinfo: Wrong len %d\n", len);
+				return -1;
+			}
+			sh = RTA_DATA(tb[XFRMA_SPD_HINFO]);
+			fprintf(fp, "\t SPD buckets:");
+			fprintf(fp, " count %d", sh->spdhcnt);
+			fprintf(fp, " Max %d", sh->spdhmcnt);
+			fprintf(fp, "%s", _SL_);
+		}
+		if (tb[XFRMA_SPD_IPV4_HTHRESH]) {
+			struct xfrmu_spdhthresh *th;
+
+			if (RTA_PAYLOAD(tb[XFRMA_SPD_IPV4_HTHRESH]) < sizeof(*th)) {
+				fprintf(stderr, "SPDinfo: Wrong len %d\n", len);
+				return -1;
+			}
+			th = RTA_DATA(tb[XFRMA_SPD_IPV4_HTHRESH]);
+			fprintf(fp, "\t SPD IPv4 thresholds:");
+			fprintf(fp, " local %d", th->lbits);
+			fprintf(fp, " remote %d", th->rbits);
+			fprintf(fp, "%s", _SL_);
+
+		}
+		if (tb[XFRMA_SPD_IPV6_HTHRESH]) {
+			struct xfrmu_spdhthresh *th;
+
+			if (RTA_PAYLOAD(tb[XFRMA_SPD_IPV6_HTHRESH]) < sizeof(*th)) {
+				fprintf(stderr, "SPDinfo: Wrong len %d\n", len);
+				return -1;
+			}
+			th = RTA_DATA(tb[XFRMA_SPD_IPV6_HTHRESH]);
+			fprintf(fp, "\t SPD IPv6 thresholds:");
+			fprintf(fp, " local %d", th->lbits);
+			fprintf(fp, " remote %d", th->rbits);
+			fprintf(fp, "%s", _SL_);
+		}
+	}
+
+	if (oneline)
+		fprintf(fp, "\n");
+
+	return 0;
+}
+
+static int xfrm_spd_setinfo(int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	struct {
+		struct nlmsghdr			n;
+		__u32				flags;
+		char				buf[RTA_BUF_SIZE];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(__u32)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = XFRM_MSG_NEWSPDINFO,
+		.flags = 0XFFFFFFFF,
+	};
+
+	char *thr4 = NULL;
+	char *thr6 = NULL;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "hthresh4") == 0) {
+			struct xfrmu_spdhthresh thr;
+
+			if (thr4)
+				duparg("hthresh4", *argv);
+			thr4 = *argv;
+			NEXT_ARG();
+			if (get_u8(&thr.lbits, *argv, 0) || thr.lbits > 32)
+				invarg("hthresh4 LBITS value is invalid", *argv);
+			NEXT_ARG();
+			if (get_u8(&thr.rbits, *argv, 0) || thr.rbits > 32)
+				invarg("hthresh4 RBITS value is invalid", *argv);
+
+			addattr_l(&req.n, sizeof(req), XFRMA_SPD_IPV4_HTHRESH,
+				  (void *)&thr, sizeof(thr));
+		} else if (strcmp(*argv, "hthresh6") == 0) {
+			struct xfrmu_spdhthresh thr;
+
+			if (thr6)
+				duparg("hthresh6", *argv);
+			thr6 = *argv;
+			NEXT_ARG();
+			if (get_u8(&thr.lbits, *argv, 0) || thr.lbits > 128)
+				invarg("hthresh6 LBITS value is invalid", *argv);
+			NEXT_ARG();
+			if (get_u8(&thr.rbits, *argv, 0) || thr.rbits > 128)
+				invarg("hthresh6 RBITS value is invalid", *argv);
+
+			addattr_l(&req.n, sizeof(req), XFRMA_SPD_IPV6_HTHRESH,
+				  (void *)&thr, sizeof(thr));
+		} else {
+			invarg("unknown", *argv);
+		}
+
+		argc--; argv++;
+	}
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		exit(2);
+
+	rtnl_close(&rth);
+
+	return 0;
+}
+
+static int xfrm_spd_getinfo(int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	struct {
+		struct nlmsghdr			n;
+		__u32				flags;
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(__u32)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = XFRM_MSG_GETSPDINFO,
+		.flags = 0XFFFFFFFF,
+	};
+	struct nlmsghdr *answer;
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (rtnl_talk(&rth, &req.n, &answer) < 0)
+		exit(2);
+
+	print_spdinfo(answer, (void *)stdout);
+
+	free(answer);
+	rtnl_close(&rth);
+
+	return 0;
+}
+
+static int xfrm_policy_flush(int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	struct {
+		struct nlmsghdr	n;
+		char		buf[RTA_BUF_SIZE];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(0), /* nlmsg data is nothing */
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = XFRM_MSG_FLUSHPOLICY,
+	};
+	char *ptypep = NULL;
+	struct xfrm_userpolicy_type upt = {};
+
+	while (argc > 0) {
+		if (strcmp(*argv, "ptype") == 0) {
+			if (ptypep)
+				duparg("ptype", *argv);
+			ptypep = *argv;
+
+			NEXT_ARG();
+			xfrm_policy_ptype_parse(&upt.type, &argc, &argv);
+		} else
+			invarg("unknown", *argv);
+
+		argc--; argv++;
+	}
+
+	if (ptypep) {
+		addattr_l(&req.n, sizeof(req), XFRMA_POLICY_TYPE,
+			  (void *)&upt, sizeof(upt));
+	}
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (show_stats > 1)
+		fprintf(stderr, "Flush policy\n");
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		exit(2);
+
+	rtnl_close(&rth);
+
+	return 0;
+}
+
+int do_xfrm_policy(int argc, char **argv)
+{
+	if (argc < 1)
+		return xfrm_policy_list_or_deleteall(0, NULL, 0);
+
+	if (matches(*argv, "add") == 0)
+		return xfrm_policy_modify(XFRM_MSG_NEWPOLICY, 0,
+					  argc-1, argv+1);
+	if (matches(*argv, "update") == 0)
+		return xfrm_policy_modify(XFRM_MSG_UPDPOLICY, 0,
+					  argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return xfrm_policy_delete(argc-1, argv+1);
+	if (matches(*argv, "deleteall") == 0 || matches(*argv, "delall") == 0)
+		return xfrm_policy_list_or_deleteall(argc-1, argv+1, 1);
+	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+	    || matches(*argv, "lst") == 0)
+		return xfrm_policy_list_or_deleteall(argc-1, argv+1, 0);
+	if (matches(*argv, "get") == 0)
+		return xfrm_policy_get(argc-1, argv+1);
+	if (matches(*argv, "flush") == 0)
+		return xfrm_policy_flush(argc-1, argv+1);
+	if (matches(*argv, "count") == 0)
+		return xfrm_spd_getinfo(argc, argv);
+	if (matches(*argv, "set") == 0)
+		return xfrm_spd_setinfo(argc-1, argv+1);
+	if (matches(*argv, "help") == 0)
+		usage();
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip xfrm policy help\".\n", *argv);
+	exit(-1);
+}
diff --git a/ip/xfrm_state.c b/ip/xfrm_state.c
new file mode 100644
index 0000000..b03ccc5
--- /dev/null
+++ b/ip/xfrm_state.c
@@ -0,0 +1,1488 @@
+/* $USAGI: $ */
+
+/*
+ * Copyright (C)2004 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 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, see <http://www.gnu.org/licenses>.
+ */
+/*
+ * based on iproute.c
+ */
+/*
+ * Authors:
+ *	Masahide NAKAMURA @USAGI
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include "utils.h"
+#include "xfrm.h"
+#include "ip_common.h"
+
+/* #define NLMSG_DELETEALL_BUF_SIZE (4096-512) */
+#define NLMSG_DELETEALL_BUF_SIZE 8192
+
+/*
+ * Receiving buffer defines:
+ * nlmsg
+ *   data = struct xfrm_usersa_info
+ *   rtattr
+ *   rtattr
+ *   ... (max count of rtattr is XFRM_MAX+1
+ *
+ *  each rtattr data = struct xfrm_algo(dynamic size) or xfrm_address_t
+ */
+#define NLMSG_BUF_SIZE 4096
+#define RTA_BUF_SIZE 2048
+#define XFRM_ALGO_KEY_BUF_SIZE 512
+#define CTX_BUF_SIZE 256
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: ip xfrm state { add | update } ID [ ALGO-LIST ] [ mode MODE ]\n"
+		"        [ mark MARK [ mask MASK ] ] [ reqid REQID ] [ seq SEQ ]\n"
+		"        [ replay-window SIZE ] [ replay-seq SEQ ] [ replay-oseq SEQ ]\n"
+		"        [ replay-seq-hi SEQ ] [ replay-oseq-hi SEQ ]\n"
+		"        [ flag FLAG-LIST ] [ sel SELECTOR ] [ LIMIT-LIST ] [ encap ENCAP ]\n"
+		"        [ coa ADDR[/PLEN] ] [ ctx CTX ] [ extra-flag EXTRA-FLAG-LIST ]\n"
+		"        [ offload [dev DEV] dir DIR ]\n"
+		"        [ output-mark OUTPUT-MARK ]\n"
+		"        [ if_id IF_ID ]\n"
+		"Usage: ip xfrm state allocspi ID [ mode MODE ] [ mark MARK [ mask MASK ] ]\n"
+		"        [ reqid REQID ] [ seq SEQ ] [ min SPI max SPI ]\n"
+		"Usage: ip xfrm state { delete | get } ID [ mark MARK [ mask MASK ] ]\n"
+		"Usage: ip xfrm state deleteall [ ID ] [ mode MODE ] [ reqid REQID ]\n"
+		"        [ flag FLAG-LIST ]\n"
+		"Usage: ip xfrm state list [ nokeys ] [ ID ] [ mode MODE ] [ reqid REQID ]\n"
+		"        [ flag FLAG-LIST ]\n"
+		"Usage: ip xfrm state flush [ proto XFRM-PROTO ]\n"
+		"Usage: ip xfrm state count\n"
+		"ID := [ src ADDR ] [ dst ADDR ] [ proto XFRM-PROTO ] [ spi SPI ]\n"
+		"XFRM-PROTO := ");
+	fprintf(stderr,
+		"%s | %s | %s | %s | %s\n",
+		strxf_xfrmproto(IPPROTO_ESP),
+		strxf_xfrmproto(IPPROTO_AH),
+		strxf_xfrmproto(IPPROTO_COMP),
+		strxf_xfrmproto(IPPROTO_ROUTING),
+		strxf_xfrmproto(IPPROTO_DSTOPTS));
+	fprintf(stderr,
+		"ALGO-LIST := [ ALGO-LIST ] ALGO\n"
+		"ALGO := { ");
+	fprintf(stderr,
+		"%s | %s",
+		strxf_algotype(XFRMA_ALG_CRYPT),
+		strxf_algotype(XFRMA_ALG_AUTH));
+	fprintf(stderr,
+		" } ALGO-NAME ALGO-KEYMAT |\n"
+		"        %s", strxf_algotype(XFRMA_ALG_AUTH_TRUNC));
+	fprintf(stderr,
+		" ALGO-NAME ALGO-KEYMAT ALGO-TRUNC-LEN |\n"
+		"        %s", strxf_algotype(XFRMA_ALG_AEAD));
+	fprintf(stderr,
+		" ALGO-NAME ALGO-KEYMAT ALGO-ICV-LEN |\n"
+		"        %s", strxf_algotype(XFRMA_ALG_COMP));
+	fprintf(stderr,
+		" ALGO-NAME\n"
+		"MODE := transport | tunnel | beet | ro | in_trigger\n"
+		"FLAG-LIST := [ FLAG-LIST ] FLAG\n"
+		"FLAG := noecn | decap-dscp | nopmtudisc | wildrecv | icmp | af-unspec | align4 | esn\n"
+		"EXTRA-FLAG-LIST := [ EXTRA-FLAG-LIST ] EXTRA-FLAG\n"
+		"EXTRA-FLAG := dont-encap-dscp\n"
+		"SELECTOR := [ src ADDR[/PLEN] ] [ dst ADDR[/PLEN] ] [ dev DEV ] [ UPSPEC ]\n"
+		"UPSPEC := proto { { ");
+	fprintf(stderr,
+		"%s | %s | %s | %s",
+		strxf_proto(IPPROTO_TCP),
+		strxf_proto(IPPROTO_UDP),
+		strxf_proto(IPPROTO_SCTP),
+		strxf_proto(IPPROTO_DCCP));
+	fprintf(stderr,
+		" } [ sport PORT ] [ dport PORT ] |\n"
+		"                  { ");
+	fprintf(stderr,
+		"%s | %s | %s",
+		strxf_proto(IPPROTO_ICMP),
+		strxf_proto(IPPROTO_ICMPV6),
+		strxf_proto(IPPROTO_MH));
+	fprintf(stderr,
+		" } [ type NUMBER ] [ code NUMBER ] |\n");
+	fprintf(stderr,
+		"                  %s", strxf_proto(IPPROTO_GRE));
+	fprintf(stderr,
+		" [ key { DOTTED-QUAD | NUMBER } ] | PROTO }\n"
+		"LIMIT-LIST := [ LIMIT-LIST ] limit LIMIT\n"
+		"LIMIT := { time-soft | time-hard | time-use-soft | time-use-hard } SECONDS |\n"
+		"         { byte-soft | byte-hard } SIZE | { packet-soft | packet-hard } COUNT\n"
+		"ENCAP := { espinudp | espinudp-nonike } SPORT DPORT OADDR\n"
+		"DIR := in | out\n");
+
+	exit(-1);
+}
+
+static int xfrm_algo_parse(struct xfrm_algo *alg, enum xfrm_attr_type_t type,
+			   char *name, char *key, char *buf, int max)
+{
+	int len;
+	int slen = strlen(key);
+
+#if 0
+	/* XXX: verifying both name and key is required! */
+	fprintf(stderr, "warning: ALGO-NAME/ALGO-KEYMAT values will be sent to the kernel promiscuously! (verifying them isn't implemented yet)\n");
+#endif
+
+	strlcpy(alg->alg_name, name, sizeof(alg->alg_name));
+
+	if (slen > 2 && strncmp(key, "0x", 2) == 0) {
+		/* split two chars "0x" from the top */
+		char *p = key + 2;
+		int plen = slen - 2;
+		int i;
+		int j;
+
+		/* Converting hexadecimal numbered string into real key;
+		 * Convert each two chars into one char(value). If number
+		 * of the length is odd, add zero on the top for rounding.
+		 */
+
+		/* calculate length of the converted values(real key) */
+		len = (plen + 1) / 2;
+		if (len > max)
+			invarg("ALGO-KEYMAT value makes buffer overflow\n", key);
+
+		for (i = -(plen % 2), j = 0; j < len; i += 2, j++) {
+			char vbuf[3];
+			__u8 val;
+
+			vbuf[0] = i >= 0 ? p[i] : '0';
+			vbuf[1] = p[i + 1];
+			vbuf[2] = '\0';
+
+			if (get_u8(&val, vbuf, 16))
+				invarg("ALGO-KEYMAT value is invalid", key);
+
+			buf[j] = val;
+		}
+	} else {
+		len = slen;
+		if (len > 0) {
+			if (len > max)
+				invarg("ALGO-KEYMAT value makes buffer overflow\n", key);
+
+			memcpy(buf, key, len);
+		}
+	}
+
+	alg->alg_key_len = len * 8;
+
+	return 0;
+}
+
+static int xfrm_seq_parse(__u32 *seq, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+
+	if (get_be32(seq, *argv, 0))
+		invarg("SEQ value is invalid", *argv);
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static int xfrm_state_flag_parse(__u8 *flags, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int len = strlen(*argv);
+
+	if (len > 2 && strncmp(*argv, "0x", 2) == 0) {
+		__u8 val = 0;
+
+		if (get_u8(&val, *argv, 16))
+			invarg("FLAG value is invalid", *argv);
+		*flags = val;
+	} else {
+		while (1) {
+			if (strcmp(*argv, "noecn") == 0)
+				*flags |= XFRM_STATE_NOECN;
+			else if (strcmp(*argv, "decap-dscp") == 0)
+				*flags |= XFRM_STATE_DECAP_DSCP;
+			else if (strcmp(*argv, "nopmtudisc") == 0)
+				*flags |= XFRM_STATE_NOPMTUDISC;
+			else if (strcmp(*argv, "wildrecv") == 0)
+				*flags |= XFRM_STATE_WILDRECV;
+			else if (strcmp(*argv, "icmp") == 0)
+				*flags |= XFRM_STATE_ICMP;
+			else if (strcmp(*argv, "af-unspec") == 0)
+				*flags |= XFRM_STATE_AF_UNSPEC;
+			else if (strcmp(*argv, "align4") == 0)
+				*flags |= XFRM_STATE_ALIGN4;
+			else if (strcmp(*argv, "esn") == 0)
+				*flags |= XFRM_STATE_ESN;
+			else {
+				PREV_ARG(); /* back track */
+				break;
+			}
+
+			if (!NEXT_ARG_OK())
+				break;
+			NEXT_ARG();
+		}
+	}
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static int xfrm_state_extra_flag_parse(__u32 *extra_flags, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+	int len = strlen(*argv);
+
+	if (len > 2 && strncmp(*argv, "0x", 2) == 0) {
+		__u32 val = 0;
+
+		if (get_u32(&val, *argv, 16))
+			invarg("\"EXTRA-FLAG\" is invalid", *argv);
+		*extra_flags = val;
+	} else {
+		while (1) {
+			if (strcmp(*argv, "dont-encap-dscp") == 0)
+				*extra_flags |= XFRM_SA_XFLAG_DONT_ENCAP_DSCP;
+			else {
+				PREV_ARG(); /* back track */
+				break;
+			}
+
+			if (!NEXT_ARG_OK())
+				break;
+			NEXT_ARG();
+		}
+	}
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static int xfrm_offload_dir_parse(__u8 *dir, int *argcp, char ***argvp)
+{
+	int argc = *argcp;
+	char **argv = *argvp;
+
+	if (strcmp(*argv, "in") == 0)
+		*dir = XFRM_OFFLOAD_INBOUND;
+	else if (strcmp(*argv, "out") == 0)
+		*dir = 0;
+	else
+		invarg("DIR value is invalid", *argv);
+
+	*argcp = argc;
+	*argvp = argv;
+
+	return 0;
+}
+
+static int xfrm_state_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	struct {
+		struct nlmsghdr	n;
+		struct xfrm_usersa_info xsinfo;
+		char			buf[RTA_BUF_SIZE];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xsinfo)),
+		.n.nlmsg_flags = NLM_F_REQUEST | flags,
+		.n.nlmsg_type = cmd,
+		.xsinfo.family = preferred_family,
+		.xsinfo.lft.soft_byte_limit = XFRM_INF,
+		.xsinfo.lft.hard_byte_limit = XFRM_INF,
+		.xsinfo.lft.soft_packet_limit = XFRM_INF,
+		.xsinfo.lft.hard_packet_limit = XFRM_INF,
+	};
+	struct xfrm_replay_state replay = {};
+	struct xfrm_replay_state_esn replay_esn = {};
+	struct xfrm_user_offload xuo = {};
+	unsigned int ifindex = 0;
+	__u8 dir = 0;
+	bool is_offload = false;
+	__u32 replay_window = 0;
+	__u32 seq = 0, oseq = 0, seq_hi = 0, oseq_hi = 0;
+	char *idp = NULL;
+	char *aeadop = NULL;
+	char *ealgop = NULL;
+	char *aalgop = NULL;
+	char *calgop = NULL;
+	char *coap = NULL;
+	char *sctxp = NULL;
+	__u32 extra_flags = 0;
+	struct xfrm_mark mark = {0, 0};
+	struct {
+		struct xfrm_user_sec_ctx sctx;
+		char    str[CTX_BUF_SIZE];
+	} ctx = {};
+	__u32 output_mark = 0;
+	bool is_if_id_set = false;
+	__u32 if_id = 0;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "mode") == 0) {
+			NEXT_ARG();
+			xfrm_mode_parse(&req.xsinfo.mode, &argc, &argv);
+		} else if (strcmp(*argv, "mark") == 0) {
+			xfrm_parse_mark(&mark, &argc, &argv);
+		} else if (strcmp(*argv, "reqid") == 0) {
+			NEXT_ARG();
+			xfrm_reqid_parse(&req.xsinfo.reqid, &argc, &argv);
+		} else if (strcmp(*argv, "seq") == 0) {
+			NEXT_ARG();
+			xfrm_seq_parse(&req.xsinfo.seq, &argc, &argv);
+		} else if (strcmp(*argv, "replay-window") == 0) {
+			NEXT_ARG();
+			if (get_u32(&replay_window, *argv, 0))
+				invarg("value after \"replay-window\" is invalid", *argv);
+		} else if (strcmp(*argv, "replay-seq") == 0) {
+			NEXT_ARG();
+			if (get_u32(&seq, *argv, 0))
+				invarg("value after \"replay-seq\" is invalid", *argv);
+		} else if (strcmp(*argv, "replay-seq-hi") == 0) {
+			NEXT_ARG();
+			if (get_u32(&seq_hi, *argv, 0))
+				invarg("value after \"replay-seq-hi\" is invalid", *argv);
+		} else if (strcmp(*argv, "replay-oseq") == 0) {
+			NEXT_ARG();
+			if (get_u32(&oseq, *argv, 0))
+				invarg("value after \"replay-oseq\" is invalid", *argv);
+		} else if (strcmp(*argv, "replay-oseq-hi") == 0) {
+			NEXT_ARG();
+			if (get_u32(&oseq_hi, *argv, 0))
+				invarg("value after \"replay-oseq-hi\" is invalid", *argv);
+		} else if (strcmp(*argv, "flag") == 0) {
+			NEXT_ARG();
+			xfrm_state_flag_parse(&req.xsinfo.flags, &argc, &argv);
+		} else if (strcmp(*argv, "extra-flag") == 0) {
+			NEXT_ARG();
+			xfrm_state_extra_flag_parse(&extra_flags, &argc, &argv);
+		} else if (strcmp(*argv, "sel") == 0) {
+			NEXT_ARG();
+			preferred_family = AF_UNSPEC;
+			xfrm_selector_parse(&req.xsinfo.sel, &argc, &argv);
+			preferred_family = req.xsinfo.sel.family;
+		} else if (strcmp(*argv, "limit") == 0) {
+			NEXT_ARG();
+			xfrm_lifetime_cfg_parse(&req.xsinfo.lft, &argc, &argv);
+		} else if (strcmp(*argv, "encap") == 0) {
+			struct xfrm_encap_tmpl encap;
+			inet_prefix oa;
+			NEXT_ARG();
+			xfrm_encap_type_parse(&encap.encap_type, &argc, &argv);
+			NEXT_ARG();
+			if (get_be16(&encap.encap_sport, *argv, 0))
+				invarg("SPORT value after \"encap\" is invalid", *argv);
+			NEXT_ARG();
+			if (get_be16(&encap.encap_dport, *argv, 0))
+				invarg("DPORT value after \"encap\" is invalid", *argv);
+			NEXT_ARG();
+			get_addr(&oa, *argv, AF_UNSPEC);
+			memcpy(&encap.encap_oa, &oa.data, sizeof(encap.encap_oa));
+			addattr_l(&req.n, sizeof(req.buf), XFRMA_ENCAP,
+				  (void *)&encap, sizeof(encap));
+		} else if (strcmp(*argv, "coa") == 0) {
+			inet_prefix coa;
+			xfrm_address_t xcoa = {};
+
+			if (coap)
+				duparg("coa", *argv);
+			coap = *argv;
+
+			NEXT_ARG();
+
+			get_prefix(&coa, *argv, preferred_family);
+			if (coa.family == AF_UNSPEC)
+				invarg("value after \"coa\" has an unrecognized address family", *argv);
+			if (coa.bytelen > sizeof(xcoa))
+				invarg("value after \"coa\" is too large", *argv);
+
+			memcpy(&xcoa, &coa.data, coa.bytelen);
+
+			addattr_l(&req.n, sizeof(req.buf), XFRMA_COADDR,
+				  (void *)&xcoa, sizeof(xcoa));
+		} else if (strcmp(*argv, "ctx") == 0) {
+			char *context;
+
+			if (sctxp)
+				duparg("ctx", *argv);
+			sctxp = *argv;
+
+			NEXT_ARG();
+			context = *argv;
+
+			xfrm_sctx_parse((char *)&ctx.str, context, &ctx.sctx);
+			addattr_l(&req.n, sizeof(req.buf), XFRMA_SEC_CTX,
+				  (void *)&ctx, ctx.sctx.len);
+		} else if (strcmp(*argv, "offload") == 0) {
+			is_offload = true;
+			NEXT_ARG();
+			if (strcmp(*argv, "dev") == 0) {
+				NEXT_ARG();
+				ifindex = ll_name_to_index(*argv);
+				if (!ifindex) {
+					invarg("value after \"offload dev\" is invalid", *argv);
+					is_offload = false;
+				}
+				NEXT_ARG();
+			}
+			if (strcmp(*argv, "dir") == 0) {
+				NEXT_ARG();
+				xfrm_offload_dir_parse(&dir, &argc, &argv);
+			} else {
+				invarg("value after \"offload dir\" is invalid", *argv);
+				is_offload = false;
+			}
+		} else if (strcmp(*argv, "output-mark") == 0) {
+			NEXT_ARG();
+			if (get_u32(&output_mark, *argv, 0))
+				invarg("value after \"output-mark\" is invalid", *argv);
+		} else if (strcmp(*argv, "if_id") == 0) {
+			NEXT_ARG();
+			if (get_u32(&if_id, *argv, 0))
+				invarg("value after \"if_id\" is invalid", *argv);
+			is_if_id_set = true;
+		} else {
+			/* try to assume ALGO */
+			int type = xfrm_algotype_getbyname(*argv);
+
+			switch (type) {
+			case XFRMA_ALG_AEAD:
+			case XFRMA_ALG_CRYPT:
+			case XFRMA_ALG_AUTH:
+			case XFRMA_ALG_AUTH_TRUNC:
+			case XFRMA_ALG_COMP:
+			{
+				/* ALGO */
+				struct {
+					union {
+						struct xfrm_algo alg;
+						struct xfrm_algo_aead aead;
+						struct xfrm_algo_auth auth;
+					} u;
+					char buf[XFRM_ALGO_KEY_BUF_SIZE];
+				} alg = {};
+				int len;
+				__u32 icvlen, trunclen;
+				char *name;
+				char *key = "";
+				char *buf;
+
+				switch (type) {
+				case XFRMA_ALG_AEAD:
+					if (ealgop || aalgop || aeadop)
+						duparg("ALGO-TYPE", *argv);
+					aeadop = *argv;
+					break;
+				case XFRMA_ALG_CRYPT:
+					if (ealgop || aeadop)
+						duparg("ALGO-TYPE", *argv);
+					ealgop = *argv;
+					break;
+				case XFRMA_ALG_AUTH:
+				case XFRMA_ALG_AUTH_TRUNC:
+					if (aalgop || aeadop)
+						duparg("ALGO-TYPE", *argv);
+					aalgop = *argv;
+					break;
+				case XFRMA_ALG_COMP:
+					if (calgop)
+						duparg("ALGO-TYPE", *argv);
+					calgop = *argv;
+					break;
+				default:
+					/* not reached */
+					invarg("ALGO-TYPE value is invalid\n", *argv);
+				}
+
+				if (!NEXT_ARG_OK())
+					missarg("ALGO-NAME");
+				NEXT_ARG();
+				name = *argv;
+
+				switch (type) {
+				case XFRMA_ALG_AEAD:
+				case XFRMA_ALG_CRYPT:
+				case XFRMA_ALG_AUTH:
+				case XFRMA_ALG_AUTH_TRUNC:
+					if (!NEXT_ARG_OK())
+						missarg("ALGO-KEYMAT");
+					NEXT_ARG();
+					key = *argv;
+					break;
+				}
+
+				buf = alg.u.alg.alg_key;
+				len = sizeof(alg.u.alg);
+
+				switch (type) {
+				case XFRMA_ALG_AEAD:
+					if (!NEXT_ARG_OK())
+						missarg("ALGO-ICV-LEN");
+					NEXT_ARG();
+					if (get_u32(&icvlen, *argv, 0))
+						invarg("ALGO-ICV-LEN value is invalid",
+						       *argv);
+					alg.u.aead.alg_icv_len = icvlen;
+
+					buf = alg.u.aead.alg_key;
+					len = sizeof(alg.u.aead);
+					break;
+				case XFRMA_ALG_AUTH_TRUNC:
+					if (!NEXT_ARG_OK())
+						missarg("ALGO-TRUNC-LEN");
+					NEXT_ARG();
+					if (get_u32(&trunclen, *argv, 0))
+						invarg("ALGO-TRUNC-LEN value is invalid",
+						       *argv);
+					alg.u.auth.alg_trunc_len = trunclen;
+
+					buf = alg.u.auth.alg_key;
+					len = sizeof(alg.u.auth);
+					break;
+				}
+
+				xfrm_algo_parse((void *)&alg, type, name, key,
+						buf, sizeof(alg.buf));
+				len += alg.u.alg.alg_key_len / 8;
+
+				addattr_l(&req.n, sizeof(req.buf), type,
+					  (void *)&alg, len);
+				break;
+			}
+			default:
+				/* try to assume ID */
+				if (idp)
+					invarg("unknown", *argv);
+				idp = *argv;
+
+				/* ID */
+				xfrm_id_parse(&req.xsinfo.saddr, &req.xsinfo.id,
+					      &req.xsinfo.family, 0, &argc, &argv);
+				if (preferred_family == AF_UNSPEC)
+					preferred_family = req.xsinfo.family;
+			}
+		}
+		argc--; argv++;
+	}
+
+	if (req.xsinfo.flags & XFRM_STATE_ESN &&
+	    replay_window == 0) {
+		fprintf(stderr, "Error: esn flag set without replay-window.\n");
+		exit(-1);
+	}
+
+	if (replay_window > XFRMA_REPLAY_ESN_MAX) {
+		fprintf(stderr,
+			"Error: replay-window (%u) > XFRMA_REPLAY_ESN_MAX (%u).\n",
+			replay_window, XFRMA_REPLAY_ESN_MAX);
+		exit(-1);
+	}
+
+	if (is_offload) {
+		xuo.ifindex = ifindex;
+		xuo.flags = dir;
+		addattr_l(&req.n, sizeof(req.buf), XFRMA_OFFLOAD_DEV, &xuo,
+			  sizeof(xuo));
+	}
+	if (req.xsinfo.flags & XFRM_STATE_ESN ||
+	    replay_window > (sizeof(replay.bitmap) * 8)) {
+		replay_esn.seq = seq;
+		replay_esn.oseq = oseq;
+		replay_esn.seq_hi = seq_hi;
+		replay_esn.oseq_hi = oseq_hi;
+		replay_esn.replay_window = replay_window;
+		replay_esn.bmp_len = (replay_window + sizeof(__u32) * 8 - 1) /
+				     (sizeof(__u32) * 8);
+		addattr_l(&req.n, sizeof(req.buf), XFRMA_REPLAY_ESN_VAL,
+			  &replay_esn, sizeof(replay_esn));
+	} else {
+		if (seq || oseq) {
+			replay.seq = seq;
+			replay.oseq = oseq;
+			addattr_l(&req.n, sizeof(req.buf), XFRMA_REPLAY_VAL,
+				  &replay, sizeof(replay));
+		}
+		req.xsinfo.replay_window = replay_window;
+	}
+
+	if (extra_flags)
+		addattr32(&req.n, sizeof(req.buf), XFRMA_SA_EXTRA_FLAGS,
+			  extra_flags);
+
+	if (!idp) {
+		fprintf(stderr, "Not enough information: ID is required\n");
+		exit(1);
+	}
+
+	if (mark.m) {
+		int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
+				  (void *)&mark, sizeof(mark));
+		if (r < 0) {
+			fprintf(stderr, "XFRMA_MARK failed\n");
+			exit(1);
+		}
+	}
+
+	if (is_if_id_set)
+		addattr32(&req.n, sizeof(req.buf), XFRMA_IF_ID, if_id);
+
+	if (xfrm_xfrmproto_is_ipsec(req.xsinfo.id.proto)) {
+		switch (req.xsinfo.mode) {
+		case XFRM_MODE_TRANSPORT:
+		case XFRM_MODE_TUNNEL:
+			break;
+		case XFRM_MODE_BEET:
+			if (req.xsinfo.id.proto == IPPROTO_ESP)
+				break;
+		default:
+			fprintf(stderr, "MODE value is invalid with XFRM-PROTO value \"%s\"\n",
+				strxf_xfrmproto(req.xsinfo.id.proto));
+			exit(1);
+		}
+
+		switch (req.xsinfo.id.proto) {
+		case IPPROTO_ESP:
+			if (calgop) {
+				fprintf(stderr, "ALGO-TYPE value \"%s\" is invalid with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_COMP),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			if (!ealgop && !aeadop) {
+				fprintf(stderr, "ALGO-TYPE value \"%s\" or \"%s\" is required with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_CRYPT),
+					strxf_algotype(XFRMA_ALG_AEAD),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			break;
+		case IPPROTO_AH:
+			if (ealgop || aeadop || calgop) {
+				fprintf(stderr, "ALGO-TYPE values \"%s\", \"%s\", and \"%s\" are invalid with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_CRYPT),
+					strxf_algotype(XFRMA_ALG_AEAD),
+					strxf_algotype(XFRMA_ALG_COMP),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			if (!aalgop) {
+				fprintf(stderr, "ALGO-TYPE value \"%s\" or \"%s\" is required with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_AUTH),
+					strxf_algotype(XFRMA_ALG_AUTH_TRUNC),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			break;
+		case IPPROTO_COMP:
+			if (ealgop || aalgop || aeadop) {
+				fprintf(stderr, "ALGO-TYPE values \"%s\", \"%s\", \"%s\", and \"%s\" are invalid with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_CRYPT),
+					strxf_algotype(XFRMA_ALG_AUTH),
+					strxf_algotype(XFRMA_ALG_AUTH_TRUNC),
+					strxf_algotype(XFRMA_ALG_AEAD),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			if (!calgop) {
+				fprintf(stderr, "ALGO-TYPE value \"%s\" is required with XFRM-PROTO value \"%s\"\n",
+					strxf_algotype(XFRMA_ALG_COMP),
+					strxf_xfrmproto(req.xsinfo.id.proto));
+				exit(1);
+			}
+			break;
+		}
+	} else {
+		if (ealgop || aalgop || aeadop || calgop) {
+			fprintf(stderr, "ALGO is invalid with XFRM-PROTO value \"%s\"\n",
+				strxf_xfrmproto(req.xsinfo.id.proto));
+			exit(1);
+		}
+	}
+
+	if (xfrm_xfrmproto_is_ro(req.xsinfo.id.proto)) {
+		switch (req.xsinfo.mode) {
+		case XFRM_MODE_ROUTEOPTIMIZATION:
+		case XFRM_MODE_IN_TRIGGER:
+			break;
+		case 0:
+			fprintf(stderr, "\"mode\" is required with XFRM-PROTO value \"%s\"\n",
+				strxf_xfrmproto(req.xsinfo.id.proto));
+			exit(1);
+		default:
+			fprintf(stderr, "MODE value is invalid with XFRM-PROTO value \"%s\"\n",
+				strxf_xfrmproto(req.xsinfo.id.proto));
+			exit(1);
+		}
+
+		if (!coap) {
+			fprintf(stderr, "\"coa\" is required with XFRM-PROTO value \"%s\"\n",
+				strxf_xfrmproto(req.xsinfo.id.proto));
+			exit(1);
+		}
+	} else {
+		if (coap) {
+			fprintf(stderr, "\"coa\" is invalid with XFRM-PROTO value \"%s\"\n",
+				strxf_xfrmproto(req.xsinfo.id.proto));
+			exit(1);
+		}
+	}
+
+	if (output_mark)
+		addattr32(&req.n, sizeof(req.buf), XFRMA_OUTPUT_MARK, output_mark);
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (req.xsinfo.family == AF_UNSPEC)
+		req.xsinfo.family = AF_INET;
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		exit(2);
+
+	rtnl_close(&rth);
+
+	return 0;
+}
+
+static int xfrm_state_allocspi(int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	struct {
+		struct nlmsghdr	n;
+		struct xfrm_userspi_info xspi;
+		char			buf[RTA_BUF_SIZE];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xspi)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = XFRM_MSG_ALLOCSPI,
+		.xspi.info.family = preferred_family,
+#if 0
+		.xspi.lft.soft_byte_limit = XFRM_INF,
+		.xspi.lft.hard_byte_limit = XFRM_INF,
+		.xspi.lft.soft_packet_limit = XFRM_INF,
+		.xspi.lft.hard_packet_limit = XFRM_INF,
+#endif
+	};
+	char *idp = NULL;
+	char *minp = NULL;
+	char *maxp = NULL;
+	struct xfrm_mark mark = {0, 0};
+	struct nlmsghdr *answer;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "mode") == 0) {
+			NEXT_ARG();
+			xfrm_mode_parse(&req.xspi.info.mode, &argc, &argv);
+		} else if (strcmp(*argv, "mark") == 0) {
+			xfrm_parse_mark(&mark, &argc, &argv);
+		} else if (strcmp(*argv, "reqid") == 0) {
+			NEXT_ARG();
+			xfrm_reqid_parse(&req.xspi.info.reqid, &argc, &argv);
+		} else if (strcmp(*argv, "seq") == 0) {
+			NEXT_ARG();
+			xfrm_seq_parse(&req.xspi.info.seq, &argc, &argv);
+		} else if (strcmp(*argv, "min") == 0) {
+			if (minp)
+				duparg("min", *argv);
+			minp = *argv;
+
+			NEXT_ARG();
+
+			if (get_u32(&req.xspi.min, *argv, 0))
+				invarg("value after \"min\" is invalid", *argv);
+		} else if (strcmp(*argv, "max") == 0) {
+			if (maxp)
+				duparg("max", *argv);
+			maxp = *argv;
+
+			NEXT_ARG();
+
+			if (get_u32(&req.xspi.max, *argv, 0))
+				invarg("value after \"max\" is invalid", *argv);
+		} else {
+			/* try to assume ID */
+			if (idp)
+				invarg("unknown", *argv);
+			idp = *argv;
+
+			/* ID */
+			xfrm_id_parse(&req.xspi.info.saddr, &req.xspi.info.id,
+				      &req.xspi.info.family, 0, &argc, &argv);
+			if (req.xspi.info.id.spi) {
+				fprintf(stderr, "\"spi\" is invalid\n");
+				exit(1);
+			}
+			if (preferred_family == AF_UNSPEC)
+				preferred_family = req.xspi.info.family;
+		}
+		argc--; argv++;
+	}
+
+	if (!idp) {
+		fprintf(stderr, "Not enough information: ID is required\n");
+		exit(1);
+	}
+
+	if (minp) {
+		if (!maxp) {
+			fprintf(stderr, "\"max\" is missing\n");
+			exit(1);
+		}
+		if (req.xspi.min > req.xspi.max) {
+			fprintf(stderr, "value after \"min\" is larger than value after \"max\"\n");
+			exit(1);
+		}
+	} else {
+		if (maxp) {
+			fprintf(stderr, "\"min\" is missing\n");
+			exit(1);
+		}
+
+		/* XXX: Default value defined in PF_KEY;
+		 * See kernel's net/key/af_key.c(pfkey_getspi).
+		 */
+		req.xspi.min = 0x100;
+		req.xspi.max = 0x0fffffff;
+
+		/* XXX: IPCOMP spi is 16-bits;
+		 * See kernel's net/xfrm/xfrm_user(verify_userspi_info).
+		 */
+		if (req.xspi.info.id.proto == IPPROTO_COMP)
+			req.xspi.max = 0xffff;
+	}
+
+	if (mark.m & mark.v) {
+		int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
+				  (void *)&mark, sizeof(mark));
+		if (r < 0) {
+			fprintf(stderr, "XFRMA_MARK failed\n");
+			exit(1);
+		}
+	}
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (req.xspi.info.family == AF_UNSPEC)
+		req.xspi.info.family = AF_INET;
+
+
+	if (rtnl_talk(&rth, &req.n, &answer) < 0)
+		exit(2);
+
+	if (xfrm_state_print(answer, (void *)stdout) < 0) {
+		fprintf(stderr, "An error :-)\n");
+		exit(1);
+	}
+
+	free(answer);
+	rtnl_close(&rth);
+
+	return 0;
+}
+
+static int xfrm_state_filter_match(struct xfrm_usersa_info *xsinfo)
+{
+	if (!filter.use)
+		return 1;
+
+	if (filter.xsinfo.family != AF_UNSPEC &&
+	    filter.xsinfo.family != xsinfo->family)
+		return 0;
+
+	if (filter.id_src_mask)
+		if (xfrm_addr_match(&xsinfo->saddr, &filter.xsinfo.saddr,
+				    filter.id_src_mask))
+			return 0;
+	if (filter.id_dst_mask)
+		if (xfrm_addr_match(&xsinfo->id.daddr, &filter.xsinfo.id.daddr,
+				    filter.id_dst_mask))
+			return 0;
+	if ((xsinfo->id.proto^filter.xsinfo.id.proto)&filter.id_proto_mask)
+		return 0;
+	if ((xsinfo->id.spi^filter.xsinfo.id.spi)&filter.id_spi_mask)
+		return 0;
+	if ((xsinfo->mode^filter.xsinfo.mode)&filter.mode_mask)
+		return 0;
+	if ((xsinfo->reqid^filter.xsinfo.reqid)&filter.reqid_mask)
+		return 0;
+	if (filter.state_flags_mask)
+		if ((xsinfo->flags & filter.xsinfo.flags) == 0)
+			return 0;
+
+	return 1;
+}
+
+static int __do_xfrm_state_print(struct nlmsghdr *n, void *arg, bool nokeys)
+{
+	FILE *fp = (FILE *)arg;
+	struct rtattr *tb[XFRMA_MAX+1];
+	struct rtattr *rta;
+	struct xfrm_usersa_info *xsinfo = NULL;
+	struct xfrm_user_expire *xexp = NULL;
+	struct xfrm_usersa_id	*xsid = NULL;
+	int len = n->nlmsg_len;
+
+	if (n->nlmsg_type != XFRM_MSG_NEWSA &&
+	    n->nlmsg_type != XFRM_MSG_DELSA &&
+	    n->nlmsg_type != XFRM_MSG_UPDSA &&
+	    n->nlmsg_type != XFRM_MSG_EXPIRE) {
+		fprintf(stderr, "Not a state: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+
+	if (n->nlmsg_type == XFRM_MSG_DELSA) {
+		/* Don't blame me for this .. Herbert made me do it */
+		xsid = NLMSG_DATA(n);
+		len -= NLMSG_SPACE(sizeof(*xsid));
+	} else if (n->nlmsg_type == XFRM_MSG_EXPIRE) {
+		xexp = NLMSG_DATA(n);
+		xsinfo = &xexp->state;
+		len -= NLMSG_SPACE(sizeof(*xexp));
+	} else {
+		xexp = NULL;
+		xsinfo = NLMSG_DATA(n);
+		len -= NLMSG_SPACE(sizeof(*xsinfo));
+	}
+
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (xsinfo && !xfrm_state_filter_match(xsinfo))
+		return 0;
+
+	if (n->nlmsg_type == XFRM_MSG_DELSA)
+		fprintf(fp, "Deleted ");
+	else if (n->nlmsg_type == XFRM_MSG_UPDSA)
+		fprintf(fp, "Updated ");
+	else if (n->nlmsg_type == XFRM_MSG_EXPIRE)
+		fprintf(fp, "Expired ");
+
+	if (n->nlmsg_type == XFRM_MSG_DELSA)
+		rta = XFRMSID_RTA(xsid);
+	else if (n->nlmsg_type == XFRM_MSG_EXPIRE)
+		rta = XFRMEXP_RTA(xexp);
+	else
+		rta = XFRMS_RTA(xsinfo);
+
+	parse_rtattr(tb, XFRMA_MAX, rta, len);
+
+	if (n->nlmsg_type == XFRM_MSG_DELSA) {
+		/* xfrm_policy_id_print(); */
+
+		if (!tb[XFRMA_SA]) {
+			fprintf(stderr, "Buggy XFRM_MSG_DELSA: no XFRMA_SA\n");
+			return -1;
+		}
+		if (RTA_PAYLOAD(tb[XFRMA_SA]) < sizeof(*xsinfo)) {
+			fprintf(stderr, "Buggy XFRM_MSG_DELPOLICY: too short XFRMA_POLICY len\n");
+			return -1;
+		}
+		xsinfo = RTA_DATA(tb[XFRMA_SA]);
+	}
+
+	xfrm_state_info_print(xsinfo, tb, fp, NULL, NULL, nokeys);
+
+	if (n->nlmsg_type == XFRM_MSG_EXPIRE) {
+		fprintf(fp, "\t");
+		fprintf(fp, "hard %u", xexp->hard);
+		fprintf(fp, "%s", _SL_);
+	}
+
+	if (oneline)
+		fprintf(fp, "\n");
+	fflush(fp);
+
+	return 0;
+}
+
+int xfrm_state_print(struct nlmsghdr *n, void *arg)
+{
+	return __do_xfrm_state_print(n, arg, false);
+}
+
+int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg)
+{
+	return __do_xfrm_state_print(n, arg, true);
+}
+
+static int xfrm_state_get_or_delete(int argc, char **argv, int delete)
+{
+	struct rtnl_handle rth;
+	struct {
+		struct nlmsghdr	n;
+		struct xfrm_usersa_id	xsid;
+		char			buf[RTA_BUF_SIZE];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xsid)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = delete ? XFRM_MSG_DELSA : XFRM_MSG_GETSA,
+		.xsid.family = preferred_family,
+	};
+	struct xfrm_id id;
+	char *idp = NULL;
+	struct xfrm_mark mark = {0, 0};
+
+	while (argc > 0) {
+		xfrm_address_t saddr;
+
+		if (strcmp(*argv, "mark") == 0) {
+			xfrm_parse_mark(&mark, &argc, &argv);
+		} else {
+			if (idp)
+				invarg("unknown", *argv);
+			idp = *argv;
+
+			/* ID */
+			memset(&id, 0, sizeof(id));
+			memset(&saddr, 0, sizeof(saddr));
+			xfrm_id_parse(&saddr, &id, &req.xsid.family, 0,
+				      &argc, &argv);
+
+			memcpy(&req.xsid.daddr, &id.daddr, sizeof(req.xsid.daddr));
+			req.xsid.spi = id.spi;
+			req.xsid.proto = id.proto;
+
+			addattr_l(&req.n, sizeof(req.buf), XFRMA_SRCADDR,
+				  (void *)&saddr, sizeof(saddr));
+		}
+
+		argc--; argv++;
+	}
+
+	if (mark.m & mark.v) {
+		int r = addattr_l(&req.n, sizeof(req.buf), XFRMA_MARK,
+				  (void *)&mark, sizeof(mark));
+		if (r < 0) {
+			fprintf(stderr, "XFRMA_MARK failed\n");
+			exit(1);
+		}
+	}
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (req.xsid.family == AF_UNSPEC)
+		req.xsid.family = AF_INET;
+
+	if (delete) {
+		if (rtnl_talk(&rth, &req.n, NULL) < 0)
+			exit(2);
+	} else {
+		struct nlmsghdr *answer;
+
+		if (rtnl_talk(&rth, &req.n, &answer) < 0)
+			exit(2);
+
+		if (xfrm_state_print(answer, (void *)stdout) < 0) {
+			fprintf(stderr, "An error :-)\n");
+			exit(1);
+		}
+
+		free(answer);
+	}
+
+	rtnl_close(&rth);
+
+	return 0;
+}
+
+/*
+ * With an existing state of nlmsg, make new nlmsg for deleting the state
+ * and store it to buffer.
+ */
+static int xfrm_state_keep(struct nlmsghdr *n, void *arg)
+{
+	struct xfrm_buffer *xb = (struct xfrm_buffer *)arg;
+	struct rtnl_handle *rth = xb->rth;
+	struct xfrm_usersa_info *xsinfo = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	struct nlmsghdr *new_n;
+	struct xfrm_usersa_id *xsid;
+	struct rtattr *tb[XFRMA_MAX+1];
+
+	if (n->nlmsg_type != XFRM_MSG_NEWSA) {
+		fprintf(stderr, "Not a state: %08x %08x %08x\n",
+			n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+		return 0;
+	}
+
+	len -= NLMSG_LENGTH(sizeof(*xsinfo));
+	if (len < 0) {
+		fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+		return -1;
+	}
+
+	if (!xfrm_state_filter_match(xsinfo))
+		return 0;
+
+	if (xb->offset > xb->size) {
+		fprintf(stderr, "State buffer overflow\n");
+		return -1;
+	}
+
+	new_n = (struct nlmsghdr *)(xb->buf + xb->offset);
+	new_n->nlmsg_len = NLMSG_LENGTH(sizeof(*xsid));
+	new_n->nlmsg_flags = NLM_F_REQUEST;
+	new_n->nlmsg_type = XFRM_MSG_DELSA;
+	new_n->nlmsg_seq = ++rth->seq;
+
+	xsid = NLMSG_DATA(new_n);
+	xsid->family = xsinfo->family;
+	memcpy(&xsid->daddr, &xsinfo->id.daddr, sizeof(xsid->daddr));
+	xsid->spi = xsinfo->id.spi;
+	xsid->proto = xsinfo->id.proto;
+
+	addattr_l(new_n, xb->size, XFRMA_SRCADDR, &xsinfo->saddr,
+		  sizeof(xsid->daddr));
+
+	parse_rtattr(tb, XFRMA_MAX, XFRMS_RTA(xsinfo), len);
+
+	if (tb[XFRMA_MARK]) {
+		int r = addattr_l(new_n, xb->size, XFRMA_MARK,
+				(void *)RTA_DATA(tb[XFRMA_MARK]), tb[XFRMA_MARK]->rta_len);
+		if (r < 0) {
+			fprintf(stderr, "%s: XFRMA_MARK failed\n", __func__);
+			exit(1);
+		}
+	}
+
+	xb->offset += new_n->nlmsg_len;
+	xb->nlmsg_count++;
+
+	return 0;
+}
+
+static int xfrm_state_list_or_deleteall(int argc, char **argv, int deleteall)
+{
+	char *idp = NULL;
+	struct rtnl_handle rth;
+	bool nokeys = false;
+
+	if (argc > 0 || preferred_family != AF_UNSPEC)
+		filter.use = 1;
+	filter.xsinfo.family = preferred_family;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "nokeys") == 0) {
+			nokeys = true;
+		} else if (strcmp(*argv, "mode") == 0) {
+			NEXT_ARG();
+			xfrm_mode_parse(&filter.xsinfo.mode, &argc, &argv);
+
+			filter.mode_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "reqid") == 0) {
+			NEXT_ARG();
+			xfrm_reqid_parse(&filter.xsinfo.reqid, &argc, &argv);
+
+			filter.reqid_mask = XFRM_FILTER_MASK_FULL;
+
+		} else if (strcmp(*argv, "flag") == 0) {
+			NEXT_ARG();
+			xfrm_state_flag_parse(&filter.xsinfo.flags, &argc, &argv);
+
+			filter.state_flags_mask = XFRM_FILTER_MASK_FULL;
+
+		} else {
+			if (idp)
+				invarg("unknown", *argv);
+			idp = *argv;
+
+			/* ID */
+			xfrm_id_parse(&filter.xsinfo.saddr, &filter.xsinfo.id,
+				      &filter.xsinfo.family, 1, &argc, &argv);
+			if (preferred_family == AF_UNSPEC)
+				preferred_family = filter.xsinfo.family;
+		}
+		argc--; argv++;
+	}
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (deleteall) {
+		struct xfrm_buffer xb;
+		char buf[NLMSG_DELETEALL_BUF_SIZE];
+		int i;
+
+		xb.buf = buf;
+		xb.size = sizeof(buf);
+		xb.rth = &rth;
+
+		for (i = 0; ; i++) {
+			struct {
+				struct nlmsghdr n;
+				char buf[NLMSG_BUF_SIZE];
+			} req = {
+				.n.nlmsg_len = NLMSG_HDRLEN,
+				.n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+				.n.nlmsg_type = XFRM_MSG_GETSA,
+				.n.nlmsg_seq = rth.dump = ++rth.seq,
+			};
+
+			xb.offset = 0;
+			xb.nlmsg_count = 0;
+
+			if (show_stats > 1)
+				fprintf(stderr, "Delete-all round = %d\n", i);
+
+			if (rtnl_send(&rth, (void *)&req, req.n.nlmsg_len) < 0) {
+				perror("Cannot send dump request");
+				exit(1);
+			}
+
+			if (rtnl_dump_filter(&rth, xfrm_state_keep, &xb) < 0) {
+				fprintf(stderr, "Delete-all terminated\n");
+				exit(1);
+			}
+			if (xb.nlmsg_count == 0) {
+				if (show_stats > 1)
+					fprintf(stderr, "Delete-all completed\n");
+				break;
+			}
+
+			if (rtnl_send_check(&rth, xb.buf, xb.offset) < 0) {
+				perror("Failed to send delete-all request\n");
+				exit(1);
+			}
+			if (show_stats > 1)
+				fprintf(stderr, "Delete-all nlmsg count = %d\n", xb.nlmsg_count);
+
+			xb.offset = 0;
+			xb.nlmsg_count = 0;
+		}
+
+	} else {
+		struct xfrm_address_filter addrfilter = {
+			.saddr = filter.xsinfo.saddr,
+			.daddr = filter.xsinfo.id.daddr,
+			.family = filter.xsinfo.family,
+			.splen = filter.id_src_mask,
+			.dplen = filter.id_dst_mask,
+		};
+		struct {
+			struct nlmsghdr n;
+			char buf[NLMSG_BUF_SIZE];
+		} req = {
+			.n.nlmsg_len = NLMSG_HDRLEN,
+			.n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+			.n.nlmsg_type = XFRM_MSG_GETSA,
+			.n.nlmsg_seq = rth.dump = ++rth.seq,
+		};
+
+		if (filter.xsinfo.id.proto)
+			addattr8(&req.n, sizeof(req), XFRMA_PROTO,
+				 filter.xsinfo.id.proto);
+		addattr_l(&req.n, sizeof(req), XFRMA_ADDRESS_FILTER,
+			  &addrfilter, sizeof(addrfilter));
+
+		if (rtnl_send(&rth, (void *)&req, req.n.nlmsg_len) < 0) {
+			perror("Cannot send dump request");
+			exit(1);
+		}
+
+		rtnl_filter_t filter = nokeys ?
+				xfrm_state_print_nokeys : xfrm_state_print;
+		if (rtnl_dump_filter(&rth, filter, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+	}
+
+	rtnl_close(&rth);
+
+	exit(0);
+}
+
+static int print_sadinfo(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE *)arg;
+	__u32 *f = NLMSG_DATA(n);
+	struct rtattr *tb[XFRMA_SAD_MAX+1];
+	struct rtattr *rta;
+	int len = n->nlmsg_len;
+
+	len -= NLMSG_LENGTH(sizeof(__u32));
+	if (len < 0) {
+		fprintf(stderr, "SADinfo: Wrong len %d\n", len);
+		return -1;
+	}
+
+	rta = XFRMSAPD_RTA(f);
+	parse_rtattr(tb, XFRMA_SAD_MAX, rta, len);
+
+	if (tb[XFRMA_SAD_CNT]) {
+		__u32 cnt;
+
+		fprintf(fp, "\t SAD");
+		cnt = rta_getattr_u32(tb[XFRMA_SAD_CNT]);
+		fprintf(fp, " count %u", cnt);
+	} else {
+		fprintf(fp, "BAD SAD info returned\n");
+		return -1;
+	}
+
+	if (show_stats) {
+		if (tb[XFRMA_SAD_HINFO]) {
+			struct xfrmu_sadhinfo *si;
+
+			if (RTA_PAYLOAD(tb[XFRMA_SAD_HINFO]) < sizeof(*si)) {
+				fprintf(fp, "BAD SAD length returned\n");
+				return -1;
+			}
+
+			si = RTA_DATA(tb[XFRMA_SAD_HINFO]);
+			fprintf(fp, " (buckets ");
+			fprintf(fp, "count %d", si->sadhcnt);
+			fprintf(fp, " Max %d", si->sadhmcnt);
+			fprintf(fp, ")");
+		}
+	}
+	fprintf(fp, "\n");
+
+	return 0;
+}
+
+static int xfrm_sad_getinfo(int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	struct {
+		struct nlmsghdr			n;
+		__u32				flags;
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.flags)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = XFRM_MSG_GETSADINFO,
+		.flags = 0XFFFFFFFF,
+	};
+	struct nlmsghdr *answer;
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (rtnl_talk(&rth, &req.n, &answer) < 0)
+		exit(2);
+
+	print_sadinfo(answer, (void *)stdout);
+
+	free(answer);
+	rtnl_close(&rth);
+
+	return 0;
+}
+
+static int xfrm_state_flush(int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	struct {
+		struct nlmsghdr			n;
+		struct xfrm_usersa_flush	xsf;
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xsf)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = XFRM_MSG_FLUSHSA,
+	};
+	char *protop = NULL;
+
+	while (argc > 0) {
+		if (strcmp(*argv, "proto") == 0) {
+			int ret;
+
+			if (protop)
+				duparg("proto", *argv);
+			protop = *argv;
+
+			NEXT_ARG();
+
+			ret = xfrm_xfrmproto_getbyname(*argv);
+			if (ret < 0)
+				invarg("XFRM-PROTO value is invalid", *argv);
+
+			req.xsf.proto = (__u8)ret;
+		} else
+			invarg("unknown", *argv);
+
+		argc--; argv++;
+	}
+
+	if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0)
+		exit(1);
+
+	if (show_stats > 1)
+		fprintf(stderr, "Flush state with XFRM-PROTO value \"%s\"\n",
+			strxf_xfrmproto(req.xsf.proto));
+
+	if (rtnl_talk(&rth, &req.n, NULL) < 0)
+		exit(2);
+
+	rtnl_close(&rth);
+
+	return 0;
+}
+
+int do_xfrm_state(int argc, char **argv)
+{
+	if (argc < 1)
+		return xfrm_state_list_or_deleteall(0, NULL, 0);
+
+	if (matches(*argv, "add") == 0)
+		return xfrm_state_modify(XFRM_MSG_NEWSA, 0,
+					 argc-1, argv+1);
+	if (matches(*argv, "update") == 0)
+		return xfrm_state_modify(XFRM_MSG_UPDSA, 0,
+					 argc-1, argv+1);
+	if (matches(*argv, "allocspi") == 0)
+		return xfrm_state_allocspi(argc-1, argv+1);
+	if (matches(*argv, "delete") == 0)
+		return xfrm_state_get_or_delete(argc-1, argv+1, 1);
+	if (matches(*argv, "deleteall") == 0 || matches(*argv, "delall") == 0)
+		return xfrm_state_list_or_deleteall(argc-1, argv+1, 1);
+	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+	    || matches(*argv, "lst") == 0)
+		return xfrm_state_list_or_deleteall(argc-1, argv+1, 0);
+	if (matches(*argv, "get") == 0)
+		return xfrm_state_get_or_delete(argc-1, argv+1, 0);
+	if (matches(*argv, "flush") == 0)
+		return xfrm_state_flush(argc-1, argv+1);
+	if (matches(*argv, "count") == 0) {
+		return xfrm_sad_getinfo(argc, argv);
+	}
+	if (matches(*argv, "help") == 0)
+		usage();
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip xfrm state help\".\n", *argv);
+	exit(-1);
+}
diff --git a/lib/Android.mk b/lib/Android.mk
new file mode 100644
index 0000000..b6bcb6f
--- /dev/null
+++ b/lib/Android.mk
@@ -0,0 +1,77 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := utils.c rt_names.c ll_map.c ll_types.c ll_proto.c ll_addr.c i\
+                   inet_proto.c namespace.c json_writer.c json_print.c \
+                   names.c color.c bpf.c exec.c fs.c mpls_ntop.c mpls_pton.c
+LOCAL_MODULE := libiprouteutil
+LOCAL_MODULE_TAGS := eng
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/uapi
+LOCAL_CFLAGS := -O2 -g -W -Wall \
+	-DHAVE_UNISTD_H \
+	-DHAVE_ERRNO_H \
+	-DHAVE_NETINET_IN_H \
+	-DHAVE_SYS_IOCTL_H \
+	-DHAVE_SYS_MMAN_H \
+	-DHAVE_SYS_MOUNT_H \
+	-DHAVE_SYS_PRCTL_H \
+	-DHAVE_SYS_RESOURCE_H \
+	-DHAVE_SYS_SELECT_H \
+	-DHAVE_SYS_STAT_H \
+	-DHAVE_SYS_TYPES_H \
+	-DHAVE_STDLIB_H \
+	-DHAVE_STRDUP \
+	-DHAVE_MMAP \
+	-DHAVE_UTIME_H \
+	-DHAVE_GETPAGESIZE \
+	-DHAVE_LSEEK64 \
+	-DHAVE_LSEEK64_PROTOTYPE \
+	-DHAVE_EXT2_IOCTLS \
+	-DHAVE_LINUX_FD_H \
+	-DHAVE_TYPE_SSIZE_T \
+	-DHAVE_SETNS \
+	-DNEED_STRLCPY \
+	-Wno-pointer-arith \
+	-Wno-sign-compare \
+	-Wno-unused-parameter \
+	-Werror
+
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := libgenl.c libnetlink.c
+LOCAL_MODULE := libnetlink
+LOCAL_MODULE_TAGS := eng
+LOCAL_C_INCLUDES := \
+        $(LOCAL_PATH)/../include \
+        $(LOCAL_PATH)/../include/uapi
+
+LOCAL_CFLAGS := -O2 -g -W -Wall \
+	-DHAVE_UNISTD_H \
+	-DHAVE_ERRNO_H \
+	-DHAVE_NETINET_IN_H \
+	-DHAVE_SYS_IOCTL_H \
+	-DHAVE_SYS_MMAN_H \
+	-DHAVE_SYS_MOUNT_H \
+	-DHAVE_SYS_PRCTL_H \
+	-DHAVE_SYS_RESOURCE_H \
+	-DHAVE_SYS_SELECT_H \
+	-DHAVE_SYS_STAT_H \
+	-DHAVE_SYS_TYPES_H \
+	-DHAVE_STDLIB_H \
+	-DHAVE_STRDUP \
+	-DHAVE_MMAP \
+	-DHAVE_UTIME_H \
+	-DHAVE_GETPAGESIZE \
+	-DHAVE_LSEEK64 \
+	-DHAVE_LSEEK64_PROTOTYPE \
+	-DHAVE_EXT2_IOCTLS \
+	-DHAVE_LINUX_FD_H \
+	-DHAVE_TYPE_SSIZE_T \
+	-Wno-pointer-arith \
+	-Wno-sign-compare \
+	-Wno-unused-parameter \
+	-Werror \
+	-Wno-\#warnings
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/lib/MODULE_LICENSE_GPL b/lib/MODULE_LICENSE_GPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/MODULE_LICENSE_GPL
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644
index 0000000..bab8cbf
--- /dev/null
+++ b/lib/Makefile
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../config.mk
+
+CFLAGS += -fPIC
+
+UTILOBJ = utils.o rt_names.o ll_map.o ll_types.o ll_proto.o ll_addr.o \
+	inet_proto.o namespace.o json_writer.o json_print.o \
+	names.o color.o bpf.o exec.o fs.o
+
+NLOBJ=libgenl.o libnetlink.o
+
+all: libnetlink.a libutil.a
+
+libnetlink.a: $(NLOBJ)
+	$(QUIET_AR)$(AR) rcs $@ $^
+
+libutil.a: $(UTILOBJ) $(ADDLIB)
+	$(QUIET_AR)$(AR) rcs $@ $^
+
+install:
+
+clean:
+	rm -f $(NLOBJ) $(UTILOBJ) $(ADDLIB) libnetlink.a libutil.a
diff --git a/lib/NOTICE b/lib/NOTICE
new file mode 100644
index 0000000..3912109
--- /dev/null
+++ b/lib/NOTICE
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, 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 Library 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 St, 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 Library General
+Public License instead of this License.
diff --git a/lib/bpf.c b/lib/bpf.c
new file mode 100644
index 0000000..10cf9bf
--- /dev/null
+++ b/lib/bpf.c
@@ -0,0 +1,3240 @@
+/*
+ * bpf.c	BPF common code
+ *
+ *		This program is free software; you can distribute 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.
+ *
+ * Authors:	Daniel Borkmann <daniel@iogearbox.net>
+ *		Jiri Pirko <jiri@resnulli.us>
+ *		Alexei Starovoitov <ast@kernel.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <assert.h>
+
+#ifdef HAVE_ELF
+#include <libelf.h>
+#include <gelf.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/vfs.h>
+#include <sys/mount.h>
+#include <sys/syscall.h>
+#include <sys/sendfile.h>
+#include <sys/resource.h>
+
+#include <arpa/inet.h>
+
+#include "utils.h"
+#include "json_print.h"
+
+#include "bpf_util.h"
+#include "bpf_elf.h"
+#include "bpf_scm.h"
+
+struct bpf_prog_meta {
+	const char *type;
+	const char *subdir;
+	const char *section;
+	bool may_uds_export;
+};
+
+static const enum bpf_prog_type __bpf_types[] = {
+	BPF_PROG_TYPE_SCHED_CLS,
+	BPF_PROG_TYPE_SCHED_ACT,
+	BPF_PROG_TYPE_XDP,
+	BPF_PROG_TYPE_LWT_IN,
+	BPF_PROG_TYPE_LWT_OUT,
+	BPF_PROG_TYPE_LWT_XMIT,
+};
+
+static const struct bpf_prog_meta __bpf_prog_meta[] = {
+	[BPF_PROG_TYPE_SCHED_CLS] = {
+		.type		= "cls",
+		.subdir		= "tc",
+		.section	= ELF_SECTION_CLASSIFIER,
+		.may_uds_export	= true,
+	},
+	[BPF_PROG_TYPE_SCHED_ACT] = {
+		.type		= "act",
+		.subdir		= "tc",
+		.section	= ELF_SECTION_ACTION,
+		.may_uds_export	= true,
+	},
+	[BPF_PROG_TYPE_XDP] = {
+		.type		= "xdp",
+		.subdir		= "xdp",
+		.section	= ELF_SECTION_PROG,
+	},
+	[BPF_PROG_TYPE_LWT_IN] = {
+		.type		= "lwt_in",
+		.subdir		= "ip",
+		.section	= ELF_SECTION_PROG,
+	},
+	[BPF_PROG_TYPE_LWT_OUT] = {
+		.type		= "lwt_out",
+		.subdir		= "ip",
+		.section	= ELF_SECTION_PROG,
+	},
+	[BPF_PROG_TYPE_LWT_XMIT] = {
+		.type		= "lwt_xmit",
+		.subdir		= "ip",
+		.section	= ELF_SECTION_PROG,
+	},
+	[BPF_PROG_TYPE_LWT_SEG6LOCAL] = {
+		.type		= "lwt_seg6local",
+		.subdir		= "ip",
+		.section	= ELF_SECTION_PROG,
+	},
+};
+
+static const char *bpf_prog_to_subdir(enum bpf_prog_type type)
+{
+	assert(type < ARRAY_SIZE(__bpf_prog_meta) &&
+	       __bpf_prog_meta[type].subdir);
+	return __bpf_prog_meta[type].subdir;
+}
+
+const char *bpf_prog_to_default_section(enum bpf_prog_type type)
+{
+	assert(type < ARRAY_SIZE(__bpf_prog_meta) &&
+	       __bpf_prog_meta[type].section);
+	return __bpf_prog_meta[type].section;
+}
+
+#ifdef HAVE_ELF
+static int bpf_obj_open(const char *path, enum bpf_prog_type type,
+			const char *sec, __u32 ifindex, bool verbose);
+#else
+static int bpf_obj_open(const char *path, enum bpf_prog_type type,
+			const char *sec, __u32 ifindex, bool verbose)
+{
+	fprintf(stderr, "No ELF library support compiled in.\n");
+	errno = ENOSYS;
+	return -1;
+}
+#endif
+
+static inline __u64 bpf_ptr_to_u64(const void *ptr)
+{
+	return (__u64)(unsigned long)ptr;
+}
+
+static int bpf(int cmd, union bpf_attr *attr, unsigned int size)
+{
+#ifdef __NR_bpf
+	return syscall(__NR_bpf, cmd, attr, size);
+#else
+	fprintf(stderr, "No bpf syscall, kernel headers too old?\n");
+	errno = ENOSYS;
+	return -1;
+#endif
+}
+
+static int bpf_map_update(int fd, const void *key, const void *value,
+			  uint64_t flags)
+{
+	union bpf_attr attr = {};
+
+	attr.map_fd = fd;
+	attr.key = bpf_ptr_to_u64(key);
+	attr.value = bpf_ptr_to_u64(value);
+	attr.flags = flags;
+
+	return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
+}
+
+static int bpf_prog_fd_by_id(uint32_t id)
+{
+	union bpf_attr attr = {};
+
+	attr.prog_id = id;
+
+	return bpf(BPF_PROG_GET_FD_BY_ID, &attr, sizeof(attr));
+}
+
+static int bpf_prog_info_by_fd(int fd, struct bpf_prog_info *info,
+			       uint32_t *info_len)
+{
+	union bpf_attr attr = {};
+	int ret;
+
+	attr.info.bpf_fd = fd;
+	attr.info.info = bpf_ptr_to_u64(info);
+	attr.info.info_len = *info_len;
+
+	*info_len = 0;
+	ret = bpf(BPF_OBJ_GET_INFO_BY_FD, &attr, sizeof(attr));
+	if (!ret)
+		*info_len = attr.info.info_len;
+
+	return ret;
+}
+
+int bpf_dump_prog_info(FILE *f, uint32_t id)
+{
+	struct bpf_prog_info info = {};
+	uint32_t len = sizeof(info);
+	int fd, ret, dump_ok = 0;
+	SPRINT_BUF(tmp);
+
+	open_json_object("prog");
+	print_uint(PRINT_ANY, "id", "id %u ", id);
+
+	fd = bpf_prog_fd_by_id(id);
+	if (fd < 0)
+		goto out;
+
+	ret = bpf_prog_info_by_fd(fd, &info, &len);
+	if (!ret && len) {
+		int jited = !!info.jited_prog_len;
+
+		print_string(PRINT_ANY, "tag", "tag %s ",
+			     hexstring_n2a(info.tag, sizeof(info.tag),
+					   tmp, sizeof(tmp)));
+		print_uint(PRINT_JSON, "jited", NULL, jited);
+		if (jited && !is_json_context())
+			fprintf(f, "jited ");
+		dump_ok = 1;
+	}
+
+	close(fd);
+out:
+	close_json_object();
+	return dump_ok;
+}
+
+static int bpf_parse_string(char *arg, bool from_file, __u16 *bpf_len,
+			    char **bpf_string, bool *need_release,
+			    const char separator)
+{
+	char sp;
+
+	if (from_file) {
+		size_t tmp_len, op_len = sizeof("65535 255 255 4294967295,");
+		char *tmp_string, *pos, c_prev = ' ';
+		FILE *fp;
+		int c;
+
+		tmp_len = sizeof("4096,") + BPF_MAXINSNS * op_len;
+		tmp_string = pos = calloc(1, tmp_len);
+		if (tmp_string == NULL)
+			return -ENOMEM;
+
+		fp = fopen(arg, "r");
+		if (fp == NULL) {
+			perror("Cannot fopen");
+			free(tmp_string);
+			return -ENOENT;
+		}
+
+		while ((c = fgetc(fp)) != EOF) {
+			switch (c) {
+			case '\n':
+				if (c_prev != ',')
+					*(pos++) = ',';
+				c_prev = ',';
+				break;
+			case ' ':
+			case '\t':
+				if (c_prev != ' ')
+					*(pos++) = c;
+				c_prev = ' ';
+				break;
+			default:
+				*(pos++) = c;
+				c_prev = c;
+			}
+			if (pos - tmp_string == tmp_len)
+				break;
+		}
+
+		if (!feof(fp)) {
+			free(tmp_string);
+			fclose(fp);
+			return -E2BIG;
+		}
+
+		fclose(fp);
+		*pos = 0;
+
+		*need_release = true;
+		*bpf_string = tmp_string;
+	} else {
+		*need_release = false;
+		*bpf_string = arg;
+	}
+
+	if (sscanf(*bpf_string, "%hu%c", bpf_len, &sp) != 2 ||
+	    sp != separator) {
+		if (*need_release)
+			free(*bpf_string);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int bpf_ops_parse(int argc, char **argv, struct sock_filter *bpf_ops,
+			 bool from_file)
+{
+	char *bpf_string, *token, separator = ',';
+	int ret = 0, i = 0;
+	bool need_release;
+	__u16 bpf_len = 0;
+
+	if (argc < 1)
+		return -EINVAL;
+	if (bpf_parse_string(argv[0], from_file, &bpf_len, &bpf_string,
+			     &need_release, separator))
+		return -EINVAL;
+	if (bpf_len == 0 || bpf_len > BPF_MAXINSNS) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	token = bpf_string;
+	while ((token = strchr(token, separator)) && (++token)[0]) {
+		if (i >= bpf_len) {
+			fprintf(stderr, "Real program length exceeds encoded length parameter!\n");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (sscanf(token, "%hu %hhu %hhu %u,",
+			   &bpf_ops[i].code, &bpf_ops[i].jt,
+			   &bpf_ops[i].jf, &bpf_ops[i].k) != 4) {
+			fprintf(stderr, "Error at instruction %d!\n", i);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		i++;
+	}
+
+	if (i != bpf_len) {
+		fprintf(stderr, "Parsed program length is less than encoded length parameter!\n");
+		ret = -EINVAL;
+		goto out;
+	}
+	ret = bpf_len;
+out:
+	if (need_release)
+		free(bpf_string);
+
+	return ret;
+}
+
+void bpf_print_ops(struct rtattr *bpf_ops, __u16 len)
+{
+	struct sock_filter *ops = RTA_DATA(bpf_ops);
+	int i;
+
+	if (len == 0)
+		return;
+
+	open_json_object("bytecode");
+	print_uint(PRINT_ANY, "length", "bytecode \'%u,", len);
+	open_json_array(PRINT_JSON, "insns");
+
+	for (i = 0; i < len; i++) {
+		open_json_object(NULL);
+		print_hu(PRINT_ANY, "code", "%hu ", ops[i].code);
+		print_hhu(PRINT_ANY, "jt", "%hhu ", ops[i].jt);
+		print_hhu(PRINT_ANY, "jf", "%hhu ", ops[i].jf);
+		if (i == len - 1)
+			print_uint(PRINT_ANY, "k", "%u\'", ops[i].k);
+		else
+			print_uint(PRINT_ANY, "k", "%u,", ops[i].k);
+		close_json_object();
+	}
+
+	close_json_array(PRINT_JSON, NULL);
+	close_json_object();
+}
+
+static void bpf_map_pin_report(const struct bpf_elf_map *pin,
+			       const struct bpf_elf_map *obj)
+{
+	fprintf(stderr, "Map specification differs from pinned file!\n");
+
+	if (obj->type != pin->type)
+		fprintf(stderr, " - Type:         %u (obj) != %u (pin)\n",
+			obj->type, pin->type);
+	if (obj->size_key != pin->size_key)
+		fprintf(stderr, " - Size key:     %u (obj) != %u (pin)\n",
+			obj->size_key, pin->size_key);
+	if (obj->size_value != pin->size_value)
+		fprintf(stderr, " - Size value:   %u (obj) != %u (pin)\n",
+			obj->size_value, pin->size_value);
+	if (obj->max_elem != pin->max_elem)
+		fprintf(stderr, " - Max elems:    %u (obj) != %u (pin)\n",
+			obj->max_elem, pin->max_elem);
+	if (obj->flags != pin->flags)
+		fprintf(stderr, " - Flags:        %#x (obj) != %#x (pin)\n",
+			obj->flags, pin->flags);
+
+	fprintf(stderr, "\n");
+}
+
+struct bpf_prog_data {
+	unsigned int type;
+	unsigned int jited;
+};
+
+struct bpf_map_ext {
+	struct bpf_prog_data owner;
+	unsigned int btf_id_key;
+	unsigned int btf_id_val;
+};
+
+static int bpf_derive_elf_map_from_fdinfo(int fd, struct bpf_elf_map *map,
+					  struct bpf_map_ext *ext)
+{
+	unsigned int val, owner_type = 0, owner_jited = 0;
+	char *file = NULL;
+	char buff[4096];
+	FILE *fp;
+	int ret;
+
+	ret = asprintf(&file, "/proc/%d/fdinfo/%d", getpid(), fd);
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		free(file);
+		return ret;
+	}
+	memset(map, 0, sizeof(*map));
+
+	fp = fopen(file, "r");
+	free(file);
+	if (!fp) {
+		fprintf(stderr, "No procfs support?!\n");
+		return -EIO;
+	}
+
+	while (fgets(buff, sizeof(buff), fp)) {
+		if (sscanf(buff, "map_type:\t%u", &val) == 1)
+			map->type = val;
+		else if (sscanf(buff, "key_size:\t%u", &val) == 1)
+			map->size_key = val;
+		else if (sscanf(buff, "value_size:\t%u", &val) == 1)
+			map->size_value = val;
+		else if (sscanf(buff, "max_entries:\t%u", &val) == 1)
+			map->max_elem = val;
+		else if (sscanf(buff, "map_flags:\t%i", &val) == 1)
+			map->flags = val;
+		else if (sscanf(buff, "owner_prog_type:\t%i", &val) == 1)
+			owner_type = val;
+		else if (sscanf(buff, "owner_jited:\t%i", &val) == 1)
+			owner_jited = val;
+	}
+
+	fclose(fp);
+	if (ext) {
+		memset(ext, 0, sizeof(*ext));
+		ext->owner.type  = owner_type;
+		ext->owner.jited = owner_jited;
+	}
+
+	return 0;
+}
+
+static int bpf_map_selfcheck_pinned(int fd, const struct bpf_elf_map *map,
+				    struct bpf_map_ext *ext, int length,
+				    enum bpf_prog_type type)
+{
+	struct bpf_elf_map tmp, zero = {};
+	int ret;
+
+	ret = bpf_derive_elf_map_from_fdinfo(fd, &tmp, ext);
+	if (ret < 0)
+		return ret;
+
+	/* The decision to reject this is on kernel side eventually, but
+	 * at least give the user a chance to know what's wrong.
+	 */
+	if (ext->owner.type && ext->owner.type != type)
+		fprintf(stderr, "Program array map owner types differ: %u (obj) != %u (pin)\n",
+			type, ext->owner.type);
+
+	if (!memcmp(&tmp, map, length)) {
+		return 0;
+	} else {
+		/* If kernel doesn't have eBPF-related fdinfo, we cannot do much,
+		 * so just accept it. We know we do have an eBPF fd and in this
+		 * case, everything is 0. It is guaranteed that no such map exists
+		 * since map type of 0 is unloadable BPF_MAP_TYPE_UNSPEC.
+		 */
+		if (!memcmp(&tmp, &zero, length))
+			return 0;
+
+		bpf_map_pin_report(&tmp, map);
+		return -EINVAL;
+	}
+}
+
+static int bpf_mnt_fs(const char *target)
+{
+	bool bind_done = false;
+
+	while (mount("", target, "none", MS_PRIVATE | MS_REC, NULL)) {
+		if (errno != EINVAL || bind_done) {
+			fprintf(stderr, "mount --make-private %s failed: %s\n",
+				target,	strerror(errno));
+			return -1;
+		}
+
+		if (mount(target, target, "none", MS_BIND, NULL)) {
+			fprintf(stderr, "mount --bind %s %s failed: %s\n",
+				target,	target, strerror(errno));
+			return -1;
+		}
+
+		bind_done = true;
+	}
+
+	if (mount("bpf", target, "bpf", 0, "mode=0700")) {
+		fprintf(stderr, "mount -t bpf bpf %s failed: %s\n",
+			target,	strerror(errno));
+		return -1;
+	}
+
+	return 0;
+}
+
+static int bpf_mnt_check_target(const char *target)
+{
+	struct stat sb = {};
+	int ret;
+
+	ret = stat(target, &sb);
+	if (ret) {
+		ret = mkdir(target, S_IRWXU);
+		if (ret) {
+			fprintf(stderr, "mkdir %s failed: %s\n", target,
+				strerror(errno));
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int bpf_valid_mntpt(const char *mnt, unsigned long magic)
+{
+	struct statfs st_fs;
+
+	if (statfs(mnt, &st_fs) < 0)
+		return -ENOENT;
+	if ((unsigned long)st_fs.f_type != magic)
+		return -ENOENT;
+
+	return 0;
+}
+
+static const char *bpf_find_mntpt_single(unsigned long magic, char *mnt,
+					 int len, const char *mntpt)
+{
+	int ret;
+
+	ret = bpf_valid_mntpt(mntpt, magic);
+	if (!ret) {
+		strlcpy(mnt, mntpt, len);
+		return mnt;
+	}
+
+	return NULL;
+}
+
+static const char *bpf_find_mntpt(const char *fstype, unsigned long magic,
+				  char *mnt, int len,
+				  const char * const *known_mnts)
+{
+	const char * const *ptr;
+	char type[100];
+	FILE *fp;
+
+	if (known_mnts) {
+		ptr = known_mnts;
+		while (*ptr) {
+			if (bpf_find_mntpt_single(magic, mnt, len, *ptr))
+				return mnt;
+			ptr++;
+		}
+	}
+
+	if (len != PATH_MAX)
+		return NULL;
+
+	fp = fopen("/proc/mounts", "r");
+	if (fp == NULL)
+		return NULL;
+
+	while (fscanf(fp, "%*s %" textify(PATH_MAX) "s %99s %*s %*d %*d\n",
+		      mnt, type) == 2) {
+		if (strcmp(type, fstype) == 0)
+			break;
+	}
+
+	fclose(fp);
+	if (strcmp(type, fstype) != 0)
+		return NULL;
+
+	return mnt;
+}
+
+int bpf_trace_pipe(void)
+{
+	char tracefs_mnt[PATH_MAX] = TRACE_DIR_MNT;
+	static const char * const tracefs_known_mnts[] = {
+		TRACE_DIR_MNT,
+		"/sys/kernel/debug/tracing",
+		"/tracing",
+		"/trace",
+		0,
+	};
+	int fd_in, fd_out = STDERR_FILENO;
+	char *tpipe = NULL;
+	const char *mnt;
+	int ret;
+
+	mnt = bpf_find_mntpt("tracefs", TRACEFS_MAGIC, tracefs_mnt,
+			     sizeof(tracefs_mnt), tracefs_known_mnts);
+	if (!mnt) {
+		fprintf(stderr, "tracefs not mounted?\n");
+		return -1;
+	}
+
+	ret = asprintf(&tpipe, "%s/trace_pipe", mnt);
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		free(tpipe);
+		return ret;
+	}
+
+	fd_in = open(tpipe, O_RDONLY);
+	free(tpipe);
+	if (fd_in < 0)
+		return -1;
+
+	fprintf(stderr, "Running! Hang up with ^C!\n\n");
+	while (1) {
+		static char buff[4096];
+		ssize_t ret;
+
+		ret = read(fd_in, buff, sizeof(buff));
+		if (ret > 0 && write(fd_out, buff, ret) == ret)
+			continue;
+		break;
+	}
+
+	close(fd_in);
+	return -1;
+}
+
+static int bpf_gen_global(const char *bpf_sub_dir)
+{
+	char *bpf_glo_dir = NULL;
+	int ret;
+
+	ret = asprintf(&bpf_glo_dir, "%s/%s/", bpf_sub_dir, BPF_DIR_GLOBALS);
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		goto out;
+	}
+
+	ret = mkdir(bpf_glo_dir, S_IRWXU);
+	if (ret && errno != EEXIST) {
+		fprintf(stderr, "mkdir %s failed: %s\n", bpf_glo_dir,
+			strerror(errno));
+		goto out;
+	}
+
+	ret = 0;
+out:
+	free(bpf_glo_dir);
+	return ret;
+}
+
+static int bpf_gen_master(const char *base, const char *name)
+{
+	char *bpf_sub_dir = NULL;
+	int ret;
+
+	ret = asprintf(&bpf_sub_dir, "%s%s/", base, name);
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		goto out;
+	}
+
+	ret = mkdir(bpf_sub_dir, S_IRWXU);
+	if (ret && errno != EEXIST) {
+		fprintf(stderr, "mkdir %s failed: %s\n", bpf_sub_dir,
+			strerror(errno));
+		goto out;
+	}
+
+	ret = bpf_gen_global(bpf_sub_dir);
+out:
+	free(bpf_sub_dir);
+	return ret;
+}
+
+static int bpf_slave_via_bind_mnt(const char *full_name,
+				  const char *full_link)
+{
+	int ret;
+
+	ret = mkdir(full_name, S_IRWXU);
+	if (ret) {
+		assert(errno != EEXIST);
+		fprintf(stderr, "mkdir %s failed: %s\n", full_name,
+			strerror(errno));
+		return ret;
+	}
+
+	ret = mount(full_link, full_name, "none", MS_BIND, NULL);
+	if (ret) {
+		rmdir(full_name);
+		fprintf(stderr, "mount --bind %s %s failed: %s\n",
+			full_link, full_name, strerror(errno));
+	}
+
+	return ret;
+}
+
+static int bpf_gen_slave(const char *base, const char *name,
+			 const char *link)
+{
+	char *bpf_lnk_dir = NULL;
+	char *bpf_sub_dir = NULL;
+	struct stat sb = {};
+	int ret;
+
+	ret = asprintf(&bpf_lnk_dir, "%s%s/", base, link);
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		goto out;
+	}
+
+	ret = asprintf(&bpf_sub_dir, "%s%s",  base, name);
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		goto out;
+	}
+
+	ret = symlink(bpf_lnk_dir, bpf_sub_dir);
+	if (ret) {
+		if (errno != EEXIST) {
+			if (errno != EPERM) {
+				fprintf(stderr, "symlink %s failed: %s\n",
+					bpf_sub_dir, strerror(errno));
+				goto out;
+			}
+
+			ret = bpf_slave_via_bind_mnt(bpf_sub_dir, bpf_lnk_dir);
+			goto out;
+		}
+
+		ret = lstat(bpf_sub_dir, &sb);
+		if (ret) {
+			fprintf(stderr, "lstat %s failed: %s\n",
+				bpf_sub_dir, strerror(errno));
+			goto out;
+		}
+
+		if ((sb.st_mode & S_IFMT) != S_IFLNK) {
+			ret = bpf_gen_global(bpf_sub_dir);
+			goto out;
+		}
+	}
+
+out:
+	free(bpf_lnk_dir);
+	free(bpf_sub_dir);
+	return ret;
+}
+
+static int bpf_gen_hierarchy(const char *base)
+{
+	int ret, i;
+
+	ret = bpf_gen_master(base, bpf_prog_to_subdir(__bpf_types[0]));
+	for (i = 1; i < ARRAY_SIZE(__bpf_types) && !ret; i++)
+		ret = bpf_gen_slave(base,
+				    bpf_prog_to_subdir(__bpf_types[i]),
+				    bpf_prog_to_subdir(__bpf_types[0]));
+	return ret;
+}
+
+static const char *bpf_get_work_dir(enum bpf_prog_type type)
+{
+	static char bpf_tmp[PATH_MAX] = BPF_DIR_MNT;
+	static char *bpf_wrk_dir;
+	static const char *mnt;
+	static bool bpf_mnt_cached;
+	const char *mnt_env = getenv(BPF_ENV_MNT);
+	static const char * const bpf_known_mnts[] = {
+		BPF_DIR_MNT,
+		"/bpf",
+		0,
+	};
+	int ret;
+
+	if (bpf_mnt_cached) {
+		const char *out = mnt;
+
+		if (out && type) {
+			snprintf(bpf_tmp, sizeof(bpf_tmp), "%s%s/",
+				 out, bpf_prog_to_subdir(type));
+			out = bpf_tmp;
+		}
+		return out;
+	}
+
+	if (mnt_env)
+		mnt = bpf_find_mntpt_single(BPF_FS_MAGIC, bpf_tmp,
+					    sizeof(bpf_tmp), mnt_env);
+	else
+		mnt = bpf_find_mntpt("bpf", BPF_FS_MAGIC, bpf_tmp,
+				     sizeof(bpf_tmp), bpf_known_mnts);
+	if (!mnt) {
+		mnt = mnt_env ? : BPF_DIR_MNT;
+		ret = bpf_mnt_check_target(mnt);
+		if (!ret)
+			ret = bpf_mnt_fs(mnt);
+		if (ret) {
+			mnt = NULL;
+			goto out;
+		}
+	}
+
+	ret = asprintf(&bpf_wrk_dir, "%s/", mnt);
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		free(bpf_wrk_dir);
+		goto out;
+	}
+
+	ret = bpf_gen_hierarchy(bpf_wrk_dir);
+	if (ret) {
+		mnt = NULL;
+		goto out;
+	}
+
+	mnt = bpf_wrk_dir;
+out:
+	bpf_mnt_cached = true;
+	return mnt;
+}
+
+static int bpf_obj_get(const char *pathname, enum bpf_prog_type type)
+{
+	union bpf_attr attr = {};
+	char tmp[PATH_MAX];
+
+	if (strlen(pathname) > 2 && pathname[0] == 'm' &&
+	    pathname[1] == ':' && bpf_get_work_dir(type)) {
+		snprintf(tmp, sizeof(tmp), "%s/%s",
+			 bpf_get_work_dir(type), pathname + 2);
+		pathname = tmp;
+	}
+
+	attr.pathname = bpf_ptr_to_u64(pathname);
+
+	return bpf(BPF_OBJ_GET, &attr, sizeof(attr));
+}
+
+static int bpf_obj_pinned(const char *pathname, enum bpf_prog_type type)
+{
+	int prog_fd = bpf_obj_get(pathname, type);
+
+	if (prog_fd < 0)
+		fprintf(stderr, "Couldn\'t retrieve pinned program \'%s\': %s\n",
+			pathname, strerror(errno));
+	return prog_fd;
+}
+
+static int bpf_do_parse(struct bpf_cfg_in *cfg, const bool *opt_tbl)
+{
+	const char *file, *section, *uds_name;
+	bool verbose = false;
+	int i, ret, argc;
+	char **argv;
+
+	argv = cfg->argv;
+	argc = cfg->argc;
+
+	if (opt_tbl[CBPF_BYTECODE] &&
+	    (matches(*argv, "bytecode") == 0 ||
+	     strcmp(*argv, "bc") == 0)) {
+		cfg->mode = CBPF_BYTECODE;
+	} else if (opt_tbl[CBPF_FILE] &&
+		   (matches(*argv, "bytecode-file") == 0 ||
+		    strcmp(*argv, "bcf") == 0)) {
+		cfg->mode = CBPF_FILE;
+	} else if (opt_tbl[EBPF_OBJECT] &&
+		   (matches(*argv, "object-file") == 0 ||
+		    strcmp(*argv, "obj") == 0)) {
+		cfg->mode = EBPF_OBJECT;
+	} else if (opt_tbl[EBPF_PINNED] &&
+		   (matches(*argv, "object-pinned") == 0 ||
+		    matches(*argv, "pinned") == 0 ||
+		    matches(*argv, "fd") == 0)) {
+		cfg->mode = EBPF_PINNED;
+	} else {
+		fprintf(stderr, "What mode is \"%s\"?\n", *argv);
+		return -1;
+	}
+
+	NEXT_ARG();
+	file = section = uds_name = NULL;
+	if (cfg->mode == EBPF_OBJECT || cfg->mode == EBPF_PINNED) {
+		file = *argv;
+		NEXT_ARG_FWD();
+
+		if (cfg->type == BPF_PROG_TYPE_UNSPEC) {
+			if (argc > 0 && matches(*argv, "type") == 0) {
+				NEXT_ARG();
+				for (i = 0; i < ARRAY_SIZE(__bpf_prog_meta);
+				     i++) {
+					if (!__bpf_prog_meta[i].type)
+						continue;
+					if (!matches(*argv,
+						     __bpf_prog_meta[i].type)) {
+						cfg->type = i;
+						break;
+					}
+				}
+
+				if (cfg->type == BPF_PROG_TYPE_UNSPEC) {
+					fprintf(stderr, "What type is \"%s\"?\n",
+						*argv);
+					return -1;
+				}
+				NEXT_ARG_FWD();
+			} else {
+				cfg->type = BPF_PROG_TYPE_SCHED_CLS;
+			}
+		}
+
+		section = bpf_prog_to_default_section(cfg->type);
+		if (argc > 0 && matches(*argv, "section") == 0) {
+			NEXT_ARG();
+			section = *argv;
+			NEXT_ARG_FWD();
+		}
+
+		if (__bpf_prog_meta[cfg->type].may_uds_export) {
+			uds_name = getenv(BPF_ENV_UDS);
+			if (argc > 0 && !uds_name &&
+			    matches(*argv, "export") == 0) {
+				NEXT_ARG();
+				uds_name = *argv;
+				NEXT_ARG_FWD();
+			}
+		}
+
+		if (argc > 0 && matches(*argv, "verbose") == 0) {
+			verbose = true;
+			NEXT_ARG_FWD();
+		}
+
+		PREV_ARG();
+	}
+
+	if (cfg->mode == CBPF_BYTECODE || cfg->mode == CBPF_FILE) {
+		ret = bpf_ops_parse(argc, argv, cfg->opcodes,
+				    cfg->mode == CBPF_FILE);
+		cfg->n_opcodes = ret;
+	} else if (cfg->mode == EBPF_OBJECT) {
+		ret = 0; /* program will be loaded by load stage */
+	} else if (cfg->mode == EBPF_PINNED) {
+		ret = bpf_obj_pinned(file, cfg->type);
+		cfg->prog_fd = ret;
+	} else {
+		return -1;
+	}
+
+	cfg->object  = file;
+	cfg->section = section;
+	cfg->uds     = uds_name;
+	cfg->argc    = argc;
+	cfg->argv    = argv;
+	cfg->verbose = verbose;
+
+	return ret;
+}
+
+static int bpf_do_load(struct bpf_cfg_in *cfg)
+{
+	if (cfg->mode == EBPF_OBJECT) {
+		cfg->prog_fd = bpf_obj_open(cfg->object, cfg->type,
+					    cfg->section, cfg->ifindex,
+					    cfg->verbose);
+		return cfg->prog_fd;
+	}
+	return 0;
+}
+
+int bpf_load_common(struct bpf_cfg_in *cfg, const struct bpf_cfg_ops *ops,
+		    void *nl)
+{
+	char annotation[256];
+	int ret;
+
+	ret = bpf_do_load(cfg);
+	if (ret < 0)
+		return ret;
+
+	if (cfg->mode == CBPF_BYTECODE || cfg->mode == CBPF_FILE)
+		ops->cbpf_cb(nl, cfg->opcodes, cfg->n_opcodes);
+	if (cfg->mode == EBPF_OBJECT || cfg->mode == EBPF_PINNED) {
+		snprintf(annotation, sizeof(annotation), "%s:[%s]",
+			 basename(cfg->object), cfg->mode == EBPF_PINNED ?
+			 "*fsobj" : cfg->section);
+		ops->ebpf_cb(nl, cfg->prog_fd, annotation);
+	}
+
+	return 0;
+}
+
+int bpf_parse_common(struct bpf_cfg_in *cfg, const struct bpf_cfg_ops *ops)
+{
+	bool opt_tbl[BPF_MODE_MAX] = {};
+
+	if (ops->cbpf_cb) {
+		opt_tbl[CBPF_BYTECODE] = true;
+		opt_tbl[CBPF_FILE]     = true;
+	}
+
+	if (ops->ebpf_cb) {
+		opt_tbl[EBPF_OBJECT]   = true;
+		opt_tbl[EBPF_PINNED]   = true;
+	}
+
+	return bpf_do_parse(cfg, opt_tbl);
+}
+
+int bpf_parse_and_load_common(struct bpf_cfg_in *cfg,
+			      const struct bpf_cfg_ops *ops, void *nl)
+{
+	int ret;
+
+	ret = bpf_parse_common(cfg, ops);
+	if (ret < 0)
+		return ret;
+
+	return bpf_load_common(cfg, ops, nl);
+}
+
+int bpf_graft_map(const char *map_path, uint32_t *key, int argc, char **argv)
+{
+	const bool opt_tbl[BPF_MODE_MAX] = {
+		[EBPF_OBJECT]	= true,
+		[EBPF_PINNED]	= true,
+	};
+	const struct bpf_elf_map test = {
+		.type		= BPF_MAP_TYPE_PROG_ARRAY,
+		.size_key	= sizeof(int),
+		.size_value	= sizeof(int),
+	};
+	struct bpf_cfg_in cfg = {
+		.type		= BPF_PROG_TYPE_UNSPEC,
+		.argc		= argc,
+		.argv		= argv,
+	};
+	struct bpf_map_ext ext = {};
+	int ret, prog_fd, map_fd;
+	uint32_t map_key;
+
+	ret = bpf_do_parse(&cfg, opt_tbl);
+	if (ret < 0)
+		return ret;
+
+	ret = bpf_do_load(&cfg);
+	if (ret < 0)
+		return ret;
+
+	prog_fd = cfg.prog_fd;
+
+	if (key) {
+		map_key = *key;
+	} else {
+		ret = sscanf(cfg.section, "%*i/%i", &map_key);
+		if (ret != 1) {
+			fprintf(stderr, "Couldn\'t infer map key from section name! Please provide \'key\' argument!\n");
+			ret = -EINVAL;
+			goto out_prog;
+		}
+	}
+
+	map_fd = bpf_obj_get(map_path, cfg.type);
+	if (map_fd < 0) {
+		fprintf(stderr, "Couldn\'t retrieve pinned map \'%s\': %s\n",
+			map_path, strerror(errno));
+		ret = map_fd;
+		goto out_prog;
+	}
+
+	ret = bpf_map_selfcheck_pinned(map_fd, &test, &ext,
+				       offsetof(struct bpf_elf_map, max_elem),
+				       cfg.type);
+	if (ret < 0) {
+		fprintf(stderr, "Map \'%s\' self-check failed!\n", map_path);
+		goto out_map;
+	}
+
+	ret = bpf_map_update(map_fd, &map_key, &prog_fd, BPF_ANY);
+	if (ret < 0)
+		fprintf(stderr, "Map update failed: %s\n", strerror(errno));
+out_map:
+	close(map_fd);
+out_prog:
+	close(prog_fd);
+	return ret;
+}
+
+int bpf_prog_attach_fd(int prog_fd, int target_fd, enum bpf_attach_type type)
+{
+	union bpf_attr attr = {};
+
+	attr.target_fd = target_fd;
+	attr.attach_bpf_fd = prog_fd;
+	attr.attach_type = type;
+
+	return bpf(BPF_PROG_ATTACH, &attr, sizeof(attr));
+}
+
+int bpf_prog_detach_fd(int target_fd, enum bpf_attach_type type)
+{
+	union bpf_attr attr = {};
+
+	attr.target_fd = target_fd;
+	attr.attach_type = type;
+
+	return bpf(BPF_PROG_DETACH, &attr, sizeof(attr));
+}
+
+static int bpf_prog_load_dev(enum bpf_prog_type type,
+			     const struct bpf_insn *insns, size_t size_insns,
+			     const char *license, __u32 ifindex,
+			     char *log, size_t size_log)
+{
+	union bpf_attr attr = {};
+
+	attr.prog_type = type;
+	attr.insns = bpf_ptr_to_u64(insns);
+	attr.insn_cnt = size_insns / sizeof(struct bpf_insn);
+	attr.license = bpf_ptr_to_u64(license);
+	attr.prog_ifindex = ifindex;
+
+	if (size_log > 0) {
+		attr.log_buf = bpf_ptr_to_u64(log);
+		attr.log_size = size_log;
+		attr.log_level = 1;
+	}
+
+	return bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
+}
+
+int bpf_prog_load(enum bpf_prog_type type, const struct bpf_insn *insns,
+		  size_t size_insns, const char *license, char *log,
+		  size_t size_log)
+{
+	return bpf_prog_load_dev(type, insns, size_insns, license, 0,
+				 log, size_log);
+}
+
+#ifdef HAVE_ELF
+struct bpf_elf_prog {
+	enum bpf_prog_type	type;
+	struct bpf_insn		*insns;
+	unsigned int		insns_num;
+	size_t			size;
+	const char		*license;
+};
+
+struct bpf_hash_entry {
+	unsigned int		pinning;
+	const char		*subpath;
+	struct bpf_hash_entry	*next;
+};
+
+struct bpf_config {
+	unsigned int		jit_enabled;
+};
+
+struct bpf_btf {
+	const struct btf_header	*hdr;
+	const void		*raw;
+	const char		*strings;
+	const struct btf_type	**types;
+	int			types_num;
+};
+
+struct bpf_elf_ctx {
+	struct bpf_config	cfg;
+	Elf			*elf_fd;
+	GElf_Ehdr		elf_hdr;
+	Elf_Data		*sym_tab;
+	Elf_Data		*str_tab;
+	Elf_Data		*btf_data;
+	char			obj_uid[64];
+	int			obj_fd;
+	int			btf_fd;
+	int			map_fds[ELF_MAX_MAPS];
+	struct bpf_elf_map	maps[ELF_MAX_MAPS];
+	struct bpf_map_ext	maps_ext[ELF_MAX_MAPS];
+	struct bpf_elf_prog	prog_text;
+	struct bpf_btf		btf;
+	int			sym_num;
+	int			map_num;
+	int			map_len;
+	bool			*sec_done;
+	int			sec_maps;
+	int			sec_text;
+	int			sec_btf;
+	char			license[ELF_MAX_LICENSE_LEN];
+	enum bpf_prog_type	type;
+	__u32			ifindex;
+	bool			verbose;
+	bool			noafalg;
+	struct bpf_elf_st	stat;
+	struct bpf_hash_entry	*ht[256];
+	char			*log;
+	size_t			log_size;
+};
+
+struct bpf_elf_sec_data {
+	GElf_Shdr		sec_hdr;
+	Elf_Data		*sec_data;
+	const char		*sec_name;
+};
+
+struct bpf_map_data {
+	int			*fds;
+	const char		*obj;
+	struct bpf_elf_st	*st;
+	struct bpf_elf_map	*ent;
+};
+
+static bool bpf_log_has_data(struct bpf_elf_ctx *ctx)
+{
+	return ctx->log && ctx->log[0];
+}
+
+static __check_format_string(2, 3) void
+bpf_dump_error(struct bpf_elf_ctx *ctx, const char *format, ...)
+{
+	va_list vl;
+
+	va_start(vl, format);
+	vfprintf(stderr, format, vl);
+	va_end(vl);
+
+	if (bpf_log_has_data(ctx)) {
+		if (ctx->verbose) {
+			fprintf(stderr, "%s\n", ctx->log);
+		} else {
+			unsigned int off = 0, len = strlen(ctx->log);
+
+			if (len > BPF_MAX_LOG) {
+				off = len - BPF_MAX_LOG;
+				fprintf(stderr, "Skipped %u bytes, use \'verb\' option for the full verbose log.\n[...]\n",
+					off);
+			}
+			fprintf(stderr, "%s\n", ctx->log + off);
+		}
+
+		memset(ctx->log, 0, ctx->log_size);
+	}
+}
+
+static int bpf_log_realloc(struct bpf_elf_ctx *ctx)
+{
+	const size_t log_max = UINT_MAX >> 8;
+	size_t log_size = ctx->log_size;
+	char *ptr;
+
+	if (!ctx->log) {
+		log_size = 65536;
+	} else if (log_size < log_max) {
+		log_size <<= 1;
+		if (log_size > log_max)
+			log_size = log_max;
+	} else {
+		return -EINVAL;
+	}
+
+	ptr = realloc(ctx->log, log_size);
+	if (!ptr)
+		return -ENOMEM;
+
+	ptr[0] = 0;
+	ctx->log = ptr;
+	ctx->log_size = log_size;
+
+	return 0;
+}
+
+static int bpf_map_create(enum bpf_map_type type, uint32_t size_key,
+			  uint32_t size_value, uint32_t max_elem,
+			  uint32_t flags, int inner_fd, int btf_fd,
+			  uint32_t ifindex, uint32_t btf_id_key,
+			  uint32_t btf_id_val)
+{
+	union bpf_attr attr = {};
+
+	attr.map_type = type;
+	attr.key_size = size_key;
+	attr.value_size = inner_fd ? sizeof(int) : size_value;
+	attr.max_entries = max_elem;
+	attr.map_flags = flags;
+	attr.inner_map_fd = inner_fd;
+	attr.map_ifindex = ifindex;
+	attr.btf_fd = btf_fd;
+	attr.btf_key_type_id = btf_id_key;
+	attr.btf_value_type_id = btf_id_val;
+
+	return bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
+}
+
+static int bpf_btf_load(void *btf, size_t size_btf,
+			char *log, size_t size_log)
+{
+	union bpf_attr attr = {};
+
+	attr.btf = bpf_ptr_to_u64(btf);
+	attr.btf_size = size_btf;
+
+	if (size_log > 0) {
+		attr.btf_log_buf = bpf_ptr_to_u64(log);
+		attr.btf_log_size = size_log;
+		attr.btf_log_level = 1;
+	}
+
+	return bpf(BPF_BTF_LOAD, &attr, sizeof(attr));
+}
+
+static int bpf_obj_pin(int fd, const char *pathname)
+{
+	union bpf_attr attr = {};
+
+	attr.pathname = bpf_ptr_to_u64(pathname);
+	attr.bpf_fd = fd;
+
+	return bpf(BPF_OBJ_PIN, &attr, sizeof(attr));
+}
+
+static int bpf_obj_hash(const char *object, uint8_t *out, size_t len)
+{
+	struct sockaddr_alg alg = {
+		.salg_family	= AF_ALG,
+		.salg_type	= "hash",
+		.salg_name	= "sha1",
+	};
+	int ret, cfd, ofd, ffd;
+	struct stat stbuff;
+	ssize_t size;
+
+	if (!object || len != 20)
+		return -EINVAL;
+
+	cfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
+	if (cfd < 0)
+		return cfd;
+
+	ret = bind(cfd, (struct sockaddr *)&alg, sizeof(alg));
+	if (ret < 0)
+		goto out_cfd;
+
+	ofd = accept(cfd, NULL, 0);
+	if (ofd < 0) {
+		ret = ofd;
+		goto out_cfd;
+	}
+
+	ffd = open(object, O_RDONLY);
+	if (ffd < 0) {
+		fprintf(stderr, "Error opening object %s: %s\n",
+			object, strerror(errno));
+		ret = ffd;
+		goto out_ofd;
+	}
+
+	ret = fstat(ffd, &stbuff);
+	if (ret < 0) {
+		fprintf(stderr, "Error doing fstat: %s\n",
+			strerror(errno));
+		goto out_ffd;
+	}
+
+	size = sendfile(ofd, ffd, NULL, stbuff.st_size);
+	if (size != stbuff.st_size) {
+		fprintf(stderr, "Error from sendfile (%zd vs %zu bytes): %s\n",
+			size, stbuff.st_size, strerror(errno));
+		ret = -1;
+		goto out_ffd;
+	}
+
+	size = read(ofd, out, len);
+	if (size != len) {
+		fprintf(stderr, "Error from read (%zd vs %zu bytes): %s\n",
+			size, len, strerror(errno));
+		ret = -1;
+	} else {
+		ret = 0;
+	}
+out_ffd:
+	close(ffd);
+out_ofd:
+	close(ofd);
+out_cfd:
+	close(cfd);
+	return ret;
+}
+
+static void bpf_init_env(void)
+{
+	struct rlimit limit = {
+		.rlim_cur = RLIM_INFINITY,
+		.rlim_max = RLIM_INFINITY,
+	};
+
+	/* Don't bother in case we fail! */
+	setrlimit(RLIMIT_MEMLOCK, &limit);
+
+	if (!bpf_get_work_dir(BPF_PROG_TYPE_UNSPEC))
+		fprintf(stderr, "Continuing without mounted eBPF fs. Too old kernel?\n");
+}
+
+static const char *bpf_custom_pinning(const struct bpf_elf_ctx *ctx,
+				      uint32_t pinning)
+{
+	struct bpf_hash_entry *entry;
+
+	entry = ctx->ht[pinning & (ARRAY_SIZE(ctx->ht) - 1)];
+	while (entry && entry->pinning != pinning)
+		entry = entry->next;
+
+	return entry ? entry->subpath : NULL;
+}
+
+static bool bpf_no_pinning(const struct bpf_elf_ctx *ctx,
+			   uint32_t pinning)
+{
+	switch (pinning) {
+	case PIN_OBJECT_NS:
+	case PIN_GLOBAL_NS:
+		return false;
+	case PIN_NONE:
+		return true;
+	default:
+		return !bpf_custom_pinning(ctx, pinning);
+	}
+}
+
+static void bpf_make_pathname(char *pathname, size_t len, const char *name,
+			      const struct bpf_elf_ctx *ctx, uint32_t pinning)
+{
+	switch (pinning) {
+	case PIN_OBJECT_NS:
+		snprintf(pathname, len, "%s/%s/%s",
+			 bpf_get_work_dir(ctx->type),
+			 ctx->obj_uid, name);
+		break;
+	case PIN_GLOBAL_NS:
+		snprintf(pathname, len, "%s/%s/%s",
+			 bpf_get_work_dir(ctx->type),
+			 BPF_DIR_GLOBALS, name);
+		break;
+	default:
+		snprintf(pathname, len, "%s/../%s/%s",
+			 bpf_get_work_dir(ctx->type),
+			 bpf_custom_pinning(ctx, pinning), name);
+		break;
+	}
+}
+
+static int bpf_probe_pinned(const char *name, const struct bpf_elf_ctx *ctx,
+			    uint32_t pinning)
+{
+	char pathname[PATH_MAX];
+
+	if (bpf_no_pinning(ctx, pinning) || !bpf_get_work_dir(ctx->type))
+		return 0;
+
+	bpf_make_pathname(pathname, sizeof(pathname), name, ctx, pinning);
+	return bpf_obj_get(pathname, ctx->type);
+}
+
+static int bpf_make_obj_path(const struct bpf_elf_ctx *ctx)
+{
+	char *tmp = NULL;
+	int ret;
+
+	ret = asprintf(&tmp, "%s/%s", bpf_get_work_dir(ctx->type), ctx->obj_uid);
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		goto out;
+	}
+
+	ret = mkdir(tmp, S_IRWXU);
+	if (ret && errno != EEXIST) {
+		fprintf(stderr, "mkdir %s failed: %s\n", tmp, strerror(errno));
+		goto out;
+	}
+
+	ret = 0;
+out:
+	free(tmp);
+	return ret;
+}
+
+static int bpf_make_custom_path(const struct bpf_elf_ctx *ctx,
+				const char *todo)
+{
+	char *tmp = NULL;
+	char *rem = NULL;
+	char *sub;
+	int ret;
+
+	ret = asprintf(&tmp, "%s/../", bpf_get_work_dir(ctx->type));
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		goto out;
+	}
+
+	ret = asprintf(&rem, "%s/", todo);
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		goto out;
+	}
+
+	sub = strtok(rem, "/");
+	while (sub) {
+		if (strlen(tmp) + strlen(sub) + 2 > PATH_MAX)
+			return -EINVAL;
+
+		strcat(tmp, sub);
+		strcat(tmp, "/");
+
+		ret = mkdir(tmp, S_IRWXU);
+		if (ret && errno != EEXIST) {
+			fprintf(stderr, "mkdir %s failed: %s\n", tmp,
+				strerror(errno));
+			goto out;
+		}
+
+		sub = strtok(NULL, "/");
+	}
+
+	ret = 0;
+out:
+	free(rem);
+	free(tmp);
+	return ret;
+}
+
+static int bpf_place_pinned(int fd, const char *name,
+			    const struct bpf_elf_ctx *ctx, uint32_t pinning)
+{
+	char pathname[PATH_MAX];
+	const char *tmp;
+	int ret = 0;
+
+	if (bpf_no_pinning(ctx, pinning) || !bpf_get_work_dir(ctx->type))
+		return 0;
+
+	if (pinning == PIN_OBJECT_NS)
+		ret = bpf_make_obj_path(ctx);
+	else if ((tmp = bpf_custom_pinning(ctx, pinning)))
+		ret = bpf_make_custom_path(ctx, tmp);
+	if (ret < 0)
+		return ret;
+
+	bpf_make_pathname(pathname, sizeof(pathname), name, ctx, pinning);
+	return bpf_obj_pin(fd, pathname);
+}
+
+static void bpf_prog_report(int fd, const char *section,
+			    const struct bpf_elf_prog *prog,
+			    struct bpf_elf_ctx *ctx)
+{
+	unsigned int insns = prog->size / sizeof(struct bpf_insn);
+
+	fprintf(stderr, "\nProg section \'%s\' %s%s (%d)!\n", section,
+		fd < 0 ? "rejected: " : "loaded",
+		fd < 0 ? strerror(errno) : "",
+		fd < 0 ? errno : fd);
+
+	fprintf(stderr, " - Type:         %u\n", prog->type);
+	fprintf(stderr, " - Instructions: %u (%u over limit)\n",
+		insns, insns > BPF_MAXINSNS ? insns - BPF_MAXINSNS : 0);
+	fprintf(stderr, " - License:      %s\n\n", prog->license);
+
+	bpf_dump_error(ctx, "Verifier analysis:\n\n");
+}
+
+static int bpf_prog_attach(const char *section,
+			   const struct bpf_elf_prog *prog,
+			   struct bpf_elf_ctx *ctx)
+{
+	int tries = 0, fd;
+retry:
+	errno = 0;
+	fd = bpf_prog_load_dev(prog->type, prog->insns, prog->size,
+			       prog->license, ctx->ifindex,
+			       ctx->log, ctx->log_size);
+	if (fd < 0 || ctx->verbose) {
+		/* The verifier log is pretty chatty, sometimes so chatty
+		 * on larger programs, that we could fail to dump everything
+		 * into our buffer. Still, try to give a debuggable error
+		 * log for the user, so enlarge it and re-fail.
+		 */
+		if (fd < 0 && (errno == ENOSPC || !ctx->log_size)) {
+			if (tries++ < 10 && !bpf_log_realloc(ctx))
+				goto retry;
+
+			fprintf(stderr, "Log buffer too small to dump verifier log %zu bytes (%d tries)!\n",
+				ctx->log_size, tries);
+			return fd;
+		}
+
+		bpf_prog_report(fd, section, prog, ctx);
+	}
+
+	return fd;
+}
+
+static void bpf_map_report(int fd, const char *name,
+			   const struct bpf_elf_map *map,
+			   struct bpf_elf_ctx *ctx, int inner_fd)
+{
+	fprintf(stderr, "Map object \'%s\' %s%s (%d)!\n", name,
+		fd < 0 ? "rejected: " : "loaded",
+		fd < 0 ? strerror(errno) : "",
+		fd < 0 ? errno : fd);
+
+	fprintf(stderr, " - Type:         %u\n", map->type);
+	fprintf(stderr, " - Identifier:   %u\n", map->id);
+	fprintf(stderr, " - Pinning:      %u\n", map->pinning);
+	fprintf(stderr, " - Size key:     %u\n", map->size_key);
+	fprintf(stderr, " - Size value:   %u\n",
+		inner_fd ? (int)sizeof(int) : map->size_value);
+	fprintf(stderr, " - Max elems:    %u\n", map->max_elem);
+	fprintf(stderr, " - Flags:        %#x\n\n", map->flags);
+}
+
+static int bpf_find_map_id(const struct bpf_elf_ctx *ctx, uint32_t id)
+{
+	int i;
+
+	for (i = 0; i < ctx->map_num; i++) {
+		if (ctx->maps[i].id != id)
+			continue;
+		if (ctx->map_fds[i] < 0)
+			return -EINVAL;
+
+		return ctx->map_fds[i];
+	}
+
+	return -ENOENT;
+}
+
+static void bpf_report_map_in_map(int outer_fd, uint32_t idx)
+{
+	struct bpf_elf_map outer_map;
+	int ret;
+
+	fprintf(stderr, "Cannot insert map into map! ");
+
+	ret = bpf_derive_elf_map_from_fdinfo(outer_fd, &outer_map, NULL);
+	if (!ret) {
+		if (idx >= outer_map.max_elem &&
+		    outer_map.type == BPF_MAP_TYPE_ARRAY_OF_MAPS) {
+			fprintf(stderr, "Outer map has %u elements, index %u is invalid!\n",
+				outer_map.max_elem, idx);
+			return;
+		}
+	}
+
+	fprintf(stderr, "Different map specs used for outer and inner map?\n");
+}
+
+static bool bpf_is_map_in_map_type(const struct bpf_elf_map *map)
+{
+	return map->type == BPF_MAP_TYPE_ARRAY_OF_MAPS ||
+	       map->type == BPF_MAP_TYPE_HASH_OF_MAPS;
+}
+
+static bool bpf_map_offload_neutral(enum bpf_map_type type)
+{
+	return type == BPF_MAP_TYPE_PERF_EVENT_ARRAY;
+}
+
+static int bpf_map_attach(const char *name, struct bpf_elf_ctx *ctx,
+			  const struct bpf_elf_map *map, struct bpf_map_ext *ext,
+			  int *have_map_in_map)
+{
+	int fd, ifindex, ret, map_inner_fd = 0;
+	bool retried = false;
+
+probe:
+	fd = bpf_probe_pinned(name, ctx, map->pinning);
+	if (fd > 0) {
+		ret = bpf_map_selfcheck_pinned(fd, map, ext,
+					       offsetof(struct bpf_elf_map,
+							id), ctx->type);
+		if (ret < 0) {
+			close(fd);
+			fprintf(stderr, "Map \'%s\' self-check failed!\n",
+				name);
+			return ret;
+		}
+		if (ctx->verbose)
+			fprintf(stderr, "Map \'%s\' loaded as pinned!\n",
+				name);
+		return fd;
+	}
+
+	if (have_map_in_map && bpf_is_map_in_map_type(map)) {
+		(*have_map_in_map)++;
+		if (map->inner_id)
+			return 0;
+		fprintf(stderr, "Map \'%s\' cannot be created since no inner map ID defined!\n",
+			name);
+		return -EINVAL;
+	}
+
+	if (!have_map_in_map && bpf_is_map_in_map_type(map)) {
+		map_inner_fd = bpf_find_map_id(ctx, map->inner_id);
+		if (map_inner_fd < 0) {
+			fprintf(stderr, "Map \'%s\' cannot be loaded. Inner map with ID %u not found!\n",
+				name, map->inner_id);
+			return -EINVAL;
+		}
+	}
+
+	ifindex = bpf_map_offload_neutral(map->type) ? 0 : ctx->ifindex;
+	errno = 0;
+	fd = bpf_map_create(map->type, map->size_key, map->size_value,
+			    map->max_elem, map->flags, map_inner_fd, ctx->btf_fd,
+			    ifindex, ext->btf_id_key, ext->btf_id_val);
+
+	if (fd < 0 || ctx->verbose) {
+		bpf_map_report(fd, name, map, ctx, map_inner_fd);
+		if (fd < 0)
+			return fd;
+	}
+
+	ret = bpf_place_pinned(fd, name, ctx, map->pinning);
+	if (ret < 0) {
+		close(fd);
+		if (!retried && errno == EEXIST) {
+			retried = true;
+			goto probe;
+		}
+		fprintf(stderr, "Could not pin %s map: %s\n", name,
+			strerror(errno));
+		return ret;
+	}
+
+	return fd;
+}
+
+static const char *bpf_str_tab_name(const struct bpf_elf_ctx *ctx,
+				    const GElf_Sym *sym)
+{
+	return ctx->str_tab->d_buf + sym->st_name;
+}
+
+static int bpf_btf_find(struct bpf_elf_ctx *ctx, const char *name)
+{
+	const struct btf_type *type;
+	const char *res;
+	int id;
+
+	for (id = 1; id < ctx->btf.types_num; id++) {
+		type = ctx->btf.types[id];
+		if (type->name_off >= ctx->btf.hdr->str_len)
+			continue;
+		res = &ctx->btf.strings[type->name_off];
+		if (!strcmp(res, name))
+			return id;
+	}
+
+	return -ENOENT;
+}
+
+static int bpf_btf_find_kv(struct bpf_elf_ctx *ctx, const struct bpf_elf_map *map,
+			   const char *name, uint32_t *id_key, uint32_t *id_val)
+{
+	const struct btf_member *key, *val;
+	const struct btf_type *type;
+	char btf_name[512];
+	const char *res;
+	int id;
+
+	snprintf(btf_name, sizeof(btf_name), "____btf_map_%s", name);
+	id = bpf_btf_find(ctx, btf_name);
+	if (id < 0)
+		return id;
+
+	type = ctx->btf.types[id];
+	if (BTF_INFO_KIND(type->info) != BTF_KIND_STRUCT)
+		return -EINVAL;
+	if (BTF_INFO_VLEN(type->info) != 2)
+		return -EINVAL;
+
+	key = ((void *) type) + sizeof(*type);
+	val = key + 1;
+	if (!key->type || key->type >= ctx->btf.types_num ||
+	    !val->type || val->type >= ctx->btf.types_num)
+		return -EINVAL;
+
+	if (key->name_off >= ctx->btf.hdr->str_len ||
+	    val->name_off >= ctx->btf.hdr->str_len)
+		return -EINVAL;
+
+	res = &ctx->btf.strings[key->name_off];
+	if (strcmp(res, "key"))
+		return -EINVAL;
+
+	res = &ctx->btf.strings[val->name_off];
+	if (strcmp(res, "value"))
+		return -EINVAL;
+
+	*id_key = key->type;
+	*id_val = val->type;
+	return 0;
+}
+
+static void bpf_btf_annotate(struct bpf_elf_ctx *ctx, int which, const char *name)
+{
+	uint32_t id_key = 0, id_val = 0;
+
+	if (!bpf_btf_find_kv(ctx, &ctx->maps[which], name, &id_key, &id_val)) {
+		ctx->maps_ext[which].btf_id_key = id_key;
+		ctx->maps_ext[which].btf_id_val = id_val;
+	}
+}
+
+static const char *bpf_map_fetch_name(struct bpf_elf_ctx *ctx, int which)
+{
+	const char *name;
+	GElf_Sym sym;
+	int i;
+
+	for (i = 0; i < ctx->sym_num; i++) {
+		int type;
+
+		if (gelf_getsym(ctx->sym_tab, i, &sym) != &sym)
+			continue;
+
+		type = GELF_ST_TYPE(sym.st_info);
+		if (GELF_ST_BIND(sym.st_info) != STB_GLOBAL ||
+		    (type != STT_NOTYPE && type != STT_OBJECT) ||
+		    sym.st_shndx != ctx->sec_maps ||
+		    sym.st_value / ctx->map_len != which)
+			continue;
+
+		name = bpf_str_tab_name(ctx, &sym);
+		bpf_btf_annotate(ctx, which, name);
+		return name;
+	}
+
+	return NULL;
+}
+
+static int bpf_maps_attach_all(struct bpf_elf_ctx *ctx)
+{
+	int i, j, ret, fd, inner_fd, inner_idx, have_map_in_map = 0;
+	const char *map_name;
+
+	for (i = 0; i < ctx->map_num; i++) {
+		if (ctx->maps[i].pinning == PIN_OBJECT_NS &&
+		    ctx->noafalg) {
+			fprintf(stderr, "Missing kernel AF_ALG support for PIN_OBJECT_NS!\n");
+			return -ENOTSUP;
+		}
+
+		map_name = bpf_map_fetch_name(ctx, i);
+		if (!map_name)
+			return -EIO;
+
+		fd = bpf_map_attach(map_name, ctx, &ctx->maps[i],
+				    &ctx->maps_ext[i], &have_map_in_map);
+		if (fd < 0)
+			return fd;
+
+		ctx->map_fds[i] = !fd ? -1 : fd;
+	}
+
+	for (i = 0; have_map_in_map && i < ctx->map_num; i++) {
+		if (ctx->map_fds[i] >= 0)
+			continue;
+
+		map_name = bpf_map_fetch_name(ctx, i);
+		if (!map_name)
+			return -EIO;
+
+		fd = bpf_map_attach(map_name, ctx, &ctx->maps[i],
+				    &ctx->maps_ext[i], NULL);
+		if (fd < 0)
+			return fd;
+
+		ctx->map_fds[i] = fd;
+	}
+
+	for (i = 0; have_map_in_map && i < ctx->map_num; i++) {
+		if (!ctx->maps[i].id ||
+		    ctx->maps[i].inner_id ||
+		    ctx->maps[i].inner_idx == -1)
+			continue;
+
+		inner_fd  = ctx->map_fds[i];
+		inner_idx = ctx->maps[i].inner_idx;
+
+		for (j = 0; j < ctx->map_num; j++) {
+			if (!bpf_is_map_in_map_type(&ctx->maps[j]))
+				continue;
+			if (ctx->maps[j].inner_id != ctx->maps[i].id)
+				continue;
+
+			ret = bpf_map_update(ctx->map_fds[j], &inner_idx,
+					     &inner_fd, BPF_ANY);
+			if (ret < 0) {
+				bpf_report_map_in_map(ctx->map_fds[j],
+						      inner_idx);
+				return ret;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int bpf_map_num_sym(struct bpf_elf_ctx *ctx)
+{
+	int i, num = 0;
+	GElf_Sym sym;
+
+	for (i = 0; i < ctx->sym_num; i++) {
+		int type;
+
+		if (gelf_getsym(ctx->sym_tab, i, &sym) != &sym)
+			continue;
+
+		type = GELF_ST_TYPE(sym.st_info);
+		if (GELF_ST_BIND(sym.st_info) != STB_GLOBAL ||
+		    (type != STT_NOTYPE && type != STT_OBJECT) ||
+		    sym.st_shndx != ctx->sec_maps)
+			continue;
+		num++;
+	}
+
+	return num;
+}
+
+static int bpf_fill_section_data(struct bpf_elf_ctx *ctx, int section,
+				 struct bpf_elf_sec_data *data)
+{
+	Elf_Data *sec_edata;
+	GElf_Shdr sec_hdr;
+	Elf_Scn *sec_fd;
+	char *sec_name;
+
+	memset(data, 0, sizeof(*data));
+
+	sec_fd = elf_getscn(ctx->elf_fd, section);
+	if (!sec_fd)
+		return -EINVAL;
+	if (gelf_getshdr(sec_fd, &sec_hdr) != &sec_hdr)
+		return -EIO;
+
+	sec_name = elf_strptr(ctx->elf_fd, ctx->elf_hdr.e_shstrndx,
+			      sec_hdr.sh_name);
+	if (!sec_name || !sec_hdr.sh_size)
+		return -ENOENT;
+
+	sec_edata = elf_getdata(sec_fd, NULL);
+	if (!sec_edata || elf_getdata(sec_fd, sec_edata))
+		return -EIO;
+
+	memcpy(&data->sec_hdr, &sec_hdr, sizeof(sec_hdr));
+
+	data->sec_name = sec_name;
+	data->sec_data = sec_edata;
+	return 0;
+}
+
+struct bpf_elf_map_min {
+	__u32 type;
+	__u32 size_key;
+	__u32 size_value;
+	__u32 max_elem;
+};
+
+static int bpf_fetch_maps_begin(struct bpf_elf_ctx *ctx, int section,
+				struct bpf_elf_sec_data *data)
+{
+	ctx->map_num = data->sec_data->d_size;
+	ctx->sec_maps = section;
+	ctx->sec_done[section] = true;
+
+	if (ctx->map_num > sizeof(ctx->maps)) {
+		fprintf(stderr, "Too many BPF maps in ELF section!\n");
+		return -ENOMEM;
+	}
+
+	memcpy(ctx->maps, data->sec_data->d_buf, ctx->map_num);
+	return 0;
+}
+
+static int bpf_map_verify_all_offs(struct bpf_elf_ctx *ctx, int end)
+{
+	GElf_Sym sym;
+	int off, i;
+
+	for (off = 0; off < end; off += ctx->map_len) {
+		/* Order doesn't need to be linear here, hence we walk
+		 * the table again.
+		 */
+		for (i = 0; i < ctx->sym_num; i++) {
+			int type;
+
+			if (gelf_getsym(ctx->sym_tab, i, &sym) != &sym)
+				continue;
+
+			type = GELF_ST_TYPE(sym.st_info);
+			if (GELF_ST_BIND(sym.st_info) != STB_GLOBAL ||
+			    (type != STT_NOTYPE && type != STT_OBJECT) ||
+			    sym.st_shndx != ctx->sec_maps)
+				continue;
+			if (sym.st_value == off)
+				break;
+			if (i == ctx->sym_num - 1)
+				return -1;
+		}
+	}
+
+	return off == end ? 0 : -1;
+}
+
+static int bpf_fetch_maps_end(struct bpf_elf_ctx *ctx)
+{
+	struct bpf_elf_map fixup[ARRAY_SIZE(ctx->maps)] = {};
+	int i, sym_num = bpf_map_num_sym(ctx);
+	__u8 *buff;
+
+	if (sym_num == 0 || sym_num > ARRAY_SIZE(ctx->maps)) {
+		fprintf(stderr, "%u maps not supported in current map section!\n",
+			sym_num);
+		return -EINVAL;
+	}
+
+	if (ctx->map_num % sym_num != 0 ||
+	    ctx->map_num % sizeof(__u32) != 0) {
+		fprintf(stderr, "Number BPF map symbols are not multiple of struct bpf_elf_map!\n");
+		return -EINVAL;
+	}
+
+	ctx->map_len = ctx->map_num / sym_num;
+	if (bpf_map_verify_all_offs(ctx, ctx->map_num)) {
+		fprintf(stderr, "Different struct bpf_elf_map in use!\n");
+		return -EINVAL;
+	}
+
+	if (ctx->map_len == sizeof(struct bpf_elf_map)) {
+		ctx->map_num = sym_num;
+		return 0;
+	} else if (ctx->map_len > sizeof(struct bpf_elf_map)) {
+		fprintf(stderr, "struct bpf_elf_map not supported, coming from future version?\n");
+		return -EINVAL;
+	} else if (ctx->map_len < sizeof(struct bpf_elf_map_min)) {
+		fprintf(stderr, "struct bpf_elf_map too small, not supported!\n");
+		return -EINVAL;
+	}
+
+	ctx->map_num = sym_num;
+	for (i = 0, buff = (void *)ctx->maps; i < ctx->map_num;
+	     i++, buff += ctx->map_len) {
+		/* The fixup leaves the rest of the members as zero, which
+		 * is fine currently, but option exist to set some other
+		 * default value as well when needed in future.
+		 */
+		memcpy(&fixup[i], buff, ctx->map_len);
+	}
+
+	memcpy(ctx->maps, fixup, sizeof(fixup));
+	if (ctx->verbose)
+		printf("%zu bytes struct bpf_elf_map fixup performed due to size mismatch!\n",
+		       sizeof(struct bpf_elf_map) - ctx->map_len);
+	return 0;
+}
+
+static int bpf_fetch_license(struct bpf_elf_ctx *ctx, int section,
+			     struct bpf_elf_sec_data *data)
+{
+	if (data->sec_data->d_size > sizeof(ctx->license))
+		return -ENOMEM;
+
+	memcpy(ctx->license, data->sec_data->d_buf, data->sec_data->d_size);
+	ctx->sec_done[section] = true;
+	return 0;
+}
+
+static int bpf_fetch_symtab(struct bpf_elf_ctx *ctx, int section,
+			    struct bpf_elf_sec_data *data)
+{
+	ctx->sym_tab = data->sec_data;
+	ctx->sym_num = data->sec_hdr.sh_size / data->sec_hdr.sh_entsize;
+	ctx->sec_done[section] = true;
+	return 0;
+}
+
+static int bpf_fetch_strtab(struct bpf_elf_ctx *ctx, int section,
+			    struct bpf_elf_sec_data *data)
+{
+	ctx->str_tab = data->sec_data;
+	ctx->sec_done[section] = true;
+	return 0;
+}
+
+static int bpf_fetch_text(struct bpf_elf_ctx *ctx, int section,
+			  struct bpf_elf_sec_data *data)
+{
+	ctx->sec_text = section;
+	ctx->sec_done[section] = true;
+	return 0;
+}
+
+static void bpf_btf_report(int fd, struct bpf_elf_ctx *ctx)
+{
+	fprintf(stderr, "\nBTF debug data section \'.BTF\' %s%s (%d)!\n",
+		fd < 0 ? "rejected: " : "loaded",
+		fd < 0 ? strerror(errno) : "",
+		fd < 0 ? errno : fd);
+
+	fprintf(stderr, " - Length:       %zu\n", ctx->btf_data->d_size);
+
+	bpf_dump_error(ctx, "Verifier analysis:\n\n");
+}
+
+static int bpf_btf_attach(struct bpf_elf_ctx *ctx)
+{
+	int tries = 0, fd;
+retry:
+	errno = 0;
+	fd = bpf_btf_load(ctx->btf_data->d_buf, ctx->btf_data->d_size,
+			  ctx->log, ctx->log_size);
+	if (fd < 0 || ctx->verbose) {
+		if (fd < 0 && (errno == ENOSPC || !ctx->log_size)) {
+			if (tries++ < 10 && !bpf_log_realloc(ctx))
+				goto retry;
+
+			fprintf(stderr, "Log buffer too small to dump verifier log %zu bytes (%d tries)!\n",
+				ctx->log_size, tries);
+			return fd;
+		}
+
+		if (bpf_log_has_data(ctx))
+			bpf_btf_report(fd, ctx);
+	}
+
+	return fd;
+}
+
+static int bpf_fetch_btf_begin(struct bpf_elf_ctx *ctx, int section,
+			       struct bpf_elf_sec_data *data)
+{
+	ctx->btf_data = data->sec_data;
+	ctx->sec_btf = section;
+	ctx->sec_done[section] = true;
+	return 0;
+}
+
+static int bpf_btf_check_header(struct bpf_elf_ctx *ctx)
+{
+	const struct btf_header *hdr = ctx->btf_data->d_buf;
+	const char *str_start, *str_end;
+	unsigned int data_len;
+
+	if (hdr->magic != BTF_MAGIC) {
+		fprintf(stderr, "Object has wrong BTF magic: %x, expected: %x!\n",
+			hdr->magic, BTF_MAGIC);
+		return -EINVAL;
+	}
+
+	if (hdr->version != BTF_VERSION) {
+		fprintf(stderr, "Object has wrong BTF version: %u, expected: %u!\n",
+			hdr->version, BTF_VERSION);
+		return -EINVAL;
+	}
+
+	if (hdr->flags) {
+		fprintf(stderr, "Object has unsupported BTF flags %x!\n",
+			hdr->flags);
+		return -EINVAL;
+	}
+
+	data_len = ctx->btf_data->d_size - sizeof(*hdr);
+	if (data_len < hdr->type_off ||
+	    data_len < hdr->str_off ||
+	    data_len < hdr->type_len + hdr->str_len ||
+	    hdr->type_off >= hdr->str_off ||
+	    hdr->type_off + hdr->type_len != hdr->str_off ||
+	    hdr->str_off + hdr->str_len != data_len ||
+	    (hdr->type_off & (sizeof(uint32_t) - 1))) {
+		fprintf(stderr, "Object has malformed BTF data!\n");
+		return -EINVAL;
+	}
+
+	ctx->btf.hdr = hdr;
+	ctx->btf.raw = hdr + 1;
+
+	str_start = ctx->btf.raw + hdr->str_off;
+	str_end = str_start + hdr->str_len;
+	if (!hdr->str_len ||
+	    hdr->str_len - 1 > BTF_MAX_NAME_OFFSET ||
+	    str_start[0] || str_end[-1]) {
+		fprintf(stderr, "Object has malformed BTF string data!\n");
+		return -EINVAL;
+	}
+
+	ctx->btf.strings = str_start;
+	return 0;
+}
+
+static int bpf_btf_register_type(struct bpf_elf_ctx *ctx,
+				 const struct btf_type *type)
+{
+	int cur = ctx->btf.types_num, num = cur + 1;
+	const struct btf_type **types;
+
+	types = realloc(ctx->btf.types, num * sizeof(type));
+	if (!types) {
+		free(ctx->btf.types);
+		ctx->btf.types = NULL;
+		ctx->btf.types_num = 0;
+		return -ENOMEM;
+	}
+
+	ctx->btf.types = types;
+	ctx->btf.types[cur] = type;
+	ctx->btf.types_num = num;
+	return 0;
+}
+
+static struct btf_type btf_type_void;
+
+static int bpf_btf_prep_type_data(struct bpf_elf_ctx *ctx)
+{
+	const void *type_cur = ctx->btf.raw + ctx->btf.hdr->type_off;
+	const void *type_end = ctx->btf.raw + ctx->btf.hdr->str_off;
+	const struct btf_type *type;
+	uint16_t var_len;
+	int ret, kind;
+
+	ret = bpf_btf_register_type(ctx, &btf_type_void);
+	if (ret < 0)
+		return ret;
+
+	while (type_cur < type_end) {
+		type = type_cur;
+		type_cur += sizeof(*type);
+
+		var_len = BTF_INFO_VLEN(type->info);
+		kind = BTF_INFO_KIND(type->info);
+
+		switch (kind) {
+		case BTF_KIND_INT:
+			type_cur += sizeof(int);
+			break;
+		case BTF_KIND_ARRAY:
+			type_cur += sizeof(struct btf_array);
+			break;
+		case BTF_KIND_STRUCT:
+		case BTF_KIND_UNION:
+			type_cur += var_len * sizeof(struct btf_member);
+			break;
+		case BTF_KIND_ENUM:
+			type_cur += var_len * sizeof(struct btf_enum);
+			break;
+		case BTF_KIND_FUNC_PROTO:
+			type_cur += var_len * sizeof(struct btf_param);
+			break;
+		case BTF_KIND_TYPEDEF:
+		case BTF_KIND_PTR:
+		case BTF_KIND_FWD:
+		case BTF_KIND_VOLATILE:
+		case BTF_KIND_CONST:
+		case BTF_KIND_RESTRICT:
+		case BTF_KIND_FUNC:
+			break;
+		default:
+			fprintf(stderr, "Object has unknown BTF type: %u!\n", kind);
+			return -EINVAL;
+		}
+
+		ret = bpf_btf_register_type(ctx, type);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int bpf_btf_prep_data(struct bpf_elf_ctx *ctx)
+{
+	int ret = bpf_btf_check_header(ctx);
+
+	if (!ret)
+		return bpf_btf_prep_type_data(ctx);
+	return ret;
+}
+
+static void bpf_fetch_btf_end(struct bpf_elf_ctx *ctx)
+{
+	int fd = bpf_btf_attach(ctx);
+
+	if (fd < 0)
+		return;
+	ctx->btf_fd = fd;
+	if (bpf_btf_prep_data(ctx) < 0) {
+		close(ctx->btf_fd);
+		ctx->btf_fd = 0;
+	}
+}
+
+static bool bpf_has_map_data(const struct bpf_elf_ctx *ctx)
+{
+	return ctx->sym_tab && ctx->str_tab && ctx->sec_maps;
+}
+
+static bool bpf_has_btf_data(const struct bpf_elf_ctx *ctx)
+{
+	return ctx->sec_btf;
+}
+
+static bool bpf_has_call_data(const struct bpf_elf_ctx *ctx)
+{
+	return ctx->sec_text;
+}
+
+static int bpf_fetch_ancillary(struct bpf_elf_ctx *ctx, bool check_text_sec)
+{
+	struct bpf_elf_sec_data data;
+	int i, ret = -1;
+
+	for (i = 1; i < ctx->elf_hdr.e_shnum; i++) {
+		ret = bpf_fill_section_data(ctx, i, &data);
+		if (ret < 0)
+			continue;
+
+		if (data.sec_hdr.sh_type == SHT_PROGBITS &&
+		    !strcmp(data.sec_name, ELF_SECTION_MAPS))
+			ret = bpf_fetch_maps_begin(ctx, i, &data);
+		else if (data.sec_hdr.sh_type == SHT_PROGBITS &&
+			 !strcmp(data.sec_name, ELF_SECTION_LICENSE))
+			ret = bpf_fetch_license(ctx, i, &data);
+		else if (data.sec_hdr.sh_type == SHT_PROGBITS &&
+			 (data.sec_hdr.sh_flags & SHF_EXECINSTR) &&
+			 !strcmp(data.sec_name, ".text") &&
+			 check_text_sec)
+			ret = bpf_fetch_text(ctx, i, &data);
+		else if (data.sec_hdr.sh_type == SHT_SYMTAB &&
+			 !strcmp(data.sec_name, ".symtab"))
+			ret = bpf_fetch_symtab(ctx, i, &data);
+		else if (data.sec_hdr.sh_type == SHT_STRTAB &&
+			 !strcmp(data.sec_name, ".strtab"))
+			ret = bpf_fetch_strtab(ctx, i, &data);
+		else if (data.sec_hdr.sh_type == SHT_PROGBITS &&
+			 !strcmp(data.sec_name, ".BTF"))
+			ret = bpf_fetch_btf_begin(ctx, i, &data);
+		if (ret < 0) {
+			fprintf(stderr, "Error parsing section %d! Perhaps check with readelf -a?\n",
+				i);
+			return ret;
+		}
+	}
+
+	if (bpf_has_btf_data(ctx))
+		bpf_fetch_btf_end(ctx);
+	if (bpf_has_map_data(ctx)) {
+		ret = bpf_fetch_maps_end(ctx);
+		if (ret < 0) {
+			fprintf(stderr, "Error fixing up map structure, incompatible struct bpf_elf_map used?\n");
+			return ret;
+		}
+
+		ret = bpf_maps_attach_all(ctx);
+		if (ret < 0) {
+			fprintf(stderr, "Error loading maps into kernel!\n");
+			return ret;
+		}
+	}
+
+	return ret;
+}
+
+static int bpf_fetch_prog(struct bpf_elf_ctx *ctx, const char *section,
+			  bool *sseen)
+{
+	struct bpf_elf_sec_data data;
+	struct bpf_elf_prog prog;
+	int ret, i, fd = -1;
+
+	for (i = 1; i < ctx->elf_hdr.e_shnum; i++) {
+		if (ctx->sec_done[i])
+			continue;
+
+		ret = bpf_fill_section_data(ctx, i, &data);
+		if (ret < 0 ||
+		    !(data.sec_hdr.sh_type == SHT_PROGBITS &&
+		      (data.sec_hdr.sh_flags & SHF_EXECINSTR) &&
+		      !strcmp(data.sec_name, section)))
+			continue;
+
+		*sseen = true;
+
+		memset(&prog, 0, sizeof(prog));
+		prog.type      = ctx->type;
+		prog.license   = ctx->license;
+		prog.size      = data.sec_data->d_size;
+		prog.insns_num = prog.size / sizeof(struct bpf_insn);
+		prog.insns     = data.sec_data->d_buf;
+
+		fd = bpf_prog_attach(section, &prog, ctx);
+		if (fd < 0)
+			return fd;
+
+		ctx->sec_done[i] = true;
+		break;
+	}
+
+	return fd;
+}
+
+struct bpf_relo_props {
+	struct bpf_tail_call {
+		unsigned int total;
+		unsigned int jited;
+	} tc;
+	int main_num;
+};
+
+static int bpf_apply_relo_map(struct bpf_elf_ctx *ctx, struct bpf_elf_prog *prog,
+			      GElf_Rel *relo, GElf_Sym *sym,
+			      struct bpf_relo_props *props)
+{
+	unsigned int insn_off = relo->r_offset / sizeof(struct bpf_insn);
+	unsigned int map_idx = sym->st_value / ctx->map_len;
+
+	if (insn_off >= prog->insns_num)
+		return -EINVAL;
+	if (prog->insns[insn_off].code != (BPF_LD | BPF_IMM | BPF_DW)) {
+		fprintf(stderr, "ELF contains relo data for non ld64 instruction at offset %u! Compiler bug?!\n",
+			insn_off);
+		return -EINVAL;
+	}
+
+	if (map_idx >= ARRAY_SIZE(ctx->map_fds))
+		return -EINVAL;
+	if (!ctx->map_fds[map_idx])
+		return -EINVAL;
+	if (ctx->maps[map_idx].type == BPF_MAP_TYPE_PROG_ARRAY) {
+		props->tc.total++;
+		if (ctx->maps_ext[map_idx].owner.jited ||
+		    (ctx->maps_ext[map_idx].owner.type == 0 &&
+		     ctx->cfg.jit_enabled))
+			props->tc.jited++;
+	}
+
+	prog->insns[insn_off].src_reg = BPF_PSEUDO_MAP_FD;
+	prog->insns[insn_off].imm = ctx->map_fds[map_idx];
+	return 0;
+}
+
+static int bpf_apply_relo_call(struct bpf_elf_ctx *ctx, struct bpf_elf_prog *prog,
+			       GElf_Rel *relo, GElf_Sym *sym,
+			       struct bpf_relo_props *props)
+{
+	unsigned int insn_off = relo->r_offset / sizeof(struct bpf_insn);
+	struct bpf_elf_prog *prog_text = &ctx->prog_text;
+
+	if (insn_off >= prog->insns_num)
+		return -EINVAL;
+	if (prog->insns[insn_off].code != (BPF_JMP | BPF_CALL) &&
+	    prog->insns[insn_off].src_reg != BPF_PSEUDO_CALL) {
+		fprintf(stderr, "ELF contains relo data for non call instruction at offset %u! Compiler bug?!\n",
+			insn_off);
+		return -EINVAL;
+	}
+
+	if (!props->main_num) {
+		struct bpf_insn *insns = realloc(prog->insns,
+						 prog->size + prog_text->size);
+		if (!insns)
+			return -ENOMEM;
+
+		memcpy(insns + prog->insns_num, prog_text->insns,
+		       prog_text->size);
+		props->main_num = prog->insns_num;
+		prog->insns = insns;
+		prog->insns_num += prog_text->insns_num;
+		prog->size += prog_text->size;
+	}
+
+	prog->insns[insn_off].imm += props->main_num - insn_off;
+	return 0;
+}
+
+static int bpf_apply_relo_data(struct bpf_elf_ctx *ctx,
+			       struct bpf_elf_sec_data *data_relo,
+			       struct bpf_elf_prog *prog,
+			       struct bpf_relo_props *props)
+{
+	GElf_Shdr *rhdr = &data_relo->sec_hdr;
+	int relo_ent, relo_num = rhdr->sh_size / rhdr->sh_entsize;
+
+	for (relo_ent = 0; relo_ent < relo_num; relo_ent++) {
+		GElf_Rel relo;
+		GElf_Sym sym;
+		int ret = -EIO;
+
+		if (gelf_getrel(data_relo->sec_data, relo_ent, &relo) != &relo)
+			return -EIO;
+		if (gelf_getsym(ctx->sym_tab, GELF_R_SYM(relo.r_info), &sym) != &sym)
+			return -EIO;
+
+		if (sym.st_shndx == ctx->sec_maps)
+			ret = bpf_apply_relo_map(ctx, prog, &relo, &sym, props);
+		else if (sym.st_shndx == ctx->sec_text)
+			ret = bpf_apply_relo_call(ctx, prog, &relo, &sym, props);
+		else
+			fprintf(stderr, "ELF contains non-{map,call} related relo data in entry %u pointing to section %u! Compiler bug?!\n",
+				relo_ent, sym.st_shndx);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int bpf_fetch_prog_relo(struct bpf_elf_ctx *ctx, const char *section,
+			       bool *lderr, bool *sseen, struct bpf_elf_prog *prog)
+{
+	struct bpf_elf_sec_data data_relo, data_insn;
+	int ret, idx, i, fd = -1;
+
+	for (i = 1; i < ctx->elf_hdr.e_shnum; i++) {
+		struct bpf_relo_props props = {};
+
+		ret = bpf_fill_section_data(ctx, i, &data_relo);
+		if (ret < 0 || data_relo.sec_hdr.sh_type != SHT_REL)
+			continue;
+
+		idx = data_relo.sec_hdr.sh_info;
+
+		ret = bpf_fill_section_data(ctx, idx, &data_insn);
+		if (ret < 0 ||
+		    !(data_insn.sec_hdr.sh_type == SHT_PROGBITS &&
+		      (data_insn.sec_hdr.sh_flags & SHF_EXECINSTR) &&
+		      !strcmp(data_insn.sec_name, section)))
+			continue;
+		if (sseen)
+			*sseen = true;
+
+		memset(prog, 0, sizeof(*prog));
+		prog->type = ctx->type;
+		prog->license = ctx->license;
+		prog->size = data_insn.sec_data->d_size;
+		prog->insns_num = prog->size / sizeof(struct bpf_insn);
+		prog->insns = malloc(prog->size);
+		if (!prog->insns) {
+			*lderr = true;
+			return -ENOMEM;
+		}
+
+		memcpy(prog->insns, data_insn.sec_data->d_buf, prog->size);
+
+		ret = bpf_apply_relo_data(ctx, &data_relo, prog, &props);
+		if (ret < 0) {
+			*lderr = true;
+			if (ctx->sec_text != idx)
+				free(prog->insns);
+			return ret;
+		}
+		if (ctx->sec_text == idx) {
+			fd = 0;
+			goto out;
+		}
+
+		fd = bpf_prog_attach(section, prog, ctx);
+		free(prog->insns);
+		if (fd < 0) {
+			*lderr = true;
+			if (props.tc.total) {
+				if (ctx->cfg.jit_enabled &&
+				    props.tc.total != props.tc.jited)
+					fprintf(stderr, "JIT enabled, but only %u/%u tail call maps in the program have JITed owner!\n",
+						props.tc.jited, props.tc.total);
+				if (!ctx->cfg.jit_enabled &&
+				    props.tc.jited)
+					fprintf(stderr, "JIT disabled, but %u/%u tail call maps in the program have JITed owner!\n",
+						props.tc.jited, props.tc.total);
+			}
+			return fd;
+		}
+out:
+		ctx->sec_done[i]   = true;
+		ctx->sec_done[idx] = true;
+		break;
+	}
+
+	return fd;
+}
+
+static int bpf_fetch_prog_sec(struct bpf_elf_ctx *ctx, const char *section)
+{
+	bool lderr = false, sseen = false;
+	struct bpf_elf_prog prog;
+	int ret = -1;
+
+	if (bpf_has_call_data(ctx)) {
+		ret = bpf_fetch_prog_relo(ctx, ".text", &lderr, NULL,
+					  &ctx->prog_text);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (bpf_has_map_data(ctx) || bpf_has_call_data(ctx))
+		ret = bpf_fetch_prog_relo(ctx, section, &lderr, &sseen, &prog);
+	if (ret < 0 && !lderr)
+		ret = bpf_fetch_prog(ctx, section, &sseen);
+	if (ret < 0 && !sseen)
+		fprintf(stderr, "Program section \'%s\' not found in ELF file!\n",
+			section);
+	return ret;
+}
+
+static int bpf_find_map_by_id(struct bpf_elf_ctx *ctx, uint32_t id)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ctx->map_fds); i++)
+		if (ctx->map_fds[i] && ctx->maps[i].id == id &&
+		    ctx->maps[i].type == BPF_MAP_TYPE_PROG_ARRAY)
+			return i;
+	return -1;
+}
+
+struct bpf_jited_aux {
+	int prog_fd;
+	int map_fd;
+	struct bpf_prog_data prog;
+	struct bpf_map_ext map;
+};
+
+static int bpf_derive_prog_from_fdinfo(int fd, struct bpf_prog_data *prog)
+{
+	char *file = NULL;
+	char buff[4096];
+	unsigned int val;
+	FILE *fp;
+	int ret;
+
+	ret = asprintf(&file, "/proc/%d/fdinfo/%d", getpid(), fd);
+	if (ret < 0) {
+		fprintf(stderr, "asprintf failed: %s\n", strerror(errno));
+		free(file);
+		return ret;
+	}
+
+	memset(prog, 0, sizeof(*prog));
+
+	fp = fopen(file, "r");
+	free(file);
+	if (!fp) {
+		fprintf(stderr, "No procfs support?!\n");
+		return -EIO;
+	}
+
+	while (fgets(buff, sizeof(buff), fp)) {
+		if (sscanf(buff, "prog_type:\t%u", &val) == 1)
+			prog->type = val;
+		else if (sscanf(buff, "prog_jited:\t%u", &val) == 1)
+			prog->jited = val;
+	}
+
+	fclose(fp);
+	return 0;
+}
+
+static int bpf_tail_call_get_aux(struct bpf_jited_aux *aux)
+{
+	struct bpf_elf_map tmp;
+	int ret;
+
+	ret = bpf_derive_elf_map_from_fdinfo(aux->map_fd, &tmp, &aux->map);
+	if (!ret)
+		ret = bpf_derive_prog_from_fdinfo(aux->prog_fd, &aux->prog);
+
+	return ret;
+}
+
+static int bpf_fill_prog_arrays(struct bpf_elf_ctx *ctx)
+{
+	struct bpf_elf_sec_data data;
+	uint32_t map_id, key_id;
+	int fd, i, ret, idx;
+
+	for (i = 1; i < ctx->elf_hdr.e_shnum; i++) {
+		if (ctx->sec_done[i])
+			continue;
+
+		ret = bpf_fill_section_data(ctx, i, &data);
+		if (ret < 0)
+			continue;
+
+		ret = sscanf(data.sec_name, "%i/%i", &map_id, &key_id);
+		if (ret != 2)
+			continue;
+
+		idx = bpf_find_map_by_id(ctx, map_id);
+		if (idx < 0)
+			continue;
+
+		fd = bpf_fetch_prog_sec(ctx, data.sec_name);
+		if (fd < 0)
+			return -EIO;
+
+		ret = bpf_map_update(ctx->map_fds[idx], &key_id,
+				     &fd, BPF_ANY);
+		if (ret < 0) {
+			struct bpf_jited_aux aux = {};
+
+			ret = -errno;
+			if (errno == E2BIG) {
+				fprintf(stderr, "Tail call key %u for map %u out of bounds?\n",
+					key_id, map_id);
+				return ret;
+			}
+
+			aux.map_fd  = ctx->map_fds[idx];
+			aux.prog_fd = fd;
+
+			if (bpf_tail_call_get_aux(&aux))
+				return ret;
+			if (!aux.map.owner.type)
+				return ret;
+
+			if (aux.prog.type != aux.map.owner.type)
+				fprintf(stderr, "Tail call map owned by prog type %u, but prog type is %u!\n",
+					aux.map.owner.type, aux.prog.type);
+			if (aux.prog.jited != aux.map.owner.jited)
+				fprintf(stderr, "Tail call map %s jited, but prog %s!\n",
+					aux.map.owner.jited ? "is" : "not",
+					aux.prog.jited ? "is" : "not");
+			return ret;
+		}
+
+		ctx->sec_done[i] = true;
+	}
+
+	return 0;
+}
+
+static void bpf_save_finfo(struct bpf_elf_ctx *ctx)
+{
+	struct stat st;
+	int ret;
+
+	memset(&ctx->stat, 0, sizeof(ctx->stat));
+
+	ret = fstat(ctx->obj_fd, &st);
+	if (ret < 0) {
+		fprintf(stderr, "Stat of elf file failed: %s\n",
+			strerror(errno));
+		return;
+	}
+
+	ctx->stat.st_dev = st.st_dev;
+	ctx->stat.st_ino = st.st_ino;
+}
+
+static int bpf_read_pin_mapping(FILE *fp, uint32_t *id, char *path)
+{
+	char buff[PATH_MAX];
+
+	while (fgets(buff, sizeof(buff), fp)) {
+		char *ptr = buff;
+
+		while (*ptr == ' ' || *ptr == '\t')
+			ptr++;
+
+		if (*ptr == '#' || *ptr == '\n' || *ptr == 0)
+			continue;
+
+		if (sscanf(ptr, "%i %s\n", id, path) != 2 &&
+		    sscanf(ptr, "%i %s #", id, path) != 2) {
+			strcpy(path, ptr);
+			return -1;
+		}
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static bool bpf_pinning_reserved(uint32_t pinning)
+{
+	switch (pinning) {
+	case PIN_NONE:
+	case PIN_OBJECT_NS:
+	case PIN_GLOBAL_NS:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static void bpf_hash_init(struct bpf_elf_ctx *ctx, const char *db_file)
+{
+	struct bpf_hash_entry *entry;
+	char subpath[PATH_MAX] = {};
+	uint32_t pinning;
+	FILE *fp;
+	int ret;
+
+	fp = fopen(db_file, "r");
+	if (!fp)
+		return;
+
+	while ((ret = bpf_read_pin_mapping(fp, &pinning, subpath))) {
+		if (ret == -1) {
+			fprintf(stderr, "Database %s is corrupted at: %s\n",
+				db_file, subpath);
+			fclose(fp);
+			return;
+		}
+
+		if (bpf_pinning_reserved(pinning)) {
+			fprintf(stderr, "Database %s, id %u is reserved - ignoring!\n",
+				db_file, pinning);
+			continue;
+		}
+
+		entry = malloc(sizeof(*entry));
+		if (!entry) {
+			fprintf(stderr, "No memory left for db entry!\n");
+			continue;
+		}
+
+		entry->pinning = pinning;
+		entry->subpath = strdup(subpath);
+		if (!entry->subpath) {
+			fprintf(stderr, "No memory left for db entry!\n");
+			free(entry);
+			continue;
+		}
+
+		entry->next = ctx->ht[pinning & (ARRAY_SIZE(ctx->ht) - 1)];
+		ctx->ht[pinning & (ARRAY_SIZE(ctx->ht) - 1)] = entry;
+	}
+
+	fclose(fp);
+}
+
+static void bpf_hash_destroy(struct bpf_elf_ctx *ctx)
+{
+	struct bpf_hash_entry *entry;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ctx->ht); i++) {
+		while ((entry = ctx->ht[i]) != NULL) {
+			ctx->ht[i] = entry->next;
+			free((char *)entry->subpath);
+			free(entry);
+		}
+	}
+}
+
+static int bpf_elf_check_ehdr(const struct bpf_elf_ctx *ctx)
+{
+	if (ctx->elf_hdr.e_type != ET_REL ||
+	    (ctx->elf_hdr.e_machine != EM_NONE &&
+	     ctx->elf_hdr.e_machine != EM_BPF) ||
+	    ctx->elf_hdr.e_version != EV_CURRENT) {
+		fprintf(stderr, "ELF format error, ELF file not for eBPF?\n");
+		return -EINVAL;
+	}
+
+	switch (ctx->elf_hdr.e_ident[EI_DATA]) {
+	default:
+		fprintf(stderr, "ELF format error, wrong endianness info?\n");
+		return -EINVAL;
+	case ELFDATA2LSB:
+		if (htons(1) == 1) {
+			fprintf(stderr,
+				"We are big endian, eBPF object is little endian!\n");
+			return -EIO;
+		}
+		break;
+	case ELFDATA2MSB:
+		if (htons(1) != 1) {
+			fprintf(stderr,
+				"We are little endian, eBPF object is big endian!\n");
+			return -EIO;
+		}
+		break;
+	}
+
+	return 0;
+}
+
+static void bpf_get_cfg(struct bpf_elf_ctx *ctx)
+{
+	static const char *path_jit = "/proc/sys/net/core/bpf_jit_enable";
+	int fd;
+
+	fd = open(path_jit, O_RDONLY);
+	if (fd > 0) {
+		char tmp[16] = {};
+
+		if (read(fd, tmp, sizeof(tmp)) > 0)
+			ctx->cfg.jit_enabled = atoi(tmp);
+		close(fd);
+	}
+}
+
+static int bpf_elf_ctx_init(struct bpf_elf_ctx *ctx, const char *pathname,
+			    enum bpf_prog_type type, __u32 ifindex,
+			    bool verbose)
+{
+	uint8_t tmp[20];
+	int ret;
+
+	if (elf_version(EV_CURRENT) == EV_NONE)
+		return -EINVAL;
+
+	bpf_init_env();
+
+	memset(ctx, 0, sizeof(*ctx));
+	bpf_get_cfg(ctx);
+
+	ret = bpf_obj_hash(pathname, tmp, sizeof(tmp));
+	if (ret)
+		ctx->noafalg = true;
+	else
+		hexstring_n2a(tmp, sizeof(tmp), ctx->obj_uid,
+			      sizeof(ctx->obj_uid));
+
+	ctx->verbose = verbose;
+	ctx->type    = type;
+	ctx->ifindex = ifindex;
+
+	ctx->obj_fd = open(pathname, O_RDONLY);
+	if (ctx->obj_fd < 0)
+		return ctx->obj_fd;
+
+	ctx->elf_fd = elf_begin(ctx->obj_fd, ELF_C_READ, NULL);
+	if (!ctx->elf_fd) {
+		ret = -EINVAL;
+		goto out_fd;
+	}
+
+	if (elf_kind(ctx->elf_fd) != ELF_K_ELF) {
+		ret = -EINVAL;
+		goto out_fd;
+	}
+
+	if (gelf_getehdr(ctx->elf_fd, &ctx->elf_hdr) !=
+	    &ctx->elf_hdr) {
+		ret = -EIO;
+		goto out_elf;
+	}
+
+	ret = bpf_elf_check_ehdr(ctx);
+	if (ret < 0)
+		goto out_elf;
+
+	ctx->sec_done = calloc(ctx->elf_hdr.e_shnum,
+			       sizeof(*(ctx->sec_done)));
+	if (!ctx->sec_done) {
+		ret = -ENOMEM;
+		goto out_elf;
+	}
+
+	if (ctx->verbose && bpf_log_realloc(ctx)) {
+		ret = -ENOMEM;
+		goto out_free;
+	}
+
+	bpf_save_finfo(ctx);
+	bpf_hash_init(ctx, CONFDIR "/bpf_pinning");
+
+	return 0;
+out_free:
+	free(ctx->sec_done);
+out_elf:
+	elf_end(ctx->elf_fd);
+out_fd:
+	close(ctx->obj_fd);
+	return ret;
+}
+
+static int bpf_maps_count(struct bpf_elf_ctx *ctx)
+{
+	int i, count = 0;
+
+	for (i = 0; i < ARRAY_SIZE(ctx->map_fds); i++) {
+		if (!ctx->map_fds[i])
+			break;
+		count++;
+	}
+
+	return count;
+}
+
+static void bpf_maps_teardown(struct bpf_elf_ctx *ctx)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ctx->map_fds); i++) {
+		if (ctx->map_fds[i])
+			close(ctx->map_fds[i]);
+	}
+
+	if (ctx->btf_fd)
+		close(ctx->btf_fd);
+	free(ctx->btf.types);
+}
+
+static void bpf_elf_ctx_destroy(struct bpf_elf_ctx *ctx, bool failure)
+{
+	if (failure)
+		bpf_maps_teardown(ctx);
+
+	bpf_hash_destroy(ctx);
+
+	free(ctx->prog_text.insns);
+	free(ctx->sec_done);
+	free(ctx->log);
+
+	elf_end(ctx->elf_fd);
+	close(ctx->obj_fd);
+}
+
+static struct bpf_elf_ctx __ctx;
+
+static int bpf_obj_open(const char *pathname, enum bpf_prog_type type,
+			const char *section, __u32 ifindex, bool verbose)
+{
+	struct bpf_elf_ctx *ctx = &__ctx;
+	int fd = 0, ret;
+
+	ret = bpf_elf_ctx_init(ctx, pathname, type, ifindex, verbose);
+	if (ret < 0) {
+		fprintf(stderr, "Cannot initialize ELF context!\n");
+		return ret;
+	}
+
+	ret = bpf_fetch_ancillary(ctx, strcmp(section, ".text"));
+	if (ret < 0) {
+		fprintf(stderr, "Error fetching ELF ancillary data!\n");
+		goto out;
+	}
+
+	fd = bpf_fetch_prog_sec(ctx, section);
+	if (fd < 0) {
+		fprintf(stderr, "Error fetching program/map!\n");
+		ret = fd;
+		goto out;
+	}
+
+	ret = bpf_fill_prog_arrays(ctx);
+	if (ret < 0)
+		fprintf(stderr, "Error filling program arrays!\n");
+out:
+	bpf_elf_ctx_destroy(ctx, ret < 0);
+	if (ret < 0) {
+		if (fd)
+			close(fd);
+		return ret;
+	}
+
+	return fd;
+}
+
+static int
+bpf_map_set_send(int fd, struct sockaddr_un *addr, unsigned int addr_len,
+		 const struct bpf_map_data *aux, unsigned int entries)
+{
+	struct bpf_map_set_msg msg = {
+		.aux.uds_ver = BPF_SCM_AUX_VER,
+		.aux.num_ent = entries,
+	};
+	int *cmsg_buf, min_fd;
+	char *amsg_buf;
+	int i;
+
+	strlcpy(msg.aux.obj_name, aux->obj, sizeof(msg.aux.obj_name));
+	memcpy(&msg.aux.obj_st, aux->st, sizeof(msg.aux.obj_st));
+
+	cmsg_buf = bpf_map_set_init(&msg, addr, addr_len);
+	amsg_buf = (char *)msg.aux.ent;
+
+	for (i = 0; i < entries; i += min_fd) {
+		int ret;
+
+		min_fd = min(BPF_SCM_MAX_FDS * 1U, entries - i);
+		bpf_map_set_init_single(&msg, min_fd);
+
+		memcpy(cmsg_buf, &aux->fds[i], sizeof(aux->fds[0]) * min_fd);
+		memcpy(amsg_buf, &aux->ent[i], sizeof(aux->ent[0]) * min_fd);
+
+		ret = sendmsg(fd, &msg.hdr, 0);
+		if (ret <= 0)
+			return ret ? : -1;
+	}
+
+	return 0;
+}
+
+static int
+bpf_map_set_recv(int fd, int *fds,  struct bpf_map_aux *aux,
+		 unsigned int entries)
+{
+	struct bpf_map_set_msg msg;
+	int *cmsg_buf, min_fd;
+	char *amsg_buf, *mmsg_buf;
+	unsigned int needed = 1;
+	int i;
+
+	cmsg_buf = bpf_map_set_init(&msg, NULL, 0);
+	amsg_buf = (char *)msg.aux.ent;
+	mmsg_buf = (char *)&msg.aux;
+
+	for (i = 0; i < min(entries, needed); i += min_fd) {
+		struct cmsghdr *cmsg;
+		int ret;
+
+		min_fd = min(entries, entries - i);
+		bpf_map_set_init_single(&msg, min_fd);
+
+		ret = recvmsg(fd, &msg.hdr, 0);
+		if (ret <= 0)
+			return ret ? : -1;
+
+		cmsg = CMSG_FIRSTHDR(&msg.hdr);
+		if (!cmsg || cmsg->cmsg_type != SCM_RIGHTS)
+			return -EINVAL;
+		if (msg.hdr.msg_flags & MSG_CTRUNC)
+			return -EIO;
+		if (msg.aux.uds_ver != BPF_SCM_AUX_VER)
+			return -ENOSYS;
+
+		min_fd = (cmsg->cmsg_len - sizeof(*cmsg)) / sizeof(fd);
+		if (min_fd > entries || min_fd <= 0)
+			return -EINVAL;
+
+		memcpy(&fds[i], cmsg_buf, sizeof(fds[0]) * min_fd);
+		memcpy(&aux->ent[i], amsg_buf, sizeof(aux->ent[0]) * min_fd);
+		memcpy(aux, mmsg_buf, offsetof(struct bpf_map_aux, ent));
+
+		needed = aux->num_ent;
+	}
+
+	return 0;
+}
+
+int bpf_send_map_fds(const char *path, const char *obj)
+{
+	struct bpf_elf_ctx *ctx = &__ctx;
+	struct sockaddr_un addr = { .sun_family = AF_UNIX };
+	struct bpf_map_data bpf_aux = {
+		.fds = ctx->map_fds,
+		.ent = ctx->maps,
+		.st  = &ctx->stat,
+		.obj = obj,
+	};
+	int fd, ret;
+
+	fd = socket(AF_UNIX, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		fprintf(stderr, "Cannot open socket: %s\n",
+			strerror(errno));
+		return -1;
+	}
+
+	strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
+
+	ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+	if (ret < 0) {
+		fprintf(stderr, "Cannot connect to %s: %s\n",
+			path, strerror(errno));
+		return -1;
+	}
+
+	ret = bpf_map_set_send(fd, &addr, sizeof(addr), &bpf_aux,
+			       bpf_maps_count(ctx));
+	if (ret < 0)
+		fprintf(stderr, "Cannot send fds to %s: %s\n",
+			path, strerror(errno));
+
+	bpf_maps_teardown(ctx);
+	close(fd);
+	return ret;
+}
+
+int bpf_recv_map_fds(const char *path, int *fds, struct bpf_map_aux *aux,
+		     unsigned int entries)
+{
+	struct sockaddr_un addr = { .sun_family = AF_UNIX };
+	int fd, ret;
+
+	fd = socket(AF_UNIX, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		fprintf(stderr, "Cannot open socket: %s\n",
+			strerror(errno));
+		return -1;
+	}
+
+	strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
+
+	ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+	if (ret < 0) {
+		fprintf(stderr, "Cannot bind to socket: %s\n",
+			strerror(errno));
+		return -1;
+	}
+
+	ret = bpf_map_set_recv(fd, fds, aux, entries);
+	if (ret < 0)
+		fprintf(stderr, "Cannot recv fds from %s: %s\n",
+			path, strerror(errno));
+
+	unlink(addr.sun_path);
+	close(fd);
+	return ret;
+}
+#endif /* HAVE_ELF */
diff --git a/lib/color.c b/lib/color.c
new file mode 100644
index 0000000..5997684
--- /dev/null
+++ b/lib/color.c
@@ -0,0 +1,183 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <linux/if.h>
+
+#include "color.h"
+#include "utils.h"
+
+static void set_color_palette(void);
+
+enum color {
+	C_RED,
+	C_GREEN,
+	C_YELLOW,
+	C_BLUE,
+	C_MAGENTA,
+	C_CYAN,
+	C_WHITE,
+	C_BOLD_RED,
+	C_BOLD_GREEN,
+	C_BOLD_YELLOW,
+	C_BOLD_BLUE,
+	C_BOLD_MAGENTA,
+	C_BOLD_CYAN,
+	C_BOLD_WHITE,
+	C_CLEAR
+};
+
+static const char * const color_codes[] = {
+	"\e[31m",
+	"\e[32m",
+	"\e[33m",
+	"\e[34m",
+	"\e[35m",
+	"\e[36m",
+	"\e[37m",
+	"\e[1;31m",
+	"\e[1;32m",
+	"\e[1;33m",
+	"\e[1;34m",
+	"\e[1;35m",
+	"\e[1;36m",
+	"\e[1;37m",
+	"\e[0m",
+	NULL,
+};
+
+/* light background */
+static enum color attr_colors_light[] = {
+	C_CYAN,
+	C_YELLOW,
+	C_MAGENTA,
+	C_BLUE,
+	C_GREEN,
+	C_RED,
+	C_CLEAR,
+};
+
+/* dark background */
+static enum color attr_colors_dark[] = {
+	C_BOLD_CYAN,
+	C_BOLD_YELLOW,
+	C_BOLD_MAGENTA,
+	C_BOLD_BLUE,
+	C_BOLD_GREEN,
+	C_BOLD_RED,
+	C_CLEAR
+};
+
+static int is_dark_bg;
+static int color_is_enabled;
+
+static void enable_color(void)
+{
+	color_is_enabled = 1;
+	set_color_palette();
+}
+
+bool check_enable_color(int color, int json)
+{
+	if (json || color == COLOR_OPT_NEVER)
+		return false;
+
+	if (color == COLOR_OPT_ALWAYS || isatty(fileno(stdout))) {
+		enable_color();
+		return true;
+	}
+	return false;
+}
+
+bool matches_color(const char *arg, int *val)
+{
+	char *dup, *p;
+
+	if (!val)
+		return false;
+
+	dup = strdupa(arg);
+	p = strchrnul(dup, '=');
+	if (*p)
+		*(p++) = '\0';
+
+	if (matches(dup, "-color"))
+		return false;
+
+	if (*p == '\0' || !strcmp(p, "always"))
+		*val = COLOR_OPT_ALWAYS;
+	else if (!strcmp(p, "auto"))
+		*val = COLOR_OPT_AUTO;
+	else if (!strcmp(p, "never"))
+		*val = COLOR_OPT_NEVER;
+	else
+		return false;
+	return true;
+}
+
+static void set_color_palette(void)
+{
+	char *p = getenv("COLORFGBG");
+
+	/*
+	 * COLORFGBG environment variable usually contains either two or three
+	 * values separated by semicolons; we want the last value in either case.
+	 * If this value is 0-6 or 8, background is dark.
+	 */
+	if (p && (p = strrchr(p, ';')) != NULL
+		&& ((p[1] >= '0' && p[1] <= '6') || p[1] == '8')
+		&& p[2] == '\0')
+		is_dark_bg = 1;
+}
+
+__attribute__((format(printf, 3, 4)))
+int color_fprintf(FILE *fp, enum color_attr attr, const char *fmt, ...)
+{
+	int ret = 0;
+	va_list args;
+
+	va_start(args, fmt);
+
+	if (!color_is_enabled || attr == COLOR_NONE) {
+		ret = vfprintf(fp, fmt, args);
+		goto end;
+	}
+
+	ret += fprintf(fp, "%s", color_codes[is_dark_bg ?
+		attr_colors_dark[attr] : attr_colors_light[attr]]);
+
+	ret += vfprintf(fp, fmt, args);
+	ret += fprintf(fp, "%s", color_codes[C_CLEAR]);
+
+end:
+	va_end(args);
+	return ret;
+}
+
+enum color_attr ifa_family_color(__u8 ifa_family)
+{
+	switch (ifa_family) {
+	case AF_INET:
+		return COLOR_INET;
+	case AF_INET6:
+		return COLOR_INET6;
+	default:
+		return COLOR_NONE;
+	}
+}
+
+enum color_attr oper_state_color(__u8 state)
+{
+	switch (state) {
+	case IF_OPER_UP:
+		return COLOR_OPERSTATE_UP;
+	case IF_OPER_DOWN:
+		return COLOR_OPERSTATE_DOWN;
+	default:
+		return COLOR_NONE;
+	}
+}
diff --git a/lib/coverity_model.c b/lib/coverity_model.c
new file mode 100644
index 0000000..1321fe8
--- /dev/null
+++ b/lib/coverity_model.c
@@ -0,0 +1,17 @@
+/*
+ * Coverity Scan model
+ *
+ * This is a modeling file for Coverity Scan. Modeling helps to avoid false
+ * positives.
+ *
+ * - A model file can't import any header files.
+ * - Therefore only some built-in primitives like int, char and void are
+ *   available but not wchar_t, NULL etc.
+ * - Modeling doesn't need full structs and typedefs. Rudimentary structs
+ *   and similar types are sufficient.
+ * - An uninitialized local pointer is not an error. It signifies that the
+ *   variable could be either NULL or have some data.
+ *
+ * Coverity Scan doesn't pick up modifications automatically. The model file
+ * must be uploaded by an admin.
+ */
diff --git a/lib/exec.c b/lib/exec.c
new file mode 100644
index 0000000..9b1c8f4
--- /dev/null
+++ b/lib/exec.c
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <sys/wait.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "utils.h"
+#include "namespace.h"
+
+int cmd_exec(const char *cmd, char **argv, bool do_fork,
+	     int (*setup)(void *), void *arg)
+{
+	fflush(stdout);
+	if (do_fork) {
+		int status;
+		pid_t pid;
+
+		pid = fork();
+		if (pid < 0) {
+			perror("fork");
+			exit(1);
+		}
+
+		if (pid != 0) {
+			/* Parent  */
+			if (waitpid(pid, &status, 0) < 0) {
+				perror("waitpid");
+				exit(1);
+			}
+
+			if (WIFEXITED(status)) {
+				return WEXITSTATUS(status);
+			}
+
+			exit(1);
+		}
+	}
+
+	if (setup && setup(arg))
+		return -1;
+
+	if (execvp(cmd, argv)  < 0)
+		fprintf(stderr, "exec of \"%s\" failed: %s\n",
+				cmd, strerror(errno));
+	_exit(1);
+}
diff --git a/lib/fs.c b/lib/fs.c
new file mode 100644
index 0000000..86efd4e
--- /dev/null
+++ b/lib/fs.c
@@ -0,0 +1,182 @@
+/*
+ * fs.c         filesystem APIs
+ *
+ *		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.
+ *
+ * Authors:	David Ahern <dsa@cumulusnetworks.com>
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "utils.h"
+
+#define CGROUP2_FS_NAME "cgroup2"
+
+/* if not already mounted cgroup2 is mounted here for iproute2's use */
+#define MNT_CGRP2_PATH  "/var/run/cgroup2"
+
+/* return mount path of first occurrence of given fstype */
+static char *find_fs_mount(const char *fs_to_find)
+{
+	char path[4096];
+	char fstype[128];    /* max length of any filesystem name */
+	char *mnt = NULL;
+	FILE *fp;
+
+	fp = fopen("/proc/mounts", "r");
+	if (!fp) {
+		fprintf(stderr,
+			"Failed to open mounts file: %s\n", strerror(errno));
+		return NULL;
+	}
+
+	while (fscanf(fp, "%*s %4095s %127s %*s %*d %*d\n",
+		      path, fstype) == 2) {
+		if (strcmp(fstype, fs_to_find) == 0) {
+			mnt = strdup(path);
+			break;
+		}
+	}
+
+	fclose(fp);
+
+	return mnt;
+}
+
+/* caller needs to free string returned */
+char *find_cgroup2_mount(void)
+{
+	char *mnt = find_fs_mount(CGROUP2_FS_NAME);
+
+	if (mnt)
+		return mnt;
+
+	mnt = strdup(MNT_CGRP2_PATH);
+	if (!mnt) {
+		fprintf(stderr, "Failed to allocate memory for cgroup2 path\n");
+		return NULL;
+
+	}
+
+	if (make_path(mnt, 0755)) {
+		fprintf(stderr, "Failed to setup vrf cgroup2 directory\n");
+		free(mnt);
+		return NULL;
+	}
+
+	if (mount("none", mnt, CGROUP2_FS_NAME, 0, NULL)) {
+		/* EBUSY means already mounted */
+		if (errno == EBUSY)
+			goto out;
+
+		if (errno == ENODEV) {
+			fprintf(stderr,
+				"Failed to mount cgroup2. Are CGROUPS enabled in your kernel?\n");
+		} else {
+			fprintf(stderr,
+				"Failed to mount cgroup2: %s\n",
+				strerror(errno));
+		}
+		free(mnt);
+		return NULL;
+	}
+out:
+	return mnt;
+}
+
+int make_path(const char *path, mode_t mode)
+{
+	char *dir, *delim;
+	int rc = -1;
+
+	delim = dir = strdup(path);
+	if (dir == NULL) {
+		fprintf(stderr, "strdup failed copying path");
+		return -1;
+	}
+
+	/* skip '/' -- it had better exist */
+	if (*delim == '/')
+		delim++;
+
+	while (1) {
+		delim = strchr(delim, '/');
+		if (delim)
+			*delim = '\0';
+
+		rc = mkdir(dir, mode);
+		if (mkdir(dir, mode) != 0 && errno != EEXIST) {
+			fprintf(stderr, "mkdir failed for %s: %s\n",
+				dir, strerror(errno));
+			goto out;
+		}
+
+		if (delim == NULL)
+			break;
+
+		*delim = '/';
+		delim++;
+		if (*delim == '\0')
+			break;
+	}
+	rc = 0;
+out:
+	free(dir);
+
+	return rc;
+}
+
+int get_command_name(const char *pid, char *comm, size_t len)
+{
+	char path[PATH_MAX];
+	char line[128];
+	FILE *fp;
+
+	if (snprintf(path, sizeof(path),
+		     "/proc/%s/status", pid) >= sizeof(path)) {
+		return -1;
+	}
+
+	fp = fopen(path, "r");
+	if (!fp)
+		return -1;
+
+	comm[0] = '\0';
+	while (fgets(line, sizeof(line), fp)) {
+		char *nl, *name;
+
+		name = strstr(line, "Name:");
+		if (!name)
+			continue;
+
+		name += 5;
+		while (isspace(*name))
+			name++;
+
+		nl = strchr(name, '\n');
+		if (nl)
+			*nl = '\0';
+
+		strlcpy(comm, name, len);
+		break;
+	}
+
+	fclose(fp);
+
+	return 0;
+}
diff --git a/lib/inet_proto.c b/lib/inet_proto.c
new file mode 100644
index 0000000..41e2e8b
--- /dev/null
+++ b/lib/inet_proto.c
@@ -0,0 +1,69 @@
+/*
+ * inet_proto.c
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+
+#include "rt_names.h"
+#include "utils.h"
+
+const char *inet_proto_n2a(int proto, char *buf, int len)
+{
+	static char *ncache;
+	static int icache = -1;
+	struct protoent *pe;
+
+	if (proto == icache)
+		return ncache;
+
+	pe = getprotobynumber(proto);
+	if (pe && !numeric) {
+		if (icache != -1)
+			free(ncache);
+		icache = proto;
+		ncache = strdup(pe->p_name);
+		strlcpy(buf, pe->p_name, len);
+		return buf;
+	}
+	snprintf(buf, len, "ipproto-%d", proto);
+	return buf;
+}
+
+int inet_proto_a2n(const char *buf)
+{
+	static char *ncache;
+	static int icache = -1;
+	struct protoent *pe;
+	__u8 ret;
+
+	if (icache != -1 && strcmp(ncache, buf) == 0)
+		return icache;
+
+	if (!get_u8(&ret, buf, 10))
+		return ret;
+
+	pe = getprotobyname(buf);
+	if (pe) {
+		if (icache != -1)
+			free(ncache);
+		icache = pe->p_proto;
+		ncache = strdup(pe->p_name);
+		return pe->p_proto;
+	}
+	return -1;
+}
diff --git a/lib/json_print.c b/lib/json_print.c
new file mode 100644
index 0000000..43ea69b
--- /dev/null
+++ b/lib/json_print.c
@@ -0,0 +1,232 @@
+/*
+ * json_print.c		"print regular or json output, based on json_writer".
+ *
+ *             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.
+ *
+ * Authors:    Julien Fortin, <julien@cumulusnetworks.com>
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "utils.h"
+#include "json_print.h"
+
+static json_writer_t *_jw;
+
+#define _IS_JSON_CONTEXT(type) ((type & PRINT_JSON || type & PRINT_ANY) && _jw)
+#define _IS_FP_CONTEXT(type) (!_jw && (type & PRINT_FP || type & PRINT_ANY))
+
+void new_json_obj(int json)
+{
+	if (json) {
+		_jw = jsonw_new(stdout);
+		if (!_jw) {
+			perror("json object");
+			exit(1);
+		}
+		if (pretty)
+			jsonw_pretty(_jw, true);
+		jsonw_start_array(_jw);
+	}
+}
+
+void delete_json_obj(void)
+{
+	if (_jw) {
+		jsonw_end_array(_jw);
+		jsonw_destroy(&_jw);
+	}
+}
+
+bool is_json_context(void)
+{
+	return _jw != NULL;
+}
+
+json_writer_t *get_json_writer(void)
+{
+	return _jw;
+}
+
+void open_json_object(const char *str)
+{
+	if (_IS_JSON_CONTEXT(PRINT_JSON)) {
+		if (str)
+			jsonw_name(_jw, str);
+		jsonw_start_object(_jw);
+	}
+}
+
+void close_json_object(void)
+{
+	if (_IS_JSON_CONTEXT(PRINT_JSON))
+		jsonw_end_object(_jw);
+}
+
+/*
+ * Start json array or string array using
+ * the provided string as json key (if not null)
+ * or as array delimiter in non-json context.
+ */
+void open_json_array(enum output_type type, const char *str)
+{
+	if (_IS_JSON_CONTEXT(type)) {
+		if (str)
+			jsonw_name(_jw, str);
+		jsonw_start_array(_jw);
+	} else if (_IS_FP_CONTEXT(type)) {
+		printf("%s", str);
+	}
+}
+
+/*
+ * End json array or string array
+ */
+void close_json_array(enum output_type type, const char *str)
+{
+	if (_IS_JSON_CONTEXT(type)) {
+		jsonw_end_array(_jw);
+	} else if (_IS_FP_CONTEXT(type)) {
+		printf("%s", str);
+	}
+}
+
+/*
+ * pre-processor directive to generate similar
+ * functions handling different types
+ */
+#define _PRINT_FUNC(type_name, type)					\
+	__attribute__((format(printf, 4, 0)))				\
+	void print_color_##type_name(enum output_type t,		\
+				     enum color_attr color,		\
+				     const char *key,			\
+				     const char *fmt,			\
+				     type value)			\
+	{								\
+		if (_IS_JSON_CONTEXT(t)) {				\
+			if (!key)					\
+				jsonw_##type_name(_jw, value);		\
+			else						\
+				jsonw_##type_name##_field(_jw, key, value); \
+		} else if (_IS_FP_CONTEXT(t)) {				\
+			color_fprintf(stdout, color, fmt, value);          \
+		}							\
+	}
+_PRINT_FUNC(int, int);
+_PRINT_FUNC(s64, int64_t);
+_PRINT_FUNC(hhu, unsigned char);
+_PRINT_FUNC(hu, unsigned short);
+_PRINT_FUNC(uint, unsigned int);
+_PRINT_FUNC(u64, uint64_t);
+_PRINT_FUNC(luint, unsigned long);
+_PRINT_FUNC(lluint, unsigned long long);
+_PRINT_FUNC(float, double);
+#undef _PRINT_FUNC
+
+void print_color_string(enum output_type type,
+			enum color_attr color,
+			const char *key,
+			const char *fmt,
+			const char *value)
+{
+	if (_IS_JSON_CONTEXT(type)) {
+		if (key && !value)
+			jsonw_name(_jw, key);
+		else if (!key && value)
+			jsonw_string(_jw, value);
+		else
+			jsonw_string_field(_jw, key, value);
+	} else if (_IS_FP_CONTEXT(type)) {
+		color_fprintf(stdout, color, fmt, value);
+	}
+}
+
+/*
+ * value's type is bool. When using this function in FP context you can't pass
+ * a value to it, you will need to use "is_json_context()" to have different
+ * branch for json and regular output. grep -r "print_bool" for example
+ */
+void print_color_bool(enum output_type type,
+		      enum color_attr color,
+		      const char *key,
+		      const char *fmt,
+		      bool value)
+{
+	if (_IS_JSON_CONTEXT(type)) {
+		if (key)
+			jsonw_bool_field(_jw, key, value);
+		else
+			jsonw_bool(_jw, value);
+	} else if (_IS_FP_CONTEXT(type)) {
+		color_fprintf(stdout, color, fmt, value ? "true" : "false");
+	}
+}
+
+/*
+ * In JSON context uses hardcode %#x format: 42 -> 0x2a
+ */
+void print_color_0xhex(enum output_type type,
+		       enum color_attr color,
+		       const char *key,
+		       const char *fmt,
+		       unsigned long long hex)
+{
+	if (_IS_JSON_CONTEXT(type)) {
+		SPRINT_BUF(b1);
+
+		snprintf(b1, sizeof(b1), "%#llx", hex);
+		print_string(PRINT_JSON, key, NULL, b1);
+	} else if (_IS_FP_CONTEXT(type)) {
+		color_fprintf(stdout, color, fmt, hex);
+	}
+}
+
+void print_color_hex(enum output_type type,
+		     enum color_attr color,
+		     const char *key,
+		     const char *fmt,
+		     unsigned int hex)
+{
+	if (_IS_JSON_CONTEXT(type)) {
+		SPRINT_BUF(b1);
+
+		snprintf(b1, sizeof(b1), "%x", hex);
+		if (key)
+			jsonw_string_field(_jw, key, b1);
+		else
+			jsonw_string(_jw, b1);
+	} else if (_IS_FP_CONTEXT(type)) {
+		color_fprintf(stdout, color, fmt, hex);
+	}
+}
+
+/*
+ * In JSON context we don't use the argument "value" we simply call jsonw_null
+ * whereas FP context can use "value" to output anything
+ */
+void print_color_null(enum output_type type,
+		      enum color_attr color,
+		      const char *key,
+		      const char *fmt,
+		      const char *value)
+{
+	if (_IS_JSON_CONTEXT(type)) {
+		if (key)
+			jsonw_null_field(_jw, key);
+		else
+			jsonw_null(_jw);
+	} else if (_IS_FP_CONTEXT(type)) {
+		color_fprintf(stdout, color, fmt, value);
+	}
+}
+
+/* Print line separator (if not in JSON mode) */
+void print_nl(void)
+{
+	if (!_jw)
+		printf("%s", _SL_);
+}
diff --git a/lib/json_writer.c b/lib/json_writer.c
new file mode 100644
index 0000000..88c5eb8
--- /dev/null
+++ b/lib/json_writer.c
@@ -0,0 +1,389 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
+/*
+ * Simple streaming JSON writer
+ *
+ * This takes care of the annoying bits of JSON syntax like the commas
+ * after elements
+ *
+ * Authors:	Stephen Hemminger <stephen@networkplumber.org>
+ */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <malloc.h>
+#include <inttypes.h>
+#include <stdint.h>
+
+#include "json_writer.h"
+
+struct json_writer {
+	FILE		*out;	/* output file */
+	unsigned	depth;  /* nesting */
+	bool		pretty; /* optional whitepace */
+	char		sep;	/* either nul or comma */
+};
+
+/* indentation for pretty print */
+static void jsonw_indent(json_writer_t *self)
+{
+	unsigned i;
+	for (i = 0; i < self->depth; ++i)
+		fputs("    ", self->out);
+}
+
+/* end current line and indent if pretty printing */
+static void jsonw_eol(json_writer_t *self)
+{
+	if (!self->pretty)
+		return;
+
+	putc('\n', self->out);
+	jsonw_indent(self);
+}
+
+/* If current object is not empty print a comma */
+static void jsonw_eor(json_writer_t *self)
+{
+	if (self->sep != '\0')
+		putc(self->sep, self->out);
+	self->sep = ',';
+}
+
+
+/* Output JSON encoded string */
+/* Handles C escapes, does not do Unicode */
+static void jsonw_puts(json_writer_t *self, const char *str)
+{
+	putc('"', self->out);
+	for (; *str; ++str)
+		switch (*str) {
+		case '\t':
+			fputs("\\t", self->out);
+			break;
+		case '\n':
+			fputs("\\n", self->out);
+			break;
+		case '\r':
+			fputs("\\r", self->out);
+			break;
+		case '\f':
+			fputs("\\f", self->out);
+			break;
+		case '\b':
+			fputs("\\b", self->out);
+			break;
+		case '\\':
+			fputs("\\\\", self->out);
+			break;
+		case '"':
+			fputs("\\\"", self->out);
+			break;
+		case '\'':
+			fputs("\\\'", self->out);
+			break;
+		default:
+			putc(*str, self->out);
+		}
+	putc('"', self->out);
+}
+
+/* Create a new JSON stream */
+json_writer_t *jsonw_new(FILE *f)
+{
+	json_writer_t *self = malloc(sizeof(*self));
+	if (self) {
+		self->out = f;
+		self->depth = 0;
+		self->pretty = false;
+		self->sep = '\0';
+	}
+	return self;
+}
+
+/* End output to JSON stream */
+void jsonw_destroy(json_writer_t **self_p)
+{
+	json_writer_t *self = *self_p;
+
+	assert(self->depth == 0);
+	fputs("\n", self->out);
+	fflush(self->out);
+	free(self);
+	*self_p = NULL;
+}
+
+void jsonw_pretty(json_writer_t *self, bool on)
+{
+	self->pretty = on;
+}
+
+/* Basic blocks */
+static void jsonw_begin(json_writer_t *self, int c)
+{
+	jsonw_eor(self);
+	putc(c, self->out);
+	++self->depth;
+	self->sep = '\0';
+}
+
+static void jsonw_end(json_writer_t *self, int c)
+{
+	assert(self->depth > 0);
+
+	--self->depth;
+	if (self->sep != '\0')
+		jsonw_eol(self);
+	putc(c, self->out);
+	self->sep = ',';
+}
+
+
+/* Add a JSON property name */
+void jsonw_name(json_writer_t *self, const char *name)
+{
+	jsonw_eor(self);
+	jsonw_eol(self);
+	self->sep = '\0';
+	jsonw_puts(self, name);
+	putc(':', self->out);
+	if (self->pretty)
+		putc(' ', self->out);
+}
+
+__attribute__((format(printf, 2, 3)))
+void jsonw_printf(json_writer_t *self, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	jsonw_eor(self);
+	vfprintf(self->out, fmt, ap);
+	va_end(ap);
+}
+
+/* Collections */
+void jsonw_start_object(json_writer_t *self)
+{
+	jsonw_begin(self, '{');
+}
+
+void jsonw_end_object(json_writer_t *self)
+{
+	jsonw_end(self, '}');
+}
+
+void jsonw_start_array(json_writer_t *self)
+{
+	jsonw_begin(self, '[');
+	if (self->pretty)
+		putc(' ', self->out);
+}
+
+void jsonw_end_array(json_writer_t *self)
+{
+	if (self->pretty && self->sep)
+		putc(' ', self->out);
+	self->sep = '\0';
+	jsonw_end(self, ']');
+}
+
+/* JSON value types */
+void jsonw_string(json_writer_t *self, const char *value)
+{
+	jsonw_eor(self);
+	jsonw_puts(self, value);
+}
+
+void jsonw_bool(json_writer_t *self, bool val)
+{
+	jsonw_printf(self, "%s", val ? "true" : "false");
+}
+
+void jsonw_null(json_writer_t *self)
+{
+	jsonw_printf(self, "null");
+}
+
+void jsonw_float(json_writer_t *self, double num)
+{
+	jsonw_printf(self, "%g", num);
+}
+
+void jsonw_hhu(json_writer_t *self, unsigned char num)
+{
+	jsonw_printf(self, "%hhu", num);
+}
+
+void jsonw_hu(json_writer_t *self, unsigned short num)
+{
+	jsonw_printf(self, "%hu", num);
+}
+
+void jsonw_uint(json_writer_t *self, unsigned int num)
+{
+	jsonw_printf(self, "%u", num);
+}
+
+void jsonw_u64(json_writer_t *self, uint64_t num)
+{
+	jsonw_printf(self, "%"PRIu64, num);
+}
+
+void jsonw_xint(json_writer_t *self, uint64_t num)
+{
+	jsonw_printf(self, "%"PRIx64, num);
+}
+
+void jsonw_luint(json_writer_t *self, unsigned long num)
+{
+	jsonw_printf(self, "%lu", num);
+}
+
+void jsonw_lluint(json_writer_t *self, unsigned long long num)
+{
+	jsonw_printf(self, "%llu", num);
+}
+
+void jsonw_int(json_writer_t *self, int num)
+{
+	jsonw_printf(self, "%d", num);
+}
+
+void jsonw_s64(json_writer_t *self, int64_t num)
+{
+	jsonw_printf(self, "%"PRId64, num);
+}
+
+/* Basic name/value objects */
+void jsonw_string_field(json_writer_t *self, const char *prop, const char *val)
+{
+	jsonw_name(self, prop);
+	jsonw_string(self, val);
+}
+
+void jsonw_bool_field(json_writer_t *self, const char *prop, bool val)
+{
+	jsonw_name(self, prop);
+	jsonw_bool(self, val);
+}
+
+void jsonw_float_field(json_writer_t *self, const char *prop, double val)
+{
+	jsonw_name(self, prop);
+	jsonw_float(self, val);
+}
+
+void jsonw_uint_field(json_writer_t *self, const char *prop, unsigned int num)
+{
+	jsonw_name(self, prop);
+	jsonw_uint(self, num);
+}
+
+void jsonw_u64_field(json_writer_t *self, const char *prop, uint64_t num)
+{
+	jsonw_name(self, prop);
+	jsonw_u64(self, num);
+}
+
+void jsonw_xint_field(json_writer_t *self, const char *prop, uint64_t num)
+{
+	jsonw_name(self, prop);
+	jsonw_xint(self, num);
+}
+
+void jsonw_hhu_field(json_writer_t *self, const char *prop, unsigned char num)
+{
+	jsonw_name(self, prop);
+	jsonw_hhu(self, num);
+}
+
+void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num)
+{
+	jsonw_name(self, prop);
+	jsonw_hu(self, num);
+}
+
+void jsonw_luint_field(json_writer_t *self,
+			const char *prop,
+			unsigned long num)
+{
+	jsonw_name(self, prop);
+	jsonw_luint(self, num);
+}
+
+void jsonw_lluint_field(json_writer_t *self,
+			const char *prop,
+			unsigned long long num)
+{
+	jsonw_name(self, prop);
+	jsonw_lluint(self, num);
+}
+
+void jsonw_int_field(json_writer_t *self, const char *prop, int num)
+{
+	jsonw_name(self, prop);
+	jsonw_int(self, num);
+}
+
+void jsonw_s64_field(json_writer_t *self, const char *prop, int64_t num)
+{
+	jsonw_name(self, prop);
+	jsonw_s64(self, num);
+}
+
+void jsonw_null_field(json_writer_t *self, const char *prop)
+{
+	jsonw_name(self, prop);
+	jsonw_null(self);
+}
+
+#ifdef TEST
+int main(int argc, char **argv)
+{
+	json_writer_t *wr = jsonw_new(stdout);
+
+	jsonw_start_object(wr);
+	jsonw_pretty(wr, true);
+	jsonw_name(wr, "Vyatta");
+	jsonw_start_object(wr);
+	jsonw_string_field(wr, "url", "http://vyatta.com");
+	jsonw_uint_field(wr, "downloads", 2000000ul);
+	jsonw_float_field(wr, "stock", 8.16);
+
+	jsonw_name(wr, "ARGV");
+	jsonw_start_array(wr);
+	while (--argc)
+		jsonw_string(wr, *++argv);
+	jsonw_end_array(wr);
+
+	jsonw_name(wr, "empty");
+	jsonw_start_array(wr);
+	jsonw_end_array(wr);
+
+	jsonw_name(wr, "NIL");
+	jsonw_start_object(wr);
+	jsonw_end_object(wr);
+
+	jsonw_null_field(wr, "my_null");
+
+	jsonw_name(wr, "special chars");
+	jsonw_start_array(wr);
+	jsonw_string_field(wr, "slash", "/");
+	jsonw_string_field(wr, "newline", "\n");
+	jsonw_string_field(wr, "tab", "\t");
+	jsonw_string_field(wr, "ff", "\f");
+	jsonw_string_field(wr, "quote", "\"");
+	jsonw_string_field(wr, "tick", "\'");
+	jsonw_string_field(wr, "backslash", "\\");
+	jsonw_end_array(wr);
+
+	jsonw_end_object(wr);
+
+	jsonw_end_object(wr);
+	jsonw_destroy(&wr);
+	return 0;
+}
+
+#endif
diff --git a/lib/libgenl.c b/lib/libgenl.c
new file mode 100644
index 0000000..f2ce698
--- /dev/null
+++ b/lib/libgenl.c
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * libgenl.c	GENL library
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <linux/genetlink.h>
+#include "libgenl.h"
+
+static int genl_parse_getfamily(struct nlmsghdr *nlh)
+{
+	struct rtattr *tb[CTRL_ATTR_MAX + 1];
+	struct genlmsghdr *ghdr = NLMSG_DATA(nlh);
+	int len = nlh->nlmsg_len;
+	struct rtattr *attrs;
+
+	if (nlh->nlmsg_type != GENL_ID_CTRL) {
+		fprintf(stderr, "Not a controller message, nlmsg_len=%d "
+			"nlmsg_type=0x%x\n", nlh->nlmsg_len, nlh->nlmsg_type);
+		return -1;
+	}
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+
+	if (len < 0) {
+		fprintf(stderr, "wrong controller message len %d\n", len);
+		return -1;
+	}
+
+	if (ghdr->cmd != CTRL_CMD_NEWFAMILY) {
+		fprintf(stderr, "Unknown controller command %d\n", ghdr->cmd);
+		return -1;
+	}
+
+	attrs = (struct rtattr *) ((char *) ghdr + GENL_HDRLEN);
+	parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len);
+
+	if (tb[CTRL_ATTR_FAMILY_ID] == NULL) {
+		fprintf(stderr, "Missing family id TLV\n");
+		return -1;
+	}
+
+	return rta_getattr_u16(tb[CTRL_ATTR_FAMILY_ID]);
+}
+
+int genl_resolve_family(struct rtnl_handle *grth, const char *family)
+{
+	GENL_REQUEST(req, 1024, GENL_ID_CTRL, 0, 0, CTRL_CMD_GETFAMILY,
+		     NLM_F_REQUEST);
+	struct nlmsghdr *answer;
+	int fnum;
+
+	addattr_l(&req.n, sizeof(req), CTRL_ATTR_FAMILY_NAME,
+		  family, strlen(family) + 1);
+
+	if (rtnl_talk(grth, &req.n, &answer) < 0) {
+		fprintf(stderr, "Error talking to the kernel\n");
+		return -2;
+	}
+
+	fnum = genl_parse_getfamily(answer);
+	free(answer);
+
+	return fnum;
+}
+
+int genl_init_handle(struct rtnl_handle *grth, const char *family,
+		     int *genl_family)
+{
+	if (*genl_family >= 0)
+		return 0;
+
+	if (rtnl_open_byproto(grth, 0, NETLINK_GENERIC) < 0) {
+		fprintf(stderr, "Cannot open generic netlink socket\n");
+		return -1;
+	}
+
+	*genl_family = genl_resolve_family(grth, family);
+	if (*genl_family < 0)
+		return -1;
+
+	return 0;
+}
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
new file mode 100644
index 0000000..e02d629
--- /dev/null
+++ b/lib/libnetlink.c
@@ -0,0 +1,1442 @@
+/*
+ * libnetlink.c	RTnetlink service routines.
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <net/if_arp.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/uio.h>
+#include <linux/fib_rules.h>
+#include <linux/if_addrlabel.h>
+#include <linux/if_bridge.h>
+#include <linux/nexthop.h>
+
+#include "libnetlink.h"
+
+#define __aligned(x)		__attribute__((aligned(x)))
+
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+int rcvbuf = 1024 * 1024;
+
+#ifdef HAVE_LIBMNL
+#include <libmnl/libmnl.h>
+
+static const enum mnl_attr_data_type extack_policy[NLMSGERR_ATTR_MAX + 1] = {
+	[NLMSGERR_ATTR_MSG]	= MNL_TYPE_NUL_STRING,
+	[NLMSGERR_ATTR_OFFS]	= MNL_TYPE_U32,
+};
+
+static int err_attr_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	uint16_t type;
+
+	if (mnl_attr_type_valid(attr, NLMSGERR_ATTR_MAX) < 0) {
+		fprintf(stderr, "Invalid extack attribute\n");
+		return MNL_CB_ERROR;
+	}
+
+	type = mnl_attr_get_type(attr);
+	if (mnl_attr_validate(attr, extack_policy[type]) < 0) {
+		fprintf(stderr, "extack attribute %d failed validation\n",
+			type);
+		return MNL_CB_ERROR;
+	}
+
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static void print_ext_ack_msg(bool is_err, const char *msg)
+{
+	fprintf(stderr, "%s: %s", is_err ? "Error" : "Warning", msg);
+	if (msg[strlen(msg) - 1] != '.')
+		fprintf(stderr, ".");
+	fprintf(stderr, "\n");
+}
+
+/* dump netlink extended ack error message */
+int nl_dump_ext_ack(const struct nlmsghdr *nlh, nl_ext_ack_fn_t errfn)
+{
+	struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {};
+	const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+	const struct nlmsghdr *err_nlh = NULL;
+	unsigned int hlen = sizeof(*err);
+	const char *msg = NULL;
+	uint32_t off = 0;
+
+	/* no TLVs, nothing to do here */
+	if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
+		return 0;
+
+	/* if NLM_F_CAPPED is set then the inner err msg was capped */
+	if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
+		hlen += mnl_nlmsg_get_payload_len(&err->msg);
+
+	if (mnl_attr_parse(nlh, hlen, err_attr_cb, tb) != MNL_CB_OK)
+		return 0;
+
+	if (tb[NLMSGERR_ATTR_MSG])
+		msg = mnl_attr_get_str(tb[NLMSGERR_ATTR_MSG]);
+
+	if (tb[NLMSGERR_ATTR_OFFS]) {
+		off = mnl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]);
+
+		if (off > nlh->nlmsg_len) {
+			fprintf(stderr,
+				"Invalid offset for NLMSGERR_ATTR_OFFS\n");
+			off = 0;
+		} else if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
+			err_nlh = &err->msg;
+	}
+
+	if (errfn)
+		return errfn(msg, off, err_nlh);
+
+	if (msg && *msg != '\0') {
+		bool is_err = !!err->error;
+
+		print_ext_ack_msg(is_err, msg);
+		return is_err ? 1 : 0;
+	}
+
+	return 0;
+}
+
+int nl_dump_ext_ack_done(const struct nlmsghdr *nlh, int error)
+{
+	struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {};
+	unsigned int hlen = sizeof(int);
+	const char *msg = NULL;
+
+	if (mnl_attr_parse(nlh, hlen, err_attr_cb, tb) != MNL_CB_OK)
+		return 0;
+
+	if (tb[NLMSGERR_ATTR_MSG])
+		msg = mnl_attr_get_str(tb[NLMSGERR_ATTR_MSG]);
+
+	if (msg && *msg != '\0') {
+		bool is_err = !!error;
+
+		print_ext_ack_msg(is_err, msg);
+		return is_err ? 1 : 0;
+	}
+
+	return 0;
+}
+#else
+#warning "libmnl required for error support"
+
+/* No extended error ack without libmnl */
+int nl_dump_ext_ack(const struct nlmsghdr *nlh, nl_ext_ack_fn_t errfn)
+{
+	return 0;
+}
+
+int nl_dump_ext_ack_done(const struct nlmsghdr *nlh, int error)
+{
+	return 0;
+}
+#endif
+
+/* Older kernels may not support strict dump and filtering */
+void rtnl_set_strict_dump(struct rtnl_handle *rth)
+{
+	int one = 1;
+
+	if (setsockopt(rth->fd, SOL_NETLINK, NETLINK_GET_STRICT_CHK,
+		       &one, sizeof(one)) < 0)
+		return;
+
+	rth->flags |= RTNL_HANDLE_F_STRICT_CHK;
+}
+
+int rtnl_add_nl_group(struct rtnl_handle *rth, unsigned int group)
+{
+	return setsockopt(rth->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
+			  &group, sizeof(group));
+}
+
+void rtnl_close(struct rtnl_handle *rth)
+{
+	if (rth->fd >= 0) {
+		close(rth->fd);
+		rth->fd = -1;
+	}
+}
+
+int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions,
+		      int protocol)
+{
+	socklen_t addr_len;
+	int sndbuf = 32768;
+	int one = 1;
+
+	memset(rth, 0, sizeof(*rth));
+
+	rth->proto = protocol;
+	rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
+	if (rth->fd < 0) {
+		perror("Cannot open netlink socket");
+		return -1;
+	}
+
+	if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF,
+		       &sndbuf, sizeof(sndbuf)) < 0) {
+		perror("SO_SNDBUF");
+		return -1;
+	}
+
+	if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF,
+		       &rcvbuf, sizeof(rcvbuf)) < 0) {
+		perror("SO_RCVBUF");
+		return -1;
+	}
+
+	/* Older kernels may no support extended ACK reporting */
+	setsockopt(rth->fd, SOL_NETLINK, NETLINK_EXT_ACK,
+		   &one, sizeof(one));
+
+	memset(&rth->local, 0, sizeof(rth->local));
+	rth->local.nl_family = AF_NETLINK;
+	rth->local.nl_groups = subscriptions;
+
+	if (bind(rth->fd, (struct sockaddr *)&rth->local,
+		 sizeof(rth->local)) < 0) {
+		perror("Cannot bind netlink socket");
+		return -1;
+	}
+	addr_len = sizeof(rth->local);
+	if (getsockname(rth->fd, (struct sockaddr *)&rth->local,
+			&addr_len) < 0) {
+		perror("Cannot getsockname");
+		return -1;
+	}
+	if (addr_len != sizeof(rth->local)) {
+		fprintf(stderr, "Wrong address length %d\n", addr_len);
+		return -1;
+	}
+	if (rth->local.nl_family != AF_NETLINK) {
+		fprintf(stderr, "Wrong address family %d\n",
+			rth->local.nl_family);
+		return -1;
+	}
+	rth->seq = time(NULL);
+	return 0;
+}
+
+int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions)
+{
+	return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
+}
+
+int rtnl_nexthopdump_req(struct rtnl_handle *rth, int family,
+			 req_filter_fn_t filter_fn)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct nhmsg nhm;
+		char buf[128];
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
+		.nlh.nlmsg_type = RTM_GETNEXTHOP,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.nhm.nh_family = family,
+	};
+
+	if (filter_fn) {
+		int err;
+
+		err = filter_fn(&req.nlh, sizeof(req));
+		if (err)
+			return err;
+	}
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_addrdump_req(struct rtnl_handle *rth, int family,
+		      req_filter_fn_t filter_fn)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct ifaddrmsg ifm;
+		char buf[128];
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
+		.nlh.nlmsg_type = RTM_GETADDR,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.ifm.ifa_family = family,
+	};
+
+	if (filter_fn) {
+		int err;
+
+		err = filter_fn(&req.nlh, sizeof(req));
+		if (err)
+			return err;
+	}
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_addrlbldump_req(struct rtnl_handle *rth, int family)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct ifaddrlblmsg ifal;
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrlblmsg)),
+		.nlh.nlmsg_type = RTM_GETADDRLABEL,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.ifal.ifal_family = family,
+	};
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_routedump_req(struct rtnl_handle *rth, int family,
+		       req_filter_fn_t filter_fn)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct rtmsg rtm;
+		char buf[128];
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)),
+		.nlh.nlmsg_type = RTM_GETROUTE,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.rtm.rtm_family = family,
+	};
+
+	if (filter_fn) {
+		int err;
+
+		err = filter_fn(&req.nlh, sizeof(req));
+		if (err)
+			return err;
+	}
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_ruledump_req(struct rtnl_handle *rth, int family)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct fib_rule_hdr frh;
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct fib_rule_hdr)),
+		.nlh.nlmsg_type = RTM_GETRULE,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.frh.family = family
+	};
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_neighdump_req(struct rtnl_handle *rth, int family,
+		       req_filter_fn_t filter_fn)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct ndmsg ndm;
+		char buf[256];
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)),
+		.nlh.nlmsg_type = RTM_GETNEIGH,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.ndm.ndm_family = family,
+	};
+
+	if (filter_fn) {
+		int err;
+
+		err = filter_fn(&req.nlh, sizeof(req));
+		if (err)
+			return err;
+	}
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_neightbldump_req(struct rtnl_handle *rth, int family)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct ndtmsg ndtmsg;
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndtmsg)),
+		.nlh.nlmsg_type = RTM_GETNEIGHTBL,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.ndtmsg.ndtm_family = family,
+	};
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_mdbdump_req(struct rtnl_handle *rth, int family)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct br_port_msg bpm;
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_port_msg)),
+		.nlh.nlmsg_type = RTM_GETMDB,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.bpm.family = family,
+	};
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_netconfdump_req(struct rtnl_handle *rth, int family)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct netconfmsg ncm;
+		char buf[0] __aligned(NLMSG_ALIGNTO);
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(NLMSG_ALIGN(sizeof(struct netconfmsg))),
+		.nlh.nlmsg_type = RTM_GETNETCONF,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.ncm.ncm_family = family,
+	};
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_nsiddump_req_filter_fn(struct rtnl_handle *rth, int family,
+				req_filter_fn_t filter_fn)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct rtgenmsg rtm;
+		char buf[1024];
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(NLMSG_ALIGN(sizeof(struct rtgenmsg))),
+		.nlh.nlmsg_type = RTM_GETNSID,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.rtm.rtgen_family = family,
+	};
+	int err;
+
+	if (!filter_fn)
+		return -EINVAL;
+
+	err = filter_fn(&req.nlh, sizeof(req));
+	if (err)
+		return err;
+
+	return send(rth->fd, &req, req.nlh.nlmsg_len, 0);
+}
+
+static int __rtnl_linkdump_req(struct rtnl_handle *rth, int family)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct ifinfomsg ifm;
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+		.nlh.nlmsg_type = RTM_GETLINK,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.ifm.ifi_family = family,
+	};
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_linkdump_req(struct rtnl_handle *rth, int family)
+{
+	if (family == AF_UNSPEC)
+		return rtnl_linkdump_req_filter(rth, family, RTEXT_FILTER_VF);
+
+	return __rtnl_linkdump_req(rth, family);
+}
+
+int rtnl_linkdump_req_filter(struct rtnl_handle *rth, int family,
+			    __u32 filt_mask)
+{
+	if (family == AF_UNSPEC || family == AF_BRIDGE) {
+		struct {
+			struct nlmsghdr nlh;
+			struct ifinfomsg ifm;
+			/* attribute has to be NLMSG aligned */
+			struct rtattr ext_req __aligned(NLMSG_ALIGNTO);
+			__u32 ext_filter_mask;
+		} req = {
+			.nlh.nlmsg_len = sizeof(req),
+			.nlh.nlmsg_type = RTM_GETLINK,
+			.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+			.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+			.ifm.ifi_family = family,
+			.ext_req.rta_type = IFLA_EXT_MASK,
+			.ext_req.rta_len = RTA_LENGTH(sizeof(__u32)),
+			.ext_filter_mask = filt_mask,
+		};
+
+		return send(rth->fd, &req, sizeof(req), 0);
+	}
+
+	return __rtnl_linkdump_req(rth, family);
+}
+
+int rtnl_linkdump_req_filter_fn(struct rtnl_handle *rth, int family,
+				req_filter_fn_t filter_fn)
+{
+	if (family == AF_UNSPEC || family == AF_PACKET) {
+		struct {
+			struct nlmsghdr nlh;
+			struct ifinfomsg ifm;
+			char buf[1024];
+		} req = {
+			.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+			.nlh.nlmsg_type = RTM_GETLINK,
+			.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+			.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+			.ifm.ifi_family = family,
+		};
+		int err;
+
+		if (!filter_fn)
+			return -EINVAL;
+
+		err = filter_fn(&req.nlh, sizeof(req));
+		if (err)
+			return err;
+
+		return send(rth->fd, &req, req.nlh.nlmsg_len, 0);
+	}
+
+	return __rtnl_linkdump_req(rth, family);
+}
+
+int rtnl_fdb_linkdump_req_filter_fn(struct rtnl_handle *rth,
+				    req_filter_fn_t filter_fn)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct ifinfomsg ifm;
+		char buf[128];
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+		.nlh.nlmsg_type = RTM_GETNEIGH,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.ifm.ifi_family = PF_BRIDGE,
+	};
+	int err;
+
+	err = filter_fn(&req.nlh, sizeof(req));
+	if (err)
+		return err;
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_statsdump_req_filter(struct rtnl_handle *rth, int fam, __u32 filt_mask)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct if_stats_msg ifsm;
+	} req;
+
+	memset(&req, 0, sizeof(req));
+	req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct if_stats_msg));
+	req.nlh.nlmsg_type = RTM_GETSTATS;
+	req.nlh.nlmsg_flags = NLM_F_DUMP|NLM_F_REQUEST;
+	req.nlh.nlmsg_pid = 0;
+	req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
+	req.ifsm.family = fam;
+	req.ifsm.filter_mask = filt_mask;
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
+int rtnl_send(struct rtnl_handle *rth, const void *buf, int len)
+{
+	return send(rth->fd, buf, len, 0);
+}
+
+int rtnl_send_check(struct rtnl_handle *rth, const void *buf, int len)
+{
+	struct nlmsghdr *h;
+	int status;
+	char resp[1024];
+
+	status = send(rth->fd, buf, len, 0);
+	if (status < 0)
+		return status;
+
+	/* Check for immediate errors */
+	status = recv(rth->fd, resp, sizeof(resp), MSG_DONTWAIT|MSG_PEEK);
+	if (status < 0) {
+		if (errno == EAGAIN)
+			return 0;
+		return -1;
+	}
+
+	for (h = (struct nlmsghdr *)resp; NLMSG_OK(h, status);
+	     h = NLMSG_NEXT(h, status)) {
+		if (h->nlmsg_type == NLMSG_ERROR) {
+			struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h);
+
+			if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr)))
+				fprintf(stderr, "ERROR truncated\n");
+			else
+				errno = -err->error;
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len)
+{
+	struct nlmsghdr nlh = {
+		.nlmsg_len = NLMSG_LENGTH(len),
+		.nlmsg_type = type,
+		.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlmsg_seq = rth->dump = ++rth->seq,
+	};
+	struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
+	struct iovec iov[2] = {
+		{ .iov_base = &nlh, .iov_len = sizeof(nlh) },
+		{ .iov_base = req, .iov_len = len }
+	};
+	struct msghdr msg = {
+		.msg_name = &nladdr,
+		.msg_namelen = sizeof(nladdr),
+		.msg_iov = iov,
+		.msg_iovlen = 2,
+	};
+
+	return sendmsg(rth->fd, &msg, 0);
+}
+
+int rtnl_dump_request_n(struct rtnl_handle *rth, struct nlmsghdr *n)
+{
+	struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
+	struct iovec iov = {
+		.iov_base = n,
+		.iov_len = n->nlmsg_len
+	};
+	struct msghdr msg = {
+		.msg_name = &nladdr,
+		.msg_namelen = sizeof(nladdr),
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+
+	n->nlmsg_flags = NLM_F_DUMP|NLM_F_REQUEST;
+	n->nlmsg_pid = 0;
+	n->nlmsg_seq = rth->dump = ++rth->seq;
+
+	return sendmsg(rth->fd, &msg, 0);
+}
+
+static int rtnl_dump_done(struct nlmsghdr *h)
+{
+	int len = *(int *)NLMSG_DATA(h);
+
+	if (h->nlmsg_len < NLMSG_LENGTH(sizeof(int))) {
+		fprintf(stderr, "DONE truncated\n");
+		return -1;
+	}
+
+	if (len < 0) {
+		/* check for any messages returned from kernel */
+		if (nl_dump_ext_ack_done(h, len))
+			return len;
+
+		errno = -len;
+		switch (errno) {
+		case ENOENT:
+		case EOPNOTSUPP:
+			return -1;
+		case EMSGSIZE:
+			fprintf(stderr,
+				"Error: Buffer too small for object.\n");
+			break;
+		default:
+			perror("RTNETLINK answers");
+		}
+		return len;
+	}
+
+	/* check for any messages returned from kernel */
+	nl_dump_ext_ack(h, NULL);
+
+	return 0;
+}
+
+static void rtnl_dump_error(const struct rtnl_handle *rth,
+			    struct nlmsghdr *h)
+{
+
+	if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
+		fprintf(stderr, "ERROR truncated\n");
+	} else {
+		const struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h);
+
+		errno = -err->error;
+		if (rth->proto == NETLINK_SOCK_DIAG &&
+		    (errno == ENOENT ||
+		     errno == EOPNOTSUPP))
+			return;
+
+		if (!(rth->flags & RTNL_HANDLE_F_SUPPRESS_NLERR))
+			perror("RTNETLINK answers");
+	}
+}
+
+static int __rtnl_recvmsg(int fd, struct msghdr *msg, int flags)
+{
+	int len;
+
+	do {
+		len = recvmsg(fd, msg, flags);
+	} while (len < 0 && (errno == EINTR || errno == EAGAIN));
+
+	if (len < 0) {
+		fprintf(stderr, "netlink receive error %s (%d)\n",
+			strerror(errno), errno);
+		return -errno;
+	}
+
+	if (len == 0) {
+		fprintf(stderr, "EOF on netlink\n");
+		return -ENODATA;
+	}
+
+	return len;
+}
+
+static int rtnl_recvmsg(int fd, struct msghdr *msg, char **answer)
+{
+	struct iovec *iov = msg->msg_iov;
+	char *buf;
+	int len;
+
+	iov->iov_base = NULL;
+	iov->iov_len = 0;
+
+	len = __rtnl_recvmsg(fd, msg, MSG_PEEK | MSG_TRUNC);
+	if (len < 0)
+		return len;
+
+	if (len < 32768)
+		len = 32768;
+	buf = malloc(len);
+	if (!buf) {
+		fprintf(stderr, "malloc error: not enough buffer\n");
+		return -ENOMEM;
+	}
+
+	iov->iov_base = buf;
+	iov->iov_len = len;
+
+	len = __rtnl_recvmsg(fd, msg, 0);
+	if (len < 0) {
+		free(buf);
+		return len;
+	}
+
+	if (answer)
+		*answer = buf;
+	else
+		free(buf);
+
+	return len;
+}
+
+static int rtnl_dump_filter_l(struct rtnl_handle *rth,
+			      const struct rtnl_dump_filter_arg *arg)
+{
+	struct sockaddr_nl nladdr;
+	struct iovec iov;
+	struct msghdr msg = {
+		.msg_name = &nladdr,
+		.msg_namelen = sizeof(nladdr),
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	char *buf;
+	int dump_intr = 0;
+
+	while (1) {
+		int status;
+		const struct rtnl_dump_filter_arg *a;
+		int found_done = 0;
+		int msglen = 0;
+
+		status = rtnl_recvmsg(rth->fd, &msg, &buf);
+		if (status < 0)
+			return status;
+
+		if (rth->dump_fp)
+			fwrite(buf, 1, NLMSG_ALIGN(status), rth->dump_fp);
+
+		for (a = arg; a->filter; a++) {
+			struct nlmsghdr *h = (struct nlmsghdr *)buf;
+
+			msglen = status;
+
+			while (NLMSG_OK(h, msglen)) {
+				int err = 0;
+
+				h->nlmsg_flags &= ~a->nc_flags;
+
+				if (nladdr.nl_pid != 0 ||
+				    h->nlmsg_pid != rth->local.nl_pid ||
+				    h->nlmsg_seq != rth->dump)
+					goto skip_it;
+
+				if (h->nlmsg_flags & NLM_F_DUMP_INTR)
+					dump_intr = 1;
+
+				if (h->nlmsg_type == NLMSG_DONE) {
+					err = rtnl_dump_done(h);
+					if (err < 0) {
+						free(buf);
+						return -1;
+					}
+
+					found_done = 1;
+					break; /* process next filter */
+				}
+
+				if (h->nlmsg_type == NLMSG_ERROR) {
+					rtnl_dump_error(rth, h);
+					free(buf);
+					return -1;
+				}
+
+				if (!rth->dump_fp) {
+					err = a->filter(h, a->arg1);
+					if (err < 0) {
+						free(buf);
+						return err;
+					}
+				}
+
+skip_it:
+				h = NLMSG_NEXT(h, msglen);
+			}
+		}
+		free(buf);
+
+		if (found_done) {
+			if (dump_intr)
+				fprintf(stderr,
+					"Dump was interrupted and may be inconsistent.\n");
+			return 0;
+		}
+
+		if (msg.msg_flags & MSG_TRUNC) {
+			fprintf(stderr, "Message truncated\n");
+			continue;
+		}
+		if (msglen) {
+			fprintf(stderr, "!!!Remnant of size %d\n", msglen);
+			exit(1);
+		}
+	}
+}
+
+int rtnl_dump_filter_nc(struct rtnl_handle *rth,
+		     rtnl_filter_t filter,
+		     void *arg1, __u16 nc_flags)
+{
+	const struct rtnl_dump_filter_arg a[2] = {
+		{ .filter = filter, .arg1 = arg1, .nc_flags = nc_flags, },
+		{ .filter = NULL,   .arg1 = NULL, .nc_flags = 0, },
+	};
+
+	return rtnl_dump_filter_l(rth, a);
+}
+
+static void rtnl_talk_error(struct nlmsghdr *h, struct nlmsgerr *err,
+			    nl_ext_ack_fn_t errfn)
+{
+	if (nl_dump_ext_ack(h, errfn))
+		return;
+
+	fprintf(stderr, "RTNETLINK answers: %s\n",
+		strerror(-err->error));
+}
+
+
+static int __rtnl_talk_iov(struct rtnl_handle *rtnl, struct iovec *iov,
+			   size_t iovlen, struct nlmsghdr **answer,
+			   bool show_rtnl_err, nl_ext_ack_fn_t errfn)
+{
+	struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
+	struct iovec riov;
+	struct msghdr msg = {
+		.msg_name = &nladdr,
+		.msg_namelen = sizeof(nladdr),
+		.msg_iov = iov,
+		.msg_iovlen = iovlen,
+	};
+	unsigned int seq = 0;
+	struct nlmsghdr *h;
+	int i, status;
+	char *buf;
+
+	for (i = 0; i < iovlen; i++) {
+		h = iov[i].iov_base;
+		h->nlmsg_seq = seq = ++rtnl->seq;
+		if (answer == NULL)
+			h->nlmsg_flags |= NLM_F_ACK;
+	}
+
+	status = sendmsg(rtnl->fd, &msg, 0);
+	if (status < 0) {
+		perror("Cannot talk to rtnetlink");
+		return -1;
+	}
+
+	/* change msg to use the response iov */
+	msg.msg_iov = &riov;
+	msg.msg_iovlen = 1;
+	i = 0;
+	while (1) {
+next:
+		status = rtnl_recvmsg(rtnl->fd, &msg, &buf);
+		++i;
+
+		if (status < 0)
+			return status;
+
+		if (msg.msg_namelen != sizeof(nladdr)) {
+			fprintf(stderr,
+				"sender address length == %d\n",
+				msg.msg_namelen);
+			exit(1);
+		}
+		for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); ) {
+			int len = h->nlmsg_len;
+			int l = len - sizeof(*h);
+
+			if (l < 0 || len > status) {
+				if (msg.msg_flags & MSG_TRUNC) {
+					fprintf(stderr, "Truncated message\n");
+					free(buf);
+					return -1;
+				}
+				fprintf(stderr,
+					"!!!malformed message: len=%d\n",
+					len);
+				exit(1);
+			}
+
+			if (nladdr.nl_pid != 0 ||
+			    h->nlmsg_pid != rtnl->local.nl_pid ||
+			    h->nlmsg_seq > seq || h->nlmsg_seq < seq - iovlen) {
+				/* Don't forget to skip that message. */
+				status -= NLMSG_ALIGN(len);
+				h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
+				continue;
+			}
+
+			if (h->nlmsg_type == NLMSG_ERROR) {
+				struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h);
+				int error = err->error;
+
+				if (l < sizeof(struct nlmsgerr)) {
+					fprintf(stderr, "ERROR truncated\n");
+					free(buf);
+					return -1;
+				}
+
+				if (!error) {
+					/* check messages from kernel */
+					nl_dump_ext_ack(h, errfn);
+				} else {
+					errno = -error;
+
+					if (rtnl->proto != NETLINK_SOCK_DIAG &&
+					    show_rtnl_err)
+						rtnl_talk_error(h, err, errfn);
+				}
+
+				if (answer)
+					*answer = (struct nlmsghdr *)buf;
+				else
+					free(buf);
+
+				if (i < iovlen)
+					goto next;
+				return error ? -i : 0;
+			}
+
+			if (answer) {
+				*answer = (struct nlmsghdr *)buf;
+				return 0;
+			}
+
+			fprintf(stderr, "Unexpected reply!!!\n");
+
+			status -= NLMSG_ALIGN(len);
+			h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
+		}
+		free(buf);
+
+		if (msg.msg_flags & MSG_TRUNC) {
+			fprintf(stderr, "Message truncated\n");
+			continue;
+		}
+
+		if (status) {
+			fprintf(stderr, "!!!Remnant of size %d\n", status);
+			exit(1);
+		}
+	}
+}
+
+static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
+		       struct nlmsghdr **answer,
+		       bool show_rtnl_err, nl_ext_ack_fn_t errfn)
+{
+	struct iovec iov = {
+		.iov_base = n,
+		.iov_len = n->nlmsg_len
+	};
+
+	return __rtnl_talk_iov(rtnl, &iov, 1, answer, show_rtnl_err, errfn);
+}
+
+int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
+	      struct nlmsghdr **answer)
+{
+	return __rtnl_talk(rtnl, n, answer, true, NULL);
+}
+
+int rtnl_talk_iov(struct rtnl_handle *rtnl, struct iovec *iovec, size_t iovlen,
+		  struct nlmsghdr **answer)
+{
+	return __rtnl_talk_iov(rtnl, iovec, iovlen, answer, true, NULL);
+}
+
+int rtnl_talk_suppress_rtnl_errmsg(struct rtnl_handle *rtnl, struct nlmsghdr *n,
+				   struct nlmsghdr **answer)
+{
+	return __rtnl_talk(rtnl, n, answer, false, NULL);
+}
+
+int rtnl_listen_all_nsid(struct rtnl_handle *rth)
+{
+	unsigned int on = 1;
+
+	if (setsockopt(rth->fd, SOL_NETLINK, NETLINK_LISTEN_ALL_NSID, &on,
+		       sizeof(on)) < 0) {
+		perror("NETLINK_LISTEN_ALL_NSID");
+		return -1;
+	}
+	rth->flags |= RTNL_HANDLE_F_LISTEN_ALL_NSID;
+	return 0;
+}
+
+int rtnl_listen(struct rtnl_handle *rtnl,
+		rtnl_listen_filter_t handler,
+		void *jarg)
+{
+	int status;
+	struct nlmsghdr *h;
+	struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
+	struct iovec iov;
+	struct msghdr msg = {
+		.msg_name = &nladdr,
+		.msg_namelen = sizeof(nladdr),
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	char   buf[16384];
+	char   cmsgbuf[BUFSIZ];
+
+	if (rtnl->flags & RTNL_HANDLE_F_LISTEN_ALL_NSID) {
+		msg.msg_control = &cmsgbuf;
+		msg.msg_controllen = sizeof(cmsgbuf);
+	}
+
+	iov.iov_base = buf;
+	while (1) {
+		struct rtnl_ctrl_data ctrl;
+		struct cmsghdr *cmsg;
+
+		iov.iov_len = sizeof(buf);
+		status = recvmsg(rtnl->fd, &msg, 0);
+
+		if (status < 0) {
+			if (errno == EINTR || errno == EAGAIN)
+				continue;
+			fprintf(stderr, "netlink receive error %s (%d)\n",
+				strerror(errno), errno);
+			if (errno == ENOBUFS)
+				continue;
+			return -1;
+		}
+		if (status == 0) {
+			fprintf(stderr, "EOF on netlink\n");
+			return -1;
+		}
+		if (msg.msg_namelen != sizeof(nladdr)) {
+			fprintf(stderr,
+				"Sender address length == %d\n",
+				msg.msg_namelen);
+			exit(1);
+		}
+
+		if (rtnl->flags & RTNL_HANDLE_F_LISTEN_ALL_NSID) {
+			memset(&ctrl, 0, sizeof(ctrl));
+			ctrl.nsid = -1;
+			for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
+			     cmsg = CMSG_NXTHDR(&msg, cmsg))
+				if (cmsg->cmsg_level == SOL_NETLINK &&
+				    cmsg->cmsg_type == NETLINK_LISTEN_ALL_NSID &&
+				    cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
+					int *data = (int *)CMSG_DATA(cmsg);
+
+					ctrl.nsid = *data;
+				}
+		}
+
+		for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); ) {
+			int err;
+			int len = h->nlmsg_len;
+			int l = len - sizeof(*h);
+
+			if (l < 0 || len > status) {
+				if (msg.msg_flags & MSG_TRUNC) {
+					fprintf(stderr, "Truncated message\n");
+					return -1;
+				}
+				fprintf(stderr,
+					"!!!malformed message: len=%d\n",
+					len);
+				exit(1);
+			}
+
+			err = handler(&ctrl, h, jarg);
+			if (err < 0)
+				return err;
+
+			status -= NLMSG_ALIGN(len);
+			h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
+		}
+		if (msg.msg_flags & MSG_TRUNC) {
+			fprintf(stderr, "Message truncated\n");
+			continue;
+		}
+		if (status) {
+			fprintf(stderr, "!!!Remnant of size %d\n", status);
+			exit(1);
+		}
+	}
+}
+
+int rtnl_from_file(FILE *rtnl, rtnl_listen_filter_t handler,
+		   void *jarg)
+{
+	size_t status;
+	char buf[16384];
+	struct nlmsghdr *h = (struct nlmsghdr *)buf;
+
+	while (1) {
+		int err, len;
+		int l;
+
+		status = fread(&buf, 1, sizeof(*h), rtnl);
+
+		if (status == 0 && feof(rtnl))
+			return 0;
+		if (status != sizeof(*h)) {
+			if (ferror(rtnl))
+				perror("rtnl_from_file: fread");
+			if (feof(rtnl))
+				fprintf(stderr, "rtnl-from_file: truncated message\n");
+			return -1;
+		}
+
+		len = h->nlmsg_len;
+		l = len - sizeof(*h);
+
+		if (l < 0 || len > sizeof(buf)) {
+			fprintf(stderr, "!!!malformed message: len=%d @%lu\n",
+				len, ftell(rtnl));
+			return -1;
+		}
+
+		status = fread(NLMSG_DATA(h), 1, NLMSG_ALIGN(l), rtnl);
+
+		if (status != NLMSG_ALIGN(l)) {
+			if (ferror(rtnl))
+				perror("rtnl_from_file: fread");
+			if (feof(rtnl))
+				fprintf(stderr, "rtnl-from_file: truncated message\n");
+			return -1;
+		}
+
+		err = handler(NULL, h, jarg);
+		if (err < 0)
+			return err;
+	}
+}
+
+int addattr(struct nlmsghdr *n, int maxlen, int type)
+{
+	return addattr_l(n, maxlen, type, NULL, 0);
+}
+
+int addattr8(struct nlmsghdr *n, int maxlen, int type, __u8 data)
+{
+	return addattr_l(n, maxlen, type, &data, sizeof(__u8));
+}
+
+int addattr16(struct nlmsghdr *n, int maxlen, int type, __u16 data)
+{
+	return addattr_l(n, maxlen, type, &data, sizeof(__u16));
+}
+
+int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data)
+{
+	return addattr_l(n, maxlen, type, &data, sizeof(__u32));
+}
+
+int addattr64(struct nlmsghdr *n, int maxlen, int type, __u64 data)
+{
+	return addattr_l(n, maxlen, type, &data, sizeof(__u64));
+}
+
+int addattrstrz(struct nlmsghdr *n, int maxlen, int type, const char *str)
+{
+	return addattr_l(n, maxlen, type, str, strlen(str)+1);
+}
+
+int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data,
+	      int alen)
+{
+	int len = RTA_LENGTH(alen);
+	struct rtattr *rta;
+
+	if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) {
+		fprintf(stderr,
+			"addattr_l ERROR: message exceeded bound of %d\n",
+			maxlen);
+		return -1;
+	}
+	rta = NLMSG_TAIL(n);
+	rta->rta_type = type;
+	rta->rta_len = len;
+	if (alen)
+		memcpy(RTA_DATA(rta), data, alen);
+	n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
+	return 0;
+}
+
+int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len)
+{
+	if (NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len) > maxlen) {
+		fprintf(stderr,
+			"addraw_l ERROR: message exceeded bound of %d\n",
+			maxlen);
+		return -1;
+	}
+
+	memcpy(NLMSG_TAIL(n), data, len);
+	memset((void *) NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len);
+	n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len);
+	return 0;
+}
+
+struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type)
+{
+	struct rtattr *nest = NLMSG_TAIL(n);
+
+	addattr_l(n, maxlen, type, NULL, 0);
+	return nest;
+}
+
+int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest)
+{
+	nest->rta_len = (void *)NLMSG_TAIL(n) - (void *)nest;
+	return n->nlmsg_len;
+}
+
+struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, int type,
+				   const void *data, int len)
+{
+	struct rtattr *start = NLMSG_TAIL(n);
+
+	addattr_l(n, maxlen, type, data, len);
+	addattr_nest(n, maxlen, type);
+	return start;
+}
+
+int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *start)
+{
+	struct rtattr *nest = (void *)start + NLMSG_ALIGN(start->rta_len);
+
+	start->rta_len = (void *)NLMSG_TAIL(n) - (void *)start;
+	addattr_nest_end(n, nest);
+	return n->nlmsg_len;
+}
+
+int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data)
+{
+	int len = RTA_LENGTH(4);
+	struct rtattr *subrta;
+
+	if (RTA_ALIGN(rta->rta_len) + len > maxlen) {
+		fprintf(stderr,
+			"rta_addattr32: Error! max allowed bound %d exceeded\n",
+			maxlen);
+		return -1;
+	}
+	subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len));
+	subrta->rta_type = type;
+	subrta->rta_len = len;
+	memcpy(RTA_DATA(subrta), &data, 4);
+	rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len;
+	return 0;
+}
+
+int rta_addattr_l(struct rtattr *rta, int maxlen, int type,
+		  const void *data, int alen)
+{
+	struct rtattr *subrta;
+	int len = RTA_LENGTH(alen);
+
+	if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen) {
+		fprintf(stderr,
+			"rta_addattr_l: Error! max allowed bound %d exceeded\n",
+			maxlen);
+		return -1;
+	}
+	subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len));
+	subrta->rta_type = type;
+	subrta->rta_len = len;
+	if (alen)
+		memcpy(RTA_DATA(subrta), data, alen);
+	rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len);
+	return 0;
+}
+
+int rta_addattr8(struct rtattr *rta, int maxlen, int type, __u8 data)
+{
+	return rta_addattr_l(rta, maxlen, type, &data, sizeof(__u8));
+}
+
+int rta_addattr16(struct rtattr *rta, int maxlen, int type, __u16 data)
+{
+	return rta_addattr_l(rta, maxlen, type, &data, sizeof(__u16));
+}
+
+int rta_addattr64(struct rtattr *rta, int maxlen, int type, __u64 data)
+{
+	return rta_addattr_l(rta, maxlen, type, &data, sizeof(__u64));
+}
+
+struct rtattr *rta_nest(struct rtattr *rta, int maxlen, int type)
+{
+	struct rtattr *nest = RTA_TAIL(rta);
+
+	rta_addattr_l(rta, maxlen, type, NULL, 0);
+	nest->rta_type |= NLA_F_NESTED;
+
+	return nest;
+}
+
+int rta_nest_end(struct rtattr *rta, struct rtattr *nest)
+{
+	nest->rta_len = (void *)RTA_TAIL(rta) - (void *)nest;
+
+	return rta->rta_len;
+}
+
+int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
+{
+	return parse_rtattr_flags(tb, max, rta, len, 0);
+}
+
+int parse_rtattr_flags(struct rtattr *tb[], int max, struct rtattr *rta,
+		       int len, unsigned short flags)
+{
+	unsigned short type;
+
+	memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
+	while (RTA_OK(rta, len)) {
+		type = rta->rta_type & ~flags;
+		if ((type <= max) && (!tb[type]))
+			tb[type] = rta;
+		rta = RTA_NEXT(rta, len);
+	}
+	if (len)
+		fprintf(stderr, "!!!Deficit %d, rta_len=%d\n",
+			len, rta->rta_len);
+	return 0;
+}
+
+struct rtattr *parse_rtattr_one(int type, struct rtattr *rta, int len)
+{
+	while (RTA_OK(rta, len)) {
+		if (rta->rta_type == type)
+			return rta;
+		rta = RTA_NEXT(rta, len);
+	}
+
+	if (len)
+		fprintf(stderr, "!!!Deficit %d, rta_len=%d\n",
+			len, rta->rta_len);
+	return NULL;
+}
+
+int __parse_rtattr_nested_compat(struct rtattr *tb[], int max,
+				 struct rtattr *rta,
+				 int len)
+{
+	if (RTA_PAYLOAD(rta) < len)
+		return -1;
+	if (RTA_PAYLOAD(rta) >= RTA_ALIGN(len) + sizeof(struct rtattr)) {
+		rta = RTA_DATA(rta) + RTA_ALIGN(len);
+		return parse_rtattr_nested(tb, max, rta);
+	}
+	memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
+	return 0;
+}
diff --git a/lib/ll_addr.c b/lib/ll_addr.c
new file mode 100644
index 0000000..00b562a
--- /dev/null
+++ b/lib/ll_addr.c
@@ -0,0 +1,89 @@
+/*
+ * ll_addr.c
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/sockios.h>
+
+#include "rt_names.h"
+#include "utils.h"
+
+const char *ll_addr_n2a(const unsigned char *addr, int alen, int type,
+			char *buf, int blen)
+{
+	int i;
+	int l;
+
+	if (alen == 4 &&
+	    (type == ARPHRD_TUNNEL || type == ARPHRD_SIT
+	     || type == ARPHRD_IPGRE))
+		return inet_ntop(AF_INET, addr, buf, blen);
+
+	if (alen == 16 && (type == ARPHRD_TUNNEL6 || type == ARPHRD_IP6GRE))
+		return inet_ntop(AF_INET6, addr, buf, blen);
+
+	snprintf(buf, blen, "%02x", addr[0]);
+	for (i = 1, l = 2; i < alen && l < blen; i++, l += 3)
+		snprintf(buf + l, blen - l, ":%02x", addr[i]);
+	return buf;
+}
+
+/*NB: lladdr is char * (rather than u8 *) because sa_data is char * (1003.1g) */
+int ll_addr_a2n(char *lladdr, int len, const char *arg)
+{
+	if (strchr(arg, '.')) {
+		inet_prefix pfx;
+		if (get_addr_1(&pfx, arg, AF_INET)) {
+			fprintf(stderr, "\"%s\" is invalid lladdr.\n", arg);
+			return -1;
+		}
+		if (len < 4)
+			return -1;
+		memcpy(lladdr, pfx.data, 4);
+		return 4;
+	} else {
+		int i;
+
+		for (i = 0; i < len; i++) {
+			int temp;
+			char *cp = strchr(arg, ':');
+			if (cp) {
+				*cp = 0;
+				cp++;
+			}
+			if (sscanf(arg, "%x", &temp) != 1) {
+				fprintf(stderr, "\"%s\" is invalid lladdr.\n",
+					arg);
+				return -1;
+			}
+			if (temp < 0 || temp > 255) {
+				fprintf(stderr, "\"%s\" is invalid lladdr.\n",
+					arg);
+				return -1;
+			}
+			lladdr[i] = temp;
+			if (!cp)
+				break;
+			arg = cp;
+		}
+		return i + 1;
+	}
+}
diff --git a/lib/ll_map.c b/lib/ll_map.c
new file mode 100644
index 0000000..e0ed54b
--- /dev/null
+++ b/lib/ll_map.c
@@ -0,0 +1,295 @@
+/*
+ * ll_map.c
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <net/if.h>
+
+#include "libnetlink.h"
+#include "ll_map.h"
+#include "list.h"
+
+struct ll_cache {
+	struct hlist_node idx_hash;
+	struct hlist_node name_hash;
+	unsigned	flags;
+	unsigned 	index;
+	unsigned short	type;
+	char		name[];
+};
+
+#define IDXMAP_SIZE	1024
+static struct hlist_head idx_head[IDXMAP_SIZE];
+static struct hlist_head name_head[IDXMAP_SIZE];
+
+static struct ll_cache *ll_get_by_index(unsigned index)
+{
+	struct hlist_node *n;
+	unsigned h = index & (IDXMAP_SIZE - 1);
+
+	hlist_for_each(n, &idx_head[h]) {
+		struct ll_cache *im
+			= container_of(n, struct ll_cache, idx_hash);
+		if (im->index == index)
+			return im;
+	}
+
+	return NULL;
+}
+
+unsigned namehash(const char *str)
+{
+	unsigned hash = 5381;
+
+	while (*str)
+		hash = ((hash << 5) + hash) + *str++; /* hash * 33 + c */
+
+	return hash;
+}
+
+static struct ll_cache *ll_get_by_name(const char *name)
+{
+	struct hlist_node *n;
+	unsigned h = namehash(name) & (IDXMAP_SIZE - 1);
+
+	hlist_for_each(n, &name_head[h]) {
+		struct ll_cache *im
+			= container_of(n, struct ll_cache, name_hash);
+
+		if (strncmp(im->name, name, IFNAMSIZ) == 0)
+			return im;
+	}
+
+	return NULL;
+}
+
+int ll_remember_index(struct nlmsghdr *n, void *arg)
+{
+	unsigned int h;
+	const char *ifname;
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct ll_cache *im;
+	struct rtattr *tb[IFLA_MAX+1];
+
+	if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+		return 0;
+
+	if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifi)))
+		return -1;
+
+	im = ll_get_by_index(ifi->ifi_index);
+	if (n->nlmsg_type == RTM_DELLINK) {
+		if (im) {
+			hlist_del(&im->name_hash);
+			hlist_del(&im->idx_hash);
+			free(im);
+		}
+		return 0;
+	}
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n));
+	ifname = rta_getattr_str(tb[IFLA_IFNAME]);
+	if (ifname == NULL)
+		return 0;
+
+	if (im) {
+		/* change to existing entry */
+		if (strcmp(im->name, ifname) != 0) {
+			hlist_del(&im->name_hash);
+			h = namehash(ifname) & (IDXMAP_SIZE - 1);
+			hlist_add_head(&im->name_hash, &name_head[h]);
+		}
+
+		im->flags = ifi->ifi_flags;
+		return 0;
+	}
+
+	im = malloc(sizeof(*im) + strlen(ifname) + 1);
+	if (im == NULL)
+		return 0;
+	im->index = ifi->ifi_index;
+	strcpy(im->name, ifname);
+	im->type = ifi->ifi_type;
+	im->flags = ifi->ifi_flags;
+
+	h = ifi->ifi_index & (IDXMAP_SIZE - 1);
+	hlist_add_head(&im->idx_hash, &idx_head[h]);
+
+	h = namehash(ifname) & (IDXMAP_SIZE - 1);
+	hlist_add_head(&im->name_hash, &name_head[h]);
+
+	return 0;
+}
+
+const char *ll_idx_n2a(unsigned int idx)
+{
+	static char buf[IFNAMSIZ];
+
+	snprintf(buf, sizeof(buf), "if%u", idx);
+	return buf;
+}
+
+static unsigned int ll_idx_a2n(const char *name)
+{
+	unsigned int idx;
+
+	if (sscanf(name, "if%u", &idx) != 1)
+		return 0;
+	return idx;
+}
+
+static int ll_link_get(const char *name, int index)
+{
+	struct {
+		struct nlmsghdr		n;
+		struct ifinfomsg	ifm;
+		char			buf[1024];
+	} req = {
+		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+		.n.nlmsg_flags = NLM_F_REQUEST,
+		.n.nlmsg_type = RTM_GETLINK,
+		.ifm.ifi_index = index,
+	};
+	__u32 filt_mask = RTEXT_FILTER_VF | RTEXT_FILTER_SKIP_STATS;
+	struct rtnl_handle rth = {};
+	struct nlmsghdr *answer;
+	int rc = 0;
+
+	if (rtnl_open(&rth, 0) < 0)
+		return 0;
+
+	addattr32(&req.n, sizeof(req), IFLA_EXT_MASK, filt_mask);
+	if (name)
+		addattr_l(&req.n, sizeof(req), IFLA_IFNAME, name,
+			  strlen(name) + 1);
+
+	if (rtnl_talk_suppress_rtnl_errmsg(&rth, &req.n, &answer) < 0)
+		goto out;
+
+	/* add entry to cache */
+	rc  = ll_remember_index(answer, NULL);
+	if (!rc) {
+		struct ifinfomsg *ifm = NLMSG_DATA(answer);
+
+		rc = ifm->ifi_index;
+	}
+
+	free(answer);
+out:
+	rtnl_close(&rth);
+	return rc;
+}
+
+const char *ll_index_to_name(unsigned int idx)
+{
+	static char buf[IFNAMSIZ];
+	const struct ll_cache *im;
+
+	if (idx == 0)
+		return "*";
+
+	im = ll_get_by_index(idx);
+	if (im)
+		return im->name;
+
+	if (ll_link_get(NULL, idx) == idx) {
+		im = ll_get_by_index(idx);
+		if (im)
+			return im->name;
+	}
+
+	if (if_indextoname(idx, buf) == NULL)
+		snprintf(buf, IFNAMSIZ, "if%u", idx);
+
+	return buf;
+}
+
+int ll_index_to_type(unsigned idx)
+{
+	const struct ll_cache *im;
+
+	if (idx == 0)
+		return -1;
+
+	im = ll_get_by_index(idx);
+	return im ? im->type : -1;
+}
+
+int ll_index_to_flags(unsigned idx)
+{
+	const struct ll_cache *im;
+
+	if (idx == 0)
+		return 0;
+
+	im = ll_get_by_index(idx);
+	return im ? im->flags : -1;
+}
+
+unsigned ll_name_to_index(const char *name)
+{
+	const struct ll_cache *im;
+	unsigned idx;
+
+	if (name == NULL)
+		return 0;
+
+	im = ll_get_by_name(name);
+	if (im)
+		return im->index;
+
+	idx = ll_link_get(name, 0);
+	if (idx == 0)
+		idx = if_nametoindex(name);
+	if (idx == 0)
+		idx = ll_idx_a2n(name);
+	return idx;
+}
+
+void ll_drop_by_index(unsigned index)
+{
+	struct ll_cache *im;
+
+	im = ll_get_by_index(index);
+	if (!im)
+		return;
+
+	hlist_del(&im->idx_hash);
+	hlist_del(&im->name_hash);
+
+	free(im);
+}
+
+void ll_init_map(struct rtnl_handle *rth)
+{
+	static int initialized;
+
+	if (initialized)
+		return;
+
+	if (rtnl_linkdump_req(rth, AF_UNSPEC) < 0) {
+		perror("Cannot send dump request");
+		exit(1);
+	}
+
+	if (rtnl_dump_filter(rth, ll_remember_index, NULL) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		exit(1);
+	}
+
+	initialized = 1;
+}
diff --git a/lib/ll_proto.c b/lib/ll_proto.c
new file mode 100644
index 0000000..2a0c1cb
--- /dev/null
+++ b/lib/ll_proto.c
@@ -0,0 +1,117 @@
+/*
+ * ll_proto.c
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/sockios.h>
+
+#include "utils.h"
+#include "rt_names.h"
+
+
+#define __PF(f,n) { ETH_P_##f, #n },
+static const struct {
+	int id;
+	const char *name;
+} llproto_names[] = {
+__PF(LOOP,loop)
+__PF(PUP,pup)
+__PF(PUPAT,pupat)
+__PF(IP,ip)
+__PF(X25,x25)
+__PF(ARP,arp)
+__PF(BPQ,bpq)
+__PF(IEEEPUP,ieeepup)
+__PF(IEEEPUPAT,ieeepupat)
+__PF(DEC,dec)
+__PF(DNA_DL,dna_dl)
+__PF(DNA_RC,dna_rc)
+__PF(DNA_RT,dna_rt)
+__PF(LAT,lat)
+__PF(DIAG,diag)
+__PF(CUST,cust)
+__PF(SCA,sca)
+__PF(RARP,rarp)
+__PF(ATALK,atalk)
+__PF(AARP,aarp)
+__PF(IPX,ipx)
+__PF(IPV6,ipv6)
+__PF(PPP_DISC,ppp_disc)
+__PF(PPP_SES,ppp_ses)
+__PF(ATMMPOA,atmmpoa)
+__PF(ATMFATE,atmfate)
+__PF(802_3,802_3)
+__PF(AX25,ax25)
+__PF(ALL,all)
+__PF(802_2,802_2)
+__PF(SNAP,snap)
+__PF(DDCMP,ddcmp)
+__PF(WAN_PPP,wan_ppp)
+__PF(PPP_MP,ppp_mp)
+__PF(LOCALTALK,localtalk)
+__PF(CAN,can)
+__PF(PPPTALK,ppptalk)
+__PF(TR_802_2,tr_802_2)
+__PF(MOBITEX,mobitex)
+__PF(CONTROL,control)
+__PF(IRDA,irda)
+__PF(ECONET,econet)
+__PF(TIPC,tipc)
+__PF(AOE,aoe)
+__PF(8021Q,802.1Q)
+__PF(8021AD,802.1ad)
+__PF(MPLS_UC,mpls_uc)
+__PF(MPLS_MC,mpls_mc)
+
+{ 0x8100, "802.1Q" },
+{ 0x88cc, "LLDP" },
+{ ETH_P_IP, "ipv4" },
+};
+#undef __PF
+
+
+const char * ll_proto_n2a(unsigned short id, char *buf, int len)
+{
+        int i;
+
+	id = ntohs(id);
+
+        for (i=0; !numeric && i<sizeof(llproto_names)/sizeof(llproto_names[0]); i++) {
+                 if (llproto_names[i].id == id)
+			return llproto_names[i].name;
+	}
+        snprintf(buf, len, "[%d]", id);
+        return buf;
+}
+
+int ll_proto_a2n(unsigned short *id, const char *buf)
+{
+        int i;
+        for (i=0; i < sizeof(llproto_names)/sizeof(llproto_names[0]); i++) {
+                 if (strcasecmp(llproto_names[i].name, buf) == 0) {
+			 *id = htons(llproto_names[i].id);
+			 return 0;
+		 }
+	}
+	if (get_be16(id, buf, 0))
+		return -1;
+	return 0;
+}
diff --git a/lib/ll_types.c b/lib/ll_types.c
new file mode 100644
index 0000000..49da15d
--- /dev/null
+++ b/lib/ll_types.c
@@ -0,0 +1,122 @@
+/*
+ * ll_types.c
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/sockios.h>
+
+#include "rt_names.h"
+#include "utils.h"
+
+const char * ll_type_n2a(int type, char *buf, int len)
+{
+#define __PF(f,n) { ARPHRD_##f, #n },
+static const struct {
+	int type;
+	const char *name;
+} arphrd_names[] = {
+__PF(NETROM,netrom)
+__PF(ETHER,ether)
+__PF(EETHER,eether)
+__PF(AX25,ax25)
+__PF(PRONET,pronet)
+__PF(CHAOS,chaos)
+__PF(IEEE802,ieee802)
+__PF(ARCNET,arcnet)
+__PF(APPLETLK,atalk)
+__PF(DLCI,dlci)
+__PF(ATM,atm)
+__PF(METRICOM,metricom)
+__PF(IEEE1394,ieee1394)
+__PF(INFINIBAND,infiniband)
+__PF(SLIP,slip)
+__PF(CSLIP,cslip)
+__PF(SLIP6,slip6)
+__PF(CSLIP6,cslip6)
+__PF(RSRVD,rsrvd)
+__PF(ADAPT,adapt)
+__PF(ROSE,rose)
+__PF(X25,x25)
+__PF(HWX25,hwx25)
+__PF(CAN,can)
+__PF(PPP,ppp)
+__PF(HDLC,hdlc)
+__PF(LAPB,lapb)
+__PF(DDCMP,ddcmp)
+__PF(RAWHDLC,rawhdlc)
+__PF(TUNNEL,ipip)
+__PF(TUNNEL6,tunnel6)
+__PF(FRAD,frad)
+__PF(SKIP,skip)
+__PF(LOOPBACK,loopback)
+__PF(LOCALTLK,ltalk)
+__PF(FDDI,fddi)
+__PF(BIF,bif)
+__PF(SIT,sit)
+__PF(IPDDP,ip/ddp)
+__PF(IPGRE,gre)
+__PF(PIMREG,pimreg)
+__PF(HIPPI,hippi)
+__PF(ASH,ash)
+__PF(ECONET,econet)
+__PF(IRDA,irda)
+__PF(FCPP,fcpp)
+__PF(FCAL,fcal)
+__PF(FCPL,fcpl)
+__PF(FCFABRIC,fcfb0)
+__PF(FCFABRIC+1,fcfb1)
+__PF(FCFABRIC+2,fcfb2)
+__PF(FCFABRIC+3,fcfb3)
+__PF(FCFABRIC+4,fcfb4)
+__PF(FCFABRIC+5,fcfb5)
+__PF(FCFABRIC+6,fcfb6)
+__PF(FCFABRIC+7,fcfb7)
+__PF(FCFABRIC+8,fcfb8)
+__PF(FCFABRIC+9,fcfb9)
+__PF(FCFABRIC+10,fcfb10)
+__PF(FCFABRIC+11,fcfb11)
+__PF(FCFABRIC+12,fcfb12)
+__PF(IEEE802_TR,tr)
+__PF(IEEE80211,ieee802.11)
+__PF(IEEE80211_PRISM,ieee802.11/prism)
+__PF(IEEE80211_RADIOTAP,ieee802.11/radiotap)
+__PF(IEEE802154, ieee802.15.4)
+__PF(IEEE802154_MONITOR, ieee802.15.4/monitor)
+__PF(PHONET, phonet)
+__PF(PHONET_PIPE, phonet_pipe)
+__PF(CAIF, caif)
+__PF(IP6GRE, gre6)
+__PF(NETLINK, netlink)
+__PF(6LOWPAN, 6lowpan)
+
+__PF(NONE, none)
+__PF(VOID,void)
+};
+#undef __PF
+
+        int i;
+        for (i=0; !numeric && i<sizeof(arphrd_names)/sizeof(arphrd_names[0]); i++) {
+                 if (arphrd_names[i].type == type)
+			return arphrd_names[i].name;
+	}
+        snprintf(buf, len, "[%d]", type);
+        return buf;
+}
diff --git a/lib/mpls_ntop.c b/lib/mpls_ntop.c
new file mode 100644
index 0000000..f8d89f4
--- /dev/null
+++ b/lib/mpls_ntop.c
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <linux/mpls.h>
+
+#include "utils.h"
+
+static const char *mpls_ntop1(const struct mpls_label *addr, char *buf, size_t buflen)
+{
+	size_t destlen = buflen;
+	char *dest = buf;
+	int count = 0;
+
+	while (1) {
+		uint32_t entry = ntohl(addr[count++].entry);
+		uint32_t label = (entry & MPLS_LS_LABEL_MASK) >> MPLS_LS_LABEL_SHIFT;
+		int len = snprintf(dest, destlen, "%u", label);
+
+		if (len >= destlen)
+			break;
+
+		/* Is this the end? */
+		if (entry & MPLS_LS_S_MASK)
+			return buf;
+
+		dest += len;
+		destlen -= len;
+		if (destlen) {
+			*dest = '/';
+			dest++;
+			destlen--;
+		}
+	}
+	errno = -E2BIG;
+	return NULL;
+}
+
+const char *mpls_ntop(int af, const void *addr, char *buf, size_t buflen)
+{
+	switch(af) {
+	case AF_MPLS:
+		errno = 0;
+		return mpls_ntop1((struct mpls_label *)addr, buf, buflen);
+	default:
+		errno = EAFNOSUPPORT;
+	}
+
+	return NULL;
+}
diff --git a/lib/mpls_pton.c b/lib/mpls_pton.c
new file mode 100644
index 0000000..065374e
--- /dev/null
+++ b/lib/mpls_pton.c
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <linux/mpls.h>
+
+#include "utils.h"
+
+
+static int mpls_pton1(const char *name, struct mpls_label *addr,
+		      unsigned int maxlabels)
+{
+	char *endp;
+	unsigned count;
+
+	for (count = 0; count < maxlabels; count++) {
+		unsigned long label;
+
+		label = strtoul(name, &endp, 0);
+		/* Fail when the label value is out or range */
+		if (label >= (1 << 20))
+			return 0;
+
+		if (endp == name) /* no digits */
+			return 0;
+
+		addr->entry = htonl(label << MPLS_LS_LABEL_SHIFT);
+		if (*endp == '\0') {
+			addr->entry |= htonl(1 << MPLS_LS_S_SHIFT);
+			return 1;
+		}
+
+		/* Bad character in the address */
+		if (*endp != '/')
+			return 0;
+
+		name = endp + 1;
+		addr += 1;
+	}
+	/* The address was too long */
+	fprintf(stderr, "Error: too many labels.\n");
+	return 0;
+}
+
+int mpls_pton(int af, const char *src, void *addr, size_t alen)
+{
+	unsigned int maxlabels = alen / sizeof(struct mpls_label);
+	int err;
+
+	switch(af) {
+	case AF_MPLS:
+		errno = 0;
+		err = mpls_pton1(src, (struct mpls_label *)addr, maxlabels);
+		break;
+	default:
+		errno = EAFNOSUPPORT;
+		err = -1;
+	}
+
+	return err;
+}
diff --git a/lib/names.c b/lib/names.c
new file mode 100644
index 0000000..b46ea79
--- /dev/null
+++ b/lib/names.c
@@ -0,0 +1,152 @@
+/*
+ * names.c		db names
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "names.h"
+#include "utils.h"
+
+#define MAX_ENTRIES  256
+#define NAME_MAX_LEN 512
+
+static int read_id_name(FILE *fp, int *id, char *name)
+{
+	char buf[NAME_MAX_LEN];
+	int min, maj;
+
+	while (fgets(buf, sizeof(buf), fp)) {
+		char *p = buf;
+
+		while (*p == ' ' || *p == '\t')
+			p++;
+
+		if (*p == '#' || *p == '\n' || *p == 0)
+			continue;
+
+		if (sscanf(p, "%x:%x %s\n", &maj, &min, name) == 3) {
+			*id = (maj << 16) | min;
+		} else if (sscanf(p, "%x:%x %s #", &maj, &min, name) == 3) {
+			*id = (maj << 16) | min;
+		} else if (sscanf(p, "0x%x %s\n", id, name) != 2 &&
+				sscanf(p, "0x%x %s #", id, name) != 2 &&
+				sscanf(p, "%d %s\n", id, name) != 2 &&
+				sscanf(p, "%d %s #", id, name) != 2) {
+			strcpy(name, p);
+			return -1;
+		}
+		return 1;
+	}
+
+	return 0;
+}
+
+struct db_names *db_names_alloc(void)
+{
+	struct db_names *db;
+
+	db = calloc(1, sizeof(*db));
+	if (!db)
+		return NULL;
+
+	db->size = MAX_ENTRIES;
+	db->hash = calloc(db->size, sizeof(struct db_entry *));
+
+	return db;
+}
+
+int db_names_load(struct db_names *db, const char *path)
+{
+	struct db_entry *entry;
+	FILE *fp;
+	int id;
+	char namebuf[NAME_MAX_LEN] = {0};
+	int ret = -1;
+
+	fp = fopen(path, "r");
+	if (!fp)
+		return -ENOENT;
+
+	while ((ret = read_id_name(fp, &id, &namebuf[0]))) {
+		if (ret == -1) {
+			fprintf(stderr, "Database %s is corrupted at %s\n",
+					path, namebuf);
+			goto Exit;
+		}
+		ret = -1;
+
+		if (id < 0)
+			continue;
+
+		entry = malloc(sizeof(*entry));
+		if (!entry)
+			goto Exit;
+
+		entry->name = strdup(namebuf);
+		if (!entry->name) {
+			free(entry);
+			goto Exit;
+		}
+
+		entry->id   = id;
+		entry->next = db->hash[id & (db->size - 1)];
+		db->hash[id & (db->size - 1)] = entry;
+	}
+	ret = 0;
+
+Exit:
+	fclose(fp);
+	return ret;
+}
+
+void db_names_free(struct db_names *db)
+{
+	int i;
+
+	if (!db)
+		return;
+
+	for (i = 0; i < db->size; i++) {
+		struct db_entry *entry = db->hash[i];
+
+		while (entry) {
+			struct db_entry *next = entry->next;
+
+			free(entry->name);
+			free(entry);
+			entry = next;
+		}
+	}
+
+	free(db->hash);
+	free(db);
+}
+
+char *id_to_name(struct db_names *db, int id, char *name)
+{
+	struct db_entry *entry;
+
+	if (!db)
+		return NULL;
+
+	entry = db->hash[id & (db->size - 1)];
+	while (entry && entry->id != id)
+		entry = entry->next;
+
+	if (entry) {
+		strncpy(name, entry->name, IDNAME_MAX);
+		return name;
+	}
+
+	snprintf(name, IDNAME_MAX, "%d", id);
+	return NULL;
+}
diff --git a/lib/namespace.c b/lib/namespace.c
new file mode 100644
index 0000000..06ae0a4
--- /dev/null
+++ b/lib/namespace.c
@@ -0,0 +1,139 @@
+/*
+ * namespace.c
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ */
+
+#include <sys/statvfs.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <limits.h>
+
+#include "utils.h"
+#include "namespace.h"
+
+static void bind_etc(const char *name)
+{
+	char etc_netns_path[sizeof(NETNS_ETC_DIR) + NAME_MAX];
+	char netns_name[PATH_MAX];
+	char etc_name[PATH_MAX];
+	struct dirent *entry;
+	DIR *dir;
+
+	if (strlen(name) >= NAME_MAX)
+		return;
+
+	snprintf(etc_netns_path, sizeof(etc_netns_path), "%s/%s", NETNS_ETC_DIR, name);
+	dir = opendir(etc_netns_path);
+	if (!dir)
+		return;
+
+	while ((entry = readdir(dir)) != NULL) {
+		if (strcmp(entry->d_name, ".") == 0)
+			continue;
+		if (strcmp(entry->d_name, "..") == 0)
+			continue;
+		snprintf(netns_name, sizeof(netns_name), "%s/%s", etc_netns_path, entry->d_name);
+		snprintf(etc_name, sizeof(etc_name), "/etc/%s", entry->d_name);
+		if (mount(netns_name, etc_name, "none", MS_BIND, NULL) < 0) {
+			fprintf(stderr, "Bind %s -> %s failed: %s\n",
+				netns_name, etc_name, strerror(errno));
+		}
+	}
+	closedir(dir);
+}
+
+int netns_switch(char *name)
+{
+	char net_path[PATH_MAX];
+	int netns;
+	unsigned long mountflags = 0;
+	struct statvfs fsstat;
+
+	snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name);
+	netns = open(net_path, O_RDONLY | O_CLOEXEC);
+	if (netns < 0) {
+		fprintf(stderr, "Cannot open network namespace \"%s\": %s\n",
+			name, strerror(errno));
+		return -1;
+	}
+
+	if (setns(netns, CLONE_NEWNET) < 0) {
+		fprintf(stderr, "setting the network namespace \"%s\" failed: %s\n",
+			name, strerror(errno));
+		close(netns);
+		return -1;
+	}
+	close(netns);
+
+	if (unshare(CLONE_NEWNS) < 0) {
+		fprintf(stderr, "unshare failed: %s\n", strerror(errno));
+		return -1;
+	}
+	/* Don't let any mounts propagate back to the parent */
+	if (mount("", "/", "none", MS_SLAVE | MS_REC, NULL)) {
+		fprintf(stderr, "\"mount --make-rslave /\" failed: %s\n",
+			strerror(errno));
+		return -1;
+	}
+
+	/* Mount a version of /sys that describes the network namespace */
+
+	if (umount2("/sys", MNT_DETACH) < 0) {
+		/* If this fails, perhaps there wasn't a sysfs instance mounted. Good. */
+		if (statvfs("/sys", &fsstat) == 0) {
+			/* We couldn't umount the sysfs, we'll attempt to overlay it.
+			 * A read-only instance can't be shadowed with a read-write one. */
+			if (fsstat.f_flag & ST_RDONLY)
+				mountflags = MS_RDONLY;
+		}
+	}
+	if (mount(name, "/sys", "sysfs", mountflags, NULL) < 0) {
+		fprintf(stderr, "mount of /sys failed: %s\n",strerror(errno));
+		return -1;
+	}
+
+	/* Setup bind mounts for config files in /etc */
+	bind_etc(name);
+	return 0;
+}
+
+int netns_get_fd(const char *name)
+{
+	char pathbuf[PATH_MAX];
+	const char *path, *ptr;
+
+	path = name;
+	ptr = strchr(name, '/');
+	if (!ptr) {
+		snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
+			NETNS_RUN_DIR, name );
+		path = pathbuf;
+	}
+	return open(path, O_RDONLY);
+}
+
+int netns_foreach(int (*func)(char *nsname, void *arg), void *arg)
+{
+	DIR *dir;
+	struct dirent *entry;
+
+	dir = opendir(NETNS_RUN_DIR);
+	if (!dir)
+		return -1;
+
+	while ((entry = readdir(dir)) != NULL) {
+		if (strcmp(entry->d_name, ".") == 0)
+			continue;
+		if (strcmp(entry->d_name, "..") == 0)
+			continue;
+		if (func(entry->d_name, arg))
+			break;
+	}
+
+	closedir(dir);
+	return 0;
+}
diff --git a/lib/rt_names.c b/lib/rt_names.c
new file mode 100644
index 0000000..41cccfb
--- /dev/null
+++ b/lib/rt_names.c
@@ -0,0 +1,683 @@
+/*
+ * rt_names.c		rtnetlink names DB.
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <dirent.h>
+#include <limits.h>
+
+#include <asm/types.h>
+#include <linux/rtnetlink.h>
+
+#include "rt_names.h"
+#include "utils.h"
+
+#define NAME_MAX_LEN 512
+
+int numeric;
+
+struct rtnl_hash_entry {
+	struct rtnl_hash_entry	*next;
+	const char		*name;
+	unsigned int		id;
+};
+
+static int fread_id_name(FILE *fp, int *id, char *namebuf)
+{
+	char buf[NAME_MAX_LEN];
+
+	while (fgets(buf, sizeof(buf), fp)) {
+		char *p = buf;
+
+		while (*p == ' ' || *p == '\t')
+			p++;
+
+		if (*p == '#' || *p == '\n' || *p == 0)
+			continue;
+
+		if (sscanf(p, "0x%x %s\n", id, namebuf) != 2 &&
+				sscanf(p, "0x%x %s #", id, namebuf) != 2 &&
+				sscanf(p, "%d %s\n", id, namebuf) != 2 &&
+				sscanf(p, "%d %s #", id, namebuf) != 2) {
+			strcpy(namebuf, p);
+			return -1;
+		}
+		return 1;
+	}
+	return 0;
+}
+
+static void
+rtnl_hash_initialize(const char *file, struct rtnl_hash_entry **hash, int size)
+{
+	struct rtnl_hash_entry *entry;
+	FILE *fp;
+	int id;
+	char namebuf[NAME_MAX_LEN] = {0};
+	int ret;
+
+	fp = fopen(file, "r");
+	if (!fp)
+		return;
+
+	while ((ret = fread_id_name(fp, &id, &namebuf[0]))) {
+		if (ret == -1) {
+			fprintf(stderr, "Database %s is corrupted at %s\n",
+					file, namebuf);
+			fclose(fp);
+			return;
+		}
+
+		if (id < 0)
+			continue;
+
+		entry = malloc(sizeof(*entry));
+		entry->id   = id;
+		entry->name = strdup(namebuf);
+		entry->next = hash[id & (size - 1)];
+		hash[id & (size - 1)] = entry;
+	}
+	fclose(fp);
+}
+
+static void rtnl_tab_initialize(const char *file, char **tab, int size)
+{
+	FILE *fp;
+	int id;
+	char namebuf[NAME_MAX_LEN] = {0};
+	int ret;
+
+	fp = fopen(file, "r");
+	if (!fp)
+		return;
+
+	while ((ret = fread_id_name(fp, &id, &namebuf[0]))) {
+		if (ret == -1) {
+			fprintf(stderr, "Database %s is corrupted at %s\n",
+					file, namebuf);
+			fclose(fp);
+			return;
+		}
+		if (id < 0 || id > size)
+			continue;
+
+		tab[id] = strdup(namebuf);
+	}
+	fclose(fp);
+}
+
+static char *rtnl_rtprot_tab[256] = {
+	[RTPROT_UNSPEC]   = "unspec",
+	[RTPROT_REDIRECT] = "redirect",
+	[RTPROT_KERNEL]	  = "kernel",
+	[RTPROT_BOOT]	  = "boot",
+	[RTPROT_STATIC]	  = "static",
+
+	[RTPROT_GATED]	  = "gated",
+	[RTPROT_RA]	  = "ra",
+	[RTPROT_MRT]	  = "mrt",
+	[RTPROT_ZEBRA]	  = "zebra",
+	[RTPROT_BIRD]	  = "bird",
+	[RTPROT_BABEL]	  = "babel",
+	[RTPROT_DNROUTED] = "dnrouted",
+	[RTPROT_XORP]	  = "xorp",
+	[RTPROT_NTK]	  = "ntk",
+	[RTPROT_DHCP]	  = "dhcp",
+	[RTPROT_BGP]	  = "bgp",
+	[RTPROT_ISIS]	  = "isis",
+	[RTPROT_OSPF]	  = "ospf",
+	[RTPROT_RIP]	  = "rip",
+	[RTPROT_EIGRP]	  = "eigrp",
+};
+
+
+static int rtnl_rtprot_init;
+
+static void rtnl_rtprot_initialize(void)
+{
+	struct dirent *de;
+	DIR *d;
+
+	rtnl_rtprot_init = 1;
+	rtnl_tab_initialize(CONFDIR "/rt_protos",
+			    rtnl_rtprot_tab, 256);
+
+	d = opendir(CONFDIR "/rt_protos.d");
+	if (!d)
+		return;
+
+	while ((de = readdir(d)) != NULL) {
+		char path[PATH_MAX];
+		size_t len;
+
+		if (*de->d_name == '.')
+			continue;
+
+		/* only consider filenames ending in '.conf' */
+		len = strlen(de->d_name);
+		if (len <= 5)
+			continue;
+		if (strcmp(de->d_name + len - 5, ".conf"))
+			continue;
+
+		snprintf(path, sizeof(path), CONFDIR "/rt_protos.d/%s",
+			 de->d_name);
+		rtnl_tab_initialize(path, rtnl_rtprot_tab, 256);
+	}
+	closedir(d);
+}
+
+const char *rtnl_rtprot_n2a(int id, char *buf, int len)
+{
+	if (id < 0 || id >= 256 || numeric) {
+		snprintf(buf, len, "%u", id);
+		return buf;
+	}
+	if (!rtnl_rtprot_tab[id]) {
+		if (!rtnl_rtprot_init)
+			rtnl_rtprot_initialize();
+	}
+	if (rtnl_rtprot_tab[id])
+		return rtnl_rtprot_tab[id];
+	snprintf(buf, len, "%u", id);
+	return buf;
+}
+
+int rtnl_rtprot_a2n(__u32 *id, const char *arg)
+{
+	static char *cache;
+	static unsigned long res;
+	char *end;
+	int i;
+
+	if (cache && strcmp(cache, arg) == 0) {
+		*id = res;
+		return 0;
+	}
+
+	if (!rtnl_rtprot_init)
+		rtnl_rtprot_initialize();
+
+	for (i = 0; i < 256; i++) {
+		if (rtnl_rtprot_tab[i] &&
+		    strcmp(rtnl_rtprot_tab[i], arg) == 0) {
+			cache = rtnl_rtprot_tab[i];
+			res = i;
+			*id = res;
+			return 0;
+		}
+	}
+
+	res = strtoul(arg, &end, 0);
+	if (!end || end == arg || *end || res > 255)
+		return -1;
+	*id = res;
+	return 0;
+}
+
+
+static char *rtnl_rtscope_tab[256] = {
+	[RT_SCOPE_UNIVERSE]	= "global",
+	[RT_SCOPE_NOWHERE]	= "nowhere",
+	[RT_SCOPE_HOST]		= "host",
+	[RT_SCOPE_LINK]		= "link",
+	[RT_SCOPE_SITE]		= "site",
+};
+
+static int rtnl_rtscope_init;
+
+static void rtnl_rtscope_initialize(void)
+{
+	rtnl_rtscope_init = 1;
+	rtnl_tab_initialize(CONFDIR "/rt_scopes",
+			    rtnl_rtscope_tab, 256);
+}
+
+const char *rtnl_rtscope_n2a(int id, char *buf, int len)
+{
+	if (id < 0 || id >= 256 || numeric) {
+		snprintf(buf, len, "%d", id);
+		return buf;
+	}
+
+	if (!rtnl_rtscope_tab[id]) {
+		if (!rtnl_rtscope_init)
+			rtnl_rtscope_initialize();
+	}
+
+	if (rtnl_rtscope_tab[id])
+		return rtnl_rtscope_tab[id];
+
+	snprintf(buf, len, "%d", id);
+	return buf;
+}
+
+int rtnl_rtscope_a2n(__u32 *id, const char *arg)
+{
+	static const char *cache;
+	static unsigned long res;
+	char *end;
+	int i;
+
+	if (cache && strcmp(cache, arg) == 0) {
+		*id = res;
+		return 0;
+	}
+
+	if (!rtnl_rtscope_init)
+		rtnl_rtscope_initialize();
+
+	for (i = 0; i < 256; i++) {
+		if (rtnl_rtscope_tab[i] &&
+		    strcmp(rtnl_rtscope_tab[i], arg) == 0) {
+			cache = rtnl_rtscope_tab[i];
+			res = i;
+			*id = res;
+			return 0;
+		}
+	}
+
+	res = strtoul(arg, &end, 0);
+	if (!end || end == arg || *end || res > 255)
+		return -1;
+	*id = res;
+	return 0;
+}
+
+
+static char *rtnl_rtrealm_tab[256] = {
+	"unknown",
+};
+
+static int rtnl_rtrealm_init;
+
+static void rtnl_rtrealm_initialize(void)
+{
+	rtnl_rtrealm_init = 1;
+	rtnl_tab_initialize(CONFDIR "/rt_realms",
+			    rtnl_rtrealm_tab, 256);
+}
+
+const char *rtnl_rtrealm_n2a(int id, char *buf, int len)
+{
+	if (id < 0 || id >= 256 || numeric) {
+		snprintf(buf, len, "%d", id);
+		return buf;
+	}
+	if (!rtnl_rtrealm_tab[id]) {
+		if (!rtnl_rtrealm_init)
+			rtnl_rtrealm_initialize();
+	}
+	if (rtnl_rtrealm_tab[id])
+		return rtnl_rtrealm_tab[id];
+	snprintf(buf, len, "%d", id);
+	return buf;
+}
+
+
+int rtnl_rtrealm_a2n(__u32 *id, const char *arg)
+{
+	static char *cache;
+	static unsigned long res;
+	char *end;
+	int i;
+
+	if (cache && strcmp(cache, arg) == 0) {
+		*id = res;
+		return 0;
+	}
+
+	if (!rtnl_rtrealm_init)
+		rtnl_rtrealm_initialize();
+
+	for (i = 0; i < 256; i++) {
+		if (rtnl_rtrealm_tab[i] &&
+		    strcmp(rtnl_rtrealm_tab[i], arg) == 0) {
+			cache = rtnl_rtrealm_tab[i];
+			res = i;
+			*id = res;
+			return 0;
+		}
+	}
+
+	res = strtoul(arg, &end, 0);
+	if (!end || end == arg || *end || res > 255)
+		return -1;
+	*id = res;
+	return 0;
+}
+
+
+static struct rtnl_hash_entry dflt_table_entry  = { .name = "default" };
+static struct rtnl_hash_entry main_table_entry  = { .name = "main" };
+static struct rtnl_hash_entry local_table_entry = { .name = "local" };
+
+static struct rtnl_hash_entry *rtnl_rttable_hash[256] = {
+	[RT_TABLE_DEFAULT] = &dflt_table_entry,
+	[RT_TABLE_MAIN]    = &main_table_entry,
+	[RT_TABLE_LOCAL]   = &local_table_entry,
+};
+
+static int rtnl_rttable_init;
+
+static void rtnl_rttable_initialize(void)
+{
+	struct dirent *de;
+	DIR *d;
+	int i;
+
+	rtnl_rttable_init = 1;
+	for (i = 0; i < 256; i++) {
+		if (rtnl_rttable_hash[i])
+			rtnl_rttable_hash[i]->id = i;
+	}
+	rtnl_hash_initialize(CONFDIR "/rt_tables",
+			     rtnl_rttable_hash, 256);
+
+	d = opendir(CONFDIR "/rt_tables.d");
+	if (!d)
+		return;
+
+	while ((de = readdir(d)) != NULL) {
+		char path[PATH_MAX];
+		size_t len;
+
+		if (*de->d_name == '.')
+			continue;
+
+		/* only consider filenames ending in '.conf' */
+		len = strlen(de->d_name);
+		if (len <= 5)
+			continue;
+		if (strcmp(de->d_name + len - 5, ".conf"))
+			continue;
+
+		snprintf(path, sizeof(path),
+			 CONFDIR "/rt_tables.d/%s", de->d_name);
+		rtnl_hash_initialize(path, rtnl_rttable_hash, 256);
+	}
+	closedir(d);
+}
+
+const char *rtnl_rttable_n2a(__u32 id, char *buf, int len)
+{
+	struct rtnl_hash_entry *entry;
+
+	if (!rtnl_rttable_init)
+		rtnl_rttable_initialize();
+	entry = rtnl_rttable_hash[id & 255];
+	while (entry && entry->id != id)
+		entry = entry->next;
+	if (!numeric && entry)
+		return entry->name;
+	snprintf(buf, len, "%u", id);
+	return buf;
+}
+
+int rtnl_rttable_a2n(__u32 *id, const char *arg)
+{
+	static const char *cache;
+	static unsigned long res;
+	struct rtnl_hash_entry *entry;
+	char *end;
+	unsigned long i;
+
+	if (cache && strcmp(cache, arg) == 0) {
+		*id = res;
+		return 0;
+	}
+
+	if (!rtnl_rttable_init)
+		rtnl_rttable_initialize();
+
+	for (i = 0; i < 256; i++) {
+		entry = rtnl_rttable_hash[i];
+		while (entry && strcmp(entry->name, arg))
+			entry = entry->next;
+		if (entry) {
+			cache = entry->name;
+			res = entry->id;
+			*id = res;
+			return 0;
+		}
+	}
+
+	i = strtoul(arg, &end, 0);
+	if (!end || end == arg || *end || i > RT_TABLE_MAX)
+		return -1;
+	*id = i;
+	return 0;
+}
+
+
+static char *rtnl_rtdsfield_tab[256] = {
+	"0",
+};
+
+static int rtnl_rtdsfield_init;
+
+static void rtnl_rtdsfield_initialize(void)
+{
+	rtnl_rtdsfield_init = 1;
+	rtnl_tab_initialize(CONFDIR "/rt_dsfield",
+			    rtnl_rtdsfield_tab, 256);
+}
+
+const char *rtnl_dsfield_n2a(int id, char *buf, int len)
+{
+	if (id < 0 || id >= 256) {
+		snprintf(buf, len, "%d", id);
+		return buf;
+	}
+	if (!rtnl_rtdsfield_tab[id]) {
+		if (!rtnl_rtdsfield_init)
+			rtnl_rtdsfield_initialize();
+	}
+	if (!numeric && rtnl_rtdsfield_tab[id])
+		return rtnl_rtdsfield_tab[id];
+	snprintf(buf, len, "0x%02x", id);
+	return buf;
+}
+
+
+int rtnl_dsfield_a2n(__u32 *id, const char *arg)
+{
+	static char *cache;
+	static unsigned long res;
+	char *end;
+	int i;
+
+	if (cache && strcmp(cache, arg) == 0) {
+		*id = res;
+		return 0;
+	}
+
+	if (!rtnl_rtdsfield_init)
+		rtnl_rtdsfield_initialize();
+
+	for (i = 0; i < 256; i++) {
+		if (rtnl_rtdsfield_tab[i] &&
+		    strcmp(rtnl_rtdsfield_tab[i], arg) == 0) {
+			cache = rtnl_rtdsfield_tab[i];
+			res = i;
+			*id = res;
+			return 0;
+		}
+	}
+
+	res = strtoul(arg, &end, 16);
+	if (!end || end == arg || *end || res > 255)
+		return -1;
+	*id = res;
+	return 0;
+}
+
+
+static struct rtnl_hash_entry dflt_group_entry = {
+	.id = 0, .name = "default"
+};
+
+static struct rtnl_hash_entry *rtnl_group_hash[256] = {
+	[0] = &dflt_group_entry,
+};
+
+static int rtnl_group_init;
+
+static void rtnl_group_initialize(void)
+{
+	rtnl_group_init = 1;
+	rtnl_hash_initialize(CONFDIR "/group",
+			     rtnl_group_hash, 256);
+}
+
+int rtnl_group_a2n(int *id, const char *arg)
+{
+	static const char *cache;
+	static unsigned long res;
+	struct rtnl_hash_entry *entry;
+	char *end;
+	int i;
+
+	if (cache && strcmp(cache, arg) == 0) {
+		*id = res;
+		return 0;
+	}
+
+	if (!rtnl_group_init)
+		rtnl_group_initialize();
+
+	for (i = 0; i < 256; i++) {
+		entry = rtnl_group_hash[i];
+		while (entry && strcmp(entry->name, arg))
+			entry = entry->next;
+		if (entry) {
+			cache = entry->name;
+			res = entry->id;
+			*id = res;
+			return 0;
+		}
+	}
+
+	i = strtol(arg, &end, 0);
+	if (!end || end == arg || *end || i < 0)
+		return -1;
+	*id = i;
+	return 0;
+}
+
+const char *rtnl_group_n2a(int id, char *buf, int len)
+{
+	struct rtnl_hash_entry *entry;
+	int i;
+
+	if (!rtnl_group_init)
+		rtnl_group_initialize();
+
+	for (i = 0; !numeric && i < 256; i++) {
+		entry = rtnl_group_hash[i];
+
+		while (entry) {
+			if (entry->id == id)
+				return entry->name;
+			entry = entry->next;
+		}
+	}
+
+	snprintf(buf, len, "%d", id);
+	return buf;
+}
+
+static char *nl_proto_tab[256] = {
+	[NETLINK_ROUTE]          = "rtnl",
+	[NETLINK_UNUSED]         = "unused",
+	[NETLINK_USERSOCK]       = "usersock",
+	[NETLINK_FIREWALL]       = "fw",
+	[NETLINK_SOCK_DIAG]      = "tcpdiag",
+	[NETLINK_NFLOG]          = "nflog",
+	[NETLINK_XFRM]           = "xfrm",
+	[NETLINK_SELINUX]        = "selinux",
+	[NETLINK_ISCSI]          = "iscsi",
+	[NETLINK_AUDIT]          = "audit",
+	[NETLINK_FIB_LOOKUP]     = "fiblookup",
+	[NETLINK_CONNECTOR]      = "connector",
+	[NETLINK_NETFILTER]      = "nft",
+	[NETLINK_IP6_FW]         = "ip6fw",
+	[NETLINK_DNRTMSG]        = "dec-rt",
+	[NETLINK_KOBJECT_UEVENT] = "uevent",
+	[NETLINK_GENERIC]        = "genl",
+	[NETLINK_SCSITRANSPORT]  = "scsi-trans",
+	[NETLINK_ECRYPTFS]       = "ecryptfs",
+	[NETLINK_RDMA]           = "rdma",
+	[NETLINK_CRYPTO]         = "crypto",
+};
+
+static int nl_proto_init;
+
+static void nl_proto_initialize(void)
+{
+	nl_proto_init = 1;
+	rtnl_tab_initialize(CONFDIR "/nl_protos",
+			    nl_proto_tab, 256);
+}
+
+const char *nl_proto_n2a(int id, char *buf, int len)
+{
+	if (id < 0 || id >= 256 || numeric) {
+		snprintf(buf, len, "%d", id);
+		return buf;
+	}
+
+	if (!nl_proto_init)
+		nl_proto_initialize();
+
+	if (nl_proto_tab[id])
+		return nl_proto_tab[id];
+
+	snprintf(buf, len, "%u", id);
+	return buf;
+}
+
+int nl_proto_a2n(__u32 *id, const char *arg)
+{
+	static char *cache;
+	static unsigned long res;
+	char *end;
+	int i;
+
+	if (cache && strcmp(cache, arg) == 0) {
+		*id = res;
+		return 0;
+	}
+
+	if (!nl_proto_init)
+		nl_proto_initialize();
+
+	for (i = 0; i < 256; i++) {
+		if (nl_proto_tab[i] &&
+		    strcmp(nl_proto_tab[i], arg) == 0) {
+			cache = nl_proto_tab[i];
+			res = i;
+			*id = res;
+			return 0;
+		}
+	}
+
+	res = strtoul(arg, &end, 0);
+	if (!end || end == arg || *end || res > 255)
+		return -1;
+	*id = res;
+	return 0;
+}
diff --git a/lib/utils.c b/lib/utils.c
new file mode 100644
index 0000000..95d46ff
--- /dev/null
+++ b/lib/utils.c
@@ -0,0 +1,1688 @@
+/*
+ * utils.c
+ *
+ *		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.
+ *
+ * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <asm/types.h>
+#include <linux/pkt_sched.h>
+#include <linux/param.h>
+#include <linux/if_arp.h>
+#include <linux/mpls.h>
+#include <linux/snmp.h>
+#include <time.h>
+#include <sys/time.h>
+#include <errno.h>
+#ifdef HAVE_LIBCAP
+#include <sys/capability.h>
+#endif
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ll_map.h"
+#include "namespace.h"
+
+int resolve_hosts;
+int timestamp_short;
+int pretty;
+const char *_SL_ = "\n";
+
+static int af_byte_len(int af);
+static void print_time(char *buf, int len, __u32 time);
+static void print_time64(char *buf, int len, __s64 time);
+
+int read_prop(const char *dev, char *prop, long *value)
+{
+	char fname[128], buf[80], *endp, *nl;
+	FILE *fp;
+	long result;
+	int ret;
+
+	ret = snprintf(fname, sizeof(fname), "/sys/class/net/%s/%s",
+			dev, prop);
+
+	if (ret <= 0 || ret >= sizeof(fname)) {
+		fprintf(stderr, "could not build pathname for property\n");
+		return -1;
+	}
+
+	fp = fopen(fname, "r");
+	if (fp == NULL) {
+		fprintf(stderr, "fopen %s: %s\n", fname, strerror(errno));
+		return -1;
+	}
+
+	if (!fgets(buf, sizeof(buf), fp)) {
+		fprintf(stderr, "property \"%s\" in file %s is currently unknown\n", prop, fname);
+		fclose(fp);
+		goto out;
+	}
+
+	nl = strchr(buf, '\n');
+	if (nl)
+		*nl = '\0';
+
+	fclose(fp);
+	result = strtol(buf, &endp, 0);
+
+	if (*endp || buf == endp) {
+		fprintf(stderr, "value \"%s\" in file %s is not a number\n",
+			buf, fname);
+		goto out;
+	}
+
+	if ((result == LONG_MAX || result == LONG_MIN) && errno == ERANGE) {
+		fprintf(stderr, "strtol %s: %s", fname, strerror(errno));
+		goto out;
+	}
+
+	*value = result;
+	return 0;
+out:
+	fprintf(stderr, "Failed to parse %s\n", fname);
+	return -1;
+}
+
+int get_hex(char c)
+{
+	if (c >= 'A' && c <= 'F')
+		return c - 'A' + 10;
+	if (c >= 'a' && c <= 'f')
+		return c - 'a' + 10;
+	if (c >= '0' && c <= '9')
+		return c - '0';
+
+	return -1;
+}
+
+int get_integer(int *val, const char *arg, int base)
+{
+	long res;
+	char *ptr;
+
+	if (!arg || !*arg)
+		return -1;
+
+	res = strtol(arg, &ptr, base);
+
+	/* If there were no digits at all, strtol()  stores
+	 * the original value of nptr in *endptr (and returns 0).
+	 * In particular, if *nptr is not '\0' but **endptr is '\0' on return,
+	 * the entire string is valid.
+	 */
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+
+	/* If an underflow occurs, strtol() returns LONG_MIN.
+	 * If an overflow occurs,  strtol() returns LONG_MAX.
+	 * In both cases, errno is set to ERANGE.
+	 */
+	if ((res == LONG_MAX || res == LONG_MIN) && errno == ERANGE)
+		return -1;
+
+	/* Outside range of int */
+	if (res < INT_MIN || res > INT_MAX)
+		return -1;
+
+	*val = res;
+	return 0;
+}
+
+int mask2bits(__u32 netmask)
+{
+	unsigned int bits = 0;
+	__u32 mask = ntohl(netmask);
+	__u32 host = ~mask;
+
+	/* a valid netmask must be 2^n - 1 */
+	if ((host & (host + 1)) != 0)
+		return -1;
+
+	for (; mask; mask <<= 1)
+		++bits;
+	return bits;
+}
+
+static int get_netmask(unsigned int *val, const char *arg, int base)
+{
+	inet_prefix addr;
+
+	if (!get_unsigned(val, arg, base))
+		return 0;
+
+	/* try converting dotted quad to CIDR */
+	if (!get_addr_1(&addr, arg, AF_INET) && addr.family == AF_INET) {
+		int b = mask2bits(addr.data[0]);
+
+		if (b >= 0) {
+			*val = b;
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+int get_unsigned(unsigned int *val, const char *arg, int base)
+{
+	unsigned long res;
+	char *ptr;
+
+	if (!arg || !*arg)
+		return -1;
+
+	res = strtoul(arg, &ptr, base);
+
+	/* empty string or trailing non-digits */
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+
+	/* overflow */
+	if (res == ULONG_MAX && errno == ERANGE)
+		return -1;
+
+	/* out side range of unsigned */
+	if (res > UINT_MAX)
+		return -1;
+
+	*val = res;
+	return 0;
+}
+
+/*
+ * get_time_rtt is "translated" from a similar routine "get_time" in
+ * tc_util.c.  We don't use the exact same routine because tc passes
+ * microseconds to the kernel and the callers of get_time_rtt want to
+ * pass milliseconds (standard unit for rtt values since 2.6.27), and
+ * have a different assumption for the units of a "raw" number.
+ */
+int get_time_rtt(unsigned int *val, const char *arg, int *raw)
+{
+	double t;
+	unsigned long res;
+	char *p;
+
+	if (strchr(arg, '.') != NULL) {
+		t = strtod(arg, &p);
+		if (t < 0.0)
+			return -1;
+
+		/* no digits? */
+		if (!p || p == arg)
+			return -1;
+
+		/* over/underflow */
+		if ((t == HUGE_VALF || t == HUGE_VALL) && errno == ERANGE)
+			return -1;
+	} else {
+		res = strtoul(arg, &p, 0);
+
+		/* empty string? */
+		if (!p || p == arg)
+			return -1;
+
+		/* overflow */
+		if (res == ULONG_MAX && errno == ERANGE)
+			return -1;
+
+		t = (double)res;
+	}
+
+	if (p == arg)
+		return -1;
+	*raw = 1;
+
+	if (*p) {
+		*raw = 0;
+		if (strcasecmp(p, "s") == 0 ||
+		    strcasecmp(p, "sec") == 0 ||
+		    strcasecmp(p, "secs") == 0)
+			t *= 1000;
+		else if (strcasecmp(p, "ms") == 0 ||
+			 strcasecmp(p, "msec") == 0 ||
+			 strcasecmp(p, "msecs") == 0)
+			t *= 1.0; /* allow suffix, do nothing */
+		else
+			return -1;
+	}
+
+	/* emulate ceil() without having to bring-in -lm and always be >= 1 */
+	*val = t;
+	if (*val < t)
+		*val += 1;
+
+	return 0;
+
+}
+
+int get_u64(__u64 *val, const char *arg, int base)
+{
+	unsigned long long res;
+	char *ptr;
+
+	if (!arg || !*arg)
+		return -1;
+
+	res = strtoull(arg, &ptr, base);
+
+	/* empty string or trailing non-digits */
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+
+	/* overflow */
+	if (res == ULLONG_MAX && errno == ERANGE)
+		return -1;
+
+	/* in case ULL is 128 bits */
+	if (res > 0xFFFFFFFFFFFFFFFFULL)
+		return -1;
+
+	*val = res;
+	return 0;
+}
+
+int get_u32(__u32 *val, const char *arg, int base)
+{
+	unsigned long res;
+	char *ptr;
+
+	if (!arg || !*arg)
+		return -1;
+	res = strtoul(arg, &ptr, base);
+
+	/* empty string or trailing non-digits */
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+
+	/* overflow */
+	if (res == ULONG_MAX && errno == ERANGE)
+		return -1;
+
+	/* in case UL > 32 bits */
+	if (res > 0xFFFFFFFFUL)
+		return -1;
+
+	*val = res;
+	return 0;
+}
+
+int get_u16(__u16 *val, const char *arg, int base)
+{
+	unsigned long res;
+	char *ptr;
+
+	if (!arg || !*arg)
+		return -1;
+	res = strtoul(arg, &ptr, base);
+
+	/* empty string or trailing non-digits */
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+
+	/* overflow */
+	if (res == ULONG_MAX && errno == ERANGE)
+		return -1;
+
+	if (res > 0xFFFFUL)
+		return -1;
+
+	*val = res;
+	return 0;
+}
+
+int get_u8(__u8 *val, const char *arg, int base)
+{
+	unsigned long res;
+	char *ptr;
+
+	if (!arg || !*arg)
+		return -1;
+
+	res = strtoul(arg, &ptr, base);
+	/* empty string or trailing non-digits */
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+
+	/* overflow */
+	if (res == ULONG_MAX && errno == ERANGE)
+		return -1;
+
+	if (res > 0xFFUL)
+		return -1;
+
+	*val = res;
+	return 0;
+}
+
+int get_s64(__s64 *val, const char *arg, int base)
+{
+	long long res;
+	char *ptr;
+
+	errno = 0;
+
+	if (!arg || !*arg)
+		return -1;
+	res = strtoll(arg, &ptr, base);
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+	if ((res == LLONG_MIN || res == LLONG_MAX) && errno == ERANGE)
+		return -1;
+	if (res > INT64_MAX || res < INT64_MIN)
+		return -1;
+
+	*val = res;
+	return 0;
+}
+
+int get_s32(__s32 *val, const char *arg, int base)
+{
+	long res;
+	char *ptr;
+
+	errno = 0;
+
+	if (!arg || !*arg)
+		return -1;
+	res = strtol(arg, &ptr, base);
+	if (!ptr || ptr == arg || *ptr)
+		return -1;
+	if ((res == LONG_MIN || res == LONG_MAX) && errno == ERANGE)
+		return -1;
+	if (res > INT32_MAX || res < INT32_MIN)
+		return -1;
+
+	*val = res;
+	return 0;
+}
+
+int get_be64(__be64 *val, const char *arg, int base)
+{
+	__u64 v;
+	int ret = get_u64(&v, arg, base);
+
+	if (!ret)
+		*val = htonll(v);
+
+	return ret;
+}
+
+int get_be32(__be32 *val, const char *arg, int base)
+{
+	__u32 v;
+	int ret = get_u32(&v, arg, base);
+
+	if (!ret)
+		*val = htonl(v);
+
+	return ret;
+}
+
+int get_be16(__be16 *val, const char *arg, int base)
+{
+	__u16 v;
+	int ret = get_u16(&v, arg, base);
+
+	if (!ret)
+		*val = htons(v);
+
+	return ret;
+}
+
+/* This uses a non-standard parsing (ie not inet_aton, or inet_pton)
+ * because of legacy choice to parse 10.8 as 10.8.0.0 not 10.0.0.8
+ */
+static int get_addr_ipv4(__u8 *ap, const char *cp)
+{
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		unsigned long n;
+		char *endp;
+
+		n = strtoul(cp, &endp, 0);
+		if (n > 255)
+			return -1;	/* bogus network value */
+
+		if (endp == cp) /* no digits */
+			return -1;
+
+		ap[i] = n;
+
+		if (*endp == '\0')
+			break;
+
+		if (i == 3 || *endp != '.')
+			return -1;	/* extra characters */
+		cp = endp + 1;
+	}
+
+	return 1;