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;
+}
+
+int get_addr64(__u64 *ap, const char *cp)
+{
+ int i;
+
+ union {
+ __u16 v16[4];
+ __u64 v64;
+ } val;
+
+ for (i = 0; i < 4; i++) {
+ unsigned long n;
+ char *endp;
+
+ n = strtoul(cp, &endp, 16);
+ if (n > 0xffff)
+ return -1; /* bogus network value */
+
+ if (endp == cp) /* no digits */
+ return -1;
+
+ val.v16[i] = htons(n);
+
+ if (*endp == '\0')
+ break;
+
+ if (i == 3 || *endp != ':')
+ return -1; /* extra characters */
+ cp = endp + 1;
+ }
+
+ *ap = val.v64;
+
+ return 1;
+}
+
+static void set_address_type(inet_prefix *addr)
+{
+ switch (addr->family) {
+ case AF_INET:
+ if (!addr->data[0])
+ addr->flags |= ADDRTYPE_INET_UNSPEC;
+ else if (IN_MULTICAST(ntohl(addr->data[0])))
+ addr->flags |= ADDRTYPE_INET_MULTI;
+ else
+ addr->flags |= ADDRTYPE_INET;
+ break;
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(addr->data))
+ addr->flags |= ADDRTYPE_INET_UNSPEC;
+ else if (IN6_IS_ADDR_MULTICAST(addr->data))
+ addr->flags |= ADDRTYPE_INET_MULTI;
+ else
+ addr->flags |= ADDRTYPE_INET;
+ break;
+ }
+}
+
+static int __get_addr_1(inet_prefix *addr, const char *name, int family)
+{
+ memset(addr, 0, sizeof(*addr));
+
+ if (strcmp(name, "default") == 0) {
+ if ((family == AF_DECnet) || (family == AF_MPLS))
+ return -1;
+ addr->family = family;
+ addr->bytelen = af_byte_len(addr->family);
+ addr->bitlen = -2;
+ addr->flags |= PREFIXLEN_SPECIFIED;
+ return 0;
+ }
+
+ if (strcmp(name, "all") == 0 ||
+ strcmp(name, "any") == 0) {
+ if ((family == AF_DECnet) || (family == AF_MPLS))
+ return -1;
+ addr->family = family;
+ addr->bytelen = 0;
+ addr->bitlen = -2;
+ return 0;
+ }
+
+ if (family == AF_PACKET) {
+ int len;
+
+ len = ll_addr_a2n((char *) &addr->data, sizeof(addr->data),
+ name);
+ if (len < 0)
+ return -1;
+
+ addr->family = AF_PACKET;
+ addr->bytelen = len;
+ addr->bitlen = len * 8;
+ return 0;
+ }
+
+ if (strchr(name, ':')) {
+ addr->family = AF_INET6;
+ if (family != AF_UNSPEC && family != AF_INET6)
+ return -1;
+ if (inet_pton(AF_INET6, name, addr->data) <= 0)
+ return -1;
+ addr->bytelen = 16;
+ addr->bitlen = -1;
+ return 0;
+ }
+
+ if (family == AF_MPLS) {
+ unsigned int maxlabels;
+ int i;
+
+ addr->family = AF_MPLS;
+ if (mpls_pton(AF_MPLS, name, addr->data,
+ sizeof(addr->data)) <= 0)
+ return -1;
+ addr->bytelen = 4;
+ addr->bitlen = 20;
+ /* How many bytes do I need? */
+ maxlabels = sizeof(addr->data) / sizeof(struct mpls_label);
+ for (i = 0; i < maxlabels; i++) {
+ if (ntohl(addr->data[i]) & MPLS_LS_S_MASK) {
+ addr->bytelen = (i + 1)*4;
+ break;
+ }
+ }
+ return 0;
+ }
+
+ addr->family = AF_INET;
+ if (family != AF_UNSPEC && family != AF_INET)
+ return -1;
+
+ if (get_addr_ipv4((__u8 *)addr->data, name) <= 0)
+ return -1;
+
+ addr->bytelen = 4;
+ addr->bitlen = -1;
+ return 0;
+}
+
+int get_addr_1(inet_prefix *addr, const char *name, int family)
+{
+ int ret;
+
+ ret = __get_addr_1(addr, name, family);
+ if (ret)
+ return ret;
+
+ set_address_type(addr);
+ return 0;
+}
+
+int af_bit_len(int af)
+{
+ switch (af) {
+ case AF_INET6:
+ return 128;
+ case AF_INET:
+ return 32;
+ case AF_DECnet:
+ return 16;
+ case AF_IPX:
+ return 80;
+ case AF_MPLS:
+ return 20;
+ }
+
+ return 0;
+}
+
+static int af_byte_len(int af)
+{
+ return af_bit_len(af) / 8;
+}
+
+int get_prefix_1(inet_prefix *dst, char *arg, int family)
+{
+ char *slash;
+ int err, bitlen, flags;
+
+ slash = strchr(arg, '/');
+ if (slash)
+ *slash = 0;
+
+ err = get_addr_1(dst, arg, family);
+
+ if (slash)
+ *slash = '/';
+
+ if (err)
+ return err;
+
+ bitlen = af_bit_len(dst->family);
+
+ flags = 0;
+ if (slash) {
+ unsigned int plen;
+
+ if (dst->bitlen == -2)
+ return -1;
+ if (get_netmask(&plen, slash + 1, 0))
+ return -1;
+ if (plen > bitlen)
+ return -1;
+
+ flags |= PREFIXLEN_SPECIFIED;
+ bitlen = plen;
+ } else {
+ if (dst->bitlen == -2)
+ bitlen = 0;
+ }
+
+ dst->flags |= flags;
+ dst->bitlen = bitlen;
+
+ return 0;
+}
+
+static const char *family_name_verbose(int family)
+{
+ if (family == AF_UNSPEC)
+ return "any valid";
+ return family_name(family);
+}
+
+int get_addr(inet_prefix *dst, const char *arg, int family)
+{
+ if (get_addr_1(dst, arg, family)) {
+ fprintf(stderr,
+ "Error: %s address is expected rather than \"%s\".\n",
+ family_name_verbose(family), arg);
+ exit(1);
+ }
+ return 0;
+}
+
+int get_addr_rta(inet_prefix *dst, const struct rtattr *rta, int family)
+{
+ const int len = RTA_PAYLOAD(rta);
+ const void *data = RTA_DATA(rta);
+
+ switch (len) {
+ case 4:
+ dst->family = AF_INET;
+ dst->bytelen = 4;
+ memcpy(dst->data, data, 4);
+ break;
+ case 16:
+ dst->family = AF_INET6;
+ dst->bytelen = 16;
+ memcpy(dst->data, data, 16);
+ break;
+ case 2:
+ dst->family = AF_DECnet;
+ dst->bytelen = 2;
+ memcpy(dst->data, data, 2);
+ break;
+ case 10:
+ dst->family = AF_IPX;
+ dst->bytelen = 10;
+ memcpy(dst->data, data, 10);
+ break;
+ default:
+ return -1;
+ }
+
+ if (family != AF_UNSPEC && family != dst->family)
+ return -2;
+
+ dst->bitlen = -1;
+ dst->flags = 0;
+
+ set_address_type(dst);
+ return 0;
+}
+
+int get_prefix(inet_prefix *dst, char *arg, int family)
+{
+ if (family == AF_PACKET) {
+ fprintf(stderr,
+ "Error: \"%s\" may be inet prefix, but it is not allowed in this context.\n",
+ arg);
+ exit(1);
+ }
+
+ if (get_prefix_1(dst, arg, family)) {
+ fprintf(stderr,
+ "Error: %s prefix is expected rather than \"%s\".\n",
+ family_name_verbose(family), arg);
+ exit(1);
+ }
+ return 0;
+}
+
+__u32 get_addr32(const char *name)
+{
+ inet_prefix addr;
+
+ if (get_addr_1(&addr, name, AF_INET)) {
+ fprintf(stderr,
+ "Error: an IP address is expected rather than \"%s\"\n",
+ name);
+ exit(1);
+ }
+ return addr.data[0];
+}
+
+void incomplete_command(void)
+{
+ fprintf(stderr, "Command line is not complete. Try option \"help\"\n");
+ exit(-1);
+}
+
+void missarg(const char *key)
+{
+ fprintf(stderr, "Error: argument \"%s\" is required\n", key);
+ exit(-1);
+}
+
+void invarg(const char *msg, const char *arg)
+{
+ fprintf(stderr, "Error: argument \"%s\" is wrong: %s\n", arg, msg);
+ exit(-1);
+}
+
+void duparg(const char *key, const char *arg)
+{
+ fprintf(stderr,
+ "Error: duplicate \"%s\": \"%s\" is the second value.\n",
+ key, arg);
+ exit(-1);
+}
+
+void duparg2(const char *key, const char *arg)
+{
+ fprintf(stderr,
+ "Error: either \"%s\" is duplicate, or \"%s\" is a garbage.\n",
+ key, arg);
+ exit(-1);
+}
+
+int nodev(const char *dev)
+{
+ fprintf(stderr, "Cannot find device \"%s\"\n", dev);
+ return -1;
+}
+
+int check_ifname(const char *name)
+{
+ /* These checks mimic kernel checks in dev_valid_name */
+ if (*name == '\0')
+ return -1;
+ if (strlen(name) >= IFNAMSIZ)
+ return -1;
+
+ while (*name) {
+ if (*name == '/' || isspace(*name))
+ return -1;
+ ++name;
+ }
+ return 0;
+}
+
+/* buf is assumed to be IFNAMSIZ */
+int get_ifname(char *buf, const char *name)
+{
+ int ret;
+
+ ret = check_ifname(name);
+ if (ret == 0)
+ strncpy(buf, name, IFNAMSIZ);
+
+ return ret;
+}
+
+const char *get_ifname_rta(int ifindex, const struct rtattr *rta)
+{
+ const char *name;
+
+ if (rta) {
+ name = rta_getattr_str(rta);
+ } else {
+ fprintf(stderr,
+ "BUG: device with ifindex %d has nil ifname\n",
+ ifindex);
+ name = ll_idx_n2a(ifindex);
+ }
+
+ if (check_ifname(name))
+ return NULL;
+
+ return name;
+}
+
+/* Returns false if 'prefix' is a not empty prefix of 'string'.
+ */
+bool matches(const char *prefix, const char *string)
+{
+ if (!*prefix)
+ return true;
+ while (*string && *prefix == *string) {
+ prefix++;
+ string++;
+ }
+
+ return !!*prefix;
+}
+
+int inet_addr_match(const inet_prefix *a, const inet_prefix *b, int bits)
+{
+ const __u32 *a1 = a->data;
+ const __u32 *a2 = b->data;
+ 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 inet_addr_match_rta(const inet_prefix *m, const struct rtattr *rta)
+{
+ inet_prefix dst;
+
+ if (!rta || m->family == AF_UNSPEC || m->bitlen <= 0)
+ return 0;
+
+ if (get_addr_rta(&dst, rta, m->family))
+ return -1;
+
+ return inet_addr_match(&dst, m, m->bitlen);
+}
+
+int __iproute2_hz_internal;
+
+int __get_hz(void)
+{
+ char name[1024];
+ int hz = 0;
+ FILE *fp;
+
+ if (getenv("HZ"))
+ return atoi(getenv("HZ")) ? : HZ;
+
+ if (getenv("PROC_NET_PSCHED"))
+ snprintf(name, sizeof(name)-1,
+ "%s", getenv("PROC_NET_PSCHED"));
+ else if (getenv("PROC_ROOT"))
+ snprintf(name, sizeof(name)-1,
+ "%s/net/psched", getenv("PROC_ROOT"));
+ else
+ strcpy(name, "/proc/net/psched");
+
+ fp = fopen(name, "r");
+
+ if (fp) {
+ unsigned int nom, denom;
+
+ if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2)
+ if (nom == 1000000)
+ hz = denom;
+ fclose(fp);
+ }
+ if (hz)
+ return hz;
+ return HZ;
+}
+
+int __iproute2_user_hz_internal;
+
+int __get_user_hz(void)
+{
+ return sysconf(_SC_CLK_TCK);
+}
+
+const char *rt_addr_n2a_r(int af, int len,
+ const void *addr, char *buf, int buflen)
+{
+ switch (af) {
+ case AF_INET:
+ case AF_INET6:
+ return inet_ntop(af, addr, buf, buflen);
+ case AF_MPLS:
+ return mpls_ntop(af, addr, buf, buflen);
+ case AF_PACKET:
+ return ll_addr_n2a(addr, len, ARPHRD_VOID, buf, buflen);
+ case AF_BRIDGE:
+ {
+ const union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ } *sa = addr;
+
+ switch (sa->sa.sa_family) {
+ case AF_INET:
+ return inet_ntop(AF_INET, &sa->sin.sin_addr,
+ buf, buflen);
+ case AF_INET6:
+ return inet_ntop(AF_INET6, &sa->sin6.sin6_addr,
+ buf, buflen);
+ }
+
+ /* fallthrough */
+ }
+ default:
+ return "???";
+ }
+}
+
+const char *rt_addr_n2a(int af, int len, const void *addr)
+{
+ static char buf[256];
+
+ return rt_addr_n2a_r(af, len, addr, buf, 256);
+}
+
+int read_family(const char *name)
+{
+ int family = AF_UNSPEC;
+
+ if (strcmp(name, "inet") == 0)
+ family = AF_INET;
+ else if (strcmp(name, "inet6") == 0)
+ family = AF_INET6;
+ else if (strcmp(name, "link") == 0)
+ family = AF_PACKET;
+ else if (strcmp(name, "ipx") == 0)
+ family = AF_IPX;
+ else if (strcmp(name, "mpls") == 0)
+ family = AF_MPLS;
+ else if (strcmp(name, "bridge") == 0)
+ family = AF_BRIDGE;
+ return family;
+}
+
+const char *family_name(int family)
+{
+ if (family == AF_INET)
+ return "inet";
+ if (family == AF_INET6)
+ return "inet6";
+ if (family == AF_PACKET)
+ return "link";
+ if (family == AF_IPX)
+ return "ipx";
+ if (family == AF_MPLS)
+ return "mpls";
+ if (family == AF_BRIDGE)
+ return "bridge";
+ return "???";
+}
+
+#ifdef RESOLVE_HOSTNAMES
+struct namerec {
+ struct namerec *next;
+ const char *name;
+ inet_prefix addr;
+};
+
+#define NHASH 257
+static struct namerec *nht[NHASH];
+
+static const char *resolve_address(const void *addr, int len, int af)
+{
+ struct namerec *n;
+ struct hostent *h_ent;
+ unsigned int hash;
+ static int notfirst;
+
+
+ if (af == AF_INET6 && ((__u32 *)addr)[0] == 0 &&
+ ((__u32 *)addr)[1] == 0 && ((__u32 *)addr)[2] == htonl(0xffff)) {
+ af = AF_INET;
+ addr += 12;
+ len = 4;
+ }
+
+ hash = *(__u32 *)(addr + len - 4) % NHASH;
+
+ for (n = nht[hash]; n; n = n->next) {
+ if (n->addr.family == af &&
+ n->addr.bytelen == len &&
+ memcmp(n->addr.data, addr, len) == 0)
+ return n->name;
+ }
+ n = malloc(sizeof(*n));
+ if (n == NULL)
+ return NULL;
+ n->addr.family = af;
+ n->addr.bytelen = len;
+ n->name = NULL;
+ memcpy(n->addr.data, addr, len);
+ n->next = nht[hash];
+ nht[hash] = n;
+ if (++notfirst == 1)
+ sethostent(1);
+ fflush(stdout);
+
+ h_ent = gethostbyaddr(addr, len, af);
+ if (h_ent != NULL)
+ n->name = strdup(h_ent->h_name);
+
+ /* Even if we fail, "negative" entry is remembered. */
+ return n->name;
+}
+#endif
+
+const char *format_host_r(int af, int len, const void *addr,
+ char *buf, int buflen)
+{
+#ifdef RESOLVE_HOSTNAMES
+ if (resolve_hosts) {
+ const char *n;
+
+ len = len <= 0 ? af_byte_len(af) : len;
+
+ if (len > 0 &&
+ (n = resolve_address(addr, len, af)) != NULL)
+ return n;
+ }
+#endif
+ return rt_addr_n2a_r(af, len, addr, buf, buflen);
+}
+
+const char *format_host(int af, int len, const void *addr)
+{
+ static char buf[256];
+
+ return format_host_r(af, len, addr, buf, 256);
+}
+
+
+char *hexstring_n2a(const __u8 *str, int len, char *buf, int blen)
+{
+ char *ptr = buf;
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (blen < 3)
+ break;
+ sprintf(ptr, "%02x", str[i]);
+ ptr += 2;
+ blen -= 2;
+ }
+ return buf;
+}
+
+__u8 *hexstring_a2n(const char *str, __u8 *buf, int blen, unsigned int *len)
+{
+ unsigned int cnt = 0;
+ char *endptr;
+
+ if (strlen(str) % 2)
+ return NULL;
+ while (cnt < blen && strlen(str) > 1) {
+ unsigned int tmp;
+ char tmpstr[3];
+
+ strncpy(tmpstr, str, 2);
+ tmpstr[2] = '\0';
+ errno = 0;
+ tmp = strtoul(tmpstr, &endptr, 16);
+ if (errno != 0 || tmp > 0xFF || *endptr != '\0')
+ return NULL;
+ buf[cnt++] = tmp;
+ str += 2;
+ }
+
+ if (len)
+ *len = cnt;
+
+ return buf;
+}
+
+int hex2mem(const char *buf, uint8_t *mem, int count)
+{
+ int i, j;
+ int c;
+
+ for (i = 0, j = 0; i < count; i++, j += 2) {
+ c = get_hex(buf[j]);
+ if (c < 0)
+ return -1;
+
+ mem[i] = c << 4;
+
+ c = get_hex(buf[j + 1]);
+ if (c < 0)
+ return -1;
+
+ mem[i] |= c;
+ }
+
+ return 0;
+}
+
+int addr64_n2a(__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);
+ if (ret < 0)
+ return ret;
+
+ written += ret;
+ }
+
+ return written;
+}
+
+/* Print buffer and escape bytes that are !isprint or among 'escape' */
+void print_escape_buf(const __u8 *buf, size_t len, const char *escape)
+{
+ size_t i;
+
+ for (i = 0; i < len; ++i) {
+ if (isprint(buf[i]) && buf[i] != '\\' &&
+ !strchr(escape, buf[i]))
+ printf("%c", buf[i]);
+ else
+ printf("\\%03o", buf[i]);
+ }
+}
+
+int print_timestamp(FILE *fp)
+{
+ struct timeval tv;
+ struct tm *tm;
+
+ gettimeofday(&tv, NULL);
+ tm = localtime(&tv.tv_sec);
+
+ if (timestamp_short) {
+ char tshort[40];
+
+ strftime(tshort, sizeof(tshort), "%Y-%m-%dT%H:%M:%S", tm);
+ fprintf(fp, "[%s.%06ld] ", tshort, tv.tv_usec);
+ } else {
+ char *tstr = asctime(tm);
+
+ tstr[strlen(tstr)-1] = 0;
+ fprintf(fp, "Timestamp: %s %ld usec\n",
+ tstr, tv.tv_usec);
+ }
+
+ return 0;
+}
+
+unsigned int print_name_and_link(const char *fmt,
+ const char *name, struct rtattr *tb[])
+{
+ const char *link = NULL;
+ unsigned int m_flag = 0;
+ SPRINT_BUF(b1);
+
+ if (tb[IFLA_LINK]) {
+ int iflink = rta_getattr_u32(tb[IFLA_LINK]);
+
+ if (iflink) {
+ if (tb[IFLA_LINK_NETNSID]) {
+ if (is_json_context()) {
+ print_int(PRINT_JSON,
+ "link_index", NULL, iflink);
+ } else {
+ link = ll_idx_n2a(iflink);
+ }
+ } else {
+ link = ll_index_to_name(iflink);
+
+ if (is_json_context()) {
+ print_string(PRINT_JSON,
+ "link", NULL, link);
+ link = NULL;
+ }
+
+ m_flag = ll_index_to_flags(iflink);
+ m_flag = !(m_flag & IFF_UP);
+ }
+ } else {
+ if (is_json_context())
+ print_null(PRINT_JSON, "link", NULL, NULL);
+ else
+ link = "NONE";
+ }
+
+ if (link) {
+ snprintf(b1, sizeof(b1), "%s@%s", name, link);
+ name = b1;
+ }
+ }
+
+ print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname", fmt, name);
+
+ return m_flag;
+}
+
+int cmdlineno;
+
+/* Like glibc getline but handle continuation lines and comments */
+ssize_t getcmdline(char **linep, size_t *lenp, FILE *in)
+{
+ ssize_t cc;
+ char *cp;
+
+ cc = getline(linep, lenp, in);
+ if (cc < 0)
+ return cc; /* eof or error */
+ ++cmdlineno;
+
+ cp = strchr(*linep, '#');
+ if (cp)
+ *cp = '\0';
+
+ while ((cp = strstr(*linep, "\\\n")) != NULL) {
+ char *line1 = NULL;
+ size_t len1 = 0;
+ ssize_t cc1;
+
+ cc1 = getline(&line1, &len1, in);
+ if (cc1 < 0) {
+ fprintf(stderr, "Missing continuation line\n");
+ return cc1;
+ }
+
+ ++cmdlineno;
+ *cp = 0;
+
+ cp = strchr(line1, '#');
+ if (cp)
+ *cp = '\0';
+
+ *lenp = strlen(*linep) + strlen(line1) + 1;
+ *linep = realloc(*linep, *lenp);
+ if (!*linep) {
+ fprintf(stderr, "Out of memory\n");
+ *lenp = 0;
+ return -1;
+ }
+ cc += cc1 - 2;
+ strcat(*linep, line1);
+ free(line1);
+ }
+ return cc;
+}
+
+/* split command line into argument vector */
+int makeargs(char *line, char *argv[], int maxargs)
+{
+ static const char ws[] = " \t\r\n";
+ char *cp = line;
+ int argc = 0;
+
+ while (*cp) {
+ /* skip leading whitespace */
+ cp += strspn(cp, ws);
+
+ if (*cp == '\0')
+ break;
+
+ if (argc >= (maxargs - 1)) {
+ fprintf(stderr, "Too many arguments to command\n");
+ exit(1);
+ }
+
+ /* word begins with quote */
+ if (*cp == '\'' || *cp == '"') {
+ char quote = *cp++;
+
+ argv[argc++] = cp;
+ /* find ending quote */
+ cp = strchr(cp, quote);
+ if (cp == NULL) {
+ fprintf(stderr, "Unterminated quoted string\n");
+ exit(1);
+ }
+ } else {
+ argv[argc++] = cp;
+
+ /* find end of word */
+ cp += strcspn(cp, ws);
+ if (*cp == '\0')
+ break;
+ }
+
+ /* separate words */
+ *cp++ = 0;
+ }
+ argv[argc] = NULL;
+
+ return argc;
+}
+
+void print_nlmsg_timestamp(FILE *fp, const struct nlmsghdr *n)
+{
+ char *tstr;
+ time_t secs = ((__u32 *)NLMSG_DATA(n))[0];
+ long usecs = ((__u32 *)NLMSG_DATA(n))[1];
+
+ tstr = asctime(localtime(&secs));
+ tstr[strlen(tstr)-1] = 0;
+ fprintf(fp, "Timestamp: %s %lu us\n", tstr, usecs);
+}
+
+char *int_to_str(int val, char *buf)
+{
+ sprintf(buf, "%d", val);
+ return buf;
+}
+
+int get_guid(__u64 *guid, const char *arg)
+{
+ unsigned long tmp;
+ char *endptr;
+ int i;
+
+#define GUID_STR_LEN 23
+ /* Verify strict format: format string must be
+ * xx:xx:xx:xx:xx:xx:xx:xx where xx can be an arbitrary
+ * hex digit
+ */
+
+ if (strlen(arg) != GUID_STR_LEN)
+ return -1;
+
+ /* make sure columns are in place */
+ for (i = 0; i < 7; i++)
+ if (arg[2 + i * 3] != ':')
+ return -1;
+
+ *guid = 0;
+ for (i = 0; i < 8; i++) {
+ tmp = strtoul(arg + i * 3, &endptr, 16);
+ if (endptr != arg + i * 3 + 2)
+ return -1;
+
+ if (tmp > 255)
+ return -1;
+
+ *guid |= tmp << (56 - 8 * i);
+ }
+
+ return 0;
+}
+
+/* This is a necessary workaround for multicast route dumps */
+int get_real_family(int rtm_type, int rtm_family)
+{
+ if (rtm_type != RTN_MULTICAST)
+ return rtm_family;
+
+ if (rtm_family == RTNL_FAMILY_IPMR)
+ return AF_INET;
+
+ if (rtm_family == RTNL_FAMILY_IP6MR)
+ return AF_INET6;
+
+ return rtm_family;
+}
+
+/* Based on copy_rtnl_link_stats() from kernel at net/core/rtnetlink.c */
+static void copy_rtnl_link_stats64(struct rtnl_link_stats64 *stats64,
+ const struct rtnl_link_stats *stats)
+{
+ __u64 *a = (__u64 *)stats64;
+ const __u32 *b = (const __u32 *)stats;
+ const __u32 *e = b + sizeof(*stats) / sizeof(*b);
+
+ while (b < e)
+ *a++ = *b++;
+}
+
+#define IPSTATS_MIB_MAX_LEN (__IPSTATS_MIB_MAX * sizeof(__u64))
+static void get_snmp_counters(struct rtnl_link_stats64 *stats64,
+ struct rtattr *s)
+{
+ __u64 *mib = (__u64 *)RTA_DATA(s);
+
+ memset(stats64, 0, sizeof(*stats64));
+
+ stats64->rx_packets = mib[IPSTATS_MIB_INPKTS];
+ stats64->rx_bytes = mib[IPSTATS_MIB_INOCTETS];
+ stats64->tx_packets = mib[IPSTATS_MIB_OUTPKTS];
+ stats64->tx_bytes = mib[IPSTATS_MIB_OUTOCTETS];
+ stats64->rx_errors = mib[IPSTATS_MIB_INDISCARDS];
+ stats64->tx_errors = mib[IPSTATS_MIB_OUTDISCARDS];
+ stats64->multicast = mib[IPSTATS_MIB_INMCASTPKTS];
+ stats64->rx_frame_errors = mib[IPSTATS_MIB_CSUMERRORS];
+}
+
+int get_rtnl_link_stats_rta(struct rtnl_link_stats64 *stats64,
+ struct rtattr *tb[])
+{
+ struct rtnl_link_stats stats;
+ void *s;
+ struct rtattr *rta;
+ int size, len;
+
+ if (tb[IFLA_STATS64]) {
+ rta = tb[IFLA_STATS64];
+ size = sizeof(struct rtnl_link_stats64);
+ s = stats64;
+ } else if (tb[IFLA_STATS]) {
+ rta = tb[IFLA_STATS];
+ size = sizeof(struct rtnl_link_stats);
+ s = &stats;
+ } else if (tb[IFLA_PROTINFO]) {
+ struct rtattr *ptb[IPSTATS_MIB_MAX_LEN + 1];
+
+ parse_rtattr_nested(ptb, IPSTATS_MIB_MAX_LEN,
+ tb[IFLA_PROTINFO]);
+ if (ptb[IFLA_INET6_STATS])
+ get_snmp_counters(stats64, ptb[IFLA_INET6_STATS]);
+ return sizeof(*stats64);
+ } else {
+ return -1;
+ }
+
+ len = RTA_PAYLOAD(rta);
+ if (len < size)
+ memset(s + len, 0, size - len);
+ else
+ len = size;
+
+ memcpy(s, RTA_DATA(rta), len);
+
+ if (s != stats64)
+ copy_rtnl_link_stats64(stats64, s);
+ return size;
+}
+
+#ifdef NEED_STRLCPY
+size_t strlcpy(char *dst, const char *src, size_t size)
+{
+ size_t srclen = strlen(src);
+
+ if (size) {
+ size_t minlen = min(srclen, size - 1);
+
+ memcpy(dst, src, minlen);
+ dst[minlen] = '\0';
+ }
+ return srclen;
+}
+
+size_t strlcat(char *dst, const char *src, size_t size)
+{
+ size_t dlen = strlen(dst);
+
+ if (dlen >= size)
+ return dlen + strlen(src);
+
+ return dlen + strlcpy(dst + dlen, src, size - dlen);
+}
+#endif
+
+void drop_cap(void)
+{
+#ifdef HAVE_LIBCAP
+ /* don't harmstring root/sudo */
+ if (getuid() != 0 && geteuid() != 0) {
+ cap_t capabilities;
+ cap_value_t net_admin = CAP_NET_ADMIN;
+ cap_flag_t inheritable = CAP_INHERITABLE;
+ cap_flag_value_t is_set;
+
+ capabilities = cap_get_proc();
+ if (!capabilities)
+ exit(EXIT_FAILURE);
+ if (cap_get_flag(capabilities, net_admin, inheritable,
+ &is_set) != 0)
+ exit(EXIT_FAILURE);
+ /* apps with ambient caps can fork and call ip */
+ if (is_set == CAP_CLEAR) {
+ if (cap_clear(capabilities) != 0)
+ exit(EXIT_FAILURE);
+ if (cap_set_proc(capabilities) != 0)
+ exit(EXIT_FAILURE);
+ }
+ cap_free(capabilities);
+ }
+#endif
+}
+
+int get_time(unsigned int *time, const char *str)
+{
+ double t;
+ char *p;
+
+ t = strtod(str, &p);
+ if (p == str)
+ return -1;
+
+ if (*p) {
+ if (strcasecmp(p, "s") == 0 || strcasecmp(p, "sec") == 0 ||
+ strcasecmp(p, "secs") == 0)
+ t *= TIME_UNITS_PER_SEC;
+ else if (strcasecmp(p, "ms") == 0 || strcasecmp(p, "msec") == 0 ||
+ strcasecmp(p, "msecs") == 0)
+ t *= TIME_UNITS_PER_SEC/1000;
+ else if (strcasecmp(p, "us") == 0 || strcasecmp(p, "usec") == 0 ||
+ strcasecmp(p, "usecs") == 0)
+ t *= TIME_UNITS_PER_SEC/1000000;
+ else
+ return -1;
+ }
+
+ *time = t;
+ return 0;
+}
+
+static void print_time(char *buf, int len, __u32 time)
+{
+ double tmp = time;
+
+ if (tmp >= TIME_UNITS_PER_SEC)
+ snprintf(buf, len, "%.1fs", tmp/TIME_UNITS_PER_SEC);
+ else if (tmp >= TIME_UNITS_PER_SEC/1000)
+ snprintf(buf, len, "%.1fms", tmp/(TIME_UNITS_PER_SEC/1000));
+ else
+ snprintf(buf, len, "%uus", time);
+}
+
+char *sprint_time(__u32 time, char *buf)
+{
+ print_time(buf, SPRINT_BSIZE-1, time);
+ return buf;
+}
+
+/* 64 bit times are represented internally in nanoseconds */
+int get_time64(__s64 *time, const char *str)
+{
+ double nsec;
+ char *p;
+
+ nsec = strtod(str, &p);
+ if (p == str)
+ return -1;
+
+ if (*p) {
+ if (strcasecmp(p, "s") == 0 ||
+ strcasecmp(p, "sec") == 0 ||
+ strcasecmp(p, "secs") == 0)
+ nsec *= NSEC_PER_SEC;
+ else if (strcasecmp(p, "ms") == 0 ||
+ strcasecmp(p, "msec") == 0 ||
+ strcasecmp(p, "msecs") == 0)
+ nsec *= NSEC_PER_MSEC;
+ else if (strcasecmp(p, "us") == 0 ||
+ strcasecmp(p, "usec") == 0 ||
+ strcasecmp(p, "usecs") == 0)
+ nsec *= NSEC_PER_USEC;
+ else if (strcasecmp(p, "ns") == 0 ||
+ strcasecmp(p, "nsec") == 0 ||
+ strcasecmp(p, "nsecs") == 0)
+ nsec *= 1;
+ else
+ return -1;
+ }
+
+ *time = nsec;
+ return 0;
+}
+
+static void print_time64(char *buf, int len, __s64 time)
+{
+ double nsec = time;
+
+ if (time >= NSEC_PER_SEC)
+ snprintf(buf, len, "%.3fs", nsec/NSEC_PER_SEC);
+ else if (time >= NSEC_PER_MSEC)
+ snprintf(buf, len, "%.3fms", nsec/NSEC_PER_MSEC);
+ else if (time >= NSEC_PER_USEC)
+ snprintf(buf, len, "%.3fus", nsec/NSEC_PER_USEC);
+ else
+ snprintf(buf, len, "%lldns", time);
+}
+
+char *sprint_time64(__s64 time, char *buf)
+{
+ print_time64(buf, SPRINT_BSIZE-1, time);
+ return buf;
+}
diff --git a/man/Makefile b/man/Makefile
new file mode 100644
index 0000000..0c759dd
--- /dev/null
+++ b/man/Makefile
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0
+INSTALL=install
+INSTALLDIR=install -m 0755 -d
+INSTALLMAN=install -m 0644
+# Pass the same parameters as Lintian uses on Debian.
+MAN_CHECK=LC_ALL=en_US.UTF-8 MANROFFSEQ='' MANWIDTH=80 man --warnings \
+ --encoding=UTF-8 --local-file --troff-device=utf8 --ditroff
+# Hide man output, count and print errors.
+MAN_REDIRECT=2>&1 >/dev/null | tee /dev/fd/2 | wc -l
+
+SUBDIRS = man3 man7 man8
+
+all clean install check:
+ @for subdir in $(SUBDIRS); do $(MAKE) -C $$subdir $@ || exit $$?; done
+
+distclean: clean
+
+.PHONY: install clean distclean check
+
+.EXPORT_ALL_VARIABLES:
diff --git a/man/man3/Makefile b/man/man3/Makefile
new file mode 100644
index 0000000..1732be2
--- /dev/null
+++ b/man/man3/Makefile
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+MAN3PAGES = $(wildcard *.3)
+
+all:
+
+distclean: clean
+
+clean:
+
+install:
+ $(INSTALLDIR) $(DESTDIR)$(MANDIR)/man3
+ $(INSTALLMAN) $(MAN3PAGES) $(DESTDIR)$(MANDIR)/man3
+
+check:
+ @for page in $(MAN3PAGES); do test 0 -eq $$($(MAN_CHECK) $$page \
+ $(MAN_REDIRECT)) || { echo "Error in $$page"; exit 1; }; done
+
+.PHONY: install clean distclean check
diff --git a/man/man3/libnetlink.3 b/man/man3/libnetlink.3
new file mode 100644
index 0000000..8e3dc62
--- /dev/null
+++ b/man/man3/libnetlink.3
@@ -0,0 +1,200 @@
+.TH libnetlink 3
+.SH NAME
+libnetlink \- A library for accessing the netlink service
+.SH SYNOPSIS
+.nf
+#include <asm/types.h>
+.br
+#include <libnetlink.h>
+.br
+#include <linux/netlink.h>
+.br
+#include <linux/rtnetlink.h>
+.sp
+int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions)
+.sp
+int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type)
+.sp
+int rtnl_send(struct rtnl_handle *rth, char *buf, int len)
+.sp
+int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len)
+.sp
+int rtnl_dump_filter(struct rtnl_handle *rth,
+ int (*filter)(struct sockaddr_nl *, struct nlmsghdr *n, void *),
+ void *arg1,
+ int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n, void *),
+ void *arg2)
+.sp
+int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer,
+ unsigned groups, struct nlmsghdr *answer,
+.br
+ int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n, void *),
+.br
+ void *jarg)
+.sp
+int rtnl_listen(struct rtnl_handle *rtnl,
+ int (*handler)(struct sockaddr_nl *, struct rtnl_ctrl_data *,
+ struct nlmsghdr *n, void *),
+ void *jarg)
+.sp
+int rtnl_from_file(FILE *rtnl,
+ int (*handler)(struct sockaddr_nl *,struct nlmsghdr *n, void *),
+ void *jarg)
+.sp
+int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data)
+.sp
+int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen)
+.sp
+int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data)
+.sp
+int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen)
+.SH DESCRIPTION
+libnetlink provides a higher level interface to
+.BR rtnetlink(7).
+The read functions return 0 on success and a negative errno on failure.
+The send functions return the amount of data sent, or -1 on error.
+.TP
+rtnl_open
+Open a rtnetlink socket and save the state into the
+.B rth
+handle. This handle is passed to all subsequent calls.
+.B subscriptions
+is a bitmap of the rtnetlink multicast groups the socket will be
+a member of.
+
+.TP
+rtnl_wilddump_request
+Request a full dump of the
+.B type
+database for
+.B family
+addresses.
+.B type
+is a rtnetlink message type.
+.\" XXX
+
+.TP
+rtnl_dump_request
+Request a full dump of the
+.B type
+data buffer into
+.B buf
+with maximum length of
+.B len.
+.B type
+is a rtnetlink message type.
+
+.TP
+rtnl_dump_filter
+Receive netlink data after a request and filter it.
+The
+.B filter
+callback checks if the received message is wanted. It gets the source
+address of the message, the message itself and
+.B arg1
+as arguments. 0 as return means that the filter passed, a negative
+value is returned
+by
+.I rtnl_dump_filter
+in case of error. NULL for
+.I filter
+means to not use a filter.
+.B junk
+is used to filter messages not destined to the local socket.
+Only one message bundle is received. If there is a message
+pending, this function does not block.
+
+.TP
+rtnl_listen
+Receive netlink data after a request and pass it to
+.I handler.
+.B handler
+is a callback that gets the message source address, anscillary data, the message
+itself, and the
+.B jarg
+cookie as arguments. It will get called for all received messages.
+Only one message bundle is received. If there is a message
+pending this function does not block.
+
+.TP
+rtnl_from_file
+Works like
+.I rtnl_listen,
+but reads a netlink message bundle from the file
+.B file
+and passes the messages to
+.B handler
+for parsing. The file should contain raw data as received from a rtnetlink socket.
+.PP
+The following functions are useful to construct custom rtnetlink messages. For
+simple database dumping with filtering it is better to use the higher level
+functions above. See
+.BR rtnetlink(3)
+and
+.BR netlink(3)
+on how to generate a rtnetlink message. The following utility functions
+require a continuous buffer that already contains a netlink message header
+and a rtnetlink request.
+
+.TP
+rtnl_send
+Send the rtnetlink message in
+.B buf
+of length
+.B len
+to handle
+.B rth.
+
+.TP
+addattr32
+Add a __u32 attribute of type
+.B type
+and with value
+.B data
+to netlink message
+.B n,
+which is part of a buffer of length
+.B maxlen.
+
+.TP
+addattr_l
+Add a variable length attribute of type
+.B type
+and with value
+.B data
+and
+.B alen
+length to netlink message
+.B n,
+which is part of a buffer of length
+.B maxlen.
+.B data
+is copied.
+
+.TP
+rta_addattr32
+Initialize the rtnetlink attribute
+.B rta
+with a __u32 data value.
+
+.TP
+rta_addattr32
+Initialize the rtnetlink attribute
+.B rta
+with a variable length data value.
+
+.SH BUGS
+This library is meant for internal use, use libmnl for new programs.
+
+The functions sometimes use fprintf and exit when a fatal error occurs.
+This library should be named librtnetlink.
+
+.SH AUTHORS
+netlink/rtnetlink was designed and written by Alexey Kuznetsov.
+Andi Kleen wrote the man page.
+
+.SH SEE ALSO
+.BR netlink(7),
+.BR rtnetlink(7)
+.br
+/usr/include/linux/rtnetlink.h
diff --git a/man/man7/Makefile b/man/man7/Makefile
new file mode 100644
index 0000000..c0e545a
--- /dev/null
+++ b/man/man7/Makefile
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+MAN7PAGES = $(wildcard *.7)
+
+all:
+
+distclean: clean
+
+clean:
+
+install:
+ $(INSTALLDIR) $(DESTDIR)$(MANDIR)/man7
+ $(INSTALLMAN) $(MAN7PAGES) $(DESTDIR)$(MANDIR)/man7
+
+check:
+ @for page in $(MAN7PAGES); do test 0 -eq $$($(MAN_CHECK) $$page \
+ $(MAN_REDIRECT)) || { echo "Error in $$page"; exit 1; }; done
+
+.PHONY: install clean distclean check
diff --git a/man/man7/tc-hfsc.7 b/man/man7/tc-hfsc.7
new file mode 100644
index 0000000..5ae5e6b
--- /dev/null
+++ b/man/man7/tc-hfsc.7
@@ -0,0 +1,562 @@
+.TH "TC\-HFSC" 7 "31 October 2011" iproute2 Linux
+.SH "NAME"
+tc-hfcs \- Hierarchical Fair Service Curve
+.
+.SH "HISTORY & INTRODUCTION"
+.
+HFSC (Hierarchical Fair Service Curve) is a network packet scheduling algorithm that was first presented at
+SIGCOMM'97. Developed as a part of ALTQ (ALTernative Queuing) on NetBSD, found
+its way quickly to other BSD systems, and then a few years ago became part of
+the linux kernel. Still, it's not the most popular scheduling algorithm \-
+especially if compared to HTB \- and it's not well documented for the enduser. This introduction aims to explain how HFSC works without using
+too much math (although some math it will be
+inevitable).
+
+In short HFSC aims to:
+.
+.RS 4
+.IP \fB1)\fR 4
+guarantee precise bandwidth and delay allocation for all leaf classes (realtime
+criterion)
+.IP \fB2)\fR
+allocate excess bandwidth fairly as specified by class hierarchy (linkshare &
+upperlimit criterion)
+.IP \fB3)\fR
+minimize any discrepancy between the service curve and the actual amount of
+service provided during linksharing
+.RE
+.PP
+.
+The main "selling" point of HFSC is feature \fB(1)\fR, which is achieved by
+using nonlinear service curves (more about what it actually is later). This is
+particularly useful in VoIP or games, where not only a guarantee of consistent
+bandwidth is important, but also limiting the initial delay of a data stream. Note that
+it matters only for leaf classes (where the actual queues are) \- thus class
+hierarchy is ignored in the realtime case.
+
+Feature \fB(2)\fR is well, obvious \- any algorithm featuring class hierarchy
+(such as HTB or CBQ) strives to achieve that. HFSC does that well, although
+you might end with unusual situations, if you define service curves carelessly
+\- see section CORNER CASES for examples.
+
+Feature \fB(3)\fR is mentioned due to the nature of the problem. There may be
+situations where it's either not possible to guarantee service of all curves at
+the same time, and/or it's impossible to do so fairly. Both will be explained
+later. Note that this is mainly related to interior (aka aggregate) classes, as
+the leafs are already handled by \fB(1)\fR. Still, it's perfectly possible to
+create a leaf class without realtime service, and in such a case the caveats will
+naturally extend to leaf classes as well.
+
+.SH ABBREVIATIONS
+For the remaining part of the document, we'll use following shortcuts:
+.nf
+.RS 4
+
+RT \- realtime
+LS \- linkshare
+UL \- upperlimit
+SC \- service curve
+.fi
+.
+.SH "BASICS OF HFSC"
+.
+To understand how HFSC works, we must first introduce a service curve.
+Overall, it's a nondecreasing function of some time unit, returning the amount
+of
+service (an allowed or allocated amount of bandwidth) at some specific point in
+time. The purpose of it should be subconsciously obvious: if a class was
+allowed to transfer not less than the amount specified by its service curve,
+then the service curve is not violated.
+
+Still, we need more elaborate criterion than just the above (although in
+the most generic case it can be reduced to it). The criterion has to take two
+things into account:
+.
+.RS 4
+.IP \(bu 4
+idling periods
+.IP \(bu
+the ability to "look back", so if during current active period the service curve is violated, maybe it
+isn't if we count excess bandwidth received during earlier active period(s)
+.RE
+.PP
+Let's define the criterion as follows:
+.RS 4
+.nf
+.IP "\fB(1)\fR" 4
+For each t1, there must exist t0 in set B, so S(t1\-t0)\~<=\~w(t0,t1)
+.fi
+.RE
+.
+.PP
+Here 'w' denotes the amount of service received during some time period between t0
+and t1. B is a set of all times, where a session becomes active after idling
+period (further denoted as 'becoming backlogged'). For a clearer picture,
+imagine two situations:
+.
+.RS 4
+.IP \fBa)\fR 4
+our session was active during two periods, with a small time gap between them
+.IP \fBb)\fR
+as in (a), but with a larger gap
+.RE
+.
+.PP
+Consider \fB(a)\fR: if the service received during both periods meets
+\fB(1)\fR, then all is well. But what if it doesn't do so during the 2nd
+period? If the amount of service received during the 1st period is larger
+than the service curve, then it might compensate for smaller service during
+the 2nd period \fIand\fR the gap \- if the gap is small enough.
+
+If the gap is larger \fB(b)\fR \- then it's less likely to happen (unless the
+excess bandwidth allocated during the 1st part was really large). Still, the
+larger the gap \- the less interesting is what happened in the past (e.g. 10
+minutes ago) \- what matters is the current traffic that just started.
+
+From HFSC's perspective, more interesting is answering the following question:
+when should we start transferring packets, so a service curve of a class is not
+violated. Or rephrasing it: How much X() amount of service should a session
+receive by time t, so the service curve is not violated. Function X() defined
+as below is the basic building block of HFSC, used in: eligible, deadline,
+virtual\-time and fit\-time curves. Of course, X() is based on equation
+\fB(1)\fR and is defined recursively:
+
+.RS 4
+.IP \(bu 4
+At the 1st backlogged period beginning function X is initialized to generic
+service curve assigned to a class
+.IP \(bu
+At any subsequent backlogged period, X() is:
+.nf
+\fBmin(X() from previous period ; w(t0)+S(t\-t0) for t>=t0),\fR
+.fi
+\&... where t0 denotes the beginning of the current backlogged period.
+.RE
+.
+.PP
+HFSC uses either linear, or two\-piece linear service curves. In case of
+linear or two\-piece linear convex functions (first slope < second slope),
+min() in X's definition reduces to the 2nd argument. But in case of two\-piece
+concave functions, the 1st argument might quickly become lesser for some
+t>=t0. Note, that for some backlogged period, X() is defined only from that
+period's beginning. We also define X^(\-1)(w) as smallest t>=t0, for which
+X(t)\~=\~w. We have to define it this way, as X() is usually not an injection.
+
+The above generic X() can be one of the following:
+.
+.RS 4
+.IP "E()" 4
+In realtime criterion, selects packets eligible for sending. If none are
+eligible, HFSC will use linkshare criterion. Eligible time \&'et' is calculated
+with reference to packets' heads ( et\~=\~E^(\-1)(w) ). It's based on RT
+service curve, \fIbut in case of a convex curve, uses its 2nd slope only.\fR
+.IP "D()"
+In realtime criterion, selects the most suitable packet from the ones chosen
+by E(). Deadline time \&'dt' corresponds to packets' tails
+(dt\~=\~D^(\-1)(w+l), where \&'l' is packet's length). Based on RT service
+curve.
+.IP "V()"
+In linkshare criterion, arbitrates which packet to send next. Note that V() is
+function of a virtual time \- see \fBLINKSHARE CRITERION\fR section for
+details. Virtual time \&'vt' corresponds to packets' heads
+(vt\~=\~V^(\-1)(w)). Based on LS service curve.
+.IP "F()"
+An extension to linkshare criterion, used to limit at which speed linkshare
+criterion is allowed to dequeue. Fit\-time 'ft' corresponds to packets' heads
+as well (ft\~=\~F^(\-1)(w)). Based on UL service curve.
+.RE
+
+Be sure to make clean distinction between session's RT, LS and UL service
+curves and the above "utility" functions.
+.
+.SH "REALTIME CRITERION"
+.
+RT criterion \fIignores class hierarchy\fR and guarantees precise bandwidth and
+delay allocation. We say that a packet is eligible for sending, when the
+current real
+time is later than the eligible time of the packet. From all eligible packets, the one most
+suited for sending is the one with the shortest deadline time. This sounds
+simple, but consider the following example:
+
+Interface 10Mbit, two classes, both with two\-piece linear service curves:
+.RS 4
+.IP \(bu 4
+1st class \- 2Mbit for 100ms, then 7Mbit (convex \- 1st slope < 2nd slope)
+.IP \(bu
+2nd class \- 7Mbit for 100ms, then 2Mbit (concave \- 1st slope > 2nd slope)
+.RE
+.PP
+Assume for a moment, that we only use D() for both finding eligible packets,
+and choosing the most fitting one, thus eligible time would be computed as
+D^(\-1)(w) and deadline time would be computed as D^(\-1)(w+l). If the 2nd
+class starts sending packets 1 second after the 1st class, it's of course
+impossible to guarantee 14Mbit, as the interface capability is only 10Mbit.
+The only workaround in this scenario is to allow the 1st class to send the
+packets earlier that would normally be allowed. That's where separate E() comes
+to help. Putting all the math aside (see HFSC paper for details), E() for RT
+concave service curve is just like D(), but for the RT convex service curve \-
+it's constructed using \fIonly\fR RT service curve's 2nd slope (in our example
+ 7Mbit).
+
+The effect of such E() \- packets will be sent earlier, and at the same time
+D() \fIwill\fR be updated \- so the current deadline time calculated from it
+will be later. Thus, when the 2nd class starts sending packets later, both
+the 1st and the 2nd class will be eligible, but the 2nd session's deadline
+time will be smaller and its packets will be sent first. When the 1st class
+becomes idle at some later point, the 2nd class will be able to "buffer" up
+again for later active period of the 1st class.
+
+A short remark \- in a situation, where the total amount of bandwidth
+available on the interface is larger than the allocated total realtime parts
+(imagine a 10 Mbit interface, but 1Mbit/2Mbit and 2Mbit/1Mbit classes), the sole
+speed of the interface could suffice to guarantee the times.
+
+Important part of RT criterion is that apart from updating its D() and E(),
+also V() used by LS criterion is updated. Generally the RT criterion is
+secondary to LS one, and used \fIonly\fR if there's a risk of violating precise
+realtime requirements. Still, the "participation" in bandwidth distributed by
+LS criterion is there, so V() has to be updated along the way. LS criterion can
+than properly compensate for non\-ideal fair sharing situation, caused by RT
+scheduling. If you use UL service curve its F() will be updated as well (UL
+service curve is an extension to LS one \- see \fBUPPERLIMIT CRITERION\fR
+section).
+
+Anyway \- careless specification of LS and RT service curves can lead to
+potentially undesired situations (see CORNER CASES for examples). This wasn't
+the case in HFSC paper where LS and RT service curves couldn't be specified
+separately.
+
+.SH "LINKSHARING CRITERION"
+.
+LS criterion's task is to distribute bandwidth according to specified class
+hierarchy. Contrary to RT criterion, there're no comparisons between current
+real time and virtual time \- the decision is based solely on direct comparison
+of virtual times of all active subclasses \- the one with the smallest vt wins
+and gets scheduled. One immediate conclusion from this fact is that absolute
+values don't matter \- only ratios between them (so for example, two children
+classes with simple linear 1Mbit service curves will get the same treatment
+from LS criterion's perspective, as if they were 5Mbit). The other conclusion
+is, that in perfectly fluid system with linear curves, all virtual times across
+whole class hierarchy would be equal.
+
+Why is VC defined in term of virtual time (and what is it)?
+
+Imagine an example: class A with two children \- A1 and A2, both with let's say
+10Mbit SCs. If A2 is idle, A1 receives all the bandwidth of A (and update its
+V() in the process). When A2 becomes active, A1's virtual time is already
+\fIfar\fR later than A2's one. Considering the type of decision made by LS
+criterion, A1 would become idle for a long time. We can workaround this
+situation by adjusting virtual time of the class becoming active \- we do that
+by getting such time "up to date". HFSC uses a mean of the smallest and the
+biggest virtual time of currently active children fit for sending. As it's not
+real time anymore (excluding trivial case of situation where all classes become
+active at the same time, and never become idle), it's called virtual time.
+
+Such approach has its price though. The problem is analogous to what was
+presented in previous section and is caused by non\-linearity of service
+curves:
+.IP 1) 4
+either it's impossible to guarantee service curves and satisfy fairness
+during certain time periods:
+
+.RS 4
+Recall the example from RT section, slightly modified (with 3Mbit slopes
+instead of 2Mbit ones):
+
+.IP \(bu 4
+1st class \- 3Mbit for 100ms, then 7Mbit (convex \- 1st slope < 2nd slope)
+.IP \(bu
+2nd class \- 7Mbit for 100ms, then 3Mbit (concave \- 1st slope > 2nd slope)
+
+.PP
+They sum up nicely to 10Mbit \- the interface's capacity. But if we wanted to only
+use LS for guarantees and fairness \- it simply won't work. In LS context,
+only V() is used for making decision which class to schedule. If the 2nd class
+becomes active when the 1st one is in its second slope, the fairness will be
+preserved \- ratio will be 1:1 (7Mbit:7Mbit), but LS itself is of course
+unable to guarantee the absolute values themselves \- as it would have to go
+beyond of what the interface is capable of.
+.RE
+
+.IP 2) 4
+and/or it's impossible to guarantee service curves of all classes at the same
+time [fairly or not]:
+
+.RS 4
+
+This is similar to the above case, but a bit more subtle. We will consider two
+subtrees, arbitrated by their common (root here) parent:
+
+.nf
+R (root) -\ 10Mbit
+
+A \- 7Mbit, then 3Mbit
+A1 \- 5Mbit, then 2Mbit
+A2 \- 2Mbit, then 1Mbit
+
+B \- 3Mbit, then 7Mbit
+.fi
+
+R arbitrates between left subtree (A) and right (B). Assume that A2 and B are
+constantly backlogged, and at some later point A1 becomes backlogged (when all
+other classes are in their 2nd linear part).
+
+What happens now? B (choice made by R) will \fIalways\fR get 7 Mbit as R is
+only (obviously) concerned with the ratio between its direct children. Thus A
+subtree gets 3Mbit, but its children would want (at the point when A1 became
+backlogged) 5Mbit + 1Mbit. That's of course impossible, as they can only get
+3Mbit due to interface limitation.
+
+In the left subtree \- we have the same situation as previously (fair split
+between A1 and A2, but violated guarantees), but in the whole tree \- there's
+no fairness (B got 7Mbit, but A1 and A2 have to fit together in 3Mbit) and
+there's no guarantees for all classes (only B got what it wanted). Even if we
+violated fairness in the A subtree and set A2's service curve to 0, A1 would
+still not get the required bandwidth.
+.RE
+.
+.SH "UPPERLIMIT CRITERION"
+.
+UL criterion is an extensions to LS one, that permits sending packets only
+if current real time is later than fit\-time ('ft'). So the modified LS
+criterion becomes: choose the smallest virtual time from all active children,
+such that fit\-time < current real time also holds. Fit\-time is calculated
+from F(), which is based on UL service curve. As you can see, its role is
+kinda similar to E() used in RT criterion. Also, for obvious reasons \- you
+can't specify UL service curve without LS one.
+
+The main purpose of the UL service curve is to limit HFSC to bandwidth available on the
+upstream router (think adsl home modem/router, and linux server as
+NAT/firewall/etc. with 100Mbit+ connection to mentioned modem/router).
+Typically, it's used to create a single class directly under root, setting
+a linear UL service curve to available bandwidth \- and then creating your class
+structure from that class downwards. Of course, you're free to add a UL service
+curve (linear or not) to any class with LS criterion.
+
+An important part about the UL service curve is that whenever at some point in time
+a class doesn't qualify for linksharing due to its fit\-time, the next time it
+does qualify it will update its virtual time to the smallest virtual time of
+all active children fit for linksharing. This way, one of the main things the LS
+criterion tries to achieve \- equality of all virtual times across whole
+hierarchy \- is preserved (in perfectly fluid system with only linear curves,
+all virtual times would be equal).
+
+Without that, 'vt' would lag behind other virtual times, and could cause
+problems. Consider an interface with a capacity of 10Mbit, and the following leaf classes
+(just in case you're skipping this text quickly \- this example shows behavior
+that \f(BIdoesn't happen\fR):
+
+.nf
+A \- ls 5.0Mbit
+B \- ls 2.5Mbit
+C \- ls 2.5Mbit, ul 2.5Mbit
+.fi
+
+If B was idle, while A and C were constantly backlogged, A and C would normally
+(as far as LS criterion is concerned) divide bandwidth in 2:1 ratio. But due
+to UL service curve in place, C would get at most 2.5Mbit, and A would get the
+remaining 7.5Mbit. The longer the backlogged period, the more the virtual times of
+A and C would drift apart. If B became backlogged at some later point in time,
+its virtual time would be set to (A's\~vt\~+\~C's\~vt)/2, thus blocking A from
+sending any traffic until B's virtual time catches up with A.
+.
+.SH "SEPARATE LS / RT SCs"
+.
+Another difference from the original HFSC paper is that RT and LS SCs can be
+specified separately. Moreover, leaf classes are allowed to have only either
+RT SC or LS SC. For interior classes, only LS SCs make sense: any RT SC will
+be ignored.
+.
+.SH "CORNER CASES"
+.
+Separate service curves for LS and RT criteria can lead to certain traps
+that come from "fighting" between ideal linksharing and enforced realtime
+guarantees. Those situations didn't exist in original HFSC paper, where
+specifying separate LS / RT service curves was not discussed.
+
+Consider an interface with a 10Mbit capacity, with the following leaf classes:
+
+.nf
+A \- ls 5.0Mbit, rt 8Mbit
+B \- ls 2.5Mbit
+C \- ls 2.5Mbit
+.fi
+
+Imagine A and C are constantly backlogged. As B is idle, A and C would divide
+bandwidth in 2:1 ratio, considering LS service curve (so in theory \- 6.66 and
+3.33). Alas RT criterion takes priority, so A will get 8Mbit and LS will be
+able to compensate class C for only 2 Mbit \- this will cause discrepancy
+between virtual times of A and C.
+
+Assume this situation lasts for a long time with no idle periods, and
+suddenly B becomes active. B's virtual time will be updated to
+(A's\~vt\~+\~C's\~vt)/2, effectively landing in the middle between A's and C's
+virtual time. The effect \- B, having no RT guarantees, will be punished and
+will not be allowed to transfer until C's virtual time catches up.
+
+If the interface had a higher capacity, for example 100Mbit, this example
+would behave perfectly fine though.
+
+Let's look a bit closer at the above example \- it "cleverly" invalidates one
+of the basic things LS criterion tries to achieve \- equality of all virtual
+times across class hierarchy. Leaf classes without RT service curves are
+literally left to their own fate (governed by messed up virtual times).
+
+Also, it doesn't make much sense. Class A will always be guaranteed up to
+8Mbit, and this is more than any absolute bandwidth that could happen from its
+LS criterion (excluding trivial case of only A being active). If the bandwidth
+taken by A is smaller than absolute value from LS criterion, the unused part
+will be automatically assigned to other active classes (as A has idling periods
+in such case). The only "advantage" is, that even in case of low bandwidth on
+average, bursts would be handled at the speed defined by RT criterion. Still,
+if extra speed is needed (e.g. due to latency), non linear service curves
+should be used in such case.
+
+In the other words: the LS criterion is meaningless in the above example.
+
+You can quickly "workaround" it by making sure each leaf class has RT service
+curve assigned (thus guaranteeing all of them will get some bandwidth), but it
+doesn't make it any more valid.
+
+Keep in mind - if you use nonlinear curves and irregularities explained above
+happen \fIonly\fR in the first segment, then there's little wrong with
+"overusing" RT curve a bit:
+
+.nf
+A \- ls 5.0Mbit, rt 9Mbit/30ms, then 1Mbit
+B \- ls 2.5Mbit
+C \- ls 2.5Mbit
+.fi
+
+Here, the vt of A will "spike" in the initial period, but then A will never get more
+than 1Mbit until B & C catch up. Then everything will be back to normal.
+.
+.SH "LINUX AND TIMER RESOLUTION"
+.
+In certain situations, the scheduler can throttle itself and setup so
+called watchdog to wakeup dequeue function at some time later. In case of HFSC
+it happens when for example no packet is eligible for scheduling, and UL
+service curve is used to limit the speed at which LS criterion is allowed to
+dequeue packets. It's called throttling, and accuracy of it is dependent on
+how the kernel is compiled.
+
+There're 3 important options in modern kernels, as far as timers' resolution
+goes: \&'tickless system', \&'high resolution timer support' and \&'timer
+frequency'.
+
+If you have \&'tickless system' enabled, then the timer interrupt will trigger
+as slowly as possible, but each time a scheduler throttles itself (or any
+other part of the kernel needs better accuracy), the rate will be increased as
+needed / possible. The ceiling is either \&'timer frequency' if \&'high
+resolution timer support' is not available or not compiled in, or it's
+hardware dependent and can go \fIfar\fR beyond the highest \&'timer frequency'
+setting available.
+
+If \&'tickless system' is not enabled, the timer will trigger at a fixed rate
+specified by \&'timer frequency' \- regardless if high resolution timers are
+or aren't available.
+
+This is important to keep those settings in mind, as in scenario like: no
+tickless, no HR timers, frequency set to 100hz \- throttling accuracy would be
+at 10ms. It doesn't automatically mean you would be limited to ~0.8Mbit/s
+(assuming packets at ~1KB) \- as long as your queues are prepared to cover for
+timer inaccuracy. Of course, in case of e.g. locally generated UDP traffic \-
+appropriate socket size is needed as well. Short example to make it more
+understandable (assume hardcore anti\-schedule settings \- HZ=100, no HR
+timers, no tickless):
+
+.nf
+tc qdisc add dev eth0 root handle 1:0 hfsc default 1
+tc class add dev eth0 parent 1:0 classid 1:1 hfsc rt m2 10Mbit
+.fi
+
+Assuming packet of ~1KB size and HZ=100, that averages to ~0.8Mbit \- anything
+beyond it (e.g. the above example with specified rate over 10x larger) will
+require appropriate queuing and cause bursts every ~10 ms. As you can
+imagine, any HFSC's RT guarantees will be seriously invalidated by that.
+Aforementioned example is mainly important if you deal with old hardware \- as
+is particularly popular for home server chores. Even then, you can easily
+set HZ=1000 and have very accurate scheduling for typical adsl speeds.
+
+Anything modern (apic or even hpet msi based timers + \&'tickless system')
+will provide enough accuracy for superb 1Gbit scheduling. For example, on one
+of my cheap dual-core AMD boards I have the following settings:
+
+.nf
+tc qdisc add dev eth0 parent root handle 1:0 hfsc default 1
+tc class add dev eth0 parent 1:0 classid 1:1 hfsc rt m2 300mbit
+.fi
+
+And a simple:
+
+.nf
+nc \-u dst.host.com 54321 </dev/zero
+nc \-l \-p 54321 >/dev/null
+.fi
+
+\&...will yield the following effects over a period of ~10 seconds (taken from
+/proc/interrupts):
+
+.nf
+319: 42124229 0 HPET_MSI\-edge hpet2 (before)
+319: 42436214 0 HPET_MSI\-edge hpet2 (after 10s.)
+.fi
+
+That's roughly 31000/s. Now compare it with HZ=1000 setting. The obvious
+drawback of it is that cpu load can be rather high with servicing that
+many timer interrupts. The example with 300Mbit RT service curve on 1Gbit link is
+particularly ugly, as it requires a lot of throttling with minuscule delays.
+
+Also note that it's just an example showing the capabilities of current hardware.
+The above example (essentially a 300Mbit TBF emulator) is pointless on an internal
+interface to begin with: you will pretty much always want a regular LS service
+curve there, and in such a scenario HFSC simply doesn't throttle at all.
+
+300Mbit RT service curve (selected columns from mpstat \-P ALL 1):
+
+.nf
+10:56:43 PM CPU %sys %irq %soft %idle
+10:56:44 PM all 20.10 6.53 34.67 37.19
+10:56:44 PM 0 35.00 0.00 63.00 0.00
+10:56:44 PM 1 4.95 12.87 6.93 73.27
+.fi
+
+So, in the rare case you need those speeds with only a RT service curve, or with a UL
+service curve: remember the drawbacks.
+.
+.SH "CAVEAT: RANDOM ONLINE EXAMPLES"
+.
+For reasons unknown (though well guessed), many examples you can google love to
+overuse UL criterion and stuff it in every node possible. This makes no sense
+and works against what HFSC tries to do (and does pretty damn well). Use UL
+where it makes sense: on the uppermost node to match upstream router's uplink
+capacity. Or in special cases, such as testing (limit certain subtree to some
+speed), or customers that must never get more than certain speed. In the last
+case you can usually achieve the same by just using a RT criterion without LS+UL
+on leaf nodes.
+
+As for the router case - remember it's good to differentiate between "traffic to
+router" (remote console, web config, etc.) and "outgoing traffic", so for
+example:
+
+.nf
+tc qdisc add dev eth0 root handle 1:0 hfsc default 0x8002
+tc class add dev eth0 parent 1:0 classid 1:999 hfsc rt m2 50Mbit
+tc class add dev eth0 parent 1:0 classid 1:1 hfsc ls m2 2Mbit ul m2 2Mbit
+.fi
+
+\&... so "internet" tree under 1:1 and "router itself" as 1:999
+.
+.SH "LAYER2 ADAPTATION"
+.
+Please refer to \fBtc\-stab\fR(8)
+.
+.SH "SEE ALSO"
+.
+\fBtc\fR(8), \fBtc\-hfsc\fR(8), \fBtc\-stab\fR(8)
+
+Please direct bugreports and patches to: <netdev@vger.kernel.org>
+.
+.SH "AUTHOR"
+.
+Manpage created by Michal Soltys (soltys@ziu.info)
diff --git a/man/man8/.gitignore b/man/man8/.gitignore
new file mode 100644
index 0000000..0c3d150
--- /dev/null
+++ b/man/man8/.gitignore
@@ -0,0 +1,4 @@
+# these pages are built
+ip-address.8
+ip-link.8
+ip-route.8
diff --git a/man/man8/Makefile b/man/man8/Makefile
new file mode 100644
index 0000000..0269e17
--- /dev/null
+++ b/man/man8/Makefile
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0
+TARGETS = ip-address.8 ip-link.8 ip-route.8
+
+MAN8PAGES = $(TARGETS) $(filter-out $(TARGETS),$(wildcard *.8))
+
+all: $(TARGETS)
+
+ip-address.8: ip-address.8.in
+ sed "s|@SYSCONFDIR@|$(CONFDIR)|g" $< > $@
+
+ip-link.8: ip-link.8.in
+ sed "s|@SYSCONFDIR@|$(CONFDIR)|g" $< > $@
+
+ip-route.8: ip-route.8.in
+ sed "s|@SYSCONFDIR@|$(CONFDIR)|g" $< > $@
+
+distclean: clean
+
+clean:
+ @rm -f $(TARGETS)
+
+install:
+ $(INSTALLDIR) $(DESTDIR)$(MANDIR)/man8
+ $(INSTALLMAN) $(MAN8PAGES) $(DESTDIR)$(MANDIR)/man8
+
+check: all
+ @for page in $(MAN8PAGES); do test 0 -eq $$($(MAN_CHECK) $$page \
+ $(MAN_REDIRECT)) || { echo "Error in $$page"; exit 1; }; done
+
+.PHONY: install clean distclean check
diff --git a/man/man8/arpd.8 b/man/man8/arpd.8
new file mode 100644
index 0000000..5050a98
--- /dev/null
+++ b/man/man8/arpd.8
@@ -0,0 +1,69 @@
+.TH ARPD 8 "28 June, 2007"
+
+.SH NAME
+arpd \- userspace arp daemon.
+
+.SH SYNOPSIS
+Usage: arpd [ -lkh? ] [ -a N ] [ -b dbase ] [ -B number ] [ -f file ] [-p interval ] [ -n time ] [ -R rate ] [ <INTERFACES> ]
+
+.SH DESCRIPTION
+The
+.B arpd
+daemon collects gratuitous ARP information, saving it on local disk and feeding it to the kernel on demand to avoid redundant broadcasting due to limited size of the kernel ARP cache.
+
+.SH OPTIONS
+.TP
+-h -?
+Print help
+.TP
+-l
+Dump the arpd database to stdout and exit. The output consists of three columns: the interface index, the IP address of the interface, and the MAC address of the interface. Negative entries for dead hosts are also shown, in this case the MAC address is replaced by the word FAILED followed by a colon and the most recent time when the fact that the host is dead was proven.
+.TP
+-f <FILE>
+Read and load an arpd database from FILE in a text format similar to that dumped by option -l. Exit after load, possibly listing resulting database, if option -l is also given. If FILE is -, stdin is read to get the ARP table.
+.TP
+-b <DATABASE>
+the location of the database file. The default location is /var/lib/arpd/arpd.db
+.TP
+-a <NUMBER>
+With this option, arpd not only passively listens for ARP packets on the interface, but also sends broadcast queries itself. NUMBER is the number of such queries to make before a destination is considered dead. When arpd is started as kernel helper (i.e. with app_solicit enabled in sysctl or even with option -k) without this option and still did not learn enough information, you can observe 1 second gaps in service. Not fatal, but not good.
+.TP
+-k
+Suppress sending broadcast queries by the kernel. This option only makes sense together with option -a.
+.TP
+-n <TIME>
+Specifies the timeout of the negative cache. When resolution fails, arpd suppresses further attempts to resolve for this period. This option only makes sense together with option '-k'. This timeout should not be too much longer than the boot time of a typical host not supporting gratuitous ARP. Default value is 60 seconds.
+.TP
+-p <TIME>
+The time to wait in seconds between polling attempts to the kernel ARP table. TIME may be a floating point number. The default value is 30.
+.TP
+-R <RATE>
+Maximal steady rate of broadcasts sent by arpd in packets per second. Default value is 1.
+.TP
+-B <NUMBER>
+The number of broadcasts sent by arpd back to back. Default value is 3. Together with the -R option, this option ensures that the number of ARP queries that are broadcast does not exceed B+R*T over any interval of time T.
+.P
+<INTERFACES> is a list of names of networking interfaces to watch. If no interfaces are given, arpd monitors all the interfaces. In this case arpd does not adjust sysctl parameters, it is assumed that the user does this himself after arpd is started.
+.P
+.SH SIGNALS
+.TP
+When arpd receives a SIGINT or SIGTERM signal, it exits gracefully, syncing the database and restoring adjusted sysctl parameters. On a SIGHUP it syncs the database to disk. With SIGUSR1 it sends some statistics to syslog. The effect of any other signals is undefined. In particular, they may corrupt the database and leave the sysctl parameters in an unpredictable state.
+.P
+.SH NOTE
+.TP
+In order for arpd to be able to serve as ARP resolver, the kernel must be compiled with the option CONFIG_ARPD and, in the case when interface list in not given on command line, variable app_solicit on interfaces of interest should be in /proc/sys/net/ipv4/neigh/*. If this is not made arpd still collects gratuitous ARP information in its database.
+.SH EXAMPLES
+.TP
+arpd -b /var/tmp/arpd.db
+Start arpd to collect gratuitous ARP, but not messing with kernel functionality.
+.TP
+killall arpd ; arpd -l -b /var/tmp/arpd.db
+Look at result after some time.
+.TP
+arpd -b /var/tmp/arpd.db -a 1 eth0 eth1
+Enable kernel helper, leaving leading role to kernel.
+.TP
+arpd -b /var/tmp/arpd.db -a 3 -k eth0 eth1
+Completely replace kernel resolution on interfaces eth0 and eth1. In this case the kernel still does unicast probing to validate entries, but all the broadcast activity is suppressed and made under authority of arpd.
+.PP
+This is the mode in which arpd normally is supposed to work. It is not the default to prevent occasional enabling of too aggressive a mode.
diff --git a/man/man8/bridge.8 b/man/man8/bridge.8
new file mode 100644
index 0000000..bb4fb52
--- /dev/null
+++ b/man/man8/bridge.8
@@ -0,0 +1,739 @@
+.TH BRIDGE 8 "1 August 2012" "iproute2" "Linux"
+.SH NAME
+bridge \- show / manipulate bridge addresses and devices
+.SH SYNOPSIS
+
+.ad l
+.in +8
+.ti -8
+.B bridge
+.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OBJECT " := { "
+.BR link " | " fdb " | " mdb " | " vlan " | " monitor " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-s\fR[\fItatistics\fR] |
+\fB\-n\fR[\fIetns\fR] name |
+\fB\-b\fR[\fIatch\fR] filename |
+\fB\-c\fR[\folor\fR] |
+\fB\-p\fR[\fIretty\fR] |
+\fB\-j\fR[\fIson\fR] |
+\fB\-o\fR[\fIneline\fr] }
+
+.ti -8
+.BR "bridge link set"
+.B dev
+.IR DEV
+.IR " [ "
+.B cost
+.IR COST " ] [ "
+.B priority
+.IR PRIO " ] [ "
+.B state
+.IR STATE " ] [ "
+.BR guard " { " on " | " off " } ] [ "
+.BR hairpin " { " on " | " off " } ] [ "
+.BR fastleave " { " on " | " off " } ] [ "
+.BR root_block " { " on " | " off " } ] [ "
+.BR learning " { " on " | " off " } ] [ "
+.BR learning_sync " { " on " | " off " } ] [ "
+.BR flood " { " on " | " off " } ] [ "
+.BR hwmode " { " vepa " | " veb " } ] [ "
+.BR mcast_flood " { " on " | " off " } ] [ "
+.BR mcast_to_unicast " { " on " | " off " } ] [ "
+.BR neigh_suppress " { " on " | " off " } ] [ "
+.BR vlan_tunnel " { " on " | " off " } ] [ "
+.BR isolated " { " on " | " off " } ] [ "
+.B backup_port
+.IR DEVICE " ] ["
+.BR nobackup_port " ] [ "
+.BR self " ] [ " master " ]"
+
+.ti -8
+.BR "bridge link" " [ " show " ] [ "
+.B dev
+.IR DEV " ]"
+
+.ti -8
+.BR "bridge fdb" " { " add " | " append " | " del " | " replace " } "
+.I LLADDR
+.B dev
+.IR DEV " { "
+.BR local " | " static " | " dynamic " } [ "
+.BR self " ] [ " master " ] [ " router " ] [ " use " ] [ " extern_learn " ] [ " sticky " ] [ "
+.B dst
+.IR IPADDR " ] [ "
+.B src_vni
+.IR SRC_VNI " ] ["
+.B vni
+.IR VNI " ] ["
+.B port
+.IR PORT " ] ["
+.B via
+.IR DEVICE " ]"
+
+.ti -8
+.BR "bridge fdb" " [ " show " ] [ "
+.B dev
+.IR DEV " ] [ "
+.B br
+.IR BRDEV " ] [ "
+.B brport
+.IR DEV " ] [ "
+.B vlan
+.IR VID " ] [ "
+.B state
+.IR STATE " ]"
+
+.ti -8
+.BR "bridge mdb" " { " add " | " del " } "
+.B dev
+.IR DEV
+.B port
+.IR PORT
+.B grp
+.IR GROUP " [ "
+.BR permanent " | " temp " ] [ "
+.B vid
+.IR VID " ] "
+
+.ti -8
+.BR "bridge mdb show " [ "
+.B dev
+.IR DEV " ]"
+
+.ti -8
+.BR "bridge vlan" " { " add " | " del " } "
+.B dev
+.IR DEV
+.B vid
+.IR VID " [ "
+.BR tunnel_info
+.IR TUNNEL_ID " ] [ "
+.BR pvid " ] [ " untagged " ] [ "
+.BR self " ] [ " master " ] "
+
+.ti -8
+.BR "bridge vlan" " [ " show " | " tunnelshow " ] [ "
+.B dev
+.IR DEV " ]"
+
+.ti -8
+.BR "bridge monitor" " [ " all " | " neigh " | " link " | " mdb " ]"
+
+.SH OPTIONS
+
+.TP
+.BR "\-V" , " -Version"
+print the version of the
+.B bridge
+utility and exit.
+
+.TP
+.BR "\-s" , " \-stats", " \-statistics"
+output more information. If this option
+is given multiple times, the amount of information increases.
+As a rule, the information is statistics or some time values.
+
+.TP
+.BR "\-d" , " \-details"
+print detailed information about MDB router ports.
+
+.TP
+.BR "\-n" , " \-net" , " \-netns " <NETNS>
+switches
+.B bridge
+to the specified network namespace
+.IR NETNS .
+Actually it just simplifies executing of:
+
+.B ip netns exec
+.IR NETNS
+.B bridge
+.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+to
+
+.B bridge
+.RI "-n[etns] " NETNS " [ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+.TP
+.BR "\-b", " \-batch " <FILENAME>
+Read commands from provided file or standard input and invoke them.
+First failure will cause termination of bridge command.
+
+.TP
+.BR "\-force"
+Don't terminate bridge command on errors in batch mode.
+If there were any errors during execution of the commands, the application
+return code will be non zero.
+
+.TP
+.BR \-c [ color ][ = { always | auto | never }
+Configure color output. If parameter is omitted or
+.BR always ,
+color output is enabled regardless of stdout state. If parameter is
+.BR auto ,
+stdout is checked to be a terminal before enabling color output. If parameter is
+.BR never ,
+color output is disabled. If specified multiple times, the last one takes
+precedence. This flag is ignored if
+.B \-json
+is also given.
+
+.TP
+.BR "\-j", " \-json"
+Output results in JavaScript Object Notation (JSON).
+
+.TP
+.BR "\-p", " \-pretty"
+When combined with -j generate a pretty JSON output.
+
+.TP
+.BR "\-o", " \-oneline"
+output each record on a single line, replacing line feeds
+with the
+.B '\e'
+character. This is convenient when you want to count records
+with
+.BR wc (1)
+or to
+.BR grep (1)
+the output.
+
+
+.SH BRIDGE - COMMAND SYNTAX
+
+.SS
+.I OBJECT
+
+.TP
+.B link
+- Bridge port.
+
+.TP
+.B fdb
+- Forwarding Database entry.
+
+.TP
+.B mdb
+- Multicast group database entry.
+
+.TP
+.B vlan
+- VLAN filter list.
+
+.SS
+.I COMMAND
+
+Specifies the action to perform on the object.
+The set of possible actions depends on the object type.
+As a rule, it is possible to
+.BR "add" , " delete"
+and
+.B show
+(or
+.B list
+) objects, but some objects do not allow all of these operations
+or have some additional commands. The
+.B help
+command is available for all objects. It prints
+out a list of available commands and argument syntax conventions.
+.sp
+If no command is given, some default command is assumed.
+Usually it is
+.B list
+or, if the objects of this class cannot be listed,
+.BR "help" .
+
+.SH bridge link - bridge port
+
+.B link
+objects correspond to the port devices of the bridge.
+
+.P
+The corresponding commands set and display port status and bridge specific
+attributes.
+
+.SS bridge link set - set bridge specific attributes on a port
+
+.TP
+.BI dev " NAME "
+interface name of the bridge port
+
+.TP
+.BI cost " COST "
+the STP path cost of the specified port.
+
+.TP
+.BI priority " PRIO "
+the STP port priority. The priority value is an unsigned 8-bit quantity
+(number between 0 and 255). This metric is used in the designated port an
+droot port selectio algorithms.
+
+.TP
+.BI state " STATE "
+the operation state of the port. This is primarily used by user space STP/RSTP
+implementation. One may enter a lowercased port state name, or one of the
+numbers below. Negative inputs are ignored, and unrecognized names return an
+error.
+
+.B 0
+- port is DISABLED. Make this port completely inactive.
+.sp
+
+.B 1
+- STP LISTENING state. Only valid if STP is enabled on the bridge. In this
+state the port listens for STP BPDUs and drops all other traffic frames.
+.sp
+
+.B 2
+- STP LEARNING state. Only valid if STP is enabled on the bridge. In this
+state the port will accept traffic only for the purpose of updating MAC
+address tables.
+.sp
+
+.B 3
+- STP FORWARDING state. Port is fully active.
+.sp
+
+.B 4
+- STP BLOCKING state. Only valid if STP is enabled on the bridge. This state
+is used during the STP election process. In this state, port will only process
+STP BPDUs.
+.sp
+
+.TP
+.BR "guard on " or " guard off "
+Controls whether STP BPDUs will be processed by the bridge port. By default,
+the flag is turned off allowed BPDU processing. Turning this flag on will
+cause the port to stop processing STP BPDUs.
+
+.TP
+.BR "hairpin on " or " hairpin off "
+Controls whether traffic may be send back out of the port on which it was
+received. By default, this flag is turned off and the bridge will not forward
+traffic back out of the receiving port.
+
+.TP
+.BR "fastleave on " or " fastleave off "
+This flag allows the bridge to immediately stop multicast traffic on a port
+that receives IGMP Leave message. It is only used with IGMP snooping is
+enabled on the bridge. By default the flag is off.
+
+.TP
+.BR "root_block on " or " root_block off "
+Controls whether a given port is allowed to become root port or not. Only used
+when STP is enabled on the bridge. By default the flag is off.
+
+.TP
+.BR "learning on " or " learning off "
+Controls whether a given port will learn MAC addresses from received traffic or
+not. If learning if off, the bridge will end up flooding any traffic for which
+it has no FDB entry. By default this flag is on.
+
+.TP
+.BR "learning_sync on " or " learning_sync off "
+Controls whether a given port will sync MAC addresses learned on device port to
+bridge FDB.
+
+.TP
+.BR "flood on " or " flood off "
+Controls whether a given port will flood unicast traffic for which there is no FDB entry. By default this flag is on.
+
+.TP
+.BI hwmode
+Some network interface cards support HW bridge functionality and they may be
+configured in different modes. Currently support modes are:
+
+.B vepa
+- Data sent between HW ports is sent on the wire to the external
+switch.
+
+.B veb
+- bridging happens in hardware.
+
+.TP
+.BR "mcast_flood on " or " mcast_flood off "
+Controls whether a given port will flood multicast traffic for which
+there is no MDB entry. By default this flag is on.
+
+.TP
+.BR "mcast_to_unicast on " or " mcast_to_unicast off "
+Controls whether a given port will replicate packets using unicast
+instead of multicast. By default this flag is off.
+
+.TP
+.BR "neigh_suppress on " or " neigh_suppress off "
+Controls whether neigh discovery (arp and nd) proxy and suppression is
+enabled on the port. By default this flag is off.
+
+.TP
+.BR "vlan_tunnel on " or " vlan_tunnel off "
+Controls whether vlan to tunnel mapping is enabled on the port. By
+default this flag is off.
+
+.TP
+.BR "isolated on " or " isolated off "
+Controls whether a given port will be isolated, which means it will be
+able to communicate with non-isolated ports only. By default this
+flag is off.
+
+.TP
+.BI backup_port " DEVICE"
+If the port loses carrier all traffic will be redirected to the
+configured backup port
+
+.TP
+.BR nobackup_port
+Removes the currently configured backup port
+
+.TP
+.BI self
+link setting is configured on specified physical device
+
+.TP
+.BI master
+link setting is configured on the software bridge (default)
+
+.TP
+.BR "\-t" , " \-timestamp"
+display current time when using monitor option.
+
+.SS bridge link show - list bridge port configuration.
+
+This command displays the current bridge port configuration and flags.
+
+.SH bridge fdb - forwarding database management
+
+.B fdb
+objects contain known Ethernet addresses on a link.
+
+.P
+The corresponding commands display fdb entries, add new entries,
+append entries,
+and delete old ones.
+
+.SS bridge fdb add - add a new fdb entry
+
+This command creates a new fdb entry.
+
+.TP
+.BI "LLADDR"
+the Ethernet MAC address.
+
+.TP
+.BI dev " DEV"
+the interface to which this address is associated.
+
+.B local
+- is a local permanent fdb entry
+.sp
+
+.B static
+- is a static (no arp) fdb entry
+.sp
+
+.B dynamic
+- is a dynamic reachable age-able fdb entry
+.sp
+
+.B self
+- the address is associated with the port drivers fdb. Usually hardware.
+.sp
+
+.B master
+- the address is associated with master devices fdb. Usually software (default).
+.sp
+
+.B router
+- the destination address is associated with a router.
+Valid if the referenced device is a VXLAN type device and has
+route shortcircuit enabled.
+.sp
+
+.B use
+- the address is in use. User space can use this option to
+indicate to the kernel that the fdb entry is in use.
+.sp
+
+.B extern_learn
+- this entry was learned externally. This option can be used to
+indicate to the kernel that an entry was hardware or user-space
+controller learnt dynamic entry. Kernel will not age such an entry.
+.sp
+
+.B sticky
+- this entry will not change its port due to learning.
+.sp
+
+.in -8
+The next command line parameters apply only
+when the specified device
+.I DEV
+is of type VXLAN.
+.TP
+.BI dst " IPADDR"
+the IP address of the destination
+VXLAN tunnel endpoint where the Ethernet MAC ADDRESS resides.
+
+.TP
+.BI src_vni " SRC VNI"
+the src VNI Network Identifier (or VXLAN Segment ID)
+this entry belongs to. Used only when the vxlan device is in
+external or collect metadata mode. If omitted the value specified at
+vxlan device creation will be used.
+
+.TP
+.BI vni " VNI"
+the VXLAN VNI Network Identifier (or VXLAN Segment ID)
+to use to connect to the remote VXLAN tunnel endpoint.
+If omitted the value specified at vxlan device creation
+will be used.
+
+.TP
+.BI port " PORT"
+the UDP destination PORT number to use to connect to the
+remote VXLAN tunnel endpoint.
+If omitted the default value is used.
+
+.TP
+.BI via " DEVICE"
+device name of the outgoing interface for the
+VXLAN device driver to reach the
+remote VXLAN tunnel endpoint.
+
+.SS bridge fdb append - append a forwarding database entry
+This command adds a new fdb entry with an already known
+.IR LLADDR .
+Valid only for multicast link layer addresses.
+The command adds support for broadcast and multicast
+Ethernet MAC addresses.
+The Ethernet MAC address is added multiple times into
+the forwarding database and the vxlan device driver
+sends a copy of the data packet to each entry found.
+
+.PP
+The arguments are the same as with
+.BR "bridge fdb add" .
+
+.SS bridge fdb delete - delete a forwarding database entry
+This command removes an existing fdb entry.
+
+.PP
+The arguments are the same as with
+.BR "bridge fdb add" .
+
+.SS bridge fdb replace - replace a forwarding database entry
+If no matching entry is found, a new one will be created instead.
+
+.PP
+The arguments are the same as with
+.BR "bridge fdb add" .
+
+.SS bridge fdb show - list forwarding entries.
+
+This command displays the current forwarding table.
+
+.PP
+With the
+.B -statistics
+option, the command becomes verbose. It prints out the last updated
+and last used time for each entry.
+
+.SH bridge mdb - multicast group database management
+
+.B mdb
+objects contain known IP multicast group addresses on a link.
+
+.P
+The corresponding commands display mdb entries, add new entries,
+and delete old ones.
+
+.SS bridge mdb add - add a new multicast group database entry
+
+This command creates a new mdb entry.
+
+.TP
+.BI dev " DEV"
+the interface where this group address is associated.
+
+.TP
+.BI port " PORT"
+the port whose link is known to have members of this multicast group.
+
+.TP
+.BI grp " GROUP"
+the IP multicast group address whose members reside on the link connected to
+the port.
+
+.B permanent
+- the mdb entry is permanent
+.sp
+
+.B temp
+- the mdb entry is temporary (default)
+.sp
+
+.TP
+.BI vid " VID"
+the VLAN ID which is known to have members of this multicast group.
+
+.in -8
+.SS bridge mdb delete - delete a multicast group database entry
+This command removes an existing mdb entry.
+
+.PP
+The arguments are the same as with
+.BR "bridge mdb add" .
+
+.SS bridge mdb show - list multicast group database entries
+
+This command displays the current multicast group membership table. The table
+is populated by IGMP and MLD snooping in the bridge driver automatically. It
+can be altered by
+.B bridge mdb add
+and
+.B bridge mdb del
+commands manually too.
+
+.TP
+.BI dev " DEV"
+the interface only whose entries should be listed. Default is to list all
+bridge interfaces.
+
+.PP
+With the
+.B -details
+option, the command becomes verbose. It prints out the ports known to have
+a connected router.
+
+.PP
+With the
+.B -statistics
+option, the command displays timer values for mdb and router port entries.
+
+.SH bridge vlan - VLAN filter list
+
+.B vlan
+objects contain known VLAN IDs for a link.
+
+.P
+The corresponding commands display vlan filter entries, add new entries,
+and delete old ones.
+
+.SS bridge vlan add - add a new vlan filter entry
+
+This command creates a new vlan filter entry.
+
+.TP
+.BI dev " NAME"
+the interface with which this vlan is associated.
+
+.TP
+.BI vid " VID"
+the VLAN ID that identifies the vlan.
+
+.TP
+.BI tunnel_info " TUNNEL_ID"
+the TUNNEL ID that maps to this vlan. The tunnel id is set in
+dst_metadata for every packet that belongs to this vlan (applicable to
+bridge ports with vlan_tunnel flag set).
+
+.TP
+.BI pvid
+the vlan specified is to be considered a PVID at ingress.
+Any untagged frames will be assigned to this VLAN.
+
+.TP
+.BI untagged
+the vlan specified is to be treated as untagged on egress.
+
+.TP
+.BI self
+the vlan is configured on the specified physical device. Required if the
+device is the bridge device.
+
+.TP
+.BI master
+the vlan is configured on the software bridge (default).
+
+.SS bridge vlan delete - delete a vlan filter entry
+This command removes an existing vlan filter entry.
+
+.PP
+The arguments are the same as with
+.BR "bridge vlan add".
+The
+.BR "pvid " and " untagged"
+flags are ignored.
+
+.SS bridge vlan show - list vlan configuration.
+
+This command displays the current VLAN filter table.
+
+.PP
+With the
+.B -statistics
+option, the command displays per-vlan traffic statistics.
+
+.SS bridge vlan tunnelshow - list vlan tunnel mapping.
+
+This command displays the current vlan tunnel info mapping.
+
+.SH bridge monitor - state monitoring
+
+The
+.B bridge
+utility can monitor the state of devices and addresses
+continuously. This option has a slightly different format.
+Namely, the
+.B monitor
+command is the first in the command line and then the object list follows:
+
+.BR "bridge monitor" " [ " all " |"
+.IR OBJECT-LIST " ]"
+
+.I OBJECT-LIST
+is the list of object types that we want to monitor.
+It may contain
+.BR link ", " fdb ", and " mdb "."
+If no
+.B file
+argument is given,
+.B bridge
+opens RTNETLINK, listens on it and dumps state changes in the format
+described in previous sections.
+
+.P
+If a file name is given, it does not listen on RTNETLINK,
+but opens the file containing RTNETLINK messages saved in binary format
+and dumps them.
+
+.SH NOTES
+This command uses facilities added in Linux 3.0.
+
+Although the forwarding table is maintained on a per-bridge device basis
+the bridge device is not part of the syntax. This is a limitation of the
+underlying netlink neighbour message protocol. When displaying the
+forwarding table, entries for all bridges are displayed.
+Add/delete/modify commands determine the underlying bridge device
+based on the bridge to which the corresponding ethernet device is attached.
+
+
+.SH SEE ALSO
+.BR ip (8)
+.SH BUGS
+.RB "Please direct bugreports and patches to: " <netdev@vger.kernel.org>
+
+.SH AUTHOR
+Original Manpage by Stephen Hemminger
diff --git a/man/man8/ctstat.8 b/man/man8/ctstat.8
new file mode 100644
index 0000000..080e2b2
--- /dev/null
+++ b/man/man8/ctstat.8
@@ -0,0 +1 @@
+.so man8/lnstat.8
diff --git a/man/man8/devlink-dev.8 b/man/man8/devlink-dev.8
new file mode 100644
index 0000000..1021ee8
--- /dev/null
+++ b/man/man8/devlink-dev.8
@@ -0,0 +1,267 @@
+.TH DEVLINK\-DEV 8 "14 Mar 2016" "iproute2" "Linux"
+.SH NAME
+devlink-dev \- devlink device configuration
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B devlink
+.RI "[ " OPTIONS " ]"
+.B dev
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-n\fR[\fIno-nice-names\fR] }
+
+.ti -8
+.B devlink dev show
+.RI "[ " DEV " ]"
+
+.ti -8
+.B devlink dev help
+
+.ti -8
+.BR "devlink dev eswitch set"
+.IR DEV
+.RI "[ "
+.BR mode " { " legacy " | " switchdev " } "
+.RI "]"
+.RI "[ "
+.BR inline-mode " { " none " | " link " | " network " | " transport " } "
+.RI "]"
+.RI "[ "
+.BR encap " { " disable " | " enable " } "
+.RI "]"
+
+.ti -8
+.BR "devlink dev eswitch show"
+.IR DEV
+
+.ti -8
+.BR "devlink dev param set"
+.IR DEV
+.BR name
+.IR PARAMETER
+.BR value
+.IR VALUE
+.BR cmode " { " runtime " | " driverinit " | " permanent " } "
+
+.ti -8
+.BR "devlink dev param show"
+.RI "[ "
+.IR DEV
+.BR name
+.IR PARAMETER
+.RI "]"
+
+.ti -8
+.BR "devlink dev reload"
+.IR DEV
+
+.ti -8
+.BR "devlink dev info"
+.RI "[ "
+.IR DEV
+.RI "]"
+
+.ti -8
+.BR "devlink dev flash"
+.IR DEV
+.BR file
+.IR PATH
+.RI "["
+.BR target
+.IR ID
+.RI "]"
+
+.SH "DESCRIPTION"
+.SS devlink dev show - display devlink device attributes
+
+.PP
+.I "DEV"
+- specifies the devlink device to show.
+If this argument is omitted all devices are listed.
+
+.in +4
+Format is:
+.in +2
+BUS_NAME/BUS_ADDRESS
+
+.SS devlink dev eswitch show - display devlink device eswitch attributes
+.SS devlink dev eswitch set - sets devlink device eswitch attributes
+
+.TP
+.BR mode " { " legacy " | " switchdev " } "
+Set eswitch mode
+
+.I legacy
+- Legacy SRIOV
+
+.I switchdev
+- SRIOV switchdev offloads
+
+.TP
+.BR inline-mode " { " none " | " link " | " network " | " transport " } "
+Some HWs need the VF driver to put part of the packet headers on the TX descriptor so the e-switch can do proper matching and steering.
+
+.I none
+- None
+
+.I link
+- L2 mode
+
+.I network
+- L3 mode
+
+.I transport
+- L4 mode
+
+.TP
+.BR encap " { " disable " | " enable " } "
+Set eswitch encapsulation support
+
+.I disable
+- Disable encapsulation support
+
+.I enable
+- Enable encapsulation support
+
+.SS devlink dev param set - set new value to devlink device configuration parameter
+
+.TP
+.BI name " PARAMETER"
+Specify parameter name to set.
+
+.TP
+.BI value " VALUE"
+New value to set.
+
+.TP
+.BR cmode " { " runtime " | " driverinit " | " permanent " } "
+Configuration mode in which the new value is set.
+
+.I runtime
+- Set new value while driver is running. This configuration mode doesn't require any reset to apply the new value.
+
+.I driverinit
+- Set new value which will be applied during driver initialization. This configuration mode requires restart driver by devlink reload command to apply the new value.
+
+.I permanent
+- New value is written to device's non-volatile memory. This configuration mode requires hard reset to apply the new value.
+
+.SS devlink dev param show - display devlink device supported configuration parameters attributes
+
+.BR name
+.IR PARAMETER
+Specify parameter name to show.
+If this argument is omitted all parameters supported by devlink devices are listed.
+
+.SS devlink dev reload - perform hot reload of the driver.
+
+.PP
+.I "DEV"
+- Specifies the devlink device to reload.
+
+.SS devlink dev info - display device information.
+Display device information provided by the driver. This command can be used
+to query versions of the hardware components or device components which
+can't be updated (
+.I fixed
+) as well as device firmware which can be updated. For firmware components
+.I running
+displays the versions of firmware currently loaded into the device, while
+.I stored
+reports the versions in device's flash.
+.I Running
+and
+.I stored
+versions may differ after flash has been updated, but before reboot.
+
+.PP
+.I "DEV"
+- specifies the devlink device to show.
+If this argument is omitted all devices are listed.
+
+.SS devlink dev flash - write device's non-volatile memory.
+
+.PP
+.I "DEV"
+- specifies the devlink device to write to.
+
+.BR file
+.I PATH
+- Path to the file which will be written into device's flash. The path needs
+to be relative to one of the directories searched by the kernel firmware loaded,
+such as /lib/firmware.
+
+.BR component
+.I NAME
+- If device stores multiple firmware images in non-volatile memory, this
+parameter may be used to indicate which firmware image should be written.
+The value of
+.I NAME
+should match the component names from
+.B "devlink dev info"
+and may be driver-dependent.
+
+.SH "EXAMPLES"
+.PP
+devlink dev show
+.RS 4
+Shows the state of all devlink devices on the system.
+.RE
+.PP
+devlink dev show pci/0000:01:00.0
+.RS 4
+Shows the state of specified devlink device.
+.RE
+.PP
+devlink dev eswitch show pci/0000:01:00.0
+.RS 4
+Shows the eswitch mode of specified devlink device.
+.RE
+.PP
+devlink dev eswitch set pci/0000:01:00.0 mode switchdev
+.RS 4
+Sets the eswitch mode of specified devlink device to switchdev.
+.RE
+.PP
+devlink dev param show pci/0000:01:00.0 name max_macs
+.RS 4
+Shows the parameter max_macs attributes.
+.RE
+.PP
+devlink dev param set pci/0000:01:00.0 name internal_error_reset value true cmode runtime
+.RS 4
+Sets the parameter internal_error_reset of specified devlink device to true.
+.RE
+.PP
+devlink dev reload pci/0000:01:00.0
+.RS 4
+Performs hot reload of specified devlink device.
+.RE
+.PP
+devlink dev flash pci/0000:01:00.0 file firmware.bin
+.RS 4
+Flashes the specified devlink device with provided firmware file name. If the driver supports it, user gets updates about the flash status. For example:
+.br
+Preparing to flash
+.br
+Flashing 100%
+.br
+Flashing done
+
+.SH SEE ALSO
+.BR devlink (8),
+.BR devlink-port (8),
+.BR devlink-sb (8),
+.BR devlink-monitor (8),
+.br
+
+.SH AUTHOR
+Jiri Pirko <jiri@mellanox.com>
diff --git a/man/man8/devlink-health.8 b/man/man8/devlink-health.8
new file mode 100644
index 0000000..7ed0ae4
--- /dev/null
+++ b/man/man8/devlink-health.8
@@ -0,0 +1,197 @@
+.TH DEVLINK\-HEALTH 8 "20 Feb 2019" "iproute2" "Linux"
+.SH NAME
+devlink-health \- devlink health reporting and recovery
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B devlink
+.RI "[ " OPTIONS " ]"
+.B health
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] }
+
+.ti -8
+.BR "devlink health show"
+.RI "[ " DEV ""
+.B reporter
+.RI ""REPORTER " ] "
+
+.ti -8
+.BR "devlink health recover"
+.RI "" DEV ""
+.B reporter
+.RI "" REPORTER ""
+
+.ti -8
+.BR "devlink health diagnose"
+.RI "" DEV ""
+.B reporter
+.RI "" REPORTER ""
+
+.ti -8
+.BR "devlink health dump show"
+.RI "" DEV ""
+.B reporter
+.RI "" REPORTER ""
+
+.ti -8
+.BR "devlink health dump clear"
+.RI "" DEV ""
+.B reporter
+.RI "" REPORTER ""
+
+.ti -8
+.BR "devlink health set"
+.RI "" DEV ""
+.B reporter
+.RI "" REPORTER ""
+.RI " { "
+.B grace_period | auto_recover
+.RI " } { "
+.RI "" msec ""
+.RI "|"
+.RI "" boolean ""
+.RI " } "
+.ti -8
+.B devlink health help
+
+.SH "DESCRIPTION"
+.SS devlink health show - Show status and configuration on all supported reporters on all devlink devices.
+
+.PP
+.I "DEV"
+- specifies the devlink device.
+
+.PP
+.I "REPORTER"
+- specifies the reporter's name registered on the devlink device.
+
+.SS devlink health recover - Initiate a recovery operation on a reporter.
+This action performs a recovery and increases the recoveries counter on success.
+
+.PP
+.I "DEV"
+- specifies the devlink device.
+
+.PP
+.I "REPORTER"
+- specifies the reporter's name registered on the devlink device.
+
+.SS devlink health diagnose - Retrieve diagnostics data on a reporter.
+
+.PP
+.I "DEV"
+- specifies the devlink device.
+
+.PP
+.I "REPORTER"
+- specifies the reporter's name registered on the devlink device.
+
+.SS devlink health dump show - Display the last saved dump.
+
+.PD 0
+.P
+devlink health saves a single dump per reporter. If an dump is
+.P
+not already stored by the Devlink, this command will generate a new
+.P
+dump. The dump can be generated either automatically when a
+.P
+reporter reports on an error or manually at the user's request.
+.PD
+
+.PP
+.I "DEV"
+- specifies the devlink device.
+
+.PP
+.I "REPORTER"
+- specifies the reporter's name registered on the devlink device.
+
+.SS devlink health dump clear - Delete the saved dump.
+Deleting the saved dump enables a generation of a new dump on
+.PD 0
+.P
+the next "devlink health dump show" command.
+.PD
+
+.PP
+.I "DEV"
+- specifies the devlink device.
+
+.PP
+.I "REPORTER"
+- specifies the reporter's name registered on the devlink device.
+
+.SS devlink health set - Enable the user to configure:
+.PD 0
+1) grace_period [msec] - Time interval between consecutive auto recoveries.
+.P
+2) auto_recover [true/false] - Indicates whether the devlink should execute automatic recover on error.
+.P
+Please note that this command is not supported on a reporter which
+doesn't support a recovery method.
+.PD
+
+.PP
+.I "DEV"
+- specifies the devlink device.
+
+.PP
+.I "REPORTER"
+- specifies the reporter's name registered on the devlink device.
+
+.SH "EXAMPLES"
+.PP
+devlink health show
+.RS 4
+List status and configuration of available reporters on devices.
+.RE
+.PP
+devlink health recover pci/0000:00:09.0 reporter tx
+.RS 4
+Initiate recovery on tx reporter registered on pci/0000:00:09.0.
+.RE
+.PP
+devlink health diagnose pci/0000:00:09.0 reporter tx
+.RS 4
+List diagnostics data on the specified device and reporter.
+.RE
+.PP
+devlink health dump show pci/0000:00:09.0 reporter tx
+.RS 4
+Display the last saved dump on the specified device and reporter.
+.RE
+.PP
+devlink health dump clear pci/0000:00:09.0 reporter tx
+.RS 4
+Delete saved dump on the specified device and reporter.
+.RE
+.PP
+devlink health set pci/0000:00:09.0 reporter tx grace_period 3500
+.RS 4
+Set time interval between auto recoveries to minimum of 3500 msec on
+the specified device and reporter.
+.RE
+.PP
+devlink health set pci/0000:00:09.0 reporter tx auto_recover false
+.RS 4
+Turn off auto recovery on the specified device and reporter.
+.RE
+.SH SEE ALSO
+.BR devlink (8),
+.BR devlink-dev (8),
+.BR devlink-port (8),
+.BR devlink-param (8),
+.BR devlink-region (8),
+.br
+
+.SH AUTHOR
+Aya Levin <ayal@mellanox.com>
diff --git a/man/man8/devlink-monitor.8 b/man/man8/devlink-monitor.8
new file mode 100644
index 0000000..fffab3a
--- /dev/null
+++ b/man/man8/devlink-monitor.8
@@ -0,0 +1,38 @@
+.TH DEVLINK\-MONITOR 8 "14 Mar 2016" "iproute2" "Linux"
+.SH "NAME"
+devlink-monitor \- state monitoring
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.BR "devlink monitor" " [ " all " |"
+.IR OBJECT-LIST " ]"
+.sp
+
+.SH DESCRIPTION
+The
+.B devlink
+utility can monitor the state of devlink devices and ports
+continuously. This option has a slightly different format. Namely, the
+.B monitor
+command is the first in the command line and then the object list.
+
+.I OBJECT-LIST
+is the list of object types that we want to monitor.
+It may contain
+.BR dev ", " port ", " trap ", " trap-group .
+
+.B devlink
+opens Devlink Netlink socket, listens on it and dumps state changes.
+
+.SH SEE ALSO
+.BR devlink (8),
+.BR devlink-dev (8),
+.BR devlink-sb (8),
+.BR devlink-port (8),
+.BR devlink-trap (8),
+.br
+
+.SH AUTHOR
+Jiri Pirko <jiri@mellanox.com>
diff --git a/man/man8/devlink-port.8 b/man/man8/devlink-port.8
new file mode 100644
index 0000000..a639d01
--- /dev/null
+++ b/man/man8/devlink-port.8
@@ -0,0 +1,128 @@
+.TH DEVLINK\-PORT 8 "14 Mar 2016" "iproute2" "Linux"
+.SH NAME
+devlink-port \- devlink port configuration
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B devlink
+.RI "[ " OPTIONS " ]"
+.B port
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-n\fR[\fIno-nice-names\fR] }
+
+.ti -8
+.BR "devlink port set "
+.IR DEV/PORT_INDEX
+.RI "[ "
+.BR type " { " eth " | " ib " | " auto " }"
+.RI "]"
+
+.ti -8
+.BR "devlink port split "
+.IR DEV/PORT_INDEX
+.BR count
+.IR COUNT
+
+.ti -8
+.BR "devlink port unsplit "
+.IR DEV/PORT_INDEX
+
+.ti -8
+.B devlink port show
+.RI "[ " DEV/PORT_INDEX " ]"
+
+.ti -8
+.B devlink port help
+
+.SH "DESCRIPTION"
+.SS devlink port set - change devlink port attributes
+
+.PP
+.B "DEV/PORT_INDEX"
+- specifies the devlink port to operate on.
+
+.in +4
+Format is:
+.in +2
+BUS_NAME/BUS_ADDRESS/PORT_INDEX
+
+.TP
+.BR type " { " eth " | " ib " | " auto " } "
+set port type
+
+.I eth
+- Ethernet
+
+.I ib
+- Infiniband
+
+.I auto
+- autoselect
+
+.SS devlink port split - split devlink port into more
+
+.PP
+.B "DEV/PORT_INDEX"
+- specifies the devlink port to operate on.
+
+.TP
+.BI count " COUNT"
+number of ports to split to.
+
+.SS devlink port unsplit - unsplit previously split devlink port
+Could be performed on any split port of the same split group.
+
+.PP
+.B "DEV/PORT_INDEX"
+- specifies the devlink port to operate on.
+
+.SS devlink port show - display devlink port attributes
+
+.PP
+.I "DEV/PORT_INDEX"
+- specifies the devlink port to show.
+If this argument is omitted all ports are listed.
+
+.SH "EXAMPLES"
+.PP
+devlink port show
+.RS 4
+Shows the state of all devlink ports on the system.
+.RE
+.PP
+devlink port show pci/0000:01:00.0/1
+.RS 4
+Shows the state of specified devlink port.
+.RE
+.PP
+devlink port set pci/0000:01:00.0/1 type eth
+.RS 4
+Set type of specified devlink port to Ethernet.
+.RE
+.PP
+devlink port split pci/0000:01:00.0/1 count 4
+.RS 4
+Split the specified devlink port into four ports.
+.RE
+.PP
+devlink port unsplit pci/0000:01:00.0/1
+.RS 4
+Unplit the specified previously split devlink port.
+
+.SH SEE ALSO
+.BR devlink (8),
+.BR devlink-dev (8),
+.BR devlink-sb (8),
+.BR devlink-monitor (8),
+.br
+
+.SH AUTHOR
+Jiri Pirko <jiri@mellanox.com>
diff --git a/man/man8/devlink-region.8 b/man/man8/devlink-region.8
new file mode 100644
index 0000000..ff10cdb
--- /dev/null
+++ b/man/man8/devlink-region.8
@@ -0,0 +1,131 @@
+.TH DEVLINK\-REGION 8 "10 Jan 2018" "iproute2" "Linux"
+.SH NAME
+devlink-region \- devlink address region access
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B devlink
+.RI "[ " OPTIONS " ]"
+.B region
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-n\fR[\fIno-nice-names\fR] }
+
+.ti -8
+.BR "devlink region show"
+.RI "[ " DEV/REGION " ]"
+
+.ti -8
+.BR "devlink region del"
+.RI "" DEV/REGION ""
+.BR "snapshot"
+.RI "" SNAPSHOT_ID ""
+
+.ti -8
+.BR "devlink region dump"
+.RI "" DEV/REGION ""
+.BR "snapshot"
+.RI "" SNAPSHOT_ID ""
+
+.ti -8
+.BR "devlink region read"
+.RI "" DEV/REGION ""
+.BR "[ "
+.BR "snapshot"
+.RI "" SNAPSHOT_ID ""
+.BR "]"
+.BR "address"
+.RI "" ADDRESS "
+.BR "length"
+.RI "" LENGTH ""
+
+.ti -8
+.B devlink region help
+
+.SH "DESCRIPTION"
+.SS devlink region show - Show all supported address regions names, snapshots and sizes
+
+.PP
+.I "DEV/REGION"
+- specifies the devlink device and address-region to query.
+
+.SS devlink region del - Delete a snapshot specified by address-region name and snapshot ID
+
+.PP
+.I "DEV/REGION"
+- specifies the devlink device and address-region to delete the snapshot from
+
+.PP
+snapshot
+.I "SNAPSHOT_ID"
+- specifies the snapshot ID to delete
+
+.SS devlink region dump - Dump all the available data from a region or from snapshot of a region
+
+.PP
+.I "DEV/REGION"
+- specifies the device and address-region to dump from.
+
+.PP
+snapshot
+.I "SNAPSHOT_ID"
+- specifies the snapshot-id of the region to dump.
+
+.SS devlink region read - Read from a specific region address for a given length
+
+.PP
+.I "DEV/REGION"
+- specifies the device and address-region to read from.
+
+.PP
+snapshot
+.I "SNAPSHOT_ID"
+- specifies the snapshot-id of the region to read.
+
+.PP
+address
+.I "ADDRESS"
+- specifies the address to read from.
+
+.PP
+length
+.I "LENGTH"
+- specifies the length of data to read.
+
+.SH "EXAMPLES"
+.PP
+devlink region show
+.RS 4
+List available address regions and snapshot.
+.RE
+.PP
+devlink region del pci/0000:00:05.0/cr-space snapshot 1
+.RS 4
+Delete snapshot id 1 from cr-space address region from device pci/0000:00:05.0.
+.RE
+.PP
+devlink region dump pci/0000:00:05.0/cr-space snapshot 1
+.RS 4
+Dump the snapshot taken from cr-space address region with ID 1
+.RE
+.PP
+devlink region read pci/0000:00:05.0/cr-space snapshot 1 address 0x10 legth 16
+.RS 4
+Read from address 0x10, 16 Bytes of snapshot ID 1 taken from cr-space address region
+
+.SH SEE ALSO
+.BR devlink (8),
+.BR devlink-dev (8),
+.BR devlink-port (8),
+.BR devlink-monitor (8),
+.br
+
+.SH AUTHOR
+Alex Vesker <valex@mellanox.com>
diff --git a/man/man8/devlink-resource.8 b/man/man8/devlink-resource.8
new file mode 100644
index 0000000..b8f7880
--- /dev/null
+++ b/man/man8/devlink-resource.8
@@ -0,0 +1,78 @@
+.TH DEVLINK\-RESOURCE 8 "11 Feb 2018" "iproute2" "Linux"
+.SH NAME
+devlink-resource \- devlink device resource configuration
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B devlink
+.RI "[ " OPTIONS " ]"
+.B resource
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-v\fR[\fIerbose\fR] }
+
+.ti -8
+.B devlink resource show
+.IR DEV
+
+.ti -8
+.B devlink resource help
+
+.ti -8
+.BR "devlink resource set"
+.IR DEV
+.BI path " RESOURCE_PATH"
+.BI size " RESOURCE_SIZE"
+
+.SH "DESCRIPTION"
+.SS devlink resource show - display devlink device's resosources
+
+.PP
+.I "DEV"
+- specifies the devlink device to show.
+
+.in +4
+Format is:
+.in +2
+BUS_NAME/BUS_ADDRESS
+
+.SS devlink resource set - sets resource size of specific resource
+
+.PP
+.I "DEV"
+- specifies the devlink device.
+
+.TP
+.BI path " RESOURCE_PATH"
+Resource's path.
+
+.TP
+.BI size " RESOURCE_SIZE"
+The new resource's size.
+
+.SH "EXAMPLES"
+.PP
+devlink resource show pci/0000:01:00.0
+.RS 4
+Shows the resources of the specified devlink device.
+.RE
+.PP
+devlink resource set pci/0000:01:00.0 /kvd/linear 98304
+.RS 4
+Sets the size of the specified resource for the specified devlink device.
+
+.SH SEE ALSO
+.BR devlink (8),
+.BR devlink-port (8),
+.BR devlink-sb (8),
+.BR devlink-monitor (8),
+.br
+
+.SH AUTHOR
+Arkadi Sharshevsky <arkadis@mellanox.com>
diff --git a/man/man8/devlink-sb.8 b/man/man8/devlink-sb.8
new file mode 100644
index 0000000..91b6818
--- /dev/null
+++ b/man/man8/devlink-sb.8
@@ -0,0 +1,323 @@
+.TH DEVLINK\-SB 8 "14 Apr 2016" "iproute2" "Linux"
+.SH NAME
+devlink-sb \- devlink shared buffer configuration
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B devlink
+.RI "[ " OPTIONS " ]"
+.B sb
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-n\fR[\fIno-nice-names\fR] }
+
+.ti -8
+.BR "devlink sb show "
+.RI "[ " DEV " [ "
+.B sb
+.IR SB_INDEX " ] ]"
+
+.ti -8
+.BR "devlink sb pool show "
+.RI "[ " DEV " [ "
+.B sb
+.IR SB_INDEX " ] "
+.br
+.B pool
+.IR POOL_INDEX " ]"
+
+.ti -8
+.BI "devlink sb pool set " DEV "
+.RB "[ " sb
+.IR SB_INDEX " ] "
+.br
+.BI pool " POOL_INDEX "
+.br
+.BI size " POOL_SIZE "
+.br
+.BR thtype " { " static " | " dynamic " }"
+
+.ti -8
+.BR "devlink sb port pool show "
+.RI "[ " DEV/PORT_INDEX " [ "
+.B sb
+.IR SB_INDEX " ] "
+.br
+.B pool
+.IR POOL_INDEX " ]"
+
+.ti -8
+.BI "devlink sb port pool set " DEV/PORT_INDEX "
+.RB "[ " sb
+.IR SB_INDEX " ] "
+.br
+.BI pool " POOL_INDEX "
+.br
+.BI th " THRESHOLD "
+
+.ti -8
+.BR "devlink sb tc bind show "
+.RI "[ " DEV/PORT_INDEX " [ "
+.B sb
+.IR SB_INDEX " ] "
+.br
+.BI tc " TC_INDEX "
+.br
+.B type
+.RB "{ " ingress " | " egress " } ]"
+
+.ti -8
+.BI "devlink sb tc bind set " DEV/PORT_INDEX "
+.RB "[ " sb
+.IR SB_INDEX " ] "
+.br
+.BI tc " TC_INDEX "
+.br
+.BR type " { " ingress " | " egress " }"
+.br
+.BI pool " POOL_INDEX "
+.br
+.BI th " THRESHOLD "
+
+.ti -8
+.BR "devlink sb occupancy show "
+.RI "{ " DEV " | " DEV/PORT_INDEX " } [ "
+.B sb
+.IR SB_INDEX " ] "
+
+.ti -8
+.BR "devlink sb occupancy snapshot "
+.IR DEV " [ "
+.B sb
+.IR SB_INDEX " ]"
+
+.ti -8
+.BR "devlink sb occupancy clearmax "
+.IR DEV " [ "
+.B sb
+.IR SB_INDEX " ]"
+
+.ti -8
+.B devlink sb help
+
+.SH "DESCRIPTION"
+.SS devlink sb show - display available shared buffers and their attributes
+
+.PP
+.I "DEV"
+- specifies the devlink device to show shared buffers.
+If this argument is omitted all shared buffers of all devices are listed.
+
+.PP
+.I "SB_INDEX"
+- specifies the shared buffer.
+If this argument is omitted shared buffer with index 0 is selected.
+Behaviour of this argument it the same for every command.
+
+.SS devlink sb pool show - display available pools and their attributes
+
+.PP
+.I "DEV"
+- specifies the devlink device to show pools.
+If this argument is omitted all pools of all devices are listed.
+
+Display available pools listing their
+.B type, size, thtype
+and
+.B cell_size. cell_size
+is the allocation granularity of memory within the shared buffer. Drivers
+may round up, round down or reject
+.B size
+passed to the set command if it is not multiple of
+.B cell_size.
+
+.SS devlink sb pool set - set attributes of pool
+
+.PP
+.I "DEV"
+- specifies the devlink device to set pool.
+
+.TP
+.BI size " POOL_SIZE"
+size of the pool in Bytes.
+
+.TP
+.BR thtype " { " static " | " dynamic " } "
+pool threshold type.
+
+.I static
+- Threshold values for the pool will be passed in Bytes.
+
+.I dynamic
+- Threshold values ("to_alpha") for the pool will be used to compute alpha parameter according to formula:
+.br
+.in +16
+alpha = 2 ^ (to_alpha - 10)
+.in -16
+
+.in +10
+The range of the passed value is between 0 to 20. The computed alpha is used to determine the maximum usage of the flow:
+.in -10
+.br
+.in +16
+max_usage = alpha / (1 + alpha) * Free_Buffer
+.in -16
+
+.SS devlink sb port pool show - display port-pool combinations and threshold for each
+.I "DEV/PORT_INDEX"
+- specifies the devlink port.
+
+.TP
+.BI pool " POOL_INDEX"
+pool index.
+
+.SS devlink sb port pool set - set port-pool threshold
+.I "DEV/PORT_INDEX"
+- specifies the devlink port.
+
+.TP
+.BI pool " POOL_INDEX"
+pool index.
+
+.TP
+.BI th " THRESHOLD"
+threshold value. Type of the value is either Bytes or "to_alpha", depends on
+.B thtype
+set for the pool.
+
+.SS devlink sb tc bind show - display port-TC to pool bindings and threshold for each
+
+.I "DEV/PORT_INDEX"
+- specifies the devlink port.
+
+.TP
+.BI tc " TC_INDEX"
+index of either ingress or egress TC, usually in range 0 to 8 (depends on device).
+
+.TP
+.BR type " { " ingress " | " egress " } "
+TC type.
+
+.SS devlink sb tc bind set - set port-TC to pool binding with specified threshold
+
+.I "DEV/PORT_INDEX"
+- specifies the devlink port.
+
+.TP
+.BI tc " TC_INDEX"
+index of either ingress or egress TC, usually in range 0 to 8 (depends on device).
+
+.TP
+.BR type " { " ingress " | " egress " } "
+TC type.
+
+.TP
+.BI pool " POOL_INDEX"
+index of pool to bind this to.
+
+.TP
+.BI th " THRESHOLD"
+threshold value. Type of the value is either Bytes or "to_alpha", depends on
+.B thtype
+set for the pool.
+
+.SS devlink sb occupancy show - display shared buffer occupancy values for device or port
+
+.PP
+This command is used to browse shared buffer occupancy values. Values are showed for every port-pool combination as well as for all port-TC combinations (with pool this port-TC is bound to). Format of value is:
+.br
+.in +16
+current_value/max_value
+.in -16
+Note that before showing values, one has to issue
+.B occupancy snapshot
+command first.
+
+.PP
+.I "DEV"
+- specifies the devlink device to show occupancy values for.
+
+.I "DEV/PORT_INDEX"
+- specifies the devlink port to show occupancy values for.
+
+.SS devlink sb occupancy snapshot - take occupancy snapshot of shared buffer for device
+This command is used to take a snapshot of shared buffer occupancy values. After that, the values can be showed using
+.B occupancy show
+command.
+
+.PP
+.I "DEV"
+- specifies the devlink device to take occupancy snapshot on.
+
+.SS devlink sb occupancy clearmax - clear occupancy watermarks of shared buffer for device
+This command is used to reset maximal occupancy values reached for whole device. Note that before browsing reset values, one has to issue
+.B occupancy snapshot
+command.
+
+.PP
+.I "DEV"
+- specifies the devlink device to clear occupancy watermarks on.
+
+.SH "EXAMPLES"
+.PP
+devlink sb show
+.RS 4
+List available share buffers.
+.RE
+.PP
+devlink sb pool show
+.RS 4
+List available pools and their config.
+.RE
+.PP
+devlink sb port pool show pci/0000:03:00.0/1 pool 0
+.RS 4
+Show port-pool setup for specified port and pool.
+.RE
+.PP
+sudo devlink sb port pool set pci/0000:03:00.0/1 pool 0 th 15
+.RS 4
+Change threshold for port specified port and pool.
+.RE
+.PP
+devlink sb tc bind show pci/0000:03:00.0/1 tc 0 type ingress
+.RS 4
+Show pool binding and threshold for specified port and TC.
+.RE
+.PP
+sudo devlink sb tc bind set pci/0000:03:00.0/1 tc 0 type ingress pool 0 th 9
+.RS 4
+Set pool binding and threshold for specified port and TC.
+.RE
+.PP
+sudo devlink sb occupancy snapshot pci/0000:03:00.0
+.RS 4
+Make a snapshot of occupancy of shared buffer for specified devlink device.
+.RE
+.PP
+devlink sb occupancy show pci/0000:03:00.0/1
+.RS 4
+Show occupancy for specified port from the snapshot.
+.RE
+.PP
+sudo devlink sb occupancy clearmax pci/0000:03:00.0
+.RS 4
+Clear watermarks for shared buffer of specified devlink device.
+
+
+.SH SEE ALSO
+.BR devlink (8),
+.BR devlink-dev (8),
+.BR devlink-port (8),
+.BR devlink-monitor (8),
+.br
+
+.SH AUTHOR
+Jiri Pirko <jiri@mellanox.com>
diff --git a/man/man8/devlink-trap.8 b/man/man8/devlink-trap.8
new file mode 100644
index 0000000..4f079eb
--- /dev/null
+++ b/man/man8/devlink-trap.8
@@ -0,0 +1,138 @@
+.TH DEVLINK\-TRAP 8 "2 August 2019" "iproute2" "Linux"
+.SH NAME
+devlink-trap \- devlink trap configuration
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B devlink
+.RI "[ " OPTIONS " ]"
+.B trap
+.RI "{ " COMMAND " |"
+.BR help " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-v\fR[\fIerbose\fR] |
+\fB\-s\fR[\fItatistics\fR] }
+
+.ti -8
+.B "devlink trap show"
+.RI "[ " DEV
+.B trap
+.IR TRAP " ]"
+
+.ti -8
+.BI "devlink trap set " DEV " trap " TRAP
+.RB "[ " action " { " trap " | " drop " } ]"
+
+.ti -8
+.B "devlink trap group show"
+.RI "[ " DEV
+.B group
+.IR GROUP " ]"
+
+.ti -8
+.BI "devlink trap group set " DEV " group " GROUP
+.RB "[ " action " { " trap " | " drop " } ]"
+
+.ti -8
+.B devlink trap help
+
+.SH "DESCRIPTION"
+.SS devlink trap show - display available packet traps and their attributes
+
+.PP
+.I "DEV"
+- specifies the devlink device from which to show packet traps.
+If this argument is omitted all packet traps of all devices are listed.
+
+.PP
+.BI "trap " TRAP
+- specifies the packet trap.
+Only applicable if a devlink device is also specified.
+
+.SS devlink trap set - set attributes of a packet trap
+
+.PP
+.I "DEV"
+- specifies the devlink device the packet trap belongs to.
+
+.PP
+.BI "trap " TRAP
+- specifies the packet trap.
+
+.TP
+.BR action " { " trap " | " drop " } "
+packet trap action.
+
+.I trap
+- the sole copy of the packet is sent to the CPU.
+
+.I drop
+- the packet is dropped by the underlying device and a copy is not sent to the CPU.
+
+.SS devlink trap group show - display available packet trap groups and their attributes
+
+.PP
+.I "DEV"
+- specifies the devlink device from which to show packet trap groups.
+If this argument is omitted all packet trap groups of all devices are listed.
+
+.PP
+.BI "group " GROUP
+- specifies the packet trap group.
+Only applicable if a devlink device is also specified.
+
+.SS devlink trap group set - set attributes of a packet trap group
+
+.PP
+.I "DEV"
+- specifies the devlink device the packet trap group belongs to.
+
+.PP
+.BI "group " GROUP
+- specifies the packet trap group.
+
+.TP
+.BR action " { " trap " | " drop " } "
+packet trap action. The action is set for all the packet traps member in the
+trap group. The actions of non-drop traps cannot be changed and are thus
+skipped.
+
+.SH "EXAMPLES"
+.PP
+devlink trap show
+.RS 4
+List available packet traps.
+.RE
+.PP
+devlink trap group show
+.RS 4
+List available packet trap groups.
+.RE
+.PP
+devlink -vs trap show pci/0000:01:00.0 trap source_mac_is_multicast
+.RS 4
+Show attributes and statistics of a specific packet trap.
+.RE
+.PP
+devlink -s trap group show pci/0000:01:00.0 group l2_drops
+.RS 4
+Show attributes and statistics of a specific packet trap group.
+.RE
+.PP
+devlink trap set pci/0000:01:00.0 trap source_mac_is_multicast action trap
+.RS 4
+Set the action of a specific packet trap to 'trap'.
+
+.SH SEE ALSO
+.BR devlink (8),
+.BR devlink-dev (8),
+.BR devlink-monitor (8),
+.br
+
+.SH AUTHOR
+Ido Schimmel <idosch@mellanox.com>
diff --git a/man/man8/devlink.8 b/man/man8/devlink.8
new file mode 100644
index 0000000..12d4894
--- /dev/null
+++ b/man/man8/devlink.8
@@ -0,0 +1,135 @@
+.TH DEVLINK 8 "14 Mar 2016" "iproute2" "Linux"
+.SH NAME
+devlink \- Devlink tool
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B devlink
+.RI "[ " OPTIONS " ] { " dev | port | monitor | sb | resource | region | health | trap " } { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.B devlink
+.RB "[ " -force " ] "
+.BI "-batch " filename
+.sp
+
+.SH OPTIONS
+
+.TP
+.BR "\-V" , " --Version"
+Print the version of the
+.B devlink
+utility and exit.
+
+.TP
+.BR "\-b", " \-batch " <FILENAME>
+Read commands from provided file or standard input and invoke them.
+First failure will cause termination of devlink.
+
+.TP
+.BR "\-force"
+Don't terminate devlink on errors in batch mode.
+If there were any errors during execution of the commands, the application return code will be non zero.
+
+.TP
+.BR "\-n" , " --no-nice-names"
+Turn off printing out nice names, for example netdevice ifnames instead of devlink port identification.
+
+.TP
+.BR "\-j" , " --json"
+Generate JSON output.
+
+.TP
+.BR "\-p" , " --pretty"
+When combined with -j generate a pretty JSON output.
+
+.TP
+.BR "\-v" , " --verbose"
+Turn on verbose output.
+
+.TP
+.BR "\-s" , " --statistics"
+Output statistics.
+
+.SS
+.I OBJECT
+
+.TP
+.B dev
+- devlink device.
+
+.TP
+.B port
+- devlink port.
+
+.TP
+.B monitor
+- watch for netlink messages.
+
+.TP
+.B sb
+- devlink shared buffer configuration.
+
+.TP
+.B resource
+- devlink device resource configuration.
+
+.TP
+.B region
+- devlink address region access
+
+.TP
+.B health
+- devlink reporting and recovery
+
+.TP
+.B trap
+- devlink trap configuration
+
+.SS
+.I COMMAND
+
+Specifies the action to perform on the object.
+The set of possible actions depends on the object type.
+As a rule, it is possible to
+.B show
+(or
+.B list
+) objects, but some objects do not allow all of these operations
+or have some additional commands. The
+.B help
+command is available for all objects. It prints
+out a list of available commands and argument syntax conventions.
+.sp
+If no command is given, some default command is assumed.
+Usually it is
+.B list
+or, if the objects of this class cannot be listed,
+.BR "help" .
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR devlink-dev (8),
+.BR devlink-port (8),
+.BR devlink-monitor (8),
+.BR devlink-sb (8),
+.BR devlink-resource (8),
+.BR devlink-region (8),
+.BR devlink-health (8),
+.BR devlink-trap (8),
+.br
+
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Jiri Pirko <jiri@mellanox.com>
diff --git a/man/man8/genl.8 b/man/man8/genl.8
new file mode 100644
index 0000000..b9de594
--- /dev/null
+++ b/man/man8/genl.8
@@ -0,0 +1,77 @@
+.TH GENL 8 "29 Oct 2015" "iproute2" "Linux"
+.SH NAME
+genl \- generic netlink utility frontend
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR genl " [ " -s [ tatistics "] ] [ " -d [ etails "] ] [ " -r [ aw "] ] " OBJECT
+
+.ti -8
+.BR genl " { " -V [ ersion "] | " -h [ elp "] }"
+
+.ti -8
+.IR OBJECT " := { "
+.B ctrl
+.IR CTRL_OPTS " }"
+
+.ti -8
+.IR CTRL_OPTS " := { "
+.BR help " | " list " | " monitor " | " get
+.IR PARMS " }"
+
+.ti -8
+.IR PARMS " := { "
+.B name
+.IR NAME " | "
+.B id
+.IR ID " }"
+.SH DESCRIPTION
+The
+.B genl
+utility provides a simple frontend to the generic netlink library. Although it's
+designed to support multiple
+.IR OBJECT s,
+for now only the
+.B ctrl
+object is available, which is used to query the generic netlink controller.
+.SS ctrl
+The generic netlink controller can be queried in various ways:
+.TP
+.B help
+This command just prints a help text for the
+.B ctrl
+object.
+.TP
+.B list
+Show the registered netlink users.
+.TP
+.B monitor
+Listen for generic netlink notifications.
+.TP
+.B get
+Query the controller for a given user, identified either by
+.BR name " or " id .
+.SH OPTIONS
+genl supports the following options.
+.TP
+.B \-h, \-help
+Show summary of options.
+.TP
+.B \-V, \-Version
+Show version of program.
+.TP
+.B \-s, \-stats, \-statistics
+Show object statistics.
+.TP
+.B \-d, \-details
+Show object details.
+.TP
+.B \-r, \-raw
+Dump raw output only.
+.SH SEE ALSO
+.BR ip (8)
+.br
+.SH AUTHOR
+genl was written by Jamal Hadi Salim <hadi@cyberus.ca>.
+.PP
+This manual page was written by Petr Sabata <contyk@redhat.com>.
diff --git a/man/man8/ifcfg.8 b/man/man8/ifcfg.8
new file mode 100644
index 0000000..1a3786c
--- /dev/null
+++ b/man/man8/ifcfg.8
@@ -0,0 +1,48 @@
+.TH IFCFG 8 "September 24 2009" "iproute2" "Linux"
+.SH NAME
+ifcfg \- simplistic script which replaces ifconfig IP management
+.SH SYNOPSIS
+.ad l
+.in +8
+.ti -8
+.B ifcfg
+.RI "[ " DEVICE " ] [ " command " ] " ADDRESS " [ " PEER " ] "
+.sp
+
+.SH DESCRIPTION
+This manual page documents briefly the
+.B ifcfg
+command.
+.PP
+This is a simplistic script replacing one option of
+.B ifconfig
+, namely, IP address management. It not only adds
+addresses, but also carries out Duplicate Address Detection RFC-DHCP,
+sends unsolicited ARP to update the caches of other hosts sharing
+the interface, adds some control routes and restarts Router Discovery
+when it is necessary.
+
+.SH IFCONFIG - COMMAND SYNTAX
+
+.SS
+.TP
+.B DEVICE
+- it may have alias, suffix, separated by colon.
+
+.TP
+.B command
+- add, delete or stop.
+
+.TP
+.B ADDRESS
+- optionally followed by prefix length.
+
+.TP
+.B peer
+- optional peer address for pointpoint interfaces.
+
+.SH NOTES
+This script is not suitable for use with IPv6.
+
+.SH SEE ALSO
+.RB "IP Command reference " ip-cref.ps
diff --git a/man/man8/ifstat.8 b/man/man8/ifstat.8
new file mode 100644
index 0000000..8cd164d
--- /dev/null
+++ b/man/man8/ifstat.8
@@ -0,0 +1,77 @@
+.TH IFSTAT 8 "28 Oct 2015" "iproute2" "Linux"
+.SH NAME
+ifstat \- handy utility to read network interface statistics
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR ifstat " [ "
+.IR OPTIONS " ] [ " INTERFACE_LIST " ]"
+
+.ti -8
+.IR INTERFACE_LIST " := " INTERFACE_LIST " | " interface
+.SH DESCRIPTION
+\fBifstat\fP neatly prints out network interface statistics.
+The utility keeps records of the previous data displayed in history files and
+by default only shows difference between the last and the current call.
+Location of the history files defaults to /tmp/.ifstat.u$UID but may be
+overridden with the IFSTAT_HISTORY environment variable. Similarly, the default
+location for xstat (extended stats) is /tmp/.<xstat name>_ifstat.u$UID.
+.SH OPTIONS
+.TP
+.B \-h, \-\-help
+Show summary of options.
+.TP
+.B \-V, \-\-version
+Show version of program.
+.TP
+.B \-a, \-\-ignore
+Ignore the history file.
+.TP
+.B \-d, \-\-scan=SECS
+Sample statistics every SECS second.
+.TP
+.B \-e, \-\-errors
+Show errors.
+.TP
+.B \-n, \-\-nooutput
+Don't display any output. Update the history file only.
+.TP
+.B \-r, \-\-reset
+Reset history.
+.TP
+.B \-s, \-\-noupdate
+Don't update the history file.
+.TP
+.B \-t, \-\-interval=SECS
+Report average over the last SECS seconds.
+.TP
+.B \-z, \-\-zeros
+Show entries with zero activity.
+.TP
+.B \-j, \-\-json
+Display results in JSON format
+.TP
+.B \-p, \-\-pretty
+If combined with
+.BR \-\-json ,
+pretty print the output.
+.TP
+.B \-x, \-\-extended=TYPE
+Show extended stats of TYPE. Supported types are:
+
+.in +8
+.B cpu_hits
+- Counts only packets that went via the CPU.
+.in -8
+
+.SH ENVIRONMENT
+.TP
+.B IFSTAT_HISTORY
+If set, it's value is interpreted as alternate history file path.
+.SH SEE ALSO
+.BR ip (8)
+.br
+.SH AUTHOR
+ifstat was written by Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>.
+.PP
+This manual page was written by Petr Sabata <contyk@redhat.com>.
diff --git a/man/man8/ip-address.8.in b/man/man8/ip-address.8.in
new file mode 100644
index 0000000..fe773c9
--- /dev/null
+++ b/man/man8/ip-address.8.in
@@ -0,0 +1,465 @@
+.TH "IP\-ADDRESS" 8 "20 Dec 2011" "iproute2" "Linux"
+.SH "NAME"
+ip-address \- protocol address management
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B address
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.BR "ip address" " { " add " | " change " | " replace " } "
+.IB IFADDR " dev " IFNAME
+.RI "[ " LIFETIME " ] [ " CONFFLAG-LIST " ]"
+
+.ti -8
+.BR "ip address del"
+.IB IFADDR " dev " IFNAME " [ " mngtmpaddr " ]"
+
+.ti -8
+.BR "ip address" " { " save " | " flush " } [ " dev
+.IR IFNAME " ] [ "
+.B scope
+.IR SCOPE-ID " ] [ "
+.B metric
+.IR METRIC " ] [ "
+.B to
+.IR PREFIX " ] [ " FLAG-LIST " ] [ "
+.B label
+.IR PATTERN " ] [ " up " ]"
+
+.ti -8
+.BR "ip address" " [ " show " [ " dev
+.IR IFNAME " ] [ "
+.B scope
+.IR SCOPE-ID " ] [ "
+.B to
+.IR PREFIX " ] [ " FLAG-LIST " ] [ "
+.B label
+.IR PATTERN " ] [ "
+.B master
+.IR DEVICE " ] [ "
+.B type
+.IR TYPE " ] [ "
+.B vrf
+.IR NAME " ] [ "
+.BR up " ] ]"
+
+.ti -8
+.BR "ip address" " { " showdump " | " restore " }"
+
+.ti -8
+.IR IFADDR " := " PREFIX " | " ADDR
+.B peer
+.IR PREFIX " [ "
+.B broadcast
+.IR ADDR " ] [ "
+.B anycast
+.IR ADDR " ] [ "
+.B label
+.IR LABEL " ] [ "
+.B scope
+.IR SCOPE-ID " ]"
+
+.ti -8
+.IR SCOPE-ID " := "
+.RB "[ " host " | " link " | " global " | "
+.IR NUMBER " ]"
+
+.ti -8
+.IR FLAG-LIST " := [ " FLAG-LIST " ] " FLAG
+
+.ti -8
+.IR FLAG " := ["
+.RB [ - ] permanent " |"
+.RB [ - ] dynamic " |"
+.RB [ - ] secondary " |"
+.RB [ - ] primary " |"
+.RB [ - ] tentative " |"
+.RB [ - ] deprecated " |"
+.RB [ - ] dadfailed " |"
+.RB [ - ] temporary " |"
+.IR CONFFLAG-LIST " ]"
+
+.ti -8
+.IR CONFFLAG-LIST " := [ " CONFFLAG-LIST " ] " CONFFLAG
+
+.ti -8
+.IR CONFFLAG " := "
+.RB "[ " home " | " mngtmpaddr " | " nodad " | " optimstic " | " noprefixroute " | " autojoin " ]"
+
+.ti -8
+.IR LIFETIME " := [ "
+.BI valid_lft " LFT"
+.RB "] [ " preferred_lft
+.IR LFT " ]"
+
+.ti -8
+.IR LFT " := [ "
+.BR forever " |"
+.IR SECONDS " ]"
+
+.ti -8
+.IR TYPE " := [ "
+.BR bridge " | "
+.BR bridge_slave " |"
+.BR bond " | "
+.BR bond_slave " |"
+.BR can " | "
+.BR dummy " | "
+.BR hsr " | "
+.BR ifb " | "
+.BR ipoib " |"
+.BR macvlan " | "
+.BR macvtap " | "
+.BR vcan " | "
+.BR veth " | "
+.BR vlan " | "
+.BR vxlan " |"
+.BR ip6tnl " |"
+.BR ipip " |"
+.BR sit " |"
+.BR gre " |"
+.BR gretap " |"
+.BR erspan " |"
+.BR ip6gre " |"
+.BR ip6gretap " |"
+.BR ip6erspan " |"
+.BR vti " |"
+.BR vrf " |"
+.BR nlmon " |"
+.BR ipvlan " |"
+.BR lowpan " |"
+.BR geneve " |"
+.BR macsec " ]"
+
+.SH "DESCRIPTION"
+The
+.B address
+is a protocol (IPv4 or IPv6) address attached
+to a network device. Each device must have at least one address
+to use the corresponding protocol. It is possible to have several
+different addresses attached to one device. These addresses are not
+discriminated, so that the term
+.B alias
+is not quite appropriate for them and we do not use it in this document.
+.sp
+The
+.B ip address
+command displays addresses and their properties, adds new addresses
+and deletes old ones.
+
+.SS ip address add - add new protocol address.
+
+.TP
+.BI dev " IFNAME "
+the name of the device to add the address to.
+
+.TP
+.BI local " ADDRESS " (default)
+the address of the interface. The format of the address depends
+on the protocol. It is a dotted quad for IP and a sequence of
+hexadecimal halfwords separated by colons for IPv6. The
+.I ADDRESS
+may be followed by a slash and a decimal number which encodes
+the network prefix length.
+
+.TP
+.BI peer " ADDRESS"
+the address of the remote endpoint for pointopoint interfaces.
+Again, the
+.I ADDRESS
+may be followed by a slash and a decimal number, encoding the network
+prefix length. If a peer address is specified, the local address
+cannot have a prefix length. The network prefix is associated
+with the peer rather than with the local address.
+
+.TP
+.BI broadcast " ADDRESS"
+the broadcast address on the interface.
+.sp
+It is possible to use the special symbols
+.B '+'
+and
+.B '-'
+instead of the broadcast address. In this case, the broadcast address
+is derived by setting/resetting the host bits of the interface prefix.
+
+.TP
+.BI label " LABEL"
+Each address may be tagged with a label string.
+In order to preserve compatibility with Linux-2.0 net aliases,
+this string must coincide with the name of the device or must be prefixed
+with the device name followed by colon.
+The maximum allowed total length of label is 15 characters.
+
+.TP
+.BI scope " SCOPE_VALUE"
+the scope of the area where this address is valid.
+The available scopes are listed in file
+.BR "@SYSCONFDIR@/rt_scopes" .
+Predefined scope values are:
+
+.in +8
+.B global
+- the address is globally valid.
+.sp
+.B site
+- (IPv6 only, deprecated) the address is site local, i.e. it is
+valid inside this site.
+.sp
+.B link
+- the address is link local, i.e. it is valid only on this device.
+.sp
+.B host
+- the address is valid only inside this host.
+.in -8
+
+.TP
+.BI metric " NUMBER"
+priority of prefix route associated with address.
+
+.TP
+.BI valid_lft " LFT"
+the valid lifetime of this address; see section 5.5.4 of
+RFC 4862. When it expires, the address is removed by the kernel.
+Defaults to
+.BR "forever" .
+
+.TP
+.BI preferred_lft " LFT"
+the preferred lifetime of this address; see section 5.5.4
+of RFC 4862. When it expires, the address is no longer used for new
+outgoing connections. Defaults to
+.BR "forever" .
+
+.TP
+.B home
+(IPv6 only) designates this address the "home address" as defined in
+RFC 6275.
+
+.TP
+.B mngtmpaddr
+(IPv6 only) make the kernel manage temporary addresses created from this one as
+template on behalf of Privacy Extensions (RFC3041). For this to become active,
+the \fBuse_tempaddr\fP sysctl setting has to be set to a value greater than
+zero. The given address needs to have a prefix length of 64. This flag allows
+to use privacy extensions in a manually configured network, just like if
+stateless auto-configuration was active.
+
+.TP
+.B nodad
+(IPv6 only) do not perform Duplicate Address Detection (RFC 4862) when
+adding this address.
+
+.TP
+.B optimistic
+(IPv6 only) When performing Duplicate Address Detection, use the RFC 4429
+optimistic variant.
+
+.TP
+.B noprefixroute
+Do not automatically create a route for the network prefix of the added
+address, and don't search for one to delete when removing the address. Changing
+an address to add this flag will remove the automatically added prefix route,
+changing it to remove this flag will create the prefix route automatically.
+
+.TP
+.B autojoin
+Joining multicast groups on Ethernet level via
+.B "ip maddr"
+command does not work if connected to an Ethernet switch that does IGMP
+snooping since the switch would not replicate multicast packets on ports that
+did not have IGMP reports for the multicast addresses.
+
+Linux VXLAN interfaces created via
+.B "ip link add vxlan"
+have the
+.B group
+option that enables them to do the required join.
+
+Using the
+.B autojoin
+flag when adding a multicast address enables similar functionality for
+Openvswitch VXLAN interfaces as well as other tunneling mechanisms that need to
+receive multicast traffic.
+
+.SS ip address delete - delete protocol address
+.B Arguments:
+coincide with the arguments of
+.B ip addr add.
+The device name is a required argument. The rest are optional.
+If no arguments are given, the first address is deleted.
+
+.SS ip address show - look at protocol addresses
+
+.TP
+.BI dev " IFNAME " (default)
+name of device.
+
+.TP
+.BI scope " SCOPE_VAL"
+only list addresses with this scope.
+
+.TP
+.BI to " PREFIX"
+only list addresses matching this prefix.
+
+.TP
+.BI label " PATTERN"
+only list addresses with labels matching the
+.IR "PATTERN" .
+.I PATTERN
+is a usual shell style pattern.
+
+.TP
+.BI master " DEVICE"
+only list interfaces enslaved to this master device.
+
+.TP
+.BI vrf " NAME "
+only list interfaces enslaved to this vrf.
+
+.TP
+.BI type " TYPE"
+only list interfaces of the given type.
+
+Note that the type name is not checked against the list of supported types -
+instead it is sent as-is to the kernel. Later it is used to filter the returned
+interface list by comparing it with the relevant attribute in case the kernel
+didn't filter already. Therefore any string is accepted, but may lead to empty
+output.
+
+.TP
+.B up
+only list running interfaces.
+
+.TP
+.BR dynamic " and " permanent
+(IPv6 only) only list addresses installed due to stateless
+address configuration or only list permanent (not dynamic)
+addresses. These two flags are inverses of each other, so
+.BR -dynamic " is equal to " permanent " and "
+.BR -permanent " is equal to " dynamic .
+
+.TP
+.B tentative
+(IPv6 only) only list addresses which have not yet passed duplicate
+address detection.
+
+.TP
+.B -tentative
+(IPv6 only) only list addresses which are not in the process of
+duplicate address detection currently.
+
+.TP
+.B deprecated
+(IPv6 only) only list deprecated addresses.
+
+.TP
+.B -deprecated
+(IPv6 only) only list addresses not being deprecated.
+
+.TP
+.B dadfailed
+(IPv6 only) only list addresses which have failed duplicate
+address detection.
+
+.TP
+.B -dadfailed
+(IPv6 only) only list addresses which have not failed duplicate
+address detection.
+
+.TP
+.BR temporary " or " secondary
+List temporary IPv6 or secondary IPv4 addresses only. The Linux kernel shares a
+single bit for those, so they are actually aliases for each other although the
+meaning differs depending on address family.
+
+.TP
+.BR -temporary " or " -secondary
+These flags are aliases for
+.BR primary .
+
+.TP
+.B primary
+List only primary addresses, in IPv6 exclude temporary ones. This flag is the
+inverse of
+.BR temporary " and " secondary .
+
+.TP
+.B -primary
+This is an alias for
+.BR temporary " or " secondary .
+
+.SS ip address flush - flush protocol addresses
+This command flushes the protocol addresses selected by some criteria.
+
+.PP
+This command has the same arguments as
+.BR show " except that " type " and " master " selectors are not supported."
+Another difference is that it does not run when no arguments are given.
+
+.PP
+.B Warning:
+This command and other
+.B flush
+commands are unforgiving. They will cruelly purge all the addresses.
+
+.PP
+With the
+.B -statistics
+option, the command becomes verbose. It prints out the number of deleted
+addresses and the number of rounds made to flush the address list.
+If this option is given twice,
+.B ip address flush
+also dumps all the deleted addresses in the format described in the
+previous subsection.
+
+.SH "EXAMPLES"
+.PP
+ip address show
+.RS 4
+Shows IPv4 and IPv6 addresses assigned to all network interfaces. The 'show'
+subcommand can be omitted.
+.RE
+.PP
+ip address show up
+.RS 4
+Same as above except that only addresses assigned to active network interfaces
+are shown.
+.RE
+.PP
+ip address show dev eth0
+.RS 4
+Shows IPv4 and IPv6 addresses assigned to network interface eth0.
+.RE
+.PP
+ip address add 2001:0db8:85a3::0370:7334/64 dev eth1
+.RS 4
+Adds an IPv6 address to network interface eth1.
+.RE
+.PP
+ip address delete 2001:0db8:85a3::0370:7334/64 dev eth1
+.RS 4
+Delete the IPv6 address added above.
+.RE
+.PP
+ip address flush dev eth4 scope global
+.RS 4
+Removes all global IPv4 and IPv6 addresses from device eth4. Without 'scope
+global' it would remove all addresses including IPv6 link-local ones.
+.RE
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-addrlabel.8 b/man/man8/ip-addrlabel.8
new file mode 100644
index 0000000..233d606
--- /dev/null
+++ b/man/man8/ip-addrlabel.8
@@ -0,0 +1,56 @@
+.TH IP\-ADDRLABEL 8 "20 Dec 2011" "iproute2" "Linux"
+.SH "NAME"
+ip-addrlabel \- protocol address label management
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip addrlabel
+.RI " { " COMMAND " | "
+.BR help " }"
+
+.ti -8
+.BR "ip addrlabel" " { " add " | " del " } " prefix
+.BR PREFIX " [ "
+.B dev
+.IR DEV " ] [ "
+.B label
+.IR NUMBER " ]"
+
+.ti -8
+.BR "ip addrlabel" " { " list " | " flush " }"
+
+.SH "DESCRIPTION"
+IPv6 address labels are used for address selection;
+they are described in RFC 3484. Precedence is managed by userspace,
+and only the label itself is stored in the kernel.
+
+.SS ip addrlabel add - add an address label
+add an address label entry to the kernel.
+.TP
+.BI prefix " PREFIX"
+.TP
+.BI dev " DEV"
+the outgoing interface.
+.TP
+.BI label " NUMBER"
+the label for the prefix.
+0xffffffff is reserved.
+.SS ip addrlabel del - delete an address label
+delete an address label entry from the kernel.
+.B Arguments:
+coincide with the arguments of
+.B ip addrlabel add
+but the label is not required.
+.SS ip addrlabel list - list address labels
+list the current address label entries in the kernel.
+.SS ip addrlabel flush - flush address labels
+flush all address labels in the kernel. This does not restore any default settings.
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Manpage by Yoshifuji Hideaki / 吉藤英明
diff --git a/man/man8/ip-fou.8 b/man/man8/ip-fou.8
new file mode 100644
index 0000000..f4e08f1
--- /dev/null
+++ b/man/man8/ip-fou.8
@@ -0,0 +1,126 @@
+.TH IP\-FOU 8 "2 Nov 2014" "iproute2" "Linux"
+.SH "NAME"
+ip-fou \- Foo-over-UDP receive port configuration
+.P
+ip-gue \- Generic UDP Encapsulation receive port configuration
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B fou
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+.ti -8
+.BR "ip fou add"
+.B port
+.IR PORT
+.RB "{ "
+.B gue
+.RI "|"
+.B ipproto
+.IR PROTO
+.RB " }"
+.RB "[ "
+.B local
+.IR IFADDR
+.RB " ]"
+.RB "[ "
+.B peer
+.IR IFADDR
+.RB " ]"
+.RB "[ "
+.B peer_port
+.IR PORT
+.RB " ]"
+.RB "[ "
+.B dev
+.IR IFNAME
+.RB " ]"
+.br
+.ti -8
+.BR "ip fou del"
+.B port
+.IR PORT
+.RB "[ "
+.B local
+.IR IFADDR
+.RB " ]"
+.RB "[ "
+.B peer
+.IR IFADDR
+.RB " ]"
+.RB "[ "
+.B peer_port
+.IR PORT
+.RB " ]"
+.RB "[ "
+.B dev
+.IR IFNAME
+.RB " ]"
+.br
+.ti -8
+.B ip fou show
+.SH DESCRIPTION
+The
+.B ip fou
+commands are used to create and delete receive ports for Foo-over-UDP
+(FOU) as well as Generic UDP Encapsulation (GUE).
+.PP
+Foo-over-UDP allows encapsulating packets of an IP protocol directly
+over UDP. The receiver infers the protocol of a packet received on
+a FOU UDP port to be the protocol configured for the port.
+.PP
+Generic UDP Encapsulation (GUE) encapsulates packets of an IP protocol
+within UDP and an encapsulation header. The encapsulation header contains the
+IP protocol number for the encapsulated packet.
+.PP
+When creating a FOU or GUE receive port, the port number is specified in
+.I PORT
+argument. If FOU is used, the IP protocol number associated with the port is specified in
+.I PROTO
+argument. You can bind a port to a local address/interface, by specifying the
+address in the local
+.I IFADDR
+argument or the device in the
+.I IFNAME
+argument. If you would like to connect the port, you can specify the peer
+address in the peer
+.I IFADDR
+argument and peer port in the peer_port
+.I PORT
+argument.
+.PP
+A FOU or GUE receive port is deleted by specifying
+.I PORT
+in the delete command, as well as local address/interface or peer address/port
+(if set).
+.SH EXAMPLES
+.PP
+.SS Configure a FOU receive port for GRE bound to 7777
+.nf
+# ip fou add port 7777 ipproto 47
+.PP
+.SS Configure a FOU receive port for IPIP bound to 8888
+.nf
+# ip fou add port 8888 ipproto 4
+.PP
+.SS Configure a GUE receive port bound to 9999
+.nf
+# ip fou add port 9999 gue
+.PP
+.SS Delete the GUE receive port bound to 9999
+.nf
+# ip fou del port 9999
+.SS Configure a FOU receive port for GRE bound to 1.2.3.4:7777
+.nf
+# ip fou add port 7777 ipproto 47 local 1.2.3.4
+.PP
+.SH SEE ALSO
+.br
+.BR ip (8)
+.SH AUTHOR
+Tom Herbert <therbert@google.com>
diff --git a/man/man8/ip-gue.8 b/man/man8/ip-gue.8
new file mode 100644
index 0000000..4d2914c
--- /dev/null
+++ b/man/man8/ip-gue.8
@@ -0,0 +1 @@
+.so man8/ip-fou.8
diff --git a/man/man8/ip-l2tp.8 b/man/man8/ip-l2tp.8
new file mode 100644
index 0000000..9aba6be
--- /dev/null
+++ b/man/man8/ip-l2tp.8
@@ -0,0 +1,412 @@
+.TH IP\-L2TP 8 "19 Apr 2012" "iproute2" "Linux"
+.SH "NAME"
+ip-l2tp - L2TPv3 static unmanaged tunnel configuration
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B l2tp
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+.ti -8
+.BR "ip l2tp add tunnel"
+.br
+.BI remote " ADDR " local " ADDR "
+.br
+.B tunnel_id
+.IR ID
+.B peer_tunnel_id
+.IR ID
+.br
+.RB "[ " encap " { " ip " | " udp " } ]"
+.br
+.RB "[ " udp_sport
+.IR PORT
+.RB " ] [ " udp_dport
+.IR PORT
+.RB " ]"
+.br
+.RB "[ " udp_csum " { " on " | " off " } ]"
+.br
+.RB "[ " udp6_csum_tx " { " on " | " off " } ]"
+.br
+.RB "[ " udp6_csum_rx " { " on " | " off " } ]"
+.br
+.ti -8
+.BR "ip l2tp add session"
+.RB "[ " name
+.IR NAME
+.RB " ]"
+.br
+.B tunnel_id
+.IR ID
+.B session_id
+.IR ID
+.B peer_session_id
+.IR ID
+.br
+.RB "[ " cookie
+.IR HEXSTR
+.RB " ] [ " peer_cookie
+.IR HEXSTR
+.RB " ]"
+.br
+.RB "[ " l2spec_type " { " none " | " default " } ]"
+.br
+.RB "[ " seq " { " none " | " send " | " recv " | " both " } ]"
+.br
+.ti -8
+.BR "ip l2tp del tunnel"
+.B tunnel_id
+.IR ID
+.br
+.ti -8
+.BR "ip l2tp del session"
+.B tunnel_id
+.IR ID
+.B session_id
+.IR ID
+.br
+.ti -8
+.BR "ip l2tp show tunnel" " [ " tunnel_id
+.IR ID " ]"
+.br
+.ti -8
+.BR "ip l2tp show session" " [ " tunnel_id
+.IR ID .B " ] ["
+.B session_id
+.IR ID " ]"
+.br
+.ti -8
+.IR NAME " := "
+.IR STRING
+.ti -8
+.IR ADDR " := { " IP_ADDRESS " |"
+.BR any " }"
+.ti -8
+.IR PORT " := { " NUMBER " }"
+.ti -8
+.IR ID " := { " NUMBER " }"
+.ti -8
+.ti -8
+.IR HEXSTR " := { 8 or 16 hex digits (4 / 8 bytes) }"
+.SH DESCRIPTION
+The
+.B ip l2tp
+commands are used to establish static, or so-called
+.I unmanaged
+L2TPv3 ethernet tunnels. For unmanaged tunnels, there is no L2TP
+control protocol so no userspace daemon is required - tunnels are
+manually created by issuing commands at a local system and at a remote
+peer.
+.PP
+L2TPv3 is suitable for Layer-2 tunneling. Static tunnels are useful
+to establish network links across IP networks when the tunnels are
+fixed. L2TPv3 tunnels can carry data of more than one session. Each
+session is identified by a session_id and its parent tunnel's
+tunnel_id. A tunnel must be created before a session can be created in
+the tunnel.
+.PP
+When creating an L2TP tunnel, the IP address of the remote peer is
+specified, which can be either an IPv4 or IPv6 address. The local IP
+address to be used to reach the peer must also be specified. This is
+the address on which the local system will listen for and accept
+received L2TP data packets from the peer.
+.PP
+L2TPv3 defines two packet encapsulation formats: UDP or IP. UDP
+encapsulation is most common. IP encapsulation uses a dedicated IP
+protocol value to carry L2TP data without the overhead of UDP. Use IP
+encapsulation only when there are no NAT devices or firewalls in the
+network path.
+.PP
+When an L2TPv3 ethernet session is created, a virtual network
+interface is created for the session, which must then be configured
+and brought up, just like any other network interface. When data is
+passed through the interface, it is carried over the L2TP tunnel to
+the peer. By configuring the system's routing tables or adding the
+interface to a bridge, the L2TP interface is like a virtual wire
+(pseudowire) connected to the peer.
+.PP
+Establishing an unmanaged L2TPv3 ethernet pseudowire involves manually
+creating L2TP contexts on the local system and at the peer. Parameters
+used at each site must correspond or no data will be passed. No
+consistency checks are possible since there is no control protocol
+used to establish unmanaged L2TP tunnels. Once the virtual network
+interface of a given L2TP session is configured and enabled, data can
+be transmitted, even if the peer isn't yet configured. If the peer
+isn't configured, the L2TP data packets will be discarded by
+the peer.
+.PP
+To establish an unmanaged L2TP tunnel, use
+.B l2tp add tunnel
+and
+.B l2tp add session
+commands described in this document. Then configure and enable the
+tunnel's virtual network interface, as required.
+.PP
+Note that unmanaged tunnels carry only ethernet frames. If you need to
+carry PPP traffic (L2TPv2) or your peer doesn't support unmanaged
+L2TPv3 tunnels, you will need an L2TP server which implements the L2TP
+control protocol. The L2TP control protocol allows dynamic L2TP
+tunnels and sessions to be established and provides for detecting and
+acting upon network failures.
+.SS ip l2tp add tunnel - add a new tunnel
+.TP
+.BI tunnel_id " ID"
+set the tunnel id, which is a 32-bit integer value. Uniquely
+identifies the tunnel. The value used must match the peer_tunnel_id
+value being used at the peer.
+.TP
+.BI peer_tunnel_id " ID"
+set the peer tunnel id, which is a 32-bit integer value assigned to
+the tunnel by the peer. The value used must match the tunnel_id value
+being used at the peer.
+.TP
+.BI remote " ADDR"
+set the IP address of the remote peer. May be specified as an IPv4
+address or an IPv6 address.
+.TP
+.BI local " ADDR"
+set the IP address of the local interface to be used for the
+tunnel. This address must be the address of a local interface. May be
+specified as an IPv4 address or an IPv6 address.
+.TP
+.BI encap " ENCAP"
+set the encapsulation type of the tunnel.
+.br
+Valid values for encapsulation are:
+.BR udp ", " ip "."
+.TP
+.BI udp_sport " PORT"
+set the UDP source port to be used for the tunnel. Must be present
+when udp encapsulation is selected. Ignored when ip encapsulation is
+selected.
+.TP
+.BI udp_dport " PORT"
+set the UDP destination port to be used for the tunnel. Must be
+present when udp encapsulation is selected. Ignored when ip
+encapsulation is selected.
+.TP
+.BI udp_csum " STATE"
+(IPv4 only) control if IPv4 UDP checksums should be calculated and checked for the
+encapsulating UDP packets, when UDP encapsulating is selected.
+Default is
+.BR off "."
+.br
+Valid values are:
+.BR on ", " off "."
+.TP
+.BI udp6_csum_tx " STATE"
+(IPv6 only) control if IPv6 UDP checksums should be calculated for encapsulating
+UDP packets, when UDP encapsulating is selected.
+Default is
+.BR on "."
+.br
+Valid values are:
+.BR on ", " off "."
+.TP
+.BI udp6_csum_rx " STATE"
+(IPv6 only) control if IPv6 UDP checksums should be checked for the encapsulating
+UDP packets, when UDP encapsulating is selected.
+Default is
+.BR on "."
+.br
+Valid values are:
+.BR on ", " off "."
+.SS ip l2tp del tunnel - destroy a tunnel
+.TP
+.BI tunnel_id " ID"
+set the tunnel id of the tunnel to be deleted. All sessions within the
+tunnel must be deleted first.
+.SS ip l2tp show tunnel - show information about tunnels
+.TP
+.BI tunnel_id " ID"
+set the tunnel id of the tunnel to be shown. If not specified,
+information about all tunnels is printed.
+.SS ip l2tp add session - add a new session to a tunnel
+.TP
+.BI name " NAME "
+sets the session network interface name. Default is l2tpethN.
+.TP
+.BI tunnel_id " ID"
+set the tunnel id, which is a 32-bit integer value. Uniquely
+identifies the tunnel into which the session will be created. The
+tunnel must already exist.
+.TP
+.BI session_id " ID"
+set the session id, which is a 32-bit integer value. Uniquely
+identifies the session being created. The value used must match the
+peer_session_id value being used at the peer.
+.TP
+.BI peer_session_id " ID"
+set the peer session id, which is a 32-bit integer value assigned to
+the session by the peer. The value used must match the session_id
+value being used at the peer.
+.TP
+.BI cookie " HEXSTR"
+sets an optional cookie value to be assigned to the session. This is a
+4 or 8 byte value, specified as 8 or 16 hex digits,
+e.g. 014d3636deadbeef. The value must match the peer_cookie value set
+at the peer. The cookie value is carried in L2TP data packets and is
+checked for expected value at the peer. Default is to use no cookie.
+.TP
+.BI peer_cookie " HEXSTR"
+sets an optional peer cookie value to be assigned to the session. This
+is a 4 or 8 byte value, specified as 8 or 16 hex digits,
+e.g. 014d3636deadbeef. The value must match the cookie value set at
+the peer. It tells the local system what cookie value to expect to
+find in received L2TP packets. Default is to use no cookie.
+.TP
+.BI l2spec_type " L2SPECTYPE"
+set the layer2specific header type of the session.
+.br
+Valid values are:
+.BR none ", " default "."
+.TP
+.BI seq " SEQ"
+controls sequence numbering to prevent or detect out of order packets.
+.B send
+puts a sequence number in the default layer2specific header of each
+outgoing packet.
+.B recv
+reorder packets if they are received out of order.
+Default is
+.BR none "."
+.br
+Valid values are:
+.BR none ", " send ", " recv ", " both "."
+.SS ip l2tp del session - destroy a session
+.TP
+.BI tunnel_id " ID"
+set the tunnel id in which the session to be deleted is located.
+.TP
+.BI session_id " ID"
+set the session id of the session to be deleted.
+.SS ip l2tp show session - show information about sessions
+.TP
+.BI tunnel_id " ID"
+set the tunnel id of the session(s) to be shown. If not specified,
+information about sessions in all tunnels is printed.
+.TP
+.BI session_id " ID"
+set the session id of the session to be shown. If not specified,
+information about all sessions is printed.
+.SH EXAMPLES
+.PP
+.SS Setup L2TP tunnels and sessions
+.nf
+site-A:# ip l2tp add tunnel tunnel_id 3000 peer_tunnel_id 4000 \\
+ encap udp local 1.2.3.4 remote 5.6.7.8 \\
+ udp_sport 5000 udp_dport 6000
+site-A:# ip l2tp add session tunnel_id 3000 session_id 1000 \\
+ peer_session_id 2000
+
+site-B:# ip l2tp add tunnel tunnel_id 4000 peer_tunnel_id 3000 \\
+ encap udp local 5.6.7.8 remote 1.2.3.4 \\
+ udp_sport 6000 udp_dport 5000
+site-B:# ip l2tp add session tunnel_id 4000 session_id 2000 \\
+ peer_session_id 1000
+
+site-A:# ip link set l2tpeth0 up mtu 1488
+
+site-B:# ip link set l2tpeth0 up mtu 1488
+.fi
+.PP
+Notice that the IP addresses, UDP ports and tunnel / session ids are
+matched and reversed at each site.
+.SS Configure as IP interfaces
+The two interfaces can be configured with IP addresses if only IP data
+is to be carried. This is perhaps the simplest configuration.
+.PP
+.nf
+site-A:# ip addr add 10.42.1.1 peer 10.42.1.2 dev l2tpeth0
+
+site-B:# ip addr add 10.42.1.2 peer 10.42.1.1 dev l2tpeth0
+
+site-A:# ping 10.42.1.2
+.fi
+.PP
+Now the link should be usable. Add static routes as needed to have
+data sent over the new link.
+.PP
+.SS Configure as bridged interfaces
+To carry non-IP data, the L2TP network interface is added to a bridge
+instead of being assigned its own IP address, using standard Linux
+utilities. Since raw ethernet frames are then carried inside the
+tunnel, the MTU of the L2TP interfaces must be set to allow space for
+those headers.
+.PP
+.nf
+site-A:# ip link set l2tpeth0 up mtu 1446
+site-A:# ip link add br0 type bridge
+site-A:# ip link set l2tpeth0 master br0
+site-A:# ip link set eth0 master br0
+site-A:# ip link set br0 up
+.fi
+.PP
+If you are using VLANs, setup a bridge per VLAN and bridge each VLAN
+over a separate L2TP session. For example, to bridge VLAN ID 5 on eth1
+over an L2TP pseudowire:
+.PP
+.nf
+site-A:# ip link set l2tpeth0 up mtu 1446
+site-A:# ip link add brvlan5 type bridge
+site-A:# ip link set l2tpeth0.5 master brvlan5
+site-A:# ip link set eth1.5 master brvlan5
+site-A:# ip link set brvlan5 up
+.fi
+.PP
+Adding the L2TP interface to a bridge causes the bridge to forward
+traffic over the L2TP pseudowire just like it forwards over any other
+interface. The bridge learns MAC addresses of hosts attached to each
+interface and intelligently forwards frames from one bridge port to
+another. IP addresses are not assigned to the l2tpethN interfaces. If
+the bridge is correctly configured at both sides of the L2TP
+pseudowire, it should be possible to reach hosts in the peer's bridged
+network.
+.PP
+When raw ethernet frames are bridged across an L2TP tunnel, large
+frames may be fragmented and forwarded as individual IP fragments to
+the recipient, depending on the MTU of the physical interface used by
+the tunnel. When the ethernet frames carry protocols which are
+reassembled by the recipient, like IP, this isn't a problem. However,
+such fragmentation can cause problems for protocols like PPPoE where
+the recipient expects to receive ethernet frames exactly as
+transmitted. In such cases, it is important that frames leaving the
+tunnel are reassembled back into a single frame before being
+forwarded on. To do so, enable netfilter connection tracking
+(conntrack) or manually load the Linux netfilter defrag modules at
+each tunnel endpoint.
+.PP
+.nf
+site-A:# modprobe nf_defrag_ipv4
+
+site-B:# modprobe nf_defrag_ipv4
+.fi
+.PP
+If L2TP is being used over IPv6, use the IPv6 defrag module.
+.SH INTEROPERABILITY
+.PP
+Unmanaged (static) L2TPv3 tunnels are supported by some network
+equipment equipment vendors such as Cisco.
+.PP
+In Linux, L2TP Hello messages are not supported in unmanaged
+tunnels. Hello messages are used by L2TP clients and servers to detect
+link failures in order to automate tearing down and reestablishing
+dynamic tunnels. If a non-Linux peer supports Hello messages in
+unmanaged tunnels, it must be turned off to interoperate with Linux.
+.PP
+Linux defaults to use the Default Layer2SpecificHeader type as defined
+in the L2TPv3 protocol specification, RFC3931. This setting must be
+consistent with that configured at the peer. Some vendor
+implementations (e.g. Cisco) default to use a Layer2SpecificHeader
+type of None.
+.SH SEE ALSO
+.br
+.BR ip (8)
+.SH AUTHOR
+James Chapman <jchapman@katalix.com>
diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in
new file mode 100644
index 0000000..d1db0e9
--- /dev/null
+++ b/man/man8/ip-link.8.in
@@ -0,0 +1,2522 @@
+.TH IP\-LINK 8 "13 Dec 2012" "iproute2" "Linux"
+.SH "NAME"
+ip-link \- network device configuration
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip link
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.BI "ip link add"
+.RB "[ " link
+.IR DEVICE " ]"
+.RB "[ " name " ]"
+.I NAME
+.br
+.RB "[ " txqueuelen
+.IR PACKETS " ]"
+.br
+.RB "[ " address
+.IR LLADDR " ]"
+.RB "[ " broadcast
+.IR LLADDR " ]"
+.br
+.RB "[ " mtu
+.IR MTU " ]"
+.RB "[ " index
+.IR IDX " ]"
+.br
+.RB "[ " numtxqueues
+.IR QUEUE_COUNT " ]"
+.RB "[ " numrxqueues
+.IR QUEUE_COUNT " ]"
+.br
+.BR "[ " gso_max_size
+.IR BYTES " ]"
+.RB "[ " gso_max_segs
+.IR SEGMENTS " ]"
+.br
+.BI type " TYPE"
+.RI "[ " ARGS " ]"
+
+.ti -8
+.BR "ip link delete " {
+.IR DEVICE " | "
+.BI "group " GROUP
+}
+.BI type " TYPE"
+.RI "[ " ARGS " ]"
+
+.ti -8
+.BR "ip link set " {
+.IR DEVICE " | "
+.BI "group " GROUP
+}
+.br
+.RB "[ { " up " | " down " } ]"
+.br
+.RB "[ " type
+.IR "ETYPE TYPE_ARGS" " ]"
+.br
+.RB "[ " arp " { " on " | " off " } ]"
+.br
+.RB "[ " dynamic " { " on " | " off " } ]"
+.br
+.RB "[ " multicast " { " on " | " off " } ]"
+.br
+.RB "[ " allmulticast " { " on " | " off " } ]"
+.br
+.RB "[ " promisc " { " on " | " off " } ]"
+.br
+.RB "[ " protodown " { " on " | " off " } ]"
+.br
+.RB "[ " trailers " { " on " | " off " } ]"
+.br
+.RB "[ " txqueuelen
+.IR PACKETS " ]"
+.br
+.RB "[ " name
+.IR NEWNAME " ]"
+.br
+.RB "[ " address
+.IR LLADDR " ]"
+.br
+.RB "[ " broadcast
+.IR LLADDR " ]"
+.br
+.RB "[ " mtu
+.IR MTU " ]"
+.br
+.RB "[ " netns " {"
+.IR PID " | " NETNSNAME " } ]"
+.br
+.RB "[ " link-netnsid
+.IR ID " ]"
+.br
+.RB "[ " alias
+.IR NAME " ]"
+.br
+.RB "[ " vf
+.IR NUM " ["
+.B mac
+.IR LLADDR " ]"
+.br
+.in +9
+.RI "[ " VFVLAN-LIST " ]"
+.br
+.RB "[ " rate
+.IR TXRATE " ]"
+.br
+.RB "[ " max_tx_rate
+.IR TXRATE " ]"
+.br
+.RB "[ " min_tx_rate
+.IR TXRATE " ]"
+.br
+.RB "[ " spoofchk " { " on " | " off " } ]"
+.br
+.RB "[ " query_rss " { " on " | " off " } ]"
+.br
+.RB "[ " state " { " auto " | " enable " | " disable " } ]"
+.br
+.RB "[ " trust " { " on " | " off " } ]"
+.br
+.RB "[ " node_guid " eui64 ]"
+.br
+.RB "[ " port_guid " eui64 ] ]"
+.br
+.in -9
+.RB "[ { " xdp " | " xdpgeneric " | " xdpdrv " | " xdpoffload " } { " off " | "
+.br
+.in +8
+.BR object
+.IR FILE
+.RB "[ " section
+.IR NAME " ]"
+.RB "[ " verbose " ] |"
+.br
+.BR pinned
+.IR FILE " } ]"
+.br
+.in -8
+.RB "[ " master
+.IR DEVICE " ]"
+.br
+.RB "[ " nomaster " ]"
+.br
+.RB "[ " vrf
+.IR NAME " ]"
+.br
+.RB "[ " addrgenmode " { " eui64 " | " none " | " stable_secret " | " random " } ]"
+.br
+.RB "[ " macaddr
+.RI "[ " MACADDR " ]"
+.br
+.in +10
+.RB "[ { " flush " | " add " | " del " } "
+.IR MACADDR " ]"
+.br
+.RB "[ " set
+.IR MACADDR " ] ]"
+.br
+
+.ti -8
+.B ip link show
+.RI "[ " DEVICE " | "
+.B group
+.IR GROUP " ] ["
+.BR up " ] ["
+.B master
+.IR DEVICE " ] ["
+.B type
+.IR ETYPE " ] ["
+.B vrf
+.IR NAME " ]"
+
+.ti -8
+.B ip link xstats
+.BI type " TYPE"
+.RI "[ " ARGS " ]"
+
+.ti -8
+.B ip link afstats
+.RB "[ " dev
+.IR DEVICE " ]"
+
+.ti -8
+.B ip link help
+.RI "[ " TYPE " ]"
+
+.ti -8
+.IR TYPE " := [ "
+.BR bridge " | "
+.BR bond " | "
+.BR can " | "
+.BR dummy " | "
+.BR hsr " | "
+.BR ifb " | "
+.BR ipoib " |"
+.BR macvlan " | "
+.BR macvtap " | "
+.BR vcan " | "
+.BR vxcan " | "
+.BR veth " | "
+.BR vlan " | "
+.BR vxlan " |"
+.BR ip6tnl " |"
+.BR ipip " |"
+.BR sit " |"
+.BR gre " |"
+.BR gretap " |"
+.BR erspan " |"
+.BR ip6gre " |"
+.BR ip6gretap " |"
+.BR ip6erspan " |"
+.BR vti " |"
+.BR nlmon " |"
+.BR ipvlan " |"
+.BR ipvtap " |"
+.BR lowpan " |"
+.BR geneve " |"
+.BR vrf " |"
+.BR macsec " |"
+.BR netdevsim " |"
+.BR rmnet " |"
+.BR xfrm " ]"
+
+.ti -8
+.IR ETYPE " := [ " TYPE " |"
+.BR bridge_slave " | " bond_slave " ]"
+
+.ti -8
+.IR VFVLAN-LIST " := [ " VFVLAN-LIST " ] " VFVLAN
+
+.ti -8
+.IR VFVLAN " := "
+.RB "[ " vlan
+.IR VLANID " [ "
+.B qos
+.IR VLAN-QOS " ] ["
+.B proto
+.IR VLAN-PROTO " ] ]"
+
+.SH "DESCRIPTION"
+.SS ip link add - add virtual link
+
+.TP
+.BI link " DEVICE "
+specifies the physical device to act operate on.
+
+.I NAME
+specifies the name of the new virtual device.
+
+.I TYPE
+specifies the type of the new device.
+.sp
+Link types:
+
+.in +8
+.B bridge
+- Ethernet Bridge device
+.sp
+.B bond
+- Bonding device
+.sp
+.B dummy
+- Dummy network interface
+.sp
+.B hsr
+- High-availability Seamless Redundancy device
+.sp
+.B ifb
+- Intermediate Functional Block device
+.sp
+.B ipoib
+- IP over Infiniband device
+.sp
+.B macvlan
+- Virtual interface base on link layer address (MAC)
+.sp
+.B macvtap
+- Virtual interface based on link layer address (MAC) and TAP.
+.sp
+.B vcan
+- Virtual Controller Area Network interface
+.sp
+.B vxcan
+- Virtual Controller Area Network tunnel interface
+.sp
+.B veth
+- Virtual ethernet interface
+.sp
+.BR vlan
+- 802.1q tagged virtual LAN interface
+.sp
+.BR vxlan
+- Virtual eXtended LAN
+.sp
+.BR ip6tnl
+- Virtual tunnel interface IPv4|IPv6 over IPv6
+.sp
+.BR ipip
+- Virtual tunnel interface IPv4 over IPv4
+.sp
+.BR sit
+- Virtual tunnel interface IPv6 over IPv4
+.sp
+.BR gre
+- Virtual tunnel interface GRE over IPv4
+.sp
+.BR gretap
+- Virtual L2 tunnel interface GRE over IPv4
+.sp
+.BR erspan
+- Encapsulated Remote SPAN over GRE and IPv4
+.sp
+.BR ip6gre
+- Virtual tunnel interface GRE over IPv6
+.sp
+.BR ip6gretap
+- Virtual L2 tunnel interface GRE over IPv6
+.sp
+.BR ip6erspan
+- Encapsulated Remote SPAN over GRE and IPv6
+.sp
+.BR vti
+- Virtual tunnel interface
+.sp
+.BR nlmon
+- Netlink monitoring device
+.sp
+.BR ipvlan
+- Interface for L3 (IPv6/IPv4) based VLANs
+.sp
+.BR ipvtap
+- Interface for L3 (IPv6/IPv4) based VLANs and TAP
+.sp
+.BR lowpan
+- Interface for 6LoWPAN (IPv6) over IEEE 802.15.4 / Bluetooth
+.sp
+.BR geneve
+- GEneric NEtwork Virtualization Encapsulation
+.sp
+.BR macsec
+- Interface for IEEE 802.1AE MAC Security (MACsec)
+.sp
+.BR vrf
+- Interface for L3 VRF domains
+.sp
+.BR netdevsim
+- Interface for netdev API tests
+.sp
+.BR rmnet
+- Qualcomm rmnet device
+.sp
+.BR xfrm
+- Virtual xfrm interface
+.in -8
+
+.TP
+.BI numtxqueues " QUEUE_COUNT "
+specifies the number of transmit queues for new device.
+
+.TP
+.BI numrxqueues " QUEUE_COUNT "
+specifies the number of receive queues for new device.
+
+.TP
+.BI gso_max_size " BYTES "
+specifies the recommended maximum size of a Generic Segment Offload
+packet the new device should accept.
+
+.TP
+.BI gso_max_segs " SEGMENTS "
+specifies the recommended maximum number of a Generic Segment Offload
+segments the new device should accept.
+
+.TP
+.BI index " IDX "
+specifies the desired index of the new virtual device. The link
+creation fails, if the index is busy.
+
+.TP
+VLAN Type Support
+For a link of type
+.I VLAN
+the following additional arguments are supported:
+
+.BI "ip link add
+.BI link " DEVICE "
+.BI name " NAME "
+.B "type vlan"
+[
+.BI protocol " VLAN_PROTO "
+]
+.BI id " VLANID "
+[
+.BR reorder_hdr " { " on " | " off " } "
+]
+[
+.BR gvrp " { " on " | " off " } "
+]
+[
+.BR mvrp " { " on " | " off " } "
+]
+[
+.BR loose_binding " { " on " | " off " } "
+]
+[
+.BR bridge_binding " { " on " | " off " } "
+]
+[
+.BI ingress-qos-map " QOS-MAP "
+]
+[
+.BI egress-qos-map " QOS-MAP "
+]
+
+.in +8
+.sp
+.BI protocol " VLAN_PROTO "
+- either 802.1Q or 802.1ad.
+
+.BI id " VLANID "
+- specifies the VLAN Identifer to use. Note that numbers with a leading " 0 " or " 0x " are interpreted as octal or hexadeimal, respectively.
+
+.BR reorder_hdr " { " on " | " off " } "
+- specifies whether ethernet headers are reordered or not (default is
+.BR on ")."
+
+.in +4
+If
+.BR reorder_hdr " is " on
+then VLAN header will be not inserted immediately but only before
+passing to the physical device (if this device does not support VLAN
+offloading), the similar on the RX direction - by default the packet
+will be untagged before being received by VLAN device. Reordering
+allows to accelerate tagging on egress and to hide VLAN header on
+ingress so the packet looks like regular Ethernet packet, at the same
+time it might be confusing for packet capture as the VLAN header does
+not exist within the packet.
+
+VLAN offloading can be checked by
+.BR ethtool "(8):"
+.in +4
+.sp
+.B ethtool -k
+<phy_dev> |
+.RB grep " tx-vlan-offload"
+.sp
+.in -4
+where <phy_dev> is the physical device to which VLAN device is bound.
+.in -4
+
+.BR gvrp " { " on " | " off " } "
+- specifies whether this VLAN should be registered using GARP VLAN
+ Registration Protocol.
+
+.BR mvrp " { " on " | " off " } "
+- specifies whether this VLAN should be registered using Multiple VLAN
+ Registration Protocol.
+
+.BR loose_binding " { " on " | " off " } "
+- specifies whether the VLAN device state is bound to the physical device state.
+
+.BR bridge_binding " { " on " | " off " } "
+- specifies whether the VLAN device link state tracks the state of bridge ports
+that are members of the VLAN.
+
+.BI ingress-qos-map " QOS-MAP "
+- defines a mapping of VLAN header prio field to the Linux internal packet
+priority on incoming frames. The format is FROM:TO with multiple mappings
+separated by spaces.
+
+.BI egress-qos-map " QOS-MAP "
+- defines a mapping of Linux internal packet priority to VLAN header prio field
+but for outgoing frames. The format is the same as for ingress-qos-map.
+.in +4
+
+Linux packet priority can be set by
+.BR iptables "(8)":
+.in +4
+.sp
+.B iptables
+-t mangle -A POSTROUTING [...] -j CLASSIFY --set-class 0:4
+.sp
+.in -4
+and this "4" priority can be used in the egress qos mapping to set
+VLAN prio "5":
+.sp
+.in +4
+.B ip
+link set veth0.10 type vlan egress 4:5
+.in -4
+.in -4
+.in -8
+
+.TP
+VXLAN Type Support
+For a link of type
+.I VXLAN
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE
+.BI type " vxlan " id " VNI"
+[
+.BI dev " PHYS_DEV "
+.RB " ] [ { " group " | " remote " } "
+.I IPADDR
+] [
+.B local
+.RI "{ "IPADDR " | "any " } "
+] [
+.BI ttl " TTL "
+] [
+.BI tos " TOS "
+] [
+.BI df " DF "
+] [
+.BI flowlabel " FLOWLABEL "
+] [
+.BI dstport " PORT "
+] [
+.BI srcport " MIN MAX "
+] [
+.RB [ no ] learning
+] [
+.RB [ no ] proxy
+] [
+.RB [ no ] rsc
+] [
+.RB [ no ] l2miss
+] [
+.RB [ no ] l3miss
+] [
+.RB [ no ] udpcsum
+] [
+.RB [ no ] udp6zerocsumtx
+] [
+.RB [ no ] udp6zerocsumrx
+] [
+.BI ageing " SECONDS "
+] [
+.BI maxaddress " NUMBER "
+] [
+.RB [ no ] external
+] [
+.B gbp
+] [
+.B gpe
+]
+
+.in +8
+.sp
+.BI id " VNI "
+- specifies the VXLAN Network Identifer (or VXLAN Segment
+Identifier) to use.
+
+.BI dev " PHYS_DEV"
+- specifies the physical device to use for tunnel endpoint communication.
+
+.sp
+.BI group " IPADDR"
+- specifies the multicast IP address to join.
+This parameter cannot be specified with the
+.B remote
+parameter.
+
+.sp
+.BI remote " IPADDR"
+- specifies the unicast destination IP address to use in outgoing packets
+when the destination link layer address is not known in the VXLAN device
+forwarding database. This parameter cannot be specified with the
+.B group
+parameter.
+
+.sp
+.BI local " IPADDR"
+- specifies the source IP address to use in outgoing packets.
+
+.sp
+.BI ttl " TTL"
+- specifies the TTL value to use in outgoing packets.
+
+.sp
+.BI tos " TOS"
+- specifies the TOS value to use in outgoing packets.
+
+.sp
+.BI df " DF"
+- specifies the usage of the Don't Fragment flag (DF) bit in outgoing packets
+with IPv4 headers. The value
+.B inherit
+causes the bit to be copied from the original IP header. The values
+.B unset
+and
+.B set
+cause the bit to be always unset or always set, respectively. By default, the
+bit is not set.
+
+.sp
+.BI flowlabel " FLOWLABEL"
+- specifies the flow label to use in outgoing packets.
+
+.sp
+.BI dstport " PORT"
+- specifies the UDP destination port to communicate to the remote
+ VXLAN tunnel endpoint.
+
+.sp
+.BI srcport " MIN MAX"
+- specifies the range of port numbers to use as UDP
+source ports to communicate to the remote VXLAN tunnel endpoint.
+
+.sp
+.RB [ no ] learning
+- specifies if unknown source link layer addresses and IP addresses
+are entered into the VXLAN device forwarding database.
+
+.sp
+.RB [ no ] rsc
+- specifies if route short circuit is turned on.
+
+.sp
+.RB [ no ] proxy
+- specifies ARP proxy is turned on.
+
+.sp
+.RB [ no ] l2miss
+- specifies if netlink LLADDR miss notifications are generated.
+
+.sp
+.RB [ no ] l3miss
+- specifies if netlink IP ADDR miss notifications are generated.
+
+.sp
+.RB [ no ] udpcsum
+- specifies if UDP checksum is calculated for transmitted packets over IPv4.
+
+.sp
+.RB [ no ] udp6zerocsumtx
+- skip UDP checksum calculation for transmitted packets over IPv6.
+
+.sp
+.RB [ no ] udp6zerocsumrx
+- allow incoming UDP packets over IPv6 with zero checksum field.
+
+.sp
+.BI ageing " SECONDS"
+- specifies the lifetime in seconds of FDB entries learnt by the kernel.
+
+.sp
+.BI maxaddress " NUMBER"
+- specifies the maximum number of FDB entries.
+
+.sp
+.RB [ no ] external
+- specifies whether an external control plane
+.RB "(e.g. " "ip route encap" )
+or the internal FDB should be used.
+
+.sp
+.B gbp
+- enables the Group Policy extension (VXLAN-GBP).
+
+.in +4
+Allows to transport group policy context across VXLAN network peers.
+If enabled, includes the mark of a packet in the VXLAN header for outgoing
+packets and fills the packet mark based on the information found in the
+VXLAN header for incoming packets.
+
+Format of upper 16 bits of packet mark (flags);
+
+.in +2
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+.br
+|-|-|-|-|-|-|-|-|-|D|-|-|A|-|-|-|
+.br
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+.B D :=
+Don't Learn bit. When set, this bit indicates that the egress
+VTEP MUST NOT learn the source address of the encapsulated frame.
+
+.B A :=
+Indicates that the group policy has already been applied to
+this packet. Policies MUST NOT be applied by devices when the A bit is set.
+.in -2
+
+Format of lower 16 bits of packet mark (policy ID):
+
+.in +2
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+.br
+| Group Policy ID |
+.br
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+.in -2
+
+Example:
+ iptables -A OUTPUT [...] -j MARK --set-mark 0x800FF
+
+.in -4
+
+.sp
+.B gpe
+- enables the Generic Protocol extension (VXLAN-GPE). Currently, this is
+only supported together with the
+.B external
+keyword.
+
+.in -8
+
+.TP
+VETH, VXCAN Type Support
+For a link of types
+.I VETH/VXCAN
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE
+.BR type " { " veth " | " vxcan " }"
+[
+.BR peer
+.BI "name " NAME
+]
+
+.in +8
+.sp
+.BR peer
+.BI "name " NAME
+- specifies the virtual pair device name of the
+.I VETH/VXCAN
+tunnel.
+
+.in -8
+
+.TP
+IPIP, SIT Type Support
+For a link of type
+.IR IPIP or SIT
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE
+.BR type " { " ipip " | " sit " }"
+.BI " remote " ADDR " local " ADDR
+[
+.BR encap " { " fou " | " gue " | " none " }"
+] [
+.BR encap-sport " { " \fIPORT " | " auto " }"
+] [
+.BI "encap-dport " PORT
+] [
+.RB [ no ] encap-csum
+] [
+.I " [no]encap-remcsum "
+] [
+.I " mode " { ip6ip | ipip | mplsip | any } "
+] [
+.BR external
+]
+
+.in +8
+.sp
+.BI remote " ADDR "
+- specifies the remote address of the tunnel.
+
+.sp
+.BI local " ADDR "
+- specifies the fixed local address for tunneled packets.
+It must be an address on another interface on this host.
+
+.sp
+.BR encap " { " fou " | " gue " | " none " }"
+- specifies type of secondary UDP encapsulation. "fou" indicates
+Foo-Over-UDP, "gue" indicates Generic UDP Encapsulation.
+
+.sp
+.BR encap-sport " { " \fIPORT " | " auto " }"
+- specifies the source port in UDP encapsulation.
+.IR PORT
+indicates the port by number, "auto"
+indicates that the port number should be chosen automatically
+(the kernel picks a flow based on the flow hash of the
+encapsulated packet).
+
+.sp
+.RB [ no ] encap-csum
+- specifies if UDP checksums are enabled in the secondary
+encapsulation.
+
+.sp
+.RB [ no ] encap-remcsum
+- specifies if Remote Checksum Offload is enabled. This is only
+applicable for Generic UDP Encapsulation.
+
+.sp
+.BI mode " { ip6ip | ipip | mplsip | any } "
+- specifies mode in which device should run. "ip6ip" indicates
+IPv6-Over-IPv4, "ipip" indicates "IPv4-Over-IPv4", "mplsip" indicates
+MPLS-Over-IPv4, "any" indicates IPv6, IPv4 or MPLS Over IPv4. Supported for
+SIT where the default is "ip6ip" and IPIP where the default is "ipip".
+IPv6-Over-IPv4 is not supported for IPIP.
+
+.sp
+.BR external
+- make this tunnel externally controlled
+.RB "(e.g. " "ip route encap" ).
+
+.in -8
+.TP
+GRE Type Support
+For a link of type
+.IR GRE " or " GRETAP
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE
+.BR type " { " gre " | " gretap " }"
+.BI " remote " ADDR " local " ADDR
+[
+.RB [ no ] "" [ i | o ] seq
+] [
+.RB [ i | o ] key
+.I KEY
+|
+.BR no [ i | o ] key
+] [
+.RB [ no ] "" [ i | o ] csum
+] [
+.BI ttl " TTL "
+] [
+.BI tos " TOS "
+] [
+.RB [ no ] pmtudisc
+] [
+.RB [ no ] ignore-df
+] [
+.BI dev " PHYS_DEV "
+] [
+.BR encap " { " fou " | " gue " | " none " }"
+] [
+.BR encap-sport " { " \fIPORT " | " auto " }"
+] [
+.BI "encap-dport " PORT
+] [
+.RB [ no ] encap-csum
+] [
+.RB [ no ] encap-remcsum
+] [
+.BR external
+]
+
+.in +8
+.sp
+.BI remote " ADDR "
+- specifies the remote address of the tunnel.
+
+.sp
+.BI local " ADDR "
+- specifies the fixed local address for tunneled packets.
+It must be an address on another interface on this host.
+
+.sp
+.RB [ no ] "" [ i | o ] seq
+- serialize packets.
+The
+.B oseq
+flag enables sequencing of outgoing packets.
+The
+.B iseq
+flag requires that all input packets are serialized.
+
+.sp
+.RB [ i | o ] key
+.I KEY
+|
+.BR no [ i | o ] key
+- use keyed GRE with key
+.IR KEY ". "KEY
+is either a number or an IPv4 address-like dotted quad.
+The
+.B key
+parameter specifies the same key to use in both directions.
+The
+.BR ikey " and " okey
+parameters specify different keys for input and output.
+
+.sp
+.RB [ no ] "" [ i | o ] csum
+- generate/require checksums for tunneled packets.
+The
+.B ocsum
+flag calculates checksums for outgoing packets.
+The
+.B icsum
+flag requires that all input packets have the correct
+checksum. The
+.B csum
+flag is equivalent to the combination
+.B "icsum ocsum" .
+
+.sp
+.BI ttl " TTL"
+- specifies the TTL value to use in outgoing packets.
+
+.sp
+.BI tos " TOS"
+- specifies the TOS value to use in outgoing packets.
+
+.sp
+.RB [ no ] pmtudisc
+- enables/disables Path MTU Discovery on this tunnel.
+It is enabled by default. Note that a fixed ttl is incompatible
+with this option: tunneling with a fixed ttl always makes pmtu
+discovery.
+
+.sp
+.RB [ no ] ignore-df
+- enables/disables IPv4 DF suppression on this tunnel.
+Normally datagrams that exceed the MTU will be fragmented; the presence
+of the DF flag inhibits this, resulting instead in an ICMP Unreachable
+(Fragmentation Required) message. Enabling this attribute causes the
+DF flag to be ignored.
+
+.sp
+.BI dev " PHYS_DEV"
+- specifies the physical device to use for tunnel endpoint communication.
+
+.sp
+.BR encap " { " fou " | " gue " | " none " }"
+- specifies type of secondary UDP encapsulation. "fou" indicates
+Foo-Over-UDP, "gue" indicates Generic UDP Encapsulation.
+
+.sp
+.BR encap-sport " { " \fIPORT " | " auto " }"
+- specifies the source port in UDP encapsulation.
+.IR PORT
+indicates the port by number, "auto"
+indicates that the port number should be chosen automatically
+(the kernel picks a flow based on the flow hash of the
+encapsulated packet).
+
+.sp
+.RB [ no ] encap-csum
+- specifies if UDP checksums are enabled in the secondary
+encapsulation.
+
+.sp
+.RB [ no ] encap-remcsum
+- specifies if Remote Checksum Offload is enabled. This is only
+applicable for Generic UDP Encapsulation.
+
+.sp
+.BR external
+- make this tunnel externally controlled
+.RB "(e.g. " "ip route encap" ).
+
+.in -8
+
+.TP
+IP6GRE/IP6GRETAP Type Support
+For a link of type
+.I IP6GRE/IP6GRETAP
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE
+.BR type " { " ip6gre " | " ip6gretap " }"
+.BI remote " ADDR " local " ADDR"
+[
+.RB [ no ] "" [ i | o ] seq
+] [
+.RB [ i | o ] key
+.I KEY
+|
+.BR no [ i | o ] key
+] [
+.RB [ no ] "" [ i | o ] csum
+] [
+.BI hoplimit " TTL "
+] [
+.BI encaplimit " ELIM "
+] [
+.BI tclass " TCLASS "
+] [
+.BI flowlabel " FLOWLABEL "
+] [
+.BI "dscp inherit"
+] [
+.BI "[no]allow-localremote"
+] [
+.BI dev " PHYS_DEV "
+] [
+.RB external
+]
+
+.in +8
+.sp
+.BI remote " ADDR "
+- specifies the remote IPv6 address of the tunnel.
+
+.sp
+.BI local " ADDR "
+- specifies the fixed local IPv6 address for tunneled packets.
+It must be an address on another interface on this host.
+
+.sp
+.RB [ no ] "" [ i | o ] seq
+- serialize packets.
+The
+.B oseq
+flag enables sequencing of outgoing packets.
+The
+.B iseq
+flag requires that all input packets are serialized.
+
+.sp
+.RB [ i | o ] key
+.I KEY
+|
+.BR no [ i | o ] key
+- use keyed GRE with key
+.IR KEY ". "KEY
+is either a number or an IPv4 address-like dotted quad.
+The
+.B key
+parameter specifies the same key to use in both directions.
+The
+.BR ikey " and " okey
+parameters specify different keys for input and output.
+
+.sp
+.RB [ no ] "" [ i | o ] csum
+- generate/require checksums for tunneled packets.
+The
+.B ocsum
+flag calculates checksums for outgoing packets.
+The
+.B icsum
+flag requires that all input packets have the correct
+checksum. The
+.B csum
+flag is equivalent to the combination
+.BR "icsum ocsum" .
+
+.sp
+.BI hoplimit " TTL"
+- specifies Hop Limit value to use in outgoing packets.
+
+.sp
+.BI encaplimit " ELIM"
+- specifies a fixed encapsulation limit. Default is 4.
+
+.sp
+.BI flowlabel " FLOWLABEL"
+- specifies a fixed flowlabel.
+
+.sp
+.BI [no]allow-localremote
+- specifies whether to allow remote endpoint to have an address configured on
+local host.
+
+.sp
+.BI tclass " TCLASS"
+- specifies the traffic class field on
+tunneled packets, which can be specified as either a two-digit
+hex value (e.g. c0) or a predefined string (e.g. internet).
+The value
+.B inherit
+causes the field to be copied from the original IP header. The
+values
+.BI "inherit/" STRING
+or
+.BI "inherit/" 00 ".." ff
+will set the field to
+.I STRING
+or
+.IR 00 ".." ff
+when tunneling non-IP packets. The default value is 00.
+
+.sp
+.RB external
+- make this tunnel externally controlled (or not, which is the default).
+In the kernel, this is referred to as collect metadata mode. This flag is
+mutually exclusive with the
+.BR remote ,
+.BR local ,
+.BR seq ,
+.BR key,
+.BR csum,
+.BR hoplimit,
+.BR encaplimit,
+.BR flowlabel " and " tclass
+options.
+
+.in -8
+
+.TP
+IPoIB Type Support
+For a link of type
+.I IPoIB
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE " name " NAME
+.BR "type ipoib " [ " pkey \fIPKEY" " ] [ " mode " \fIMODE \fR]"
+
+.in +8
+.sp
+.BI pkey " PKEY "
+- specifies the IB P-Key to use.
+
+.BI mode " MODE "
+- specifies the mode (datagram or connected) to use.
+
+.TP
+ERSPAN Type Support
+For a link of type
+.I ERSPAN/IP6ERSPAN
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE
+.BR type " { " erspan " | " ip6erspan " }"
+.BI remote " ADDR " local " ADDR " seq
+.RB key
+.I KEY
+.BR erspan_ver " \fIversion "
+[
+.BR erspan " \fIIDX "
+] [
+.BR erspan_dir " { " \fIingress " | " \fIegress " }"
+] [
+.BR erspan_hwid " \fIhwid "
+] [
+.BI "[no]allow-localremote"
+] [
+.RB external
+]
+
+.in +8
+.sp
+.BI remote " ADDR "
+- specifies the remote address of the tunnel.
+
+.sp
+.BI local " ADDR "
+- specifies the fixed local address for tunneled packets.
+It must be an address on another interface on this host.
+
+.sp
+.BR erspan_ver " \fIversion "
+- specifies the ERSPAN version number.
+.IR version
+indicates the ERSPAN version to be created: 1 for version 1 (type II)
+or 2 for version 2 (type III).
+
+.sp
+.BR erspan " \fIIDX "
+- specifies the ERSPAN v1 index field.
+.IR IDX
+indicates a 20 bit index/port number associated with the ERSPAN
+traffic's source port and direction.
+
+.sp
+.BR erspan_dir " { " \fIingress " | " \fIegress " }"
+- specifies the ERSPAN v2 mirrored traffic's direction.
+
+.sp
+.BR erspan_hwid " \fIhwid "
+- an unique identifier of an ERSPAN v2 engine within a system.
+.IR hwid
+is a 6-bit value for users to configure.
+
+.sp
+.BI [no]allow-localremote
+- specifies whether to allow remote endpoint to have an address configured on
+local host.
+
+.sp
+.BR external
+- make this tunnel externally controlled (or not, which is the default).
+In the kernel, this is referred to as collect metadata mode. This flag is
+mutually exclusive with the
+.BR remote ,
+.BR local ,
+.BR erspan_ver ,
+.BR erspan ,
+.BR erspan_dir " and " erspan_hwid
+options.
+
+.in -8
+
+.TP
+GENEVE Type Support
+For a link of type
+.I GENEVE
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE
+.BI type " geneve " id " VNI " remote " IPADDR"
+[
+.BI ttl " TTL "
+] [
+.BI tos " TOS "
+] [
+.BI df " DF "
+] [
+.BI flowlabel " FLOWLABEL "
+] [
+.BI dstport " PORT"
+] [
+.RB [ no ] external
+] [
+.RB [ no ] udpcsum
+] [
+.RB [ no ] udp6zerocsumtx
+] [
+.RB [ no ] udp6zerocsumrx
+]
+
+.in +8
+.sp
+.BI id " VNI "
+- specifies the Virtual Network Identifer to use.
+
+.sp
+.BI remote " IPADDR"
+- specifies the unicast destination IP address to use in outgoing packets.
+
+.sp
+.BI ttl " TTL"
+- specifies the TTL value to use in outgoing packets. "0" or "auto" means
+use whatever default value, "inherit" means inherit the inner protocol's
+ttl. Default option is "0".
+
+.sp
+.BI tos " TOS"
+- specifies the TOS value to use in outgoing packets.
+
+.sp
+.BI df " DF"
+- specifies the usage of the Don't Fragment flag (DF) bit in outgoing packets
+with IPv4 headers. The value
+.B inherit
+causes the bit to be copied from the original IP header. The values
+.B unset
+and
+.B set
+cause the bit to be always unset or always set, respectively. By default, the
+bit is not set.
+
+.sp
+.BI flowlabel " FLOWLABEL"
+- specifies the flow label to use in outgoing packets.
+
+.sp
+.BI dstport " PORT"
+- select a destination port other than the default of 6081.
+
+.sp
+.RB [ no ] external
+- make this tunnel externally controlled (or not, which is the default). This
+flag is mutually exclusive with the
+.BR id ,
+.BR remote ,
+.BR ttl ,
+.BR tos " and " flowlabel
+options.
+
+.sp
+.RB [ no ] udpcsum
+- specifies if UDP checksum is calculated for transmitted packets over IPv4.
+
+.sp
+.RB [ no ] udp6zerocsumtx
+- skip UDP checksum calculation for transmitted packets over IPv6.
+
+.sp
+.RB [ no ] udp6zerocsumrx
+- allow incoming UDP packets over IPv6 with zero checksum field.
+
+.in -8
+
+.TP
+MACVLAN and MACVTAP Type Support
+For a link of type
+.I MACVLAN
+or
+.I MACVTAP
+the following additional arguments are supported:
+
+.BI "ip link add link " DEVICE " name " NAME
+.BR type " { " macvlan " | " macvtap " } "
+.BR mode " { " private " | " vepa " | " bridge " | " passthru
+.RB " [ " nopromisc " ] | " source " } "
+
+.in +8
+.sp
+.BR type " { " macvlan " | " macvtap " } "
+- specifies the link type to use.
+.BR macvlan " creates just a virtual interface, while "
+.BR macvtap " in addition creates a character device "
+.BR /dev/tapX " to be used just like a " tuntap " device."
+
+.B mode private
+- Do not allow communication between
+.B macvlan
+instances on the same physical interface, even if the external switch supports
+hairpin mode.
+
+.B mode vepa
+- Virtual Ethernet Port Aggregator mode. Data from one
+.B macvlan
+instance to the other on the same physical interface is transmitted over the
+physical interface. Either the attached switch needs to support hairpin mode,
+or there must be a TCP/IP router forwarding the packets in order to allow
+communication. This is the default mode.
+
+.B mode bridge
+- In bridge mode, all endpoints are directly connected to each other,
+communication is not redirected through the physical interface's peer.
+
+.BR mode " " passthru " [ " nopromisc " ] "
+- This mode gives more power to a single endpoint, usually in
+.BR macvtap " mode. It is not allowed for more than one endpoint on the same "
+physical interface. All traffic will be forwarded to this endpoint, allowing
+virtio guests to change MAC address or set promiscuous mode in order to bridge
+the interface or create vlan interfaces on top of it. By default, this mode
+forces the underlying interface into promiscuous mode. Passing the
+.BR nopromisc " flag prevents this, so the promisc flag may be controlled "
+using standard tools.
+
+.B mode source
+- allows one to set a list of allowed mac address, which is used to match
+against source mac address from received frames on underlying interface. This
+allows creating mac based VLAN associations, instead of standard port or tag
+based. The feature is useful to deploy 802.1x mac based behavior,
+where drivers of underlying interfaces doesn't allows that.
+.in -8
+
+.TP
+High-availability Seamless Redundancy (HSR) Support
+For a link of type
+.I HSR
+the following additional arguments are supported:
+
+.BI "ip link add link " DEVICE " name " NAME " type hsr"
+.BI slave1 " SLAVE1-IF " slave2 " SLAVE2-IF "
+.RB [ " supervision"
+.IR ADDR-BYTE " ] ["
+.BR version " { " 0 " | " 1 " } ]"
+
+.in +8
+.sp
+.BR type " hsr "
+- specifies the link type to use, here HSR.
+
+.BI slave1 " SLAVE1-IF "
+- Specifies the physical device used for the first of the two ring ports.
+
+.BI slave2 " SLAVE2-IF "
+- Specifies the physical device used for the second of the two ring ports.
+
+.BI supervision " ADDR-BYTE"
+- The last byte of the multicast address used for HSR supervision frames.
+Default option is "0", possible values 0-255.
+
+.BR version " { " 0 " | " 1 " }"
+- Selects the protocol version of the interface. Default option is "0", which
+corresponds to the 2010 version of the HSR standard. Option "1" activates the
+2012 version.
+.in -8
+
+.TP
+BRIDGE Type Support
+For a link of type
+.I BRIDGE
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE " type bridge "
+[
+.BI ageing_time " AGEING_TIME "
+] [
+.BI group_fwd_mask " MASK "
+] [
+.BI group_address " ADDRESS "
+] [
+.BI forward_delay " FORWARD_DELAY "
+] [
+.BI hello_time " HELLO_TIME "
+] [
+.BI max_age " MAX_AGE "
+] [
+.BI stp_state " STP_STATE "
+] [
+.BI priority " PRIORITY "
+] [
+.BI vlan_filtering " VLAN_FILTERING "
+] [
+.BI vlan_protocol " VLAN_PROTOCOL "
+] [
+.BI vlan_default_pvid " VLAN_DEFAULT_PVID "
+] [
+.BI vlan_stats_enabled " VLAN_STATS_ENABLED "
+] [
+.BI vlan_stats_per_port " VLAN_STATS_PER_PORT "
+] [
+.BI mcast_snooping " MULTICAST_SNOOPING "
+] [
+.BI mcast_router " MULTICAST_ROUTER "
+] [
+.BI mcast_query_use_ifaddr " MCAST_QUERY_USE_IFADDR "
+] [
+.BI mcast_querier " MULTICAST_QUERIER "
+] [
+.BI mcast_hash_elasticity " HASH_ELASTICITY "
+] [
+.BI mcast_hash_max " HASH_MAX "
+] [
+.BI mcast_last_member_count " LAST_MEMBER_COUNT "
+] [
+.BI mcast_startup_query_count " STARTUP_QUERY_COUNT "
+] [
+.BI mcast_last_member_interval " LAST_MEMBER_INTERVAL "
+] [
+.BI mcast_membership_interval " MEMBERSHIP_INTERVAL "
+] [
+.BI mcast_querier_interval " QUERIER_INTERVAL "
+] [
+.BI mcast_query_interval " QUERY_INTERVAL "
+] [
+.BI mcast_query_response_interval " QUERY_RESPONSE_INTERVAL "
+] [
+.BI mcast_startup_query_interval " STARTUP_QUERY_INTERVAL "
+] [
+.BI mcast_stats_enabled " MCAST_STATS_ENABLED "
+] [
+.BI mcast_igmp_version " IGMP_VERSION "
+] [
+.BI mcast_mld_version " MLD_VERSION "
+] [
+.BI nf_call_iptables " NF_CALL_IPTABLES "
+] [
+.BI nf_call_ip6tables " NF_CALL_IP6TABLES "
+] [
+.BI nf_call_arptables " NF_CALL_ARPTABLES "
+]
+
+.in +8
+.sp
+.BI ageing_time " AGEING_TIME "
+- configure the bridge's FDB entries ageing time, ie the number of
+seconds a MAC address will be kept in the FDB after a packet has been
+received from that address. after this time has passed, entries are
+cleaned up.
+
+.BI group_fwd_mask " MASK "
+- set the group forward mask. This is the bitmask that is applied to
+decide whether to forward incoming frames destined to link-local
+addresses, ie addresses of the form 01:80:C2:00:00:0X (defaults to 0,
+ie the bridge does not forward any link-local frames).
+
+.BI group_address " ADDRESS "
+- set the MAC address of the multicast group this bridge uses for STP.
+The address must be a link-local address in standard Ethernet MAC
+address format, ie an address of the form 01:80:C2:00:00:0X, with X
+ in [0, 4..f].
+
+.BI forward_delay " FORWARD_DELAY "
+- set the forwarding delay in seconds, ie the time spent in LISTENING
+state (before moving to LEARNING) and in LEARNING state (before
+moving to FORWARDING). Only relevant if STP is enabled. Valid values
+are between 2 and 30.
+
+.BI hello_time " HELLO_TIME "
+- set the time in seconds between hello packets sent by the bridge,
+when it is a root bridge or a designated bridges.
+Only relevant if STP is enabled. Valid values are between 1 and 10.
+
+.BI max_age " MAX_AGE "
+- set the hello packet timeout, ie the time in seconds until another
+bridge in the spanning tree is assumed to be dead, after reception of
+its last hello message. Only relevant if STP is enabled. Valid values
+are between 6 and 40.
+
+.BI stp_state " STP_STATE "
+- turn spanning tree protocol on
+.RI ( STP_STATE " > 0) "
+or off
+.RI ( STP_STATE " == 0). "
+for this bridge.
+
+.BI priority " PRIORITY "
+- set this bridge's spanning tree priority, used during STP root
+bridge election.
+.I PRIORITY
+is a 16bit unsigned integer.
+
+.BI vlan_filtering " VLAN_FILTERING "
+- turn VLAN filtering on
+.RI ( VLAN_FILTERING " > 0) "
+or off
+.RI ( VLAN_FILTERING " == 0). "
+When disabled, the bridge will not consider the VLAN tag when handling packets.
+
+.BR vlan_protocol " { " 802.1Q " | " 802.1ad " } "
+- set the protocol used for VLAN filtering.
+
+.BI vlan_default_pvid " VLAN_DEFAULT_PVID "
+- set the default PVID (native/untagged VLAN ID) for this bridge.
+
+.BI vlan_stats_enabled " VLAN_STATS_ENABLED "
+- enable
+.RI ( VLAN_STATS_ENABLED " == 1) "
+or disable
+.RI ( VLAN_STATS_ENABLED " == 0) "
+per-VLAN stats accounting.
+
+.BI vlan_stats_per_port " VLAN_STATS_PER_PORT "
+- enable
+.RI ( VLAN_STATS_PER_PORT " == 1) "
+or disable
+.RI ( VLAN_STATS_PER_PORT " == 0) "
+per-VLAN per-port stats accounting. Can be changed only when there are no port VLANs configured.
+
+.BI mcast_snooping " MULTICAST_SNOOPING "
+- turn multicast snooping on
+.RI ( MULTICAST_SNOOPING " > 0) "
+or off
+.RI ( MULTICAST_SNOOPING " == 0). "
+
+.BI mcast_router " MULTICAST_ROUTER "
+- set bridge's multicast router if IGMP snooping is enabled.
+.I MULTICAST_ROUTER
+is an integer value having the following meaning:
+.in +8
+.sp
+.B 0
+- disabled.
+
+.B 1
+- automatic (queried).
+
+.B 2
+- permanently enabled.
+.in -8
+
+.BI mcast_query_use_ifaddr " MCAST_QUERY_USE_IFADDR "
+- whether to use the bridge's own IP address as source address for IGMP queries
+.RI ( MCAST_QUERY_USE_IFADDR " > 0) "
+or the default of 0.0.0.0
+.RI ( MCAST_QUERY_USE_IFADDR " == 0). "
+
+.BI mcast_querier " MULTICAST_QUERIER "
+- enable
+.RI ( MULTICAST_QUERIER " > 0) "
+or disable
+.RI ( MULTICAST_QUERIER " == 0) "
+IGMP querier, ie sending of multicast queries by the bridge (default: disabled).
+
+.BI mcast_querier_interval " QUERIER_INTERVAL "
+- interval between queries sent by other routers. if no queries are seen
+after this delay has passed, the bridge will start to send its own queries
+(as if
+.BI mcast_querier
+was enabled).
+
+.BI mcast_hash_elasticity " HASH_ELASTICITY "
+- set multicast database hash elasticity, ie the maximum chain length
+in the multicast hash table (defaults to 4).
+
+.BI mcast_hash_max " HASH_MAX "
+- set maximum size of multicast hash table (defaults to 512,
+value must be a power of 2).
+
+.BI mcast_last_member_count " LAST_MEMBER_COUNT "
+- set multicast last member count, ie the number of queries the bridge
+will send before stopping forwarding a multicast group after a "leave"
+message has been received (defaults to 2).
+
+.BI mcast_last_member_interval " LAST_MEMBER_INTERVAL "
+- interval between queries to find remaining members of a group,
+after a "leave" message is received.
+
+.BI mcast_startup_query_count " STARTUP_QUERY_COUNT "
+- set the number of IGMP queries to send during startup phase (defaults to 2).
+
+.BI mcast_startup_query_interval " STARTUP_QUERY_INTERVAL "
+- interval between queries in the startup phase.
+
+.BI mcast_query_interval " QUERY_INTERVAL "
+- interval between queries sent by the bridge after the end of the
+startup phase.
+
+.BI mcast_query_response_interval " QUERY_RESPONSE_INTERVAL "
+- set the Max Response Time/Maximum Response Delay for IGMP/MLD
+queries sent by the bridge.
+
+.BI mcast_membership_interval " MEMBERSHIP_INTERVAL "
+- delay after which the bridge will leave a group,
+if no membership reports for this group are received.
+
+.BI mcast_stats_enabled " MCAST_STATS_ENABLED "
+- enable
+.RI ( MCAST_STATS_ENABLED " > 0) "
+or disable
+.RI ( MCAST_STATS_ENABLED " == 0) "
+multicast (IGMP/MLD) stats accounting.
+
+.BI mcast_igmp_version " IGMP_VERSION "
+- set the IGMP version.
+
+.BI mcast_mld_version " MLD_VERSION "
+- set the MLD version.
+
+.BI nf_call_iptables " NF_CALL_IPTABLES "
+- enable
+.RI ( NF_CALL_IPTABLES " > 0) "
+or disable
+.RI ( NF_CALL_IPTABLES " == 0) "
+iptables hooks on the bridge.
+
+.BI nf_call_ip6tables " NF_CALL_IP6TABLES "
+- enable
+.RI ( NF_CALL_IP6TABLES " > 0) "
+or disable
+.RI ( NF_CALL_IP6TABLES " == 0) "
+ip6tables hooks on the bridge.
+
+.BI nf_call_arptables " NF_CALL_ARPTABLES "
+- enable
+.RI ( NF_CALL_ARPTABLES " > 0) "
+or disable
+.RI ( NF_CALL_ARPTABLES " == 0) "
+arptables hooks on the bridge.
+
+
+.in -8
+
+.TP
+MACsec Type Support
+For a link of type
+.I MACsec
+the following additional arguments are supported:
+
+.BI "ip link add link " DEVICE " name " NAME " type macsec"
+[ [
+.BI address " <lladdr>"
+]
+.BI port " PORT"
+|
+.BI sci " SCI"
+] [
+.BI cipher " CIPHER_SUITE"
+] [
+.BR icvlen " { "
+.IR 8..16 " } ] ["
+.BR encrypt " {"
+.BR on " | " off " } ] [ "
+.BR send_sci " { " on " | " off " } ] ["
+.BR end_station " { " on " | " off " } ] ["
+.BR scb " { " on " | " off " } ] ["
+.BR protect " { " on " | " off " } ] ["
+.BR replay " { " on " | " off " }"
+.BR window " { "
+.IR 0..2^32-1 " } ] ["
+.BR validate " { " strict " | " check " | " disabled " } ] ["
+.BR encodingsa " { "
+.IR 0..3 " } ]"
+
+.in +8
+.sp
+.BI address " <lladdr> "
+- sets the system identifier component of secure channel for this MACsec device.
+
+.sp
+.BI port " PORT "
+- sets the port number component of secure channel for this MACsec
+device, in a range from 1 to 65535 inclusive. Numbers with a leading "
+0 " or " 0x " are interpreted as octal and hexadecimal, respectively.
+
+.sp
+.BI sci " SCI "
+- sets the secure channel identifier for this MACsec device.
+.I SCI
+is a 64bit wide number in hexadecimal format.
+
+.sp
+.BI cipher " CIPHER_SUITE "
+- defines the cipher suite to use.
+
+.sp
+.BI icvlen " LENGTH "
+- sets the length of the Integrity Check Value (ICV).
+
+.sp
+.BR "encrypt on " or " encrypt off"
+- switches between authenticated encryption, or authenticity mode only.
+
+.sp
+.BR "send_sci on " or " send_sci off"
+- specifies whether the SCI is included in every packet,
+or only when it is necessary.
+
+.sp
+.BR "end_station on " or " end_station off"
+- sets the End Station bit.
+
+.sp
+.BR "scb on " or " scb off"
+- sets the Single Copy Broadcast bit.
+
+.sp
+.BR "protect on " or " protect off"
+- enables MACsec protection on the device.
+
+.sp
+.BR "replay on " or " replay off"
+- enables replay protection on the device.
+
+.in +8
+
+.sp
+.BI window " SIZE "
+- sets the size of the replay window.
+
+.in -8
+
+.sp
+.BR "validate strict " or " validate check " or " validate disabled"
+- sets the validation mode on the device.
+
+.sp
+.BI encodingsa " AN "
+- sets the active secure association for transmission.
+
+.in -8
+
+.TP
+VRF Type Support
+For a link of type
+.I VRF
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE " type vrf table " TABLE
+
+.in +8
+.sp
+.BR table " table id associated with VRF device"
+
+.in -8
+
+.TP
+RMNET Type Support
+For a link of type
+.I RMNET
+the following additional arguments are supported:
+
+.BI "ip link add link " DEVICE " name " NAME " type rmnet mux_id " MUXID
+
+.in +8
+.sp
+.BI mux_id " MUXID "
+- specifies the mux identifier for the rmnet device, possible values 1-254.
+
+.in -8
+
+.TP
+XFRM Type Support
+For a link of type
+.I XFRM
+the following additional arguments are supported:
+
+.BI "ip link add " DEVICE " type xfrm dev " PHYS_DEV " [ if_id " IF_ID " ]"
+
+.in +8
+.sp
+.BI dev " PHYS_DEV "
+- specifies the underlying physical interface from which transform traffic is sent and received.
+
+.sp
+.BI if_id " IF-ID "
+- specifies the hexadecimal lookup key used to send traffic to and from specific xfrm
+policies. Policies must be configured with the same key. If not set, the key defaults to
+0 and will match any policies which similarly do not have a lookup key configuration.
+
+.in -8
+
+.SS ip link delete - delete virtual link
+
+.TP
+.BI dev " DEVICE "
+specifies the virtual device to act operate on.
+
+.TP
+.BI group " GROUP "
+specifies the group of virtual links to delete. Group 0 is not allowed to be
+deleted since it is the default group.
+
+.TP
+.BI type " TYPE "
+specifies the type of the device.
+
+.SS ip link set - change device attributes
+
+.PP
+.B Warning:
+If multiple parameter changes are requested,
+.B ip
+aborts immediately after any of the changes have failed.
+This is the only case when
+.B ip
+can move the system to an unpredictable state. The solution
+is to avoid changing several parameters with one
+.B ip link set
+call.
+The modifier
+.B change
+is equivalent to
+.BR "set" .
+
+
+.TP
+.BI dev " DEVICE "
+.I DEVICE
+specifies network device to operate on. When configuring SR-IOV
+Virtual Function (VF) devices, this keyword should specify the
+associated Physical Function (PF) device.
+
+.TP
+.BI group " GROUP "
+.I GROUP
+has a dual role: If both group and dev are present, then move the device to the
+specified group. If only a group is specified, then the command operates on
+all devices in that group.
+
+.TP
+.BR up " and " down
+change the state of the device to
+.B UP
+or
+.BR "DOWN" .
+
+.TP
+.BR "arp on " or " arp off"
+change the
+.B NOARP
+flag on the device.
+
+.TP
+.BR "multicast on " or " multicast off"
+change the
+.B MULTICAST
+flag on the device.
+
+.TP
+.BR "protodown on " or " protodown off"
+change the
+.B PROTODOWN
+state on the device. Indicates that a protocol error has been detected
+on the port. Switch drivers can react to this error by doing a phys
+down on the switch port.
+
+.TP
+.BR "dynamic on " or " dynamic off"
+change the
+.B DYNAMIC
+flag on the device. Indicates that address can change when interface
+goes down (currently
+.B NOT
+used by the Linux).
+
+.TP
+.BI name " NAME"
+change the name of the device. This operation is not
+recommended if the device is running or has some addresses
+already configured.
+
+.TP
+.BI txqueuelen " NUMBER"
+.TP
+.BI txqlen " NUMBER"
+change the transmit queue length of the device.
+
+.TP
+.BI mtu " NUMBER"
+change the
+.I MTU
+of the device.
+
+.TP
+.BI address " LLADDRESS"
+change the station address of the interface.
+
+.TP
+.BI broadcast " LLADDRESS"
+.TP
+.BI brd " LLADDRESS"
+.TP
+.BI peer " LLADDRESS"
+change the link layer broadcast address or the peer address when
+the interface is
+.IR "POINTOPOINT" .
+
+.TP
+.BI netns " NETNSNAME " \fR| " PID"
+move the device to the network namespace associated with name
+.IR "NETNSNAME " or
+.RI process " PID".
+
+Some devices are not allowed to change network namespace: loopback, bridge,
+wireless. These are network namespace local devices. In such case
+.B ip
+tool will return "Invalid argument" error. It is possible to find out
+if device is local to a single network namespace by checking
+.B netns-local
+flag in the output of the
+.BR ethtool ":"
+
+.in +8
+.B ethtool -k
+.I DEVICE
+.in -8
+
+To change network namespace for wireless devices the
+.B iw
+tool can be used. But it allows to change network namespace only for
+physical devices and by process
+.IR PID .
+
+.TP
+.BI alias " NAME"
+give the device a symbolic name for easy reference.
+
+.TP
+.BI group " GROUP"
+specify the group the device belongs to.
+The available groups are listed in file
+.BR "@SYSCONFDIR@/group" .
+
+.TP
+.BI vf " NUM"
+specify a Virtual Function device to be configured. The associated PF device
+must be specified using the
+.B dev
+parameter.
+
+.in +8
+.BI mac " LLADDRESS"
+- change the station address for the specified VF. The
+.B vf
+parameter must be specified.
+
+.sp
+.BI vlan " VLANID"
+- change the assigned VLAN for the specified VF. When specified, all traffic
+sent from the VF will be tagged with the specified VLAN ID. Incoming traffic
+will be filtered for the specified VLAN ID, and will have all VLAN tags
+stripped before being passed to the VF. Setting this parameter to 0 disables
+VLAN tagging and filtering. The
+.B vf
+parameter must be specified.
+
+.sp
+.BI qos " VLAN-QOS"
+- assign VLAN QOS (priority) bits for the VLAN tag. When specified, all VLAN
+tags transmitted by the VF will include the specified priority bits in the
+VLAN tag. If not specified, the value is assumed to be 0. Both the
+.B vf
+and
+.B vlan
+parameters must be specified. Setting both
+.B vlan
+and
+.B qos
+as 0 disables VLAN tagging and filtering for the VF.
+
+.sp
+.BI proto " VLAN-PROTO"
+- assign VLAN PROTOCOL for the VLAN tag, either 802.1Q or 802.1ad.
+Setting to 802.1ad, all traffic sent from the VF will be tagged with
+VLAN S-Tag. Incoming traffic will have VLAN S-Tags stripped before
+being passed to the VF. Setting to 802.1ad also enables an option to
+concatenate another VLAN tag, so both S-TAG and C-TAG will be
+inserted/stripped for outgoing/incoming traffic, respectively. If not
+specified, the value is assumed to be 802.1Q. Both the
+.B vf
+and
+.B vlan
+parameters must be specified.
+
+.sp
+.BI rate " TXRATE"
+-- change the allowed transmit bandwidth, in Mbps, for the specified VF.
+Setting this parameter to 0 disables rate limiting.
+.B vf
+parameter must be specified.
+Please use new API
+.B "max_tx_rate"
+option instead.
+
+.sp
+.BI max_tx_rate " TXRATE"
+- change the allowed maximum transmit bandwidth, in Mbps, for the
+specified VF. Setting this parameter to 0 disables rate limiting.
+.B vf
+parameter must be specified.
+
+.sp
+.BI min_tx_rate " TXRATE"
+- change the allowed minimum transmit bandwidth, in Mbps, for the specified VF.
+Minimum TXRATE should be always <= Maximum TXRATE.
+Setting this parameter to 0 disables rate limiting.
+.B vf
+parameter must be specified.
+
+.sp
+.BI spoofchk " on|off"
+- turn packet spoof checking on or off for the specified VF.
+.sp
+.BI query_rss " on|off"
+- toggle the ability of querying the RSS configuration of a specific
+ VF. VF RSS information like RSS hash key may be considered sensitive
+ on some devices where this information is shared between VF and PF
+ and thus its querying may be prohibited by default.
+.sp
+.BI state " auto|enable|disable"
+- set the virtual link state as seen by the specified VF. Setting to
+auto means a reflection of the PF link state, enable lets the VF to
+communicate with other VFs on this host even if the PF link state is
+down, disable causes the HW to drop any packets sent by the VF.
+.sp
+.BI trust " on|off"
+- trust the specified VF user. This enables that VF user can set a
+specific feature which may impact security and/or
+performance. (e.g. VF multicast promiscuous mode)
+.sp
+.BI node_guid " eui64"
+- configure node GUID for Infiniband VFs.
+.sp
+.BI port_guid " eui64"
+- configure port GUID for Infiniband VFs.
+.in -8
+
+.TP
+.B xdp object "|" pinned "|" off
+set (or unset) a XDP ("eXpress Data Path") BPF program to run on every
+packet at driver level.
+.B ip link
+output will indicate a
+.B xdp
+flag for the networking device. If the driver does not have native XDP
+support, the kernel will fall back to a slower, driver-independent "generic"
+XDP variant. The
+.B ip link
+output will in that case indicate
+.B xdpgeneric
+instead of
+.B xdp
+only. If the driver does have native XDP support, but the program is
+loaded under
+.B xdpgeneric object "|" pinned
+then the kernel will use the generic XDP variant instead of the native one.
+.B xdpdrv
+has the opposite effect of requestsing that the automatic fallback to the
+generic XDP variant be disabled and in case driver is not XDP-capable error
+should be returned.
+.B xdpdrv
+also disables hardware offloads.
+.B xdpoffload
+in ip link output indicates that the program has been offloaded to hardware
+and can also be used to request the "offload" mode, much like
+.B xdpgeneric
+it forces program to be installed specifically in HW/FW of the apater.
+
+.B off
+(or
+.B none
+)
+- Detaches any currently attached XDP/BPF program from the given device.
+
+.BI object " FILE "
+- Attaches a XDP/BPF program to the given device. The
+.I FILE
+points to a BPF ELF file (f.e. generated by LLVM) that contains the BPF
+program code, map specifications, etc. If a XDP/BPF program is already
+attached to the given device, an error will be thrown. If no XDP/BPF
+program is currently attached, the device supports XDP and the program
+from the BPF ELF file passes the kernel verifier, then it will be attached
+to the device. If the option
+.I -force
+is passed to
+.B ip
+then any prior attached XDP/BPF program will be atomically overridden and
+no error will be thrown in this case. If no
+.B section
+option is passed, then the default section name ("prog") will be assumed,
+otherwise the provided section name will be used. If no
+.B verbose
+option is passed, then a verifier log will only be dumped on load error.
+See also
+.B EXAMPLES
+section for usage examples.
+
+.BI section " NAME "
+- Specifies a section name that contains the BPF program code. If no section
+name is specified, the default one ("prog") will be used. This option is
+to be passed with the
+.B object
+option.
+
+.BI verbose
+- Act in verbose mode. For example, even in case of success, this will
+print the verifier log in case a program was loaded from a BPF ELF file.
+
+.BI pinned " FILE "
+- Attaches a XDP/BPF program to the given device. The
+.I FILE
+points to an already pinned BPF program in the BPF file system. The option
+.B section
+doesn't apply here, but otherwise semantics are the same as with the option
+.B object
+described already.
+
+.TP
+.BI master " DEVICE"
+set master device of the device (enslave device).
+
+.TP
+.BI nomaster
+unset master device of the device (release device).
+
+.TP
+.BI addrgenmode " eui64|none|stable_secret|random"
+set the IPv6 address generation mode
+
+.I eui64
+- use a Modified EUI-64 format interface identifier
+
+.I none
+- disable automatic address generation
+
+.I stable_secret
+- generate the interface identifier based on a preset
+ /proc/sys/net/ipv6/conf/{default,DEVICE}/stable_secret
+
+.I random
+- like stable_secret, but auto-generate a new random secret if none is set
+
+.TP
+.BR "link-netnsid "
+set peer netnsid for a cross-netns interface
+
+.TP
+.BI type " ETYPE TYPE_ARGS"
+Change type-specific settings. For a list of supported types and arguments refer
+to the description of
+.B "ip link add"
+above. In addition to that, it is possible to manipulate settings to slave
+devices:
+
+.TP
+Bridge Slave Support
+For a link with master
+.B bridge
+the following additional arguments are supported:
+
+.B "ip link set type bridge_slave"
+[
+.B fdb_flush
+] [
+.BI state " STATE"
+] [
+.BI priority " PRIO"
+] [
+.BI cost " COST"
+] [
+.BR guard " { " on " | " off " }"
+] [
+.BR hairpin " { " on " | " off " }"
+] [
+.BR fastleave " { " on " | " off " }"
+] [
+.BR root_block " { " on " | " off " }"
+] [
+.BR learning " { " on " | " off " }"
+] [
+.BR flood " { " on " | " off " }"
+] [
+.BR proxy_arp " { " on " | " off " }"
+] [
+.BR proxy_arp_wifi " { " on " | " off " }"
+] [
+.BI mcast_router " MULTICAST_ROUTER"
+] [
+.BR mcast_fast_leave " { " on " | " off "}"
+] [
+.BR mcast_flood " { " on " | " off " }"
+] [
+.BR mcast_to_unicast " { " on " | " off " }"
+] [
+.BR group_fwd_mask " MASK"
+] [
+.BR neigh_suppress " { " on " | " off " }"
+] [
+.BR vlan_tunnel " { " on " | " off " }"
+] [
+.BR isolated " { " on " | " off " }"
+] [
+.BR backup_port " DEVICE"
+] [
+.BR nobackup_port " ]"
+
+.in +8
+.sp
+.B fdb_flush
+- flush bridge slave's fdb dynamic entries.
+
+.BI state " STATE"
+- Set port state.
+.I STATE
+is a number representing the following states:
+.BR 0 " (disabled),"
+.BR 1 " (listening),"
+.BR 2 " (learning),"
+.BR 3 " (forwarding),"
+.BR 4 " (blocking)."
+
+.BI priority " PRIO"
+- set port priority (allowed values are between 0 and 63, inclusively).
+
+.BI cost " COST"
+- set port cost (allowed values are between 1 and 65535, inclusively).
+
+.BR guard " { " on " | " off " }"
+- block incoming BPDU packets on this port.
+
+.BR hairpin " { " on " | " off " }"
+- enable hairpin mode on this port. This will allow incoming packets on this
+port to be reflected back.
+
+.BR fastleave " { " on " | " off " }"
+- enable multicast fast leave on this port.
+
+.BR root_block " { " on " | " off " }"
+- block this port from becoming the bridge's root port.
+
+.BR learning " { " on " | " off " }"
+- allow MAC address learning on this port.
+
+.BR flood " { " on " | " off " }"
+- open the flood gates on this port, i.e. forward all unicast frames to this
+port also. Requires
+.BR proxy_arp " and " proxy_arp_wifi
+to be turned off.
+
+.BR proxy_arp " { " on " | " off " }"
+- enable proxy ARP on this port.
+
+.BR proxy_arp_wifi " { " on " | " off " }"
+- enable proxy ARP on this port which meets extended requirements by IEEE
+802.11 and Hotspot 2.0 specifications.
+
+.BI mcast_router " MULTICAST_ROUTER"
+- configure this port for having multicast routers attached. A port with a
+multicast router will receive all multicast traffic.
+.I MULTICAST_ROUTER
+may be either
+.B 0
+to disable multicast routers on this port,
+.B 1
+to let the system detect the presence of of routers (this is the default),
+.B 2
+to permanently enable multicast traffic forwarding on this port or
+.B 3
+to enable multicast routers temporarily on this port, not depending on incoming
+queries.
+
+.BR mcast_fast_leave " { " on " | " off " }"
+- this is a synonym to the
+.B fastleave
+option above.
+
+.BR mcast_flood " { " on " | " off " }"
+- controls whether a given port will flood multicast traffic for which
+ there is no MDB entry.
+
+.BR mcast_to_unicast " { " on " | " off " }"
+- controls whether a given port will replicate packets using unicast
+ instead of multicast. By default this flag is off.
+
+.BI group_fwd_mask " MASK "
+- set the group forward mask. This is the bitmask that is applied to
+decide whether to forward incoming frames destined to link-local
+addresses, ie addresses of the form 01:80:C2:00:00:0X (defaults to
+0, ie the bridge does not forward any link-local frames coming on
+this port).
+
+.BR neigh_suppress " { " on " | " off " }"
+- controls whether neigh discovery (arp and nd) proxy and suppression
+is enabled on the port. By default this flag is off.
+
+.BR vlan_tunnel " { " on " | " off " }"
+- controls whether vlan to tunnel mapping is enabled on the port. By
+default this flag is off.
+
+.BI backup_port " DEVICE"
+- if the port loses carrier all traffic will be redirected to the
+configured backup port
+
+.BR nobackup_port
+- removes the currently configured backup port
+
+.in -8
+
+.TP
+Bonding Slave Support
+For a link with master
+.B bond
+the following additional arguments are supported:
+
+.B "ip link set type bond_slave"
+[
+.BI queue_id " ID"
+]
+
+.in +8
+.sp
+.BI queue_id " ID"
+- set the slave's queue ID (a 16bit unsigned value).
+
+.in -8
+
+.TP
+MACVLAN and MACVTAP Support
+Modify list of allowed macaddr for link in source mode.
+
+.B "ip link set type { macvlan | macvap } "
+[
+.BI macaddr " " "" COMMAND " " MACADDR " ..."
+]
+
+Commands:
+.in +8
+.B add
+- add MACADDR to allowed list
+.sp
+.B set
+- replace allowed list
+.sp
+.B del
+- remove MACADDR from allowed list
+.sp
+.B flush
+- flush whole allowed list
+.sp
+.in -8
+
+
+.SS ip link show - display device attributes
+
+.TP
+.BI dev " NAME " (default)
+.I NAME
+specifies the network device to show.
+
+.TP
+.BI group " GROUP "
+.I GROUP
+specifies what group of devices to show.
+
+.TP
+.B up
+only display running interfaces.
+
+.TP
+.BI master " DEVICE "
+.I DEVICE
+specifies the master device which enslaves devices to show.
+
+.TP
+.BI vrf " NAME "
+.I NAME
+speficies the VRF which enslaves devices to show.
+
+.TP
+.BI type " TYPE "
+.I TYPE
+specifies the type of devices to show.
+
+Note that the type name is not checked against the list of supported types -
+instead it is sent as-is to the kernel. Later it is used to filter the returned
+interface list by comparing it with the relevant attribute in case the kernel
+didn't filter already. Therefore any string is accepted, but may lead to empty
+output.
+
+.SS ip link xstats - display extended statistics
+
+.TP
+.BI type " TYPE "
+.I TYPE
+specifies the type of devices to display extended statistics for.
+
+.SS ip link afstats - display address-family specific statistics
+
+.TP
+.BI dev " DEVICE "
+.I DEVICE
+specifies the device to display address-family statistics for.
+
+.SS ip link help - display help
+
+.PP
+.I "TYPE"
+specifies which help of link type to dislpay.
+
+.SS
+.I GROUP
+may be a number or a string from the file
+.B @SYSCONFDIR@/group
+which can be manually filled.
+
+.SH "EXAMPLES"
+.PP
+ip link show
+.RS 4
+Shows the state of all network interfaces on the system.
+.RE
+.PP
+ip link show type bridge
+.RS 4
+Shows the bridge devices.
+.RE
+.PP
+ip link show type vlan
+.RS 4
+Shows the vlan devices.
+.RE
+.PP
+ip link show master br0
+.RS 4
+Shows devices enslaved by br0
+.RE
+.PP
+ip link set dev ppp0 mtu 1400
+.RS 4
+Change the MTU the ppp0 device.
+.RE
+.PP
+ip link add link eth0 name eth0.10 type vlan id 10
+.RS 4
+Creates a new vlan device eth0.10 on device eth0.
+.RE
+.PP
+ip link delete dev eth0.10
+.RS 4
+Removes vlan device.
+.RE
+
+ip link help gre
+.RS 4
+Display help for the gre link type.
+.RE
+.PP
+ip link add name tun1 type ipip remote 192.168.1.1
+local 192.168.1.2 ttl 225 encap gue encap-sport auto
+encap-dport 5555 encap-csum encap-remcsum
+.RS 4
+Creates an IPIP that is encapsulated with Generic UDP Encapsulation,
+and the outer UDP checksum and remote checksum offload are enabled.
+.RE
+.PP
+ip link set dev eth0 xdp obj prog.o
+.RS 4
+Attaches a XDP/BPF program to device eth0, where the program is
+located in prog.o, section "prog" (default section). In case a
+XDP/BPF program is already attached, throw an error.
+.RE
+.PP
+ip -force link set dev eth0 xdp obj prog.o sec foo
+.RS 4
+Attaches a XDP/BPF program to device eth0, where the program is
+located in prog.o, section "foo". In case a XDP/BPF program is
+already attached, it will be overridden by the new one.
+.RE
+.PP
+ip -force link set dev eth0 xdp pinned /sys/fs/bpf/foo
+.RS 4
+Attaches a XDP/BPF program to device eth0, where the program was
+previously pinned as an object node into BPF file system under
+name foo.
+.RE
+.PP
+ip link set dev eth0 xdp off
+.RS 4
+If a XDP/BPF program is attached on device eth0, detach it and
+effectively turn off XDP for device eth0.
+.RE
+.PP
+ip link add link wpan0 lowpan0 type lowpan
+.RS 4
+Creates a 6LoWPAN interface named lowpan0 on the underlying
+IEEE 802.15.4 device wpan0.
+.RE
+.PP
+ip link add dev ip6erspan11 type ip6erspan seq key 102
+local fc00:100::2 remote fc00:100::1
+erspan_ver 2 erspan_dir ingress erspan_hwid 17
+.RS 4
+Creates a IP6ERSPAN version 2 interface named ip6erspan00.
+.RE
+
+.SH SEE ALSO
+.br
+.BR ip (8),
+.BR ip-netns (8),
+.BR ethtool (8),
+.BR iptables (8)
+
+.SH AUTHOR
+Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-macsec.8 b/man/man8/ip-macsec.8
new file mode 100644
index 0000000..2179b33
--- /dev/null
+++ b/man/man8/ip-macsec.8
@@ -0,0 +1,120 @@
+.TH IP\-MACSEC 8 "07 Mar 2016" "iproute" "Linux"
+.SH NAME
+ip-macsec \- MACsec device configuration
+.SH "SYNOPSIS"
+.BI "ip link add link " DEVICE " name " NAME " type macsec "
+[ [
+.BI address " <lladdr>"
+]
+.BI port " PORT"
+|
+.BI sci " <u64>"
+] [
+.BR cipher " { " default " | " gcm-aes-128 " | "gcm-aes-256" } ] ["
+.BI icvlen " ICVLEN"
+] [
+.BR encrypt " { " on " | " off " } ] ["
+.BR send_sci " { " on " | " off " } ] ["
+.BR end_station " { " on " | " off " } ] ["
+.BR scb " { " on " | " off " } ] ["
+.BR protect " { " on " | " off " } ] ["
+.BR replay " { " on " | " off " } ] ["
+.BI window " WINDOW"
+] [
+.BR validate " { " strict " | " check " | " disabled " } ] ["
+.BI encodingsa " SA"
+]
+
+.BI "ip macsec add " DEV " tx sa"
+.RI "{ " 0..3 " } [ " OPTS " ]"
+.BI key " ID KEY"
+.br
+.BI "ip macsec set " DEV " tx sa"
+.RI "{ " 0..3 " } [ " OPTS " ]"
+.br
+.BI "ip macsec del " DEV " tx sa"
+.RI "{ " 0..3 " }"
+
+.BI "ip macsec add " DEV " rx " SCI
+.RB [ " on " | " off " ]
+.br
+.BI "ip macsec set " DEV " rx " SCI
+.RB [ " on " | " off " ]
+.br
+.BI "ip macsec del " DEV " rx " SCI
+
+.BI "ip macsec add " DEV " rx " SCI " sa"
+.RI "{ " 0..3 " } [ " OPTS " ]"
+.BI key " ID KEY"
+.br
+.BI "ip macsec set " DEV " rx " SCI " sa"
+.RI "{ " 0..3 " } [ " OPTS " ]"
+.br
+.BI "ip macsec del " DEV " rx " SCI " sa"
+.RI "{ " 0..3 " }"
+
+.B ip macsec show
+.RI [ " DEV " ]
+
+.IR OPTS " := [ "
+.BR pn " { "
+.IR 1..2^32-1 " } ] ["
+.BR on " | " off " ]"
+.br
+.IR SCI " := { "
+.B sci
+.IR <u64> " | "
+.BI port
+.IR PORT
+.BI address " <lladdr> "
+}
+.br
+.IR PORT " := { " 1..2^16-1 " } "
+
+
+.SH DESCRIPTION
+The
+.B ip macsec
+commands are used to configure transmit secure associations and receive secure channels and their secure associations on a MACsec device created with the
+.B ip link add
+command using the
+.I macsec
+type.
+
+.SH EXAMPLES
+.PP
+.SS Create a MACsec device on link eth0
+.nf
+# ip link add link eth0 macsec0 type macsec port 11 encrypt on
+.PP
+.SS Configure a secure association on that device
+.nf
+# ip macsec add macsec0 tx sa 0 pn 1024 on key 01 81818181818181818181818181818181
+.PP
+.SS Configure a receive channel
+.nf
+# ip macsec add macsec0 rx port 1234 address c6:19:52:8f:e6:a0
+.PP
+.SS Configure a receive association
+.nf
+# ip macsec add macsec0 rx port 1234 address c6:19:52:8f:e6:a0 sa 0 pn 1 on key 00 82828282828282828282828282828282
+.PP
+.SS Display MACsec configuration
+.nf
+# ip macsec show
+
+.SH NOTES
+This tool can be used to configure the 802.1AE keys of the interface. Note that 802.1AE uses GCM-AES
+with a initialization vector (IV) derived from the packet number. The same key must not be used
+with the same IV more than once. Instead, keys must be frequently regenerated and distibuted.
+This tool is thus mostly for debugging and testing, or in combination with a user-space application
+that reconfigures the keys. It is wrong to just configure the keys statically and assume them to work
+indefinitely. The suggested and standardized way for key management is 802.1X-2010, which is implemented
+by wpa_supplicant.
+
+.SH SEE ALSO
+.br
+.BR ip-link (8)
+.BR wpa_supplicant (8)
+.SH AUTHOR
+Sabrina Dubroca <sd@queasysnail.net>
diff --git a/man/man8/ip-maddress.8 b/man/man8/ip-maddress.8
new file mode 100644
index 0000000..f3432bb
--- /dev/null
+++ b/man/man8/ip-maddress.8
@@ -0,0 +1,59 @@
+.TH IP\-MADDRESS 8 "20 Dec 2011" "iproute2" "Linux"
+.SH "NAME"
+ip-maddress \- multicast addresses management
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B maddress
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+.ti -8
+
+.BR "ip maddress" " [ " add " | " del " ]"
+.IB MULTIADDR " dev " NAME
+
+.ti -8
+.BR "ip maddress show" " [ " dev
+.IR NAME " ]"
+
+.SH DESCRIPTION
+.B maddress
+objects are multicast addresses.
+
+.SS ip maddress show - list multicast addresses
+
+.TP
+.BI dev " NAME " (default)
+the device name.
+
+.TP
+.B ip maddress add - add a multicast address
+.TP
+.B ip maddress delete - delete a multicast address
+.sp
+These commands attach/detach a static link-layer multicast address
+to listen on the interface.
+Note that it is impossible to join protocol multicast groups
+statically. This command only manages link-layer addresses.
+
+.RS
+.TP
+.BI address " LLADDRESS " (default)
+the link-layer multicast address.
+
+.TP
+.BI dev " NAME"
+the device to join/leave this multicast address.
+.RE
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-monitor.8 b/man/man8/ip-monitor.8
new file mode 100644
index 0000000..86f8f98
--- /dev/null
+++ b/man/man8/ip-monitor.8
@@ -0,0 +1,133 @@
+.TH IP\-MONITOR 8 "13 Dec 2012" "iproute2" "Linux"
+.SH "NAME"
+ip-monitor, rtmon \- state monitoring
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.BR "ip monitor" " [ " all " |"
+.IR OBJECT-LIST " ] ["
+.BI file " FILENAME "
+] [
+.BI label
+] [
+.BI all-nsid
+] [
+.BI dev " DEVICE "
+]
+.sp
+
+.SH OPTIONS
+
+.TP
+.BR "\-t" , " \-timestamp"
+Prints timestamp before the event message on the separated line in format:
+ Timestamp: <Day> <Month> <DD> <hh:mm:ss> <YYYY> <usecs> usec
+ <EVENT>
+
+.TP
+.BR "\-ts" , " \-tshort"
+Prints short timestamp before the event message on the same line in format:
+ [<YYYY>-<MM>-<DD>T<hh:mm:ss>.<ms>] <EVENT>
+
+.SH DESCRIPTION
+The
+.B ip
+utility can monitor the state of devices, addresses
+and routes continuously. This option has a slightly different format.
+Namely, the
+.B monitor
+command is the first in the command line and then the object list follows:
+
+.BR "ip monitor" " [ " all " |"
+.IR OBJECT-LIST " ] ["
+.BI file " FILENAME "
+] [
+.BI label
+] [
+.BI all-nsid
+] [
+.BI dev " DEVICE "
+]
+
+.I OBJECT-LIST
+is the list of object types that we want to monitor.
+It may contain
+.BR link ", " address ", " route ", " mroute ", " prefix ", "
+.BR neigh ", " netconf ", " rule " and " nsid "."
+If no
+.B file
+argument is given,
+.B ip
+opens RTNETLINK, listens on it and dumps state changes in the format
+described in previous sections.
+
+.P
+If the
+.BI label
+option is set, a prefix is displayed before each message to
+show the family of the message. For example:
+.sp
+.in +2
+[NEIGH]10.16.0.112 dev eth0 lladdr 00:04:23:df:2f:d0 REACHABLE
+[LINK]3: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast state DOWN group default
+ link/ether 52:54:00:12:34:57 brd ff:ff:ff:ff:ff:ff
+.in -2
+.sp
+
+.P
+If the
+.BI all-nsid
+option is set, the program listens to all network namespaces that have a
+nsid assigned into the network namespace were the program is running.
+A prefix is displayed to show the network namespace where the message
+originates. Example:
+.sp
+.in +2
+[nsid 0]10.16.0.112 dev eth0 lladdr 00:04:23:df:2f:d0 REACHABLE
+.in -2
+.sp
+
+.P
+If the
+.BI file
+option is given, the program does not listen on RTNETLINK,
+but opens the given file, and dumps its contents. The file
+should contain RTNETLINK messages saved in binary format.
+Such a file can be generated with the
+.B rtmon
+utility. This utility has a command line syntax similar to
+.BR "ip monitor" .
+Ideally,
+.B rtmon
+should be started before the first network configuration command
+is issued. F.e. if you insert:
+.sp
+.in +8
+rtmon file /var/log/rtmon.log
+.in -8
+.sp
+in a startup script, you will be able to view the full history
+later.
+
+.P
+Nevertheless, it is possible to start
+.B rtmon
+at any time.
+It prepends the history with the state snapshot dumped at the moment
+of starting.
+
+.P
+If the
+.BI dev
+option is given, the program prints only events related to this device.
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Michail Litvak <mci@owl.openwall.com>
+.br
+Manpage revised by Nicolas Dichtel <nicolas.dichtel@6wind.com>
diff --git a/man/man8/ip-mroute.8 b/man/man8/ip-mroute.8
new file mode 100644
index 0000000..b64e30d
--- /dev/null
+++ b/man/man8/ip-mroute.8
@@ -0,0 +1,58 @@
+.TH IP\-MROUTE 8 "13 Dec 2012" "iproute2" "Linux"
+.SH "NAME"
+ip-mroute \- multicast routing cache management
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.BR "ip mroute show" " [ [ "
+.BR " to " " ] "
+.IR PREFIX " ] [ "
+.B from
+.IR PREFIX " ] [ "
+.B iif
+.IR DEVICE " ] [ "
+.B table
+.IR TABLE_ID " ] "
+
+.SH DESCRIPTION
+.B mroute
+objects are multicast routing cache entries created by a user-level
+mrouting daemon (f.e.
+.B pimd
+or
+.B mrouted
+).
+
+Due to the limitations of the current interface to the multicast routing
+engine, it is impossible to change
+.B mroute
+objects administratively, so we can only display them. This limitation
+will be removed in the future.
+
+.SS ip mroute show - list mroute cache entries
+
+.TP
+.BI to " PREFIX " (default)
+the prefix selecting the destination multicast addresses to list.
+
+.TP
+.BI iif " NAME"
+the interface on which multicast packets are received.
+
+.TP
+.BI from " PREFIX"
+the prefix selecting the IP source addresses of the multicast route.
+
+.TP
+.BI table " TABLE_ID"
+the table id selecting the multicast table. It can be
+.BR local ", " main ", " default ", " all " or a number."
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-neighbour.8 b/man/man8/ip-neighbour.8
new file mode 100644
index 0000000..4a672bb
--- /dev/null
+++ b/man/man8/ip-neighbour.8
@@ -0,0 +1,251 @@
+.TH IP\-NEIGHBOUR 8 "20 Dec 2011" "iproute2" "Linux"
+.SH "NAME"
+ip-neighbour \- neighbour/arp tables management.
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B neigh
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.BR "ip neigh" " { " add " | " del " | " change " | " replace " } { "
+.IR ADDR " [ "
+.B lladdr
+.IR LLADDR " ] [ "
+.B nud
+.IR STATE " ] |"
+.B proxy
+.IR ADDR " } [ "
+.B dev
+.IR DEV " ] [ "
+.BR router " ] [ "
+.BR extern_learn " ]"
+
+.ti -8
+.BR "ip neigh" " { " show " | " flush " } [ " proxy " ] [ " to
+.IR PREFIX " ] [ "
+.B dev
+.IR DEV " ] [ "
+.B nud
+.IR STATE " ] [ "
+.B vrf
+.IR NAME " ] "
+
+.ti -8
+.IR STATE " := {"
+.BR permanent " | " noarp " | " stale " | " reachable " | " none " |"
+.BR incomplete " | " delay " | " probe " | " failed " }"
+
+.SH DESCRIPTION
+The
+.B ip neigh
+command manipulates
+.I neighbour
+objects that establish bindings between protocol addresses and
+link layer addresses for hosts sharing the same link.
+Neighbour entries are organized into tables. The IPv4 neighbour table
+is also known by another name - the ARP table.
+
+.P
+The corresponding commands display neighbour bindings
+and their properties, add new neighbour entries and delete old ones.
+
+.TP
+ip neighbour add
+add a new neighbour entry
+.TP
+ip neighbour change
+change an existing entry
+.TP
+ip neighbour replace
+add a new entry or change an existing one
+.RS
+.PP
+These commands create new neighbour records or update existing ones.
+
+.TP
+.BI to " ADDRESS " (default)
+the protocol address of the neighbour. It is either an IPv4 or IPv6 address.
+
+.TP
+.BI dev " NAME"
+the interface to which this neighbour is attached.
+
+.TP
+.BI proxy
+indicates whether we are proxying for this neigbour entry
+
+.TP
+.BI router
+indicates whether neigbour is a router
+
+.TP
+.BI extern_learn
+this neigh entry was learned externally. This option can be used to
+indicate to the kernel that this is a controller learnt dynamic entry.
+Kernel will not gc such an entry.
+
+.TP
+.BI lladdr " LLADDRESS"
+the link layer address of the neighbour.
+.I LLADDRESS
+can also be
+.BR "null" .
+
+.TP
+.BI nud " STATE"
+the state of the neighbour entry.
+.B nud
+is an abbreviation for 'Neighbour Unreachability Detection'.
+The state can take one of the following values:
+
+.RS
+.TP
+.B permanent
+the neighbour entry is valid forever and can be only
+be removed administratively.
+.TP
+.B noarp
+the neighbour entry is valid. No attempts to validate
+this entry will be made but it can be removed when its lifetime expires.
+.TP
+.B reachable
+the neighbour entry is valid until the reachability
+timeout expires.
+.TP
+.B stale
+the neighbour entry is valid but suspicious.
+This option to
+.B ip neigh
+does not change the neighbour state if it was valid and the address
+is not changed by this command.
+.TP
+.B none
+this is a pseudo state used when initially creating a neighbour entry or after
+trying to remove it before it becomes free to do so.
+.TP
+.B incomplete
+the neighbour entry has not (yet) been validated/resolved.
+.TP
+.B delay
+neighbor entry validation is currently delayed.
+.TP
+.B probe
+neighbor is being probed.
+.TP
+.B failed
+max number of probes exceeded without success, neighbor validation has
+ultimately failed.
+.RE
+.RE
+
+.TP
+ip neighbour delete
+delete a neighbour entry
+.RS
+.PP
+The arguments are the same as with
+.BR "ip neigh add" ,
+except that
+.B lladdr
+and
+.B nud
+are ignored.
+
+.PP
+.B Warning:
+Attempts to delete or manually change a
+.B noarp
+entry created by the kernel may result in unpredictable behaviour.
+Particularly, the kernel may try to resolve this address even
+on a
+.B NOARP
+interface or if the address is multicast or broadcast.
+.RE
+
+.TP
+ip neighbour show
+list neighbour entries
+.RS
+.TP
+.BI to " ADDRESS " (default)
+the prefix selecting the neighbours to list.
+
+.TP
+.BI dev " NAME"
+only list the neighbours attached to this device.
+
+.TP
+.BI vrf " NAME"
+only list the neighbours for given VRF.
+
+.TP
+.BI proxy
+list neighbour proxies.
+
+.TP
+.B unused
+only list neighbours which are not currently in use.
+
+.TP
+.BI nud " STATE"
+only list neighbour entries in this state.
+.I NUD_STATE
+takes values listed below or the special value
+.B all
+which means all states. This option may occur more than once.
+If this option is absent,
+.B ip
+lists all entries except for
+.B none
+and
+.BR "noarp" .
+.RE
+
+.TP
+ip neighbour flush
+flush neighbour entries
+.RS
+This command has the same arguments as
+.B show.
+The differences are that it does not run when no arguments are given,
+and that the default neighbour states to be flushed do not include
+.B permanent
+and
+.BR "noarp" .
+
+.PP
+With the
+.B -statistics
+option, the command becomes verbose. It prints out the number of
+deleted neighbours and the number of rounds made to flush the
+neighbour table. If the option is given
+twice,
+.B ip neigh flush
+also dumps all the deleted neighbours.
+.RE
+
+.SH EXAMPLES
+.PP
+ip neighbour
+.RS
+Shows the current neighbour table in kernel.
+.RE
+.PP
+ip neigh flush dev eth0
+.RS
+Removes entries in the neighbour table on device eth0.
+.RE
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-netconf.8 b/man/man8/ip-netconf.8
new file mode 100644
index 0000000..7fe3e5f
--- /dev/null
+++ b/man/man8/ip-netconf.8
@@ -0,0 +1,36 @@
+.TH IP\-NETCONF 8 "13 Dec 2012" "iproute2" "Linux"
+.SH "NAME"
+ip-netconf \- network configuration monitoring
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.BR "ip " " [ ip-OPTIONS ] " "netconf show" " [ "
+.B dev
+.IR NAME " ]"
+
+.SH DESCRIPTION
+The
+.B ip netconf
+utility can monitor IPv4 and IPv6 parameters (see
+.BR "/proc/sys/net/ipv[4|6]/conf/[all|DEV]/" ")"
+like forwarding, rp_filter, proxy_neigh, ignore_routes_with_linkdown
+or mc_forwarding status.
+
+If no interface is specified, the entry
+.B all
+is displayed.
+
+.SS ip netconf show - display network parameters
+
+.TP
+.BI dev " NAME"
+the name of the device to display network parameters for.
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Nicolas Dichtel <nicolas.dichtel@6wind.com>
diff --git a/man/man8/ip-netns.8 b/man/man8/ip-netns.8
new file mode 100644
index 0000000..c75917d
--- /dev/null
+++ b/man/man8/ip-netns.8
@@ -0,0 +1,271 @@
+.TH IP\-NETNS 8 "16 Jan 2013" "iproute2" "Linux"
+.SH NAME
+ip-netns \- process network namespace management
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B netns
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+.ti -8
+.BR "ip netns" " [ " list " ]"
+
+.ti -8
+.B ip netns add
+.I NETNSNAME
+
+.ti -8
+.B ip netns attach
+.I NETNSNAME PID
+
+.ti -8
+.B ip [-all] netns del
+.RI "[ " NETNSNAME " ]"
+
+.ti -8
+.B ip netns set
+.I NETNSNAME NETNSID
+
+.ti -8
+.IR NETNSID " := " auto " | " POSITIVE-INT
+
+.ti -8
+.BR "ip netns identify"
+.RI "[ " PID " ]"
+
+.ti -8
+.BR "ip netns pids"
+.I NETNSNAME
+
+.ti -8
+.BR "ip [-all] netns exec "
+.RI "[ " NETNSNAME " ] " command ...
+
+.ti -8
+.BR "ip netns monitor"
+
+.ti -8
+.BR "ip netns list-id"
+.RI "[ target-nsid " POSITIVE-INT " ] [ nsid " POSITIVE-INT " ]"
+
+.SH DESCRIPTION
+A network namespace is logically another copy of the network stack,
+with its own routes, firewall rules, and network devices.
+
+By default a process inherits its network namespace from its parent. Initially all
+the processes share the same default network namespace from the init process.
+
+By convention a named network namespace is an object at
+.BR "/var/run/netns/" NAME
+that can be opened. The file descriptor resulting from opening
+.BR "/var/run/netns/" NAME
+refers to the specified network namespace. Holding that file
+descriptor open keeps the network namespace alive. The file
+descriptor can be used with the
+.B setns(2)
+system call to change the network namespace associated with a task.
+
+For applications that are aware of network namespaces, the convention
+is to look for global network configuration files first in
+.BR "/etc/netns/" NAME "/"
+then in
+.BR "/etc/".
+For example, if you want a different version of
+.BR /etc/resolv.conf
+for a network namespace used to isolate your vpn you would name it
+.BR /etc/netns/myvpn/resolv.conf.
+
+.B ip netns exec
+automates handling of this configuration, file convention for network
+namespace unaware applications, by creating a mount namespace and
+bind mounting all of the per network namespace configure files into
+their traditional location in /etc.
+
+.TP
+.B ip netns list - show all of the named network namespaces
+.sp
+This command displays all of the network namespaces in /var/run/netns
+
+.TP
+.B ip netns add NAME - create a new named network namespace
+.sp
+If NAME is available in /var/run/netns/ this command creates a new
+network namespace and assigns NAME.
+
+.TP
+.B ip netns attach NAME PID - create a new named network namespace
+.sp
+If NAME is available in /var/run/netns/ this command attaches the network
+namespace of the process PID to NAME as if it were created with ip netns.
+
+.TP
+.B ip [-all] netns delete [ NAME ] - delete the name of a network namespace(s)
+.sp
+If NAME is present in /var/run/netns it is umounted and the mount
+point is removed. If this is the last user of the network namespace the
+network namespace will be freed and all physical devices will be moved to the
+default one, otherwise the network namespace persists until it has no more
+users. ip netns delete may fail if the mount point is in use in another mount
+namespace.
+
+If
+.B -all
+option was specified then all the network namespace names will be removed.
+
+It is possible to lose the physical device when it was moved to netns and
+then this netns was deleted with a running process:
+
+.RS 10
+$ ip netns add net0
+.RE
+.RS 10
+$ ip link set dev eth0 netns net0
+.RE
+.RS 10
+$ ip netns exec net0 SOME_PROCESS_IN_BACKGROUND
+.RE
+.RS 10
+$ ip netns del net0
+.RE
+
+.RS
+and eth0 will appear in the default netns only after SOME_PROCESS_IN_BACKGROUND
+will exit or will be killed. To prevent this the processes running in net0
+should be killed before deleting the netns:
+
+.RE
+.RS 10
+$ ip netns pids net0 | xargs kill
+.RE
+.RS 10
+$ ip netns del net0
+.RE
+
+.TP
+.B ip netns set NAME NETNSID - assign an id to a peer network namespace
+.sp
+This command assigns a id to a peer network namespace. This id is valid
+only in the current network namespace.
+If the keyword "auto" is specified an available nsid will be chosen.
+This id will be used by the kernel in some netlink messages. If no id is
+assigned when the kernel needs it, it will be automatically assigned by
+the kernel.
+Once it is assigned, it's not possible to change it.
+
+.TP
+.B ip netns identify [PID] - Report network namespaces names for process
+.sp
+This command walks through /var/run/netns and finds all the network
+namespace names for network namespace of the specified process, if PID is
+not specified then the current process will be used.
+
+.TP
+.B ip netns pids NAME - Report processes in the named network namespace
+.sp
+This command walks through proc and finds all of the process who have
+the named network namespace as their primary network namespace.
+
+.TP
+.B ip [-all] netns exec [ NAME ] cmd ... - Run cmd in the named network namespace
+.sp
+This command allows applications that are network namespace unaware
+to be run in something other than the default network namespace with
+all of the configuration for the specified network namespace appearing
+in the customary global locations. A network namespace and bind mounts
+are used to move files from their network namespace specific location
+to their default locations without affecting other processes.
+
+If
+.B -all
+option was specified then
+.B cmd
+will be executed synchronously on the each named network namespace even if
+.B cmd
+fails on some of them. Network namespace name is printed on each
+.B cmd
+executing.
+
+.TP
+.B ip netns monitor - Report as network namespace names are added and deleted
+.sp
+This command watches network namespace name addition and deletion events
+and prints a line for each event it sees.
+
+.TP
+.B ip netns list-id [target-nsid POSITIVE-INT] [nsid POSITIVE-INT] - list network namespace ids (nsid)
+.sp
+Network namespace ids are used to identify a peer network namespace. This
+command displays nsids of the current network namespace and provides the
+corresponding iproute2 netns name (from /var/run/netns) if any.
+
+The
+.B target-nsid
+option enables to display nsids of the specified network namespace instead of the current network
+namespace. This
+.B target-nsid
+is a nsid from the current network namespace.
+
+The
+.B nsid
+option enables to display only this nsid. It is a nsid from the current network namespace. In
+combination with the
+.B target-nsid
+option, it enables to convert a specific nsid from the current network namespace to a nsid of the
+.B target-nsid
+network namespace.
+
+.SH EXAMPLES
+.PP
+ip netns list
+.RS
+Shows the list of current named network namespaces
+.RE
+.PP
+ip netns add vpn
+.RS
+Creates a network namespace and names it vpn
+.RE
+.PP
+ip netns exec vpn ip link set lo up
+.RS
+Bring up the loopback interface in the vpn network namespace.
+.RE
+.PP
+ip netns add foo
+.br
+ip netns add bar
+.br
+ip netns set foo 12
+.br
+ip netns set bar 13
+.br
+ip -n foo netns set foo 22
+.br
+ip -n foo netns set bar 23
+.br
+ip -n bar netns set foo 32
+.br
+ip -n bar netns set bar 33
+.br
+ip netns list-id target-nsid 12
+.RS
+Shows the list of nsids from the network namespace foo.
+.RE
+ip netns list-id target-nsid 12 nsid 13
+.RS
+Get nsid of bar from the network namespace foo (result is 23).
+.RE
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Eric W. Biederman
+.br
+Manpage revised by Nicolas Dichtel <nicolas.dichtel@6wind.com>
diff --git a/man/man8/ip-nexthop.8 b/man/man8/ip-nexthop.8
new file mode 100644
index 0000000..da87ca3
--- /dev/null
+++ b/man/man8/ip-nexthop.8
@@ -0,0 +1,196 @@
+.TH IP\-NEXTHOP 8 "30 May 2019" "iproute2" "Linux"
+.SH "NAME"
+ip-nexthop \- nexthop object management
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " ip-OPTIONS " ]"
+.B nexthop
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+.ti -8
+
+.ti -8
+.BR "ip nexthop" " { "
+.BR show " | " flush " } "
+.I SELECTOR
+
+.ti -8
+.BR "ip nexthop" " { " add " | " replace " } id "
+.I ID
+.IR NH
+
+.ti -8
+.BR "ip nexthop" " { " get " | " del " } id "
+.I ID
+
+.ti -8
+.IR SELECTOR " := "
+.RB "[ " id
+.IR ID " ] [ "
+.B dev
+.IR DEV " ] [ "
+.B vrf
+.IR NAME " ] [ "
+.B master
+.IR DEV " ] [ "
+.BR groups " ] "
+
+.ti -8
+.IR NH " := { "
+.BR blackhole " | [ "
+.B via
+.IR ADDRESS " ] [ "
+.B dev
+.IR DEV " ] [ "
+.BR onlink " ] [ "
+.B encap
+.IR ENCAP " ] | "
+.B group
+.IR GROUP " } "
+
+.ti -8
+.IR ENCAP " := [ "
+.IR ENCAP_MPLS " ] "
+
+.ti -8
+.IR ENCAP_MPLS " := "
+.BR mpls " [ "
+.IR LABEL " ] ["
+.B ttl
+.IR TTL " ]"
+
+.ti -8
+.IR GROUP " := "
+.BR id "[," weight "[/...]"
+
+.SH DESCRIPTION
+.B ip nexthop
+is used to manipulate entries in the kernel's nexthop tables.
+.TP
+ip nexthop add id ID
+add new nexthop entry
+.TP
+ip nexthop replace id ID
+change the configuration of a nexthop or add new one
+.RS
+.TP
+.BI via " [ FAMILY ] ADDRESS"
+the address of the nexthop router, in the address family FAMILY.
+Address family must match address family of nexthop instance.
+.TP
+.BI dev " NAME"
+is the output device.
+.TP
+.B onlink
+pretend that the nexthop is directly attached to this link,
+even if it does not match any interface prefix.
+.TP
+.BI encap " ENCAPTYPE ENCAPHDR"
+attach tunnel encapsulation attributes to this route.
+.sp
+.I ENCAPTYPE
+is a string specifying the supported encapsulation type. Namely:
+
+.in +8
+.BI mpls
+- encapsulation type MPLS
+.sp
+.in -8
+.I ENCAPHDR
+is a set of encapsulation attributes specific to the
+.I ENCAPTYPE.
+
+.in +8
+.B mpls
+.in +2
+.I MPLSLABEL
+- mpls label stack with labels separated by
+.I "/"
+.sp
+
+.B ttl
+.I TTL
+- TTL to use for MPLS header or 0 to inherit from IP header
+.in -2
+
+.TP
+.BI group " GROUP"
+create a nexthop group. Group specification is id with an optional
+weight (id,weight) and a '/' as a separator between entries.
+.TP
+.B blackhole
+create a blackhole nexthop
+.RE
+
+.TP
+ip nexthop delete id ID
+delete nexthop with given id.
+.RE
+
+.TP
+ip nexthop show
+show the contents of the nexthop table or the nexthops
+selected by some criteria.
+.RS
+.TP
+.BI dev " DEV "
+show the nexthops using the given device.
+.TP
+.BI vrf " NAME "
+show the nexthops using devices associated with the vrf name
+.TP
+.BI master " DEV "
+show the nexthops using devices enslaved to given master device
+.TP
+.BI groups
+show only nexthop groups
+.RE
+.TP
+ip nexthop flush
+flushes nexthops selected by some criteria. Criteria options are the same
+as show.
+.RE
+
+.TP
+ip nexthop get id ID
+get a single nexthop by id
+
+.SH EXAMPLES
+.PP
+ip nexthop ls
+.RS 4
+Show all nexthop entries in the kernel.
+.RE
+.PP
+ip nexthop add id 1 via 192.168.1.1 dev eth0
+.RS 4
+Adds an IPv4 nexthop with id 1 using the gateway 192.168.1.1 out device eth0.
+.RE
+.PP
+ip nexthop add id 2 encap mpls 200/300 via 10.1.1.1 dev eth0
+.RS 4
+Adds an IPv4 nexthop with mpls encapsulation attributes attached to it.
+.RE
+.PP
+ip nexthop add id 3 group 1/2
+.RS 4
+Adds a nexthop with id 3. The nexthop is a group using nexthops with ids
+1 and 2 at equal weight.
+.RE
+.PP
+ip nexthop add id 4 group 1,5/2,11
+.RS 4
+Adds a nexthop with id 4. The nexthop is a group using nexthops with ids
+1 and 2 with nexthop 1 at weight 5 and nexthop 2 at weight 11.
+.RE
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by David Ahern <dsahern@kernel.org>
diff --git a/man/man8/ip-ntable.8 b/man/man8/ip-ntable.8
new file mode 100644
index 0000000..4f0f2e5
--- /dev/null
+++ b/man/man8/ip-ntable.8
@@ -0,0 +1,106 @@
+.TH IP\-NTABLE 8 "20 Dec 2011" "iproute2" "Linux"
+.SH "NAME"
+ip-ntable - neighbour table configuration
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B ntable
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.BR "ip ntable change name"
+.IR NAME " [ "
+.B dev
+.IR DEV " ] ["
+.B thresh1
+.IR VAL " ] ["
+.B thresh2
+.IR VAL " ] ["
+.B thresh3
+.IR VAL " ] ["
+.B gc_int
+.IR MSEC " ] ["
+.B base_reachable
+.IR MSEC " ] ["
+.B retrans
+.IR MSEC " ] ["
+.B gc_stale
+.IR MSEC " ] ["
+.B delay_probe
+.IR MSEC " ] ["
+.B queue
+.IR LEN " ] ["
+.B app_probs
+.IR VAL " ] ["
+.B ucast_probes
+.IR VAL " ] ["
+.B mcast_probes
+.IR VAL " ] ["
+.B anycast_delay
+.IR MSEC " ] ["
+.B proxy_delay
+.IR MSEC " ] ["
+.B proxy_queue
+.IR LEN " ] ["
+.B locktime
+.IR MSEC " ]"
+
+.ti -8
+.BR "ip ntable show" " [ "
+.B dev
+.IR DEV " ] [ "
+.B name
+.IR NAME " ]"
+
+.SH DESCRIPTION
+.I ip ntable
+controls the parameters for the neighbour tables.
+
+.SS ip ntable show - list the ip neighbour tables
+
+This commands displays neighbour table parameters and statistics.
+
+.TP
+.BI dev " DEV"
+only list the table attached to this device.
+
+.TP
+.BI name " NAME"
+only lists the table with the given name.
+
+.SS ip ntable change - modify table parameter
+
+This command allows modifying table parameters such as timers and queue lengths.
+.TP
+.BI name " NAME"
+the name of the table to modify.
+
+.TP
+.BI dev " DEV"
+the name of the device to modify the table values.
+
+.SH EXAMPLES
+.PP
+ip ntable show dev eth0
+.RS 4
+Shows the neighbour table (IPv4 ARP and IPv6 ndisc) parameters on device eth0.
+.RE
+.PP
+ip ntable change name arp_cache queue 8 dev eth0
+.RS 4
+Changes the number of packets queued while address is being resolved from the
+default value (3) to 8 packets.
+.RE
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Manpage by Stephen Hemminger
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
new file mode 100644
index 0000000..34763cc
--- /dev/null
+++ b/man/man8/ip-route.8.in
@@ -0,0 +1,1177 @@
+.TH IP\-ROUTE 8 "13 Dec 2012" "iproute2" "Linux"
+.SH "NAME"
+ip-route \- routing table management
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " ip-OPTIONS " ]"
+.B route
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+.ti -8
+
+.ti -8
+.BR "ip route" " { "
+.BR show " | " flush " } "
+.I SELECTOR
+
+.ti -8
+.BR "ip route save"
+.I SELECTOR
+
+.ti -8
+.BR "ip route restore"
+
+.ti -8
+.B ip route get
+.I ROUTE_GET_FLAGS
+.IR ADDRESS " [ "
+.BI from " ADDRESS " iif " STRING"
+.RB " ] [ " oif
+.IR STRING " ] [ "
+.B mark
+.IR MARK " ] [ "
+.B tos
+.IR TOS " ] [ "
+.B vrf
+.IR NAME " ] [ "
+.B ipproto
+.IR PROTOCOL " ] [ "
+.B sport
+.IR NUMBER " ] [ "
+.B dport
+.IR NUMBER " ] "
+
+.ti -8
+.BR "ip route" " { " add " | " del " | " change " | " append " | "\
+replace " } "
+.I ROUTE
+
+.ti -8
+.IR SELECTOR " := "
+.RB "[ " root
+.IR PREFIX " ] [ "
+.B match
+.IR PREFIX " ] [ "
+.B exact
+.IR PREFIX " ] [ "
+.B table
+.IR TABLE_ID " ] [ "
+.B vrf
+.IR NAME " ] [ "
+.B proto
+.IR RTPROTO " ] [ "
+.B type
+.IR TYPE " ] [ "
+.B scope
+.IR SCOPE " ]"
+
+.ti -8
+.IR ROUTE " := " NODE_SPEC " [ " INFO_SPEC " ]"
+
+.ti -8
+.IR NODE_SPEC " := [ " TYPE " ] " PREFIX " ["
+.B tos
+.IR TOS " ] [ "
+.B table
+.IR TABLE_ID " ] [ "
+.B proto
+.IR RTPROTO " ] [ "
+.B scope
+.IR SCOPE " ] [ "
+.B metric
+.IR METRIC " ] [ "
+.B ttl-propagate
+.RB "{ " enabled " | " disabled " } ]"
+
+.ti -8
+.IR INFO_SPEC " := { " NH " | "
+.B nhid
+.IR ID " } " "OPTIONS FLAGS" " ["
+.B nexthop
+.IR NH " ] ..."
+
+.ti -8
+.IR NH " := [ "
+.B encap
+.IR ENCAP " ] [ "
+.B via
+[
+.IR FAMILY " ] " ADDRESS " ] [ "
+.B dev
+.IR STRING " ] [ "
+.B weight
+.IR NUMBER " ] " NHFLAGS
+
+.ti -8
+.IR FAMILY " := [ "
+.BR inet " | " inet6 " | " mpls " | " bridge " | " link " ]"
+
+.ti -8
+.IR OPTIONS " := " FLAGS " [ "
+.B mtu
+.IR NUMBER " ] [ "
+.B advmss
+.IR NUMBER " ] [ "
+.B as
+[
+.B to
+]
+.IR ADDRESS " ]"
+.B rtt
+.IR TIME " ] [ "
+.B rttvar
+.IR TIME " ] [ "
+.B reordering
+.IR NUMBER " ] [ "
+.B window
+.IR NUMBER " ] [ "
+.B cwnd
+.IR NUMBER " ] [ "
+.B ssthresh
+.IR NUMBER " ] [ "
+.B realms
+.IR REALM " ] [ "
+.B rto_min
+.IR TIME " ] [ "
+.B initcwnd
+.IR NUMBER " ] [ "
+.B initrwnd
+.IR NUMBER " ] [ "
+.B features
+.IR FEATURES " ] [ "
+.B quickack
+.IR BOOL " ] [ "
+.B congctl
+.IR NAME " ] [ "
+.B pref
+.IR PREF " ] [ "
+.B expires
+.IR TIME " ] ["
+.B fastopen_no_cookie
+.IR BOOL " ]"
+
+.ti -8
+.IR TYPE " := [ "
+.BR unicast " | " local " | " broadcast " | " multicast " | "\
+throw " | " unreachable " | " prohibit " | " blackhole " | " nat " ]"
+
+.ti -8
+.IR TABLE_ID " := [ "
+.BR local "| " main " | " default " | " all " |"
+.IR NUMBER " ]"
+
+.ti -8
+.IR SCOPE " := [ "
+.BR host " | " link " | " global " |"
+.IR NUMBER " ]"
+
+.ti -8
+.IR NHFLAGS " := [ "
+.BR onlink " | " pervasive " ]"
+
+.ti -8
+.IR RTPROTO " := [ "
+.BR kernel " | " boot " | " static " |"
+.IR NUMBER " ]"
+
+.ti -8
+.IR FEATURES " := [ "
+.BR ecn " | ]"
+
+.ti -8
+.IR PREF " := [ "
+.BR low " | " medium " | " high " ]"
+
+.ti -8
+.IR ENCAP " := [ "
+.IR ENCAP_MPLS " | " ENCAP_IP " | " ENCAP_BPF " | "
+.IR ENCAP_SEG6 " | " ENCAP_SEG6LOCAL " ] "
+
+.ti -8
+.IR ENCAP_MPLS " := "
+.BR mpls " [ "
+.IR LABEL " ] ["
+.B ttl
+.IR TTL " ]"
+
+.ti -8
+.IR ENCAP_IP " := "
+.B ip
+.B id
+.IR TUNNEL_ID
+.B dst
+.IR REMOTE_IP " [ "
+.B src
+.IR SRC " ] ["
+.B tos
+.IR TOS " ] ["
+.B ttl
+.IR TTL " ]"
+
+.ti -8
+.IR ENCAP_BPF " := "
+.BR bpf " [ "
+.B in
+.IR PROG " ] ["
+.B out
+.IR PROG " ] ["
+.B xmit
+.IR PROG " ] ["
+.B headroom
+.IR SIZE " ]"
+
+.ti -8
+.IR ENCAP_SEG6 " := "
+.B seg6
+.BR mode " [ "
+.BR encap " | " inline " | " l2encap " ] "
+.B segs
+.IR SEGMENTS " [ "
+.B hmac
+.IR KEYID " ]"
+
+.ti -8
+.IR ENCAP_SEG6LOCAL " := "
+.B seg6local
+.BR action
+.IR SEG6_ACTION " [ "
+.IR SEG6_ACTION_PARAM " ] "
+
+.ti -8
+.IR ROUTE_GET_FLAGS " := "
+.BR " [ "
+.BR fibmatch
+.BR " ] "
+
+.SH DESCRIPTION
+.B ip route
+is used to manipulate entries in the kernel routing tables.
+.sp
+.B Route types:
+
+.in +8
+.B unicast
+- the route entry describes real paths to the destinations covered
+by the route prefix.
+
+.sp
+.B unreachable
+- these destinations are unreachable. Packets are discarded and the
+ICMP message
+.I host unreachable
+is generated.
+The local senders get an
+.I EHOSTUNREACH
+error.
+
+.sp
+.B blackhole
+- these destinations are unreachable. Packets are discarded silently.
+The local senders get an
+.I EINVAL
+error.
+
+.sp
+.B prohibit
+- these destinations are unreachable. Packets are discarded and the
+ICMP message
+.I communication administratively prohibited
+is generated. The local senders get an
+.I EACCES
+error.
+
+.sp
+.B local
+- the destinations are assigned to this host. The packets are looped
+back and delivered locally.
+
+.sp
+.B broadcast
+- the destinations are broadcast addresses. The packets are sent as
+link broadcasts.
+
+.sp
+.B throw
+- a special control route used together with policy rules. If such a
+route is selected, lookup in this table is terminated pretending that
+no route was found. Without policy routing it is equivalent to the
+absence of the route in the routing table. The packets are dropped
+and the ICMP message
+.I net unreachable
+is generated. The local senders get an
+.I ENETUNREACH
+error.
+
+.sp
+.B nat
+- a special NAT route. Destinations covered by the prefix
+are considered to be dummy (or external) addresses which require translation
+to real (or internal) ones before forwarding. The addresses to translate to
+are selected with the attribute
+.BR "via" .
+.B Warning:
+Route NAT is no longer supported in Linux 2.6.
+
+.sp
+.B anycast
+.RI "- " "not implemented"
+the destinations are
+.I anycast
+addresses assigned to this host. They are mainly equivalent
+to
+.B local
+with one difference: such addresses are invalid when used
+as the source address of any packet.
+
+.sp
+.B multicast
+- a special type used for multicast routing. It is not present in
+normal routing tables.
+.in -8
+
+.P
+.B Route tables:
+Linux-2.x can pack routes into several routing tables identified
+by a number in the range from 1 to 2^32-1 or by name from the file
+.B @SYSCONFDIR@/rt_tables
+By default all normal routes are inserted into the
+.B main
+table (ID 254) and the kernel only uses this table when calculating routes.
+Values (0, 253, 254, and 255) are reserved for built-in use.
+
+.sp
+Actually, one other table always exists, which is invisible but
+even more important. It is the
+.B local
+table (ID 255). This table
+consists of routes for local and broadcast addresses. The kernel maintains
+this table automatically and the administrator usually need not modify it
+or even look at it.
+
+The multiple routing tables enter the game when
+.I policy routing
+is used.
+
+.TP
+ip route add
+add new route
+.TP
+ip route change
+change route
+.TP
+ip route replace
+change or add new one
+.RS
+.TP
+.BI to " TYPE PREFIX " (default)
+the destination prefix of the route. If
+.I TYPE
+is omitted,
+.B ip
+assumes type
+.BR "unicast" .
+Other values of
+.I TYPE
+are listed above.
+.I PREFIX
+is an IP or IPv6 address optionally followed by a slash and the
+prefix length. If the length of the prefix is missing,
+.B ip
+assumes a full-length host route. There is also a special
+.I PREFIX
+.B default
+- which is equivalent to IP
+.B 0/0
+or to IPv6
+.BR "::/0" .
+
+.TP
+.BI tos " TOS"
+.TP
+.BI dsfield " TOS"
+the Type Of Service (TOS) key. This key has no associated mask and
+the longest match is understood as: First, compare the TOS
+of the route and of the packet. If they are not equal, then the packet
+may still match a route with a zero TOS.
+.I TOS
+is either an 8 bit hexadecimal number or an identifier
+from
+.BR "@SYSCONFDIR@/rt_dsfield" .
+
+.TP
+.BI metric " NUMBER"
+.TP
+.BI preference " NUMBER"
+the preference value of the route.
+.I NUMBER
+is an arbitrary 32bit number, where routes with lower values are preferred.
+
+.TP
+.BI table " TABLEID"
+the table to add this route to.
+.I TABLEID
+may be a number or a string from the file
+.BR "@SYSCONFDIR@/rt_tables" .
+If this parameter is omitted,
+.B ip
+assumes the
+.B main
+table, with the exception of
+.BR local ", " broadcast " and " nat
+routes, which are put into the
+.B local
+table by default.
+
+.TP
+.BI vrf " NAME"
+the vrf name to add this route to. Implicitly means the table
+associated with the VRF.
+
+.TP
+.BI dev " NAME"
+the output device name.
+
+.TP
+.BI via " [ FAMILY ] ADDRESS"
+the address of the nexthop router, in the address family FAMILY.
+Actually, the sense of this field depends on the route type. For
+normal
+.B unicast
+routes it is either the true next hop router or, if it is a direct
+route installed in BSD compatibility mode, it can be a local address
+of the interface. For NAT routes it is the first address of the block
+of translated IP destinations.
+
+.TP
+.BI src " ADDRESS"
+the source address to prefer when sending to the destinations
+covered by the route prefix.
+
+.TP
+.BI realm " REALMID"
+the realm to which this route is assigned.
+.I REALMID
+may be a number or a string from the file
+.BR "@SYSCONFDIR@/rt_realms" .
+
+.TP
+.BI mtu " MTU"
+.TP
+.BI "mtu lock" " MTU"
+the MTU along the path to the destination. If the modifier
+.B lock
+is not used, the MTU may be updated by the kernel due to
+Path MTU Discovery. If the modifier
+.B lock
+is used, no path MTU discovery will be tried, all packets
+will be sent without the DF bit in IPv4 case or fragmented
+to MTU for IPv6.
+
+.TP
+.BI window " NUMBER"
+the maximal window for TCP to advertise to these destinations,
+measured in bytes. It limits maximal data bursts that our TCP
+peers are allowed to send to us.
+
+.TP
+.BI rtt " TIME"
+the initial RTT ('Round Trip Time') estimate. If no suffix is
+specified the units are raw values passed directly to the
+routing code to maintain compatibility with previous releases.
+Otherwise if a suffix of s, sec or secs is used to specify
+seconds and ms, msec or msecs to specify milliseconds.
+
+
+.TP
+.BI rttvar " TIME " "(Linux 2.3.15+ only)"
+the initial RTT variance estimate. Values are specified as with
+.BI rtt
+above.
+
+.TP
+.BI rto_min " TIME " "(Linux 2.6.23+ only)"
+the minimum TCP Retransmission TimeOut to use when communicating with this
+destination. Values are specified as with
+.BI rtt
+above.
+
+.TP
+.BI ssthresh " NUMBER " "(Linux 2.3.15+ only)"
+an estimate for the initial slow start threshold.
+
+.TP
+.BI cwnd " NUMBER " "(Linux 2.3.15+ only)"
+the clamp for congestion window. It is ignored if the
+.B lock
+flag is not used.
+
+.TP
+.BI initcwnd " NUMBER " "(Linux 2.5.70+ only)"
+the initial congestion window size for connections to this destination.
+Actual window size is this value multiplied by the MSS
+(``Maximal Segment Size'') for same connection. The default is
+zero, meaning to use the values specified in RFC2414.
+
+.TP
+.BI initrwnd " NUMBER " "(Linux 2.6.33+ only)"
+the initial receive window size for connections to this destination.
+Actual window size is this value multiplied by the MSS of the connection.
+The default value is zero, meaning to use Slow Start value.
+
+.TP
+.BI features " FEATURES " (Linux 3.18+ only)
+Enable or disable per-route features. Only available feature at this
+time is
+.B ecn
+to enable explicit congestion notification when initiating connections to the
+given destination network.
+When responding to a connection request from the given network, ecn will
+also be used even if the
+.B net.ipv4.tcp_ecn
+sysctl is set to 0.
+
+.TP
+.BI quickack " BOOL " "(Linux 3.11+ only)"
+Enable or disable quick ack for connections to this destination.
+
+.TP
+.BI fastopen_no_cookie " BOOL " "(Linux 4.15+ only)"
+Enable TCP Fastopen without a cookie for connections to this destination.
+
+.TP
+.BI congctl " NAME " "(Linux 3.20+ only)"
+.TP
+.BI "congctl lock" " NAME " "(Linux 3.20+ only)"
+Sets a specific TCP congestion control algorithm only for a given destination.
+If not specified, Linux keeps the current global default TCP congestion control
+algorithm, or the one set from the application. If the modifier
+.B lock
+is not used, an application may nevertheless overwrite the suggested congestion
+control algorithm for that destination. If the modifier
+.B lock
+is used, then an application is not allowed to overwrite the specified congestion
+control algorithm for that destination, thus it will be enforced/guaranteed to
+use the proposed algorithm.
+
+.TP
+.BI advmss " NUMBER " "(Linux 2.3.15+ only)"
+the MSS ('Maximal Segment Size') to advertise to these
+destinations when establishing TCP connections. If it is not given,
+Linux uses a default value calculated from the first hop device MTU.
+(If the path to these destination is asymmetric, this guess may be wrong.)
+
+.TP
+.BI reordering " NUMBER " "(Linux 2.3.15+ only)"
+Maximal reordering on the path to this destination.
+If it is not given, Linux uses the value selected with
+.B sysctl
+variable
+.BR "net/ipv4/tcp_reordering" .
+
+.TP
+.BI nexthop " NEXTHOP"
+the nexthop of a multipath route.
+.I NEXTHOP
+is a complex value with its own syntax similar to the top level
+argument lists:
+
+.in +8
+.BI via " [ FAMILY ] ADDRESS"
+- is the nexthop router.
+.sp
+
+.BI dev " NAME"
+- is the output device.
+.sp
+
+.BI weight " NUMBER"
+- is a weight for this element of a multipath
+route reflecting its relative bandwidth or quality.
+.in -8
+
+The internal buffer used in iproute2 limits the maximum number of nexthops that
+may be specified in one go. If only
+.I ADDRESS
+is given, the current buffer size allows for 144 IPv6 nexthops and 253 IPv4
+ones. For IPv4, this effectively limits the number of nexthops possible per
+route. With IPv6, further nexthops may be appended to the same route via
+.B "ip route append"
+command.
+
+.TP
+.BI scope " SCOPE_VAL"
+the scope of the destinations covered by the route prefix.
+.I SCOPE_VAL
+may be a number or a string from the file
+.BR "@SYSCONFDIR@/rt_scopes" .
+If this parameter is omitted,
+.B ip
+assumes scope
+.B global
+for all gatewayed
+.B unicast
+routes, scope
+.B link
+for direct
+.BR unicast " and " broadcast
+routes and scope
+.BR host " for " local
+routes.
+
+.TP
+.BI protocol " RTPROTO"
+the routing protocol identifier of this route.
+.I RTPROTO
+may be a number or a string from the file
+.BR "@SYSCONFDIR@/rt_protos" .
+If the routing protocol ID is not given,
+.B ip assumes protocol
+.B boot
+(i.e. it assumes the route was added by someone who doesn't
+understand what they are doing). Several protocol values have
+a fixed interpretation.
+Namely:
+
+.in +8
+.B redirect
+- the route was installed due to an ICMP redirect.
+.sp
+
+.B kernel
+- the route was installed by the kernel during autoconfiguration.
+.sp
+
+.B boot
+- the route was installed during the bootup sequence.
+If a routing daemon starts, it will purge all of them.
+.sp
+
+.B static
+- the route was installed by the administrator
+to override dynamic routing. Routing daemon will respect them
+and, probably, even advertise them to its peers.
+.sp
+
+.B ra
+- the route was installed by Router Discovery protocol.
+.in -8
+
+.sp
+The rest of the values are not reserved and the administrator is free
+to assign (or not to assign) protocol tags.
+
+.TP
+.B onlink
+pretend that the nexthop is directly attached to this link,
+even if it does not match any interface prefix.
+
+.TP
+.BI pref " PREF"
+the IPv6 route preference.
+.I PREF
+is a string specifying the route preference as defined in RFC4191 for Router
+Discovery messages. Namely:
+
+.in +8
+.B low
+- the route has a lowest priority
+.sp
+
+.B medium
+- the route has a default priority
+.sp
+
+.B high
+- the route has a highest priority
+.sp
+
+.TP
+.BI nhid " ID"
+use nexthop object with given id as nexthop specification.
+.sp
+.TP
+.BI encap " ENCAPTYPE ENCAPHDR"
+attach tunnel encapsulation attributes to this route.
+.sp
+.I ENCAPTYPE
+is a string specifying the supported encapsulation type. Namely:
+
+.in +8
+.BI mpls
+- encapsulation type MPLS
+.sp
+.BI ip
+- IP encapsulation (Geneve, GRE, VXLAN, ...)
+.sp
+.BI bpf
+- Execution of BPF program
+.sp
+.BI seg6
+- encapsulation type IPv6 Segment Routing
+.sp
+.BI seg6local
+- local SRv6 segment processing
+
+.in -8
+.I ENCAPHDR
+is a set of encapsulation attributes specific to the
+.I ENCAPTYPE.
+
+.in +8
+.B mpls
+.in +2
+.I MPLSLABEL
+- mpls label stack with labels separated by
+.I "/"
+.sp
+
+.B ttl
+.I TTL
+- TTL to use for MPLS header or 0 to inherit from IP header
+.in -2
+.sp
+
+.B ip
+.in +2
+.B id
+.I TUNNEL_ID
+.B dst
+.IR REMOTE_IP " [ "
+.B src
+.IR SRC " ] ["
+.B tos
+.IR TOS " ] ["
+.B ttl
+.IR TTL " ] [ "
+.BR key " ] [ " csum " ] [ " seq " ] "
+.in -2
+.sp
+
+.B bpf
+.in +2
+.B in
+.I PROG
+- BPF program to execute for incoming packets
+.sp
+
+.B out
+.I PROG
+- BPF program to execute for outgoing packets
+.sp
+
+.B xmit
+.I PROG
+- BPF program to execute for transmitted packets
+.sp
+
+.B headroom
+.I SIZE
+- Size of header BPF program will attach (xmit)
+.in -2
+.sp
+
+.B seg6
+.in +2
+.B mode inline
+- Directly insert Segment Routing Header after IPv6 header
+.sp
+
+.B mode encap
+- Encapsulate packet in an outer IPv6 header with SRH
+.sp
+
+.B mode l2encap
+- Encapsulate ingress L2 frame within an outer IPv6 header and SRH
+.sp
+
+.I SEGMENTS
+- List of comma-separated IPv6 addresses
+.sp
+
+.I KEYID
+- Numerical value in decimal representation. See \fBip-sr\fR(8).
+.in -2
+.sp
+
+.B seg6local
+.in +2
+.IR SEG6_ACTION " [ "
+.IR SEG6_ACTION_PARAM " ] "
+- Operation to perform on matching packets.
+The following actions are currently supported (\fBLinux 4.14+ only\fR).
+.in +2
+
+.B End
+- Regular SRv6 processing as intermediate segment endpoint.
+This action only accepts packets with a non-zero Segments Left
+value. Other matching packets are dropped.
+
+.B End.X nh6
+.I NEXTHOP
+- Regular SRv6 processing as intermediate segment endpoint.
+Additionally, forward processed packets to given next-hop.
+This action only accepts packets with a non-zero Segments Left
+value. Other matching packets are dropped.
+
+.B End.DX6 nh6
+.I NEXTHOP
+- Decapsulate inner IPv6 packet and forward it to the
+specified next-hop. If the argument is set to ::, then
+the next-hop is selected according to the local selection
+rules. This action only accepts packets with either a zero Segments
+Left value or no SRH at all, and an inner IPv6 packet. Other
+matching packets are dropped.
+
+.B End.B6 srh segs
+.IR SEGMENTS " [ "
+.B hmac
+.IR KEYID " ] "
+- Insert the specified SRH immediately after the IPv6 header,
+update the DA with the first segment of the newly inserted SRH,
+then forward the resulting packet. The original SRH is not
+modified. This action only accepts packets with a non-zero
+Segments Left value. Other matching packets are dropped.
+
+.B End.B6.Encaps srh segs
+.IR SEGMENTS " [ "
+.B hmac
+.IR KEYID " ] "
+- Regular SRv6 processing as intermediate segment endpoint.
+Additionally, encapsulate the matching packet within an outer IPv6 header
+followed by the specified SRH. The destination address of the outer IPv6
+header is set to the first segment of the new SRH. The source
+address is set as described in \fBip-sr\fR(8).
+.in -4
+
+.in -8
+
+.TP
+.BI expires " TIME " "(Linux 4.4+ only)"
+the route will be deleted after the expires time.
+.B Only
+support IPv6 at present.
+
+.TP
+.BR ttl-propagate " { " enabled " | " disabled " } "
+Control whether TTL should be propagated from any encap into the
+un-encapsulated packet, overriding any global configuration. Only
+supported for MPLS at present.
+.RE
+
+.TP
+ip route delete
+delete route
+.RS
+.B ip route del
+has the same arguments as
+.BR "ip route add" ,
+but their semantics are a bit different.
+
+Key values
+.RB "(" to ", " tos ", " preference " and " table ")"
+select the route to delete. If optional attributes are present,
+.B ip
+verifies that they coincide with the attributes of the route to delete.
+If no route with the given key and attributes was found,
+.B ip route del
+fails.
+.RE
+
+.TP
+ip route show
+list routes
+.RS
+the command displays the contents of the routing tables or the route(s)
+selected by some criteria.
+
+.TP
+.BI to " SELECTOR " (default)
+only select routes from the given range of destinations.
+.I SELECTOR
+consists of an optional modifier
+.RB "(" root ", " match " or " exact ")"
+and a prefix.
+.BI root " PREFIX"
+selects routes with prefixes not shorter than
+.IR PREFIX "."
+F.e.
+.BI root " 0/0"
+selects the entire routing table.
+.BI match " PREFIX"
+selects routes with prefixes not longer than
+.IR PREFIX "."
+F.e.
+.BI match " 10.0/16"
+selects
+.IR 10.0/16 ","
+.IR 10/8 " and " 0/0 ,
+but it does not select
+.IR 10.1/16 " and " 10.0.0/24 .
+And
+.BI exact " PREFIX"
+(or just
+.IR PREFIX ")"
+selects routes with this exact prefix. If neither of these options
+are present,
+.B ip
+assumes
+.BI root " 0/0"
+i.e. it lists the entire table.
+
+.TP
+.BI tos " TOS"
+.TP
+.BI dsfield " TOS"
+only select routes with the given TOS.
+
+.TP
+.BI table " TABLEID"
+show the routes from this table(s). The default setting is to show table
+.BR main "."
+.I TABLEID
+may either be the ID of a real table or one of the special values:
+.sp
+.in +8
+.B all
+- list all of the tables.
+.sp
+.B cache
+- dump the routing cache.
+.in -8
+
+.TP
+.BI vrf " NAME"
+show the routes for the table associated with the vrf name
+
+.TP
+.B cloned
+.TP
+.B cached
+list cloned routes i.e. routes which were dynamically forked from
+other routes because some route attribute (f.e. MTU) was updated.
+Actually, it is equivalent to
+.BR "table cache" "."
+
+.TP
+.BI from " SELECTOR"
+the same syntax as for
+.BR to ","
+but it binds the source address range rather than destinations.
+Note that the
+.B from
+option only works with cloned routes.
+
+.TP
+.BI protocol " RTPROTO"
+only list routes of this protocol.
+
+.TP
+.BI scope " SCOPE_VAL"
+only list routes with this scope.
+
+.TP
+.BI type " TYPE"
+only list routes of this type.
+
+.TP
+.BI dev " NAME"
+only list routes going via this device.
+
+.TP
+.BI via " [ FAMILY ] PREFIX"
+only list routes going via the nexthop routers selected by
+.IR PREFIX "."
+
+.TP
+.BI src " PREFIX"
+only list routes with preferred source addresses selected
+by
+.IR PREFIX "."
+
+.TP
+.BI realm " REALMID"
+.TP
+.BI realms " FROMREALM/TOREALM"
+only list routes with these realms.
+.RE
+
+.TP
+ip route flush
+flush routing tables
+.RS
+this command flushes routes selected by some criteria.
+
+.sp
+The arguments have the same syntax and semantics as the arguments of
+.BR "ip route show" ,
+but routing tables are not listed but purged. The only difference is
+the default action:
+.B show
+dumps all the IP main routing table but
+.B flush
+prints the helper page.
+
+.sp
+With the
+.B -statistics
+option, the command becomes verbose. It prints out the number of
+deleted routes and the number of rounds made to flush the routing
+table. If the option is given
+twice,
+.B ip route flush
+also dumps all the deleted routes in the format described in the
+previous subsection.
+.RE
+
+.TP
+ip route get
+get a single route
+.RS
+this command gets a single route to a destination and prints its
+contents exactly as the kernel sees it.
+
+.TP
+.BI fibmatch
+Return full fib lookup matched route. Default is to return the resolved
+dst entry
+
+.TP
+.BI to " ADDRESS " (default)
+the destination address.
+
+.TP
+.BI from " ADDRESS"
+the source address.
+
+.TP
+.BI tos " TOS"
+.TP
+.BI dsfield " TOS"
+the Type Of Service.
+
+.TP
+.BI iif " NAME"
+the device from which this packet is expected to arrive.
+
+.TP
+.BI oif " NAME"
+force the output device on which this packet will be routed.
+
+.TP
+.BI mark " MARK"
+the firewall mark
+.RB ( "fwmark" )
+
+.TP
+.BI vrf " NAME"
+force the vrf device on which this packet will be routed.
+
+.TP
+.BI ipproto " PROTOCOL"
+ip protocol as seen by the route lookup
+
+.TP
+.BI sport " NUMBER"
+source port as seen by the route lookup
+
+.TP
+.BI dport " NUMBER"
+destination port as seen by the route lookup
+
+.TP
+.B connected
+if no source address
+.RB "(option " from ")"
+was given, relookup the route with the source set to the preferred
+address received from the first lookup.
+If policy routing is used, it may be a different route.
+
+.P
+Note that this operation is not equivalent to
+.BR "ip route show" .
+.B show
+shows existing routes.
+.B get
+resolves them and creates new clones if necessary. Essentially,
+.B get
+is equivalent to sending a packet along this path.
+If the
+.B iif
+argument is not given, the kernel creates a route
+to output packets towards the requested destination.
+This is equivalent to pinging the destination
+with a subsequent
+.BR "ip route ls cache" ,
+however, no packets are actually sent. With the
+.B iif
+argument, the kernel pretends that a packet arrived from this interface
+and searches for a path to forward the packet.
+.RE
+
+.TP
+ip route save
+save routing table information to stdout
+.RS
+This command behaves like
+.BR "ip route show"
+except that the output is raw data suitable for passing to
+.BR "ip route restore" .
+.RE
+
+.TP
+ip route restore
+restore routing table information from stdin
+.RS
+This command expects to read a data stream as returned from
+.BR "ip route save" .
+It will attempt to restore the routing table information exactly as
+it was at the time of the save, so any translation of information
+in the stream (such as device indexes) must be done first. Any existing
+routes are left unchanged. Any routes specified in the data stream that
+already exist in the table will be ignored.
+.RE
+
+.SH NOTES
+Starting with Linux kernel version 3.6, there is no routing cache for IPv4
+anymore. Hence
+.B "ip route show cached"
+will never print any entries on systems with this or newer kernel versions.
+
+.SH EXAMPLES
+.PP
+ip ro
+.RS 4
+Show all route entries in the kernel.
+.RE
+.PP
+ip route add default via 192.168.1.1 dev eth0
+.RS 4
+Adds a default route (for all addresses) via the local gateway 192.168.1.1 that can
+be reached on device eth0.
+.RE
+.PP
+ip route add 10.1.1.0/30 encap mpls 200/300 via 10.1.1.1 dev eth0
+.RS 4
+Adds an ipv4 route with mpls encapsulation attributes attached to it.
+.RE
+.PP
+ip -6 route add 2001:db8:1::/64 encap seg6 mode encap segs 2001:db8:42::1,2001:db8:ffff::2 dev eth0
+.RS 4
+Adds an IPv6 route with SRv6 encapsulation and two segments attached.
+.RE
+.PP
+ip route add 10.1.1.0/30 nhid 10
+.RS 4
+Adds an ipv4 route using nexthop object with id 10.
+.RE
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-rule.8 b/man/man8/ip-rule.8
new file mode 100644
index 0000000..2c12bf6
--- /dev/null
+++ b/man/man8/ip-rule.8
@@ -0,0 +1,358 @@
+.TH IP\-RULE 8 "20 Dec 2011" "iproute2" "Linux"
+.SH "NAME"
+ip-rule \- routing policy database management
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B rule
+.RI "{ " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.B ip rule
+.RB "[ " list
+.RI "[ " SELECTOR " ]]"
+
+.ti -8
+.B ip rule
+.RB "{ " add " | " del " }"
+.I SELECTOR ACTION
+
+.ti -8
+.B ip rule
+.RB "{ " flush " | " save " | " restore " }"
+
+.ti -8
+.IR SELECTOR " := [ "
+.BR not " ] ["
+.B from
+.IR PREFIX " ] [ "
+.B to
+.IR PREFIX " ] [ "
+.B tos
+.IR TOS " ] [ "
+.B fwmark
+.IR FWMARK\fR[\fB/\fIMASK "] ] [ "
+.B iif
+.IR STRING " ] [ "
+.B oif
+.IR STRING " ] [ "
+.B pref
+.IR NUMBER " ] [ "
+.IR l3mdev " ] [ "
+.B uidrange
+.IR NUMBER "-" NUMBER " ] [ "
+.B ipproto
+.IR PROTOCOL " ] [ "
+.BR sport " [ "
+.IR NUMBER " | "
+.IR NUMBER "-" NUMBER " ] ] [ "
+.BR dport " [ "
+.IR NUMBER " | "
+.IR NUMBER "-" NUMBER " ] ] [ "
+.B tun_id
+.IR TUN_ID " ]"
+.BR
+
+
+.ti -8
+.IR ACTION " := [ "
+.B table
+.IR TABLE_ID " ] [ "
+.B protocol
+.IR PROTO " ] [ "
+.B nat
+.IR ADDRESS " ] [ "
+.B realms
+.RI "[" SRCREALM "\fB/\fR]" DSTREALM " ] ["
+.B goto
+.IR NUMBER " ] " SUPPRESSOR
+
+.ti -8
+.IR SUPPRESSOR " := [ "
+.B suppress_prefixlength
+.IR NUMBER " ] [ "
+.B suppress_ifgroup
+.IR GROUP " ]"
+
+.ti -8
+.IR TABLE_ID " := [ "
+.BR local " | " main " | " default " |"
+.IR NUMBER " ]"
+
+.SH DESCRIPTION
+.I ip rule
+manipulates rules
+in the routing policy database control the route selection algorithm.
+
+.P
+Classic routing algorithms used in the Internet make routing decisions
+based only on the destination address of packets (and in theory,
+but not in practice, on the TOS field).
+
+.P
+In some circumstances we want to route packets differently depending not only
+on destination addresses, but also on other packet fields: source address,
+IP protocol, transport protocol ports or even packet payload.
+This task is called 'policy routing'.
+
+.P
+To solve this task, the conventional destination based routing table, ordered
+according to the longest match rule, is replaced with a 'routing policy
+database' (or RPDB), which selects routes by executing some set of rules.
+
+.P
+Each policy routing rule consists of a
+.B selector
+and an
+.B action predicate.
+The RPDB is scanned in order of decreasing priority (note that lower number
+means higher priority, see the description of
+.I PREFERENCE
+below). The selector
+of each rule is applied to {source address, destination address, incoming
+interface, tos, fwmark} and, if the selector matches the packet,
+the action is performed. The action predicate may return with success.
+In this case, it will either give a route or failure indication
+and the RPDB lookup is terminated. Otherwise, the RPDB program
+continues with the next rule.
+
+.P
+Semantically, the natural action is to select the nexthop and the output device.
+
+.P
+At startup time the kernel configures the default RPDB consisting of three
+rules:
+
+.TP
+1.
+Priority: 0, Selector: match anything, Action: lookup routing
+table
+.B local
+(ID 255).
+The
+.B local
+table is a special routing table containing
+high priority control routes for local and broadcast addresses.
+
+.TP
+2.
+Priority: 32766, Selector: match anything, Action: lookup routing
+table
+.B main
+(ID 254).
+The
+.B main
+table is the normal routing table containing all non-policy
+routes. This rule may be deleted and/or overridden with other
+ones by the administrator.
+
+.TP
+3.
+Priority: 32767, Selector: match anything, Action: lookup routing
+table
+.B default
+(ID 253).
+The
+.B default
+table is empty. It is reserved for some post-processing if no previous
+default rules selected the packet.
+This rule may also be deleted.
+
+.P
+Each RPDB entry has additional
+attributes. F.e. each rule has a pointer to some routing
+table. NAT and masquerading rules have an attribute to select new IP
+address to translate/masquerade. Besides that, rules have some
+optional attributes, which routes have, namely
+.BR "realms" .
+These values do not override those contained in the routing tables. They
+are only used if the route did not select any attributes.
+
+.sp
+The RPDB may contain rules of the following types:
+
+.RS
+.B unicast
+- the rule prescribes to return the route found
+in the routing table referenced by the rule.
+
+.B blackhole
+- the rule prescribes to silently drop the packet.
+
+.B unreachable
+- the rule prescribes to generate a 'Network is unreachable' error.
+
+.B prohibit
+- the rule prescribes to generate 'Communication is administratively
+prohibited' error.
+
+.B nat
+- the rule prescribes to translate the source address
+of the IP packet into some other value.
+.RE
+
+.TP
+.B ip rule add - insert a new rule
+.TP
+.B ip rule delete - delete a rule
+.RS
+.TP
+.BI type " TYPE " (default)
+the type of this rule. The list of valid types was given in the previous
+subsection.
+
+.TP
+.BI from " PREFIX"
+select the source prefix to match.
+
+.TP
+.BI to " PREFIX"
+select the destination prefix to match.
+
+.TP
+.BI iif " NAME"
+select the incoming device to match. If the interface is loopback,
+the rule only matches packets originating from this host. This means
+that you may create separate routing tables for forwarded and local
+packets and, hence, completely segregate them.
+
+.TP
+.BI oif " NAME"
+select the outgoing device to match. The outgoing interface is only
+available for packets originating from local sockets that are bound to
+a device.
+
+.TP
+.BI tos " TOS"
+.TP
+.BI dsfield " TOS"
+select the TOS value to match.
+
+.TP
+.BI fwmark " MARK"
+select the
+.B fwmark
+value to match.
+
+.TP
+.BI uidrange " NUMBER-NUMBER"
+select the
+.B uid
+value to match.
+
+.TP
+.BI ipproto " PROTOCOL"
+select the ip protocol value to match.
+
+.TP
+.BI sport " NUMBER | NUMBER-NUMBER"
+select the source port value to match. supports port range.
+
+.TP
+.BI dport " NUMBER | NUMBER-NUMBER"
+select the destination port value to match. supports port range.
+
+.TP
+.BI priority " PREFERENCE"
+the priority of this rule.
+.I PREFERENCE
+is an unsigned integer value, higher number means lower priority, and rules get
+processed in order of increasing number. Each rule
+should have an explicitly set
+.I unique
+priority value.
+The options preference and order are synonyms with priority.
+
+.TP
+.BI table " TABLEID"
+the routing table identifier to lookup if the rule selector matches.
+It is also possible to use lookup instead of table.
+
+.TP
+.BI protocol " PROTO"
+the routing protocol who installed the rule in question. As an example when zebra installs a rule it would get RTPROT_ZEBRA as the installing protocol.
+
+.TP
+.BI suppress_prefixlength " NUMBER"
+reject routing decisions that have a prefix length of NUMBER or less.
+
+.TP
+.BI suppress_ifgroup " GROUP"
+reject routing decisions that use a device belonging to the interface
+group GROUP.
+
+.TP
+.BI realms " FROM/TO"
+Realms to select if the rule matched and the routing table lookup
+succeeded. Realm
+.I TO
+is only used if the route did not select any realm.
+
+.TP
+.BI nat " ADDRESS"
+The base of the IP address block to translate (for source addresses).
+The
+.I ADDRESS
+may be either the start of the block of NAT addresses (selected by NAT
+routes) or a local host address (or even zero).
+In the last case the router does not translate the packets, but
+masquerades them to this address.
+Using map-to instead of nat means the same thing.
+
+.B Warning:
+Changes to the RPDB made with these commands do not become active
+immediately. It is assumed that after a script finishes a batch of
+updates, it flushes the routing cache with
+.BR "ip route flush cache" .
+.RE
+.TP
+.B ip rule flush - also dumps all the deleted rules.
+.RS
+.TP
+.BI protocol " PROTO"
+Select the originating protocol.
+.RE
+.TP
+.B ip rule show - list rules
+This command has no arguments.
+The options list or lst are synonyms with show.
+
+.TP
+.B ip rule save
+.RS
+.TP
+.BI protocol " PROTO"
+Select the originating protocol.
+.RE
+.TP
+save rules table information to stdout
+.RS
+This command behaves like
+.BR "ip rule show"
+except that the output is raw data suitable for passing to
+.BR "ip rule restore" .
+.RE
+
+.TP
+.B ip rule restore
+restore rules table information from stdin
+.RS
+This command expects to read a data stream as returned from
+.BR "ip rule save" .
+It will attempt to restore the rules table information exactly as
+it was at the time of the save. Any rules already in the table are
+left unchanged, and duplicates are not ignored.
+.RE
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-sr.8 b/man/man8/ip-sr.8
new file mode 100644
index 0000000..6be1cc5
--- /dev/null
+++ b/man/man8/ip-sr.8
@@ -0,0 +1,58 @@
+.TH IP\-SR 8 "14 Apr 2017" "iproute2" "Linux"
+.SH "NAME"
+ip-sr \- IPv6 Segment Routing management
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip sr
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+.ti -8
+
+.ti -8
+.B ip sr hmac show
+
+.ti -8
+.B ip sr hmac set
+.I KEYID ALGO
+
+.ti -8
+.B ip sr tunsrc show
+
+.ti -8
+.B ip sr tunsrc set
+.I ADDRESS
+
+.SH DESCRIPTION
+The \fBip sr\fR command is used to configure IPv6 Segment Routing (SRv6)
+internal parameters.
+.PP
+Those parameters include the mapping between an HMAC key ID and its associated
+hashing algorithm and secret, and the IPv6 address to use as source for encapsulated
+packets.
+.PP
+The \fBip sr hmac set\fR command prompts for a passphrase that will be used as the
+HMAC secret for the corresponding key ID. A blank passphrase removes the mapping.
+The currently supported algorithms for \fIALGO\fR are \fBsha1\fR and \fBsha256\fR.
+.PP
+If the tunnel source is set to the address :: (which is the default), then an address
+of the egress interface will be selected. As this operation may hinder performances,
+it is recommended to set a non-default address.
+
+.SH EXAMPLES
+.PP
+.SS Configure an HMAC mapping for key ID 42 and hashing algorithm SHA-256
+.nf
+# ip sr hmac set 42 sha256
+.PP
+.SS Set the tunnel source address to 2001:db8::1
+.nf
+# ip sr tunsrc set 2001:db8::1
+.SH SEE ALSO
+.br
+.BR ip-route (8)
+.SH AUTHOR
+David Lebrun <david.lebrun@uclouvain.be>
diff --git a/man/man8/ip-tcp_metrics.8 b/man/man8/ip-tcp_metrics.8
new file mode 100644
index 0000000..5d2dac8
--- /dev/null
+++ b/man/man8/ip-tcp_metrics.8
@@ -0,0 +1,143 @@
+.TH "IP\-TCP_METRICS" 8 "23 Aug 2012" "iproute2" "Linux"
+.SH "NAME"
+ip-tcp_metrics \- management for TCP Metrics
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B tcp_metrics
+.RI "{ " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.BR "ip tcp_metrics" " { " show " | " flush " }
+.IR SELECTOR
+
+.ti -8
+.BR "ip tcp_metrics delete " [ " address " ]
+.IR ADDRESS
+
+.ti -8
+.IR SELECTOR " := "
+.RB "[ [ " address " ] "
+.IR PREFIX " ]"
+
+.SH "DESCRIPTION"
+.B ip tcp_metrics
+is used to manipulate entries in the kernel that keep TCP information
+for IPv4 and IPv6 destinations. The entries are created when
+TCP sockets want to share information for destinations and are
+stored in a cache keyed by the destination address. The saved
+information may include values for metrics (initially obtained from
+routes), recent TSVAL for TIME-WAIT recycling purposes, state for the
+Fast Open feature, etc.
+For performance reasons the cache can not grow above configured limit
+and the older entries are replaced with fresh information, sometimes
+reclaimed and used for new destinations. The kernel never removes
+entries, they can be flushed only with this tool.
+
+.SS ip tcp_metrics show - show cached entries
+
+.TP
+.BI address " PREFIX " (default)
+IPv4/IPv6 prefix or address. If no prefix is provided all entries are shown.
+
+.LP
+The output may contain the following information:
+
+.BI age " <S.MMM>" sec
+- time after the entry was created, reset or updated with metrics
+from sockets. The entry is reset and refreshed on use with metrics from
+route if the metrics are not updated in last hour. Not all cached values
+reset the age on update.
+
+.BI cwnd " <N>"
+- CWND metric value
+
+.BI fo_cookie " <HEX-STRING>"
+- Cookie value received in SYN-ACK to be used by Fast Open for next SYNs
+
+.BI fo_mss " <N>"
+- MSS value received in SYN-ACK to be used by Fast Open for next SYNs
+
+.BI fo_syn_drops " <N>/<S.MMM>" "sec ago"
+- Number of drops of initial outgoing Fast Open SYNs with data
+detected by monitoring the received SYN-ACK after SYN retransmission.
+The seconds show the time after last SYN drop and together with
+the drop count can be used to disable Fast Open for some time.
+
+.BI reordering " <N>"
+- Reordering metric value
+
+.BI rtt " <N>" us
+- RTT metric value
+
+.BI rttvar " <N>" us
+- RTTVAR metric value
+
+.BI ssthresh " <SSTHRESH>"
+- SSTHRESH metric value
+
+.BI tw_ts " <TSVAL>/<SEC>" "sec ago"
+- recent TSVAL and the seconds after saving it into TIME-WAIT socket
+
+.SS ip tcp_metrics delete - delete single entry
+
+.TP
+.BI address " ADDRESS " (default)
+IPv4/IPv6 address. The address is a required argument.
+
+.SS ip tcp_metrics flush - flush entries
+This command flushes the entries selected by some criteria.
+
+.PP
+This command has the same arguments as
+.B show.
+
+.SH "EXAMPLES"
+.PP
+ip tcp_metrics show address 192.168.0.0/24
+.RS 4
+Shows the entries for destinations from subnet
+.RE
+.PP
+ip tcp_metrics show 192.168.0.0/24
+.RS 4
+The same but address keyword is optional
+.RE
+.PP
+ip tcp_metrics
+.RS 4
+Show all is the default action
+.RE
+.PP
+ip tcp_metrics delete 192.168.0.1
+.RS 4
+Removes the entry for 192.168.0.1 from cache.
+.RE
+.PP
+ip tcp_metrics flush 192.168.0.0/24
+.RS 4
+Removes entries for destinations from subnet
+.RE
+.PP
+ip tcp_metrics flush all
+.RS 4
+Removes all entries from cache
+.RE
+.PP
+ip -6 tcp_metrics flush all
+.RS 4
+Removes all IPv6 entries from cache keeping the IPv4 entries.
+.RE
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Julian Anastasov <ja@ssi.bg>
diff --git a/man/man8/ip-token.8 b/man/man8/ip-token.8
new file mode 100644
index 0000000..6505b8c
--- /dev/null
+++ b/man/man8/ip-token.8
@@ -0,0 +1,75 @@
+.TH IP\-TOKEN 8 "28 Mar 2013" "iproute2" "Linux"
+.SH "NAME"
+ip-token \- tokenized interface identifier support
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip token
+.RI "{ " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.B ip token set
+.IR TOKEN
+.B dev
+.IR DEV
+
+.ti -8
+.B ip token del dev
+.IR DEV
+
+.ti -8
+.B ip token get
+.RB "[ " dev
+.IR DEV " ]"
+
+.ti -8
+.BR "ip token" " [ " list " ]"
+
+.SH "DESCRIPTION"
+IPv6 tokenized interface identifier support is used for assigning well-known
+host-part addresses to nodes whilst still obtaining a global network prefix
+from Router advertisements. The primary target for tokenized identifiers are
+server platforms where addresses are usually manually configured, rather than
+using DHCPv6 or SLAAC. By using tokenized identifiers, hosts can still
+determine their network prefix by use of SLAAC, but more readily be
+automatically renumbered should their network prefix change [1]. Tokenized
+IPv6 Identifiers are described in the draft
+[1]: <draft-chown-6man-tokenised-ipv6-identifiers-02>.
+
+.SS ip token set - set an interface token
+set the interface token to the kernel.
+.TP
+.I TOKEN
+the interface identifier token address.
+.TP
+.BI dev " DEV"
+the networking interface.
+
+.SS ip token del - delete an interface token
+delete the interface token from the kernel.
+.TP
+.BI dev " DEV"
+the networking interface.
+
+.SS ip token get - get the interface token from the kernel
+show a tokenized interface identifier of a particular networking device.
+.B Arguments:
+coincide with the arguments of
+.B ip token set
+but the
+.I TOKEN
+must be left out.
+.SS ip token list - list all interface tokens
+list all tokenized interface identifiers for the networking interfaces from
+the kernel.
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Manpage by Daniel Borkmann
diff --git a/man/man8/ip-tunnel.8 b/man/man8/ip-tunnel.8
new file mode 100644
index 0000000..9a510af
--- /dev/null
+++ b/man/man8/ip-tunnel.8
@@ -0,0 +1,281 @@
+.TH IP\-TUNNEL 8 "20 Dec 2011" "iproute2" "Linux"
+.SH "NAME"
+ip-tunnel - tunnel configuration
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip tunnel help
+.sp
+.ti -8
+.BR "ip "
+.RI "[ " OPTIONS " ]"
+.BR "tunnel" " { " add " | " change " | " del " | " show " | " prl " | " 6rd " }"
+.RI "[ " NAME " ]"
+.br
+.RB "[ " mode
+.IR MODE " ] [ "
+.B remote
+.IR ADDR " ] [ "
+.B local
+.IR ADDR " ]"
+.br
+.RB "[ [" i "|" o "]" seq " ] [ [" i "|" o "]" key
+.IR KEY " ] [ "
+.RB "[" i "|" o "]" csum " ] ]"
+.br
+.RB "[ " encaplimit
+.IR ELIM " ]"
+.RB "[ " ttl "|" hoplimit
+.IR TTL " ]"
+.br
+.RB "[ " tos
+.IR TOS " ] [ "
+.B flowlabel
+.IR FLOWLABEL " ]"
+.br
+.RB "[ " prl-default
+.IR ADDR " ] [ "
+.B prl-nodefault
+.IR ADDR " ] [ "
+.B prl-delete
+.IR ADDR " ]"
+.br
+.RB "[ " 6rd-prefix
+.IR ADDR " ] ["
+.B 6rd-relay_prefix
+.IR ADDR " ] [
+.BR 6rd-reset " ]"
+.br
+.RB "[ [" no "]" pmtudisc " ]"
+.RB "[ [" no "]" ignore-df " ]"
+.RB "[ [" no "]" allow-localremote " ]"
+.br
+.RB "[ " dev
+.IR PHYS_DEV " ]"
+
+.ti -8
+.IR MODE " := "
+.RB " { " ipip " | " gre " | " sit " | " isatap " | " vti " | " ip6ip6 " | " ipip6 " | " ip6gre " | " vti6 " | " any " }"
+
+.ti -8
+.IR ADDR " := { " IP_ADDRESS " |"
+.BR any " }"
+
+.ti -8
+.IR TOS " := { " STRING " | " 00 ".." ff " |"
+.BR inherit " |"
+.BI "inherit/" STRING
+.RB "|"
+.BI "inherit/" 00 ".." ff
+.RB "}"
+
+.ti -8
+.IR ELIM " := {"
+.BR none " | "
+.IR 0 ".." 255 " }"
+
+.ti -8
+.ti -8
+.IR TTL " := { " 1 ".." 255 " | "
+.BR inherit " }"
+
+.ti -8
+.IR KEY " := { " DOTTED_QUAD " | " NUMBER " }"
+
+.SH DESCRIPTION
+.B tunnel
+objects are tunnels, encapsulating packets in IP packets and then
+sending them over the IP infrastructure.
+The encapsulating (or outer) address family is specified by the
+.B -f
+option. The default is IPv4.
+
+.TP
+.B ip tunnel add
+add a new tunnel
+.TP
+.B ip tunnel change
+change an existing tunnel
+.TP
+.B ip tunnel delete
+destroy a tunnel
+.RS
+.TP
+.BI name " NAME " (default)
+select the tunnel device name.
+
+.TP
+.BI mode " MODE"
+set the tunnel mode. Available modes depend on the encapsulating address family.
+.br
+Modes for IPv4 encapsulation available:
+.BR ipip ", " sit ", " isatap ", " vti ", and " gre "."
+.br
+Modes for IPv6 encapsulation available:
+.BR ip6ip6 ", " ipip6 ", " ip6gre ", " vti6 ", and " any "."
+
+.TP
+.BI remote " ADDRESS"
+set the remote endpoint of the tunnel.
+
+.TP
+.BI local " ADDRESS"
+set the fixed local address for tunneled packets.
+It must be an address on another interface of this host.
+
+.TP
+.BI ttl " N"
+.TP
+.BI hoplimit " N"
+set a fixed TTL (IPv4) or hoplimit (IPv6)
+.I N
+on tunneled packets.
+.I N
+is a number in the range 1--255. 0 is a special value
+meaning that packets inherit the TTL value.
+The default value for IPv4 tunnels is:
+.BR "inherit" .
+The default value for IPv6 tunnels is:
+.BR "64" .
+
+
+.TP
+.BI tos " T"
+.TP
+.BI dsfield " T"
+.TP
+.BI tclass " T"
+set the type of service (IPv4) or traffic class (IPv6) field on
+tunneled packets, which can be specified as either a two-digit
+hex value (e.g. c0) or a predefined string (e.g. internet).
+The value
+.B inherit
+causes the field to be copied from the original IP header. The
+values
+.BI "inherit/" STRING
+or
+.BI "inherit/" 00 ".." ff
+will set the field to
+.I STRING
+or
+.IR 00 ".." ff
+when tunneling non-IP packets. The default value is 00.
+
+.TP
+.BI dev " NAME"
+bind the tunnel to the device
+.I NAME
+so that tunneled packets will only be routed via this device and will
+not be able to escape to another device when the route to endpoint
+changes.
+
+.TP
+.B nopmtudisc
+disable Path MTU Discovery on this tunnel.
+It is enabled by default. Note that a fixed ttl is incompatible
+with this option: tunneling with a fixed ttl always makes pmtu
+discovery.
+
+.TP
+.B ignore-df
+enable IPv4 DF suppression on this tunnel.
+Normally datagrams that exceed the MTU will be fragmented; the presence
+of the DF flag inhibits this, resulting instead in an ICMP Unreachable
+(Fragmentation Required) message. Enabling this attribute causes the
+DF flag to be ignored.
+
+.TP
+.BI key " K"
+.TP
+.BI ikey " K"
+.TP
+.BI okey " K"
+.RB ( " only GRE tunnels " )
+use keyed GRE with key
+.IR K ". " K
+is either a number or an IP address-like dotted quad.
+The
+.B key
+parameter sets the key to use in both directions.
+The
+.BR ikey " and " okey
+parameters set different keys for input and output.
+
+.TP
+.BR csum ", " icsum ", " ocsum
+.RB ( " only GRE tunnels " )
+generate/require checksums for tunneled packets.
+The
+.B ocsum
+flag calculates checksums for outgoing packets.
+The
+.B icsum
+flag requires that all input packets have the correct
+checksum. The
+.B csum
+flag is equivalent to the combination
+.BR "icsum ocsum" .
+
+.TP
+.BR seq ", " iseq ", " oseq
+.RB ( " only GRE tunnels " )
+serialize packets.
+The
+.B oseq
+flag enables sequencing of outgoing packets.
+The
+.B iseq
+flag requires that all input packets are serialized.
+The
+.B seq
+flag is equivalent to the combination
+.BR "iseq oseq" .
+.B It doesn't work. Don't use it.
+
+.TP
+.BI encaplim " ELIM"
+.RB ( " only IPv6 tunnels " )
+set a fixed encapsulation limit. Default is 4.
+
+.TP
+.BI flowlabel " FLOWLABEL"
+.RB ( " only IPv6 tunnels " )
+set a fixed flowlabel.
+
+.TP
+.BI allow-localremote
+.RB ( " only IPv6 tunnels " )
+allow remote endpoint on the local host.
+.RE
+
+.TP
+.B ip tunnel prl
+potential router list (ISATAP only)
+.RS
+.TP
+.BI dev " NAME"
+mandatory device name.
+
+.TP
+.BI prl-default " ADDR"
+.TP
+.BI prl-nodefault " ADDR"
+.TP
+.BI prl-delete " ADDR"
+.RB "Add or delete " ADDR
+as a potential router or default router.
+.RE
+
+.TP
+.B ip tunnel show
+list tunnels
+This command has no arguments.
+
+.SH SEE ALSO
+.br
+.BR ip (8)
+
+.SH AUTHOR
+Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/ip-vrf.8 b/man/man8/ip-vrf.8
new file mode 100644
index 0000000..c1c9b95
--- /dev/null
+++ b/man/man8/ip-vrf.8
@@ -0,0 +1,111 @@
+.TH IP\-VRF 8 "7 Dec 2016" "iproute2" "Linux"
+.SH NAME
+ip-vrf \- run a command against a vrf
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.B vrf
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.BR "ip vrf show"
+.RI "[ " NAME " ]"
+
+.ti -8
+.BR "ip vrf identify"
+.RI "[ " PID " ]"
+
+.ti -8
+.BR "ip vrf pids"
+.I NAME
+
+.ti -8
+.BR "ip vrf exec "
+.RI "[ " NAME " ] " command ...
+
+.SH DESCRIPTION
+A VRF provides traffic isolation at layer 3 for routing, similar to how a
+VLAN is used to isolate traffic at layer 2. Fundamentally, a VRF is a separate
+routing table. Network devices are associated with a VRF by enslaving the
+device to the VRF. At that point network addresses assigned to the device are
+local to the VRF with host and connected routes moved to the table associated
+with the VRF.
+
+A process can specify a VRF using several APIs -- binding the socket to the
+VRF device using SO_BINDTODEVICE, setting the VRF association using
+IP_UNICAST_IF or IPV6_UNICAST_IF, or specifying the VRF for a specific message
+using IP_PKTINFO or IPV6_PKTINFO.
+
+By default a process is not bound to any VRF. An association can be set
+explicitly by making the program use one of the APIs mentioned above or
+implicitly using a helper to set SO_BINDTODEVICE for all IPv4 and IPv6
+sockets (AF_INET and AF_INET6) when the socket is created. This ip-vrf command
+is a helper to run a command against a specific VRF with the VRF association
+inherited parent to child.
+
+.TP
+.B ip vrf show [ NAME ] - Show all configured VRF
+.sp
+This command lists all VRF and their corresponding table ids. If NAME is
+given, then only that VRF and table id is shown. The latter command is
+useful for scripting where the table id for a VRF is needed.
+
+.TP
+.B ip vrf exec [ NAME ] cmd ... - Run cmd against the named VRF
+.sp
+This command allows applications that are VRF unaware to be run against
+a VRF other than the default VRF (main table). A command can be run against
+the default VRF by passing the "default" as the VRF name. This is useful if
+the current shell is associated with another VRF (e.g, Management VRF).
+
+This command requires the system to be booted with cgroup v2 (e.g. with systemd,
+add systemd.unified_cgroup_hierarchy=1 to the kernel command line).
+
+This command also requires to be ran as root or with the CAP_SYS_ADMIN,
+CAP_NET_ADMIN and CAP_DAC_OVERRIDE capabilities. If built with libcap and if
+capabilities are added to the ip binary program via setcap, the program will
+drop them as the first thing when invoked, unless the command is vrf exec.
+.br
+NOTE: capabilities will NOT be dropped if CAP_NET_ADMIN is set to INHERITABLE
+to avoid breaking programs with ambient capabilities that call ip.
+Do not set the INHERITABLE flag on the ip binary itself.
+
+.TP
+.B ip vrf identify [PID] - Report VRF association for process
+.sp
+This command shows the VRF association of the specified process. If PID is
+not specified then the id of the current process is used.
+
+.TP
+.B ip vrf pids NAME - Report processes associated with the named VRF
+.sp
+This command shows all process ids that are associated with the given
+VRF.
+
+.SH CAVEATS
+This command requires a kernel compiled with CGROUPS and CGROUP_BPF enabled.
+
+The VRF helper *only* affects network layer sockets.
+
+.SH EXAMPLES
+.PP
+ip vrf exec red ssh 10.100.1.254
+.RS
+Executes ssh to 10.100.1.254 against the VRF red table.
+.RE
+
+.SH SEE ALSO
+.br
+.BR ip (8),
+.BR ip-link (8),
+.BR ip-address (8),
+.BR ip-route (8),
+.BR ip-neighbor (8)
+
+.SH AUTHOR
+Original Manpage by David Ahern
diff --git a/man/man8/ip-xfrm.8 b/man/man8/ip-xfrm.8
new file mode 100644
index 0000000..cfce1e4
--- /dev/null
+++ b/man/man8/ip-xfrm.8
@@ -0,0 +1,734 @@
+.TH IP\-XFRM 8 "20 Dec 2011" "iproute2" "Linux"
+.SH "NAME"
+ip-xfrm \- transform configuration
+.SH "SYNOPSIS"
+.sp
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ]"
+.B xfrm
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.B "ip xfrm"
+.IR XFRM-OBJECT " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR XFRM-OBJECT " :="
+.BR state " | " policy " | " monitor
+.sp
+
+.ti -8
+.BR "ip xfrm state" " { " add " | " update " } "
+.IR ID " [ " ALGO-LIST " ]"
+.RB "[ " mode
+.IR MODE " ]"
+.RB "[ " mark
+.I MARK
+.RB "[ " mask
+.IR MASK " ] ]"
+.RB "[ " reqid
+.IR REQID " ]"
+.RB "[ " seq
+.IR SEQ " ]"
+.RB "[ " replay-window
+.IR SIZE " ]"
+.RB "[ " replay-seq
+.IR SEQ " ]"
+.RB "[ " replay-oseq
+.IR SEQ " ]"
+.RB "[ " replay-seq-hi
+.IR SEQ " ]"
+.RB "[ " replay-oseq-hi
+.IR SEQ " ]"
+.RB "[ " flag
+.IR FLAG-LIST " ]"
+.RB "[ " sel
+.IR SELECTOR " ] [ " LIMIT-LIST " ]"
+.RB "[ " encap
+.IR ENCAP " ]"
+.RB "[ " coa
+.IR ADDR "[/" PLEN "] ]"
+.RB "[ " ctx
+.IR CTX " ]"
+.RB "[ " extra-flag
+.IR EXTRA-FLAG-LIST " ]"
+.RB "[ " output-mark
+.IR OUTPUT-MARK " ]"
+
+.ti -8
+.B "ip xfrm state allocspi"
+.I ID
+.RB "[ " mode
+.IR MODE " ]"
+.RB "[ " mark
+.I MARK
+.RB "[ " mask
+.IR MASK " ] ]"
+.RB "[ " reqid
+.IR REQID " ]"
+.RB "[ " seq
+.IR SEQ " ]"
+.RB "[ " min
+.I SPI
+.B max
+.IR SPI " ]"
+
+.ti -8
+.BR "ip xfrm state" " { " delete " | " get " } "
+.I ID
+.RB "[ " mark
+.I MARK
+.RB "[ " mask
+.IR MASK " ] ]"
+
+.ti -8
+.BR ip " [ " -4 " | " -6 " ] " "xfrm state deleteall" " ["
+.IR ID " ]"
+.RB "[ " mode
+.IR MODE " ]"
+.RB "[ " reqid
+.IR REQID " ]"
+.RB "[ " flag
+.IR FLAG-LIST " ]"
+
+.ti -8
+.BR ip " [ " -4 " | " -6 " ] " "xfrm state list" " ["
+.IR ID " ]"
+.RB "[ " nokeys " ]"
+.RB "[ " mode
+.IR MODE " ]"
+.RB "[ " reqid
+.IR REQID " ]"
+.RB "[ " flag
+.IR FLAG-LIST " ]"
+
+.ti -8
+.BR "ip xfrm state flush" " [ " proto
+.IR XFRM-PROTO " ]"
+
+.ti -8
+.BR "ip xfrm state count"
+
+.ti -8
+.IR ID " :="
+.RB "[ " src
+.IR ADDR " ]"
+.RB "[ " dst
+.IR ADDR " ]"
+.RB "[ " proto
+.IR XFRM-PROTO " ]"
+.RB "[ " spi
+.IR SPI " ]"
+
+.ti -8
+.IR XFRM-PROTO " :="
+.BR esp " | " ah " | " comp " | " route2 " | " hao
+
+.ti -8
+.IR ALGO-LIST " := [ " ALGO-LIST " ] " ALGO
+
+.ti -8
+.IR ALGO " :="
+.RB "{ " enc " | " auth " } "
+.IR ALGO-NAME " " ALGO-KEYMAT " |"
+.br
+.B auth-trunc
+.IR ALGO-NAME " " ALGO-KEYMAT " " ALGO-TRUNC-LEN " |"
+.br
+.B aead
+.IR ALGO-NAME " " ALGO-KEYMAT " " ALGO-ICV-LEN " |"
+.br
+.B comp
+.IR ALGO-NAME
+
+.ti -8
+.IR MODE " := "
+.BR transport " | " tunnel " | " beet " | " ro " | " in_trigger
+
+.ti -8
+.IR FLAG-LIST " := [ " FLAG-LIST " ] " FLAG
+
+.ti -8
+.IR FLAG " :="
+.BR noecn " | " decap-dscp " | " nopmtudisc " | " wildrecv " | " icmp " | "
+.BR af-unspec " | " align4 " | " esn
+
+.ti -8
+.IR SELECTOR " :="
+.RB "[ " src
+.IR ADDR "[/" PLEN "] ]"
+.RB "[ " dst
+.IR ADDR "[/" PLEN "] ]"
+.RB "[ " dev
+.IR DEV " ]"
+.br
+.RI "[ " UPSPEC " ]"
+
+.ti -8
+.IR UPSPEC " := "
+.BR proto " {"
+.IR PROTO " |"
+.br
+.RB "{ " tcp " | " udp " | " sctp " | " dccp " } [ " sport
+.IR PORT " ]"
+.RB "[ " dport
+.IR PORT " ] |"
+.br
+.RB "{ " icmp " | " ipv6-icmp " | " mobility-header " } [ " type
+.IR NUMBER " ]"
+.RB "[ " code
+.IR NUMBER " ] |"
+.br
+.BR gre " [ " key
+.RI "{ " DOTTED-QUAD " | " NUMBER " } ] }"
+
+.ti -8
+.IR LIMIT-LIST " := [ " LIMIT-LIST " ]"
+.B limit
+.I LIMIT
+
+.ti -8
+.IR LIMIT " :="
+.RB "{ " time-soft " | " time-hard " | " time-use-soft " | " time-use-hard " }"
+.IR "SECONDS" " |"
+.br
+.RB "{ " byte-soft " | " byte-hard " }"
+.IR SIZE " |"
+.br
+.RB "{ " packet-soft " | " packet-hard " }"
+.I COUNT
+
+.ti -8
+.IR ENCAP " :="
+.RB "{ " espinudp " | " espinudp-nonike " }"
+.IR SPORT " " DPORT " " OADDR
+
+.ti -8
+.IR EXTRA-FLAG-LIST " := [ " EXTRA-FLAG-LIST " ] " EXTRA-FLAG
+
+.ti -8
+.IR EXTRA-FLAG " := "
+.B dont-encap-dscp
+
+.ti -8
+.BR "ip xfrm policy" " { " add " | " update " }"
+.I SELECTOR
+.B dir
+.I DIR
+.RB "[ " ctx
+.IR CTX " ]"
+.RB "[ " mark
+.I MARK
+.RB "[ " mask
+.IR MASK " ] ]"
+.RB "[ " index
+.IR INDEX " ]"
+.RB "[ " ptype
+.IR PTYPE " ]"
+.RB "[ " action
+.IR ACTION " ]"
+.RB "[ " priority
+.IR PRIORITY " ]"
+.RB "[ " flag
+.IR FLAG-LIST " ]"
+.RI "[ " LIMIT-LIST " ] [ " TMPL-LIST " ]"
+
+.ti -8
+.BR "ip xfrm policy" " { " delete " | " get " }"
+.RI "{ " SELECTOR " | "
+.B index
+.IR INDEX " }"
+.B dir
+.I DIR
+.RB "[ " ctx
+.IR CTX " ]"
+.RB "[ " mark
+.I MARK
+.RB "[ " mask
+.IR MASK " ] ]"
+.RB "[ " ptype
+.IR PTYPE " ]"
+
+.ti -8
+.BR ip " [ " -4 " | " -6 " ] " "xfrm policy" " { " deleteall " | " list " }"
+.RB "[ " nosock " ]"
+.RI "[ " SELECTOR " ]"
+.RB "[ " dir
+.IR DIR " ]"
+.RB "[ " index
+.IR INDEX " ]"
+.RB "[ " ptype
+.IR PTYPE " ]"
+.RB "[ " action
+.IR ACTION " ]"
+.RB "[ " priority
+.IR PRIORITY " ]"
+.RB "[ " flag
+.IR FLAG-LIST "]"
+
+.ti -8
+.B "ip xfrm policy flush"
+.RB "[ " ptype
+.IR PTYPE " ]"
+
+.ti -8
+.B "ip xfrm policy count"
+
+.ti -8
+.B "ip xfrm policy set"
+.RB "[ " hthresh4
+.IR LBITS " " RBITS " ]"
+.RB "[ " hthresh6
+.IR LBITS " " RBITS " ]"
+
+.ti -8
+.IR SELECTOR " :="
+.RB "[ " src
+.IR ADDR "[/" PLEN "] ]"
+.RB "[ " dst
+.IR ADDR "[/" PLEN "] ]"
+.RB "[ " dev
+.IR DEV " ]"
+.RI "[ " UPSPEC " ]"
+
+.ti -8
+.IR UPSPEC " := "
+.BR proto " {"
+.IR PROTO " |"
+.br
+.RB "{ " tcp " | " udp " | " sctp " | " dccp " } [ " sport
+.IR PORT " ]"
+.RB "[ " dport
+.IR PORT " ] |"
+.br
+.RB "{ " icmp " | " ipv6-icmp " | " mobility-header " } [ " type
+.IR NUMBER " ]"
+.RB "[ " code
+.IR NUMBER " ] |"
+.br
+.BR gre " [ " key
+.RI "{ " DOTTED-QUAD " | " NUMBER " } ] }"
+
+.ti -8
+.IR DIR " := "
+.BR in " | " out " | " fwd
+
+.ti -8
+.IR PTYPE " := "
+.BR main " | " sub
+
+.ti -8
+.IR ACTION " := "
+.BR allow " | " block
+
+.ti -8
+.IR FLAG-LIST " := [ " FLAG-LIST " ] " FLAG
+
+.ti -8
+.IR FLAG " :="
+.BR localok " | " icmp
+
+.ti -8
+.IR LIMIT-LIST " := [ " LIMIT-LIST " ]"
+.B limit
+.I LIMIT
+
+.ti -8
+.IR LIMIT " :="
+.RB "{ " time-soft " | " time-hard " | " time-use-soft " | " time-use-hard " }"
+.IR "SECONDS" " |"
+.br
+.RB "{ " byte-soft " | " byte-hard " }"
+.IR SIZE " |"
+.br
+.RB "{ " packet-soft " | " packet-hard " }"
+.I COUNT
+
+.ti -8
+.IR TMPL-LIST " := [ " TMPL-LIST " ]"
+.B tmpl
+.I TMPL
+
+.ti -8
+.IR TMPL " := " ID
+.RB "[ " mode
+.IR MODE " ]"
+.RB "[ " reqid
+.IR REQID " ]"
+.RB "[ " level
+.IR LEVEL " ]"
+
+.ti -8
+.IR ID " :="
+.RB "[ " src
+.IR ADDR " ]"
+.RB "[ " dst
+.IR ADDR " ]"
+.RB "[ " proto
+.IR XFRM-PROTO " ]"
+.RB "[ " spi
+.IR SPI " ]"
+
+.ti -8
+.IR XFRM-PROTO " :="
+.BR esp " | " ah " | " comp " | " route2 " | " hao
+
+.ti -8
+.IR MODE " := "
+.BR transport " | " tunnel " | " beet " | " ro " | " in_trigger
+
+.ti -8
+.IR LEVEL " :="
+.BR required " | " use
+
+.ti -8
+.BR "ip xfrm monitor" " ["
+.BI all-nsid
+] [
+.BI nokeys
+] [
+.BI all
+ |
+.IR LISTofXFRM-OBJECTS " ]"
+
+.ti -8
+.IR LISTofXFRM-OBJECTS " := [ " LISTofXFRM-OBJECTS " ] " XFRM-OBJECT
+
+.ti -8
+.IR XFRM-OBJECT " := "
+.BR acquire " | " expire " | " SA " | " policy " | " aevent " | " report
+
+.in -8
+.ad b
+
+.SH DESCRIPTION
+
+xfrm is an IP framework for transforming packets (such as encrypting
+their payloads). This framework is used to implement the IPsec protocol
+suite (with the
+.B state
+object operating on the Security Association Database, and the
+.B policy
+object operating on the Security Policy Database). It is also used for
+the IP Payload Compression Protocol and features of Mobile IPv6.
+
+.TS
+l l.
+ip xfrm state add add new state into xfrm
+ip xfrm state update update existing state in xfrm
+ip xfrm state allocspi allocate an SPI value
+ip xfrm state delete delete existing state in xfrm
+ip xfrm state get get existing state in xfrm
+ip xfrm state deleteall delete all existing state in xfrm
+ip xfrm state list print out the list of existing state in xfrm
+ip xfrm state flush flush all state in xfrm
+ip xfrm state count count all existing state in xfrm
+.TE
+
+.TP
+.IR ID
+is specified by a source address, destination address,
+.RI "transform protocol " XFRM-PROTO ","
+and/or Security Parameter Index
+.IR SPI "."
+(For IP Payload Compression, the Compression Parameter Index or CPI is used for
+.IR SPI ".)"
+
+.TP
+.I XFRM-PROTO
+specifies a transform protocol:
+.RB "IPsec Encapsulating Security Payload (" esp "),"
+.RB "IPsec Authentication Header (" ah "),"
+.RB "IP Payload Compression (" comp "),"
+.RB "Mobile IPv6 Type 2 Routing Header (" route2 "), or"
+.RB "Mobile IPv6 Home Address Option (" hao ")."
+
+.TP
+.I ALGO-LIST
+contains one or more algorithms to use. Each algorithm
+.I ALGO
+is specified by:
+.RS
+.IP \[bu]
+the algorithm type:
+.RB "encryption (" enc "),"
+.RB "authentication (" auth " or " auth-trunc "),"
+.RB "authenticated encryption with associated data (" aead "), or"
+.RB "compression (" comp ")"
+.IP \[bu]
+the algorithm name
+.IR ALGO-NAME
+(see below)
+.IP \[bu]
+.RB "(for all except " comp ")"
+the keying material
+.IR ALGO-KEYMAT ","
+which may include both a key and a salt or nonce value; refer to the
+corresponding RFC
+.IP \[bu]
+.RB "(for " auth-trunc " only)"
+the truncation length
+.I ALGO-TRUNC-LEN
+in bits
+.IP \[bu]
+.RB "(for " aead " only)"
+the Integrity Check Value length
+.I ALGO-ICV-LEN
+in bits
+.RE
+
+.nh
+.RS
+Encryption algorithms include
+.BR ecb(cipher_null) ", " cbc(des) ", " cbc(des3_ede) ", " cbc(cast5) ","
+.BR cbc(blowfish) ", " cbc(aes) ", " cbc(serpent) ", " cbc(camellia) ","
+.BR cbc(twofish) ", and " rfc3686(ctr(aes)) "."
+
+Authentication algorithms include
+.BR digest_null ", " hmac(md5) ", " hmac(sha1) ", " hmac(sha256) ","
+.BR hmac(sha384) ", " hmac(sha512) ", " hmac(rmd160) ", and " xcbc(aes) "."
+
+Authenticated encryption with associated data (AEAD) algorithms include
+.BR rfc4106(gcm(aes)) ", " rfc4309(ccm(aes)) ", and " rfc4543(gcm(aes)) "."
+
+Compression algorithms include
+.BR deflate ", " lzs ", and " lzjh "."
+.RE
+.hy
+
+.TP
+.I MODE
+specifies a mode of operation for the transform protocol. IPsec and IP Payload
+Compression modes are
+.BR transport ", " tunnel ","
+and (for IPsec ESP only) Bound End-to-End Tunnel
+.RB "(" beet ")."
+Mobile IPv6 modes are route optimization
+.RB "(" ro ")"
+and inbound trigger
+.RB "(" in_trigger ")."
+
+.TP
+.I FLAG-LIST
+contains one or more of the following optional flags:
+.BR noecn ", " decap-dscp ", " nopmtudisc ", " wildrecv ", " icmp ", "
+.BR af-unspec ", " align4 ", or " esn "."
+
+.TP
+.IR SELECTOR
+selects the traffic that will be controlled by the policy, based on the source
+address, the destination address, the network device, and/or
+.IR UPSPEC "."
+
+.TP
+.IR UPSPEC
+selects traffic by protocol. For the
+.BR tcp ", " udp ", " sctp ", or " dccp
+protocols, the source and destination port can optionally be specified.
+For the
+.BR icmp ", " ipv6-icmp ", or " mobility-header
+protocols, the type and code numbers can optionally be specified.
+For the
+.B gre
+protocol, the key can optionally be specified as a dotted-quad or number.
+Other protocols can be selected by name or number
+.IR PROTO "."
+
+.TP
+.I LIMIT-LIST
+sets limits in seconds, bytes, or numbers of packets.
+
+.TP
+.I ENCAP
+encapsulates packets with protocol
+.BR espinudp " or " espinudp-nonike ","
+.RI "using source port " SPORT ", destination port " DPORT
+.RI ", and original address " OADDR "."
+
+.TP
+.I MARK
+used to match xfrm policies and states
+
+.TP
+.I OUTPUT-MARK
+used to set the output mark to influence the routing
+of the packets emitted by the state
+
+.sp
+.PP
+.TS
+l l.
+ip xfrm policy add add a new policy
+ip xfrm policy update update an existing policy
+ip xfrm policy delete delete an existing policy
+ip xfrm policy get get an existing policy
+ip xfrm policy deleteall delete all existing xfrm policies
+ip xfrm policy list print out the list of xfrm policies
+ip xfrm policy flush flush policies
+.TE
+
+.TP
+.BR nosock
+filter (remove) all socket policies from the output.
+
+.TP
+.IR SELECTOR
+selects the traffic that will be controlled by the policy, based on the source
+address, the destination address, the network device, and/or
+.IR UPSPEC "."
+
+.TP
+.IR UPSPEC
+selects traffic by protocol. For the
+.BR tcp ", " udp ", " sctp ", or " dccp
+protocols, the source and destination port can optionally be specified.
+For the
+.BR icmp ", " ipv6-icmp ", or " mobility-header
+protocols, the type and code numbers can optionally be specified.
+For the
+.B gre
+protocol, the key can optionally be specified as a dotted-quad or number.
+Other protocols can be selected by name or number
+.IR PROTO "."
+
+.TP
+.I DIR
+selects the policy direction as
+.BR in ", " out ", or " fwd "."
+
+.TP
+.I CTX
+sets the security context.
+
+.TP
+.I PTYPE
+can be
+.BR main " (default) or " sub "."
+
+.TP
+.I ACTION
+can be
+.BR allow " (default) or " block "."
+
+.TP
+.I PRIORITY
+is a number that defaults to zero.
+
+.TP
+.I FLAG-LIST
+contains one or both of the following optional flags:
+.BR local " or " icmp "."
+
+.TP
+.I LIMIT-LIST
+sets limits in seconds, bytes, or numbers of packets.
+
+.TP
+.I TMPL-LIST
+is a template list specified using
+.IR ID ", " MODE ", " REQID ", and/or " LEVEL ". "
+
+.TP
+.IR ID
+is specified by a source address, destination address,
+.RI "transform protocol " XFRM-PROTO ","
+and/or Security Parameter Index
+.IR SPI "."
+(For IP Payload Compression, the Compression Parameter Index or CPI is used for
+.IR SPI ".)"
+
+.TP
+.I XFRM-PROTO
+specifies a transform protocol:
+.RB "IPsec Encapsulating Security Payload (" esp "),"
+.RB "IPsec Authentication Header (" ah "),"
+.RB "IP Payload Compression (" comp "),"
+.RB "Mobile IPv6 Type 2 Routing Header (" route2 "), or"
+.RB "Mobile IPv6 Home Address Option (" hao ")."
+
+.TP
+.I MODE
+specifies a mode of operation for the transform protocol. IPsec and IP Payload
+Compression modes are
+.BR transport ", " tunnel ","
+and (for IPsec ESP only) Bound End-to-End Tunnel
+.RB "(" beet ")."
+Mobile IPv6 modes are route optimization
+.RB "(" ro ")"
+and inbound trigger
+.RB "(" in_trigger ")."
+
+.TP
+.I LEVEL
+can be
+.BR required " (default) or " use "."
+
+.sp
+.PP
+.TS
+l l.
+ip xfrm policy count count existing policies
+.TE
+
+.PP
+Use one or more -s options to display more details, including policy hash table
+information.
+
+.sp
+.PP
+.TS
+l l.
+ip xfrm policy set configure the policy hash table
+.TE
+
+.PP
+Security policies whose address prefix lengths are greater than or equal
+policy hash table thresholds are hashed. Others are stored in the
+policy_inexact chained list.
+
+.TP
+.I LBITS
+specifies the minimum local address prefix length of policies that are
+stored in the Security Policy Database hash table.
+
+.TP
+.I RBITS
+specifies the minimum remote address prefix length of policies that are
+stored in the Security Policy Database hash table.
+
+.sp
+.PP
+.TS
+l l.
+ip xfrm monitor state monitoring for xfrm objects
+.TE
+
+.PP
+The xfrm objects to monitor can be optionally specified.
+
+.P
+If the
+.BI all-nsid
+option is set, the program listens to all network namespaces that have a
+nsid assigned into the network namespace were the program is running.
+A prefix is displayed to show the network namespace where the message
+originates. Example:
+.sp
+.in +2
+[nsid 1]Flushed state proto 0
+.in -2
+.sp
+
+.SH AUTHOR
+Manpage revised by David Ward <david.ward@ll.mit.edu>
+.br
+Manpage revised by Christophe Gouault <christophe.gouault@6wind.com>
+.br
+Manpage revised by Nicolas Dichtel <nicolas.dichtel@6wind.com>
diff --git a/man/man8/ip.8 b/man/man8/ip.8
new file mode 100644
index 0000000..e2bda2a
--- /dev/null
+++ b/man/man8/ip.8
@@ -0,0 +1,424 @@
+.TH IP 8 "20 Dec 2011" "iproute2" "Linux"
+.SH NAME
+ip \- show / manipulate routing, network devices, interfaces and tunnels
+.SH SYNOPSIS
+
+.ad l
+.in +8
+.ti -8
+.B ip
+.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.B ip
+.RB "[ " -force " ] "
+.BI "-batch " filename
+.sp
+
+.ti -8
+.IR OBJECT " := { "
+.BR link " | " address " | " addrlabel " | " route " | " rule " | " neigh " | "\
+ ntable " | " tunnel " | " tuntap " | " maddress " | " mroute " | " mrule " | "\
+ monitor " | " xfrm " | " netns " | " l2tp " | " tcp_metrics " | " token " | "\
+ macsec " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-h\fR[\fIuman-readable\fR] |
+\fB\-s\fR[\fItatistics\fR] |
+\fB\-d\fR[\fIetails\fR] |
+\fB\-r\fR[\fIesolve\fR] |
+\fB\-iec\fR |
+\fB\-f\fR[\fIamily\fR] {
+.BR inet " | " inet6 " | " link " } | "
+\fB-4\fR |
+\fB-6\fR |
+\fB-I\fR |
+\fB-D\fR |
+\fB-B\fR |
+\fB-0\fR |
+\fB-l\fR[\fIoops\fR] { \fBmaximum-addr-flush-attempts\fR } |
+\fB\-o\fR[\fIneline\fR] |
+\fB\-rc\fR[\fIvbuf\fR] [\fBsize\fR] |
+\fB\-t\fR[\fIimestamp\fR] |
+\fB\-ts\fR[\fIhort\fR] |
+\fB\-n\fR[\fIetns\fR] name |
+\fB\-N\fR[\fIumeric\fR] |
+\fB\-a\fR[\fIll\fR] |
+\fB\-c\fR[\fIolor\fR] |
+\fB\-br\fR[\fIief\fR] |
+\fB\-j\fR[son\fR] |
+\fB\-p\fR[retty\fR] }
+
+.SH OPTIONS
+
+.TP
+.BR "\-V" , " -Version"
+Print the version of the
+.B ip
+utility and exit.
+
+.TP
+.BR "\-h", " \-human", " \-human-readable"
+output statistics with human readable values followed by suffix.
+
+.TP
+.BR "\-b", " \-batch " <FILENAME>
+Read commands from provided file or standard input and invoke them.
+First failure will cause termination of ip.
+
+.TP
+.BR "\-force"
+Don't terminate ip on errors in batch mode. If there were any errors
+during execution of the commands, the application return code will be
+non zero.
+
+.TP
+.BR "\-s" , " \-stats" , " \-statistics"
+Output more information. If the option
+appears twice or more, the amount of information increases.
+As a rule, the information is statistics or some time values.
+
+.TP
+.BR "\-d" , " \-details"
+Output more detailed information.
+
+.TP
+.BR "\-l" , " \-loops " <COUNT>
+Specify maximum number of loops the 'ip address flush' logic
+will attempt before giving up. The default is 10.
+Zero (0) means loop until all addresses are removed.
+
+.TP
+.BR "\-f" , " \-family " <FAMILY>
+Specifies the protocol family to use. The protocol family identifier
+can be one of
+.BR "inet" , " inet6" , " bridge" , " mpls"
+or
+.BR link .
+If this option is not present,
+the protocol family is guessed from other arguments. If the rest
+of the command line does not give enough information to guess the
+family,
+.B ip
+falls back to the default one, usually
+.B inet
+or
+.BR "any" .
+.B link
+is a special family identifier meaning that no networking protocol
+is involved.
+
+.TP
+.B \-4
+shortcut for
+.BR "-family inet" .
+
+.TP
+.B \-6
+shortcut for
+.BR "\-family inet6" .
+
+.TP
+.B \-B
+shortcut for
+.BR "\-family bridge" .
+
+.TP
+.B \-M
+shortcut for
+.BR "\-family mpls" .
+
+.TP
+.B \-0
+shortcut for
+.BR "\-family link" .
+
+.TP
+.BR "\-o" , " \-oneline"
+output each record on a single line, replacing line feeds
+with the
+.B '\e'
+character. This is convenient when you want to count records
+with
+.BR wc (1)
+or to
+.BR grep (1)
+the output.
+
+.TP
+.BR "\-r" , " \-resolve"
+use the system's name resolver to print DNS names instead of
+host addresses.
+
+.TP
+.BR "\-n" , " \-netns " <NETNS>
+switches
+.B ip
+to the specified network namespace
+.IR NETNS .
+Actually it just simplifies executing of:
+
+.B ip netns exec
+.IR NETNS
+.B ip
+.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+to
+
+.B ip
+.RI "-n[etns] " NETNS " [ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+.TP
+.BR "\-N" , " \-Numeric"
+Print the number of protocol, scope, dsfield, etc directly instead of
+converting it to human readable name.
+
+.TP
+.BR "\-a" , " \-all"
+executes specified command over all objects, it depends if command
+supports this option.
+
+.TP
+.BR \-c [ color ][ = { always | auto | never }
+Configure color output. If parameter is omitted or
+.BR always ,
+color output is enabled regardless of stdout state. If parameter is
+.BR auto ,
+stdout is checked to be a terminal before enabling color output. If
+parameter is
+.BR never ,
+color output is disabled. If specified multiple times, the last one takes
+precedence. This flag is ignored if
+.B \-json
+is also given.
+
+Used color palette can be influenced by
+.BR COLORFGBG
+environment variable
+(see
+.BR ENVIRONMENT ).
+
+.TP
+.BR "\-t" , " \-timestamp"
+display current time when using monitor option.
+
+.TP
+.BR "\-ts" , " \-tshort"
+Like
+.BR \-timestamp ,
+but use shorter format.
+
+.TP
+.BR "\-rc" , " \-rcvbuf" <SIZE>
+Set the netlink socket receive buffer size, defaults to 1MB.
+
+.TP
+.BR "\-iec"
+print human readable rates in IEC units (e.g. 1Ki = 1024).
+
+.TP
+.BR "\-br" , " \-brief"
+Print only basic information in a tabular format for better
+readability. This option is currently only supported by
+.BR "ip addr show " and " ip link show " commands.
+
+.TP
+.BR "\-j", " \-json"
+Output results in JavaScript Object Notation (JSON).
+
+.TP
+.BR "\-p", " \-pretty"
+The default JSON format is compact and more efficient to parse but
+hard for most users to read. This flag adds indentation for
+readability.
+
+.SH IP - COMMAND SYNTAX
+
+.SS
+.I OBJECT
+
+.TP
+.B address
+- protocol (IP or IPv6) address on a device.
+
+.TP
+.B addrlabel
+- label configuration for protocol address selection.
+
+.TP
+.B l2tp
+- tunnel ethernet over IP (L2TPv3).
+
+.TP
+.B link
+- network device.
+
+.TP
+.B maddress
+- multicast address.
+
+.TP
+.B monitor
+- watch for netlink messages.
+
+.TP
+.B mroute
+- multicast routing cache entry.
+
+.TP
+.B mrule
+- rule in multicast routing policy database.
+
+.TP
+.B neighbour
+- manage ARP or NDISC cache entries.
+
+.TP
+.B netns
+- manage network namespaces.
+
+.TP
+.B ntable
+- manage the neighbor cache's operation.
+
+.TP
+.B route
+- routing table entry.
+
+.TP
+.B rule
+- rule in routing policy database.
+
+.TP
+.B tcp_metrics/tcpmetrics
+- manage TCP Metrics
+
+.TP
+.B token
+- manage tokenized interface identifiers.
+
+.TP
+.B tunnel
+- tunnel over IP.
+
+.TP
+.B tuntap
+- manage TUN/TAP devices.
+
+.TP
+.B xfrm
+- manage IPSec policies.
+
+.PP
+The names of all objects may be written in full or
+abbreviated form, for example
+.B address
+can be abbreviated as
+.B addr
+or just
+.B a.
+
+.SS
+.I COMMAND
+
+Specifies the action to perform on the object.
+The set of possible actions depends on the object type.
+As a rule, it is possible to
+.BR "add" , " delete"
+and
+.B show
+(or
+.B list
+) objects, but some objects do not allow all of these operations
+or have some additional commands. The
+.B help
+command is available for all objects. It prints
+out a list of available commands and argument syntax conventions.
+.sp
+If no command is given, some default command is assumed.
+Usually it is
+.B list
+or, if the objects of this class cannot be listed,
+.BR "help" .
+
+.SH ENVIRONMENT
+.TP
+.B COLORFGBG
+If set, it's value is used for detection whether background is dark or
+light and use contrast colors for it.
+
+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, chose colors suitable for dark background:
+
+COLORFGBG=";0" ip -c a
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful, and 1 if there is a syntax error.
+If an error was reported by the kernel exit status is 2.
+
+.SH "EXAMPLES"
+.PP
+ip addr
+.RS 4
+Shows addresses assigned to all network interfaces.
+.RE
+.PP
+ip neigh
+.RS 4
+Shows the current neighbour table in kernel.
+.RE
+.PP
+ip link set x up
+.RS 4
+Bring up interface x.
+.RE
+.PP
+ip link set x down
+.RE
+.RS 4
+Bring down interface x.
+.RE
+.PP
+ip route
+.RS 4
+Show table routes.
+.RE
+
+.SH HISTORY
+.B ip
+was written by Alexey N. Kuznetsov and added in Linux 2.2.
+.SH SEE ALSO
+.BR ip-address (8),
+.BR ip-addrlabel (8),
+.BR ip-l2tp (8),
+.BR ip-link (8),
+.BR ip-maddress (8),
+.BR ip-monitor (8),
+.BR ip-mroute (8),
+.BR ip-neighbour (8),
+.BR ip-netns (8),
+.BR ip-ntable (8),
+.BR ip-route (8),
+.BR ip-rule (8),
+.BR ip-tcp_metrics (8),
+.BR ip-token (8),
+.BR ip-tunnel (8),
+.BR ip-xfrm (8)
+.br
+.RB "IP Command reference " ip-cref.ps
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Original Manpage by Michail Litvak <mci@owl.openwall.com>
diff --git a/man/man8/lnstat.8 b/man/man8/lnstat.8
new file mode 100644
index 0000000..b98241b
--- /dev/null
+++ b/man/man8/lnstat.8
@@ -0,0 +1,262 @@
+.TH LNSTAT 8
+.SH NAME
+lnstat \- unified linux network statistics
+.SH SYNOPSIS
+.B lnstat
+.RI [ options ]
+.SH DESCRIPTION
+This manual page documents briefly the
+.B lnstat
+command.
+.PP
+\fBlnstat\fP is a generalized and more feature-complete replacement for the old
+rtstat program. It is commonly used to periodically print a selection of
+statistical values exported by the kernel.
+In addition to routing cache statistics, it supports any kind of statistics the
+linux kernel exports via a file in /proc/net/stat/.
+.PP
+Each file in /proc/net/stat/ contains a header line listing the column names.
+These names are used by \fBlnstat\fP as keys for selecting which statistics to
+print. For every CPU present in the system, a line follows which lists the
+actual values for each column of the file. \fBlnstat\fP sums these values up
+(which in fact are counters) before printing them. After each interval, only
+the difference to the last value is printed.
+.PP
+Files and columns may be selected by using the \fB-f\fP and \fB-k\fP
+parameters. By default, all columns of all files are printed.
+.SH OPTIONS
+lnstat supports the following options.
+.TP
+.B \-h, \-\-help
+Show summary of options.
+.TP
+.B \-V, \-\-version
+Show version of program.
+.TP
+.B \-c, \-\-count <count>
+Print <count> number of intervals.
+.TP
+.B \-d, \-\-dump
+Dump list of available files/keys.
+.TP
+.B \-f, \-\-file <file>
+Statistics file to use, may be specified multiple times. By default all files in /proc/net/stat are scanned.
+.TP
+.B \-i, \-\-interval <intv>
+Set interval to 'intv' seconds.
+.TP
+.B \-j, \-\-json
+Display results in JSON format
+.TP
+.B \-k, \-\-keys k,k,k,...
+Display only keys specified. Each key \fBk\fP is of the form \fB[file:]key\fP. If \fB<file>\fP
+is given, the search for the given key is limited to that file. Otherwise the first file containing
+the searched key is being used.
+.TP
+.B \-s, \-\-subject [0-2]
+Specify display of subject/header. '0' means no header at all, '1' prints a header only at start of the program and '2' prints a header every 20 lines.
+.TP
+.B \-w, \-\-width n,n,n,...
+Width for each field.
+.SH USAGE EXAMPLES
+.TP
+.B # lnstat -d
+Get a list of supported statistics files.
+.TP
+.B # lnstat -k arp_cache:entries,rt_cache:in_hit,arp_cache:destroys
+Select the specified files and keys.
+.TP
+.B # lnstat -i 10
+Use an interval of 10 seconds.
+.TP
+.B # lnstat -f ip_conntrack
+Use only the specified file for statistics.
+.TP
+.B # lnstat -s 0
+Do not print a header at all.
+.TP
+.B # lnstat -s 20
+Print a header at start and every 20 lines.
+.TP
+.B # lnstat -c -1 -i 1 -f rt_cache -k entries,in_hit,in_slow_tot
+Display statistics for keys entries, in_hit and in_slow_tot of field rt_cache every second.
+
+.SH FILES
+.TP
+.B /proc/net/stat/arp_cache, /proc/net/stat/ndisc_cache
+Statistics around neighbor cache and ARP. \fBarp_cache\fP is for IPv4, \fBndisc_cache\fP is the same for IPv6.
+.sp
+.B entries
+Number of entries in the neighbor table.
+.sp
+.B allocs
+How many neighbor entries have been allocated.
+.sp
+.B destroys
+How many neighbor entries have been removed.
+.sp
+.B hash_grows
+How often the neighbor (hash) table was increased.
+.sp
+.B lookups
+How many lookups were performed.
+.sp
+.B hits
+How many \fBlookups\fP were successful.
+.sp
+.B res_failed
+How many neighbor lookups failed.
+.sp
+.B rcv_probes_mcast
+How many multicast neighbor solicitations were received. (IPv6 only.)
+.sp
+.B rcv_probes_ucast
+How many unicast neighbor solicitations were received. (IPv6 only.)
+.sp
+.B periodic_gc_runs
+How many garbage collection runs were executed.
+.sp
+.B forced_gc_runs
+How many forced garbage collection runs were executed. Happens when adding an
+entry and the table is too full.
+.sp
+.B unresolved_discards
+How many neighbor table entries were discarded due to lookup failure.
+.sp
+.B table_fulls
+Number of table overflows. Happens if table is full and forced GC run (see
+\fBforced_gc_runs\fP) has failed.
+
+.TP
+.B /proc/net/stat/ip_conntrack, /proc/net/stat/nf_conntrack
+Conntrack related counters. \fBip_conntrack\fP is for backwards compatibility
+with older userspace only and shows the same data as \fBnf_conntrack\fP.
+.sp
+.B entries
+Number of entries in conntrack table.
+.sp
+.B searched
+Number of conntrack table lookups performed.
+.sp
+.B found
+Number of \fBsearched\fP entries which were successful.
+.sp
+.B new
+Number of conntrack entries added which were not expected before.
+.sp
+.B invalid
+Number of packets seen which can not be tracked.
+.sp
+.B ignore
+Number of packets seen which are already connected to a conntrack entry.
+.sp
+.B delete
+Number of conntrack entries which were removed.
+.sp
+.B delete_list
+Number of conntrack entries which were put to dying list.
+.sp
+.B insert
+Number of entries inserted into the list.
+.sp
+.B insert_failed
+Number of entries for which list insertion was attempted but failed (happens if
+the same entry is already present).
+.sp
+.B drop
+Number of packets dropped due to conntrack failure. Either new conntrack entry
+allocation failed, or protocol helper dropped the packet.
+.sp
+.B early_drop
+Number of dropped conntrack entries to make room for new ones, if maximum table
+size was reached.
+.sp
+.B icmp_error
+Number of packets which could not be tracked due to error situation. This is a
+subset of \fBinvalid\fP.
+.sp
+.B expect_new
+Number of conntrack entries added after an expectation for them was already
+present.
+.sp
+.B expect_create
+Number of expectations added.
+.sp
+.B expect_delete
+Number of expectations deleted.
+.sp
+.B search_restart
+Number of conntrack table lookups which had to be restarted due to hashtable
+resizes.
+
+.TP
+.B /proc/net/stat/rt_cache
+Routing cache statistics.
+.sp
+.B entries
+Number of entries in routing cache.
+.sp
+.B in_hit
+Number of route cache hits for incoming packets. Deprecated since IP route
+cache removal, therefore always zero.
+.sp
+.B in_slow_tot
+Number of routing cache entries added for input traffic.
+.sp
+.B in_slow_mc
+Number of multicast routing cache entries added for input traffic.
+.sp
+.B in_no_route
+Number of input packets for which no routing table entry was found.
+.sp
+.B in_brd
+Number of matched input broadcast packets.
+.sp
+.B in_martian_dst
+Number of incoming martian destination packets.
+.sp
+.B in_martian_src
+Number of incoming martian source packets.
+.sp
+.B out_hit
+Number of route cache hits for outgoing packets. Deprecated since IP route
+cache removal, therefore always zero.
+.sp
+.B out_slow_tot
+Number of routing cache entries added for output traffic.
+.sp
+.B out_slow_mc
+Number of multicast routing cache entries added for output traffic.
+.sp
+.B gc_total
+Total number of garbage collection runs. Deprecated since IP route cache
+removal, therefore always zero.
+.sp
+.B gc_ignored
+Number of ignored garbage collection runs due to minimum GC interval not
+reached and routing cache not full. Deprecated since IP route cache removal,
+therefore always zero.
+.sp
+.B gc_goal_miss
+Number of garbage collector goal misses. Deprecated since IP route cache
+removal, therefore always zero.
+.sp
+.B gc_dst_overflow
+Number of destination cache overflows. Deprecated since IP route cache removal,
+therefore always zero.
+.sp
+.B in_hlist_search
+Number of hash table list traversals for input traffic. Deprecated since IP
+route cache removal, therefore always zero.
+.sp
+.B out_hlist_search
+Number of hash table list traversals for output traffic. Deprecated since IP
+route cache removal, therefore always zero.
+
+.SH SEE ALSO
+.BR ip (8)
+.br
+.SH AUTHOR
+lnstat was written by Harald Welte <laforge@gnumonks.org>.
+.PP
+This manual page was written by Michael Prokop <mika@grml.org> for the Debian project (but may be used by others).
diff --git a/man/man8/nstat.8 b/man/man8/nstat.8
new file mode 100644
index 0000000..c703cc8
--- /dev/null
+++ b/man/man8/nstat.8
@@ -0,0 +1 @@
+.so man8/rtacct.8
diff --git a/man/man8/rdma-dev.8 b/man/man8/rdma-dev.8
new file mode 100644
index 0000000..368cdc7
--- /dev/null
+++ b/man/man8/rdma-dev.8
@@ -0,0 +1,98 @@
+.TH RDMA\-DEV 8 "06 Jul 2017" "iproute2" "Linux"
+.SH NAME
+rdma-dev \- RDMA device configuration
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B rdma
+.RI "[ " OPTIONS " ]"
+.B dev
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-d\fR[\fIetails\fR] }
+
+.ti -8
+.B rdma dev show
+.RI "[ " DEV " ]"
+
+.ti -8
+.B rdma dev set
+.RI "[ " DEV " ]"
+.BR name
+.BR NEWNAME
+
+.ti -8
+.B rdma dev set
+.RI "[ " DEV " ]"
+.BR netns
+.BR NSNAME
+
+.ti -8
+.B rdma dev set
+.RI "[ " DEV " ]"
+.BR adaptive-moderation
+.BR [on/off]
+
+.ti -8
+.B rdma dev help
+
+.SH "DESCRIPTION"
+.SS rdma dev set - rename RDMA device or set network namespace or set RDMA device adaptive-moderation
+
+.SS rdma dev show - display RDMA device attributes
+
+.PP
+.I "DEV"
+- specifies the RDMA device to show.
+If this argument is omitted all devices are listed.
+
+.SH "EXAMPLES"
+.PP
+rdma dev
+.RS 4
+Shows the state of all RDMA devices on the system.
+.RE
+.PP
+rdma dev show mlx5_3
+.RS 4
+Shows the state of specified RDMA device.
+.RE
+.PP
+rdma dev set mlx5_3 name rdma_0
+.RS 4
+Renames the mlx5_3 device to rdma_0.
+.RE
+.PP
+rdma dev set mlx5_3 netns foo
+.RS 4
+Changes the network namespace of RDMA device to foo where foo is
+previously created using iproute2 ip command.
+.RE
+.PP
+rdma dev set mlx5_3 adaptive-moderation [on/off]
+.RS 4
+Sets the state of adaptive interrupt moderation for the RDMA device.
+.RE
+.RS 4
+This is a global setting for the RDMA device but the value is printed for each CQ individually because the state is constant from CQ allocation.
+.RE
+.PP
+
+.SH SEE ALSO
+.BR ip (8),
+.BR rdma (8),
+.BR rdma-link (8),
+.BR rdma-resource (8),
+.BR rdma-system (8),
+.BR rdma-statistic (8),
+.br
+
+.SH AUTHOR
+Leon Romanovsky <leonro@mellanox.com>
diff --git a/man/man8/rdma-link.8 b/man/man8/rdma-link.8
new file mode 100644
index 0000000..32f8022
--- /dev/null
+++ b/man/man8/rdma-link.8
@@ -0,0 +1,104 @@
+.TH RDMA\-LINK 8 "06 Jul 2017" "iproute2" "Linux"
+.SH NAME
+rdma-link \- rdma link configuration
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B devlink
+.RI "[ " OPTIONS " ]"
+.B link
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-d\fR[\fIetails\fR] }
+
+.ti -8
+.B rdma link show
+.RI "[ " DEV/PORT_INDEX " ]"
+
+.ti -8
+.B rdma link add
+.BR NAME
+.BR type
+.BR TYPE
+.BR netdev
+.BR NETDEV
+
+.ti -8
+.B rdma link delete
+.RI NAME
+
+.ti -8
+.B rdma link help
+
+.SH "DESCRIPTION"
+.SS rdma link show - display rdma link attributes
+
+.PP
+.I "DEV/PORT_INDEX"
+- specifies the RDMA link to show.
+If this argument is omitted all links are listed.
+
+.SS rdma link add NAME type TYPE netdev NETDEV - add an rdma link for the specified type to the network device
+.sp
+.BR NAME
+- specifies the new name of the rdma link to add
+
+.BR TYPE
+- specifies which rdma type to use. Link types:
+.sp
+.in +8
+.B rxe
+- Soft RoCE driver
+.sp
+.B siw
+- Soft iWARP driver
+.in -8
+
+.BR NETDEV
+- specifies the network device to which the link is bound
+
+.SS rdma link delete NAME - delete an rdma link
+.PP
+.BR NAME
+- specifies the name of the rdma link to delete
+.PP
+
+.SH "EXAMPLES"
+.PP
+rdma link show
+.RS 4
+Shows the state of all rdma links on the system.
+.RE
+.PP
+rdma link show mlx5_2/1
+.RS 4
+Shows the state of specified rdma link.
+.RE
+.PP
+rdma link add rxe_eth0 type rxe netdev eth0
+.RS 4
+Adds a RXE link named rxe_eth0 to network device eth0
+.RE
+.PP
+rdma link del rxe_eth0
+.RS 4
+Removes RXE link rxe_eth0
+.RE
+.PP
+
+.SH SEE ALSO
+.BR rdma (8),
+.BR rdma-dev (8),
+.BR rdma-resource (8),
+.BR rdma-statistic (8),
+.br
+
+.SH AUTHOR
+Leon Romanovsky <leonro@mellanox.com>
diff --git a/man/man8/rdma-resource.8 b/man/man8/rdma-resource.8
new file mode 100644
index 0000000..05030d0
--- /dev/null
+++ b/man/man8/rdma-resource.8
@@ -0,0 +1,110 @@
+.TH RDMA\-RESOURCE 8 "26 Dec 2017" "iproute2" "Linux"
+.SH NAME
+rdma-resource \- rdma resource configuration
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B rdma
+.RI "[ " OPTIONS " ] " RESOURCE " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR RESOURCE " := { "
+.BR cm_id " | " cq " | " mr " | " pd " | " qp " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-j\fR[\fIson\fR] |
+\fB\-d\fR[\fIetails\fR] }
+
+.ti -8
+.B rdma resource show
+.RI "[ " DEV/PORT_INDEX " ]"
+
+.ti -8
+.B rdma resource help
+
+.SH "DESCRIPTION"
+.SS rdma resource show - display rdma resource tracking information
+
+.PP
+.I "DEV/PORT_INDEX"
+- specifies the RDMA link to show.
+If this argument is omitted all links are listed.
+
+.SH "EXAMPLES"
+.PP
+rdma resource show
+.RS 4
+Shows summary for all devices on the system.
+.RE
+.PP
+rdma resource show mlx5_2
+.RS 4
+Shows the state of specified rdma device.
+.RE
+.PP
+rdma res show qp link mlx5_4
+.RS 4
+Get all QPs for the specific device.
+.RE
+.PP
+rdma res show qp link mlx5_4/1
+.RS 4
+Get QPs of specific port.
+.RE
+.PP
+rdma res show qp link mlx5_4/0
+.RS 4
+Provide illegal port number (0 is illegal).
+.RE
+.PP
+rdma res show qp link mlx5_4/-
+.RS 4
+Get QPs which have not assigned port yet.
+.RE
+.PP
+rdma res show qp link mlx5_4/- -d
+.RS 4
+Detailed view.
+.RE
+.PP
+rdma res show qp link mlx5_4/- -dd
+.RS 4
+Detailed view including driver-specific details.
+.RE
+.PP
+rdma res show qp link mlx5_4/1 lqpn 0-6
+.RS 4
+Limit to specific Local QPNs.
+.RE
+.PP
+rdma resource show cm_id dst-port 7174
+.RS 4
+Show CM_IDs with destination ip port of 7174.
+.RE
+.PP
+rdma resource show cm_id src-addr 172.16.0.100
+.RS 4
+Show CM_IDs bound to local ip address 172.16.0.100
+.RE
+.PP
+rdma resource show cq pid 30489
+.RS 4
+Show CQs belonging to pid 30489
+.RE
+.PP
+
+.SH SEE ALSO
+.BR rdma (8),
+.BR rdma-dev (8),
+.BR rdma-link (8),
+.BR rdma-statistic (8),
+.br
+
+.SH AUTHOR
+Leon Romanovsky <leonro@mellanox.com>
diff --git a/man/man8/rdma-statistic.8 b/man/man8/rdma-statistic.8
new file mode 100644
index 0000000..dea6ff2
--- /dev/null
+++ b/man/man8/rdma-statistic.8
@@ -0,0 +1,167 @@
+.TH RDMA\-STATISTIC 8 "17 Mar 2019" "iproute2" "Linux"
+.SH NAME
+rdma-statistic \- RDMA statistic counter configuration
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B rdma
+.RI "[ " OPTIONS " ]"
+.B statistic
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.B rdma statistic
+.RI "[ " OBJECT " ]"
+.B show
+
+.ti -8
+.B rdma statistic
+.RI "[ " OBJECT " ]"
+.B show link
+.RI "[ " DEV/PORT_INDX " ]"
+
+.ti -8
+.B rdma statistic
+.IR OBJECT
+.B mode
+
+.ti -8
+.B rdma statistic
+.IR OBJECT
+.B set
+.IR COUNTER_SCOPE
+.RI "[ " DEV/PORT_INDEX "]"
+.B auto
+.RI "{ " CRITERIA " | "
+.BR off " }"
+
+.ti -8
+.B rdma statistic
+.IR OBJECT
+.B bind
+.IR COUNTER_SCOPE
+.RI "[ " DEV/PORT_INDEX "]"
+.RI "[ " OBJECT-ID " ]"
+.RI "[ " COUNTER-ID " ]"
+
+.ti -8
+.B rdma statistic
+.IR OBJECT
+.B unbind
+.IR COUNTER_SCOPE
+.RI "[ " DEV/PORT_INDEX "]"
+.RI "[ " COUNTER-ID " ]"
+.RI "[ " OBJECT-ID " ]"
+
+.ti -8
+.IR COUNTER_SCOPE " := "
+.RB "{ " link " | " dev " }"
+
+.ti -8
+.IR OBJECT " := "
+.RB "{ " qp " }"
+
+.ti -8
+.IR CRITERIA " := "
+.RB "{ " type " }"
+
+.SH "DESCRIPTION"
+.SS rdma statistic [object] show - Queries the specified RDMA device for RDMA and driver-specific statistics. Show the default hw counters if object is not specified
+
+.PP
+.I "DEV"
+- specifies counters on this RDMA device to show.
+
+.I "PORT_INDEX"
+- specifies counters on this RDMA port to show.
+
+.SS rdma statistic <object> set - configure counter statistic auto-mode for a specific device/port
+In auto mode all objects belong to one category are bind automatically to a single counter set.
+
+.SS rdma statistic <object> bind - manually bind an object (e.g., a qp) with a counter
+When bound the statistics of this object are available in this counter.
+
+.SS rdma statistic <object> unbind - manually unbind an object (e.g., a qp) from the counter previously bound
+When unbound the statistics of this object are no longer available in this counter; And if object id is not specified then all objects on this counter will be unbound.
+
+.I "COUNTER-ID"
+- specifies the id of the counter to be bound.
+If this argument is omitted then a new counter will be allocated.
+
+.SH "EXAMPLES"
+.PP
+rdma statistic show
+.RS 4
+Shows the state of the default counter of all RDMA devices on the system.
+.RE
+.PP
+rdma statistic show link mlx5_2/1
+.RS 4
+Shows the state of the default counter of specified RDMA port
+.RE
+.PP
+rdma statistic qp show
+.RS 4
+Shows the state of all qp counters of all RDMA devices on the system.
+.RE
+.PP
+rdma statistic qp show link mlx5_2/1
+.RS 4
+Shows the state of all qp counters of specified RDMA port.
+.RE
+.PP
+rdma statistic qp show link mlx5_2 pid 30489
+.RS 4
+Shows the state of all qp counters of specified RDMA port and belonging to pid 30489
+.RE
+.PP
+rdma statistic qp mode
+.RS 4
+List current counter mode on all devices
+.RE
+.PP
+rdma statistic qp mode link mlx5_2/1
+.RS 4
+List current counter mode of device mlx5_2 port 1
+.RE
+.PP
+rdma statistic qp set link mlx5_2/1 auto type on
+.RS 4
+On device mlx5_2 port 1, for each new QP bind it with a counter automatically. Per counter for QPs with same qp type in each process. Currently only "type" is supported.
+.RE
+.PP
+rdma statistic qp set link mlx5_2/1 auto off
+.RS 4
+Turn-off auto mode on device mlx5_2 port 1. The allocated counters can be manually accessed.
+.RE
+.PP
+rdma statistic qp bind link mlx5_2/1 lqpn 178
+.RS 4
+On device mlx5_2 port 1, allocate a counter and bind the specified qp on it
+.RE
+.PP
+rdma statistic qp unbind link mlx5_2/1 cntn 4 lqpn 178
+.RS 4
+On device mlx5_2 port 1, bind the specified qp on the specified counter
+.RE
+.PP
+rdma statistic qp unbind link mlx5_2/1 cntn 4
+.RS 4
+On device mlx5_2 port 1, unbind all QPs on the specified counter. After that this counter will be released automatically by the kernel.
+
+.RE
+.PP
+
+.SH SEE ALSO
+.BR rdma (8),
+.BR rdma-dev (8),
+.BR rdma-link (8),
+.BR rdma-resource (8),
+.br
+
+.SH AUTHOR
+Mark Zhang <markz@mellanox.com>
diff --git a/man/man8/rdma-system.8 b/man/man8/rdma-system.8
new file mode 100644
index 0000000..ab1d89f
--- /dev/null
+++ b/man/man8/rdma-system.8
@@ -0,0 +1,82 @@
+.TH RDMA\-SYSTEM 8 "06 Jul 2017" "iproute2" "Linux"
+.SH NAME
+rdma-system \- RDMA subsystem configuration
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B rdma
+.RI "[ " OPTIONS " ]"
+.B sys
+.RI " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-d\fR[\fIetails\fR] }
+
+.ti -8
+.B rdma system show
+
+.ti -8
+.B rdma system set
+.BR netns
+.BR NEWMODE
+
+.ti -8
+.B rdma system help
+
+.SH "DESCRIPTION"
+.SS rdma system set - set RDMA subsystem network namespace mode
+
+.SS rdma system show - display RDMA subsystem network namespace mode
+
+.PP
+.I "NEWMODE"
+- specifies the RDMA subsystem mode. Either exclusive or shared.
+When user wants to assign dedicated RDMA device to a particular
+network namespace, exclusive mode should be set before creating
+any network namespace. If there are active network namespaces and if
+one or more RDMA devices exist, changing mode from shared to
+exclusive returns error code EBUSY.
+
+When RDMA subsystem is in shared mode, RDMA device is accessible in
+all network namespace. When RDMA device isolation among multiple
+network namespaces is not needed, shared mode can be used.
+
+It is preferred to not change the subsystem mode when there is active
+RDMA traffic running, even though it is supported.
+
+.SH "EXAMPLES"
+.PP
+rdma system show
+.RS 4
+Shows the state of RDMA subsystem network namespace mode on the system.
+.RE
+.PP
+rdma system set netns exclusive
+.RS 4
+Sets the RDMA subsystem in network namespace exclusive mode. In this mode RDMA devices
+are visible only in single network namespace.
+.RE
+.PP
+rdma system set netns shared
+.RS 4
+Sets the RDMA subsystem in network namespace shared mode. In this mode RDMA devices
+are shared among network namespaces.
+.RE
+.PP
+
+.SH SEE ALSO
+.BR rdma (8),
+.BR rdma-link (8),
+.BR rdma-resource (8),
+.BR network_namespaces (7),
+.BR namespaces (7),
+.br
+
+.SH AUTHOR
+Parav Pandit <parav@mellanox.com>
diff --git a/man/man8/rdma.8 b/man/man8/rdma.8
new file mode 100644
index 0000000..ef29b1c
--- /dev/null
+++ b/man/man8/rdma.8
@@ -0,0 +1,129 @@
+.TH RDMA 8 "28 Mar 2017" "iproute2" "Linux"
+.SH NAME
+rdma \- RDMA tool
+.SH SYNOPSIS
+.sp
+.ad l
+.in +8
+.ti -8
+.B rdma
+.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+.sp
+
+.ti -8
+.B rdma
+.RB "[ " -force " ] "
+.BI "-batch " filename
+.sp
+
+.ti -8
+.IR OBJECT " := { "
+.BR dev " | " link " | " system " | " statistic " }"
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-V\fR[\fIersion\fR] |
+\fB\-d\fR[\fIetails\fR] }
+\fB\-j\fR[\fIson\fR] }
+\fB\-p\fR[\fIretty\fR] }
+
+.SH OPTIONS
+
+.TP
+.BR "\-V" , " -Version"
+Print the version of the
+.B rdma
+tool and exit.
+
+.TP
+.BR "\-b", " \-batch " <FILENAME>
+Read commands from provided file or standard input and invoke them.
+First failure will cause termination of rdma.
+
+.TP
+.BR "\-force"
+Don't terminate rdma on errors in batch mode.
+If there were any errors during execution of the commands, the application return code will be non zero.
+
+.TP
+.BR "\-d" , " --details"
+Output detailed information. Adding a second \-d includes driver-specific details.
+
+.TP
+.BR "\-p" , " --pretty"
+When combined with -j generate a pretty JSON output.
+
+.TP
+.BR "\-j" , " --json"
+Generate JSON output.
+
+.SS
+.I OBJECT
+
+.TP
+.B dev
+- RDMA device.
+
+.TP
+.B link
+- RDMA port related.
+
+.TP
+.B sys
+- RDMA subsystem related.
+
+.TP
+.B statistic
+- RDMA counter statistic related.
+
+.PP
+The names of all objects may be written in full or
+abbreviated form, for example
+.B stats
+can be abbreviated as
+.B stat
+or just
+.B s.
+
+.SS
+.I COMMAND
+
+Specifies the action to perform on the object.
+The set of possible actions depends on the object type.
+As a rule, it is possible to
+.B show
+(or
+.B list
+) objects, but some objects do not allow all of these operations
+or have some additional commands. The
+.B help
+command is available for all objects. It prints
+out a list of available commands and argument syntax conventions.
+.sp
+If no command is given, some default command is assumed.
+Usually it is
+.B list
+or, if the objects of this class cannot be listed,
+.BR "help" .
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR rdma-dev (8),
+.BR rdma-link (8),
+.BR rdma-resource (8),
+.BR rdma-system (8),
+.BR rdma-statistic (8),
+.br
+
+.SH REPORTING BUGS
+Report any bugs to the Linux RDMA mailing list
+.B <linux-rdma@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Leon Romanovsky <leonro@mellanox.com>
diff --git a/man/man8/routef.8 b/man/man8/routef.8
new file mode 100644
index 0000000..c2a7059
--- /dev/null
+++ b/man/man8/routef.8
@@ -0,0 +1 @@
+.so man8/routel.8
diff --git a/man/man8/routel.8 b/man/man8/routel.8
new file mode 100644
index 0000000..2270eac
--- /dev/null
+++ b/man/man8/routel.8
@@ -0,0 +1,27 @@
+.TH "ROUTEL" "8" "3 Jan, 2008" "iproute2" "Linux"
+.SH "NAME"
+.LP
+routel \- list routes with pretty output format
+.br
+routef \- flush routes
+.SH "SYNTAX"
+.LP
+routel [\fItablenr\fP [\fIraw ip args...\fP]]
+.br
+routef
+.SH "DESCRIPTION"
+.LP
+These programs are a set of helper scripts you can use instead of raw iproute2 commands.
+.br
+The routel script will list routes in a format that some might consider easier to interpret then the ip route list equivalent.
+.br
+The routef script does not take any arguments and will simply flush the routing table down the drain. Beware! This means deleting all routes which will make your network unusable!
+
+.SH "AUTHORS"
+.LP
+The routel script was written by Stephen R. van den Berg <srb@cuci.nl>, 1999/04/18 and donated to the public domain.
+.br
+This manual page was written by Andreas Henriksson <andreas@fatal.se>, for the Debian GNU/Linux system.
+.SH "SEE ALSO"
+.LP
+ip(8)
diff --git a/man/man8/rtacct.8 b/man/man8/rtacct.8
new file mode 100644
index 0000000..ccdbf6c
--- /dev/null
+++ b/man/man8/rtacct.8
@@ -0,0 +1,54 @@
+.TH RTACCT 8 "27 June, 2007"
+
+.SH NAME
+nstat, rtacct - network statistics tools.
+
+.SH SYNOPSIS
+Usage: nstat [ -h?vVzrnasd:t:jp ] [ PATTERN [ PATTERN ] ]
+.br
+Usage: rtacct [ -h?vVzrnasd:t: ] [ ListOfRealms ]
+
+.SH DESCRIPTION
+.B nstat
+and
+.B rtacct
+are simple tools to monitor kernel snmp counters and network interface statistics.
+
+.SH OPTIONS
+.B \-h, \-\-help
+Print help
+.TP
+.B \-V, \-\-version
+Print version
+.TP
+.B \-z, \-\-zeros
+Dump zero counters too. By default they are not shown.
+.TP
+.B \-r, \-\-reset
+Reset history.
+.TP
+.B \-n, \-\-nooutput
+Do not display anything, only update history.
+.TP
+.B \-a, \-\-ignore
+Dump absolute values of counters. The default is to calculate increments since the previous use.
+.TP
+.B \-s, \-\-noupdate
+Do not update history, so that the next time you will see counters including values accumulated to the moment of this measurement too.
+.TP
+.B \-j, \-\-json
+Display results in JSON format.
+.TP
+.B \-p, \-\-pretty
+When combined with
+.BR \-\-json ,
+pretty print the output.
+.TP
+.B \-d, \-\-scan <INTERVAL>
+Run in daemon mode collecting statistics. <INTERVAL> is interval between measurements in seconds.
+.TP
+.B \-t, \-\-interval <INTERVAL>
+Time interval to average rates. Default value is 60 seconds.
+
+.SH SEE ALSO
+lnstat(8)
diff --git a/man/man8/rtmon.8 b/man/man8/rtmon.8
new file mode 100644
index 0000000..38a2b77
--- /dev/null
+++ b/man/man8/rtmon.8
@@ -0,0 +1,68 @@
+.TH RTMON 8
+.SH NAME
+rtmon \- listens to and monitors RTnetlink
+.SH SYNOPSIS
+.B rtmon
+.RI "[ options ] file FILE [ all | LISTofOBJECTS ]"
+.SH DESCRIPTION
+This manual page documents briefly the
+.B rtmon
+command.
+.PP
+.B rtmon
+listens on
+.I netlink
+socket and monitors routing table changes.
+
+.I rtmon
+can be started before the first network configuration command is issued.
+For example if you insert:
+
+.B rtmon file /var/log/rtmon.log
+
+in a startup script, you will be able to view the full history later.
+Certainly, it is possible to start rtmon at any time. It prepends the history with the state snapshot dumped at the moment of starting.
+
+.SH OPTIONS
+.I rtmon supports the following options:
+.TP
+.B \-Version
+Print version and exit.
+.TP
+.B help
+Show summary of options.
+.TP
+.B file FILE [ all | LISTofOBJECTS ]
+Log output to FILE. LISTofOBJECTS is the list of object types that we
+want to monitor. It may contain 'link', 'address', 'route'
+and 'all'. 'link' specifies the network device, 'address' the protocol
+(IP or IPv6) address on a device, 'route' the routing table entry
+and 'all' does what the name says.
+.TP
+.B \-family [ inet | inet6 | link | help ]
+Specify protocol family. 'inet' is IPv4, 'inet6' is IPv6, 'link'
+means that no networking protocol is involved and 'help' prints usage information.
+.TP
+.B \-4
+Use IPv4. Shortcut for -family inet.
+.TP
+.B \-6
+Use IPv6. Shortcut for -family inet6.
+.TP
+.B \-0
+Use a special family identifier meaning that no networking protocol is involved. Shortcut for -family link.
+.SH USAGE EXAMPLES
+.TP
+.B # rtmon file /var/log/rtmon.log
+Log to file /var/log/rtmon.log, then run:
+.TP
+.B # ip monitor file /var/log/rtmon.log
+to display logged output from file.
+.SH SEE ALSO
+.BR ip (8)
+.SH AUTHOR
+.B rtmon
+was written by Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>.
+.PP
+This manual page was written by Michael Prokop <mika@grml.org>,
+for the Debian project (but may be used by others).
diff --git a/man/man8/rtpr.8 b/man/man8/rtpr.8
new file mode 100644
index 0000000..87f291a
--- /dev/null
+++ b/man/man8/rtpr.8
@@ -0,0 +1,25 @@
+.TH RTPR 8 "18 September, 2015"
+
+.SH NAME
+rtpr \- replace backslashes with newlines.
+
+.SH DESCRIPTION
+.B rtpr
+is a trivial shell script which converts backslashes in standard input to newlines. It's sole purpose is to be fed with input from
+.B ip
+when executed with it's
+.B --oneline
+flag.
+
+.SH EXAMPLES
+.TP
+ip --oneline address show | rtpr
+Undo oneline converted
+.B ip-address
+output.
+
+.SH SEE ALSO
+.BR ip (8)
+
+.SH AUTHORS
+Stephen Hemminger <shemming@brocade.com>
diff --git a/man/man8/rtstat.8 b/man/man8/rtstat.8
new file mode 100644
index 0000000..080e2b2
--- /dev/null
+++ b/man/man8/rtstat.8
@@ -0,0 +1 @@
+.so man8/lnstat.8
diff --git a/man/man8/ss.8 b/man/man8/ss.8
new file mode 100644
index 0000000..023d771
--- /dev/null
+++ b/man/man8/ss.8
@@ -0,0 +1,460 @@
+.TH SS 8
+.SH NAME
+ss \- another utility to investigate sockets
+.SH SYNOPSIS
+.B ss
+.RI [ options ] " [ FILTER ]"
+.SH DESCRIPTION
+.B ss
+is used to dump socket statistics. It allows showing information similar
+to
+.IR netstat .
+It can display more TCP and state information than other tools.
+
+.SH OPTIONS
+When no option is used ss displays a list of open non-listening
+sockets (e.g. TCP/UNIX/UDP) that have established connection.
+.TP
+.B \-h, \-\-help
+Show summary of options.
+.TP
+.B \-V, \-\-version
+Output version information.
+.TP
+.B \-H, \-\-no-header
+Suppress header line.
+.TP
+.B \-O, \-\-oneline
+Print each socket's data on a single line.
+.TP
+.B \-n, \-\-numeric
+Do not try to resolve service names. Show exact bandwidth values, instead of human-readable.
+.TP
+.B \-r, \-\-resolve
+Try to resolve numeric address/ports.
+.TP
+.B \-a, \-\-all
+Display both listening and non-listening (for TCP this means
+established connections) sockets.
+.TP
+.B \-l, \-\-listening
+Display only listening sockets (these are omitted by default).
+.TP
+.B \-o, \-\-options
+Show timer information. For TCP protocol, the output format is:
+.RS
+.P
+timer:(<timer_name>,<expire_time>,<retrans>)
+.P
+.TP
+.B <timer_name>
+the name of the timer, there are five kind of timer names:
+.RS
+.P
+.B on
+: means one of these timers: TCP retrans timer, TCP early retrans
+timer and tail loss probe timer
+.P
+.BR keepalive ": tcp keep alive timer"
+.P
+.BR timewait ": timewait stage timer"
+.P
+.BR persist ": zero window probe timer"
+.P
+.BR unknown ": none of the above timers"
+.RE
+.TP
+.B <expire_time>
+how long time the timer will expire
+.P
+.TP
+.B <retrans>
+how many times the retransmission occured
+.RE
+.TP
+.B \-e, \-\-extended
+Show detailed socket information. The output format is:
+.RS
+.P
+uid:<uid_number> ino:<inode_number> sk:<cookie>
+.P
+.TP
+.B <uid_number>
+the user id the socket belongs to
+.P
+.TP
+.B <inode_number>
+the socket's inode number in VFS
+.P
+.TP
+.B <cookie>
+an uuid of the socket
+.RE
+.TP
+.B \-m, \-\-memory
+Show socket memory usage. The output format is:
+.RS
+.P
+skmem:(r<rmem_alloc>,rb<rcv_buf>,t<wmem_alloc>,tb<snd_buf>,
+.br
+.RS
+.RS
+f<fwd_alloc>,w<wmem_queued>,o<opt_mem>,
+.RE
+.RE
+.br
+.RS
+.RS
+bl<back_log>,d<sock_drop>)
+.RE
+.RE
+.P
+.TP
+.B <rmem_alloc>
+the memory allocated for receiving packet
+.P
+.TP
+.B <rcv_buf>
+the total memory can be allocated for receiving packet
+.P
+.TP
+.B <wmem_alloc>
+the memory used for sending packet (which has been sent to layer 3)
+.P
+.TP
+.B <snd_buf>
+the total memory can be allocated for sending packet
+.P
+.TP
+.B <fwd_alloc>
+the memory allocated by the socket as cache, but not used for
+receiving/sending packet yet. If need memory to send/receive packet,
+the memory in this cache will be used before allocate additional
+memory.
+.P
+.TP
+.B <wmem_queued>
+The memory allocated for sending packet (which has not been sent to layer 3)
+.P
+.TP
+.B <ropt_mem>
+The memory used for storing socket option, e.g., the key for TCP MD5 signature
+.P
+.TP
+.B <back_log>
+The memory used for the sk backlog queue. On a process context, if the
+process is receiving packet, and a new packet is received, it will be
+put into the sk backlog queue, so it can be received by the process
+immediately
+.P
+.TP
+.B <sock_drop>
+the number of packets dropped before they are de-multiplexed into the socket
+.RE
+.TP
+.B \-p, \-\-processes
+Show process using socket.
+.TP
+.B \-i, \-\-info
+Show internal TCP information. Below fields may appear:
+.RS
+.P
+.TP
+.B ts
+show string "ts" if the timestamp option is set
+.P
+.TP
+.B sack
+show string "sack" if the sack option is set
+.P
+.TP
+.B ecn
+show string "ecn" if the explicit congestion notification option is set
+.P
+.TP
+.B ecnseen
+show string "ecnseen" if the saw ecn flag is found in received packets
+.P
+.TP
+.B fastopen
+show string "fastopen" if the fastopen option is set
+.P
+.TP
+.B cong_alg
+the congestion algorithm name, the default congestion algorithm is "cubic"
+.P
+.TP
+.B wscale:<snd_wscale>:<rcv_wscale>
+if window scale option is used, this field shows the send scale factor
+and receive scale factor
+.P
+.TP
+.B rto:<icsk_rto>
+tcp re-transmission timeout value, the unit is millisecond
+.P
+.TP
+.B backoff:<icsk_backoff>
+used for exponential backoff re-transmission, the actual
+re-transmission timeout value is icsk_rto << icsk_backoff
+.P
+.TP
+.B rtt:<rtt>/<rttvar>
+rtt is the average round trip time, rttvar is the mean deviation of
+rtt, their units are millisecond
+.P
+.TP
+.B ato:<ato>
+ack timeout, unit is millisecond, used for delay ack mode
+.P
+.TP
+.B mss:<mss>
+max segment size
+.P
+.TP
+.B cwnd:<cwnd>
+congestion window size
+.P
+.TP
+.B pmtu:<pmtu>
+path MTU value
+.P
+.TP
+.B ssthresh:<ssthresh>
+tcp congestion window slow start threshold
+.P
+.TP
+.B bytes_acked:<bytes_acked>
+bytes acked
+.P
+.TP
+.B bytes_received:<bytes_received>
+bytes received
+.P
+.TP
+.B segs_out:<segs_out>
+segments sent out
+.P
+.TP
+.B segs_in:<segs_in>
+segments received
+.P
+.TP
+.B send <send_bps>bps
+egress bps
+.P
+.TP
+.B lastsnd:<lastsnd>
+how long time since the last packet sent, the unit is millisecond
+.P
+.TP
+.B lastrcv:<lastrcv>
+how long time since the last packet received, the unit is millisecond
+.P
+.TP
+.B lastack:<lastack>
+how long time since the last ack received, the unit is millisecond
+.P
+.TP
+.B pacing_rate <pacing_rate>bps/<max_pacing_rate>bps
+the pacing rate and max pacing rate
+.P
+.TP
+.B rcv_space:<rcv_space>
+a helper variable for TCP internal auto tuning socket receive buffer
+.RE
+.TP
+.B \-\-tos
+Show ToS and priority information. Below fields may appear:
+.RS
+.P
+.TP
+.B tos
+IPv4 Type-of-Service byte
+.P
+.TP
+.B tclass
+IPv6 Traffic Class byte
+.P
+.TP
+.B class_id
+Class id set by net_cls cgroup. If class is zero this shows priority
+set by SO_PRIORITY.
+.RE
+.TP
+.B \-K, \-\-kill
+Attempts to forcibly close sockets. This option displays sockets that are
+successfully closed and silently skips sockets that the kernel does not support
+closing. It supports IPv4 and IPv6 sockets only.
+.TP
+.B \-s, \-\-summary
+Print summary statistics. This option does not parse socket lists obtaining
+summary from various sources. It is useful when amount of sockets is so huge
+that parsing /proc/net/tcp is painful.
+.TP
+.B \-E, \-\-events
+Continually display sockets as they are destroyed
+.TP
+.B \-Z, \-\-context
+As the
+.B \-p
+option but also shows process security context.
+.sp
+For
+.BR netlink (7)
+sockets the initiating process context is displayed as follows:
+.RS
+.RS
+.IP "1." 4
+If valid pid show the process context.
+.IP "2." 4
+If destination is kernel (pid = 0) show kernel initial context.
+.IP "3." 4
+If a unique identifier has been allocated by the kernel or netlink user,
+show context as "unavailable". This will generally indicate that a
+process has more than one netlink socket active.
+.RE
+.RE
+.TP
+.B \-z, \-\-contexts
+As the
+.B \-Z
+option but also shows the socket context. The socket context is
+taken from the associated inode and is not the actual socket
+context held by the kernel. Sockets are typically labeled with the
+context of the creating process, however the context shown will reflect
+any policy role, type and/or range transition rules applied,
+and is therefore a useful reference.
+.TP
+.B \-N NSNAME, \-\-net=NSNAME
+Switch to the specified network namespace name.
+.TP
+.B \-b, \-\-bpf
+Show socket BPF filters (only administrators are allowed to get these
+information).
+.TP
+.B \-4, \-\-ipv4
+Display only IP version 4 sockets (alias for -f inet).
+.TP
+.B \-6, \-\-ipv6
+Display only IP version 6 sockets (alias for -f inet6).
+.TP
+.B \-0, \-\-packet
+Display PACKET sockets (alias for -f link).
+.TP
+.B \-t, \-\-tcp
+Display TCP sockets.
+.TP
+.B \-u, \-\-udp
+Display UDP sockets.
+.TP
+.B \-d, \-\-dccp
+Display DCCP sockets.
+.TP
+.B \-w, \-\-raw
+Display RAW sockets.
+.TP
+.B \-x, \-\-unix
+Display Unix domain sockets (alias for -f unix).
+.TP
+.B \-S, \-\-sctp
+Display SCTP sockets.
+.TP
+.B \-\-vsock
+Display vsock sockets (alias for -f vsock).
+.TP
+.B \-\-xdp
+Display XDP sockets (alias for -f xdp).
+.TP
+.B \-f FAMILY, \-\-family=FAMILY
+Display sockets of type FAMILY. Currently the following families are
+supported: unix, inet, inet6, link, netlink, vsock, xdp.
+.TP
+.B \-A QUERY, \-\-query=QUERY, \-\-socket=QUERY
+List of socket tables to dump, separated by commas. The following identifiers
+are understood: all, inet, tcp, udp, raw, unix, packet, netlink, unix_dgram,
+unix_stream, unix_seqpacket, packet_raw, packet_dgram, dccp, sctp,
+vsock_stream, vsock_dgram, xdp Any item in the list may optionally be
+prefixed by an exclamation mark
+.RB ( ! )
+to exclude that socket table from being dumped.
+.TP
+.B \-D FILE, \-\-diag=FILE
+Do not display anything, just dump raw information about TCP sockets
+to FILE after applying filters. If FILE is - stdout is used.
+.TP
+.B \-F FILE, \-\-filter=FILE
+Read filter information from FILE. Each line of FILE is interpreted
+like single command line option. If FILE is - stdin is used.
+.TP
+.B FILTER := [ state STATE-FILTER ] [ EXPRESSION ]
+Please take a look at the official documentation for details regarding filters.
+
+.SH STATE-FILTER
+
+.B STATE-FILTER
+allows to construct arbitrary set of states to match. Its syntax is
+sequence of keywords state and exclude followed by identifier of
+state.
+.TP
+Available identifiers are:
+
+All standard TCP states:
+.BR established ", " syn-sent ", " syn-recv ", " fin-wait-1 ", " fin-wait-2 ", " time-wait ", " closed ", " close-wait ", " last-ack ", "
+.BR listening " and " closing.
+
+.B all
+- for all the states
+
+.B connected
+- all the states except for
+.BR listening " and " closed
+
+.B synchronized
+- all the
+.B connected
+states except for
+.B syn-sent
+
+.B bucket
+- states, which are maintained as minisockets, i.e.
+.BR time-wait " and " syn-recv
+
+.B big
+- opposite to
+.B bucket
+
+.SH USAGE EXAMPLES
+.TP
+.B ss -t -a
+Display all TCP sockets.
+.TP
+.B ss -t -a -Z
+Display all TCP sockets with process SELinux security contexts.
+.TP
+.B ss -u -a
+Display all UDP sockets.
+.TP
+.B ss -o state established '( dport = :ssh or sport = :ssh )'
+Display all established ssh connections.
+.TP
+.B ss -x src /tmp/.X11-unix/*
+Find all local processes connected to X server.
+.TP
+.B ss -o state fin-wait-1 '( sport = :http or sport = :https )' dst 193.233.7/24
+List all the tcp sockets in state FIN-WAIT-1 for our apache to network
+193.233.7/24 and look at their timers.
+.TP
+.B ss -a -A 'all,!tcp'
+List sockets in all states from all socket tables but TCP.
+.SH SEE ALSO
+.BR ip (8),
+.br
+.BR RFC " 793 "
+- https://tools.ietf.org/rfc/rfc793.txt (TCP states)
+
+.SH AUTHOR
+.I ss
+was written by Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>.
+.PP
+This manual page was written by Michael Prokop <mika@grml.org>
+for the Debian project (but may be used by others).
diff --git a/man/man8/tc-actions.8 b/man/man8/tc-actions.8
new file mode 100644
index 0000000..f46166e
--- /dev/null
+++ b/man/man8/tc-actions.8
@@ -0,0 +1,244 @@
+.TH "actions in tc" 8 "1 Aug 2017" "iproute2" "Linux"
+
+.SH NAME
+actions \- independently defined actions in tc
+.SH SYNOPSIS
+.B tc
+[
+.I TC_OPTIONS
+]
+.B actions
+.BR add " | " change " | " replace
+.I ACTSPEC
+
+.B tc
+[
+.I TC_OPTIONS
+]
+.B actions
+.BR get " | " delete
+.I ACTISPEC
+
+.B tc
+[
+.I TC_OPTIONS
+]
+.B actions flush
+.I ACTNAMESPEC
+
+.B tc
+[
+.I TC_OPTIONS
+]
+.B actions
+.BR ls " | " list
+.I ACTNAMESPEC
+[
+.I ACTFILTER
+]
+
+.in +8
+.I ACTSPEC
+:=
+.B action
+.I ACTDETAIL
+[
+.I INDEXSPEC
+] [
+.I COOKIESPEC
+] [
+.I CONTROL
+]
+
+.I ACTISPEC
+:=
+.I ACTNAMESPEC INDEXSPEC
+
+.I ACTNAMESPEC
+:=
+.B action
+ACTNAME
+
+.I INDEXSPEC
+:=
+.BI index " INDEX"
+
+.I ACTFILTER
+:=
+.BI since " MSTIME"
+
+.I COOKIESPEC
+:=
+.BI cookie " COOKIE"
+
+.I ACTDETAIL
+:=
+.I ACTNAME ACTPARAMS
+
+.I ACTNAME
+may be any valid action type: gact, mirred, bpf, connmark, csum, police, etc.
+
+.I MSTIME
+Time since last update.
+
+.I CONTROL
+:= {
+.IR reclassify " | " pipe " | " drop " | " continue " | " ok
+}
+
+.I TC_OPTIONS
+These are the options that are specific to
+.B tc
+and not only the options. Refer to
+.BR tc(8)
+for more information.
+.in
+
+.SH DESCRIPTION
+
+The
+.B actions
+object in
+.B tc
+allows a user to define actions independently of a classifier (filter). These
+actions can then be assigned to one or more filters, with any
+packets matching the classifier's criteria having that action performed
+on them.
+
+Each action type (mirred, police, etc.) will have its own table to store
+all created actions.
+
+.SH OPERATIONS
+.TP
+.B add
+Create a new action in that action's table.
+
+.TP
+.B change
+.TQ
+.B replace
+Make modifications to an existing action.
+.TP
+.B get
+Display the action with the specified index value. When combined with the
+.B -s
+option for
+.BR tc ","
+display the statistics for that action.
+.TP
+.B delete
+Delete the action with the specified index value. If the action is already
+associated with a classifier, it does not delete the classifier.
+.TP
+.B ls
+.TQ
+.B list
+List all the actions in the specified table. When combined with the
+.B -s
+option for
+.BR tc ","
+display the statistics for all actions in the specified table.
+When combined with the option
+.B since
+allows doing a millisecond time-filter since the last time an
+action was used in the datapath.
+.TP
+.B flush
+Delete all actions stored in the specified table.
+
+.SH ACTION OPTIONS
+Note that these options are available to all action types.
+.TP
+.BI index " INDEX"
+Specify the table index value of an action.
+.I INDEX
+is a 32-bit value that is unique to the specific type of action referenced.
+
+.RS
+For
+.BR add ", " change ", and"
+.B replace
+operations, the index is
+.BR optional.
+When adding a new action,
+specifying an index value will assign the action to that index unless that
+index value has already been assigned. Omitting the index value for an add
+operation will cause the kernel to assign a value to the new action.
+.RE
+
+.RS
+For
+.BR get " and " delete
+operations, the index is
+.B required
+to identify the specific action to be displayed or deleted.
+.RE
+
+.TP
+.BI cookie " COOKIE"
+In addition to the specific action, mark the matching packet with the value
+specified by
+.IR COOKIE "."
+The
+.I COOKIE
+is a 128-bit value that will not be interpreted by the kernel whatsoever.
+As such, it can be used as a correlating value for maintaining user state.
+The value to be stored is completely arbitrary and does not require a specific
+format. It is stored inside the action structure itself.
+
+.TP
+.BI since " MSTIME"
+When dumping large number of actions, a millisecond time-filter can be
+specified
+.IR MSTIME "."
+The
+.I MSTIME
+is a millisecond count since last time a packet hit the action.
+As an example specifying "since 20000" implies to dump all actions
+that have seen packets in the last 20 seconds. This option is useful
+when the kernel has a large number of actions and you are only interested
+in recently used actions.
+
+.TP
+.I CONTROL
+The
+.I CONTROL
+indicates how
+.B tc
+should proceed after executing the action. Any of the following are valid:
+.RS
+.TP
+.B reclassify
+Restart the classifiction by jumping back to the first filter attached to
+the action's parent.
+.TP
+.B pipe
+Continue with the next action. This is the default control.
+.TP
+.B drop
+Drop the packed without running any further actions.
+.TP
+.B continue
+Continue the classification with the next filter.
+.TP
+.B pass
+Return to the calling qdisc for packet processing, and end classification of
+this packet.
+.RE
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-bpf (8),
+.BR tc-connmark (8),
+.BR tc-csum (8),
+.BR tc-ife (8),
+.BR tc-mirred (8),
+.BR tc-nat (8),
+.BR tc-pedit (8),
+.BR tc-police (8),
+.BR tc-simple (8),
+.BR tc-skbedit (8),
+.BR tc-skbmod (8),
+.BR tc-tunnel_key (8),
+.BR tc-vlan (8),
+.BR tc-xt (8)
diff --git a/man/man8/tc-basic.8 b/man/man8/tc-basic.8
new file mode 100644
index 0000000..fb39eaa
--- /dev/null
+++ b/man/man8/tc-basic.8
@@ -0,0 +1,34 @@
+.TH "Basic classifier in tc" 8 "21 Oct 2015" "iproute2" "Linux"
+
+.SH NAME
+basic \- basic traffic control filter
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " " filter " ... " basic " [ " match
+.IR EMATCH_TREE " ] [ "
+.B action
+.IR ACTION_SPEC " ] [ "
+.B classid
+.IR CLASSID " ]"
+.SH DESCRIPTION
+The
+.B basic
+filter allows to classify packets using the extended match infrastructure.
+.SH OPTIONS
+.TP
+.BI action " ACTION_SPEC"
+Apply an action from the generic actions framework on matching packets.
+.TP
+.BI classid " CLASSID"
+Push matching packets into the class identified by
+.IR CLASSID .
+.TP
+.BI match " EMATCH_TREE"
+Match packets using the extended match infrastructure. See
+.BR tc-ematch (8)
+for a detailed description of the allowed syntax in
+.IR EMATCH_TREE .
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-ematch (8)
diff --git a/man/man8/tc-bfifo.8 b/man/man8/tc-bfifo.8
new file mode 100644
index 0000000..3e29032
--- /dev/null
+++ b/man/man8/tc-bfifo.8
@@ -0,0 +1,74 @@
+.TH PBFIFO 8 "10 January 2002" "iproute2" "Linux"
+.SH NAME
+pfifo \- Packet limited First In, First Out queue
+.P
+bfifo \- Byte limited First In, First Out queue
+
+.SH SYNOPSIS
+.B tc qdisc ... add pfifo
+.B [ limit
+packets
+.B ]
+.P
+.B tc qdisc ... add bfifo
+.B [ limit
+bytes
+.B ]
+
+.SH DESCRIPTION
+The pfifo and bfifo qdiscs are unadorned First In, First Out queues. They are the
+simplest queues possible and therefore have no overhead.
+.B pfifo
+constrains the queue size as measured in packets.
+.B bfifo
+does so as measured in bytes.
+
+Like all non-default qdiscs, they maintain statistics. This might be a reason to prefer
+pfifo or bfifo over the default.
+
+.SH ALGORITHM
+A list of packets is maintained, when a packet is enqueued it gets inserted at the tail of
+a list. When a packet needs to be sent out to the network, it is taken from the head of the list.
+
+If the list is too long, no further packets are allowed on. This is called 'tail drop'.
+
+.SH PARAMETERS
+.TP
+limit
+Maximum queue size. Specified in bytes for bfifo, in packets for pfifo. For pfifo, defaults
+to the interface txqueuelen, as specified with
+.BR ifconfig (8)
+or
+.BR ip (8).
+The range for this parameter is [0, UINT32_MAX].
+
+For bfifo, it defaults to the txqueuelen multiplied by the interface MTU.
+The range for this parameter is [0, UINT32_MAX] bytes.
+
+Note: The link layer header was considered when counting packets length.
+
+.SH OUTPUT
+The output of
+.B tc -s qdisc ls
+contains the limit, either in packets or in bytes, and the number of bytes
+and packets actually sent. An unsent and dropped packet only appears between braces
+and is not counted as 'Sent'.
+
+In this example, the queue length is 100 packets, 45894 bytes were sent over 681 packets.
+No packets were dropped, and as the pfifo queue does not slow down packets, there were also no
+overlimits:
+.P
+.nf
+# tc -s qdisc ls dev eth0
+qdisc pfifo 8001: dev eth0 limit 100p
+ Sent 45894 bytes 681 pkts (dropped 0, overlimits 0)
+.fi
+
+If a backlog occurs, this is displayed as well.
+.SH SEE ALSO
+.BR tc (8)
+
+.SH AUTHORS
+Alexey N. Kuznetsov, <kuznet@ms2.inr.ac.ru>
+
+This manpage maintained by bert hubert <ahu@ds9a.nl>
diff --git a/man/man8/tc-bpf.8 b/man/man8/tc-bpf.8
new file mode 100644
index 0000000..e4f68aa
--- /dev/null
+++ b/man/man8/tc-bpf.8
@@ -0,0 +1,986 @@
+.TH "BPF classifier and actions in tc" 8 "18 May 2015" "iproute2" "Linux"
+.SH NAME
+BPF \- BPF programmable classifier and actions for ingress/egress
+queueing disciplines
+.SH SYNOPSIS
+.SS eBPF classifier (filter) or action:
+.B tc filter ... bpf
+[
+.B object-file
+OBJ_FILE ] [
+.B section
+CLS_NAME ] [
+.B export
+UDS_FILE ] [
+.B verbose
+] [
+.B direct-action
+|
+.B da
+] [
+.B skip_hw
+|
+.B skip_sw
+] [
+.B police
+POLICE_SPEC ] [
+.B action
+ACTION_SPEC ] [
+.B classid
+CLASSID ]
+.br
+.B tc action ... bpf
+[
+.B object-file
+OBJ_FILE ] [
+.B section
+CLS_NAME ] [
+.B export
+UDS_FILE ] [
+.B verbose
+]
+
+.SS cBPF classifier (filter) or action:
+.B tc filter ... bpf
+[
+.B bytecode-file
+BPF_FILE |
+.B bytecode
+BPF_BYTECODE ] [
+.B police
+POLICE_SPEC ] [
+.B action
+ACTION_SPEC ] [
+.B classid
+CLASSID ]
+.br
+.B tc action ... bpf
+[
+.B bytecode-file
+BPF_FILE |
+.B bytecode
+BPF_BYTECODE ]
+
+.SH DESCRIPTION
+
+Extended Berkeley Packet Filter (
+.B eBPF
+) and classic Berkeley Packet Filter
+(originally known as BPF, for better distinction referred to as
+.B cBPF
+here) are both available as a fully programmable and highly efficient
+classifier and actions. They both offer a minimal instruction set for
+implementing small programs which can safely be loaded into the kernel
+and thus executed in a tiny virtual machine from kernel space. An in-kernel
+verifier guarantees that a specified program always terminates and neither
+crashes nor leaks data from the kernel.
+
+In Linux, it's generally considered that eBPF is the successor of cBPF.
+The kernel internally transforms cBPF expressions into eBPF expressions and
+executes the latter. Execution of them can be performed in an interpreter
+or at setup time, they can be just-in-time compiled (JIT'ed) to run as
+native machine code.
+.PP
+Currently, the eBPF JIT compiler is available for the following architectures:
+.IP * 4
+x86_64 (since Linux 3.18)
+.PD 0
+.IP *
+arm64 (since Linux 3.18)
+.IP *
+s390 (since Linux 4.1)
+.IP *
+ppc64 (since Linux 4.8)
+.IP *
+sparc64 (since Linux 4.12)
+.IP *
+mips64 (since Linux 4.13)
+.IP *
+arm32 (since Linux 4.14)
+.IP *
+x86_32 (since Linux 4.18)
+.PD
+.PP
+Whereas the following architectures have cBPF, but did not (yet) switch to eBPF
+JIT support:
+.IP * 4
+ppc32
+.PD 0
+.IP *
+sparc32
+.IP *
+mips32
+.PD
+.PP
+eBPF's instruction set has similar underlying principles as the cBPF
+instruction set, it however is modelled closer to the underlying
+architecture to better mimic native instruction sets with the aim to
+achieve a better run-time performance. It is designed to be JIT'ed with
+a one to one mapping, which can also open up the possibility for compilers
+to generate optimized eBPF code through an eBPF backend that performs
+almost as fast as natively compiled code. Given that LLVM provides such
+an eBPF backend, eBPF programs can therefore easily be programmed in a
+subset of the C language. Other than that, eBPF infrastructure also comes
+with a construct called "maps". eBPF maps are key/value stores that are
+shared between multiple eBPF programs, but also between eBPF programs and
+user space applications.
+
+For the traffic control subsystem, classifier and actions that can be
+attached to ingress and egress qdiscs can be written in eBPF or cBPF. The
+advantage over other classifier and actions is that eBPF/cBPF provides the
+generic framework, while users can implement their highly specialized use
+cases efficiently. This means that the classifier or action written that
+way will not suffer from feature bloat, and can therefore execute its task
+highly efficient. It allows for non-linear classification and even merging
+the action part into the classification. Combined with efficient eBPF map
+data structures, user space can push new policies like classids into the
+kernel without reloading a classifier, or it can gather statistics that
+are pushed into one map and use another one for dynamically load balancing
+traffic based on the determined load, just to provide a few examples.
+
+.SH PARAMETERS
+.SS object-file
+points to an object file that has an executable and linkable format (ELF)
+and contains eBPF opcodes and eBPF map definitions. The LLVM compiler
+infrastructure with
+.B clang(1)
+as a C language front end is one project that supports emitting eBPF object
+files that can be passed to the eBPF classifier (more details in the
+.B EXAMPLES
+section). This option is mandatory when an eBPF classifier or action is
+to be loaded.
+
+.SS section
+is the name of the ELF section from the object file, where the eBPF
+classifier or action resides. By default the section name for the
+classifier is called "classifier", and for the action "action". Given
+that a single object file can contain multiple classifier and actions,
+the corresponding section name needs to be specified, if it differs
+from the defaults.
+
+.SS export
+points to a Unix domain socket file. In case the eBPF object file also
+contains a section named "maps" with eBPF map specifications, then the
+map file descriptors can be handed off via the Unix domain socket to
+an eBPF "agent" herding all descriptors after tc lifetime. This can be
+some third party application implementing the IPC counterpart for the
+import, that uses them for calling into
+.B bpf(2)
+system call to read out or update eBPF map data from user space, for
+example, for monitoring purposes or to push down new policies.
+
+.SS verbose
+if set, it will dump the eBPF verifier output, even if loading the eBPF
+program was successful. By default, only on error, the verifier log is
+being emitted to the user.
+
+.SS direct-action | da
+instructs eBPF classifier to not invoke external TC actions, instead use the
+TC actions return codes (\fBTC_ACT_OK\fR, \fBTC_ACT_SHOT\fR etc.) for
+classifiers.
+
+.SS skip_hw | skip_sw
+hardware offload control flags. By default TC will try to offload
+filters to hardware if possible.
+.B skip_hw
+explicitly disables the attempt to offload.
+.B skip_sw
+forces the offload and disables running the eBPF program in the kernel.
+If hardware offload is not possible and this flag was set kernel will
+report an error and filter will not be installed at all.
+
+.SS police
+is an optional parameter for an eBPF/cBPF classifier that specifies a
+police in
+.B tc(1)
+which is attached to the classifier, for example, on an ingress qdisc.
+
+.SS action
+is an optional parameter for an eBPF/cBPF classifier that specifies a
+subsequent action in
+.B tc(1)
+which is attached to a classifier.
+
+.SS classid
+.SS flowid
+provides the default traffic control class identifier for this eBPF/cBPF
+classifier. The default class identifier can also be overwritten by the
+return code of the eBPF/cBPF program. A default return code of
+.B -1
+specifies the here provided default class identifier to be used. A return
+code of the eBPF/cBPF program of 0 implies that no match took place, and
+a return code other than these two will override the default classid. This
+allows for efficient, non-linear classification with only a single eBPF/cBPF
+program as opposed to having multiple individual programs for various class
+identifiers which would need to reparse packet contents.
+
+.SS bytecode
+is being used for loading cBPF classifier and actions only. The cBPF bytecode
+is directly passed as a text string in the form of
+.B \'s,c t f k,c t f k,c t f k,...\'
+, where
+.B s
+denotes the number of subsequent 4-tuples. One such 4-tuple consists of
+.B c t f k
+decimals, where
+.B c
+represents the cBPF opcode,
+.B t
+the jump true offset target,
+.B f
+the jump false offset target and
+.B k
+the immediate constant/literal. There are various tools that generate code
+in this loadable format, for example,
+.B bpf_asm
+that ships with the Linux kernel source tree under
+.B tools/net/
+, so it is certainly not expected to hack this by hand. The
+.B bytecode
+or
+.B bytecode-file
+option is mandatory when a cBPF classifier or action is to be loaded.
+
+.SS bytecode-file
+also being used to load a cBPF classifier or action. It's effectively the
+same as
+.B bytecode
+only that the cBPF bytecode is not passed directly via command line, but
+rather resides in a text file.
+
+.SH EXAMPLES
+.SS eBPF TOOLING
+A full blown example including eBPF agent code can be found inside the
+iproute2 source package under:
+.B examples/bpf/
+
+As prerequisites, the kernel needs to have the eBPF system call namely
+.B bpf(2)
+enabled and ships with
+.B cls_bpf
+and
+.B act_bpf
+kernel modules for the traffic control subsystem. To enable eBPF/eBPF JIT
+support, depending which of the two the given architecture supports:
+
+.in +4n
+.B echo 1 > /proc/sys/net/core/bpf_jit_enable
+.in
+
+A given restricted C file can be compiled via LLVM as:
+
+.in +4n
+.B clang -O2 -emit-llvm -c bpf.c -o - | llc -march=bpf -filetype=obj -o bpf.o
+.in
+
+The compiler invocation might still simplify in future, so for now,
+it's quite handy to alias this construct in one way or another, for
+example:
+.in +4n
+.nf
+.sp
+__bcc() {
+ clang -O2 -emit-llvm -c $1 -o - | \\
+ llc -march=bpf -filetype=obj -o "`basename $1 .c`.o"
+}
+
+alias bcc=__bcc
+.fi
+.in
+
+A minimal, stand-alone unit, which matches on all traffic with the
+default classid (return code of -1) looks like:
+
+.in +4n
+.nf
+.sp
+#include <linux/bpf.h>
+
+#ifndef __section
+# define __section(x) __attribute__((section(x), used))
+#endif
+
+__section("classifier") int cls_main(struct __sk_buff *skb)
+{
+ return -1;
+}
+
+char __license[] __section("license") = "GPL";
+.fi
+.in
+
+More examples can be found further below in subsection
+.B eBPF PROGRAMMING
+as focus here will be on tooling.
+
+There can be various other sections, for example, also for actions.
+Thus, an object file in eBPF can contain multiple entrance points.
+Always a specific entrance point, however, must be specified when
+configuring with tc. A license must be part of the restricted C code
+and the license string syntax is the same as with Linux kernel modules.
+The kernel reserves its right that some eBPF helper functions can be
+restricted to GPL compatible licenses only, and thus may reject a program
+from loading into the kernel when such a license mismatch occurs.
+
+The resulting object file from the compilation can be inspected with
+the usual set of tools that also operate on normal object files, for
+example
+.B objdump(1)
+for inspecting ELF section headers:
+
+.in +4n
+.nf
+.sp
+objdump -h bpf.o
+[...]
+3 classifier 000007f8 0000000000000000 0000000000000000 00000040 2**3
+ CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+4 action-mark 00000088 0000000000000000 0000000000000000 00000838 2**3
+ CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+5 action-rand 00000098 0000000000000000 0000000000000000 000008c0 2**3
+ CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+6 maps 00000030 0000000000000000 0000000000000000 00000958 2**2
+ CONTENTS, ALLOC, LOAD, DATA
+7 license 00000004 0000000000000000 0000000000000000 00000988 2**0
+ CONTENTS, ALLOC, LOAD, DATA
+[...]
+.fi
+.in
+
+Adding an eBPF classifier from an object file that contains a classifier
+in the default ELF section is trivial (note that instead of "object-file"
+also shortcuts such as "obj" can be used):
+
+.in +4n
+.B bcc bpf.c
+.br
+.B tc filter add dev em1 parent 1: bpf obj bpf.o flowid 1:1
+.in
+
+In case the classifier resides in ELF section "mycls", then that same
+command needs to be invoked as:
+
+.in +4n
+.B tc filter add dev em1 parent 1: bpf obj bpf.o sec mycls flowid 1:1
+.in
+
+Dumping the classifier configuration will tell the location of the
+classifier, in other words that it's from object file "bpf.o" under
+section "mycls":
+
+.in +4n
+.B tc filter show dev em1
+.br
+.B filter parent 1: protocol all pref 49152 bpf
+.br
+.B filter parent 1: protocol all pref 49152 bpf handle 0x1 flowid 1:1 bpf.o:[mycls]
+.in
+
+The same program can also be installed on ingress qdisc side as opposed
+to egress ...
+
+.in +4n
+.B tc qdisc add dev em1 handle ffff: ingress
+.br
+.B tc filter add dev em1 parent ffff: bpf obj bpf.o sec mycls flowid ffff:1
+.in
+
+\&... and again dumped from there:
+
+.in +4n
+.B tc filter show dev em1 parent ffff:
+.br
+.B filter protocol all pref 49152 bpf
+.br
+.B filter protocol all pref 49152 bpf handle 0x1 flowid ffff:1 bpf.o:[mycls]
+.in
+
+Attaching a classifier and action on ingress has the restriction that
+it doesn't have an actual underlying queueing discipline. What ingress
+can do is to classify, mangle, redirect or drop packets. When queueing
+is required on ingress side, then ingress must redirect packets to the
+.B ifb
+device, otherwise policing can be used. Moreover, ingress can be used to
+have an early drop point of unwanted packets before they hit upper layers
+of the networking stack, perform network accounting with eBPF maps that
+could be shared with egress, or have an early mangle and/or redirection
+point to different networking devices.
+
+Multiple eBPF actions and classifier can be placed into a single
+object file within various sections. In that case, non-default section
+names must be provided, which is the case for both actions in this
+example:
+
+.in +4n
+.B tc filter add dev em1 parent 1: bpf obj bpf.o flowid 1:1 \e
+.br
+.in +25n
+.B action bpf obj bpf.o sec action-mark \e
+.br
+.B action bpf obj bpf.o sec action-rand ok
+.in -25n
+.in -4n
+
+The advantage of this is that the classifier and the two actions can
+then share eBPF maps with each other, if implemented in the programs.
+
+In order to access eBPF maps from user space beyond
+.B tc(8)
+setup lifetime, the ownership can be transferred to an eBPF agent via
+Unix domain sockets. There are two possibilities for implementing this:
+
+.B 1)
+implementation of an own eBPF agent that takes care of setting up
+the Unix domain socket and implementing the protocol that
+.B tc(8)
+dictates. A code example of this can be found inside the iproute2
+source package under:
+.B examples/bpf/
+
+.B 2)
+use
+.B tc exec
+for transferring the eBPF map file descriptors through a Unix domain
+socket, and spawning an application such as
+.B sh(1)
+\&. This approach's advantage is that tc will place the file descriptors
+into the environment and thus make them available just like stdin, stdout,
+stderr file descriptors, meaning, in case user applications run from within
+this fd-owner shell, they can terminate and restart without losing eBPF
+maps file descriptors. Example invocation with the previous classifier and
+action mixture:
+
+.in +4n
+.B tc exec bpf imp /tmp/bpf
+.br
+.B tc filter add dev em1 parent 1: bpf obj bpf.o exp /tmp/bpf flowid 1:1 \e
+.br
+.in +25n
+.B action bpf obj bpf.o sec action-mark \e
+.br
+.B action bpf obj bpf.o sec action-rand ok
+.in -25n
+.in -4n
+
+Assuming that eBPF maps are shared with classifier and actions, it's
+enough to export them once, for example, from within the classifier
+or action command. tc will setup all eBPF map file descriptors at the
+time when the object file is first parsed.
+
+When a shell has been spawned, the environment will have a couple of
+eBPF related variables. BPF_NUM_MAPS provides the total number of maps
+that have been transferred over the Unix domain socket. BPF_MAP<X>'s
+value is the file descriptor number that can be accessed in eBPF agent
+applications, in other words, it can directly be used as the file
+descriptor value for the
+.B bpf(2)
+system call to retrieve or alter eBPF map values. <X> denotes the
+identifier of the eBPF map. It corresponds to the
+.B id
+member of
+.B struct bpf_elf_map
+\& from the tc eBPF map specification.
+
+The environment in this example looks as follows:
+
+.in +4n
+.nf
+.sp
+sh# env | grep BPF
+ BPF_NUM_MAPS=3
+ BPF_MAP1=6
+ BPF_MAP0=5
+ BPF_MAP2=7
+sh# ls -la /proc/self/fd
+ [...]
+ lrwx------. 1 root root 64 Apr 14 16:46 5 -> anon_inode:bpf-map
+ lrwx------. 1 root root 64 Apr 14 16:46 6 -> anon_inode:bpf-map
+ lrwx------. 1 root root 64 Apr 14 16:46 7 -> anon_inode:bpf-map
+sh# my_bpf_agent
+.fi
+.in
+
+eBPF agents are very useful in that they can prepopulate eBPF maps from
+user space, monitor statistics via maps and based on that feedback, for
+example, rewrite classids in eBPF map values during runtime. Given that eBPF
+agents are implemented as normal applications, they can also dynamically
+receive traffic control policies from external controllers and thus push
+them down into eBPF maps to dynamically adapt to network conditions. Moreover,
+eBPF maps can also be shared with other eBPF program types (e.g. tracing),
+thus very powerful combination can therefore be implemented.
+
+.SS eBPF PROGRAMMING
+
+eBPF classifier and actions are being implemented in restricted C syntax
+(in future, there could additionally be new language frontends supported).
+
+The header file
+.B linux/bpf.h
+provides eBPF helper functions that can be called from an eBPF program.
+This man page will only provide two minimal, stand-alone examples, have a
+look at
+.B examples/bpf
+from the iproute2 source package for a fully fledged flow dissector
+example to better demonstrate some of the possibilities with eBPF.
+
+Supported 32 bit classifier return codes from the C program and their meanings:
+.in +4n
+.B 0
+, denotes a mismatch
+.br
+.B -1
+, denotes the default classid configured from the command line
+.br
+.B else
+, everything else will override the default classid to provide a facility for
+non-linear matching
+.in
+
+Supported 32 bit action return codes from the C program and their meanings (
+.B linux/pkt_cls.h
+):
+.in +4n
+.B TC_ACT_OK (0)
+, will terminate the packet processing pipeline and allows the packet to
+proceed
+.br
+.B TC_ACT_SHOT (2)
+, will terminate the packet processing pipeline and drops the packet
+.br
+.B TC_ACT_UNSPEC (-1)
+, will use the default action configured from tc (similarly as returning
+.B -1
+from a classifier)
+.br
+.B TC_ACT_PIPE (3)
+, will iterate to the next action, if available
+.br
+.B TC_ACT_RECLASSIFY (1)
+, will terminate the packet processing pipeline and start classification
+from the beginning
+.br
+.B else
+, everything else is an unspecified return code
+.in
+
+Both classifier and action return codes are supported in eBPF and cBPF
+programs.
+
+To demonstrate restricted C syntax, a minimal toy classifier example is
+provided, which assumes that egress packets, for instance originating
+from a container, have previously been marked in interval [0, 255]. The
+program keeps statistics on different marks for user space and maps the
+classid to the root qdisc with the marking itself as the minor handle:
+
+.in +4n
+.nf
+.sp
+#include <stdint.h>
+#include <asm/types.h>
+
+#include <linux/bpf.h>
+#include <linux/pkt_sched.h>
+
+#include "helpers.h"
+
+struct tuple {
+ long packets;
+ long bytes;
+};
+
+#define BPF_MAP_ID_STATS 1 /* agent's map identifier */
+#define BPF_MAX_MARK 256
+
+struct bpf_elf_map __section("maps") map_stats = {
+ .type = BPF_MAP_TYPE_ARRAY,
+ .id = BPF_MAP_ID_STATS,
+ .size_key = sizeof(uint32_t),
+ .size_value = sizeof(struct tuple),
+ .max_elem = BPF_MAX_MARK,
+ .pinning = PIN_GLOBAL_NS,
+};
+
+static inline void cls_update_stats(const struct __sk_buff *skb,
+ uint32_t mark)
+{
+ struct tuple *tu;
+
+ tu = bpf_map_lookup_elem(&map_stats, &mark);
+ if (likely(tu)) {
+ __sync_fetch_and_add(&tu->packets, 1);
+ __sync_fetch_and_add(&tu->bytes, skb->len);
+ }
+}
+
+__section("cls") int cls_main(struct __sk_buff *skb)
+{
+ uint32_t mark = skb->mark;
+
+ if (unlikely(mark >= BPF_MAX_MARK))
+ return 0;
+
+ cls_update_stats(skb, mark);
+
+ return TC_H_MAKE(TC_H_ROOT, mark);
+}
+
+char __license[] __section("license") = "GPL";
+.fi
+.in
+
+Another small example is a port redirector which demuxes destination port
+80 into the interval [8080, 8087] steered by RSS, that can then be attached
+to ingress qdisc. The exercise of adding the egress counterpart and IPv6
+support is left to the reader:
+
+.in +4n
+.nf
+.sp
+#include <asm/types.h>
+#include <asm/byteorder.h>
+
+#include <linux/bpf.h>
+#include <linux/filter.h>
+#include <linux/in.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+
+#include "helpers.h"
+
+static inline void set_tcp_dport(struct __sk_buff *skb, int nh_off,
+ __u16 old_port, __u16 new_port)
+{
+ bpf_l4_csum_replace(skb, nh_off + offsetof(struct tcphdr, check),
+ old_port, new_port, sizeof(new_port));
+ bpf_skb_store_bytes(skb, nh_off + offsetof(struct tcphdr, dest),
+ &new_port, sizeof(new_port), 0);
+}
+
+static inline int lb_do_ipv4(struct __sk_buff *skb, int nh_off)
+{
+ __u16 dport, dport_new = 8080, off;
+ __u8 ip_proto, ip_vl;
+
+ ip_proto = load_byte(skb, nh_off +
+ offsetof(struct iphdr, protocol));
+ if (ip_proto != IPPROTO_TCP)
+ return 0;
+
+ ip_vl = load_byte(skb, nh_off);
+ if (likely(ip_vl == 0x45))
+ nh_off += sizeof(struct iphdr);
+ else
+ nh_off += (ip_vl & 0xF) << 2;
+
+ dport = load_half(skb, nh_off + offsetof(struct tcphdr, dest));
+ if (dport != 80)
+ return 0;
+
+ off = skb->queue_mapping & 7;
+ set_tcp_dport(skb, nh_off - BPF_LL_OFF, __constant_htons(80),
+ __cpu_to_be16(dport_new + off));
+ return -1;
+}
+
+__section("lb") int lb_main(struct __sk_buff *skb)
+{
+ int ret = 0, nh_off = BPF_LL_OFF + ETH_HLEN;
+
+ if (likely(skb->protocol == __constant_htons(ETH_P_IP)))
+ ret = lb_do_ipv4(skb, nh_off);
+
+ return ret;
+}
+
+char __license[] __section("license") = "GPL";
+.fi
+.in
+
+The related helper header file
+.B helpers.h
+in both examples was:
+
+.in +4n
+.nf
+.sp
+/* Misc helper macros. */
+#define __section(x) __attribute__((section(x), used))
+#define offsetof(x, y) __builtin_offsetof(x, y)
+#define likely(x) __builtin_expect(!!(x), 1)
+#define unlikely(x) __builtin_expect(!!(x), 0)
+
+/* 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;
+};
+
+/* Some used BPF function calls. */
+static int (*bpf_skb_store_bytes)(void *ctx, int off, void *from,
+ int len, int flags) =
+ (void *) BPF_FUNC_skb_store_bytes;
+static int (*bpf_l4_csum_replace)(void *ctx, int off, int from,
+ int to, int flags) =
+ (void *) BPF_FUNC_l4_csum_replace;
+static void *(*bpf_map_lookup_elem)(void *map, void *key) =
+ (void *) BPF_FUNC_map_lookup_elem;
+
+/* Some used BPF intrinsics. */
+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");
+.fi
+.in
+
+Best practice, we recommend to only have a single eBPF classifier loaded
+in tc and perform
+.B all
+necessary matching and mangling from there instead of a list of individual
+classifier and separate actions. Just a single classifier tailored for a
+given use-case will be most efficient to run.
+
+.SS eBPF DEBUGGING
+
+Both tc
+.B filter
+and
+.B action
+commands for
+.B bpf
+support an optional
+.B verbose
+parameter that can be used to inspect the eBPF verifier log. It is dumped
+by default in case of an error.
+
+In case the eBPF/cBPF JIT compiler has been enabled, it can also be
+instructed to emit a debug output of the resulting opcode image into
+the kernel log, which can be read via
+.B dmesg(1)
+:
+
+.in +4n
+.B echo 2 > /proc/sys/net/core/bpf_jit_enable
+.in
+
+The Linux kernel source tree ships additionally under
+.B tools/net/
+a small helper called
+.B bpf_jit_disasm
+that reads out the opcode image dump from the kernel log and dumps the
+resulting disassembly:
+
+.in +4n
+.B bpf_jit_disasm -o
+.in
+
+Other than that, the Linux kernel also contains an extensive eBPF/cBPF
+test suite module called
+.B test_bpf
+\&. Upon ...
+
+.in +4n
+.B modprobe test_bpf
+.in
+
+\&... it performs a diversity of test cases and dumps the results into
+the kernel log that can be inspected with
+.B dmesg(1)
+\&. The results can differ depending on whether the JIT compiler is enabled
+or not. In case of failed test cases, the module will fail to load. In
+such cases, we urge you to file a bug report to the related JIT authors,
+Linux kernel and networking mailing lists.
+
+.SS cBPF
+
+Although we generally recommend switching to implementing
+.B eBPF
+classifier and actions, for the sake of completeness, a few words on how to
+program in cBPF will be lost here.
+
+Likewise, the
+.B bpf_jit_enable
+switch can be enabled as mentioned already. Tooling such as
+.B bpf_jit_disasm
+is also independent whether eBPF or cBPF code is being loaded.
+
+Unlike in eBPF, classifier and action are not implemented in restricted C,
+but rather in a minimal assembler-like language or with the help of other
+tooling.
+
+The raw interface with tc takes opcodes directly. For example, the most
+minimal classifier matching on every packet resulting in the default
+classid of 1:1 looks like:
+
+.in +4n
+.B tc filter add dev em1 parent 1: bpf bytecode '1,6 0 0 4294967295,' flowid 1:1
+.in
+
+The first decimal of the bytecode sequence denotes the number of subsequent
+4-tuples of cBPF opcodes. As mentioned, such a 4-tuple consists of
+.B c t f k
+decimals, where
+.B c
+represents the cBPF opcode,
+.B t
+the jump true offset target,
+.B f
+the jump false offset target and
+.B k
+the immediate constant/literal. Here, this denotes an unconditional return
+from the program with immediate value of -1.
+
+Thus, for egress classification, Willem de Bruijn implemented a minimal stand-alone
+helper tool under the GNU General Public License version 2 for
+.B iptables(8)
+BPF extension, which abuses the
+.B libpcap
+internal classic BPF compiler, his code derived here for usage with
+.B tc(8)
+:
+
+.in +4n
+.nf
+.sp
+#include <pcap.h>
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+ struct bpf_program prog;
+ struct bpf_insn *ins;
+ int i, ret, dlt = DLT_RAW;
+
+ if (argc < 2 || argc > 3)
+ return 1;
+ if (argc == 3) {
+ dlt = pcap_datalink_name_to_val(argv[1]);
+ if (dlt == -1)
+ return 1;
+ }
+
+ ret = pcap_compile_nopcap(-1, dlt, &prog, argv[argc - 1],
+ 1, PCAP_NETMASK_UNKNOWN);
+ if (ret)
+ return 1;
+
+ printf("%d,", prog.bf_len);
+ ins = prog.bf_insns;
+
+ for (i = 0; i < prog.bf_len - 1; ++ins, ++i)
+ printf("%u %u %u %u,", ins->code,
+ ins->jt, ins->jf, ins->k);
+ printf("%u %u %u %u",
+ ins->code, ins->jt, ins->jf, ins->k);
+
+ pcap_freecode(&prog);
+ return 0;
+}
+.fi
+.in
+
+Given this small helper, any
+.B tcpdump(8)
+filter expression can be abused as a classifier where a match will
+result in the default classid:
+
+.in +4n
+.B bpftool EN10MB 'tcp[tcpflags] & tcp-syn != 0' > /var/bpf/tcp-syn
+.br
+.B tc filter add dev em1 parent 1: bpf bytecode-file /var/bpf/tcp-syn flowid 1:1
+.in
+
+Basically, such a minimal generator is equivalent to:
+
+.in +4n
+.B tcpdump -iem1 -ddd 'tcp[tcpflags] & tcp-syn != 0' | tr '\\\\n' ',' > /var/bpf/tcp-syn
+.in
+
+Since
+.B libpcap
+does not support all Linux' specific cBPF extensions in its compiler, the
+Linux kernel also ships under
+.B tools/net/
+a minimal BPF assembler called
+.B bpf_asm
+for providing full control. For detailed syntax and semantics on implementing
+such programs by hand, see references under
+.B FURTHER READING
+\&.
+
+Trivial toy example in
+.B bpf_asm
+for classifying IPv4/TCP packets, saved in a text file called
+.B foobar
+:
+
+.in +4n
+.nf
+.sp
+ldh [12]
+jne #0x800, drop
+ldb [23]
+jneq #6, drop
+ret #-1
+drop: ret #0
+.fi
+.in
+
+Similarly, such a classifier can be loaded as:
+
+.in +4n
+.B bpf_asm foobar > /var/bpf/tcp-syn
+.br
+.B tc filter add dev em1 parent 1: bpf bytecode-file /var/bpf/tcp-syn flowid 1:1
+.in
+
+For BPF classifiers, the Linux kernel provides additionally under
+.B tools/net/
+a small BPF debugger called
+.B bpf_dbg
+, which can be used to test a classifier against pcap files, single-step
+or add various breakpoints into the classifier program and dump register
+contents during runtime.
+
+Implementing an action in classic BPF is rather limited in the sense that
+packet mangling is not supported. Therefore, it's generally recommended to
+make the switch to eBPF, whenever possible.
+
+.SH FURTHER READING
+Further and more technical details about the BPF architecture can be found
+in the Linux kernel source tree under
+.B Documentation/networking/filter.txt
+\&.
+
+Further details on eBPF
+.B tc(8)
+examples can be found in the iproute2 source
+tree under
+.B examples/bpf/
+\&.
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-ematch (8)
+.BR bpf (2)
+.BR bpf (4)
+
+.SH AUTHORS
+Manpage written by Daniel Borkmann.
+
+Please report corrections or improvements to the Linux kernel networking
+mailing list:
+.B <netdev@vger.kernel.org>
diff --git a/man/man8/tc-cake.8 b/man/man8/tc-cake.8
new file mode 100644
index 0000000..4112b75
--- /dev/null
+++ b/man/man8/tc-cake.8
@@ -0,0 +1,726 @@
+.TH CAKE 8 "19 July 2018" "iproute2" "Linux"
+.SH NAME
+CAKE \- Common Applications Kept Enhanced (CAKE)
+.SH SYNOPSIS
+.B tc qdisc ... cake
+.br
+[
+.BR bandwidth
+RATE |
+.BR unlimited*
+|
+.BR autorate-ingress
+]
+.br
+[
+.BR rtt
+TIME |
+.BR datacentre
+|
+.BR lan
+|
+.BR metro
+|
+.BR regional
+|
+.BR internet*
+|
+.BR oceanic
+|
+.BR satellite
+|
+.BR interplanetary
+]
+.br
+[
+.BR besteffort
+|
+.BR diffserv8
+|
+.BR diffserv4
+|
+.BR diffserv3*
+]
+.br
+[
+.BR flowblind
+|
+.BR srchost
+|
+.BR dsthost
+|
+.BR hosts
+|
+.BR flows
+|
+.BR dual-srchost
+|
+.BR dual-dsthost
+|
+.BR triple-isolate*
+]
+.br
+[
+.BR nat
+|
+.BR nonat*
+]
+.br
+[
+.BR wash
+|
+.BR nowash*
+]
+.br
+[
+.BR split-gso*
+|
+.BR no-split-gso
+]
+.br
+[
+.BR ack-filter
+|
+.BR ack-filter-aggressive
+|
+.BR no-ack-filter*
+]
+.br
+[
+.BR memlimit
+LIMIT ]
+.br
+[
+.BR fwmark
+MASK ]
+.br
+[
+.BR ptm
+|
+.BR atm
+|
+.BR noatm*
+]
+.br
+[
+.BR overhead
+N |
+.BR conservative
+|
+.BR raw*
+]
+.br
+[
+.BR mpu
+N ]
+.br
+[
+.BR ingress
+|
+.BR egress*
+]
+.br
+(* marks defaults)
+
+
+.SH DESCRIPTION
+CAKE (Common Applications Kept Enhanced) is a shaping-capable queue discipline
+which uses both AQM and FQ. It combines COBALT, which is an AQM algorithm
+combining Codel and BLUE, a shaper which operates in deficit mode, and a variant
+of DRR++ for flow isolation. 8-way set-associative hashing is used to virtually
+eliminate hash collisions. Priority queuing is available through a simplified
+diffserv implementation. Overhead compensation for various encapsulation
+schemes is tightly integrated.
+
+All settings are optional; the default settings are chosen to be sensible in
+most common deployments. Most people will only need to set the
+.B bandwidth
+parameter to get useful results, but reading the
+.B Overhead Compensation
+and
+.B Round Trip Time
+sections is strongly encouraged.
+
+.SH SHAPER PARAMETERS
+CAKE uses a deficit-mode shaper, which does not exhibit the initial burst
+typical of token-bucket shapers. It will automatically burst precisely as much
+as required to maintain the configured throughput. As such, it is very
+straightforward to configure.
+.PP
+.B unlimited
+(default)
+.br
+ No limit on the bandwidth.
+.PP
+.B bandwidth
+RATE
+.br
+ Set the shaper bandwidth. See
+.BR tc(8)
+or examples below for details of the RATE value.
+.PP
+.B autorate-ingress
+.br
+ Automatic capacity estimation based on traffic arriving at this qdisc.
+This is most likely to be useful with cellular links, which tend to change
+quality randomly. A
+.B bandwidth
+parameter can be used in conjunction to specify an initial estimate. The shaper
+will periodically be set to a bandwidth slightly below the estimated rate. This
+estimator cannot estimate the bandwidth of links downstream of itself.
+
+.SH OVERHEAD COMPENSATION PARAMETERS
+The size of each packet on the wire may differ from that seen by Linux. The
+following parameters allow CAKE to compensate for this difference by internally
+considering each packet to be bigger than Linux informs it. To assist users who
+are not expert network engineers, keywords have been provided to represent a
+number of common link technologies.
+
+.SS Manual Overhead Specification
+.B overhead
+BYTES
+.br
+ Adds BYTES to the size of each packet. BYTES may be negative; values
+between -64 and 256 (inclusive) are accepted.
+.PP
+.B mpu
+BYTES
+.br
+ Rounds each packet (including overhead) up to a minimum length
+BYTES. BYTES may not be negative; values between 0 and 256 (inclusive)
+are accepted.
+.PP
+.B atm
+.br
+ Compensates for ATM cell framing, which is normally found on ADSL links.
+This is performed after the
+.B overhead
+parameter above. ATM uses fixed 53-byte cells, each of which can carry 48 bytes
+payload.
+.PP
+.B ptm
+.br
+ Compensates for PTM encoding, which is normally found on VDSL2 links and
+uses a 64b/65b encoding scheme. It is even more efficient to simply
+derate the specified shaper bandwidth by a factor of 64/65 or 0.984. See
+ITU G.992.3 Annex N and IEEE 802.3 Section 61.3 for details.
+.PP
+.B noatm
+.br
+ Disables ATM and PTM compensation.
+
+.SS Failsafe Overhead Keywords
+These two keywords are provided for quick-and-dirty setup. Use them if you
+can't be bothered to read the rest of this section.
+.PP
+.B raw
+(default)
+.br
+ Turns off all overhead compensation in CAKE. The packet size reported
+by Linux will be used directly.
+.PP
+ Other overhead keywords may be added after "raw". The effect of this is
+to make the overhead compensation operate relative to the reported packet size,
+not the underlying IP packet size.
+.PP
+.B conservative
+.br
+ Compensates for more overhead than is likely to occur on any
+widely-deployed link technology.
+.br
+ Equivalent to
+.B overhead 48 atm.
+
+.SS ADSL Overhead Keywords
+Most ADSL modems have a way to check which framing scheme is in use. Often this
+is also specified in the settings document provided by the ISP. The keywords in
+this section are intended to correspond with these sources of information. All
+of them implicitly set the
+.B atm
+flag.
+.PP
+.B pppoa-vcmux
+.br
+ Equivalent to
+.B overhead 10 atm
+.PP
+.B pppoa-llc
+.br
+ Equivalent to
+.B overhead 14 atm
+.PP
+.B pppoe-vcmux
+.br
+ Equivalent to
+.B overhead 32 atm
+.PP
+.B pppoe-llcsnap
+.br
+ Equivalent to
+.B overhead 40 atm
+.PP
+.B bridged-vcmux
+.br
+ Equivalent to
+.B overhead 24 atm
+.PP
+.B bridged-llcsnap
+.br
+ Equivalent to
+.B overhead 32 atm
+.PP
+.B ipoa-vcmux
+.br
+ Equivalent to
+.B overhead 8 atm
+.PP
+.B ipoa-llcsnap
+.br
+ Equivalent to
+.B overhead 16 atm
+.PP
+See also the Ethernet Correction Factors section below.
+
+.SS VDSL2 Overhead Keywords
+ATM was dropped from VDSL2 in favour of PTM, which is a much more
+straightforward framing scheme. Some ISPs retained PPPoE for compatibility with
+their existing back-end systems.
+.PP
+.B pppoe-ptm
+.br
+ Equivalent to
+.B overhead 30 ptm
+
+.br
+ PPPoE: 2B PPP + 6B PPPoE +
+.br
+ ETHERNET: 6B dest MAC + 6B src MAC + 2B ethertype + 4B Frame Check Sequence +
+.br
+ PTM: 1B Start of Frame (S) + 1B End of Frame (Ck) + 2B TC-CRC (PTM-FCS)
+.br
+.PP
+.B bridged-ptm
+.br
+ Equivalent to
+.B overhead 22 ptm
+.br
+ ETHERNET: 6B dest MAC + 6B src MAC + 2B ethertype + 4B Frame Check Sequence +
+.br
+ PTM: 1B Start of Frame (S) + 1B End of Frame (Ck) + 2B TC-CRC (PTM-FCS)
+.br
+.PP
+See also the Ethernet Correction Factors section below.
+
+.SS DOCSIS Cable Overhead Keyword
+DOCSIS is the universal standard for providing Internet service over cable-TV
+infrastructure.
+
+In this case, the actual on-wire overhead is less important than the packet size
+the head-end equipment uses for shaping and metering. This is specified to be
+an Ethernet frame including the CRC (aka FCS).
+.PP
+.B docsis
+.br
+ Equivalent to
+.B overhead 18 mpu 64 noatm
+
+.SS Ethernet Overhead Keywords
+.PP
+.B ethernet
+.br
+ Accounts for Ethernet's preamble, inter-frame gap, and Frame Check
+Sequence. Use this keyword when the bottleneck being shaped for is an
+actual Ethernet cable.
+.br
+ Equivalent to
+.B overhead 38 mpu 84 noatm
+.PP
+.B ether-vlan
+.br
+ Adds 4 bytes to the overhead compensation, accounting for an IEEE 802.1Q
+VLAN header appended to the Ethernet frame header. NB: Some ISPs use one or
+even two of these within PPPoE; this keyword may be repeated as necessary to
+express this.
+
+.SH ROUND TRIP TIME PARAMETERS
+Active Queue Management (AQM) consists of embedding congestion signals in the
+packet flow, which receivers use to instruct senders to slow down when the queue
+is persistently occupied. CAKE uses ECN signalling when available, and packet
+drops otherwise, according to a combination of the Codel and BLUE AQM algorithms
+called COBALT.
+
+Very short latencies require a very rapid AQM response to adequately control
+latency. However, such a rapid response tends to impair throughput when the
+actual RTT is relatively long. CAKE allows specifying the RTT it assumes for
+tuning various parameters. Actual RTTs within an order of magnitude of this
+will generally work well for both throughput and latency management.
+
+At the 'lan' setting and below, the time constants are similar in magnitude to
+the jitter in the Linux kernel itself, so congestion might be signalled
+prematurely. The flows will then become sparse and total throughput reduced,
+leaving little or no back-pressure for the fairness logic to work against. Use
+the "metro" setting for local lans unless you have a custom kernel.
+.PP
+.B rtt
+TIME
+.br
+ Manually specify an RTT.
+.PP
+.B datacentre
+.br
+ For extremely high-performance 10GigE+ networks only. Equivalent to
+.B rtt 100us.
+.PP
+.B lan
+.br
+ For pure Ethernet (not Wi-Fi) networks, at home or in the office. Don't
+use this when shaping for an Internet access link. Equivalent to
+.B rtt 1ms.
+.PP
+.B metro
+.br
+ For traffic mostly within a single city. Equivalent to
+.B rtt 10ms.
+.PP
+.B regional
+.br
+ For traffic mostly within a European-sized country. Equivalent to
+.B rtt 30ms.
+.PP
+.B internet
+(default)
+.br
+ This is suitable for most Internet traffic. Equivalent to
+.B rtt 100ms.
+.PP
+.B oceanic
+.br
+ For Internet traffic with generally above-average latency, such as that
+suffered by Australasian residents. Equivalent to
+.B rtt 300ms.
+.PP
+.B satellite
+.br
+ For traffic via geostationary satellites. Equivalent to
+.B rtt 1000ms.
+.PP
+.B interplanetary
+.br
+ So named because Jupiter is about 1 light-hour from Earth. Use this to
+(almost) completely disable AQM actions. Equivalent to
+.B rtt 3600s.
+
+.SH FLOW ISOLATION PARAMETERS
+With flow isolation enabled, CAKE places packets from different flows into
+different queues, each of which carries its own AQM state. Packets from each
+queue are then delivered fairly, according to a DRR++ algorithm which minimises
+latency for "sparse" flows. CAKE uses a set-associative hashing algorithm to
+minimise flow collisions.
+
+These keywords specify whether fairness based on source address, destination
+address, individual flows, or any combination of those is desired.
+.PP
+.B flowblind
+.br
+ Disables flow isolation; all traffic passes through a single queue for
+each tin.
+.PP
+.B srchost
+.br
+ Flows are defined only by source address. Could be useful on the egress
+path of an ISP backhaul.
+.PP
+.B dsthost
+.br
+ Flows are defined only by destination address. Could be useful on the
+ingress path of an ISP backhaul.
+.PP
+.B hosts
+.br
+ Flows are defined by source-destination host pairs. This is host
+isolation, rather than flow isolation.
+.PP
+.B flows
+.br
+ Flows are defined by the entire 5-tuple of source address, destination
+address, transport protocol, source port and destination port. This is the type
+of flow isolation performed by SFQ and fq_codel.
+.PP
+.B dual-srchost
+.br
+ Flows are defined by the 5-tuple, and fairness is applied first over
+source addresses, then over individual flows. Good for use on egress traffic
+from a LAN to the internet, where it'll prevent any one LAN host from
+monopolising the uplink, regardless of the number of flows they use.
+.PP
+.B dual-dsthost
+.br
+ Flows are defined by the 5-tuple, and fairness is applied first over
+destination addresses, then over individual flows. Good for use on ingress
+traffic to a LAN from the internet, where it'll prevent any one LAN host from
+monopolising the downlink, regardless of the number of flows they use.
+.PP
+.B triple-isolate
+(default)
+.br
+ Flows are defined by the 5-tuple, and fairness is applied over source
+*and* destination addresses intelligently (ie. not merely by host-pairs), and
+also over individual flows. Use this if you're not certain whether to use
+dual-srchost or dual-dsthost; it'll do both jobs at once, preventing any one
+host on *either* side of the link from monopolising it with a large number of
+flows.
+.PP
+.B nat
+.br
+ Instructs Cake to perform a NAT lookup before applying flow-isolation
+rules, to determine the true addresses and port numbers of the packet, to
+improve fairness between hosts "inside" the NAT. This has no practical effect
+in "flowblind" or "flows" modes, or if NAT is performed on a different host.
+.PP
+.B nonat
+(default)
+.br
+ Cake will not perform a NAT lookup. Flow isolation will be performed
+using the addresses and port numbers directly visible to the interface Cake is
+attached to.
+
+.SH PRIORITY QUEUE PARAMETERS
+CAKE can divide traffic into "tins" based on the Diffserv field. Each tin has
+its own independent set of flow-isolation queues, and is serviced based on a WRR
+algorithm. To avoid perverse Diffserv marking incentives, tin weights have a
+"priority sharing" value when bandwidth used by that tin is below a threshold,
+and a lower "bandwidth sharing" value when above. Bandwidth is compared against
+the threshold using the same algorithm as the deficit-mode shaper.
+
+Detailed customisation of tin parameters is not provided. The following presets
+perform all necessary tuning, relative to the current shaper bandwidth and RTT
+settings.
+.PP
+.B besteffort
+.br
+ Disables priority queuing by placing all traffic in one tin.
+.PP
+.B precedence
+.br
+ Enables legacy interpretation of TOS "Precedence" field. Use of this
+preset on the modern Internet is firmly discouraged.
+.PP
+.B diffserv4
+.br
+ Provides a general-purpose Diffserv implementation with four tins:
+.br
+ Bulk (CS1), 6.25% threshold, generally low priority.
+.br
+ Best Effort (general), 100% threshold.
+.br
+ Video (AF4x, AF3x, CS3, AF2x, CS2, TOS4, TOS1), 50% threshold.
+.br
+ Voice (CS7, CS6, EF, VA, CS5, CS4), 25% threshold.
+.PP
+.B diffserv3
+(default)
+.br
+ Provides a simple, general-purpose Diffserv implementation with three tins:
+.br
+ Bulk (CS1), 6.25% threshold, generally low priority.
+.br
+ Best Effort (general), 100% threshold.
+.br
+ Voice (CS7, CS6, EF, VA, TOS4), 25% threshold, reduced Codel interval.
+
+.PP
+.B fwmark
+MASK
+.br
+ This options turns on fwmark-based overriding of CAKE's tin selection.
+If set, the option specifies a bitmask that will be applied to the fwmark
+associated with each packet. If the result of this masking is non-zero, the
+result will be right-shifted by the number of least-significant unset bits in
+the mask value, and the result will be used as a the tin number for that packet.
+This can be used to set policies in a firewall script that will override CAKE's
+built-in tin selection.
+
+.SH OTHER PARAMETERS
+.B memlimit
+LIMIT
+.br
+ Limit the memory consumed by Cake to LIMIT bytes. Note that this does
+not translate directly to queue size (so do not size this based on bandwidth
+delay product considerations, but rather on worst case acceptable memory
+consumption), as there is some overhead in the data structures containing the
+packets, especially for small packets.
+
+ By default, the limit is calculated based on the bandwidth and RTT
+settings.
+
+.PP
+.B wash
+
+.br
+ Traffic entering your diffserv domain is frequently mis-marked in
+transit from the perspective of your network, and traffic exiting yours may be
+mis-marked from the perspective of the transiting provider.
+
+Apply the wash option to clear all extra diffserv (but not ECN bits), after
+priority queuing has taken place.
+
+If you are shaping inbound, and cannot trust the diffserv markings (as is the
+case for Comcast Cable, among others), it is best to use a single queue
+"besteffort" mode with wash.
+
+.PP
+.B split-gso
+
+.br
+ This option controls whether CAKE will split General Segmentation
+Offload (GSO) super-packets into their on-the-wire components and
+dequeue them individually.
+
+.br
+Super-packets are created by the networking stack to improve efficiency.
+However, because they are larger they take longer to dequeue, which
+translates to higher latency for competing flows, especially at lower
+bandwidths. CAKE defaults to splitting GSO packets to achieve the lowest
+possible latency. At link speeds higher than 10 Gbps, setting the
+no-split-gso parameter can increase the maximum achievable throughput by
+retaining the full GSO packets.
+
+.SH OVERRIDING CLASSIFICATION WITH TC FILTERS
+
+CAKE supports overriding of its internal classification of packets through the
+tc filter mechanism. Packets can be assigned to different priority tins by
+setting the
+.B priority
+field on the skb, and the flow hashing can be overridden by setting the
+.B classid
+parameter.
+
+.PP
+.B Tin override
+
+.br
+ To assign a priority tin, the major number of the priority field needs
+to match the qdisc handle of the cake instance; if it does, the minor number
+will be interpreted as the tin index. For example, to classify all ICMP packets
+as 'bulk', the following filter can be used:
+
+.br
+ # tc qdisc replace dev eth0 handle 1: root cake diffserv3
+ # tc filter add dev eth0 parent 1: protocol ip prio 1 \\
+ u32 match icmp type 0 0 action skbedit priority 1:1
+
+.PP
+.B Flow hash override
+
+.br
+ To override flow hashing, the classid can be set. CAKE will interpret
+the major number of the classid as the host hash used in host isolation mode,
+and the minor number as the flow hash used for flow-based queueing. One or both
+of those can be set, and will be used if the relevant flow isolation parameter
+is set (i.e., the major number will be ignored if CAKE is not configured in
+hosts mode, and the minor number will be ignored if CAKE is not configured in
+flows mode).
+
+.br
+This example will assign all ICMP packets to the first queue:
+
+.br
+ # tc qdisc replace dev eth0 handle 1: root cake
+ # tc filter add dev eth0 parent 1: protocol ip prio 1 \\
+ u32 match icmp type 0 0 classid 0:1
+
+.br
+If only one of the host and flow overrides is set, CAKE will compute the other
+hash from the packet as normal. Note, however, that the host isolation mode
+works by assigning a host ID to the flow queue; so if overriding both host and
+flow, the same flow cannot have more than one host assigned. In addition, it is
+not possible to assign different source and destination host IDs through the
+override mechanism; if a host ID is assigned, it will be used as both source and
+destination host.
+
+
+
+.SH EXAMPLES
+# tc qdisc delete root dev eth0
+.br
+# tc qdisc add root dev eth0 cake bandwidth 100Mbit ethernet
+.br
+# tc -s qdisc show dev eth0
+.br
+qdisc cake 1: root refcnt 2 bandwidth 100Mbit diffserv3 triple-isolate rtt 100.0ms noatm overhead 38 mpu 84
+ Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
+ backlog 0b 0p requeues 0
+ memory used: 0b of 5000000b
+ capacity estimate: 100Mbit
+ min/max network layer size: 65535 / 0
+ min/max overhead-adjusted size: 65535 / 0
+ average network hdr offset: 0
+
+ Bulk Best Effort Voice
+ thresh 6250Kbit 100Mbit 25Mbit
+ target 5.0ms 5.0ms 5.0ms
+ interval 100.0ms 100.0ms 100.0ms
+ pk_delay 0us 0us 0us
+ av_delay 0us 0us 0us
+ sp_delay 0us 0us 0us
+ pkts 0 0 0
+ bytes 0 0 0
+ way_inds 0 0 0
+ way_miss 0 0 0
+ way_cols 0 0 0
+ drops 0 0 0
+ marks 0 0 0
+ ack_drop 0 0 0
+ sp_flows 0 0 0
+ bk_flows 0 0 0
+ un_flows 0 0 0
+ max_len 0 0 0
+ quantum 300 1514 762
+
+After some use:
+.br
+# tc -s qdisc show dev eth0
+
+qdisc cake 1: root refcnt 2 bandwidth 100Mbit diffserv3 triple-isolate rtt 100.0ms noatm overhead 38 mpu 84
+ Sent 44709231 bytes 31931 pkt (dropped 45, overlimits 93782 requeues 0)
+ backlog 33308b 22p requeues 0
+ memory used: 292352b of 5000000b
+ capacity estimate: 100Mbit
+ min/max network layer size: 28 / 1500
+ min/max overhead-adjusted size: 84 / 1538
+ average network hdr offset: 14
+
+ Bulk Best Effort Voice
+ thresh 6250Kbit 100Mbit 25Mbit
+ target 5.0ms 5.0ms 5.0ms
+ interval 100.0ms 100.0ms 100.0ms
+ pk_delay 8.7ms 6.9ms 5.0ms
+ av_delay 4.9ms 5.3ms 3.8ms
+ sp_delay 727us 1.4ms 511us
+ pkts 2590 21271 8137
+ bytes 3081804 30302659 11426206
+ way_inds 0 46 0
+ way_miss 3 17 4
+ way_cols 0 0 0
+ drops 20 15 10
+ marks 0 0 0
+ ack_drop 0 0 0
+ sp_flows 2 4 1
+ bk_flows 1 2 1
+ un_flows 0 0 0
+ max_len 1514 1514 1514
+ quantum 300 1514 762
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-codel (8),
+.BR tc-fq_codel (8),
+.BR tc-htb (8)
+
+.SH AUTHORS
+Cake's principal author is Jonathan Morton, with contributions from
+Tony Ambardar, Kevin Darbyshire-Bryant, Toke Høiland-Jørgensen,
+Sebastian Moeller, Ryan Mounce, Dean Scarff, Nils Andreas Svee, and Dave Täht.
+
+This manual page was written by Loganaden Velvindron. Please report corrections
+to the Linux Networking mailing list <netdev@vger.kernel.org>.
diff --git a/man/man8/tc-cbq-details.8 b/man/man8/tc-cbq-details.8
new file mode 100644
index 0000000..9368103
--- /dev/null
+++ b/man/man8/tc-cbq-details.8
@@ -0,0 +1,423 @@
+.TH CBQ 8 "8 December 2001" "iproute2" "Linux"
+.SH NAME
+CBQ \- Class Based Queueing
+.SH SYNOPSIS
+.B tc qdisc ... dev
+dev
+.B ( parent
+classid
+.B | root) [ handle
+major:
+.B ] cbq avpkt
+bytes
+.B bandwidth
+rate
+.B [ cell
+bytes
+.B ] [ ewma
+log
+.B ] [ mpu
+bytes
+.B ]
+
+.B tc class ... dev
+dev
+.B parent
+major:[minor]
+.B [ classid
+major:minor
+.B ] cbq allot
+bytes
+.B [ bandwidth
+rate
+.B ] [ rate
+rate
+.B ] prio
+priority
+.B [ weight
+weight
+.B ] [ minburst
+packets
+.B ] [ maxburst
+packets
+.B ] [ ewma
+log
+.B ] [ cell
+bytes
+.B ] avpkt
+bytes
+.B [ mpu
+bytes
+.B ] [ bounded isolated ] [ split
+handle
+.B & defmap
+defmap
+.B ] [ estimator
+interval timeconstant
+.B ]
+
+.SH DESCRIPTION
+Class Based Queueing is a classful qdisc that implements a rich
+linksharing hierarchy of classes. It contains shaping elements as
+well as prioritizing capabilities. Shaping is performed using link
+idle time calculations based on the timing of dequeue events and
+underlying link bandwidth.
+
+.SH SHAPING ALGORITHM
+Shaping is done using link idle time calculations, and actions taken if
+these calculations deviate from set limits.
+
+When shaping a 10mbit/s connection to 1mbit/s, the link will
+be idle 90% of the time. If it isn't, it needs to be throttled so that it
+IS idle 90% of the time.
+
+From the kernel's perspective, this is hard to measure, so CBQ instead
+derives the idle time from the number of microseconds (in fact, jiffies)
+that elapse between requests from the device driver for more data. Combined
+with the knowledge of packet sizes, this is used to approximate how full or
+empty the link is.
+
+This is rather circumspect and doesn't always arrive at proper
+results. For example, what is the actual link speed of an interface
+that is not really able to transmit the full 100mbit/s of data,
+perhaps because of a badly implemented driver? A PCMCIA network card
+will also never achieve 100mbit/s because of the way the bus is
+designed - again, how do we calculate the idle time?
+
+The physical link bandwidth may be ill defined in case of not-quite-real
+network devices like PPP over Ethernet or PPTP over TCP/IP. The effective
+bandwidth in that case is probably determined by the efficiency of pipes
+to userspace - which not defined.
+
+During operations, the effective idletime is measured using an
+exponential weighted moving average (EWMA), which considers recent
+packets to be exponentially more important than past ones. The Unix
+loadaverage is calculated in the same way.
+
+The calculated idle time is subtracted from the EWMA measured one,
+the resulting number is called 'avgidle'. A perfectly loaded link has
+an avgidle of zero: packets arrive exactly at the calculated
+interval.
+
+An overloaded link has a negative avgidle and if it gets too negative,
+CBQ throttles and is then 'overlimit'.
+
+Conversely, an idle link might amass a huge avgidle, which would then
+allow infinite bandwidths after a few hours of silence. To prevent
+this, avgidle is capped at
+.B maxidle.
+
+If overlimit, in theory, the CBQ could throttle itself for exactly the
+amount of time that was calculated to pass between packets, and then
+pass one packet, and throttle again. Due to timer resolution constraints,
+this may not be feasible, see the
+.B minburst
+parameter below.
+
+.SH CLASSIFICATION
+Within the one CBQ instance many classes may exist. Each of these classes
+contains another qdisc, by default
+.BR tc-pfifo (8).
+
+When enqueueing a packet, CBQ starts at the root and uses various methods to
+determine which class should receive the data. If a verdict is reached, this
+process is repeated for the recipient class which might have further
+means of classifying traffic to its children, if any.
+
+CBQ has the following methods available to classify a packet to any child
+classes.
+.TP
+(i)
+.B skb->priority class encoding.
+Can be set from userspace by an application with the
+.B SO_PRIORITY
+setsockopt.
+The
+.B skb->priority class encoding
+only applies if the skb->priority holds a major:minor handle of an existing
+class within this qdisc.
+.TP
+(ii)
+tc filters attached to the class.
+.TP
+(iii)
+The defmap of a class, as set with the
+.B split & defmap
+parameters. The defmap may contain instructions for each possible Linux packet
+priority.
+
+.P
+Each class also has a
+.B level.
+Leaf nodes, attached to the bottom of the class hierarchy, have a level of 0.
+.SH CLASSIFICATION ALGORITHM
+
+Classification is a loop, which terminates when a leaf class is found. At any
+point the loop may jump to the fallback algorithm.
+
+The loop consists of the following steps:
+.TP
+(i)
+If the packet is generated locally and has a valid classid encoded within its
+.B skb->priority,
+choose it and terminate.
+
+.TP
+(ii)
+Consult the tc filters, if any, attached to this child. If these return
+a class which is not a leaf class, restart loop from the class returned.
+If it is a leaf, choose it and terminate.
+.TP
+(iii)
+If the tc filters did not return a class, but did return a classid,
+try to find a class with that id within this qdisc.
+Check if the found class is of a lower
+.B level
+than the current class. If so, and the returned class is not a leaf node,
+restart the loop at the found class. If it is a leaf node, terminate.
+If we found an upward reference to a higher level, enter the fallback
+algorithm.
+.TP
+(iv)
+If the tc filters did not return a class, nor a valid reference to one,
+consider the minor number of the reference to be the priority. Retrieve
+a class from the defmap of this class for the priority. If this did not
+contain a class, consult the defmap of this class for the
+.B BEST_EFFORT
+class. If this is an upward reference, or no
+.B BEST_EFFORT
+class was defined,
+enter the fallback algorithm. If a valid class was found, and it is not a
+leaf node, restart the loop at this class. If it is a leaf, choose it and
+terminate. If
+neither the priority distilled from the classid, nor the
+.B BEST_EFFORT
+priority yielded a class, enter the fallback algorithm.
+.P
+The fallback algorithm resides outside of the loop and is as follows.
+.TP
+(i)
+Consult the defmap of the class at which the jump to fallback occurred. If
+the defmap contains a class for the
+.B
+priority
+of the class (which is related to the TOS field), choose this class and
+terminate.
+.TP
+(ii)
+Consult the map for a class for the
+.B BEST_EFFORT
+priority. If found, choose it, and terminate.
+.TP
+(iii)
+Choose the class at which break out to the fallback algorithm occurred. Terminate.
+.P
+The packet is enqueued to the class which was chosen when either algorithm
+terminated. It is therefore possible for a packet to be enqueued *not* at a
+leaf node, but in the middle of the hierarchy.
+
+.SH LINK SHARING ALGORITHM
+When dequeuing for sending to the network device, CBQ decides which of its
+classes will be allowed to send. It does so with a Weighted Round Robin process
+in which each class with packets gets a chance to send in turn. The WRR process
+starts by asking the highest priority classes (lowest numerically -
+highest semantically) for packets, and will continue to do so until they
+have no more data to offer, in which case the process repeats for lower
+priorities.
+
+.B CERTAINTY ENDS HERE, ANK PLEASE HELP
+
+Each class is not allowed to send at length though - they can only dequeue a
+configurable amount of data during each round.
+
+If a class is about to go overlimit, and it is not
+.B bounded
+it will try to borrow avgidle from siblings that are not
+.B isolated.
+This process is repeated from the bottom upwards. If a class is unable
+to borrow enough avgidle to send a packet, it is throttled and not asked
+for a packet for enough time for the avgidle to increase above zero.
+
+.B I REALLY NEED HELP FIGURING THIS OUT. REST OF DOCUMENT IS PRETTY CERTAIN
+.B AGAIN.
+
+.SH QDISC
+The root qdisc of a CBQ class tree has the following parameters:
+
+.TP
+parent major:minor | root
+This mandatory parameter determines the place of the CBQ instance, either at the
+.B root
+of an interface or within an existing class.
+.TP
+handle major:
+Like all other qdiscs, the CBQ can be assigned a handle. Should consist only
+of a major number, followed by a colon. Optional.
+.TP
+avpkt bytes
+For calculations, the average packet size must be known. It is silently capped
+at a minimum of 2/3 of the interface MTU. Mandatory.
+.TP
+bandwidth rate
+To determine the idle time, CBQ must know the bandwidth of your underlying
+physical interface, or parent qdisc. This is a vital parameter, more about it
+later. Mandatory.
+.TP
+cell
+The cell size determines he granularity of packet transmission time calculations. Has a sensible default.
+.TP
+mpu
+A zero sized packet may still take time to transmit. This value is the lower
+cap for packet transmission time calculations - packets smaller than this value
+are still deemed to have this size. Defaults to zero.
+.TP
+ewma log
+When CBQ needs to measure the average idle time, it does so using an
+Exponentially Weighted Moving Average which smooths out measurements into
+a moving average. The EWMA LOG determines how much smoothing occurs. Defaults
+to 5. Lower values imply greater sensitivity. Must be between 0 and 31.
+.P
+A CBQ qdisc does not shape out of its own accord. It only needs to know certain
+parameters about the underlying link. Actual shaping is done in classes.
+
+.SH CLASSES
+Classes have a host of parameters to configure their operation.
+
+.TP
+parent major:minor
+Place of this class within the hierarchy. If attached directly to a qdisc
+and not to another class, minor can be omitted. Mandatory.
+.TP
+classid major:minor
+Like qdiscs, classes can be named. The major number must be equal to the
+major number of the qdisc to which it belongs. Optional, but needed if this
+class is going to have children.
+.TP
+weight weight
+When dequeuing to the interface, classes are tried for traffic in a
+round-robin fashion. Classes with a higher configured qdisc will generally
+have more traffic to offer during each round, so it makes sense to allow
+it to dequeue more traffic. All weights under a class are normalized, so
+only the ratios matter. Defaults to the configured rate, unless the priority
+of this class is maximal, in which case it is set to 1.
+.TP
+allot bytes
+Allot specifies how many bytes a qdisc can dequeue
+during each round of the process. This parameter is weighted using the
+renormalized class weight described above.
+
+.TP
+priority priority
+In the round-robin process, classes with the lowest priority field are tried
+for packets first. Mandatory.
+
+.TP
+rate rate
+Maximum rate this class and all its children combined can send at. Mandatory.
+
+.TP
+bandwidth rate
+This is different from the bandwidth specified when creating a CBQ disc. Only
+used to determine maxidle and offtime, which are only calculated when
+specifying maxburst or minburst. Mandatory if specifying maxburst or minburst.
+
+.TP
+maxburst
+This number of packets is used to calculate maxidle so that when
+avgidle is at maxidle, this number of average packets can be burst
+before avgidle drops to 0. Set it higher to be more tolerant of
+bursts. You can't set maxidle directly, only via this parameter.
+
+.TP
+minburst
+As mentioned before, CBQ needs to throttle in case of
+overlimit. The ideal solution is to do so for exactly the calculated
+idle time, and pass 1 packet. However, Unix kernels generally have a
+hard time scheduling events shorter than 10ms, so it is better to
+throttle for a longer period, and then pass minburst packets in one
+go, and then sleep minburst times longer.
+
+The time to wait is called the offtime. Higher values of minburst lead
+to more accurate shaping in the long term, but to bigger bursts at
+millisecond timescales.
+
+.TP
+minidle
+If avgidle is below 0, we are overlimits and need to wait until
+avgidle will be big enough to send one packet. To prevent a sudden
+burst from shutting down the link for a prolonged period of time,
+avgidle is reset to minidle if it gets too low.
+
+Minidle is specified in negative microseconds, so 10 means that
+avgidle is capped at -10us.
+
+.TP
+bounded
+Signifies that this class will not borrow bandwidth from its siblings.
+.TP
+isolated
+Means that this class will not borrow bandwidth to its siblings
+
+.TP
+split major:minor & defmap bitmap[/bitmap]
+If consulting filters attached to a class did not give a verdict,
+CBQ can also classify based on the packet's priority. There are 16
+priorities available, numbered from 0 to 15.
+
+The defmap specifies which priorities this class wants to receive,
+specified as a bitmap. The Least Significant Bit corresponds to priority
+zero. The
+.B split
+parameter tells CBQ at which class the decision must be made, which should
+be a (grand)parent of the class you are adding.
+
+As an example, 'tc class add ... classid 10:1 cbq .. split 10:0 defmap c0'
+configures class 10:0 to send packets with priorities 6 and 7 to 10:1.
+
+The complimentary configuration would then
+be: 'tc class add ... classid 10:2 cbq ... split 10:0 defmap 3f'
+Which would send all packets 0, 1, 2, 3, 4 and 5 to 10:1.
+.TP
+estimator interval timeconstant
+CBQ can measure how much bandwidth each class is using, which tc filters
+can use to classify packets with. In order to determine the bandwidth
+it uses a very simple estimator that measures once every
+.B interval
+microseconds how much traffic has passed. This again is a EWMA, for which
+the time constant can be specified, also in microseconds. The
+.B time constant
+corresponds to the sluggishness of the measurement or, conversely, to the
+sensitivity of the average to short bursts. Higher values mean less
+sensitivity.
+
+
+
+.SH SOURCES
+.TP
+o
+Sally Floyd and Van Jacobson, "Link-sharing and Resource
+Management Models for Packet Networks",
+IEEE/ACM Transactions on Networking, Vol.3, No.4, 1995
+
+.TP
+o
+Sally Floyd, "Notes on CBQ and Guarantee Service", 1995
+
+.TP
+o
+Sally Floyd, "Notes on Class-Based Queueing: Setting
+Parameters", 1996
+
+.TP
+o
+Sally Floyd and Michael Speer, "Experimental Results
+for Class-Based Queueing", 1998, not published.
+
+
+
+.SH SEE ALSO
+.BR tc (8)
+
+.SH AUTHOR
+Alexey N. Kuznetsov, <kuznet@ms2.inr.ac.ru>. This manpage maintained by
+bert hubert <ahu@ds9a.nl>
diff --git a/man/man8/tc-cbq.8 b/man/man8/tc-cbq.8
new file mode 100644
index 0000000..301265d
--- /dev/null
+++ b/man/man8/tc-cbq.8
@@ -0,0 +1,351 @@
+.TH CBQ 8 "16 December 2001" "iproute2" "Linux"
+.SH NAME
+CBQ \- Class Based Queueing
+.SH SYNOPSIS
+.B tc qdisc ... dev
+dev
+.B ( parent
+classid
+.B | root) [ handle
+major:
+.B ] cbq [ allot
+bytes
+.B ] avpkt
+bytes
+.B bandwidth
+rate
+.B [ cell
+bytes
+.B ] [ ewma
+log
+.B ] [ mpu
+bytes
+.B ]
+
+.B tc class ... dev
+dev
+.B parent
+major:[minor]
+.B [ classid
+major:minor
+.B ] cbq allot
+bytes
+.B [ bandwidth
+rate
+.B ] [ rate
+rate
+.B ] prio
+priority
+.B [ weight
+weight
+.B ] [ minburst
+packets
+.B ] [ maxburst
+packets
+.B ] [ ewma
+log
+.B ] [ cell
+bytes
+.B ] avpkt
+bytes
+.B [ mpu
+bytes
+.B ] [ bounded isolated ] [ split
+handle
+.B & defmap
+defmap
+.B ] [ estimator
+interval timeconstant
+.B ]
+
+.SH DESCRIPTION
+Class Based Queueing is a classful qdisc that implements a rich
+linksharing hierarchy of classes. It contains shaping elements as
+well as prioritizing capabilities. Shaping is performed using link
+idle time calculations based on the timing of dequeue events and
+underlying link bandwidth.
+
+.SH SHAPING ALGORITHM
+When shaping a 10mbit/s connection to 1mbit/s, the link will
+be idle 90% of the time. If it isn't, it needs to be throttled so that it
+IS idle 90% of the time.
+
+During operations, the effective idletime is measured using an
+exponential weighted moving average (EWMA), which considers recent
+packets to be exponentially more important than past ones. The Unix
+loadaverage is calculated in the same way.
+
+The calculated idle time is subtracted from the EWMA measured one,
+the resulting number is called 'avgidle'. A perfectly loaded link has
+an avgidle of zero: packets arrive exactly at the calculated
+interval.
+
+An overloaded link has a negative avgidle and if it gets too negative,
+CBQ throttles and is then 'overlimit'.
+
+Conversely, an idle link might amass a huge avgidle, which would then
+allow infinite bandwidths after a few hours of silence. To prevent
+this, avgidle is capped at
+.B maxidle.
+
+If overlimit, in theory, the CBQ could throttle itself for exactly the
+amount of time that was calculated to pass between packets, and then
+pass one packet, and throttle again. Due to timer resolution constraints,
+this may not be feasible, see the
+.B minburst
+parameter below.
+
+.SH CLASSIFICATION
+Within the one CBQ instance many classes may exist. Each of these classes
+contains another qdisc, by default
+.BR tc-pfifo (8).
+
+When enqueueing a packet, CBQ starts at the root and uses various methods to
+determine which class should receive the data.
+
+In the absence of uncommon configuration options, the process is rather easy.
+At each node we look for an instruction, and then go to the class the
+instruction refers us to. If the class found is a barren leaf-node (without
+children), we enqueue the packet there. If it is not yet a leaf node, we do
+the whole thing over again starting from that node.
+
+The following actions are performed, in order at each node we visit, until one
+sends us to another node, or terminates the process.
+.TP
+(i)
+Consult filters attached to the class. If sent to a leafnode, we are done.
+Otherwise, restart.
+.TP
+(ii)
+Consult the defmap for the priority assigned to this packet, which depends
+on the TOS bits. Check if the referral is leafless, otherwise restart.
+.TP
+(iii)
+Ask the defmap for instructions for the 'best effort' priority. Check the
+answer for leafness, otherwise restart.
+.TP
+(iv)
+If none of the above returned with an instruction, enqueue at this node.
+.P
+This algorithm makes sure that a packet always ends up somewhere, even while
+you are busy building your configuration.
+
+For more details, see
+.BR tc-cbq-details(8).
+
+.SH LINK SHARING ALGORITHM
+When dequeuing for sending to the network device, CBQ decides which of its
+classes will be allowed to send. It does so with a Weighted Round Robin process
+in which each class with packets gets a chance to send in turn. The WRR process
+starts by asking the highest priority classes (lowest numerically -
+highest semantically) for packets, and will continue to do so until they
+have no more data to offer, in which case the process repeats for lower
+priorities.
+
+Classes by default borrow bandwidth from their siblings. A class can be
+prevented from doing so by declaring it 'bounded'. A class can also indicate
+its unwillingness to lend out bandwidth by being 'isolated'.
+
+.SH QDISC
+The root of a CBQ qdisc class tree has the following parameters:
+
+.TP
+parent major:minor | root
+This mandatory parameter determines the place of the CBQ instance, either at the
+.B root
+of an interface or within an existing class.
+.TP
+handle major:
+Like all other qdiscs, the CBQ can be assigned a handle. Should consist only
+of a major number, followed by a colon. Optional, but very useful if classes
+will be generated within this qdisc.
+.TP
+allot bytes
+This allotment is the 'chunkiness' of link sharing and is used for determining packet
+transmission time tables. The qdisc allot differs slightly from the class allot discussed
+below. Optional. Defaults to a reasonable value, related to avpkt.
+.TP
+avpkt bytes
+The average size of a packet is needed for calculating maxidle, and is also used
+for making sure 'allot' has a safe value. Mandatory.
+.TP
+bandwidth rate
+To determine the idle time, CBQ must know the bandwidth of your underlying
+physical interface, or parent qdisc. This is a vital parameter, more about it
+later. Mandatory.
+.TP
+cell
+The cell size determines he granularity of packet transmission time calculations. Has a sensible default.
+.TP
+mpu
+A zero sized packet may still take time to transmit. This value is the lower
+cap for packet transmission time calculations - packets smaller than this value
+are still deemed to have this size. Defaults to zero.
+.TP
+ewma log
+When CBQ needs to measure the average idle time, it does so using an
+Exponentially Weighted Moving Average which smooths out measurements into
+a moving average. The EWMA LOG determines how much smoothing occurs. Lower
+values imply greater sensitivity. Must be between 0 and 31. Defaults
+to 5.
+.P
+A CBQ qdisc does not shape out of its own accord. It only needs to know certain
+parameters about the underlying link. Actual shaping is done in classes.
+
+.SH CLASSES
+Classes have a host of parameters to configure their operation.
+
+.TP
+parent major:minor
+Place of this class within the hierarchy. If attached directly to a qdisc
+and not to another class, minor can be omitted. Mandatory.
+.TP
+classid major:minor
+Like qdiscs, classes can be named. The major number must be equal to the
+major number of the qdisc to which it belongs. Optional, but needed if this
+class is going to have children.
+.TP
+weight weight
+When dequeuing to the interface, classes are tried for traffic in a
+round-robin fashion. Classes with a higher configured qdisc will generally
+have more traffic to offer during each round, so it makes sense to allow
+it to dequeue more traffic. All weights under a class are normalized, so
+only the ratios matter. Defaults to the configured rate, unless the priority
+of this class is maximal, in which case it is set to 1.
+.TP
+allot bytes
+Allot specifies how many bytes a qdisc can dequeue
+during each round of the process. This parameter is weighted using the
+renormalized class weight described above. Silently capped at a minimum of
+3/2 avpkt. Mandatory.
+
+.TP
+prio priority
+In the round-robin process, classes with the lowest priority field are tried
+for packets first. Mandatory.
+
+.TP
+avpkt
+See the QDISC section.
+
+.TP
+rate rate
+Maximum rate this class and all its children combined can send at. Mandatory.
+
+.TP
+bandwidth rate
+This is different from the bandwidth specified when creating a CBQ disc! Only
+used to determine maxidle and offtime, which are only calculated when
+specifying maxburst or minburst. Mandatory if specifying maxburst or minburst.
+
+.TP
+maxburst
+This number of packets is used to calculate maxidle so that when
+avgidle is at maxidle, this number of average packets can be burst
+before avgidle drops to 0. Set it higher to be more tolerant of
+bursts. You can't set maxidle directly, only via this parameter.
+
+.TP
+minburst
+As mentioned before, CBQ needs to throttle in case of
+overlimit. The ideal solution is to do so for exactly the calculated
+idle time, and pass 1 packet. However, Unix kernels generally have a
+hard time scheduling events shorter than 10ms, so it is better to
+throttle for a longer period, and then pass minburst packets in one
+go, and then sleep minburst times longer.
+
+The time to wait is called the offtime. Higher values of minburst lead
+to more accurate shaping in the long term, but to bigger bursts at
+millisecond timescales. Optional.
+
+.TP
+minidle
+If avgidle is below 0, we are overlimits and need to wait until
+avgidle will be big enough to send one packet. To prevent a sudden
+burst from shutting down the link for a prolonged period of time,
+avgidle is reset to minidle if it gets too low.
+
+Minidle is specified in negative microseconds, so 10 means that
+avgidle is capped at -10us. Optional.
+
+.TP
+bounded
+Signifies that this class will not borrow bandwidth from its siblings.
+.TP
+isolated
+Means that this class will not borrow bandwidth to its siblings
+
+.TP
+split major:minor & defmap bitmap[/bitmap]
+If consulting filters attached to a class did not give a verdict,
+CBQ can also classify based on the packet's priority. There are 16
+priorities available, numbered from 0 to 15.
+
+The defmap specifies which priorities this class wants to receive,
+specified as a bitmap. The Least Significant Bit corresponds to priority
+zero. The
+.B split
+parameter tells CBQ at which class the decision must be made, which should
+be a (grand)parent of the class you are adding.
+
+As an example, 'tc class add ... classid 10:1 cbq .. split 10:0 defmap c0'
+configures class 10:0 to send packets with priorities 6 and 7 to 10:1.
+
+The complimentary configuration would then
+be: 'tc class add ... classid 10:2 cbq ... split 10:0 defmap 3f'
+Which would send all packets 0, 1, 2, 3, 4 and 5 to 10:1.
+.TP
+estimator interval timeconstant
+CBQ can measure how much bandwidth each class is using, which tc filters
+can use to classify packets with. In order to determine the bandwidth
+it uses a very simple estimator that measures once every
+.B interval
+microseconds how much traffic has passed. This again is a EWMA, for which
+the time constant can be specified, also in microseconds. The
+.B time constant
+corresponds to the sluggishness of the measurement or, conversely, to the
+sensitivity of the average to short bursts. Higher values mean less
+sensitivity.
+
+.SH BUGS
+The actual bandwidth of the underlying link may not be known, for example
+in the case of PPoE or PPTP connections which in fact may send over a
+pipe, instead of over a physical device. CBQ is quite resilient to major
+errors in the configured bandwidth, probably a the cost of coarser shaping.
+
+Default kernels rely on coarse timing information for making decisions. These
+may make shaping precise in the long term, but inaccurate on second long scales.
+
+See
+.BR tc-cbq-details(8)
+for hints on how to improve this.
+
+.SH SOURCES
+.TP
+o
+Sally Floyd and Van Jacobson, "Link-sharing and Resource
+Management Models for Packet Networks",
+IEEE/ACM Transactions on Networking, Vol.3, No.4, 1995
+
+.TP
+o
+Sally Floyd, "Notes on CBQ and Guaranteed Service", 1995
+
+.TP
+o
+Sally Floyd, "Notes on Class-Based Queueing: Setting
+Parameters", 1996
+
+.TP
+o
+Sally Floyd and Michael Speer, "Experimental Results
+for Class-Based Queueing", 1998, not published.
+
+
+
+.SH SEE ALSO
+.BR tc (8)
+
+.SH AUTHOR
+Alexey N. Kuznetsov, <kuznet@ms2.inr.ac.ru>. This manpage maintained by
+bert hubert <ahu@ds9a.nl>
diff --git a/man/man8/tc-cbs.8 b/man/man8/tc-cbs.8
new file mode 100644
index 0000000..ad1d882
--- /dev/null
+++ b/man/man8/tc-cbs.8
@@ -0,0 +1,124 @@
+.TH CBS 8 "18 Sept 2017" "iproute2" "Linux"
+.SH NAME
+CBS \- Credit Based Shaper (CBS) Qdisc
+.SH SYNOPSIS
+.B tc qdisc ... dev
+dev
+.B parent
+classid
+.B [ handle
+major:
+.B ] cbs idleslope
+idleslope
+.B sendslope
+sendslope
+.B hicredit
+hicredit
+.B locredit
+locredit
+.B [ offload
+0|1
+.B ]
+
+.SH DESCRIPTION
+The CBS (Credit Based Shaper) qdisc implements the shaping algorithm
+defined by the IEEE 802.1Q-2014 Section 8.6.8.2, which applies a well
+defined rate limiting method to the traffic.
+
+This queueing discipline is intended to be used by TSN (Time Sensitive
+Networking) applications, the CBS parameters are derived directly by
+what is described by the Annex L of the IEEE 802.1Q-2014
+Specification. The algorithm and how it affects the latency are
+detailed there.
+
+CBS is meant to be installed under another qdisc that maps packet
+flows to traffic classes, one example is
+.BR mqprio(8).
+
+.SH PARAMETERS
+.TP
+idleslope
+Idleslope is the rate of credits that is accumulated (in kilobits per
+second) when there is at least one packet waiting for transmission.
+Packets are transmitted when the current value of credits is equal or
+greater than zero. When there is no packet to be transmitted the
+amount of credits is set to zero. This is the main tunable of the CBS
+algorithm and represents the bandwidth that will be consumed.
+Note that when calculating idleslope, the entire packet size must be
+considered, including headers from all layers (i.e. MAC framing and any
+overhead from the physical layer), as described by IEEE 802.1Q-2014
+section 34.4.
+
+As an example, for an ethernet frame carrying 284 bytes of payload,
+and with no VLAN tags, you must add 14 bytes for the Ethernet headers,
+4 bytes for the Frame check sequence (CRC), and 20 bytes for the L1
+overhead: 12 bytes of interpacket gap, 7 bytes of preamble and 1 byte
+of start of frame delimiter. That results in 322 bytes for the total
+packet size, which is then used for calculating the idleslope.
+
+.TP
+sendslope
+Sendslope is the rate of credits that is depleted (it should be a
+negative number of kilobits per second) when a transmission is
+occurring. It can be calculated as follows, (IEEE 802.1Q-2014 Section
+8.6.8.2 item g):
+
+sendslope = idleslope - port_transmit_rate
+
+.TP
+hicredit
+Hicredit defines the maximum amount of credits (in bytes) that can be
+accumulated. Hicredit depends on the characteristics of interfering
+traffic, 'max_interference_size' is the maximum size of any burst of
+traffic that can delay the transmission of a frame that is available
+for transmission for this traffic class, (IEEE 802.1Q-2014 Annex L,
+Equation L-3):
+
+hicredit = max_interference_size * (idleslope / port_transmit_rate)
+
+.TP
+locredit
+Locredit is the minimum amount of credits that can be reached. It is a
+function of the traffic flowing through this qdisc (IEEE 802.1Q-2014
+Annex L, Equation L-2):
+
+locredit = max_frame_size * (sendslope / port_transmit_rate)
+
+.TP
+offload
+When
+.B offload
+is 1,
+.BR cbs(8)
+will try to configure the network interface so the CBS algorithm runs
+in the controller. The default is 0.
+
+.SH EXAMPLES
+
+CBS is used to enforce a Quality of Service by limiting the data rate
+of a traffic class, to separate packets into traffic classes the user
+may choose
+.BR mqprio(8),
+and configure it like this:
+
+.EX
+# tc qdisc add dev eth0 handle 100: parent root mqprio num_tc 3 \\
+ map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 \\
+ queues 1@0 1@1 2@2 \\
+ hw 0
+.EE
+.P
+To replace the current queuing disciple by CBS in the current queueing
+discipline connected to traffic class number 0, issue:
+.P
+.EX
+# tc qdisc replace dev eth0 parent 100:4 cbs \\
+ locredit -1470 hicredit 30 sendslope -980000 idleslope 20000
+.EE
+
+These values are obtained from the following parameters, idleslope is
+20mbit/s, the transmission rate is 1Gbit/s and the maximum interfering
+frame size is 1500 bytes.
+
+.SH AUTHORS
+Vinicius Costa Gomes <vinicius.gomes@intel.com>
diff --git a/man/man8/tc-cgroup.8 b/man/man8/tc-cgroup.8
new file mode 100644
index 0000000..2bea7d4
--- /dev/null
+++ b/man/man8/tc-cgroup.8
@@ -0,0 +1,80 @@
+.TH "Cgroup classifier in tc" 8 " 21 Oct 2015" "iproute2" "Linux"
+
+.SH NAME
+cgroup \- control group based traffic control filter
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " " filter " ... " cgroup " [ " match
+.IR EMATCH_TREE " ] [ "
+.B action
+.IR ACTION_SPEC " ]"
+.SH DESCRIPTION
+This filter serves as a hint to
+.B tc
+that the assigned class ID of the net_cls control group the process the packet
+originates from belongs to should be used for classification. Obviously, it is
+useful for locally generated packets only.
+.SH OPTIONS
+.TP
+.BI action " ACTION_SPEC"
+Apply an action from the generic actions framework on matching packets.
+.TP
+.BI match " EMATCH_TREE"
+Match packets using the extended match infrastructure. See
+.BR tc-ematch (8)
+for a detailed description of the allowed syntax in
+.IR EMATCH_TREE .
+.SH EXAMPLES
+In order to use this filter, a net_cls control group has to be created first and
+class as well as process ID(s) assigned to it. The following creates a net_cls
+cgroup named "foobar":
+
+.RS
+.EX
+modprobe cls_cgroup
+mkdir /sys/fs/cgroup/net_cls
+mount -t cgroup -onet_cls net_cls /sys/fs/cgroup/net_cls
+mkdir /sys/fs/cgroup/net_cls/foobar
+.EE
+.RE
+
+To assign a class ID to the created cgroup, a file named
+.I net_cls.classid
+has to be created which contains the class ID to be assigned as a hexadecimal,
+64bit wide number. The upper 32bits are reserved for the major handle, the
+remaining hold the minor. So a class ID of e.g.
+.B ff:be
+has to be written like so:
+.B 0xff00be
+(leading zeroes may be omitted). To continue the above example, the following
+assigns class ID 1:2 to foobar cgroup:
+
+.RS
+.EX
+echo 0x10002 > /sys/fs/cgroup/net_cls/foobar/net_cls.classid
+.EE
+.RE
+
+Finally some PIDs can be assigned to the given cgroup:
+
+.RS
+.EX
+echo 1234 > /sys/fs/cgroup/net_cls/foobar/tasks
+echo 5678 > /sys/fs/cgroup/net_cls/foobar/tasks
+.EE
+.RE
+
+Now by simply attaching a
+.B cgroup
+filter to a
+.B qdisc
+makes packets from PIDs 1234 and 5678 be pushed into class 1:2.
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-ematch (8),
+.br
+the file
+.I Documentation/cgroups/net_cls.txt
+of the Linux kernel tree
diff --git a/man/man8/tc-choke.8 b/man/man8/tc-choke.8
new file mode 100644
index 0000000..1916a3d
--- /dev/null
+++ b/man/man8/tc-choke.8
@@ -0,0 +1,63 @@
+.TH TC 8 "August 2011" "iproute2" "Linux"
+.SH NAME
+choke \- choose and keep scheduler
+.SH SYNOPSIS
+.B tc qdisc ... choke
+.B limit
+packets
+.B min
+packets
+.B max
+packets
+.B avpkt
+bytes
+.B burst
+packets
+.B [ ecn ] [ bandwidth
+rate
+.B ] probability
+chance
+
+.SH DESCRIPTION
+
+CHOKe (CHOose and Keep for responsive flows, CHOose and Kill for unresponsive flows)
+is a classless qdisc designed to both identify and penalize flows that monopolize the
+queue. CHOKe is a variation of RED, and the configuration is similar to RED.
+
+.SH ALGORITHM
+Once the queue hits a certain average length, a random packet is drawn from the
+queue. If both the to-be-queued and the drawn packet belong to the same flow,
+both packets are dropped. Otherwise, if the queue length is still below the maximum length,
+the new packet has a configurable chance of being marked (which may mean dropped).
+If the queue length exceeds
+.BR max ,
+the new packet will always be marked (or dropped).
+If the queue length exceeds
+.BR limit ,
+the new packet is always dropped.
+
+The marking probability computation is the same as used by the RED qdisc.
+
+.SH PARAMETERS
+The parameters are the same as for RED, except that RED uses bytes whereas choke
+counts packets. See
+.BR tc-red (8)
+for a description.
+
+.SH SOURCE
+.TP
+o
+R. Pan, B. Prabhakar, and K. Psounis, "CHOKe, A Stateless
+Active Queue Management Scheme for Approximating Fair Bandwidth Allocation",
+IEEE INFOCOM, 2000.
+.TP
+o
+A. Tang, J. Wang, S. Low, "Understanding CHOKe: Throughput and Spatial
+Characteristics", IEEE/ACM Transactions on Networking, 2004
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-red (8)
+
+.SH AUTHOR
+sched_choke was contributed by Stephen Hemminger.
diff --git a/man/man8/tc-codel.8 b/man/man8/tc-codel.8
new file mode 100644
index 0000000..e538e94
--- /dev/null
+++ b/man/man8/tc-codel.8
@@ -0,0 +1,122 @@
+.TH CoDel 8 "23 May 2012" "iproute2" "Linux"
+.SH NAME
+CoDel \- Controlled-Delay Active Queue Management algorithm
+.SH SYNOPSIS
+.B tc qdisc ... codel
+[
+.B limit
+PACKETS ] [
+.B target
+TIME ] [
+.B interval
+TIME ] [
+.B ecn
+|
+.B noecn
+] [
+.B ce_threshold
+TIME ]
+
+.SH DESCRIPTION
+CoDel (pronounced "coddle") is an adaptive "no-knobs" active queue management
+algorithm (AQM) scheme that was developed to address the shortcomings of
+RED and its variants. It was developed with the following goals
+in mind:
+ o It should be parameterless.
+ o It should keep delays low while permitting bursts of traffic.
+ o It should control delay.
+ o It should adapt dynamically to changing link rates with no impact on
+utilization.
+ o It should be simple and efficient and should scale from simple to
+complex routers.
+
+.SH ALGORITHM
+CoDel comes with three major innovations. Instead of using queue size or queue
+average, it uses the local minimum queue as a measure of the standing/persistent queue.
+Second, it uses a single state-tracking variable of the minimum delay to see where it
+is relative to the standing queue delay. Third, instead of measuring queue size
+in bytes or packets, it is measured in packet-sojourn time in the queue.
+
+CoDel measures the minimum local queue delay (i.e. standing queue delay) and
+compares it to the value of the given acceptable queue delay
+.B target.
+As long as the minimum queue delay is less than
+.B target
+or the buffer contains fewer than MTU worth of bytes, packets are not dropped.
+Codel enters a dropping mode when the minimum queue delay has exceeded
+.B target
+for a time greater than
+.B interval.
+In this mode, packets are dropped at different drop times which is set by a
+control law. The control law ensures that the packet drops cause a linear change
+in the throughput. Once the minimum delay goes below
+.B target,
+packets are no longer dropped.
+
+Additional details can be found in the paper cited below.
+
+.SH PARAMETERS
+.SS limit
+hard limit on the real queue size. When this limit is reached, incoming packets
+are dropped. If the value is lowered, packets are dropped so that the new limit is
+met. Default is 1000 packets.
+
+.SS target
+is the acceptable minimum standing/persistent queue delay. This minimum delay
+is identified by tracking the local minimum queue delay that packets experience.
+Default and recommended value is 5ms.
+
+.SS interval
+is used to ensure that the measured minimum delay does not become too stale. The
+minimum delay must be experienced in the last epoch of length
+.B interval.
+It should be set on the order of the worst-case RTT through the bottleneck to
+give endpoints sufficient time to react. Default value is 100ms.
+
+.SS ecn | noecn
+can be used to mark packets instead of dropping them. If
+.B ecn
+has been enabled,
+.B noecn
+can be used to turn it off and vice-a-versa. By default,
+.B ecn
+is turned off.
+
+.SS ce_threshold
+sets a threshold above which all packets are marked with ECN Congestion
+Experienced. This is useful for DCTCP-style congestion control algorithms that
+require marking at very shallow queueing thresholds.
+
+
+.SH EXAMPLES
+ # tc qdisc add dev eth0 root codel
+ # tc -s qdisc show
+ qdisc codel 801b: dev eth0 root refcnt 2 limit 1000p target 5.0ms
+interval 100.0ms
+ Sent 245801662 bytes 275853 pkt (dropped 0, overlimits 0 requeues 24)
+ backlog 0b 0p requeues 24
+ count 0 lastcount 0 ldelay 2us drop_next 0us
+ maxpacket 7306 ecn_mark 0 drop_overlimit 0
+
+ # tc qdisc add dev eth0 root codel limit 100 target 4ms interval 30ms ecn
+ # tc -s qdisc show
+ qdisc codel 801c: dev eth0 root refcnt 2 limit 100p target 4.0ms
+interval 30.0ms ecn
+ Sent 237573074 bytes 268561 pkt (dropped 0, overlimits 0 requeues 5)
+ backlog 0b 0p requeues 5
+ count 0 lastcount 0 ldelay 76us drop_next 0us
+ maxpacket 2962 ecn_mark 0 drop_overlimit 0
+
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-red (8)
+
+.SH SOURCES
+o Kathleen Nichols and Van Jacobson, "Controlling Queue Delay", ACM Queue,
+http://queue.acm.org/detail.cfm?id=2209336
+
+.SH AUTHORS
+CoDel was implemented by Eric Dumazet and David Taht. This manpage was written
+by Vijay Subramanian. Please reports corrections to the Linux Networking
+mailing list <netdev@vger.kernel.org>.
diff --git a/man/man8/tc-connmark.8 b/man/man8/tc-connmark.8
new file mode 100644
index 0000000..44f29f5
--- /dev/null
+++ b/man/man8/tc-connmark.8
@@ -0,0 +1,55 @@
+.TH "Connmark retriever action in tc" 8 "11 Jan 2016" "iproute2" "Linux"
+
+.SH NAME
+connmark - netfilter connmark retriever action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action connmark " [ " zone"
+.IR u16_zone_index " ] [ " CONTROL " ] ["
+.BI index " u32_index "
+]
+
+.ti -8
+.IR CONTROL " := { " reclassify " | " pipe " | " drop " | " continue " | " ok " }"
+.SH DESCRIPTION
+The connmark action is used to restore the connection's mark value into the
+packet's fwmark.
+.SH OPTIONS
+.TP
+.BI zone " u16_zone_index"
+Specify the conntrack zone when doing conntrack lookups for packets.
+.I u16_zone_index
+is a 16bit unsigned decimal value.
+.TP
+.I CONTROL
+How to continue after executing this action.
+.RS
+.TP
+.B reclassify
+Restarts classification by jumping back to the first filter attached to this
+action's parent.
+.TP
+.B pipe
+Continue with the next action, this is the default.
+.TP
+.B drop
+.TQ
+.B shot
+Packet will be dropped without running further actions.
+.TP
+.B continue
+Continue classification with next filter in line.
+.TP
+.B pass
+Return to calling qdisc for packet processing. This ends the classification
+process.
+.RE
+.TP
+.BI index " u32_index "
+Specify an index for this action in order to being able to identify it in later
+commands.
+.I u32_index
+is a 32bit unsigned decimal value.
+.SH SEE ALSO
+.BR tc (8)
diff --git a/man/man8/tc-csum.8 b/man/man8/tc-csum.8
new file mode 100644
index 0000000..65724b8
--- /dev/null
+++ b/man/man8/tc-csum.8
@@ -0,0 +1,72 @@
+.TH "Checksum action in tc" 8 "11 Jan 2015" "iproute2" "Linux"
+
+.SH NAME
+csum - checksum update action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action csum"
+.I UPDATE
+
+.ti -8
+.IR UPDATE " := " TARGET " [ " UPDATE " ]"
+
+.ti -8
+.IR TARGET " := { "
+.BR ip4h " |"
+.BR icmp " |"
+.BR igmp " |"
+.BR tcp " |"
+.BR udp " |"
+.BR udplite " |"
+.BR sctp " |"
+.IR SWEETS " }"
+
+.ti -8
+.IR SWEETS " := { "
+.BR and " | " or " | " + " }"
+.SH DESCRIPTION
+The
+.B csum
+action triggers checksum recalculation of specified packet headers. It is
+commonly used to fix incorrect checksums after the
+.B pedit
+action has modified the packet content.
+.SH OPTIONS
+.TP
+.I TARGET
+Specify which headers to update: IPv4 header
+.RB ( ip4h ),
+ICMP header
+.RB ( icmp ),
+IGMP header
+.RB ( igmp ),
+TCP header
+.RB ( tcp ),
+UDP header
+.RB ( udp ),
+UDPLite header
+.RB ( udplite ") or"
+SCTP header
+.RB ( sctp ).
+.TP
+.B SWEETS
+These are merely syntactic sugar and ignored internally.
+.SH EXAMPLES
+The following performs stateless NAT for incoming packets from 192.0.2.100 to
+new destination 198.51.100.1. Assuming these are UDP
+packets, both IP and UDP checksums have to be recalculated:
+
+.RS
+.EX
+# tc qdisc add dev eth0 ingress handle ffff:
+# tc filter add dev eth0 prio 1 protocol ip parent ffff: \\
+ u32 match ip src 192.0.2.100/32 flowid :1 \\
+ action pedit munge ip dst set 198.51.100.1 pipe \\
+ csum ip and udp
+.EE
+.RE
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-pedit (8)
diff --git a/man/man8/tc-ctinfo.8 b/man/man8/tc-ctinfo.8
new file mode 100644
index 0000000..9015b84
--- /dev/null
+++ b/man/man8/tc-ctinfo.8
@@ -0,0 +1,170 @@
+.TH "ctinfo action in tc" 8 "4 Jun 2019" "iproute2" "Linux"
+.SH NAME
+ctinfo \- tc connmark processing action
+.SH SYNOPSIS
+.B tc ... action ctinfo
+[
+.B dscp
+MASK [STATEMASK] ] [
+.B cpmark
+[MASK] ] [
+.B zone
+ZONE ] [
+.B CONTROL
+] [
+.B index
+<INDEX>
+]
+
+.SH DESCRIPTION
+CTINFO (Conntrack Information) is a tc action for retrieving data from
+conntrack marks into various fields. At present it has two independent
+processing modes which may be viewed as sub-functions.
+
+DSCP mode copies a DSCP stored in conntrack's connmark into the IPv4/v6 diffserv
+field. The copying may conditionally occur based on a flag also stored in the
+connmark. DSCP mode was designed to assist in restoring packet classifications on
+ingress, classifications which may then be used by qdiscs such as CAKE. It may be
+used in any circumstance where ingress classification needs to be maintained across
+links that otherwise bleach or remap according to their own policies.
+
+CPMARK (copymark) mode copies the conntrack connmark into the packet's mark field. Without
+additional parameters it is functionally completely equivalent to the existing
+connmark action. An optional mask may be specified to mask which bits of the
+connmark are restored. This may be useful when DSCP and CPMARK modes are combined.
+
+Simple statistics (tc -s) on DSCP restores and CPMARK copies are maintained where values for
+set indicate a count of packets altered for that mode. DSCP includes an error count
+where the destination packet's diffserv field was unwriteable.
+.SH PARAMETERS
+.SS DSCP mode parameters:
+.IP mask
+A mask of 6 contiguous bits indicating where the DSCP value is located in the 32 bit
+conntrack mark field. A mask must be provided for this mode. mask is a 32 bit
+unsigned value.
+.IP statemask
+A mask of at least 1 bit indicating where a conditional restore flag is located in the
+32 bit conntrack mark field. The statemask bit/s must NOT overlap the mask bits. The
+DSCP will be restored if the conntrack mark logically ANDed with the statemask yields
+a non-zero result. statemask is an optional unsigned 32 bit value.
+.SS CPMARK mode parameters:
+.IP mask
+Store the logically ANDed result of conntrack mark and mask into the packet's mark
+field. Default is 0xffffffff i.e. the whole mark field. mask is an optional unsigned 32 bit
+value
+.SS Overall action parameters:
+.IP zone
+Specify the conntrack zone when doing conntrack lookups for packets.
+zone is a 16bit unsigned decimal value.
+Default is 0.
+.IP CONTROL
+The following keywords allow to control how the tree of qdisc, classes,
+filters and actions is further traversed after this action.
+.RS
+.TP
+.B reclassify
+Restart with the first filter in the current list.
+.TP
+.B pipe
+Continue with the next action attached to the same filter.
+.TP
+.B drop
+Drop the packet.
+.TP
+.B shot
+synonym for
+.B drop
+.TP
+.B continue
+Continue classification with the next filter in line.
+.TP
+.B pass
+Finish classification process and return to calling qdisc for further packet
+processing. This is the default.
+.RE
+.IP index
+Specify an index for this action in order to being able to identify it in later
+commands. index is a 32bit unsigned decimal value.
+.SH EXAMPLES
+Example showing conditional restoration of DSCP on ingress via an IFB
+.RS
+.EX
+
+#Set up the IFB interface
+.br
+tc qdisc add dev ifb4eth0 handle ffff: ingress
+
+#Put CAKE qdisc on it
+.br
+tc qdisc add dev ifb4eth0 root cake bandwidth 40mbit
+
+#Set interface UP
+.br
+ip link set dev ifb4eth0 up
+
+#Add 2 actions, ctinfo to restore dscp & mirred to redirect the packets to IFB
+.br
+tc filter add dev eth0 parent ffff: protocol all prio 10 u32 \\
+ match u32 0 0 flowid 1:1 action \\
+ ctinfo dscp 0xfc000000 0x01000000 \\
+ mirred egress redirect dev ifb4eth0
+
+tc -s qdisc show dev eth0 ingress
+
+ filter parent ffff: protocol all pref 10 u32 chain 0
+ filter parent ffff: protocol all pref 10 u32 chain 0 fh 800: ht divisor 1
+ filter parent ffff: protocol all pref 10 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 not_in_hw
+ match 00000000/00000000 at 0
+ action order 1: ctinfo zone 0 pipe
+ index 2 ref 1 bind 1 dscp 0xfc000000 0x01000000 installed 72 sec used 0 sec DSCP set 1333 error 0 CPMARK set 0
+ Action statistics:
+ Sent 658484 bytes 1833 pkt (dropped 0, overlimits 0 requeues 0)
+ backlog 0b 0p requeues 0
+
+ action order 2: mirred (Egress Redirect to device ifb4eth0) stolen
+ index 1 ref 1 bind 1 installed 72 sec used 0 sec
+ Action statistics:
+ Sent 658484 bytes 1833 pkt (dropped 0, overlimits 0 requeues 0)
+ backlog 0b 0p requeues 0
+.EE
+.RE
+
+Example showing conditional restoration of DSCP on egress
+
+This may appear nonsensical since iptables marking of egress packets is easy
+to achieve, however the iptables flow classification rules may be extensive
+and so some sort of set once and forget may be useful especially on cpu
+constrained devices.
+.RS
+.EX
+
+# Send unmarked connections to a marking chain which needs to store a DSCP
+and set statemask bit in the connmark
+.br
+iptables -t mangle -A POSTROUTING -o eth0 -m connmark \\
+ --mark 0x00000000/0x01000000 -g CLASS_MARKING_CHAIN
+
+# Apply marked DSCP to the packets
+.br
+tc filter add dev eth0 protocol all prio 10 u32 \\
+ match u32 0 0 flowid 1:1 action \\
+ ctinfo dscp 0xfc000000 0x01000000
+
+tc -s filter show dev eth0
+ filter parent 800e: protocol all pref 10 u32 chain 0
+ filter parent 800e: protocol all pref 10 u32 chain 0 fh 800: ht divisor 1
+ filter parent 800e: protocol all pref 10 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 not_in_hw
+ match 00000000/00000000 at 0
+ action order 1: ctinfo zone 0 pipe
+ index 1 ref 1 bind 1 dscp 0xfc000000 0x01000000 installed 7414 sec used 0 sec DSCP set 53404 error 0 CPMARK set 0
+ Action statistics:
+ Sent 32890260 bytes 120441 pkt (dropped 0, overlimits 0 requeues 0)
+ backlog 0b 0p requeues 0
+.br
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-cake (8)
+.BR tc-connmark (8)
+.BR tc-mirred (8)
+.SH AUTHORS
+ctinfo was written by Kevin Darbyshire-Bryant.
diff --git a/man/man8/tc-drr.8 b/man/man8/tc-drr.8
new file mode 100644
index 0000000..2fea4ee
--- /dev/null
+++ b/man/man8/tc-drr.8
@@ -0,0 +1,94 @@
+.TH TC 8 "January 2010" "iproute2" "Linux"
+.SH NAME
+drr \- deficit round robin scheduler
+.SH SYNOPSIS
+.B tc qdisc ... add drr
+.B [ quantum
+bytes
+.B ]
+
+.SH DESCRIPTION
+
+The Deficit Round Robin Scheduler is a classful queuing discipline as
+a more flexible replacement for Stochastic Fairness Queuing.
+
+Unlike SFQ, there are no built-in queues \-\- you need to add classes
+and then set up filters to classify packets accordingly.
+This can be useful e.g. for using RED qdiscs with different settings for particular
+traffic. There is no default class \-\- if a packet cannot be classified,
+it is dropped.
+
+.SH ALGORITHM
+Each class is assigned a deficit counter, initialized to
+.B quantum.
+
+DRR maintains an (internal) ''active'' list of classes whose qdiscs are
+non-empty. This list is used for dequeuing. A packet is dequeued from
+the class at the head of the list if the packet size is smaller or equal
+to the deficit counter. If the counter is too small, it is increased by
+.B quantum
+and the scheduler moves on to the next class in the active list.
+
+
+.SH PARAMETERS
+.TP
+quantum
+Amount of bytes a flow is allowed to dequeue before the scheduler moves to
+the next class. Defaults to the MTU of the interface. The minimum value is 1.
+
+.SH EXAMPLE & USAGE
+
+To attach to device eth0, using the interface MTU as its quantum:
+.P
+# tc qdisc add dev eth0 handle 1 root drr
+.P
+Adding two classes:
+.P
+# tc class add dev eth0 parent 1: classid 1:1 drr
+.br
+# tc class add dev eth0 parent 1: classid 1:2 drr
+.P
+You also need to add at least one filter to classify packets.
+.P
+# tc filter add dev eth0 protocol .. classid 1:1
+.P
+
+Like SFQ, DRR is only useful when it owns the queue \-\- it is a pure scheduler and does
+not delay packets. Attaching non-work-conserving qdiscs like tbf to it does not make
+sense \-\- other qdiscs in the active list will also become inactive until the dequeue
+operation succeeds. Embed DRR within another qdisc like HTB or HFSC to ensure it owns the queue.
+.P
+You can mimic SFQ behavior by assigning packets to the attached classes using the
+flow filter:
+
+.B tc qdisc add dev .. drr
+
+.B for i in .. 1024;do
+.br
+.B "\ttc class add dev .. classid $handle:$(print %x $i)"
+.br
+.B "\ttc qdisc add dev .. fifo limit 16"
+.br
+.B done
+
+.B tc filter add .. protocol ip .. $handle flow hash keys src,dst,proto,proto-src,proto-dst divisor 1024 perturb 10
+
+
+.SH SOURCE
+.TP
+o
+M. Shreedhar and George Varghese "Efficient Fair
+Queuing using Deficit Round Robin", Proc. SIGCOMM 95.
+
+.SH NOTES
+
+This implementation does not drop packets from the longest queue on overrun,
+as limits are handled by the individual child qdiscs.
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-htb (8),
+.BR tc-sfq (8)
+
+.SH AUTHOR
+sched_drr was written by Patrick McHardy.
diff --git a/man/man8/tc-ematch.8 b/man/man8/tc-ematch.8
new file mode 100644
index 0000000..3df870f
--- /dev/null
+++ b/man/man8/tc-ematch.8
@@ -0,0 +1,160 @@
+.TH ematch 8 "6 August 2012" iproute2 Linux
+.
+.SH NAME
+ematch \- extended matches for use with "basic", "cgroup" or "flow" filters
+.
+.SH SYNOPSIS
+.sp
+.ad l
+.B "tc filter add .. basic match"
+.RI EXPR
+.B .. flowid ..
+.sp
+
+.IR EXPR " := " TERM " [ { "
+.B and | or
+}
+.IR EXPR
+]
+
+.IR TERM " := [ " \fBnot " ] { " MATCH " | '(' " EXPR " ')' } "
+
+.IR MATCH " := " module " '(' " ARGS " ')' "
+
+.IR ARGS " := " ARG1 " " ARG2 " ..
+
+.SH MATCHES
+
+.SS cmp
+Simple comparison ematch: arithmetic compare of packet data to a given value.
+
+.IR cmp "( " ALIGN " at " OFFSET " [ " ATTRS " ] { " eq " | " lt " | " gt " } " VALUE " )
+
+.IR ALIGN " := { " u8 " | " u16 " | " u32 " } "
+
+.IR ATTRS " := [ layer " LAYER " ] [ mask " MASK " ] [ trans ]
+
+.IR LAYER " := { " link " | " network " | " transport " | " 0..2 " }
+
+.SS meta
+Metadata ematch
+
+.IR meta "( " OBJECT " { " eq " | " lt " |" gt " } " OBJECT " )
+
+.IR OBJECT " := { " META_ID " | " VALUE " }
+
+.IR META_ID " := " id " [ shift " SHIFT " ] [ mask " MASK " ]
+
+.TP
+meta attributes:
+
+\fBrandom\fP 32 bit random value
+
+\fBloadavg_1\fP Load average in last 5 minutes
+
+\fBnf_mark\fP Netfilter mark
+
+\fBvlan\fP Vlan tag
+
+\fBsk_rcvbuf\fP Receive buffer size
+
+\fBsk_snd_queue\fP Send queue length
+
+.PP
+A full list of meta attributes can be obtained via
+
+# tc filter add dev eth1 basic match 'meta(list)'
+
+.SS nbyte
+match packet data byte sequence
+
+.IR nbyte "( " NEEDLE " at " OFFSET " [ layer " LAYER " ] )
+
+.IR NEEDLE " := { " string " | " c-escape-sequence " } "
+
+.IR OFFSET " := " int
+
+.IR LAYER " := { " link " | " network " | " transport " | " 0..2 " }
+
+.SS u32
+u32 ematch
+
+.IR u32 "( " ALIGN " " VALUE " " MASK " at [ nexthdr+ ] " OFFSET " )
+
+.IR ALIGN " := { " u8 " | " u16 " | " u32 " }
+
+.SS ipset
+test packet against ipset membership
+
+.IR ipset "( " SETNAME " " FLAGS " )
+
+.IR SETNAME " := " string
+
+.IR FLAGS " := { " FLAG " [, " FLAGS "] }
+
+The flag options are the same as those used by the iptables "set" match.
+
+When using the ipset ematch with the "ip_set_hash:net,iface" set type,
+the interface can be queried using "src,dst (source ip address, outgoing interface) or
+"src,src" (source ip address, incoming interface) syntax.
+
+.SS ipt
+test packet against xtables matches
+
+.IR ipt "( " [-6] " "-m " " MATCH_NAME " " FLAGS " )
+
+.IR MATCH_NAME " := " string
+
+.IR FLAGS " := { " FLAG " [, " FLAGS "] }
+
+The flag options are the same as those used by the xtable match used.
+
+.SS canid
+ematch rule to match CAN frames
+
+.IR canid "( " IDLIST " )
+
+.IR IDLIST " := " IDSPEC [ IDLIST ]
+
+.IR IDSPEC " := { ’sff’ " CANID " | ’eff’ " CANID " }
+
+.IR CANID " := " ID [ ":MASK" ]
+
+.IR ID ", " MASK " := hexadecimal number (i.e. 0x123)
+
+.SH CAVEATS
+
+The ematch syntax uses '(' and ')' to group expressions. All braces need to be
+escaped properly to prevent shell commandline from interpreting these directly.
+
+When using the ipset ematch with the "ifb" device, the outgoing device will be the
+ifb device itself, e.g. "ifb0".
+The original interface (i.e. the device the packet arrived on) is treated as the incoming interface.
+
+.SH EXAMPLE & USAGE
+
+# tc filter add .. basic match ...
+
+# 'cmp(u16 at 3 layer 2 mask 0xff00 gt 20)'
+
+# 'meta(nfmark gt 24)' and 'meta(tcindex mask 0xf0 eq 0xf0)'
+
+# 'nbyte("ababa" at 12 layer 1)'
+
+# 'u32(u16 0x1122 0xffff at nexthdr+4)'
+
+Check if packet source ip address is member of set named \fBbulk\fP:
+
+# 'ipset(bulk src)'
+
+Check if packet source ip and the interface the packet arrived on is member of "hash:net,iface" set named \fBinteractive\fP:
+
+# 'ipset(interactive src,src)'
+
+Check if packet matches an IPSec state with reqid 1:
+
+# 'ipt(-m policy --dir in --pol ipsec --reqid 1)'
+
+.SH "AUTHOR"
+
+The extended match infrastructure was added by Thomas Graf.
diff --git a/man/man8/tc-etf.8 b/man/man8/tc-etf.8
new file mode 100644
index 0000000..4cb3b9e
--- /dev/null
+++ b/man/man8/tc-etf.8
@@ -0,0 +1,151 @@
+.TH ETF 8 "05 Jul 2018" "iproute2" "Linux"
+.SH NAME
+ETF \- Earliest TxTime First (ETF) Qdisc
+.SH SYNOPSIS
+.B tc qdisc ... dev
+dev
+.B parent
+classid
+.B [ handle
+major:
+.B ] etf clockid
+clockid
+.B [ delta
+delta_nsecs
+.B ] [ deadline_mode ]
+.B [ offload ]
+
+.SH DESCRIPTION
+The ETF (Earliest TxTime First) qdisc allows applications to control
+the instant when a packet should be dequeued from the traffic control
+layer into the netdevice. If
+.B offload
+is configured and supported by the network interface card, the it will
+also control when packets leave the network controller.
+
+ETF achieves that by buffering packets until a configurable time
+before their transmission time (i.e. txtime, or deadline), which can
+be configured through the
+.B delta
+option.
+
+The qdisc uses a rb-tree internally so packets are always 'ordered' by
+their txtime and will be dequeued following the (next) earliest txtime
+first.
+
+It relies on the SO_TXTIME socket option and the SCM_TXTIME CMSG in
+each packet field to configure the behavior of time dependent sockets:
+the clockid to be used as a reference, if the expected mode of txtime
+for that socket is deadline or strict mode, and if packet drops should
+be reported on the socket's error queue. See
+.BR socket(7)
+for more information.
+
+The etf qdisc will drop any packets with a txtime in the past, or if a
+packet expires while waiting for being dequeued.
+
+This queueing discipline is intended to be used by TSN (Time Sensitive
+Networking) applications, and it exposes a traffic shaping functionality
+that is commonly documented as "Launch Time" or "Time-Based Scheduling"
+by vendors and the documentation of network interface controllers.
+
+ETF is meant to be installed under another qdisc that maps packet flows
+to traffic classes, one example is
+.BR mqprio(8).
+
+.SH PARAMETERS
+.TP
+clockid
+.br
+Specifies the clock to be used by qdisc's internal timer for measuring
+time and scheduling events. The qdisc expects that packets passing
+through it to be using this same
+.B clockid
+as the reference of their txtime timestamps. It will drop packets
+coming from sockets that do not comply with that.
+
+For more information about time and clocks on Linux, please refer
+to
+.BR time(7)
+and
+.BR clock_gettime(3).
+
+.TP
+delta
+.br
+After enqueueing or dequeueing a packet, the qdisc will schedule its
+next wake-up time for the next txtime minus this delta value.
+This means
+.B delta
+can be used as a fudge factor for the scheduler latency of a system.
+This value must be specified in nanoseconds.
+The default value is 0 nanoseconds.
+
+.TP
+deadline_mode
+.br
+When
+.B deadline_mode
+is set, the qdisc will handle txtime with a different semantics,
+changed from a 'strict' transmission time to a deadline.
+In practice, this means during the dequeue flow
+.BR etf(8)
+will set the txtime of the packet being dequeued to 'now'.
+The default is for this option to be disabled.
+
+.TP
+offload
+.br
+When
+.B offload
+is set,
+.BR etf(8)
+will try to configure the network interface so time-based transmission
+arbitration is enabled in the controller. This feature is commonly
+referred to as "Launch Time" or "Time-Based Scheduling" by the
+documentation of network interface controllers.
+The default is for this option to be disabled.
+
+.TP
+skip_sock_check
+.br
+.BR etf(8)
+currently drops any packet which does not have a socket associated with it or
+if the socket does not have SO_TXTIME socket option set. But, this will not
+work if the launchtime is set by another entity inside the kernel (e.g. some
+other Qdisc). Setting the skip_sock_check will skip checking for a socket
+associated with the packet.
+
+.SH EXAMPLES
+
+ETF is used to enforce a Quality of Service. It controls when each
+packets should be dequeued and transmitted, and can be used for
+limiting the data rate of a traffic class. To separate packets into
+traffic classes the user may choose
+.BR mqprio(8),
+and configure it like this:
+
+.EX
+# tc qdisc add dev eth0 handle 100: parent root mqprio num_tc 3 \\
+ map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 \\
+ queues 1@0 1@1 2@2 \\
+ hw 0
+.EE
+.P
+To replace the current queueing discipline by ETF in traffic class
+number 0, issue:
+.P
+.EX
+# tc qdisc replace dev eth0 parent 100:1 etf \\
+ clockid CLOCK_TAI delta 300000 offload
+.EE
+
+With the options above, etf will be configured to use CLOCK_TAI as
+its clockid_t, will schedule packets for 300 us before their txtime,
+and will enable the functionality on that in the network interface
+card. Deadline mode will not be configured for this mode.
+
+.SH AUTHORS
+Jesus Sanchez-Palencia <jesus.sanchez-palencia@intel.com>
+.br
+Vinicius Costa Gomes <vinicius.gomes@intel.com>
diff --git a/man/man8/tc-flow.8 b/man/man8/tc-flow.8
new file mode 100644
index 0000000..54f6bf7
--- /dev/null
+++ b/man/man8/tc-flow.8
@@ -0,0 +1,267 @@
+.TH "Flow filter in tc" 8 "20 Oct 2015" "iproute2" "Linux"
+
+.SH NAME
+flow \- flow based traffic control filter
+.SH SYNOPSIS
+.TP
+Mapping mode:
+
+.RS
+.in +8
+.ti -8
+.BR tc " " filter " ... " "flow map key "
+.IR KEY " [ " OPS " ] [ " OPTIONS " ] "
+.RE
+.TP
+Hashing mode:
+
+.RS
+.in +8
+.ti -8
+.BR tc " " filter " ... " "flow hash keys "
+.IR KEY_LIST " [ "
+.B perturb
+.IR secs " ] [ " OPTIONS " ] "
+.RE
+
+.in +8
+.ti -8
+.IR OPS " := [ " OPS " ] " OP
+
+.ti -8
+.IR OPTIONS " := [ "
+.B divisor
+.IR NUM " ] [ "
+.B baseclass
+.IR ID " ] [ "
+.B match
+.IR EMATCH_TREE " ] [ "
+.B action
+.IR ACTION_SPEC " ]"
+
+.ti -8
+.IR KEY_LIST " := [ " KEY_LIST " ] " KEY
+
+.ti -8
+.IR OP " := { "
+.BR or " | " and " | " xor " | " rshift " | " addend " } "
+.I NUM
+
+.ti -8
+.IR ID " := " X : Y
+
+.ti -8
+.IR KEY " := { "
+.BR src " | " dst " | " proto " | " proto-src " | " proto-dst " | " iif " | "
+.BR priority " | " mark " | " nfct " | " nfct-src " | " nfct-dst " | "
+.BR nfct-proto-src " | " nfct-proto-dst " | " rt-classid " | " sk-uid " | "
+.BR sk-gid " | " vlan-tag " | " rxhash " }"
+.SH DESCRIPTION
+The
+.B flow
+classifier is meant to extend the
+.B SFQ
+hashing capabilities without hard-coding new hash functions. It also allows
+deterministic mappings of keys to classes.
+.SH OPTIONS
+.TP
+.BI action " ACTION_SPEC"
+Apply an action from the generic actions framework on matching packets.
+.TP
+.BI baseclass " ID"
+An offset for the resulting class ID.
+.I ID
+may be
+.BR root ", " none
+or a hexadecimal class ID in the form [\fIX\fB:\fR]\fIY\fR. \fIX\fR must
+match qdisc's/class's major handle (if omitted, the correct value is chosen
+automatically). If the whole \fBbaseclass\fR is omitted, \fIY\fR defaults
+to 1.
+.TP
+.BI divisor " NUM"
+Number of buckets to use for sorting into. Keys are calculated modulo
+.IR NUM .
+.TP
+.BI "hash keys " KEY-LIST
+Perform a
+.B jhash2
+operation over the keys in
+.IR KEY-LIST ,
+the result (modulo the
+.B divisor
+if given) is taken as class ID, optionally offset by the value of
+.BR baseclass .
+It is possible to specify an interval (in seconds) after which
+.BR jhash2 's
+entropy source is recreated using the
+.B perturb
+parameter.
+.TP
+.BI "map key " KEY
+Packet data identified by
+.I KEY
+is translated into class IDs to push the packet into. The value may be mangled by
+.I OPS
+before using it for the mapping. They are applied in the order listed here:
+.RS
+.TP 4
+.BI and " NUM"
+Perform bitwise
+.B AND
+operation with numeric value
+.IR NUM .
+.TP
+.BI or " NUM"
+Perform bitwise
+.B OR
+operation with numeric value
+.IR NUM .
+.TP
+.BI xor " NUM"
+Perform bitwise
+.B XOR
+operation with numeric value
+.IR NUM .
+.TP
+.BI rshift " NUM"
+Shift the value of
+.I KEY
+to the right by
+.I NUM
+bits.
+.TP
+.BI addend " NUM"
+Add
+.I NUM
+to the value of
+.IR KEY .
+
+.RE
+.RS
+For the
+.BR or ", " and ", " xor " and " rshift
+operations,
+.I NUM
+is assumed to be an unsigned, 32bit integer value. For the
+.B addend
+operation,
+.I NUM
+may be much more complex: It may be prefixed by a minus ('-') sign to cause
+subtraction instead of addition and for keys of
+.BR src ", " dst ", " nfct-src " and " nfct-dst
+it may be given in IP address notation. See below for an illustrating example.
+.RE
+.TP
+.BI match " EMATCH_TREE"
+Match packets using the extended match infrastructure. See
+.BR tc-ematch (8)
+for a detailed description of the allowed syntax in
+.IR EMATCH_TREE .
+.SH KEYS
+In mapping mode, a single key is used (after optional permutation) to build a
+class ID. The resulting ID is deducible in most cases. In hashing more, a number
+of keys may be specified which are then hashed and the output used as class ID.
+This ID is not deducible in beforehand, and may even change over time for a
+given flow if a
+.B perturb
+interval has been given.
+
+The range of class IDs can be limited by the
+.B divisor
+option, which is used for a modulus.
+.TP
+.BR src ", " dst
+Use source or destination address as key. In case of IPv4 and TIPC, this is the
+actual address value. For IPv6, the 128bit address is folded into a 32bit value
+by XOR'ing the four 32bit words. In all other cases, the kernel-internal socket
+address is used (after folding into 32bits on 64bit systems).
+.TP
+.B proto
+Use the layer four protocol number as key.
+.TP
+.B proto-src
+Use the layer four source port as key. If not available, the kernel-internal
+socket address is used instead.
+.TP
+.B proto-dst
+Use the layer four destination port as key. If not available, the associated
+kernel-internal dst_entry address is used after XOR'ing with the packet's
+layer three protocol number.
+.TP
+.B iif
+Use the incoming interface index as key.
+.TP
+.B priority
+Use the packet's priority as key. Usually this is the IP header's DSCP/ECN
+value.
+.TP
+.B mark
+Use the netfilter
+.B fwmark
+as key.
+.TP
+.B nfct
+Use the associated conntrack entry address as key.
+.TP
+.BR nfct-src ", " nfct-dst ", " nfct-proto-src ", " nfct-proto-dst
+These are conntrack-aware variants of
+.BR src ", " dst ", " proto-src " and " proto-dst .
+In case of NAT, these are basically the packet header's values before NAT was
+applied.
+.TP
+.B rt-classid
+Use the packet's destination routing table entry's realm as key.
+.TP
+.B sk-uid
+.TQ
+.B sk-gid
+For locally generated packets, use the user or group ID the originating socket
+belongs to as key.
+.TP
+.B vlan-tag
+Use the packet's vlan ID as key.
+.TP
+.B rxhash
+Use the flow hash as key.
+
+.SH EXAMPLES
+.TP
+Classic SFQ hash:
+
+.EX
+tc filter add ... flow hash \\
+ keys src,dst,proto,proto-src,proto-dst divisor 1024
+.EE
+.TP
+Classic SFQ hash, but using information from conntrack to work properly in combination with NAT:
+
+.EX
+tc filter add ... flow hash \\
+ keys nfct-src,nfct-dst,proto,nfct-proto-src,nfct-proto-dst \\
+ divisor 1024
+.EE
+.TP
+Map destination IPs of 192.168.0.0/24 to classids 1-256:
+
+.EX
+tc filter add ... flow map \\
+ key dst addend -192.168.0.0 divisor 256
+.EE
+.TP
+Alternative to the above:
+
+.EX
+tc filter add ... flow map \\
+ key dst and 0xff
+.EE
+.TP
+The same, but in reverse order:
+
+.EX
+tc filter add ... flow map \\
+ key dst and 0xff xor 0xff
+.EE
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-ematch (8),
+.BR tc-sfq (8)
diff --git a/man/man8/tc-flower.8 b/man/man8/tc-flower.8
new file mode 100644
index 0000000..04ee194
--- /dev/null
+++ b/man/man8/tc-flower.8
@@ -0,0 +1,384 @@
+.TH "Flower filter in tc" 8 "22 Oct 2015" "iproute2" "Linux"
+
+.SH NAME
+flower \- flow based traffic control filter
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " " filter " ... " flower " [ "
+.IR MATCH_LIST " ] [ "
+.B action
+.IR ACTION_SPEC " ] [ "
+.B classid
+.IR CLASSID " ] [ "
+.B hw_tc
+.IR TCID " ]"
+
+
+.ti -8
+.IR MATCH_LIST " := [ " MATCH_LIST " ] " MATCH
+
+.ti -8
+.IR MATCH " := { "
+.B indev
+.IR ifname " | "
+.BR verbose
+.RI " | "
+.BR skip_sw " | " skip_hw
+.RI " | { "
+.BR dst_mac " | " src_mac " } "
+.IR MASKED_LLADDR " | "
+.B vlan_id
+.IR VID " | "
+.B vlan_prio
+.IR PRIORITY " | "
+.BR vlan_ethtype " { " ipv4 " | " ipv6 " | "
+.IR ETH_TYPE " } | "
+.B cvlan_id
+.IR VID " | "
+.B cvlan_prio
+.IR PRIORITY " | "
+.BR cvlan_ethtype " { " ipv4 " | " ipv6 " | "
+.IR ETH_TYPE " } | "
+.B mpls_label
+.IR LABEL " | "
+.B mpls_tc
+.IR TC " | "
+.B mpls_bos
+.IR BOS " | "
+.B mpls_ttl
+.IR TTL " | "
+.BR ip_proto " { " tcp " | " udp " | " sctp " | " icmp " | " icmpv6 " | "
+.IR IP_PROTO " } | "
+.B ip_tos
+.IR MASKED_IP_TOS " | "
+.B ip_ttl
+.IR MASKED_IP_TTL " | { "
+.BR dst_ip " | " src_ip " } "
+.IR PREFIX " | { "
+.BR dst_port " | " src_port " } { "
+.IR port_number " | "
+.IR min_port_number-max_port_number " } | "
+.B tcp_flags
+.IR MASKED_TCP_FLAGS " | "
+.B type
+.IR MASKED_TYPE " | "
+.B code
+.IR MASKED_CODE " | { "
+.BR arp_tip " | " arp_sip " } "
+.IR IPV4_PREFIX " | "
+.BR arp_op " { " request " | " reply " | "
+.IR OP " } | { "
+.BR arp_tha " | " arp_sha " } "
+.IR MASKED_LLADDR " | "
+.B enc_key_id
+.IR KEY-ID " | {"
+.BR enc_dst_ip " | " enc_src_ip " } { "
+.IR ipv4_address " | " ipv6_address " } | "
+.B enc_dst_port
+.IR port_number " | "
+.B enc_tos
+.IR TOS " | "
+.B enc_ttl
+.IR TTL " | "
+.B geneve_opts
+.IR OPTIONS " | "
+.BR ip_flags
+.IR IP_FLAGS
+.SH DESCRIPTION
+The
+.B flower
+filter matches flows to the set of keys specified and assigns an arbitrarily
+chosen class ID to packets belonging to them. Additionally (or alternatively) an
+action from the generic action framework may be called.
+.SH OPTIONS
+.TP
+.BI action " ACTION_SPEC"
+Apply an action from the generic actions framework on matching packets.
+.TP
+.BI classid " CLASSID"
+Specify a class to pass matching packets on to.
+.I CLASSID
+is in the form
+.BR X : Y ", while " X " and " Y
+are interpreted as numbers in hexadecimal format.
+.TP
+.BI hw_tc " TCID"
+Specify a hardware traffic class to pass matching packets on to. TCID is in the
+range 0 through 15.
+.TP
+.BI indev " ifname"
+Match on incoming interface name. Obviously this makes sense only for forwarded
+flows.
+.I ifname
+is the name of an interface which must exist at the time of
+.B tc
+invocation.
+.TP
+.BI verbose
+Enable verbose logging, including offloading errors when not using
+.B skip_sw
+flag.
+.TP
+.BI skip_sw
+Do not process filter by software. If hardware has no offload support for this
+filter, or TC offload is not enabled for the interface, operation will fail.
+.TP
+.BI skip_hw
+Do not process filter by hardware.
+.TP
+.BI dst_mac " MASKED_LLADDR"
+.TQ
+.BI src_mac " MASKED_LLADDR"
+Match on source or destination MAC address. A mask may be optionally
+provided to limit the bits of the address which are matched. A mask is
+provided by following the address with a slash and then the mask. It may be
+provided in LLADDR format, in which case it is a bitwise mask, or as a
+number of high bits to match. If the mask is missing then a match on all
+bits is assumed.
+.TP
+.BI vlan_id " VID"
+Match on vlan tag id.
+.I VID
+is an unsigned 12bit value in decimal format.
+.TP
+.BI vlan_prio " PRIORITY"
+Match on vlan tag priority.
+.I PRIORITY
+is an unsigned 3bit value in decimal format.
+.TP
+.BI vlan_ethtype " VLAN_ETH_TYPE"
+Match on layer three protocol.
+.I VLAN_ETH_TYPE
+may be either
+.BR ipv4 ", " ipv6
+or an unsigned 16bit value in hexadecimal format. To match on QinQ packet, it must be 802.1Q or 802.1AD.
+.TP
+.BI cvlan_id " VID"
+Match on QinQ inner vlan tag id.
+.I VID
+is an unsigned 12bit value in decimal format.
+.TP
+.BI cvlan_prio " PRIORITY"
+Match on QinQ inner vlan tag priority.
+.I PRIORITY
+is an unsigned 3bit value in decimal format.
+.TP
+.BI cvlan_ethtype " VLAN_ETH_TYPE"
+Match on QinQ layer three protocol.
+.I VLAN_ETH_TYPE
+may be either
+.BR ipv4 ", " ipv6
+or an unsigned 16bit value in hexadecimal format.
+.TP
+.BI mpls_label " LABEL"
+Match the label id in the outermost MPLS label stack entry.
+.I LABEL
+is an unsigned 20 bit value in decimal format.
+.TP
+.BI mpls_tc " TC"
+Match on the MPLS TC field, which is typically used for packet priority,
+in the outermost MPLS label stack entry.
+.I TC
+is an unsigned 3 bit value in decimal format.
+.TP
+.BI mpls_bos " BOS"
+Match on the MPLS Bottom Of Stack field in the outermost MPLS label stack
+entry.
+.I BOS
+is a 1 bit value in decimal format.
+.TP
+.BI mpls_ttl " TTL"
+Match on the MPLS Time To Live field in the outermost MPLS label stack
+entry.
+.I TTL
+is an unsigned 8 bit value in decimal format.
+.TP
+.BI ip_proto " IP_PROTO"
+Match on layer four protocol.
+.I IP_PROTO
+may be
+.BR tcp ", " udp ", " sctp ", " icmp ", " icmpv6
+or an unsigned 8bit value in hexadecimal format.
+.TP
+.BI ip_tos " MASKED_IP_TOS"
+Match on ipv4 TOS or ipv6 traffic-class - eight bits in hexadecimal format.
+A mask may be optionally provided to limit the bits which are matched. A mask
+is provided by following the value with a slash and then the mask. If the mask
+is missing then a match on all bits is assumed.
+.TP
+.BI ip_ttl " MASKED_IP_TTL"
+Match on ipv4 TTL or ipv6 hop-limit - eight bits value in decimal or hexadecimal format.
+A mask may be optionally provided to limit the bits which are matched. Same
+logic is used for the mask as with matching on ip_tos.
+.TP
+.BI dst_ip " PREFIX"
+.TQ
+.BI src_ip " PREFIX"
+Match on source or destination IP address.
+.I PREFIX
+must be a valid IPv4 or IPv6 address, depending on the \fBprotocol\fR
+option to tc filter, optionally followed by a slash and the prefix length.
+If the prefix is missing, \fBtc\fR assumes a full-length host match.
+.TP
+.IR \fBdst_port " { " NUMBER " | " " MIN_VALUE-MAX_VALUE " }
+.TQ
+.IR \fBsrc_port " { " NUMBER " | " " MIN_VALUE-MAX_VALUE " }
+Match on layer 4 protocol source or destination port number. Alternatively, the
+mininum and maximum values can be specified to match on a range of layer 4
+protocol source or destination port numbers. Only available for
+.BR ip_proto " values " udp ", " tcp " and " sctp
+which have to be specified in beforehand.
+.TP
+.BI tcp_flags " MASKED_TCP_FLAGS"
+Match on TCP flags represented as 12bit bitfield in in hexadecimal format.
+A mask may be optionally provided to limit the bits which are matched. A mask
+is provided by following the value with a slash and then the mask. If the mask
+is missing then a match on all bits is assumed.
+.TP
+.BI type " MASKED_TYPE"
+.TQ
+.BI code " MASKED_CODE"
+Match on ICMP type or code. A mask may be optionally provided to limit the
+bits of the address which are matched. A mask is provided by following the
+address with a slash and then the mask. The mask must be as a number which
+represents a bitwise mask If the mask is missing then a match on all bits
+is assumed. Only available for
+.BR ip_proto " values " icmp " and " icmpv6
+which have to be specified in beforehand.
+.TP
+.BI arp_tip " IPV4_PREFIX"
+.TQ
+.BI arp_sip " IPV4_PREFIX"
+Match on ARP or RARP sender or target IP address.
+.I IPV4_PREFIX
+must be a valid IPv4 address optionally followed by a slash and the prefix
+length. If the prefix is missing, \fBtc\fR assumes a full-length host
+match.
+.TP
+.BI arp_op " ARP_OP"
+Match on ARP or RARP operation.
+.I ARP_OP
+may be
+.BR request ", " reply
+or an integer value 0, 1 or 2. A mask may be optionally provided to limit
+the bits of the operation which are matched. A mask is provided by
+following the address with a slash and then the mask. It may be provided as
+an unsigned 8 bit value representing a bitwise mask. If the mask is missing
+then a match on all bits is assumed.
+.TP
+.BI arp_sha " MASKED_LLADDR"
+.TQ
+.BI arp_tha " MASKED_LLADDR"
+Match on ARP or RARP sender or target MAC address. A mask may be optionally
+provided to limit the bits of the address which are matched. A mask is
+provided by following the address with a slash and then the mask. It may be
+provided in LLADDR format, in which case it is a bitwise mask, or as a
+number of high bits to match. If the mask is missing then a match on all
+bits is assumed.
+.TP
+.BI enc_key_id " NUMBER"
+.TQ
+.BI enc_dst_ip " PREFIX"
+.TQ
+.BI enc_src_ip " PREFIX"
+.TQ
+.BI enc_dst_port " NUMBER"
+.TQ
+.BI enc_tos " NUMBER"
+.TQ
+.BI enc_ttl " NUMBER"
+.TQ
+.BR
+.TP
+.BI ct_state " CT_STATE"
+.TQ
+.BI ct_zone " CT_MASKED_ZONE"
+.TQ
+.BI ct_mark " CT_MASKED_MARK"
+.TQ
+.BI ct_label " CT_MASKED_LABEL"
+Matches on connection tracking info
+.RS
+.TP
+.I CT_STATE
+Match the connection state, and can ne combination of [{+|-}flag] flags, where flag can be one of
+.RS
+.TP
+trk - Tracked connection.
+.TP
+new - New connection.
+.TP
+est - Established connection.
+.TP
+Example: +trk+est
+.RE
+.TP
+.I CT_MASKED_ZONE
+Match the connection zone, and can be masked.
+.TP
+.I CT_MASKED_MARK
+32bit match on the connection mark, and can be masked.
+.TP
+.I CT_MASKED_LABEL
+128bit match on the connection label, and can be masked.
+.RE
+.TP
+.BI geneve_opts " OPTIONS"
+Match on IP tunnel metadata. Key id
+.I NUMBER
+is a 32 bit tunnel key id (e.g. VNI for VXLAN tunnel).
+.I PREFIX
+must be a valid IPv4 or IPv6 address optionally followed by a slash and the
+prefix length. If the prefix is missing, \fBtc\fR assumes a full-length
+host match. Dst port
+.I NUMBER
+is a 16 bit UDP dst port. Tos
+.I NUMBER
+is an 8 bit tos (dscp+ecn) value, ttl
+.I NUMBER
+is an 8 bit time-to-live value. geneve_opts
+.I OPTIONS
+must be a valid list of comma-separated geneve options where each option
+consists of a key optionally followed by a slash and corresponding mask. If
+the masks is missing, \fBtc\fR assumes a full-length match. The options can
+be described in the form CLASS:TYPE:DATA/CLASS_MASK:TYPE_MASK:DATA_MASK,
+where CLASS is represented as a 16bit hexadecimal value, TYPE as an 8bit
+hexadecimal value and DATA as a variable length hexadecimal value.
+.TP
+.BI ip_flags " IP_FLAGS"
+.I IP_FLAGS
+may be either
+.BR frag ", " nofrag ", " firstfrag " or " nofirstfrag
+where frag and nofrag could be used to match on fragmented packets or not,
+respectively. firstfrag and nofirstfrag can be used to further distinguish
+fragmented packet. firstfrag can be used to indicate the first fragmented
+packet. nofirstfrag can be used to indicates subsequent fragmented packets
+or non-fragmented packets.
+.SH NOTES
+As stated above where applicable, matches of a certain layer implicitly depend
+on the matches of the next lower layer. Precisely, layer one and two matches
+(\fBindev\fR, \fBdst_mac\fR and \fBsrc_mac\fR)
+have no dependency,
+MPLS and layer three matches
+(\fBmpls_label\fR, \fBmpls_tc\fR, \fBmpls_bos\fR, \fBmpls_ttl\fR,
+\fBip_proto\fR, \fBdst_ip\fR, \fBsrc_ip\fR, \fBarp_tip\fR, \fBarp_sip\fR,
+\fBarp_op\fR, \fBarp_tha\fR, \fBarp_sha\fR and \fBip_flags\fR)
+depend on the
+.B protocol
+option of tc filter, layer four port matches
+(\fBdst_port\fR and \fBsrc_port\fR)
+depend on
+.B ip_proto
+being set to
+.BR tcp ", " udp " or " sctp,
+and finally ICMP matches (\fBcode\fR and \fBtype\fR) depend on
+.B ip_proto
+being set to
+.BR icmp " or " icmpv6.
+.P
+There can be only used one mask per one prio. If user needs to specify different
+mask, he has to use different prio.
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-flow (8)
diff --git a/man/man8/tc-fq.8 b/man/man8/tc-fq.8
new file mode 100644
index 0000000..1febe62
--- /dev/null
+++ b/man/man8/tc-fq.8
@@ -0,0 +1,107 @@
+.TH FQ 8 "10 Sept 2015" "iproute2" "Linux"
+.SH NAME
+FQ \- Fair Queue traffic policing
+.SH SYNOPSIS
+.B tc qdisc ... fq
+[
+.B limit
+PACKETS ] [
+.B flow_limit
+PACKETS ] [
+.B quantum
+BYTES ] [
+.B initial_quantum
+BYTES ] [
+.B maxrate
+RATE ] [
+.B buckets
+NUMBER ] [
+.B orphan_mask
+NUMBER ] [
+.B pacing
+|
+.B nopacing
+] [
+.B ce_threshold
+TIME ]
+
+.SH DESCRIPTION
+FQ (Fair Queue) is a classless packet scheduler meant to be mostly
+used for locally generated traffic. It is designed to achieve per flow pacing.
+FQ does flow separation, and is able to respect pacing requirements set by TCP stack.
+All packets belonging to a socket are considered as a 'flow'.
+For non local packets (router workload), packet hash is used as fallback.
+
+An application can specify a maximum pacing rate using the
+.B SO_MAX_PACING_RATE
+setsockopt call. This packet scheduler adds delay between packets to
+respect rate limitation set on each socket. Note that after linux-4.20, linux adopted EDT (Earliest Departure Time)
+and TCP directly sets the appropriate Departure Time for each skb.
+
+Dequeueing happens in a round-robin fashion.
+A special FIFO queue is reserved for high priority packets (
+.B TC_PRIO_CONTROL
+priority), such packets are always dequeued first.
+
+FQ is non-work-conserving.
+
+TCP pacing is good for flows having idle times, as the congestion
+window permits TCP stack to queue a possibly large number of packets.
+This removes the 'slow start after idle' choice, badly hitting
+large BDP flows and applications delivering chunks of data such as video streams.
+
+.SH PARAMETERS
+.SS limit
+Hard limit on the real queue size. When this limit is reached, new packets
+are dropped. If the value is lowered, packets are dropped so that the new limit is
+met. Default is 10000 packets.
+.SS flow_limit
+Hard limit on the maximum number of packets queued per flow.
+Default value is 100.
+.SS quantum
+The credit per dequeue RR round, i.e. the amount of bytes a flow is allowed to
+dequeue at once. A larger value means a longer time period before the next flow
+will be served.
+Default is 2 * interface MTU bytes.
+.SS initial_quantum
+The initial sending rate credit, i.e. the amount of bytes a new flow is allowed
+to dequeue initially.
+This is specifically meant to allow using IW10 without added delay.
+Default is 10 * interface MTU, i.e. 15140 for 'standard' ethernet.
+.SS maxrate
+Maximum sending rate of a flow. Default is unlimited.
+Application specific setting via
+.B SO_MAX_PACING_RATE
+is ignored only if it is larger than this value.
+.SS buckets
+The size of the hash table used for flow lookups. Each bucket is assigned a
+red-black tree for efficient collision sorting.
+Default: 1024.
+.SS orphan_mask
+For packets not owned by a socket, fq is able to mask a part of skb->hash
+and reduce number of buckets associated with the traffic. This is a DDOS
+prevention mechanism, and the default is 1023 (meaning no more than 1024 flows
+are allocated for these packets)
+.SS [no]pacing
+Enable or disable flow pacing. Default is enabled.
+.SS ce_threshold
+sets a threshold above which all packets are marked with ECN Congestion
+Experienced. This is useful for DCTCP-style congestion control algorithms that
+require marking at very shallow queueing thresholds.
+
+.SH EXAMPLES
+#tc qdisc add dev eth0 root est 1sec 4sec fq ce_threshold 4ms
+.br
+#tc -s -d qdisc sh dev eth0
+.br
+qdisc fq 800e: root refcnt 9 limit 10000p flow_limit 1000p buckets 1024 orphan_mask 1023 quantum 3028 initial_quantum 15140 low_rate_threshold 550Kbit refill_delay 40.0ms ce_threshold 4.0ms
+ Sent 533368436185 bytes 352296695 pkt (dropped 0, overlimits 0 requeues 1339864)
+ rate 39220Mbit 3238202pps backlog 12417828b 358p requeues 1339864
+ 1052 flows (852 inactive, 0 throttled)
+ 112 gc, 0 highprio, 212 throttled, 21501 ns latency, 470241 ce_mark
+.br
+.SH SEE ALSO
+.BR tc (8),
+.BR socket (7)
+.SH AUTHORS
+FQ was written by Eric Dumazet.
diff --git a/man/man8/tc-fq_codel.8 b/man/man8/tc-fq_codel.8
new file mode 100644
index 0000000..7ee6c26
--- /dev/null
+++ b/man/man8/tc-fq_codel.8
@@ -0,0 +1,124 @@
+.TH FQ_CoDel 8 "4 June 2012" "iproute2" "Linux"
+.SH NAME
+CoDel \- Fair Queuing (FQ) with Controlled Delay (CoDel)
+.SH SYNOPSIS
+.B tc qdisc ... fq_codel
+[
+.B limit
+PACKETS ] [
+.B flows
+NUMBER ] [
+.B target
+TIME ] [
+.B interval
+TIME ] [
+.B quantum
+BYTES ] [
+.B ecn
+|
+.B noecn
+] [
+.B ce_threshold
+TIME ] [
+.B memory_limit
+BYTES ]
+
+.SH DESCRIPTION
+FQ_Codel (Fair Queuing Controlled Delay) is queuing discipline that combines Fair
+Queuing with the CoDel AQM scheme. FQ_Codel uses a stochastic model to classify
+incoming packets into different flows and is used to provide a fair share of the
+bandwidth to all the flows using the queue. Each such flow is managed by the
+CoDel queuing discipline. Reordering within a flow is avoided since Codel
+internally uses a FIFO queue.
+
+.SH PARAMETERS
+.SS limit
+has the same semantics as
+.B codel
+and is the hard limit on the real queue size.
+When this limit is reached, incoming packets are dropped. Default is 10240
+packets.
+
+.SS memory_limit
+sets a limit on the total number of bytes that can be queued in this FQ-CoDel
+instance. The lower of the packet limit of the
+.B limit
+parameter and the memory limit will be enforced. Default is 32 MB.
+
+
+.SS flows
+is the number of flows into which the incoming packets are classified. Due to
+the stochastic nature of hashing, multiple flows may end up being hashed into
+the same slot. Newer flows have priority over older ones. This parameter can be
+set only at load time since memory has to be allocated for the hash table.
+Default value is 1024.
+
+.SS target
+has the same semantics as
+.B codel
+and is the acceptable minimum
+standing/persistent queue delay. This minimum delay is identified by tracking
+the local minimum queue delay that packets experience. Default value is 5ms.
+
+.SS interval
+has the same semantics as
+.B codel
+and is used to ensure that the measured minimum delay does not become too stale.
+The minimum delay must be experienced in the last epoch of length .B interval.
+It should be set on the order of the worst-case RTT through the bottleneck to
+give endpoints sufficient time to react. Default value is 100ms.
+
+.SS quantum
+is the number of bytes used as 'deficit' in the fair queuing algorithm. Default
+is set to 1514 bytes which corresponds to the Ethernet MTU plus the hardware
+header length of 14 bytes.
+
+.SS ecn | noecn
+has the same semantics as
+.B codel
+and can be used to mark packets instead of dropping them. If
+.B ecn
+has been enabled,
+.B noecn
+can be used to turn it off and vice-a-versa. Unlike
+.B codel, ecn
+is turned on by default.
+
+.SS ce_threshold
+sets a threshold above which all packets are marked with ECN Congestion
+Experienced. This is useful for DCTCP-style congestion control algorithms that
+require marking at very shallow queueing thresholds.
+
+.SH EXAMPLES
+#tc qdisc add dev eth0 root fq_codel
+.br
+#tc -s qdisc show
+.br
+qdisc fq_codel 8002: dev eth0 root refcnt 2 limit 10240p flows 1024 quantum 1514
+ target 5.0ms interval 100.0ms ecn
+ Sent 428514 bytes 2269 pkt (dropped 0, overlimits 0 requeues 0)
+ backlog 0b 0p requeues 0
+ maxpacket 256 drop_overlimit 0 new_flow_count 0 ecn_mark 0
+ new_flows_len 0 old_flows_len 0
+
+#tc qdisc add dev eth0 root fq_codel limit 2000 target 3ms interval 40ms noecn
+.br
+#tc -s qdisc show
+.br
+qdisc fq_codel 8003: dev eth0 root refcnt 2 limit 2000p flows 1024 quantum 1514
+target 3.0ms interval 40.0ms
+ Sent 2588985006 bytes 1783629 pkt (dropped 0, overlimits 0 requeues 34869)
+ backlog 0b 0p requeues 34869
+ maxpacket 65226 drop_overlimit 0 new_flow_count 73 ecn_mark 0
+ new_flows_len 1 old_flows_len 3
+
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-codel (8),
+.BR tc-red (8)
+
+.SH AUTHORS
+FQ_CoDel was implemented by Eric Dumazet. This manpage was written
+by Vijay Subramanian. Please report corrections to the Linux Networking
+mailing list <netdev@vger.kernel.org>.
diff --git a/man/man8/tc-fw.8 b/man/man8/tc-fw.8
new file mode 100644
index 0000000..d742b47
--- /dev/null
+++ b/man/man8/tc-fw.8
@@ -0,0 +1,66 @@
+.TH "Firewall mark classifier in tc" 8 "21 Oct 2015" "iproute2" "Linux"
+
+.SH NAME
+fw \- fwmark traffic control filter
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " " filter " ... " fw " [ " classid
+.IR CLASSID " ] [ "
+.B action
+.IR ACTION_SPEC " ]"
+.SH DESCRIPTION
+the
+.B fw
+filter allows to classify packets based on a previously set
+.BR fwmark " by " iptables .
+If it is identical to the filter's
+.BR handle ,
+the filter matches.
+.B iptables
+allows to mark single packets with the
+.B MARK
+target, or whole connections using
+.BR CONNMARK .
+The benefit of using this filter instead of doing the
+heavy-lifting with
+.B tc
+itself is that on one hand it might be convenient to keep packet filtering and
+classification in one place, possibly having to match a packet just once, and on
+the other users familiar with
+.BR iptables " but not " tc
+will have a less hard time adding QoS to their setups.
+.SH OPTIONS
+.TP
+.BI classid " CLASSID"
+Push matching packets to the class identified by
+.IR CLASSID .
+.TP
+.BI action " ACTION_SPEC"
+Apply an action from the generic actions framework on matching packets.
+.SH EXAMPLES
+Take e.g. the following tc filter statement:
+
+.RS
+.EX
+tc filter add ... handle 6 fw classid 1:1
+.EE
+.RE
+
+will match if the packet's
+.B fwmark
+value is
+.BR 6 .
+This is a sample
+.B iptables
+statement marking packets coming in on eth0:
+
+.RS
+.EX
+iptables -t mangle -A PREROUTING -i eth0 -j MARK --set-mark 6
+.EE
+.RE
+.SH SEE ALSO
+.BR tc (8),
+.BR iptables (8),
+.BR iptables-extensions (8)
diff --git a/man/man8/tc-hfsc.8 b/man/man8/tc-hfsc.8
new file mode 100644
index 0000000..fd0df8f
--- /dev/null
+++ b/man/man8/tc-hfsc.8
@@ -0,0 +1,61 @@
+.TH HFSC 8 "31 October 2011" iproute2 Linux
+.
+.SH NAME
+HFSC \- Hierarchical Fair Service Curve's control under linux
+.
+.SH SYNOPSIS
+.nf
+tc qdisc add ... hfsc [ \fBdefault\fR CLASSID ]
+
+tc class add ... hfsc [ [ \fBrt\fR SC ] [ \fBls\fR SC ] | [ \fBsc\fR SC ] ] [ \fBul\fR SC ]
+
+\fBrt\fR : realtime service curve
+\fBls\fR : linkshare service curve
+\fBsc\fR : rt+ls service curve
+\fBul\fR : upperlimit service curve
+
+\(bu at least one of \fBrt\fR, \fBls\fR or \fBsc\fR must be specified
+\(bu \fBul\fR can only be specified with \fBls\fR or \fBsc\fR
+.
+.IP "SC := [ [ \fBm1\fR BPS ] \fBd\fR SEC ] \fBm2\fR BPS"
+\fBm1\fR : slope of the first segment
+\fBd\fR : x\-coordinate of intersection
+\fBm2\fR : slope of the second segment
+.PP
+.IP "SC := [ [ \fBumax\fR BYTE ] \fBdmax\fR SEC ] \fBrate\fR BPS"
+\fBumax\fR : maximum unit of work
+\fBdmax\fR : maximum delay
+\fBrate\fR : rate
+.PP
+.fi
+For description of BYTE, BPS and SEC \- please see \fBUNITS\fR
+section of \fBtc\fR(8).
+.
+.SH DESCRIPTION (qdisc)
+HFSC qdisc has only one optional parameter \- \fBdefault\fR. CLASSID specifies
+the minor part of the default classid, where packets not classified by other
+means (e.g. u32 filter, CLASSIFY target of iptables) will be enqueued. If
+\fBdefault\fR is not specified, unclassified packets will be dropped.
+.
+.SH DESCRIPTION (class)
+HFSC class is used to create a class hierarchy for HFSC scheduler. For
+explanation of the algorithm, and the meaning behind \fBrt\fR, \fBls\fR,
+\fBsc\fR and \fBul\fR service curves \- please refer to \fBtc\-hfsc\fR(7).
+
+As you can see in \fBSYNOPSIS\fR, service curve (SC) can be specified in two
+ways. Either as maximum delay for certain amount of work, or as a bandwidth
+assigned for certain amount of time. Obviously, \fBm1\fR is simply
+\fBumax\fR/\fBdmax\fR.
+
+Both \fBm2\fR and \fBrate\fR are mandatory. If you omit other
+parameters, you will specify linear service curve.
+.
+.SH "SEE ALSO"
+.
+\fBtc\fR(8), \fBtc\-hfsc\fR(7), \fBtc\-stab\fR(8)
+
+Please direct bugreports and patches to: <netdev@vger.kernel.org>
+.
+.SH "AUTHOR"
+.
+Manpage created by Michal Soltys (soltys@ziu.info)
diff --git a/man/man8/tc-htb.8 b/man/man8/tc-htb.8
new file mode 100644
index 0000000..ae310f4
--- /dev/null
+++ b/man/man8/tc-htb.8
@@ -0,0 +1,148 @@
+.TH HTB 8 "10 January 2002" "iproute2" "Linux"
+.SH NAME
+HTB \- Hierarchy Token Bucket
+.SH SYNOPSIS
+.B tc qdisc ... dev
+dev
+.B ( parent
+classid
+.B | root) [ handle
+major:
+.B ] htb [ default
+minor-id
+.B ]
+
+.B tc class ... dev
+dev
+.B parent
+major:[minor]
+.B [ classid
+major:minor
+.B ] htb rate
+rate
+.B [ ceil
+rate
+.B ] burst
+bytes
+.B [ cburst
+bytes
+.B ] [ prio
+priority
+.B ]
+
+.SH DESCRIPTION
+HTB is meant as a more understandable and intuitive replacement for
+the CBQ qdisc in Linux. Both CBQ and HTB help you to control the use
+of the outbound bandwidth on a given link. Both allow you to use one
+physical link to simulate several slower links and to send different
+kinds of traffic on different simulated links. In both cases, you have
+to specify how to divide the physical link into simulated links and
+how to decide which simulated link to use for a given packet to be sent.
+
+Unlike CBQ, HTB shapes traffic based on the Token Bucket Filter algorithm
+which does not depend on interface characteristics and so does not need to
+know the underlying bandwidth of the outgoing interface.
+
+.SH SHAPING ALGORITHM
+Shaping works as documented in
+.B tc-tbf (8).
+
+.SH CLASSIFICATION
+Within the one HTB instance many classes may exist. Each of these classes
+contains another qdisc, by default
+.BR tc-pfifo (8).
+
+When enqueueing a packet, HTB starts at the root and uses various methods to
+determine which class should receive the data.
+
+In the absence of uncommon configuration options, the process is rather easy.
+At each node we look for an instruction, and then go to the class the
+instruction refers us to. If the class found is a barren leaf-node (without
+children), we enqueue the packet there. If it is not yet a leaf node, we do
+the whole thing over again starting from that node.
+
+The following actions are performed, in order at each node we visit, until one
+sends us to another node, or terminates the process.
+.TP
+(i)
+Consult filters attached to the class. If sent to a leafnode, we are done.
+Otherwise, restart.
+.TP
+(ii)
+If none of the above returned with an instruction, enqueue at this node.
+.P
+This algorithm makes sure that a packet always ends up somewhere, even while
+you are busy building your configuration.
+
+.SH LINK SHARING ALGORITHM
+FIXME
+
+.SH QDISC
+The root of a HTB qdisc class tree has the following parameters:
+
+.TP
+parent major:minor | root
+This mandatory parameter determines the place of the HTB instance, either at the
+.B root
+of an interface or within an existing class.
+.TP
+handle major:
+Like all other qdiscs, the HTB can be assigned a handle. Should consist only
+of a major number, followed by a colon. Optional, but very useful if classes
+will be generated within this qdisc.
+.TP
+default minor-id
+Unclassified traffic gets sent to the class with this minor-id.
+
+.SH CLASSES
+Classes have a host of parameters to configure their operation.
+
+.TP
+parent major:minor
+Place of this class within the hierarchy. If attached directly to a qdisc
+and not to another class, minor can be omitted. Mandatory.
+.TP
+classid major:minor
+Like qdiscs, classes can be named. The major number must be equal to the
+major number of the qdisc to which it belongs. Optional, but needed if this
+class is going to have children.
+.TP
+prio priority
+In the round-robin process, classes with the lowest priority field are tried
+for packets first. Mandatory.
+
+.TP
+rate rate
+Maximum rate this class and all its children are guaranteed. Mandatory.
+
+.TP
+ceil rate
+Maximum rate at which a class can send, if its parent has bandwidth to spare.
+Defaults to the configured rate, which implies no borrowing
+
+.TP
+burst bytes
+Amount of bytes that can be burst at
+.B ceil
+speed, in excess of the configured
+.B rate.
+Should be at least as high as the highest burst of all children.
+
+.TP
+cburst bytes
+Amount of bytes that can be burst at 'infinite' speed, in other words, as fast
+as the interface can transmit them. For perfect evening out, should be equal to at most one average
+packet. Should be at least as high as the highest cburst of all children.
+
+.SH NOTES
+Due to Unix timing constraints, the maximum ceil rate is not infinite and may in fact be quite low. On Intel,
+there are 100 timer events per second, the maximum rate is that rate at which 'burst' bytes are sent each timer tick.
+From this, the minimum burst size for a specified rate can be calculated. For i386, a 10mbit rate requires a 12 kilobyte
+burst as 100*12kb*8 equals 10mbit.
+
+.SH SEE ALSO
+.BR tc (8)
+.P
+HTB website: http://luxik.cdi.cz/~devik/qos/htb/
+.SH AUTHOR
+Martin Devera <devik@cdi.cz>. This manpage maintained by bert hubert <ahu@ds9a.nl>
diff --git a/man/man8/tc-ife.8 b/man/man8/tc-ife.8
new file mode 100644
index 0000000..fd2df6c
--- /dev/null
+++ b/man/man8/tc-ife.8
@@ -0,0 +1,143 @@
+.TH "IFE action in tc" 8 "22 Apr 2016" "iproute2" "Linux"
+
+.SH NAME
+IFE - encapsulate/decapsulate metadata
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " " action ife"
+.IR DIRECTION " [ " ACTION " ] "
+.RB "[ " dst
+.IR DMAC " ] "
+.RB "[ " src
+.IR SMAC " ] "
+.RB "[ " type
+.IR TYPE " ] "
+.RI "[ "
+.IR CONTROL " ] "
+.RB "[ " index
+.IR INDEX " ] "
+
+.ti -8
+.IR DIRECTION " := { "
+.BR decode " | " encode " }"
+
+.ti -8
+.IR ACTION " := { "
+.BI allow " ATTR"
+.RB "| " use
+.IR "ATTR value" " }"
+
+.ti -8
+.IR ATTR " := { "
+.BR mark " | " prio " | " tcindex " }"
+
+.ti -8
+.IR CONTROL " := { "
+.BR reclassify " | " use " | " pipe " | " drop " | " continue " | " ok " | " goto " " chain " " CHAIN_INDEX " }"
+.SH DESCRIPTION
+The
+.B ife
+action allows for a sending side to encapsulate arbitrary metadata, which is
+then decapsulated by the receiving end. The sender runs in encoding mode and
+the receiver in decode mode. Both sender and receiver must specify the same
+ethertype. In the future, a registered ethertype may be available as a default.
+.SH OPTIONS
+.TP
+.B decode
+For the receiving side; decode the metadata if the packet matches.
+.TP
+.B encode
+For the sending side. Encode the specified metadata if the packet matches.
+.TP
+.B allow
+Encode direction only. Allows encoding specified metadata.
+.TP
+.B use
+Encode direction only. Enforce static encoding of specified metadata.
+.TP
+.BR mark " [ "
+.IR u32_value " ]"
+The value to set for the skb mark. The u32 value is required only when
+.BR use " is specified. If
+.BR mark " value is zero, it will not be encoded, instead
+"overlimits" statistics increment and
+.BR CONTROL " action is taken.
+.TP
+.BR prio " [ "
+.IR u32_value " ]"
+The value to set for priority in the skb structure. The u32 value is required
+only when
+.BR use " is specified."
+.TP
+.BR tcindex " ["
+.IR u16_value " ]"
+Value to set for the traffic control index in the skb structure. The u16 value
+is required only when
+.BR use " is specified."
+.TP
+.BI dmac " DMAC"
+.TQ
+.BI smac " SMAC"
+Optional six byte destination or source MAC address to encode.
+.TP
+.BI type " TYPE"
+Optional 16-bit ethertype to encode. If not specified value of 0xED3E will be used.
+.TP
+.BI CONTROL
+Action to take following an encode/decode.
+.TP
+.BI index " INDEX"
+Assign a unique ID to this action instead of letting the kernel choose one
+automatically.
+.I INDEX
+is a 32bit unsigned integer greater than zero.
+.SH EXAMPLES
+
+On the receiving side, match packets with ethertype 0xdead and restart
+classification so that it will match ICMP on the next rule, at prio 3:
+.RS
+.EX
+# tc qdisc add dev eth0 handle ffff: ingress
+# tc filter add dev eth0 parent ffff: prio 2 protocol 0xdead \\
+ u32 match u32 0 0 flowid 1:1 \\
+ action ife decode reclassify
+# tc filter add dev eth0 parent ffff: prio 3 protocol ip \\
+ u32 match ip protocol 0xff flowid 1:1 \\
+ action continue
+.EE
+.RE
+
+Match with skb mark of 17:
+
+.RS
+.EX
+# tc filter add dev eth0 parent ffff: prio 4 protocol ip \\
+ handle 0x11 fw flowid 1:1 \\
+ action ok
+.EE
+.RE
+
+Configure the sending side to encode for the filters above. Use a destination
+IP address of 192.168.122.237/24, then tag with skb mark of decimal 17. Encode
+the packaet with ethertype 0xdead, add skb->mark to whitelist of metadatum to
+send, and rewrite the destination MAC address to 02:15:15:15:15:15.
+
+.RS
+.EX
+# tc qdisc add dev eth0 root handle 1: prio
+# tc filter add dev eth0 parent 1: protocol ip prio 10 u32 \\
+ match ip dst 192.168.122.237/24 \\
+ match ip protocol 1 0xff \\
+ flowid 1:2 \\
+ action skbedit mark 17 \\
+ action ife encode \\
+ type 0xDEAD \\
+ allow mark \\
+ dst 02:15:15:15:15:15
+.EE
+.RE
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-u32 (8)
diff --git a/man/man8/tc-matchall.8 b/man/man8/tc-matchall.8
new file mode 100644
index 0000000..e3cddb1
--- /dev/null
+++ b/man/man8/tc-matchall.8
@@ -0,0 +1,87 @@
+.TH "Match-all classifier in tc" 8 "21 Oct 2015" "iproute2" "Linux"
+
+.SH NAME
+matchall \- traffic control filter that matches every packet
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " " filter " ... " matchall " [ "
+.BR skip_sw " | " skip_hw
+.RI " ] [ "
+.B action
+.IR ACTION_SPEC " ] [ "
+.B classid
+.IR CLASSID " ]"
+.SH DESCRIPTION
+The
+.B matchall
+filter allows to classify every packet that flows on the port and run a
+action on it.
+.SH OPTIONS
+.TP
+.BI action " ACTION_SPEC"
+Apply an action from the generic actions framework on matching packets.
+.TP
+.BI classid " CLASSID"
+Push matching packets into the class identified by
+.IR CLASSID .
+.TP
+.BI skip_sw
+Do not process filter by software. If hardware has no offload support for this
+filter, or TC offload is not enabled for the interface, operation will fail.
+.TP
+.BI skip_hw
+Do not process filter by hardware.
+.SH EXAMPLES
+To create ingress mirroring from port eth1 to port eth2:
+.RS
+.EX
+
+tc qdisc add dev eth1 handle ffff: ingress
+tc filter add dev eth1 parent ffff: \\
+ matchall skip_sw \\
+ action mirred egress mirror \\
+ dev eth2
+.EE
+.RE
+
+The first command creats an ingress qdisc with handle
+.BR ffff:
+on device
+.BR eth1
+where the second command attaches a matchall filters on it that mirrors the
+packets to device eth2.
+
+To create egress mirroring from port eth1 to port eth2:
+.RS
+.EX
+
+tc qdisc add dev eth1 handle 1: root prio
+tc filter add dev eth1 parent 1: \\
+ matchall skip_sw \\
+ action mirred egress mirror \\
+ dev eth2
+.EE
+.RE
+
+The first command creats an egress qdisc with handle
+.BR 1:
+that replaces the root qdisc on device
+.BR eth1
+where the second command attaches a matchall filters on it that mirrors the
+packets to device eth2.
+
+To sample one of every 100 packets flowing into interface eth0 to psample group
+12:
+.RS
+.EX
+
+tc qdisc add dev eth0 handle ffff: ingress
+tc filter add dev eth0 parent ffff: matchall \\
+ action sample rate 100 group 12
+.EE
+.RE
+
+.EE
+.SH SEE ALSO
+.BR tc (8),
diff --git a/man/man8/tc-mirred.8 b/man/man8/tc-mirred.8
new file mode 100644
index 0000000..38833b4
--- /dev/null
+++ b/man/man8/tc-mirred.8
@@ -0,0 +1,99 @@
+.TH "Mirror/redirect action in tc" 8 "11 Jan 2015" "iproute2" "Linux"
+
+.SH NAME
+mirred - mirror/redirect action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action mirred"
+.I DIRECTION ACTION
+.RB "[ " index
+.IR INDEX " ] "
+.BI dev " DEVICENAME"
+
+.ti -8
+.IR DIRECTION " := { "
+.BR ingress " | " egress " }"
+
+.ti -8
+.IR ACTION " := { "
+.BR mirror " | " redirect " }"
+.SH DESCRIPTION
+The
+.B mirred
+action allows packet mirroring (copying) or redirecting (stealing) the packet it
+receives. Mirroring is what is sometimes referred to as Switch Port Analyzer
+(SPAN) and is commonly used to analyze and/or debug flows.
+.SH OPTIONS
+.TP
+.B ingress
+.TQ
+.B egress
+Specify the direction in which the packet shall appear on the destination
+interface.
+.TP
+.B mirror
+.TQ
+.B redirect
+Define whether the packet should be copied
+.RB ( mirror )
+or moved
+.RB ( redirect )
+to the destination interface.
+.TP
+.BI index " INDEX"
+Assign a unique ID to this action instead of letting the kernel choose one
+automatically.
+.I INDEX
+is a 32bit unsigned integer greater than zero.
+.TP
+.BI dev " DEVICENAME"
+Specify the network interface to redirect or mirror to.
+.SH EXAMPLES
+Limit ingress bandwidth on eth0 to 1mbit/s, redirect exceeding traffic to lo for
+debugging purposes:
+
+.RS
+.EX
+# tc qdisc add dev eth0 handle ffff: ingress
+# tc filter add dev eth0 parent ffff: u32 \\
+ match u32 0 0 \\
+ action police rate 1mbit burst 100k conform-exceed pipe \\
+ action mirred egress redirect dev lo
+.EE
+.RE
+
+Mirror all incoming ICMP packets on eth0 to a dummy interface for examination
+with e.g. tcpdump:
+
+.RS
+.EX
+# ip link add dummy0 type dummy
+# ip link set dummy0 up
+# tc qdisc add dev eth0 handle ffff: ingress
+# tc filter add dev eth0 parent ffff: protocol ip \\
+ u32 match ip protocol 1 0xff \\
+ action mirred egress mirror dev dummy0
+.EE
+.RE
+
+Using an
+.B ifb
+interface, it is possible to send ingress traffic through an instance of
+.BR sfq :
+
+.RS
+.EX
+# modprobe ifb
+# ip link set ifb0 up
+# tc qdisc add dev ifb0 root sfq
+# tc qdisc add dev eth0 handle ffff: ingress
+# tc filter add dev eth0 parent ffff: u32 \\
+ match u32 0 0 \\
+ action mirred egress redirect dev ifb0
+.EE
+.RE
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-u32 (8)
diff --git a/man/man8/tc-mpls.8 b/man/man8/tc-mpls.8
new file mode 100644
index 0000000..84ef2ef
--- /dev/null
+++ b/man/man8/tc-mpls.8
@@ -0,0 +1,156 @@
+.TH "MPLS manipulation action in tc" 8 "22 May 2019" "iproute2" "Linux"
+
+.SH NAME
+mpls - mpls manipulation module
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action mpls" " { "
+.IR POP " | " PUSH " | " MODIFY " | "
+.BR dec_ttl " } [ "
+.IR CONTROL " ]"
+
+.ti -8
+.IR POP " := "
+.BR pop " " protocol
+.IR MPLS_PROTO
+
+.ti -8
+.IR PUSH " := "
+.BR push " [ " protocol
+.IR MPLS_PROTO " ]"
+.RB " [ " tc
+.IR MPLS_TC " ] "
+.RB " [ " ttl
+.IR MPLS_TTL " ] "
+.RB " [ " bos
+.IR MPLS_BOS " ] "
+.BI label " MPLS_LABEL"
+
+.ti -8
+.IR MODIFY " := "
+.BR modify " [ " label
+.IR MPLS_LABEL " ]"
+.RB " [ " tc
+.IR MPLS_TC " ] "
+.RB " [ " ttl
+.IR MPLS_TTL " ] "
+
+.ti -8
+.IR CONTROL " := { "
+.BR reclassify " | " pipe " | " drop " | " continue " | " pass " | " goto " " chain " " CHAIN_INDEX " }"
+.SH DESCRIPTION
+The
+.B mpls
+action performs mpls encapsulation or decapsulation on a packet, reflected by the
+operation modes
+.IR POP ", " PUSH ", " MODIFY " and " DEC_TTL .
+The
+.I POP
+mode requires the ethertype of the header that follows the MPLS header (e.g.
+IPv4 or another MPLS). It will remove the outer MPLS header and replace the
+ethertype in the MAC header with that passed. The
+.IR PUSH " and " MODIFY
+modes update the current MPLS header information or add a new header.
+.IR PUSH
+requires at least an
+.IR MPLS_LABEL ". "
+.I DEC_TTL
+requires no arguments and simply subtracts 1 from the MPLS header TTL field.
+
+.SH OPTIONS
+.TP
+.B pop
+Decapsulation mode. Requires the protocol of the next header.
+.TP
+.B push
+Encapsulation mode. Requires at least the
+.B label
+option.
+.TP
+.B modify
+Replace mode. Existing MPLS tag is replaced.
+.BR label ", "
+.BR tc ", "
+and
+.B ttl
+are all optional.
+.TP
+.B dec_ttl
+Decrement the TTL field on the outer most MPLS header.
+.TP
+.BI label " MPLS_LABEL"
+Specify the MPLS LABEL for the outer MPLS header.
+.I MPLS_LABEL
+is an unsigned 20bit integer, the format is detected automatically (e.g. prefix
+with
+.RB ' 0x '
+for hexadecimal interpretation, etc.).
+.TP
+.BI protocol " MPLS_PROTO"
+Choose the protocol to use. For push actions this must be
+.BR mpls_uc " or " mpls_mc " (" mpls_uc
+is the default). For pop actions it should be the protocol of the next header.
+This option cannot be used with modify.
+.TP
+.BI tc " MPLS_TC"
+Choose the TC value for the outer MPLS header. Decimal number in range of 0-7.
+Defaults to 0.
+.TP
+.BI ttl " MPLS_TTL"
+Choose the TTL value for the outer MPLS header. Number in range of 0-255. A
+non-zero default value will be selected if this is not explicitly set.
+.TP
+.BI bos " MPLS_BOS"
+Manually configure the bottom of stack bit for an MPLS header push. The default
+is for TC to automatically set (or unset) the bit based on the next header of
+the packet.
+.TP
+.I CONTROL
+How to continue after executing this action.
+.RS
+.TP
+.B reclassify
+Restarts classification by jumping back to the first filter attached to this
+action's parent.
+.TP
+.B pipe
+Continue with the next action, this is the default.
+.TP
+.B drop
+Packet will be dropped without running further actions.
+.TP
+.B continue
+Continue classification with next filter in line.
+.TP
+.B pass
+Return to calling qdisc for packet processing. This ends the classification
+process.
+.RE
+.SH EXAMPLES
+The following example encapsulates incoming IP packets on eth0 into MPLS with
+a label 123 and sends them out eth1:
+
+.RS
+.EX
+#tc qdisc add dev eth0 handle ffff: ingress
+#tc filter add dev eth0 protocol ip parent ffff: flower \\
+ action mpls push protocol mpls_uc label 123 \\
+ action mirred egress redirect dev eth1
+.EE
+.RE
+
+In this example, incoming MPLS unicast packets on eth0 are decapsulated and to
+ip packets and output to eth1:
+
+.RS
+.EX
+#tc qdisc add dev eth0 handle ffff: ingress
+#tc filter add dev eth0 protocol mpls_uc parent ffff: flower \\
+ action mpls pop protocol ipv4 \\
+ action mirred egress redirect dev eth0
+.EE
+.RE
+
+.SH SEE ALSO
+.BR tc (8)
diff --git a/man/man8/tc-mqprio.8 b/man/man8/tc-mqprio.8
new file mode 100644
index 0000000..4b9e942
--- /dev/null
+++ b/man/man8/tc-mqprio.8
@@ -0,0 +1,150 @@
+.TH MQPRIO 8 "24 Sept 2013" "iproute2" "Linux"
+.SH NAME
+MQPRIO \- Multiqueue Priority Qdisc (Offloaded Hardware QOS)
+.SH SYNOPSIS
+.B tc qdisc ... dev
+dev
+.B ( parent
+classid
+.B | root) [ handle
+major:
+.B ] mqprio [ num_tc
+tcs
+.B ] [ map
+P0 P1 P2...
+.B ] [ queues
+count1@offset1 count2@offset2 ...
+.B ] [ hw
+1|0
+.B ] [ mode
+dcb|channel]
+.B ] [ shaper
+dcb|
+.B [ bw_rlimit
+.B min_rate
+min_rate1 min_rate2 ...
+.B max_rate
+max_rate1 max_rate2 ...
+.B ]]
+
+
+.SH DESCRIPTION
+The MQPRIO qdisc is a simple queuing discipline that allows mapping
+traffic flows to hardware queue ranges using priorities and a configurable
+priority to traffic class mapping. A traffic class in this context is
+a set of contiguous qdisc classes which map 1:1 to a set of hardware
+exposed queues.
+
+By default the qdisc allocates a pfifo qdisc (packet limited first in, first
+out queue) per TX queue exposed by the lower layer device. Other queuing
+disciplines may be added subsequently. Packets are enqueued using the
+.B map
+parameter and hashed across the indicated queues in the
+.B offset
+and
+.B count.
+By default these parameters are configured by the hardware
+driver to match the hardware QOS structures.
+
+.B Channel
+mode supports full offload of the mqprio options, the traffic classes, the queue
+configurations and QOS attributes to the hardware. Enabled hardware can provide
+hardware QOS with the ability to steer traffic flows to designated traffic
+classes provided by this qdisc. Hardware based QOS is configured using the
+.B shaper
+parameter.
+.B bw_rlimit
+with minimum and maximum bandwidth rates can be used for setting
+transmission rates on each traffic class. Also further qdiscs may be added
+to the classes of MQPRIO to create more complex configurations.
+
+.SH ALGORITHM
+On creation with 'tc qdisc add', eight traffic classes are created mapping
+priorities 0..7 to traffic classes 0..7 and priorities greater than 7 to
+traffic class 0. This requires base driver support and the creation will
+fail on devices that do not support hardware QOS schemes.
+
+These defaults can be overridden using the qdisc parameters. Providing
+the 'hw 0' flag allows software to run without hardware coordination.
+
+If hardware coordination is being used and arguments are provided that
+the hardware can not support then an error is returned. For many users
+hardware defaults should work reasonably well.
+
+As one specific example numerous Ethernet cards support the 802.1Q
+link strict priority transmission selection algorithm (TSA). MQPRIO
+enabled hardware in conjunction with the classification methods below
+can provide hardware offloaded support for this TSA.
+
+.SH CLASSIFICATION
+Multiple methods are available to set the SKB priority which MQPRIO
+uses to select which traffic class to enqueue the packet.
+.TP
+From user space
+A process with sufficient privileges can encode the destination class
+directly with SO_PRIORITY, see
+.BR socket(7).
+.TP
+with iptables/nftables
+An iptables/nftables rule can be created to match traffic flows and
+set the priority.
+.BR iptables(8)
+.TP
+with net_prio cgroups
+The net_prio cgroup can be used to set the priority of all sockets
+belong to an application. See kernel and cgroup documentation for details.
+
+.SH QDISC PARAMETERS
+.TP
+num_tc
+Number of traffic classes to use. Up to 16 classes supported.
+
+.TP
+map
+The priority to traffic class map. Maps priorities 0..15 to a specified
+traffic class.
+
+.TP
+queues
+Provide count and offset of queue range for each traffic class. In the
+format,
+.B count@offset.
+Queue ranges for each traffic classes cannot overlap and must be a
+contiguous range of queues.
+
+.TP
+hw
+Set to
+.B 1
+to support hardware offload. Set to
+.B 0
+to configure user specified values in software only.
+
+.TP
+mode
+Set to
+.B channel
+for full use of the mqprio options. Use
+.B dcb
+to offload only TC values and use hardware QOS defaults. Supported with 'hw'
+set to 1 only.
+
+.TP
+shaper
+Use
+.B bw_rlimit
+to set bandwidth rate limits for a traffic class. Use
+.B dcb
+for hardware QOS defaults. Supported with 'hw' set to 1 only.
+
+.TP
+min_rate
+Minimum value of bandwidth rate limit for a traffic class.
+
+.TP
+max_rate
+Maximum value of bandwidth rate limit for a traffic class.
+
+
+.SH AUTHORS
+John Fastabend, <john.r.fastabend@intel.com>
diff --git a/man/man8/tc-nat.8 b/man/man8/tc-nat.8
new file mode 100644
index 0000000..fdcc052
--- /dev/null
+++ b/man/man8/tc-nat.8
@@ -0,0 +1,78 @@
+.TH "NAT action in tc" 8 "12 Jan 2015" "iproute2" "Linux"
+
+.SH NAME
+nat - stateless native address translation action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action nat"
+.I DIRECTION OLD NEW
+
+.ti -8
+.IR DIRECTION " := { "
+.BR ingress " | " egress " }"
+
+.ti -8
+.IR OLD " := " IPV4_ADDR_SPEC
+
+.ti -8
+.IR NEW " := " IPV4_ADDR_SPEC
+
+.ti -8
+.IR IPV4_ADDR_SPEC " := { "
+.BR default " | " any " | " all " | "
+\fIin_addr\fR[\fB/\fR{\fIprefix\fR|\fInetmask\fR}]
+.SH DESCRIPTION
+The
+.B nat
+action allows to perform NAT without the overhead of conntrack, which is
+desirable if the number of flows or addresses to perform NAT on is large. This
+action is best used in combination with the
+.B u32
+filter to allow for efficient lookups of a large number of stateless NAT rules
+in constant time.
+.SH OPTIONS
+.TP
+.B ingress
+Translate destination addresses, i.e. perform DNAT.
+.TP
+.B egress
+Translate source addresses, i.e. perform SNAT.
+.TP
+.I OLD
+Specifies addresses which should be translated.
+.TP
+.I NEW
+Specifies addresses which
+.I OLD
+should be translated into.
+.SH NOTES
+The accepted address format in
+.IR OLD " and " NEW
+is quite flexible. It may either consist of one of the keywords
+.BR default ", " any " or " all ,
+representing the all-zero IP address or a combination of IP address and netmask
+or prefix length separated by a slash
+.RB ( / )
+sign. In any case, the mask (or prefix length) value of
+.I OLD
+is used for
+.I NEW
+as well so that a one-to-one mapping of addresses is assured.
+
+Address translation is done using a combination of binary operations. First, the
+original (source or destination) address is matched against the value of
+.IR OLD .
+If the original address fits, the new address is created by taking the leading
+bits from
+.I NEW
+(defined by the netmask of
+.IR OLD )
+and taking the remaining bits from the original address.
+
+There is rudimental support for upper layer protocols, namely TCP, UDP and ICMP.
+While for the first two only checksum recalculation is performed, the action
+also takes care of embedded IP headers in ICMP packets by translating the
+respective address therein, too.
+.SH SEE ALSO
+.BR tc (8)
diff --git a/man/man8/tc-netem.8 b/man/man8/tc-netem.8
new file mode 100644
index 0000000..5a08a40
--- /dev/null
+++ b/man/man8/tc-netem.8
@@ -0,0 +1,236 @@
+.TH NETEM 8 "25 November 2011" "iproute2" "Linux"
+.SH NAME
+NetEm \- Network Emulator
+.SH SYNOPSIS
+.B "tc qdisc ... dev"
+.IR DEVICE " ] "
+.BR "add netem"
+.I OPTIONS
+
+.IR OPTIONS " := [ " LIMIT " ] [ " DELAY " ] [ " LOSS \
+" ] [ " CORRUPT " ] [ " DUPLICATION " ] [ " REORDERING " ] [ " RATE \
+" ] [ " SLOT " ]"
+
+.IR LIMIT " := "
+.B limit
+.I packets
+
+.IR DELAY " := "
+.BI delay
+.IR TIME " [ " JITTER " [ " CORRELATION " ]]]"
+.br
+ [
+.BR distribution " { "uniform " | " normal " | " pareto " | " paretonormal " } ]"
+
+.IR LOSS " := "
+.BR loss " { "
+.BI random
+.IR PERCENT " [ " CORRELATION " ] |"
+.br
+.RB " " state
+.IR p13 " [ " p31 " [ " p32 " [ " p23 " [ " p14 "]]]] |"
+.br
+.RB " " gemodel
+.IR p " [ " r " [ " 1-h " [ " 1-k " ]]] } "
+.RB " [ " ecn " ] "
+
+.IR CORRUPT " := "
+.B corrupt
+.IR PERCENT " [ " CORRELATION " ]]"
+
+.IR DUPLICATION " := "
+.B duplicate
+.IR PERCENT " [ " CORRELATION " ]]"
+
+.IR REORDERING " := "
+.B reorder
+.IR PERCENT " [ " CORRELATION " ] [ "
+.B gap
+.IR DISTANCE " ]"
+
+.IR RATE " := "
+.B rate
+.IR RATE " [ " PACKETOVERHEAD " [ " CELLSIZE " [ " CELLOVERHEAD " ]]]]"
+
+.IR SLOT " := "
+.BR slot " { "
+.IR MIN_DELAY " [ " MAX_DELAY " ] |"
+.br
+.RB " " distribution " { "uniform " | " normal " | " pareto " | " paretonormal " | "
+.IR FILE " } " DELAY " " JITTER " } "
+.br
+.RB " [ " packets
+.IR PACKETS " ] [ "
+.BR bytes
+.IR BYTES " ]"
+
+
+.SH DESCRIPTION
+NetEm is an enhancement of the Linux traffic control facilities
+that allow to add delay, packet loss, duplication and more other
+characteristics to packets outgoing from a selected network
+interface. NetEm is built using the existing Quality Of Service (QOS)
+and Differentiated Services (diffserv) facilities in the Linux
+kernel.
+
+.SH netem OPTIONS
+netem has the following options:
+
+.SS limit packets
+
+maximum number of packets the qdisc may hold queued at a time.
+
+.SS delay
+adds the chosen delay to the packets outgoing to chosen network interface. The
+optional parameters allows to introduce a delay variation and a correlation.
+Delay and jitter values are expressed in ms while correlation is percentage.
+
+.SS distribution
+allow the user to choose the delay distribution. If not specified, the default
+distribution is Normal. Additional parameters allow to consider situations in
+which network has variable delays depending on traffic flows concurring on the
+same path, that causes several delay peaks and a tail.
+
+.SS loss random
+adds an independent loss probability to the packets outgoing from the chosen
+network interface. It is also possible to add a correlation, but this option
+is now deprecated due to the noticed bad behavior.
+
+.SS loss state
+adds packet losses according to the 4-state Markov using the transition
+probabilities as input parameters. The parameter p13 is mandatory and if used
+alone corresponds to the Bernoulli model. The optional parameters allows to
+extend the model to 2-state (p31), 3-state (p23 and p32) and 4-state (p14).
+State 1 corresponds to good reception, State 4 to independent losses, State 3
+to burst losses and State 2 to good reception within a burst.
+
+.SS loss gemodel
+adds packet losses according to the Gilbert-Elliot loss model or its special
+cases (Gilbert, Simple Gilbert and Bernoulli). To use the Bernoulli model, the
+only needed parameter is p while the others will be set to the default
+values r=1-p, 1-h=1 and 1-k=0. The parameters needed for the Simple Gilbert
+model are two (p and r), while three parameters (p, r, 1-h) are needed for the
+Gilbert model and four (p, r, 1-h and 1-k) are needed for the Gilbert-Elliot
+model. As known, p and r are the transition probabilities between the bad and
+the good states, 1-h is the loss probability in the bad state and 1-k is the
+loss probability in the good state.
+
+.SS ecn
+can be used optionally to mark packets instead of dropping them. A loss model
+has to be used for this to be enabled.
+
+.SS corrupt
+allows the emulation of random noise introducing an error in a random position
+for a chosen percent of packets. It is also possible to add a correlation
+through the proper parameter.
+
+.SS duplicate
+using this option the chosen percent of packets is duplicated before queuing
+them. It is also possible to add a correlation through the proper parameter.
+
+.SS reorder
+to use reordering, a delay option must be specified. There are two ways to use
+this option (assuming 'delay 10ms' in the options list).
+
+.B "reorder "
+.I 25% 50%
+.B "gap"
+.I 5
+.br
+in this first example, the first 4 (gap - 1) packets are delayed by 10ms and
+subsequent packets are sent immediately with a probability of 0.25 (with
+correlation of 50% ) or delayed with a probability of 0.75. After a packet is
+reordered, the process restarts i.e. the next 4 packets are delayed and
+subsequent packets are sent immediately or delayed based on reordering
+probability. To cause a repeatable pattern where every 5th packet is reordered
+reliably, a reorder probability of 100% can be used.
+
+.B reorder
+.I 25% 50%
+.br
+in this second example 25% of packets are sent immediately (with correlation of
+50%) while the others are delayed by 10 ms.
+
+.SS rate
+delay packets based on packet size and is a replacement for
+.IR TBF .
+Rate can be
+specified in common units (e.g. 100kbit). Optional
+.I PACKETOVERHEAD
+(in bytes) specify an per packet overhead and can be negative. A positive value can be
+used to simulate additional link layer headers. A negative value can be used to
+artificial strip the Ethernet header (e.g. -14) and/or simulate a link layer
+header compression scheme. The third parameter - an unsigned value - specify
+the cellsize. Cellsize can be used to simulate link layer schemes. ATM for
+example has an payload cellsize of 48 bytes and 5 byte per cell header. If a
+packet is 50 byte then ATM must use two cells: 2 * 48 bytes payload including 2
+* 5 byte header, thus consume 106 byte on the wire. The last optional value
+.I CELLOVERHEAD
+can be used to specify per cell overhead - for our ATM example 5.
+.I CELLOVERHEAD
+can be negative, but use negative values with caution.
+
+Note that rate throttling is limited by several factors: the kernel clock
+granularity avoid a perfect shaping at a specific level. This will show up in
+an artificial packet compression (bursts). Another influence factor are network
+adapter buffers which can also add artificial delay.
+
+.SS slot
+defer delivering accumulated packets to within a slot. Each available slot can be
+configured with a minimum delay to acquire, and an optional maximum delay.
+Alternatively it can be configured with the distribution similar to
+.BR distribution
+for
+.BR delay
+option. Slot delays can be specified in nanoseconds, microseconds, milliseconds or seconds
+(e.g. 800us). Values for the optional parameters
+.I BYTES
+will limit the number of bytes delivered per slot, and/or
+.I PACKETS
+will limit the number of packets delivered per slot.
+
+These slot options can provide a crude approximation of bursty MACs such as
+DOCSIS, WiFi, and LTE.
+
+Note that slotting is limited by several factors: the kernel clock granularity,
+as with a rate, and attempts to deliver many packets within a slot will be
+smeared by the timer resolution, and by the underlying native bandwidth also.
+
+It is possible to combine slotting with a rate, in which case complex behaviors
+where either the rate, or the slot limits on bytes or packets per slot, govern
+the actual delivered rate.
+
+.SH LIMITATIONS
+The main known limitation of Netem are related to timer granularity, since
+Linux is not a real-time operating system.
+
+.SH EXAMPLES
+.PP
+tc qdisc add dev eth0 root netem rate 5kbit 20 100 5
+.RS 4
+delay all outgoing packets on device eth0 with a rate of 5kbit, a per packet
+overhead of 20 byte, a cellsize of 100 byte and a per celloverhead of 5 byte:
+.RE
+
+.SH SOURCES
+.IP " 1. " 4
+Hemminger S. , "Network Emulation with NetEm", Open Source Development Lab,
+April 2005
+(http://devresources.linux-foundation.org/shemminger/netem/LCA2005_paper.pdf)
+
+.IP " 2. " 4
+Netem page from Linux foundation, (https://wiki.linuxfoundation.org/networking/netem)
+
+.IP " 3. " 4
+Salsano S., Ludovici F., Ordine A., "Definition of a general and intuitive loss
+model for packet networks and its implementation in the Netem module in the
+Linux kernel", available at http://netgroup.uniroma2.it/NetemCLG
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-tbf (8)
+
+.SH AUTHOR
+Netem was written by Stephen Hemminger at Linux foundation and is based on NISTnet.
+This manpage was created by Fabio Ludovici <fabio.ludovici at yahoo dot it> and
+Hagen Paul Pfeifer <hagen@jauu.net>
diff --git a/man/man8/tc-pedit.8 b/man/man8/tc-pedit.8
new file mode 100644
index 0000000..bbd725c
--- /dev/null
+++ b/man/man8/tc-pedit.8
@@ -0,0 +1,373 @@
+.TH "Generic packet editor action in tc" 8 "12 Jan 2015" "iproute2" "Linux"
+
+.SH NAME
+pedit - generic packet editor action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action pedit [ex] munge " {
+.IR RAW_OP " | " LAYERED_OP " | " EXTENDED_LAYERED_OP " } [ " CONTROL " ]"
+
+.ti -8
+.IR RAW_OP " := "
+.BI offset " OFFSET"
+.RB "{ " u8 " | " u16 " | " u32 " } ["
+.IR AT_SPEC " ] " CMD_SPEC
+
+.ti -8
+.IR AT_SPEC " := "
+.BI at " AT " offmask " MASK " shift " SHIFT"
+
+.ti -8
+.IR LAYERED_OP " := { "
+.BI ip " IPHDR_FIELD"
+|
+.BI ip " BEYOND_IPHDR_FIELD"
+.RI } " CMD_SPEC"
+
+.ti -8
+.IR EXTENDED_LAYERED_OP " := { "
+.BI eth " ETHHDR_FIELD"
+|
+.BI ip " IPHDR_FIELD"
+|
+.BI ip " EX_IPHDR_FIELD"
+|
+.BI ip6 " IP6HDR_FIELD"
+|
+.BI tcp " TCPHDR_FIELD"
+|
+.BI udp " UDPHDR_FIELD"
+.RI } " CMD_SPEC"
+
+.ti -8
+.IR ETHHDR_FIELD " := { "
+.BR src " | " dst " | " type " }"
+
+.ti -8
+.IR IPHDR_FIELD " := { "
+.BR src " | " dst " | " tos " | " dsfield " | " ihl " | " protocol " |"
+.BR precedence " | " nofrag " | " firstfrag " | " ce " | " df " }"
+
+.ti -8
+.IR BEYOND_IPHDR_FIELD " := { "
+.BR dport " | " sport " | " icmp_type " | " icmp_code " }"
+
+.ti -8
+.IR EX_IPHDR_FIELD " := { "
+.BR ttl " }"
+
+
+.ti -8
+.IR IP6HDR_FIELD " := { "
+.BR src " | " dst " | " flow_lbl " | " payload_len " | " nexthdr " |"
+.BR hoplimit " }"
+
+.ti -8
+.IR TCPHDR_FIELD " := { "
+.BR sport " | " dport " | " flags " }"
+
+.ti -8
+.IR UDPHDR_FIELD " := { "
+.BR sport " | " dport " }"
+
+.ti -8
+.IR CMD_SPEC " := {"
+.BR clear " | " invert " | " set
+.IR VAL " | "
+.BR add
+.IR VAL " | "
+.BR preserve " } [ " retain
+.IR RVAL " ]"
+
+.ti -8
+.IR CONTROL " := {"
+.BR reclassify " | " pipe " | " drop " | " shot " | " continue " | " pass " | " goto " " chain " " CHAIN_INDEX " }"
+.SH DESCRIPTION
+The
+.B pedit
+action can be used to change arbitrary packet data. The location of data to
+change can either be specified by giving an offset and size as in
+.IR RAW_OP ,
+or for header values by naming the header and field to edit the size is then
+chosen automatically based on the header field size. Currently this is supported
+only for IPv4 headers.
+.SH OPTIONS
+.TP
+.B ex
+Use extended pedit.
+.I EXTENDED_LAYERED_OP
+and the add
+.I CMD_SPEC
+are allowed only in this mode.
+.TP
+.BI offset " OFFSET " "\fR{ \fBu32 \fR| \fBu16 \fR| \fBu8 \fR}"
+Specify the offset at which to change data.
+.I OFFSET
+is a signed integer, it's base is automatically chosen (e.g. hex if prefixed by
+.B 0x
+or octal if prefixed by
+.BR 0 ).
+The second argument specifies the length of data to change, that is four bytes
+.RB ( u32 ),
+two bytes
+.RB ( u16 )
+or a single byte
+.RB ( u8 ).
+.TP
+.BI at " AT " offmask " MASK " shift " SHIFT"
+This is an optional part of
+.IR RAW_OP
+which allows to have a variable
+.I OFFSET
+depending on packet data at offset
+.IR AT ,
+which is binary ANDed with
+.I MASK
+and right-shifted by
+.I SHIFT
+before adding it to
+.IR OFFSET .
+.TP
+.BI eth " ETHHDR_FIELD"
+Change an ETH header field. The supported keywords for
+.I ETHHDR_FIELD
+are:
+.RS
+.TP
+.B src
+.TQ
+.B dst
+Source or destination MAC address in the standard format: XX:XX:XX:XX:XX:XX
+.TP
+.B type
+Ether-type in numeric value
+.RE
+.TP
+.BI ip " IPHDR_FIELD"
+Change an IPv4 header field. The supported keywords for
+.I IPHDR_FIELD
+are:
+.RS
+.TP
+.B src
+.TQ
+.B dst
+Source or destination IP address, a four-byte value.
+.TP
+.B tos
+.TQ
+.B dsfield
+.TQ
+.B precedence
+Type Of Service field, an eight-bit value.
+.TP
+.B ihl
+Change the IP Header Length field, a four-bit value.
+.TP
+.B protocol
+Next-layer Protocol field, an eight-bit value.
+.TP
+.B nofrag
+.TQ
+.B firstfrag
+.TQ
+.B ce
+.TQ
+.B df
+.TQ
+.B mf
+Change IP header flags. Note that the value to pass to the
+.B set
+command is not just a bit value, but the full byte including the flags field.
+Though only the relevant bits of that value are respected, the rest ignored.
+.RE
+.TP
+.BI ip " BEYOND_IPHDR_FIELD"
+Supported only for non-extended layered op. It is passed to the kernel as
+offsets relative to the beginning of the IP header and assumes the IP header is
+of minimum size (20 bytes). The supported keywords for
+.I BEYOND_IPHDR_FIELD
+are:
+.RS
+.TP
+.B dport
+.TQ
+.B sport
+Destination or source port numbers, a 16-bit value. Indeed, IPv4 headers don't
+contain this information. Instead, this will set an offset which suits at least
+TCP and UDP if the IP header is of minimum size (20 bytes). If not, this will do
+unexpected things.
+.TP
+.B icmp_type
+.TQ
+.B icmp_code
+Again, this allows to change data past the actual IP header itself. It assumes
+an ICMP header is present immediately following the (minimal sized) IP header.
+If it is not or the latter is bigger than the minimum of 20 bytes, this will do
+unexpected things. These fields are eight-bit values.
+.RE
+.TP
+.BI ip " EX_IPHDR_FIELD"
+Supported only when
+.I ex
+is used. The supported keywords for
+.I EX_IPHDR_FIELD
+are:
+.RS
+.TP
+.B ttl
+.RE
+.TP
+.BI ip6 " IP6HDR_FIELD"
+The supported keywords for
+.I IP6HDR_FIELD
+are:
+.RS
+.TP
+.B src
+.TQ
+.B dst
+.TQ
+.B flow_lbl
+.TQ
+.B payload_len
+.TQ
+.B nexthdr
+.TQ
+.B hoplimit
+.RE
+.TP
+.BI tcp " TCPHDR_FIELD"
+The supported keywords for
+.I TCPHDR_FIELD
+are:
+.RS
+.TP
+.B sport
+.TQ
+.B dport
+Source or destination TCP port number, a 16-bit value.
+.TP
+.B flags
+.RE
+.TP
+.BI udp " UDPHDR_FIELD"
+The supported keywords for
+.I UDPHDR_FIELD
+are:
+.RS
+.TP
+.B sport
+.TQ
+.B dport
+Source or destination TCP port number, a 16-bit value.
+.RE
+.TP
+.B clear
+Clear the addressed data (i.e., set it to zero).
+.TP
+.B invert
+Swap every bit in the addressed data.
+.TP
+.BI set " VAL"
+Set the addressed data to a specific value. The size of
+.I VAL
+is defined by either one of the
+.BR u32 ", " u16 " or " u8
+keywords in
+.IR RAW_OP ,
+or the size of the addressed header field in
+.IR LAYERED_OP .
+.TP
+.BI add " VAL"
+Add the addressed data by a specific value. The size of
+.I VAL
+is defined by the size of the addressed header field in
+.IR EXTENDED_LAYERED_OP .
+This operation is supported only for extended layered op.
+.TP
+.B preserve
+Keep the addressed data as is.
+.TP
+.BI retain " RVAL"
+This optional extra part of
+.I CMD_SPEC
+allows to exclude bits from being changed. Supported only for 32 bits fields
+or smaller.
+.TP
+.I CONTROL
+The following keywords allow to control how the tree of qdisc, classes,
+filters and actions is further traversed after this action.
+.RS
+.TP
+.B reclassify
+Restart with the first filter in the current list.
+.TP
+.B pipe
+Continue with the next action attached to the same filter.
+.TP
+.B drop
+.TQ
+.B shot
+Drop the packet.
+.TP
+.B continue
+Continue classification with the next filter in line.
+.TP
+.B pass
+Finish classification process and return to calling qdisc for further packet
+processing. This is the default.
+.RE
+.SH EXAMPLES
+Being able to edit packet data, one could do all kinds of things, such as e.g.
+implementing port redirection. Certainly not the most useful application, but
+as an example it should do:
+
+First, qdiscs need to be set up to attach filters to. For the receive path, a simple
+.B ingress
+qdisc will do, for transmit path a classful qdisc
+.RB ( HTB
+in this case) is necessary:
+
+.RS
+.EX
+tc qdisc replace dev eth0 root handle 1: htb
+tc qdisc add dev eth0 ingress handle ffff:
+.EE
+.RE
+
+Finally, a filter with
+.B pedit
+action can be added for each direction. In this case,
+.B u32
+is used matching on the port number to redirect from, while
+.B pedit
+then does the actual rewriting:
+
+.RS
+.EX
+tc filter add dev eth0 parent 1: u32 \\
+ match ip dport 23 0xffff \\
+ action pedit pedit munge ip dport set 22
+tc filter add dev eth0 parent ffff: u32 \\
+ match ip sport 22 0xffff \\
+ action pedit pedit munge ip sport set 23
+tc filter add dev eth0 parent ffff: u32 \\
+ match ip sport 22 0xffff \\
+ action pedit ex munge ip dst set 192.168.1.199
+tc filter add dev eth0 parent ffff: u32 \\
+ match ip sport 22 0xffff \\
+ action pedit ex munge ip6 dst set fe80::dacb:8aff:fec7:320e
+tc filter add dev eth0 parent ffff: u32 \\
+ match ip sport 22 0xffff \\
+ action pedit ex munge eth dst set 11:22:33:44:55:66
+tc filter add dev eth0 parent ffff: u32 \\
+ match ip dport 23 0xffff \\
+ action pedit ex munge tcp dport set 22
+.EE
+.RE
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-htb (8),
+.BR tc-u32 (8)
diff --git a/man/man8/tc-pfifo.8 b/man/man8/tc-pfifo.8
new file mode 100644
index 0000000..ed23850
--- /dev/null
+++ b/man/man8/tc-pfifo.8
@@ -0,0 +1 @@
+.so man8/tc-bfifo.8
diff --git a/man/man8/tc-pfifo_fast.8 b/man/man8/tc-pfifo_fast.8
new file mode 100644
index 0000000..baf34b1
--- /dev/null
+++ b/man/man8/tc-pfifo_fast.8
@@ -0,0 +1,57 @@
+.TH PFIFO_FAST 8 "10 January 2002" "iproute2" "Linux"
+.SH NAME
+pfifo_fast \- three-band first in, first out queue
+
+.SH DESCRIPTION
+pfifo_fast is the default qdisc of each interface.
+
+Whenever an interface is created, the pfifo_fast qdisc is automatically used
+as a queue. If another qdisc is attached, it preempts the default
+pfifo_fast, which automatically returns to function when an existing qdisc
+is detached.
+
+In this sense this qdisc is magic, and unlike other qdiscs.
+
+.SH ALGORITHM
+The algorithm is very similar to that of the classful
+.BR tc-prio (8)
+qdisc.
+.B pfifo_fast
+is like three
+.BR tc-pfifo (8)
+queues side by side, where packets can be enqueued in any of the three bands
+based on their Type of Service bits or assigned priority.
+
+Not all three bands are dequeued simultaneously - as long as lower bands
+have traffic, higher bands are never dequeued. This can be used to
+prioritize interactive traffic or penalize 'lowest cost' traffic.
+
+Each band can be txqueuelen packets long, as configured with
+.BR ifconfig (8)
+or
+.BR ip (8).
+Additional packets coming in are not enqueued but are instead dropped.
+
+See
+.BR tc-prio (8)
+for complete details on how TOS bits are translated into bands.
+.SH PARAMETERS
+.TP
+txqueuelen
+The length of the three bands depends on the interface txqueuelen, as
+specified with
+.BR ifconfig (8)
+or
+.BR ip (8).
+
+.SH BUGS
+Does not maintain statistics and does not show up in tc qdisc ls. This is because
+it is the automatic default in the absence of a configured qdisc.
+
+.SH SEE ALSO
+.BR tc (8)
+
+.SH AUTHORS
+Alexey N. Kuznetsov, <kuznet@ms2.inr.ac.ru>
+
+This manpage maintained by bert hubert <ahu@ds9a.nl>
diff --git a/man/man8/tc-pie.8 b/man/man8/tc-pie.8
new file mode 100644
index 0000000..a302132
--- /dev/null
+++ b/man/man8/tc-pie.8
@@ -0,0 +1,127 @@
+.TH PIE 8 "16 January 2014" "iproute2" "Linux"
+.SH NAME
+PIE \- Proportional Integral controller-Enhanced AQM algorithm
+.SH SYNOPSIS
+.B tc qdisc ... pie
+[
+.B limit
+PACKETS ] [
+.B target
+TIME ] [
+.B tupdate
+TIME ] [
+.B alpha
+int ] [
+.B beta
+int ] [
+.B ecn
+|
+.B noecn
+] [
+.B bytemode
+|
+.B nobytemode
+]
+
+.SH DESCRIPTION
+Proportional Integral controller-Enhanced (PIE) is a control theoretic active
+queue management scheme. It is based on the proportional integral controller but
+aims to control delay. The main design goals are
+ o Low latency control
+ o High link utilization
+ o Simple implementation
+ o Guaranteed stability and fast responsiveness
+
+.SH ALGORITHM
+PIE is designed to control delay effectively. First, an average dequeue rate is
+estimated based on the standing queue. The rate is used to calculate the current
+delay. Then, on a periodic basis, the delay is used to calculate the dropping
+probabilty. Finally, on arrival, a packet is dropped (or marked) based on this
+probability.
+
+PIE makes adjustments to the probability based on the trend of the delay i.e.
+whether it is going up or down.The delay converges quickly to the target value
+specified.
+
+alpha and beta are statically chosen parameters chosen to control the drop probability
+growth and are determined through control theoretic approaches. alpha determines how
+the deviation between the current and target latency changes probability. beta exerts
+additional adjustments depending on the latency trend.
+
+The drop probabilty is used to mark packets in ecn mode. However, as in RED,
+beyond 10% packets are dropped based on this probability. The bytemode is used
+to drop packets proportional to the packet size.
+
+Additional details can be found in the paper cited below.
+
+.SH PARAMETERS
+.SS limit
+limit on the queue size in packets. Incoming packets are dropped when this limit
+is reached. Default is 1000 packets.
+
+.SS target
+is the expected queue delay. The default target delay is 15ms.
+
+.SS tupdate
+is the frequency at which the system drop probability is calculated. The default is 15ms.
+
+.SS alpha
+.SS beta
+alpha and beta are parameters chosen to control the drop probability. These
+should be in the range between 0 and 32.
+
+.SS ecn | noecn
+is used to mark packets instead of dropping
+.B ecn
+to turn on ecn mode,
+.B noecn
+to turn off ecn mode. By default,
+.B ecn
+is turned off.
+
+.SS bytemode | nobytemode
+is used to scale drop probability proportional to packet size
+.B bytemode
+to turn on bytemode,
+.B nobytemode
+to turn off bytemode. By default,
+.B bytemode
+is turned off.
+
+.SH EXAMPLES
+ # tc qdisc add dev eth0 root pie
+ # tc -s qdisc show
+ qdisc pie 8036: dev eth0 root refcnt 2 limit 1000p target 15.0ms tupdate 16.0ms alpha 2 beta 20
+ Sent 31216108 bytes 20800 pkt (dropped 80, overlimits 0 requeues 0)
+ backlog 16654b 11p requeues 0
+ prob 0.006161 delay 15666us avg_dq_rate 1159667
+ pkts_in 20811 overlimit 0 dropped 80 maxq 50 ecn_mark 0
+
+ # tc qdisc add dev eth0 root pie limit 100 target 20ms tupdate 30ms ecn
+ # tc -s qdisc show
+ qdisc pie 8036: dev eth0 root refcnt 2 limit 100p target 20.0ms tupdate 32.0ms alpha 2 beta 20 ecn
+ Sent 6591724 bytes 4442 pkt (dropped 27, overlimits 0 requeues 0)
+ backlog 18168b 12p requeues 0
+ prob 0.008845 delay 11348us avg_dq_rate 1342773
+ pkts_in 4454 overlimit 0 dropped 27 maxq 65 ecn_mark 0
+
+ # tc qdisc add dev eth0 root pie limit 100 target 50ms tupdate 30ms bytemode
+ # tc -s qdisc show
+ qdisc pie 8036: dev eth0 root refcnt 2 limit 100p target 50.0ms tupdate 32.0ms alpha 2 beta 20 bytemode
+ Sent 1616274 bytes 1137 pkt (dropped 0, overlimits 0 requeues 0)
+ backlog 13626b 9p requeues 0
+ prob 0.000000 delay 0us avg_dq_rate 0
+ pkts_in 1146 overlimit 0 dropped 0 maxq 23 ecn_mark 0
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-codel (8)
+.BR tc-red (8)
+
+.SH SOURCES
+ o RFC 8033: https://tools.ietf.org/html/rfc8033
+
+.SH AUTHORS
+PIE was implemented by Vijay Subramanian and Mythili Prabhu, also the authors of
+this man page. Please report bugs and corrections to the Linux networking
+development mailing list at <netdev@vger.kernel.org>.
diff --git a/man/man8/tc-police.8 b/man/man8/tc-police.8
new file mode 100644
index 0000000..bcc5f43
--- /dev/null
+++ b/man/man8/tc-police.8
@@ -0,0 +1,146 @@
+.TH "Policing action in tc" 8 "20 Jan 2015" "iproute2" "Linux"
+
+.SH NAME
+police - policing action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action police"
+.BI rate " RATE " burst
+.IR BYTES [\fB/ BYTES "] ["
+.B mtu
+.IR BYTES [\fB/ BYTES "] ] ["
+.BI peakrate " RATE"
+] [
+.BI overhead " BYTES"
+] [
+.BI linklayer " TYPE"
+] [
+.IR CONTROL " ]"
+
+.ti -8
+.BR tc " ... " filter " ... [ " estimator
+.IR "SAMPLE AVERAGE " ]
+.BR "action police avrate"
+.IR RATE " [ " CONTROL " ]"
+
+.ti -8
+.IR CONTROL " :="
+.BI conform-exceed " EXCEEDACT\fR[\fB/\fINOTEXCEEDACT"
+
+.ti -8
+.IR EXCEEDACT/NOTEXCEEDACT " := { "
+.BR pipe " | " ok " | " reclassify " | " drop " | " continue " | " goto " " chain " " CHAIN_INDEX " }"
+.SH DESCRIPTION
+The
+.B police
+action allows to limit bandwidth of traffic matched by the filter it is
+attached to. Basically there are two different algorithms available to measure
+the packet rate: The first one uses an internal dual token bucket and is
+configured using the
+.BR rate ", " burst ", " mtu ", " peakrate ", " overhead " and " linklayer
+parameters. The second one uses an in-kernel sampling mechanism. It can be
+fine-tuned using the
+.B estimator
+filter parameter.
+.SH OPTIONS
+.TP
+.BI rate " RATE"
+The maximum traffic rate of packets passing this action. Those exceeding it will
+be treated as defined by the
+.B conform-exceed
+option.
+.TP
+.BI burst " BYTES\fR[\fB/\fIBYTES\fR]"
+Set the maximum allowed burst in bytes, optionally followed by a slash ('/')
+sign and cell size which must be a power of 2.
+.TP
+.BI mtu " BYTES\fR[\fB/\fIBYTES\fR]"
+This is the maximum packet size handled by the policer (larger ones will be
+handled like they exceeded the configured rate). Setting this value correctly
+will improve the scheduler's precision.
+Value formatting is identical to
+.B burst
+above. Defaults to unlimited.
+.TP
+.BI peakrate " RATE"
+Set the maximum bucket depletion rate, exceeding
+.BR rate .
+.TP
+.BI avrate " RATE"
+Make use of an in-kernel bandwidth rate estimator and match the given
+.I RATE
+against it.
+.TP
+.BI overhead " BYTES"
+Account for protocol overhead of encapsulating output devices when computing
+.BR rate " and " peakrate .
+.TP
+.BI linklayer " TYPE"
+Specify the link layer type.
+.I TYPE
+may be one of
+.B ethernet
+(the default),
+.BR atm " or " adsl
+(which are synonyms). It is used to align the precomputed rate tables to ATM
+cell sizes, for
+.B ethernet
+no action is taken.
+.TP
+.BI estimator " SAMPLE AVERAGE"
+Fine-tune the in-kernel packet rate estimator.
+.IR SAMPLE " and " AVERAGE
+are time values and control the frequency in which samples are taken and over
+what timespan an average is built.
+.TP
+.BI conform-exceed " EXCEEDACT\fR[\fB/\fINOTEXCEEDACT\fR]"
+Define how to handle packets which exceed or conform the
+configured bandwidth limit. Possible values are:
+.RS
+.IP continue
+Don't do anything, just continue with the next action in line.
+.IP drop
+Drop the packet immediately.
+.IP shot
+This is a synonym to
+.BR drop .
+.IP ok
+Accept the packet. This is the default for conforming packets.
+.IP pass
+This is a synonym to
+.BR ok .
+.IP reclassify
+Treat the packet as non-matching to the filter this action is attached to and
+continue with the next filter in line (if any). This is the default for
+exceeding packets.
+.IP pipe
+Pass the packet to the next action in line.
+.SH EXAMPLES
+A typical application of the police action is to enforce ingress traffic rate
+by dropping exceeding packets. Although better done on the sender's side,
+especially in scenarios with lack of peer control (e.g. with dial-up providers)
+this is often the best one can do in order to keep latencies low under high
+load. The following establishes input bandwidth policing to 1mbit/s using the
+.B ingress
+qdisc and
+.B u32
+filter:
+
+.RS
+.EX
+# tc qdisc add dev eth0 handle ffff: ingress
+# tc filter add dev eth0 parent ffff: u32 \\
+ match u32 0 0 \\
+ police rate 1mbit burst 100k
+.EE
+.RE
+
+As an action can not live on it's own, there always has to be a filter involved as link between qdisc and action. The example above uses
+.B u32
+for that, which is configured to effectively match any packet (passing it to the
+.B police
+action thereby).
+
+.SH SEE ALSO
+.BR tc (8)
diff --git a/man/man8/tc-prio.8 b/man/man8/tc-prio.8
new file mode 100644
index 0000000..605f3d3
--- /dev/null
+++ b/man/man8/tc-prio.8
@@ -0,0 +1,185 @@
+.TH PRIO 8 "16 December 2001" "iproute2" "Linux"
+.SH NAME
+PRIO \- Priority qdisc
+.SH SYNOPSIS
+.B tc qdisc ... dev
+dev
+.B ( parent
+classid
+.B | root) [ handle
+major:
+.B ] prio [ bands
+bands
+.B ] [ priomap
+band band band...
+.B ] [ estimator
+interval timeconstant
+.B ]
+
+.SH DESCRIPTION
+The PRIO qdisc is a simple classful queueing discipline that contains
+an arbitrary number of classes of differing priority. The classes are
+dequeued in numerical descending order of priority. PRIO is a scheduler
+and never delays packets - it is a work-conserving qdisc, though the qdiscs
+contained in the classes may not be.
+
+Very useful for lowering latency when there is no need for slowing down
+traffic.
+
+.SH ALGORITHM
+On creation with 'tc qdisc add', a fixed number of bands is created. Each
+band is a class, although is not possible to add classes with 'tc qdisc
+add', the number of bands to be created must instead be specified on the
+command line attaching PRIO to its root.
+
+When dequeueing, band 0 is tried first and only if it did not deliver a
+packet does PRIO try band 1, and so onwards. Maximum reliability packets
+should therefore go to band 0, minimum delay to band 1 and the rest to band
+2.
+
+As the PRIO qdisc itself will have minor number 0, band 0 is actually
+major:1, band 1 is major:2, etc. For major, substitute the major number
+assigned to the qdisc on 'tc qdisc add' with the
+.B handle
+parameter.
+
+.SH CLASSIFICATION
+Three methods are available to PRIO to determine in which band a packet will
+be enqueued.
+.TP
+From userspace
+A process with sufficient privileges can encode the destination class
+directly with SO_PRIORITY, see
+.BR socket(7).
+.TP
+with a tc filter
+A tc filter attached to the root qdisc can point traffic directly to a class
+.TP
+with the priomap
+Based on the packet priority, which in turn is derived from the Type of
+Service assigned to the packet.
+.P
+Only the priomap is specific to this qdisc.
+.SH QDISC PARAMETERS
+.TP
+bands
+Number of bands. If changed from the default of 3,
+.B priomap
+must be updated as well.
+.TP
+priomap
+The priomap maps the priority of
+a packet to a class. The priority can either be set directly from userspace,
+or be derived from the Type of Service of the packet.
+
+Determines how packet priorities, as assigned by the kernel, map to
+bands. Mapping occurs based on the TOS octet of the packet, which looks like
+this:
+
+.nf
+0 1 2 3 4 5 6 7
++---+---+---+---+---+---+---+---+
+| | | |
+|PRECEDENCE | TOS |MBZ|
+| | | |
++---+---+---+---+---+---+---+---+
+.fi
+
+The four TOS bits (the 'TOS field') are defined as:
+
+.nf
+Binary Decimal Meaning
+-----------------------------------------
+1000 8 Minimize delay (md)
+0100 4 Maximize throughput (mt)
+0010 2 Maximize reliability (mr)
+0001 1 Minimize monetary cost (mmc)
+0000 0 Normal Service
+.fi
+
+As there is 1 bit to the right of these four bits, the actual value of the
+TOS field is double the value of the TOS bits. Tcpdump -v -v shows you the
+value of the entire TOS field, not just the four bits. It is the value you
+see in the first column of this table:
+
+.nf
+TOS Bits Means Linux Priority Band
+------------------------------------------------------------
+0x0 0 Normal Service 0 Best Effort 1
+0x2 1 Minimize Monetary Cost 0 Best Effort 1
+0x4 2 Maximize Reliability 0 Best Effort 1
+0x6 3 mmc+mr 0 Best Effort 1
+0x8 4 Maximize Throughput 2 Bulk 2
+0xa 5 mmc+mt 2 Bulk 2
+0xc 6 mr+mt 2 Bulk 2
+0xe 7 mmc+mr+mt 2 Bulk 2
+0x10 8 Minimize Delay 6 Interactive 0
+0x12 9 mmc+md 6 Interactive 0
+0x14 10 mr+md 6 Interactive 0
+0x16 11 mmc+mr+md 6 Interactive 0
+0x18 12 mt+md 4 Int. Bulk 1
+0x1a 13 mmc+mt+md 4 Int. Bulk 1
+0x1c 14 mr+mt+md 4 Int. Bulk 1
+0x1e 15 mmc+mr+mt+md 4 Int. Bulk 1
+.fi
+
+The second column contains the value of the relevant
+four TOS bits, followed by their translated meaning. For example, 15 stands
+for a packet wanting Minimal Monetary Cost, Maximum Reliability, Maximum
+Throughput AND Minimum Delay.
+
+The fourth column lists the way the Linux kernel interprets the TOS bits, by
+showing to which Priority they are mapped.
+
+The last column shows the result of the default priomap. On the command line,
+the default priomap looks like this:
+
+ 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
+
+This means that priority 4, for example, gets mapped to band number 1.
+The priomap also allows you to list higher priorities (> 7) which do not
+correspond to TOS mappings, but which are set by other means.
+
+This table from RFC 1349 (read it for more details) explains how
+applications might very well set their TOS bits:
+
+.nf
+TELNET 1000 (minimize delay)
+FTP
+ Control 1000 (minimize delay)
+ Data 0100 (maximize throughput)
+
+TFTP 1000 (minimize delay)
+
+SMTP
+ Command phase 1000 (minimize delay)
+ DATA phase 0100 (maximize throughput)
+
+Domain Name Service
+ UDP Query 1000 (minimize delay)
+ TCP Query 0000
+ Zone Transfer 0100 (maximize throughput)
+
+NNTP 0001 (minimize monetary cost)
+
+ICMP
+ Errors 0000
+ Requests 0000 (mostly)
+ Responses <same as request> (mostly)
+.fi
+
+
+.SH CLASSES
+PRIO classes cannot be configured further - they are automatically created
+when the PRIO qdisc is attached. Each class however can contain yet a
+further qdisc.
+
+.SH BUGS
+Large amounts of traffic in the lower bands can cause starvation of higher
+bands. Can be prevented by attaching a shaper (for example,
+.BR tc-tbf(8)
+to these bands to make sure they cannot dominate the link.
+
+.SH AUTHORS
+Alexey N. Kuznetsov, <kuznet@ms2.inr.ac.ru>, J Hadi Salim
+<hadi@cyberus.ca>. This manpage maintained by bert hubert <ahu@ds9a.nl>
diff --git a/man/man8/tc-red.8 b/man/man8/tc-red.8
new file mode 100644
index 0000000..dd1ab74
--- /dev/null
+++ b/man/man8/tc-red.8
@@ -0,0 +1,160 @@
+.TH RED 8 "13 December 2001" "iproute2" "Linux"
+.SH NAME
+red \- Random Early Detection
+.SH SYNOPSIS
+.B tc qdisc ... red
+.B limit
+bytes
+.B [ min
+bytes
+.B ] [ max
+bytes
+.B ] avpkt
+bytes
+.B [ burst
+packets
+.B ] [ ecn ] [ harddrop] [ bandwidth
+rate
+.B ] [ probability
+chance
+.B ] [ adaptive ]
+
+.SH DESCRIPTION
+Random Early Detection is a classless qdisc which manages its queue size
+smartly. Regular queues simply drop packets from the tail when they are
+full, which may not be the optimal behaviour. RED also performs tail drop,
+but does so in a more gradual way.
+
+Once the queue hits a certain average length, packets enqueued have a
+configurable chance of being marked (which may mean dropped). This chance
+increases linearly up to a point called the
+.B max
+average queue length, although the queue might get bigger.
+
+This has a host of benefits over simple taildrop, while not being processor
+intensive. It prevents synchronous retransmits after a burst in traffic,
+which cause further retransmits, etc.
+
+The goal is to have a small queue size, which is good for interactivity
+while not disturbing TCP/IP traffic with too many sudden drops after a burst
+of traffic.
+
+Depending on if ECN is configured, marking either means dropping or
+purely marking a packet as overlimit.
+.SH ALGORITHM
+The average queue size is used for determining the marking
+probability. This is calculated using an Exponential Weighted Moving
+Average, which can be more or less sensitive to bursts.
+
+When the average queue size is below
+.B min
+bytes, no packet will ever be marked. When it exceeds
+.B min,
+the probability of doing so climbs linearly up
+to
+.B probability,
+until the average queue size hits
+.B max
+bytes. Because
+.B probability
+is normally not set to 100%, the queue size might
+conceivably rise above
+.B max
+bytes, so the
+.B limit
+parameter is provided to set a hard maximum for the size of the queue.
+
+.SH PARAMETERS
+.TP
+min
+Average queue size at which marking becomes a possibility. Defaults to
+.B max
+/3
+
+.TP
+max
+At this average queue size, the marking probability is maximal. Should be at
+least twice
+.B min
+to prevent synchronous retransmits, higher for low
+.B min.
+Default to
+.B limit
+/4
+.TP
+probability
+Maximum probability for marking, specified as a floating point
+number from 0.0 to 1.0. Suggested values are 0.01 or 0.02 (1 or 2%,
+respectively). Default : 0.02
+.TP
+limit
+Hard limit on the real (not average) queue size in bytes. Further packets
+are dropped. Should be set higher than max+burst. It is advised to set this
+a few times higher than
+.B max.
+.TP
+burst
+Used for determining how fast the average queue size is influenced by the
+real queue size. Larger values make the calculation more sluggish, allowing
+longer bursts of traffic before marking starts. Real life experiments
+support the following guideline: (min+min+max)/(3*avpkt).
+.TP
+avpkt
+Specified in bytes. Used with burst to determine the time constant for
+average queue size calculations. 1000 is a good value.
+.TP
+bandwidth
+This rate is used for calculating the average queue size after some
+idle time. Should be set to the bandwidth of your interface. Does not mean
+that RED will shape for you! Optional. Default : 10Mbit
+.TP
+ecn
+As mentioned before, RED can either 'mark' or 'drop'. Explicit Congestion
+Notification allows RED to notify remote hosts that their rate exceeds the
+amount of bandwidth available. Non-ECN capable hosts can only be notified by
+dropping a packet. If this parameter is specified, packets which indicate
+that their hosts honor ECN will only be marked and not dropped, unless the
+queue size hits
+.B limit
+bytes. Recommended.
+.TP
+harddrop
+If average flow queue size is above
+.B max
+bytes, this parameter forces a drop instead of ecn marking.
+.TP
+adaptive
+(Added in linux-3.3) Sets RED in adaptive mode as described in http://icir.org/floyd/papers/adaptiveRed.pdf
+.nf
+Goal of Adaptive RED is to make 'probability' dynamic value between 1% and 50% to reach the target average queue :
+.B (max - min) / 2
+.fi
+
+.SH EXAMPLE
+
+.P
+# tc qdisc add dev eth0 parent 1:1 handle 10: red
+ limit 400000 min 30000 max 90000 avpkt 1000
+ burst 55 ecn adaptive bandwidth 10Mbit
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-choke (8)
+
+.SH SOURCES
+.TP
+o
+Floyd, S., and Jacobson, V., Random Early Detection gateways for
+Congestion Avoidance. http://www.aciri.org/floyd/papers/red/red.html
+.TP
+o
+Some changes to the algorithm by Alexey N. Kuznetsov.
+.TP
+o
+Adaptive RED : http://icir.org/floyd/papers/adaptiveRed.pdf
+
+.SH AUTHORS
+Alexey N. Kuznetsov, <kuznet@ms2.inr.ac.ru>, Alexey Makarenko
+<makar@phoenix.kharkov.ua>, J Hadi Salim <hadi@nortelnetworks.com>,
+Eric Dumazet <eric.dumazet@gmail.com>.
+This manpage maintained by bert hubert <ahu@ds9a.nl>
diff --git a/man/man8/tc-route.8 b/man/man8/tc-route.8
new file mode 100644
index 0000000..b865cd1
--- /dev/null
+++ b/man/man8/tc-route.8
@@ -0,0 +1,74 @@
+.TH "Route classifier in tc" 8 "21 Oct 2015" "iproute2" "Linux"
+
+.SH NAME
+route \- route traffic control filter
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " " filter " ... " route " [ " from
+.IR REALM " | "
+.B fromif
+.IR TAG " ] [ "
+.B to
+.IR REALM " ] [ "
+.B classid
+.IR CLASSID " ] [ "
+.B action
+.IR ACTION_SPEC " ]"
+.SH DESCRIPTION
+Match packets based on routing table entries. This filter centers around the
+possibility to assign a
+.B realm
+to routing table entries. For any packet to be classified by this filter, a
+routing table lookup is performed and the returned
+.B realm
+is used to decide on whether the packet is a match or not.
+.SH OPTIONS
+.TP
+.BI action " ACTION_SPEC"
+Apply an action from the generic actions framework on matching packets.
+.TP
+.BI classid " CLASSID"
+Push matching packets into the class identified by
+.IR CLASSID .
+.TP
+.BI from " REALM"
+.TQ
+.BI fromif " TAG"
+Perform source route lookups.
+.I TAG
+is the name of an interface which must be present on the system at the time of
+.B tc
+invocation.
+.TP
+.BI to " REALM"
+Match if normal (i.e., destination) routing returns the given
+.IR REALM .
+.SH EXAMPLES
+Consider the subnet 192.168.2.0/24 being attached to eth0:
+
+.RS
+.EX
+ip route add 192.168.2.0/24 dev eth0 realm 2
+.EE
+.RE
+
+The following
+.B route
+filter will then match packets from that subnet:
+
+.RS
+.EX
+tc filter add ... route from 2 classid 1:2
+.EE
+.RE
+
+and pass packets on to class 1:2.
+.SH NOTES
+Due to implementation details,
+.B realm
+values must be in a range from 0 to 255, inclusive. Alternatively, a verbose
+name defined in /etc/iproute2/rt_realms may be given instead.
+.SH SEE ALSO
+.BR tc (8),
+.BR ip-route (8)
diff --git a/man/man8/tc-sample.8 b/man/man8/tc-sample.8
new file mode 100644
index 0000000..3e03eba
--- /dev/null
+++ b/man/man8/tc-sample.8
@@ -0,0 +1,125 @@
+.TH "Packet sample action in tc" 8 "31 Jan 2017" "iproute2" "Linux"
+
+.SH NAME
+sample - packet sampling tc action
+.SH SYNOPSIS
+.in +8
+.ti -8
+
+.BR tc " ... " "action sample rate"
+.I RATE
+.BR "group"
+.I GROUP
+.RB "[ " trunc
+.IR SIZE " ] "
+.RB "[ " index
+.IR INDEX " ] "
+.ti -8
+
+.BR tc " ... " "action sample index "
+.I INDEX
+.ti -8
+
+.SH DESCRIPTION
+The
+.B sample
+action allows sampling packets matching classifier.
+
+The packets are chosen randomly according to the
+.B rate
+parameter, and are sampled using the
+.B psample
+generic netlink channel. The user can also specify packet truncation to save
+user-kernel traffic. Each sample includes some informative metadata about the
+original packet, which is sent using netlink attributes, alongside the original
+packet data.
+
+The user can either specify the sample action parameters as presented in the
+first form above, or use an existing sample action using its index, as presented
+in the second form.
+
+.SH SAMPLED PACKETS METADATA FIELDS
+The metadata are delivered to userspace applications using the
+.B psample
+generic netlink channel, where each sample includes the following netlink
+attributes:
+.TP
+.BI PSAMPLE_ATTR_IIFINDEX
+The input interface index of the packet, if there is one.
+.TP
+.BI PSAMPLE_ATTR_OIFINDEX
+The output interface index of the packet. This field is not relevant on ingress
+sampling
+.TP
+.BI PSAMPLE_ATTR_ORIGSIZE
+The size of the original packet (before truncation)
+.TP
+.BI PSAMPLE_ATTR_SAMPLE_GROUP
+The
+.B psample
+group the packet was sent to
+.TP
+.BI PSAMPLE_ATTR_GROUP_SEQ
+A sequence number of the sampled packet. This number is incremented with each
+sampled packet of the current
+.B psample
+group
+.TP
+.BI PSAMPLE_ATTR_SAMPLE_RATE
+The rate the packet was sampled with
+.RE
+
+.SH OPTIONS
+.TP
+.BI rate " RATE"
+The packet sample rate.
+.I "RATE"
+is the expected ratio between observed packets and sampled packets. For example,
+.I "RATE"
+of 100 will lead to an average of one sampled packet out of every 100 observed.
+.TP
+.BI trunc " SIZE"
+Upon set, defines the maximum size of the sampled packets, and causes truncation
+if needed
+.TP
+.BI group " GROUP"
+The
+.B psample
+group the packet will be sent to. The
+.B psample
+module defines the concept of groups, which allows the user to match specific
+sampled packets in the case of multiple sampling rules, thus identify only the
+packets that came from a specific rule.
+.TP
+.BI index " INDEX"
+Is a unique ID for an action. When creating new action instance, this parameter
+allows to set the new action index. When using existing action, this parameter
+allows to specify the existing action index. The index must 32bit unsigned
+integer greater than zero.
+.SH EXAMPLES
+Sample one of every 100 packets flowing into interface eth0 to psample group 12:
+
+.RS
+.EX
+tc qdisc add dev eth0 handle ffff: ingress
+tc filter add dev eth0 parent ffff: matchall \\
+ action sample rate 100 group 12 index 19
+.EE
+.RE
+
+Use the same action instance to sample eth1 too:
+
+.RS
+.EX
+tc qdisc add dev eth1 handle ffff: ingress
+tc filter add dev eth1 parent ffff: matchall \\
+ action sample index 19
+.EE
+.RE
+
+.EE
+.RE
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-matchall (8)
+.BR psample (1)
diff --git a/man/man8/tc-sfb.8 b/man/man8/tc-sfb.8
new file mode 100644
index 0000000..aad19e1
--- /dev/null
+++ b/man/man8/tc-sfb.8
@@ -0,0 +1,213 @@
+.TH SFB 8 "August 2011" "iproute2" "Linux"
+.SH NAME
+sfb \- Stochastic Fair Blue
+.SH SYNOPSIS
+.B tc qdisc ... blue
+.B rehash
+milliseconds
+.B db
+milliseconds
+.B limit
+packets
+.B max
+packets
+.B target
+packets
+.B increment
+float
+.B decrement
+float
+.B penalty_rate
+packets per second
+.B penalty_burst
+packets
+
+.SH DESCRIPTION
+Stochastic Fair Blue is a classless qdisc to manage congestion based on
+packet loss and link utilization history while trying to prevent
+non-responsive flows (i.e. flows that do not react to congestion marking
+or dropped packets) from impacting performance of responsive flows.
+Unlike RED, where the marking probability has to be configured, BLUE
+tries to determine the ideal marking probability automatically.
+
+.SH ALGORITHM
+
+The
+.B BLUE
+algorithm maintains a probability which is used to mark or drop packets
+that are to be queued. If the queue overflows, the mark/drop probability
+is increased. If the queue becomes empty, the probability is decreased. The
+.B Stochastic Fair Blue
+(SFB) algorithm is designed to protect TCP flows against non-responsive flows.
+
+This SFB implementation maintains 8 levels of 16 bins each for accounting.
+Each flow is mapped into a bin of each level using a per-level hash value.
+
+Every bin maintains a marking probability, which gets increased or decreased
+based on bin occupancy. If the number of packets exceeds the size of that
+bin, the marking probability is increased. If the number drops to zero, it
+is decreased.
+
+The marking probability is based on the minimum value of all bins a flow is
+mapped into, thus, when a flow does not respond to marking or gradual packet
+drops, the marking probability quickly reaches one.
+
+In this case, the flow is rate-limited to
+.B penalty_rate
+packets per second.
+
+.SH LIMITATIONS
+
+Due to SFBs nature, it is possible for responsive flows to share all of its bins
+with a non-responsive flow, causing the responsive flow to be misidentified as
+being non-responsive.
+
+The probability of a responsive flow to be misidentified is dependent on
+the number of non-responsive flows, M. It is (1 - (1 - (1 / 16.0)) ** M) **8,
+so for example with 10 non-responsive flows approximately 0.2% of responsive flows
+will be misidentified.
+
+To mitigate this, SFB performs performs periodic re-hashing to avoid
+misclassification for prolonged periods of time.
+
+The default hashing method will use source and destination ip addresses and port numbers
+if possible, and also supports tunneling protocols.
+Alternatively, an external classifier can be configured, too.
+
+.SH PARAMETERS
+.TP
+rehash
+Time interval in milliseconds when queue perturbation occurs to avoid erroneously
+detecting unrelated, responsive flows as being part of a non-responsive flow for
+prolonged periods of time.
+Defaults to 10 minutes.
+.TP
+db
+Double buffering warmup wait time, in milliseconds.
+To avoid destroying the probability history when rehashing is performed, this
+implementation maintains a second set of levels/bins as described in section
+4.4 of the SFB reference.
+While one set is used to manage the queue, a second set is warmed up:
+Whenever a flow is then determined to be non-responsive, the marking
+probabilities in the second set are updated. When the rehashing
+happens, these bins will be used to manage the queue and all non-responsive
+flows can be rate-limited immediately.
+This value determines how much time has to pass before the 2nd set
+will start to be warmed up.
+Defaults to one minute, should be lower than
+.B
+rehash.
+.TP
+limit
+Hard limit on the real (not average) total queue size in packets.
+Further packets are dropped. Defaults to the transmit queue length of the
+device the qdisc is attached to.
+.TP
+max
+Maximum length of a buckets queue, in packets, before packets start being
+dropped. Should be sightly larger than
+.B target
+, but should not be set to values exceeding 1.5 times that of
+.B target .
+Defaults to 25.
+.TP
+target
+The desired average bin length. If the bin queue length reaches this value,
+the marking probability is increased by
+.B increment.
+The default value depends on the
+.B max
+setting, with max set to 25
+.B target
+will default to 20.
+.TP
+increment
+A value used to increase the marking probability when the queue appears
+to be over-used. Must be between 0 and 1.0. Defaults to 0.00050.
+.TP
+decrement
+Value used to decrease the marking probability when the queue is found
+to be empty. Must be between 0 and 1.0.
+Defaults to 0.00005.
+.TP
+penalty_rate
+The maximum number of packets belonging to flows identified as being
+non-responsive that can be enqueued per second. Once this number has been
+reached, further packets of such non-responsive flows are dropped.
+Set this to a reasonable fraction of your uplink throughput; the
+default value of 10 packets is probably too small.
+.TP
+penalty_burst
+The number of packets a flow is permitted to exceed the penalty rate before packets
+start being dropped.
+Defaults to 20 packets.
+
+.SH STATISTICS
+
+This qdisc exposes additional statistics via 'tc -s qdisc' output.
+These are:
+.TP
+earlydrop
+The number of packets dropped before a per-flow queue was full.
+.TP
+ratedrop
+The number of packets dropped because of rate-limiting.
+If this value is high, there are many non-reactive flows being
+sent through sfb. In such cases, it might be better to
+embed sfb within a classful qdisc to better control such
+flows using a different, shaping qdisc.
+.TP
+bucketdrop
+The number of packets dropped because a per-flow queue was full.
+High bucketdrop may point to a high number of aggressive, short-lived
+flows.
+.TP
+queuedrop
+The number of packets dropped due to reaching limit. This should normally be 0.
+.TP
+marked
+The number of packets marked with ECN.
+.TP
+maxqlen
+The length of the current longest per-flow (virtual) queue.
+.TP
+maxprob
+The maximum per-flow drop probability. 1 means that some
+flows have been detected as non-reactive.
+
+.SH NOTES
+
+SFB automatically enables use of Explicit Congestion Notification (ECN).
+Also, this SFB implementation does not queue packets itself.
+Rather, packets are enqueued to the inner qdisc (defaults to pfifo).
+Because sfb maintains virtual queue states, the inner qdisc must not
+drop a packet previously queued.
+Furthermore, if a buckets queue has a very high marking rate,
+this implementation will start dropping packets instead of
+marking them, as such a situation points to either bad congestion, or an
+unresponsive flow.
+
+.SH EXAMPLE & USAGE
+
+To attach to interface $DEV, using default options:
+.P
+# tc qdisc add dev $DEV handle 1: root sfb
+
+Only use destination ip addresses for assigning packets to bins, perturbing
+hash results every 10 minutes:
+.P
+# tc filter add dev $DEV parent 1: handle 1 flow hash keys dst perturb 600
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-red (8),
+.BR tc-sfq (8)
+.SH SOURCES
+.TP
+o
+W. Feng, D. Kandlur, D. Saha, K. Shin, BLUE: A New Class of Active Queue Management Algorithms,
+U. Michigan CSE-TR-387-99, April 1999.
+
+.SH AUTHORS
+
+This SFB implementation was contributed by Juliusz Chroboczek and Eric Dumazet.
diff --git a/man/man8/tc-sfq.8 b/man/man8/tc-sfq.8
new file mode 100644
index 0000000..ec4d8b8
--- /dev/null
+++ b/man/man8/tc-sfq.8
@@ -0,0 +1,222 @@
+.TH TC 8 "24 January 2012" "iproute2" "Linux"
+.SH NAME
+sfq \- Stochastic Fairness Queueing
+.SH SYNOPSIS
+.B tc qdisc ...
+.B [ divisor
+hashtablesize
+.B ] [ limit
+packets
+.B ] [ perturb
+seconds
+.B ] [ quantum
+bytes
+.B ] [ flows
+number
+.B ] [ depth
+number
+.B ] [ headdrop
+.B ] [ redflowlimit
+bytes
+.B ] [ min
+bytes
+.B ] [ max
+bytes
+.B ] [ avpkt
+bytes
+.B ] [ burst
+packets
+.B ] [ probability
+P
+.B ] [ ecn
+.B ] [ harddrop ]
+.SH DESCRIPTION
+
+Stochastic Fairness Queueing is a classless queueing discipline available for
+traffic control with the
+.BR tc (8)
+command.
+
+SFQ does not shape traffic but only schedules the transmission of packets, based on 'flows'.
+The goal is to ensure fairness so that each flow is able to send data in turn, thus preventing
+any single flow from drowning out the rest.
+
+This may in fact have some effect in mitigating a Denial of Service attempt.
+
+SFQ is work-conserving and therefore always delivers a packet if it has one available.
+.SH ALGORITHM
+On enqueueing, each packet is assigned to a hash bucket, based on the packets hash value.
+This hash value is either obtained from an external flow classifier (use
+.B
+tc filter
+to set them), or a default internal classifier if no external classifier has been configured.
+
+When the internal classifier is used, sfq uses
+.TP
+(i)
+Source address
+.TP
+(ii)
+Destination address
+.TP
+(iii)
+Source and Destination port
+.P
+If these are available. SFQ knows about ipv4 and ipv6 and also UDP, TCP and ESP.
+Packets with other protocols are hashed based on the 32bits representation of their
+destination and source. A flow corresponds mostly to a TCP/IP connection.
+
+Each of these buckets should represent a unique flow. Because multiple flows may
+get hashed to the same bucket, sfqs internal hashing algorithm may be perturbed at configurable
+intervals so that the unfairness lasts only for a short while. Perturbation may
+however cause some inadvertent packet reordering to occur. After linux-3.3, there is
+no packet reordering problem, but possible packet drops if rehashing hits one limit
+(number of flows or packets per flow)
+
+When dequeuing, each hashbucket with data is queried in a round robin fashion.
+
+Before linux-3.3, the compile time maximum length of the SFQ is 128 packets, which can be spread over
+at most 128 buckets of 1024 available. In case of overflow, tail-drop is performed
+on the fullest bucket, thus maintaining fairness.
+
+After linux-3.3, maximum length of SFQ is 65535 packets, and divisor limit is 65536.
+In case of overflow, tail-drop is performed on the fullest bucket, unless headdrop was requested.
+
+.SH PARAMETERS
+.TP
+divisor
+Can be used to set a different hash table size, available from kernel 2.6.39 onwards.
+The specified divisor must be a power of two and cannot be larger than 65536.
+Default value: 1024.
+.TP
+limit
+Upper limit of the SFQ. Can be used to reduce the default length of 127 packets.
+After linux-3.3, it can be raised.
+.TP
+depth
+Limit of packets per flow (after linux-3.3). Default to 127 and can be lowered.
+.TP
+perturb
+Interval in seconds for queue algorithm perturbation. Defaults to 0, which means that
+no perturbation occurs. Do not set too low for each perturbation may cause some packet
+reordering or losses. Advised value: 60
+This value has no effect when external flow classification is used.
+Its better to increase divisor value to lower risk of hash collisions.
+.TP
+quantum
+Amount of bytes a flow is allowed to dequeue during a round of the round robin process.
+Defaults to the MTU of the interface which is also the advised value and the minimum value.
+.TP
+flows
+After linux-3.3, it is possible to change the default limit of flows.
+Default value is 127
+.TP
+headdrop
+Default SFQ behavior is to perform tail-drop of packets from a flow.
+You can ask a headdrop instead, as this is known to provide a better feedback for TCP flows.
+.TP
+redflowlimit
+Configure the optional RED module on top of each SFQ flow.
+Random Early Detection principle is to perform packet marks or drops in a probabilistic way.
+(man tc-red for details about RED)
+.nf
+redflowlimit configures the hard limit on the real (not average) queue size per SFQ flow in bytes.
+.fi
+.TP
+min
+Average queue size at which marking becomes a possibility. Defaults to
+.B max
+/3
+.TP
+max
+At this average queue size, the marking probability is maximal. Defaults to
+.B redflowlimit
+/4
+.TP
+probability
+Maximum probability for marking, specified as a floating point number from 0.0 to 1.0. Default value is 0.02
+.TP
+avpkt
+Specified in bytes. Used with burst to determine the time constant for average queue size calculations. Default value is 1000
+.TP
+burst
+Used for determining how fast the average queue size is influenced by the real queue size.
+.nf
+Default value is :
+.B (2 * min + max) / (3 * avpkt)
+.fi
+.TP
+ecn
+RED can either 'mark' or 'drop'. Explicit Congestion
+Notification allows RED to notify remote hosts that their rate exceeds the
+amount of bandwidth available. Non-ECN capable hosts can only be notified by
+dropping a packet. If this parameter is specified, packets which indicate
+that their hosts honor ECN will only be marked and not dropped, unless the
+queue size hits
+.B depth
+packets.
+.TP
+harddrop
+If average flow queue size is above
+.B max
+bytes, this parameter forces a drop instead of ecn marking.
+.SH EXAMPLE & USAGE
+
+To attach to device ppp0:
+.P
+# tc qdisc add dev ppp0 root sfq
+.P
+Please note that SFQ, like all non-shaping (work-conserving) qdiscs, is only useful
+if it owns the queue.
+This is the case when the link speed equals the actually available bandwidth. This holds
+for regular phone modems, ISDN connections and direct non-switched ethernet links.
+.P
+Most often, cable modems and DSL devices do not fall into this category. The same holds
+for when connected to a switch and trying to send data to a congested segment also
+connected to the switch.
+.P
+In this case, the effective queue does not reside within Linux and is therefore not
+available for scheduling.
+.P
+Embed SFQ in a classful qdisc to make sure it owns the queue.
+
+It is possible to use external classifiers with sfq, for example to hash traffic based only
+on source/destination ip addresses:
+.P
+# tc filter add ... flow hash keys src,dst perturb 30 divisor 1024
+.P
+Note that the given divisor should match the one used by sfq. If you have
+changed the sfq default of 1024, use the same value for the flow hash filter, too.
+
+.P
+Example of sfq with optional RED mode :
+.P
+# tc qdisc add dev eth0 parent 1:1 handle 10: sfq limit 3000 flows 512 divisor 16384
+ redflowlimit 100000 min 8000 max 60000 probability 0.20 ecn headdrop
+
+.SH SOURCE
+.TP
+o
+Paul E. McKenney "Stochastic Fairness Queuing",
+IEEE INFOCOMM'90 Proceedings, San Francisco, 1990.
+
+.TP
+o
+Paul E. McKenney "Stochastic Fairness Queuing",
+"Interworking: Research and Experience", v.2, 1991, p.113-131.
+
+.TP
+o
+See also:
+M. Shreedhar and George Varghese "Efficient Fair
+Queuing using Deficit Round Robin", Proc. SIGCOMM 95.
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-red (8)
+
+.SH AUTHORS
+Alexey N. Kuznetsov, <kuznet@ms2.inr.ac.ru>,
+Eric Dumazet <eric.dumazet@gmail.com>.
+.P
+This manpage maintained by bert hubert <ahu@ds9a.nl>
diff --git a/man/man8/tc-simple.8 b/man/man8/tc-simple.8
new file mode 100644
index 0000000..7363ab5
--- /dev/null
+++ b/man/man8/tc-simple.8
@@ -0,0 +1,99 @@
+.TH "Simple action in tc" 8 "12 Jan 2015" "iproute2" "Linux"
+
+.SH NAME
+simple - basic example action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action simple"
+[
+.BI sdata " STRING"
+] [
+.BI index " INDEX"
+] [
+.I CONTROL
+]
+
+.ti -8
+.IR CONTROL " := {"
+.BR reclassify " | " pipe " | " drop " | " continue " | " ok " }"
+
+.SH DESCRIPTION
+This is a pedagogical example rather than an actually useful action. Upon every access, it prints the given
+.I STRING
+which may be of arbitrary length.
+.SH OPTIONS
+.TP
+.BI sdata " STRING"
+The actual string to print.
+.TP
+.BI index " INDEX"
+Optional action index value.
+.TP
+.I CONTROL
+Indicate how
+.B tc
+should proceed after executing the action. For a description of the possible
+.I CONTROL
+values, see
+.BR tc-actions (8).
+.SH EXAMPLES
+The following example makes the kernel yell "Incoming ICMP!" every time it sees
+an incoming ICMP on eth0. Steps are:
+.IP 1) 4
+Add an ingress qdisc point to eth0
+.IP 2) 4
+Start a chain on ingress of eth0 that first matches ICMP then invokes the
+simple action to shout.
+.IP 3) 4
+display stats and show that no packet has been seen by the action
+.IP 4) 4
+Send one ping packet to google (expect to receive a response back)
+.IP 5) 4
+grep the logs to see the logged message
+.IP 6) 4
+display stats again and observe increment by 1
+
+.RE
+.EX
+ hadi@noma1:$ tc qdisc add dev eth0 ingress
+ hadi@noma1:$tc filter add dev eth0 parent ffff: protocol ip prio 5 \\
+ u32 match ip protocol 1 0xff flowid 1:1 action simple sdata "Incoming ICMP"
+
+ hadi@noma1:$ sudo tc -s filter ls dev eth0 parent ffff:
+ filter protocol ip pref 5 u32
+ filter protocol ip pref 5 u32 fh 800: ht divisor 1
+ filter protocol ip pref 5 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1
+ match 00010000/00ff0000 at 8
+ action order 1: Simple <Incoming ICMP>
+ index 4 ref 1 bind 1 installed 29 sec used 29 sec
+ Action statistics:
+ Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
+ backlog 0b 0p requeues 0
+
+
+ hadi@noma1$ ping -c 1 www.google.ca
+ PING www.google.ca (74.125.225.120) 56(84) bytes of data.
+ 64 bytes from ord08s08-in-f24.1e100.net (74.125.225.120): icmp_req=1 ttl=53 time=31.3 ms
+
+ --- www.google.ca ping statistics ---
+ 1 packets transmitted, 1 received, 0% packet loss, time 0ms
+ rtt min/avg/max/mdev = 31.316/31.316/31.316/0.000 ms
+
+ hadi@noma1$ dmesg | grep simple
+ [135354.473951] simple: Incoming ICMP_1
+
+ hadi@noma1$ sudo tc/tc -s filter ls dev eth0 parent ffff:
+ filter protocol ip pref 5 u32
+ filter protocol ip pref 5 u32 fh 800: ht divisor 1
+ filter protocol ip pref 5 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1
+ match 00010000/00ff0000 at 8
+ action order 1: Simple <Incoming ICMP>
+ index 4 ref 1 bind 1 installed 206 sec used 67 sec
+ Action statistics:
+ Sent 84 bytes 1 pkt (dropped 0, overlimits 0 requeues 0)
+ backlog 0b 0p requeues 0
+.EE
+.SH SEE ALSO
+.BR tc (8)
+.BR tc-actions (8)
diff --git a/man/man8/tc-skbedit.8 b/man/man8/tc-skbedit.8
new file mode 100644
index 0000000..704f63b
--- /dev/null
+++ b/man/man8/tc-skbedit.8
@@ -0,0 +1,74 @@
+.TH "SKB editing action in tc" 8 "12 Jan 2015" "iproute2" "Linux"
+
+.SH NAME
+skbedit - SKB editing action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action skbedit " [ " queue_mapping
+.IR QUEUE_MAPPING " ] ["
+.B priority
+.IR PRIORITY " ] ["
+.BI mark " MARK\fR[\fB/\fIMASK] ] ["
+.B ptype
+.IR PTYPE " ] ["
+.BR inheritdsfield " ]"
+.SH DESCRIPTION
+The
+.B skbedit
+action allows to change a packet's associated meta data. It complements the
+.B pedit
+action, which in turn allows to change parts of the packet data itself.
+
+The most unique feature of
+.B skbedit
+is its ability to decide over which queue of an interface with multiple
+transmit queues the packet is to be sent out. The number of available transmit
+queues is reflected by sysfs entries within
+.I /sys/class/net/<interface>/queues
+with name
+.I tx-N
+(where
+.I N
+is the actual queue number).
+.SH OPTIONS
+.TP
+.BI queue_mapping " QUEUE_MAPPING"
+Override the packet's transmit queue. Useful when applied to packets transmitted
+over MQ-capable network interfaces.
+.I QUEUE_MAPPING
+is an unsigned 16bit value in decimal format.
+.TP
+.BI priority " PRIORITY"
+Override the packet classification decision.
+.I PRIORITY
+is either
+.BR root ", " none
+or a hexadecimal major class ID optionally followed by a colon
+.RB ( : )
+and a hexadecimal minor class ID.
+.TP
+.BI mark " MARK\fR[\fB/\fIMASK]"
+Change the packet's firewall mark value.
+.I MARK
+is an unsigned 32bit value in automatically detected format (i.e., prefix with
+.RB ' 0x '
+for hexadecimal interpretation, etc.).
+.I MASK
+defines the 32-bit mask selecting bits of mark value. Default is 0xffffffff.
+.TP
+.BI ptype " PTYPE"
+Override the packet's type. Useful for setting packet type to host when
+needing to allow ingressing packets with the wrong MAC address but
+correct IP address.
+.I PTYPE
+is one of: host, otherhost, broadcast, multicast
+.TP
+.BI inheritdsfield
+Override the packet classification decision, and any value specified with
+.BR priority ", "
+using the information stored in the Differentiated Services Field of the
+IPv6/IPv4 header (RFC2474).
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-pedit (8)
diff --git a/man/man8/tc-skbmod.8 b/man/man8/tc-skbmod.8
new file mode 100644
index 0000000..46418b6
--- /dev/null
+++ b/man/man8/tc-skbmod.8
@@ -0,0 +1,137 @@
+.TH "skbmod action in tc" 8 "21 Sep 2016" "iproute2" "Linux"
+
+.SH NAME
+skbmod - user-friendly packet editor action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action skbmod " "{ [ " "set "
+.IR SETTABLE " ] [ "
+.BI swap " SWAPPABLE"
+.RI " ] [ " CONTROL " ] [ "
+.BI index " INDEX "
+] }
+
+.ti -8
+.IR SETTABLE " := "
+.RB " [ " dmac
+.IR DMAC " ] "
+.RB " [ " smac
+.IR SMAC " ] "
+.RB " [ " etype
+.IR ETYPE " ] "
+
+.ti -8
+.IR SWAPPABLE " := "
+.B mac
+.ti -8
+.IR CONTROL " := {"
+.BR reclassify " | " pipe " | " drop " | " shot " | " continue " | " pass " }"
+.SH DESCRIPTION
+The
+.B skbmod
+action is intended as a usability upgrade to the existing
+.B pedit
+action. Instead of having to manually edit 8-, 16-, or 32-bit chunks of an
+ethernet header,
+.B skbmod
+allows complete substitution of supported elements.
+.SH OPTIONS
+.TP
+.BI dmac " DMAC"
+Change the destination mac to the specified address.
+.TP
+.BI smac " SMAC"
+Change the source mac to the specified address.
+.TP
+.BI etype " ETYPE"
+Change the ethertype to the specified value.
+.TP
+.BI mac
+Used to swap mac addresses. The
+.B swap mac
+directive is performed
+after any outstanding D/SMAC changes.
+.TP
+.I CONTROL
+The following keywords allow to control how the tree of qdisc, classes,
+filters and actions is further traversed after this action.
+.RS
+.TP
+.B reclassify
+Restart with the first filter in the current list.
+.TP
+.B pipe
+Continue with the next action attached to the same filter.
+.TP
+.B drop
+.TQ
+.B shot
+Drop the packet.
+.TP
+.B continue
+Continue classification with the next filter in line.
+.TP
+.B pass
+Finish classification process and return to calling qdisc for further packet
+processing. This is the default.
+.SH EXAMPLES
+To start, observe the following filter with a pedit action:
+
+.RS
+.EX
+tc filter add dev eth1 parent 1: protocol ip prio 10 \\
+ u32 match ip protocol 1 0xff flowid 1:2 \\
+ action pedit munge offset -14 u8 set 0x02 \\
+ munge offset -13 u8 set 0x15 \\
+ munge offset -12 u8 set 0x15 \\
+ munge offset -11 u8 set 0x15 \\
+ munge offset -10 u16 set 0x1515 \\
+ pipe
+.EE
+.RE
+
+Using the skbmod action, this command can be simplified to:
+
+.RS
+.EX
+tc filter add dev eth1 parent 1: protocol ip prio 10 \\
+ u32 match ip protocol 1 0xff flowid 1:2 \\
+ action skbmod set dmac 02:15:15:15:15:15 \\
+ pipe
+.EE
+.RE
+
+Complexity will increase if source mac and ethertype are also being edited
+as part of the action. If all three fields are to be changed with skbmod:
+
+.RS
+.EX
+tc filter add dev eth5 parent 1: protocol ip prio 10 \\
+ u32 match ip protocol 1 0xff flowid 1:2 \\
+ action skbmod \\
+ set etype 0xBEEF \\
+ set dmac 02:12:13:14:15:16 \\
+ set smac 02:22:23:24:25:26
+.EE
+.RE
+
+Finally, swap the destination and source mac addresses in the header:
+
+.RS
+.EX
+tc filter add dev eth3 parent 1: protocol ip prio 10 \\
+ u32 match ip protocol 1 0xff flowid 1:2 \\
+ action skbmod \\
+ swap mac
+.EE
+.RE
+
+As mentioned above, the swap action will occur after any
+.B " smac/dmac "
+substitutions are executed, if they are present.
+
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-u32 (8),
+.BR tc-pedit (8)
diff --git a/man/man8/tc-skbprio.8 b/man/man8/tc-skbprio.8
new file mode 100644
index 0000000..a0a316b
--- /dev/null
+++ b/man/man8/tc-skbprio.8
@@ -0,0 +1,70 @@
+.TH SKBPRIO 8 "13 August 2018" "iproute2" "Linux"
+.SH NAME
+skbprio \- SKB Priority Queue
+
+.SH SYNOPSIS
+.B tc qdisc ... add skbprio
+.B [ limit
+packets
+.B ]
+
+.SH DESCRIPTION
+SKB Priority Queue is a queueing discipline intended to prioritize
+the most important packets during a denial-of-service (
+.B DoS
+) attack. The priority of a packet is given by
+.B skb->priority
+, where a higher value places the packet closer to the exit of the queue. When
+the queue is full, the lowest priority packet in the queue is dropped to make
+room for the packet to be added if it has higher priority. If the packet to be
+added has lower priority than all packets in the queue, it is dropped.
+
+Without SKB priority queue, queue length limits must be imposed
+on individual sub-queues, and there is no straightforward way to enforce
+a global queue length limit across all priorities. SKBprio queue enforces
+a global queue length limit while not restricting the lengths of
+individual sub-queues.
+
+While SKB Priority Queue is agnostic to how
+.B skb->priority
+is assigned. A typical use case is to copy
+the 6-bit DS field of IPv4 and IPv6 packets using
+.BR tc-skbedit (8).
+If
+.B skb->priority
+is greater or equal to 64, the priority is assumed to be 63.
+Priorities less than 64 are taken at face value.
+
+SKB Priority Queue enables routers to locally decide which
+packets to drop under a DoS attack.
+Priorities should be assigned to packets such that the higher the priority,
+the more expected behavior a source shows.
+So sources have an incentive to play by the rules.
+
+.SH ALGORITHM
+
+Skbprio maintains 64 lists (priorities go from 0 to 63).
+When a packet is enqueued, it gets inserted at the
+.B tail
+of its priority list. When a packet needs to be sent out to the network, it is
+taken from the head of the highest priority list. When the queue is full,
+the packet at the tail of the lowest priority list is dropped to serve the
+ingress packet - if it is of higher priority, otherwise the ingress packet is
+dropped. This algorithm allocates as much bandwidth as possible to high
+priority packets, while only servicing low priority packets when
+there is enough bandwidth.
+
+.SH PARAMETERS
+.TP
+limit
+Maximum queue size specified in packets. It defaults to 64.
+The range for this parameter is [0, UINT32_MAX].
+
+.SH SEE ALSO
+.BR tc-prio (8),
+.BR tc-skbedit (8)
+
+.SH AUTHORS
+Nishanth Devarajan <devarajn@uci.edu>, Michel Machado <michel@digirati.com.br>
+
+This manpage maintained by Bert Hubert <ahu@ds9a.nl>
diff --git a/man/man8/tc-stab.8 b/man/man8/tc-stab.8
new file mode 100644
index 0000000..03a0659
--- /dev/null
+++ b/man/man8/tc-stab.8
@@ -0,0 +1,163 @@
+.TH STAB 8 "31 October 2011" iproute2 Linux
+.
+.SH NAME
+tc\-stab \- Generic size table manipulations
+.
+.SH SYNOPSIS
+.nf
+tc qdisc add ... stab
+.RS 4
+[ \fBmtu\fR BYTES ] [ \fBtsize\fR SLOTS ]
+[ \fBmpu\fR BYTES ] [ \fBoverhead\fR BYTES ]
+[ \fBlinklayer\fR { adsl | atm | ethernet } ] ...
+.RE
+.fi
+
+.SH OPTIONS
+For the description of BYTES \- please refer to the \fBUNITS\fR
+section of \fBtc\fR(8).
+
+.IP \fBmtu\fR 4
+.br
+maximum packet size we create size table for, assumed 2048 if not specified explicitly
+.IP \fBtsize\fR
+.br
+required table size, assumed 512 if not specified explicitly
+.IP \fBmpu\fR
+.br
+minimum packet size used in computations
+.IP \fBoverhead\fR
+.br
+per\-packet size overhead (can be negative) used in computations
+.IP \fBlinklayer\fR
+.br
+required linklayer specification.
+.PP
+.
+.SH DESCRIPTION
+.
+Size tables allow manipulation of packet sizes, as seen by the whole scheduler
+framework (of course, the actual packet size remains the same). Adjusted packet
+size is calculated only once \- when a qdisc enqueues the packet. Initial root
+enqueue initializes it to the real packet's size.
+
+Each qdisc can use a different size table, but the adjusted size is stored in
+an area shared by whole qdisc hierarchy attached to the interface. The effect is
+that if you have such a setup, the last qdisc with a stab in a chain "wins". For
+example, consider HFSC with simple pfifo attached to one of its leaf classes.
+If that pfifo qdisc has stab defined, it will override lengths calculated
+during HFSC's enqueue; and in turn, whenever HFSC tries to dequeue a packet, it
+will use a potentially invalid size in its calculations. Normal setups will
+usually include stab defined only on root qdisc, but further overriding gives
+extra flexibility for less usual setups.
+
+The initial size table is calculated by \fBtc\fR tool using \fBmtu\fR and
+\fBtsize\fR parameters. The algorithm sets each slot's size to the smallest
+power of 2 value, so the whole \fBmtu\fR is covered by the size table. Neither
+\fBtsize\fR, nor \fBmtu\fR have to be power of 2 value, so the size
+table will usually support more than is required by \fBmtu\fR.
+
+For example, with \fBmtu\fR\~=\~1500 and \fBtsize\fR\~=\~128, a table with 128
+slots will be created, where slot 0 will correspond to sizes 0\-16, slot 1 to
+17\~\-\~32, \&..., slot 127 to 2033\~\-\~2048. Sizes assigned to each slot
+depend on \fBlinklayer\fR parameter.
+
+Stab calculation is also safe for an unusual case, when a size assigned to a
+slot would be larger than 2^16\-1 (you will lose the accuracy though).
+
+During the kernel part of packet size adjustment, \fBoverhead\fR will be added
+to original size, and then slot will be calculated. If the size would cause
+overflow, more than 1 slot will be used to get the final size. This of course
+will affect accuracy, but it's only a guard against unusual situations.
+
+Currently there are two methods of creating values stored in the size table \-
+ethernet and atm (adsl):
+
+.IP ethernet 4
+.br
+This is basically 1\-1 mapping, so following our example from above
+(disregarding \fBmpu\fR for a moment) slot 0 would have 8, slot 1 would have 16
+and so on, up to slot 127 with 2048. Note, that \fBmpu\fR\~>\~0 must be
+specified, and slots that would get less than specified by \fBmpu\fR will get
+\fBmpu\fR instead. If you don't specify \fBmpu\fR, the size table will not be
+created at all (it wouldn't make any difference), although any \fBoverhead\fR
+value will be respected during calculations.
+.IP "atm, adsl"
+.br
+ATM linklayer consists of 53 byte cells, where each of them provides 48 bytes
+for payload. Also all the cells must be fully utilized, thus the last one is
+padded if/as necessary.
+
+When the size table is calculated, adjusted size that fits properly into lowest
+amount of cells is assigned to a slot. For example, a 100 byte long packet
+requires three 48\-byte payloads, so the final size would require 3 ATM cells
+\- 159 bytes.
+
+For ATM size tables, 16\~bytes sized slots are perfectly enough. The default
+values of \fBmtu\fR and \fBtsize\fR create 4\~bytes sized slots.
+.PP
+.
+.SH "TYPICAL OVERHEADS"
+The following values are typical for different adsl scenarios (based on
+\fB[1]\fR and \fB[2]\fR):
+
+.nf
+LLC based:
+.RS 4
+PPPoA \- 14 (PPP \- 2, ATM \- 12)
+PPPoE \- 40+ (PPPoE \- 8, ATM \- 18, ethernet 14, possibly FCS \- 4+padding)
+Bridged \- 32 (ATM \- 18, ethernet 14, possibly FCS \- 4+padding)
+IPoA \- 16 (ATM \- 16)
+.RE
+
+VC Mux based:
+.RS 4
+PPPoA \- 10 (PPP \- 2, ATM \- 8)
+PPPoE \- 32+ (PPPoE \- 8, ATM \- 10, ethernet 14, possibly FCS \- 4+padding)
+Bridged \- 24+ (ATM \- 10, ethernet 14, possibly FCS \- 4+padding)
+IPoA \- 8 (ATM \- 8)
+.RE
+.fi
+There are a few important things regarding the above overheads:
+.
+.IP \(bu 4
+IPoA in LLC case requires SNAP, instead of LLC\-NLPID (see rfc2684) \- this is
+the reason why it actually takes more space than PPPoA.
+.IP \(bu
+In rare cases, FCS might be preserved on protocols that include Ethernet frames
+(Bridged and PPPoE). In such situation, any Ethernet specific padding
+guaranteeing 64 bytes long frame size has to be included as well (see RFC2684).
+In the other words, it also guarantees that any packet you send will take
+minimum 2 atm cells. You should set \fBmpu\fR accordingly for that.
+.IP \(bu
+When the size table is consulted, and you're shaping traffic for the sake of
+another modem/router, an Ethernet header (without padding) will already be added
+to initial packet's length. You should compensate for that by subtracting 14
+from the above overheads in this case. If you're shaping directly on the router
+(for example, with speedtouch usb modem) using ppp daemon, you're using raw ip
+interface without underlying layer2, so nothing will be added.
+
+For more thorough explanations, please see \fB[1]\fR and \fB[2]\fR.
+.
+.SH "ETHERNET CARDS CONSIDERATIONS"
+.
+It's often forgotten that modern network cards (even cheap ones on desktop
+motherboards) and/or their drivers often support different offloading
+mechanisms. In the context of traffic shaping, 'tso' and 'gso' might cause
+undesirable effects, due to massive TCP segments being considered during
+traffic shaping (including stab calculations). For slow uplink interfaces,
+it's good to use \fBethtool\fR to turn off offloading features.
+.
+.SH "SEE ALSO"
+.
+\fBtc\fR(8), \fBtc\-hfsc\fR(7), \fBtc\-hfsc\fR(8),
+.br
+\fB[1]\fR http://ace\-host.stuart.id.au/russell/files/tc/tc\-atm/
+.br
+\fB[2]\fR http://www.faqs.org/rfcs/rfc2684.html
+
+Please direct bugreports and patches to: <netdev@vger.kernel.org>
+.
+.SH "AUTHOR"
+.
+Manpage created by Michal Soltys (soltys@ziu.info)
diff --git a/man/man8/tc-taprio.8 b/man/man8/tc-taprio.8
new file mode 100644
index 0000000..e1d19ba
--- /dev/null
+++ b/man/man8/tc-taprio.8
@@ -0,0 +1,182 @@
+.TH TAPRIO 8 "25 Sept 2018" "iproute2" "Linux"
+.SH NAME
+TAPRIO \- Time Aware Priority Shaper
+.SH SYNOPSIS
+.B tc qdisc ... dev
+dev
+.B parent
+classid
+.B [ handle
+major:
+.B ] taprio num_tc
+tcs
+.ti +8
+.B map
+P0 P1 P2 ...
+.B queues
+count1@offset1 count2@offset2 ...
+.ti +8
+.B base-time
+base-time
+.B clockid
+clockid
+.ti +8
+.B sched-entry
+<command 1> <gate mask 1> <interval 1>
+.ti +8
+.B sched-entry
+<command 2> <gate mask 2> <interval 2>
+.ti +8
+.B sched-entry
+<command 3> <gate mask 3> <interval 3>
+.ti +8
+.B sched-entry
+<command N> <gate mask N> <interval N>
+
+.SH DESCRIPTION
+The TAPRIO qdisc implements a simplified version of the scheduling
+state machine defined by IEEE 802.1Q-2018 Section 8.6.9, which allows
+configuration of a sequence of gate states, where each gate state
+allows outgoing traffic for a subset (potentially empty) of traffic
+classes.
+
+How traffic is mapped to different hardware queues is similar to
+.BR mqprio(8)
+and so the
+.B map
+and
+.B queues
+parameters have the same meaning.
+
+The other parameters specify the schedule, and at what point in time
+it should start (it can behave as the schedule started in the past).
+
+.SH PARAMETERS
+.TP
+num_tc
+.BR
+Number of traffic classes to use. Up to 16 classes supported.
+
+.TP
+map
+.br
+The priority to traffic class map. Maps priorities 0..15 to a specified
+traffic class. See
+.BR mqprio(8)
+for more details.
+
+.TP
+queues
+.br
+Provide count and offset of queue range for each traffic class. In the
+format,
+.B count@offset.
+Queue ranges for each traffic classes cannot overlap and must be a
+contiguous range of queues.
+
+.TP
+base-time
+.br
+Specifies the instant in nanoseconds, using the reference of
+.B clockid,
+defining the time when the schedule starts. If 'base-time' is a time
+in the past, the schedule will start at
+
+base-time + (N * cycle-time)
+
+where N is the smallest integer so the resulting time is greater than
+"now", and "cycle-time" is the sum of all the intervals of the entries
+in the schedule;
+
+.TP
+clockid
+.br
+Specifies the clock to be used by qdisc's internal timer for measuring
+time and scheduling events.
+
+.TP
+sched-entry
+.br
+There may multiple
+.B sched-entry
+parameters in a single schedule. Each one has the
+
+sched-entry <command> <gatemask> <interval>
+
+format. The only supported <command> is "S", which
+means "SetGateStates", following the IEEE 802.1Q-2018 definition
+(Table 8-7). <gate mask> is a bitmask where each bit is a associated
+with a traffic class, so bit 0 (the least significant bit) being "on"
+means that traffic class 0 is "active" for that schedule entry.
+<interval> is a time duration, in nanoseconds, that specifies for how
+long that state defined by <command> and <gate mask> should be held
+before moving to the next entry.
+
+.TP
+flags
+.br
+Specifies different modes for taprio. Currently, only txtime-assist is
+supported which can be enabled by setting it to 0x1. In this mode, taprio will
+set the transmit timestamp depending on the interval in which the packet needs
+to be transmitted. It will then utililize the
+.BR etf(8)
+qdisc to sort and transmit the packets at the right time. The second example
+can be used as a reference to configure this mode.
+
+.TP
+txtime-delay
+.br
+This parameter is specific to the txtime offload mode. It specifies the maximum
+time a packet might take to reach the network card from the taprio qdisc. The
+value should always be greater than the delta specified in the
+.BR etf(8)
+qdisc.
+
+.SH EXAMPLES
+
+The following example shows how an traffic schedule with three traffic
+classes ("num_tc 3"), which are separated different traffic classes,
+we are going to call these TC 0, TC 1 and TC 2. We could read the
+"map" parameter below as: traffic with priority 3 is classified as TC
+0, priority 2 is classified as TC 1 and the rest is classified as TC
+2.
+
+The schedule will start at instant 1528743495910289987 using the
+reference CLOCK_TAI. The schedule is composed of three entries each of
+300us duration.
+
+.EX
+# tc qdisc replace dev eth0 parent root handle 100 taprio \\
+ num_tc 3 \\
+ map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 \\
+ queues 1@0 1@1 2@2 \\
+ base-time 1528743495910289987 \\
+ sched-entry S 01 300000 \\
+ sched-entry S 02 300000 \\
+ sched-entry S 04 300000 \\
+ clockid CLOCK_TAI
+.EE
+
+Following is an example to enable the txtime offload mode in taprio. See
+.BR etf(8)
+for more information about configuring the ETF qdisc.
+
+.EX
+# tc qdisc replace dev eth0 parent root handle 100 taprio \\
+ num_tc 3 \\
+ map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 \\
+ queues 1@0 1@0 1@0 \\
+ base-time 1528743495910289987 \\
+ sched-entry S 01 300000 \\
+ sched-entry S 02 300000 \\
+ sched-entry S 04 400000 \\
+ flags 0x1 \\
+ txtime-delay 200000 \\
+ clockid CLOCK_TAI
+
+# tc qdisc replace dev $IFACE parent 100:1 etf skip_skb_check \\
+ offload delta 200000 clockid CLOCK_TAI
+.EE
+
+.SH AUTHORS
+Vinicius Costa Gomes <vinicius.gomes@intel.com>
diff --git a/man/man8/tc-tbf.8 b/man/man8/tc-tbf.8
new file mode 100644
index 0000000..d721b5d
--- /dev/null
+++ b/man/man8/tc-tbf.8
@@ -0,0 +1,141 @@
+.TH TC 8 "13 December 2001" "iproute2" "Linux"
+.SH NAME
+tbf \- Token Bucket Filter
+.SH SYNOPSIS
+.B tc qdisc ... tbf rate
+rate
+.B burst
+bytes/cell
+.B ( latency
+ms
+.B | limit
+bytes
+.B ) [ mpu
+bytes
+.B [ peakrate
+rate
+.B mtu
+bytes/cell
+.B ] ]
+.P
+burst is also known as buffer and maxburst. mtu is also known as minburst.
+.SH DESCRIPTION
+
+The Token Bucket Filter is a classful queueing discipline available for
+traffic control with the
+.BR tc (8)
+command.
+
+TBF is a pure shaper and never schedules traffic. It is non-work-conserving and may throttle
+itself, although packets are available, to ensure that the configured rate is not exceeded.
+It is able to shape up to 1mbit/s of normal traffic with ideal minimal burstiness,
+sending out data exactly at the configured rates.
+
+Much higher rates are possible but at the cost of losing the minimal burstiness. In that
+case, data is on average dequeued at the configured rate but may be sent much faster at millisecond
+timescales. Because of further queues living in network adaptors, this is often not a problem.
+
+.SH ALGORITHM
+As the name implies, traffic is filtered based on the expenditure of
+.B tokens.
+Tokens roughly correspond to bytes, with the additional constraint
+that each packet consumes some tokens, no matter how small it is. This
+reflects the fact that even a zero-sized packet occupies the link for
+some time.
+
+On creation, the TBF is stocked with tokens which correspond to the amount of traffic that can be burst
+in one go. Tokens arrive at a steady rate, until the bucket is full.
+
+If no tokens are available, packets are queued, up to a configured limit. The TBF now
+calculates the token deficit, and throttles until the first packet in the queue can be sent.
+
+If it is not acceptable to burst out packets at maximum speed, a peakrate can be configured
+to limit the speed at which the bucket empties. This peakrate is implemented as a second TBF
+with a very small bucket, so that it doesn't burst.
+
+To achieve perfection, the second bucket may contain only a single packet, which leads to
+the earlier mentioned 1mbit/s limit.
+
+This limit is caused by the fact that the kernel can only throttle for at minimum 1 'jiffy', which depends
+on HZ as 1/HZ. For perfect shaping, only a single packet can get sent per jiffy - for HZ=100, this means 100
+packets of on average 1000 bytes each, which roughly corresponds to 1mbit/s.
+
+.SH PARAMETERS
+See
+.BR tc (8)
+for how to specify the units of these values.
+.TP
+limit or latency
+Limit is the number of bytes that can be queued waiting for tokens to become
+available. You can also specify this the other way around by setting the
+latency parameter, which specifies the maximum amount of time a packet can
+sit in the TBF. The latter calculation takes into account the size of the
+bucket, the rate and possibly the peakrate (if set). These two parameters
+are mutually exclusive.
+.TP
+burst
+Also known as buffer or maxburst.
+Size of the bucket, in bytes. This is the maximum amount of bytes that tokens can be available for instantaneously.
+In general, larger shaping rates require a larger buffer. For 10mbit/s on Intel, you need at least 10kbyte buffer
+if you want to reach your configured rate!
+
+If your buffer is too small, packets may be dropped because more tokens arrive per timer tick than fit in your bucket.
+The minimum buffer size can be calculated by dividing the rate by HZ.
+
+Token usage calculations are performed using a table which by default has a resolution of 8 packets.
+This resolution can be changed by specifying the
+.B cell
+size with the burst. For example, to specify a 6000 byte buffer with a 16
+byte cell size, set a burst of 6000/16. You will probably never have to set
+this. Must be an integral power of 2.
+.TP
+mpu
+A zero-sized packet does not use zero bandwidth. For ethernet, no packet uses less than 64 bytes. The Minimum Packet Unit
+determines the minimal token usage (specified in bytes) for a packet. Defaults to zero.
+.TP
+rate
+The speed knob. See remarks above about limits! See
+.BR tc (8)
+for units.
+.PP
+Furthermore, if a peakrate is desired, the following parameters are available:
+
+.TP
+peakrate
+Maximum depletion rate of the bucket. The peakrate does not
+need to be set, it is only necessary if perfect millisecond timescale
+shaping is required.
+
+.TP
+mtu/minburst
+Specifies the size of the peakrate bucket. For perfect accuracy, should be set to the MTU of the interface.
+If a peakrate is needed, but some burstiness is acceptable, this size can be raised. A 3000 byte minburst
+allows around 3mbit/s of peakrate, given 1000 byte packets.
+
+Like the regular burstsize you can also specify a
+.B cell
+size.
+.SH EXAMPLE & USAGE
+
+To attach a TBF with a sustained maximum rate of 0.5mbit/s, a peakrate of 1.0mbit/s,
+a 5kilobyte buffer, with a pre-bucket queue size limit calculated so the TBF causes
+at most 70ms of latency, with perfect peakrate behaviour, issue:
+.P
+# tc qdisc add dev eth0 handle 10: root tbf rate 0.5mbit \\
+ burst 5kb latency 70ms peakrate 1mbit \\
+ minburst 1540
+.P
+To attach an inner qdisc, for example sfq, issue:
+.P
+# tc qdisc add dev eth0 parent 10:1 handle 100: sfq
+.P
+Without inner qdisc TBF queue acts as bfifo. If the inner qdisc is changed
+the limit/latency is not effective anymore.
+.P
+
+.SH SEE ALSO
+.BR tc (8)
+
+.SH AUTHOR
+Alexey N. Kuznetsov, <kuznet@ms2.inr.ac.ru>. This manpage maintained by
+bert hubert <ahu@ds9a.nl>
diff --git a/man/man8/tc-tcindex.8 b/man/man8/tc-tcindex.8
new file mode 100644
index 0000000..9a4e5ff
--- /dev/null
+++ b/man/man8/tc-tcindex.8
@@ -0,0 +1,58 @@
+.TH "Traffic control index filter" 8 "21 Oct 2015" "iproute2" "Linux"
+
+.SH NAME
+tcindex \- traffic control index filter
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " " filter " ... " tcindex " [ " hash
+.IR SIZE " ] [ "
+.B mask
+.IR MASK " ] [ "
+.B shift
+.IR SHIFT " ] [ "
+.BR pass_on " | " fall_through " ] [ " classid
+.IR CLASSID " ] [ "
+.B action
+.BR ACTION_SPEC " ]"
+.SH DESCRIPTION
+This filter allows to match packets based on their
+.B tcindex
+field value, i.e. the combination of the DSCP and ECN fields as present in IPv4
+and IPv6 headers.
+.SH OPTIONS
+.TP
+.BI action " ACTION_SPEC"
+Apply an action from the generic actions framework on matching packets.
+.TP
+.BI classid " CLASSID"
+Push matching packets into the class identified by
+.IR CLASSID .
+.TP
+.BI hash " SIZE"
+Hash table size in entries to use. Defaults to 64.
+.TP
+.BI mask " MASK"
+An optional bitmask to binary
+.BR AND " to the packet's " tcindex
+field before use.
+.TP
+.BI shift " SHIFT"
+The number of bits to right-shift a packet's
+.B tcindex
+value before use. If a
+.B mask
+has been set, masking is done before shifting.
+.TP
+.B pass_on
+If this flag is set, failure to find a class for the resulting ID will make the
+filter fail and lead to the next filter being consulted.
+.TP
+.B fall_through
+This is the opposite of
+.B pass_on
+and the default. The filter will classify the packet even if there is no class
+present for the resulting class ID.
+
+.SH SEE ALSO
+.BR tc (8)
diff --git a/man/man8/tc-tunnel_key.8 b/man/man8/tc-tunnel_key.8
new file mode 100644
index 0000000..2145eb6
--- /dev/null
+++ b/man/man8/tc-tunnel_key.8
@@ -0,0 +1,154 @@
+.TH "Tunnel metadata manipulation action in tc" 8 "10 Nov 2016" "iproute2" "Linux"
+
+.SH NAME
+tunnel_key - Tunnel metadata manipulation
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action tunnel_key" " { " unset " | "
+.IR SET " }"
+
+.ti -8
+.IR SET " := "
+.BR set " " src_ip
+.IR ADDRESS
+.BR dst_ip
+.IR ADDRESS
+.BI id " KEY_ID"
+.BI dst_port " UDP_PORT"
+.BI tos " TOS"
+.BI ttl " TTL"
+.RB "[ " csum " | " nocsum " ]"
+
+.SH DESCRIPTION
+The
+.B tunnel_key
+action combined with a shared IP tunnel device, allows to perform IP tunnel en-
+or decapsulation on a packet, reflected by
+the operation modes
+.IR UNSET " and " SET .
+The
+.I UNSET
+mode is optional - even without using it, the metadata information will be
+released automatically when packet processing will be finished.
+.IR UNSET
+function could be used in cases when traffic is forwarded between two tunnels,
+where the metadata from the first tunnel will be used for encapsulation done by
+the second tunnel.
+.IR SET
+mode requires the source and destination ip
+.I ADDRESS
+and the tunnel key id
+.I KEY_ID
+which will be used by the ip tunnel shared device to create the tunnel header. The
+.B tunnel_key
+action is useful only in combination with a
+.B mirred redirect
+action to a shared IP tunnel device which will use the metadata (for
+.I SET
+) and unset the metadata created by it (for
+.I UNSET
+).
+
+.SH OPTIONS
+.TP
+.B unset
+Unset the tunnel metadata created by the IP tunnel device. This function is
+not mandatory and might be used only in some specific use cases (as explained
+above).
+.TP
+.B set
+Set tunnel metadata to be used by the IP tunnel device. Requires
+.B src_ip
+and
+.B dst_ip
+options.
+.B id
+,
+.B dst_port
+and
+.B geneve_opts
+are optional.
+.RS
+.TP
+.B id
+Tunnel ID (for example VNI in VXLAN tunnel)
+.TP
+.B src_ip
+Outer header source IP address (IPv4 or IPv6)
+.TP
+.B dst_ip
+Outer header destination IP address (IPv4 or IPv6)
+.TP
+.B dst_port
+Outer header destination UDP port
+.TP
+.B geneve_opts
+Geneve variable length options.
+.B geneve_opts
+is specified in the form CLASS:TYPE:DATA, where CLASS is represented as a
+16bit hexadecimal value, TYPE as an 8bit hexadecimal value and DATA as a
+variable length hexadecimal value. Additionally multiple options may be
+listed using a comma delimiter.
+.TP
+.B tos
+Outer header TOS
+.TP
+.B ttl
+Outer header TTL
+.TP
+.RB [ no ] csum
+Controls outer UDP checksum. When set to
+.B csum
+(which is default), the outer UDP checksum is calculated and included in the
+packets. When set to
+.BR nocsum ,
+outer UDP checksum is zero. Note that when using zero UDP checksums with
+IPv6, the other tunnel endpoint must be configured to accept such packets.
+In Linux, this would be the
+.B udp6zerocsumrx
+option for the VXLAN tunnel interface.
+.IP
+If using
+.B nocsum
+with IPv6, be sure you know what you are doing. Zero UDP checksums provide
+weaker protection against corrupted packets. See RFC6935 for details.
+.RE
+.SH EXAMPLES
+The following example encapsulates incoming ICMP packets on eth0 into a vxlan
+tunnel, by setting metadata to VNI 11, source IP 11.11.0.1 and destination IP
+11.11.0.2, and by redirecting the packet with the metadata to device vxlan0,
+which will do the actual encapsulation using the metadata:
+
+.RS
+.EX
+#tc qdisc add dev eth0 handle ffff: ingress
+#tc filter add dev eth0 protocol ip parent ffff: \\
+ flower \\
+ ip_proto icmp \\
+ action tunnel_key set \\
+ src_ip 11.11.0.1 \\
+ dst_ip 11.11.0.2 \\
+ id 11 \\
+ action mirred egress redirect dev vxlan0
+.EE
+.RE
+
+Here is an example of the
+.B unset
+function: Incoming VXLAN traffic with outer IP's and VNI 11 is decapsulated by
+vxlan0 and metadata is unset before redirecting to tunl1 device:
+
+.RS
+.EX
+#tc qdisc add dev eth0 handle ffff: ingress
+#tc filter add dev vxlan0 protocol ip parent ffff: \
+ flower \\
+ enc_src_ip 11.11.0.2 enc_dst_ip 11.11.0.1 enc_key_id 11 \
+ action tunnel_key unset \
+ action mirred egress redirect dev tunl1
+.EE
+.RE
+
+.SH SEE ALSO
+.BR tc (8)
diff --git a/man/man8/tc-u32.8 b/man/man8/tc-u32.8
new file mode 100644
index 0000000..2bf2e3e
--- /dev/null
+++ b/man/man8/tc-u32.8
@@ -0,0 +1,673 @@
+.TH "Universal 32bit classifier in tc" 8 "25 Sep 2015" "iproute2" "Linux"
+
+.SH NAME
+u32 \- universal 32bit traffic control filter
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " " filter " ... [ " handle
+.IR HANDLE " ] "
+.B u32
+.IR OPTION_LIST " [ "
+.B offset
+.IR OFFSET " ] [ "
+.B hashkey
+.IR HASHKEY " ] [ "
+.B classid
+.IR CLASSID " ] [ "
+.B divisor
+.IR uint_value " ] [ "
+.B order
+.IR u32_value " ] [ "
+.B ht
+.IR HANDLE " ] [ "
+.B sample
+.IR SELECTOR " [ "
+.B divisor
+.IR uint_value " ] ] [ "
+.B link
+.IR HANDLE " ] [ "
+.B indev
+.IR ifname " ] [ "
+.BR skip_hw " | "
+.BR skip_sw " ] [ "
+.BR help " ]"
+
+.ti -8
+.IR HANDLE " := { "
+\fIu12_hex_htid\fB:\fR[\fIu8_hex_hash\fB:\fR[\fIu12_hex_nodeid\fR] | \fB0x\fIu32_hex_value\fR }
+
+.ti -8
+.IR OPTION_LIST " := [ " OPTION_LIST " ] " OPTION
+
+.ti -8
+.IR HASHKEY " := [ "
+.B mask
+.IR u32_hex_value " ] [ "
+.B at
+.IR 4*int_value " ]"
+
+.ti -8
+.IR CLASSID " := { "
+.BR root " | "
+.BR none " | "
+[\fIu16_major\fR]\fB:\fIu16_minor\fR | \fIu32_hex_value\fR }
+
+.ti -8
+.IR OFFSET " := [ "
+.B plus
+.IR int_value " ] [ "
+.B at
+.IR 2*int_value " ] [ "
+.B mask
+.IR u16_hex_value " ] [ "
+.B shift
+.IR int_value " ] [ "
+.BR eat " ]"
+
+.ti -8
+.IR OPTION " := { "
+.B match
+.IR SELECTOR " | "
+.B action
+.IR ACTION " } "
+
+.ti -8
+.IR SELECTOR " := { "
+.B u32
+.IR VAL_MASK_32 " | "
+.B u16
+.IR VAL_MASK_16 " | "
+.B u8
+.IR VAL_MASK_8 " | "
+.B ip
+.IR IP " | "
+.B ip6
+.IR IP6 " | { "
+.BR tcp " | " udp " } "
+.IR TCPUDP " | "
+.B icmp
+.IR ICMP " | "
+.B mark
+.IR VAL_MASK_32 " | "
+.B ether
+.IR ETHER " }"
+
+.ti -8
+.IR IP " := { { "
+.BR src " | " dst " } { " default " | " any " | " all " | "
+.IR ip_address " [ "
+.BR / " { "
+.IR prefixlen " | " netmask " } ] } " AT " | { "
+.BR dsfield " | " ihl " | " protocol " | " precedence " | "
+.BR icmp_type " | " icmp_code " } "
+.IR VAL_MASK_8 " | { "
+.BR sport " | " dport " } "
+.IR VAL_MASK_16 " | "
+.BR nofrag " | " firstfrag " | " df " | " mf " }"
+
+.ti -8
+.IR IP6 " := { { "
+.BR src " | " dst " } { " default " | " any " | " all " | "
+.IR ip6_address " [/" prefixlen " ] } " AT " | "
+.B priority
+.IR VAL_MASK_8 " | { "
+.BR protocol " | " icmp_type " | " icmp_code " } "
+.IR VAL_MASK_8 " | "
+.B flowlabel
+.IR VAL_MASK_32 " | { "
+.BR sport " | " dport " } "
+.IR VAL_MASK_16 " }"
+
+.ti -8
+.IR TCPUDP " := { "
+.BR src " | " dst " } "
+.I VAL_MASK_16
+
+.ti -8
+.IR ICMP " := { "
+.B type
+.IR VAL_MASK_8 " | "
+.B code
+.IR VAL_MASK_8 " }"
+
+.ti -8
+.IR ETHER " := { "
+.BR src " | " dst " } "
+.IR ether_address " " AT
+
+.ti -8
+.IR VAL_MASK_32 " := " u32_value " " u32_hex_mask " [ " AT " ]"
+
+.ti -8
+.IR VAL_MASK_16 " := " u16_value " " u16_hex_mask " [ " AT " ]"
+
+.ti -8
+.IR VAL_MASK_8 " := " u8_value " " u8_hex_mask " [ " AT " ]"
+
+.ti -8
+.IR AT " := [ "
+.BR at " [ " nexthdr+ " ] "
+.IR int_value " ]"
+.SH DESCRIPTION
+The Universal/Ugly 32bit filter allows to match arbitrary bitfields in the
+packet. Due to breaking everything down to values, masks and offsets, It is
+equally powerful and hard to use. Luckily many abstracting directives are
+present which allow defining rules on a higher level and therefore free the
+user from having to fiddle with bits and masks in many cases.
+
+There are two general modes of invocation: The first mode creates a new filter
+to delegate packets to different destinations. Apart from the obvious ones,
+namely classifying the packet by specifying a
+.I CLASSID
+or calling an
+.BR action ,
+one may
+.B link
+one filter to another one (or even a list of them), effectively organizing
+filters into a tree-like hierarchy.
+
+Typically filter delegation is done by means of a hash table, which leads to the
+second mode of invocation: it merely serves to set up these hash tables. Filters
+can select a hash table and provide a key selector from which a hash is to be
+computed and used as key to lookup the table's bucket which contains filters for
+further processing. This is useful if a high number of filters is in use, as the
+overhead of performing the hash operation and table lookup becomes negligible in
+that case. Using hashtables with
+.B u32
+basically involves the following pattern:
+.IP (1) 4
+Creating a new hash table, specifying it's size using the
+.B divisor
+parameter and ideally a handle by which the table can be identified. If the
+latter is not given, the kernel chooses one on it's own, which has to be
+guessed later.
+.IP (2) 4
+Creating filters which link to the created table in
+.I (1)
+using the
+.B link
+parameter and defining the packet data which the kernel will use to calculate
+the
+.BR hashkey .
+.IP (3) 4
+Adding filters to buckets in the hash table from
+.IR (1) .
+In order to avoid having to know how exactly the kernel creates the hash key,
+there is the
+.B sample
+parameter, which gives sample data to hash and thereby define the table bucket
+the filter should be added to.
+
+.RE
+In fact, even if not explicitly requested
+.B u32
+creates a hash table for every
+.B priority
+a filter is being added with. The table's size is 1 though, so it is in fact
+merely a linked list.
+.SH VALUES
+Options and selectors require values to be specified in a specific format, which
+is often non-intuitive. Therefore the terminals in
+.I SYNOPSIS
+have been given descriptive names to indicate the required format and/or maximum
+allowed numeric value: Prefixes
+.IR u32 ", " u16 " and " u8
+indicate four, two and single byte unsigned values. E.g.
+.I u16
+indicates a two byte-sized value in range between 0 and 65535 (0xFFFF)
+inclusive. A prefix of
+.I int
+indicates a four byte signed value. A middle part of
+.I _hex_
+indicates that the value is parsed in hexadecimal format. Otherwise, the
+value's base is automatically detected, i.e. values prefixed with
+.I 0x
+are considered hexadecimal, a leading
+.I 0
+indicates octal format and decimal format otherwise. There are some values with
+special formatting as well:
+.IR ip_address " and " netmask
+are in dotted-quad formatting as usual for IPv4 addresses. An
+.I ip6_address
+is specified in common, colon-separated hexadecimal format. Finally,
+.I prefixlen
+is an unsigned, decimal integer value in range from 0 to the address width in
+bits (32 for IPv4 and 128 for IPv6).
+
+Sometimes values need to be dividable by a certain number. In that case a name
+of the form
+.I N*val
+was chosen, indicating that
+.I val
+must be dividable by
+.IR N .
+Or the other way around: the resulting value must be a multiple of
+.IR N .
+.SH OPTIONS
+.B U32
+recognizes the following options:
+.TP
+.BI handle " HANDLE"
+The handle is used to reference a filter and therefore must be unique. It
+consists of a hash table identifier
+.B htid
+and optional
+.B hash
+(which identifies the hash table's bucket) and
+.BR nodeid .
+All these values are parsed as unsigned, hexadecimal numbers with length 12bits
+(
+.BR htid " and " nodeid )
+or 8bits (
+.BR hash ).
+Alternatively one may specify a single, 32bit long hex number which contains
+the three fields bits in concatenated form. Other than the fields themselves, it
+has to be prefixed by
+.BR 0x .
+.TP
+.BI offset " OFFSET"
+Set an offset which defines where matches of subsequent filters are applied to.
+Therefore this option is useful only when combined with
+.BR link " or a combination of " ht " and " sample .
+The offset may be given explicitly by using the
+.B plus
+keyword, or extracted from the packet data with
+.BR at .
+It is possible to mangle the latter using
+.BR mask " and/or " shift
+keywords. By default, this offset is recorded but not implicitly applied. It is
+used only to substitute the
+.B nexthdr+
+statement. Using the keyword
+.B eat
+though inverses this behaviour: the offset is applied always, and
+.B nexthdr+
+will fall back to zero.
+.TP
+.BI hashkey " HASHKEY"
+Spefify what packet data to use to calculate a hash key for bucket lookup. The
+kernel adjusts the value according to the hash table's size. For this to work,
+the option
+.B link
+must be given.
+.TP
+.BI classid " CLASSID"
+Classify matching packets into the given
+.IR CLASSID ,
+which consists of either 16bit
+.BR major " and " minor
+numbers or a single 32bit value combining both.
+.TP
+.BI divisor " u32_value"
+Specify a modulo value. Used when creating hash tables to define their size or
+for declaring a
+.B sample
+to calculate hash table keys from. Must be a power of two with exponent not
+exceeding eight.
+.TP
+.BI order " u32_value"
+A value to order filters by, ascending. Conflicts with
+.B handle
+which serves the same purpose.
+.TP
+.BI sample " SELECTOR"
+Used together with
+.B ht
+to specify which bucket to add this filter to. This allows one to avoid having
+to know how exactly the kernel calculates hashes. The additional
+.B divisor
+defaults to 256, so must be given for hash tables of different size.
+.TP
+.BI link " HANDLE"
+Delegate matching packets to filters in a hash table.
+.I HANDLE
+is used to only specify the hash table, so only
+.BR htid " may be given, " hash " and " nodeid
+have to be omitted. By default, bucket number 0 will be used and can be
+overridden by the
+.B hashkey
+option.
+.TP
+.BI indev " ifname"
+Filter on the incoming interface of the packet. Obviously works only for
+forwarded traffic.
+.TP
+.BI skip_sw
+Do not process filter by software. If hardware has no offload support for this
+filter, or TC offload is not enabled for the interface, operation will fail.
+.TP
+.BI skip_hw
+Do not process filter by hardware.
+.TP
+.BI help
+Print a brief help text about possible options.
+.SH SELECTORS
+Basically the only real selector is
+.B u32 .
+All others merely provide a higher level syntax and are internally translated
+into
+.B u32 .
+.TP
+.BI u32 " VAL_MASK_32"
+.TQ
+.BI u16 " VAL_MASK_16"
+.TQ
+.BI u8 " VAL_MASK_8"
+Match packet data to a given value. The selector name defines the sample length
+to extract (32bits for
+.BR u32 ,
+16bits for
+.B u16
+and 8bits for
+.BR u8 ).
+Before comparing, the sample is binary AND'ed with the given mask. This way
+uninteresting bits can be cleared before comparison. The position of the sample
+is defined by the offset specified in
+.IR AT .
+.TP
+.BI ip " IP"
+.TQ
+.BI ip6 " IP6"
+Assume packet starts with an IPv4 (
+.BR ip )
+or IPv6 (
+.BR ip6 )
+header.
+.IR IP / IP6
+then allows to match various header fields:
+.RS
+.TP
+.BI src " ADDR"
+.TQ
+.BI dst " ADDR"
+Compare Source or Destination Address fields against the value of
+.IR ADDR .
+The reserved words
+.BR default ", " any " and " all
+effectively match any address. Otherwise an IP address of the particular
+protocol is expected, optionally suffixed by a prefix length to match whole
+subnets. In case of IPv4 a netmask may also be given.
+.TP
+.BI dsfield " VAL_MASK_8"
+IPv4 only. Match the packet header's DSCP/ECN field. Synonyms to this are
+.BR tos " and " precedence .
+.TP
+.BI ihl " VAL_MASK_8"
+IPv4 only. Match the Internet Header Length field. Note that the value's unit is
+32bits, so to match a packet with 24byte header length
+.I u8_value
+has to be 6.
+.TP
+.BI protocol " VAL_MASK_8"
+Match the Protocol (IPv4) or Next Header (IPv6) field value, e.g. 6 for TCP.
+.TP
+.BI icmp_type " VAL_MASK_8"
+.TQ
+.BI icmp_code " VAL_MASK_8"
+Assume a next-header protocol of icmp or ipv6-icmp and match Type or Code
+field values. This is dangerous, as the code assumes minimal header size for
+IPv4 and lack of extension headers for IPv6.
+.TP
+.BI sport " VAL_MASK_16"
+.TQ
+.BI dport " VAL_MASK_16"
+Match layer four source or destination ports. This is dangerous as well, as it
+assumes a suitable layer four protocol is present (which has Source and
+Destination Port fields right at the start of the header and 16bit in size).
+Also minimal header size for IPv4 and lack of IPv6 extension headers is assumed.
+.TP
+.B nofrag
+.TQ
+.B firstfrag
+.TQ
+.B df
+.TQ
+.B mf
+IPv4 only, check certain flags and fragment offset values. Match if the packet
+is not a fragment
+.RB ( nofrag ),
+the first fragment
+.RB ( firstfrag ),
+if Don't Fragment
+.RB ( df )
+or More Fragments
+.RB ( mf )
+bits are set.
+.TP
+.BI priority " VAL_MASK_8"
+IPv6 only. Match the header's Traffic Class field, which has the same purpose
+and semantics of IPv4's ToS field since RFC 3168: upper six bits are DSCP, the
+lower two ECN.
+.TP
+.BI flowlabel " VAL_MASK_32"
+IPv6 only. Match the Flow Label field's value. Note that Flow Label itself is
+only 20bytes long, which are the least significant ones here. The remaining
+upper 12bytes match Version and Traffic Class fields.
+.RE
+.TP
+.BI tcp " TCPUDP"
+.TQ
+.BI udp " TCPUDP"
+Match fields of next header of protocol TCP or UDP. The possible values for
+.I TCPDUP
+are:
+.RS
+.TP
+.BI src " VAL_MASK_16"
+Match on Source Port field value.
+.TP
+.BI dst " VALMASK_16"
+Match on Destination Port field value.
+.RE
+.TP
+.BI icmp " ICMP"
+Match fields of next header of protocol ICMP. The possible values for
+.I ICMP
+are:
+.RS
+.TP
+.BI type " VAL_MASK_8"
+Match on ICMP Type field.
+.TP
+.BI code " VAL_MASK_8"
+Match on ICMP Code field.
+.RE
+.TP
+.BI mark " VAL_MASK_32"
+Match on netfilter fwmark value.
+.TP
+.BI ether " ETHER"
+Match on ethernet header fields. Possible values for
+.I ETHER
+are:
+.RS
+.TP
+.BI src " ether_address" " " AT
+.TQ
+.BI dst " ether_address" " " AT
+Match on source or destination ethernet address. This is dangerous: It assumes
+an ethernet header is present at the start of the packet. This will probably
+lead to unexpected things if used with layer three interfaces like e.g. tun or
+ppp.
+.SH EXAMPLES
+.RS
+.EX
+tc filter add dev eth0 parent 999:0 prio 99 protocol ip u32 \\
+ match ip src 192.168.8.0/24 classid 1:1
+.EE
+.RE
+
+This attaches a filter to the qdisc identified by
+.BR 999:0.
+It's priority is
+.BR 99 ,
+which affects in which order multiple filters attached to the same
+.B parent
+are consulted (the lower the earlier). The filter handles packets of
+.B protocol
+type
+.BR ip ,
+and
+.BR match es
+if the IP header's source address is within the
+.B 192.168.8.0/24
+subnet. Matching packets are classified into class
+.BR 1.1 .
+The effect of this command might be surprising at first glance:
+
+.RS
+.EX
+filter parent 1: protocol ip pref 99 u32
+filter parent 1: protocol ip pref 99 u32 \\
+ fh 800: ht divisor 1
+filter parent 1: protocol ip pref 99 u32 \\
+ fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 \\
+ match c0a80800/ffffff00 at 12
+.EE
+.RE
+
+So parent
+.B 1:
+is assigned a new
+.B u32
+filter, which contains a hash table of size 1 (as the
+.B divisor
+indicates). The table ID is
+.BR 800 .
+The third line then shows the actual filter which was added above: it sits in
+table
+.B 800
+and bucket
+.BR 0 ,
+classifies packets into class ID
+.B 1:1
+and matches the upper three bytes of the four byte value at offset
+.B 12
+to be
+.BR 0xc0a808 ,
+which is 192, 168 and 8.
+
+Now for something more complicated, namely creating a custom hash table:
+
+.RS
+.EX
+tc filter add dev eth0 prio 99 handle 1: u32 divisor 256
+.EE
+.RE
+
+This creates a table of size 256 with handle
+.B 1:
+in priority
+.BR 99 .
+The effect is as follows:
+
+.RS
+.EX
+filter parent 1: protocol all pref 99 u32
+filter parent 1: protocol all pref 99 u32 fh 1: ht divisor 256
+filter parent 1: protocol all pref 99 u32 fh 800: ht divisor 1
+.EE
+.RE
+
+So along with the requested hash table (handle
+.BR 1: ),
+the kernel has created his own table of size 1 to hold other filters of the same
+priority.
+
+The next step is to create a filter which links to the created hash table:
+
+.RS
+.EX
+tc filter add dev eth0 parent 1: prio 1 u32 \\
+ link 1: hashkey mask 0x0000ff00 at 12 \\
+ match ip src 192.168.0.0/16
+.EE
+.RE
+
+The filter is given a lower priority than the hash table itself so
+.B u32
+consults it before manually traversing the hash table. The options
+.BR link " and " hashkey
+determine which table and bucket to redirect to. In this case the hash key
+should be constructed out of the second byte at offset 12, which corresponds to
+an IP packet's third byte of the source address field. Along with the
+.B match
+statement, this effectively maps all class C networks below 192.168.0.0/16 to
+different buckets of the hash table.
+
+Filters for certain subnets can be created like so:
+
+.RS
+.EX
+tc filter add dev eth0 parent 1: prio 99 u32 \\
+ ht 1: sample u32 0x00000800 0x0000ff00 at 12 \\
+ match ip src 192.168.8.0/24 classid 1:1
+.EE
+.RE
+
+The bucket is defined using the
+.B sample
+option: In this case, the second byte at offset 12 must be 0x08, exactly. In
+this case, the resulting bucket ID is obviously 8, but as soon as
+.B sample
+selects an amount of data which could exceed the
+.BR divisor ,
+one would have to know the kernel-internal algorithm to deduce the destination
+bucket. This filter's
+.B match
+statement is redundant in this case, as the entropy for the hash key does not
+exceed the table size and therefore no collisions can occur. Otherwise it's
+necessary to prevent matching unwanted packets.
+
+Matching upper layer fields is problematic since IPv4 header length is variable
+and IPv6 supports extension headers which affect upper layer header offset. To
+overcome this, there is the possibility to specify
+.B nexthdr+
+when giving an offset, and to make things easier there are the
+.BR tcp " and " udp
+matches which use
+.B nexthdr+
+implicitly. This offset has to be calculated in beforehand though, and the only
+way to achieve that is by doing it in a separate filter which then links to the
+filter which wants to use it. Here is an example of doing so:
+
+.RS
+.EX
+tc filter add dev eth0 parent 1:0 protocol ip handle 1: \\
+ u32 divisor 1
+tc filter add dev eth0 parent 1:0 protocol ip \\
+ u32 ht 1: \\
+ match tcp src 22 FFFF \\
+ classid 1:2
+tc filter add dev eth0 parent 1:0 protocol ip \\
+ u32 ht 800: \\
+ match ip protocol 6 FF \\
+ match ip firstfrag \\
+ offset at 0 mask 0f00 shift 6 \\
+ link 1:
+.EE
+.RE
+
+This is what is being done: In the first call, a single element sized hash table
+is created so there is a place to hold the linked to filter and a known handle
+.RB ( 1: )
+to reference to it. The second call then adds the actual filter, which pushes
+packets with TCP source port 22 into class
+.BR 1:2 .
+Using
+.BR ht ,
+it is moved into the hash table created by the first call. The third call then
+does the actual magic: It matches IPv4 packets with next layer protocol 6 (TCP),
+only if it's the first fragment (usually TCP sets DF bit, but if it doesn't and
+the packet is fragmented, only the first one contains the TCP header), and then
+sets the offset based on the IP header's IHL field (right-shifting by 6
+eliminates the offset of the field and at the same time converts the value into
+byte unit). Finally, using
+.BR link ,
+the hash table from first call is referenced which holds the filter from second
+call.
+.SH SEE ALSO
+.BR tc (8),
+.br
+.BR cls_u32.txt " at " http://linux-tc-notes.sourceforge.net/
diff --git a/man/man8/tc-vlan.8 b/man/man8/tc-vlan.8
new file mode 100644
index 0000000..f5ffc25
--- /dev/null
+++ b/man/man8/tc-vlan.8
@@ -0,0 +1,126 @@
+.TH "VLAN manipulation action in tc" 8 "12 Jan 2015" "iproute2" "Linux"
+
+.SH NAME
+vlan - vlan manipulation module
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action vlan" " { " pop " |"
+.IR PUSH " | " MODIFY " } [ " CONTROL " ]"
+
+.ti -8
+.IR PUSH " := "
+.BR push " [ " protocol
+.IR VLANPROTO " ]"
+.BR " [ " priority
+.IR VLANPRIO " ] "
+.BI id " VLANID"
+
+.ti -8
+.IR MODIFY " := "
+.BR modify " [ " protocol
+.IR VLANPROTO " ]"
+.BR " [ " priority
+.IR VLANPRIO " ] "
+.BI id " VLANID"
+
+.ti -8
+.IR CONTROL " := { "
+.BR reclassify " | " pipe " | " drop " | " continue " | " pass " | " goto " " chain " " CHAIN_INDEX " }"
+.SH DESCRIPTION
+The
+.B vlan
+action allows to perform 802.1Q en- or decapsulation on a packet, reflected by
+the operation modes
+.IR POP ", " PUSH " and " MODIFY .
+The
+.I POP
+mode is simple, as no further information is required to just drop the
+outer-most VLAN encapsulation. The
+.IR PUSH " and " MODIFY
+modes require at least a
+.I VLANID
+and allow to optionally choose the
+.I VLANPROTO
+to use.
+.SH OPTIONS
+.TP
+.B pop
+Decapsulation mode, no further arguments allowed.
+.TP
+.B push
+Encapsulation mode. Requires at least
+.B id
+option.
+.TP
+.B modify
+Replace mode. Existing 802.1Q tag is replaced. Requires at least
+.B id
+option.
+.TP
+.BI id " VLANID"
+Specify the VLAN ID to encapsulate into.
+.I VLANID
+is an unsigned 16bit integer, the format is detected automatically (e.g. prefix
+with
+.RB ' 0x '
+for hexadecimal interpretation, etc.).
+.TP
+.BI protocol " VLANPROTO"
+Choose the VLAN protocol to use. At the time of writing, the kernel accepts only
+.BR 802.1Q " or " 802.1ad .
+.TP
+.BI priority " VLANPRIO"
+Choose the VLAN priority to use. Decimal number in range of 0-7.
+.TP
+.I CONTROL
+How to continue after executing this action.
+.RS
+.TP
+.B reclassify
+Restarts classification by jumping back to the first filter attached to this
+action's parent.
+.TP
+.B pipe
+Continue with the next action, this is the default.
+.TP
+.B drop
+Packet will be dropped without running further actions.
+.TP
+.B continue
+Continue classification with next filter in line.
+.TP
+.B pass
+Return to calling qdisc for packet processing. This ends the classification
+process.
+.RE
+.SH EXAMPLES
+The following example encapsulates incoming ICMP packets on eth0 from 10.0.0.2
+into VLAN ID 123:
+
+.RS
+.EX
+#tc qdisc add dev eth0 handle ffff: ingress
+#tc filter add dev eth0 parent ffff: pref 11 protocol ip \\
+ u32 match ip protocol 1 0xff flowid 1:1 \\
+ match ip src 10.0.0.2 flowid 1:1 \\
+ action vlan push id 123
+.EE
+.RE
+
+Here is an example of the
+.B pop
+function: Incoming VLAN packets on eth0 are decapsulated and the classification
+process then restarted for the plain packet:
+
+.RS
+.EX
+#tc qdisc add dev eth0 handle ffff: ingress
+#tc filter add dev $ETH parent ffff: pref 1 protocol 802.1Q \\
+ u32 match u32 0 0 flowid 1:1 \\
+ action vlan pop reclassify
+.EE
+.RE
+
+.SH SEE ALSO
+.BR tc (8)
diff --git a/man/man8/tc-xt.8 b/man/man8/tc-xt.8
new file mode 100644
index 0000000..4fd800c
--- /dev/null
+++ b/man/man8/tc-xt.8
@@ -0,0 +1,42 @@
+.TH "iptables action in tc" 8 "3 Mar 2016" "iproute2" "Linux"
+
+.SH NAME
+xt - tc iptables action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action xt \-j"
+.IR TARGET " [ " TARGET_OPTS " ]"
+.SH DESCRIPTION
+The
+.B xt
+action allows to call arbitrary iptables targets for packets matching the filter
+this action is attached to.
+.SH OPTIONS
+.TP
+.BI -j " TARGET \fR[\fI TARGET_OPTS \fR]"
+Perform a jump to the given iptables target, optionally passing any target
+specific options in
+.IR TARGET_OPTS .
+.SH EXAMPLES
+The following will attach a
+.B u32
+filter to the
+.B ingress
+qdisc matching ICMP replies and using the
+.B xt
+action to make the kernel yell 'PONG' each time:
+
+.RS
+.EX
+tc qdisc add dev eth0 ingress
+tc filter add dev eth0 parent ffff: proto ip u32 \\
+ match ip protocol 1 0xff \\
+ match ip icmp_type 0 0xff \\
+ action xt -j LOG --log-prefix PONG
+.EE
+.RE
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-u32 (8),
+.BR iptables-extensions (8)
diff --git a/man/man8/tc.8 b/man/man8/tc.8
new file mode 100644
index 0000000..b81a396
--- /dev/null
+++ b/man/man8/tc.8
@@ -0,0 +1,870 @@
+.TH TC 8 "16 December 2001" "iproute2" "Linux"
+.SH NAME
+tc \- show / manipulate traffic control settings
+.SH SYNOPSIS
+.B tc
+.RI "[ " OPTIONS " ]"
+.B qdisc [ add | change | replace | link | delete ] dev
+\fIDEV\fR
+.B
+[ parent
+\fIqdisc-id\fR
+.B | root ]
+.B [ handle
+\fIqdisc-id\fR ]
+.B [ ingress_block
+\fIBLOCK_INDEX\fR ]
+.B [ egress_block
+\fIBLOCK_INDEX\fR ] qdisc
+[ qdisc specific parameters ]
+.P
+
+.B tc
+.RI "[ " OPTIONS " ]"
+.B class [ add | change | replace | delete ] dev
+\fIDEV\fR
+.B parent
+\fIqdisc-id\fR
+.B [ classid
+\fIclass-id\fR ] qdisc
+[ qdisc specific parameters ]
+.P
+
+.B tc
+.RI "[ " OPTIONS " ]"
+.B filter [ add | change | replace | delete | get ] dev
+\fIDEV\fR
+.B [ parent
+\fIqdisc-id\fR
+.B | root ] [ handle \fIfilter-id\fR ]
+.B protocol
+\fIprotocol\fR
+.B prio
+\fIpriority\fR filtertype
+[ filtertype specific parameters ]
+.B flowid
+\fIflow-id\fR
+
+.B tc
+.RI "[ " OPTIONS " ]"
+.B filter [ add | change | replace | delete | get ] block
+\fIBLOCK_INDEX\fR
+.B [ handle \fIfilter-id\fR ]
+.B protocol
+\fIprotocol\fR
+.B prio
+\fIpriority\fR filtertype
+[ filtertype specific parameters ]
+.B flowid
+\fIflow-id\fR
+
+.B tc
+.RI "[ " OPTIONS " ]"
+.B chain [ add | delete | get ] dev
+\fIDEV\fR
+.B [ parent
+\fIqdisc-id\fR
+.B | root ]\fR filtertype
+[ filtertype specific parameters ]
+
+.B tc
+.RI "[ " OPTIONS " ]"
+.B chain [ add | delete | get ] block
+\fIBLOCK_INDEX\fR filtertype
+[ filtertype specific parameters ]
+
+
+.B tc
+.RI "[ " OPTIONS " ]"
+.RI "[ " FORMAT " ]"
+.B qdisc show [ dev
+\fIDEV\fR
+.B ]
+.P
+.B tc
+.RI "[ " OPTIONS " ]"
+.RI "[ " FORMAT " ]"
+.B class show dev
+\fIDEV\fR
+.P
+.B tc
+.RI "[ " OPTIONS " ]"
+.B filter show dev
+\fIDEV\fR
+.P
+.B tc
+.RI "[ " OPTIONS " ]"
+.B filter show block
+\fIBLOCK_INDEX\fR
+.P
+.B tc
+.RI "[ " OPTIONS " ]"
+.B chain show dev
+\fIDEV\fR
+.P
+.B tc
+.RI "[ " OPTIONS " ]"
+.B chain show block
+\fIBLOCK_INDEX\fR
+
+.P
+.B tc
+.RI "[ " OPTIONS " ]"
+.B monitor [ file
+\fIFILENAME\fR
+.B ]
+
+.P
+.ti 8
+.IR OPTIONS " := {"
+\fB[ -force ] -b\fR[\fIatch\fR] \fB[ filename ] \fR|
+\fB[ \fB-n\fR[\fIetns\fR] name \fB] \fR|
+\fB[ \fB-N\fR[\fIumeric\fR] \fB] \fR|
+\fB[ \fB-nm \fR| \fB-nam\fR[\fIes\fR] \fB] \fR|
+\fB[ \fR{ \fB-cf \fR| \fB-c\fR[\fIonf\fR] \fR} \fB[ filename ] \fB] \fR
+\fB[ -t\fR[imestamp\fR] \fB\] \fR| \fB[ -t\fR[short\fR] \fR| \fB[
+-o\fR[neline\fR] \fB]\fR }
+
+.ti 8
+.IR FORMAT " := {"
+\fB\-s\fR[\fItatistics\fR] |
+\fB\-d\fR[\fIetails\fR] |
+\fB\-r\fR[\fIaw\fR] |
+\fB\-i\fR[\fIec\fR] |
+\fB\-g\fR[\fIraph\fR] |
+\fB\-j\fR[\fIjson\fR] |
+\fB\-p\fR[\fIretty\fR] |
+\fB\-col\fR[\fIor\fR] }
+
+.SH DESCRIPTION
+.B Tc
+is used to configure Traffic Control in the Linux kernel. Traffic Control consists
+of the following:
+
+.TP
+SHAPING
+When traffic is shaped, its rate of transmission is under control. Shaping may
+be more than lowering the available bandwidth - it is also used to smooth out
+bursts in traffic for better network behaviour. Shaping occurs on egress.
+
+.TP
+SCHEDULING
+By scheduling the transmission of packets it is possible to improve interactivity
+for traffic that needs it while still guaranteeing bandwidth to bulk transfers. Reordering
+is also called prioritizing, and happens only on egress.
+
+.TP
+POLICING
+Whereas shaping deals with transmission of traffic, policing pertains to traffic
+arriving. Policing thus occurs on ingress.
+
+.TP
+DROPPING
+Traffic exceeding a set bandwidth may also be dropped forthwith, both on
+ingress and on egress.
+
+.P
+Processing of traffic is controlled by three kinds of objects: qdiscs,
+classes and filters.
+
+.SH QDISCS
+.B qdisc
+is short for 'queueing discipline' and it is elementary to
+understanding traffic control. Whenever the kernel needs to send a
+packet to an interface, it is
+.B enqueued
+to the qdisc configured for that interface. Immediately afterwards, the kernel
+tries to get as many packets as possible from the qdisc, for giving them
+to the network adaptor driver.
+
+A simple QDISC is the 'pfifo' one, which does no processing at all and is a pure
+First In, First Out queue. It does however store traffic when the network interface
+can't handle it momentarily.
+
+.SH CLASSES
+Some qdiscs can contain classes, which contain further qdiscs - traffic may
+then be enqueued in any of the inner qdiscs, which are within the
+.B classes.
+When the kernel tries to dequeue a packet from such a
+.B classful qdisc
+it can come from any of the classes. A qdisc may for example prioritize
+certain kinds of traffic by trying to dequeue from certain classes
+before others.
+
+.SH FILTERS
+A
+.B filter
+is used by a classful qdisc to determine in which class a packet will
+be enqueued. Whenever traffic arrives at a class with subclasses, it needs
+to be classified. Various methods may be employed to do so, one of these
+are the filters. All filters attached to the class are called, until one of
+them returns with a verdict. If no verdict was made, other criteria may be
+available. This differs per qdisc.
+
+It is important to notice that filters reside
+.B within
+qdiscs - they are not masters of what happens.
+
+The available filters are:
+.TP
+basic
+Filter packets based on an ematch expression. See
+.BR tc-ematch (8)
+for details.
+.TP
+bpf
+Filter packets using (e)BPF, see
+.BR tc-bpf (8)
+for details.
+.TP
+cgroup
+Filter packets based on the control group of their process. See
+. BR tc-cgroup (8)
+for details.
+.TP
+flow, flower
+Flow-based classifiers, filtering packets based on their flow (identified by selectable keys). See
+.BR tc-flow "(8) and"
+.BR tc-flower (8)
+for details.
+.TP
+fw
+Filter based on fwmark. Directly maps fwmark value to traffic class. See
+.BR tc-fw (8).
+.TP
+route
+Filter packets based on routing table. See
+.BR tc-route (8)
+for details.
+.TP
+rsvp
+Match Resource Reservation Protocol (RSVP) packets.
+.TP
+tcindex
+Filter packets based on traffic control index. See
+.BR tc-tcindex (8).
+.TP
+u32
+Generic filtering on arbitrary packet data, assisted by syntax to abstract common operations. See
+.BR tc-u32 (8)
+for details.
+.TP
+matchall
+Traffic control filter that matches every packet. See
+.BR tc-matchall (8)
+for details.
+
+.SH CLASSLESS QDISCS
+The classless qdiscs are:
+.TP
+choke
+CHOKe (CHOose and Keep for responsive flows, CHOose and Kill for unresponsive
+flows) is a classless qdisc designed to both identify and penalize flows that
+monopolize the queue. CHOKe is a variation of RED, and the configuration is
+similar to RED.
+.TP
+codel
+CoDel (pronounced "coddle") is an adaptive "no-knobs" active queue management
+algorithm (AQM) scheme that was developed to address the shortcomings of
+RED and its variants.
+.TP
+[p|b]fifo
+Simplest usable qdisc, pure First In, First Out behaviour. Limited in
+packets or in bytes.
+.TP
+fq
+Fair Queue Scheduler realises TCP pacing and scales to millions of concurrent
+flows per qdisc.
+.TP
+fq_codel
+Fair Queuing Controlled Delay is queuing discipline that combines Fair
+Queuing with the CoDel AQM scheme. FQ_Codel uses a stochastic model to classify
+incoming packets into different flows and is used to provide a fair share of the
+bandwidth to all the flows using the queue. Each such flow is managed by the
+CoDel queuing discipline. Reordering within a flow is avoided since Codel
+internally uses a FIFO queue.
+.TP
+gred
+Generalized Random Early Detection combines multiple RED queues in order to
+achieve multiple drop priorities. This is required to realize Assured
+Forwarding (RFC 2597).
+.TP
+hhf
+Heavy-Hitter Filter differentiates between small flows and the opposite,
+heavy-hitters. The goal is to catch the heavy-hitters and move them to a
+separate queue with less priority so that bulk traffic does not affect the
+latency of critical traffic.
+.TP
+ingress
+This is a special qdisc as it applies to incoming traffic on an interface, allowing for it to be filtered and policed.
+.TP
+mqprio
+The Multiqueue Priority Qdisc is a simple queuing discipline that allows
+mapping traffic flows to hardware queue ranges using priorities and a
+configurable priority to traffic class mapping. A traffic class in this context
+is a set of contiguous qdisc classes which map 1:1 to a set of hardware exposed
+queues.
+.TP
+multiq
+Multiqueue is a qdisc optimized for devices with multiple Tx queues. It has
+been added for hardware that wishes to avoid head-of-line blocking. It will
+cycle though the bands and verify that the hardware queue associated with the
+band is not stopped prior to dequeuing a packet.
+.TP
+netem
+Network Emulator is an enhancement of the Linux traffic control facilities that
+allow to add delay, packet loss, duplication and more other characteristics to
+packets outgoing from a selected network interface.
+.TP
+pfifo_fast
+Standard qdisc for 'Advanced Router' enabled kernels. Consists of a three-band
+queue which honors Type of Service flags, as well as the priority that may be
+assigned to a packet.
+.TP
+pie
+Proportional Integral controller-Enhanced (PIE) is a control theoretic active
+queue management scheme. It is based on the proportional integral controller but
+aims to control delay.
+.TP
+red
+Random Early Detection simulates physical congestion by randomly dropping
+packets when nearing configured bandwidth allocation. Well suited to very
+large bandwidth applications.
+.TP
+rr
+Round-Robin qdisc with support for multiqueue network devices. Removed from
+Linux since kernel version 2.6.27.
+.TP
+sfb
+Stochastic Fair Blue is a classless qdisc to manage congestion based on
+packet loss and link utilization history while trying to prevent
+non-responsive flows (i.e. flows that do not react to congestion marking
+or dropped packets) from impacting performance of responsive flows.
+Unlike RED, where the marking probability has to be configured, BLUE
+tries to determine the ideal marking probability automatically.
+.TP
+sfq
+Stochastic Fairness Queueing reorders queued traffic so each 'session'
+gets to send a packet in turn.
+.TP
+tbf
+The Token Bucket Filter is suited for slowing traffic down to a precisely
+configured rate. Scales well to large bandwidths.
+.SH CONFIGURING CLASSLESS QDISCS
+In the absence of classful qdiscs, classless qdiscs can only be attached at
+the root of a device. Full syntax:
+.P
+.B tc qdisc add dev
+\fIDEV\fR
+.B root
+QDISC QDISC-PARAMETERS
+
+To remove, issue
+.P
+.B tc qdisc del dev
+\fIDEV\fR
+.B root
+
+The
+.B pfifo_fast
+qdisc is the automatic default in the absence of a configured qdisc.
+
+.SH CLASSFUL QDISCS
+The classful qdiscs are:
+.TP
+ATM
+Map flows to virtual circuits of an underlying asynchronous transfer mode
+device.
+.TP
+CBQ
+Class Based Queueing implements a rich linksharing hierarchy of classes.
+It contains shaping elements as well as prioritizing capabilities. Shaping is
+performed using link idle time calculations based on average packet size and
+underlying link bandwidth. The latter may be ill-defined for some interfaces.
+.TP
+DRR
+The Deficit Round Robin Scheduler is a more flexible replacement for Stochastic
+Fairness Queuing. Unlike SFQ, there are no built-in queues \-\- you need to add
+classes and then set up filters to classify packets accordingly. This can be
+useful e.g. for using RED qdiscs with different settings for particular
+traffic. There is no default class \-\- if a packet cannot be classified, it is
+dropped.
+.TP
+DSMARK
+Classify packets based on TOS field, change TOS field of packets based on
+classification.
+.TP
+HFSC
+Hierarchical Fair Service Curve guarantees precise bandwidth and delay allocation for leaf classes and allocates excess bandwidth fairly. Unlike HTB, it makes use of packet dropping to achieve low delays which interactive sessions benefit from.
+.TP
+HTB
+The Hierarchy Token Bucket implements a rich linksharing hierarchy of
+classes with an emphasis on conforming to existing practices. HTB facilitates
+guaranteeing bandwidth to classes, while also allowing specification of upper
+limits to inter-class sharing. It contains shaping elements, based on TBF and
+can prioritize classes.
+.TP
+PRIO
+The PRIO qdisc is a non-shaping container for a configurable number of
+classes which are dequeued in order. This allows for easy prioritization
+of traffic, where lower classes are only able to send if higher ones have
+no packets available. To facilitate configuration, Type Of Service bits are
+honored by default.
+.TP
+QFQ
+Quick Fair Queueing is an O(1) scheduler that provides near-optimal guarantees,
+and is the first to achieve that goal with a constant cost also with respect to
+the number of groups and the packet length. The QFQ algorithm has no loops, and
+uses very simple instructions and data structures that lend themselves very
+well to a hardware implementation.
+.SH THEORY OF OPERATION
+Classes form a tree, where each class has a single parent.
+A class may have multiple children. Some qdiscs allow for runtime addition
+of classes (CBQ, HTB) while others (PRIO) are created with a static number of
+children.
+
+Qdiscs which allow dynamic addition of classes can have zero or more
+subclasses to which traffic may be enqueued.
+
+Furthermore, each class contains a
+.B leaf qdisc
+which by default has
+.B pfifo
+behaviour, although another qdisc can be attached in place. This qdisc may again
+contain classes, but each class can have only one leaf qdisc.
+
+When a packet enters a classful qdisc it can be
+.B classified
+to one of the classes within. Three criteria are available, although not all
+qdiscs will use all three:
+.TP
+tc filters
+If tc filters are attached to a class, they are consulted first
+for relevant instructions. Filters can match on all fields of a packet header,
+as well as on the firewall mark applied by ipchains or iptables.
+.TP
+Type of Service
+Some qdiscs have built in rules for classifying packets based on the TOS field.
+.TP
+skb->priority
+Userspace programs can encode a \fIclass-id\fR in the 'skb->priority' field using
+the SO_PRIORITY option.
+.P
+Each node within the tree can have its own filters but higher level filters
+may also point directly to lower classes.
+
+If classification did not succeed, packets are enqueued to the leaf qdisc
+attached to that class. Check qdisc specific manpages for details, however.
+
+.SH NAMING
+All qdiscs, classes and filters have IDs, which can either be specified
+or be automatically assigned.
+
+IDs consist of a
+.BR major " number and a " minor
+number, separated by a colon -
+.BR major ":" minor "."
+Both
+.BR major " and " minor
+are hexadecimal numbers and are limited to 16 bits. There are two special
+values: root is signified by
+.BR major " and " minor
+of all ones, and unspecified is all zeros.
+
+.TP
+QDISCS
+A qdisc, which potentially can have children, gets assigned a
+.B major
+number, called a 'handle', leaving the
+.B minor
+number namespace available for classes. The handle is expressed as '10:'.
+It is customary to explicitly assign a handle to qdiscs expected to have children.
+
+.TP
+CLASSES
+Classes residing under a qdisc share their qdisc
+.B major
+number, but each have a separate
+.B minor
+number called a 'classid' that has no relation to their
+parent classes, only to their parent qdisc. The same naming custom as for
+qdiscs applies.
+
+.TP
+FILTERS
+Filters have a three part ID, which is only needed when using a hashed
+filter hierarchy.
+
+.SH PARAMETERS
+The following parameters are widely used in TC. For other parameters,
+see the man pages for individual qdiscs.
+
+.TP
+RATES
+Bandwidths or rates.
+These parameters accept a floating point number, possibly followed by
+either a unit (both SI and IEC units supported), or a float followed by a '%'
+character to specify the rate as a percentage of the device's speed
+(e.g. 5%, 99.5%). Warning: specifying the rate as a percentage means a fraction
+of the current speed; if the speed changes, the value will not be recalculated.
+.RS
+.TP
+bit or a bare number
+Bits per second
+.TP
+kbit
+Kilobits per second
+.TP
+mbit
+Megabits per second
+.TP
+gbit
+Gigabits per second
+.TP
+tbit
+Terabits per second
+.TP
+bps
+Bytes per second
+.TP
+kbps
+Kilobytes per second
+.TP
+mbps
+Megabytes per second
+.TP
+gbps
+Gigabytes per second
+.TP
+tbps
+Terabytes per second
+
+.P
+To specify in IEC units, replace the SI prefix (k-, m-, g-, t-) with
+IEC prefix (ki-, mi-, gi- and ti-) respectively.
+
+.P
+TC store rates as a 32-bit unsigned integer in bps internally,
+so we can specify a max rate of 4294967295 bps.
+.RE
+
+.TP
+TIMES
+Length of time. Can be specified as a floating point number
+followed by an optional unit:
+.RS
+.TP
+s, sec or secs
+Whole seconds
+.TP
+ms, msec or msecs
+Milliseconds
+.TP
+us, usec, usecs or a bare number
+Microseconds.
+
+.P
+TC defined its own time unit (equal to microsecond) and stores
+time values as 32-bit unsigned integer, thus we can specify a max time value
+of 4294967295 usecs.
+.RE
+
+.TP
+SIZES
+Amounts of data. Can be specified as a floating point number
+followed by an optional unit:
+.RS
+.TP
+b or a bare number
+Bytes.
+.TP
+kbit
+Kilobits
+.TP
+kb or k
+Kilobytes
+.TP
+mbit
+Megabits
+.TP
+mb or m
+Megabytes
+.TP
+gbit
+Gigabits
+.TP
+gb or g
+Gigabytes
+
+.P
+TC stores sizes internally as 32-bit unsigned integer in byte,
+so we can specify a max size of 4294967295 bytes.
+.RE
+
+.TP
+VALUES
+Other values without a unit.
+These parameters are interpreted as decimal by default, but you can
+indicate TC to interpret them as octal and hexadecimal by adding a '0'
+or '0x' prefix respectively.
+
+.SH TC COMMANDS
+The following commands are available for qdiscs, classes and filter:
+.TP
+add
+Add a qdisc, class or filter to a node. For all entities, a
+.B parent
+must be passed, either by passing its ID or by attaching directly to the root of a device.
+When creating a qdisc or a filter, it can be named with the
+.B handle
+parameter. A class is named with the
+.B \fBclassid\fR
+parameter.
+
+.TP
+delete
+A qdisc can be deleted by specifying its handle, which may also be 'root'. All subclasses and their leaf qdiscs
+are automatically deleted, as well as any filters attached to them.
+
+.TP
+change
+Some entities can be modified 'in place'. Shares the syntax of 'add', with the exception
+that the handle cannot be changed and neither can the parent. In other words,
+.B
+change
+cannot move a node.
+
+.TP
+replace
+Performs a nearly atomic remove/add on an existing node id. If the node does not exist yet
+it is created.
+
+.TP
+get
+Displays a single filter given the interface \fIDEV\fR, \fIqdisc-id\fR,
+\fIpriority\fR, \fIprotocol\fR and \fIfilter-id\fR.
+
+.TP
+show
+Displays all filters attached to the given interface. A valid parent ID must be passed.
+
+.TP
+link
+Only available for qdiscs and performs a replace where the node
+must exist already.
+
+.SH MONITOR
+The\fB\ tc\fR\ utility can monitor events generated by the kernel such as
+adding/deleting qdiscs, filters or actions, or modifying existing ones.
+
+The following command is available for\fB\ monitor\fR\ :
+.TP
+\fBfile\fR
+If the file option is given, the \fBtc\fR does not listen to kernel events, but opens
+the given file and dumps its contents. The file has to be in binary
+format and contain netlink messages.
+
+.SH OPTIONS
+
+.TP
+.BR "\-b", " \-b filename", " \-batch", " \-batch filename"
+read commands from provided file or standard input and invoke them.
+First failure will cause termination of tc.
+
+.TP
+.BR "\-force"
+don't terminate tc on errors in batch mode.
+If there were any errors during execution of the commands, the application return code will be non zero.
+
+.TP
+.BR "\-o" , " \-oneline"
+output each record on a single line, replacing line feeds
+with the
+.B '\e'
+character. This is convenient when you want to count records
+with
+.BR wc (1)
+or to
+.BR grep (1)
+the output.
+
+.TP
+.BR "\-n" , " \-net" , " \-netns " <NETNS>
+switches
+.B tc
+to the specified network namespace
+.IR NETNS .
+Actually it just simplifies executing of:
+
+.B ip netns exec
+.IR NETNS
+.B tc
+.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+to
+
+.B tc
+.RI "-n[etns] " NETNS " [ " OPTIONS " ] " OBJECT " { " COMMAND " | "
+.BR help " }"
+
+.TP
+.BR "\-N" , " \-Numeric"
+Print the number of protocol, scope, dsfield, etc directly instead of
+converting it to human readable name.
+
+.TP
+.BR "\-cf" , " \-conf " <FILENAME>
+specifies path to the config file. This option is used in conjunction with other options (e.g.
+.BR -nm ")."
+
+.TP
+.BR "\-t", " \-timestamp"
+When\fB\ tc monitor\fR\ runs, print timestamp before the event message in format:
+ Timestamp: <Day> <Month> <DD> <hh:mm:ss> <YYYY> <usecs> usec
+
+.TP
+.BR "\-ts", " \-tshort"
+When\fB\ tc monitor\fR\ runs, prints short timestamp before the event message in format:
+ [<YYYY>-<MM>-<DD>T<hh:mm:ss>.<ms>]
+
+.SH FORMAT
+The show command has additional formatting options:
+
+.TP
+.BR "\-s" , " \-stats", " \-statistics"
+output more statistics about packet usage.
+
+.TP
+.BR "\-d", " \-details"
+output more detailed information about rates and cell sizes.
+
+.TP
+.BR "\-r", " \-raw"
+output raw hex values for handles.
+
+.TP
+.BR "\-p", " \-pretty"
+for u32 filter, decode offset and mask values to equivalent filter commands based on TCP/IP.
+In JSON output, add whitespace to improve readability.
+
+.TP
+.BR "\-iec"
+print rates in IEC units (ie. 1K = 1024).
+
+.TP
+.BR "\-g", " \-graph"
+shows classes as ASCII graph. Prints generic stats info under each class if
+.BR "-s"
+option was specified. Classes can be filtered only by
+.BR "dev"
+option.
+
+.TP
+.BR \-c [ color ][ = { always | auto | never }
+Configure color output. If parameter is omitted or
+.BR always ,
+color output is enabled regardless of stdout state. If parameter is
+.BR auto ,
+stdout is checked to be a terminal before enabling color output. If parameter is
+.BR never ,
+color output is disabled. If specified multiple times, the last one takes
+precedence. This flag is ignored if
+.B \-json
+is also given.
+
+.TP
+.BR "\-j", " \-json"
+Display results in JSON format.
+
+.TP
+.BR "\-nm" , " \-name"
+resolve class name from
+.B /etc/iproute2/tc_cls
+file or from file specified by
+.B -cf
+option. This file is just a mapping of
+.B classid
+to class name:
+
+.RS 10
+# Here is comment
+.RE
+.RS 10
+1:40 voip # Here is another comment
+.RE
+.RS 10
+1:50 web
+.RE
+.RS 10
+1:60 ftp
+.RE
+.RS 10
+1:2 home
+.RE
+
+.RS
+.B tc
+will not fail if
+.B -nm
+was specified without
+.B -cf
+option but
+.B /etc/iproute2/tc_cls
+file does not exist, which makes it possible to pass
+.B -nm
+option for creating
+.B tc
+alias.
+.RE
+
+.SH "EXAMPLES"
+.PP
+tc -g class show dev eth0
+.RS 4
+Shows classes as ASCII graph on eth0 interface.
+.RE
+.PP
+tc -g -s class show dev eth0
+.RS 4
+Shows classes as ASCII graph with stats info under each class.
+
+.SH HISTORY
+.B tc
+was written by Alexey N. Kuznetsov and added in Linux 2.2.
+.SH SEE ALSO
+.BR tc-basic (8),
+.BR tc-bfifo (8),
+.BR tc-bpf (8),
+.BR tc-cake (8),
+.BR tc-cbq (8),
+.BR tc-cgroup (8),
+.BR tc-choke (8),
+.BR tc-codel (8),
+.BR tc-drr (8),
+.BR tc-ematch (8),
+.BR tc-flow (8),
+.BR tc-flower (8),
+.BR tc-fq (8),
+.BR tc-fq_codel (8),
+.BR tc-fw (8),
+.BR tc-hfsc (7),
+.BR tc-hfsc (8),
+.BR tc-htb (8),
+.BR tc-mqprio (8),
+.BR tc-pfifo (8),
+.BR tc-pfifo_fast (8),
+.BR tc-pie (8),
+.BR tc-red (8),
+.BR tc-route (8),
+.BR tc-sfb (8),
+.BR tc-sfq (8),
+.BR tc-stab (8),
+.BR tc-tbf (8),
+.BR tc-tcindex (8),
+.BR tc-u32 (8),
+.br
+.RB "User documentation at " http://lartc.org/ ", but please direct bugreports and patches to: " <netdev@vger.kernel.org>
+
+.SH AUTHOR
+Manpage maintained by bert hubert (ahu@ds9a.nl)
diff --git a/man/man8/tipc-bearer.8 b/man/man8/tipc-bearer.8
new file mode 100644
index 0000000..d95b1e1
--- /dev/null
+++ b/man/man8/tipc-bearer.8
@@ -0,0 +1,250 @@
+.TH TIPC-BEARER 8 "02 Jun 2015" "iproute2" "Linux"
+
+.\" For consistency, please keep padding right aligned.
+.\" For example '.B "foo " bar' and not '.B foo " bar"'
+
+.SH NAME
+tipc-bearer \- show or modify TIPC bearers
+
+.SH SYNOPSIS
+.ad l
+.in +8
+
+.ti -8
+.B tipc bearer add media udp name
+.IB "NAME " "remoteip " REMOTEIP
+.br
+
+.ti -8
+.B tipc bearer enable
+.RB "[ " domain
+.IR DOMAIN " ]"
+.RB "[ " priority
+.IR PRIORITY " ]"
+.BR media
+.br
+.RB "{ { " eth " | " ib " } " device
+.IR "DEVICE" " }"
+.RB "|"
+.br
+.RB "{ " udp
+.B name
+.IR NAME
+.B localip
+.IR LOCALIP
+.RB "[ " localport
+.IR LOCALPORT " ]"
+.RB "[ " remoteip
+.IR REMOTEIP " ]"
+.RB "[ " remoteport
+.IR REMOTEPORT " ] }"
+.br
+
+.ti -8
+.B tipc bearer disable media
+.br
+.RB "{ { " eth " | " ib " } " device
+.IR "DEVICE " }
+.RB "|"
+.br
+.RB "{ " udp
+.B name
+.IR NAME " }"
+.br
+
+.ti -8
+.B tipc bearer set
+.RB "{ " "priority "
+.IR PRIORITY
+.RB "| " tolerance
+.IR TOLERANCE
+.RB "| " window
+.IR WINDOW
+.RB "} " media
+.br
+.RB "{ { " eth " | " ib " } " device
+.IR "DEVICE" " }"
+.RB "|"
+.br
+.RB "{ " udp
+.B name
+.IR NAME " }"
+.br
+
+.ti -8
+.B tipc bearer get
+.RB "[ " "priority" " | " tolerance " | " window " ] " media
+.br
+.RB "{ { " eth " | " ib " } " device
+.IR "DEVICE" " }"
+.RB "|"
+.br
+.RB "{ " udp
+.B name
+.IR NAME
+.RB "[ " "localip " "| " "localport " "| " "remoteip " "| " "remoteport " "] }"
+.br
+
+.ti -8
+.B tipc bearer list
+.br
+
+.SH OPTIONS
+Options (flags) that can be passed anywhere in the command chain.
+.TP
+.BR "\-h" , " --help"
+Show help about last valid command. For example
+.B tipc bearer --help
+will show bearer help and
+.B tipc --help
+will show general help. The position of the option in the string is irrelevant.
+.SH DESCRIPTION
+
+.SS Bearer identification
+.TP
+.BI "media " MEDIA
+.br
+Specifies the TIPC media type for a particular bearer to operate on.
+Different media types have different ways of identifying a unique bearer.
+For example,
+.BR "ib " "and " eth
+identify a bearer with a
+.I DEVICE
+while
+.B udp
+identify a bearer with a
+.IR "LOCALIP " "and a " NAME
+
+.B ib
+- Infiniband
+.sp
+.B eth
+- Ethernet
+.sp
+.B udp
+- User Datagram Protocol (UDP)
+.sp
+
+.TP
+.BI "name " NAME
+.br
+Logical bearer identifier valid for bearers on
+.B udp
+media.
+
+.TP
+.BI "device " DEVICE
+.br
+Physical bearer device valid for bearers on
+.B eth
+and
+.B ib
+media.
+
+.SS Bearer properties
+
+.TP
+.B domain
+.br
+The addressing domain (region) in which a bearer will establish links and accept
+link establish requests.
+
+.TP
+.B priority
+.br
+Default link priority inherited by all links subsequently established over a
+bearer. A single bearer can only host one link to a particular node. This means
+the default link priority for a bearer typically affects which bearer to use
+when communicating with a particular node in an multi bearer setup. For more
+info about link priority see
+.BR tipc-link (8)
+
+.TP
+.B tolerance
+.br
+Default link tolerance inherited by all links subsequently established over a
+bearer. For more info about link tolerance see
+.BR tipc-link (8)
+
+.TP
+.B window
+.br
+Default link window inherited by all links subsequently established over a
+bearer. For more info about the link window size see
+.BR tipc-link (8)
+
+.SS UDP bearer options
+
+.TP
+.BI "localip " LOCALIP
+.br
+Specify a local IP v4/v6 address for a
+.B udp
+bearer.
+
+.TP
+.BI "localport " LOCALPORT
+.br
+Specify the local port for a
+.B udp
+bearer. The default port 6118 is used if no port is specified.
+
+.TP
+.BI "remoteip " REMOTEIP
+.br
+Specify a remote IP for a
+.B udp
+bearer. If no remote IP is specified a
+.B udp
+bearer runs in multicast mode and tries to auto-discover its neighbours.
+The multicast IP address is generated based on the TIPC network ID. If a remote
+IP is specified the
+.B udp
+bearer runs in point-to-point mode.
+
+Multiple
+.B remoteip
+addresses can be added via the
+.B bearer add
+command. Adding one or more unicast
+.B remoteip
+addresses to an existing
+.B udp
+bearer puts the bearer in replicast mode where IP
+multicast is emulated by sending multiple unicast messages to each configured
+.B remoteip.
+When a peer sees a TIPC discovery message from an unknown peer the peer address
+is automatically added to the
+.B remoteip
+(replicast) list, thus only one side of
+a link needs to be manually configured. A
+.B remoteip
+address cannot be added to a multicast bearer.
+
+.TP
+.BI "remoteport " REMOTEPORT
+.br
+Specify the remote port for a
+.B udp
+bearer. The default port 6118 is used if no port is specified.
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR tipc (8),
+.BR tipc-link (8),
+.BR tipc-media (8),
+.BR tipc-nametable (8),
+.BR tipc-node (8),
+.BR tipc-peer (8),
+.BR tipc-socket (8)
+.br
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Richard Alpe <richard.alpe@ericsson.com>
diff --git a/man/man8/tipc-link.8 b/man/man8/tipc-link.8
new file mode 100644
index 0000000..47dae25
--- /dev/null
+++ b/man/man8/tipc-link.8
@@ -0,0 +1,383 @@
+.TH TIPC-LINK 8 "22 Mar 2019" "iproute2" "Linux"
+
+.\" For consistency, please keep padding right aligned.
+.\" For example '.B "foo " bar' and not '.B foo " bar"'
+
+.SH NAME
+tipc-link \- show links or modify link properties
+
+.SH SYNOPSIS
+.ad l
+.in +8
+
+.ti -8
+
+.ti -8
+.B tipc link set
+.br
+.RB "[ " "{ " "priority "
+.IR PRIORITY
+.RB "| " tolerance
+.IR TOLERANCE
+.RB "| " window
+.IR "WINDOW " }
+.BI "link " LINK " ]"
+.RB "|"
+.br
+.RB "[ "
+.RB "{ " broadcast " [ "
+.IR BROADCAST
+.RB " | "
+.IR REPLICAST
+.RB " | "
+.IR AUTOSELECT
+.RB "[ " ratio
+.IR SIZE
+.RB "] " ] " } " "]"
+
+.ti -8
+.B tipc link get
+.br
+.RB "[ " "{ " "priority" " | " tolerance " | " window " } " link
+.IR LINK " ] "
+.RB "|"
+.br
+.RB "[ " { " broadcast " } " ]"
+.br
+
+.ti -8
+.B tipc link statistics
+.RB "{ " "show " "[ " link
+.I LINK
+.RB "] | " "reset
+.BI "link " "LINK "
+}
+
+.ti -8
+.B tipc link list
+.br
+
+.ti -8
+.B tipc link monitor set
+.RB "{ " "threshold" " } "
+
+.ti -8
+.B tipc link monitor get
+.RB "{ " "threshold" " } "
+
+.ti -8
+.B tipc link monitor summary
+.br
+
+.ti -8
+.B tipc link monitor list
+.br
+.RB "[ " "media " " { " eth " | " ib " } " device
+.IR "DEVICE" " ]"
+.RB "|"
+.br
+.RB "[ " "media udp name"
+.IR NAME " ]"
+.br
+
+.SH OPTIONS
+Options (flags) that can be passed anywhere in the command chain.
+.TP
+.BR "\-h" , " --help"
+Show help about last valid command. For example
+.B tipc link --help
+will show link help and
+.B tipc --help
+will show general help. The position of the option in the string is irrelevant.
+
+.TP
+.BR "\-j", " \-json"
+Output results in JavaScript Object Notation (JSON).
+
+.TP
+.BR "\-p", " \-pretty"
+The default JSON format is compact and more efficient to parse but hard for most users to read.
+This flag adds indentation for readability.
+
+.SH DESCRIPTION
+
+.SS Link statistics
+
+.TP
+.BR "ACTIVE " "link state"
+.br
+An
+.B ACTIVE
+link is serving traffic. Two links to the same node can become
+.B ACTIVE
+if they have the same link
+.BR priority .
+If there is more than two links with the same priority the additional links will
+be put in
+.B STANDBY
+state.
+
+.TP
+.BR "STANDBY " "link state"
+.br
+A
+.B STANDBY
+link has lower link priority than an
+.B ACTIVE
+link. A
+.B STANDBY
+link has control traffic flowing and is ready to take over should the
+.B ACTIVE
+link(s) go down.
+
+.TP
+.B MTU
+.br
+The Maximum Transmission Unit. The two endpoints advertise their default or
+configured
+.B MTU
+at initial link setup and will agree to use the lower of the two values should
+they differ.
+
+.TP
+.B Packets
+.br
+The total amount of transmitted or received TIPC packets on a link. Including
+.BR "fragmented " "and " "bundled " packets.
+
+.TP
+.B Fragments
+.br
+Represented in the form
+.BR fragments / fragmented .
+Where
+.B fragmented
+is the amount of data messages which have been broken into
+.BR fragments .
+Subsequently the
+.B fragments
+are the total amount of packets that the
+.B fragmented
+messages has been broken into.
+
+.TP
+.B Bundles
+.br
+Represented in the form
+.BR bundles / bundled .
+If a link becomes congested the link will attempt to bundle data from small
+.B bundled
+packets into
+.B bundles
+of full MTU size packets before they are transmitted.
+
+.TP
+.B Profile
+.br
+Shows the
+.B average
+packet size in octets/bytes for a
+.B sample
+of packets. It also shows the packet size distribution of the
+.B sampled
+packets in the intervals
+
+0-64 bytes
+.br
+64-256 bytes
+.br
+256-1024 bytes
+.br
+1024-4096 bytes
+.br
+4096-16384 bytes
+.br
+16384-32768 bytes
+.br
+32768-66000 bytes
+
+.TP
+.B Message counters
+
+.B states
+- Number of link state messages
+.sp
+
+.B probes
+- Link state messages with probe flag set. Typically sent when a link is idle
+.sp
+
+.B nacks
+- Number of negative acknowledgement (NACK) packets sent and received by the
+link
+.sp
+
+.B defs
+- Number of packets received out of order
+.sp
+
+.B dups
+- Number of duplicate packets received
+
+.TP
+.B Congestion link
+The number of times an application has tried to send data when the TIPC link
+was congested
+
+.TP
+.B Send queue
+.B Max
+is the maximum amount of messages that has resided in the out queue during the
+statistics collection period of a link.
+
+.B Avg
+is the average outqueue size during the lifetime of a link.
+
+.SS Link properties
+
+.TP
+.B priority
+.br
+The priority between logical TIPC links to a particular node. Link priority can
+range from 0 (lowest) to 31 (highest).
+
+.TP
+.B tolerance
+.br
+Link tolerance specifies the maximum time in milliseconds that TIPC will allow
+a communication problem to exist before taking the link down. The default value
+is 1500 milliseconds.
+
+.TP
+.B window
+.br
+The link window controls how many unacknowledged messages a link endpoint can
+have in its transmit queue before TIPC's congestion control mechanism is
+activated.
+
+.SS Monitor properties
+
+.TP
+.B threshold
+.br
+The threshold specifies the cluster size exceeding which the link monitoring
+algorithm will switch from "full-mesh" to "overlapping-ring".
+If set of 0 the overlapping-ring monitoring is always on and if set to a
+value larger than anticipated cluster size the overlapping-ring is disabled.
+The default value is 32.
+
+.SS Monitor information
+
+.TP
+.B table_generation
+.br
+Represents the event count in a node's local monitoring list. It steps every
+time something changes in the local monitor list, including changes in the
+local domain.
+
+.TP
+.B cluster_size
+.br
+Represents the current count of cluster members.
+
+.TP
+.B algorithm
+.br
+The current supervision algorithm used for neighbour monitoring for the bearer.
+Possible values are full-mesh or overlapping-ring.
+
+.TP
+.B status
+.br
+The node status derived by the local node.
+Possible status are up or down.
+
+.TP
+.B monitored
+.br
+Represent the type of monitoring chosen by the local node.
+Possible values are direct or indirect.
+
+.TP
+.B generation
+.br
+Represents the domain generation which is the event count in a node's local
+domain. Every time something changes (peer add/remove/up/down) the domain
+generation is stepped and a new version of node record is sent to inform
+the neighbors about this change. The domain generation helps the receiver
+of a domain record to know if it should ignore or process the record.
+
+.TP
+.B applied_node_status
+.br
+The node status reported by the peer node for the succeeding peers in
+the node list. The Node list is a circular list of ascending addresses
+starting with the local node.
+Possible status are: U or D. The status U implies up and D down.
+
+.TP
+.B [non_applied_node:status]
+.br
+Represents the nodes and their status as reported by the peer node.
+These nodes were not applied to the monitoring list for this peer node.
+They are usually transient and occur during the cluster startup phase
+or network reconfiguration.
+Possible status are: U or D. The status U implies up and D down.
+
+.SS Broadcast properties
+.TP
+.B BROADCAST
+.br
+Forces all multicast traffic to be transmitted via broadcast only,
+irrespective of cluster size and number of destinations.
+
+.TP
+.B REPLICAST
+.br
+Forces all multicast traffic to be transmitted via replicast only,
+irrespective of cluster size and number of destinations.
+
+.TP
+.B AUTOSELECT
+.br
+Auto switching to broadcast or replicast depending on cluster size and
+destination node number.
+
+.TP
+.B ratio SIZE
+.br
+Set the AUTOSELECT criteria, percentage of destination nodes vs cluster
+size.
+
+.SH EXAMPLES
+.PP
+tipc link monitor list
+.RS 4
+Shows the link monitoring information for cluster members on device data0.
+.RE
+.PP
+tipc link monitor summary
+.RS 4
+The monitor summary command prints the basic attributes.
+.RE
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR tipc (8),
+.BR tipc-media (8),
+.BR tipc-bearer (8),
+.BR tipc-nametable (8),
+.BR tipc-node (8),
+.BR tipc-peer (8),
+.BR tipc-socket (8)
+.br
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Richard Alpe <richard.alpe@ericsson.com>
diff --git a/man/man8/tipc-media.8 b/man/man8/tipc-media.8
new file mode 100644
index 0000000..4689cb3
--- /dev/null
+++ b/man/man8/tipc-media.8
@@ -0,0 +1,87 @@
+.TH TIPC-MEDIA 8 "02 Jun 2015" "iproute2" "Linux"
+
+.\" For consistency, please keep padding right aligned.
+.\" For example '.B "foo " bar' and not '.B foo " bar"'
+
+.SH NAME
+tipc-media \- list or modify media properties
+
+.SH SYNOPSIS
+.ad l
+.in +8
+
+.ti -8
+
+.ti -8
+.B tipc media set
+.RB "{ " "priority "
+.IR PRIORITY
+.RB "| " tolerance
+.IR TOLERANCE
+.RB "| " window
+.IR "WINDOW " }
+.BI "media " MEDIA
+
+.ti -8
+.B tipc media get
+.RB "{ " "priority" " | " tolerance " | " window " } " media
+.I MEDIA
+
+.ti -8
+.B tipc media list
+.br
+
+.SH OPTIONS
+Options (flags) that can be passed anywhere in the command chain.
+.TP
+.BR "\-h" , " --help"
+Show help about last valid command. For example
+.B tipc media --help
+will show media help and
+.B tipc --help
+will show general help. The position of the option in the string is irrelevant.
+.SH DESCRIPTION
+
+.SS Media properties
+
+.TP
+.B priority
+.br
+Default link priority inherited by all bearers subsequently enabled on a
+media. For more info about link priority see
+.BR tipc-link (8)
+
+.TP
+.B tolerance
+.br
+Default link tolerance inherited by all bearers subsequently enabled on a
+media. For more info about link tolerance see
+.BR tipc-link (8)
+
+.TP
+.B window
+.br
+Default link window inherited by all bearers subsequently enabled on a
+media. For more info about link window see
+.BR tipc-link (8)
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR tipc (8),
+.BR tipc-bearer (8),
+.BR tipc-link (8),
+.BR tipc-nametable (8),
+.BR tipc-node (8),
+.BR tipc-peer (8),
+.BR tipc-socket (8)
+.br
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Richard Alpe <richard.alpe@ericsson.com>
diff --git a/man/man8/tipc-nametable.8 b/man/man8/tipc-nametable.8
new file mode 100644
index 0000000..b187d25
--- /dev/null
+++ b/man/man8/tipc-nametable.8
@@ -0,0 +1,110 @@
+.TH TIPC-NAMETABLE 8 "02 Jun 2015" "iproute2" "Linux"
+
+.\" For consistency, please keep padding right aligned.
+.\" For example '.B "foo " bar' and not '.B foo " bar"'
+
+.SH NAME
+tipc-nametable \- show TIPC nametable
+
+.SH SYNOPSIS
+.ad l
+.in +8
+
+.ti -8
+.B tipc nametable show
+.br
+
+.SH OPTIONS
+Options (flags) that can be passed anywhere in the command chain.
+.TP
+.BR "\-h" , " --help"
+
+.TP
+.BR "\-j", " \-json"
+Output results in JavaScript Object Notation (JSON).
+
+.TP
+.BR "\-p", " \-pretty"
+The default JSON format is compact and more efficient to parse but hard for most users to read.
+This flag adds indentation for readability.
+
+Show help about last valid command. For example
+.B tipc nametable --help
+will show nametable help and
+.B tipc --help
+will show general help. The position of the option in the string is irrelevant.
+
+.SH DESCRIPTION
+The nametable shows TIPC publication information.
+
+.SS Nametable format
+
+.TP
+.B Type
+.br
+The 32-bit type field of the port name. The type field often indicates the class of service
+provided by a port.
+
+.TP
+.B Lower
+.br
+The lower bound of the 32-bit instance field of the port name.
+The instance field is often used as as a sub-class indicator.
+
+.TP
+.B Upper
+.br
+The upper bound of the 32-bit instance field of the port name.
+The instance field is often used as as a sub-class indicator.
+A difference in
+.BR "lower " "and " upper
+means the socket is bound to the port name range [lower,upper]
+
+.TP
+.B Port Identity
+.br
+The unique socket (port) identifier within the TIPC cluster. The
+.B port identity
+consists of a node identity followed by a socket reference number.
+
+.TP
+.B Publication
+.br
+The
+.B publication
+ID is a random number used internally to represent a publication.
+
+.TP
+.B Scope
+.br
+The publication
+.B scope
+specifies the visibility of a bound port name.
+The
+.B scope
+can be specified to comprise three different domains:
+.BR node ", " "cluster " "and " zone.
+Applications residing within the specified
+.B scope
+can see and access the port using the displayed port name.
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR tipc (8),
+.BR tipc-bearer (8),
+.BR tipc-link (8),
+.BR tipc-media (8),
+.BR tipc-node (8),
+.BR tipc-peer (8),
+.BR tipc-socket (8)
+.br
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Richard Alpe <richard.alpe@ericsson.com>
diff --git a/man/man8/tipc-node.8 b/man/man8/tipc-node.8
new file mode 100644
index 0000000..a72a409
--- /dev/null
+++ b/man/man8/tipc-node.8
@@ -0,0 +1,72 @@
+.TH TIPC-NODE 8 "02 Jun 2015" "iproute2" "Linux"
+
+.\" For consistency, please keep padding right aligned.
+.\" For example '.B "foo " bar' and not '.B foo " bar"'
+
+.SH NAME
+tipc-node \- modify and show local node parameters or list peer nodes
+
+.SH SYNOPSIS
+.ad l
+.in +8
+
+.ti -8
+.B tipc node set
+.RB "{ " "address "
+.IR ADDRESS
+.RB "| " netid
+.IR NETID
+.RB "} "
+
+.ti -8
+.B tipc node get
+.RB "{ " "address" " | " netid " } "
+
+.ti -8
+.B tipc node list
+.br
+
+.SH OPTIONS
+Options (flags) that can be passed anywhere in the command chain.
+.TP
+.BR "\-h" , " --help"
+Show help about last valid command. For example
+.B tipc node --help
+will show node help and
+.B tipc --help
+will show general help. The position of the option in the string is irrelevant.
+.SH DESCRIPTION
+
+.SS Node parameters
+.TP
+.BI address
+.br
+The TIPC logical address. On the form x.y.z where x, y and z are unsigned
+integers.
+
+.TP
+.BI netid
+.br
+Network identity. Can by used to create individual TIPC clusters on the same
+media.
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR tipc (8),
+.BR tipc-bearer (8),
+.BR tipc-link (8),
+.BR tipc-media (8),
+.BR tipc-nametable (8),
+.BR tipc-peer (8),
+.BR tipc-socket (8)
+.br
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Richard Alpe <richard.alpe@ericsson.com>
diff --git a/man/man8/tipc-peer.8 b/man/man8/tipc-peer.8
new file mode 100644
index 0000000..430651f
--- /dev/null
+++ b/man/man8/tipc-peer.8
@@ -0,0 +1,52 @@
+.TH TIPC-PEER 8 "04 Dec 2015" "iproute2" "Linux"
+
+.\" For consistency, please keep padding right aligned.
+.\" For example '.B "foo " bar' and not '.B foo " bar"'
+
+.SH NAME
+tipc-peer \- modify peer information
+
+.SH SYNOPSIS
+.ad l
+.in +8
+
+.ti -8
+.B tipc peer remove address
+.IR ADDRESS
+
+.SH OPTIONS
+Options (flags) that can be passed anywhere in the command chain.
+.TP
+.BR "\-h" , " --help"
+Show help about last valid command. For example
+.B tipc peer --help
+will show peer help and
+.B tipc --help
+will show general help. The position of the option in the string is irrelevant.
+.SH DESCRIPTION
+
+.SS Peer remove
+Remove an offline peer node from the local data structures. The peer is
+identified by its
+.B address
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR tipc (8),
+.BR tipc-bearer (8),
+.BR tipc-link (8),
+.BR tipc-media (8),
+.BR tipc-nametable (8),
+.BR tipc-node (8),
+.BR tipc-socket (8)
+.br
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Richard Alpe <richard.alpe@ericsson.com>
diff --git a/man/man8/tipc-socket.8 b/man/man8/tipc-socket.8
new file mode 100644
index 0000000..23ec1e5
--- /dev/null
+++ b/man/man8/tipc-socket.8
@@ -0,0 +1,59 @@
+.TH TIPC-SOCKET 8 "02 Jun 2015" "iproute2" "Linux"
+
+.\" For consistency, please keep padding right aligned.
+.\" For example '.B "foo " bar' and not '.B foo " bar"'
+
+.SH NAME
+tipc-socket \- show TIPC socket (port) information
+
+.SH SYNOPSIS
+.ad l
+.in +8
+
+.ti -8
+.B tipc socket list
+
+.SH OPTIONS
+Options (flags) that can be passed anywhere in the command chain.
+.TP
+.BR "\-h" , " --help"
+Show help about last valid command. For example
+.B tipc socket --help
+will show socket help and
+.B tipc --help
+will show general help. The position of the option in the string is irrelevant.
+
+.SH DESCRIPTION
+A TIPC socket is represented by an unsigned integer.
+
+.TP
+.SS Bound state
+A bound socket has a logical TIPC port name associated with it.
+
+.TP
+.SS Connected state
+A connected socket is directly connected to another socket creating a point
+to point connection between TIPC sockets. If the connection to X was made using
+a logical port name Y that name will show up as
+.BR "connected to " "X " "via " Y
+.
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR tipc (8),
+.BR tipc-bearer (8)
+.BR tipc-link (8),
+.BR tipc-media (8),
+.BR tipc-nametable (8),
+.BR tipc-node (8),
+.br
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Richard Alpe <richard.alpe@ericsson.com>
diff --git a/man/man8/tipc.8 b/man/man8/tipc.8
new file mode 100644
index 0000000..6706cca
--- /dev/null
+++ b/man/man8/tipc.8
@@ -0,0 +1,109 @@
+.TH TIPC 8 "02 Jun 2015" "iproute2" "Linux"
+.SH NAME
+tipc \- a TIPC configuration and management tool
+.SH SYNOPSIS
+
+.ad l
+.in +8
+.ti -8
+.B tipc
+.RI "[ " OPTIONS " ] " COMMAND " " ARGUMENTS "
+.sp
+
+.ti -8
+.IR COMMAND " := { "
+.BR bearer " | " link " | " media " | " nametable " | " node " | " socket " }
+.sp
+
+.ti -8
+.IR OPTIONS " := { "
+\fB\-h\fR[\fIhelp\fR] }
+
+.SH DESCRIPTION
+The Transparent Inter-Process Communication (TIPC) protocol offers total address
+transparency between processes which allows applications in a clustered computer
+environment to communicate quickly and reliably with each other, regardless of
+their location within the cluster.
+
+TIPC originated at the telecommunications manufacturer Ericsson. The first open
+source version of TIPC was created in 2000 when Ericsson released its first
+Linux version of TIPC. TIPC was introduced in the mainline Linux kernel in 2006
+and is now widely used both within and outside of Ericsson.
+
+.SH OPTIONS
+
+.TP
+.BR "\-h" , " --help"
+Show help about last given command. For example
+.B tipc bearer --help
+will show bearer help and
+.B tipc --help
+will show general help. The position of the option in the string is irrelevant.
+
+.TP
+.BR "\-j", " \-json"
+Output results in JavaScript Object Notation (JSON).
+
+.TP
+.BR "\-p", " \-pretty"
+The default JSON format is compact and more efficient to parse but hard for most users to read.
+This flag adds indentation for readability.
+
+.SH COMMANDS
+
+.TP
+.B BEARER
+- Show or modify TIPC bearers
+
+.TP
+.B LINK
+- Show or modify TIPC links
+
+.TP
+.B MEDIA
+- Show or modify TIPC media
+
+.TP
+.B NAMETABLE
+- Show TIPC nametable
+
+.TP
+.B NODE
+- Show or modify TIPC node parameters
+
+.TP
+.B SOCKET
+- Show TIPC sockets
+
+.SH ARGUMENTS
+
+Command arguments are described in a command specific man page and typically
+consists of nested commands along with key value pairs.
+If no arguments are given a command typically shows its help text. The explicit
+help option
+.B -h
+or
+.B --help
+can occur anywhere among the arguments and will show help for the last valid
+command given.
+
+.SH EXIT STATUS
+Exit status is 0 if command was successful or a positive integer upon failure.
+
+.SH SEE ALSO
+.BR tipc-bearer (8),
+.BR tipc-link (8),
+.BR tipc-media (8),
+.BR tipc-nametable (8),
+.BR tipc-node (8),
+.BR tipc-peer (8),
+.BR tipc-socket (8)
+.br
+.SH REPORTING BUGS
+Report any bugs to the Network Developers mailing list
+.B <netdev@vger.kernel.org>
+where the development and maintenance is primarily done.
+You do not have to be subscribed to the list to send a message there.
+
+.SH AUTHOR
+Richard Alpe <richard.alpe@ericsson.com>
diff --git a/misc/.gitignore b/misc/.gitignore
new file mode 100644
index 0000000..f73f7f2
--- /dev/null
+++ b/misc/.gitignore
@@ -0,0 +1,7 @@
+arpd
+ifstat
+ss
+ssfilter.c
+nstat
+lnstat
+rtacct
diff --git a/misc/Android.mk b/misc/Android.mk
new file mode 100644
index 0000000..6b07f70
--- /dev/null
+++ b/misc/Android.mk
@@ -0,0 +1,55 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := ss
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := ss.c ssfilter.c
+
+LOCAL_STATIC_LIBRARIES += libiprouteutil libnetlink
+
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/uapi
+
+ss_local_path := $(LOCAL_PATH)
+
+LOCAL_CFLAGS := -O2 -g -W -Wall -Wno-pointer-arith -Wno-sign-compare -Werror \
+ -Wno-missing-field-initializers -Wno-unused-parameter \
+ -Wno-unused-result -Wno-tautological-pointer-compare \
+ -DHAVE_SETNS -DNEED_STRLCPY
+
+$(ss_local_path)/ssfilter.c: $(ss_local_path)/ssfilter.y
+ bison $(ss_local_path)/ssfilter.y -o $(ss_local_path)/ssfilter.c
+
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := nstat
+LOCAL_MODULE_TAGS := eng
+
+LOCAL_SRC_FILES := nstat.c
+LOCAL_STATIC_LIBRARIES += libiprouteutil libnetlink
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/uapi
+LOCAL_CFLAGS := -O2 -g -W -Wall -Wno-pointer-arith -Wno-sign-compare -Werror \
+ -Wno-missing-field-initializers -Wno-unused-parameter \
+ -Wno-unused-result -Wno-tautological-pointer-compare \
+ -DHAVE_SETNS
+LOCAL_LDLIBS += -lm
+
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := ifstat
+LOCAL_MODULE_TAGS := eng
+
+LOCAL_SRC_FILES := ifstat.c
+LOCAL_STATIC_LIBRARIES += libiprouteutil libnetlink
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/uapi
+LOCAL_CFLAGS := -O2 -g -W -Wall -Wno-pointer-arith -Wno-sign-compare -Werror \
+ -Wno-missing-field-initializers -Wno-unused-parameter \
+ -Wno-unused-result -Wno-tautological-pointer-compare \
+ -DHAVE_SETNS
+LOCAL_LDLIBS += -lm
+
+include $(BUILD_EXECUTABLE)
+
+
diff --git a/misc/Makefile b/misc/Makefile
new file mode 100644
index 0000000..6a849af
--- /dev/null
+++ b/misc/Makefile
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: GPL-2.0
+SSOBJ=ss.o ssfilter.o
+LNSTATOBJ=lnstat.o lnstat_util.o
+
+TARGETS=ss nstat ifstat rtacct lnstat
+
+include ../config.mk
+
+ifeq ($(HAVE_BERKELEY_DB),y)
+ TARGETS += arpd
+endif
+
+all: $(TARGETS)
+
+ss: $(SSOBJ)
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+nstat: nstat.c
+ $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o nstat nstat.c $(LDLIBS) -lm
+
+ifstat: ifstat.c
+ $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o ifstat ifstat.c $(LDLIBS) -lm
+
+rtacct: rtacct.c
+ $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o rtacct rtacct.c $(LDLIBS) -lm
+
+arpd: arpd.c
+ $(QUIET_CC)$(CC) $(CFLAGS) -I$(DBM_INCLUDE) $(CPPFLAGS) $(LDFLAGS) -o arpd arpd.c $(LDLIBS) -ldb
+
+ssfilter.c: ssfilter.y
+ $(QUIET_YACC)bison ssfilter.y -o ssfilter.c
+
+lnstat: $(LNSTATOBJ)
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+ install -m 0755 $(TARGETS) $(DESTDIR)$(SBINDIR)
+ ln -sf lnstat $(DESTDIR)$(SBINDIR)/rtstat
+ ln -sf lnstat $(DESTDIR)$(SBINDIR)/ctstat
+
+clean:
+ rm -f *.o $(TARGETS) ssfilter.c
diff --git a/misc/arpd.c b/misc/arpd.c
new file mode 100644
index 0000000..504961c
--- /dev/null
+++ b/misc/arpd.c
@@ -0,0 +1,837 @@
+/*
+ * arpd.c ARP helper daemon.
+ *
+ * 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 <syslog.h>
+#include <malloc.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <db_185.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <linux/if_packet.h>
+#include <linux/filter.h>
+
+#include "libnetlink.h"
+#include "utils.h"
+#include "rt_names.h"
+
+DB *dbase;
+char *dbname = "/var/lib/arpd/arpd.db";
+
+int ifnum;
+int *ifvec;
+char **ifnames;
+
+struct dbkey {
+ __u32 iface;
+ __u32 addr;
+};
+
+#define IS_NEG(x) (((__u8 *)(x))[0] == 0xFF)
+#define NEG_TIME(x) (((x)[2]<<24)|((x)[3]<<16)|((x)[4]<<8)|(x)[5])
+#define NEG_AGE(x) ((__u32)time(NULL) - NEG_TIME((__u8 *)x))
+#define NEG_VALID(x) (NEG_AGE(x) < negative_timeout)
+#define NEG_CNT(x) (((__u8 *)(x))[1])
+
+struct rtnl_handle rth;
+
+struct pollfd pset[2];
+int udp_sock = -1;
+
+volatile int do_exit;
+volatile int do_sync;
+volatile int do_stats;
+
+struct {
+ unsigned long arp_new;
+ unsigned long arp_change;
+
+ unsigned long app_recv;
+ unsigned long app_success;
+ unsigned long app_bad;
+ unsigned long app_neg;
+ unsigned long app_suppressed;
+
+ unsigned long kern_neg;
+ unsigned long kern_new;
+ unsigned long kern_change;
+
+ unsigned long probes_sent;
+ unsigned long probes_suppressed;
+} stats;
+
+int active_probing;
+int negative_timeout = 60;
+int no_kernel_broadcasts;
+int broadcast_rate = 1000;
+int broadcast_burst = 3000;
+int poll_timeout = 30000;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: arpd [ -lkh? ] [ -a N ] [ -b dbase ] [ -B number ] [ -f file ] [ -n time ] [-p interval ] [ -R rate ] [ interfaces ]\n");
+ exit(1);
+}
+
+static int handle_if(int ifindex)
+{
+ int i;
+
+ if (ifnum == 0)
+ return 1;
+
+ for (i = 0; i < ifnum; i++)
+ if (ifvec[i] == ifindex)
+ return 1;
+ return 0;
+}
+
+int sysctl_adjusted;
+
+static void do_sysctl_adjustments(void)
+{
+ int i;
+
+ if (!ifnum)
+ return;
+
+ for (i = 0; i < ifnum; i++) {
+ char buf[128];
+ FILE *fp;
+
+ if (active_probing) {
+ sprintf(buf, "/proc/sys/net/ipv4/neigh/%s/mcast_solicit", ifnames[i]);
+ if ((fp = fopen(buf, "w")) != NULL) {
+ if (no_kernel_broadcasts)
+ strcpy(buf, "0\n");
+ else
+ sprintf(buf, "%d\n", active_probing >= 2 ? 1 : 3-active_probing);
+ fputs(buf, fp);
+ fclose(fp);
+ }
+ }
+
+ sprintf(buf, "/proc/sys/net/ipv4/neigh/%s/app_solicit", ifnames[i]);
+ if ((fp = fopen(buf, "w")) != NULL) {
+ sprintf(buf, "%d\n", active_probing <= 1 ? 1 : active_probing);
+ fputs(buf, fp);
+ fclose(fp);
+ }
+ }
+ sysctl_adjusted = 1;
+}
+
+static void undo_sysctl_adjustments(void)
+{
+ int i;
+
+ if (!sysctl_adjusted)
+ return;
+
+ for (i = 0; i < ifnum; i++) {
+ char buf[128];
+ FILE *fp;
+
+ if (active_probing) {
+ sprintf(buf, "/proc/sys/net/ipv4/neigh/%s/mcast_solicit", ifnames[i]);
+ if ((fp = fopen(buf, "w")) != NULL) {
+ strcpy(buf, "3\n");
+ fputs(buf, fp);
+ fclose(fp);
+ }
+ }
+ sprintf(buf, "/proc/sys/net/ipv4/neigh/%s/app_solicit", ifnames[i]);
+ if ((fp = fopen(buf, "w")) != NULL) {
+ strcpy(buf, "0\n");
+ fputs(buf, fp);
+ fclose(fp);
+ }
+ }
+ sysctl_adjusted = 0;
+}
+
+
+static int send_probe(int ifindex, __u32 addr)
+{
+ struct ifreq ifr = { .ifr_ifindex = ifindex };
+ struct sockaddr_in dst = {
+ .sin_family = AF_INET,
+ .sin_port = htons(1025),
+ .sin_addr.s_addr = addr,
+ };
+ socklen_t len;
+ unsigned char buf[256];
+ struct arphdr *ah = (struct arphdr *)buf;
+ unsigned char *p = (unsigned char *)(ah+1);
+ struct sockaddr_ll sll = {
+ .sll_family = AF_PACKET,
+ .sll_ifindex = ifindex,
+ .sll_protocol = htons(ETH_P_ARP),
+ };
+
+ if (ioctl(udp_sock, SIOCGIFNAME, &ifr))
+ return -1;
+ if (ioctl(udp_sock, SIOCGIFHWADDR, &ifr))
+ return -1;
+ if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
+ return -1;
+ if (setsockopt(udp_sock, SOL_SOCKET, SO_BINDTODEVICE, ifr.ifr_name, strlen(ifr.ifr_name)+1) < 0)
+ return -1;
+
+ if (connect(udp_sock, (struct sockaddr *)&dst, sizeof(dst)) < 0)
+ return -1;
+ len = sizeof(dst);
+ if (getsockname(udp_sock, (struct sockaddr *)&dst, &len) < 0)
+ return -1;
+
+ ah->ar_hrd = htons(ifr.ifr_hwaddr.sa_family);
+ ah->ar_pro = htons(ETH_P_IP);
+ ah->ar_hln = 6;
+ ah->ar_pln = 4;
+ ah->ar_op = htons(ARPOP_REQUEST);
+
+ memcpy(p, ifr.ifr_hwaddr.sa_data, ah->ar_hln);
+ p += ah->ar_hln;
+
+ memcpy(p, &dst.sin_addr, 4);
+ p += 4;
+
+ memset(sll.sll_addr, 0xFF, sizeof(sll.sll_addr));
+ memcpy(p, &sll.sll_addr, ah->ar_hln);
+ p += ah->ar_hln;
+
+ memcpy(p, &addr, 4);
+ p += 4;
+
+ if (sendto(pset[0].fd, buf, p-buf, 0, (struct sockaddr *)&sll, sizeof(sll)) < 0)
+ return -1;
+ stats.probes_sent++;
+ return 0;
+}
+
+/* Be very tough on sending probes: 1 per second with burst of 3. */
+
+static int queue_active_probe(int ifindex, __u32 addr)
+{
+ static struct timeval prev;
+ static int buckets;
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+ if (prev.tv_sec) {
+ int diff = (now.tv_sec-prev.tv_sec)*1000+(now.tv_usec-prev.tv_usec)/1000;
+
+ buckets += diff;
+ } else {
+ buckets = broadcast_burst;
+ }
+ if (buckets > broadcast_burst)
+ buckets = broadcast_burst;
+ if (buckets >= broadcast_rate && !send_probe(ifindex, addr)) {
+ buckets -= broadcast_rate;
+ prev = now;
+ return 0;
+ }
+ stats.probes_suppressed++;
+ return -1;
+}
+
+static int respond_to_kernel(int ifindex, __u32 addr, char *lla, int llalen)
+{
+ 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,
+ .n.nlmsg_type = RTM_NEWNEIGH,
+ .ndm.ndm_family = AF_INET,
+ .ndm.ndm_state = NUD_STALE,
+ .ndm.ndm_ifindex = ifindex,
+ .ndm.ndm_type = RTN_UNICAST,
+ };
+
+ addattr_l(&req.n, sizeof(req), NDA_DST, &addr, 4);
+ addattr_l(&req.n, sizeof(req), NDA_LLADDR, lla, llalen);
+ return rtnl_send(&rth, &req, req.n.nlmsg_len) <= 0;
+}
+
+static void prepare_neg_entry(__u8 *ndata, __u32 stamp)
+{
+ ndata[0] = 0xFF;
+ ndata[1] = 0;
+ ndata[2] = stamp>>24;
+ ndata[3] = stamp>>16;
+ ndata[4] = stamp>>8;
+ ndata[5] = stamp;
+}
+
+
+static int do_one_request(struct nlmsghdr *n)
+{
+ struct ndmsg *ndm = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[NDA_MAX+1];
+ struct dbkey key;
+ DBT dbkey, dbdat;
+ int do_acct = 0;
+
+ if (n->nlmsg_type == NLMSG_DONE) {
+ dbase->sync(dbase, 0);
+
+ /* Now we have at least mirror of kernel db, so that
+ * may start real resolution.
+ */
+ do_sysctl_adjustments();
+ return 0;
+ }
+
+ if (n->nlmsg_type != RTM_GETNEIGH && n->nlmsg_type != RTM_NEWNEIGH)
+ return 0;
+
+ len -= NLMSG_LENGTH(sizeof(*ndm));
+ if (len < 0)
+ return -1;
+
+ if (ndm->ndm_family != AF_INET ||
+ (ifnum && !handle_if(ndm->ndm_ifindex)) ||
+ ndm->ndm_flags ||
+ ndm->ndm_type != RTN_UNICAST ||
+ !(ndm->ndm_state&~NUD_NOARP))
+ return 0;
+
+ parse_rtattr(tb, NDA_MAX, NDA_RTA(ndm), len);
+
+ if (!tb[NDA_DST])
+ return 0;
+
+ key.iface = ndm->ndm_ifindex;
+ memcpy(&key.addr, RTA_DATA(tb[NDA_DST]), 4);
+ dbkey.data = &key;
+ dbkey.size = sizeof(key);
+
+ if (dbase->get(dbase, &dbkey, &dbdat, 0) != 0) {
+ dbdat.data = 0;
+ dbdat.size = 0;
+ }
+
+ if (n->nlmsg_type == RTM_GETNEIGH) {
+ if (!(n->nlmsg_flags&NLM_F_REQUEST))
+ return 0;
+
+ if (!(ndm->ndm_state&(NUD_PROBE|NUD_INCOMPLETE))) {
+ stats.app_bad++;
+ return 0;
+ }
+
+ if (ndm->ndm_state&NUD_PROBE) {
+ /* If we get this, kernel still has some valid
+ * address, but unicast probing failed and host
+ * is either dead or changed its mac address.
+ * Kernel is going to initiate broadcast resolution.
+ * OK, we invalidate our information as well.
+ */
+ if (dbdat.data && !IS_NEG(dbdat.data))
+ stats.app_neg++;
+
+ dbase->del(dbase, &dbkey, 0);
+ } else {
+ /* If we get this kernel does not have any information.
+ * If we have something tell this to kernel. */
+ stats.app_recv++;
+ if (dbdat.data && !IS_NEG(dbdat.data)) {
+ stats.app_success++;
+ respond_to_kernel(key.iface, key.addr, dbdat.data, dbdat.size);
+ return 0;
+ }
+
+ /* Sheeit! We have nothing to tell. */
+ /* If we have recent negative entry, be silent. */
+ if (dbdat.data && NEG_VALID(dbdat.data)) {
+ if (NEG_CNT(dbdat.data) >= active_probing) {
+ stats.app_suppressed++;
+ return 0;
+ }
+ do_acct = 1;
+ }
+ }
+
+ if (active_probing &&
+ queue_active_probe(ndm->ndm_ifindex, key.addr) == 0 &&
+ do_acct) {
+ NEG_CNT(dbdat.data)++;
+ dbase->put(dbase, &dbkey, &dbdat, 0);
+ }
+ } else if (n->nlmsg_type == RTM_NEWNEIGH) {
+ if (n->nlmsg_flags&NLM_F_REQUEST)
+ return 0;
+
+ if (ndm->ndm_state&NUD_FAILED) {
+ /* Kernel was not able to resolve. Host is dead.
+ * Create negative entry if it is not present
+ * or renew it if it is too old. */
+ if (!dbdat.data ||
+ !IS_NEG(dbdat.data) ||
+ !NEG_VALID(dbdat.data)) {
+ __u8 ndata[6];
+
+ stats.kern_neg++;
+ prepare_neg_entry(ndata, time(NULL));
+ dbdat.data = ndata;
+ dbdat.size = sizeof(ndata);
+ dbase->put(dbase, &dbkey, &dbdat, 0);
+ }
+ } else if (tb[NDA_LLADDR]) {
+ if (dbdat.data && !IS_NEG(dbdat.data)) {
+ if (memcmp(RTA_DATA(tb[NDA_LLADDR]), dbdat.data, dbdat.size) == 0)
+ return 0;
+ stats.kern_change++;
+ } else {
+ stats.kern_new++;
+ }
+ dbdat.data = RTA_DATA(tb[NDA_LLADDR]);
+ dbdat.size = RTA_PAYLOAD(tb[NDA_LLADDR]);
+ dbase->put(dbase, &dbkey, &dbdat, 0);
+ }
+ }
+ return 0;
+}
+
+static void load_initial_table(void)
+{
+ if (rtnl_neighdump_req(&rth, AF_INET, NULL) < 0) {
+ perror("dump request failed");
+ exit(1);
+ }
+
+}
+
+static void get_kern_msg(void)
+{
+ int status;
+ struct nlmsghdr *h;
+ struct sockaddr_nl nladdr = {};
+ struct iovec iov;
+ char buf[8192];
+ struct msghdr msg = {
+ (void *)&nladdr, sizeof(nladdr),
+ &iov, 1,
+ NULL, 0,
+ 0
+ };
+
+ iov.iov_base = buf;
+ iov.iov_len = sizeof(buf);
+
+ status = recvmsg(rth.fd, &msg, MSG_DONTWAIT);
+
+ if (status <= 0)
+ return;
+
+ if (msg.msg_namelen != sizeof(nladdr))
+ return;
+
+ if (nladdr.nl_pid)
+ return;
+
+ for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); ) {
+ int len = h->nlmsg_len;
+ int l = len - sizeof(*h);
+
+ if (l < 0 || len > status)
+ return;
+
+ if (do_one_request(h) < 0)
+ return;
+
+ status -= NLMSG_ALIGN(len);
+ h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
+ }
+}
+
+/* Receive gratuitous ARP messages and store them, that's all. */
+static void get_arp_pkt(void)
+{
+ unsigned char buf[1024];
+ struct sockaddr_ll sll;
+ socklen_t sll_len = sizeof(sll);
+ struct arphdr *a = (struct arphdr *)buf;
+ struct dbkey key;
+ DBT dbkey, dbdat;
+ int n;
+
+ n = recvfrom(pset[0].fd, buf, sizeof(buf), MSG_DONTWAIT,
+ (struct sockaddr *)&sll, &sll_len);
+ if (n < 0) {
+ if (errno != EINTR && errno != EAGAIN)
+ syslog(LOG_ERR, "recvfrom: %m");
+ return;
+ }
+
+ if (ifnum && !handle_if(sll.sll_ifindex))
+ return;
+
+ /* Sanity checks */
+
+ if (n < sizeof(*a) ||
+ (a->ar_op != htons(ARPOP_REQUEST) &&
+ a->ar_op != htons(ARPOP_REPLY)) ||
+ a->ar_pln != 4 ||
+ a->ar_pro != htons(ETH_P_IP) ||
+ a->ar_hln != sll.sll_halen ||
+ sizeof(*a) + 2*4 + 2*a->ar_hln > n)
+ return;
+
+ key.iface = sll.sll_ifindex;
+ memcpy(&key.addr, (char *)(a+1) + a->ar_hln, 4);
+
+ /* DAD message, ignore. */
+ if (key.addr == 0)
+ return;
+
+ dbkey.data = &key;
+ dbkey.size = sizeof(key);
+
+ if (dbase->get(dbase, &dbkey, &dbdat, 0) == 0 && !IS_NEG(dbdat.data)) {
+ if (memcmp(dbdat.data, a+1, dbdat.size) == 0)
+ return;
+ stats.arp_change++;
+ } else {
+ stats.arp_new++;
+ }
+
+ dbdat.data = a+1;
+ dbdat.size = a->ar_hln;
+ dbase->put(dbase, &dbkey, &dbdat, 0);
+}
+
+static void catch_signal(int sig, void (*handler)(int))
+{
+ struct sigaction sa = { .sa_handler = handler };
+
+#ifdef SA_INTERRUPT
+ sa.sa_flags = SA_INTERRUPT;
+#endif
+ sigaction(sig, &sa, NULL);
+}
+
+#include <setjmp.h>
+sigjmp_buf env;
+volatile int in_poll;
+
+static void sig_exit(int signo)
+{
+ do_exit = 1;
+ if (in_poll)
+ siglongjmp(env, 1);
+}
+
+static void sig_sync(int signo)
+{
+ do_sync = 1;
+ if (in_poll)
+ siglongjmp(env, 1);
+}
+
+static void sig_stats(int signo)
+{
+ do_sync = 1;
+ do_stats = 1;
+ if (in_poll)
+ siglongjmp(env, 1);
+}
+
+static void send_stats(void)
+{
+ syslog(LOG_INFO, "arp_rcv: n%lu c%lu app_rcv: tot %lu hits %lu bad %lu neg %lu sup %lu",
+ stats.arp_new, stats.arp_change,
+
+ stats.app_recv, stats.app_success,
+ stats.app_bad, stats.app_neg, stats.app_suppressed
+ );
+ syslog(LOG_INFO, "kern: n%lu c%lu neg %lu arp_send: %lu rlim %lu",
+ stats.kern_new, stats.kern_change, stats.kern_neg,
+
+ stats.probes_sent, stats.probes_suppressed
+ );
+ do_stats = 0;
+}
+
+
+int main(int argc, char **argv)
+{
+ int opt;
+ int do_list = 0;
+ char *do_load = NULL;
+
+ while ((opt = getopt(argc, argv, "h?b:lf:a:n:p:kR:B:")) != EOF) {
+ switch (opt) {
+ case 'b':
+ dbname = optarg;
+ break;
+ case 'f':
+ if (do_load) {
+ fprintf(stderr, "Duplicate option -f\n");
+ usage();
+ }
+ do_load = optarg;
+ break;
+ case 'l':
+ do_list = 1;
+ break;
+ case 'a':
+ active_probing = atoi(optarg);
+ break;
+ case 'n':
+ negative_timeout = atoi(optarg);
+ break;
+ case 'k':
+ no_kernel_broadcasts = 1;
+ break;
+ case 'p':
+ if ((poll_timeout = 1000 * strtod(optarg, NULL)) < 100) {
+ fprintf(stderr, "Invalid poll timeout\n");
+ exit(-1);
+ }
+ break;
+ case 'R':
+ if ((broadcast_rate = atoi(optarg)) <= 0 ||
+ (broadcast_rate = 1000/broadcast_rate) <= 0) {
+ fprintf(stderr, "Invalid ARP rate\n");
+ exit(-1);
+ }
+ break;
+ case 'B':
+ if ((broadcast_burst = atoi(optarg)) <= 0 ||
+ (broadcast_burst = 1000*broadcast_burst) <= 0) {
+ fprintf(stderr, "Invalid ARP burst\n");
+ exit(-1);
+ }
+ break;
+ case 'h':
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0) {
+ ifnum = argc;
+ ifnames = argv;
+ ifvec = malloc(argc*sizeof(int));
+ if (!ifvec) {
+ perror("malloc");
+ exit(-1);
+ }
+ }
+
+ if ((udp_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ perror("socket");
+ exit(-1);
+ }
+
+ if (ifnum) {
+ int i;
+ struct ifreq ifr = {};
+
+ for (i = 0; i < ifnum; i++) {
+ if (get_ifname(ifr.ifr_name, ifnames[i]))
+ invarg("not a valid ifname", ifnames[i]);
+ if (ioctl(udp_sock, SIOCGIFINDEX, &ifr)) {
+ perror("ioctl(SIOCGIFINDEX)");
+ exit(-1);
+ }
+ ifvec[i] = ifr.ifr_ifindex;
+ }
+ }
+
+ dbase = dbopen(dbname, O_CREAT|O_RDWR, 0644, DB_HASH, NULL);
+ if (dbase == NULL) {
+ perror("db_open");
+ exit(-1);
+ }
+
+ if (do_load) {
+ char buf[128];
+ FILE *fp;
+ struct dbkey k;
+ DBT dbkey, dbdat;
+
+ dbkey.data = &k;
+ dbkey.size = sizeof(k);
+
+ if (strcmp(do_load, "-") == 0 || strcmp(do_load, "--") == 0) {
+ fp = stdin;
+ } else if ((fp = fopen(do_load, "r")) == NULL) {
+ perror("fopen");
+ goto do_abort;
+ }
+
+ buf[sizeof(buf)-1] = 0;
+ while (fgets(buf, sizeof(buf), fp)) {
+ __u8 b1[6];
+ char ipbuf[128];
+ char macbuf[128];
+
+ if (buf[0] == '#')
+ continue;
+
+ if (sscanf(buf, "%u%s%s", &k.iface, ipbuf, macbuf) != 3) {
+ fprintf(stderr, "Wrong format of input file \"%s\"\n", do_load);
+ goto do_abort;
+ }
+ if (strncmp(macbuf, "FAILED:", 7) == 0)
+ continue;
+ if (!inet_aton(ipbuf, (struct in_addr *)&k.addr)) {
+ fprintf(stderr, "Invalid IP address: \"%s\"\n", ipbuf);
+ goto do_abort;
+ }
+
+ if (ll_addr_a2n((char *) b1, 6, macbuf) != 6)
+ goto do_abort;
+ dbdat.size = 6;
+
+ if (dbase->put(dbase, &dbkey, &dbdat, 0)) {
+ perror("hash->put");
+ goto do_abort;
+ }
+ }
+ dbase->sync(dbase, 0);
+ if (fp != stdin)
+ fclose(fp);
+ }
+
+ if (do_list) {
+ DBT dbkey, dbdat;
+
+ printf("%-8s %-15s %s\n", "#Ifindex", "IP", "MAC");
+ while (dbase->seq(dbase, &dbkey, &dbdat, R_NEXT) == 0) {
+ struct dbkey *key = dbkey.data;
+
+ if (handle_if(key->iface)) {
+ if (!IS_NEG(dbdat.data)) {
+ char b1[18];
+
+ printf("%-8d %-15s %s\n",
+ key->iface,
+ inet_ntoa(*(struct in_addr *)&key->addr),
+ ll_addr_n2a(dbdat.data, 6, ARPHRD_ETHER, b1, 18));
+ } else {
+ printf("%-8d %-15s FAILED: %dsec ago\n",
+ key->iface,
+ inet_ntoa(*(struct in_addr *)&key->addr),
+ NEG_AGE(dbdat.data));
+ }
+ }
+ }
+ }
+
+ if (do_load || do_list)
+ goto out;
+
+ pset[0].fd = socket(PF_PACKET, SOCK_DGRAM, 0);
+ if (pset[0].fd < 0) {
+ perror("socket");
+ exit(-1);
+ }
+
+ if (1) {
+ struct sockaddr_ll sll = {
+ .sll_family = AF_PACKET,
+ .sll_protocol = htons(ETH_P_ARP),
+ .sll_ifindex = (ifnum == 1 ? ifvec[0] : 0),
+ };
+
+ if (bind(pset[0].fd, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
+ perror("bind");
+ goto do_abort;
+ }
+ }
+
+ if (rtnl_open(&rth, RTMGRP_NEIGH) < 0) {
+ perror("rtnl_open");
+ goto do_abort;
+ }
+ pset[1].fd = rth.fd;
+
+ load_initial_table();
+
+ if (daemon(0, 0)) {
+ perror("arpd: daemon");
+ goto do_abort;
+ }
+
+ openlog("arpd", LOG_PID | LOG_CONS, LOG_DAEMON);
+ catch_signal(SIGINT, sig_exit);
+ catch_signal(SIGTERM, sig_exit);
+ catch_signal(SIGHUP, sig_sync);
+ catch_signal(SIGUSR1, sig_stats);
+
+#define EVENTS (POLLIN|POLLPRI|POLLERR|POLLHUP)
+ pset[0].events = EVENTS;
+ pset[0].revents = 0;
+ pset[1].events = EVENTS;
+ pset[1].revents = 0;
+
+ sigsetjmp(env, 1);
+
+ for (;;) {
+ in_poll = 1;
+
+ if (do_exit)
+ break;
+ if (do_sync) {
+ in_poll = 0;
+ dbase->sync(dbase, 0);
+ do_sync = 0;
+ in_poll = 1;
+ }
+ if (do_stats)
+ send_stats();
+ if (poll(pset, 2, poll_timeout) > 0) {
+ in_poll = 0;
+ if (pset[0].revents&EVENTS)
+ get_arp_pkt();
+ if (pset[1].revents&EVENTS)
+ get_kern_msg();
+ } else {
+ do_sync = 1;
+ }
+ }
+
+ undo_sysctl_adjustments();
+out:
+ dbase->close(dbase);
+ exit(0);
+
+do_abort:
+ dbase->close(dbase);
+ exit(-1);
+}
diff --git a/misc/ifstat.c b/misc/ifstat.c
new file mode 100644
index 0000000..60efe6c
--- /dev/null
+++ b/misc/ifstat.c
@@ -0,0 +1,1034 @@
+/*
+ * ifstat.c handy utility to read net interface statistics
+ *
+ * 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 <errno.h>
+#include <time.h>
+#include <sys/time.h>
+#include <fnmatch.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/poll.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <math.h>
+#include <getopt.h>
+
+#include <linux/if.h>
+#include <linux/if_link.h>
+
+#include "libnetlink.h"
+#include "json_writer.h"
+#include "SNAPSHOT.h"
+#include "utils.h"
+
+int dump_zeros;
+int reset_history;
+int ignore_history;
+int no_output;
+int json_output;
+int no_update;
+int scan_interval;
+int time_constant;
+int show_errors;
+double W;
+char **patterns;
+int npatterns;
+bool is_extended;
+int filter_type;
+int sub_type;
+
+char info_source[128];
+int source_mismatch;
+
+#define MAXS (sizeof(struct rtnl_link_stats)/sizeof(__u32))
+#define NO_SUB_TYPE 0xffff
+
+struct ifstat_ent {
+ struct ifstat_ent *next;
+ char *name;
+ int ifindex;
+ __u64 val[MAXS];
+ double rate[MAXS];
+ __u32 ival[MAXS];
+};
+
+static const char *stats[MAXS] = {
+ "rx_packets",
+ "tx_packets",
+ "rx_bytes",
+ "tx_bytes",
+ "rx_errors",
+ "tx_errors",
+ "rx_dropped",
+ "tx_dropped",
+ "multicast",
+ "collisions",
+ "rx_length_errors",
+ "rx_over_errors",
+ "rx_crc_errors",
+ "rx_frame_errors",
+ "rx_fifo_errors",
+ "rx_missed_errors",
+ "tx_aborted_errors",
+ "tx_carrier_errors",
+ "tx_fifo_errors",
+ "tx_heartbeat_errors",
+ "tx_window_errors",
+ "rx_compressed",
+ "tx_compressed"
+};
+
+struct ifstat_ent *kern_db;
+struct ifstat_ent *hist_db;
+
+static int match(const char *id)
+{
+ int i;
+
+ if (npatterns == 0)
+ return 1;
+
+ for (i = 0; i < npatterns; i++) {
+ if (!fnmatch(patterns[i], id, 0))
+ return 1;
+ }
+ return 0;
+}
+
+static int get_nlmsg_extended(struct nlmsghdr *m, void *arg)
+{
+ struct if_stats_msg *ifsm = NLMSG_DATA(m);
+ struct rtattr *tb[IFLA_STATS_MAX+1];
+ int len = m->nlmsg_len;
+ struct ifstat_ent *n;
+
+ if (m->nlmsg_type != RTM_NEWSTATS)
+ return 0;
+
+ len -= NLMSG_LENGTH(sizeof(*ifsm));
+ if (len < 0)
+ return -1;
+
+ parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len);
+ if (tb[filter_type] == NULL)
+ return 0;
+
+ n = malloc(sizeof(*n));
+ if (!n)
+ abort();
+
+ n->ifindex = ifsm->ifindex;
+ n->name = strdup(ll_index_to_name(ifsm->ifindex));
+
+ if (sub_type == NO_SUB_TYPE) {
+ memcpy(&n->val, RTA_DATA(tb[filter_type]), sizeof(n->val));
+ } else {
+ struct rtattr *attr;
+
+ attr = parse_rtattr_one_nested(sub_type, tb[filter_type]);
+ if (attr == NULL) {
+ free(n);
+ return 0;
+ }
+ memcpy(&n->val, RTA_DATA(attr), sizeof(n->val));
+ }
+ memset(&n->rate, 0, sizeof(n->rate));
+ n->next = kern_db;
+ kern_db = n;
+ return 0;
+}
+
+static int get_nlmsg(struct nlmsghdr *m, void *arg)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(m);
+ struct rtattr *tb[IFLA_MAX+1];
+ int len = m->nlmsg_len;
+ struct ifstat_ent *n;
+ int i;
+
+ if (m->nlmsg_type != RTM_NEWLINK)
+ return 0;
+
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ return -1;
+
+ if (!(ifi->ifi_flags&IFF_UP))
+ return 0;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+ if (tb[IFLA_IFNAME] == NULL || tb[IFLA_STATS] == NULL)
+ return 0;
+
+ n = malloc(sizeof(*n));
+ if (!n)
+ abort();
+ n->ifindex = ifi->ifi_index;
+ n->name = strdup(RTA_DATA(tb[IFLA_IFNAME]));
+ memcpy(&n->ival, RTA_DATA(tb[IFLA_STATS]), sizeof(n->ival));
+ memset(&n->rate, 0, sizeof(n->rate));
+ for (i = 0; i < MAXS; i++)
+ n->val[i] = n->ival[i];
+ n->next = kern_db;
+ kern_db = n;
+ return 0;
+}
+
+static void load_info(void)
+{
+ struct ifstat_ent *db, *n;
+ struct rtnl_handle rth;
+ __u32 filter_mask;
+
+ if (rtnl_open(&rth, 0) < 0)
+ exit(1);
+
+ if (is_extended) {
+ ll_init_map(&rth);
+ filter_mask = IFLA_STATS_FILTER_BIT(filter_type);
+ if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC,
+ filter_mask) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ if (rtnl_dump_filter(&rth, get_nlmsg_extended, NULL) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ } else {
+ if (rtnl_linkdump_req(&rth, AF_INET) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ if (rtnl_dump_filter(&rth, get_nlmsg, NULL) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ }
+
+ rtnl_close(&rth);
+
+ db = kern_db;
+ kern_db = NULL;
+
+ while (db) {
+ n = db;
+ db = db->next;
+ n->next = kern_db;
+ kern_db = n;
+ }
+}
+
+static void load_raw_table(FILE *fp)
+{
+ char buf[4096];
+ struct ifstat_ent *db = NULL;
+ struct ifstat_ent *n;
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ char *p;
+ char *next;
+ int i;
+
+ if (buf[0] == '#') {
+ buf[strlen(buf)-1] = 0;
+ if (info_source[0] && strcmp(info_source, buf+1))
+ source_mismatch = 1;
+ strncpy(info_source, buf+1, sizeof(info_source)-1);
+ continue;
+ }
+ if ((n = malloc(sizeof(*n))) == NULL)
+ abort();
+
+ if (!(p = strchr(buf, ' ')))
+ abort();
+ *p++ = 0;
+
+ if (sscanf(buf, "%d", &n->ifindex) != 1)
+ abort();
+ if (!(next = strchr(p, ' ')))
+ abort();
+ *next++ = 0;
+
+ n->name = strdup(p);
+ p = next;
+
+ for (i = 0; i < MAXS; i++) {
+ unsigned int rate;
+
+ if (!(next = strchr(p, ' ')))
+ abort();
+ *next++ = 0;
+ if (sscanf(p, "%llu", n->val+i) != 1)
+ abort();
+ n->ival[i] = (__u32)n->val[i];
+ p = next;
+ if (!(next = strchr(p, ' ')))
+ abort();
+ *next++ = 0;
+ if (sscanf(p, "%u", &rate) != 1)
+ abort();
+ n->rate[i] = rate;
+ p = next;
+ }
+ n->next = db;
+ db = n;
+ }
+
+ while (db) {
+ n = db;
+ db = db->next;
+ n->next = kern_db;
+ kern_db = n;
+ }
+}
+
+static void dump_raw_db(FILE *fp, int to_hist)
+{
+ json_writer_t *jw = json_output ? jsonw_new(fp) : NULL;
+ struct ifstat_ent *n, *h;
+
+ h = hist_db;
+ if (jw) {
+ jsonw_start_object(jw);
+ jsonw_pretty(jw, pretty);
+ jsonw_name(jw, info_source);
+ jsonw_start_object(jw);
+ } else
+ fprintf(fp, "#%s\n", info_source);
+
+ for (n = kern_db; n; n = n->next) {
+ int i;
+ unsigned long long *vals = n->val;
+ double *rates = n->rate;
+
+ if (!match(n->name)) {
+ struct ifstat_ent *h1;
+
+ if (!to_hist)
+ continue;
+ for (h1 = h; h1; h1 = h1->next) {
+ if (h1->ifindex == n->ifindex) {
+ vals = h1->val;
+ rates = h1->rate;
+ h = h1->next;
+ break;
+ }
+ }
+ }
+
+ if (jw) {
+ jsonw_name(jw, n->name);
+ jsonw_start_object(jw);
+
+ for (i = 0; i < MAXS && stats[i]; i++)
+ jsonw_uint_field(jw, stats[i], vals[i]);
+ jsonw_end_object(jw);
+ } else {
+ fprintf(fp, "%d %s ", n->ifindex, n->name);
+ for (i = 0; i < MAXS; i++)
+ fprintf(fp, "%llu %u ", vals[i],
+ (unsigned int)rates[i]);
+ fprintf(fp, "\n");
+ }
+ }
+ if (jw) {
+ jsonw_end_object(jw);
+
+ jsonw_end_object(jw);
+ jsonw_destroy(&jw);
+ }
+}
+
+/* use communication definitions of meg/kilo etc */
+static const unsigned long long giga = 1000000000ull;
+static const unsigned long long mega = 1000000;
+static const unsigned long long kilo = 1000;
+
+static void format_rate(FILE *fp, const unsigned long long *vals,
+ const double *rates, int i)
+{
+ char temp[64];
+
+ if (vals[i] > giga)
+ fprintf(fp, "%7lluM ", vals[i]/mega);
+ else if (vals[i] > mega)
+ fprintf(fp, "%7lluK ", vals[i]/kilo);
+ else
+ fprintf(fp, "%8llu ", vals[i]);
+
+ if (rates[i] > mega) {
+ sprintf(temp, "%uM", (unsigned int)(rates[i]/mega));
+ fprintf(fp, "%-6s ", temp);
+ } else if (rates[i] > kilo) {
+ sprintf(temp, "%uK", (unsigned int)(rates[i]/kilo));
+ fprintf(fp, "%-6s ", temp);
+ } else
+ fprintf(fp, "%-6u ", (unsigned int)rates[i]);
+}
+
+static void format_pair(FILE *fp, const unsigned long long *vals, int i, int k)
+{
+ char temp[64];
+
+ if (vals[i] > giga)
+ fprintf(fp, "%7lluM ", vals[i]/mega);
+ else if (vals[i] > mega)
+ fprintf(fp, "%7lluK ", vals[i]/kilo);
+ else
+ fprintf(fp, "%8llu ", vals[i]);
+
+ if (vals[k] > giga) {
+ sprintf(temp, "%uM", (unsigned int)(vals[k]/mega));
+ fprintf(fp, "%-6s ", temp);
+ } else if (vals[k] > mega) {
+ sprintf(temp, "%uK", (unsigned int)(vals[k]/kilo));
+ fprintf(fp, "%-6s ", temp);
+ } else
+ fprintf(fp, "%-6u ", (unsigned int)vals[k]);
+}
+
+static void print_head(FILE *fp)
+{
+ fprintf(fp, "#%s\n", info_source);
+ fprintf(fp, "%-15s ", "Interface");
+
+ fprintf(fp, "%8s/%-6s ", "RX Pkts", "Rate");
+ fprintf(fp, "%8s/%-6s ", "TX Pkts", "Rate");
+ fprintf(fp, "%8s/%-6s ", "RX Data", "Rate");
+ fprintf(fp, "%8s/%-6s\n", "TX Data", "Rate");
+
+ if (!show_errors) {
+ fprintf(fp, "%-15s ", "");
+ fprintf(fp, "%8s/%-6s ", "RX Errs", "Drop");
+ fprintf(fp, "%8s/%-6s ", "TX Errs", "Drop");
+ fprintf(fp, "%8s/%-6s ", "RX Over", "Rate");
+ fprintf(fp, "%8s/%-6s\n", "TX Coll", "Rate");
+ } else {
+ fprintf(fp, "%-15s ", "");
+ fprintf(fp, "%8s/%-6s ", "RX Errs", "Rate");
+ fprintf(fp, "%8s/%-6s ", "RX Drop", "Rate");
+ fprintf(fp, "%8s/%-6s ", "RX Over", "Rate");
+ fprintf(fp, "%8s/%-6s\n", "RX Leng", "Rate");
+
+ fprintf(fp, "%-15s ", "");
+ fprintf(fp, "%8s/%-6s ", "RX Crc", "Rate");
+ fprintf(fp, "%8s/%-6s ", "RX Frm", "Rate");
+ fprintf(fp, "%8s/%-6s ", "RX Fifo", "Rate");
+ fprintf(fp, "%8s/%-6s\n", "RX Miss", "Rate");
+
+ fprintf(fp, "%-15s ", "");
+ fprintf(fp, "%8s/%-6s ", "TX Errs", "Rate");
+ fprintf(fp, "%8s/%-6s ", "TX Drop", "Rate");
+ fprintf(fp, "%8s/%-6s ", "TX Coll", "Rate");
+ fprintf(fp, "%8s/%-6s\n", "TX Carr", "Rate");
+
+ fprintf(fp, "%-15s ", "");
+ fprintf(fp, "%8s/%-6s ", "TX Abrt", "Rate");
+ fprintf(fp, "%8s/%-6s ", "TX Fifo", "Rate");
+ fprintf(fp, "%8s/%-6s ", "TX Hear", "Rate");
+ fprintf(fp, "%8s/%-6s\n", "TX Wind", "Rate");
+ }
+}
+
+static void print_one_json(json_writer_t *jw, const struct ifstat_ent *n,
+ const unsigned long long *vals)
+{
+ int i, m = show_errors ? 20 : 10;
+
+ jsonw_name(jw, n->name);
+ jsonw_start_object(jw);
+
+ for (i = 0; i < m && stats[i]; i++)
+ jsonw_uint_field(jw, stats[i], vals[i]);
+
+ jsonw_end_object(jw);
+}
+
+static void print_one_if(FILE *fp, const struct ifstat_ent *n,
+ const unsigned long long *vals)
+{
+ int i;
+
+ fprintf(fp, "%-15s ", n->name);
+ for (i = 0; i < 4; i++)
+ format_rate(fp, vals, n->rate, i);
+ fprintf(fp, "\n");
+
+ if (!show_errors) {
+ fprintf(fp, "%-15s ", "");
+ format_pair(fp, vals, 4, 6);
+ format_pair(fp, vals, 5, 7);
+ format_rate(fp, vals, n->rate, 11);
+ format_rate(fp, vals, n->rate, 9);
+ fprintf(fp, "\n");
+ } else {
+ fprintf(fp, "%-15s ", "");
+ format_rate(fp, vals, n->rate, 4);
+ format_rate(fp, vals, n->rate, 6);
+ format_rate(fp, vals, n->rate, 11);
+ format_rate(fp, vals, n->rate, 10);
+ fprintf(fp, "\n");
+
+ fprintf(fp, "%-15s ", "");
+ format_rate(fp, vals, n->rate, 12);
+ format_rate(fp, vals, n->rate, 13);
+ format_rate(fp, vals, n->rate, 14);
+ format_rate(fp, vals, n->rate, 15);
+ fprintf(fp, "\n");
+
+ fprintf(fp, "%-15s ", "");
+ format_rate(fp, vals, n->rate, 5);
+ format_rate(fp, vals, n->rate, 7);
+ format_rate(fp, vals, n->rate, 9);
+ format_rate(fp, vals, n->rate, 17);
+ fprintf(fp, "\n");
+
+ fprintf(fp, "%-15s ", "");
+ format_rate(fp, vals, n->rate, 16);
+ format_rate(fp, vals, n->rate, 18);
+ format_rate(fp, vals, n->rate, 19);
+ format_rate(fp, vals, n->rate, 20);
+ fprintf(fp, "\n");
+ }
+}
+
+static void dump_kern_db(FILE *fp)
+{
+ json_writer_t *jw = json_output ? jsonw_new(fp) : NULL;
+ struct ifstat_ent *n;
+
+ if (jw) {
+ jsonw_start_object(jw);
+ jsonw_pretty(jw, pretty);
+ jsonw_name(jw, info_source);
+ jsonw_start_object(jw);
+ } else
+ print_head(fp);
+
+ for (n = kern_db; n; n = n->next) {
+ if (!match(n->name))
+ continue;
+
+ if (jw)
+ print_one_json(jw, n, n->val);
+ else
+ print_one_if(fp, n, n->val);
+ }
+ if (jw) {
+ jsonw_end_object(jw);
+
+ jsonw_end_object(jw);
+ jsonw_destroy(&jw);
+ }
+}
+
+static void dump_incr_db(FILE *fp)
+{
+ struct ifstat_ent *n, *h;
+ json_writer_t *jw = json_output ? jsonw_new(fp) : NULL;
+
+ h = hist_db;
+ if (jw) {
+ jsonw_start_object(jw);
+ jsonw_pretty(jw, pretty);
+ jsonw_name(jw, info_source);
+ jsonw_start_object(jw);
+ } else
+ print_head(fp);
+
+ for (n = kern_db; n; n = n->next) {
+ int i;
+ unsigned long long vals[MAXS];
+ struct ifstat_ent *h1;
+
+ memcpy(vals, n->val, sizeof(vals));
+
+ for (h1 = h; h1; h1 = h1->next) {
+ if (h1->ifindex == n->ifindex) {
+ for (i = 0; i < MAXS; i++)
+ vals[i] -= h1->val[i];
+ h = h1->next;
+ break;
+ }
+ }
+ if (!match(n->name))
+ continue;
+
+ if (jw)
+ print_one_json(jw, n, n->val);
+ else
+ print_one_if(fp, n, vals);
+ }
+
+ if (jw) {
+ jsonw_end_object(jw);
+
+ jsonw_end_object(jw);
+ jsonw_destroy(&jw);
+ }
+}
+
+static int children;
+
+static void sigchild(int signo)
+{
+}
+
+static void update_db(int interval)
+{
+ struct ifstat_ent *n, *h;
+
+ n = kern_db;
+ kern_db = NULL;
+
+ load_info();
+
+ h = kern_db;
+ kern_db = n;
+
+ for (n = kern_db; n; n = n->next) {
+ struct ifstat_ent *h1;
+
+ for (h1 = h; h1; h1 = h1->next) {
+ if (h1->ifindex == n->ifindex) {
+ int i;
+
+ for (i = 0; i < MAXS; i++) {
+ if ((long)(h1->ival[i] - n->ival[i]) < 0) {
+ memset(n->ival, 0, sizeof(n->ival));
+ break;
+ }
+ }
+ for (i = 0; i < MAXS; i++) {
+ double sample;
+ __u64 incr;
+
+ if (is_extended) {
+ incr = h1->val[i] - n->val[i];
+ n->val[i] = h1->val[i];
+ } else {
+ incr = (__u32) (h1->ival[i] - n->ival[i]);
+ n->val[i] += incr;
+ n->ival[i] = h1->ival[i];
+ }
+
+ sample = (double)(incr*1000)/interval;
+ if (interval >= scan_interval) {
+ n->rate[i] += W*(sample-n->rate[i]);
+ } else if (interval >= 1000) {
+ if (interval >= time_constant) {
+ n->rate[i] = sample;
+ } else {
+ double w = W*(double)interval/scan_interval;
+
+ n->rate[i] += w*(sample-n->rate[i]);
+ }
+ }
+ }
+
+ while (h != h1) {
+ struct ifstat_ent *tmp = h;
+
+ h = h->next;
+ free(tmp->name);
+ free(tmp);
+ };
+ h = h1->next;
+ free(h1->name);
+ free(h1);
+ break;
+ }
+ }
+ }
+}
+
+#define T_DIFF(a, b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
+
+
+static void server_loop(int fd)
+{
+ struct timeval snaptime = { 0 };
+ struct pollfd p;
+
+ p.fd = fd;
+ p.events = p.revents = POLLIN;
+
+ sprintf(info_source, "%d.%lu sampling_interval=%d time_const=%d",
+ getpid(), (unsigned long)random(), scan_interval/1000, time_constant/1000);
+
+ load_info();
+
+ for (;;) {
+ int status;
+ time_t tdiff;
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+ tdiff = T_DIFF(now, snaptime);
+ if (tdiff >= scan_interval) {
+ update_db(tdiff);
+ snaptime = now;
+ tdiff = 0;
+ }
+
+ if (poll(&p, 1, scan_interval - tdiff) > 0
+ && (p.revents&POLLIN)) {
+ int clnt = accept(fd, NULL, NULL);
+
+ if (clnt >= 0) {
+ pid_t pid;
+
+ if (children >= 5) {
+ close(clnt);
+ } else if ((pid = fork()) != 0) {
+ if (pid > 0)
+ children++;
+ close(clnt);
+ } else {
+ FILE *fp = fdopen(clnt, "w");
+
+ if (fp)
+ dump_raw_db(fp, 0);
+ exit(0);
+ }
+ }
+ }
+ while (children && waitpid(-1, &status, WNOHANG) > 0)
+ children--;
+ }
+}
+
+static int verify_forging(int fd)
+{
+ struct ucred cred;
+ socklen_t olen = sizeof(cred);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void *)&cred, &olen) ||
+ olen < sizeof(cred))
+ return -1;
+ if (cred.uid == getuid() || cred.uid == 0)
+ return 0;
+ return -1;
+}
+
+static void xstat_usage(void)
+{
+ fprintf(stderr,
+"Usage: ifstat supported xstats:\n"
+" cpu_hits Counts only packets that went via the CPU.\n");
+}
+
+struct extended_stats_options_t {
+ char *name;
+ int id;
+ int sub_type;
+};
+
+/* Note: if one xstat name is subset of another, it should be before it in this
+ * list.
+ * Name length must be under 64 chars.
+ */
+static const struct extended_stats_options_t extended_stats_options[] = {
+ {"cpu_hits", IFLA_STATS_LINK_OFFLOAD_XSTATS, IFLA_OFFLOAD_XSTATS_CPU_HIT},
+};
+
+static const char *get_filter_type(const char *name)
+{
+ int name_len;
+ int i;
+
+ name_len = strlen(name);
+ for (i = 0; i < ARRAY_SIZE(extended_stats_options); i++) {
+ const struct extended_stats_options_t *xstat;
+
+ xstat = &extended_stats_options[i];
+ if (strncmp(name, xstat->name, name_len) == 0) {
+ filter_type = xstat->id;
+ sub_type = xstat->sub_type;
+ return xstat->name;
+ }
+ }
+
+ fprintf(stderr, "invalid ifstat extension %s\n", name);
+ xstat_usage();
+ return NULL;
+}
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+"Usage: ifstat [OPTION] [ PATTERN [ PATTERN ] ]\n"
+" -h, --help this message\n"
+" -a, --ignore ignore history\n"
+" -d, --scan=SECS sample every statistics every SECS\n"
+" -e, --errors show errors\n"
+" -j, --json format output in JSON\n"
+" -n, --nooutput do history only\n"
+" -p, --pretty pretty print\n"
+" -r, --reset reset history\n"
+" -s, --noupdate don't update history\n"
+" -t, --interval=SECS report average over the last SECS\n"
+" -V, --version output version information\n"
+" -z, --zeros show entries with zero activity\n"
+" -x, --extended=TYPE show extended stats of TYPE\n");
+
+ exit(-1);
+}
+
+static const struct option longopts[] = {
+ { "help", 0, 0, 'h' },
+ { "ignore", 0, 0, 'a' },
+ { "scan", 1, 0, 'd'},
+ { "errors", 0, 0, 'e' },
+ { "nooutput", 0, 0, 'n' },
+ { "json", 0, 0, 'j' },
+ { "reset", 0, 0, 'r' },
+ { "pretty", 0, 0, 'p' },
+ { "noupdate", 0, 0, 's' },
+ { "interval", 1, 0, 't' },
+ { "version", 0, 0, 'V' },
+ { "zeros", 0, 0, 'z' },
+ { "extended", 1, 0, 'x'},
+ { 0 }
+};
+
+int main(int argc, char *argv[])
+{
+ char hist_name[128];
+ struct sockaddr_un sun;
+ FILE *hist_fp = NULL;
+ const char *stats_type = NULL;
+ int ch;
+ int fd;
+
+ is_extended = false;
+ while ((ch = getopt_long(argc, argv, "hjpvVzrnasd:t:ex:",
+ longopts, NULL)) != EOF) {
+ switch (ch) {
+ case 'z':
+ dump_zeros = 1;
+ break;
+ case 'r':
+ reset_history = 1;
+ break;
+ case 'a':
+ ignore_history = 1;
+ break;
+ case 's':
+ no_update = 1;
+ break;
+ case 'n':
+ no_output = 1;
+ break;
+ case 'e':
+ show_errors = 1;
+ break;
+ case 'j':
+ json_output = 1;
+ break;
+ case 'p':
+ pretty = 1;
+ break;
+ case 'd':
+ scan_interval = atoi(optarg) * 1000;
+ if (scan_interval <= 0) {
+ fprintf(stderr, "ifstat: invalid scan interval\n");
+ exit(-1);
+ }
+ break;
+ case 't':
+ time_constant = atoi(optarg);
+ if (time_constant <= 0) {
+ fprintf(stderr, "ifstat: invalid time constant divisor\n");
+ exit(-1);
+ }
+ break;
+ case 'x':
+ stats_type = optarg;
+ is_extended = true;
+ break;
+ case 'v':
+ case 'V':
+ printf("ifstat utility, iproute2-ss%s\n", SNAPSHOT);
+ exit(0);
+ case 'h':
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (stats_type) {
+ stats_type = get_filter_type(stats_type);
+ if (!stats_type)
+ exit(-1);
+ }
+
+ sun.sun_family = AF_UNIX;
+ sun.sun_path[0] = 0;
+ sprintf(sun.sun_path+1, "ifstat%d", getuid());
+
+ if (scan_interval > 0) {
+ if (time_constant == 0)
+ time_constant = 60;
+ time_constant *= 1000;
+ W = 1 - 1/exp(log(10)*(double)scan_interval/time_constant);
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ perror("ifstat: socket");
+ exit(-1);
+ }
+ if (bind(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) < 0) {
+ perror("ifstat: bind");
+ exit(-1);
+ }
+ if (listen(fd, 5) < 0) {
+ perror("ifstat: listen");
+ exit(-1);
+ }
+ if (daemon(0, 0)) {
+ perror("ifstat: daemon");
+ exit(-1);
+ }
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGCHLD, sigchild);
+ server_loop(fd);
+ exit(0);
+ }
+
+ patterns = argv;
+ npatterns = argc;
+
+ if (getenv("IFSTAT_HISTORY"))
+ snprintf(hist_name, sizeof(hist_name),
+ "%s", getenv("IFSTAT_HISTORY"));
+ else
+ if (!stats_type)
+ snprintf(hist_name, sizeof(hist_name),
+ "%s/.ifstat.u%d", P_tmpdir, getuid());
+ else
+ snprintf(hist_name, sizeof(hist_name),
+ "%s/.%s_ifstat.u%d", P_tmpdir, stats_type,
+ getuid());
+
+ if (reset_history)
+ unlink(hist_name);
+
+ if (!ignore_history || !no_update) {
+ struct stat stb;
+
+ fd = open(hist_name, O_RDWR|O_CREAT|O_NOFOLLOW, 0600);
+ if (fd < 0) {
+ perror("ifstat: open history file");
+ exit(-1);
+ }
+ if ((hist_fp = fdopen(fd, "r+")) == NULL) {
+ perror("ifstat: fdopen history file");
+ exit(-1);
+ }
+ if (flock(fileno(hist_fp), LOCK_EX)) {
+ perror("ifstat: flock history file");
+ exit(-1);
+ }
+ if (fstat(fileno(hist_fp), &stb) != 0) {
+ perror("ifstat: fstat history file");
+ exit(-1);
+ }
+ if (stb.st_nlink != 1 || stb.st_uid != getuid()) {
+ fprintf(stderr, "ifstat: something is so wrong with history file, that I prefer not to proceed.\n");
+ exit(-1);
+ }
+ if (!ignore_history) {
+ FILE *tfp;
+ long uptime = -1;
+
+ if ((tfp = fopen("/proc/uptime", "r")) != NULL) {
+ if (fscanf(tfp, "%ld", &uptime) != 1)
+ uptime = -1;
+ fclose(tfp);
+ }
+ if (uptime >= 0 && time(NULL) >= stb.st_mtime+uptime) {
+ fprintf(stderr, "ifstat: history is aged out, resetting\n");
+ if (ftruncate(fileno(hist_fp), 0))
+ perror("ifstat: ftruncate");
+ }
+ }
+
+ load_raw_table(hist_fp);
+
+ hist_db = kern_db;
+ kern_db = NULL;
+ }
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 &&
+ (connect(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) == 0
+ || (strcpy(sun.sun_path+1, "ifstat0"),
+ connect(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) == 0))
+ && verify_forging(fd) == 0) {
+ FILE *sfp = fdopen(fd, "r");
+
+ if (!sfp) {
+ fprintf(stderr, "ifstat: fdopen failed: %s\n",
+ strerror(errno));
+ close(fd);
+ } else {
+ load_raw_table(sfp);
+ if (hist_db && source_mismatch) {
+ fprintf(stderr, "ifstat: history is stale, ignoring it.\n");
+ hist_db = NULL;
+ }
+ fclose(sfp);
+ }
+ } else {
+ if (fd >= 0)
+ close(fd);
+ if (hist_db && info_source[0] && strcmp(info_source, "kernel")) {
+ fprintf(stderr, "ifstat: history is stale, ignoring it.\n");
+ hist_db = NULL;
+ info_source[0] = 0;
+ }
+ load_info();
+ if (info_source[0] == 0)
+ strcpy(info_source, "kernel");
+ }
+
+ if (!no_output) {
+ if (ignore_history || hist_db == NULL)
+ dump_kern_db(stdout);
+ else
+ dump_incr_db(stdout);
+ }
+
+ if (!no_update) {
+ if (ftruncate(fileno(hist_fp), 0))
+ perror("ifstat: ftruncate");
+ rewind(hist_fp);
+
+ json_output = 0;
+ dump_raw_db(hist_fp, 1);
+ fclose(hist_fp);
+ }
+ exit(0);
+}
diff --git a/misc/lnstat.c b/misc/lnstat.c
new file mode 100644
index 0000000..e3c8421
--- /dev/null
+++ b/misc/lnstat.c
@@ -0,0 +1,377 @@
+/* lnstat - Unified linux network statistics
+ *
+ * Copyright (C) 2004 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Development of this code was funded by Astaro AG, http://www.astaro.com/
+ *
+ * Based on original concept and ideas from predecessor rtstat.c:
+ *
+ * Copyright 2001 by Robert Olsson <robert.olsson@its.uu.se>
+ * Uppsala University, Sweden
+ *
+ * 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.
+ *
+ */
+
+/* Maximum number of fields that can be displayed */
+#define MAX_FIELDS 128
+
+/* Maximum number of header lines */
+#define HDR_LINES 10
+
+/* default field width if none specified */
+#define FIELD_WIDTH_DEFAULT 8
+#define FIELD_WIDTH_MAX 20
+
+#define DEFAULT_INTERVAL 2
+
+#define HDR_LINE_LENGTH (MAX_FIELDS*FIELD_WIDTH_MAX)
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <json_writer.h>
+#include "lnstat.h"
+
+static struct option opts[] = {
+ { "version", 0, NULL, 'V' },
+ { "count", 1, NULL, 'c' },
+ { "dump", 0, NULL, 'd' },
+ { "json", 0, NULL, 'j' },
+ { "file", 1, NULL, 'f' },
+ { "help", 0, NULL, 'h' },
+ { "interval", 1, NULL, 'i' },
+ { "keys", 1, NULL, 'k' },
+ { "subject", 1, NULL, 's' },
+ { "width", 1, NULL, 'w' },
+ { "oneline", 0, NULL, 0 },
+};
+
+static int usage(char *name, int exit_code)
+{
+ fprintf(stderr,
+ "%s Version %s\n"
+ "Copyright (C) 2004 by Harald Welte <laforge@gnumonks.org>\n"
+ "This program is free software licensed under GNU GPLv2\nwith ABSOLUTELY NO WARRANTY.\n"
+ "\n"
+ "Parameters:\n"
+ " -V --version Print Version of Program\n"
+ " -c --count <count> "
+ "Print <count> number of intervals\n"
+ " -d --dump "
+ "Dump list of available files/keys\n"
+ " -j --json "
+ "Display in JSON format\n"
+ " -f --file <file> Statistics file to use\n"
+ " -h --help This help message\n"
+ " -i --interval <intv> "
+ "Set interval to 'intv' seconds\n"
+ " -k --keys k,k,k,... Display only keys specified\n"
+ " -s --subject [0-2] Control header printing:\n"
+ " 0 = never\n"
+ " 1 = once\n"
+ " 2 = every 20 lines (default))\n"
+ " -w --width n,n,n,... Width for each field\n"
+ "\n",
+ name, LNSTAT_VERSION);
+
+ exit(exit_code);
+}
+
+struct field_param {
+ const char *name;
+ struct lnstat_field *lf;
+ struct {
+ unsigned int width;
+ } print;
+};
+
+struct field_params {
+ unsigned int num;
+ struct field_param params[MAX_FIELDS];
+};
+
+static void print_line(FILE *of, const struct lnstat_file *lnstat_files,
+ const struct field_params *fp)
+{
+ int i;
+
+ for (i = 0; i < fp->num; i++) {
+ const struct lnstat_field *lf = fp->params[i].lf;
+
+ fprintf(of, "%*lu|", fp->params[i].print.width, lf->result);
+ }
+ fputc('\n', of);
+}
+
+static void print_json(FILE *of, const struct lnstat_file *lnstat_files,
+ const struct field_params *fp)
+{
+ json_writer_t *jw = jsonw_new(of);
+ int i;
+
+ jsonw_start_object(jw);
+ for (i = 0; i < fp->num; i++) {
+ const struct lnstat_field *lf = fp->params[i].lf;
+
+ jsonw_uint_field(jw, lf->name, lf->result);
+ }
+ jsonw_end_object(jw);
+ jsonw_destroy(&jw);
+}
+
+/* find lnstat_field according to user specification */
+static int map_field_params(struct lnstat_file *lnstat_files,
+ struct field_params *fps, int interval)
+{
+ int i, j = 0;
+ struct lnstat_file *lf;
+
+ /* no field specification on commandline, need to build default */
+ if (!fps->num) {
+ for (lf = lnstat_files; lf; lf = lf->next) {
+ for (i = 0; i < lf->num_fields; i++) {
+ fps->params[j].lf = &lf->fields[i];
+ fps->params[j].lf->file->interval.tv_sec =
+ interval;
+ if (!fps->params[j].print.width)
+ fps->params[j].print.width =
+ FIELD_WIDTH_DEFAULT;
+
+ if (++j >= MAX_FIELDS - 1) {
+ fprintf(stderr,
+ "WARN: MAX_FIELDS (%d) reached, truncating number of keys\n",
+ MAX_FIELDS);
+ goto full;
+ }
+ }
+ }
+full:
+ fps->num = j;
+ return 1;
+ }
+
+ for (i = 0; i < fps->num; i++) {
+ fps->params[i].lf = lnstat_find_field(lnstat_files,
+ fps->params[i].name);
+ if (!fps->params[i].lf) {
+ fprintf(stderr, "Field `%s' unknown\n",
+ fps->params[i].name);
+ return 0;
+ }
+ fps->params[i].lf->file->interval.tv_sec = interval;
+ if (!fps->params[i].print.width)
+ fps->params[i].print.width = FIELD_WIDTH_DEFAULT;
+ }
+ return 1;
+}
+
+struct table_hdr {
+ int num_lines;
+ char *hdr[HDR_LINES];
+};
+
+static struct table_hdr *build_hdr_string(struct lnstat_file *lnstat_files,
+ struct field_params *fps,
+ int linewidth)
+{
+ int h, i;
+ static struct table_hdr th;
+ int ofs = 0;
+
+ for (i = 0; i < HDR_LINES; i++)
+ th.hdr[i] = calloc(1, HDR_LINE_LENGTH);
+
+ for (i = 0; i < fps->num; i++) {
+ char *cname, *fname = fps->params[i].lf->name;
+ unsigned int width = fps->params[i].print.width;
+
+ snprintf(th.hdr[0]+ofs, width+2, "%*.*s|", width, width,
+ fps->params[i].lf->file->basename);
+
+ cname = fname;
+ for (h = 1; h < HDR_LINES; h++) {
+ if (cname - fname >= strlen(fname))
+ snprintf(th.hdr[h]+ofs, width+2,
+ "%*.*s|", width, width, "");
+ else {
+ th.num_lines = h+1;
+ snprintf(th.hdr[h]+ofs, width+2,
+ "%*.*s|", width, width, cname);
+ }
+ cname += width;
+ }
+ ofs += width+1;
+ }
+ /* fill in spaces */
+ for (h = 1; h <= th.num_lines; h++) {
+ for (i = 0; i < ofs; i++) {
+ if (th.hdr[h][i] == '\0')
+ th.hdr[h][i] = ' ';
+ }
+ }
+
+ return &th;
+}
+
+static int print_hdr(FILE *of, struct table_hdr *th)
+{
+ int i;
+
+ for (i = 0; i < th->num_lines; i++) {
+ fputs(th->hdr[i], of);
+ fputc('\n', of);
+ }
+ return 0;
+}
+
+
+int main(int argc, char **argv)
+{
+ struct lnstat_file *lnstat_files;
+ const char *basename;
+ int i, c;
+ int interval = DEFAULT_INTERVAL;
+ int hdr = 2;
+ enum {
+ MODE_DUMP,
+ MODE_JSON,
+ MODE_NORMAL,
+ } mode = MODE_NORMAL;
+ unsigned long count = 0;
+ struct table_hdr *header;
+ static struct field_params fp;
+ int num_req_files = 0;
+ char *req_files[LNSTAT_MAX_FILES];
+
+ /* backwards compatibility mode for old tools */
+ basename = strrchr(argv[0], '/');
+ if (basename)
+ basename += 1; /* name after slash */
+ else
+ basename = argv[0]; /* no slash */
+
+ if (!strcmp(basename, "rtstat")) {
+ /* rtstat compatibility mode */
+ req_files[0] = "rt_cache";
+ num_req_files = 1;
+ } else if (!strcmp(basename, "ctstat")) {
+ /* ctstat compatibility mode */
+ req_files[0] = "ip_conntrack";
+ num_req_files = 1;
+ }
+
+ while ((c = getopt_long(argc, argv, "Vc:djpf:h?i:k:s:w:",
+ opts, NULL)) != -1) {
+ int len = 0;
+ char *tmp, *tok;
+
+ switch (c) {
+ case 'c':
+ count = strtoul(optarg, NULL, 0);
+ break;
+ case 'd':
+ mode = MODE_DUMP;
+ break;
+ case 'j':
+ mode = MODE_JSON;
+ break;
+ case 'f':
+ req_files[num_req_files++] = strdup(optarg);
+ break;
+ case '?':
+ case 'h':
+ usage(argv[0], 0);
+ break;
+ case 'i':
+ sscanf(optarg, "%u", &interval);
+ break;
+ case 'k':
+ tmp = strdup(optarg);
+ if (!tmp)
+ break;
+ for (tok = strtok(tmp, ",");
+ tok;
+ tok = strtok(NULL, ",")) {
+ if (fp.num >= MAX_FIELDS) {
+ fprintf(stderr,
+ "WARN: too many keys requested: (%d max)\n",
+ MAX_FIELDS);
+ break;
+ }
+ fp.params[fp.num++].name = tok;
+ }
+ break;
+ case 's':
+ sscanf(optarg, "%u", &hdr);
+ break;
+ case 'w':
+ tmp = strdup(optarg);
+ if (!tmp)
+ break;
+ i = 0;
+ for (tok = strtok(tmp, ",");
+ tok;
+ tok = strtok(NULL, ",")) {
+ len = strtoul(tok, NULL, 0);
+ if (len > FIELD_WIDTH_MAX)
+ len = FIELD_WIDTH_MAX;
+ fp.params[i].print.width = len;
+ i++;
+ }
+ if (i == 1) {
+ for (i = 0; i < MAX_FIELDS; i++)
+ fp.params[i].print.width = len;
+ }
+ break;
+ default:
+ usage(argv[0], 1);
+ break;
+ }
+ }
+
+ lnstat_files = lnstat_scan_dir(PROC_NET_STAT, num_req_files,
+ (const char **) req_files);
+
+ switch (mode) {
+ case MODE_DUMP:
+ lnstat_dump(stdout, lnstat_files);
+ break;
+
+ case MODE_NORMAL:
+ case MODE_JSON:
+ if (!map_field_params(lnstat_files, &fp, interval))
+ exit(1);
+
+ header = build_hdr_string(lnstat_files, &fp, 80);
+ if (!header)
+ exit(1);
+
+ if (interval < 1)
+ interval = 1;
+
+ for (i = 0; i < count || !count; i++) {
+ lnstat_update(lnstat_files);
+ if (mode == MODE_JSON)
+ print_json(stdout, lnstat_files, &fp);
+ else {
+ if ((hdr > 1 && !(i % 20)) ||
+ (hdr == 1 && i == 0))
+ print_hdr(stdout, header);
+ print_line(stdout, lnstat_files, &fp);
+ }
+ fflush(stdout);
+ if (i < count - 1 || !count)
+ sleep(interval);
+ }
+ break;
+ }
+
+ return 1;
+}
diff --git a/misc/lnstat.h b/misc/lnstat.h
new file mode 100644
index 0000000..199eb54
--- /dev/null
+++ b/misc/lnstat.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LNSTAT_H
+#define _LNSTAT_H
+
+#include <limits.h>
+#include <sys/select.h>
+
+#define LNSTAT_VERSION "0.02 041002"
+
+#define PROC_NET_STAT "/proc/net/stat"
+
+#define LNSTAT_MAX_FILES 32
+#define LNSTAT_MAX_FIELDS_PER_LINE 32
+#define LNSTAT_MAX_FIELD_NAME_LEN 32
+
+struct lnstat_file;
+
+struct lnstat_field {
+ struct lnstat_file *file;
+ unsigned int num; /* field number in line */
+ char name[LNSTAT_MAX_FIELD_NAME_LEN+1];
+ unsigned long values[2]; /* two buffers for values */
+ unsigned long result;
+};
+
+struct lnstat_file {
+ struct lnstat_file *next;
+ char path[PATH_MAX+1];
+ char basename[NAME_MAX+1];
+ struct timeval last_read; /* last time of read */
+ struct timeval interval; /* interval */
+ int compat; /* 1 == backwards compat mode */
+ FILE *fp;
+ unsigned int num_fields; /* number of fields */
+ struct lnstat_field fields[LNSTAT_MAX_FIELDS_PER_LINE];
+};
+
+
+struct lnstat_file *lnstat_scan_dir(const char *path, const int num_req_files,
+ const char **req_files);
+int lnstat_update(struct lnstat_file *lnstat_files);
+int lnstat_dump(FILE *outfd, struct lnstat_file *lnstat_files);
+struct lnstat_field *lnstat_find_field(struct lnstat_file *lnstat_files,
+ const char *name);
+#endif /* _LNSTAT_H */
diff --git a/misc/lnstat_util.c b/misc/lnstat_util.c
new file mode 100644
index 0000000..c2dc42e
--- /dev/null
+++ b/misc/lnstat_util.c
@@ -0,0 +1,330 @@
+/* lnstat.c: Unified linux network statistics
+ *
+ * Copyright (C) 2004 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Development of this code was funded by Astaro AG, http://www.astaro.com/
+ *
+ * Based on original concept and ideas from predecessor rtstat.c:
+ *
+ * Copyright 2001 by Robert Olsson <robert.olsson@its.uu.se>
+ * Uppsala University, Sweden
+ *
+ * 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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <limits.h>
+#include <time.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include "lnstat.h"
+
+/* size of temp buffer used to read lines from procfiles */
+#define FGETS_BUF_SIZE 1024
+
+
+#define RTSTAT_COMPAT_LINE "entries in_hit in_slow_tot in_no_route in_brd in_martian_dst in_martian_src out_hit out_slow_tot out_slow_mc gc_total gc_ignored gc_goal_miss gc_dst_overflow in_hlist_search out_hlist_search\n"
+
+/* Read (and summarize for SMP) the different stats vars. */
+static int scan_lines(struct lnstat_file *lf, int i)
+{
+ char buf[FGETS_BUF_SIZE];
+ int j, num_lines = 0;
+
+ for (j = 0; j < lf->num_fields; j++)
+ lf->fields[j].values[i] = 0;
+
+ rewind(lf->fp);
+ /* skip first line */
+ if (!lf->compat && !fgets(buf, sizeof(buf)-1, lf->fp))
+ return -1;
+
+ while (!feof(lf->fp) && fgets(buf, sizeof(buf)-1, lf->fp)) {
+ char *ptr = buf;
+
+ num_lines++;
+
+ gettimeofday(&lf->last_read, NULL);
+
+ for (j = 0; j < lf->num_fields; j++) {
+ unsigned long f = strtoul(ptr, &ptr, 16);
+
+ if (j == 0)
+ lf->fields[j].values[i] = f;
+ else
+ lf->fields[j].values[i] += f;
+ }
+ }
+ return num_lines;
+}
+
+static int time_after(struct timeval *last,
+ struct timeval *tout,
+ struct timeval *now)
+{
+ if (now->tv_sec > last->tv_sec + tout->tv_sec)
+ return 1;
+
+ if (now->tv_sec == last->tv_sec + tout->tv_sec) {
+ if (now->tv_usec > last->tv_usec + tout->tv_usec)
+ return 1;
+ }
+
+ return 0;
+}
+
+int lnstat_update(struct lnstat_file *lnstat_files)
+{
+ struct lnstat_file *lf;
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+
+ for (lf = lnstat_files; lf; lf = lf->next) {
+ if (time_after(&lf->last_read, &lf->interval, &tv)) {
+ int i;
+ struct lnstat_field *lfi;
+
+ scan_lines(lf, 1);
+
+ for (i = 0, lfi = &lf->fields[i];
+ i < lf->num_fields; i++, lfi = &lf->fields[i]) {
+ if (i == 0)
+ lfi->result = lfi->values[1];
+ else
+ lfi->result = (lfi->values[1]-lfi->values[0])
+ / lf->interval.tv_sec;
+ }
+
+ scan_lines(lf, 0);
+ }
+ }
+
+ return 0;
+}
+
+/* scan first template line and fill in per-field data structures */
+static int __lnstat_scan_fields(struct lnstat_file *lf, char *buf)
+{
+ char *tok;
+ int i;
+
+ tok = strtok(buf, " \t\n");
+ for (i = 0; i < LNSTAT_MAX_FIELDS_PER_LINE; i++) {
+ lf->fields[i].file = lf;
+ strncpy(lf->fields[i].name, tok, LNSTAT_MAX_FIELD_NAME_LEN);
+ /* has to be null-terminate since we initialize to zero
+ * and field size is NAME_LEN + 1 */
+ tok = strtok(NULL, " \t\n");
+ if (!tok) {
+ lf->num_fields = i+1;
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static int lnstat_scan_fields(struct lnstat_file *lf)
+{
+ char buf[FGETS_BUF_SIZE];
+
+ rewind(lf->fp);
+ if (!fgets(buf, sizeof(buf)-1, lf->fp))
+ return -1;
+
+ return __lnstat_scan_fields(lf, buf);
+}
+
+/* fake function emulating lnstat_scan_fields() for old kernels */
+static int lnstat_scan_compat_rtstat_fields(struct lnstat_file *lf)
+{
+ char buf[FGETS_BUF_SIZE];
+
+ strncpy(buf, RTSTAT_COMPAT_LINE, sizeof(buf) - 1);
+ buf[sizeof(buf) - 1] = '\0';
+
+ return __lnstat_scan_fields(lf, buf);
+}
+
+/* find out whether string 'name; is in given string array */
+static int name_in_array(const int num, const char **arr, const char *name)
+{
+ int i;
+
+ for (i = 0; i < num; i++) {
+ if (!strcmp(arr[i], name))
+ return 1;
+ }
+ return 0;
+}
+
+/* allocate lnstat_file and open given file */
+static struct lnstat_file *alloc_and_open(const char *path, const char *file)
+{
+ struct lnstat_file *lf;
+
+ /* allocate */
+ lf = calloc(1, sizeof(*lf));
+ if (!lf) {
+ fprintf(stderr, "out of memory\n");
+ return NULL;
+ }
+
+ /* initialize */
+ snprintf(lf->basename, sizeof(lf->basename), "%s", file);
+ snprintf(lf->path, sizeof(lf->path), "%s/%s", path, file);
+
+ /* initialize to default */
+ lf->interval.tv_sec = 1;
+
+ /* open */
+ lf->fp = fopen(lf->path, "r");
+ if (!lf->fp) {
+ perror(lf->path);
+ free(lf);
+ return NULL;
+ }
+
+ return lf;
+}
+
+
+/* lnstat_scan_dir - find and parse all available statistics files/fields */
+struct lnstat_file *lnstat_scan_dir(const char *path, const int num_req_files,
+ const char **req_files)
+{
+ DIR *dir;
+ struct lnstat_file *lnstat_files = NULL;
+ struct dirent *de;
+
+ if (!path)
+ path = PROC_NET_STAT;
+
+ dir = opendir(path);
+ if (!dir) {
+ struct lnstat_file *lf;
+ /* Old kernel, before /proc/net/stat was introduced */
+ fprintf(stderr, "Your kernel doesn't have lnstat support. ");
+
+ /* we only support rtstat, not multiple files */
+ if (num_req_files >= 2) {
+ fputc('\n', stderr);
+ return NULL;
+ }
+
+ /* we really only accept rt_cache */
+ if (num_req_files && !name_in_array(num_req_files,
+ req_files, "rt_cache")) {
+ fputc('\n', stderr);
+ return NULL;
+ }
+
+ fprintf(stderr, "Fallback to old rtstat-only operation\n");
+
+ lf = alloc_and_open("/proc/net", "rt_cache_stat");
+ if (!lf)
+ return NULL;
+ lf->compat = 1;
+ strncpy(lf->basename, "rt_cache", sizeof(lf->basename));
+
+ /* FIXME: support for old files */
+ if (lnstat_scan_compat_rtstat_fields(lf) < 0)
+ return NULL;
+
+ lf->next = lnstat_files;
+ lnstat_files = lf;
+ return lnstat_files;
+ }
+
+ while ((de = readdir(dir))) {
+ struct lnstat_file *lf;
+
+ if (de->d_type != DT_REG)
+ continue;
+
+ if (num_req_files && !name_in_array(num_req_files,
+ req_files, de->d_name))
+ continue;
+
+ lf = alloc_and_open(path, de->d_name);
+ if (!lf) {
+ closedir(dir);
+ return NULL;
+ }
+
+ /* fill in field structure */
+ if (lnstat_scan_fields(lf) < 0) {
+ closedir(dir);
+ return NULL;
+ }
+
+ /* prepend to global list */
+ lf->next = lnstat_files;
+ lnstat_files = lf;
+ }
+ closedir(dir);
+
+ return lnstat_files;
+}
+
+int lnstat_dump(FILE *outfd, struct lnstat_file *lnstat_files)
+{
+ struct lnstat_file *lf;
+
+ for (lf = lnstat_files; lf; lf = lf->next) {
+ int i;
+
+ fprintf(outfd, "%s:\n", lf->path);
+
+ for (i = 0; i < lf->num_fields; i++)
+ fprintf(outfd, "\t%2u: %s\n", i+1, lf->fields[i].name);
+
+ }
+ return 0;
+}
+
+struct lnstat_field *lnstat_find_field(struct lnstat_file *lnstat_files,
+ const char *name)
+{
+ struct lnstat_file *lf;
+ struct lnstat_field *ret = NULL;
+ const char *colon = strchr(name, ':');
+ char *file;
+ const char *field;
+
+ if (colon) {
+ file = strndup(name, colon-name);
+ field = colon+1;
+ } else {
+ file = NULL;
+ field = name;
+ }
+
+ for (lf = lnstat_files; lf; lf = lf->next) {
+ int i;
+
+ if (file && strcmp(file, lf->basename))
+ continue;
+
+ for (i = 0; i < lf->num_fields; i++) {
+ if (!strcmp(field, lf->fields[i].name)) {
+ ret = &lf->fields[i];
+ goto out;
+ }
+ }
+ }
+out:
+ free(file);
+
+ return ret;
+}
diff --git a/misc/nstat.c b/misc/nstat.c
new file mode 100644
index 0000000..23113b2
--- /dev/null
+++ b/misc/nstat.c
@@ -0,0 +1,756 @@
+/*
+ * nstat.c handy utility to read counters /proc/net/netstat and snmp
+ *
+ * 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 <errno.h>
+#include <time.h>
+#include <sys/time.h>
+#include <fnmatch.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/poll.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <math.h>
+#include <getopt.h>
+
+#include <json_writer.h>
+#include <SNAPSHOT.h>
+#include "utils.h"
+
+int dump_zeros;
+int reset_history;
+int ignore_history;
+int no_output;
+int json_output;
+int no_update;
+int scan_interval;
+int time_constant;
+double W;
+char **patterns;
+int npatterns;
+
+char info_source[128];
+int source_mismatch;
+
+static int generic_proc_open(const char *env, char *name)
+{
+ char store[128];
+ char *p = getenv(env);
+
+ if (!p) {
+ p = getenv("PROC_ROOT") ? : "/proc";
+ snprintf(store, sizeof(store)-1, "%s/%s", p, name);
+ p = store;
+ }
+ return open(p, O_RDONLY);
+}
+
+static int net_netstat_open(void)
+{
+ return generic_proc_open("PROC_NET_NETSTAT", "net/netstat");
+}
+
+static int net_snmp_open(void)
+{
+ return generic_proc_open("PROC_NET_SNMP", "net/snmp");
+}
+
+static int net_snmp6_open(void)
+{
+ return generic_proc_open("PROC_NET_SNMP6", "net/snmp6");
+}
+
+static int net_sctp_snmp_open(void)
+{
+ return generic_proc_open("PROC_NET_SCTP_SNMP", "net/sctp/snmp");
+}
+
+struct nstat_ent {
+ struct nstat_ent *next;
+ char *id;
+ unsigned long long val;
+ double rate;
+};
+
+struct nstat_ent *kern_db;
+struct nstat_ent *hist_db;
+
+static const char *useless_numbers[] = {
+ "IpForwarding", "IpDefaultTTL",
+ "TcpRtoAlgorithm", "TcpRtoMin", "TcpRtoMax",
+ "TcpMaxConn", "TcpCurrEstab"
+};
+
+static int useless_number(const char *id)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(useless_numbers); i++)
+ if (strcmp(id, useless_numbers[i]) == 0)
+ return 1;
+ return 0;
+}
+
+static int match(const char *id)
+{
+ int i;
+
+ if (npatterns == 0)
+ return 1;
+
+ for (i = 0; i < npatterns; i++) {
+ if (!fnmatch(patterns[i], id, 0))
+ return 1;
+ }
+ return 0;
+}
+
+static void load_good_table(FILE *fp)
+{
+ char buf[4096];
+ struct nstat_ent *db = NULL;
+ struct nstat_ent *n;
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ int nr;
+ unsigned long long val;
+ double rate;
+ char idbuf[sizeof(buf)];
+
+ if (buf[0] == '#') {
+ buf[strlen(buf)-1] = 0;
+ if (info_source[0] && strcmp(info_source, buf+1))
+ source_mismatch = 1;
+ info_source[0] = 0;
+ strncat(info_source, buf+1, sizeof(info_source)-1);
+ continue;
+ }
+ /* idbuf is as big as buf, so this is safe */
+ nr = sscanf(buf, "%s%llu%lg", idbuf, &val, &rate);
+ if (nr < 2)
+ abort();
+ if (nr < 3)
+ rate = 0;
+ if (useless_number(idbuf))
+ continue;
+ if ((n = malloc(sizeof(*n))) == NULL)
+ abort();
+ n->id = strdup(idbuf);
+ n->val = val;
+ n->rate = rate;
+ n->next = db;
+ db = n;
+ }
+
+ while (db) {
+ n = db;
+ db = db->next;
+ n->next = kern_db;
+ kern_db = n;
+ }
+}
+
+static int count_spaces(const char *line)
+{
+ int count = 0;
+ char c;
+
+ while ((c = *line++) != 0)
+ count += c == ' ' || c == '\n';
+ return count;
+}
+
+static void load_ugly_table(FILE *fp)
+{
+ char *buf = NULL;
+ size_t buflen = 0;
+ ssize_t nread;
+ struct nstat_ent *db = NULL;
+ struct nstat_ent *n;
+
+ while ((nread = getline(&buf, &buflen, fp)) != -1) {
+ char idbuf[4096];
+ int off;
+ char *p;
+ int count1, count2, skip = 0;
+
+ p = strchr(buf, ':');
+ if (!p)
+ abort();
+ count1 = count_spaces(buf);
+ *p = 0;
+ idbuf[0] = 0;
+ strncat(idbuf, buf, sizeof(idbuf) - 1);
+ off = p - buf;
+ p += 2;
+
+ while (*p) {
+ char *next;
+
+ if ((next = strchr(p, ' ')) != NULL)
+ *next++ = 0;
+ else if ((next = strchr(p, '\n')) != NULL)
+ *next++ = 0;
+ if (off < sizeof(idbuf)) {
+ idbuf[off] = 0;
+ strncat(idbuf, p, sizeof(idbuf) - off - 1);
+ }
+ n = malloc(sizeof(*n));
+ if (!n)
+ abort();
+ n->id = strdup(idbuf);
+ n->rate = 0;
+ n->next = db;
+ db = n;
+ p = next;
+ }
+ n = db;
+ nread = getline(&buf, &buflen, fp);
+ if (nread == -1)
+ abort();
+ count2 = count_spaces(buf);
+ if (count2 > count1)
+ skip = count2 - count1;
+ do {
+ p = strrchr(buf, ' ');
+ if (!p)
+ abort();
+ *p = 0;
+ if (sscanf(p+1, "%llu", &n->val) != 1)
+ abort();
+ /* Trick to skip "dummy" trailing ICMP MIB in 2.4 */
+ if (skip)
+ skip--;
+ else
+ n = n->next;
+ } while (p > buf + off + 2);
+ }
+ free(buf);
+
+ while (db) {
+ n = db;
+ db = db->next;
+ if (useless_number(n->id)) {
+ free(n->id);
+ free(n);
+ } else {
+ n->next = kern_db;
+ kern_db = n;
+ }
+ }
+}
+
+static void load_sctp_snmp(void)
+{
+ FILE *fp = fdopen(net_sctp_snmp_open(), "r");
+
+ if (fp) {
+ load_good_table(fp);
+ fclose(fp);
+ }
+}
+
+static void load_snmp(void)
+{
+ FILE *fp = fdopen(net_snmp_open(), "r");
+
+ if (fp) {
+ load_ugly_table(fp);
+ fclose(fp);
+ }
+}
+
+static void load_snmp6(void)
+{
+ FILE *fp = fdopen(net_snmp6_open(), "r");
+
+ if (fp) {
+ load_good_table(fp);
+ fclose(fp);
+ }
+}
+
+static void load_netstat(void)
+{
+ FILE *fp = fdopen(net_netstat_open(), "r");
+
+ if (fp) {
+ load_ugly_table(fp);
+ fclose(fp);
+ }
+}
+
+
+static void dump_kern_db(FILE *fp, int to_hist)
+{
+ json_writer_t *jw = json_output ? jsonw_new(fp) : NULL;
+ struct nstat_ent *n, *h;
+
+ h = hist_db;
+ if (jw) {
+ jsonw_start_object(jw);
+ jsonw_pretty(jw, pretty);
+ jsonw_name(jw, info_source);
+ jsonw_start_object(jw);
+ } else
+ fprintf(fp, "#%s\n", info_source);
+
+ for (n = kern_db; n; n = n->next) {
+ unsigned long long val = n->val;
+
+ if (!dump_zeros && !val && !n->rate)
+ continue;
+ if (!match(n->id)) {
+ struct nstat_ent *h1;
+
+ if (!to_hist)
+ continue;
+ for (h1 = h; h1; h1 = h1->next) {
+ if (strcmp(h1->id, n->id) == 0) {
+ val = h1->val;
+ h = h1->next;
+ break;
+ }
+ }
+ }
+
+ if (jw)
+ jsonw_uint_field(jw, n->id, val);
+ else
+ fprintf(fp, "%-32s%-16llu%6.1f\n", n->id, val, n->rate);
+ }
+
+ if (jw) {
+ jsonw_end_object(jw);
+
+ jsonw_end_object(jw);
+ jsonw_destroy(&jw);
+ }
+}
+
+static void dump_incr_db(FILE *fp)
+{
+ json_writer_t *jw = json_output ? jsonw_new(fp) : NULL;
+ struct nstat_ent *n, *h;
+
+ h = hist_db;
+ if (jw) {
+ jsonw_start_object(jw);
+ jsonw_pretty(jw, pretty);
+ jsonw_name(jw, info_source);
+ jsonw_start_object(jw);
+ } else
+ fprintf(fp, "#%s\n", info_source);
+
+ for (n = kern_db; n; n = n->next) {
+ int ovfl = 0;
+ unsigned long long val = n->val;
+ struct nstat_ent *h1;
+
+ for (h1 = h; h1; h1 = h1->next) {
+ if (strcmp(h1->id, n->id) == 0) {
+ if (val < h1->val) {
+ ovfl = 1;
+ val = h1->val;
+ }
+ val -= h1->val;
+ h = h1->next;
+ break;
+ }
+ }
+ if (!dump_zeros && !val && !n->rate)
+ continue;
+ if (!match(n->id))
+ continue;
+
+ if (jw)
+ jsonw_uint_field(jw, n->id, val);
+ else
+ fprintf(fp, "%-32s%-16llu%6.1f%s\n", n->id, val,
+ n->rate, ovfl?" (overflow)":"");
+ }
+
+ if (jw) {
+ jsonw_end_object(jw);
+
+ jsonw_end_object(jw);
+ jsonw_destroy(&jw);
+ }
+}
+
+static int children;
+
+static void sigchild(int signo)
+{
+}
+
+static void update_db(int interval)
+{
+ struct nstat_ent *n, *h;
+
+ n = kern_db;
+ kern_db = NULL;
+
+ load_netstat();
+ load_snmp6();
+ load_snmp();
+ load_sctp_snmp();
+
+ h = kern_db;
+ kern_db = n;
+
+ for (n = kern_db; n; n = n->next) {
+ struct nstat_ent *h1;
+
+ for (h1 = h; h1; h1 = h1->next) {
+ if (strcmp(h1->id, n->id) == 0) {
+ double sample;
+ unsigned long long incr = h1->val - n->val;
+
+ n->val = h1->val;
+ sample = (double)incr * 1000.0 / interval;
+ if (interval >= scan_interval) {
+ n->rate += W*(sample-n->rate);
+ } else if (interval >= 1000) {
+ if (interval >= time_constant) {
+ n->rate = sample;
+ } else {
+ double w = W*(double)interval/scan_interval;
+
+ n->rate += w*(sample-n->rate);
+ }
+ }
+
+ while (h != h1) {
+ struct nstat_ent *tmp = h;
+
+ h = h->next;
+ free(tmp->id);
+ free(tmp);
+ };
+ h = h1->next;
+ free(h1->id);
+ free(h1);
+ break;
+ }
+ }
+ }
+}
+
+#define T_DIFF(a, b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
+
+
+static void server_loop(int fd)
+{
+ struct timeval snaptime = { 0 };
+ struct pollfd p;
+
+ p.fd = fd;
+ p.events = p.revents = POLLIN;
+
+ sprintf(info_source, "%d.%lu sampling_interval=%d time_const=%d",
+ getpid(), (unsigned long)random(), scan_interval/1000, time_constant/1000);
+
+ load_netstat();
+ load_snmp6();
+ load_snmp();
+ load_sctp_snmp();
+
+ for (;;) {
+ int status;
+ time_t tdiff;
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+ tdiff = T_DIFF(now, snaptime);
+ if (tdiff >= scan_interval) {
+ update_db(tdiff);
+ snaptime = now;
+ tdiff = 0;
+ }
+ if (poll(&p, 1, scan_interval - tdiff) > 0
+ && (p.revents&POLLIN)) {
+ int clnt = accept(fd, NULL, NULL);
+
+ if (clnt >= 0) {
+ pid_t pid;
+
+ if (children >= 5) {
+ close(clnt);
+ } else if ((pid = fork()) != 0) {
+ if (pid > 0)
+ children++;
+ close(clnt);
+ } else {
+ FILE *fp = fdopen(clnt, "w");
+
+ if (fp)
+ dump_kern_db(fp, 0);
+ exit(0);
+ }
+ }
+ }
+ while (children && waitpid(-1, &status, WNOHANG) > 0)
+ children--;
+ }
+}
+
+static int verify_forging(int fd)
+{
+ struct ucred cred;
+ socklen_t olen = sizeof(cred);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void *)&cred, &olen) ||
+ olen < sizeof(cred))
+ return -1;
+ if (cred.uid == getuid() || cred.uid == 0)
+ return 0;
+ return -1;
+}
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: nstat [OPTION] [ PATTERN [ PATTERN ] ]\n"
+ " -h, --help this message\n"
+ " -a, --ignore ignore history\n"
+ " -d, --scan=SECS sample every statistics every SECS\n"
+ " -j, --json format output in JSON\n"
+ " -n, --nooutput do history only\n"
+ " -p, --pretty pretty print\n"
+ " -r, --reset reset history\n"
+ " -s, --noupdate don't update history\n"
+ " -t, --interval=SECS report average over the last SECS\n"
+ " -V, --version output version information\n"
+ " -z, --zeros show entries with zero activity\n");
+ exit(-1);
+}
+
+static const struct option longopts[] = {
+ { "help", 0, 0, 'h' },
+ { "ignore", 0, 0, 'a' },
+ { "scan", 1, 0, 'd'},
+ { "nooutput", 0, 0, 'n' },
+ { "json", 0, 0, 'j' },
+ { "reset", 0, 0, 'r' },
+ { "noupdate", 0, 0, 's' },
+ { "pretty", 0, 0, 'p' },
+ { "interval", 1, 0, 't' },
+ { "version", 0, 0, 'V' },
+ { "zeros", 0, 0, 'z' },
+ { 0 }
+};
+
+int main(int argc, char *argv[])
+{
+ char *hist_name;
+ struct sockaddr_un sun;
+ FILE *hist_fp = NULL;
+ int ch;
+ int fd;
+
+ while ((ch = getopt_long(argc, argv, "h?vVzrnasd:t:jp",
+ longopts, NULL)) != EOF) {
+ switch (ch) {
+ case 'z':
+ dump_zeros = 1;
+ break;
+ case 'r':
+ reset_history = 1;
+ break;
+ case 'a':
+ ignore_history = 1;
+ break;
+ case 's':
+ no_update = 1;
+ break;
+ case 'n':
+ no_output = 1;
+ break;
+ case 'd':
+ scan_interval = 1000*atoi(optarg);
+ break;
+ case 't':
+ if (sscanf(optarg, "%d", &time_constant) != 1 ||
+ time_constant <= 0) {
+ fprintf(stderr, "nstat: invalid time constant divisor\n");
+ exit(-1);
+ }
+ break;
+ case 'j':
+ json_output = 1;
+ break;
+ case 'p':
+ pretty = 1;
+ break;
+ case 'v':
+ case 'V':
+ printf("nstat utility, iproute2-ss%s\n", SNAPSHOT);
+ exit(0);
+ case 'h':
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ sun.sun_family = AF_UNIX;
+ sun.sun_path[0] = 0;
+ sprintf(sun.sun_path+1, "nstat%d", getuid());
+
+ if (scan_interval > 0) {
+ if (time_constant == 0)
+ time_constant = 60;
+ time_constant *= 1000;
+ W = 1 - 1/exp(log(10)*(double)scan_interval/time_constant);
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ perror("nstat: socket");
+ exit(-1);
+ }
+ if (bind(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) < 0) {
+ perror("nstat: bind");
+ exit(-1);
+ }
+ if (listen(fd, 5) < 0) {
+ perror("nstat: listen");
+ exit(-1);
+ }
+ if (daemon(0, 0)) {
+ perror("nstat: daemon");
+ exit(-1);
+ }
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGCHLD, sigchild);
+ server_loop(fd);
+ exit(0);
+ }
+
+ patterns = argv;
+ npatterns = argc;
+
+ if ((hist_name = getenv("NSTAT_HISTORY")) == NULL) {
+ hist_name = malloc(128);
+ sprintf(hist_name, "/tmp/.nstat.u%d", getuid());
+ }
+
+ if (reset_history)
+ unlink(hist_name);
+
+ if (!ignore_history || !no_update) {
+ struct stat stb;
+
+ fd = open(hist_name, O_RDWR|O_CREAT|O_NOFOLLOW, 0600);
+ if (fd < 0) {
+ perror("nstat: open history file");
+ exit(-1);
+ }
+ if ((hist_fp = fdopen(fd, "r+")) == NULL) {
+ perror("nstat: fdopen history file");
+ exit(-1);
+ }
+ if (flock(fileno(hist_fp), LOCK_EX)) {
+ perror("nstat: flock history file");
+ exit(-1);
+ }
+ if (fstat(fileno(hist_fp), &stb) != 0) {
+ perror("nstat: fstat history file");
+ exit(-1);
+ }
+ if (stb.st_nlink != 1 || stb.st_uid != getuid()) {
+ fprintf(stderr, "nstat: something is so wrong with history file, that I prefer not to proceed.\n");
+ exit(-1);
+ }
+ if (!ignore_history) {
+ FILE *tfp;
+ long uptime = -1;
+
+ if ((tfp = fopen("/proc/uptime", "r")) != NULL) {
+ if (fscanf(tfp, "%ld", &uptime) != 1)
+ uptime = -1;
+ fclose(tfp);
+ }
+ if (uptime >= 0 && time(NULL) >= stb.st_mtime+uptime) {
+ fprintf(stderr, "nstat: history is aged out, resetting\n");
+ if (ftruncate(fileno(hist_fp), 0) < 0)
+ perror("nstat: ftruncate");
+ }
+ }
+
+ load_good_table(hist_fp);
+
+ hist_db = kern_db;
+ kern_db = NULL;
+ }
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 &&
+ (connect(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) == 0
+ || (strcpy(sun.sun_path+1, "nstat0"),
+ connect(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) == 0))
+ && verify_forging(fd) == 0) {
+ FILE *sfp = fdopen(fd, "r");
+
+ if (!sfp) {
+ fprintf(stderr, "nstat: fdopen failed: %s\n",
+ strerror(errno));
+ close(fd);
+ } else {
+ load_good_table(sfp);
+ if (hist_db && source_mismatch) {
+ fprintf(stderr, "nstat: history is stale, ignoring it.\n");
+ hist_db = NULL;
+ }
+ fclose(sfp);
+ }
+ } else {
+ if (fd >= 0)
+ close(fd);
+ if (hist_db && info_source[0] && strcmp(info_source, "kernel")) {
+ fprintf(stderr, "nstat: history is stale, ignoring it.\n");
+ hist_db = NULL;
+ info_source[0] = 0;
+ }
+ load_netstat();
+ load_snmp6();
+ load_snmp();
+ load_sctp_snmp();
+ if (info_source[0] == 0)
+ strcpy(info_source, "kernel");
+ }
+
+ if (!no_output) {
+ if (ignore_history || hist_db == NULL)
+ dump_kern_db(stdout, 0);
+ else
+ dump_incr_db(stdout);
+ }
+ if (!no_update) {
+ if (ftruncate(fileno(hist_fp), 0) < 0)
+ perror("nstat: ftruncate");
+ rewind(hist_fp);
+
+ json_output = 0;
+ dump_kern_db(hist_fp, 1);
+ fclose(hist_fp);
+ }
+ exit(0);
+}
diff --git a/misc/rtacct.c b/misc/rtacct.c
new file mode 100644
index 0000000..b1cb378
--- /dev/null
+++ b/misc/rtacct.c
@@ -0,0 +1,628 @@
+/*
+ * rtacct.c Applet to display contents of /proc/net/rt_acct.
+ *
+ * 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 <errno.h>
+#include <time.h>
+#include <sys/time.h>
+#include <fnmatch.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/poll.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <signal.h>
+#include <math.h>
+
+#include "rt_names.h"
+
+#include <SNAPSHOT.h>
+
+int reset_history;
+int ignore_history;
+int no_output;
+int no_update;
+int scan_interval;
+int time_constant;
+int dump_zeros;
+unsigned long magic_number;
+double W;
+
+static int generic_proc_open(const char *env, const char *name)
+{
+ char store[1024];
+ char *p = getenv(env);
+
+ if (!p) {
+ p = getenv("PROC_ROOT") ? : "/proc";
+ snprintf(store, sizeof(store)-1, "%s/%s", p, name);
+ p = store;
+ }
+ return open(p, O_RDONLY);
+}
+
+static int net_rtacct_open(void)
+{
+ return generic_proc_open("PROC_NET_RTACCT", "net/rt_acct");
+}
+
+static __u32 rmap[256/4];
+
+struct rtacct_data {
+ __u32 ival[256*4];
+
+ unsigned long long val[256*4];
+ double rate[256*4];
+ char signature[128];
+};
+
+static struct rtacct_data kern_db_static;
+
+static struct rtacct_data *kern_db = &kern_db_static;
+static struct rtacct_data *hist_db;
+
+static void nread(int fd, char *buf, int tot)
+{
+ int count = 0;
+
+ while (count < tot) {
+ int n = read(fd, buf+count, tot-count);
+
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ exit(-1);
+ }
+ if (n == 0)
+ exit(-1);
+ count += n;
+ }
+}
+
+static __u32 *read_kern_table(__u32 *tbl)
+{
+ static __u32 *tbl_ptr;
+ int fd;
+
+ if (magic_number) {
+ if (tbl_ptr != NULL)
+ return tbl_ptr;
+
+ fd = open("/dev/mem", O_RDONLY);
+ if (fd < 0) {
+ perror("magic open");
+ exit(-1);
+ }
+ tbl_ptr = mmap(NULL, 4096,
+ PROT_READ,
+ MAP_SHARED,
+ fd, magic_number);
+ if ((unsigned long)tbl_ptr == ~0UL) {
+ perror("magic mmap");
+ exit(-1);
+ }
+ close(fd);
+ return tbl_ptr;
+ }
+
+ fd = net_rtacct_open();
+ if (fd >= 0) {
+ nread(fd, (char *)tbl, 256*16);
+ close(fd);
+ } else {
+ memset(tbl, 0, 256*16);
+ }
+ return tbl;
+}
+
+static void format_rate(FILE *fp, double rate)
+{
+ char temp[64];
+
+ if (rate > 1024*1024) {
+ sprintf(temp, "%uM", (unsigned int)rint(rate/(1024*1024)));
+ fprintf(fp, " %-10s", temp);
+ } else if (rate > 1024) {
+ sprintf(temp, "%uK", (unsigned int)rint(rate/1024));
+ fprintf(fp, " %-10s", temp);
+ } else
+ fprintf(fp, " %-10u", (unsigned int)rate);
+}
+
+static void format_count(FILE *fp, unsigned long long val)
+{
+ if (val > 1024*1024*1024)
+ fprintf(fp, " %10lluM", val/(1024*1024));
+ else if (val > 1024*1024)
+ fprintf(fp, " %10lluK", val/1024);
+ else
+ fprintf(fp, " %10llu", val);
+}
+
+static void dump_abs_db(FILE *fp)
+{
+ int realm;
+ char b1[16];
+
+ if (!no_output) {
+ fprintf(fp, "#%s\n", kern_db->signature);
+ fprintf(fp,
+"%-10s %-10s "
+"%-10s %-10s "
+"%-10s \n"
+ , "Realm", "BytesTo", "PktsTo", "BytesFrom", "PktsFrom");
+ fprintf(fp,
+"%-10s %-10s "
+"%-10s %-10s "
+"%-10s \n"
+ , "", "BPSTo", "PPSTo", "BPSFrom", "PPSFrom");
+
+ }
+
+ for (realm = 0; realm < 256; realm++) {
+ int i;
+ unsigned long long *val;
+ double *rate;
+
+ if (!(rmap[realm>>5] & (1<<(realm&0x1f))))
+ continue;
+
+ val = &kern_db->val[realm*4];
+ rate = &kern_db->rate[realm*4];
+
+ if (!dump_zeros &&
+ !val[0] && !rate[0] &&
+ !val[1] && !rate[1] &&
+ !val[2] && !rate[2] &&
+ !val[3] && !rate[3])
+ continue;
+
+ if (hist_db) {
+ memcpy(&hist_db->val[realm*4], val, sizeof(*val)*4);
+ }
+
+ if (no_output)
+ continue;
+
+ fprintf(fp, "%-10s", rtnl_rtrealm_n2a(realm, b1, sizeof(b1)));
+ for (i = 0; i < 4; i++)
+ format_count(fp, val[i]);
+ fprintf(fp, "\n%-10s", "");
+ for (i = 0; i < 4; i++)
+ format_rate(fp, rate[i]);
+ fprintf(fp, "\n");
+ }
+}
+
+
+static void dump_incr_db(FILE *fp)
+{
+ int k, realm;
+ char b1[16];
+
+ if (!no_output) {
+ fprintf(fp, "#%s\n", kern_db->signature);
+ fprintf(fp,
+"%-10s %-10s "
+"%-10s %-10s "
+"%-10s \n"
+ , "Realm", "BytesTo", "PktsTo", "BytesFrom", "PktsFrom");
+ fprintf(fp,
+"%-10s %-10s "
+"%-10s %-10s "
+"%-10s \n"
+ , "", "BPSTo", "PPSTo", "BPSFrom", "PPSFrom");
+ }
+
+ for (realm = 0; realm < 256; realm++) {
+ int ovfl = 0;
+ int i;
+ unsigned long long *val;
+ double *rate;
+ unsigned long long rval[4];
+
+ if (!(rmap[realm>>5] & (1<<(realm&0x1f))))
+ continue;
+
+ val = &kern_db->val[realm*4];
+ rate = &kern_db->rate[realm*4];
+
+ for (k = 0; k < 4; k++) {
+ rval[k] = val[k];
+ if (rval[k] < hist_db->val[realm*4+k])
+ ovfl = 1;
+ else
+ rval[k] -= hist_db->val[realm*4+k];
+ }
+ if (ovfl) {
+ for (k = 0; k < 4; k++)
+ rval[k] = val[k];
+ }
+ if (hist_db) {
+ memcpy(&hist_db->val[realm*4], val, sizeof(*val)*4);
+ }
+
+ if (no_output)
+ continue;
+
+ if (!dump_zeros &&
+ !rval[0] && !rate[0] &&
+ !rval[1] && !rate[1] &&
+ !rval[2] && !rate[2] &&
+ !rval[3] && !rate[3])
+ continue;
+
+
+ fprintf(fp, "%-10s", rtnl_rtrealm_n2a(realm, b1, sizeof(b1)));
+ for (i = 0; i < 4; i++)
+ format_count(fp, rval[i]);
+ fprintf(fp, "\n%-10s", "");
+ for (i = 0; i < 4; i++)
+ format_rate(fp, rate[i]);
+ fprintf(fp, "\n");
+ }
+}
+
+
+static int children;
+
+static void sigchild(int signo)
+{
+}
+
+/* Server side only: read kernel data, update tables, calculate rates. */
+
+static void update_db(int interval)
+{
+ int i;
+ __u32 *ival;
+ __u32 _ival[256*4];
+
+ ival = read_kern_table(_ival);
+
+ for (i = 0; i < 256*4; i++) {
+ double sample;
+ __u32 incr = ival[i] - kern_db->ival[i];
+
+ if (ival[i] == 0 && incr == 0 &&
+ kern_db->val[i] == 0 && kern_db->rate[i] == 0)
+ continue;
+
+ kern_db->val[i] += incr;
+ kern_db->ival[i] = ival[i];
+ sample = (double)(incr*1000)/interval;
+ if (interval >= scan_interval) {
+ kern_db->rate[i] += W*(sample-kern_db->rate[i]);
+ } else if (interval >= 1000) {
+ if (interval >= time_constant) {
+ kern_db->rate[i] = sample;
+ } else {
+ double w = W*(double)interval/scan_interval;
+
+ kern_db->rate[i] += w*(sample-kern_db->rate[i]);
+ }
+ }
+ }
+}
+
+static void send_db(int fd)
+{
+ int tot = 0;
+
+ while (tot < sizeof(*kern_db)) {
+ int n = write(fd, ((char *)kern_db) + tot, sizeof(*kern_db)-tot);
+
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ return;
+ }
+ tot += n;
+ }
+}
+
+
+
+#define T_DIFF(a, b) (((a).tv_sec-(b).tv_sec)*1000 + ((a).tv_usec-(b).tv_usec)/1000)
+
+
+static void pad_kern_table(struct rtacct_data *dat, __u32 *ival)
+{
+ int i;
+
+ memset(dat->rate, 0, sizeof(dat->rate));
+ if (dat->ival != ival)
+ memcpy(dat->ival, ival, sizeof(dat->ival));
+ for (i = 0; i < 256*4; i++)
+ dat->val[i] = ival[i];
+}
+
+static void server_loop(int fd)
+{
+ struct timeval snaptime = { 0 };
+ struct pollfd p;
+
+ p.fd = fd;
+ p.events = p.revents = POLLIN;
+
+ sprintf(kern_db->signature,
+ "%u.%lu sampling_interval=%d time_const=%d",
+ (unsigned int) getpid(), (unsigned long)random(),
+ scan_interval/1000, time_constant/1000);
+
+ pad_kern_table(kern_db, read_kern_table(kern_db->ival));
+
+ for (;;) {
+ int status;
+ int tdiff;
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+ tdiff = T_DIFF(now, snaptime);
+ if (tdiff >= scan_interval) {
+ update_db(tdiff);
+ snaptime = now;
+ tdiff = 0;
+ }
+ if (poll(&p, 1, tdiff + scan_interval) > 0
+ && (p.revents&POLLIN)) {
+ int clnt = accept(fd, NULL, NULL);
+
+ if (clnt >= 0) {
+ pid_t pid;
+
+ if (children >= 5) {
+ close(clnt);
+ } else if ((pid = fork()) != 0) {
+ if (pid > 0)
+ children++;
+ close(clnt);
+ } else {
+ if (tdiff > 0)
+ update_db(tdiff);
+ send_db(clnt);
+ exit(0);
+ }
+ }
+ }
+ while (children && waitpid(-1, &status, WNOHANG) > 0)
+ children--;
+ }
+}
+
+static int verify_forging(int fd)
+{
+ struct ucred cred;
+ socklen_t olen = sizeof(cred);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, (void *)&cred, &olen) ||
+ olen < sizeof(cred))
+ return -1;
+ if (cred.uid == getuid() || cred.uid == 0)
+ return 0;
+ return -1;
+}
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+"Usage: rtacct [ -h?vVzrnasd:t: ] [ ListOfRealms ]\n"
+ );
+ exit(-1);
+}
+
+int main(int argc, char *argv[])
+{
+ char hist_name[128];
+ struct sockaddr_un sun;
+ int ch;
+ int fd;
+
+ while ((ch = getopt(argc, argv, "h?vVzrM:nasd:t:")) != EOF) {
+ switch (ch) {
+ case 'z':
+ dump_zeros = 1;
+ break;
+ case 'r':
+ reset_history = 1;
+ break;
+ case 'a':
+ ignore_history = 1;
+ break;
+ case 's':
+ no_update = 1;
+ break;
+ case 'n':
+ no_output = 1;
+ break;
+ case 'd':
+ scan_interval = 1000*atoi(optarg);
+ break;
+ case 't':
+ if (sscanf(optarg, "%d", &time_constant) != 1 ||
+ time_constant <= 0) {
+ fprintf(stderr, "rtacct: invalid time constant divisor\n");
+ exit(-1);
+ }
+ break;
+ case 'v':
+ case 'V':
+ printf("rtacct utility, iproute2-ss%s\n", SNAPSHOT);
+ exit(0);
+ case 'M':
+ /* Some secret undocumented option, nobody
+ * is expected to ask about its sense. See?
+ */
+ sscanf(optarg, "%lx", &magic_number);
+ break;
+ case 'h':
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc) {
+ while (argc > 0) {
+ __u32 realm;
+
+ if (rtnl_rtrealm_a2n(&realm, argv[0])) {
+ fprintf(stderr, "Warning: realm \"%s\" does not exist.\n", argv[0]);
+ exit(-1);
+ }
+ rmap[realm>>5] |= (1<<(realm&0x1f));
+ argc--; argv++;
+ }
+ } else {
+ memset(rmap, ~0, sizeof(rmap));
+ /* Always suppress zeros. */
+ dump_zeros = 0;
+ }
+
+ sun.sun_family = AF_UNIX;
+ sun.sun_path[0] = 0;
+ sprintf(sun.sun_path+1, "rtacct%d", getuid());
+
+ if (scan_interval > 0) {
+ if (time_constant == 0)
+ time_constant = 60;
+ time_constant *= 1000;
+ W = 1 - 1/exp(log(10)*(double)scan_interval/time_constant);
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ perror("rtacct: socket");
+ exit(-1);
+ }
+ if (bind(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) < 0) {
+ perror("rtacct: bind");
+ exit(-1);
+ }
+ if (listen(fd, 5) < 0) {
+ perror("rtacct: listen");
+ exit(-1);
+ }
+ if (daemon(0, 0)) {
+ perror("rtacct: daemon");
+ exit(-1);
+ }
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGCHLD, sigchild);
+ server_loop(fd);
+ exit(0);
+ }
+
+ if (getenv("RTACCT_HISTORY"))
+ snprintf(hist_name, sizeof(hist_name), "%s", getenv("RTACCT_HISTORY"));
+ else
+ sprintf(hist_name, "/tmp/.rtacct.u%d", getuid());
+
+ if (reset_history)
+ unlink(hist_name);
+
+ if (!ignore_history || !no_update) {
+ struct stat stb;
+
+ fd = open(hist_name, O_RDWR|O_CREAT|O_NOFOLLOW, 0600);
+ if (fd < 0) {
+ perror("rtacct: open history file");
+ exit(-1);
+ }
+ if (flock(fd, LOCK_EX)) {
+ perror("rtacct: flock history file");
+ exit(-1);
+ }
+ if (fstat(fd, &stb) != 0) {
+ perror("rtacct: fstat history file");
+ exit(-1);
+ }
+ if (stb.st_nlink != 1 || stb.st_uid != getuid()) {
+ fprintf(stderr, "rtacct: something is so wrong with history file, that I prefer not to proceed.\n");
+ exit(-1);
+ }
+ if (stb.st_size != sizeof(*hist_db))
+ if (write(fd, kern_db, sizeof(*hist_db)) < 0) {
+ perror("rtacct: write history file");
+ exit(-1);
+ }
+
+ hist_db = mmap(NULL, sizeof(*hist_db),
+ PROT_READ|PROT_WRITE,
+ no_update ? MAP_PRIVATE : MAP_SHARED,
+ fd, 0);
+
+ if ((unsigned long)hist_db == ~0UL) {
+ perror("mmap");
+ exit(-1);
+ }
+
+ if (!ignore_history) {
+ FILE *tfp;
+ long uptime = -1;
+
+ if ((tfp = fopen("/proc/uptime", "r")) != NULL) {
+ if (fscanf(tfp, "%ld", &uptime) != 1)
+ uptime = -1;
+ fclose(tfp);
+ }
+
+ if (uptime >= 0 && time(NULL) >= stb.st_mtime+uptime) {
+ fprintf(stderr, "rtacct: history is aged out, resetting\n");
+ memset(hist_db, 0, sizeof(*hist_db));
+ }
+ }
+
+ close(fd);
+ }
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 &&
+ (connect(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) == 0
+ || (strcpy(sun.sun_path+1, "rtacct0"),
+ connect(fd, (struct sockaddr *)&sun, 2+1+strlen(sun.sun_path+1)) == 0))
+ && verify_forging(fd) == 0) {
+ nread(fd, (char *)kern_db, sizeof(*kern_db));
+ if (hist_db && hist_db->signature[0] &&
+ strcmp(kern_db->signature, hist_db->signature)) {
+ fprintf(stderr, "rtacct: history is stale, ignoring it.\n");
+ hist_db = NULL;
+ }
+ close(fd);
+ } else {
+ if (fd >= 0)
+ close(fd);
+
+ if (hist_db && hist_db->signature[0] &&
+ strcmp(hist_db->signature, "kernel")) {
+ fprintf(stderr, "rtacct: history is stale, ignoring it.\n");
+ hist_db = NULL;
+ }
+
+ pad_kern_table(kern_db, read_kern_table(kern_db->ival));
+ strcpy(kern_db->signature, "kernel");
+ }
+
+ if (ignore_history || hist_db == NULL)
+ dump_abs_db(stdout);
+ else
+ dump_incr_db(stdout);
+
+ exit(0);
+}
diff --git a/misc/ss.c b/misc/ss.c
new file mode 100644
index 0000000..794c189
--- /dev/null
+++ b/misc/ss.c
@@ -0,0 +1,5354 @@
+/*
+ * ss.c "sockstat", socket statistics
+ *
+ * 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 <sys/uio.h>
+#include <sys/sysmacros.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <fnmatch.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <stdarg.h>
+
+#include "utils.h"
+#include "rt_names.h"
+#include "ll_map.h"
+#include "libnetlink.h"
+#include "namespace.h"
+#include "SNAPSHOT.h"
+
+#include <linux/tcp.h>
+#include <linux/sock_diag.h>
+#include <linux/inet_diag.h>
+#include <linux/unix_diag.h>
+#include <linux/netdevice.h> /* for MAX_ADDR_LEN */
+#include <linux/filter.h>
+#include <linux/xdp_diag.h>
+#include <linux/packet_diag.h>
+#include <linux/netlink_diag.h>
+#include <linux/sctp.h>
+#include <linux/vm_sockets_diag.h>
+#include <linux/net.h>
+#include <linux/tipc.h>
+#include <linux/tipc_netlink.h>
+#include <linux/tipc_sockets_diag.h>
+
+/* AF_VSOCK/PF_VSOCK is only provided since glibc 2.18 */
+#ifndef PF_VSOCK
+#define PF_VSOCK 40
+#endif
+#ifndef AF_VSOCK
+#define AF_VSOCK PF_VSOCK
+#endif
+
+#define MAGIC_SEQ 123456
+#define BUF_CHUNK (1024 * 1024) /* Buffer chunk allocation size */
+#define BUF_CHUNKS_MAX 5 /* Maximum number of allocated buffer chunks */
+#define LEN_ALIGN(x) (((x) + 1) & ~1)
+
+#define DIAG_REQUEST(_req, _r) \
+ struct { \
+ struct nlmsghdr nlh; \
+ _r; \
+ } _req = { \
+ .nlh = { \
+ .nlmsg_type = SOCK_DIAG_BY_FAMILY, \
+ .nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST,\
+ .nlmsg_seq = MAGIC_SEQ, \
+ .nlmsg_len = sizeof(_req), \
+ }, \
+ }
+
+#if HAVE_SELINUX
+#include <selinux/selinux.h>
+#else
+/* Stubs for SELinux functions */
+static int is_selinux_enabled(void)
+{
+ return -1;
+}
+
+static int getpidcon(pid_t pid, char **context)
+{
+ *context = NULL;
+ return -1;
+}
+
+static int getfilecon(char *path, char **context)
+{
+ *context = NULL;
+ return -1;
+}
+
+static int security_get_initial_context(char *name, char **context)
+{
+ *context = NULL;
+ return -1;
+}
+#endif
+
+int preferred_family = AF_UNSPEC;
+static int show_options;
+int show_details;
+static int show_users;
+static int show_mem;
+static int show_tcpinfo;
+static int show_bpf;
+static int show_proc_ctx;
+static int show_sock_ctx;
+static int show_header = 1;
+static int follow_events;
+static int sctp_ino;
+static int show_tipcinfo;
+static int show_tos;
+int numeric;
+int oneline;
+
+enum col_id {
+ COL_NETID,
+ COL_STATE,
+ COL_RECVQ,
+ COL_SENDQ,
+ COL_ADDR,
+ COL_SERV,
+ COL_RADDR,
+ COL_RSERV,
+ COL_EXT,
+ COL_PROC,
+ COL_MAX
+};
+
+enum col_align {
+ ALIGN_LEFT,
+ ALIGN_CENTER,
+ ALIGN_RIGHT
+};
+
+struct column {
+ const enum col_align align;
+ const char *header;
+ const char *ldelim;
+ int disabled;
+ int width; /* Calculated, including additional layout spacing */
+ int max_len; /* Measured maximum field length in this column */
+};
+
+static struct column columns[] = {
+ { ALIGN_LEFT, "Netid", "", 0, 0, 0 },
+ { ALIGN_LEFT, "State", " ", 0, 0, 0 },
+ { ALIGN_LEFT, "Recv-Q", " ", 0, 0, 0 },
+ { ALIGN_LEFT, "Send-Q", " ", 0, 0, 0 },
+ { ALIGN_RIGHT, "Local Address:", " ", 0, 0, 0 },
+ { ALIGN_LEFT, "Port", "", 0, 0, 0 },
+ { ALIGN_RIGHT, "Peer Address:", " ", 0, 0, 0 },
+ { ALIGN_LEFT, "Port", "", 0, 0, 0 },
+ { ALIGN_LEFT, "Process", "", 0, 0, 0 },
+ { ALIGN_LEFT, "", "", 0, 0, 0 },
+};
+
+static struct column *current_field = columns;
+
+/* Output buffer: chained chunks of BUF_CHUNK bytes. Each field is written to
+ * the buffer as a variable size token. A token consists of a 16 bits length
+ * field, followed by a string which is not NULL-terminated.
+ *
+ * A new chunk is allocated and linked when the current chunk doesn't have
+ * enough room to store the current token as a whole.
+ */
+struct buf_chunk {
+ struct buf_chunk *next; /* Next chained chunk */
+ char *end; /* Current end of content */
+ char data[0];
+};
+
+struct buf_token {
+ uint16_t len; /* Data length, excluding length descriptor */
+ char data[0];
+};
+
+static struct {
+ struct buf_token *cur; /* Position of current token in chunk */
+ struct buf_chunk *head; /* First chunk */
+ struct buf_chunk *tail; /* Current chunk */
+ int chunks; /* Number of allocated chunks */
+} buffer;
+
+static const char *TCP_PROTO = "tcp";
+static const char *SCTP_PROTO = "sctp";
+static const char *UDP_PROTO = "udp";
+static const char *RAW_PROTO = "raw";
+static const char *dg_proto;
+
+enum {
+ TCP_DB,
+ DCCP_DB,
+ UDP_DB,
+ RAW_DB,
+ UNIX_DG_DB,
+ UNIX_ST_DB,
+ UNIX_SQ_DB,
+ PACKET_DG_DB,
+ PACKET_R_DB,
+ NETLINK_DB,
+ SCTP_DB,
+ VSOCK_ST_DB,
+ VSOCK_DG_DB,
+ TIPC_DB,
+ XDP_DB,
+ MAX_DB
+};
+
+#define PACKET_DBM ((1<<PACKET_DG_DB)|(1<<PACKET_R_DB))
+#define UNIX_DBM ((1<<UNIX_DG_DB)|(1<<UNIX_ST_DB)|(1<<UNIX_SQ_DB))
+#define ALL_DB ((1<<MAX_DB)-1)
+#define INET_L4_DBM ((1<<TCP_DB)|(1<<UDP_DB)|(1<<DCCP_DB)|(1<<SCTP_DB))
+#define INET_DBM (INET_L4_DBM | (1<<RAW_DB))
+#define VSOCK_DBM ((1<<VSOCK_ST_DB)|(1<<VSOCK_DG_DB))
+
+enum {
+ SS_UNKNOWN,
+ SS_ESTABLISHED,
+ SS_SYN_SENT,
+ SS_SYN_RECV,
+ SS_FIN_WAIT1,
+ SS_FIN_WAIT2,
+ SS_TIME_WAIT,
+ SS_CLOSE,
+ SS_CLOSE_WAIT,
+ SS_LAST_ACK,
+ SS_LISTEN,
+ SS_CLOSING,
+ SS_MAX
+};
+
+enum {
+ SCTP_STATE_CLOSED = 0,
+ SCTP_STATE_COOKIE_WAIT = 1,
+ SCTP_STATE_COOKIE_ECHOED = 2,
+ SCTP_STATE_ESTABLISHED = 3,
+ SCTP_STATE_SHUTDOWN_PENDING = 4,
+ SCTP_STATE_SHUTDOWN_SENT = 5,
+ SCTP_STATE_SHUTDOWN_RECEIVED = 6,
+ SCTP_STATE_SHUTDOWN_ACK_SENT = 7,
+};
+
+#define SS_ALL ((1 << SS_MAX) - 1)
+#define SS_CONN (SS_ALL & ~((1<<SS_LISTEN)|(1<<SS_CLOSE)|(1<<SS_TIME_WAIT)|(1<<SS_SYN_RECV)))
+#define TIPC_SS_CONN ((1<<SS_ESTABLISHED)|(1<<SS_LISTEN)|(1<<SS_CLOSE))
+
+#include "ssfilter.h"
+
+struct filter {
+ int dbs;
+ int states;
+ uint64_t families;
+ struct ssfilter *f;
+ bool kill;
+ struct rtnl_handle *rth_for_killing;
+};
+
+#define FAMILY_MASK(family) ((uint64_t)1 << (family))
+
+static const struct filter default_dbs[MAX_DB] = {
+ [TCP_DB] = {
+ .states = SS_CONN,
+ .families = FAMILY_MASK(AF_INET) | FAMILY_MASK(AF_INET6),
+ },
+ [DCCP_DB] = {
+ .states = SS_CONN,
+ .families = FAMILY_MASK(AF_INET) | FAMILY_MASK(AF_INET6),
+ },
+ [UDP_DB] = {
+ .states = (1 << SS_ESTABLISHED),
+ .families = FAMILY_MASK(AF_INET) | FAMILY_MASK(AF_INET6),
+ },
+ [RAW_DB] = {
+ .states = (1 << SS_ESTABLISHED),
+ .families = FAMILY_MASK(AF_INET) | FAMILY_MASK(AF_INET6),
+ },
+ [UNIX_DG_DB] = {
+ .states = (1 << SS_CLOSE),
+ .families = FAMILY_MASK(AF_UNIX),
+ },
+ [UNIX_ST_DB] = {
+ .states = SS_CONN,
+ .families = FAMILY_MASK(AF_UNIX),
+ },
+ [UNIX_SQ_DB] = {
+ .states = SS_CONN,
+ .families = FAMILY_MASK(AF_UNIX),
+ },
+ [PACKET_DG_DB] = {
+ .states = (1 << SS_CLOSE),
+ .families = FAMILY_MASK(AF_PACKET),
+ },
+ [PACKET_R_DB] = {
+ .states = (1 << SS_CLOSE),
+ .families = FAMILY_MASK(AF_PACKET),
+ },
+ [NETLINK_DB] = {
+ .states = (1 << SS_CLOSE),
+ .families = FAMILY_MASK(AF_NETLINK),
+ },
+ [SCTP_DB] = {
+ .states = SS_CONN,
+ .families = FAMILY_MASK(AF_INET) | FAMILY_MASK(AF_INET6),
+ },
+ [VSOCK_ST_DB] = {
+ .states = SS_CONN,
+ .families = FAMILY_MASK(AF_VSOCK),
+ },
+ [VSOCK_DG_DB] = {
+ .states = SS_CONN,
+ .families = FAMILY_MASK(AF_VSOCK),
+ },
+ [TIPC_DB] = {
+ .states = TIPC_SS_CONN,
+ .families = FAMILY_MASK(AF_TIPC),
+ },
+ [XDP_DB] = {
+ .states = (1 << SS_CLOSE),
+ .families = FAMILY_MASK(AF_XDP),
+ },
+};
+
+static const struct filter default_afs[AF_MAX] = {
+ [AF_INET] = {
+ .dbs = INET_DBM,
+ .states = SS_CONN,
+ },
+ [AF_INET6] = {
+ .dbs = INET_DBM,
+ .states = SS_CONN,
+ },
+ [AF_UNIX] = {
+ .dbs = UNIX_DBM,
+ .states = SS_CONN,
+ },
+ [AF_PACKET] = {
+ .dbs = PACKET_DBM,
+ .states = (1 << SS_CLOSE),
+ },
+ [AF_NETLINK] = {
+ .dbs = (1 << NETLINK_DB),
+ .states = (1 << SS_CLOSE),
+ },
+ [AF_VSOCK] = {
+ .dbs = VSOCK_DBM,
+ .states = SS_CONN,
+ },
+ [AF_TIPC] = {
+ .dbs = (1 << TIPC_DB),
+ .states = TIPC_SS_CONN,
+ },
+ [AF_XDP] = {
+ .dbs = (1 << XDP_DB),
+ .states = (1 << SS_CLOSE),
+ },
+};
+
+static int do_default = 1;
+static struct filter current_filter;
+
+static void filter_db_set(struct filter *f, int db, bool enable)
+{
+ if (enable) {
+ f->states |= default_dbs[db].states;
+ f->dbs |= 1 << db;
+ } else {
+ f->dbs &= ~(1 << db);
+ }
+ do_default = 0;
+}
+
+static int filter_db_parse(struct filter *f, const char *s)
+{
+ const struct {
+ const char *name;
+ int dbs[MAX_DB + 1];
+ } db_name_tbl[] = {
+#define ENTRY(name, ...) { #name, { __VA_ARGS__, MAX_DB } }
+ ENTRY(all, UDP_DB, DCCP_DB, TCP_DB, RAW_DB,
+ UNIX_ST_DB, UNIX_DG_DB, UNIX_SQ_DB,
+ PACKET_R_DB, PACKET_DG_DB, NETLINK_DB,
+ SCTP_DB, VSOCK_ST_DB, VSOCK_DG_DB, XDP_DB),
+ ENTRY(inet, UDP_DB, DCCP_DB, TCP_DB, SCTP_DB, RAW_DB),
+ ENTRY(udp, UDP_DB),
+ ENTRY(dccp, DCCP_DB),
+ ENTRY(tcp, TCP_DB),
+ ENTRY(sctp, SCTP_DB),
+ ENTRY(raw, RAW_DB),
+ ENTRY(unix, UNIX_ST_DB, UNIX_DG_DB, UNIX_SQ_DB),
+ ENTRY(unix_stream, UNIX_ST_DB),
+ ENTRY(u_str, UNIX_ST_DB), /* alias for unix_stream */
+ ENTRY(unix_dgram, UNIX_DG_DB),
+ ENTRY(u_dgr, UNIX_DG_DB), /* alias for unix_dgram */
+ ENTRY(unix_seqpacket, UNIX_SQ_DB),
+ ENTRY(u_seq, UNIX_SQ_DB), /* alias for unix_seqpacket */
+ ENTRY(packet, PACKET_R_DB, PACKET_DG_DB),
+ ENTRY(packet_raw, PACKET_R_DB),
+ ENTRY(p_raw, PACKET_R_DB), /* alias for packet_raw */
+ ENTRY(packet_dgram, PACKET_DG_DB),
+ ENTRY(p_dgr, PACKET_DG_DB), /* alias for packet_dgram */
+ ENTRY(netlink, NETLINK_DB),
+ ENTRY(vsock, VSOCK_ST_DB, VSOCK_DG_DB),
+ ENTRY(vsock_stream, VSOCK_ST_DB),
+ ENTRY(v_str, VSOCK_ST_DB), /* alias for vsock_stream */
+ ENTRY(vsock_dgram, VSOCK_DG_DB),
+ ENTRY(v_dgr, VSOCK_DG_DB), /* alias for vsock_dgram */
+ ENTRY(xdp, XDP_DB),
+#undef ENTRY
+ };
+ bool enable = true;
+ unsigned int i;
+ const int *dbp;
+
+ if (s[0] == '!') {
+ enable = false;
+ s++;
+ }
+ for (i = 0; i < ARRAY_SIZE(db_name_tbl); i++) {
+ if (strcmp(s, db_name_tbl[i].name))
+ continue;
+ for (dbp = db_name_tbl[i].dbs; *dbp != MAX_DB; dbp++)
+ filter_db_set(f, *dbp, enable);
+ return 0;
+ }
+ return -1;
+}
+
+static void filter_af_set(struct filter *f, int af)
+{
+ f->states |= default_afs[af].states;
+ f->families |= FAMILY_MASK(af);
+ do_default = 0;
+ preferred_family = af;
+}
+
+static int filter_af_get(struct filter *f, int af)
+{
+ return !!(f->families & FAMILY_MASK(af));
+}
+
+static void filter_states_set(struct filter *f, int states)
+{
+ if (states)
+ f->states = states;
+}
+
+static void filter_merge_defaults(struct filter *f)
+{
+ int db;
+ int af;
+
+ for (db = 0; db < MAX_DB; db++) {
+ if (!(f->dbs & (1 << db)))
+ continue;
+
+ if (!(default_dbs[db].families & f->families))
+ f->families |= default_dbs[db].families;
+ }
+ for (af = 0; af < AF_MAX; af++) {
+ if (!(f->families & FAMILY_MASK(af)))
+ continue;
+
+ if (!(default_afs[af].dbs & f->dbs))
+ f->dbs |= default_afs[af].dbs;
+ }
+}
+
+static FILE *generic_proc_open(const char *env, const char *name)
+{
+ const char *p = getenv(env);
+ char store[128];
+
+ if (!p) {
+ p = getenv("PROC_ROOT") ? : "/proc";
+ snprintf(store, sizeof(store)-1, "%s/%s", p, name);
+ p = store;
+ }
+
+ return fopen(p, "r");
+}
+#define net_tcp_open() generic_proc_open("PROC_NET_TCP", "net/tcp")
+#define net_tcp6_open() generic_proc_open("PROC_NET_TCP6", "net/tcp6")
+#define net_udp_open() generic_proc_open("PROC_NET_UDP", "net/udp")
+#define net_udp6_open() generic_proc_open("PROC_NET_UDP6", "net/udp6")
+#define net_raw_open() generic_proc_open("PROC_NET_RAW", "net/raw")
+#define net_raw6_open() generic_proc_open("PROC_NET_RAW6", "net/raw6")
+#define net_unix_open() generic_proc_open("PROC_NET_UNIX", "net/unix")
+#define net_packet_open() generic_proc_open("PROC_NET_PACKET", \
+ "net/packet")
+#define net_netlink_open() generic_proc_open("PROC_NET_NETLINK", \
+ "net/netlink")
+#define net_sockstat_open() generic_proc_open("PROC_NET_SOCKSTAT", \
+ "net/sockstat")
+#define net_sockstat6_open() generic_proc_open("PROC_NET_SOCKSTAT6", \
+ "net/sockstat6")
+#define net_snmp_open() generic_proc_open("PROC_NET_SNMP", "net/snmp")
+#define ephemeral_ports_open() generic_proc_open("PROC_IP_LOCAL_PORT_RANGE", \
+ "sys/net/ipv4/ip_local_port_range")
+
+struct user_ent {
+ struct user_ent *next;
+ unsigned int ino;
+ int pid;
+ int fd;
+ char *process;
+ char *process_ctx;
+ char *socket_ctx;
+};
+
+#define USER_ENT_HASH_SIZE 256
+static struct user_ent *user_ent_hash[USER_ENT_HASH_SIZE];
+
+static int user_ent_hashfn(unsigned int ino)
+{
+ int val = (ino >> 24) ^ (ino >> 16) ^ (ino >> 8) ^ ino;
+
+ return val & (USER_ENT_HASH_SIZE - 1);
+}
+
+static void user_ent_add(unsigned int ino, char *process,
+ int pid, int fd,
+ char *proc_ctx,
+ char *sock_ctx)
+{
+ struct user_ent *p, **pp;
+
+ p = malloc(sizeof(struct user_ent));
+ if (!p) {
+ fprintf(stderr, "ss: failed to malloc buffer\n");
+ abort();
+ }
+ p->next = NULL;
+ p->ino = ino;
+ p->pid = pid;
+ p->fd = fd;
+ p->process = strdup(process);
+ p->process_ctx = strdup(proc_ctx);
+ p->socket_ctx = strdup(sock_ctx);
+
+ pp = &user_ent_hash[user_ent_hashfn(ino)];
+ p->next = *pp;
+ *pp = p;
+}
+
+static void user_ent_destroy(void)
+{
+ struct user_ent *p, *p_next;
+ int cnt = 0;
+
+ while (cnt != USER_ENT_HASH_SIZE) {
+ p = user_ent_hash[cnt];
+ while (p) {
+ free(p->process);
+ free(p->process_ctx);
+ free(p->socket_ctx);
+ p_next = p->next;
+ free(p);
+ p = p_next;
+ }
+ cnt++;
+ }
+}
+
+static void user_ent_hash_build(void)
+{
+ const char *root = getenv("PROC_ROOT") ? : "/proc/";
+ struct dirent *d;
+ char name[1024];
+ int nameoff;
+ DIR *dir;
+ char *pid_context;
+ char *sock_context;
+ const char *no_ctx = "unavailable";
+ static int user_ent_hash_build_init;
+
+ /* If show_users & show_proc_ctx set only do this once */
+ if (user_ent_hash_build_init != 0)
+ return;
+
+ user_ent_hash_build_init = 1;
+
+ strlcpy(name, root, sizeof(name));
+
+ if (strlen(name) == 0 || name[strlen(name)-1] != '/')
+ strcat(name, "/");
+
+ nameoff = strlen(name);
+
+ dir = opendir(name);
+ if (!dir)
+ return;
+
+ while ((d = readdir(dir)) != NULL) {
+ struct dirent *d1;
+ char process[16];
+ char *p;
+ int pid, pos;
+ DIR *dir1;
+ char crap;
+
+ if (sscanf(d->d_name, "%d%c", &pid, &crap) != 1)
+ continue;
+
+ if (getpidcon(pid, &pid_context) != 0)
+ pid_context = strdup(no_ctx);
+
+ snprintf(name + nameoff, sizeof(name) - nameoff, "%d/fd/", pid);
+ pos = strlen(name);
+ if ((dir1 = opendir(name)) == NULL) {
+ free(pid_context);
+ continue;
+ }
+
+ process[0] = '\0';
+ p = process;
+
+ while ((d1 = readdir(dir1)) != NULL) {
+ const char *pattern = "socket:[";
+ unsigned int ino;
+ char lnk[64];
+ int fd;
+ ssize_t link_len;
+ char tmp[1024];
+
+ if (sscanf(d1->d_name, "%d%c", &fd, &crap) != 1)
+ continue;
+
+ snprintf(name+pos, sizeof(name) - pos, "%d", fd);
+
+ link_len = readlink(name, lnk, sizeof(lnk)-1);
+ if (link_len == -1)
+ continue;
+ lnk[link_len] = '\0';
+
+ if (strncmp(lnk, pattern, strlen(pattern)))
+ continue;
+
+ sscanf(lnk, "socket:[%u]", &ino);
+
+ snprintf(tmp, sizeof(tmp), "%s/%d/fd/%s",
+ root, pid, d1->d_name);
+
+ if (getfilecon(tmp, &sock_context) <= 0)
+ sock_context = strdup(no_ctx);
+
+ if (*p == '\0') {
+ FILE *fp;
+
+ snprintf(tmp, sizeof(tmp), "%s/%d/stat",
+ root, pid);
+ if ((fp = fopen(tmp, "r")) != NULL) {
+ if (fscanf(fp, "%*d (%[^)])", p) < 1)
+ ; /* ignore */
+ fclose(fp);
+ }
+ }
+ user_ent_add(ino, p, pid, fd,
+ pid_context, sock_context);
+ free(sock_context);
+ }
+ free(pid_context);
+ closedir(dir1);
+ }
+ closedir(dir);
+}
+
+enum entry_types {
+ USERS,
+ PROC_CTX,
+ PROC_SOCK_CTX
+};
+
+#define ENTRY_BUF_SIZE 512
+static int find_entry(unsigned int ino, char **buf, int type)
+{
+ struct user_ent *p;
+ int cnt = 0;
+ char *ptr;
+ char *new_buf;
+ int len, new_buf_len;
+ int buf_used = 0;
+ int buf_len = 0;
+
+ if (!ino)
+ return 0;
+
+ p = user_ent_hash[user_ent_hashfn(ino)];
+ ptr = *buf = NULL;
+ while (p) {
+ if (p->ino != ino)
+ goto next;
+
+ while (1) {
+ ptr = *buf + buf_used;
+ switch (type) {
+ case USERS:
+ len = snprintf(ptr, buf_len - buf_used,
+ "(\"%s\",pid=%d,fd=%d),",
+ p->process, p->pid, p->fd);
+ break;
+ case PROC_CTX:
+ len = snprintf(ptr, buf_len - buf_used,
+ "(\"%s\",pid=%d,proc_ctx=%s,fd=%d),",
+ p->process, p->pid,
+ p->process_ctx, p->fd);
+ break;
+ case PROC_SOCK_CTX:
+ len = snprintf(ptr, buf_len - buf_used,
+ "(\"%s\",pid=%d,proc_ctx=%s,fd=%d,sock_ctx=%s),",
+ p->process, p->pid,
+ p->process_ctx, p->fd,
+ p->socket_ctx);
+ break;
+ default:
+ fprintf(stderr, "ss: invalid type: %d\n", type);
+ abort();
+ }
+
+ if (len < 0 || len >= buf_len - buf_used) {
+ new_buf_len = buf_len + ENTRY_BUF_SIZE;
+ new_buf = realloc(*buf, new_buf_len);
+ if (!new_buf) {
+ fprintf(stderr, "ss: failed to malloc buffer\n");
+ abort();
+ }
+ *buf = new_buf;
+ buf_len = new_buf_len;
+ continue;
+ } else {
+ buf_used += len;
+ break;
+ }
+ }
+ cnt++;
+next:
+ p = p->next;
+ }
+ if (buf_used) {
+ ptr = *buf + buf_used;
+ ptr[-1] = '\0';
+ }
+ return cnt;
+}
+
+static unsigned long long cookie_sk_get(const uint32_t *cookie)
+{
+ return (((unsigned long long)cookie[1] << 31) << 1) | cookie[0];
+}
+
+static const char *sctp_sstate_name[] = {
+ [SCTP_STATE_CLOSED] = "CLOSED",
+ [SCTP_STATE_COOKIE_WAIT] = "COOKIE_WAIT",
+ [SCTP_STATE_COOKIE_ECHOED] = "COOKIE_ECHOED",
+ [SCTP_STATE_ESTABLISHED] = "ESTAB",
+ [SCTP_STATE_SHUTDOWN_PENDING] = "SHUTDOWN_PENDING",
+ [SCTP_STATE_SHUTDOWN_SENT] = "SHUTDOWN_SENT",
+ [SCTP_STATE_SHUTDOWN_RECEIVED] = "SHUTDOWN_RECEIVED",
+ [SCTP_STATE_SHUTDOWN_ACK_SENT] = "ACK_SENT",
+};
+
+static const char * const stype_nameg[] = {
+ "UNKNOWN",
+ [SOCK_STREAM] = "STREAM",
+ [SOCK_DGRAM] = "DGRAM",
+ [SOCK_RDM] = "RDM",
+ [SOCK_SEQPACKET] = "SEQPACKET",
+};
+
+struct sockstat {
+ struct sockstat *next;
+ unsigned int type;
+ uint16_t prot;
+ uint16_t raw_prot;
+ inet_prefix local;
+ inet_prefix remote;
+ int lport;
+ int rport;
+ int state;
+ int rq, wq;
+ unsigned int ino;
+ unsigned int uid;
+ int refcnt;
+ unsigned int iface;
+ unsigned long long sk;
+ char *name;
+ char *peer_name;
+ __u32 mark;
+};
+
+struct dctcpstat {
+ unsigned int ce_state;
+ unsigned int alpha;
+ unsigned int ab_ecn;
+ unsigned int ab_tot;
+ bool enabled;
+};
+
+struct tcpstat {
+ struct sockstat ss;
+ unsigned int timer;
+ unsigned int timeout;
+ int probes;
+ char cong_alg[16];
+ double rto, ato, rtt, rttvar;
+ int qack, ssthresh, backoff;
+ double send_bps;
+ int snd_wscale;
+ int rcv_wscale;
+ int mss;
+ int rcv_mss;
+ int advmss;
+ unsigned int pmtu;
+ unsigned int cwnd;
+ unsigned int lastsnd;
+ unsigned int lastrcv;
+ unsigned int lastack;
+ double pacing_rate;
+ double pacing_rate_max;
+ double delivery_rate;
+ unsigned long long bytes_acked;
+ unsigned long long bytes_received;
+ unsigned int segs_out;
+ unsigned int segs_in;
+ unsigned int data_segs_out;
+ unsigned int data_segs_in;
+ unsigned int unacked;
+ unsigned int retrans;
+ unsigned int retrans_total;
+ unsigned int lost;
+ unsigned int sacked;
+ unsigned int fackets;
+ unsigned int reordering;
+ unsigned int not_sent;
+ unsigned int delivered;
+ unsigned int delivered_ce;
+ unsigned int dsack_dups;
+ unsigned int reord_seen;
+ double rcv_rtt;
+ double min_rtt;
+ int rcv_space;
+ unsigned int rcv_ssthresh;
+ unsigned long long busy_time;
+ unsigned long long rwnd_limited;
+ unsigned long long sndbuf_limited;
+ unsigned long long bytes_sent;
+ unsigned long long bytes_retrans;
+ bool has_ts_opt;
+ bool has_sack_opt;
+ bool has_ecn_opt;
+ bool has_ecnseen_opt;
+ bool has_fastopen_opt;
+ bool has_wscale_opt;
+ bool app_limited;
+ struct dctcpstat *dctcp;
+ struct tcp_bbr_info *bbr_info;
+};
+
+/* SCTP assocs share the same inode number with their parent endpoint. So if we
+ * have seen the inode number before, it must be an assoc instead of the next
+ * endpoint. */
+static bool is_sctp_assoc(struct sockstat *s, const char *sock_name)
+{
+ if (strcmp(sock_name, "sctp"))
+ return false;
+ if (!sctp_ino || sctp_ino != s->ino)
+ return false;
+ return true;
+}
+
+static const char *unix_netid_name(int type)
+{
+ switch (type) {
+ case SOCK_STREAM:
+ return "u_str";
+ case SOCK_SEQPACKET:
+ return "u_seq";
+ case SOCK_DGRAM:
+ default:
+ return "u_dgr";
+ }
+}
+
+static const char *proto_name(int protocol)
+{
+ switch (protocol) {
+ case 0:
+ return "raw";
+ case IPPROTO_UDP:
+ return "udp";
+ case IPPROTO_TCP:
+ return "tcp";
+ case IPPROTO_SCTP:
+ return "sctp";
+ case IPPROTO_DCCP:
+ return "dccp";
+ case IPPROTO_ICMPV6:
+ return "icmp6";
+ }
+
+ return "???";
+}
+
+static const char *vsock_netid_name(int type)
+{
+ switch (type) {
+ case SOCK_STREAM:
+ return "v_str";
+ case SOCK_DGRAM:
+ return "v_dgr";
+ default:
+ return "???";
+ }
+}
+
+static const char *tipc_netid_name(int type)
+{
+ switch (type) {
+ case SOCK_STREAM:
+ return "ti_st";
+ case SOCK_DGRAM:
+ return "ti_dg";
+ case SOCK_RDM:
+ return "ti_rd";
+ case SOCK_SEQPACKET:
+ return "ti_sq";
+ default:
+ return "???";
+ }
+}
+
+/* Allocate and initialize a new buffer chunk */
+static struct buf_chunk *buf_chunk_new(void)
+{
+ struct buf_chunk *new = malloc(BUF_CHUNK);
+
+ if (!new)
+ abort();
+
+ new->next = NULL;
+
+ /* This is also the last block */
+ buffer.tail = new;
+
+ /* Next token will be stored at the beginning of chunk data area, and
+ * its initial length is zero.
+ */
+ buffer.cur = (struct buf_token *)new->data;
+ buffer.cur->len = 0;
+
+ new->end = buffer.cur->data;
+
+ buffer.chunks++;
+
+ return new;
+}
+
+/* Return available tail room in given chunk */
+static int buf_chunk_avail(struct buf_chunk *chunk)
+{
+ return BUF_CHUNK - offsetof(struct buf_chunk, data) -
+ (chunk->end - chunk->data);
+}
+
+/* Update end pointer and token length, link new chunk if we hit the end of the
+ * current one. Return -EAGAIN if we got a new chunk, caller has to print again.
+ */
+static int buf_update(int len)
+{
+ struct buf_chunk *chunk = buffer.tail;
+ struct buf_token *t = buffer.cur;
+
+ /* Claim success if new content fits in the current chunk, and anyway
+ * if this is the first token in the chunk: in the latter case,
+ * allocating a new chunk won't help, so we'll just cut the output.
+ */
+ if ((len < buf_chunk_avail(chunk) && len != -1 /* glibc < 2.0.6 */) ||
+ t == (struct buf_token *)chunk->data) {
+ len = min(len, buf_chunk_avail(chunk));
+
+ /* Total field length can't exceed 2^16 bytes, cut as needed */
+ len = min(len, USHRT_MAX - t->len);
+
+ chunk->end += len;
+ t->len += len;
+ return 0;
+ }
+
+ /* Content truncated, time to allocate more */
+ chunk->next = buf_chunk_new();
+
+ /* Copy current token over to new chunk, including length descriptor */
+ memcpy(chunk->next->data, t, sizeof(t->len) + t->len);
+ chunk->next->end += t->len;
+
+ /* Discard partially written field in old chunk */
+ chunk->end -= t->len + sizeof(t->len);
+
+ return -EAGAIN;
+}
+
+/* Append content to buffer as part of the current field */
+__attribute__((format(printf, 1, 2)))
+static void out(const char *fmt, ...)
+{
+ struct column *f = current_field;
+ va_list args;
+ char *pos;
+ int len;
+
+ if (f->disabled)
+ return;
+
+ if (!buffer.head)
+ buffer.head = buf_chunk_new();
+
+again: /* Append to buffer: if we have a new chunk, print again */
+
+ pos = buffer.cur->data + buffer.cur->len;
+ va_start(args, fmt);
+
+ /* Limit to tail room. If we hit the limit, buf_update() will tell us */
+ len = vsnprintf(pos, buf_chunk_avail(buffer.tail), fmt, args);
+ va_end(args);
+
+ if (buf_update(len))
+ goto again;
+}
+
+static int print_left_spacing(struct column *f, int stored, int printed)
+{
+ int s;
+
+ if (!f->width || f->align == ALIGN_LEFT)
+ return 0;
+
+ s = f->width - stored - printed;
+ if (f->align == ALIGN_CENTER)
+ /* If count of total spacing is odd, shift right by one */
+ s = (s + 1) / 2;
+
+ if (s > 0)
+ return printf("%*c", s, ' ');
+
+ return 0;
+}
+
+static void print_right_spacing(struct column *f, int printed)
+{
+ int s;
+
+ if (!f->width || f->align == ALIGN_RIGHT)
+ return;
+
+ s = f->width - printed;
+ if (f->align == ALIGN_CENTER)
+ s /= 2;
+
+ if (s > 0)
+ printf("%*c", s, ' ');
+}
+
+/* Done with field: update buffer pointer, start new token after current one */
+static void field_flush(struct column *f)
+{
+ struct buf_chunk *chunk;
+ unsigned int pad;
+
+ if (f->disabled)
+ return;
+
+ chunk = buffer.tail;
+ pad = buffer.cur->len % 2;
+
+ if (buffer.cur->len > f->max_len)
+ f->max_len = buffer.cur->len;
+
+ /* We need a new chunk if we can't store the next length descriptor.
+ * Mind the gap between end of previous token and next aligned position
+ * for length descriptor.
+ */
+ if (buf_chunk_avail(chunk) - pad < sizeof(buffer.cur->len)) {
+ chunk->end += pad;
+ chunk->next = buf_chunk_new();
+ return;
+ }
+
+ buffer.cur = (struct buf_token *)(buffer.cur->data +
+ LEN_ALIGN(buffer.cur->len));
+ buffer.cur->len = 0;
+ buffer.tail->end = buffer.cur->data;
+}
+
+static int field_is_last(struct column *f)
+{
+ return f - columns == COL_MAX - 1;
+}
+
+/* Get the next available token in the buffer starting from the current token */
+static struct buf_token *buf_token_next(struct buf_token *cur)
+{
+ struct buf_chunk *chunk = buffer.tail;
+
+ /* If we reached the end of chunk contents, get token from next chunk */
+ if (cur->data + LEN_ALIGN(cur->len) == chunk->end) {
+ buffer.tail = chunk = chunk->next;
+ return chunk ? (struct buf_token *)chunk->data : NULL;
+ }
+
+ return (struct buf_token *)(cur->data + LEN_ALIGN(cur->len));
+}
+
+/* Free up all allocated buffer chunks */
+static void buf_free_all(void)
+{
+ struct buf_chunk *tmp;
+
+ for (buffer.tail = buffer.head; buffer.tail; ) {
+ tmp = buffer.tail;
+ buffer.tail = buffer.tail->next;
+ free(tmp);
+ }
+ buffer.head = NULL;
+ buffer.chunks = 0;
+}
+
+/* Get current screen width, default to 80 columns if TIOCGWINSZ fails */
+static int render_screen_width(void)
+{
+ int width = 80;
+
+ if (isatty(STDOUT_FILENO)) {
+ struct winsize w;
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) {
+ if (w.ws_col > 0)
+ width = w.ws_col;
+ }
+ }
+
+ return width;
+}
+
+/* Calculate column width from contents length. If columns don't fit on one
+ * line, break them into the least possible amount of lines and keep them
+ * aligned across lines. Available screen space is equally spread between fields
+ * as additional spacing.
+ */
+static void render_calc_width(void)
+{
+ int screen_width = render_screen_width();
+ struct column *c, *eol = columns - 1;
+ int first, len = 0, linecols = 0;
+
+ /* First pass: set width for each column to measured content length */
+ for (first = 1, c = columns; c - columns < COL_MAX; c++) {
+ if (c->disabled)
+ continue;
+
+ if (!first && c->max_len)
+ c->width = c->max_len + strlen(c->ldelim);
+ else
+ c->width = c->max_len;
+
+ /* But don't exceed screen size. If we exceed the screen size
+ * for even a single field, it will just start on a line of its
+ * own and then naturally wrap.
+ */
+ c->width = min(c->width, screen_width);
+
+ if (c->width)
+ first = 0;
+ }
+
+ /* Second pass: find out newlines and distribute available spacing */
+ for (c = columns; c - columns < COL_MAX; c++) {
+ int pad, spacing, rem, last;
+ struct column *tmp;
+
+ if (!c->width)
+ continue;
+
+ linecols++;
+ len += c->width;
+
+ for (last = 1, tmp = c + 1; tmp - columns < COL_MAX; tmp++) {
+ if (tmp->width) {
+ last = 0;
+ break;
+ }
+ }
+
+ if (!last && len < screen_width) {
+ /* Columns fit on screen so far, nothing to do yet */
+ continue;
+ }
+
+ if (len == screen_width) {
+ /* Exact fit, just start with new line */
+ goto newline;
+ }
+
+ if (len > screen_width) {
+ /* Screen width exceeded: go back one column */
+ len -= c->width;
+ c--;
+ linecols--;
+ }
+
+ /* Distribute remaining space to columns on this line */
+ pad = screen_width - len;
+ spacing = pad / linecols;
+ rem = pad % linecols;
+ for (tmp = c; tmp > eol; tmp--) {
+ if (!tmp->width)
+ continue;
+
+ tmp->width += spacing;
+ if (rem) {
+ tmp->width++;
+ rem--;
+ }
+ }
+
+newline:
+ /* Line break: reset line counters, mark end-of-line */
+ eol = c;
+ len = 0;
+ linecols = 0;
+ }
+}
+
+/* Render buffered output with spacing and delimiters, then free up buffers */
+static void render(void)
+{
+ struct buf_token *token;
+ int printed, line_started = 0;
+ struct column *f;
+
+ if (!buffer.head)
+ return;
+
+ token = (struct buf_token *)buffer.head->data;
+
+ /* Ensure end alignment of last token, it wasn't necessarily flushed */
+ buffer.tail->end += buffer.cur->len % 2;
+
+ render_calc_width();
+
+ /* Rewind and replay */
+ buffer.tail = buffer.head;
+
+ f = columns;
+ while (!f->width)
+ f++;
+
+ while (token) {
+ /* Print left delimiter only if we already started a line */
+ if (line_started++)
+ printed = printf("%s", f->ldelim);
+ else
+ printed = 0;
+
+ /* Print field content from token data with spacing */
+ printed += print_left_spacing(f, token->len, printed);
+ printed += fwrite(token->data, 1, token->len, stdout);
+ print_right_spacing(f, printed);
+
+ /* Go to next non-empty field, deal with end-of-line */
+ do {
+ if (field_is_last(f)) {
+ printf("\n");
+ f = columns;
+ line_started = 0;
+ } else {
+ f++;
+ }
+ } while (f->disabled);
+
+ token = buf_token_next(token);
+ }
+
+ buf_free_all();
+ current_field = columns;
+}
+
+/* Move to next field, and render buffer if we reached the maximum number of
+ * chunks, at the last field in a line.
+ */
+static void field_next(void)
+{
+ if (field_is_last(current_field) && buffer.chunks >= BUF_CHUNKS_MAX) {
+ render();
+ return;
+ }
+
+ field_flush(current_field);
+ if (field_is_last(current_field))
+ current_field = columns;
+ else
+ current_field++;
+}
+
+/* Walk through fields and flush them until we reach the desired one */
+static void field_set(enum col_id id)
+{
+ while (id != current_field - columns)
+ field_next();
+}
+
+/* Print header for all non-empty columns */
+static void print_header(void)
+{
+ while (!field_is_last(current_field)) {
+ if (!current_field->disabled)
+ out("%s", current_field->header);
+ field_next();
+ }
+}
+
+static void sock_state_print(struct sockstat *s)
+{
+ const char *sock_name;
+ static const char * const sstate_name[] = {
+ "UNKNOWN",
+ [SS_ESTABLISHED] = "ESTAB",
+ [SS_SYN_SENT] = "SYN-SENT",
+ [SS_SYN_RECV] = "SYN-RECV",
+ [SS_FIN_WAIT1] = "FIN-WAIT-1",
+ [SS_FIN_WAIT2] = "FIN-WAIT-2",
+ [SS_TIME_WAIT] = "TIME-WAIT",
+ [SS_CLOSE] = "UNCONN",
+ [SS_CLOSE_WAIT] = "CLOSE-WAIT",
+ [SS_LAST_ACK] = "LAST-ACK",
+ [SS_LISTEN] = "LISTEN",
+ [SS_CLOSING] = "CLOSING",
+ };
+
+ switch (s->local.family) {
+ case AF_UNIX:
+ sock_name = unix_netid_name(s->type);
+ break;
+ case AF_INET:
+ case AF_INET6:
+ sock_name = proto_name(s->type);
+ break;
+ case AF_PACKET:
+ sock_name = s->type == SOCK_RAW ? "p_raw" : "p_dgr";
+ break;
+ case AF_NETLINK:
+ sock_name = "nl";
+ break;
+ case AF_TIPC:
+ sock_name = tipc_netid_name(s->type);
+ break;
+ case AF_VSOCK:
+ sock_name = vsock_netid_name(s->type);
+ break;
+ case AF_XDP:
+ sock_name = "xdp";
+ break;
+ default:
+ sock_name = "unknown";
+ }
+
+ if (is_sctp_assoc(s, sock_name)) {
+ field_set(COL_STATE); /* Empty Netid field */
+ out("`- %s", sctp_sstate_name[s->state]);
+ } else {
+ field_set(COL_NETID);
+ out("%s", sock_name);
+ field_set(COL_STATE);
+ out("%s", sstate_name[s->state]);
+ }
+
+ field_set(COL_RECVQ);
+ out("%-6d", s->rq);
+ field_set(COL_SENDQ);
+ out("%-6d", s->wq);
+ field_set(COL_ADDR);
+}
+
+static void sock_details_print(struct sockstat *s)
+{
+ if (s->uid)
+ out(" uid:%u", s->uid);
+
+ out(" ino:%u", s->ino);
+ out(" sk:%llx", s->sk);
+
+ if (s->mark)
+ out(" fwmark:0x%x", s->mark);
+}
+
+static void sock_addr_print(const char *addr, char *delim, const char *port,
+ const char *ifname)
+{
+ if (ifname)
+ out("%s" "%%" "%s%s", addr, ifname, delim);
+ else
+ out("%s%s", addr, delim);
+
+ field_next();
+ out("%s", port);
+ field_next();
+}
+
+static const char *print_ms_timer(unsigned int timeout)
+{
+ static char buf[64];
+ int secs, msecs, minutes;
+
+ secs = timeout/1000;
+ minutes = secs/60;
+ secs = secs%60;
+ msecs = timeout%1000;
+ buf[0] = 0;
+ if (minutes) {
+ msecs = 0;
+ snprintf(buf, sizeof(buf)-16, "%dmin", minutes);
+ if (minutes > 9)
+ secs = 0;
+ }
+ if (secs) {
+ if (secs > 9)
+ msecs = 0;
+ sprintf(buf+strlen(buf), "%d%s", secs, msecs ? "." : "sec");
+ }
+ if (msecs)
+ sprintf(buf+strlen(buf), "%03dms", msecs);
+ return buf;
+}
+
+struct scache {
+ struct scache *next;
+ int port;
+ char *name;
+ const char *proto;
+};
+
+static struct scache *rlist;
+
+static void init_service_resolver(void)
+{
+ char buf[128];
+ FILE *fp = popen("/usr/sbin/rpcinfo -p 2>/dev/null", "r");
+
+ if (!fp)
+ return;
+
+ if (!fgets(buf, sizeof(buf), fp)) {
+ pclose(fp);
+ return;
+ }
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ unsigned int progn, port;
+ char proto[128], prog[128] = "rpc.";
+ struct scache *c;
+
+ if (sscanf(buf, "%u %*d %s %u %s",
+ &progn, proto, &port, prog+4) != 4)
+ continue;
+
+ if (!(c = malloc(sizeof(*c))))
+ continue;
+
+ c->port = port;
+ c->name = strdup(prog);
+ if (strcmp(proto, TCP_PROTO) == 0)
+ c->proto = TCP_PROTO;
+ else if (strcmp(proto, UDP_PROTO) == 0)
+ c->proto = UDP_PROTO;
+ else if (strcmp(proto, SCTP_PROTO) == 0)
+ c->proto = SCTP_PROTO;
+ else
+ c->proto = NULL;
+ c->next = rlist;
+ rlist = c;
+ }
+ pclose(fp);
+}
+
+/* Even do not try default linux ephemeral port ranges:
+ * default /etc/services contains so much of useless crap
+ * wouldbe "allocated" to this area that resolution
+ * is really harmful. I shrug each time when seeing
+ * "socks" or "cfinger" in dumps.
+ */
+static int is_ephemeral(int port)
+{
+ static int min = 0, max;
+
+ if (!min) {
+ FILE *f = ephemeral_ports_open();
+
+ if (!f || fscanf(f, "%d %d", &min, &max) < 2) {
+ min = 1024;
+ max = 4999;
+ }
+ if (f)
+ fclose(f);
+ }
+ return port >= min && port <= max;
+}
+
+
+static const char *__resolve_service(int port)
+{
+ struct scache *c;
+
+ for (c = rlist; c; c = c->next) {
+ if (c->port == port && c->proto == dg_proto)
+ return c->name;
+ }
+
+ if (!is_ephemeral(port)) {
+ static int notfirst;
+ struct servent *se;
+
+ if (!notfirst) {
+ setservent(1);
+ notfirst = 1;
+ }
+ se = getservbyport(htons(port), dg_proto);
+ if (se)
+ return se->s_name;
+ }
+
+ return NULL;
+}
+
+#define SCACHE_BUCKETS 1024
+static struct scache *cache_htab[SCACHE_BUCKETS];
+
+static const char *resolve_service(int port)
+{
+ static char buf[128];
+ struct scache *c;
+ const char *res;
+ int hash;
+
+ if (port == 0) {
+ buf[0] = '*';
+ buf[1] = 0;
+ return buf;
+ }
+
+ if (numeric)
+ goto do_numeric;
+
+ if (dg_proto == RAW_PROTO)
+ return inet_proto_n2a(port, buf, sizeof(buf));
+
+
+ hash = (port^(((unsigned long)dg_proto)>>2)) % SCACHE_BUCKETS;
+
+ for (c = cache_htab[hash]; c; c = c->next) {
+ if (c->port == port && c->proto == dg_proto)
+ goto do_cache;
+ }
+
+ c = malloc(sizeof(*c));
+ if (!c)
+ goto do_numeric;
+ res = __resolve_service(port);
+ c->port = port;
+ c->name = res ? strdup(res) : NULL;
+ c->proto = dg_proto;
+ c->next = cache_htab[hash];
+ cache_htab[hash] = c;
+
+do_cache:
+ if (c->name)
+ return c->name;
+
+do_numeric:
+ sprintf(buf, "%u", port);
+ return buf;
+}
+
+static void inet_addr_print(const inet_prefix *a, int port,
+ unsigned int ifindex, bool v6only)
+{
+ char buf[1024];
+ const char *ap = buf;
+ const char *ifname = NULL;
+
+ if (a->family == AF_INET) {
+ ap = format_host(AF_INET, 4, a->data);
+ } else {
+ if (!v6only &&
+ !memcmp(a->data, &in6addr_any, sizeof(in6addr_any))) {
+ buf[0] = '*';
+ buf[1] = 0;
+ } else {
+ ap = format_host(a->family, 16, a->data);
+
+ /* Numeric IPv6 addresses should be bracketed */
+ if (strchr(ap, ':')) {
+ snprintf(buf, sizeof(buf),
+ "[%s]", ap);
+ ap = buf;
+ }
+ }
+ }
+
+ if (ifindex)
+ ifname = ll_index_to_name(ifindex);
+
+ sock_addr_print(ap, ":", resolve_service(port), ifname);
+}
+
+struct aafilter {
+ inet_prefix addr;
+ int port;
+ unsigned int iface;
+ __u32 mark;
+ __u32 mask;
+ struct aafilter *next;
+};
+
+static int inet2_addr_match(const inet_prefix *a, const inet_prefix *p,
+ int plen)
+{
+ if (!inet_addr_match(a, p, plen))
+ return 0;
+
+ /* Cursed "v4 mapped" addresses: v4 mapped socket matches
+ * pure IPv4 rule, but v4-mapped rule selects only v4-mapped
+ * sockets. Fair? */
+ if (p->family == AF_INET && a->family == AF_INET6) {
+ if (a->data[0] == 0 && a->data[1] == 0 &&
+ a->data[2] == htonl(0xffff)) {
+ inet_prefix tmp = *a;
+
+ tmp.data[0] = a->data[3];
+ return inet_addr_match(&tmp, p, plen);
+ }
+ }
+ return 1;
+}
+
+static int unix_match(const inet_prefix *a, const inet_prefix *p)
+{
+ char *addr, *pattern;
+
+ memcpy(&addr, a->data, sizeof(addr));
+ memcpy(&pattern, p->data, sizeof(pattern));
+ if (pattern == NULL)
+ return 1;
+ if (addr == NULL)
+ addr = "";
+ return !fnmatch(pattern, addr, 0);
+}
+
+static int run_ssfilter(struct ssfilter *f, struct sockstat *s)
+{
+ switch (f->type) {
+ case SSF_S_AUTO:
+ {
+ if (s->local.family == AF_UNIX) {
+ char *p;
+
+ memcpy(&p, s->local.data, sizeof(p));
+ return p == NULL || (p[0] == '@' && strlen(p) == 6 &&
+ strspn(p+1, "0123456789abcdef") == 5);
+ }
+ if (s->local.family == AF_PACKET)
+ return s->lport == 0 && s->local.data[0] == 0;
+ if (s->local.family == AF_NETLINK)
+ return s->lport < 0;
+ if (s->local.family == AF_VSOCK)
+ return s->lport > 1023;
+
+ return is_ephemeral(s->lport);
+ }
+ case SSF_DCOND:
+ {
+ struct aafilter *a = (void *)f->pred;
+
+ if (a->addr.family == AF_UNIX)
+ return unix_match(&s->remote, &a->addr);
+ if (a->port != -1 && a->port != s->rport)
+ return 0;
+ if (a->addr.bitlen) {
+ do {
+ if (!inet2_addr_match(&s->remote, &a->addr, a->addr.bitlen))
+ return 1;
+ } while ((a = a->next) != NULL);
+ return 0;
+ }
+ return 1;
+ }
+ case SSF_SCOND:
+ {
+ struct aafilter *a = (void *)f->pred;
+
+ if (a->addr.family == AF_UNIX)
+ return unix_match(&s->local, &a->addr);
+ if (a->port != -1 && a->port != s->lport)
+ return 0;
+ if (a->addr.bitlen) {
+ do {
+ if (!inet2_addr_match(&s->local, &a->addr, a->addr.bitlen))
+ return 1;
+ } while ((a = a->next) != NULL);
+ return 0;
+ }
+ return 1;
+ }
+ case SSF_D_GE:
+ {
+ struct aafilter *a = (void *)f->pred;
+
+ return s->rport >= a->port;
+ }
+ case SSF_D_LE:
+ {
+ struct aafilter *a = (void *)f->pred;
+
+ return s->rport <= a->port;
+ }
+ case SSF_S_GE:
+ {
+ struct aafilter *a = (void *)f->pred;
+
+ return s->lport >= a->port;
+ }
+ case SSF_S_LE:
+ {
+ struct aafilter *a = (void *)f->pred;
+
+ return s->lport <= a->port;
+ }
+ case SSF_DEVCOND:
+ {
+ struct aafilter *a = (void *)f->pred;
+
+ return s->iface == a->iface;
+ }
+ case SSF_MARKMASK:
+ {
+ struct aafilter *a = (void *)f->pred;
+
+ return (s->mark & a->mask) == a->mark;
+ }
+ /* Yup. It is recursion. Sorry. */
+ case SSF_AND:
+ return run_ssfilter(f->pred, s) && run_ssfilter(f->post, s);
+ case SSF_OR:
+ return run_ssfilter(f->pred, s) || run_ssfilter(f->post, s);
+ case SSF_NOT:
+ return !run_ssfilter(f->pred, s);
+ default:
+ abort();
+ }
+}
+
+/* Relocate external jumps by reloc. */
+static void ssfilter_patch(char *a, int len, int reloc)
+{
+ while (len > 0) {
+ struct inet_diag_bc_op *op = (struct inet_diag_bc_op *)a;
+
+ if (op->no == len+4)
+ op->no += reloc;
+ len -= op->yes;
+ a += op->yes;
+ }
+ if (len < 0)
+ abort();
+}
+
+static int ssfilter_bytecompile(struct ssfilter *f, char **bytecode)
+{
+ switch (f->type) {
+ case SSF_S_AUTO:
+ {
+ if (!(*bytecode = malloc(4))) abort();
+ ((struct inet_diag_bc_op *)*bytecode)[0] = (struct inet_diag_bc_op){ INET_DIAG_BC_AUTO, 4, 8 };
+ return 4;
+ }
+ case SSF_DCOND:
+ case SSF_SCOND:
+ {
+ struct aafilter *a = (void *)f->pred;
+ struct aafilter *b;
+ char *ptr;
+ int code = (f->type == SSF_DCOND ? INET_DIAG_BC_D_COND : INET_DIAG_BC_S_COND);
+ int len = 0;
+
+ for (b = a; b; b = b->next) {
+ len += 4 + sizeof(struct inet_diag_hostcond);
+ if (a->addr.family == AF_INET6)
+ len += 16;
+ else
+ len += 4;
+ if (b->next)
+ len += 4;
+ }
+ if (!(ptr = malloc(len))) abort();
+ *bytecode = ptr;
+ for (b = a; b; b = b->next) {
+ struct inet_diag_bc_op *op = (struct inet_diag_bc_op *)ptr;
+ int alen = (a->addr.family == AF_INET6 ? 16 : 4);
+ int oplen = alen + 4 + sizeof(struct inet_diag_hostcond);
+ struct inet_diag_hostcond *cond = (struct inet_diag_hostcond *)(ptr+4);
+
+ *op = (struct inet_diag_bc_op){ code, oplen, oplen+4 };
+ cond->family = a->addr.family;
+ cond->port = a->port;
+ cond->prefix_len = a->addr.bitlen;
+ memcpy(cond->addr, a->addr.data, alen);
+ ptr += oplen;
+ if (b->next) {
+ op = (struct inet_diag_bc_op *)ptr;
+ *op = (struct inet_diag_bc_op){ INET_DIAG_BC_JMP, 4, len - (ptr-*bytecode)};
+ ptr += 4;
+ }
+ }
+ return ptr - *bytecode;
+ }
+ case SSF_D_GE:
+ {
+ struct aafilter *x = (void *)f->pred;
+
+ if (!(*bytecode = malloc(8))) abort();
+ ((struct inet_diag_bc_op *)*bytecode)[0] = (struct inet_diag_bc_op){ INET_DIAG_BC_D_GE, 8, 12 };
+ ((struct inet_diag_bc_op *)*bytecode)[1] = (struct inet_diag_bc_op){ 0, 0, x->port };
+ return 8;
+ }
+ case SSF_D_LE:
+ {
+ struct aafilter *x = (void *)f->pred;
+
+ if (!(*bytecode = malloc(8))) abort();
+ ((struct inet_diag_bc_op *)*bytecode)[0] = (struct inet_diag_bc_op){ INET_DIAG_BC_D_LE, 8, 12 };
+ ((struct inet_diag_bc_op *)*bytecode)[1] = (struct inet_diag_bc_op){ 0, 0, x->port };
+ return 8;
+ }
+ case SSF_S_GE:
+ {
+ struct aafilter *x = (void *)f->pred;
+
+ if (!(*bytecode = malloc(8))) abort();
+ ((struct inet_diag_bc_op *)*bytecode)[0] = (struct inet_diag_bc_op){ INET_DIAG_BC_S_GE, 8, 12 };
+ ((struct inet_diag_bc_op *)*bytecode)[1] = (struct inet_diag_bc_op){ 0, 0, x->port };
+ return 8;
+ }
+ case SSF_S_LE:
+ {
+ struct aafilter *x = (void *)f->pred;
+
+ if (!(*bytecode = malloc(8))) abort();
+ ((struct inet_diag_bc_op *)*bytecode)[0] = (struct inet_diag_bc_op){ INET_DIAG_BC_S_LE, 8, 12 };
+ ((struct inet_diag_bc_op *)*bytecode)[1] = (struct inet_diag_bc_op){ 0, 0, x->port };
+ return 8;
+ }
+
+ case SSF_AND:
+ {
+ char *a1 = NULL, *a2 = NULL, *a;
+ int l1, l2;
+
+ l1 = ssfilter_bytecompile(f->pred, &a1);
+ l2 = ssfilter_bytecompile(f->post, &a2);
+ if (!l1 || !l2) {
+ free(a1);
+ free(a2);
+ return 0;
+ }
+ if (!(a = malloc(l1+l2))) abort();
+ memcpy(a, a1, l1);
+ memcpy(a+l1, a2, l2);
+ free(a1); free(a2);
+ ssfilter_patch(a, l1, l2);
+ *bytecode = a;
+ return l1+l2;
+ }
+ case SSF_OR:
+ {
+ char *a1 = NULL, *a2 = NULL, *a;
+ int l1, l2;
+
+ l1 = ssfilter_bytecompile(f->pred, &a1);
+ l2 = ssfilter_bytecompile(f->post, &a2);
+ if (!l1 || !l2) {
+ free(a1);
+ free(a2);
+ return 0;
+ }
+ if (!(a = malloc(l1+l2+4))) abort();
+ memcpy(a, a1, l1);
+ memcpy(a+l1+4, a2, l2);
+ free(a1); free(a2);
+ *(struct inet_diag_bc_op *)(a+l1) = (struct inet_diag_bc_op){ INET_DIAG_BC_JMP, 4, l2+4 };
+ *bytecode = a;
+ return l1+l2+4;
+ }
+ case SSF_NOT:
+ {
+ char *a1 = NULL, *a;
+ int l1;
+
+ l1 = ssfilter_bytecompile(f->pred, &a1);
+ if (!l1) {
+ free(a1);
+ return 0;
+ }
+ if (!(a = malloc(l1+4))) abort();
+ memcpy(a, a1, l1);
+ free(a1);
+ *(struct inet_diag_bc_op *)(a+l1) = (struct inet_diag_bc_op){ INET_DIAG_BC_JMP, 4, 8 };
+ *bytecode = a;
+ return l1+4;
+ }
+ case SSF_DEVCOND:
+ {
+ /* bytecompile for SSF_DEVCOND not supported yet */
+ return 0;
+ }
+ case SSF_MARKMASK:
+ {
+ struct aafilter *a = (void *)f->pred;
+ struct instr {
+ struct inet_diag_bc_op op;
+ struct inet_diag_markcond cond;
+ };
+ int inslen = sizeof(struct instr);
+
+ if (!(*bytecode = malloc(inslen))) abort();
+ ((struct instr *)*bytecode)[0] = (struct instr) {
+ { INET_DIAG_BC_MARK_COND, inslen, inslen + 4 },
+ { a->mark, a->mask},
+ };
+
+ return inslen;
+ }
+ default:
+ abort();
+ }
+}
+
+static int remember_he(struct aafilter *a, struct hostent *he)
+{
+ char **ptr = he->h_addr_list;
+ int cnt = 0;
+ int len;
+
+ if (he->h_addrtype == AF_INET)
+ len = 4;
+ else if (he->h_addrtype == AF_INET6)
+ len = 16;
+ else
+ return 0;
+
+ while (*ptr) {
+ struct aafilter *b = a;
+
+ if (a->addr.bitlen) {
+ if ((b = malloc(sizeof(*b))) == NULL)
+ return cnt;
+ *b = *a;
+ a->next = b;
+ }
+ memcpy(b->addr.data, *ptr, len);
+ b->addr.bytelen = len;
+ b->addr.bitlen = len*8;
+ b->addr.family = he->h_addrtype;
+ ptr++;
+ cnt++;
+ }
+ return cnt;
+}
+
+static int get_dns_host(struct aafilter *a, const char *addr, int fam)
+{
+ static int notfirst;
+ int cnt = 0;
+ struct hostent *he;
+
+ a->addr.bitlen = 0;
+ if (!notfirst) {
+ sethostent(1);
+ notfirst = 1;
+ }
+ he = gethostbyname2(addr, fam == AF_UNSPEC ? AF_INET : fam);
+ if (he)
+ cnt = remember_he(a, he);
+ if (fam == AF_UNSPEC) {
+ he = gethostbyname2(addr, AF_INET6);
+ if (he)
+ cnt += remember_he(a, he);
+ }
+ return !cnt;
+}
+
+static int xll_initted;
+
+static void xll_init(void)
+{
+ struct rtnl_handle rth;
+
+ if (rtnl_open(&rth, 0) < 0)
+ exit(1);
+
+ ll_init_map(&rth);
+ rtnl_close(&rth);
+ xll_initted = 1;
+}
+
+static const char *xll_index_to_name(int index)
+{
+ if (!xll_initted)
+ xll_init();
+ return ll_index_to_name(index);
+}
+
+static int xll_name_to_index(const char *dev)
+{
+ if (!xll_initted)
+ xll_init();
+ return ll_name_to_index(dev);
+}
+
+void *parse_devcond(char *name)
+{
+ struct aafilter a = { .iface = 0 };
+ struct aafilter *res;
+
+ a.iface = xll_name_to_index(name);
+ if (a.iface == 0) {
+ char *end;
+ unsigned long n;
+
+ n = strtoul(name, &end, 0);
+ if (!end || end == name || *end || n > UINT_MAX)
+ return NULL;
+
+ a.iface = n;
+ }
+
+ res = malloc(sizeof(*res));
+ *res = a;
+
+ return res;
+}
+
+static void vsock_set_inet_prefix(inet_prefix *a, __u32 cid)
+{
+ *a = (inet_prefix){
+ .bytelen = sizeof(cid),
+ .family = AF_VSOCK,
+ };
+ memcpy(a->data, &cid, sizeof(cid));
+}
+
+void *parse_hostcond(char *addr, bool is_port)
+{
+ char *port = NULL;
+ struct aafilter a = { .port = -1 };
+ struct aafilter *res;
+ int fam = preferred_family;
+ struct filter *f = ¤t_filter;
+
+ if (fam == AF_UNIX || strncmp(addr, "unix:", 5) == 0) {
+ char *p;
+
+ a.addr.family = AF_UNIX;
+ if (strncmp(addr, "unix:", 5) == 0)
+ addr += 5;
+ p = strdup(addr);
+ a.addr.bitlen = 8*strlen(p);
+ memcpy(a.addr.data, &p, sizeof(p));
+ fam = AF_UNIX;
+ goto out;
+ }
+
+ if (fam == AF_PACKET || strncmp(addr, "link:", 5) == 0) {
+ a.addr.family = AF_PACKET;
+ a.addr.bitlen = 0;
+ if (strncmp(addr, "link:", 5) == 0)
+ addr += 5;
+ port = strchr(addr, ':');
+ if (port) {
+ *port = 0;
+ if (port[1] && strcmp(port+1, "*")) {
+ if (get_integer(&a.port, port+1, 0)) {
+ if ((a.port = xll_name_to_index(port+1)) <= 0)
+ return NULL;
+ }
+ }
+ }
+ if (addr[0] && strcmp(addr, "*")) {
+ unsigned short tmp;
+
+ a.addr.bitlen = 32;
+ if (ll_proto_a2n(&tmp, addr))
+ return NULL;
+ a.addr.data[0] = ntohs(tmp);
+ }
+ fam = AF_PACKET;
+ goto out;
+ }
+
+ if (fam == AF_NETLINK || strncmp(addr, "netlink:", 8) == 0) {
+ a.addr.family = AF_NETLINK;
+ a.addr.bitlen = 0;
+ if (strncmp(addr, "netlink:", 8) == 0)
+ addr += 8;
+ port = strchr(addr, ':');
+ if (port) {
+ *port = 0;
+ if (port[1] && strcmp(port+1, "*")) {
+ if (get_integer(&a.port, port+1, 0)) {
+ if (strcmp(port+1, "kernel") == 0)
+ a.port = 0;
+ else
+ return NULL;
+ }
+ }
+ }
+ if (addr[0] && strcmp(addr, "*")) {
+ a.addr.bitlen = 32;
+ if (nl_proto_a2n(&a.addr.data[0], addr) == -1)
+ return NULL;
+ }
+ fam = AF_NETLINK;
+ goto out;
+ }
+
+ if (fam == AF_VSOCK || strncmp(addr, "vsock:", 6) == 0) {
+ __u32 cid = ~(__u32)0;
+
+ a.addr.family = AF_VSOCK;
+ if (strncmp(addr, "vsock:", 6) == 0)
+ addr += 6;
+
+ if (is_port)
+ port = addr;
+ else {
+ port = strchr(addr, ':');
+ if (port) {
+ *port = '\0';
+ port++;
+ }
+ }
+
+ if (port && strcmp(port, "*") &&
+ get_u32((__u32 *)&a.port, port, 0))
+ return NULL;
+
+ if (addr[0] && strcmp(addr, "*")) {
+ a.addr.bitlen = 32;
+ if (get_u32(&cid, addr, 0))
+ return NULL;
+ }
+ vsock_set_inet_prefix(&a.addr, cid);
+ fam = AF_VSOCK;
+ goto out;
+ }
+
+ if (fam == AF_INET || !strncmp(addr, "inet:", 5)) {
+ fam = AF_INET;
+ if (!strncmp(addr, "inet:", 5))
+ addr += 5;
+ } else if (fam == AF_INET6 || !strncmp(addr, "inet6:", 6)) {
+ fam = AF_INET6;
+ if (!strncmp(addr, "inet6:", 6))
+ addr += 6;
+ }
+
+ /* URL-like literal [] */
+ if (addr[0] == '[') {
+ addr++;
+ if ((port = strchr(addr, ']')) == NULL)
+ return NULL;
+ *port++ = 0;
+ } else if (addr[0] == '*') {
+ port = addr+1;
+ } else {
+ port = strrchr(strchr(addr, '/') ? : addr, ':');
+ }
+
+ if (is_port)
+ port = addr;
+
+ if (port && *port) {
+ if (*port == ':')
+ *port++ = 0;
+
+ if (*port && *port != '*') {
+ if (get_integer(&a.port, port, 0)) {
+ struct servent *se1 = NULL;
+ struct servent *se2 = NULL;
+
+ if (current_filter.dbs&(1<<UDP_DB))
+ se1 = getservbyname(port, UDP_PROTO);
+ if (current_filter.dbs&(1<<TCP_DB))
+ se2 = getservbyname(port, TCP_PROTO);
+ if (se1 && se2 && se1->s_port != se2->s_port) {
+ fprintf(stderr, "Error: ambiguous port \"%s\".\n", port);
+ return NULL;
+ }
+ if (!se1)
+ se1 = se2;
+ if (se1) {
+ a.port = ntohs(se1->s_port);
+ } else {
+ struct scache *s;
+
+ for (s = rlist; s; s = s->next) {
+ if ((s->proto == UDP_PROTO &&
+ (current_filter.dbs&(1<<UDP_DB))) ||
+ (s->proto == TCP_PROTO &&
+ (current_filter.dbs&(1<<TCP_DB)))) {
+ if (s->name && strcmp(s->name, port) == 0) {
+ if (a.port > 0 && a.port != s->port) {
+ fprintf(stderr, "Error: ambiguous port \"%s\".\n", port);
+ return NULL;
+ }
+ a.port = s->port;
+ }
+ }
+ }
+ if (a.port <= 0) {
+ fprintf(stderr, "Error: \"%s\" does not look like a port.\n", port);
+ return NULL;
+ }
+ }
+ }
+ }
+ }
+ if (!is_port && *addr && *addr != '*') {
+ if (get_prefix_1(&a.addr, addr, fam)) {
+ if (get_dns_host(&a, addr, fam)) {
+ fprintf(stderr, "Error: an inet prefix is expected rather than \"%s\".\n", addr);
+ return NULL;
+ }
+ }
+ }
+
+out:
+ if (fam != AF_UNSPEC) {
+ int states = f->states;
+ f->families = 0;
+ filter_af_set(f, fam);
+ filter_states_set(f, states);
+ }
+
+ res = malloc(sizeof(*res));
+ if (res)
+ memcpy(res, &a, sizeof(a));
+ return res;
+}
+
+void *parse_markmask(const char *markmask)
+{
+ struct aafilter a, *res;
+
+ if (strchr(markmask, '/')) {
+ if (sscanf(markmask, "%i/%i", &a.mark, &a.mask) != 2)
+ return NULL;
+ } else {
+ a.mask = 0xffffffff;
+ if (sscanf(markmask, "%i", &a.mark) != 1)
+ return NULL;
+ }
+
+ res = malloc(sizeof(*res));
+ if (res)
+ memcpy(res, &a, sizeof(a));
+ return res;
+}
+
+static void proc_ctx_print(struct sockstat *s)
+{
+ char *buf;
+
+ if (show_proc_ctx || show_sock_ctx) {
+ if (find_entry(s->ino, &buf,
+ (show_proc_ctx & show_sock_ctx) ?
+ PROC_SOCK_CTX : PROC_CTX) > 0) {
+ out(" users:(%s)", buf);
+ free(buf);
+ }
+ } else if (show_users) {
+ if (find_entry(s->ino, &buf, USERS) > 0) {
+ out(" users:(%s)", buf);
+ free(buf);
+ }
+ }
+}
+
+static void inet_stats_print(struct sockstat *s, bool v6only)
+{
+ sock_state_print(s);
+
+ inet_addr_print(&s->local, s->lport, s->iface, v6only);
+ inet_addr_print(&s->remote, s->rport, 0, v6only);
+
+ proc_ctx_print(s);
+}
+
+static int proc_parse_inet_addr(char *loc, char *rem, int family, struct
+ sockstat * s)
+{
+ s->local.family = s->remote.family = family;
+ if (family == AF_INET) {
+ sscanf(loc, "%x:%x", s->local.data, (unsigned *)&s->lport);
+ sscanf(rem, "%x:%x", s->remote.data, (unsigned *)&s->rport);
+ s->local.bytelen = s->remote.bytelen = 4;
+ return 0;
+ } else {
+ sscanf(loc, "%08x%08x%08x%08x:%x",
+ s->local.data,
+ s->local.data + 1,
+ s->local.data + 2,
+ s->local.data + 3,
+ &s->lport);
+ sscanf(rem, "%08x%08x%08x%08x:%x",
+ s->remote.data,
+ s->remote.data + 1,
+ s->remote.data + 2,
+ s->remote.data + 3,
+ &s->rport);
+ s->local.bytelen = s->remote.bytelen = 16;
+ return 0;
+ }
+ return -1;
+}
+
+static int proc_inet_split_line(char *line, char **loc, char **rem, char **data)
+{
+ char *p;
+
+ if ((p = strchr(line, ':')) == NULL)
+ return -1;
+
+ *loc = p+2;
+ if ((p = strchr(*loc, ':')) == NULL)
+ return -1;
+
+ p[5] = 0;
+ *rem = p+6;
+ if ((p = strchr(*rem, ':')) == NULL)
+ return -1;
+
+ p[5] = 0;
+ *data = p+6;
+ return 0;
+}
+
+static char *sprint_bw(char *buf, double bw)
+{
+ if (numeric)
+ sprintf(buf, "%.0f", bw);
+ else if (bw > 1000000.)
+ sprintf(buf, "%.1fM", bw / 1000000.);
+ else if (bw > 1000.)
+ sprintf(buf, "%.1fK", bw / 1000.);
+ else
+ sprintf(buf, "%g", bw);
+
+ return buf;
+}
+
+static void sctp_stats_print(struct sctp_info *s)
+{
+ if (s->sctpi_tag)
+ out(" tag:%x", s->sctpi_tag);
+ if (s->sctpi_state)
+ out(" state:%s", sctp_sstate_name[s->sctpi_state]);
+ if (s->sctpi_rwnd)
+ out(" rwnd:%d", s->sctpi_rwnd);
+ if (s->sctpi_unackdata)
+ out(" unackdata:%d", s->sctpi_unackdata);
+ if (s->sctpi_penddata)
+ out(" penddata:%d", s->sctpi_penddata);
+ if (s->sctpi_instrms)
+ out(" instrms:%d", s->sctpi_instrms);
+ if (s->sctpi_outstrms)
+ out(" outstrms:%d", s->sctpi_outstrms);
+ if (s->sctpi_inqueue)
+ out(" inqueue:%d", s->sctpi_inqueue);
+ if (s->sctpi_outqueue)
+ out(" outqueue:%d", s->sctpi_outqueue);
+ if (s->sctpi_overall_error)
+ out(" overerr:%d", s->sctpi_overall_error);
+ if (s->sctpi_max_burst)
+ out(" maxburst:%d", s->sctpi_max_burst);
+ if (s->sctpi_maxseg)
+ out(" maxseg:%d", s->sctpi_maxseg);
+ if (s->sctpi_peer_rwnd)
+ out(" prwnd:%d", s->sctpi_peer_rwnd);
+ if (s->sctpi_peer_tag)
+ out(" ptag:%x", s->sctpi_peer_tag);
+ if (s->sctpi_peer_capable)
+ out(" pcapable:%d", s->sctpi_peer_capable);
+ if (s->sctpi_peer_sack)
+ out(" psack:%d", s->sctpi_peer_sack);
+ if (s->sctpi_s_autoclose)
+ out(" autoclose:%d", s->sctpi_s_autoclose);
+ if (s->sctpi_s_adaptation_ind)
+ out(" adapind:%d", s->sctpi_s_adaptation_ind);
+ if (s->sctpi_s_pd_point)
+ out(" pdpoint:%d", s->sctpi_s_pd_point);
+ if (s->sctpi_s_nodelay)
+ out(" nodelay:%d", s->sctpi_s_nodelay);
+ if (s->sctpi_s_disable_fragments)
+ out(" nofrag:%d", s->sctpi_s_disable_fragments);
+ if (s->sctpi_s_v4mapped)
+ out(" v4mapped:%d", s->sctpi_s_v4mapped);
+ if (s->sctpi_s_frag_interleave)
+ out(" fraginl:%d", s->sctpi_s_frag_interleave);
+}
+
+static void tcp_stats_print(struct tcpstat *s)
+{
+ char b1[64];
+
+ if (s->has_ts_opt)
+ out(" ts");
+ if (s->has_sack_opt)
+ out(" sack");
+ if (s->has_ecn_opt)
+ out(" ecn");
+ if (s->has_ecnseen_opt)
+ out(" ecnseen");
+ if (s->has_fastopen_opt)
+ out(" fastopen");
+ if (s->cong_alg[0])
+ out(" %s", s->cong_alg);
+ if (s->has_wscale_opt)
+ out(" wscale:%d,%d", s->snd_wscale, s->rcv_wscale);
+ if (s->rto)
+ out(" rto:%g", s->rto);
+ if (s->backoff)
+ out(" backoff:%u", s->backoff);
+ if (s->rtt)
+ out(" rtt:%g/%g", s->rtt, s->rttvar);
+ if (s->ato)
+ out(" ato:%g", s->ato);
+
+ if (s->qack)
+ out(" qack:%d", s->qack);
+ if (s->qack & 1)
+ out(" bidir");
+
+ if (s->mss)
+ out(" mss:%d", s->mss);
+ if (s->pmtu)
+ out(" pmtu:%u", s->pmtu);
+ if (s->rcv_mss)
+ out(" rcvmss:%d", s->rcv_mss);
+ if (s->advmss)
+ out(" advmss:%d", s->advmss);
+ if (s->cwnd)
+ out(" cwnd:%u", s->cwnd);
+ if (s->ssthresh)
+ out(" ssthresh:%d", s->ssthresh);
+
+ if (s->bytes_sent)
+ out(" bytes_sent:%llu", s->bytes_sent);
+ if (s->bytes_retrans)
+ out(" bytes_retrans:%llu", s->bytes_retrans);
+ if (s->bytes_acked)
+ out(" bytes_acked:%llu", s->bytes_acked);
+ if (s->bytes_received)
+ out(" bytes_received:%llu", s->bytes_received);
+ if (s->segs_out)
+ out(" segs_out:%u", s->segs_out);
+ if (s->segs_in)
+ out(" segs_in:%u", s->segs_in);
+ if (s->data_segs_out)
+ out(" data_segs_out:%u", s->data_segs_out);
+ if (s->data_segs_in)
+ out(" data_segs_in:%u", s->data_segs_in);
+
+ if (s->dctcp && s->dctcp->enabled) {
+ struct dctcpstat *dctcp = s->dctcp;
+
+ out(" dctcp:(ce_state:%u,alpha:%u,ab_ecn:%u,ab_tot:%u)",
+ dctcp->ce_state, dctcp->alpha, dctcp->ab_ecn,
+ dctcp->ab_tot);
+ } else if (s->dctcp) {
+ out(" dctcp:fallback_mode");
+ }
+
+ if (s->bbr_info) {
+ __u64 bw;
+
+ bw = s->bbr_info->bbr_bw_hi;
+ bw <<= 32;
+ bw |= s->bbr_info->bbr_bw_lo;
+
+ out(" bbr:(bw:%sbps,mrtt:%g",
+ sprint_bw(b1, bw * 8.0),
+ (double)s->bbr_info->bbr_min_rtt / 1000.0);
+ if (s->bbr_info->bbr_pacing_gain)
+ out(",pacing_gain:%g",
+ (double)s->bbr_info->bbr_pacing_gain / 256.0);
+ if (s->bbr_info->bbr_cwnd_gain)
+ out(",cwnd_gain:%g",
+ (double)s->bbr_info->bbr_cwnd_gain / 256.0);
+ out(")");
+ }
+
+ if (s->send_bps)
+ out(" send %sbps", sprint_bw(b1, s->send_bps));
+ if (s->lastsnd)
+ out(" lastsnd:%u", s->lastsnd);
+ if (s->lastrcv)
+ out(" lastrcv:%u", s->lastrcv);
+ if (s->lastack)
+ out(" lastack:%u", s->lastack);
+
+ if (s->pacing_rate) {
+ out(" pacing_rate %sbps", sprint_bw(b1, s->pacing_rate));
+ if (s->pacing_rate_max)
+ out("/%sbps", sprint_bw(b1, s->pacing_rate_max));
+ }
+
+ if (s->delivery_rate)
+ out(" delivery_rate %sbps", sprint_bw(b1, s->delivery_rate));
+ if (s->delivered)
+ out(" delivered:%u", s->delivered);
+ if (s->delivered_ce)
+ out(" delivered_ce:%u", s->delivered_ce);
+ if (s->app_limited)
+ out(" app_limited");
+
+ if (s->busy_time) {
+ out(" busy:%llums", s->busy_time / 1000);
+ if (s->rwnd_limited)
+ out(" rwnd_limited:%llums(%.1f%%)",
+ s->rwnd_limited / 1000,
+ 100.0 * s->rwnd_limited / s->busy_time);
+ if (s->sndbuf_limited)
+ out(" sndbuf_limited:%llums(%.1f%%)",
+ s->sndbuf_limited / 1000,
+ 100.0 * s->sndbuf_limited / s->busy_time);
+ }
+
+ if (s->unacked)
+ out(" unacked:%u", s->unacked);
+ if (s->retrans || s->retrans_total)
+ out(" retrans:%u/%u", s->retrans, s->retrans_total);
+ if (s->lost)
+ out(" lost:%u", s->lost);
+ if (s->sacked && s->ss.state != SS_LISTEN)
+ out(" sacked:%u", s->sacked);
+ if (s->dsack_dups)
+ out(" dsack_dups:%u", s->dsack_dups);
+ if (s->fackets)
+ out(" fackets:%u", s->fackets);
+ if (s->reordering != 3)
+ out(" reordering:%d", s->reordering);
+ if (s->reord_seen)
+ out(" reord_seen:%d", s->reord_seen);
+ if (s->rcv_rtt)
+ out(" rcv_rtt:%g", s->rcv_rtt);
+ if (s->rcv_space)
+ out(" rcv_space:%d", s->rcv_space);
+ if (s->rcv_ssthresh)
+ out(" rcv_ssthresh:%u", s->rcv_ssthresh);
+ if (s->not_sent)
+ out(" notsent:%u", s->not_sent);
+ if (s->min_rtt)
+ out(" minrtt:%g", s->min_rtt);
+}
+
+static void tcp_timer_print(struct tcpstat *s)
+{
+ static const char * const tmr_name[] = {
+ "off",
+ "on",
+ "keepalive",
+ "timewait",
+ "persist",
+ "unknown"
+ };
+
+ if (s->timer) {
+ if (s->timer > 4)
+ s->timer = 5;
+ out(" timer:(%s,%s,%d)",
+ tmr_name[s->timer],
+ print_ms_timer(s->timeout),
+ s->retrans);
+ }
+}
+
+static void sctp_timer_print(struct tcpstat *s)
+{
+ if (s->timer)
+ out(" timer:(T3_RTX,%s,%d)",
+ print_ms_timer(s->timeout), s->retrans);
+}
+
+static int tcp_show_line(char *line, const struct filter *f, int family)
+{
+ int rto = 0, ato = 0;
+ struct tcpstat s = {};
+ char *loc, *rem, *data;
+ char opt[256];
+ int n;
+ int hz = get_user_hz();
+
+ if (proc_inet_split_line(line, &loc, &rem, &data))
+ return -1;
+
+ int state = (data[1] >= 'A') ? (data[1] - 'A' + 10) : (data[1] - '0');
+
+ if (!(f->states & (1 << state)))
+ return 0;
+
+ proc_parse_inet_addr(loc, rem, family, &s.ss);
+
+ if (f->f && run_ssfilter(f->f, &s.ss) == 0)
+ return 0;
+
+ opt[0] = 0;
+ n = sscanf(data, "%x %x:%x %x:%x %x %d %d %u %d %llx %d %d %d %u %d %[^\n]\n",
+ &s.ss.state, &s.ss.wq, &s.ss.rq,
+ &s.timer, &s.timeout, &s.retrans, &s.ss.uid, &s.probes,
+ &s.ss.ino, &s.ss.refcnt, &s.ss.sk, &rto, &ato, &s.qack, &s.cwnd,
+ &s.ssthresh, opt);
+
+ if (n < 17)
+ opt[0] = 0;
+
+ if (n < 12) {
+ rto = 0;
+ s.cwnd = 2;
+ s.ssthresh = -1;
+ ato = s.qack = 0;
+ }
+
+ s.retrans = s.timer != 1 ? s.probes : s.retrans;
+ s.timeout = (s.timeout * 1000 + hz - 1) / hz;
+ s.ato = (double)ato / hz;
+ s.qack /= 2;
+ s.rto = (double)rto;
+ s.ssthresh = s.ssthresh == -1 ? 0 : s.ssthresh;
+ s.rto = s.rto != 3 * hz ? s.rto / hz : 0;
+ s.ss.type = IPPROTO_TCP;
+
+ inet_stats_print(&s.ss, false);
+
+ if (show_options)
+ tcp_timer_print(&s);
+
+ if (show_details) {
+ sock_details_print(&s.ss);
+ if (opt[0])
+ out(" opt:\"%s\"", opt);
+ }
+
+ if (show_tcpinfo)
+ tcp_stats_print(&s);
+
+ return 0;
+}
+
+static int generic_record_read(FILE *fp,
+ int (*worker)(char*, const struct filter *, int),
+ const struct filter *f, int fam)
+{
+ char line[256];
+
+ /* skip header */
+ if (fgets(line, sizeof(line), fp) == NULL)
+ goto outerr;
+
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ int n = strlen(line);
+
+ if (n == 0 || line[n-1] != '\n') {
+ errno = -EINVAL;
+ return -1;
+ }
+ line[n-1] = 0;
+
+ if (worker(line, f, fam) < 0)
+ return 0;
+ }
+outerr:
+
+ return ferror(fp) ? -1 : 0;
+}
+
+static void print_skmeminfo(struct rtattr *tb[], int attrtype)
+{
+ const __u32 *skmeminfo;
+
+ if (!tb[attrtype]) {
+ if (attrtype == INET_DIAG_SKMEMINFO) {
+ if (!tb[INET_DIAG_MEMINFO])
+ return;
+
+ const struct inet_diag_meminfo *minfo =
+ RTA_DATA(tb[INET_DIAG_MEMINFO]);
+
+ out(" mem:(r%u,w%u,f%u,t%u)",
+ minfo->idiag_rmem,
+ minfo->idiag_wmem,
+ minfo->idiag_fmem,
+ minfo->idiag_tmem);
+ }
+ return;
+ }
+
+ skmeminfo = RTA_DATA(tb[attrtype]);
+
+ out(" skmem:(r%u,rb%u,t%u,tb%u,f%u,w%u,o%u",
+ skmeminfo[SK_MEMINFO_RMEM_ALLOC],
+ skmeminfo[SK_MEMINFO_RCVBUF],
+ skmeminfo[SK_MEMINFO_WMEM_ALLOC],
+ skmeminfo[SK_MEMINFO_SNDBUF],
+ skmeminfo[SK_MEMINFO_FWD_ALLOC],
+ skmeminfo[SK_MEMINFO_WMEM_QUEUED],
+ skmeminfo[SK_MEMINFO_OPTMEM]);
+
+ if (RTA_PAYLOAD(tb[attrtype]) >=
+ (SK_MEMINFO_BACKLOG + 1) * sizeof(__u32))
+ out(",bl%u", skmeminfo[SK_MEMINFO_BACKLOG]);
+
+ if (RTA_PAYLOAD(tb[attrtype]) >=
+ (SK_MEMINFO_DROPS + 1) * sizeof(__u32))
+ out(",d%u", skmeminfo[SK_MEMINFO_DROPS]);
+
+ out(")");
+}
+
+static void print_md5sig(struct tcp_diag_md5sig *sig)
+{
+ out("%s/%d=",
+ format_host(sig->tcpm_family,
+ sig->tcpm_family == AF_INET6 ? 16 : 4,
+ &sig->tcpm_addr),
+ sig->tcpm_prefixlen);
+ print_escape_buf(sig->tcpm_key, sig->tcpm_keylen, " ,");
+}
+
+#define TCPI_HAS_OPT(info, opt) !!(info->tcpi_options & (opt))
+
+static void tcp_show_info(const struct nlmsghdr *nlh, struct inet_diag_msg *r,
+ struct rtattr *tb[])
+{
+ double rtt = 0;
+ struct tcpstat s = {};
+
+ s.ss.state = r->idiag_state;
+
+ print_skmeminfo(tb, INET_DIAG_SKMEMINFO);
+
+ if (tb[INET_DIAG_INFO]) {
+ struct tcp_info *info;
+ int len = RTA_PAYLOAD(tb[INET_DIAG_INFO]);
+
+ /* workaround for older kernels with less fields */
+ if (len < sizeof(*info)) {
+ info = alloca(sizeof(*info));
+ memcpy(info, RTA_DATA(tb[INET_DIAG_INFO]), len);
+ memset((char *)info + len, 0, sizeof(*info) - len);
+ } else
+ info = RTA_DATA(tb[INET_DIAG_INFO]);
+
+ if (show_options) {
+ s.has_ts_opt = TCPI_HAS_OPT(info, TCPI_OPT_TIMESTAMPS);
+ s.has_sack_opt = TCPI_HAS_OPT(info, TCPI_OPT_SACK);
+ s.has_ecn_opt = TCPI_HAS_OPT(info, TCPI_OPT_ECN);
+ s.has_ecnseen_opt = TCPI_HAS_OPT(info, TCPI_OPT_ECN_SEEN);
+ s.has_fastopen_opt = TCPI_HAS_OPT(info, TCPI_OPT_SYN_DATA);
+ }
+
+ if (tb[INET_DIAG_CONG])
+ strncpy(s.cong_alg,
+ rta_getattr_str(tb[INET_DIAG_CONG]),
+ sizeof(s.cong_alg) - 1);
+
+ if (TCPI_HAS_OPT(info, TCPI_OPT_WSCALE)) {
+ s.has_wscale_opt = true;
+ s.snd_wscale = info->tcpi_snd_wscale;
+ s.rcv_wscale = info->tcpi_rcv_wscale;
+ }
+
+ if (info->tcpi_rto && info->tcpi_rto != 3000000)
+ s.rto = (double)info->tcpi_rto / 1000;
+
+ s.backoff = info->tcpi_backoff;
+ s.rtt = (double)info->tcpi_rtt / 1000;
+ s.rttvar = (double)info->tcpi_rttvar / 1000;
+ s.ato = (double)info->tcpi_ato / 1000;
+ s.mss = info->tcpi_snd_mss;
+ s.rcv_mss = info->tcpi_rcv_mss;
+ s.advmss = info->tcpi_advmss;
+ s.rcv_space = info->tcpi_rcv_space;
+ s.rcv_rtt = (double)info->tcpi_rcv_rtt / 1000;
+ s.lastsnd = info->tcpi_last_data_sent;
+ s.lastrcv = info->tcpi_last_data_recv;
+ s.lastack = info->tcpi_last_ack_recv;
+ s.unacked = info->tcpi_unacked;
+ s.retrans = info->tcpi_retrans;
+ s.retrans_total = info->tcpi_total_retrans;
+ s.lost = info->tcpi_lost;
+ s.sacked = info->tcpi_sacked;
+ s.fackets = info->tcpi_fackets;
+ s.reordering = info->tcpi_reordering;
+ s.rcv_ssthresh = info->tcpi_rcv_ssthresh;
+ s.cwnd = info->tcpi_snd_cwnd;
+ s.pmtu = info->tcpi_pmtu;
+
+ if (info->tcpi_snd_ssthresh < 0xFFFF)
+ s.ssthresh = info->tcpi_snd_ssthresh;
+
+ rtt = (double) info->tcpi_rtt;
+ if (tb[INET_DIAG_VEGASINFO]) {
+ const struct tcpvegas_info *vinfo
+ = RTA_DATA(tb[INET_DIAG_VEGASINFO]);
+
+ if (vinfo->tcpv_enabled &&
+ vinfo->tcpv_rtt && vinfo->tcpv_rtt != 0x7fffffff)
+ rtt = vinfo->tcpv_rtt;
+ }
+
+ if (tb[INET_DIAG_DCTCPINFO]) {
+ struct dctcpstat *dctcp = malloc(sizeof(struct
+ dctcpstat));
+
+ const struct tcp_dctcp_info *dinfo
+ = RTA_DATA(tb[INET_DIAG_DCTCPINFO]);
+
+ dctcp->enabled = !!dinfo->dctcp_enabled;
+ dctcp->ce_state = dinfo->dctcp_ce_state;
+ dctcp->alpha = dinfo->dctcp_alpha;
+ dctcp->ab_ecn = dinfo->dctcp_ab_ecn;
+ dctcp->ab_tot = dinfo->dctcp_ab_tot;
+ s.dctcp = dctcp;
+ }
+
+ if (tb[INET_DIAG_BBRINFO]) {
+ const void *bbr_info = RTA_DATA(tb[INET_DIAG_BBRINFO]);
+ int len = min(RTA_PAYLOAD(tb[INET_DIAG_BBRINFO]),
+ sizeof(*s.bbr_info));
+
+ s.bbr_info = calloc(1, sizeof(*s.bbr_info));
+ if (s.bbr_info && bbr_info)
+ memcpy(s.bbr_info, bbr_info, len);
+ }
+
+ if (rtt > 0 && info->tcpi_snd_mss && info->tcpi_snd_cwnd) {
+ s.send_bps = (double) info->tcpi_snd_cwnd *
+ (double)info->tcpi_snd_mss * 8000000. / rtt;
+ }
+
+ if (info->tcpi_pacing_rate &&
+ info->tcpi_pacing_rate != ~0ULL) {
+ s.pacing_rate = info->tcpi_pacing_rate * 8.;
+
+ if (info->tcpi_max_pacing_rate &&
+ info->tcpi_max_pacing_rate != ~0ULL)
+ s.pacing_rate_max = info->tcpi_max_pacing_rate * 8.;
+ }
+ s.bytes_acked = info->tcpi_bytes_acked;
+ s.bytes_received = info->tcpi_bytes_received;
+ s.segs_out = info->tcpi_segs_out;
+ s.segs_in = info->tcpi_segs_in;
+ s.data_segs_out = info->tcpi_data_segs_out;
+ s.data_segs_in = info->tcpi_data_segs_in;
+ s.not_sent = info->tcpi_notsent_bytes;
+ if (info->tcpi_min_rtt && info->tcpi_min_rtt != ~0U)
+ s.min_rtt = (double) info->tcpi_min_rtt / 1000;
+ s.delivery_rate = info->tcpi_delivery_rate * 8.;
+ s.app_limited = info->tcpi_delivery_rate_app_limited;
+ s.busy_time = info->tcpi_busy_time;
+ s.rwnd_limited = info->tcpi_rwnd_limited;
+ s.sndbuf_limited = info->tcpi_sndbuf_limited;
+ s.delivered = info->tcpi_delivered;
+ s.delivered_ce = info->tcpi_delivered_ce;
+ s.dsack_dups = info->tcpi_dsack_dups;
+ s.reord_seen = info->tcpi_reord_seen;
+ s.bytes_sent = info->tcpi_bytes_sent;
+ s.bytes_retrans = info->tcpi_bytes_retrans;
+ tcp_stats_print(&s);
+ free(s.dctcp);
+ free(s.bbr_info);
+ }
+ if (tb[INET_DIAG_MD5SIG]) {
+ struct tcp_diag_md5sig *sig = RTA_DATA(tb[INET_DIAG_MD5SIG]);
+ int len = RTA_PAYLOAD(tb[INET_DIAG_MD5SIG]);
+
+ out(" md5keys:");
+ print_md5sig(sig++);
+ for (len -= sizeof(*sig); len > 0; len -= sizeof(*sig)) {
+ out(",");
+ print_md5sig(sig++);
+ }
+ }
+}
+
+static const char *format_host_sa(struct sockaddr_storage *sa)
+{
+ union {
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ } *saddr = (void *)sa;
+
+ switch (sa->ss_family) {
+ case AF_INET:
+ return format_host(AF_INET, 4, &saddr->sin.sin_addr);
+ case AF_INET6:
+ return format_host(AF_INET6, 16, &saddr->sin6.sin6_addr);
+ default:
+ return "";
+ }
+}
+
+static void sctp_show_info(const struct nlmsghdr *nlh, struct inet_diag_msg *r,
+ struct rtattr *tb[])
+{
+ struct sockaddr_storage *sa;
+ int len;
+
+ print_skmeminfo(tb, INET_DIAG_SKMEMINFO);
+
+ if (tb[INET_DIAG_LOCALS]) {
+ len = RTA_PAYLOAD(tb[INET_DIAG_LOCALS]);
+ sa = RTA_DATA(tb[INET_DIAG_LOCALS]);
+
+ out(" locals:%s", format_host_sa(sa));
+ for (sa++, len -= sizeof(*sa); len > 0; sa++, len -= sizeof(*sa))
+ out(",%s", format_host_sa(sa));
+
+ }
+ if (tb[INET_DIAG_PEERS]) {
+ len = RTA_PAYLOAD(tb[INET_DIAG_PEERS]);
+ sa = RTA_DATA(tb[INET_DIAG_PEERS]);
+
+ out(" peers:%s", format_host_sa(sa));
+ for (sa++, len -= sizeof(*sa); len > 0; sa++, len -= sizeof(*sa))
+ out(",%s", format_host_sa(sa));
+ }
+ if (tb[INET_DIAG_INFO]) {
+ struct sctp_info *info;
+ len = RTA_PAYLOAD(tb[INET_DIAG_INFO]);
+
+ /* workaround for older kernels with less fields */
+ if (len < sizeof(*info)) {
+ info = alloca(sizeof(*info));
+ memcpy(info, RTA_DATA(tb[INET_DIAG_INFO]), len);
+ memset((char *)info + len, 0, sizeof(*info) - len);
+ } else
+ info = RTA_DATA(tb[INET_DIAG_INFO]);
+
+ sctp_stats_print(info);
+ }
+}
+
+static void parse_diag_msg(struct nlmsghdr *nlh, struct sockstat *s)
+{
+ struct rtattr *tb[INET_DIAG_MAX+1];
+ struct inet_diag_msg *r = NLMSG_DATA(nlh);
+
+ parse_rtattr(tb, INET_DIAG_MAX, (struct rtattr *)(r+1),
+ nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+ s->state = r->idiag_state;
+ s->local.family = s->remote.family = r->idiag_family;
+ s->lport = ntohs(r->id.idiag_sport);
+ s->rport = ntohs(r->id.idiag_dport);
+ s->wq = r->idiag_wqueue;
+ s->rq = r->idiag_rqueue;
+ s->ino = r->idiag_inode;
+ s->uid = r->idiag_uid;
+ s->iface = r->id.idiag_if;
+ s->sk = cookie_sk_get(&r->id.idiag_cookie[0]);
+
+ s->mark = 0;
+ if (tb[INET_DIAG_MARK])
+ s->mark = rta_getattr_u32(tb[INET_DIAG_MARK]);
+ if (tb[INET_DIAG_PROTOCOL])
+ s->raw_prot = rta_getattr_u8(tb[INET_DIAG_PROTOCOL]);
+ else
+ s->raw_prot = 0;
+
+ if (s->local.family == AF_INET)
+ s->local.bytelen = s->remote.bytelen = 4;
+ else
+ s->local.bytelen = s->remote.bytelen = 16;
+
+ memcpy(s->local.data, r->id.idiag_src, s->local.bytelen);
+ memcpy(s->remote.data, r->id.idiag_dst, s->local.bytelen);
+}
+
+static int inet_show_sock(struct nlmsghdr *nlh,
+ struct sockstat *s)
+{
+ struct rtattr *tb[INET_DIAG_MAX+1];
+ struct inet_diag_msg *r = NLMSG_DATA(nlh);
+ unsigned char v6only = 0;
+
+ parse_rtattr(tb, INET_DIAG_MAX, (struct rtattr *)(r+1),
+ nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+ if (tb[INET_DIAG_PROTOCOL])
+ s->type = rta_getattr_u8(tb[INET_DIAG_PROTOCOL]);
+
+ if (s->local.family == AF_INET6 && tb[INET_DIAG_SKV6ONLY])
+ v6only = rta_getattr_u8(tb[INET_DIAG_SKV6ONLY]);
+
+ inet_stats_print(s, v6only);
+
+ if (show_options) {
+ struct tcpstat t = {};
+
+ t.timer = r->idiag_timer;
+ t.timeout = r->idiag_expires;
+ t.retrans = r->idiag_retrans;
+ if (s->type == IPPROTO_SCTP)
+ sctp_timer_print(&t);
+ else
+ tcp_timer_print(&t);
+ }
+
+ if (show_details) {
+ sock_details_print(s);
+ if (s->local.family == AF_INET6 && tb[INET_DIAG_SKV6ONLY])
+ out(" v6only:%u", v6only);
+
+ if (tb[INET_DIAG_SHUTDOWN]) {
+ unsigned char mask;
+
+ mask = rta_getattr_u8(tb[INET_DIAG_SHUTDOWN]);
+ out(" %c-%c",
+ mask & 1 ? '-' : '<', mask & 2 ? '-' : '>');
+ }
+ }
+
+ if (show_tos) {
+ if (tb[INET_DIAG_TOS])
+ out(" tos:%#x", rta_getattr_u8(tb[INET_DIAG_TOS]));
+ if (tb[INET_DIAG_TCLASS])
+ out(" tclass:%#x", rta_getattr_u8(tb[INET_DIAG_TCLASS]));
+ if (tb[INET_DIAG_CLASS_ID])
+ out(" class_id:%#x", rta_getattr_u32(tb[INET_DIAG_CLASS_ID]));
+ }
+
+ if (show_mem || (show_tcpinfo && s->type != IPPROTO_UDP)) {
+ if (!oneline)
+ out("\n\t");
+ if (s->type == IPPROTO_SCTP)
+ sctp_show_info(nlh, r, tb);
+ else
+ tcp_show_info(nlh, r, tb);
+ }
+ sctp_ino = s->ino;
+
+ return 0;
+}
+
+static int tcpdiag_send(int fd, int protocol, struct filter *f)
+{
+ struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
+ struct {
+ struct nlmsghdr nlh;
+ struct inet_diag_req r;
+ } req = {
+ .nlh.nlmsg_len = sizeof(req),
+ .nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST,
+ .nlh.nlmsg_seq = MAGIC_SEQ,
+ .r.idiag_family = AF_INET,
+ .r.idiag_states = f->states,
+ };
+ char *bc = NULL;
+ int bclen;
+ struct msghdr msg;
+ struct rtattr rta;
+ struct iovec iov[3];
+ int iovlen = 1;
+
+ if (protocol == IPPROTO_UDP)
+ return -1;
+
+ if (protocol == IPPROTO_TCP)
+ req.nlh.nlmsg_type = TCPDIAG_GETSOCK;
+ else
+ req.nlh.nlmsg_type = DCCPDIAG_GETSOCK;
+ if (show_mem) {
+ req.r.idiag_ext |= (1<<(INET_DIAG_MEMINFO-1));
+ req.r.idiag_ext |= (1<<(INET_DIAG_SKMEMINFO-1));
+ }
+
+ if (show_tcpinfo) {
+ req.r.idiag_ext |= (1<<(INET_DIAG_INFO-1));
+ req.r.idiag_ext |= (1<<(INET_DIAG_VEGASINFO-1));
+ req.r.idiag_ext |= (1<<(INET_DIAG_CONG-1));
+ }
+
+ if (show_tos) {
+ req.r.idiag_ext |= (1<<(INET_DIAG_TOS-1));
+ req.r.idiag_ext |= (1<<(INET_DIAG_TCLASS-1));
+ }
+
+ iov[0] = (struct iovec){
+ .iov_base = &req,
+ .iov_len = sizeof(req)
+ };
+ if (f->f) {
+ bclen = ssfilter_bytecompile(f->f, &bc);
+ if (bclen) {
+ rta.rta_type = INET_DIAG_REQ_BYTECODE;
+ rta.rta_len = RTA_LENGTH(bclen);
+ iov[1] = (struct iovec){ &rta, sizeof(rta) };
+ iov[2] = (struct iovec){ bc, bclen };
+ req.nlh.nlmsg_len += RTA_LENGTH(bclen);
+ iovlen = 3;
+ }
+ }
+
+ msg = (struct msghdr) {
+ .msg_name = (void *)&nladdr,
+ .msg_namelen = sizeof(nladdr),
+ .msg_iov = iov,
+ .msg_iovlen = iovlen,
+ };
+
+ if (sendmsg(fd, &msg, 0) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int sockdiag_send(int family, int fd, int protocol, struct filter *f)
+{
+ struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
+ DIAG_REQUEST(req, struct inet_diag_req_v2 r);
+ char *bc = NULL;
+ int bclen;
+ struct msghdr msg;
+ struct rtattr rta;
+ struct iovec iov[3];
+ int iovlen = 1;
+
+ if (family == PF_UNSPEC)
+ return tcpdiag_send(fd, protocol, f);
+
+ memset(&req.r, 0, sizeof(req.r));
+ req.r.sdiag_family = family;
+ req.r.sdiag_protocol = protocol;
+ req.r.idiag_states = f->states;
+ if (show_mem) {
+ req.r.idiag_ext |= (1<<(INET_DIAG_MEMINFO-1));
+ req.r.idiag_ext |= (1<<(INET_DIAG_SKMEMINFO-1));
+ }
+
+ if (show_tcpinfo) {
+ req.r.idiag_ext |= (1<<(INET_DIAG_INFO-1));
+ req.r.idiag_ext |= (1<<(INET_DIAG_VEGASINFO-1));
+ req.r.idiag_ext |= (1<<(INET_DIAG_CONG-1));
+ }
+
+ if (show_tos) {
+ req.r.idiag_ext |= (1<<(INET_DIAG_TOS-1));
+ req.r.idiag_ext |= (1<<(INET_DIAG_TCLASS-1));
+ }
+
+ iov[0] = (struct iovec){
+ .iov_base = &req,
+ .iov_len = sizeof(req)
+ };
+ if (f->f) {
+ bclen = ssfilter_bytecompile(f->f, &bc);
+ if (bclen) {
+ rta.rta_type = INET_DIAG_REQ_BYTECODE;
+ rta.rta_len = RTA_LENGTH(bclen);
+ iov[1] = (struct iovec){ &rta, sizeof(rta) };
+ iov[2] = (struct iovec){ bc, bclen };
+ req.nlh.nlmsg_len += RTA_LENGTH(bclen);
+ iovlen = 3;
+ }
+ }
+
+ msg = (struct msghdr) {
+ .msg_name = (void *)&nladdr,
+ .msg_namelen = sizeof(nladdr),
+ .msg_iov = iov,
+ .msg_iovlen = iovlen,
+ };
+
+ if (sendmsg(fd, &msg, 0) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ return 0;
+}
+
+struct inet_diag_arg {
+ struct filter *f;
+ int protocol;
+ struct rtnl_handle *rth;
+};
+
+static int kill_inet_sock(struct nlmsghdr *h, void *arg, struct sockstat *s)
+{
+ struct inet_diag_msg *d = NLMSG_DATA(h);
+ struct inet_diag_arg *diag_arg = arg;
+ struct rtnl_handle *rth = diag_arg->rth;
+
+ DIAG_REQUEST(req, struct inet_diag_req_v2 r);
+
+ req.nlh.nlmsg_type = SOCK_DESTROY;
+ req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ req.nlh.nlmsg_seq = ++rth->seq;
+ req.r.sdiag_family = d->idiag_family;
+ req.r.sdiag_protocol = diag_arg->protocol;
+ req.r.id = d->id;
+
+ if (diag_arg->protocol == IPPROTO_RAW) {
+ struct inet_diag_req_raw *raw = (void *)&req.r;
+
+ BUILD_BUG_ON(sizeof(req.r) != sizeof(*raw));
+ raw->sdiag_raw_protocol = s->raw_prot;
+ }
+
+ return rtnl_talk(rth, &req.nlh, NULL);
+}
+
+static int show_one_inet_sock(struct nlmsghdr *h, void *arg)
+{
+ int err;
+ struct inet_diag_arg *diag_arg = arg;
+ struct inet_diag_msg *r = NLMSG_DATA(h);
+ struct sockstat s = {};
+
+ if (!(diag_arg->f->families & FAMILY_MASK(r->idiag_family)))
+ return 0;
+
+ parse_diag_msg(h, &s);
+ s.type = diag_arg->protocol;
+
+ if (diag_arg->f->f && run_ssfilter(diag_arg->f->f, &s) == 0)
+ return 0;
+
+ if (diag_arg->f->kill && kill_inet_sock(h, arg, &s) != 0) {
+ if (errno == EOPNOTSUPP || errno == ENOENT) {
+ /* Socket can't be closed, or is already closed. */
+ return 0;
+ } else {
+ perror("SOCK_DESTROY answers");
+ return -1;
+ }
+ }
+
+ err = inet_show_sock(h, &s);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int inet_show_netlink(struct filter *f, FILE *dump_fp, int protocol)
+{
+ int err = 0;
+ struct rtnl_handle rth, rth2;
+ int family = PF_INET;
+ struct inet_diag_arg arg = { .f = f, .protocol = protocol };
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_SOCK_DIAG))
+ return -1;
+
+ if (f->kill) {
+ if (rtnl_open_byproto(&rth2, 0, NETLINK_SOCK_DIAG)) {
+ rtnl_close(&rth);
+ return -1;
+ }
+ arg.rth = &rth2;
+ }
+
+ rth.dump = MAGIC_SEQ;
+ rth.dump_fp = dump_fp;
+ if (preferred_family == PF_INET6)
+ family = PF_INET6;
+
+again:
+ if ((err = sockdiag_send(family, rth.fd, protocol, f)))
+ goto Exit;
+
+ if ((err = rtnl_dump_filter(&rth, show_one_inet_sock, &arg))) {
+ if (family != PF_UNSPEC) {
+ family = PF_UNSPEC;
+ goto again;
+ }
+ goto Exit;
+ }
+ if (family == PF_INET && preferred_family != PF_INET) {
+ family = PF_INET6;
+ goto again;
+ }
+
+Exit:
+ rtnl_close(&rth);
+ if (arg.rth)
+ rtnl_close(arg.rth);
+ return err;
+}
+
+static int tcp_show_netlink_file(struct filter *f)
+{
+ FILE *fp;
+ char buf[16384];
+ int err = -1;
+
+ if ((fp = fopen(getenv("TCPDIAG_FILE"), "r")) == NULL) {
+ perror("fopen($TCPDIAG_FILE)");
+ return err;
+ }
+
+ while (1) {
+ int err2;
+ size_t status, nitems;
+ struct nlmsghdr *h = (struct nlmsghdr *)buf;
+ struct sockstat s = {};
+
+ status = fread(buf, 1, sizeof(*h), fp);
+ if (status != sizeof(*h)) {
+ if (ferror(fp))
+ perror("Reading header from $TCPDIAG_FILE");
+ if (feof(fp))
+ fprintf(stderr, "Unexpected EOF reading $TCPDIAG_FILE");
+ break;
+ }
+
+ nitems = NLMSG_ALIGN(h->nlmsg_len - sizeof(*h));
+ status = fread(h+1, 1, nitems, fp);
+
+ if (status != nitems) {
+ if (ferror(fp))
+ perror("Reading $TCPDIAG_FILE");
+ if (feof(fp))
+ fprintf(stderr, "Unexpected EOF reading $TCPDIAG_FILE");
+ break;
+ }
+
+ /* The only legal exit point */
+ if (h->nlmsg_type == NLMSG_DONE) {
+ err = 0;
+ break;
+ }
+
+ 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;
+ perror("TCPDIAG answered");
+ }
+ break;
+ }
+
+ parse_diag_msg(h, &s);
+ s.type = IPPROTO_TCP;
+
+ if (f && f->f && run_ssfilter(f->f, &s) == 0)
+ continue;
+
+ err2 = inet_show_sock(h, &s);
+ if (err2 < 0) {
+ err = err2;
+ break;
+ }
+ }
+
+ fclose(fp);
+ return err;
+}
+
+static int tcp_show(struct filter *f)
+{
+ FILE *fp = NULL;
+ char *buf = NULL;
+ int bufsize = 1024*1024;
+
+ if (!filter_af_get(f, AF_INET) && !filter_af_get(f, AF_INET6))
+ return 0;
+
+ dg_proto = TCP_PROTO;
+
+ if (getenv("TCPDIAG_FILE"))
+ return tcp_show_netlink_file(f);
+
+ if (!getenv("PROC_NET_TCP") && !getenv("PROC_ROOT")
+ && inet_show_netlink(f, NULL, IPPROTO_TCP) == 0)
+ return 0;
+
+ /* Sigh... We have to parse /proc/net/tcp... */
+ while (bufsize >= 64*1024) {
+ if ((buf = malloc(bufsize)) != NULL)
+ break;
+ bufsize /= 2;
+ }
+ if (buf == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (f->families & FAMILY_MASK(AF_INET)) {
+ if ((fp = net_tcp_open()) == NULL)
+ goto outerr;
+
+ setbuffer(fp, buf, bufsize);
+ if (generic_record_read(fp, tcp_show_line, f, AF_INET))
+ goto outerr;
+ fclose(fp);
+ }
+
+ if ((f->families & FAMILY_MASK(AF_INET6)) &&
+ (fp = net_tcp6_open()) != NULL) {
+ setbuffer(fp, buf, bufsize);
+ if (generic_record_read(fp, tcp_show_line, f, AF_INET6))
+ goto outerr;
+ fclose(fp);
+ }
+
+ free(buf);
+ return 0;
+
+outerr:
+ do {
+ int saved_errno = errno;
+
+ free(buf);
+ if (fp)
+ fclose(fp);
+ errno = saved_errno;
+ return -1;
+ } while (0);
+}
+
+static int dccp_show(struct filter *f)
+{
+ if (!filter_af_get(f, AF_INET) && !filter_af_get(f, AF_INET6))
+ return 0;
+
+ if (!getenv("PROC_NET_DCCP") && !getenv("PROC_ROOT")
+ && inet_show_netlink(f, NULL, IPPROTO_DCCP) == 0)
+ return 0;
+
+ return 0;
+}
+
+static int sctp_show(struct filter *f)
+{
+ if (!filter_af_get(f, AF_INET) && !filter_af_get(f, AF_INET6))
+ return 0;
+
+ if (!getenv("PROC_NET_SCTP") && !getenv("PROC_ROOT")
+ && inet_show_netlink(f, NULL, IPPROTO_SCTP) == 0)
+ return 0;
+
+ return 0;
+}
+
+static int dgram_show_line(char *line, const struct filter *f, int family)
+{
+ struct sockstat s = {};
+ char *loc, *rem, *data;
+ char opt[256];
+ int n;
+
+ if (proc_inet_split_line(line, &loc, &rem, &data))
+ return -1;
+
+ int state = (data[1] >= 'A') ? (data[1] - 'A' + 10) : (data[1] - '0');
+
+ if (!(f->states & (1 << state)))
+ return 0;
+
+ proc_parse_inet_addr(loc, rem, family, &s);
+
+ if (f->f && run_ssfilter(f->f, &s) == 0)
+ return 0;
+
+ opt[0] = 0;
+ n = sscanf(data, "%x %x:%x %*x:%*x %*x %d %*d %u %d %llx %[^\n]\n",
+ &s.state, &s.wq, &s.rq,
+ &s.uid, &s.ino,
+ &s.refcnt, &s.sk, opt);
+
+ if (n < 9)
+ opt[0] = 0;
+
+ s.type = dg_proto == UDP_PROTO ? IPPROTO_UDP : 0;
+ inet_stats_print(&s, false);
+
+ if (show_details && opt[0])
+ out(" opt:\"%s\"", opt);
+
+ return 0;
+}
+
+static int udp_show(struct filter *f)
+{
+ FILE *fp = NULL;
+
+ if (!filter_af_get(f, AF_INET) && !filter_af_get(f, AF_INET6))
+ return 0;
+
+ dg_proto = UDP_PROTO;
+
+ if (!getenv("PROC_NET_UDP") && !getenv("PROC_ROOT")
+ && inet_show_netlink(f, NULL, IPPROTO_UDP) == 0)
+ return 0;
+
+ if (f->families&FAMILY_MASK(AF_INET)) {
+ if ((fp = net_udp_open()) == NULL)
+ goto outerr;
+ if (generic_record_read(fp, dgram_show_line, f, AF_INET))
+ goto outerr;
+ fclose(fp);
+ }
+
+ if ((f->families&FAMILY_MASK(AF_INET6)) &&
+ (fp = net_udp6_open()) != NULL) {
+ if (generic_record_read(fp, dgram_show_line, f, AF_INET6))
+ goto outerr;
+ fclose(fp);
+ }
+ return 0;
+
+outerr:
+ do {
+ int saved_errno = errno;
+
+ if (fp)
+ fclose(fp);
+ errno = saved_errno;
+ return -1;
+ } while (0);
+}
+
+static int raw_show(struct filter *f)
+{
+ FILE *fp = NULL;
+
+ if (!filter_af_get(f, AF_INET) && !filter_af_get(f, AF_INET6))
+ return 0;
+
+ dg_proto = RAW_PROTO;
+
+ if (!getenv("PROC_NET_RAW") && !getenv("PROC_ROOT") &&
+ inet_show_netlink(f, NULL, IPPROTO_RAW) == 0)
+ return 0;
+
+ if (f->families&FAMILY_MASK(AF_INET)) {
+ if ((fp = net_raw_open()) == NULL)
+ goto outerr;
+ if (generic_record_read(fp, dgram_show_line, f, AF_INET))
+ goto outerr;
+ fclose(fp);
+ }
+
+ if ((f->families&FAMILY_MASK(AF_INET6)) &&
+ (fp = net_raw6_open()) != NULL) {
+ if (generic_record_read(fp, dgram_show_line, f, AF_INET6))
+ goto outerr;
+ fclose(fp);
+ }
+ return 0;
+
+outerr:
+ do {
+ int saved_errno = errno;
+
+ if (fp)
+ fclose(fp);
+ errno = saved_errno;
+ return -1;
+ } while (0);
+}
+
+#define MAX_UNIX_REMEMBER (1024*1024/sizeof(struct sockstat))
+
+static void unix_list_drop_first(struct sockstat **list)
+{
+ struct sockstat *s = *list;
+
+ (*list) = (*list)->next;
+ free(s->name);
+ free(s);
+}
+
+static bool unix_type_skip(struct sockstat *s, struct filter *f)
+{
+ if (s->type == SOCK_STREAM && !(f->dbs&(1<<UNIX_ST_DB)))
+ return true;
+ if (s->type == SOCK_DGRAM && !(f->dbs&(1<<UNIX_DG_DB)))
+ return true;
+ if (s->type == SOCK_SEQPACKET && !(f->dbs&(1<<UNIX_SQ_DB)))
+ return true;
+ return false;
+}
+
+static void unix_stats_print(struct sockstat *s, struct filter *f)
+{
+ char port_name[30] = {};
+
+ sock_state_print(s);
+
+ sock_addr_print(s->name ?: "*", " ",
+ int_to_str(s->lport, port_name), NULL);
+ sock_addr_print(s->peer_name ?: "*", " ",
+ int_to_str(s->rport, port_name), NULL);
+
+ proc_ctx_print(s);
+}
+
+static int unix_show_sock(struct nlmsghdr *nlh, void *arg)
+{
+ struct filter *f = (struct filter *)arg;
+ struct unix_diag_msg *r = NLMSG_DATA(nlh);
+ struct rtattr *tb[UNIX_DIAG_MAX+1];
+ char name[128];
+ struct sockstat stat = { .name = "*", .peer_name = "*" };
+
+ parse_rtattr(tb, UNIX_DIAG_MAX, (struct rtattr *)(r+1),
+ nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+ stat.type = r->udiag_type;
+ stat.state = r->udiag_state;
+ stat.ino = stat.lport = r->udiag_ino;
+ stat.local.family = stat.remote.family = AF_UNIX;
+
+ if (unix_type_skip(&stat, f))
+ return 0;
+
+ if (tb[UNIX_DIAG_RQLEN]) {
+ struct unix_diag_rqlen *rql = RTA_DATA(tb[UNIX_DIAG_RQLEN]);
+
+ stat.rq = rql->udiag_rqueue;
+ stat.wq = rql->udiag_wqueue;
+ }
+ if (tb[UNIX_DIAG_NAME]) {
+ int len = RTA_PAYLOAD(tb[UNIX_DIAG_NAME]);
+
+ memcpy(name, RTA_DATA(tb[UNIX_DIAG_NAME]), len);
+ name[len] = '\0';
+ if (name[0] == '\0') {
+ int i;
+ for (i = 0; i < len; i++)
+ if (name[i] == '\0')
+ name[i] = '@';
+ }
+ stat.name = &name[0];
+ memcpy(stat.local.data, &stat.name, sizeof(stat.name));
+ }
+ if (tb[UNIX_DIAG_PEER])
+ stat.rport = rta_getattr_u32(tb[UNIX_DIAG_PEER]);
+
+ if (f->f && run_ssfilter(f->f, &stat) == 0)
+ return 0;
+
+ unix_stats_print(&stat, f);
+
+ if (show_mem)
+ print_skmeminfo(tb, UNIX_DIAG_MEMINFO);
+ if (show_details) {
+ if (tb[UNIX_DIAG_SHUTDOWN]) {
+ unsigned char mask;
+
+ mask = rta_getattr_u8(tb[UNIX_DIAG_SHUTDOWN]);
+ out(" %c-%c",
+ mask & 1 ? '-' : '<', mask & 2 ? '-' : '>');
+ }
+ if (tb[UNIX_DIAG_VFS]) {
+ struct unix_diag_vfs *uv = RTA_DATA(tb[UNIX_DIAG_VFS]);
+
+ out(" ino:%u dev:%u/%u", uv->udiag_vfs_ino, major(uv->udiag_vfs_dev),
+ minor(uv->udiag_vfs_dev));
+ }
+ if (tb[UNIX_DIAG_ICONS]) {
+ int len = RTA_PAYLOAD(tb[UNIX_DIAG_ICONS]);
+ __u32 *peers = RTA_DATA(tb[UNIX_DIAG_ICONS]);
+ int i;
+
+ out(" peers:");
+ for (i = 0; i < len / sizeof(__u32); i++)
+ out(" %u", peers[i]);
+ }
+ }
+
+ return 0;
+}
+
+static int handle_netlink_request(struct filter *f, struct nlmsghdr *req,
+ size_t size, rtnl_filter_t show_one_sock)
+{
+ int ret = -1;
+ struct rtnl_handle rth;
+
+ if (rtnl_open_byproto(&rth, 0, NETLINK_SOCK_DIAG))
+ return -1;
+
+ rth.dump = MAGIC_SEQ;
+
+ if (rtnl_send(&rth, req, size) < 0)
+ goto Exit;
+
+ if (rtnl_dump_filter(&rth, show_one_sock, f))
+ goto Exit;
+
+ ret = 0;
+Exit:
+ rtnl_close(&rth);
+ return ret;
+}
+
+static int unix_show_netlink(struct filter *f)
+{
+ DIAG_REQUEST(req, struct unix_diag_req r);
+
+ req.r.sdiag_family = AF_UNIX;
+ req.r.udiag_states = f->states;
+ req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN;
+ if (show_mem)
+ req.r.udiag_show |= UDIAG_SHOW_MEMINFO;
+ if (show_details)
+ req.r.udiag_show |= UDIAG_SHOW_VFS | UDIAG_SHOW_ICONS;
+
+ return handle_netlink_request(f, &req.nlh, sizeof(req), unix_show_sock);
+}
+
+static int unix_show(struct filter *f)
+{
+ FILE *fp;
+ char buf[256];
+ char name[128];
+ int newformat = 0;
+ int cnt;
+ struct sockstat *list = NULL;
+ const int unix_state_map[] = { SS_CLOSE, SS_SYN_SENT,
+ SS_ESTABLISHED, SS_CLOSING };
+
+ if (!filter_af_get(f, AF_UNIX))
+ return 0;
+
+ if (!getenv("PROC_NET_UNIX") && !getenv("PROC_ROOT")
+ && unix_show_netlink(f) == 0)
+ return 0;
+
+ if ((fp = net_unix_open()) == NULL)
+ return -1;
+ if (!fgets(buf, sizeof(buf), fp)) {
+ fclose(fp);
+ return -1;
+ }
+
+ if (memcmp(buf, "Peer", 4) == 0)
+ newformat = 1;
+ cnt = 0;
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ struct sockstat *u, **insp;
+ int flags;
+
+ if (!(u = calloc(1, sizeof(*u))))
+ break;
+
+ if (sscanf(buf, "%x: %x %x %x %x %x %d %s",
+ &u->rport, &u->rq, &u->wq, &flags, &u->type,
+ &u->state, &u->ino, name) < 8)
+ name[0] = 0;
+
+ u->lport = u->ino;
+ u->local.family = u->remote.family = AF_UNIX;
+
+ if (flags & (1 << 16)) {
+ u->state = SS_LISTEN;
+ } else if (u->state > 0 &&
+ u->state <= ARRAY_SIZE(unix_state_map)) {
+ u->state = unix_state_map[u->state-1];
+ if (u->type == SOCK_DGRAM && u->state == SS_CLOSE && u->rport)
+ u->state = SS_ESTABLISHED;
+ }
+ if (unix_type_skip(u, f) ||
+ !(f->states & (1 << u->state))) {
+ free(u);
+ continue;
+ }
+
+ if (!newformat) {
+ u->rport = 0;
+ u->rq = 0;
+ u->wq = 0;
+ }
+
+ if (name[0]) {
+ u->name = strdup(name);
+ if (!u->name) {
+ free(u);
+ break;
+ }
+ }
+
+ if (u->rport) {
+ struct sockstat *p;
+
+ for (p = list; p; p = p->next) {
+ if (u->rport == p->lport)
+ break;
+ }
+ if (!p)
+ u->peer_name = "?";
+ else
+ u->peer_name = p->name ? : "*";
+ }
+
+ if (f->f) {
+ struct sockstat st = {
+ .local.family = AF_UNIX,
+ .remote.family = AF_UNIX,
+ };
+
+ memcpy(st.local.data, &u->name, sizeof(u->name));
+ /* when parsing the old format rport is set to 0 and
+ * therefore peer_name remains NULL
+ */
+ if (u->peer_name && strcmp(u->peer_name, "*"))
+ memcpy(st.remote.data, &u->peer_name,
+ sizeof(u->peer_name));
+ if (run_ssfilter(f->f, &st) == 0) {
+ free(u->name);
+ free(u);
+ continue;
+ }
+ }
+
+ insp = &list;
+ while (*insp) {
+ if (u->type < (*insp)->type ||
+ (u->type == (*insp)->type &&
+ u->ino < (*insp)->ino))
+ break;
+ insp = &(*insp)->next;
+ }
+ u->next = *insp;
+ *insp = u;
+
+ if (++cnt > MAX_UNIX_REMEMBER) {
+ while (list) {
+ unix_stats_print(list, f);
+ unix_list_drop_first(&list);
+ }
+ cnt = 0;
+ }
+ }
+ fclose(fp);
+ while (list) {
+ unix_stats_print(list, f);
+ unix_list_drop_first(&list);
+ }
+
+ return 0;
+}
+
+static int packet_stats_print(struct sockstat *s, const struct filter *f)
+{
+ const char *addr, *port;
+ char ll_name[16];
+
+ s->local.family = s->remote.family = AF_PACKET;
+
+ if (f->f) {
+ s->local.data[0] = s->prot;
+ if (run_ssfilter(f->f, s) == 0)
+ return 1;
+ }
+
+ sock_state_print(s);
+
+ if (s->prot == 3)
+ addr = "*";
+ else
+ addr = ll_proto_n2a(htons(s->prot), ll_name, sizeof(ll_name));
+
+ if (s->iface == 0)
+ port = "*";
+ else
+ port = xll_index_to_name(s->iface);
+
+ sock_addr_print(addr, ":", port, NULL);
+ sock_addr_print("", "*", "", NULL);
+
+ proc_ctx_print(s);
+
+ if (show_details)
+ sock_details_print(s);
+
+ return 0;
+}
+
+static void packet_show_ring(struct packet_diag_ring *ring)
+{
+ out("blk_size:%d", ring->pdr_block_size);
+ out(",blk_nr:%d", ring->pdr_block_nr);
+ out(",frm_size:%d", ring->pdr_frame_size);
+ out(",frm_nr:%d", ring->pdr_frame_nr);
+ out(",tmo:%d", ring->pdr_retire_tmo);
+ out(",features:0x%x", ring->pdr_features);
+}
+
+static int packet_show_sock(struct nlmsghdr *nlh, void *arg)
+{
+ const struct filter *f = arg;
+ struct packet_diag_msg *r = NLMSG_DATA(nlh);
+ struct packet_diag_info *pinfo = NULL;
+ struct packet_diag_ring *ring_rx = NULL, *ring_tx = NULL;
+ struct rtattr *tb[PACKET_DIAG_MAX+1];
+ struct sockstat stat = {};
+ uint32_t fanout = 0;
+ bool has_fanout = false;
+
+ parse_rtattr(tb, PACKET_DIAG_MAX, (struct rtattr *)(r+1),
+ nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+ /* use /proc/net/packet if all info are not available */
+ if (!tb[PACKET_DIAG_MEMINFO])
+ return -1;
+
+ stat.type = r->pdiag_type;
+ stat.prot = r->pdiag_num;
+ stat.ino = r->pdiag_ino;
+ stat.state = SS_CLOSE;
+ stat.sk = cookie_sk_get(&r->pdiag_cookie[0]);
+
+ if (tb[PACKET_DIAG_MEMINFO]) {
+ __u32 *skmeminfo = RTA_DATA(tb[PACKET_DIAG_MEMINFO]);
+
+ stat.rq = skmeminfo[SK_MEMINFO_RMEM_ALLOC];
+ }
+
+ if (tb[PACKET_DIAG_INFO]) {
+ pinfo = RTA_DATA(tb[PACKET_DIAG_INFO]);
+ stat.lport = stat.iface = pinfo->pdi_index;
+ }
+
+ if (tb[PACKET_DIAG_UID])
+ stat.uid = rta_getattr_u32(tb[PACKET_DIAG_UID]);
+
+ if (tb[PACKET_DIAG_RX_RING])
+ ring_rx = RTA_DATA(tb[PACKET_DIAG_RX_RING]);
+
+ if (tb[PACKET_DIAG_TX_RING])
+ ring_tx = RTA_DATA(tb[PACKET_DIAG_TX_RING]);
+
+ if (tb[PACKET_DIAG_FANOUT]) {
+ has_fanout = true;
+ fanout = rta_getattr_u32(tb[PACKET_DIAG_FANOUT]);
+ }
+
+ if (packet_stats_print(&stat, f))
+ return 0;
+
+ if (show_details) {
+ if (pinfo) {
+ if (oneline)
+ out(" ver:%d", pinfo->pdi_version);
+ else
+ out("\n\tver:%d", pinfo->pdi_version);
+ out(" cpy_thresh:%d", pinfo->pdi_copy_thresh);
+ out(" flags( ");
+ if (pinfo->pdi_flags & PDI_RUNNING)
+ out("running");
+ if (pinfo->pdi_flags & PDI_AUXDATA)
+ out(" auxdata");
+ if (pinfo->pdi_flags & PDI_ORIGDEV)
+ out(" origdev");
+ if (pinfo->pdi_flags & PDI_VNETHDR)
+ out(" vnethdr");
+ if (pinfo->pdi_flags & PDI_LOSS)
+ out(" loss");
+ if (!pinfo->pdi_flags)
+ out("0");
+ out(" )");
+ }
+ if (ring_rx) {
+ if (oneline)
+ out(" ring_rx(");
+ else
+ out("\n\tring_rx(");
+ packet_show_ring(ring_rx);
+ out(")");
+ }
+ if (ring_tx) {
+ if (oneline)
+ out(" ring_tx(");
+ else
+ out("\n\tring_tx(");
+ packet_show_ring(ring_tx);
+ out(")");
+ }
+ if (has_fanout) {
+ uint16_t type = (fanout >> 16) & 0xffff;
+
+ if (oneline)
+ out(" fanout(");
+ else
+ out("\n\tfanout(");
+ out("id:%d,", fanout & 0xffff);
+ out("type:");
+
+ if (type == 0)
+ out("hash");
+ else if (type == 1)
+ out("lb");
+ else if (type == 2)
+ out("cpu");
+ else if (type == 3)
+ out("roll");
+ else if (type == 4)
+ out("random");
+ else if (type == 5)
+ out("qm");
+ else
+ out("0x%x", type);
+
+ out(")");
+ }
+ }
+
+ if (show_bpf && tb[PACKET_DIAG_FILTER]) {
+ struct sock_filter *fil =
+ RTA_DATA(tb[PACKET_DIAG_FILTER]);
+ int num = RTA_PAYLOAD(tb[PACKET_DIAG_FILTER]) /
+ sizeof(struct sock_filter);
+
+ if (oneline)
+ out(" bpf filter (%d): ", num);
+ else
+ out("\n\tbpf filter (%d): ", num);
+ while (num) {
+ out(" 0x%02x %u %u %u,",
+ fil->code, fil->jt, fil->jf, fil->k);
+ num--;
+ fil++;
+ }
+ }
+
+ if (show_mem)
+ print_skmeminfo(tb, PACKET_DIAG_MEMINFO);
+ return 0;
+}
+
+static int packet_show_netlink(struct filter *f)
+{
+ DIAG_REQUEST(req, struct packet_diag_req r);
+
+ req.r.sdiag_family = AF_PACKET;
+ req.r.pdiag_show = PACKET_SHOW_INFO | PACKET_SHOW_MEMINFO |
+ PACKET_SHOW_FILTER | PACKET_SHOW_RING_CFG | PACKET_SHOW_FANOUT;
+
+ return handle_netlink_request(f, &req.nlh, sizeof(req), packet_show_sock);
+}
+
+static int packet_show_line(char *buf, const struct filter *f, int fam)
+{
+ unsigned long long sk;
+ struct sockstat stat = {};
+ int type, prot, iface, state, rq, uid, ino;
+
+ sscanf(buf, "%llx %*d %d %x %d %d %u %u %u",
+ &sk,
+ &type, &prot, &iface, &state,
+ &rq, &uid, &ino);
+
+ if (stat.type == SOCK_RAW && !(f->dbs&(1<<PACKET_R_DB)))
+ return 0;
+ if (stat.type == SOCK_DGRAM && !(f->dbs&(1<<PACKET_DG_DB)))
+ return 0;
+
+ stat.type = type;
+ stat.prot = prot;
+ stat.lport = stat.iface = iface;
+ stat.state = state;
+ stat.rq = rq;
+ stat.uid = uid;
+ stat.ino = ino;
+ stat.state = SS_CLOSE;
+
+ if (packet_stats_print(&stat, f))
+ return 0;
+
+ return 0;
+}
+
+static int packet_show(struct filter *f)
+{
+ FILE *fp;
+ int rc = 0;
+
+ if (!filter_af_get(f, AF_PACKET) || !(f->states & (1 << SS_CLOSE)))
+ return 0;
+
+ if (!getenv("PROC_NET_PACKET") && !getenv("PROC_ROOT") &&
+ packet_show_netlink(f) == 0)
+ return 0;
+
+ if ((fp = net_packet_open()) == NULL)
+ return -1;
+ if (generic_record_read(fp, packet_show_line, f, AF_PACKET))
+ rc = -1;
+
+ fclose(fp);
+ return rc;
+}
+
+static int xdp_stats_print(struct sockstat *s, const struct filter *f)
+{
+ const char *addr, *port;
+ char q_str[16];
+
+ s->local.family = s->remote.family = AF_XDP;
+
+ if (f->f) {
+ if (run_ssfilter(f->f, s) == 0)
+ return 1;
+ }
+
+ sock_state_print(s);
+
+ if (s->iface) {
+ addr = xll_index_to_name(s->iface);
+ snprintf(q_str, sizeof(q_str), "q%d", s->lport);
+ port = q_str;
+ sock_addr_print(addr, ":", port, NULL);
+ } else {
+ sock_addr_print("", "*", "", NULL);
+ }
+
+ sock_addr_print("", "*", "", NULL);
+
+ proc_ctx_print(s);
+
+ if (show_details)
+ sock_details_print(s);
+
+ return 0;
+}
+
+static void xdp_show_ring(const char *name, struct xdp_diag_ring *ring)
+{
+ if (oneline)
+ out(" %s(", name);
+ else
+ out("\n\t%s(", name);
+ out("entries:%u", ring->entries);
+ out(")");
+}
+
+static void xdp_show_umem(struct xdp_diag_umem *umem, struct xdp_diag_ring *fr,
+ struct xdp_diag_ring *cr)
+{
+ if (oneline)
+ out(" tumem(");
+ else
+ out("\n\tumem(");
+ out("id:%u", umem->id);
+ out(",size:%llu", umem->size);
+ out(",num_pages:%u", umem->num_pages);
+ out(",chunk_size:%u", umem->chunk_size);
+ out(",headroom:%u", umem->headroom);
+ out(",ifindex:%u", umem->ifindex);
+ out(",qid:%u", umem->queue_id);
+ out(",zc:%u", umem->flags & XDP_DU_F_ZEROCOPY);
+ out(",refs:%u", umem->refs);
+ out(")");
+
+ if (fr)
+ xdp_show_ring("fr", fr);
+ if (cr)
+ xdp_show_ring("cr", cr);
+}
+
+static int xdp_show_sock(struct nlmsghdr *nlh, void *arg)
+{
+ struct xdp_diag_ring *rx = NULL, *tx = NULL, *fr = NULL, *cr = NULL;
+ struct xdp_diag_msg *msg = NLMSG_DATA(nlh);
+ struct rtattr *tb[XDP_DIAG_MAX + 1];
+ struct xdp_diag_info *info = NULL;
+ struct xdp_diag_umem *umem = NULL;
+ const struct filter *f = arg;
+ struct sockstat stat = {};
+
+ parse_rtattr(tb, XDP_DIAG_MAX, (struct rtattr *)(msg + 1),
+ nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*msg)));
+
+ stat.type = msg->xdiag_type;
+ stat.ino = msg->xdiag_ino;
+ stat.state = SS_CLOSE;
+ stat.sk = cookie_sk_get(&msg->xdiag_cookie[0]);
+
+ if (tb[XDP_DIAG_INFO]) {
+ info = RTA_DATA(tb[XDP_DIAG_INFO]);
+ stat.iface = info->ifindex;
+ stat.lport = info->queue_id;
+ }
+
+ if (tb[XDP_DIAG_UID])
+ stat.uid = rta_getattr_u32(tb[XDP_DIAG_UID]);
+ if (tb[XDP_DIAG_RX_RING])
+ rx = RTA_DATA(tb[XDP_DIAG_RX_RING]);
+ if (tb[XDP_DIAG_TX_RING])
+ tx = RTA_DATA(tb[XDP_DIAG_TX_RING]);
+ if (tb[XDP_DIAG_UMEM])
+ umem = RTA_DATA(tb[XDP_DIAG_UMEM]);
+ if (tb[XDP_DIAG_UMEM_FILL_RING])
+ fr = RTA_DATA(tb[XDP_DIAG_UMEM_FILL_RING]);
+ if (tb[XDP_DIAG_UMEM_COMPLETION_RING])
+ cr = RTA_DATA(tb[XDP_DIAG_UMEM_COMPLETION_RING]);
+ if (tb[XDP_DIAG_MEMINFO]) {
+ __u32 *skmeminfo = RTA_DATA(tb[XDP_DIAG_MEMINFO]);
+
+ stat.rq = skmeminfo[SK_MEMINFO_RMEM_ALLOC];
+ }
+
+ if (xdp_stats_print(&stat, f))
+ return 0;
+
+ if (show_details) {
+ if (rx)
+ xdp_show_ring("rx", rx);
+ if (tx)
+ xdp_show_ring("tx", tx);
+ if (umem)
+ xdp_show_umem(umem, fr, cr);
+ }
+
+ if (show_mem)
+ print_skmeminfo(tb, XDP_DIAG_MEMINFO); // really?
+
+
+ return 0;
+}
+
+static int xdp_show(struct filter *f)
+{
+ DIAG_REQUEST(req, struct xdp_diag_req r);
+
+ if (!filter_af_get(f, AF_XDP) || !(f->states & (1 << SS_CLOSE)))
+ return 0;
+
+ req.r.sdiag_family = AF_XDP;
+ req.r.xdiag_show = XDP_SHOW_INFO | XDP_SHOW_RING_CFG | XDP_SHOW_UMEM |
+ XDP_SHOW_MEMINFO;
+
+ return handle_netlink_request(f, &req.nlh, sizeof(req), xdp_show_sock);
+}
+
+static int netlink_show_one(struct filter *f,
+ int prot, int pid, unsigned int groups,
+ int state, int dst_pid, unsigned int dst_group,
+ int rq, int wq,
+ unsigned long long sk, unsigned long long cb)
+{
+ struct sockstat st = {
+ .state = SS_CLOSE,
+ .rq = rq,
+ .wq = wq,
+ .local.family = AF_NETLINK,
+ .remote.family = AF_NETLINK,
+ };
+
+ SPRINT_BUF(prot_buf) = {};
+ const char *prot_name;
+ char procname[64] = {};
+
+ if (f->f) {
+ st.rport = -1;
+ st.lport = pid;
+ st.local.data[0] = prot;
+ if (run_ssfilter(f->f, &st) == 0)
+ return 1;
+ }
+
+ sock_state_print(&st);
+
+ prot_name = nl_proto_n2a(prot, prot_buf, sizeof(prot_buf));
+
+ if (pid == -1) {
+ procname[0] = '*';
+ } else if (!numeric) {
+ int done = 0;
+
+ if (!pid) {
+ done = 1;
+ strncpy(procname, "kernel", 7);
+ } else if (pid > 0) {
+ FILE *fp;
+
+ snprintf(procname, sizeof(procname), "%s/%d/stat",
+ getenv("PROC_ROOT") ? : "/proc", pid);
+ if ((fp = fopen(procname, "r")) != NULL) {
+ if (fscanf(fp, "%*d (%[^)])", procname) == 1) {
+ snprintf(procname+strlen(procname),
+ sizeof(procname)-strlen(procname),
+ "/%d", pid);
+ done = 1;
+ }
+ fclose(fp);
+ }
+ }
+ if (!done)
+ int_to_str(pid, procname);
+ } else {
+ int_to_str(pid, procname);
+ }
+
+ sock_addr_print(prot_name, ":", procname, NULL);
+
+ if (state == NETLINK_CONNECTED) {
+ char dst_group_buf[30];
+ char dst_pid_buf[30];
+
+ sock_addr_print(int_to_str(dst_group, dst_group_buf), ":",
+ int_to_str(dst_pid, dst_pid_buf), NULL);
+ } else {
+ sock_addr_print("", "*", "", NULL);
+ }
+
+ char *pid_context = NULL;
+
+ if (show_proc_ctx) {
+ /* The pid value will either be:
+ * 0 if destination kernel - show kernel initial context.
+ * A valid process pid - use getpidcon.
+ * A unique value allocated by the kernel or netlink user
+ * to the process - show context as "not available".
+ */
+ if (!pid)
+ security_get_initial_context("kernel", &pid_context);
+ else if (pid > 0)
+ getpidcon(pid, &pid_context);
+
+ out(" proc_ctx=%s", pid_context ? : "unavailable");
+ free(pid_context);
+ }
+
+ if (show_details) {
+ out(" sk=%llx cb=%llx groups=0x%08x", sk, cb, groups);
+ }
+
+ return 0;
+}
+
+static int netlink_show_sock(struct nlmsghdr *nlh, void *arg)
+{
+ struct filter *f = (struct filter *)arg;
+ struct netlink_diag_msg *r = NLMSG_DATA(nlh);
+ struct rtattr *tb[NETLINK_DIAG_MAX+1];
+ int rq = 0, wq = 0;
+ unsigned long groups = 0;
+
+ parse_rtattr(tb, NETLINK_DIAG_MAX, (struct rtattr *)(r+1),
+ nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+ if (tb[NETLINK_DIAG_GROUPS] && RTA_PAYLOAD(tb[NETLINK_DIAG_GROUPS]))
+ groups = *(unsigned long *) RTA_DATA(tb[NETLINK_DIAG_GROUPS]);
+
+ if (tb[NETLINK_DIAG_MEMINFO]) {
+ const __u32 *skmeminfo;
+
+ skmeminfo = RTA_DATA(tb[NETLINK_DIAG_MEMINFO]);
+
+ rq = skmeminfo[SK_MEMINFO_RMEM_ALLOC];
+ wq = skmeminfo[SK_MEMINFO_WMEM_ALLOC];
+ }
+
+ if (netlink_show_one(f, r->ndiag_protocol, r->ndiag_portid, groups,
+ r->ndiag_state, r->ndiag_dst_portid, r->ndiag_dst_group,
+ rq, wq, 0, 0)) {
+ return 0;
+ }
+
+ if (show_mem) {
+ out("\t");
+ print_skmeminfo(tb, NETLINK_DIAG_MEMINFO);
+ }
+
+ return 0;
+}
+
+static int netlink_show_netlink(struct filter *f)
+{
+ DIAG_REQUEST(req, struct netlink_diag_req r);
+
+ req.r.sdiag_family = AF_NETLINK;
+ req.r.sdiag_protocol = NDIAG_PROTO_ALL;
+ req.r.ndiag_show = NDIAG_SHOW_GROUPS | NDIAG_SHOW_MEMINFO;
+
+ return handle_netlink_request(f, &req.nlh, sizeof(req), netlink_show_sock);
+}
+
+static int netlink_show(struct filter *f)
+{
+ FILE *fp;
+ char buf[256];
+ int prot, pid;
+ unsigned int groups;
+ int rq, wq, rc;
+ unsigned long long sk, cb;
+
+ if (!filter_af_get(f, AF_NETLINK) || !(f->states & (1 << SS_CLOSE)))
+ return 0;
+
+ if (!getenv("PROC_NET_NETLINK") && !getenv("PROC_ROOT") &&
+ netlink_show_netlink(f) == 0)
+ return 0;
+
+ if ((fp = net_netlink_open()) == NULL)
+ return -1;
+ if (!fgets(buf, sizeof(buf), fp)) {
+ fclose(fp);
+ return -1;
+ }
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ sscanf(buf, "%llx %d %d %x %d %d %llx %d",
+ &sk,
+ &prot, &pid, &groups, &rq, &wq, &cb, &rc);
+
+ netlink_show_one(f, prot, pid, groups, 0, 0, 0, rq, wq, sk, cb);
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+static bool vsock_type_skip(struct sockstat *s, struct filter *f)
+{
+ if (s->type == SOCK_STREAM && !(f->dbs & (1 << VSOCK_ST_DB)))
+ return true;
+ if (s->type == SOCK_DGRAM && !(f->dbs & (1 << VSOCK_DG_DB)))
+ return true;
+ return false;
+}
+
+static void vsock_addr_print(inet_prefix *a, __u32 port)
+{
+ char cid_str[sizeof("4294967295")];
+ char port_str[sizeof("4294967295")];
+ __u32 cid;
+
+ memcpy(&cid, a->data, sizeof(cid));
+
+ if (cid == ~(__u32)0)
+ snprintf(cid_str, sizeof(cid_str), "*");
+ else
+ snprintf(cid_str, sizeof(cid_str), "%u", cid);
+
+ if (port == ~(__u32)0)
+ snprintf(port_str, sizeof(port_str), "*");
+ else
+ snprintf(port_str, sizeof(port_str), "%u", port);
+
+ sock_addr_print(cid_str, ":", port_str, NULL);
+}
+
+static void vsock_stats_print(struct sockstat *s, struct filter *f)
+{
+ sock_state_print(s);
+
+ vsock_addr_print(&s->local, s->lport);
+ vsock_addr_print(&s->remote, s->rport);
+
+ proc_ctx_print(s);
+}
+
+static int vsock_show_sock(struct nlmsghdr *nlh, void *arg)
+{
+ struct filter *f = (struct filter *)arg;
+ struct vsock_diag_msg *r = NLMSG_DATA(nlh);
+ struct sockstat stat = {
+ .type = r->vdiag_type,
+ .lport = r->vdiag_src_port,
+ .rport = r->vdiag_dst_port,
+ .state = r->vdiag_state,
+ .ino = r->vdiag_ino,
+ };
+
+ vsock_set_inet_prefix(&stat.local, r->vdiag_src_cid);
+ vsock_set_inet_prefix(&stat.remote, r->vdiag_dst_cid);
+
+ if (vsock_type_skip(&stat, f))
+ return 0;
+
+ if (f->f && run_ssfilter(f->f, &stat) == 0)
+ return 0;
+
+ vsock_stats_print(&stat, f);
+
+ return 0;
+}
+
+static int vsock_show(struct filter *f)
+{
+ DIAG_REQUEST(req, struct vsock_diag_req r);
+
+ if (!filter_af_get(f, AF_VSOCK))
+ return 0;
+
+ req.r.sdiag_family = AF_VSOCK;
+ req.r.vdiag_states = f->states;
+
+ return handle_netlink_request(f, &req.nlh, sizeof(req), vsock_show_sock);
+}
+
+static void tipc_sock_addr_print(struct rtattr *net_addr, struct rtattr *id)
+{
+ uint32_t node = rta_getattr_u32(net_addr);
+ uint32_t identity = rta_getattr_u32(id);
+
+ SPRINT_BUF(addr) = {};
+ SPRINT_BUF(port) = {};
+
+ sprintf(addr, "%u", node);
+ sprintf(port, "%u", identity);
+ sock_addr_print(addr, ":", port, NULL);
+
+}
+
+static int tipc_show_sock(struct nlmsghdr *nlh, void *arg)
+{
+ struct rtattr *stat[TIPC_NLA_SOCK_STAT_MAX + 1] = {};
+ struct rtattr *attrs[TIPC_NLA_SOCK_MAX + 1] = {};
+ struct rtattr *con[TIPC_NLA_CON_MAX + 1] = {};
+ struct rtattr *info[TIPC_NLA_MAX + 1] = {};
+ struct rtattr *msg_ref;
+ struct sockstat ss = {};
+
+ parse_rtattr(info, TIPC_NLA_MAX, NLMSG_DATA(nlh),
+ NLMSG_PAYLOAD(nlh, 0));
+
+ if (!info[TIPC_NLA_SOCK])
+ return 0;
+
+ msg_ref = info[TIPC_NLA_SOCK];
+ parse_rtattr(attrs, TIPC_NLA_SOCK_MAX, RTA_DATA(msg_ref),
+ RTA_PAYLOAD(msg_ref));
+
+ msg_ref = attrs[TIPC_NLA_SOCK_STAT];
+ parse_rtattr(stat, TIPC_NLA_SOCK_STAT_MAX,
+ RTA_DATA(msg_ref), RTA_PAYLOAD(msg_ref));
+
+
+ ss.local.family = AF_TIPC;
+ ss.type = rta_getattr_u32(attrs[TIPC_NLA_SOCK_TYPE]);
+ ss.state = rta_getattr_u32(attrs[TIPC_NLA_SOCK_TIPC_STATE]);
+ ss.uid = rta_getattr_u32(attrs[TIPC_NLA_SOCK_UID]);
+ ss.ino = rta_getattr_u32(attrs[TIPC_NLA_SOCK_INO]);
+ ss.rq = rta_getattr_u32(stat[TIPC_NLA_SOCK_STAT_RCVQ]);
+ ss.wq = rta_getattr_u32(stat[TIPC_NLA_SOCK_STAT_SENDQ]);
+ ss.sk = rta_getattr_u64(attrs[TIPC_NLA_SOCK_COOKIE]);
+
+ sock_state_print (&ss);
+
+ tipc_sock_addr_print(attrs[TIPC_NLA_SOCK_ADDR],
+ attrs[TIPC_NLA_SOCK_REF]);
+
+ msg_ref = attrs[TIPC_NLA_SOCK_CON];
+ if (msg_ref) {
+ parse_rtattr(con, TIPC_NLA_CON_MAX,
+ RTA_DATA(msg_ref), RTA_PAYLOAD(msg_ref));
+
+ tipc_sock_addr_print(con[TIPC_NLA_CON_NODE],
+ con[TIPC_NLA_CON_SOCK]);
+ } else
+ sock_addr_print("", "-", "", NULL);
+
+ if (show_details)
+ sock_details_print(&ss);
+
+ proc_ctx_print(&ss);
+
+ if (show_tipcinfo) {
+ if (oneline)
+ out(" type:%s", stype_nameg[ss.type]);
+ else
+ out("\n type:%s", stype_nameg[ss.type]);
+ out(" cong:%s ",
+ stat[TIPC_NLA_SOCK_STAT_LINK_CONG] ? "link" :
+ stat[TIPC_NLA_SOCK_STAT_CONN_CONG] ? "conn" : "none");
+ out(" drop:%d ",
+ rta_getattr_u32(stat[TIPC_NLA_SOCK_STAT_DROP]));
+
+ if (attrs[TIPC_NLA_SOCK_HAS_PUBL])
+ out(" publ");
+
+ if (con[TIPC_NLA_CON_FLAG])
+ out(" via {%u,%u} ",
+ rta_getattr_u32(con[TIPC_NLA_CON_TYPE]),
+ rta_getattr_u32(con[TIPC_NLA_CON_INST]));
+ }
+
+ return 0;
+}
+
+static int tipc_show(struct filter *f)
+{
+ DIAG_REQUEST(req, struct tipc_sock_diag_req r);
+
+ memset(&req.r, 0, sizeof(req.r));
+ req.r.sdiag_family = AF_TIPC;
+ req.r.tidiag_states = f->states;
+
+ return handle_netlink_request(f, &req.nlh, sizeof(req), tipc_show_sock);
+}
+
+struct sock_diag_msg {
+ __u8 sdiag_family;
+};
+
+static int generic_show_sock(struct nlmsghdr *nlh, void *arg)
+{
+ struct sock_diag_msg *r = NLMSG_DATA(nlh);
+ struct inet_diag_arg inet_arg = { .f = arg, .protocol = IPPROTO_MAX };
+ int ret;
+
+ switch (r->sdiag_family) {
+ case AF_INET:
+ case AF_INET6:
+ inet_arg.rth = inet_arg.f->rth_for_killing;
+ ret = show_one_inet_sock(nlh, &inet_arg);
+ break;
+ case AF_UNIX:
+ ret = unix_show_sock(nlh, arg);
+ break;
+ case AF_PACKET:
+ ret = packet_show_sock(nlh, arg);
+ break;
+ case AF_NETLINK:
+ ret = netlink_show_sock(nlh, arg);
+ break;
+ case AF_VSOCK:
+ ret = vsock_show_sock(nlh, arg);
+ break;
+ case AF_XDP:
+ ret = xdp_show_sock(nlh, arg);
+ break;
+ default:
+ ret = -1;
+ }
+
+ render();
+
+ return ret;
+}
+
+static int handle_follow_request(struct filter *f)
+{
+ int ret = 0;
+ int groups = 0;
+ struct rtnl_handle rth, rth2;
+
+ if (f->families & FAMILY_MASK(AF_INET) && f->dbs & (1 << TCP_DB))
+ groups |= 1 << (SKNLGRP_INET_TCP_DESTROY - 1);
+ if (f->families & FAMILY_MASK(AF_INET) && f->dbs & (1 << UDP_DB))
+ groups |= 1 << (SKNLGRP_INET_UDP_DESTROY - 1);
+ if (f->families & FAMILY_MASK(AF_INET6) && f->dbs & (1 << TCP_DB))
+ groups |= 1 << (SKNLGRP_INET6_TCP_DESTROY - 1);
+ if (f->families & FAMILY_MASK(AF_INET6) && f->dbs & (1 << UDP_DB))
+ groups |= 1 << (SKNLGRP_INET6_UDP_DESTROY - 1);
+
+ if (groups == 0)
+ return -1;
+
+ if (rtnl_open_byproto(&rth, groups, NETLINK_SOCK_DIAG))
+ return -1;
+
+ rth.dump = 0;
+ rth.local.nl_pid = 0;
+
+ if (f->kill) {
+ if (rtnl_open_byproto(&rth2, groups, NETLINK_SOCK_DIAG)) {
+ rtnl_close(&rth);
+ return -1;
+ }
+ f->rth_for_killing = &rth2;
+ }
+
+ if (rtnl_dump_filter(&rth, generic_show_sock, f))
+ ret = -1;
+
+ rtnl_close(&rth);
+ if (f->rth_for_killing)
+ rtnl_close(f->rth_for_killing);
+ return ret;
+}
+
+static int get_snmp_int(char *proto, char *key, int *result)
+{
+ char buf[1024];
+ FILE *fp;
+ int protolen = strlen(proto);
+ int keylen = strlen(key);
+
+ *result = 0;
+
+ if ((fp = net_snmp_open()) == NULL)
+ return -1;
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ char *p = buf;
+ int pos = 0;
+
+ if (memcmp(buf, proto, protolen))
+ continue;
+ while ((p = strchr(p, ' ')) != NULL) {
+ pos++;
+ p++;
+ if (memcmp(p, key, keylen) == 0 &&
+ (p[keylen] == ' ' || p[keylen] == '\n'))
+ break;
+ }
+ if (fgets(buf, sizeof(buf), fp) == NULL)
+ break;
+ if (memcmp(buf, proto, protolen))
+ break;
+ p = buf;
+ while ((p = strchr(p, ' ')) != NULL) {
+ p++;
+ if (--pos == 0) {
+ sscanf(p, "%d", result);
+ fclose(fp);
+ return 0;
+ }
+ }
+ }
+
+ fclose(fp);
+ errno = ESRCH;
+ return -1;
+}
+
+
+/* Get stats from sockstat */
+
+struct ssummary {
+ int socks;
+ int tcp_mem;
+ int tcp_total;
+ int tcp_orphans;
+ int tcp_tws;
+ int tcp4_hashed;
+ int udp4;
+ int raw4;
+ int frag4;
+ int frag4_mem;
+ int tcp6_hashed;
+ int udp6;
+ int raw6;
+ int frag6;
+ int frag6_mem;
+};
+
+static void get_sockstat_line(char *line, struct ssummary *s)
+{
+ char id[256], rem[256];
+
+ if (sscanf(line, "%[^ ] %[^\n]\n", id, rem) != 2)
+ return;
+
+ if (strcmp(id, "sockets:") == 0)
+ sscanf(rem, "%*s%d", &s->socks);
+ else if (strcmp(id, "UDP:") == 0)
+ sscanf(rem, "%*s%d", &s->udp4);
+ else if (strcmp(id, "UDP6:") == 0)
+ sscanf(rem, "%*s%d", &s->udp6);
+ else if (strcmp(id, "RAW:") == 0)
+ sscanf(rem, "%*s%d", &s->raw4);
+ else if (strcmp(id, "RAW6:") == 0)
+ sscanf(rem, "%*s%d", &s->raw6);
+ else if (strcmp(id, "TCP6:") == 0)
+ sscanf(rem, "%*s%d", &s->tcp6_hashed);
+ else if (strcmp(id, "FRAG:") == 0)
+ sscanf(rem, "%*s%d%*s%d", &s->frag4, &s->frag4_mem);
+ else if (strcmp(id, "FRAG6:") == 0)
+ sscanf(rem, "%*s%d%*s%d", &s->frag6, &s->frag6_mem);
+ else if (strcmp(id, "TCP:") == 0)
+ sscanf(rem, "%*s%d%*s%d%*s%d%*s%d%*s%d",
+ &s->tcp4_hashed,
+ &s->tcp_orphans, &s->tcp_tws, &s->tcp_total, &s->tcp_mem);
+}
+
+static int get_sockstat(struct ssummary *s)
+{
+ char buf[256];
+ FILE *fp;
+
+ memset(s, 0, sizeof(*s));
+
+ if ((fp = net_sockstat_open()) == NULL)
+ return -1;
+ while (fgets(buf, sizeof(buf), fp) != NULL)
+ get_sockstat_line(buf, s);
+ fclose(fp);
+
+ if ((fp = net_sockstat6_open()) == NULL)
+ return 0;
+ while (fgets(buf, sizeof(buf), fp) != NULL)
+ get_sockstat_line(buf, s);
+ fclose(fp);
+
+ return 0;
+}
+
+static int print_summary(void)
+{
+ struct ssummary s;
+ int tcp_estab;
+
+ if (get_sockstat(&s) < 0)
+ perror("ss: get_sockstat");
+ if (get_snmp_int("Tcp:", "CurrEstab", &tcp_estab) < 0)
+ perror("ss: get_snmpstat");
+
+ printf("Total: %d\n", s.socks);
+
+ printf("TCP: %d (estab %d, closed %d, orphaned %d, timewait %d)\n",
+ s.tcp_total + s.tcp_tws, tcp_estab,
+ s.tcp_total - (s.tcp4_hashed + s.tcp6_hashed - s.tcp_tws),
+ s.tcp_orphans, s.tcp_tws);
+
+ printf("\n");
+ printf("Transport Total IP IPv6\n");
+ printf("RAW %-9d %-9d %-9d\n", s.raw4+s.raw6, s.raw4, s.raw6);
+ printf("UDP %-9d %-9d %-9d\n", s.udp4+s.udp6, s.udp4, s.udp6);
+ printf("TCP %-9d %-9d %-9d\n", s.tcp4_hashed+s.tcp6_hashed, s.tcp4_hashed, s.tcp6_hashed);
+ printf("INET %-9d %-9d %-9d\n",
+ s.raw4+s.udp4+s.tcp4_hashed+
+ s.raw6+s.udp6+s.tcp6_hashed,
+ s.raw4+s.udp4+s.tcp4_hashed,
+ s.raw6+s.udp6+s.tcp6_hashed);
+ printf("FRAG %-9d %-9d %-9d\n", s.frag4+s.frag6, s.frag4, s.frag6);
+
+ printf("\n");
+
+ return 0;
+}
+
+static void _usage(FILE *dest)
+{
+ fprintf(dest,
+"Usage: ss [ OPTIONS ]\n"
+" ss [ OPTIONS ] [ FILTER ]\n"
+" -h, --help this message\n"
+" -V, --version output version information\n"
+" -n, --numeric don't resolve service names\n"
+" -r, --resolve resolve host names\n"
+" -a, --all display all sockets\n"
+" -l, --listening display listening sockets\n"
+" -o, --options show timer information\n"
+" -e, --extended show detailed socket information\n"
+" -m, --memory show socket memory usage\n"
+" -p, --processes show process using socket\n"
+" -i, --info show internal TCP information\n"
+" --tipcinfo show internal tipc socket information\n"
+" -s, --summary show socket usage summary\n"
+" --tos show tos and priority information\n"
+" -b, --bpf show bpf filter socket information\n"
+" -E, --events continually display sockets as they are destroyed\n"
+" -Z, --context display process SELinux security contexts\n"
+" -z, --contexts display process and socket SELinux security contexts\n"
+" -N, --net switch to the specified network namespace name\n"
+"\n"
+" -4, --ipv4 display only IP version 4 sockets\n"
+" -6, --ipv6 display only IP version 6 sockets\n"
+" -0, --packet display PACKET sockets\n"
+" -t, --tcp display only TCP sockets\n"
+" -S, --sctp display only SCTP sockets\n"
+" -u, --udp display only UDP sockets\n"
+" -d, --dccp display only DCCP sockets\n"
+" -w, --raw display only RAW sockets\n"
+" -x, --unix display only Unix domain sockets\n"
+" --tipc display only TIPC sockets\n"
+" --vsock display only vsock sockets\n"
+" -f, --family=FAMILY display sockets of type FAMILY\n"
+" FAMILY := {inet|inet6|link|unix|netlink|vsock|tipc|xdp|help}\n"
+"\n"
+" -K, --kill forcibly close sockets, display what was closed\n"
+" -H, --no-header Suppress header line\n"
+" -O, --oneline socket's data printed on a single line\n"
+"\n"
+" -A, --query=QUERY, --socket=QUERY\n"
+" QUERY := {all|inet|tcp|udp|raw|unix|unix_dgram|unix_stream|unix_seqpacket|packet|netlink|vsock_stream|vsock_dgram|tipc}[,QUERY]\n"
+"\n"
+" -D, --diag=FILE Dump raw information about TCP sockets to FILE\n"
+" -F, --filter=FILE read filter information from FILE\n"
+" FILTER := [ state STATE-FILTER ] [ EXPRESSION ]\n"
+" STATE-FILTER := {all|connected|synchronized|bucket|big|TCP-STATES}\n"
+" TCP-STATES := {established|syn-sent|syn-recv|fin-wait-{1,2}|time-wait|closed|close-wait|last-ack|listening|closing}\n"
+" connected := {established|syn-sent|syn-recv|fin-wait-{1,2}|time-wait|close-wait|last-ack|closing}\n"
+" synchronized := {established|syn-recv|fin-wait-{1,2}|time-wait|close-wait|last-ack|closing}\n"
+" bucket := {syn-recv|time-wait}\n"
+" big := {established|syn-sent|fin-wait-{1,2}|closed|close-wait|last-ack|listening|closing}\n"
+ );
+}
+
+static void help(void) __attribute__((noreturn));
+static void help(void)
+{
+ _usage(stdout);
+ exit(0);
+}
+
+static void usage(void) __attribute__((noreturn));
+static void usage(void)
+{
+ _usage(stderr);
+ exit(-1);
+}
+
+
+static int scan_state(const char *state)
+{
+ static const char * const sstate_namel[] = {
+ "UNKNOWN",
+ [SS_ESTABLISHED] = "established",
+ [SS_SYN_SENT] = "syn-sent",
+ [SS_SYN_RECV] = "syn-recv",
+ [SS_FIN_WAIT1] = "fin-wait-1",
+ [SS_FIN_WAIT2] = "fin-wait-2",
+ [SS_TIME_WAIT] = "time-wait",
+ [SS_CLOSE] = "unconnected",
+ [SS_CLOSE_WAIT] = "close-wait",
+ [SS_LAST_ACK] = "last-ack",
+ [SS_LISTEN] = "listening",
+ [SS_CLOSING] = "closing",
+ };
+ int i;
+
+ if (strcasecmp(state, "close") == 0 ||
+ strcasecmp(state, "closed") == 0)
+ return (1<<SS_CLOSE);
+ if (strcasecmp(state, "syn-rcv") == 0)
+ return (1<<SS_SYN_RECV);
+ if (strcasecmp(state, "established") == 0)
+ return (1<<SS_ESTABLISHED);
+ if (strcasecmp(state, "all") == 0)
+ return SS_ALL;
+ if (strcasecmp(state, "connected") == 0)
+ return SS_ALL & ~((1<<SS_CLOSE)|(1<<SS_LISTEN));
+ if (strcasecmp(state, "synchronized") == 0)
+ return SS_ALL & ~((1<<SS_CLOSE)|(1<<SS_LISTEN)|(1<<SS_SYN_SENT));
+ if (strcasecmp(state, "bucket") == 0)
+ return (1<<SS_SYN_RECV)|(1<<SS_TIME_WAIT);
+ if (strcasecmp(state, "big") == 0)
+ return SS_ALL & ~((1<<SS_SYN_RECV)|(1<<SS_TIME_WAIT));
+ for (i = 0; i < SS_MAX; i++) {
+ if (strcasecmp(state, sstate_namel[i]) == 0)
+ return (1<<i);
+ }
+
+ fprintf(stderr, "ss: wrong state name: %s\n", state);
+ exit(-1);
+}
+
+/* Values 'v' and 'V' are already used so a non-character is used */
+#define OPT_VSOCK 256
+
+/* Values of 't' are already used so a non-character is used */
+#define OPT_TIPCSOCK 257
+#define OPT_TIPCINFO 258
+
+#define OPT_TOS 259
+
+/* Values of 'x' are already used so a non-character is used */
+#define OPT_XDPSOCK 260
+
+static const struct option long_opts[] = {
+ { "numeric", 0, 0, 'n' },
+ { "resolve", 0, 0, 'r' },
+ { "options", 0, 0, 'o' },
+ { "extended", 0, 0, 'e' },
+ { "memory", 0, 0, 'm' },
+ { "info", 0, 0, 'i' },
+ { "processes", 0, 0, 'p' },
+ { "bpf", 0, 0, 'b' },
+ { "events", 0, 0, 'E' },
+ { "dccp", 0, 0, 'd' },
+ { "tcp", 0, 0, 't' },
+ { "sctp", 0, 0, 'S' },
+ { "udp", 0, 0, 'u' },
+ { "raw", 0, 0, 'w' },
+ { "unix", 0, 0, 'x' },
+ { "tipc", 0, 0, OPT_TIPCSOCK},
+ { "vsock", 0, 0, OPT_VSOCK },
+ { "all", 0, 0, 'a' },
+ { "listening", 0, 0, 'l' },
+ { "ipv4", 0, 0, '4' },
+ { "ipv6", 0, 0, '6' },
+ { "packet", 0, 0, '0' },
+ { "family", 1, 0, 'f' },
+ { "socket", 1, 0, 'A' },
+ { "query", 1, 0, 'A' },
+ { "summary", 0, 0, 's' },
+ { "diag", 1, 0, 'D' },
+ { "filter", 1, 0, 'F' },
+ { "version", 0, 0, 'V' },
+ { "help", 0, 0, 'h' },
+ { "context", 0, 0, 'Z' },
+ { "contexts", 0, 0, 'z' },
+ { "net", 1, 0, 'N' },
+ { "tipcinfo", 0, 0, OPT_TIPCINFO},
+ { "tos", 0, 0, OPT_TOS },
+ { "kill", 0, 0, 'K' },
+ { "no-header", 0, 0, 'H' },
+ { "xdp", 0, 0, OPT_XDPSOCK},
+ { "oneline", 0, 0, 'O' },
+ { 0 }
+
+};
+
+int main(int argc, char *argv[])
+{
+ int saw_states = 0;
+ int saw_query = 0;
+ int do_summary = 0;
+ const char *dump_tcpdiag = NULL;
+ FILE *filter_fp = NULL;
+ int ch;
+ int state_filter = 0;
+
+ while ((ch = getopt_long(argc, argv,
+ "dhaletuwxnro460spbEf:miA:D:F:vVzZN:KHSO",
+ long_opts, NULL)) != EOF) {
+ switch (ch) {
+ case 'n':
+ numeric = 1;
+ break;
+ case 'r':
+ resolve_hosts = 1;
+ break;
+ case 'o':
+ show_options = 1;
+ break;
+ case 'e':
+ show_options = 1;
+ show_details++;
+ break;
+ case 'm':
+ show_mem = 1;
+ break;
+ case 'i':
+ show_tcpinfo = 1;
+ break;
+ case 'p':
+ show_users++;
+ user_ent_hash_build();
+ break;
+ case 'b':
+ show_options = 1;
+ show_bpf++;
+ break;
+ case 'E':
+ follow_events = 1;
+ break;
+ case 'd':
+ filter_db_set(¤t_filter, DCCP_DB, true);
+ break;
+ case 't':
+ filter_db_set(¤t_filter, TCP_DB, true);
+ break;
+ case 'S':
+ filter_db_set(¤t_filter, SCTP_DB, true);
+ break;
+ case 'u':
+ filter_db_set(¤t_filter, UDP_DB, true);
+ break;
+ case 'w':
+ filter_db_set(¤t_filter, RAW_DB, true);
+ break;
+ case 'x':
+ filter_af_set(¤t_filter, AF_UNIX);
+ break;
+ case OPT_VSOCK:
+ filter_af_set(¤t_filter, AF_VSOCK);
+ break;
+ case OPT_TIPCSOCK:
+ filter_af_set(¤t_filter, AF_TIPC);
+ break;
+ case 'a':
+ state_filter = SS_ALL;
+ break;
+ case 'l':
+ state_filter = (1 << SS_LISTEN) | (1 << SS_CLOSE);
+ break;
+ case '4':
+ filter_af_set(¤t_filter, AF_INET);
+ break;
+ case '6':
+ filter_af_set(¤t_filter, AF_INET6);
+ break;
+ case '0':
+ filter_af_set(¤t_filter, AF_PACKET);
+ break;
+ case OPT_XDPSOCK:
+ filter_af_set(¤t_filter, AF_XDP);
+ break;
+ case 'f':
+ if (strcmp(optarg, "inet") == 0)
+ filter_af_set(¤t_filter, AF_INET);
+ else if (strcmp(optarg, "inet6") == 0)
+ filter_af_set(¤t_filter, AF_INET6);
+ else if (strcmp(optarg, "link") == 0)
+ filter_af_set(¤t_filter, AF_PACKET);
+ else if (strcmp(optarg, "unix") == 0)
+ filter_af_set(¤t_filter, AF_UNIX);
+ else if (strcmp(optarg, "netlink") == 0)
+ filter_af_set(¤t_filter, AF_NETLINK);
+ else if (strcmp(optarg, "tipc") == 0)
+ filter_af_set(¤t_filter, AF_TIPC);
+ else if (strcmp(optarg, "vsock") == 0)
+ filter_af_set(¤t_filter, AF_VSOCK);
+ else if (strcmp(optarg, "xdp") == 0)
+ filter_af_set(¤t_filter, AF_XDP);
+ else if (strcmp(optarg, "help") == 0)
+ help();
+ else {
+ fprintf(stderr, "ss: \"%s\" is invalid family\n",
+ optarg);
+ usage();
+ }
+ break;
+ case 'A':
+ {
+ char *p, *p1;
+
+ if (!saw_query) {
+ current_filter.dbs = 0;
+ state_filter = state_filter ?
+ state_filter : SS_CONN;
+ saw_query = 1;
+ do_default = 0;
+ }
+ p = p1 = optarg;
+ do {
+ if ((p1 = strchr(p, ',')) != NULL)
+ *p1 = 0;
+ if (filter_db_parse(¤t_filter, p)) {
+ fprintf(stderr, "ss: \"%s\" is illegal socket table id\n", p);
+ usage();
+ }
+ p = p1 + 1;
+ } while (p1);
+ break;
+ }
+ case 's':
+ do_summary = 1;
+ break;
+ case 'D':
+ dump_tcpdiag = optarg;
+ break;
+ case 'F':
+ if (filter_fp) {
+ fprintf(stderr, "More than one filter file\n");
+ exit(-1);
+ }
+ if (optarg[0] == '-')
+ filter_fp = stdin;
+ else
+ filter_fp = fopen(optarg, "r");
+ if (!filter_fp) {
+ perror("fopen filter file");
+ exit(-1);
+ }
+ break;
+ case 'v':
+ case 'V':
+ printf("ss utility, iproute2-ss%s\n", SNAPSHOT);
+ exit(0);
+ case 'z':
+ show_sock_ctx++;
+ /* fall through */
+ case 'Z':
+ if (is_selinux_enabled() <= 0) {
+ fprintf(stderr, "ss: SELinux is not enabled.\n");
+ exit(1);
+ }
+ show_proc_ctx++;
+ user_ent_hash_build();
+ break;
+ case 'N':
+ if (netns_switch(optarg))
+ exit(1);
+ break;
+ case OPT_TIPCINFO:
+ show_tipcinfo = 1;
+ break;
+ case OPT_TOS:
+ show_tos = 1;
+ break;
+ case 'K':
+ current_filter.kill = 1;
+ break;
+ case 'H':
+ show_header = 0;
+ break;
+ case 'O':
+ oneline = 1;
+ break;
+ case 'h':
+ help();
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (do_summary) {
+ print_summary();
+ if (do_default && argc == 0)
+ exit(0);
+ }
+
+ while (argc > 0) {
+ if (strcmp(*argv, "state") == 0) {
+ NEXT_ARG();
+ if (!saw_states)
+ state_filter = 0;
+ state_filter |= scan_state(*argv);
+ saw_states = 1;
+ } else if (strcmp(*argv, "exclude") == 0 ||
+ strcmp(*argv, "excl") == 0) {
+ NEXT_ARG();
+ if (!saw_states)
+ state_filter = SS_ALL;
+ state_filter &= ~scan_state(*argv);
+ saw_states = 1;
+ } else {
+ break;
+ }
+ argc--; argv++;
+ }
+
+ if (do_default) {
+ state_filter = state_filter ? state_filter : SS_CONN;
+ filter_db_parse(¤t_filter, "all");
+ }
+
+ filter_states_set(¤t_filter, state_filter);
+ filter_merge_defaults(¤t_filter);
+
+ if (!numeric && resolve_hosts &&
+ (current_filter.dbs & (UNIX_DBM|INET_L4_DBM)))
+ init_service_resolver();
+
+ if (current_filter.dbs == 0) {
+ fprintf(stderr, "ss: no socket tables to show with such filter.\n");
+ exit(0);
+ }
+ if (current_filter.families == 0) {
+ fprintf(stderr, "ss: no families to show with such filter.\n");
+ exit(0);
+ }
+ if (current_filter.states == 0) {
+ fprintf(stderr, "ss: no socket states to show with such filter.\n");
+ exit(0);
+ }
+
+ if (dump_tcpdiag) {
+ FILE *dump_fp = stdout;
+
+ if (!(current_filter.dbs & (1<<TCP_DB))) {
+ fprintf(stderr, "ss: tcpdiag dump requested and no tcp in filter.\n");
+ exit(0);
+ }
+ if (dump_tcpdiag[0] != '-') {
+ dump_fp = fopen(dump_tcpdiag, "w");
+ if (!dump_tcpdiag) {
+ perror("fopen dump file");
+ exit(-1);
+ }
+ }
+ inet_show_netlink(¤t_filter, dump_fp, IPPROTO_TCP);
+ fflush(dump_fp);
+ exit(0);
+ }
+
+ if (ssfilter_parse(¤t_filter.f, argc, argv, filter_fp))
+ usage();
+
+ if (!(current_filter.dbs & (current_filter.dbs - 1)))
+ columns[COL_NETID].disabled = 1;
+
+ if (!(current_filter.states & (current_filter.states - 1)))
+ columns[COL_STATE].disabled = 1;
+
+ if (show_header)
+ print_header();
+
+ fflush(stdout);
+
+ if (follow_events)
+ exit(handle_follow_request(¤t_filter));
+
+ if (current_filter.dbs & (1<<NETLINK_DB))
+ netlink_show(¤t_filter);
+ if (current_filter.dbs & PACKET_DBM)
+ packet_show(¤t_filter);
+ if (current_filter.dbs & UNIX_DBM)
+ unix_show(¤t_filter);
+ if (current_filter.dbs & (1<<RAW_DB))
+ raw_show(¤t_filter);
+ if (current_filter.dbs & (1<<UDP_DB))
+ udp_show(¤t_filter);
+ if (current_filter.dbs & (1<<TCP_DB))
+ tcp_show(¤t_filter);
+ if (current_filter.dbs & (1<<DCCP_DB))
+ dccp_show(¤t_filter);
+ if (current_filter.dbs & (1<<SCTP_DB))
+ sctp_show(¤t_filter);
+ if (current_filter.dbs & VSOCK_DBM)
+ vsock_show(¤t_filter);
+ if (current_filter.dbs & (1<<TIPC_DB))
+ tipc_show(¤t_filter);
+ if (current_filter.dbs & (1<<XDP_DB))
+ xdp_show(¤t_filter);
+
+ if (show_users || show_proc_ctx || show_sock_ctx)
+ user_ent_destroy();
+
+ render();
+
+ return 0;
+}
diff --git a/misc/ssfilter.h b/misc/ssfilter.h
new file mode 100644
index 0000000..f5b0bc8
--- /dev/null
+++ b/misc/ssfilter.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#define SSF_DCOND 0
+#define SSF_SCOND 1
+#define SSF_OR 2
+#define SSF_AND 3
+#define SSF_NOT 4
+#define SSF_D_GE 5
+#define SSF_D_LE 6
+#define SSF_S_GE 7
+#define SSF_S_LE 8
+#define SSF_S_AUTO 9
+#define SSF_DEVCOND 10
+#define SSF_MARKMASK 11
+
+#include <stdbool.h>
+
+struct ssfilter
+{
+ int type;
+ struct ssfilter *post;
+ struct ssfilter *pred;
+};
+
+int ssfilter_parse(struct ssfilter **f, int argc, char **argv, FILE *fp);
+void *parse_hostcond(char *addr, bool is_port);
+void *parse_devcond(char *name);
+void *parse_markmask(const char *markmask);
diff --git a/misc/ssfilter.y b/misc/ssfilter.y
new file mode 100644
index 0000000..a901ae7
--- /dev/null
+++ b/misc/ssfilter.y
@@ -0,0 +1,341 @@
+%{
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <string.h>
+#include "ssfilter.h"
+
+typedef struct ssfilter * ssfilter_t;
+
+#define YYSTYPE ssfilter_t
+
+static struct ssfilter * alloc_node(int type, void *pred)
+{
+ struct ssfilter *n = malloc(sizeof(*n));
+ if (n == NULL)
+ abort();
+ n->type = type;
+ n->pred = pred;
+ n->post = NULL;
+ return n;
+}
+
+static char **yy_argv;
+static int yy_argc;
+static FILE *yy_fp;
+static ssfilter_t *yy_ret;
+static int tok_type = -1;
+
+static int yylex(void);
+
+static void yyerror(char *s)
+{
+ fprintf(stderr, "ss: bison bellows (while parsing filter): \"%s!\"", s);
+}
+
+%}
+
+%token HOSTCOND DCOND SCOND DPORT SPORT LEQ GEQ NEQ AUTOBOUND DEVCOND DEVNAME MARKMASK FWMARK
+%left '|'
+%left '&'
+%nonassoc '!'
+
+%%
+applet: exprlist
+ {
+ *yy_ret = $1;
+ $$ = $1;
+ }
+ | null
+ ;
+
+null: /* NOTHING */ { $$ = NULL; }
+ ;
+
+exprlist: expr
+ | exprlist '|' expr
+ {
+ $$ = alloc_node(SSF_OR, $1);
+ $$->post = $3;
+ }
+ | exprlist '&' expr
+ {
+ $$ = alloc_node(SSF_AND, $1);
+ $$->post = $3;
+ }
+ | exprlist expr
+ {
+ $$ = alloc_node(SSF_AND, $1);
+ $$->post = $2;
+ }
+ ;
+
+eq: '='
+ | /* nothing */
+ ;
+
+expr: '(' exprlist ')'
+ {
+ $$ = $2;
+ }
+ | '!' expr
+ {
+ $$ = alloc_node(SSF_NOT, $2);
+ }
+ | DCOND eq HOSTCOND
+ {
+ $$ = alloc_node(SSF_DCOND, $3);
+ }
+ | SCOND eq HOSTCOND
+ {
+ $$ = alloc_node(SSF_SCOND, $3);
+ }
+ | DPORT GEQ HOSTCOND
+ {
+ $$ = alloc_node(SSF_D_GE, $3);
+ }
+ | DPORT LEQ HOSTCOND
+ {
+ $$ = alloc_node(SSF_D_LE, $3);
+ }
+ | DPORT '>' HOSTCOND
+ {
+ $$ = alloc_node(SSF_NOT, alloc_node(SSF_D_LE, $3));
+ }
+ | DPORT '<' HOSTCOND
+ {
+ $$ = alloc_node(SSF_NOT, alloc_node(SSF_D_GE, $3));
+ }
+ | DPORT eq HOSTCOND
+ {
+ $$ = alloc_node(SSF_DCOND, $3);
+ }
+ | DPORT NEQ HOSTCOND
+ {
+ $$ = alloc_node(SSF_NOT, alloc_node(SSF_DCOND, $3));
+ }
+
+ | SPORT GEQ HOSTCOND
+ {
+ $$ = alloc_node(SSF_S_GE, $3);
+ }
+ | SPORT LEQ HOSTCOND
+ {
+ $$ = alloc_node(SSF_S_LE, $3);
+ }
+ | SPORT '>' HOSTCOND
+ {
+ $$ = alloc_node(SSF_NOT, alloc_node(SSF_S_LE, $3));
+ }
+ | SPORT '<' HOSTCOND
+ {
+ $$ = alloc_node(SSF_NOT, alloc_node(SSF_S_GE, $3));
+ }
+ | SPORT eq HOSTCOND
+ {
+ $$ = alloc_node(SSF_SCOND, $3);
+ }
+ | SPORT NEQ HOSTCOND
+ {
+ $$ = alloc_node(SSF_NOT, alloc_node(SSF_SCOND, $3));
+ }
+ | DEVNAME eq DEVCOND
+ {
+ $$ = alloc_node(SSF_DEVCOND, $3);
+ }
+ | DEVNAME NEQ DEVCOND
+ {
+ $$ = alloc_node(SSF_NOT, alloc_node(SSF_DEVCOND, $3));
+ }
+ | FWMARK eq MARKMASK
+ {
+ $$ = alloc_node(SSF_MARKMASK, $3);
+ }
+ | FWMARK NEQ MARKMASK
+ {
+ $$ = alloc_node(SSF_NOT, alloc_node(SSF_MARKMASK, $3));
+ }
+ | AUTOBOUND
+ {
+ $$ = alloc_node(SSF_S_AUTO, NULL);
+ }
+;
+%%
+
+static char *get_token_from_line(char **ptr)
+{
+ char *tok, *cp = *ptr;
+
+ while (*cp == ' ' || *cp == '\t') cp++;
+
+ if (*cp == 0) {
+ *ptr = cp;
+ return NULL;
+ }
+
+ tok = cp;
+
+ while (*cp != 0 && *cp != ' ' && *cp != '\t') {
+ /* Backslash escapes everything. */
+ if (*cp == '\\') {
+ char *tp;
+ for (tp = cp; tp != tok; tp--)
+ *tp = *(tp-1);
+ cp++;
+ tok++;
+ if (*cp == 0)
+ break;
+ }
+ cp++;
+ }
+ if (*cp)
+ *cp++ = 0;
+ *ptr = cp;
+ return tok;
+}
+
+int yylex(void)
+{
+ static char argbuf[1024];
+ static char *tokptr = argbuf;
+ static int argc;
+ char *curtok;
+
+ do {
+ while (*tokptr == 0) {
+ tokptr = NULL;
+ if (argc < yy_argc) {
+ tokptr = yy_argv[argc];
+ argc++;
+ } else if (yy_fp) {
+ while (tokptr == NULL) {
+ size_t len;
+
+ if (fgets(argbuf, sizeof(argbuf), yy_fp) == NULL)
+ return 0;
+
+ len = strnlen(argbuf, sizeof(argbuf));
+ if (len == 0) {
+ fprintf(stderr, "Invalid line\n");
+ exit(-1);
+ }
+
+ if (len >= sizeof(argbuf) - 1) {
+ fprintf(stderr, "Too long line in filter\n");
+ exit(-1);
+ }
+ if (argbuf[len - 1] == '\n')
+ argbuf[len-1] = 0;
+ if (argbuf[0] == '#' || argbuf[0] == '0')
+ continue;
+ tokptr = argbuf;
+ }
+ } else {
+ return 0;
+ }
+ }
+ } while ((curtok = get_token_from_line(&tokptr)) == NULL);
+
+ if (strcmp(curtok, "!") == 0 ||
+ strcmp(curtok, "not") == 0)
+ return '!';
+ if (strcmp(curtok, "&") == 0 ||
+ strcmp(curtok, "&&") == 0 ||
+ strcmp(curtok, "and") == 0)
+ return '&';
+ if (strcmp(curtok, "|") == 0 ||
+ strcmp(curtok, "||") == 0 ||
+ strcmp(curtok, "or") == 0)
+ return '|';
+ if (strcmp(curtok, "(") == 0)
+ return '(';
+ if (strcmp(curtok, ")") == 0)
+ return ')';
+ if (strcmp(curtok, "dst") == 0) {
+ tok_type = DCOND;
+ return DCOND;
+ }
+ if (strcmp(curtok, "src") == 0) {
+ tok_type = SCOND;
+ return SCOND;
+ }
+ if (strcmp(curtok, "dport") == 0) {
+ tok_type = DPORT;
+ return DPORT;
+ }
+ if (strcmp(curtok, "sport") == 0) {
+ tok_type = SPORT;
+ return SPORT;
+ }
+ if (strcmp(curtok, "dev") == 0) {
+ tok_type = DEVNAME;
+ return DEVNAME;
+ }
+ if (strcmp(curtok, "fwmark") == 0) {
+ tok_type = FWMARK;
+ return FWMARK;
+ }
+ if (strcmp(curtok, ">=") == 0 ||
+ strcmp(curtok, "ge") == 0 ||
+ strcmp(curtok, "geq") == 0)
+ return GEQ;
+ if (strcmp(curtok, "<=") == 0 ||
+ strcmp(curtok, "le") == 0 ||
+ strcmp(curtok, "leq") == 0)
+ return LEQ;
+ if (strcmp(curtok, "!=") == 0 ||
+ strcmp(curtok, "ne") == 0 ||
+ strcmp(curtok, "neq") == 0)
+ return NEQ;
+ if (strcmp(curtok, "=") == 0 ||
+ strcmp(curtok, "==") == 0 ||
+ strcmp(curtok, "eq") == 0)
+ return '=';
+ if (strcmp(curtok, ">") == 0 ||
+ strcmp(curtok, "gt") == 0)
+ return '>';
+ if (strcmp(curtok, "<") == 0 ||
+ strcmp(curtok, "lt") == 0)
+ return '<';
+ if (strcmp(curtok, "autobound") == 0) {
+ tok_type = AUTOBOUND;
+ return AUTOBOUND;
+ }
+ if (tok_type == DEVNAME) {
+ yylval = (void*)parse_devcond(curtok);
+ if (yylval == NULL) {
+ fprintf(stderr, "Cannot parse device.\n");
+ exit(1);
+ }
+ return DEVCOND;
+ }
+ if (tok_type == FWMARK) {
+ yylval = (void*)parse_markmask(curtok);
+ if (yylval == NULL) {
+ fprintf(stderr, "Cannot parse mark %s.\n", curtok);
+ exit(1);
+ }
+ return MARKMASK;
+ }
+ yylval = (void*)parse_hostcond(curtok, tok_type == SPORT || tok_type == DPORT);
+ if (yylval == NULL) {
+ fprintf(stderr, "Cannot parse dst/src address.\n");
+ exit(1);
+ }
+ return HOSTCOND;
+}
+
+int ssfilter_parse(struct ssfilter **f, int argc, char **argv, FILE *fp)
+{
+ yy_argc = argc;
+ yy_argv = argv;
+ yy_fp = fp;
+ yy_ret = f;
+
+ if (yyparse()) {
+ fprintf(stderr, " Sorry.\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/netem/.gitignore b/netem/.gitignore
new file mode 100644
index 0000000..e36f396
--- /dev/null
+++ b/netem/.gitignore
@@ -0,0 +1,5 @@
+*.dist
+maketable
+normal
+pareto
+paretonormal
diff --git a/netem/Makefile b/netem/Makefile
new file mode 100644
index 0000000..ba4c5a7
--- /dev/null
+++ b/netem/Makefile
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../config.mk
+
+DISTGEN = maketable normal pareto paretonormal
+DISTDATA = normal.dist pareto.dist paretonormal.dist experimental.dist
+
+HOSTCC ?= $(CC)
+CCOPTS = $(CBUILD_CFLAGS)
+LDLIBS += -lm
+
+all: $(DISTGEN) $(DISTDATA)
+
+$(DISTGEN):
+ $(HOSTCC) $(CCOPTS) -I../include -o $@ $@.c -lm
+
+%.dist: %
+ ./$* > $@
+
+experimental.dist: maketable experimental.dat
+ ./maketable experimental.dat > experimental.dist
+
+stats: stats.c
+ $(HOSTCC) $(CCOPTS) -I../include -o $@ $@.c -lm
+
+install: all
+ mkdir -p $(DESTDIR)$(LIBDIR)/tc
+ for i in $(DISTDATA); \
+ do install -m 644 $$i $(DESTDIR)$(LIBDIR)/tc; \
+ done
+
+clean:
+ rm -f $(DISTDATA) $(DISTGEN)
diff --git a/netem/README.distribution b/netem/README.distribution
new file mode 100644
index 0000000..6d52785
--- /dev/null
+++ b/netem/README.distribution
@@ -0,0 +1,97 @@
+Notes about distribution tables from Nistnet
+-------------------------------------------------------------------------------
+I. About the distribution tables
+
+The table used for "synthesizing" the distribution is essentially a scaled,
+translated, inverse to the cumulative distribution function.
+
+Here's how to think about it: Let F() be the cumulative distribution
+function for a probability distribution X. We'll assume we've scaled
+things so that X has mean 0 and standard deviation 1, though that's not
+so important here. Then:
+
+ F(x) = P(X <= x) = \int_{-inf}^x f
+
+where f is the probability density function.
+
+F is monotonically increasing, so has an inverse function G, with range
+0 to 1. Here, G(t) = the x such that P(X <= x) = t. (In general, G may
+have singularities if X has point masses, i.e., points x such that
+P(X = x) > 0.)
+
+Now we create a tabular representation of G as follows: Choose some table
+size N, and for the ith entry, put in G(i/N). Let's call this table T.
+
+The claim now is, I can create a (discrete) random variable Y whose
+distribution has the same approximate "shape" as X, simply by letting
+Y = T(U), where U is a discrete uniform random variable with range 1 to N.
+To see this, it's enough to show that Y's cumulative distribution function,
+(let's call it H), is a discrete approximation to F. But
+
+ H(x) = P(Y <= x)
+ = (# of entries in T <= x) / N -- as Y chosen uniformly from T
+ = i/N, where i is the largest integer such that G(i/N) <= x
+ = i/N, where i is the largest integer such that i/N <= F(x)
+ -- since G and F are inverse functions (and F is
+ increasing)
+ = floor(N*F(x))/N
+
+as desired.
+
+II. How to create distribution tables (in theory)
+
+How can we create this table in practice? In some cases, F may have a
+simple expression which allows evaluating its inverse directly. The
+pareto distribution is one example of this. In other cases, and
+especially for matching an experimentally observed distribution, it's
+easiest simply to create a table for F and "invert" it. Here, we give
+a concrete example, namely how the new "experimental" distribution was
+created.
+
+1. Collect enough data points to characterize the distribution. Here, I
+collected 25,000 "ping" roundtrip times to a "distant" point (time.nist.gov).
+That's far more data than is really necessary, but it was fairly painless to
+collect it, so...
+
+2. Normalize the data so that it has mean 0 and standard deviation 1.
+
+3. Determine the cumulative distribution. The code I wrote creates a table
+covering the range -10 to +10, with granularity .00005. Obviously, this
+is absurdly over-precise, but since it's a one-time only computation, I
+figured it hardly mattered.
+
+4. Invert the table: for each table entry F(x) = y, make the y*TABLESIZE
+(here, 4096) entry be x*TABLEFACTOR (here, 8192). This creates a table
+for the ("normalized") inverse of size TABLESIZE, covering its domain 0
+to 1 with granularity 1/TABLESIZE. Note that even with the granularity
+used in creating the table for F, it's possible not all the entries in
+the table for G will be filled in. So, make a pass through the
+inverse's table, filling in any missing entries by linear interpolation.
+
+III. How to create distribution tables (in practice)
+
+If you want to do all this yourself, I've provided several tools to help:
+
+1. maketable does the steps 2-4 above, and then generates the appropriate
+header file. So if you have your own time distribution, you can generate
+the header simply by:
+
+ maketable < time.values > header.h
+
+2. As explained in the other README file, the somewhat sleazy way I have
+of generating correlated values needs correction. You can generate your
+own correction tables by compiling makesigtable and makemutable with
+your header file. Check the Makefile to see how this is done.
+
+3. Warning: maketable, makesigtable and especially makemutable do
+enormous amounts of floating point arithmetic. Don't try running
+these on an old 486. (NIST Net itself will run fine on such a
+system, since in operation, it just needs to do a few simple integral
+calculations. But getting there takes some work.)
+
+4. The tables produced are all normalized for mean 0 and standard
+deviation 1. How do you know what values to use for real? Here, I've
+provided a simple "stats" utility. Give it a series of floating point
+values, and it will return their mean (mu), standard deviation (sigma),
+and correlation coefficient (rho). You can then plug these values
+directly into NIST Net.
diff --git a/netem/experimental.dat b/netem/experimental.dat
new file mode 100644
index 0000000..3663a3e
--- /dev/null
+++ b/netem/experimental.dat
@@ -0,0 +1,13448 @@
+211.6
+205.6
+203.0
+218.6
+213.9
+199.1
+208.7
+207.7
+203.4
+201.7
+200.3
+213.8
+213.4
+209.8
+204.3
+201.8
+196.3
+216.2
+208.9
+202.4
+205.2
+211.1
+210.9
+208.5
+199.9
+211.6
+211.9
+204.6
+215.4
+202.5
+206.5
+201.1
+198.4
+220.2
+203.7
+219.5
+199.1
+207.6
+205.3
+202.3
+219.7
+230.0
+211.0
+202.7
+209.9
+215.4
+202.9
+209.6
+200.5
+197.3
+212.3
+207.6
+210.5
+202.7
+205.7
+211.2
+208.0
+211.0
+209.4
+204.8
+204.8
+208.7
+210.1
+205.3
+202.5
+210.4
+209.4
+204.5
+204.7
+215.0
+202.6
+209.9
+220.2
+203.8
+206.3
+199.4
+221.8
+200.0
+199.6
+209.3
+206.2
+215.8
+196.9
+211.6
+198.4
+201.2
+209.4
+204.3
+219.0
+212.7
+214.6
+196.3
+202.0
+201.9
+197.5
+229.5
+207.5
+213.8
+209.2
+212.9
+193.9
+200.8
+208.6
+196.8
+201.3
+204.9
+204.7
+209.5
+211.3
+215.3
+203.7
+190.1
+235.6
+203.8
+210.0
+209.7
+214.3
+213.0
+206.3
+197.7
+208.2
+226.3
+216.5
+198.0
+201.3
+211.3
+195.8
+210.9
+208.1
+201.2
+201.7
+213.1
+207.9
+206.6
+207.1
+202.2
+199.6
+205.5
+207.3
+219.7
+204.1
+204.4
+209.0
+212.7
+196.4
+214.0
+208.8
+209.7
+217.2
+196.2
+195.0
+227.7
+207.2
+233.3
+207.9
+204.0
+194.4
+219.2
+208.7
+198.6
+205.0
+204.0
+223.7
+207.4
+209.2
+208.7
+205.4
+212.8
+207.8
+203.0
+204.1
+221.0
+198.4
+217.7
+218.4
+374.2
+220.1
+210.8
+212.1
+214.3
+213.3
+210.3
+202.4
+209.7
+218.1
+205.0
+204.5
+220.3
+209.8
+218.3
+216.6
+206.0
+208.9
+221.0
+213.0
+202.1
+204.2
+220.6
+212.4
+226.1
+208.8
+206.1
+220.7
+219.3
+210.9
+211.2
+213.0
+201.4
+210.5
+206.2
+201.9
+224.5
+219.3
+201.1
+195.6
+223.6
+196.7
+213.7
+202.3
+215.6
+211.4
+209.6
+207.6
+212.4
+203.4
+205.4
+216.1
+216.7
+205.3
+213.9
+208.9
+208.4
+205.1
+199.3
+200.6
+199.1
+203.2
+207.6
+203.8
+201.9
+208.5
+196.4
+213.6
+217.6
+201.5
+210.1
+213.5
+203.8
+214.1
+211.9
+201.5
+186.9
+199.7
+209.1
+200.2
+205.8
+206.7
+200.0
+198.1
+209.3
+207.8
+208.7
+208.0
+208.6
+231.3
+214.5
+210.1
+200.8
+208.9
+216.9
+205.7
+214.9
+236.8
+200.9
+219.1
+204.6
+210.0
+214.0
+222.6
+209.6
+207.0
+196.3
+207.7
+207.9
+208.0
+220.2
+198.2
+204.9
+204.1
+201.0
+204.8
+213.3
+203.9
+222.5
+205.2
+203.5
+209.7
+212.1
+210.1
+221.1
+210.2
+208.0
+201.4
+209.0
+211.9
+201.6
+214.4
+199.6
+198.8
+210.2
+207.3
+206.5
+204.8
+196.3
+199.8
+206.4
+195.3
+202.8
+202.7
+203.8
+211.2
+208.4
+198.6
+202.0
+214.9
+204.2
+201.1
+195.9
+196.1
+211.2
+197.0
+207.7
+196.6
+205.7
+211.4
+201.4
+205.0
+195.5
+198.9
+214.4
+207.3
+204.2
+207.2
+198.5
+220.7
+214.1
+213.2
+207.7
+203.6
+265.8
+221.0
+213.1
+195.4
+197.3
+213.0
+207.7
+206.0
+198.4
+202.3
+213.9
+218.6
+207.6
+206.1
+212.8
+216.8
+213.7
+209.8
+198.1
+202.4
+205.3
+207.0
+209.2
+209.9
+204.4
+199.6
+205.5
+203.9
+216.0
+213.1
+202.4
+199.0
+219.5
+193.9
+197.3
+212.2
+216.7
+217.5
+201.0
+206.2
+202.9
+211.3
+203.1
+218.0
+208.6
+217.8
+209.0
+211.8
+220.1
+212.7
+207.2
+221.2
+215.2
+196.9
+216.6
+203.1
+207.1
+216.7
+206.7
+215.0
+219.3
+204.3
+219.6
+207.1
+211.8
+210.2
+217.2
+207.9
+219.9
+205.4
+201.1
+214.1
+205.8
+212.5
+222.8
+211.9
+217.4
+203.8
+222.9
+206.6
+207.6
+197.5
+206.2
+218.5
+220.3
+207.7
+203.5
+226.4
+216.8
+206.0
+193.2
+198.2
+201.3
+202.4
+208.5
+212.6
+205.0
+202.2
+210.0
+202.4
+203.9
+193.3
+212.4
+203.4
+212.1
+206.1
+206.9
+207.0
+216.1
+201.1
+204.7
+202.4
+207.5
+203.9
+200.9
+210.0
+207.1
+217.2
+197.4
+199.2
+210.8
+209.2
+218.4
+200.2
+211.7
+213.6
+203.3
+197.9
+203.0
+204.2
+207.9
+209.4
+225.4
+237.3
+209.5
+208.2
+207.5
+207.0
+203.0
+219.3
+228.3
+213.5
+205.1
+198.9
+212.7
+201.5
+210.0
+206.5
+203.3
+206.1
+210.1
+219.7
+206.8
+215.4
+220.4
+217.3
+211.4
+206.0
+208.3
+207.3
+205.5
+210.8
+209.3
+197.2
+207.2
+191.7
+204.2
+207.2
+216.1
+209.1
+203.8
+201.8
+208.7
+212.4
+214.5
+213.8
+201.3
+219.7
+214.8
+211.9
+223.8
+208.6
+203.5
+207.4
+207.0
+198.0
+208.2
+218.6
+205.1
+214.6
+215.2
+215.3
+204.3
+210.1
+221.9
+210.7
+198.2
+205.2
+201.1
+219.0
+207.2
+205.9
+203.8
+200.5
+217.5
+208.7
+208.4
+192.6
+211.0
+209.1
+206.5
+197.4
+202.1
+210.0
+198.3
+222.2
+211.9
+212.3
+222.2
+195.1
+200.7
+212.1
+208.3
+211.8
+211.7
+206.5
+211.8
+207.6
+214.2
+207.7
+204.7
+208.2
+208.4
+207.9
+212.1
+223.2
+206.3
+205.6
+201.8
+211.9
+207.6
+203.0
+221.2
+206.3
+222.4
+253.5
+204.4
+218.9
+211.9
+210.9
+214.0
+226.7
+214.4
+199.7
+213.8
+207.0
+201.8
+206.6
+203.1
+202.1
+203.6
+213.9
+196.9
+200.4
+204.6
+333.4
+204.5
+220.9
+207.3
+212.1
+203.7
+200.9
+198.2
+204.0
+201.4
+198.2
+209.6
+211.5
+201.2
+200.4
+207.4
+200.7
+213.8
+207.7
+188.0
+210.0
+210.5
+207.3
+198.6
+206.1
+186.9
+201.4
+204.0
+200.8
+207.8
+211.7
+198.7
+206.1
+213.0
+214.8
+212.8
+208.8
+210.4
+206.5
+210.1
+201.7
+202.7
+201.3
+194.1
+200.8
+196.8
+204.2
+217.5
+209.0
+198.7
+203.2
+213.8
+198.0
+207.1
+204.0
+215.3
+199.5
+214.1
+200.1
+206.9
+219.9
+204.8
+208.6
+207.8
+207.5
+203.8
+210.9
+210.6
+205.3
+202.1
+212.9
+214.8
+210.9
+217.2
+218.3
+221.5
+201.8
+212.7
+215.0
+206.7
+222.8
+210.9
+211.5
+202.0
+208.1
+268.9
+205.8
+204.0
+198.4
+206.3
+209.3
+206.4
+207.4
+226.9
+209.9
+199.6
+206.5
+210.9
+224.1
+211.9
+214.4
+212.2
+211.5
+209.4
+205.3
+204.8
+207.7
+208.9
+213.7
+201.0
+217.4
+198.1
+219.0
+206.5
+229.1
+220.1
+196.8
+203.1
+208.8
+201.7
+195.7
+207.0
+202.4
+206.6
+204.9
+196.6
+204.3
+198.6
+203.9
+215.8
+194.9
+202.7
+225.5
+205.9
+201.4
+213.1
+214.2
+218.8
+209.4
+204.4
+206.7
+209.8
+198.4
+211.8
+212.1
+209.1
+202.3
+213.7
+215.5
+218.3
+209.1
+216.6
+214.8
+206.4
+205.6
+214.4
+209.2
+211.7
+211.3
+211.0
+205.6
+204.2
+191.7
+213.8
+204.9
+205.3
+212.0
+199.9
+198.3
+211.8
+203.0
+212.2
+203.0
+201.8
+214.4
+214.1
+199.6
+205.3
+208.2
+196.7
+196.7
+209.1
+205.1
+212.5
+213.1
+197.3
+208.8
+218.0
+220.0
+198.4
+206.3
+206.9
+253.2
+194.3
+202.6
+210.6
+219.1
+197.8
+197.1
+194.0
+211.6
+209.6
+198.3
+213.0
+207.7
+207.0
+213.3
+206.9
+197.6
+204.8
+202.0
+200.0
+215.2
+204.5
+206.3
+206.7
+203.2
+194.9
+206.3
+209.9
+210.6
+214.2
+208.6
+207.4
+213.9
+210.4
+210.0
+200.6
+203.8
+202.7
+204.2
+202.7
+210.2
+192.5
+215.4
+211.7
+208.3
+204.8
+203.3
+197.7
+216.7
+200.9
+203.6
+208.6
+206.5
+209.9
+200.1
+198.4
+203.3
+210.4
+211.6
+202.0
+203.1
+204.0
+204.0
+215.0
+211.4
+202.0
+197.2
+197.6
+209.9
+205.4
+213.1
+199.1
+212.4
+216.1
+218.3
+214.6
+224.1
+206.9
+199.4
+213.4
+261.2
+199.4
+208.8
+209.9
+205.7
+203.1
+203.2
+204.6
+201.6
+210.6
+213.2
+214.8
+203.8
+204.9
+220.7
+201.5
+212.5
+216.8
+209.7
+203.1
+213.3
+204.7
+218.2
+215.5
+215.6
+211.6
+214.2
+205.1
+216.6
+216.3
+203.5
+200.8
+213.7
+221.9
+215.0
+210.2
+217.1
+214.7
+208.8
+217.4
+231.1
+213.7
+215.0
+213.5
+216.7
+207.7
+201.0
+210.4
+210.9
+206.7
+203.7
+199.2
+209.3
+206.3
+202.4
+210.1
+212.3
+202.2
+207.2
+197.8
+205.9
+202.0
+214.2
+203.5
+204.4
+200.0
+204.0
+193.8
+192.3
+229.0
+204.5
+194.8
+213.6
+215.9
+214.8
+221.6
+208.5
+201.5
+204.4
+206.4
+194.5
+199.4
+201.5
+209.7
+212.5
+202.1
+208.2
+205.4
+204.5
+199.4
+194.5
+199.6
+201.5
+206.2
+219.9
+198.5
+216.2
+195.7
+205.0
+208.0
+204.9
+195.9
+207.4
+216.9
+195.9
+204.4
+208.3
+206.1
+188.5
+202.3
+201.7
+200.5
+206.2
+191.5
+218.6
+206.5
+208.9
+209.9
+201.5
+212.7
+203.2
+209.7
+212.1
+208.4
+207.2
+206.5
+204.5
+222.7
+207.6
+207.4
+210.3
+212.2
+219.1
+215.2
+211.1
+205.9
+205.5
+205.9
+203.1
+205.4
+184.5
+205.0
+194.8
+213.5
+209.8
+195.4
+202.9
+205.3
+196.3
+202.0
+198.2
+201.5
+195.3
+230.9
+207.8
+212.6
+202.7
+204.8
+205.0
+202.8
+206.2
+200.2
+202.7
+203.5
+205.5
+196.9
+209.4
+212.1
+200.8
+205.0
+208.0
+207.1
+198.0
+204.8
+205.8
+200.9
+202.1
+202.4
+206.9
+209.1
+199.7
+197.1
+206.9
+200.2
+193.7
+195.0
+250.8
+207.5
+204.5
+208.8
+209.8
+194.8
+200.2
+205.1
+197.3
+208.3
+200.4
+204.7
+211.1
+203.4
+218.2
+194.6
+201.5
+202.2
+202.9
+198.8
+218.2
+201.7
+189.8
+210.1
+208.0
+204.3
+205.8
+204.2
+207.8
+200.2
+197.9
+198.9
+208.1
+202.4
+196.2
+195.5
+204.6
+211.0
+205.0
+193.6
+197.2
+198.6
+193.8
+198.9
+232.4
+201.8
+212.2
+208.6
+204.5
+199.3
+211.2
+203.1
+209.7
+214.3
+203.9
+200.3
+203.3
+206.1
+206.9
+209.1
+209.1
+199.3
+199.4
+198.8
+198.9
+199.9
+193.7
+204.6
+203.4
+199.7
+212.6
+200.7
+208.1
+198.8
+200.5
+209.2
+208.4
+205.7
+197.1
+202.6
+199.5
+208.4
+200.1
+204.9
+202.9
+201.5
+207.6
+200.6
+204.2
+210.0
+207.1
+205.1
+198.5
+204.9
+196.5
+208.0
+202.4
+202.7
+196.2
+206.9
+201.5
+203.3
+198.7
+211.9
+208.4
+206.7
+209.4
+204.0
+202.3
+205.0
+205.3
+206.0
+213.1
+205.7
+199.3
+206.2
+204.6
+209.3
+205.7
+202.7
+213.3
+202.3
+197.8
+196.5
+193.4
+211.6
+209.9
+195.5
+196.2
+210.2
+207.1
+207.0
+221.8
+217.2
+215.4
+207.0
+200.1
+207.5
+206.0
+200.7
+190.9
+209.8
+213.5
+206.3
+196.0
+213.1
+202.7
+211.6
+196.5
+209.9
+212.3
+199.9
+206.8
+225.1
+203.9
+204.3
+197.7
+203.5
+203.2
+193.5
+200.9
+201.4
+189.1
+203.9
+194.5
+205.4
+204.8
+204.9
+201.3
+208.4
+196.9
+206.8
+207.7
+201.6
+210.3
+211.6
+209.8
+200.2
+205.2
+197.6
+195.9
+212.8
+206.4
+201.0
+208.2
+207.5
+202.5
+193.3
+206.5
+221.2
+198.8
+216.6
+217.0
+209.1
+206.6
+197.7
+211.0
+199.9
+198.0
+210.4
+200.5
+211.7
+219.6
+206.8
+207.2
+210.6
+205.4
+203.8
+207.4
+206.2
+205.1
+208.7
+196.3
+204.7
+210.8
+214.4
+196.3
+206.5
+210.8
+193.2
+203.3
+203.9
+207.7
+194.9
+203.7
+195.5
+218.7
+201.1
+199.5
+207.6
+209.3
+207.5
+205.7
+203.9
+205.4
+201.3
+205.8
+205.4
+208.8
+214.3
+203.4
+207.5
+188.9
+205.5
+200.7
+212.5
+197.9
+219.0
+213.6
+197.3
+202.7
+216.3
+205.0
+210.2
+203.2
+203.9
+206.8
+213.6
+200.1
+204.4
+211.4
+213.4
+200.2
+208.4
+209.1
+198.8
+207.4
+195.0
+205.6
+200.5
+204.3
+201.9
+206.4
+199.0
+196.1
+207.6
+195.4
+197.2
+200.7
+190.8
+211.9
+191.5
+201.4
+193.5
+205.1
+206.8
+199.5
+207.4
+209.8
+199.1
+194.6
+201.6
+211.6
+206.8
+203.9
+196.8
+206.3
+210.1
+200.6
+227.4
+201.9
+210.8
+205.8
+217.2
+205.8
+196.1
+200.7
+213.8
+205.4
+211.6
+212.3
+213.6
+201.7
+199.9
+203.2
+212.6
+211.0
+208.1
+198.1
+201.7
+211.6
+207.4
+212.4
+207.3
+214.9
+214.5
+214.5
+202.7
+200.1
+206.4
+213.4
+189.7
+203.4
+202.2
+198.2
+206.5
+213.7
+207.6
+202.8
+209.2
+205.5
+196.4
+207.6
+207.4
+207.3
+188.8
+215.6
+195.4
+207.7
+208.2
+200.9
+208.4
+203.3
+210.8
+199.6
+208.3
+206.7
+201.6
+202.9
+197.5
+206.4
+209.0
+208.4
+211.6
+204.4
+210.0
+190.9
+199.3
+207.6
+202.5
+197.0
+200.8
+203.1
+204.0
+199.0
+208.0
+204.6
+196.6
+200.8
+205.2
+198.8
+203.0
+208.3
+200.1
+205.5
+203.7
+202.2
+203.8
+211.5
+201.8
+213.2
+207.4
+207.8
+202.2
+208.2
+204.2
+200.4
+186.1
+188.5
+220.4
+212.8
+193.3
+196.9
+203.0
+207.3
+202.4
+201.7
+204.8
+192.2
+218.7
+226.3
+209.5
+201.4
+207.3
+202.6
+210.7
+208.4
+208.4
+207.4
+210.4
+191.2
+203.6
+197.1
+207.5
+197.8
+206.2
+214.5
+208.2
+207.3
+204.7
+199.6
+206.3
+189.0
+214.4
+209.4
+208.1
+199.9
+190.5
+223.0
+198.8
+201.1
+192.4
+204.0
+209.0
+206.7
+204.3
+198.7
+210.9
+212.0
+204.8
+204.2
+199.5
+203.5
+203.0
+190.6
+207.9
+207.9
+193.2
+210.9
+200.1
+207.6
+193.6
+204.9
+197.7
+200.9
+213.0
+215.0
+204.4
+196.6
+209.6
+209.9
+199.8
+198.8
+202.1
+203.4
+205.4
+204.4
+196.2
+190.7
+210.9
+197.7
+194.7
+204.0
+201.5
+195.3
+209.0
+203.6
+196.1
+205.2
+206.7
+206.6
+191.4
+193.4
+206.6
+205.9
+207.9
+201.7
+213.3
+199.4
+202.8
+196.1
+208.3
+206.4
+205.2
+191.9
+207.3
+191.5
+210.8
+200.9
+210.4
+208.3
+211.0
+202.7
+198.8
+196.8
+202.7
+196.9
+214.6
+210.2
+226.1
+220.8
+213.5
+194.9
+210.4
+203.7
+203.7
+180.8
+213.7
+208.0
+209.8
+209.7
+213.8
+185.5
+208.5
+203.5
+212.8
+193.1
+199.2
+211.0
+217.4
+211.0
+202.7
+205.0
+208.6
+197.5
+197.1
+201.0
+195.9
+208.4
+205.7
+205.8
+194.0
+204.4
+194.5
+194.3
+200.1
+209.5
+218.0
+202.8
+197.5
+206.7
+199.8
+205.2
+201.4
+205.2
+186.0
+208.4
+218.4
+206.7
+201.9
+209.7
+208.0
+203.9
+193.1
+202.0
+198.0
+199.5
+211.0
+191.8
+198.7
+197.3
+195.6
+202.9
+203.4
+206.1
+205.6
+207.5
+220.8
+204.7
+207.7
+252.5
+203.9
+203.2
+201.3
+200.1
+201.1
+196.8
+197.6
+206.4
+209.6
+197.9
+199.4
+212.6
+205.4
+200.9
+197.5
+202.2
+199.5
+206.7
+215.1
+216.4
+221.9
+199.2
+246.4
+196.0
+205.1
+205.4
+207.8
+192.6
+204.6
+209.2
+213.4
+198.9
+205.3
+205.8
+201.1
+195.2
+199.4
+200.8
+210.5
+202.3
+217.9
+208.4
+220.8
+218.4
+195.7
+199.4
+198.8
+192.0
+210.9
+218.5
+194.5
+203.6
+195.0
+208.8
+197.4
+204.1
+200.7
+201.0
+206.6
+202.2
+208.7
+213.1
+198.3
+212.2
+201.9
+206.3
+203.4
+198.0
+198.0
+205.3
+199.6
+196.6
+202.8
+201.7
+208.7
+195.6
+199.4
+205.4
+205.2
+202.2
+193.3
+191.9
+195.1
+201.1
+210.5
+208.7
+196.9
+193.4
+200.8
+199.6
+204.1
+200.4
+197.6
+204.1
+206.9
+205.2
+206.9
+194.7
+200.4
+198.8
+201.7
+201.8
+207.0
+193.2
+199.9
+201.3
+192.5
+197.9
+206.9
+190.0
+203.8
+208.8
+200.9
+203.3
+194.5
+192.6
+204.9
+205.5
+196.6
+194.8
+197.9
+198.1
+211.2
+198.8
+202.2
+205.9
+199.5
+204.7
+201.6
+201.2
+203.4
+204.2
+190.7
+206.7
+205.4
+208.4
+203.1
+204.2
+198.4
+194.3
+191.6
+198.9
+203.5
+198.7
+192.2
+198.4
+194.5
+181.1
+200.9
+200.0
+209.2
+210.4
+200.0
+201.1
+193.9
+207.0
+193.4
+202.6
+192.8
+196.0
+203.8
+184.2
+179.3
+202.3
+191.4
+199.7
+195.4
+189.9
+197.0
+187.5
+192.1
+198.3
+202.2
+205.0
+212.3
+198.0
+205.5
+210.1
+197.6
+198.7
+206.6
+203.4
+194.3
+181.2
+199.0
+202.4
+189.1
+181.6
+200.4
+188.1
+180.1
+203.1
+201.1
+195.5
+201.6
+201.3
+197.6
+196.0
+205.5
+184.9
+186.5
+190.8
+188.6
+207.2
+199.5
+198.6
+199.8
+212.2
+208.1
+196.9
+199.6
+205.3
+196.9
+188.9
+205.4
+212.5
+197.5
+201.8
+188.8
+187.1
+199.9
+195.4
+188.7
+198.7
+185.0
+191.6
+193.3
+191.8
+209.4
+197.4
+195.2
+189.4
+189.7
+199.9
+199.3
+188.7
+188.3
+190.9
+181.6
+209.8
+194.6
+198.2
+199.9
+198.1
+186.8
+195.3
+190.9
+198.8
+189.3
+207.5
+179.2
+188.8
+185.6
+206.2
+184.8
+190.7
+203.5
+199.2
+202.0
+197.6
+197.2
+196.4
+210.4
+200.1
+194.8
+186.7
+198.2
+197.8
+186.5
+200.2
+192.7
+192.7
+190.4
+220.9
+207.5
+188.6
+198.5
+203.0
+202.2
+189.6
+177.3
+194.8
+195.2
+243.9
+196.5
+180.6
+214.6
+196.4
+220.6
+194.7
+200.5
+193.7
+199.7
+203.0
+201.4
+187.7
+199.8
+191.8
+203.9
+203.8
+191.3
+206.6
+201.7
+202.1
+202.6
+200.0
+203.6
+195.9
+204.8
+212.8
+199.2
+203.3
+206.6
+192.2
+205.0
+198.9
+205.3
+195.0
+198.1
+190.4
+203.7
+188.2
+204.2
+211.1
+192.5
+194.5
+198.3
+205.7
+198.5
+210.2
+206.8
+195.4
+200.8
+202.7
+220.0
+204.1
+209.5
+200.2
+187.1
+205.4
+202.6
+203.3
+214.1
+193.8
+207.4
+208.2
+204.9
+215.9
+202.6
+198.0
+193.8
+198.2
+206.2
+203.9
+190.6
+210.8
+195.6
+207.6
+206.6
+195.4
+189.9
+199.7
+203.1
+207.1
+192.2
+197.3
+197.6
+193.3
+207.9
+201.3
+206.8
+201.9
+195.7
+204.1
+201.1
+192.5
+206.7
+213.1
+195.2
+205.1
+196.2
+203.5
+195.5
+200.3
+194.7
+194.5
+200.6
+211.2
+202.1
+194.6
+199.9
+212.6
+206.8
+196.2
+205.8
+202.8
+201.6
+205.2
+205.8
+193.1
+202.0
+196.2
+208.1
+209.5
+199.8
+208.8
+192.3
+207.9
+201.3
+205.7
+205.9
+208.6
+210.3
+202.2
+212.1
+210.3
+199.6
+200.8
+209.1
+202.5
+215.0
+201.5
+209.2
+207.0
+215.3
+205.6
+213.7
+203.7
+199.8
+201.4
+194.7
+194.3
+188.7
+200.9
+203.8
+203.2
+212.5
+207.0
+211.3
+204.3
+204.5
+194.0
+210.7
+207.1
+207.5
+200.7
+200.8
+200.7
+200.1
+203.7
+191.1
+201.8
+194.8
+195.2
+197.4
+190.5
+192.7
+206.6
+200.8
+204.3
+206.5
+209.8
+202.5
+207.6
+198.4
+203.3
+202.1
+200.6
+198.4
+191.0
+203.5
+198.6
+184.3
+183.7
+189.1
+205.4
+187.8
+194.5
+199.2
+196.4
+210.9
+176.8
+191.6
+182.7
+181.3
+205.7
+203.2
+186.3
+187.6
+189.1
+180.8
+180.2
+187.6
+194.9
+192.8
+185.2
+198.3
+209.3
+177.5
+193.9
+193.1
+203.4
+192.1
+200.9
+182.6
+204.9
+197.6
+212.8
+206.9
+193.3
+201.0
+195.3
+197.1
+189.6
+198.5
+190.4
+188.8
+197.7
+189.9
+200.7
+196.8
+186.3
+181.5
+184.9
+200.2
+198.7
+205.8
+200.2
+198.3
+207.9
+206.1
+201.5
+197.8
+199.5
+198.1
+211.3
+201.6
+202.4
+196.0
+197.7
+209.2
+199.3
+205.5
+191.6
+206.4
+196.5
+209.5
+203.4
+201.4
+200.1
+205.2
+190.9
+205.1
+197.5
+196.1
+194.4
+194.7
+188.9
+180.8
+206.5
+199.8
+193.1
+195.2
+192.7
+199.2
+199.5
+188.1
+180.2
+191.0
+206.9
+208.2
+202.5
+200.0
+207.0
+201.6
+195.6
+195.6
+195.0
+196.3
+190.2
+194.0
+182.9
+192.1
+206.5
+181.4
+192.3
+199.6
+201.6
+192.4
+200.5
+207.1
+198.5
+198.8
+190.8
+200.3
+199.3
+200.5
+187.7
+208.3
+205.6
+189.3
+198.4
+204.9
+197.4
+198.7
+190.6
+214.5
+212.5
+207.6
+196.9
+183.4
+185.1
+205.8
+226.5
+202.5
+201.8
+202.9
+210.4
+189.7
+195.6
+198.7
+193.0
+198.8
+193.1
+202.5
+195.2
+195.0
+198.3
+203.6
+208.3
+195.9
+200.8
+189.4
+207.9
+182.8
+194.9
+199.7
+180.7
+187.2
+189.5
+196.1
+190.1
+192.5
+185.7
+212.2
+204.2
+191.9
+184.5
+182.5
+198.5
+191.0
+192.0
+195.6
+201.1
+193.7
+203.8
+200.5
+199.1
+190.3
+209.5
+195.0
+184.0
+193.6
+203.3
+191.4
+194.9
+195.5
+193.5
+182.7
+189.7
+196.1
+178.9
+199.5
+195.3
+185.9
+199.1
+210.0
+195.7
+193.8
+196.4
+195.3
+201.4
+209.5
+205.6
+197.5
+188.9
+193.8
+185.3
+193.3
+198.1
+201.4
+184.7
+182.5
+183.7
+185.5
+199.8
+200.3
+194.1
+176.9
+192.2
+200.0
+186.4
+191.6
+200.1
+202.3
+205.1
+186.4
+182.3
+194.7
+177.5
+201.4
+189.6
+195.5
+185.4
+194.8
+204.1
+188.0
+182.1
+181.7
+184.5
+234.2
+209.4
+193.3
+204.0
+184.7
+194.3
+193.4
+191.1
+188.3
+193.9
+198.2
+202.8
+198.1
+191.2
+200.9
+205.4
+203.6
+193.8
+215.8
+185.8
+195.4
+204.7
+190.3
+190.7
+177.7
+182.1
+193.2
+178.3
+199.3
+203.5
+187.3
+198.8
+187.8
+187.7
+186.7
+200.0
+190.0
+203.1
+181.7
+207.2
+183.8
+180.3
+193.5
+190.2
+193.7
+198.6
+195.6
+192.1
+200.5
+188.6
+190.9
+188.0
+192.8
+191.4
+179.9
+197.6
+200.6
+206.1
+201.3
+199.6
+198.8
+201.0
+180.2
+202.9
+197.3
+186.1
+200.5
+182.4
+192.7
+194.5
+182.8
+193.9
+195.1
+187.7
+201.0
+196.1
+194.0
+198.8
+192.8
+186.1
+200.6
+186.3
+187.6
+178.0
+175.8
+198.9
+199.3
+193.4
+193.3
+198.7
+194.5
+180.9
+197.3
+189.7
+193.0
+208.2
+200.1
+193.9
+211.2
+206.6
+210.2
+185.5
+180.8
+206.8
+185.5
+195.8
+199.3
+187.9
+194.9
+175.8
+198.1
+199.0
+200.6
+300.8
+194.0
+199.7
+181.2
+189.9
+195.3
+209.6
+198.1
+184.9
+192.5
+188.8
+193.8
+201.4
+208.2
+192.5
+199.9
+185.0
+207.5
+196.5
+198.8
+193.3
+200.1
+186.7
+194.4
+194.3
+197.2
+198.4
+192.8
+194.3
+188.6
+194.7
+190.7
+192.1
+194.5
+185.7
+194.6
+177.5
+203.6
+180.8
+185.0
+178.9
+205.7
+187.4
+185.9
+192.9
+182.7
+197.3
+198.0
+194.5
+194.7
+194.7
+198.2
+184.7
+199.0
+200.9
+195.4
+198.7
+188.1
+187.5
+190.6
+179.2
+190.2
+195.9
+188.8
+205.7
+191.9
+204.0
+193.3
+199.5
+200.7
+179.3
+190.4
+206.4
+199.8
+189.5
+194.1
+203.3
+196.9
+200.1
+179.6
+217.2
+199.0
+184.0
+177.4
+200.5
+205.3
+193.2
+198.8
+187.2
+191.2
+186.6
+188.3
+199.4
+192.8
+209.8
+181.5
+192.8
+176.0
+189.9
+203.5
+192.5
+193.1
+190.3
+193.1
+203.0
+194.6
+188.4
+199.8
+199.6
+195.0
+200.3
+195.5
+198.5
+203.1
+193.4
+203.6
+195.6
+186.2
+206.4
+197.3
+265.4
+203.7
+205.7
+197.0
+194.9
+193.6
+201.1
+200.6
+197.1
+196.0
+196.3
+195.4
+194.2
+198.4
+202.6
+197.1
+209.4
+204.7
+195.9
+192.8
+203.4
+193.3
+188.5
+190.7
+190.3
+197.6
+197.7
+199.1
+193.0
+198.3
+205.0
+191.4
+197.2
+201.3
+197.6
+197.7
+202.9
+203.4
+198.5
+198.8
+205.4
+194.6
+189.5
+193.3
+190.4
+193.0
+202.5
+198.2
+194.7
+198.5
+184.3
+187.8
+193.3
+190.8
+194.9
+190.3
+201.8
+192.9
+198.5
+195.9
+195.8
+210.1
+194.2
+202.6
+194.5
+197.6
+200.1
+191.8
+192.8
+199.4
+199.7
+199.3
+194.2
+196.9
+195.7
+189.9
+197.1
+205.9
+191.1
+196.5
+200.9
+200.6
+199.1
+203.0
+204.2
+198.7
+192.2
+194.9
+188.6
+194.3
+198.5
+190.6
+189.4
+205.8
+207.0
+200.9
+198.0
+196.9
+196.6
+187.3
+199.9
+196.1
+196.5
+200.0
+186.0
+182.7
+193.3
+195.2
+190.0
+195.9
+190.0
+201.7
+187.1
+199.1
+203.5
+191.8
+199.5
+195.1
+207.3
+205.6
+191.9
+200.1
+196.5
+205.4
+190.8
+195.9
+194.6
+190.2
+197.4
+204.4
+210.5
+289.6
+197.2
+191.4
+199.2
+196.6
+201.4
+191.7
+194.4
+191.9
+193.1
+182.9
+191.5
+202.0
+186.8
+195.5
+193.4
+189.8
+181.3
+199.9
+200.3
+193.5
+196.5
+190.5
+200.6
+209.8
+197.7
+199.5
+200.9
+205.0
+199.4
+206.3
+205.7
+202.7
+189.0
+203.6
+198.9
+188.4
+193.7
+204.1
+198.4
+208.8
+201.4
+198.0
+188.7
+208.9
+196.9
+235.5
+198.1
+202.8
+195.8
+193.2
+203.0
+204.2
+201.2
+201.4
+202.8
+213.8
+197.2
+197.0
+191.2
+196.2
+210.8
+203.5
+193.4
+211.7
+194.0
+204.6
+197.0
+200.0
+197.9
+204.2
+196.6
+184.4
+188.6
+194.4
+188.1
+202.4
+203.0
+204.4
+194.1
+195.8
+195.3
+201.9
+194.7
+205.8
+201.9
+208.6
+195.5
+209.5
+194.8
+202.7
+202.2
+204.4
+197.1
+205.9
+196.2
+197.1
+197.4
+198.6
+198.8
+201.1
+203.9
+200.3
+199.5
+224.5
+199.2
+196.2
+197.7
+194.7
+194.1
+202.5
+191.2
+203.1
+199.1
+197.5
+201.0
+198.7
+207.9
+191.7
+192.1
+191.7
+208.7
+178.3
+202.0
+200.4
+202.1
+206.8
+194.2
+197.0
+203.3
+195.1
+210.0
+193.9
+191.1
+200.1
+192.9
+202.3
+189.5
+193.9
+200.8
+205.5
+198.7
+205.4
+184.7
+198.6
+189.7
+187.8
+202.5
+196.2
+203.5
+213.4
+199.7
+207.5
+207.3
+204.7
+190.5
+194.9
+184.7
+198.0
+200.9
+189.8
+208.9
+189.5
+218.6
+202.8
+189.0
+202.2
+204.3
+191.5
+193.6
+201.6
+204.6
+197.0
+200.9
+186.0
+205.9
+194.1
+203.3
+197.3
+200.3
+195.9
+207.3
+206.7
+206.7
+193.0
+203.1
+238.1
+192.2
+193.3
+197.4
+212.3
+202.5
+197.5
+204.1
+196.6
+183.8
+204.5
+188.1
+217.6
+194.5
+199.1
+210.9
+200.9
+187.7
+199.2
+195.0
+191.0
+198.3
+194.1
+191.9
+213.8
+199.0
+201.8
+197.5
+201.5
+195.6
+207.5
+200.6
+194.2
+210.5
+192.7
+188.1
+203.0
+237.1
+204.7
+205.1
+205.2
+200.3
+188.5
+202.2
+213.1
+195.0
+201.4
+204.2
+195.9
+186.5
+192.2
+206.5
+177.5
+189.9
+192.0
+214.9
+204.0
+194.2
+200.9
+197.1
+200.0
+196.4
+197.2
+189.0
+194.3
+206.5
+192.5
+190.5
+204.1
+196.7
+194.4
+181.9
+193.0
+190.6
+193.6
+178.0
+178.2
+200.9
+189.5
+194.2
+182.1
+183.6
+183.7
+176.9
+181.7
+194.9
+190.7
+187.4
+178.6
+182.0
+186.5
+183.7
+182.1
+186.2
+199.6
+192.4
+189.2
+194.7
+176.3
+184.6
+203.2
+201.9
+195.2
+192.7
+186.7
+195.2
+187.0
+201.1
+202.1
+187.7
+195.9
+181.7
+189.7
+179.9
+177.3
+180.3
+198.3
+184.6
+183.3
+196.9
+178.6
+184.3
+185.3
+183.2
+193.9
+194.7
+195.5
+199.6
+192.0
+189.4
+195.0
+193.6
+200.5
+177.6
+181.0
+200.0
+190.3
+189.7
+205.5
+178.5
+201.7
+192.7
+196.8
+189.2
+177.6
+198.0
+191.8
+178.6
+206.8
+190.0
+192.3
+180.1
+194.6
+179.7
+207.1
+195.6
+200.5
+186.7
+190.1
+178.6
+205.7
+346.2
+188.8
+204.4
+200.7
+176.5
+193.8
+195.9
+193.0
+186.5
+189.5
+190.6
+178.0
+188.6
+186.7
+180.9
+193.8
+194.0
+180.6
+196.7
+178.7
+180.1
+187.7
+179.6
+201.3
+219.2
+184.0
+206.3
+186.7
+192.0
+179.4
+190.1
+187.0
+191.6
+194.7
+195.5
+194.2
+195.8
+200.6
+180.3
+195.6
+209.9
+197.2
+201.1
+196.9
+186.3
+202.7
+182.7
+200.4
+201.2
+196.2
+181.1
+182.6
+187.2
+225.3
+186.8
+197.6
+192.0
+185.0
+199.8
+191.7
+187.4
+192.5
+189.1
+210.9
+187.0
+191.2
+190.9
+207.1
+198.7
+208.8
+190.6
+193.7
+186.5
+182.9
+178.7
+194.1
+184.3
+194.0
+185.4
+215.7
+194.6
+207.9
+204.7
+183.7
+189.3
+196.0
+202.6
+206.2
+190.1
+216.6
+179.8
+206.9
+188.4
+190.8
+181.7
+197.7
+195.7
+178.3
+179.0
+179.3
+203.2
+178.4
+180.1
+175.7
+194.1
+193.0
+203.2
+192.6
+194.0
+190.7
+193.0
+194.2
+183.7
+197.2
+188.4
+176.1
+184.3
+192.2
+195.7
+186.0
+193.7
+196.2
+183.7
+180.7
+181.0
+189.4
+199.3
+201.5
+199.8
+197.2
+201.7
+199.9
+192.2
+188.2
+187.5
+176.6
+205.3
+199.0
+190.0
+202.4
+201.8
+216.8
+208.2
+197.4
+194.3
+205.9
+179.0
+193.5
+182.9
+188.0
+198.3
+197.7
+177.6
+183.8
+182.3
+198.8
+192.5
+183.2
+208.4
+192.4
+193.8
+198.6
+196.2
+183.9
+186.2
+197.8
+180.6
+204.7
+195.3
+182.8
+192.5
+216.3
+203.8
+197.4
+179.5
+188.5
+196.8
+194.4
+189.2
+199.8
+200.7
+205.1
+183.4
+181.5
+193.6
+207.9
+177.4
+191.5
+197.3
+189.8
+191.9
+189.5
+201.2
+211.0
+201.4
+205.5
+201.1
+199.3
+193.5
+203.5
+191.6
+194.0
+196.7
+188.5
+189.8
+195.9
+181.1
+193.2
+197.2
+201.0
+186.2
+200.9
+183.5
+183.0
+207.9
+189.7
+193.8
+182.9
+232.1
+186.2
+200.3
+194.6
+184.2
+192.2
+182.9
+193.6
+207.4
+203.3
+185.6
+197.6
+205.9
+193.0
+182.3
+190.1
+193.3
+200.2
+183.9
+195.7
+186.9
+200.1
+189.6
+194.2
+195.5
+178.3
+183.9
+183.0
+179.2
+209.8
+199.6
+185.4
+210.9
+188.6
+188.9
+185.4
+177.0
+183.0
+186.1
+191.7
+190.7
+192.1
+195.2
+185.0
+186.6
+194.2
+189.5
+187.7
+187.9
+191.8
+181.1
+180.2
+180.0
+185.6
+181.8
+187.9
+192.9
+198.7
+182.0
+182.7
+175.8
+202.0
+190.6
+195.3
+191.0
+201.4
+194.3
+179.5
+185.0
+240.3
+187.1
+197.8
+204.8
+210.7
+203.2
+202.0
+197.8
+203.5
+205.1
+190.0
+190.3
+204.1
+197.9
+198.6
+199.3
+182.1
+200.8
+196.0
+197.1
+203.5
+198.6
+200.7
+201.1
+189.1
+175.8
+199.9
+203.7
+192.2
+182.5
+185.0
+195.9
+204.1
+197.0
+195.5
+202.0
+201.5
+187.1
+186.7
+198.2
+211.8
+197.6
+178.5
+198.5
+196.4
+188.1
+185.0
+199.5
+200.9
+201.7
+193.5
+188.3
+180.9
+190.6
+202.8
+183.9
+190.1
+205.3
+198.6
+183.2
+198.0
+200.9
+198.8
+194.8
+198.3
+195.8
+204.6
+202.6
+207.0
+185.9
+201.9
+195.9
+207.6
+197.4
+206.7
+188.2
+184.7
+183.9
+198.3
+186.4
+191.0
+208.8
+209.4
+187.7
+208.0
+198.4
+191.9
+187.1
+188.5
+189.2
+190.7
+179.9
+204.1
+195.5
+183.7
+183.1
+183.7
+194.6
+187.9
+183.5
+184.7
+203.2
+197.8
+179.4
+185.8
+205.3
+179.8
+194.8
+186.7
+191.5
+197.4
+197.0
+209.2
+194.1
+187.8
+192.9
+202.2
+194.4
+206.6
+191.6
+190.8
+187.5
+193.5
+205.2
+185.1
+185.6
+189.9
+196.0
+203.6
+195.4
+209.9
+196.5
+200.8
+190.1
+191.5
+232.8
+209.2
+193.1
+183.4
+199.5
+187.2
+199.4
+203.6
+198.3
+204.1
+196.4
+202.0
+180.6
+191.6
+202.9
+184.9
+180.8
+192.3
+205.2
+205.5
+179.0
+183.3
+203.1
+184.1
+188.6
+180.9
+195.8
+195.0
+181.1
+198.3
+190.7
+191.8
+192.1
+191.5
+257.4
+197.3
+206.5
+213.4
+184.2
+193.5
+208.6
+209.8
+192.0
+201.5
+198.6
+205.2
+198.9
+212.6
+195.9
+213.7
+215.9
+209.2
+215.2
+207.4
+208.9
+192.2
+192.3
+193.8
+185.1
+206.0
+179.1
+199.9
+198.3
+201.6
+186.8
+202.3
+190.4
+183.6
+191.8
+190.6
+194.7
+184.1
+184.4
+193.8
+194.0
+195.6
+204.5
+194.9
+196.1
+201.1
+196.1
+210.9
+198.7
+192.1
+208.3
+192.8
+193.6
+205.1
+191.5
+190.7
+179.2
+191.6
+192.1
+182.3
+176.1
+185.5
+187.1
+183.4
+191.9
+176.1
+178.0
+185.2
+191.3
+181.3
+178.1
+181.7
+200.3
+187.5
+201.6
+192.3
+177.7
+180.6
+192.9
+185.4
+183.1
+178.4
+196.9
+191.9
+186.1
+195.7
+177.7
+184.1
+195.4
+184.2
+188.8
+179.6
+182.0
+182.9
+185.1
+183.8
+189.3
+216.9
+196.6
+185.7
+235.5
+184.9
+181.8
+180.0
+186.6
+188.4
+190.7
+185.4
+210.9
+184.1
+203.0
+203.5
+198.9
+204.2
+199.7
+182.1
+178.1
+205.1
+179.4
+184.5
+180.7
+200.5
+197.3
+178.2
+178.8
+180.9
+219.2
+180.2
+192.5
+198.6
+238.0
+201.1
+182.4
+203.3
+182.0
+188.9
+201.2
+184.6
+182.7
+187.7
+188.5
+202.5
+199.8
+189.4
+191.1
+181.2
+191.7
+194.6
+181.3
+185.3
+192.8
+182.4
+191.6
+188.3
+202.6
+212.1
+179.6
+185.9
+183.4
+187.8
+184.4
+186.8
+197.0
+191.3
+186.7
+201.9
+187.2
+195.7
+178.5
+187.3
+190.8
+198.4
+198.8
+189.1
+189.6
+197.6
+201.0
+185.0
+182.2
+184.2
+193.2
+191.6
+187.4
+196.3
+190.7
+184.5
+206.1
+200.7
+193.0
+196.2
+195.1
+177.1
+180.7
+187.3
+188.3
+181.5
+180.7
+216.9
+185.7
+196.1
+193.2
+185.5
+186.9
+190.4
+189.7
+196.1
+193.9
+193.6
+185.7
+190.3
+199.8
+190.4
+187.4
+195.5
+190.0
+188.7
+190.2
+195.6
+195.2
+184.7
+186.9
+187.4
+179.3
+184.7
+206.7
+194.3
+198.9
+179.0
+185.7
+185.2
+206.7
+184.3
+202.8
+183.5
+178.9
+238.3
+181.1
+189.5
+176.7
+178.7
+183.4
+180.3
+192.4
+193.6
+186.6
+190.0
+184.0
+188.5
+188.6
+196.3
+182.2
+191.9
+191.1
+197.7
+188.4
+194.2
+201.7
+204.3
+199.6
+190.7
+183.0
+189.9
+192.9
+187.6
+182.8
+203.3
+189.3
+194.0
+189.4
+188.0
+194.4
+211.7
+186.3
+185.3
+187.2
+182.9
+194.9
+187.4
+190.4
+197.5
+184.5
+184.8
+180.0
+197.9
+205.9
+189.2
+196.5
+188.1
+185.4
+195.5
+185.0
+203.1
+188.6
+202.3
+180.6
+191.3
+204.4
+208.2
+189.2
+226.9
+200.0
+178.8
+181.5
+197.7
+204.1
+184.8
+178.5
+186.5
+190.9
+190.4
+181.4
+189.8
+179.2
+189.8
+179.6
+203.8
+205.4
+184.2
+192.8
+204.7
+186.6
+195.5
+203.7
+197.2
+178.0
+180.3
+187.0
+179.1
+187.5
+187.9
+183.4
+181.8
+184.1
+181.3
+184.0
+180.6
+230.8
+193.0
+190.5
+200.4
+187.9
+175.9
+204.2
+206.5
+183.2
+194.2
+187.2
+200.2
+189.6
+188.6
+188.6
+175.4
+184.2
+174.0
+177.2
+180.6
+181.5
+182.3
+187.1
+180.0
+184.7
+179.3
+195.7
+180.5
+179.5
+184.8
+204.0
+202.1
+178.7
+189.4
+196.4
+190.8
+184.3
+189.2
+182.8
+184.6
+178.4
+183.9
+185.0
+184.6
+179.2
+179.0
+182.4
+197.0
+188.4
+188.5
+196.0
+179.6
+188.1
+180.3
+196.1
+189.0
+178.4
+176.5
+186.4
+179.3
+187.7
+187.9
+184.8
+176.9
+188.7
+182.8
+192.3
+190.0
+197.2
+191.6
+197.8
+182.3
+184.2
+187.3
+188.2
+207.3
+189.2
+190.7
+202.3
+191.7
+195.4
+196.2
+190.1
+204.2
+194.8
+187.8
+209.1
+192.1
+193.1
+199.3
+192.1
+204.9
+190.8
+186.5
+189.3
+181.9
+193.3
+182.4
+195.2
+189.1
+196.8
+200.8
+187.6
+201.1
+197.0
+196.6
+177.7
+188.9
+186.2
+186.0
+199.9
+203.8
+190.2
+190.2
+186.3
+179.1
+198.8
+186.3
+192.4
+201.5
+190.2
+191.0
+178.0
+194.1
+197.9
+189.9
+180.2
+176.2
+193.5
+201.9
+201.3
+200.7
+190.3
+198.4
+186.0
+190.2
+182.7
+186.0
+190.0
+196.8
+193.0
+201.5
+184.7
+187.7
+190.8
+187.2
+183.5
+178.1
+178.9
+195.8
+178.2
+188.0
+177.2
+182.5
+184.2
+180.7
+189.6
+203.4
+181.7
+185.1
+177.5
+180.9
+194.0
+190.1
+190.1
+182.9
+184.4
+201.8
+191.1
+184.9
+197.7
+193.3
+190.3
+197.2
+188.2
+191.5
+190.0
+188.0
+187.5
+182.5
+186.6
+183.6
+193.9
+187.5
+186.9
+197.3
+187.2
+177.2
+186.8
+191.9
+186.9
+198.7
+179.7
+191.7
+193.6
+184.2
+178.4
+187.4
+203.3
+196.8
+195.4
+182.9
+187.7
+189.4
+192.5
+190.0
+190.0
+185.4
+194.6
+183.7
+180.2
+199.6
+190.5
+205.9
+176.4
+182.8
+179.0
+199.9
+186.7
+188.8
+189.5
+189.8
+193.5
+203.0
+183.8
+204.2
+176.4
+206.5
+184.7
+174.9
+183.2
+174.4
+196.2
+179.1
+183.7
+180.2
+174.5
+185.5
+203.5
+174.7
+186.1
+175.1
+198.2
+200.3
+184.2
+180.4
+187.9
+194.3
+194.9
+181.3
+180.2
+186.0
+187.6
+180.2
+188.1
+196.5
+175.9
+184.0
+194.4
+186.3
+207.6
+188.1
+185.2
+207.5
+177.7
+204.2
+203.3
+180.8
+176.0
+181.5
+202.3
+191.6
+186.8
+183.0
+189.7
+191.2
+179.2
+196.2
+185.9
+184.3
+183.3
+186.8
+179.3
+188.6
+184.8
+176.7
+189.6
+177.5
+186.3
+197.2
+178.1
+189.8
+175.8
+178.9
+191.9
+176.9
+186.8
+186.6
+194.1
+179.3
+178.7
+183.9
+220.3
+181.6
+183.7
+196.7
+180.2
+176.9
+189.5
+187.5
+185.8
+184.3
+179.9
+186.0
+178.5
+179.5
+227.6
+187.1
+199.2
+183.4
+177.2
+181.3
+190.7
+197.8
+180.1
+203.4
+198.1
+196.8
+184.3
+188.5
+189.6
+187.9
+200.7
+187.1
+193.3
+183.0
+199.1
+187.3
+181.8
+184.0
+178.0
+198.7
+177.4
+195.6
+184.1
+183.4
+186.0
+183.1
+181.8
+190.6
+188.8
+184.8
+196.5
+186.2
+184.7
+182.6
+187.4
+194.1
+185.3
+181.6
+198.4
+190.1
+200.2
+214.9
+195.1
+192.9
+190.8
+179.4
+194.9
+206.0
+193.2
+186.6
+214.1
+216.1
+189.0
+184.2
+201.1
+182.1
+200.2
+188.5
+193.0
+184.8
+188.8
+186.6
+188.3
+193.9
+202.4
+196.8
+214.5
+202.1
+186.7
+197.3
+184.2
+186.5
+202.6
+187.3
+204.3
+176.1
+183.9
+189.2
+189.7
+180.8
+199.4
+185.7
+193.3
+192.7
+192.8
+204.9
+188.7
+196.5
+187.9
+177.4
+189.6
+177.2
+197.3
+183.9
+189.7
+184.6
+191.0
+195.4
+175.7
+179.1
+184.4
+193.2
+180.4
+191.2
+187.2
+181.1
+203.5
+189.2
+186.4
+176.3
+191.5
+193.6
+187.0
+186.8
+187.3
+184.8
+197.6
+207.3
+191.8
+187.1
+196.3
+206.9
+199.9
+186.3
+189.9
+179.8
+200.0
+193.8
+190.9
+180.9
+176.3
+189.1
+180.4
+190.9
+182.4
+182.8
+181.1
+193.9
+184.0
+185.9
+199.1
+199.5
+204.2
+181.9
+199.0
+184.1
+207.4
+194.8
+216.5
+188.4
+206.7
+199.2
+179.3
+186.7
+200.7
+186.3
+195.5
+204.9
+194.0
+187.5
+196.3
+181.4
+186.0
+197.8
+198.1
+196.1
+184.3
+183.3
+211.3
+192.8
+187.9
+183.4
+192.9
+187.4
+194.2
+177.1
+200.0
+199.3
+177.2
+180.1
+178.6
+182.3
+189.1
+191.3
+197.7
+187.6
+187.3
+203.9
+204.4
+180.4
+186.5
+188.4
+176.1
+206.5
+187.7
+194.1
+193.5
+203.7
+186.8
+200.1
+187.0
+203.3
+196.3
+208.1
+192.7
+202.1
+204.7
+183.9
+186.7
+198.2
+189.4
+186.4
+188.3
+195.2
+203.3
+202.3
+201.1
+180.1
+191.3
+180.4
+187.7
+193.4
+196.5
+208.8
+185.3
+186.8
+203.6
+179.0
+185.0
+181.1
+193.8
+196.9
+201.0
+187.3
+189.6
+190.8
+188.9
+197.6
+203.0
+193.2
+185.6
+195.6
+199.1
+191.8
+178.7
+183.6
+184.1
+192.0
+182.4
+183.8
+209.5
+205.7
+187.0
+187.6
+200.6
+187.1
+187.2
+184.9
+180.3
+189.4
+182.7
+196.8
+184.7
+185.8
+184.0
+194.1
+180.5
+199.1
+181.7
+205.3
+182.6
+186.8
+180.2
+181.9
+187.4
+187.9
+191.8
+204.8
+178.5
+181.6
+181.6
+179.5
+181.9
+190.1
+194.1
+180.0
+179.1
+181.2
+175.0
+186.3
+184.8
+182.6
+186.8
+185.3
+181.2
+190.0
+195.3
+186.9
+190.6
+187.3
+193.4
+176.9
+190.5
+194.6
+181.0
+175.8
+187.9
+183.2
+187.7
+191.4
+183.1
+178.6
+193.4
+185.5
+190.1
+194.6
+191.0
+177.7
+187.3
+201.3
+188.6
+182.5
+196.3
+176.2
+184.9
+189.6
+186.9
+227.9
+188.1
+185.0
+182.9
+196.4
+183.1
+179.8
+186.5
+185.2
+199.7
+187.7
+191.6
+191.9
+194.8
+183.9
+185.3
+188.2
+192.7
+180.8
+183.5
+199.8
+196.8
+184.2
+179.5
+204.2
+183.5
+175.4
+207.3
+192.5
+191.9
+208.6
+194.7
+195.2
+190.3
+180.8
+182.6
+203.2
+191.4
+189.7
+183.9
+185.7
+192.3
+190.7
+206.8
+182.9
+195.2
+193.1
+201.2
+177.0
+188.1
+182.3
+185.0
+198.5
+186.4
+183.9
+189.4
+181.8
+199.9
+198.7
+183.7
+191.8
+187.5
+209.4
+201.8
+178.0
+201.8
+198.0
+187.2
+185.5
+178.1
+195.7
+200.1
+174.8
+176.4
+187.6
+177.3
+178.1
+197.5
+183.7
+207.7
+180.6
+176.6
+190.8
+191.0
+180.1
+180.4
+178.8
+185.5
+194.8
+188.1
+185.8
+179.5
+181.0
+196.1
+192.6
+179.2
+180.7
+206.3
+175.9
+196.8
+179.4
+195.1
+187.5
+183.9
+183.8
+184.6
+208.6
+195.5
+202.9
+191.9
+187.0
+189.2
+178.5
+176.1
+186.6
+195.1
+181.6
+201.0
+192.1
+192.9
+202.2
+191.0
+205.8
+204.3
+191.8
+185.1
+190.1
+193.6
+192.4
+185.2
+180.6
+192.6
+185.7
+187.5
+189.2
+184.0
+189.1
+200.6
+185.2
+182.8
+182.0
+185.4
+178.3
+188.4
+193.2
+194.9
+197.1
+194.4
+184.8
+179.2
+195.6
+182.6
+204.3
+203.1
+180.0
+187.7
+187.7
+197.8
+178.7
+186.5
+194.6
+194.8
+176.4
+202.5
+195.4
+180.5
+220.0
+191.0
+217.1
+178.4
+196.1
+182.5
+190.8
+191.7
+202.0
+196.8
+199.4
+183.8
+183.2
+186.5
+178.1
+198.6
+186.1
+195.4
+196.6
+199.3
+196.5
+177.9
+186.3
+180.5
+198.9
+190.2
+188.5
+184.1
+188.6
+201.4
+185.4
+176.4
+182.0
+176.9
+184.4
+179.0
+178.4
+183.8
+190.9
+185.5
+184.4
+179.6
+197.5
+184.3
+179.1
+193.4
+194.2
+179.1
+189.0
+191.2
+180.8
+184.5
+187.9
+191.7
+177.2
+185.4
+183.6
+190.7
+187.5
+188.8
+199.7
+190.0
+176.0
+204.4
+191.1
+188.3
+182.1
+181.6
+191.4
+191.8
+193.1
+186.0
+188.1
+178.4
+193.7
+175.1
+179.0
+186.7
+185.3
+179.7
+182.7
+198.0
+180.2
+189.5
+183.1
+177.1
+182.3
+174.7
+175.6
+186.4
+189.2
+178.9
+180.4
+181.3
+182.3
+180.3
+180.8
+180.1
+197.2
+204.7
+181.6
+182.3
+184.0
+176.5
+214.9
+194.7
+178.3
+175.1
+174.3
+176.5
+190.9
+183.1
+176.8
+177.1
+183.8
+178.5
+189.2
+180.8
+177.6
+175.3
+185.3
+183.4
+175.2
+183.6
+177.0
+181.9
+180.3
+185.1
+179.1
+188.1
+184.4
+181.6
+188.0
+181.6
+190.6
+177.9
+182.7
+177.4
+183.2
+189.3
+187.6
+179.8
+192.3
+188.1
+194.6
+186.7
+181.6
+191.8
+195.6
+187.4
+185.5
+190.0
+184.0
+178.5
+178.0
+200.8
+199.2
+186.5
+174.9
+181.3
+181.0
+187.6
+186.8
+183.3
+183.8
+195.6
+190.6
+193.8
+176.4
+179.1
+182.9
+193.4
+184.4
+186.2
+180.3
+194.7
+182.6
+186.7
+196.4
+196.7
+196.2
+192.3
+189.1
+178.4
+182.3
+195.2
+184.1
+189.0
+184.7
+189.3
+184.1
+177.3
+189.5
+186.1
+186.4
+180.5
+196.1
+187.5
+188.4
+195.8
+192.0
+192.9
+182.4
+199.6
+189.6
+180.2
+176.1
+220.4
+206.6
+195.5
+176.5
+202.2
+187.5
+185.0
+205.6
+195.7
+192.8
+192.8
+174.9
+187.3
+181.9
+180.4
+183.9
+187.6
+192.5
+202.1
+185.2
+200.2
+184.1
+189.9
+194.7
+185.1
+191.9
+176.0
+197.7
+177.5
+178.6
+179.3
+206.0
+193.3
+196.2
+179.3
+181.3
+205.5
+178.1
+186.9
+183.5
+178.5
+193.1
+179.4
+186.1
+174.3
+194.4
+187.1
+195.8
+176.0
+187.6
+176.1
+183.7
+182.0
+183.7
+176.3
+201.0
+185.8
+180.6
+186.1
+180.1
+204.4
+186.1
+182.9
+181.7
+184.6
+177.3
+186.2
+188.7
+180.5
+182.1
+185.3
+177.7
+185.4
+188.2
+194.1
+185.6
+177.8
+192.7
+198.1
+196.0
+187.5
+186.4
+206.8
+207.4
+177.3
+181.3
+185.0
+175.7
+174.2
+191.5
+194.0
+190.8
+199.2
+176.4
+202.7
+193.1
+182.6
+186.0
+196.0
+186.1
+189.4
+203.4
+184.8
+194.3
+187.2
+183.5
+199.4
+196.7
+180.2
+195.2
+180.9
+182.7
+193.5
+200.0
+193.3
+184.4
+199.1
+206.2
+199.0
+195.8
+189.4
+194.1
+196.4
+201.5
+180.7
+189.7
+193.3
+200.3
+205.5
+189.9
+192.6
+187.1
+205.7
+194.9
+188.0
+189.2
+197.9
+188.5
+185.0
+182.7
+205.8
+186.7
+189.2
+185.1
+182.4
+205.1
+181.4
+185.6
+188.5
+195.9
+199.8
+185.4
+194.7
+201.2
+185.0
+202.1
+200.0
+181.9
+186.8
+195.9
+182.2
+186.6
+187.1
+184.9
+181.1
+190.1
+191.0
+186.7
+184.6
+181.8
+190.4
+185.7
+183.4
+187.0
+182.6
+183.5
+173.9
+181.8
+194.1
+182.2
+182.6
+181.8
+187.0
+192.2
+178.9
+206.9
+197.1
+180.7
+183.4
+176.7
+187.9
+194.5
+182.0
+182.0
+190.9
+177.9
+180.3
+190.8
+187.7
+179.1
+187.9
+183.7
+187.9
+183.5
+175.9
+186.5
+178.2
+180.7
+194.3
+184.8
+181.4
+182.8
+179.8
+187.0
+179.1
+178.8
+187.2
+185.9
+182.0
+187.7
+181.3
+186.0
+181.0
+183.2
+186.5
+189.3
+187.8
+182.8
+179.4
+199.0
+180.9
+193.8
+202.6
+185.8
+177.5
+182.7
+202.4
+213.8
+190.6
+189.8
+190.5
+185.0
+183.7
+191.0
+185.7
+182.2
+194.8
+183.1
+191.8
+186.6
+185.2
+181.7
+182.1
+185.7
+195.0
+181.5
+185.1
+181.5
+187.3
+186.5
+188.7
+185.2
+180.7
+178.3
+191.8
+185.4
+188.6
+184.5
+187.5
+191.5
+191.5
+195.8
+197.9
+194.2
+184.9
+182.7
+182.1
+185.6
+188.4
+178.8
+190.4
+179.3
+186.2
+206.0
+191.2
+195.1
+198.4
+189.9
+180.8
+181.1
+198.5
+203.4
+190.8
+177.1
+184.0
+181.0
+192.2
+175.9
+200.4
+192.2
+181.1
+202.0
+198.6
+203.6
+191.7
+193.4
+192.6
+186.5
+182.4
+195.8
+184.9
+191.0
+176.7
+182.7
+194.8
+181.9
+191.1
+205.5
+176.3
+200.1
+184.1
+208.8
+184.3
+196.2
+190.2
+197.1
+186.8
+181.7
+188.5
+188.6
+187.2
+195.3
+198.6
+205.7
+185.4
+187.5
+176.2
+186.1
+199.4
+294.6
+232.7
+200.5
+181.8
+189.1
+186.9
+190.7
+179.7
+181.3
+181.5
+200.5
+181.2
+181.7
+185.4
+195.5
+201.9
+199.7
+194.2
+179.6
+181.4
+205.5
+183.1
+499.4
+188.5
+184.5
+194.5
+195.5
+193.6
+189.0
+196.4
+188.3
+186.7
+190.6
+176.9
+188.1
+189.4
+198.2
+178.8
+196.5
+203.1
+192.3
+193.8
+188.4
+180.2
+192.3
+187.1
+195.2
+181.6
+185.5
+190.9
+185.2
+200.5
+190.0
+180.2
+194.4
+196.5
+187.7
+184.3
+189.5
+188.2
+189.5
+194.5
+192.4
+202.1
+177.9
+193.8
+191.7
+182.2
+190.1
+197.4
+196.6
+193.7
+195.9
+200.9
+178.8
+181.4
+193.6
+193.5
+177.7
+192.9
+190.9
+201.2
+184.9
+175.8
+197.9
+202.4
+192.3
+202.5
+198.3
+196.8
+185.2
+184.8
+198.1
+191.3
+174.0
+180.5
+193.4
+183.0
+185.1
+177.0
+190.4
+181.3
+179.0
+180.6
+179.1
+188.8
+207.2
+183.7
+186.6
+198.3
+176.6
+186.5
+187.4
+179.5
+189.2
+197.7
+181.5
+194.6
+195.5
+181.6
+197.3
+190.7
+202.0
+183.9
+193.0
+183.9
+196.9
+203.5
+189.4
+187.1
+185.2
+184.8
+199.8
+175.7
+180.6
+187.6
+185.4
+178.5
+185.5
+178.7
+187.5
+185.7
+191.9
+180.2
+186.7
+183.0
+196.7
+197.6
+197.1
+187.4
+194.8
+194.7
+191.0
+185.1
+187.0
+185.8
+197.6
+204.2
+187.9
+189.9
+217.5
+202.8
+192.4
+176.4
+178.8
+199.8
+192.5
+199.9
+190.8
+202.8
+187.0
+180.2
+196.6
+176.5
+187.6
+181.3
+191.0
+180.6
+184.7
+195.8
+191.5
+182.7
+181.3
+189.3
+182.5
+184.2
+187.9
+202.4
+195.7
+189.5
+195.4
+201.2
+186.1
+208.8
+199.6
+189.4
+180.5
+194.2
+184.5
+187.5
+196.6
+188.0
+210.1
+193.3
+187.6
+191.6
+188.5
+179.2
+192.5
+189.7
+181.6
+180.9
+187.9
+180.6
+181.1
+178.7
+175.7
+176.2
+187.1
+185.0
+186.9
+186.5
+190.0
+185.6
+201.2
+189.3
+189.3
+189.7
+188.2
+187.2
+175.0
+185.5
+185.0
+205.2
+187.3
+177.2
+179.2
+190.2
+183.0
+192.9
+183.2
+185.4
+179.2
+186.2
+183.1
+183.8
+186.6
+185.7
+192.4
+179.6
+196.2
+180.2
+185.9
+197.4
+192.2
+177.5
+180.7
+190.2
+176.9
+193.6
+186.2
+184.9
+182.0
+186.1
+187.4
+184.3
+196.2
+203.1
+180.6
+192.5
+196.2
+195.9
+181.6
+189.9
+183.3
+192.7
+187.3
+191.0
+200.2
+180.1
+187.0
+175.7
+179.9
+193.1
+190.2
+202.1
+174.1
+179.5
+185.1
+182.7
+196.7
+179.8
+186.7
+192.5
+189.2
+193.7
+184.1
+185.6
+184.6
+193.6
+181.0
+189.2
+179.9
+181.4
+183.7
+184.5
+193.1
+191.5
+182.9
+190.2
+185.2
+185.9
+216.6
+184.1
+195.7
+181.2
+182.8
+199.3
+187.4
+184.9
+177.6
+206.2
+184.1
+201.5
+200.7
+192.2
+187.0
+182.1
+196.6
+181.6
+189.2
+191.6
+188.8
+186.9
+193.6
+184.2
+191.3
+194.9
+194.6
+188.7
+179.2
+205.1
+181.0
+177.3
+183.7
+184.8
+183.7
+204.3
+190.7
+181.6
+191.6
+189.5
+195.5
+176.0
+193.0
+192.1
+195.9
+205.8
+192.2
+190.6
+198.3
+185.8
+186.7
+198.6
+184.2
+203.1
+199.6
+187.0
+204.3
+207.2
+190.8
+196.7
+188.8
+193.2
+188.7
+199.8
+204.3
+188.6
+192.0
+209.8
+180.3
+203.7
+196.4
+185.7
+182.1
+193.3
+200.9
+196.6
+181.9
+182.2
+179.2
+197.3
+189.3
+200.9
+184.5
+185.8
+185.6
+187.5
+187.5
+189.5
+186.9
+187.2
+195.7
+182.0
+179.0
+191.4
+188.5
+177.7
+185.7
+182.0
+189.0
+198.7
+188.4
+186.6
+180.4
+185.1
+184.2
+207.6
+184.2
+190.0
+181.5
+190.9
+191.2
+187.8
+177.8
+182.7
+191.8
+206.2
+201.4
+182.2
+189.2
+195.4
+187.9
+195.7
+190.4
+183.3
+183.2
+178.2
+189.0
+179.2
+175.5
+187.3
+181.1
+180.1
+188.1
+184.4
+183.9
+200.2
+185.4
+180.4
+184.0
+183.8
+181.2
+203.6
+201.1
+197.1
+192.3
+182.7
+182.4
+183.0
+177.3
+181.3
+185.3
+184.6
+188.7
+179.1
+179.1
+184.6
+176.0
+177.8
+186.9
+189.8
+180.3
+177.2
+184.2
+179.2
+177.4
+182.0
+186.6
+188.7
+194.2
+184.0
+178.9
+175.3
+191.6
+182.9
+188.3
+178.7
+201.6
+186.4
+180.8
+184.7
+186.7
+196.9
+177.7
+178.8
+175.3
+182.7
+184.0
+182.4
+196.6
+174.7
+178.0
+181.5
+198.1
+185.1
+177.5
+181.2
+188.1
+196.0
+183.6
+196.2
+185.9
+193.2
+186.7
+179.7
+195.3
+190.1
+206.8
+186.2
+178.5
+192.0
+187.6
+176.9
+191.8
+190.8
+193.6
+200.0
+192.3
+178.0
+184.6
+179.4
+178.2
+183.2
+179.9
+184.9
+183.7
+189.1
+176.4
+185.5
+184.8
+192.5
+185.3
+193.9
+179.6
+181.7
+179.1
+210.8
+185.6
+184.2
+181.3
+198.8
+180.2
+182.5
+191.9
+196.4
+186.6
+185.3
+182.5
+175.8
+182.0
+186.9
+186.6
+176.0
+175.4
+186.5
+175.4
+182.4
+199.5
+192.7
+182.0
+199.9
+181.1
+196.7
+186.2
+182.0
+175.9
+184.4
+183.8
+178.4
+191.3
+177.8
+196.1
+200.8
+200.6
+191.1
+190.4
+180.3
+200.6
+201.4
+181.3
+176.9
+183.2
+185.0
+178.4
+187.8
+172.7
+182.8
+186.3
+193.9
+179.2
+194.9
+176.8
+188.1
+190.7
+192.3
+178.4
+191.9
+196.6
+183.5
+197.8
+192.0
+176.0
+192.1
+191.5
+193.3
+209.5
+185.2
+187.6
+186.3
+190.9
+183.9
+190.2
+186.3
+184.7
+177.3
+185.0
+178.3
+193.0
+181.7
+190.9
+185.3
+174.9
+192.3
+194.5
+194.1
+177.0
+185.2
+193.2
+202.4
+202.0
+197.2
+199.3
+176.9
+185.7
+188.1
+186.8
+184.4
+204.5
+196.4
+181.3
+190.9
+177.2
+192.4
+179.9
+177.0
+194.1
+203.5
+189.1
+194.7
+181.5
+183.6
+199.8
+187.3
+185.1
+195.7
+177.8
+178.3
+189.8
+200.1
+176.9
+177.9
+188.9
+190.9
+198.7
+197.7
+186.9
+190.8
+201.4
+188.5
+180.4
+196.2
+185.8
+194.9
+193.3
+181.3
+195.4
+197.5
+201.3
+179.5
+177.7
+201.9
+199.9
+183.5
+177.6
+190.4
+183.7
+178.4
+203.6
+195.9
+199.5
+193.2
+175.0
+184.5
+190.6
+194.3
+178.2
+177.5
+188.4
+179.7
+183.8
+174.2
+186.5
+179.8
+196.9
+194.9
+178.4
+185.7
+190.1
+181.3
+180.0
+179.9
+181.7
+187.4
+185.9
+178.9
+184.6
+189.5
+186.6
+185.8
+188.4
+188.3
+174.7
+184.9
+192.5
+185.3
+174.9
+180.6
+179.5
+183.4
+177.7
+177.5
+187.4
+183.7
+188.0
+177.6
+180.7
+180.9
+181.9
+188.9
+201.1
+186.6
+176.3
+177.3
+177.6
+175.6
+201.5
+175.5
+188.2
+181.8
+176.9
+185.3
+179.6
+192.7
+203.8
+183.4
+179.4
+177.9
+182.8
+182.3
+174.9
+180.5
+180.2
+187.1
+190.4
+185.5
+184.6
+185.4
+190.3
+183.9
+180.0
+201.8
+198.1
+184.2
+192.8
+184.2
+195.7
+191.4
+188.8
+192.9
+184.8
+174.3
+191.8
+185.4
+183.8
+192.7
+192.6
+194.8
+177.9
+178.7
+181.5
+177.3
+192.5
+176.0
+178.9
+199.7
+181.0
+178.8
+177.1
+183.0
+176.7
+182.3
+180.9
+183.1
+183.9
+174.2
+178.7
+173.8
+174.4
+179.1
+178.4
+175.4
+181.4
+180.9
+178.2
+182.5
+184.2
+183.7
+182.6
+176.2
+177.2
+180.5
+183.9
+193.4
+174.9
+178.8
+176.0
+178.1
+176.1
+177.3
+178.7
+182.2
+191.0
+186.8
+188.4
+180.3
+177.8
+198.8
+176.7
+198.3
+178.5
+177.9
+179.9
+186.7
+178.0
+175.4
+186.6
+190.8
+190.7
+183.5
+196.3
+182.1
+184.4
+188.8
+186.3
+184.8
+191.3
+186.2
+191.0
+188.3
+178.8
+176.3
+181.2
+183.1
+180.4
+178.6
+176.2
+176.1
+177.5
+180.6
+178.1
+176.4
+195.4
+178.8
+179.6
+178.1
+179.7
+178.3
+181.9
+177.4
+181.4
+186.7
+174.6
+179.4
+177.5
+197.6
+196.5
+193.1
+179.2
+182.2
+178.1
+186.3
+182.1
+175.4
+177.5
+188.1
+182.9
+179.8
+179.2
+180.0
+176.9
+177.4
+192.5
+177.5
+177.4
+181.8
+175.6
+178.9
+180.6
+176.8
+182.8
+186.7
+182.5
+188.3
+183.7
+185.1
+195.4
+179.2
+180.7
+190.0
+187.5
+180.3
+183.0
+184.2
+184.3
+189.4
+180.1
+179.6
+182.3
+177.6
+186.3
+188.0
+182.1
+187.1
+186.2
+184.2
+202.3
+187.0
+188.0
+180.5
+194.6
+188.8
+190.7
+181.9
+182.1
+185.3
+183.1
+197.3
+195.0
+208.3
+195.7
+179.1
+196.3
+185.5
+185.4
+186.9
+193.9
+187.1
+184.2
+180.0
+184.8
+189.6
+192.3
+185.6
+185.8
+185.9
+187.9
+186.2
+177.3
+192.2
+201.9
+185.9
+181.7
+192.8
+185.4
+187.3
+187.5
+178.9
+187.6
+181.3
+186.9
+179.6
+179.3
+189.4
+177.5
+184.5
+176.4
+193.3
+178.7
+191.6
+182.7
+194.6
+187.4
+190.4
+199.1
+191.6
+193.1
+187.0
+181.7
+180.3
+196.6
+180.5
+180.0
+184.9
+176.9
+176.3
+179.3
+189.1
+180.4
+176.0
+174.9
+181.2
+177.6
+177.4
+177.6
+179.0
+178.7
+178.7
+184.8
+177.9
+179.4
+183.8
+181.9
+177.1
+175.8
+182.9
+183.2
+176.8
+181.7
+183.7
+192.1
+178.2
+181.4
+180.0
+176.8
+179.8
+194.3
+181.6
+180.7
+177.5
+187.4
+179.0
+180.0
+186.0
+182.0
+175.4
+178.9
+181.9
+192.0
+179.5
+178.5
+175.3
+184.1
+178.3
+177.1
+186.2
+196.9
+182.0
+178.0
+181.4
+181.4
+179.6
+180.2
+180.4
+183.1
+176.6
+182.9
+176.3
+191.5
+179.9
+178.1
+178.4
+189.3
+196.8
+188.2
+180.5
+185.2
+188.0
+176.3
+181.0
+187.4
+178.9
+179.1
+185.3
+183.4
+185.8
+178.5
+176.1
+188.9
+200.9
+193.9
+193.4
+184.0
+187.2
+178.1
+193.5
+182.0
+180.1
+182.8
+176.8
+177.0
+184.0
+185.2
+197.3
+180.8
+190.4
+177.3
+183.4
+191.3
+175.4
+176.8
+192.3
+177.3
+191.1
+185.1
+179.7
+190.8
+175.9
+181.2
+194.0
+176.5
+180.3
+175.1
+185.1
+181.3
+200.0
+187.0
+182.6
+182.0
+176.8
+190.1
+182.1
+181.7
+192.3
+177.0
+185.1
+181.3
+179.2
+182.1
+176.6
+185.0
+177.4
+183.5
+196.5
+184.2
+179.2
+180.5
+180.8
+181.9
+176.9
+178.6
+175.9
+192.0
+198.7
+189.6
+190.5
+186.3
+192.6
+196.0
+176.2
+175.9
+176.5
+195.4
+174.8
+177.4
+186.7
+174.9
+176.1
+183.0
+178.1
+177.4
+192.9
+179.7
+198.5
+178.1
+178.6
+195.4
+181.6
+179.9
+179.5
+186.4
+180.6
+180.3
+187.3
+193.9
+192.5
+178.1
+184.8
+194.9
+194.6
+181.4
+177.6
+180.4
+175.5
+180.5
+177.2
+186.6
+176.8
+176.3
+186.0
+179.8
+178.2
+177.9
+178.9
+190.1
+180.8
+180.1
+181.3
+193.5
+185.0
+198.7
+199.9
+183.4
+177.3
+176.2
+182.7
+178.2
+175.2
+177.7
+186.9
+182.1
+195.1
+181.3
+182.4
+175.7
+185.5
+180.8
+181.4
+181.6
+190.1
+183.7
+186.4
+194.3
+176.4
+176.7
+189.9
+175.6
+206.6
+178.9
+180.0
+178.8
+180.1
+184.1
+176.3
+177.9
+194.3
+188.5
+182.6
+178.6
+177.5
+190.9
+178.9
+180.3
+193.9
+187.4
+186.4
+190.1
+184.7
+199.2
+187.9
+177.7
+180.5
+189.4
+176.5
+192.8
+181.4
+178.0
+193.6
+175.8
+184.4
+184.1
+191.9
+176.2
+184.5
+180.4
+185.1
+180.0
+192.6
+187.6
+190.2
+183.0
+176.5
+194.4
+178.4
+184.5
+176.6
+179.0
+182.6
+187.2
+180.4
+183.3
+180.8
+179.1
+193.9
+181.9
+188.8
+180.0
+175.2
+194.5
+194.8
+187.7
+182.5
+199.3
+175.0
+175.9
+177.9
+179.6
+179.4
+176.0
+176.4
+175.8
+177.2
+176.8
+179.0
+180.4
+177.1
+174.9
+177.9
+180.6
+187.3
+176.6
+177.3
+184.4
+179.2
+177.2
+175.7
+183.3
+177.4
+180.7
+175.6
+173.6
+179.6
+177.2
+180.8
+176.4
+187.7
+178.4
+176.0
+192.5
+181.4
+183.5
+178.5
+186.6
+179.4
+180.1
+181.6
+178.3
+178.2
+184.1
+180.6
+178.3
+184.1
+181.6
+177.4
+183.4
+180.6
+178.8
+179.0
+175.1
+177.0
+180.8
+179.9
+177.7
+176.3
+177.3
+184.2
+176.9
+190.8
+183.0
+181.0
+184.0
+178.6
+175.2
+177.2
+177.9
+183.2
+180.9
+176.3
+177.0
+182.8
+178.5
+176.9
+178.1
+175.5
+184.7
+176.6
+177.2
+182.4
+189.5
+197.3
+179.9
+190.4
+182.4
+188.7
+185.5
+181.1
+182.6
+187.0
+184.4
+179.1
+180.1
+180.9
+187.2
+175.9
+194.1
+177.9
+177.4
+200.7
+201.4
+183.5
+180.8
+195.5
+181.0
+180.2
+181.5
+175.9
+175.3
+180.1
+182.1
+180.1
+176.5
+178.9
+200.5
+178.6
+176.6
+175.8
+175.8
+176.0
+199.3
+177.5
+180.9
+185.1
+200.3
+194.0
+177.7
+181.2
+182.1
+185.1
+183.4
+180.7
+188.8
+187.3
+179.1
+177.5
+185.5
+185.5
+177.2
+175.3
+178.6
+184.5
+180.0
+179.9
+177.1
+178.0
+187.8
+183.6
+189.6
+181.5
+179.7
+179.0
+188.8
+185.1
+177.0
+177.1
+176.3
+176.4
+199.6
+178.1
+188.2
+176.9
+191.5
+194.0
+182.6
+184.0
+186.4
+184.1
+180.0
+181.1
+187.3
+176.1
+174.5
+189.3
+188.9
+178.7
+177.2
+191.0
+194.3
+187.7
+177.0
+176.4
+183.3
+176.2
+181.3
+181.0
+176.8
+187.8
+178.2
+202.3
+191.6
+180.3
+174.8
+187.8
+176.9
+176.9
+181.1
+177.5
+186.1
+179.3
+187.2
+176.6
+178.6
+178.7
+179.0
+188.7
+188.5
+183.8
+192.2
+178.8
+180.4
+182.1
+177.0
+175.1
+180.1
+176.7
+176.0
+188.1
+191.3
+186.6
+202.0
+182.5
+175.9
+182.1
+195.6
+174.8
+194.5
+181.2
+191.8
+180.4
+178.7
+186.1
+180.9
+186.4
+177.0
+183.6
+179.7
+208.1
+179.4
+179.1
+178.5
+179.2
+180.7
+183.3
+180.4
+187.6
+176.9
+185.4
+187.5
+184.3
+183.5
+181.0
+190.8
+178.6
+180.0
+180.4
+191.0
+182.8
+186.9
+179.6
+189.1
+186.9
+202.3
+181.1
+192.1
+194.3
+176.7
+185.8
+177.3
+179.5
+177.2
+177.8
+190.5
+190.1
+184.1
+191.8
+178.7
+185.9
+178.3
+177.4
+176.4
+177.8
+179.0
+177.6
+179.7
+178.1
+190.5
+177.8
+180.4
+181.6
+181.3
+177.3
+182.1
+189.7
+178.2
+179.7
+181.3
+178.8
+194.0
+200.6
+179.6
+177.9
+181.0
+184.7
+180.8
+181.1
+187.8
+183.4
+186.2
+181.8
+175.8
+176.8
+176.3
+177.0
+186.7
+178.5
+183.0
+177.1
+182.3
+182.2
+180.6
+180.6
+185.2
+178.9
+192.3
+177.6
+212.1
+180.5
+186.4
+187.4
+196.4
+181.9
+194.5
+188.5
+179.4
+179.3
+181.4
+185.7
+176.9
+182.6
+185.0
+181.7
+178.9
+184.4
+180.4
+189.2
+187.5
+204.6
+189.7
+182.6
+182.1
+180.0
+182.3
+180.1
+189.3
+178.1
+183.8
+180.9
+184.8
+182.2
+177.7
+176.1
+197.5
+195.8
+183.3
+183.4
+199.0
+205.4
+183.6
+182.5
+182.2
+199.1
+187.1
+179.5
+182.9
+180.2
+177.5
+183.4
+189.4
+196.2
+181.2
+183.3
+178.6
+181.5
+185.5
+179.8
+184.3
+198.8
+184.9
+178.0
+177.7
+181.0
+186.0
+196.3
+178.1
+192.4
+178.7
+181.4
+192.5
+181.4
+192.5
+182.2
+178.9
+183.9
+185.3
+192.2
+186.0
+193.4
+178.4
+194.5
+179.9
+187.6
+185.9
+179.2
+185.0
+188.1
+189.4
+187.9
+190.0
+191.9
+187.5
+186.0
+189.6
+182.4
+185.6
+187.0
+192.4
+183.3
+189.2
+194.6
+195.9
+190.7
+186.4
+185.9
+185.3
+177.1
+184.6
+186.4
+183.1
+186.2
+180.0
+181.8
+184.7
+176.6
+178.0
+178.5
+182.2
+200.3
+184.2
+186.9
+178.2
+190.3
+194.9
+193.1
+193.5
+188.5
+177.9
+185.4
+177.6
+179.7
+193.0
+200.3
+182.0
+192.3
+183.3
+183.6
+183.2
+190.8
+182.6
+177.7
+177.1
+183.7
+177.5
+175.4
+180.3
+181.3
+178.8
+182.3
+176.5
+177.4
+176.0
+178.8
+184.3
+183.0
+180.2
+184.9
+188.1
+187.6
+182.8
+183.0
+180.0
+177.5
+196.1
+185.1
+175.5
+175.9
+178.6
+201.5
+177.1
+191.7
+184.8
+176.2
+184.4
+175.9
+182.6
+182.0
+180.8
+178.7
+191.3
+179.5
+178.7
+174.8
+185.1
+174.9
+189.8
+191.3
+191.9
+190.7
+175.8
+177.3
+183.6
+178.0
+185.1
+177.2
+178.6
+188.7
+179.0
+186.0
+202.6
+177.2
+179.8
+196.0
+195.8
+186.8
+183.6
+202.2
+182.0
+175.8
+186.6
+178.2
+181.3
+180.8
+183.6
+181.9
+192.4
+190.8
+179.2
+184.6
+186.2
+183.0
+196.5
+179.6
+183.4
+187.2
+179.5
+185.3
+182.2
+177.8
+176.6
+177.2
+179.3
+177.1
+181.9
+178.9
+180.2
+179.8
+175.0
+176.8
+180.5
+175.7
+176.3
+180.6
+181.6
+176.6
+175.4
+177.4
+176.2
+180.8
+188.0
+178.2
+205.6
+177.8
+176.5
+179.4
+176.4
+183.3
+177.9
+174.2
+176.1
+182.8
+180.8
+186.9
+179.4
+195.8
+178.8
+181.6
+199.7
+175.8
+186.7
+179.1
+182.4
+179.1
+176.3
+180.5
+174.7
+180.8
+178.2
+185.2
+183.3
+192.7
+184.9
+205.8
+177.0
+182.5
+187.3
+185.9
+184.3
+177.0
+193.2
+179.0
+177.5
+181.3
+176.2
+178.8
+197.3
+180.8
+180.8
+189.7
+188.3
+179.5
+179.3
+185.3
+184.8
+192.3
+180.4
+186.7
+180.0
+178.9
+177.9
+179.4
+177.3
+181.9
+175.4
+174.1
+180.2
+176.9
+178.6
+177.5
+176.4
+177.7
+180.6
+182.2
+178.0
+179.3
+176.9
+188.6
+180.4
+179.8
+180.7
+177.2
+176.7
+175.3
+176.4
+178.4
+177.8
+180.1
+193.5
+181.0
+183.6
+177.9
+181.9
+186.9
+197.3
+175.7
+177.6
+184.7
+179.6
+182.2
+181.9
+181.2
+176.7
+185.5
+196.8
+185.9
+182.4
+178.1
+180.2
+178.3
+181.7
+186.4
+175.6
+182.6
+179.6
+181.0
+188.0
+178.1
+176.9
+181.0
+179.6
+185.0
+177.7
+179.4
+178.0
+185.7
+179.0
+194.2
+175.4
+178.5
+175.0
+176.7
+179.3
+188.8
+181.0
+177.3
+188.0
+178.5
+180.0
+175.9
+180.7
+181.2
+177.0
+179.2
+179.3
+193.1
+176.0
+179.4
+185.4
+177.4
+177.2
+177.9
+176.4
+186.7
+178.0
+178.7
+180.5
+186.7
+176.4
+185.8
+182.5
+177.3
+180.3
+179.5
+174.6
+184.2
+184.0
+176.3
+176.0
+211.4
+184.5
+182.1
+180.5
+188.1
+177.9
+179.7
+177.5
+184.5
+180.6
+191.1
+187.4
+176.3
+180.4
+183.2
+184.8
+178.6
+189.8
+180.9
+176.6
+176.9
+176.4
+182.0
+178.2
+177.3
+189.9
+191.1
+178.0
+179.5
+176.7
+194.2
+211.7
+175.9
+177.9
+176.0
+179.7
+180.6
+176.4
+180.3
+177.2
+183.6
+180.6
+187.4
+189.4
+189.6
+177.1
+179.4
+184.3
+186.4
+181.9
+180.1
+184.8
+182.0
+188.2
+181.4
+184.8
+177.5
+180.3
+195.4
+185.7
+176.8
+176.0
+187.3
+202.2
+194.5
+181.1
+182.9
+197.6
+186.9
+181.3
+180.9
+183.9
+191.0
+182.5
+186.9
+179.7
+179.2
+185.6
+184.3
+180.4
+190.9
+183.6
+183.1
+175.0
+180.9
+186.3
+174.8
+176.4
+176.1
+175.7
+178.1
+178.0
+180.5
+182.6
+180.0
+179.2
+186.8
+188.3
+179.8
+179.6
+180.5
+176.9
+204.2
+195.2
+180.2
+177.9
+187.9
+191.3
+183.9
+185.3
+186.8
+182.5
+189.5
+176.8
+180.5
+189.3
+185.9
+178.0
+176.0
+179.2
+181.3
+175.6
+179.9
+179.4
+183.8
+178.8
+177.9
+180.1
+182.3
+180.9
+175.7
+178.1
+173.7
+177.9
+180.0
+176.2
+177.7
+179.4
+192.3
+175.7
+185.7
+188.6
+195.5
+182.5
+184.1
+177.9
+177.8
+183.0
+174.9
+174.9
+179.7
+186.4
+185.9
+175.2
+179.2
+176.8
+181.5
+178.3
+180.6
+176.8
+175.8
+175.9
+180.3
+179.7
+175.8
+174.1
+180.6
+176.7
+179.7
+175.7
+179.2
+178.3
+203.4
+177.9
+186.9
+175.7
+177.0
+178.8
+180.2
+180.1
+185.5
+175.0
+178.6
+183.3
+177.2
+178.3
+180.1
+175.9
+175.6
+179.3
+179.8
+178.0
+186.5
+176.4
+187.2
+181.6
+175.4
+177.5
+179.1
+179.2
+175.9
+174.3
+175.5
+177.4
+178.5
+182.0
+181.4
+184.4
+183.4
+174.4
+177.5
+175.0
+175.4
+174.1
+182.8
+184.9
+178.3
+182.9
+182.3
+186.6
+180.9
+191.6
+185.6
+184.8
+190.2
+175.8
+185.2
+175.5
+176.9
+176.7
+176.9
+176.5
+179.2
+179.9
+179.2
+177.8
+188.9
+180.3
+186.8
+182.0
+180.8
+176.6
+180.5
+175.3
+185.0
+181.1
+181.1
+176.5
+176.5
+178.0
+175.9
+180.2
+178.3
+188.0
+185.9
+178.6
+179.5
+187.6
+180.2
+181.3
+182.5
+182.8
+175.0
+184.9
+181.7
+178.5
+177.5
+177.1
+176.8
+179.0
+179.2
+175.5
+177.1
+178.9
+174.8
+179.0
+177.3
+179.2
+178.2
+177.5
+191.8
+177.6
+182.1
+178.7
+177.2
+182.5
+181.5
+185.6
+179.5
+183.9
+177.6
+179.6
+193.9
+175.9
+216.0
+180.4
+177.3
+178.8
+176.7
+177.3
+178.3
+188.8
+175.8
+175.7
+176.0
+178.3
+185.1
+179.0
+191.3
+180.0
+178.8
+189.8
+182.7
+176.7
+178.0
+184.2
+185.0
+190.3
+178.0
+178.7
+177.4
+186.0
+176.4
+179.6
+178.0
+182.2
+175.9
+175.7
+178.3
+174.7
+193.6
+177.2
+186.6
+183.7
+181.0
+192.1
+176.4
+188.6
+205.9
+199.0
+194.6
+193.4
+190.4
+178.1
+188.0
+183.8
+180.5
+180.5
+190.5
+194.5
+180.8
+185.3
+193.4
+183.1
+178.1
+197.4
+190.2
+181.0
+184.9
+189.0
+184.6
+183.7
+187.7
+187.9
+182.5
+192.6
+184.3
+178.7
+179.8
+196.8
+184.4
+195.5
+183.9
+186.6
+181.0
+188.7
+185.0
+197.1
+204.2
+176.8
+179.6
+185.3
+182.1
+178.6
+180.6
+183.0
+199.1
+180.5
+180.7
+189.2
+194.5
+187.1
+192.3
+179.3
+196.8
+197.3
+180.3
+177.0
+178.8
+183.2
+183.1
+176.6
+201.6
+183.8
+182.7
+177.0
+185.2
+180.1
+199.4
+203.2
+187.4
+185.7
+184.1
+183.0
+198.7
+186.3
+187.9
+199.4
+185.9
+190.3
+202.8
+204.6
+178.1
+187.6
+196.5
+180.3
+181.7
+182.9
+199.2
+198.8
+190.5
+219.6
+180.0
+203.5
+182.0
+184.1
+176.8
+200.9
+201.9
+180.9
+177.9
+183.4
+179.5
+197.8
+188.0
+204.1
+185.6
+179.3
+190.6
+184.3
+195.2
+188.0
+200.9
+187.7
+183.0
+180.9
+180.0
+184.7
+176.8
+181.9
+180.6
+180.0
+177.7
+182.2
+175.2
+182.9
+186.3
+203.4
+183.3
+175.8
+186.2
+179.8
+178.4
+178.2
+420.1
+177.9
+193.8
+183.8
+180.0
+188.8
+187.2
+192.0
+180.8
+182.3
+191.8
+183.2
+176.6
+179.5
+215.2
+193.8
+188.3
+180.3
+186.4
+181.8
+205.8
+175.1
+186.5
+205.9
+185.5
+187.1
+191.7
+189.4
+188.3
+180.1
+179.8
+178.9
+179.3
+181.9
+184.9
+188.4
+176.2
+177.0
+181.8
+179.5
+177.3
+176.0
+208.3
+185.6
+180.6
+180.3
+195.0
+177.7
+175.7
+214.3
+188.2
+177.7
+187.1
+197.1
+184.8
+178.2
+182.1
+178.4
+176.3
+190.6
+187.3
+177.8
+177.2
+191.7
+200.2
+188.4
+177.3
+177.0
+178.5
+180.3
+177.0
+182.2
+184.4
+175.8
+185.6
+179.6
+180.8
+179.9
+199.4
+175.1
+196.3
+185.5
+182.5
+179.4
+191.2
+192.4
+180.3
+186.8
+194.7
+180.5
+178.0
+180.8
+184.9
+180.7
+179.0
+184.5
+180.5
+176.2
+182.9
+178.8
+183.5
+194.6
+182.0
+198.5
+176.5
+185.0
+187.6
+196.9
+194.2
+185.0
+190.8
+179.6
+175.8
+198.5
+180.0
+177.9
+203.3
+183.3
+181.4
+183.8
+176.5
+175.1
+193.9
+178.9
+183.0
+180.2
+178.0
+178.9
+188.1
+181.3
+179.3
+181.6
+183.1
+175.0
+178.8
+175.7
+179.4
+180.6
+185.9
+176.7
+190.8
+189.6
+180.1
+176.7
+181.7
+180.7
+191.9
+175.5
+180.1
+185.1
+182.0
+181.0
+184.3
+196.6
+188.8
+191.0
+184.2
+184.1
+180.1
+189.0
+178.2
+182.6
+190.4
+182.5
+186.3
+182.9
+191.5
+183.6
+193.2
+183.3
+201.4
+215.5
+182.4
+196.6
+183.6
+190.7
+180.8
+190.9
+178.2
+189.8
+194.8
+180.9
+184.2
+184.1
+185.3
+176.7
+181.5
+181.0
+191.1
+195.9
+192.0
+178.0
+179.9
+184.7
+181.3
+188.5
+187.6
+184.7
+193.9
+198.5
+188.5
+186.5
+184.4
+188.1
+183.0
+190.8
+184.9
+183.8
+184.5
+185.5
+180.2
+181.3
+181.8
+177.5
+178.6
+205.8
+188.5
+188.1
+183.9
+182.9
+192.0
+181.9
+181.5
+190.2
+185.2
+188.1
+174.3
+190.1
+185.6
+183.3
+182.1
+192.2
+179.6
+179.7
+187.5
+187.3
+187.1
+180.0
+184.2
+180.6
+311.7
+185.9
+185.9
+185.1
+176.8
+176.4
+177.7
+177.1
+188.2
+174.8
+184.0
+178.6
+180.2
+183.4
+182.9
+189.8
+187.3
+176.8
+192.5
+186.1
+212.1
+179.4
+181.3
+188.4
+177.7
+200.4
+182.7
+177.5
+183.5
+187.5
+182.6
+194.0
+179.6
+181.6
+201.6
+183.0
+185.5
+177.8
+200.1
+182.7
+182.1
+192.5
+185.6
+188.8
+180.8
+179.5
+181.1
+185.1
+178.1
+178.9
+183.1
+180.8
+178.7
+184.8
+180.1
+179.7
+187.2
+187.1
+182.5
+187.0
+185.6
+191.9
+187.7
+186.3
+188.0
+180.1
+179.3
+181.3
+193.7
+176.8
+184.4
+176.7
+183.1
+189.4
+178.8
+211.4
+182.2
+190.8
+194.1
+179.4
+176.3
+178.6
+175.5
+182.9
+186.0
+184.1
+187.8
+181.3
+188.4
+178.3
+178.7
+200.4
+176.0
+187.7
+197.2
+188.2
+188.6
+192.3
+180.3
+186.3
+187.6
+201.5
+183.8
+182.9
+185.8
+185.9
+181.9
+180.2
+177.3
+183.3
+186.4
+181.3
+195.4
+194.0
+182.7
+178.3
+190.5
+178.6
+194.5
+175.4
+176.8
+177.4
+191.1
+180.6
+175.5
+175.2
+186.9
+176.1
+183.6
+187.1
+178.4
+196.0
+201.0
+183.0
+179.7
+179.9
+184.1
+179.9
+193.1
+180.8
+187.2
+182.0
+176.1
+183.4
+188.4
+177.6
+177.3
+183.8
+179.5
+184.4
+179.8
+181.5
+178.6
+186.5
+183.1
+175.7
+177.3
+177.5
+194.3
+190.9
+175.4
+198.2
+182.6
+180.9
+182.7
+177.0
+190.4
+186.8
+199.4
+183.6
+180.4
+196.3
+205.0
+178.0
+182.0
+185.0
+180.0
+200.6
+185.4
+186.6
+188.4
+199.1
+183.3
+211.8
+182.2
+186.0
+186.5
+185.4
+183.8
+182.3
+179.9
+182.0
+186.9
+184.6
+187.9
+182.4
+187.3
+199.6
+187.9
+196.6
+189.9
+190.9
+180.9
+177.5
+180.2
+188.4
+181.4
+178.0
+190.8
+198.7
+191.5
+189.8
+191.4
+188.6
+186.2
+198.8
+207.4
+192.2
+185.0
+194.7
+198.7
+191.4
+197.4
+184.9
+202.5
+178.6
+180.3
+186.9
+184.5
+178.3
+179.4
+201.3
+193.5
+184.2
+193.9
+177.1
+185.8
+188.5
+183.6
+201.0
+183.6
+190.9
+190.3
+185.4
+180.2
+185.9
+182.6
+184.7
+182.1
+188.7
+180.5
+178.0
+198.6
+188.8
+183.8
+178.1
+176.4
+179.4
+179.5
+186.5
+180.4
+179.8
+180.2
+195.0
+177.2
+180.0
+195.5
+181.7
+183.6
+187.1
+180.7
+180.7
+181.2
+184.5
+181.0
+199.1
+197.4
+183.6
+189.0
+183.2
+196.4
+183.7
+191.4
+185.3
+202.8
+177.9
+182.1
+179.5
+176.3
+193.1
+184.6
+178.8
+191.8
+224.8
+180.5
+182.0
+178.8
+178.2
+178.8
+185.4
+186.5
+178.6
+188.4
+179.7
+190.9
+181.6
+179.8
+190.1
+188.0
+179.7
+204.8
+184.8
+179.7
+182.5
+184.1
+176.1
+181.1
+177.2
+185.8
+177.7
+179.1
+186.7
+183.8
+177.5
+180.2
+200.9
+181.0
+182.7
+177.7
+191.7
+176.8
+190.9
+178.6
+182.3
+178.8
+186.1
+176.8
+180.0
+182.5
+175.0
+183.2
+177.4
+189.5
+177.7
+179.1
+180.8
+177.9
+180.0
+181.6
+178.6
+178.1
+184.7
+176.8
+176.0
+175.7
+175.9
+180.3
+176.3
+180.4
+178.8
+182.5
+177.9
+187.7
+182.2
+187.2
+178.5
+179.9
+181.1
+180.9
+182.5
+179.8
+178.6
+176.0
+179.0
+189.5
+183.5
+182.5
+183.6
+186.0
+181.7
+191.1
+187.4
+194.0
+197.2
+197.3
+177.9
+179.4
+178.0
+176.9
+178.6
+189.0
+183.3
+188.5
+184.0
+184.0
+185.5
+180.7
+176.3
+180.2
+179.9
+186.7
+177.5
+181.0
+189.3
+190.3
+178.3
+188.1
+189.5
+183.6
+190.7
+178.9
+179.1
+179.5
+196.1
+180.8
+178.9
+177.9
+184.6
+178.0
+181.9
+185.3
+187.5
+183.9
+187.0
+180.1
+198.9
+182.6
+181.1
+191.1
+190.7
+183.6
+181.2
+184.5
+177.3
+179.5
+178.7
+181.5
+180.1
+185.0
+178.6
+182.8
+182.7
+192.6
+181.5
+176.5
+178.4
+187.0
+180.2
+187.5
+182.5
+178.0
+184.6
+180.3
+187.2
+177.6
+187.5
+190.6
+188.2
+187.8
+182.6
+186.8
+179.0
+211.0
+176.2
+180.5
+183.4
+215.3
+181.6
+208.2
+215.6
+207.8
+189.1
+176.5
+190.9
+187.0
+178.8
+190.2
+183.0
+193.6
+175.3
+184.4
+184.2
+181.7
+175.4
+177.4
+176.1
+187.1
+194.1
+181.3
+193.1
+182.1
+184.7
+183.1
+186.0
+180.6
+181.7
+181.7
+184.9
+191.4
+201.1
+181.7
+192.9
+177.6
+187.6
+177.6
+183.6
+195.4
+187.2
+184.2
+185.4
+178.0
+201.0
+180.0
+205.9
+201.9
+199.2
+185.2
+182.7
+181.0
+187.4
+181.5
+186.6
+180.4
+176.7
+177.9
+178.2
+179.3
+186.5
+175.7
+185.5
+195.1
+181.9
+183.3
+182.0
+176.7
+181.0
+176.1
+179.4
+186.2
+194.1
+191.1
+184.0
+185.1
+188.6
+183.2
+176.1
+179.3
+183.6
+177.5
+192.7
+180.6
+191.3
+182.6
+189.3
+183.6
+182.4
+178.8
+175.5
+179.3
+193.2
+181.3
+183.9
+185.1
+183.3
+184.9
+188.6
+185.7
+194.9
+199.5
+175.7
+202.6
+194.2
+185.3
+178.0
+182.5
+202.3
+187.5
+192.9
+181.8
+183.2
+183.5
+187.8
+179.6
+178.7
+197.1
+182.2
+202.2
+194.8
+184.9
+192.6
+175.5
+178.0
+184.7
+177.8
+178.7
+179.7
+181.4
+186.6
+179.3
+178.9
+182.3
+180.8
+180.5
+182.4
+184.0
+183.2
+181.2
+183.6
+186.2
+179.7
+178.6
+200.2
+189.2
+177.5
+188.0
+191.1
+183.2
+183.4
+191.0
+186.9
+198.9
+180.0
+175.3
+261.0
+178.7
+181.1
+181.2
+178.7
+179.1
+201.6
+178.4
+181.6
+176.2
+181.7
+192.2
+184.5
+179.1
+191.8
+181.3
+184.4
+185.0
+186.6
+178.6
+180.2
+185.2
+190.0
+177.3
+178.5
+193.7
+178.7
+184.8
+179.9
+192.0
+186.2
+194.7
+181.5
+183.9
+188.0
+183.4
+181.3
+187.2
+184.6
+188.7
+185.3
+180.1
+183.4
+182.4
+176.9
+177.6
+191.1
+180.8
+176.0
+175.4
+180.0
+179.6
+188.3
+191.3
+183.2
+181.7
+177.9
+180.0
+182.0
+181.3
+180.1
+187.7
+177.5
+186.7
+183.4
+189.1
+199.9
+203.9
+184.1
+179.2
+187.4
+185.7
+181.8
+179.7
+179.1
+175.6
+200.8
+181.2
+175.9
+179.3
+176.9
+183.8
+195.7
+185.6
+181.9
+184.0
+190.1
+188.6
+179.0
+181.7
+179.2
+212.9
+181.3
+192.8
+180.8
+180.4
+190.8
+178.1
+181.2
+183.4
+189.4
+191.6
+196.7
+179.2
+203.5
+197.4
+180.5
+184.2
+194.9
+182.6
+183.3
+177.9
+200.4
+191.9
+175.4
+214.3
+184.1
+180.8
+182.9
+181.3
+182.4
+181.4
+191.0
+178.9
+188.3
+195.6
+182.1
+193.5
+180.3
+196.3
+177.9
+198.8
+190.9
+179.1
+185.4
+188.7
+181.0
+177.8
+178.0
+203.4
+179.4
+180.8
+183.6
+179.0
+184.8
+178.0
+188.3
+203.1
+194.9
+196.8
+187.9
+185.7
+178.0
+180.8
+176.8
+181.0
+180.9
+187.0
+179.5
+186.9
+175.9
+174.7
+177.1
+187.9
+193.2
+177.3
+177.9
+181.2
+180.1
+176.1
+179.4
+176.7
+183.2
+191.8
+184.7
+180.8
+183.2
+176.9
+182.8
+187.5
+187.3
+185.7
+184.6
+178.8
+183.0
+176.7
+177.6
+198.0
+194.7
+188.8
+180.9
+187.9
+175.9
+193.1
+183.4
+186.2
+184.4
+182.3
+175.6
+224.3
+186.0
+184.0
+179.0
+176.6
+183.9
+190.9
+188.5
+183.9
+184.7
+198.6
+184.1
+182.0
+181.3
+183.3
+181.6
+182.8
+186.6
+181.4
+179.0
+192.0
+186.3
+202.8
+190.0
+182.1
+186.4
+186.0
+187.0
+192.8
+185.4
+185.4
+181.2
+182.3
+183.2
+190.4
+184.9
+186.4
+180.9
+181.4
+179.8
+191.3
+182.4
+193.0
+202.1
+187.9
+222.0
+196.9
+201.9
+197.8
+198.7
+184.1
+192.7
+203.2
+197.1
+193.7
+196.5
+186.3
+198.4
+181.2
+192.3
+190.4
+183.3
+181.9
+201.8
+198.5
+194.7
+182.3
+184.9
+186.0
+185.9
+190.8
+179.4
+182.2
+181.7
+180.6
+179.2
+180.9
+189.7
+195.7
+200.7
+191.6
+191.1
+187.6
+181.6
+185.9
+188.3
+183.5
+187.7
+190.3
+186.1
+181.1
+178.7
+178.0
+179.0
+191.5
+186.8
+184.9
+181.0
+183.4
+181.3
+180.1
+188.2
+204.3
+180.1
+185.8
+184.9
+187.4
+176.0
+184.2
+177.6
+179.0
+178.9
+188.3
+179.8
+180.9
+177.5
+188.9
+191.1
+184.7
+189.5
+178.4
+189.3
+184.7
+186.2
+178.9
+176.6
+180.8
+177.3
+183.3
+180.0
+188.8
+186.8
+179.5
+179.6
+181.6
+177.4
+176.5
+178.0
+182.6
+184.7
+189.3
+183.6
+180.6
+186.6
+180.7
+185.6
+178.2
+176.3
+181.2
+177.6
+175.7
+177.3
+184.0
+178.2
+182.9
+201.6
+175.0
+179.3
+181.6
+176.2
+180.4
+176.0
+183.7
+177.5
+183.5
+193.5
+192.7
+179.0
+182.2
+179.6
+179.3
+176.8
+177.9
+177.2
+176.5
+177.4
+177.9
+186.5
+178.9
+181.0
+178.0
+176.6
+176.5
+179.2
+187.2
+178.2
+184.6
+178.9
+177.8
+182.1
+187.2
+188.8
+184.5
+181.0
+188.4
+193.1
+178.7
+186.2
+181.1
+183.2
+181.4
+177.6
+176.7
+177.1
+179.0
+181.0
+177.9
+183.5
+181.6
+178.9
+179.4
+175.8
+175.6
+179.0
+180.1
+175.9
+179.9
+178.1
+186.0
+208.0
+178.6
+182.7
+186.3
+174.5
+178.6
+184.3
+185.4
+178.3
+178.5
+190.5
+180.8
+188.2
+177.3
+190.1
+175.5
+188.5
+185.4
+179.6
+181.4
+177.4
+217.5
+196.9
+180.8
+179.9
+192.5
+178.0
+178.4
+175.8
+175.3
+184.8
+197.2
+186.0
+176.9
+176.5
+178.4
+180.8
+185.5
+203.5
+183.3
+180.0
+177.6
+181.2
+180.2
+179.7
+194.4
+180.8
+181.3
+176.9
+187.2
+178.5
+178.7
+185.4
+183.0
+181.3
+261.7
+178.1
+175.9
+175.9
+189.8
+185.9
+184.1
+181.7
+182.7
+177.6
+176.6
+183.6
+178.4
+178.5
+185.8
+184.5
+178.5
+178.0
+181.7
+177.4
+179.3
+177.5
+175.7
+176.6
+176.4
+180.8
+184.9
+177.6
+178.0
+178.7
+178.0
+177.8
+176.7
+183.6
+196.4
+182.4
+182.2
+178.0
+175.9
+179.2
+175.6
+178.3
+176.2
+178.8
+183.1
+175.2
+182.4
+180.6
+177.6
+176.6
+180.2
+180.4
+181.1
+176.2
+188.7
+187.2
+181.6
+181.5
+177.2
+176.9
+177.4
+176.3
+185.2
+178.3
+184.6
+178.3
+189.7
+179.5
+201.1
+176.2
+185.5
+182.2
+183.4
+174.5
+176.8
+180.1
+179.8
+182.2
+178.8
+186.5
+188.2
+180.4
+182.1
+182.8
+194.7
+182.3
+177.6
+176.2
+180.7
+193.6
+178.9
+177.8
+178.7
+179.8
+176.3
+180.8
+184.2
+181.5
+179.2
+178.2
+179.9
+176.7
+180.0
+183.0
+180.6
+177.3
+186.7
+180.8
+181.5
+190.3
+186.8
+184.2
+180.4
+181.6
+182.2
+183.0
+173.3
+187.5
+185.7
+180.1
+177.8
+189.2
+194.9
+176.8
+194.9
+187.0
+209.2
+185.4
+177.7
+178.0
+175.4
+178.0
+183.1
+177.9
+182.4
+177.2
+177.7
+178.9
+183.5
+178.1
+179.2
+191.5
+182.6
+179.8
+182.5
+176.9
+180.2
+176.0
+178.6
+184.7
+279.2
+179.0
+176.5
+180.0
+182.9
+178.3
+188.1
+179.6
+177.8
+175.6
+180.0
+178.6
+181.0
+184.0
+178.2
+191.6
+176.8
+187.7
+179.1
+183.3
+203.5
+178.7
+178.6
+175.4
+187.5
+179.7
+181.6
+177.9
+180.5
+186.7
+182.3
+187.9
+182.2
+187.8
+181.1
+266.6
+186.3
+185.9
+178.0
+180.6
+176.9
+176.5
+177.0
+184.1
+182.7
+179.0
+182.6
+180.8
+179.9
+179.0
+183.1
+178.6
+176.4
+177.3
+176.4
+180.2
+177.2
+177.0
+177.5
+178.3
+175.2
+177.8
+176.7
+181.4
+177.7
+177.1
+176.5
+180.8
+177.2
+178.1
+177.9
+187.1
+183.8
+187.0
+184.4
+180.2
+179.2
+179.2
+178.6
+188.0
+184.7
+178.0
+176.3
+175.3
+181.6
+191.8
+176.9
+182.0
+183.3
+177.5
+185.0
+181.5
+182.6
+179.0
+188.4
+203.6
+184.3
+183.8
+188.0
+189.7
+184.7
+185.9
+195.0
+185.3
+178.8
+190.6
+181.6
+180.1
+187.1
+182.3
+176.8
+179.1
+185.8
+180.1
+180.3
+182.7
+185.3
+175.6
+182.8
+188.5
+175.6
+182.7
+182.0
+179.5
+180.9
+179.4
+183.2
+182.7
+182.5
+182.0
+190.1
+199.7
+177.8
+177.6
+180.9
+181.6
+183.3
+186.1
+188.0
+184.9
+185.4
+182.6
+182.5
+178.7
+185.6
+175.4
+176.7
+182.5
+180.6
+178.3
+179.7
+184.4
+180.9
+184.5
+187.2
+181.6
+192.7
+185.9
+179.6
+184.3
+182.3
+175.0
+180.3
+177.2
+177.9
+178.4
+179.7
+174.6
+190.4
+193.1
+186.8
+180.6
+176.0
+177.4
+188.4
+179.2
+180.9
+178.7
+180.9
+178.2
+176.6
+185.0
+182.1
+181.7
+175.2
+175.1
+183.2
+192.0
+188.3
+182.1
+176.7
+177.6
+177.3
+192.6
+189.3
+178.2
+177.5
+183.1
+183.4
+181.2
+179.5
+175.4
+176.8
+178.2
+175.8
+179.4
+184.7
+184.6
+191.9
+177.4
+176.7
+179.3
+181.6
+183.3
+176.6
+218.6
+175.7
+174.4
+186.4
+185.1
+176.0
+195.2
+180.3
+178.1
+179.2
+178.0
+181.5
+182.9
+179.0
+191.3
+185.1
+186.5
+177.2
+180.3
+191.0
+175.7
+179.8
+182.0
+181.6
+185.0
+183.2
+174.8
+184.5
+182.1
+178.7
+186.7
+186.9
+176.5
+184.5
+188.9
+180.4
+190.1
+181.4
+188.0
+193.5
+187.7
+180.0
+183.5
+176.7
+177.5
+179.2
+206.2
+178.4
+183.2
+181.7
+200.9
+193.1
+176.1
+181.8
+183.6
+193.5
+201.7
+188.0
+178.9
+180.5
+180.8
+176.5
+178.0
+180.6
+181.4
+184.7
+192.4
+177.4
+182.4
+202.8
+189.7
+177.3
+181.5
+178.7
+180.0
+180.6
+178.0
+178.3
+183.9
+177.4
+180.0
+189.7
+175.4
+185.2
+179.3
+184.1
+177.5
+182.1
+174.8
+179.8
+187.5
+216.9
+182.9
+183.5
+203.3
+181.5
+184.0
+181.3
+183.4
+180.3
+176.1
+180.3
+180.1
+180.2
+181.2
+182.3
+176.2
+189.5
+219.7
+180.1
+176.6
+177.3
+178.8
+181.2
+175.1
+183.3
+182.1
+178.7
+180.9
+183.3
+188.1
+225.2
+185.8
+184.3
+187.5
+187.3
+190.7
+181.0
+180.2
+184.6
+177.9
+180.1
+187.6
+181.1
+177.5
+184.5
+176.5
+182.7
+174.5
+183.2
+176.5
+183.0
+177.0
+179.2
+182.9
+184.9
+176.4
+179.9
+183.2
+178.1
+177.0
+205.5
+182.5
+175.2
+185.0
+182.7
+178.2
+189.0
+190.5
+178.8
+183.5
+185.1
+181.3
+181.3
+194.5
+187.8
+184.9
+177.2
+179.6
+195.1
+181.2
+188.8
+180.4
+180.8
+180.0
+177.6
+198.3
+180.6
+179.5
+178.4
+179.3
+177.5
+176.8
+181.6
+183.8
+181.8
+175.6
+181.0
+178.4
+179.4
+179.8
+181.3
+181.1
+193.3
+180.1
+182.8
+179.3
+181.1
+188.3
+177.6
+178.5
+178.8
+177.1
+179.2
+181.6
+185.6
+191.8
+176.3
+187.8
+184.5
+197.3
+183.1
+179.7
+178.1
+181.3
+177.6
+178.2
+179.5
+177.5
+185.4
+184.4
+186.4
+177.4
+178.0
+179.7
+177.9
+184.6
+189.1
+184.5
+197.2
+192.6
+188.0
+186.5
+177.3
+183.7
+183.7
+192.8
+181.2
+199.4
+179.7
+187.7
+192.8
+208.5
+184.1
+175.6
+185.3
+188.6
+183.7
+180.7
+182.1
+176.0
+183.1
+179.6
+175.7
+187.0
+179.9
+184.8
+179.4
+187.6
+181.6
+201.1
+193.5
+186.2
+190.3
+197.9
+208.2
+192.9
+193.1
+184.9
+210.5
+178.6
+198.2
+177.9
+192.2
+181.6
+185.1
+189.1
+188.3
+204.7
+186.2
+179.6
+189.2
+176.9
+180.5
+183.3
+193.1
+178.4
+203.8
+185.2
+180.6
+183.3
+186.7
+188.8
+184.4
+181.7
+186.4
+185.8
+180.1
+182.7
+179.8
+198.9
+180.4
+178.3
+195.6
+184.0
+179.5
+181.8
+187.4
+181.7
+181.8
+175.1
+200.0
+181.6
+203.0
+176.0
+184.9
+199.3
+178.2
+184.3
+176.6
+192.9
+188.1
+185.7
+181.0
+179.5
+183.4
+186.7
+178.6
+183.2
+179.6
+182.6
+177.3
+179.0
+197.0
+182.0
+174.8
+179.4
+180.7
+210.3
+211.3
+235.3
+255.7
+275.7
+286.8
+306.7
+347.2
+381.2
+184.5
+186.7
+190.1
+193.3
+177.9
+188.0
+184.0
+180.6
+178.5
+235.8
+179.6
+178.1
+196.0
+180.5
+179.0
+185.8
+230.8
+211.1
+178.3
+179.4
+186.5
+180.6
+181.4
+183.2
+188.7
+184.8
+175.0
+179.2
+185.4
+180.8
+182.7
+183.1
+177.3
+177.4
+181.3
+190.9
+182.7
+198.2
+180.9
+200.5
+188.4
+207.5
+190.2
+178.9
+184.1
+193.6
+321.3
+183.8
+181.4
+174.6
+185.1
+198.9
+202.2
+183.3
+187.3
+183.0
+184.5
+178.3
+183.8
+176.7
+178.3
+186.0
+181.0
+176.0
+181.3
+202.8
+179.7
+180.3
+191.6
+176.8
+182.6
+183.7
+181.3
+186.2
+205.7
+186.4
+181.9
+189.9
+188.9
+193.6
+203.2
+212.3
+178.9
+181.5
+189.6
+178.9
+177.6
+189.0
+177.3
+177.2
+178.0
+178.5
+177.9
+197.8
+182.0
+185.7
+193.6
+185.8
+183.1
+183.1
+192.7
+183.0
+187.0
+193.4
+186.9
+182.3
+183.9
+201.6
+199.5
+182.7
+184.6
+181.8
+179.1
+189.8
+183.1
+187.8
+185.7
+192.9
+179.3
+179.7
+180.2
+177.6
+180.8
+178.0
+190.2
+177.5
+199.2
+192.7
+181.2
+185.2
+183.5
+198.1
+178.9
+183.7
+191.6
+187.4
+180.5
+211.9
+206.2
+183.2
+182.1
+176.7
+187.4
+176.6
+183.2
+195.5
+178.7
+180.7
+181.2
+189.1
+187.8
+183.1
+181.9
+186.3
+197.1
+184.7
+177.2
+180.5
+179.5
+181.6
+177.9
+187.7
+196.2
+189.5
+180.5
+183.3
+180.6
+186.7
+186.2
+177.7
+192.1
+182.4
+177.5
+179.7
+193.6
+200.1
+179.9
+188.4
+182.3
+190.4
+179.4
+182.4
+181.7
+195.7
+177.2
+181.3
+182.9
+188.3
+183.6
+180.3
+192.7
+183.1
+210.5
+185.1
+183.4
+191.6
+187.9
+183.1
+175.5
+177.4
+183.7
+182.9
+188.5
+180.7
+186.2
+182.4
+186.3
+182.4
+188.1
+180.2
+195.4
+181.8
+192.9
+176.3
+177.7
+178.8
+179.4
+182.6
+180.3
+187.4
+185.3
+183.5
+180.5
+180.8
+182.0
+178.9
+176.2
+197.0
+181.5
+177.1
+189.1
+188.0
+176.5
+181.7
+184.9
+192.2
+181.5
+189.0
+204.6
+177.5
+176.6
+202.2
+176.9
+181.9
+177.9
+180.6
+197.2
+175.9
+175.6
+183.4
+182.7
+191.2
+178.8
+187.2
+178.9
+192.4
+178.6
+178.4
+184.5
+216.4
+190.7
+182.7
+181.6
+199.5
+182.0
+179.1
+189.1
+180.3
+182.0
+185.6
+196.1
+182.5
+180.4
+190.9
+178.9
+186.3
+209.4
+181.7
+186.6
+192.0
+200.0
+205.7
+191.0
+186.0
+185.5
+176.2
+191.4
+184.4
+186.1
+186.6
+180.0
+180.8
+178.0
+180.0
+198.8
+183.9
+183.5
+176.4
+176.7
+181.4
+186.4
+186.8
+186.0
+179.5
+187.2
+179.5
+185.2
+179.4
+183.6
+188.6
+179.8
+179.0
+184.4
+178.4
+177.4
+195.2
+207.0
+184.3
+179.6
+183.1
+184.0
+187.2
+181.4
+178.4
+182.7
+189.4
+183.1
+179.7
+181.8
+181.5
+181.9
+182.6
+178.2
+189.2
+189.1
+190.3
+184.1
+195.3
+176.1
+177.3
+186.8
+183.7
+188.5
+179.7
+177.4
+181.4
+180.7
+206.9
+179.2
+177.8
+180.5
+180.6
+191.9
+181.7
+190.3
+183.5
+186.2
+175.8
+179.1
+181.6
+181.6
+183.0
+179.1
+179.3
+183.7
+181.2
+185.2
+176.8
+177.8
+175.3
+184.6
+179.2
+176.9
+188.0
+184.6
+184.6
+180.9
+189.9
+396.3
+176.1
+183.1
+179.2
+184.6
+181.5
+181.3
+176.2
+177.4
+174.3
+179.4
+178.8
+184.6
+182.1
+183.4
+178.4
+184.1
+182.7
+181.4
+177.7
+185.0
+177.2
+179.4
+183.5
+183.1
+187.6
+181.1
+179.7
+189.6
+183.6
+177.1
+181.5
+193.1
+196.8
+180.2
+177.3
+178.8
+176.6
+177.8
+190.0
+176.3
+180.1
+177.5
+187.2
+186.7
+176.5
+179.6
+177.2
+177.5
+196.4
+181.9
+179.9
+178.2
+181.5
+177.5
+179.9
+183.1
+181.5
+181.8
+177.4
+177.6
+174.5
+178.4
+183.8
+181.6
+186.4
+178.1
+177.3
+179.1
+183.6
+191.2
+181.7
+187.6
+186.1
+183.5
+185.0
+186.5
+187.5
+190.5
+224.7
+177.3
+189.2
+191.7
+188.0
+182.8
+187.8
+182.2
+241.9
+197.6
+188.3
+181.7
+195.5
+184.4
+190.9
+187.4
+195.0
+190.0
+180.5
+179.1
+180.3
+195.0
+176.7
+191.8
+200.2
+180.3
+180.6
+186.1
+178.2
+182.5
+183.2
+178.9
+177.6
+185.6
+203.5
+179.7
+179.6
+190.8
+187.4
+184.7
+185.1
+195.1
+191.9
+181.1
+183.1
+177.1
+177.9
+178.5
+178.8
+186.0
+189.9
+184.7
+190.4
+192.9
+178.6
+176.1
+189.2
+186.1
+178.6
+186.9
+181.5
+181.5
+181.2
+178.1
+181.8
+187.6
+183.3
+188.5
+179.6
+183.5
+187.9
+179.6
+192.4
+182.6
+192.1
+255.4
+266.2
+191.5
+194.8
+179.7
+190.8
+182.8
+182.3
+186.7
+186.4
+178.1
+182.5
+175.0
+187.7
+199.4
+182.1
+180.5
+182.9
+175.9
+219.6
+207.0
+196.5
+190.7
+199.1
+185.4
+174.5
+180.5
+182.7
+181.8
+189.2
+181.0
+198.2
+191.9
+185.3
+182.9
+188.5
+178.7
+179.2
+188.7
+188.5
+178.2
+197.6
+178.3
+192.7
+181.9
+191.2
+181.7
+176.1
+196.5
+187.7
+190.5
+187.8
+182.9
+189.9
+192.3
+177.4
+184.7
+178.3
+185.8
+182.0
+180.1
+183.1
+182.0
+176.1
+185.2
+196.0
+185.5
+179.1
+198.1
+197.5
+182.0
+177.0
+178.3
+202.9
+177.0
+180.6
+181.6
+191.6
+179.6
+180.9
+183.7
+188.4
+182.6
+185.7
+191.5
+178.6
+188.4
+184.0
+183.8
+189.3
+185.2
+192.9
+195.4
+197.1
+190.4
+186.5
+196.0
+189.8
+191.9
+187.3
+185.7
+198.0
+175.8
+183.7
+176.0
+179.9
+183.1
+186.7
+177.9
+179.2
+181.7
+184.9
+181.6
+178.5
+176.5
+176.7
+178.4
+184.7
+181.3
+182.3
+182.8
+182.6
+190.2
+177.0
+187.9
+188.1
+186.4
+187.8
+181.3
+187.7
+191.8
+182.5
+182.0
+180.1
+185.8
+177.8
+192.5
+185.2
+179.7
+179.3
+191.0
+181.3
+192.9
+180.3
+194.5
+187.6
+184.3
+194.7
+182.7
+177.7
+181.2
+177.2
+184.0
+181.8
+188.5
+187.8
+184.7
+181.7
+184.6
+177.4
+193.0
+178.7
+176.5
+176.9
+180.9
+180.8
+187.8
+179.8
+175.2
+177.5
+180.5
+186.6
+177.0
+177.8
+177.8
+182.5
+180.0
+182.4
+188.6
+192.5
+177.5
+179.8
+178.8
+178.9
+182.0
+180.6
+184.5
+179.9
+175.4
+183.1
+177.8
+177.2
+191.7
+184.6
+183.1
+175.6
+200.6
+221.5
+177.2
+177.6
+184.0
+183.4
+199.2
+184.6
+186.1
+180.3
+188.1
+184.1
+185.9
+178.0
+183.7
+186.7
+186.1
+177.8
+183.5
+178.0
+177.0
+185.6
+187.0
+178.9
+178.9
+200.5
+180.2
+181.6
+187.8
+179.8
+182.0
+184.3
+176.3
+182.8
+184.3
+181.9
+185.3
+189.2
+177.3
+183.6
+186.7
+184.0
+243.9
+175.9
+184.3
+176.0
+179.6
+178.5
+182.3
+189.7
+179.6
+190.3
+188.8
+175.2
+179.8
+181.0
+175.6
+175.3
+187.5
+175.3
+186.8
+176.6
+177.3
+175.7
+178.5
+178.8
+178.5
+178.9
+183.7
+182.2
+182.3
+178.9
+182.0
+178.6
+184.2
+178.8
+176.7
+176.9
+176.2
+181.1
+177.9
+177.2
+178.6
+194.8
+185.4
+188.3
+178.9
+187.1
+184.3
+184.2
+190.3
+193.8
+185.0
+181.0
+181.4
+181.0
+191.8
+177.4
+178.6
+179.6
+180.2
+179.0
+187.4
+177.1
+182.7
+181.9
+177.9
+184.8
+195.1
+181.2
+182.1
+187.4
+186.8
+184.7
+181.9
+188.7
+191.8
+184.7
+185.5
+183.5
+180.8
+206.1
+179.1
+194.3
+178.0
+177.4
+181.0
+181.7
+190.6
+189.3
+176.3
+183.5
+178.9
+179.4
+187.3
+182.5
+181.7
+183.3
+188.9
+181.3
+195.6
+180.4
+194.0
+182.6
+176.6
+190.8
+176.3
+186.1
+185.1
+181.5
+179.2
+178.3
+179.6
+181.5
+181.0
+183.2
+182.6
+202.6
+183.7
+188.2
+200.6
+177.9
+184.2
+192.0
+180.9
+177.3
+177.2
+188.1
+184.2
+199.4
+185.3
+189.7
+199.8
+176.6
+189.2
+186.3
+182.8
+180.0
+190.8
+188.5
+194.6
+184.8
+202.7
+189.8
+183.2
+206.2
+175.3
+202.1
+193.5
+210.1
+200.5
+194.8
+190.0
+189.3
+182.5
+183.3
+187.1
+186.1
+208.1
+191.4
+207.5
+188.6
+176.7
+190.8
+189.1
+177.2
+189.3
+177.3
+196.2
+195.3
+188.0
+194.1
+181.3
+177.8
+186.2
+184.4
+182.0
+182.5
+187.1
+179.8
+177.4
+184.0
+180.5
+182.9
+176.4
+175.9
+176.8
+182.8
+208.0
+196.3
+183.4
+178.5
+189.7
+183.3
+182.5
+182.7
+182.5
+195.7
+183.8
+182.6
+190.0
+191.0
+191.7
+177.2
+184.1
+202.9
+189.8
+185.0
+183.3
+194.2
+182.8
+178.9
+184.9
+189.1
+192.8
+181.1
+190.5
+205.2
+179.1
+191.3
+186.8
+188.9
+178.8
+188.0
+184.1
+183.1
+182.1
+181.9
+191.6
+183.4
+189.9
+180.2
+186.6
+180.3
+182.7
+184.8
+178.4
+180.3
+193.7
+181.5
+186.6
+179.1
+184.3
+192.1
+189.5
+182.2
+296.4
+194.8
+179.0
+183.4
+194.3
+199.5
+182.7
+181.0
+181.8
+186.9
+186.4
+188.3
+179.2
+190.1
+184.9
+187.8
+186.3
+183.4
+183.1
+191.3
+188.5
+188.5
+187.9
+188.7
+175.3
+178.4
+197.5
+185.3
+201.3
+190.9
+210.1
+179.0
+176.3
+179.4
+195.0
+179.0
+176.4
+188.5
+180.5
+179.5
+179.4
+177.5
+185.2
+179.9
+178.3
+183.8
+193.9
+183.1
+187.7
+192.7
+181.3
+184.1
+182.5
+178.2
+184.0
+195.4
+179.7
+191.7
+183.7
+185.6
+188.4
+195.0
+184.8
+181.8
+186.2
+177.4
+189.8
+192.1
+192.9
+187.5
+191.5
+189.1
+195.7
+188.0
+192.7
+190.1
+190.1
+196.3
+192.4
+193.1
+178.9
+181.9
+179.2
+188.4
+179.2
+187.5
+179.7
+185.3
+177.2
+180.5
+182.2
+203.8
+186.1
+181.7
+180.8
+187.9
+177.4
+189.5
+189.3
+176.5
+179.2
+190.5
+180.0
+177.1
+183.7
+188.8
+180.6
+220.6
+190.9
+197.9
+182.8
+183.0
+177.9
+182.2
+182.4
+201.5
+181.1
+177.8
+189.6
+193.4
+180.3
+180.2
+175.3
+177.8
+178.3
+178.8
+190.7
+181.8
+185.1
+190.2
+179.1
+179.9
+179.8
+180.6
+181.6
+180.8
+184.6
+184.9
+181.7
+176.3
+182.2
+186.9
+227.1
+184.9
+184.5
+177.5
+180.2
+187.5
+186.1
+183.8
+177.4
+184.1
+188.1
+180.4
+197.4
+209.6
+198.3
+179.5
+183.9
+183.5
+181.8
+178.9
+195.5
+184.7
+191.3
+193.5
+196.8
+197.0
+184.2
+181.9
+187.5
+188.7
+177.7
+186.7
+191.6
+192.8
+194.8
+193.6
+194.7
+187.6
+197.2
+187.5
+176.0
+194.4
+199.9
+188.2
+184.5
+183.2
+186.0
+183.6
+210.5
+183.7
+190.4
+188.6
+179.8
+183.9
+210.2
+194.0
+190.8
+188.3
+186.2
+178.0
+178.3
+194.4
+184.5
+182.3
+179.1
+183.4
+182.9
+185.1
+180.6
+182.1
+189.4
+181.6
+180.5
+190.4
+182.1
+204.0
+192.7
+180.4
+190.7
+179.5
+179.2
+181.5
+186.1
+195.1
+189.7
+189.9
+185.9
+206.2
+187.5
+178.5
+206.2
+208.5
+187.4
+182.0
+192.9
+193.0
+196.5
+190.1
+179.7
+205.9
+187.7
+211.5
+177.5
+185.1
+205.3
+178.9
+179.6
+178.8
+185.9
+180.1
+179.0
+178.2
+181.9
+182.9
+191.5
+194.8
+184.9
+177.9
+186.0
+178.7
+175.2
+178.1
+183.9
+176.2
+196.3
+195.0
+214.4
+177.2
+175.2
+181.4
+191.4
+189.0
+179.6
+181.8
+177.8
+175.9
+176.1
+183.8
+182.1
+178.4
+182.1
+180.6
+177.4
+178.9
+178.7
+182.0
+176.5
+182.1
+189.2
+188.3
+184.6
+177.6
+184.6
+183.6
+182.6
+181.5
+198.4
+179.6
+179.3
+185.1
+189.8
+183.5
+180.9
+175.7
+182.6
+179.1
+181.3
+181.4
+189.8
+183.7
+185.8
+185.9
+178.1
+182.3
+196.3
+220.0
+199.4
+191.3
+186.7
+181.7
+180.7
+180.9
+176.2
+185.3
+180.3
+182.5
+177.5
+182.4
+184.8
+191.3
+185.5
+184.4
+193.8
+186.5
+178.9
+180.6
+182.7
+180.1
+184.8
+179.7
+187.8
+183.7
+190.9
+192.5
+205.5
+193.1
+180.7
+186.4
+179.3
+194.2
+187.8
+190.6
+178.8
+176.7
+181.2
+187.6
+191.3
+182.4
+204.9
+191.0
+193.8
+194.0
+198.4
+181.7
+186.5
+186.4
+182.5
+182.2
+179.0
+180.6
+180.0
+181.1
+178.6
+178.6
+183.3
+189.1
+179.9
+190.2
+187.3
+185.3
+185.0
+185.9
+189.3
+182.6
+183.5
+190.8
+183.4
+185.2
+197.1
+187.5
+182.2
+180.4
+181.3
+178.8
+182.5
+183.2
+174.8
+180.4
+200.9
+181.5
+185.5
+176.6
+179.0
+187.4
+173.8
+192.6
+179.3
+188.7
+198.9
+177.8
+185.5
+177.1
+213.1
+185.7
+189.7
+186.6
+189.2
+188.1
+184.0
+184.5
+178.7
+183.9
+186.3
+199.0
+188.2
+191.9
+181.6
+177.4
+183.8
+179.2
+182.7
+182.1
+178.2
+187.3
+189.0
+188.1
+190.8
+184.1
+188.0
+178.9
+195.8
+179.3
+204.6
+183.3
+178.8
+178.8
+176.9
+179.9
+186.0
+181.6
+199.5
+179.6
+189.0
+204.2
+189.7
+181.2
+184.5
+193.4
+183.7
+180.2
+186.2
+191.0
+195.1
+179.9
+182.6
+188.7
+184.5
+178.9
+194.1
+191.5
+179.9
+198.8
+183.4
+212.7
+179.0
+181.8
+176.5
+181.9
+179.7
+186.2
+187.0
+181.6
+184.9
+199.7
+185.4
+186.1
+179.3
+200.6
+187.7
+195.2
+185.5
+182.2
+189.4
+184.6
+177.9
+182.5
+182.9
+191.4
+192.5
+186.2
+178.4
+197.8
+175.7
+183.2
+187.5
+192.9
+175.8
+195.1
+185.7
+188.6
+191.6
+177.0
+181.8
+188.2
+202.4
+183.8
+184.5
+198.6
+186.4
+180.7
+188.6
+181.4
+178.9
+186.5
+185.5
+187.7
+187.7
+179.1
+184.2
+185.1
+178.0
+182.0
+197.0
+194.9
+193.6
+177.0
+186.6
+179.8
+186.1
+177.7
+182.1
+181.3
+178.0
+184.8
+208.3
+193.1
+187.2
+207.7
+188.2
+186.1
+178.1
+193.3
+182.1
+199.8
+182.7
+196.4
+197.9
+178.8
+191.4
+180.9
+180.5
+184.6
+177.2
+182.4
+183.7
+178.2
+187.1
+178.5
+181.3
+190.5
+179.0
+181.0
+178.0
+185.5
+179.1
+184.9
+184.7
+177.7
+176.7
+183.5
+196.3
+184.2
+190.0
+189.4
+186.0
+181.7
+186.0
+188.7
+187.5
+184.6
+176.3
+177.4
+185.9
+183.4
+179.4
+175.8
+186.1
+178.8
+179.0
+181.6
+175.2
+177.4
+176.8
+177.9
+197.6
+184.9
+176.0
+177.6
+178.4
+184.6
+178.1
+183.2
+180.2
+191.3
+180.0
+176.2
+180.3
+181.8
+187.4
+181.7
+177.1
+182.9
+190.6
+183.9
+177.9
+187.4
+174.9
+186.4
+192.0
+190.2
+198.4
+183.6
+206.3
+184.2
+181.0
+195.6
+277.2
+189.9
+187.0
+196.6
+180.6
+183.0
+208.8
+193.1
+188.9
+186.4
+179.9
+181.7
+185.2
+189.5
+188.0
+181.2
+182.5
+183.5
+190.3
+179.3
+182.3
+179.5
+184.2
+193.3
+178.5
+178.5
+182.3
+184.7
+181.9
+185.0
+181.6
+180.0
+202.6
+178.9
+176.4
+185.0
+190.3
+179.4
+182.9
+194.1
+185.2
+198.6
+177.6
+181.1
+179.8
+183.1
+187.7
+187.5
+185.9
+191.2
+183.1
+184.6
+193.0
+197.6
+183.3
+184.4
+181.6
+184.9
+178.0
+177.4
+181.0
+178.9
+180.4
+181.4
+188.0
+178.2
+181.2
+179.0
+188.1
+176.0
+176.4
+179.6
+179.1
+182.2
+185.6
+176.2
+180.0
+195.4
+177.4
+185.2
+177.5
+181.3
+187.8
+176.7
+200.8
+179.8
+183.8
+179.9
+190.4
+180.3
+178.5
+182.7
+180.3
+179.1
+178.8
+184.3
+176.7
+181.8
+179.9
+175.7
+181.6
+184.5
+183.3
+214.3
+175.7
+188.8
+180.1
+179.9
+182.8
+175.0
+177.4
+217.5
+183.7
+178.3
+184.0
+182.2
+175.4
+184.0
+181.2
+184.5
+174.6
+176.0
+180.1
+173.6
+190.4
+177.5
+181.3
+176.1
+181.5
+199.2
+176.2
+186.0
+178.6
+186.9
+187.1
+172.8
+175.5
+182.1
+175.9
+200.4
+193.1
+178.0
+177.1
+180.6
+182.2
+179.0
+175.8
+182.0
+188.0
+180.1
+207.1
+187.0
+180.6
+183.8
+192.9
+175.7
+193.6
+178.3
+182.2
+179.9
+177.2
+181.3
+177.0
+201.1
+176.2
+175.5
+183.1
+181.3
+176.0
+187.7
+185.7
+179.2
+183.4
+184.8
+176.3
+179.3
+175.2
+185.4
+181.2
+177.2
+179.9
+175.1
+175.7
+193.8
+179.7
+184.6
+181.2
+187.8
+184.9
+182.3
+184.9
+181.7
+186.0
+187.0
+178.5
+174.9
+180.7
+188.9
+181.4
+181.3
+176.2
+182.0
+183.4
+181.3
+181.3
+175.0
+179.7
+186.6
+183.7
+185.1
+180.8
+179.6
+180.6
+193.6
+187.6
+181.0
+176.3
+191.0
+177.5
+186.9
+173.8
+184.1
+178.1
+183.2
+180.2
+194.2
+175.8
+183.6
+175.4
+176.6
+183.7
+177.9
+176.4
+180.8
+179.9
+177.9
+183.0
+176.9
+181.3
+182.2
+182.3
+183.8
+181.1
+180.6
+189.9
+185.6
+184.5
+185.3
+175.3
+192.4
+195.5
+188.2
+180.3
+196.3
+184.1
+178.7
+179.7
+187.0
+177.1
+180.7
+177.5
+177.1
+180.8
+182.0
+177.0
+177.0
+193.1
+194.6
+190.0
+183.5
+180.5
+178.4
+177.4
+178.7
+184.6
+189.6
+188.1
+186.1
+184.2
+178.9
+176.7
+195.8
+178.1
+187.6
+175.4
+180.3
+185.3
+189.0
+177.2
+188.9
+179.0
+184.0
+179.1
+187.3
+199.7
+178.2
+179.1
+175.8
+179.8
+178.8
+196.3
+185.0
+196.3
+181.1
+185.5
+175.8
+189.2
+179.4
+192.0
+182.6
+177.9
+181.1
+179.6
+184.2
+194.6
+185.9
+181.4
+176.5
+178.3
+176.3
+179.1
+175.3
+180.5
+186.7
+177.5
+192.3
+187.7
+178.4
+189.4
+180.4
+180.5
+189.9
+183.8
+190.3
+195.5
+185.6
+177.2
+177.7
+178.7
+189.9
+183.8
+193.1
+185.2
+180.5
+180.5
+191.6
+175.0
+176.6
+178.6
+188.8
+180.7
+185.5
+181.2
+199.5
+193.2
+193.8
+185.8
+182.0
+178.5
+188.7
+190.1
+178.7
+181.5
+180.9
+186.6
+189.8
+194.4
+180.1
+193.0
+178.2
+183.2
+182.6
+177.8
+212.2
+177.5
+178.5
+181.9
+185.8
+177.2
+182.9
+176.7
+175.1
+180.4
+186.0
+179.1
+179.5
+177.2
+182.1
+177.8
+180.1
+180.1
+177.9
+183.6
+189.2
+193.2
+179.6
+176.9
+181.3
+178.4
+177.4
+189.2
+182.1
+195.2
+193.0
+184.5
+180.4
+184.1
+176.2
+180.1
+179.0
+174.8
+181.1
+190.0
+179.9
+192.2
+204.9
+179.2
+180.5
+174.7
+181.6
+181.5
+185.1
+176.7
+184.9
+191.9
+182.2
+182.7
+210.5
+186.1
+181.1
+179.3
+186.4
+176.0
+177.4
+180.9
+187.0
+192.2
+182.9
+182.2
+184.0
+180.3
+177.4
+178.7
+176.8
+176.4
+176.1
+182.0
+178.8
+175.2
+187.6
+184.9
+184.3
+188.7
+178.3
+190.9
+186.3
+175.2
+185.8
+175.6
+184.8
+181.1
+176.8
+179.0
+179.2
+196.5
+175.0
+182.6
+194.4
+173.7
+180.0
+178.3
+181.1
+179.5
+177.6
+185.1
+183.2
+188.6
+186.8
+182.5
+176.4
+175.1
+176.9
+182.4
+180.1
+181.7
+182.8
+184.1
+175.7
+178.7
+177.1
+178.3
+177.3
+176.7
+179.8
+176.8
+177.2
+200.5
+175.7
+181.1
+174.5
+179.3
+183.1
+178.4
+189.2
+186.0
+177.4
+178.3
+187.1
+181.0
+183.3
+191.4
+182.9
+182.6
+199.2
+181.4
+180.5
+180.4
+176.7
+184.4
+189.0
+188.1
+197.0
+181.2
+176.1
+181.0
+180.5
+176.5
+214.9
+178.3
+178.7
+181.4
+180.9
+178.0
+185.8
+182.5
+181.8
+177.4
+186.7
+183.2
+183.2
+183.1
+173.9
+181.1
+179.1
+180.3
+177.7
+181.5
+176.5
+177.1
+189.6
+191.5
+183.2
+180.0
+180.9
+183.3
+188.3
+178.4
+185.4
+176.1
+183.2
+181.1
+180.3
+176.4
+177.5
+186.8
+176.3
+338.9
+184.9
+177.0
+187.1
+175.8
+180.0
+182.5
+184.9
+175.2
+179.6
+176.9
+178.7
+177.5
+178.3
+178.9
+180.7
+182.6
+179.3
+177.0
+180.5
+179.2
+177.2
+188.0
+194.8
+183.6
+177.3
+179.1
+185.3
+177.5
+187.6
+180.9
+188.0
+174.8
+183.6
+183.0
+182.9
+178.1
+182.0
+185.9
+176.5
+182.1
+188.5
+179.9
+176.0
+184.3
+185.9
+179.2
+184.3
+181.3
+186.3
+186.7
+177.5
+188.9
+178.8
+211.3
+188.0
+178.5
+192.6
+176.4
+187.1
+189.5
+195.7
+188.5
+185.1
+182.5
+176.6
+182.5
+187.3
+180.5
+191.9
+178.3
+177.1
+188.3
+181.0
+178.0
+187.1
+175.1
+182.3
+177.4
+177.6
+181.2
+179.2
+183.8
+183.2
+173.3
+177.6
+178.6
+179.4
+180.5
+183.9
+176.3
+178.4
+178.9
+175.9
+179.2
+175.4
+183.5
+188.9
+176.5
+185.8
+179.7
+196.8
+185.9
+178.4
+176.4
+177.6
+176.0
+177.9
+181.7
+176.2
+180.6
+179.9
+186.0
+177.2
+178.9
+179.1
+186.6
+176.0
+194.6
+179.2
+181.5
+176.2
+177.2
+179.3
+181.3
+179.2
+181.7
+177.5
+186.6
+179.5
+177.4
+179.4
+177.9
+181.9
+181.3
+177.4
+176.0
+177.8
+185.2
+180.8
+181.3
+182.1
+178.2
+179.5
+185.6
+179.5
+182.5
+186.1
+188.7
+178.7
+186.8
+179.2
+197.7
+180.8
+181.4
+178.1
+207.1
+182.7
+185.5
+186.1
+180.1
+176.1
+177.6
+174.9
+176.1
+180.6
+184.5
+179.2
+186.1
+180.5
+179.5
+179.3
+182.2
+181.8
+184.6
+180.0
+177.1
+176.7
+177.6
+180.8
+176.5
+177.3
+176.8
+176.4
+188.7
+187.6
+178.5
+178.0
+179.3
+181.4
+183.2
+193.8
+178.3
+181.2
+180.4
+183.5
+179.8
+177.4
+183.2
+183.3
+176.6
+176.2
+216.9
+177.8
+177.0
+178.0
+175.3
+189.4
+182.6
+182.9
+183.6
+180.4
+177.2
+176.0
+177.9
+175.1
+182.1
+178.3
+178.5
+186.2
+177.7
+178.0
+183.3
+180.4
+197.6
+181.7
+179.9
+184.0
+194.8
+184.8
+189.4
+191.8
+176.6
+186.2
+180.3
+177.7
+197.0
+187.0
+188.1
+190.1
+176.8
+179.3
+185.4
+174.7
+189.6
+184.2
+178.0
+180.4
+183.4
+179.5
+177.7
+178.7
+186.9
+183.7
+177.2
+173.6
+184.7
+180.4
+183.1
+175.6
+179.6
+184.8
+185.6
+176.2
+177.7
+180.0
+174.7
+183.1
+181.6
+178.0
+178.8
+185.4
+181.2
+178.0
+188.7
+177.1
+189.3
+177.6
+180.6
+181.6
+175.9
+180.7
+179.1
+182.4
+186.7
+175.8
+185.1
+176.7
+180.3
+195.7
+191.3
+191.1
+181.3
+180.5
+179.3
+184.3
+177.1
+182.4
+179.4
+188.8
+174.5
+177.2
+178.4
+179.8
+183.3
+175.4
+184.1
+176.8
+182.4
+177.8
+178.7
+178.6
+189.6
+182.0
+191.2
+179.8
+179.3
+178.6
+183.0
+188.8
+205.3
+180.1
+190.6
+184.4
+179.9
+178.6
+174.7
+180.6
+193.3
+175.3
+175.5
+193.9
+180.4
+177.2
+175.6
+177.0
+182.3
+183.5
+181.9
+179.6
+182.8
+180.9
+180.6
+183.7
+185.0
+176.7
+190.5
+176.2
+179.0
+178.8
+180.0
+181.5
+174.3
+177.3
+179.2
+178.3
+184.7
+174.2
+177.3
+178.0
+178.9
+182.9
+189.8
+183.3
+183.0
+182.5
+176.3
+176.8
+177.5
+176.2
+183.1
+175.4
+175.0
+182.5
+174.9
+185.8
+181.4
+178.4
+183.8
+178.0
+265.0
+178.5
+185.0
+177.4
+179.2
+175.8
+183.0
+184.5
+195.3
+178.5
+178.2
+179.7
+175.0
+187.5
+182.7
+180.3
+181.0
+177.5
+181.5
+179.9
+179.0
+180.0
+182.6
+178.5
+179.6
+181.4
+188.0
+188.2
+179.1
+180.8
+178.5
+181.3
+179.9
+182.0
+181.8
+181.3
+186.6
+197.8
+205.8
+188.2
+194.9
+182.2
+184.6
+188.9
+189.4
+183.4
+179.2
+182.6
+184.6
+183.6
+184.3
+182.5
+181.1
+181.6
+178.9
+179.7
+176.3
+179.4
+180.2
+178.4
+179.6
+190.1
+178.5
+174.4
+177.3
+176.1
+175.3
+175.0
+178.9
+175.1
+181.3
+175.0
+176.0
+192.2
+189.2
+182.4
+179.2
+173.4
+177.9
+178.4
+175.5
+175.9
+199.0
+196.6
+181.7
+181.2
+177.6
+174.7
+174.3
+181.3
+178.9
+203.2
+176.3
+175.8
+192.3
+180.8
+184.8
+179.9
+182.4
+180.0
+174.3
+187.2
+181.4
+176.0
+174.4
+182.4
+186.8
+191.1
+177.1
+178.2
+176.8
+182.6
+184.6
+181.5
+185.4
+178.9
+176.6
+179.6
+175.1
+179.9
+176.9
+176.7
+186.2
+192.9
+183.5
+182.4
+175.5
+176.9
+180.6
+185.7
+174.2
+174.2
+178.9
+178.8
+179.2
+194.9
+177.5
+174.0
+179.0
+180.7
+174.0
+180.0
+178.3
+179.8
+180.0
+175.6
+174.9
+180.9
+177.3
+212.6
+196.0
+183.1
+186.5
+178.4
+185.9
+185.5
+176.8
+178.7
+174.0
+176.6
+179.0
+182.3
+176.6
+185.0
+175.7
+178.5
+176.4
+176.3
+186.3
+222.5
+185.7
+177.1
+183.6
+176.9
+184.0
+175.7
+174.4
+184.2
+269.8
+182.5
+174.6
+178.5
+176.1
+178.9
+177.9
+181.9
+176.8
+174.2
+181.6
+191.2
+179.1
+175.3
+182.2
+180.1
+175.2
+182.3
+175.6
+177.1
+175.1
+174.7
+188.2
+176.9
+183.6
+175.9
+175.5
+174.7
+182.4
+181.2
+179.5
+190.3
+184.8
+237.1
+178.3
+178.7
+184.1
+174.5
+178.3
+178.6
+181.8
+182.2
+190.2
+179.9
+181.0
+180.3
+177.2
+179.2
+179.2
+178.2
+183.0
+177.7
+180.1
+182.6
+196.6
+178.9
+177.0
+176.1
+178.3
+182.9
+176.5
+209.4
+181.1
+199.5
+186.3
+177.6
+182.7
+185.4
+180.2
+186.2
+187.4
+176.4
+182.8
+221.0
+179.0
+178.9
+178.4
+186.8
+174.1
+174.9
+176.8
+190.0
+178.9
+182.4
+178.7
+177.6
+179.4
+173.6
+178.1
+179.8
+177.8
+177.5
+187.5
+209.5
+178.0
+177.2
+179.3
+179.8
+179.8
+174.4
+177.1
+180.2
+182.5
+181.1
+184.7
+179.6
+193.0
+182.7
+181.6
+177.7
+178.2
+181.6
+186.6
+177.2
+178.1
+189.3
+174.3
+180.2
+186.2
+189.8
+185.9
+176.8
+178.8
+175.1
+175.5
+179.1
+180.9
+186.1
+187.0
+186.2
+187.6
+187.5
+197.8
+180.4
+196.7
+176.5
+177.6
+176.2
+180.8
+175.9
+182.8
+178.8
+184.6
+179.5
+175.6
+187.5
+175.7
+201.6
+188.2
+183.4
+184.2
+178.7
+177.0
+174.9
+181.9
+177.6
+177.7
+179.6
+176.0
+178.3
+181.5
+177.5
+184.7
+183.9
+180.9
+178.9
+177.8
+176.6
+188.4
+191.0
+189.6
+178.1
+177.9
+186.1
+178.7
+184.3
+176.5
+179.6
+174.1
+184.4
+176.8
+182.3
+179.5
+188.8
+180.2
+176.1
+198.4
+178.4
+188.6
+191.0
+180.4
+179.5
+176.0
+188.1
+189.6
+192.6
+177.0
+189.1
+177.5
+178.8
+186.8
+178.6
+180.5
+184.0
+178.7
+179.0
+176.2
+176.7
+185.2
+192.3
+178.0
+175.7
+181.0
+183.0
+175.7
+174.6
+176.7
+177.8
+179.0
+177.4
+176.7
+182.5
+194.0
+181.4
+178.7
+176.2
+176.7
+204.2
+178.3
+190.6
+188.5
+177.5
+180.6
+178.1
+175.2
+276.8
+353.6
+297.8
+235.1
+211.7
+213.4
+213.5
+191.5
+178.8
+186.2
+175.4
+178.2
+184.5
+179.9
+182.0
+179.3
+178.6
+192.5
+177.7
+179.2
+194.9
+183.1
+178.6
+179.9
+192.5
+184.1
+186.6
+181.2
+175.4
+197.2
+179.6
+184.6
+197.6
+180.6
+189.6
+175.3
+185.6
+174.5
+178.9
+174.2
+179.6
+173.9
+182.5
+185.6
+177.4
+191.4
+183.8
+177.0
+181.2
+176.3
+177.3
+173.7
+177.5
+178.9
+178.2
+177.3
+177.7
+187.8
+181.6
+178.4
+176.4
+180.8
+178.5
+189.5
+186.0
+188.4
+182.2
+184.0
+176.5
+179.5
+185.3
+173.8
+190.0
+180.2
+189.1
+184.9
+175.8
+182.2
+177.9
+176.4
+184.1
+179.5
+181.8
+175.7
+179.7
+178.7
+185.9
+180.5
+180.9
+175.5
+176.1
+174.4
+175.1
+195.2
+174.9
+190.5
+183.8
+188.7
+182.5
+179.5
+177.0
+174.9
+185.6
+180.8
+192.8
+185.2
+184.0
+177.7
+191.7
+185.3
+185.4
+186.6
+182.9
+181.1
+191.2
+183.0
+176.6
+187.7
+181.9
+179.5
+178.7
+176.9
+178.5
+182.1
+175.4
+175.9
+183.8
+180.6
+191.1
+177.0
+184.6
+188.7
+176.0
+181.4
+188.2
+181.1
+180.0
+178.9
+182.1
+179.6
+179.5
+174.5
+177.6
+179.9
+175.3
+185.2
+175.4
+185.1
+184.9
+182.0
+178.4
+177.0
+182.3
+193.8
+181.6
+197.4
+180.4
+180.9
+182.0
+181.0
+185.8
+178.5
+180.5
+182.3
+175.7
+176.5
+182.8
+176.8
+178.3
+179.5
+181.8
+175.1
+180.2
+179.6
+176.7
+174.6
+180.5
+179.7
+178.9
+177.1
+184.2
+178.4
+175.0
+183.7
+178.3
+179.1
+175.3
+187.5
+189.2
+176.7
+205.6
+179.6
+179.6
+179.1
+182.1
+178.6
+182.2
+181.6
+181.9
+188.8
+184.7
+185.3
+182.5
+174.2
+176.5
+190.1
+180.6
+182.7
+182.2
+179.5
+179.0
+184.4
+175.4
+178.2
+174.6
+179.0
+188.4
+175.9
+173.8
+180.2
+187.3
+186.3
+176.3
+174.9
+178.6
+178.2
+177.0
+180.0
+177.2
+173.5
+195.4
+180.5
+181.6
+177.2
+176.7
+175.3
+174.8
+179.7
+175.0
+176.4
+181.8
+183.0
+180.0
+177.0
+185.8
+183.2
+181.0
+176.8
+177.9
+186.1
+182.5
+174.4
+177.1
+176.7
+176.3
+180.3
+178.9
+178.3
+175.8
+179.9
+190.6
+182.8
+183.4
+177.9
+177.7
+180.0
+202.0
+184.5
+179.5
+181.5
+174.8
+194.2
+185.8
+177.0
+183.9
+177.6
+202.4
+179.9
+175.4
+174.8
+176.7
+177.9
+176.6
+176.5
+181.2
+181.3
+177.4
+179.3
+177.8
+176.2
+197.2
+180.9
+178.2
+184.6
+184.7
+191.0
+182.9
+179.9
+180.3
+177.4
+182.1
+181.9
+182.2
+174.9
+193.4
+175.1
+203.2
+176.0
+180.3
+180.0
+186.3
+174.8
+177.9
+196.1
+177.6
+183.5
+176.2
+187.6
+178.5
+180.6
+178.2
+180.7
+177.6
+176.6
+173.5
+178.7
+175.0
+179.0
+175.5
+174.2
+178.2
+176.6
+176.3
+179.4
+178.6
+175.2
+175.9
+175.0
+179.5
+176.9
+184.0
+184.6
+179.9
+175.3
+176.2
+179.1
+182.8
+180.5
+173.4
+176.0
+198.9
+177.0
+189.7
+183.6
+179.5
+176.5
+175.5
+175.5
+176.4
+182.9
+178.7
+177.5
+180.6
+175.0
+178.9
+177.7
+178.0
+178.6
+176.0
+177.6
+180.8
+179.6
+184.3
+190.2
+177.7
+178.1
+175.0
+178.6
+179.6
+185.1
+193.7
+179.8
+179.3
+178.6
+180.4
+174.9
+177.8
+187.4
+183.7
+181.6
+177.7
+185.4
+180.6
+178.5
+182.8
+179.9
+177.8
+178.0
+184.3
+191.0
+181.1
+197.9
+181.1
+197.8
+175.6
+176.9
+177.9
+187.4
+180.8
+180.6
+174.9
+180.8
+175.7
+176.4
+179.9
+178.3
+178.3
+178.4
+196.4
+177.3
+184.3
+177.3
+176.5
+181.0
+175.0
+179.2
+178.1
+183.3
+177.0
+175.2
+177.2
+173.5
+176.5
+177.0
+178.6
+181.3
+189.2
+186.4
+177.4
+181.7
+176.2
+194.1
+177.9
+186.6
+182.6
+176.6
+175.4
+250.3
+178.7
+183.0
+182.7
+183.1
+183.6
+178.8
+178.3
+179.1
+177.7
+177.9
+186.2
+195.4
+179.2
+183.3
+183.8
+186.3
+182.7
+178.9
+196.0
+177.1
+184.5
+188.3
+188.4
+181.5
+190.1
+179.0
+177.2
+189.1
+184.1
+184.2
+178.7
+187.4
+182.5
+189.8
+181.1
+186.0
+178.8
+178.0
+176.9
+180.6
+174.3
+180.4
+176.7
+175.5
+193.7
+189.7
+196.6
+178.9
+186.3
+182.5
+185.7
+175.7
+176.0
+194.1
+181.0
+178.6
+178.4
+186.8
+175.6
+185.4
+180.7
+184.7
+188.1
+180.4
+183.0
+178.4
+185.1
+181.8
+175.8
+178.9
+177.9
+177.9
+176.5
+290.3
+181.8
+180.8
+183.9
+190.8
+179.0
+183.7
+181.9
+177.5
+178.4
+179.4
+179.9
+174.2
+182.7
+182.9
+178.8
+179.2
+183.2
+181.8
+175.6
+179.0
+175.7
+178.1
+179.2
+183.8
+187.8
+187.3
+175.7
+175.0
+185.5
+175.3
+181.9
+176.7
+184.6
+185.8
+181.1
+186.6
+178.8
+183.4
+175.1
+178.4
+179.6
+176.7
+177.1
+176.5
+181.7
+199.1
+194.8
+176.5
+182.2
+189.9
+177.6
+179.3
+184.9
+178.1
+176.4
+184.6
+174.6
+188.6
+183.7
+178.1
+177.7
+186.1
+176.3
+180.9
+180.3
+181.0
+180.0
+180.0
+180.4
+174.8
+179.5
+176.5
+179.9
+175.6
+184.0
+176.4
+191.6
+178.4
+184.8
+186.7
+197.0
diff --git a/netem/maketable.c b/netem/maketable.c
new file mode 100644
index 0000000..ccb8f0c
--- /dev/null
+++ b/netem/maketable.c
@@ -0,0 +1,234 @@
+/*
+ * Experimental data distribution table generator
+ * Taken from the uncopyrighted NISTnet code (public domain).
+ *
+ * Read in a series of "random" data values, either
+ * experimentally or generated from some probability distribution.
+ * From this, create the inverse distribution table used to approximate
+ * the distribution.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <malloc.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+double *
+readdoubles(FILE *fp, int *number)
+{
+ struct stat info;
+ double *x;
+ int limit;
+ int n=0, i;
+
+ if (!fstat(fileno(fp), &info) &&
+ info.st_size > 0) {
+ limit = 2*info.st_size/sizeof(double); /* @@ approximate */
+ } else {
+ limit = 10000;
+ }
+
+ x = calloc(limit, sizeof(double));
+ if (!x) {
+ perror("double alloc");
+ exit(3);
+ }
+
+ for (i=0; i<limit; ++i){
+ if (fscanf(fp, "%lf", &x[i]) != 1 ||
+ feof(fp))
+ break;
+ ++n;
+ }
+ *number = n;
+ return x;
+}
+
+void
+arraystats(double *x, int limit, double *mu, double *sigma, double *rho)
+{
+ int n=0, i;
+ double sumsquare=0.0, sum=0.0, top=0.0;
+ double sigma2=0.0;
+
+ for (i=0; i<limit; ++i){
+ sumsquare += x[i]*x[i];
+ sum += x[i];
+ ++n;
+ }
+ *mu = sum/(double)n;
+ *sigma = sqrt((sumsquare - (double)n*(*mu)*(*mu))/(double)(n-1));
+
+ for (i=1; i < n; ++i){
+ top += ((double)x[i]- *mu)*((double)x[i-1]- *mu);
+ sigma2 += ((double)x[i-1] - *mu)*((double)x[i-1] - *mu);
+
+ }
+ *rho = top/sigma2;
+}
+
+/* Create a (normalized) distribution table from a set of observed
+ * values. The table is fixed to run from (as it happens) -4 to +4,
+ * with granularity .00002.
+ */
+
+#define TABLESIZE 16384/4
+#define TABLEFACTOR 8192
+#ifndef MINSHORT
+#define MINSHORT -32768
+#define MAXSHORT 32767
+#endif
+
+/* Since entries in the inverse are scaled by TABLEFACTOR, and can't be bigger
+ * than MAXSHORT, we don't bother looking at a larger domain than this:
+ */
+#define DISTTABLEDOMAIN ((MAXSHORT/TABLEFACTOR)+1)
+#define DISTTABLEGRANULARITY 50000
+#define DISTTABLESIZE (DISTTABLEDOMAIN*DISTTABLEGRANULARITY*2)
+
+static int *
+makedist(double *x, int limit, double mu, double sigma)
+{
+ int *table;
+ int i, index, first=DISTTABLESIZE, last=0;
+ double input;
+
+ table = calloc(DISTTABLESIZE, sizeof(int));
+ if (!table) {
+ perror("table alloc");
+ exit(3);
+ }
+
+ for (i=0; i < limit; ++i) {
+ /* Normalize value */
+ input = (x[i]-mu)/sigma;
+
+ index = (int)rint((input+DISTTABLEDOMAIN)*DISTTABLEGRANULARITY);
+ if (index < 0) index = 0;
+ if (index >= DISTTABLESIZE) index = DISTTABLESIZE-1;
+ ++table[index];
+ if (index > last)
+ last = index +1;
+ if (index < first)
+ first = index;
+ }
+ return table;
+}
+
+/* replace an array by its cumulative distribution */
+static void
+cumulativedist(int *table, int limit, int *total)
+{
+ int accum=0;
+
+ while (--limit >= 0) {
+ accum += *table;
+ *table++ = accum;
+ }
+ *total = accum;
+}
+
+static short *
+inverttable(int *table, int inversesize, int tablesize, int cumulative)
+{
+ int i, inverseindex, inversevalue;
+ short *inverse;
+ double findex, fvalue;
+
+ inverse = (short *)malloc(inversesize*sizeof(short));
+ for (i=0; i < inversesize; ++i) {
+ inverse[i] = MINSHORT;
+ }
+ for (i=0; i < tablesize; ++i) {
+ findex = ((double)i/(double)DISTTABLEGRANULARITY) - DISTTABLEDOMAIN;
+ fvalue = (double)table[i]/(double)cumulative;
+ inverseindex = (int)rint(fvalue*inversesize);
+ inversevalue = (int)rint(findex*TABLEFACTOR);
+ if (inversevalue <= MINSHORT) inversevalue = MINSHORT+1;
+ if (inversevalue > MAXSHORT) inversevalue = MAXSHORT;
+ if (inverseindex >= inversesize) inverseindex = inversesize- 1;
+
+ inverse[inverseindex] = inversevalue;
+ }
+ return inverse;
+
+}
+
+/* Run simple linear interpolation over the table to fill in missing entries */
+static void
+interpolatetable(short *table, int limit)
+{
+ int i, j, last, lasti = -1;
+
+ last = MINSHORT;
+ for (i=0; i < limit; ++i) {
+ if (table[i] == MINSHORT) {
+ for (j=i; j < limit; ++j)
+ if (table[j] != MINSHORT)
+ break;
+ if (j < limit) {
+ table[i] = last + (i-lasti)*(table[j]-last)/(j-lasti);
+ } else {
+ table[i] = last + (i-lasti)*(MAXSHORT-last)/(limit-lasti);
+ }
+ } else {
+ last = table[i];
+ lasti = i;
+ }
+ }
+}
+
+static void
+printtable(const short *table, int limit)
+{
+ int i;
+
+ printf("# This is the distribution table for the experimental distribution.\n");
+
+ for (i=0 ; i < limit; ++i) {
+ printf("%d%c", table[i],
+ (i % 8) == 7 ? '\n' : ' ');
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ FILE *fp;
+ double *x;
+ double mu, sigma, rho;
+ int limit;
+ int *table;
+ short *inverse;
+ int total;
+
+ if (argc > 1) {
+ if (!(fp = fopen(argv[1], "r"))) {
+ perror(argv[1]);
+ exit(1);
+ }
+ } else {
+ fp = stdin;
+ }
+ x = readdoubles(fp, &limit);
+ if (limit <= 0) {
+ fprintf(stderr, "Nothing much read!\n");
+ exit(2);
+ }
+ arraystats(x, limit, &mu, &sigma, &rho);
+#ifdef DEBUG
+ fprintf(stderr, "%d values, mu %10.4f, sigma %10.4f, rho %10.4f\n",
+ limit, mu, sigma, rho);
+#endif
+
+ table = makedist(x, limit, mu, sigma);
+ free((void *) x);
+ cumulativedist(table, DISTTABLESIZE, &total);
+ inverse = inverttable(table, TABLESIZE, DISTTABLESIZE, total);
+ interpolatetable(inverse, TABLESIZE);
+ printtable(inverse, TABLESIZE);
+ return 0;
+}
diff --git a/netem/normal.c b/netem/normal.c
new file mode 100644
index 0000000..90963f4
--- /dev/null
+++ b/netem/normal.c
@@ -0,0 +1,51 @@
+/*
+ * Normal distribution table generator
+ * Taken from the uncopyrighted NISTnet code.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <limits.h>
+
+#include <linux/types.h>
+#include <linux/pkt_sched.h>
+
+#define TABLESIZE 16384
+#define TABLEFACTOR NETEM_DIST_SCALE
+
+static double
+normal(double x, double mu, double sigma)
+{
+ return .5 + .5*erf((x-mu)/(sqrt(2.0)*sigma));
+}
+
+
+int
+main(int argc, char **argv)
+{
+ int i, n;
+ double x;
+ double table[TABLESIZE+1];
+
+ for (x = -10.0; x < 10.05; x += .00005) {
+ i = rint(TABLESIZE * normal(x, 0.0, 1.0));
+ table[i] = x;
+ }
+
+
+ printf("# This is the distribution table for the normal distribution.\n");
+ for (i = n = 0; i < TABLESIZE; i += 4) {
+ int value = (int) rint(table[i]*TABLEFACTOR);
+ if (value < SHRT_MIN) value = SHRT_MIN;
+ if (value > SHRT_MAX) value = SHRT_MAX;
+
+ printf(" %d", value);
+ if (++n == 8) {
+ putchar('\n');
+ n = 0;
+ }
+ }
+
+ return 0;
+}
diff --git a/netem/pareto.c b/netem/pareto.c
new file mode 100644
index 0000000..51d9437
--- /dev/null
+++ b/netem/pareto.c
@@ -0,0 +1,41 @@
+/*
+ * Pareto distribution table generator
+ * Taken from the uncopyrighted NISTnet code.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <limits.h>
+
+#include <linux/types.h>
+#include <linux/pkt_sched.h>
+
+static const double a=3.0;
+#define TABLESIZE 16384
+#define TABLEFACTOR NETEM_DIST_SCALE
+
+int
+main(int argc, char **argv)
+{
+ int i, n;
+ double dvalue;
+
+ printf("# This is the distribution table for the pareto distribution.\n");
+
+ for (i = 65536, n = 0; i > 0; i -= 16) {
+ dvalue = (double)i/(double)65536;
+ dvalue = 1.0/pow(dvalue, 1.0/a);
+ dvalue -= 1.5;
+ dvalue *= (4.0/3.0)*(double)TABLEFACTOR;
+ if (dvalue > 32767)
+ dvalue = 32767;
+
+ printf(" %d", (int)rint(dvalue));
+ if (++n == 8) {
+ putchar('\n');
+ n = 0;
+ }
+ }
+
+ return 0;
+}
diff --git a/netem/paretonormal.c b/netem/paretonormal.c
new file mode 100644
index 0000000..9773e37
--- /dev/null
+++ b/netem/paretonormal.c
@@ -0,0 +1,81 @@
+/*
+ * Paretoormal distribution table generator
+ *
+ * This distribution is simply .25*normal + .75*pareto; a combination
+ * which seems to match experimentally observed distributions reasonably
+ * well, but is computationally easy to handle.
+ * The entries represent a scaled inverse of the cumulative distribution
+ * function.
+ *
+ * Taken from the uncopyrighted NISTnet code.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <limits.h>
+#include <malloc.h>
+
+#include <linux/types.h>
+#include <linux/pkt_sched.h>
+
+#define TABLESIZE 16384
+#define TABLEFACTOR NETEM_DIST_SCALE
+
+static double
+normal(double x, double mu, double sigma)
+{
+ return .5 + .5*erf((x-mu)/(sqrt(2.0)*sigma));
+}
+
+static const double a=3.0;
+
+static int
+paretovalue(int i)
+{
+ double dvalue;
+
+ i = 65536-4*i;
+ dvalue = (double)i/(double)65536;
+ dvalue = 1.0/pow(dvalue, 1.0/a);
+ dvalue -= 1.5;
+ dvalue *= (4.0/3.0)*(double)TABLEFACTOR;
+ if (dvalue > 32767)
+ dvalue = 32767;
+ return (int)rint(dvalue);
+}
+
+int
+main(int argc, char **argv)
+{
+ int i,n;
+ double x;
+ double table[TABLESIZE+1];
+
+ for (x = -10.0; x < 10.05; x += .00005) {
+ i = rint(TABLESIZE*normal(x, 0.0, 1.0));
+ table[i] = x;
+ }
+ printf(
+"# This is the distribution table for the paretonormal distribution.\n"
+ );
+
+ for (i = n = 0; i < TABLESIZE; i += 4) {
+ int normvalue, parvalue, value;
+
+ normvalue = (int) rint(table[i]*TABLEFACTOR);
+ parvalue = paretovalue(i);
+
+ value = (normvalue+3*parvalue)/4;
+ if (value < SHRT_MIN) value = SHRT_MIN;
+ if (value > SHRT_MAX) value = SHRT_MAX;
+
+ printf(" %d", value);
+ if (++n == 8) {
+ putchar('\n');
+ n = 0;
+ }
+ }
+
+ return 0;
+}
diff --git a/netem/stats.c b/netem/stats.c
new file mode 100644
index 0000000..ed70f16
--- /dev/null
+++ b/netem/stats.c
@@ -0,0 +1,77 @@
+/*
+ * Experimental data distribution table generator
+ * Taken from the uncopyrighted NISTnet code (public domain).
+ *
+ * Rread in a series of "random" data values, either
+ * experimentally or generated from some probability distribution.
+ * From this, report statistics.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <malloc.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+void
+stats(FILE *fp)
+{
+ struct stat info;
+ double *x;
+ int limit;
+ int n=0, i;
+ double mu=0.0, sigma=0.0, sumsquare=0.0, sum=0.0, top=0.0, rho=0.0;
+ double sigma2=0.0;
+
+ fstat(fileno(fp), &info);
+ if (info.st_size > 0) {
+ limit = 2*info.st_size/sizeof(double); /* @@ approximate */
+ } else {
+ limit = 10000;
+ }
+ x = (double *)malloc(limit*sizeof(double));
+
+ for (i=0; i<limit; ++i){
+ fscanf(fp, "%lf", &x[i]);
+ if (feof(fp))
+ break;
+ sumsquare += x[i]*x[i];
+ sum += x[i];
+ ++n;
+ }
+ mu = sum/(double)n;
+ sigma = sqrt((sumsquare - (double)n*mu*mu)/(double)(n-1));
+
+ for (i=1; i < n; ++i){
+ top += ((double)x[i]-mu)*((double)x[i-1]-mu);
+ sigma2 += ((double)x[i-1] - mu)*((double)x[i-1] - mu);
+
+ }
+ rho = top/sigma2;
+
+ printf("mu = %12.6f\n", mu);
+ printf("sigma = %12.6f\n", sigma);
+ printf("rho = %12.6f\n", rho);
+ /*printf("sigma2 = %10.4f\n", sqrt(sigma2/(double)(n-1)));*/
+ /*printf("correlation rho = %10.6f\n", top/((double)(n-1)*sigma*sigma));*/
+}
+
+
+int
+main(int argc, char **argv)
+{
+ FILE *fp;
+
+ if (argc > 1) {
+ fp = fopen(argv[1], "r");
+ if (!fp) {
+ perror(argv[1]);
+ exit(1);
+ }
+ } else {
+ fp = stdin;
+ }
+ stats(fp);
+ return 0;
+}
diff --git a/rdma/.gitignore b/rdma/.gitignore
new file mode 100644
index 0000000..51fb172
--- /dev/null
+++ b/rdma/.gitignore
@@ -0,0 +1 @@
+rdma
diff --git a/rdma/Makefile b/rdma/Makefile
new file mode 100644
index 0000000..e3f550b
--- /dev/null
+++ b/rdma/Makefile
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+include ../config.mk
+
+TARGETS :=
+
+ifeq ($(HAVE_MNL),y)
+CFLAGS += -I./include/uapi/
+
+RDMA_OBJ = rdma.o utils.o dev.o link.o res.o res-pd.o res-mr.o res-cq.o \
+ res-cmid.o res-qp.o sys.o stat.o
+
+TARGETS += rdma
+endif
+
+all: $(TARGETS) $(LIBS)
+
+rdma: $(RDMA_OBJ) $(LIBS)
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+ for i in $(TARGETS); \
+ do install -m 0755 $$i $(DESTDIR)$(SBINDIR); \
+ done
+
+clean:
+ rm -f $(RDMA_OBJ) $(TARGETS)
diff --git a/rdma/dev.c b/rdma/dev.c
new file mode 100644
index 0000000..c597cba
--- /dev/null
+++ b/rdma/dev.c
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * dev.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+
+#include <fcntl.h>
+#include "rdma.h"
+
+static int dev_help(struct rd *rd)
+{
+ pr_out("Usage: %s dev show [DEV]\n", rd->filename);
+ pr_out(" %s dev set [DEV] name DEVNAME\n", rd->filename);
+ pr_out(" %s dev set [DEV] netns NSNAME\n", rd->filename);
+ pr_out(" %s dev set [DEV] adaptive-moderation [on|off]\n", rd->filename);
+ return 0;
+}
+
+static const char *dev_caps_to_str(uint32_t idx)
+{
+#define RDMA_DEV_FLAGS_LOW(x) \
+ x(RESIZE_MAX_WR, 0) \
+ x(BAD_PKEY_CNTR, 1) \
+ x(BAD_QKEY_CNTR, 2) \
+ x(RAW_MULTI, 3) \
+ x(AUTO_PATH_MIG, 4) \
+ x(CHANGE_PHY_PORT, 5) \
+ x(UD_AV_PORT_ENFORCE_PORT_ENFORCE, 6) \
+ x(CURR_QP_STATE_MOD, 7) \
+ x(SHUTDOWN_PORT, 8) \
+ x(INIT_TYPE, 9) \
+ x(PORT_ACTIVE_EVENT, 10) \
+ x(SYS_IMAGE_GUID, 11) \
+ x(RC_RNR_NAK_GEN, 12) \
+ x(SRQ_RESIZE, 13) \
+ x(N_NOTIFY_CQ, 14) \
+ x(LOCAL_DMA_LKEY, 15) \
+ x(MEM_WINDOW, 17) \
+ x(UD_IP_CSUM, 18) \
+ x(UD_TSO, 19) \
+ x(XRC, 20) \
+ x(MEM_MGT_EXTENSIONS, 21) \
+ x(BLOCK_MULTICAST_LOOPBACK, 22) \
+ x(MEM_WINDOW_TYPE_2A, 23) \
+ x(MEM_WINDOW_TYPE_2B, 24) \
+ x(RC_IP_CSUM, 25) \
+ x(RAW_IP_CSUM, 26) \
+ x(CROSS_CHANNEL, 27) \
+ x(MANAGED_FLOW_STEERING, 29) \
+ x(SIGNATURE_HANDOVER, 30) \
+ x(ON_DEMAND_PAGING, 31)
+
+#define RDMA_DEV_FLAGS_HIGH(x) \
+ x(SG_GAPS_REG, 0) \
+ x(VIRTUAL_FUNCTION, 1) \
+ x(RAW_SCATTER_FCS, 2) \
+ x(RDMA_NETDEV_OPA_VNIC, 3) \
+ x(PCI_WRITE_END_PADDING, 4)
+
+ /*
+ * Separation below is needed to allow compilation of rdmatool
+ * on 32bits systems. On such systems, C-enum is limited to be
+ * int and can't hold more than 32 bits.
+ */
+ enum { RDMA_DEV_FLAGS_LOW(RDMA_BITMAP_ENUM) };
+ enum { RDMA_DEV_FLAGS_HIGH(RDMA_BITMAP_ENUM) };
+
+ static const char * const
+ rdma_dev_names_low[] = { RDMA_DEV_FLAGS_LOW(RDMA_BITMAP_NAMES) };
+ static const char * const
+ rdma_dev_names_high[] = { RDMA_DEV_FLAGS_HIGH(RDMA_BITMAP_NAMES) };
+ uint32_t high_idx;
+ #undef RDMA_DEV_FLAGS_LOW
+ #undef RDMA_DEV_FLAGS_HIGH
+
+ if (idx < ARRAY_SIZE(rdma_dev_names_low) && rdma_dev_names_low[idx])
+ return rdma_dev_names_low[idx];
+
+ high_idx = idx - ARRAY_SIZE(rdma_dev_names_low);
+ if (high_idx < ARRAY_SIZE(rdma_dev_names_high) &&
+ rdma_dev_names_high[high_idx])
+ return rdma_dev_names_high[high_idx];
+
+ return "UNKNOWN";
+}
+
+static void dev_print_caps(struct rd *rd, struct nlattr **tb)
+{
+ uint64_t caps;
+ uint32_t idx;
+
+ if (!tb[RDMA_NLDEV_ATTR_CAP_FLAGS])
+ return;
+
+ caps = mnl_attr_get_u64(tb[RDMA_NLDEV_ATTR_CAP_FLAGS]);
+
+ if (rd->json_output) {
+ jsonw_name(rd->jw, "caps");
+ jsonw_start_array(rd->jw);
+ } else {
+ pr_out("\n caps: <");
+ }
+ for (idx = 0; caps; idx++) {
+ if (caps & 0x1) {
+ if (rd->json_output) {
+ jsonw_string(rd->jw, dev_caps_to_str(idx));
+ } else {
+ pr_out("%s", dev_caps_to_str(idx));
+ if (caps >> 0x1)
+ pr_out(", ");
+ }
+ }
+ caps >>= 0x1;
+ }
+
+ if (rd->json_output)
+ jsonw_end_array(rd->jw);
+ else
+ pr_out(">");
+}
+
+static void dev_print_fw(struct rd *rd, struct nlattr **tb)
+{
+ const char *str;
+ if (!tb[RDMA_NLDEV_ATTR_FW_VERSION])
+ return;
+
+ str = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_FW_VERSION]);
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "fw", str);
+ else
+ pr_out("fw %s ", str);
+}
+
+static void dev_print_node_guid(struct rd *rd, struct nlattr **tb)
+{
+ uint64_t node_guid;
+ uint16_t vp[4];
+ char str[32];
+
+ if (!tb[RDMA_NLDEV_ATTR_NODE_GUID])
+ return;
+
+ node_guid = mnl_attr_get_u64(tb[RDMA_NLDEV_ATTR_NODE_GUID]);
+ memcpy(vp, &node_guid, sizeof(uint64_t));
+ snprintf(str, 32, "%04x:%04x:%04x:%04x", vp[3], vp[2], vp[1], vp[0]);
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "node_guid", str);
+ else
+ pr_out("node_guid %s ", str);
+}
+
+static void dev_print_sys_image_guid(struct rd *rd, struct nlattr **tb)
+{
+ uint64_t sys_image_guid;
+ uint16_t vp[4];
+ char str[32];
+
+ if (!tb[RDMA_NLDEV_ATTR_SYS_IMAGE_GUID])
+ return;
+
+ sys_image_guid = mnl_attr_get_u64(tb[RDMA_NLDEV_ATTR_SYS_IMAGE_GUID]);
+ memcpy(vp, &sys_image_guid, sizeof(uint64_t));
+ snprintf(str, 32, "%04x:%04x:%04x:%04x", vp[3], vp[2], vp[1], vp[0]);
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "sys_image_guid", str);
+ else
+ pr_out("sys_image_guid %s ", str);
+}
+
+static void dev_print_dim_setting(struct rd *rd, struct nlattr **tb)
+{
+ uint8_t dim_setting;
+
+ if (!tb[RDMA_NLDEV_ATTR_DEV_DIM])
+ return;
+
+ dim_setting = mnl_attr_get_u8(tb[RDMA_NLDEV_ATTR_DEV_DIM]);
+ if (dim_setting > 1)
+ return;
+
+ print_on_off(rd, "adaptive-moderation", dim_setting);
+
+}
+
+static const char *node_type_to_str(uint8_t node_type)
+{
+ static const char * const node_type_str[] = { "unknown", "ca",
+ "switch", "router",
+ "rnic", "usnic",
+ "usnic_udp",
+ "unspecified" };
+ if (node_type < ARRAY_SIZE(node_type_str))
+ return node_type_str[node_type];
+ return "unknown";
+}
+
+static void dev_print_node_type(struct rd *rd, struct nlattr **tb)
+{
+ const char *node_str;
+ uint8_t node_type;
+
+ if (!tb[RDMA_NLDEV_ATTR_DEV_NODE_TYPE])
+ return;
+
+ node_type = mnl_attr_get_u8(tb[RDMA_NLDEV_ATTR_DEV_NODE_TYPE]);
+ node_str = node_type_to_str(node_type);
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "node_type", node_str);
+ else
+ pr_out("node_type %s ", node_str);
+}
+
+static int dev_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct rd *rd = data;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ if (rd->json_output) {
+ jsonw_uint_field(rd->jw, "ifindex", idx);
+ jsonw_string_field(rd->jw, "ifname", name);
+ } else {
+ pr_out("%u: %s: ", idx, name);
+ }
+
+ dev_print_node_type(rd, tb);
+ dev_print_fw(rd, tb);
+ dev_print_node_guid(rd, tb);
+ dev_print_sys_image_guid(rd, tb);
+ if (rd->show_details) {
+ dev_print_dim_setting(rd, tb);
+ dev_print_caps(rd, tb);
+ }
+
+ if (!rd->json_output)
+ pr_out("\n");
+ return MNL_CB_OK;
+}
+
+static int dev_no_args(struct rd *rd)
+{
+ uint32_t seq;
+ int ret;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_GET,
+ &seq, (NLM_F_REQUEST | NLM_F_ACK));
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ ret = rd_send_msg(rd);
+ if (ret)
+ return ret;
+
+ if (rd->json_output)
+ jsonw_start_object(rd->jw);
+ ret = rd_recv_msg(rd, dev_parse_cb, rd, seq);
+ if (rd->json_output)
+ jsonw_end_object(rd->jw);
+ return ret;
+}
+
+static int dev_one_show(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, dev_no_args},
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int dev_set_name(struct rd *rd)
+{
+ uint32_t seq;
+
+ if (rd_no_arg(rd)) {
+ pr_err("Please provide device new name.\n");
+ return -EINVAL;
+ }
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_SET,
+ &seq, (NLM_F_REQUEST | NLM_F_ACK));
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_strz(rd->nlh, RDMA_NLDEV_ATTR_DEV_NAME, rd_argv(rd));
+
+ return rd_sendrecv_msg(rd, seq);
+}
+
+static int dev_set_netns(struct rd *rd)
+{
+ char *netns_path;
+ uint32_t seq;
+ int netns;
+ int ret;
+
+ if (rd_no_arg(rd)) {
+ pr_err("Please provide device name.\n");
+ return -EINVAL;
+ }
+
+ if (asprintf(&netns_path, "%s/%s", NETNS_RUN_DIR, rd_argv(rd)) < 0)
+ return -ENOMEM;
+
+ netns = open(netns_path, O_RDONLY | O_CLOEXEC);
+ if (netns < 0) {
+ fprintf(stderr, "Cannot open network namespace \"%s\": %s\n",
+ rd_argv(rd), strerror(errno));
+ ret = -EINVAL;
+ goto done;
+ }
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_SET,
+ &seq, (NLM_F_REQUEST | NLM_F_ACK));
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_NET_NS_FD, netns);
+ ret = rd_sendrecv_msg(rd, seq);
+ close(netns);
+done:
+ free(netns_path);
+ return ret;
+}
+
+static int dev_set_dim_sendmsg(struct rd *rd, uint8_t dim_setting)
+{
+ uint32_t seq;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_SET, &seq,
+ (NLM_F_REQUEST | NLM_F_ACK));
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u8(rd->nlh, RDMA_NLDEV_ATTR_DEV_DIM, dim_setting);
+
+ return rd_sendrecv_msg(rd, seq);
+}
+
+static int dev_set_dim_off(struct rd *rd)
+{
+ return dev_set_dim_sendmsg(rd, 0);
+}
+
+static int dev_set_dim_on(struct rd *rd)
+{
+ return dev_set_dim_sendmsg(rd, 1);
+}
+
+static int dev_set_dim(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, dev_help},
+ { "on", dev_set_dim_on},
+ { "off", dev_set_dim_off},
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int dev_one_set(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, dev_help},
+ { "name", dev_set_name},
+ { "netns", dev_set_netns},
+ { "adaptive-moderation", dev_set_dim},
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int dev_show(struct rd *rd)
+{
+ return rd_exec_dev(rd, dev_one_show);
+}
+
+static int dev_set(struct rd *rd)
+{
+ return rd_exec_require_dev(rd, dev_one_set);
+}
+
+int cmd_dev(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, dev_show },
+ { "show", dev_show },
+ { "list", dev_show },
+ { "set", dev_set },
+ { "help", dev_help },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "dev command");
+}
diff --git a/rdma/link.c b/rdma/link.c
new file mode 100644
index 0000000..10b2e51
--- /dev/null
+++ b/rdma/link.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * link.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+
+#include "rdma.h"
+
+static int link_help(struct rd *rd)
+{
+ pr_out("Usage: %s link show [DEV/PORT_INDEX]\n", rd->filename);
+ pr_out("Usage: %s link add NAME type TYPE netdev NETDEV\n",
+ rd->filename);
+ pr_out("Usage: %s link delete NAME\n", rd->filename);
+ return 0;
+}
+
+static const char *caps_to_str(uint32_t idx)
+{
+#define RDMA_PORT_FLAGS_LOW(x) \
+ x(RESERVED, 0) \
+ x(SM, 1) \
+ x(NOTICE, 2) \
+ x(TRAP, 3) \
+ x(OPT_IPD, 4) \
+ x(AUTO_MIGR, 5) \
+ x(SL_MAP, 6) \
+ x(MKEY_NVRAM, 7) \
+ x(PKEY_NVRAM, 8) \
+ x(LED_INFO, 9) \
+ x(SM_DISABLED, 10) \
+ x(SYS_IMAGE_GUID, 11) \
+ x(PKEY_SW_EXT_PORT_TRAP, 12) \
+ x(CABLE_INFO, 13) \
+ x(EXTENDED_SPEEDS, 14) \
+ x(CAP_MASK2, 15) \
+ x(CM, 16) \
+ x(SNMP_TUNNEL, 17) \
+ x(REINIT, 18) \
+ x(DEVICE_MGMT, 19) \
+ x(VENDOR_CLASS, 20) \
+ x(DR_NOTICE, 21) \
+ x(CAP_MASK_NOTICE, 22) \
+ x(BOOT_MGMT, 23) \
+ x(LINK_LATENCY, 24) \
+ x(CLIENT_REG, 25) \
+ x(OTHER_LOCAL_CHANGES, 26) \
+ x(LINK_SPPED_WIDTH, 27) \
+ x(VENDOR_SPECIFIC_MADS, 28) \
+ x(MULT_PKER_TRAP, 29) \
+ x(MULT_FDB, 30) \
+ x(HIERARCHY_INFO, 31)
+
+#define RDMA_PORT_FLAGS_HIGH(x) \
+ x(SET_NODE_DESC, 0) \
+ x(EXT_INFO, 1) \
+ x(VIRT, 2) \
+ x(SWITCH_POR_STATE_TABLE, 3) \
+ x(LINK_WIDTH_2X, 4) \
+ x(LINK_SPEED_HDR, 5)
+
+ /*
+ * Separation below is needed to allow compilation of rdmatool
+ * on 32bits systems. On such systems, C-enum is limited to be
+ * int and can't hold more than 32 bits.
+ */
+ enum { RDMA_PORT_FLAGS_LOW(RDMA_BITMAP_ENUM) };
+ enum { RDMA_PORT_FLAGS_HIGH(RDMA_BITMAP_ENUM) };
+
+ static const char * const
+ rdma_port_names_low[] = { RDMA_PORT_FLAGS_LOW(RDMA_BITMAP_NAMES) };
+ static const char * const
+ rdma_port_names_high[] = { RDMA_PORT_FLAGS_HIGH(RDMA_BITMAP_NAMES) };
+ uint32_t high_idx;
+ #undef RDMA_PORT_FLAGS_LOW
+ #undef RDMA_PORT_FLAGS_HIGH
+
+ if (idx < ARRAY_SIZE(rdma_port_names_low) && rdma_port_names_low[idx])
+ return rdma_port_names_low[idx];
+
+ high_idx = idx - ARRAY_SIZE(rdma_port_names_low);
+ if (high_idx < ARRAY_SIZE(rdma_port_names_high) &&
+ rdma_port_names_high[high_idx])
+ return rdma_port_names_high[high_idx];
+
+ return "UNKNOWN";
+}
+
+static void link_print_caps(struct rd *rd, struct nlattr **tb)
+{
+ uint64_t caps;
+ uint32_t idx;
+
+ if (!tb[RDMA_NLDEV_ATTR_CAP_FLAGS])
+ return;
+
+ caps = mnl_attr_get_u64(tb[RDMA_NLDEV_ATTR_CAP_FLAGS]);
+
+ if (rd->json_output) {
+ jsonw_name(rd->jw, "caps");
+ jsonw_start_array(rd->jw);
+ } else {
+ pr_out("\n caps: <");
+ }
+ for (idx = 0; caps; idx++) {
+ if (caps & 0x1) {
+ if (rd->json_output) {
+ jsonw_string(rd->jw, caps_to_str(idx));
+ } else {
+ pr_out("%s", caps_to_str(idx));
+ if (caps >> 0x1)
+ pr_out(", ");
+ }
+ }
+ caps >>= 0x1;
+ }
+
+ if (rd->json_output)
+ jsonw_end_array(rd->jw);
+ else
+ pr_out(">");
+}
+
+static void link_print_subnet_prefix(struct rd *rd, struct nlattr **tb)
+{
+ uint64_t subnet_prefix;
+ uint16_t vp[4];
+ char str[32];
+
+ if (!tb[RDMA_NLDEV_ATTR_SUBNET_PREFIX])
+ return;
+
+ subnet_prefix = mnl_attr_get_u64(tb[RDMA_NLDEV_ATTR_SUBNET_PREFIX]);
+ memcpy(vp, &subnet_prefix, sizeof(uint64_t));
+ snprintf(str, 32, "%04x:%04x:%04x:%04x", vp[3], vp[2], vp[1], vp[0]);
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "subnet_prefix", str);
+ else
+ pr_out("subnet_prefix %s ", str);
+}
+
+static void link_print_lid(struct rd *rd, struct nlattr **tb)
+{
+ uint32_t lid;
+
+ if (!tb[RDMA_NLDEV_ATTR_LID])
+ return;
+
+ lid = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_LID]);
+ if (rd->json_output)
+ jsonw_uint_field(rd->jw, "lid", lid);
+ else
+ pr_out("lid %u ", lid);
+}
+
+static void link_print_sm_lid(struct rd *rd, struct nlattr **tb)
+{
+ uint32_t sm_lid;
+
+ if (!tb[RDMA_NLDEV_ATTR_SM_LID])
+ return;
+
+ sm_lid = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_SM_LID]);
+ if (rd->json_output)
+ jsonw_uint_field(rd->jw, "sm_lid", sm_lid);
+ else
+ pr_out("sm_lid %u ", sm_lid);
+}
+
+static void link_print_lmc(struct rd *rd, struct nlattr **tb)
+{
+ uint8_t lmc;
+
+ if (!tb[RDMA_NLDEV_ATTR_LMC])
+ return;
+
+ lmc = mnl_attr_get_u8(tb[RDMA_NLDEV_ATTR_LMC]);
+ if (rd->json_output)
+ jsonw_uint_field(rd->jw, "lmc", lmc);
+ else
+ pr_out("lmc %u ", lmc);
+}
+
+static const char *link_state_to_str(uint8_t link_state)
+{
+ static const char * const link_state_str[] = { "NOP", "DOWN",
+ "INIT", "ARMED",
+ "ACTIVE",
+ "ACTIVE_DEFER" };
+ if (link_state < ARRAY_SIZE(link_state_str))
+ return link_state_str[link_state];
+ return "UNKNOWN";
+}
+
+static void link_print_state(struct rd *rd, struct nlattr **tb)
+{
+ uint8_t state;
+
+ if (!tb[RDMA_NLDEV_ATTR_PORT_STATE])
+ return;
+
+ state = mnl_attr_get_u8(tb[RDMA_NLDEV_ATTR_PORT_STATE]);
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "state", link_state_to_str(state));
+ else
+ pr_out("state %s ", link_state_to_str(state));
+}
+
+static const char *phys_state_to_str(uint8_t phys_state)
+{
+ static const char * const phys_state_str[] = { "NOP", "SLEEP",
+ "POLLING", "DISABLED",
+ "ARMED", "LINK_UP",
+ "LINK_ERROR_RECOVER",
+ "PHY_TEST", "UNKNOWN",
+ "OPA_OFFLINE",
+ "UNKNOWN", "OPA_TEST" };
+ if (phys_state < ARRAY_SIZE(phys_state_str))
+ return phys_state_str[phys_state];
+ return "UNKNOWN";
+};
+
+static void link_print_phys_state(struct rd *rd, struct nlattr **tb)
+{
+ uint8_t phys_state;
+
+ if (!tb[RDMA_NLDEV_ATTR_PORT_PHYS_STATE])
+ return;
+
+ phys_state = mnl_attr_get_u8(tb[RDMA_NLDEV_ATTR_PORT_PHYS_STATE]);
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "physical_state",
+ phys_state_to_str(phys_state));
+ else
+ pr_out("physical_state %s ", phys_state_to_str(phys_state));
+}
+
+static void link_print_netdev(struct rd *rd, struct nlattr **tb)
+{
+ const char *netdev_name;
+ uint32_t idx;
+
+ if (!tb[RDMA_NLDEV_ATTR_NDEV_NAME] || !tb[RDMA_NLDEV_ATTR_NDEV_INDEX])
+ return;
+
+ netdev_name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_NDEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_NDEV_INDEX]);
+ if (rd->json_output) {
+ jsonw_string_field(rd->jw, "netdev", netdev_name);
+ jsonw_uint_field(rd->jw, "netdev_index", idx);
+ } else {
+ pr_out("netdev %s ", netdev_name);
+ if (rd->show_details)
+ pr_out("netdev_index %u ", idx);
+ }
+}
+
+static int link_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct rd *rd = data;
+ uint32_t port, idx;
+ char name[32];
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+
+ if (!tb[RDMA_NLDEV_ATTR_PORT_INDEX]) {
+ pr_err("This tool doesn't support switches yet\n");
+ return MNL_CB_ERROR;
+ }
+
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ port = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_PORT_INDEX]);
+ snprintf(name, 32, "%s/%u",
+ mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]), port);
+
+ if (rd->json_output) {
+ jsonw_uint_field(rd->jw, "ifindex", idx);
+ jsonw_uint_field(rd->jw, "port", port);
+ jsonw_string_field(rd->jw, "ifname", name);
+
+ } else {
+ pr_out("%u/%u: %s: ", idx, port, name);
+ }
+
+ link_print_subnet_prefix(rd, tb);
+ link_print_lid(rd, tb);
+ link_print_sm_lid(rd, tb);
+ link_print_lmc(rd, tb);
+ link_print_state(rd, tb);
+ link_print_phys_state(rd, tb);
+ link_print_netdev(rd, tb);
+ if (rd->show_details)
+ link_print_caps(rd, tb);
+
+ if (!rd->json_output)
+ pr_out("\n");
+ return MNL_CB_OK;
+}
+
+static int link_no_args(struct rd *rd)
+{
+ uint32_t seq;
+ int ret;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_PORT_GET, &seq,
+ (NLM_F_REQUEST | NLM_F_ACK));
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_PORT_INDEX, rd->port_idx);
+ ret = rd_send_msg(rd);
+ if (ret)
+ return ret;
+
+ if (rd->json_output)
+ jsonw_start_object(rd->jw);
+ ret = rd_recv_msg(rd, link_parse_cb, rd, seq);
+ if (rd->json_output)
+ jsonw_end_object(rd->jw);
+ return ret;
+}
+
+static int link_one_show(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, link_no_args},
+ { 0 }
+ };
+
+ if (!rd->port_idx)
+ return 0;
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int link_show(struct rd *rd)
+{
+ return rd_exec_link(rd, link_one_show, true);
+}
+
+static int link_add_netdev(struct rd *rd)
+{
+ char *link_netdev;
+ uint32_t seq;
+
+ if (rd_no_arg(rd)) {
+ pr_err("Please provide a net device name.\n");
+ return -EINVAL;
+ }
+
+ link_netdev = rd_argv(rd);
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_NEWLINK, &seq,
+ (NLM_F_REQUEST | NLM_F_ACK));
+ mnl_attr_put_strz(rd->nlh, RDMA_NLDEV_ATTR_DEV_NAME, rd->link_name);
+ mnl_attr_put_strz(rd->nlh, RDMA_NLDEV_ATTR_LINK_TYPE, rd->link_type);
+ mnl_attr_put_strz(rd->nlh, RDMA_NLDEV_ATTR_NDEV_NAME, link_netdev);
+ return rd_sendrecv_msg(rd, seq);
+}
+
+static int link_add_type(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, link_help},
+ { "netdev", link_add_netdev},
+ { 0 }
+ };
+
+ if (rd_no_arg(rd)) {
+ pr_err("Please provide a link type name.\n");
+ return -EINVAL;
+ }
+ rd->link_type = rd_argv(rd);
+ rd_arg_inc(rd);
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int link_add(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, link_help},
+ { "type", link_add_type},
+ { 0 }
+ };
+
+ if (rd_no_arg(rd)) {
+ pr_err("Please provide a link name to add.\n");
+ return -EINVAL;
+ }
+ rd->link_name = rd_argv(rd);
+ rd_arg_inc(rd);
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int _link_del(struct rd *rd)
+{
+ uint32_t seq;
+
+ if (!rd_no_arg(rd)) {
+ pr_err("Unknown parameter %s\n", rd_argv(rd));
+ return -EINVAL;
+ }
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_DELLINK, &seq,
+ (NLM_F_REQUEST | NLM_F_ACK));
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ return rd_sendrecv_msg(rd, seq);
+}
+
+static int link_del(struct rd *rd)
+{
+ return rd_exec_require_dev(rd, _link_del);
+}
+
+int cmd_link(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, link_show },
+ { "add", link_add },
+ { "delete", link_del },
+ { "show", link_show },
+ { "list", link_show },
+ { "help", link_help },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "link command");
+}
diff --git a/rdma/rdma.c b/rdma/rdma.c
new file mode 100644
index 0000000..4e34da9
--- /dev/null
+++ b/rdma/rdma.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * rdma.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+
+#include "rdma.h"
+#include "SNAPSHOT.h"
+
+static void help(char *name)
+{
+ pr_out("Usage: %s [ OPTIONS ] OBJECT { COMMAND | help }\n"
+ " %s [ -f[orce] ] -b[atch] filename\n"
+ "where OBJECT := { dev | link | resource | system | statistic | help }\n"
+ " OPTIONS := { -V[ersion] | -d[etails] | -j[son] | -p[retty]}\n", name, name);
+}
+
+static int cmd_help(struct rd *rd)
+{
+ help(rd->filename);
+ return 0;
+}
+
+static int rd_cmd(struct rd *rd, int argc, char **argv)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, cmd_help },
+ { "help", cmd_help },
+ { "dev", cmd_dev },
+ { "link", cmd_link },
+ { "resource", cmd_res },
+ { "system", cmd_sys },
+ { "statistic", cmd_stat },
+ { 0 }
+ };
+
+ rd->argc = argc;
+ rd->argv = argv;
+
+ return rd_exec_cmd(rd, cmds, "object");
+}
+
+static int rd_batch(struct rd *rd, const char *name, bool force)
+{
+ char *line = NULL;
+ size_t len = 0;
+ int ret = 0;
+
+ if (name && strcmp(name, "-") != 0) {
+ if (!freopen(name, "r", stdin)) {
+ pr_err("Cannot open file \"%s\" for reading: %s\n",
+ name, strerror(errno));
+ return errno;
+ }
+ }
+
+ cmdlineno = 0;
+ while (getcmdline(&line, &len, stdin) != -1) {
+ char *largv[512];
+ int largc;
+
+ largc = makeargs(line, largv, ARRAY_SIZE(largv));
+ if (!largc)
+ continue; /* blank line */
+
+ ret = rd_cmd(rd, largc, largv);
+ if (ret) {
+ pr_err("Command failed %s:%d\n", name, cmdlineno);
+ if (!force)
+ break;
+ }
+ }
+
+ free(line);
+
+ return ret;
+}
+
+static int rd_init(struct rd *rd, char *filename)
+{
+ uint32_t seq;
+ int ret;
+
+ rd->filename = filename;
+ INIT_LIST_HEAD(&rd->dev_map_list);
+ INIT_LIST_HEAD(&rd->filter_list);
+
+ if (rd->json_output) {
+ rd->jw = jsonw_new(stdout);
+ if (!rd->jw) {
+ pr_err("Failed to create JSON writer\n");
+ return -ENOMEM;
+ }
+ jsonw_pretty(rd->jw, rd->pretty_output);
+ }
+
+ rd->buff = malloc(MNL_SOCKET_BUFFER_SIZE);
+ if (!rd->buff)
+ return -ENOMEM;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_GET,
+ &seq, (NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP));
+ ret = rd_send_msg(rd);
+ if (ret)
+ return ret;
+
+ return rd_recv_msg(rd, rd_dev_init_cb, rd, seq);
+}
+
+static void rd_cleanup(struct rd *rd)
+{
+ if (rd->json_output)
+ jsonw_destroy(&rd->jw);
+ rd_free(rd);
+}
+
+int main(int argc, char **argv)
+{
+ static const struct option long_options[] = {
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { "json", no_argument, NULL, 'j' },
+ { "pretty", no_argument, NULL, 'p' },
+ { "details", no_argument, NULL, 'd' },
+ { "force", no_argument, NULL, 'f' },
+ { "batch", required_argument, NULL, 'b' },
+ { NULL, 0, NULL, 0 }
+ };
+ bool show_driver_details = false;
+ const char *batch_file = NULL;
+ bool pretty_output = false;
+ bool show_details = false;
+ bool json_output = false;
+ bool force = false;
+ struct rd rd = {};
+ char *filename;
+ int opt;
+ int err;
+
+ filename = basename(argv[0]);
+
+ while ((opt = getopt_long(argc, argv, ":Vhdpjfb:",
+ long_options, NULL)) >= 0) {
+ switch (opt) {
+ case 'V':
+ printf("%s utility, iproute2-ss%s\n",
+ filename, SNAPSHOT);
+ return EXIT_SUCCESS;
+ case 'p':
+ pretty_output = true;
+ break;
+ case 'd':
+ if (show_details)
+ show_driver_details = true;
+ else
+ show_details = true;
+ break;
+ case 'j':
+ json_output = true;
+ break;
+ case 'f':
+ force = true;
+ break;
+ case 'b':
+ batch_file = optarg;
+ break;
+ case 'h':
+ help(filename);
+ return EXIT_SUCCESS;
+ case ':':
+ pr_err("-%c option requires an argument\n", optopt);
+ return EXIT_FAILURE;
+ default:
+ pr_err("Unknown option.\n");
+ help(filename);
+ return EXIT_FAILURE;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ rd.show_details = show_details;
+ rd.show_driver_details = show_driver_details;
+ rd.json_output = json_output;
+ rd.pretty_output = pretty_output;
+
+ err = rd_init(&rd, filename);
+ if (err)
+ goto out;
+
+ if (batch_file)
+ err = rd_batch(&rd, batch_file, force);
+ else
+ err = rd_cmd(&rd, argc, argv);
+out:
+ /* Always cleanup */
+ rd_cleanup(&rd);
+ return err ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/rdma/rdma.h b/rdma/rdma.h
new file mode 100644
index 0000000..dfd1b70
--- /dev/null
+++ b/rdma/rdma.h
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB */
+/*
+ * rdma.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+#ifndef _RDMA_TOOL_H_
+#define _RDMA_TOOL_H_
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <libmnl/libmnl.h>
+#include <rdma/rdma_netlink.h>
+#include <rdma/rdma_user_cm.h>
+#include <time.h>
+#include <net/if_arp.h>
+
+#include "list.h"
+#include "utils.h"
+#include "json_writer.h"
+
+#define pr_err(args...) fprintf(stderr, ##args)
+#define pr_out(args...) fprintf(stdout, ##args)
+
+#define RDMA_BITMAP_ENUM(name, bit_no) RDMA_BITMAP_##name = BIT(bit_no),
+#define RDMA_BITMAP_NAMES(name, bit_no) [bit_no] = #name,
+
+#define MAX_NUMBER_OF_FILTERS 64
+struct filters {
+ const char *name;
+ uint8_t is_number:1;
+ uint8_t is_doit:1;
+};
+
+struct filter_entry {
+ struct list_head list;
+ char *key;
+ char *value;
+ /*
+ * This field menas that we can try to issue .doit calback
+ * on value above. This value can be converted to integer
+ * with simple atoi(). Otherwise "is_doit" will be false.
+ */
+ uint8_t is_doit:1;
+};
+
+struct dev_map {
+ struct list_head list;
+ char *dev_name;
+ uint32_t num_ports;
+ uint32_t idx;
+};
+
+struct rd {
+ int argc;
+ char **argv;
+ char *filename;
+ bool show_details;
+ bool show_driver_details;
+ struct list_head dev_map_list;
+ uint32_t dev_idx;
+ uint32_t port_idx;
+ struct mnl_socket *nl;
+ struct nlmsghdr *nlh;
+ char *buff;
+ json_writer_t *jw;
+ bool json_output;
+ bool pretty_output;
+ bool suppress_errors;
+ struct list_head filter_list;
+ char *link_name;
+ char *link_type;
+};
+
+struct rd_cmd {
+ const char *cmd;
+ int (*func)(struct rd *rd);
+};
+
+/*
+ * Parser interface
+ */
+bool rd_no_arg(struct rd *rd);
+void rd_arg_inc(struct rd *rd);
+
+char *rd_argv(struct rd *rd);
+
+/*
+ * Commands interface
+ */
+int cmd_dev(struct rd *rd);
+int cmd_link(struct rd *rd);
+int cmd_res(struct rd *rd);
+int cmd_sys(struct rd *rd);
+int cmd_stat(struct rd *rd);
+int rd_exec_cmd(struct rd *rd, const struct rd_cmd *c, const char *str);
+int rd_exec_dev(struct rd *rd, int (*cb)(struct rd *rd));
+int rd_exec_require_dev(struct rd *rd, int (*cb)(struct rd *rd));
+int rd_exec_link(struct rd *rd, int (*cb)(struct rd *rd), bool strict_port);
+void rd_free(struct rd *rd);
+int rd_set_arg_to_devname(struct rd *rd);
+int rd_argc(struct rd *rd);
+
+int strcmpx(const char *str1, const char *str2);
+
+/*
+ * Device manipulation
+ */
+struct dev_map *dev_map_lookup(struct rd *rd, bool allow_port_index);
+
+/*
+ * Filter manipulation
+ */
+bool rd_doit_index(struct rd *rd, uint32_t *idx);
+int rd_build_filter(struct rd *rd, const struct filters valid_filters[]);
+bool rd_is_filtered_attr(struct rd *rd, const char *key, uint32_t val,
+ struct nlattr *attr);
+bool rd_is_string_filtered_attr(struct rd *rd, const char *key, const char *val,
+ struct nlattr *attr);
+/*
+ * Netlink
+ */
+int rd_send_msg(struct rd *rd);
+int rd_recv_msg(struct rd *rd, mnl_cb_t callback, void *data, uint32_t seq);
+int rd_sendrecv_msg(struct rd *rd, unsigned int seq);
+void rd_prepare_msg(struct rd *rd, uint32_t cmd, uint32_t *seq, uint16_t flags);
+int rd_dev_init_cb(const struct nlmsghdr *nlh, void *data);
+int rd_attr_cb(const struct nlattr *attr, void *data);
+int rd_attr_check(const struct nlattr *attr, int *typep);
+
+/*
+ * Print helpers
+ */
+void print_driver_table(struct rd *rd, struct nlattr *tb);
+void newline(struct rd *rd);
+void newline_indent(struct rd *rd);
+void print_on_off(struct rd *rd, const char *key_str, bool on);
+#define MAX_LINE_LENGTH 80
+
+#endif /* _RDMA_TOOL_H_ */
diff --git a/rdma/res-cmid.c b/rdma/res-cmid.c
new file mode 100644
index 0000000..0b83008
--- /dev/null
+++ b/rdma/res-cmid.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * res-cmid.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+
+#include "res.h"
+#include <inttypes.h>
+
+static void print_qp_type(struct rd *rd, uint32_t val)
+{
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "qp-type", qp_types_to_str(val));
+ else
+ pr_out("qp-type %s ", qp_types_to_str(val));
+}
+
+static const char *cm_id_state_to_str(uint8_t idx)
+{
+ static const char *const cm_id_states_str[] = {
+ "IDLE", "ADDR_QUERY", "ADDR_RESOLVED",
+ "ROUTE_QUERY", "ROUTE_RESOLVED", "CONNECT",
+ "DISCONNECT", "ADDR_BOUND", "LISTEN",
+ "DEVICE_REMOVAL", "DESTROYING"
+ };
+
+ if (idx < ARRAY_SIZE(cm_id_states_str))
+ return cm_id_states_str[idx];
+ return "UNKNOWN";
+}
+
+static const char *cm_id_ps_to_str(uint32_t ps)
+{
+ switch (ps) {
+ case RDMA_PS_IPOIB:
+ return "IPoIB";
+ case RDMA_PS_IB:
+ return "IPoIB";
+ case RDMA_PS_TCP:
+ return "TCP";
+ case RDMA_PS_UDP:
+ return "UDP";
+ default:
+ return "---";
+ }
+}
+
+static void print_cm_id_state(struct rd *rd, uint8_t state)
+{
+ if (rd->json_output) {
+ jsonw_string_field(rd->jw, "state", cm_id_state_to_str(state));
+ return;
+ }
+ pr_out("state %s ", cm_id_state_to_str(state));
+}
+
+static void print_ps(struct rd *rd, uint32_t ps)
+{
+ if (rd->json_output) {
+ jsonw_string_field(rd->jw, "ps", cm_id_ps_to_str(ps));
+ return;
+ }
+ pr_out("ps %s ", cm_id_ps_to_str(ps));
+}
+
+static void print_ipaddr(struct rd *rd, const char *key, char *addrstr,
+ uint16_t port)
+{
+ if (rd->json_output) {
+ int name_size = INET6_ADDRSTRLEN + strlen(":65535");
+ char json_name[name_size];
+
+ snprintf(json_name, name_size, "%s:%u", addrstr, port);
+ jsonw_string_field(rd->jw, key, json_name);
+ return;
+ }
+ pr_out("%s %s:%u ", key, addrstr, port);
+}
+
+static int ss_ntop(struct nlattr *nla_line, char *addr_str, uint16_t *port)
+{
+ struct __kernel_sockaddr_storage *addr;
+
+ addr = (struct __kernel_sockaddr_storage *)mnl_attr_get_payload(
+ nla_line);
+ switch (addr->ss_family) {
+ case AF_INET: {
+ struct sockaddr_in *sin = (struct sockaddr_in *)addr;
+
+ if (!inet_ntop(AF_INET, (const void *)&sin->sin_addr, addr_str,
+ INET6_ADDRSTRLEN))
+ return -EINVAL;
+ *port = ntohs(sin->sin_port);
+ break;
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
+
+ if (!inet_ntop(AF_INET6, (const void *)&sin6->sin6_addr,
+ addr_str, INET6_ADDRSTRLEN))
+ return -EINVAL;
+ *port = ntohs(sin6->sin6_port);
+ break;
+ }
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+static int res_cm_id_line(struct rd *rd, const char *name, int idx,
+ struct nlattr **nla_line)
+{
+ char src_addr_str[INET6_ADDRSTRLEN];
+ char dst_addr_str[INET6_ADDRSTRLEN];
+ uint16_t src_port, dst_port;
+ uint32_t port = 0, pid = 0;
+ uint8_t type = 0, state;
+ uint32_t lqpn = 0, ps;
+ uint32_t cm_idn = 0;
+ char *comm = NULL;
+
+ if (!nla_line[RDMA_NLDEV_ATTR_RES_STATE] ||
+ !nla_line[RDMA_NLDEV_ATTR_RES_PS] ||
+ (!nla_line[RDMA_NLDEV_ATTR_RES_PID] &&
+ !nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME])) {
+ return MNL_CB_ERROR;
+ }
+
+ if (nla_line[RDMA_NLDEV_ATTR_PORT_INDEX])
+ port = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_PORT_INDEX]);
+
+ if (port && port != rd->port_idx)
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_LQPN])
+ lqpn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_LQPN]);
+
+ if (rd_is_filtered_attr(rd, "lqpn", lqpn,
+ nla_line[RDMA_NLDEV_ATTR_RES_LQPN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_TYPE])
+ type = mnl_attr_get_u8(nla_line[RDMA_NLDEV_ATTR_RES_TYPE]);
+ if (rd_is_string_filtered_attr(rd, "qp-type", qp_types_to_str(type),
+ nla_line[RDMA_NLDEV_ATTR_RES_TYPE]))
+ goto out;
+
+ ps = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_PS]);
+ if (rd_is_string_filtered_attr(rd, "ps", cm_id_ps_to_str(ps),
+ nla_line[RDMA_NLDEV_ATTR_RES_PS]))
+ goto out;
+
+ state = mnl_attr_get_u8(nla_line[RDMA_NLDEV_ATTR_RES_STATE]);
+ if (rd_is_string_filtered_attr(rd, "state", cm_id_state_to_str(state),
+ nla_line[RDMA_NLDEV_ATTR_RES_STATE]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_SRC_ADDR])
+ if (ss_ntop(nla_line[RDMA_NLDEV_ATTR_RES_SRC_ADDR],
+ src_addr_str, &src_port))
+ goto out;
+ if (rd_is_string_filtered_attr(rd, "src-addr", src_addr_str,
+ nla_line[RDMA_NLDEV_ATTR_RES_SRC_ADDR]))
+ goto out;
+ if (rd_is_filtered_attr(rd, "src-port", src_port,
+ nla_line[RDMA_NLDEV_ATTR_RES_SRC_ADDR]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_DST_ADDR])
+ if (ss_ntop(nla_line[RDMA_NLDEV_ATTR_RES_DST_ADDR],
+ dst_addr_str, &dst_port))
+ goto out;
+ if (rd_is_string_filtered_attr(rd, "dst-addr", dst_addr_str,
+ nla_line[RDMA_NLDEV_ATTR_RES_DST_ADDR]))
+ goto out;
+ if (rd_is_filtered_attr(rd, "dst-port", dst_port,
+ nla_line[RDMA_NLDEV_ATTR_RES_DST_ADDR]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PID]) {
+ pid = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ comm = get_task_name(pid);
+ }
+
+ if (rd_is_filtered_attr(rd, "pid", pid,
+ nla_line[RDMA_NLDEV_ATTR_RES_PID]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_CM_IDN])
+ cm_idn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_CM_IDN]);
+ if (rd_is_filtered_attr(rd, "cm-idn", cm_idn,
+ nla_line[RDMA_NLDEV_ATTR_RES_CM_IDN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME]) {
+ /* discard const from mnl_attr_get_str */
+ comm = (char *)mnl_attr_get_str(
+ nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME]);
+ }
+
+ if (rd->json_output)
+ jsonw_start_array(rd->jw);
+
+ print_link(rd, idx, name, port, nla_line);
+ res_print_uint(rd, "cm-idn", cm_idn,
+ nla_line[RDMA_NLDEV_ATTR_RES_CM_IDN]);
+ res_print_uint(rd, "lqpn", lqpn, nla_line[RDMA_NLDEV_ATTR_RES_LQPN]);
+ if (nla_line[RDMA_NLDEV_ATTR_RES_TYPE])
+ print_qp_type(rd, type);
+ print_cm_id_state(rd, state);
+ print_ps(rd, ps);
+ res_print_uint(rd, "pid", pid, nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ print_comm(rd, comm, nla_line);
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_SRC_ADDR])
+ print_ipaddr(rd, "src-addr", src_addr_str, src_port);
+ if (nla_line[RDMA_NLDEV_ATTR_RES_DST_ADDR])
+ print_ipaddr(rd, "dst-addr", dst_addr_str, dst_port);
+
+ print_driver_table(rd, nla_line[RDMA_NLDEV_ATTR_DRIVER]);
+ newline(rd);
+
+out: if (nla_line[RDMA_NLDEV_ATTR_RES_PID])
+ free(comm);
+ return MNL_CB_OK;
+}
+
+int res_cm_id_idx_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct rd *rd = data;
+ const char *name;
+ int idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+
+ return res_cm_id_line(rd, name, idx, tb);
+}
+
+int res_cm_id_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct nlattr *nla_table, *nla_entry;
+ struct rd *rd = data;
+ int ret = MNL_CB_OK;
+ const char *name;
+ int idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
+ !tb[RDMA_NLDEV_ATTR_RES_CM_ID])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ nla_table = tb[RDMA_NLDEV_ATTR_RES_CM_ID];
+
+ mnl_attr_for_each_nested(nla_entry, nla_table) {
+ struct nlattr *nla_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ ret = mnl_attr_parse_nested(nla_entry, rd_attr_cb, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+
+ ret = res_cm_id_line(rd, name, idx, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+ }
+ return ret;
+}
diff --git a/rdma/res-cq.c b/rdma/res-cq.c
new file mode 100644
index 0000000..d2591fb
--- /dev/null
+++ b/rdma/res-cq.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * res-cq.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+
+#include "res.h"
+#include <inttypes.h>
+
+static const char *poll_ctx_to_str(uint8_t idx)
+{
+ static const char * const cm_id_states_str[] = {
+ "DIRECT", "SOFTIRQ", "WORKQUEUE", "UNBOUND_WORKQUEUE"};
+
+ if (idx < ARRAY_SIZE(cm_id_states_str))
+ return cm_id_states_str[idx];
+ return "UNKNOWN";
+}
+
+static void print_poll_ctx(struct rd *rd, uint8_t poll_ctx, struct nlattr *attr)
+{
+ if (!attr)
+ return;
+
+ if (rd->json_output) {
+ jsonw_string_field(rd->jw, "poll-ctx",
+ poll_ctx_to_str(poll_ctx));
+ return;
+ }
+ pr_out("poll-ctx %s ", poll_ctx_to_str(poll_ctx));
+}
+
+static void print_cq_dim_setting(struct rd *rd, struct nlattr *attr)
+{
+ uint8_t dim_setting;
+
+ if (!attr)
+ return;
+
+ dim_setting = mnl_attr_get_u8(attr);
+ if (dim_setting > 1)
+ return;
+
+ print_on_off(rd, "adaptive-moderation", dim_setting);
+}
+
+static int res_cq_line(struct rd *rd, const char *name, int idx,
+ struct nlattr **nla_line)
+{
+ char *comm = NULL;
+ uint32_t pid = 0;
+ uint8_t poll_ctx = 0;
+ uint32_t ctxn = 0;
+ uint32_t cqn = 0;
+ uint64_t users;
+ uint32_t cqe;
+
+ if (!nla_line[RDMA_NLDEV_ATTR_RES_CQE] ||
+ !nla_line[RDMA_NLDEV_ATTR_RES_USECNT] ||
+ (!nla_line[RDMA_NLDEV_ATTR_RES_PID] &&
+ !nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME])) {
+ return MNL_CB_ERROR;
+ }
+
+ cqe = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_CQE]);
+
+ users = mnl_attr_get_u64(nla_line[RDMA_NLDEV_ATTR_RES_USECNT]);
+ if (rd_is_filtered_attr(rd, "users", users,
+ nla_line[RDMA_NLDEV_ATTR_RES_USECNT]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_POLL_CTX])
+ poll_ctx =
+ mnl_attr_get_u8(nla_line[RDMA_NLDEV_ATTR_RES_POLL_CTX]);
+ if (rd_is_string_filtered_attr(rd, "poll-ctx",
+ poll_ctx_to_str(poll_ctx),
+ nla_line[RDMA_NLDEV_ATTR_RES_POLL_CTX]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PID]) {
+ pid = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ comm = get_task_name(pid);
+ }
+
+ if (rd_is_filtered_attr(rd, "pid", pid,
+ nla_line[RDMA_NLDEV_ATTR_RES_PID]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_CQN])
+ cqn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_CQN]);
+ if (rd_is_filtered_attr(rd, "cqn", cqn,
+ nla_line[RDMA_NLDEV_ATTR_RES_CQN]))
+ goto out;
+ if (nla_line[RDMA_NLDEV_ATTR_RES_CTXN])
+ ctxn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_CTXN]);
+ if (rd_is_filtered_attr(rd, "ctxn", ctxn,
+ nla_line[RDMA_NLDEV_ATTR_RES_CTXN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME])
+ /* discard const from mnl_attr_get_str */
+ comm = (char *)mnl_attr_get_str(
+ nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME]);
+
+ if (rd->json_output)
+ jsonw_start_array(rd->jw);
+
+ print_dev(rd, idx, name);
+ res_print_uint(rd, "cqn", cqn, nla_line[RDMA_NLDEV_ATTR_RES_CQN]);
+ res_print_uint(rd, "cqe", cqe, nla_line[RDMA_NLDEV_ATTR_RES_CQE]);
+ res_print_uint(rd, "users", users,
+ nla_line[RDMA_NLDEV_ATTR_RES_USECNT]);
+ print_poll_ctx(rd, poll_ctx, nla_line[RDMA_NLDEV_ATTR_RES_POLL_CTX]);
+ print_cq_dim_setting(rd, nla_line[RDMA_NLDEV_ATTR_DEV_DIM]);
+ res_print_uint(rd, "ctxn", ctxn, nla_line[RDMA_NLDEV_ATTR_RES_CTXN]);
+ res_print_uint(rd, "pid", pid, nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ print_comm(rd, comm, nla_line);
+
+ print_driver_table(rd, nla_line[RDMA_NLDEV_ATTR_DRIVER]);
+ newline(rd);
+
+out: if (nla_line[RDMA_NLDEV_ATTR_RES_PID])
+ free(comm);
+ return MNL_CB_OK;
+}
+
+int res_cq_idx_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct rd *rd = data;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+
+ return res_cq_line(rd, name, idx, tb);
+}
+
+int res_cq_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct nlattr *nla_table, *nla_entry;
+ struct rd *rd = data;
+ int ret = MNL_CB_OK;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
+ !tb[RDMA_NLDEV_ATTR_RES_CQ])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ nla_table = tb[RDMA_NLDEV_ATTR_RES_CQ];
+
+ mnl_attr_for_each_nested(nla_entry, nla_table) {
+ struct nlattr *nla_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ ret = mnl_attr_parse_nested(nla_entry, rd_attr_cb, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+
+ ret = res_cq_line(rd, name, idx, nla_line);
+
+ if (ret != MNL_CB_OK)
+ break;
+ }
+ return ret;
+}
diff --git a/rdma/res-mr.c b/rdma/res-mr.c
new file mode 100644
index 0000000..f4a24dc
--- /dev/null
+++ b/rdma/res-mr.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * res-mr.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+
+#include "res.h"
+#include <inttypes.h>
+
+static int res_mr_line(struct rd *rd, const char *name, int idx,
+ struct nlattr **nla_line)
+{
+ uint32_t rkey = 0, lkey = 0;
+ uint64_t iova = 0, mrlen;
+ char *comm = NULL;
+ uint32_t pdn = 0;
+ uint32_t mrn = 0;
+ uint32_t pid = 0;
+
+ if (!nla_line[RDMA_NLDEV_ATTR_RES_MRLEN] ||
+ (!nla_line[RDMA_NLDEV_ATTR_RES_PID] &&
+ !nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME])) {
+ return MNL_CB_ERROR;
+ }
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_RKEY])
+ rkey = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_RKEY]);
+ if (nla_line[RDMA_NLDEV_ATTR_RES_LKEY])
+ lkey = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_LKEY]);
+ if (nla_line[RDMA_NLDEV_ATTR_RES_IOVA])
+ iova = mnl_attr_get_u64(nla_line[RDMA_NLDEV_ATTR_RES_IOVA]);
+
+ mrlen = mnl_attr_get_u64(nla_line[RDMA_NLDEV_ATTR_RES_MRLEN]);
+ if (rd_is_filtered_attr(rd, "mrlen", mrlen,
+ nla_line[RDMA_NLDEV_ATTR_RES_MRLEN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PID]) {
+ pid = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ comm = get_task_name(pid);
+ }
+
+ if (rd_is_filtered_attr(rd, "pid", pid,
+ nla_line[RDMA_NLDEV_ATTR_RES_PID]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_MRN])
+ mrn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_MRN]);
+ if (rd_is_filtered_attr(rd, "mrn", mrn,
+ nla_line[RDMA_NLDEV_ATTR_RES_MRN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PDN])
+ pdn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_PDN]);
+ if (rd_is_filtered_attr(rd, "pdn", pdn,
+ nla_line[RDMA_NLDEV_ATTR_RES_PDN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME])
+ /* discard const from mnl_attr_get_str */
+ comm = (char *)mnl_attr_get_str(
+ nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME]);
+
+ if (rd->json_output)
+ jsonw_start_array(rd->jw);
+
+ print_dev(rd, idx, name);
+ res_print_uint(rd, "mrn", mrn, nla_line[RDMA_NLDEV_ATTR_RES_MRN]);
+ print_key(rd, "rkey", rkey, nla_line[RDMA_NLDEV_ATTR_RES_RKEY]);
+ print_key(rd, "lkey", lkey, nla_line[RDMA_NLDEV_ATTR_RES_LKEY]);
+ print_key(rd, "iova", iova, nla_line[RDMA_NLDEV_ATTR_RES_IOVA]);
+ res_print_uint(rd, "mrlen", mrlen, nla_line[RDMA_NLDEV_ATTR_RES_MRLEN]);
+ res_print_uint(rd, "pdn", pdn, nla_line[RDMA_NLDEV_ATTR_RES_PDN]);
+ res_print_uint(rd, "pid", pid, nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ print_comm(rd, comm, nla_line);
+
+ print_driver_table(rd, nla_line[RDMA_NLDEV_ATTR_DRIVER]);
+ newline(rd);
+
+out:
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PID])
+ free(comm);
+ return MNL_CB_OK;
+}
+
+int res_mr_idx_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct rd *rd = data;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+
+ return res_mr_line(rd, name, idx, tb);
+}
+
+int res_mr_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct nlattr *nla_table, *nla_entry;
+ struct rd *rd = data;
+ int ret = MNL_CB_OK;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
+ !tb[RDMA_NLDEV_ATTR_RES_MR])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ nla_table = tb[RDMA_NLDEV_ATTR_RES_MR];
+
+ mnl_attr_for_each_nested(nla_entry, nla_table) {
+ struct nlattr *nla_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ ret = mnl_attr_parse_nested(nla_entry, rd_attr_cb, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+
+ ret = res_mr_line(rd, name, idx, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+ }
+ return ret;
+}
diff --git a/rdma/res-pd.c b/rdma/res-pd.c
new file mode 100644
index 0000000..07c836e
--- /dev/null
+++ b/rdma/res-pd.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * res-pd.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+
+#include "res.h"
+#include <inttypes.h>
+
+static int res_pd_line(struct rd *rd, const char *name, int idx,
+ struct nlattr **nla_line)
+{
+ uint32_t local_dma_lkey = 0, unsafe_global_rkey = 0;
+ char *comm = NULL;
+ uint32_t ctxn = 0;
+ uint32_t pid = 0;
+ uint32_t pdn = 0;
+ uint64_t users;
+
+ if (!nla_line[RDMA_NLDEV_ATTR_RES_USECNT] ||
+ (!nla_line[RDMA_NLDEV_ATTR_RES_PID] &&
+ !nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME])) {
+ return MNL_CB_ERROR;
+ }
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_LOCAL_DMA_LKEY])
+ local_dma_lkey = mnl_attr_get_u32(
+ nla_line[RDMA_NLDEV_ATTR_RES_LOCAL_DMA_LKEY]);
+
+ users = mnl_attr_get_u64(nla_line[RDMA_NLDEV_ATTR_RES_USECNT]);
+ if (rd_is_filtered_attr(rd, "users", users,
+ nla_line[RDMA_NLDEV_ATTR_RES_USECNT]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_UNSAFE_GLOBAL_RKEY])
+ unsafe_global_rkey = mnl_attr_get_u32(
+ nla_line[RDMA_NLDEV_ATTR_RES_UNSAFE_GLOBAL_RKEY]);
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PID]) {
+ pid = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ comm = get_task_name(pid);
+ }
+
+ if (rd_is_filtered_attr(rd, "pid", pid,
+ nla_line[RDMA_NLDEV_ATTR_RES_PID]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_CTXN])
+ ctxn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_CTXN]);
+
+ if (rd_is_filtered_attr(rd, "ctxn", ctxn,
+ nla_line[RDMA_NLDEV_ATTR_RES_CTXN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PDN])
+ pdn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_PDN]);
+ if (rd_is_filtered_attr(rd, "pdn", pdn,
+ nla_line[RDMA_NLDEV_ATTR_RES_PDN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME])
+ /* discard const from mnl_attr_get_str */
+ comm = (char *)mnl_attr_get_str(
+ nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME]);
+
+ if (rd->json_output)
+ jsonw_start_array(rd->jw);
+
+ print_dev(rd, idx, name);
+ res_print_uint(rd, "pdn", pdn, nla_line[RDMA_NLDEV_ATTR_RES_PDN]);
+ print_key(rd, "local_dma_lkey", local_dma_lkey,
+ nla_line[RDMA_NLDEV_ATTR_RES_LOCAL_DMA_LKEY]);
+ res_print_uint(rd, "users", users,
+ nla_line[RDMA_NLDEV_ATTR_RES_USECNT]);
+ print_key(rd, "unsafe_global_rkey", unsafe_global_rkey,
+ nla_line[RDMA_NLDEV_ATTR_RES_UNSAFE_GLOBAL_RKEY]);
+ res_print_uint(rd, "ctxn", ctxn, nla_line[RDMA_NLDEV_ATTR_RES_CTXN]);
+ res_print_uint(rd, "pid", pid, nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ print_comm(rd, comm, nla_line);
+
+ print_driver_table(rd, nla_line[RDMA_NLDEV_ATTR_DRIVER]);
+ newline(rd);
+
+out: if (nla_line[RDMA_NLDEV_ATTR_RES_PID])
+ free(comm);
+ return MNL_CB_OK;
+}
+
+int res_pd_idx_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct rd *rd = data;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+
+ return res_pd_line(rd, name, idx, tb);
+}
+
+int res_pd_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct nlattr *nla_table, *nla_entry;
+ struct rd *rd = data;
+ int ret = MNL_CB_OK;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
+ !tb[RDMA_NLDEV_ATTR_RES_PD])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ nla_table = tb[RDMA_NLDEV_ATTR_RES_PD];
+
+ mnl_attr_for_each_nested(nla_entry, nla_table) {
+ struct nlattr *nla_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ ret = mnl_attr_parse_nested(nla_entry, rd_attr_cb, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+
+ ret = res_pd_line(rd, name, idx, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+ }
+ return ret;
+}
diff --git a/rdma/res-qp.c b/rdma/res-qp.c
new file mode 100644
index 0000000..954e465
--- /dev/null
+++ b/rdma/res-qp.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * res-qp.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+
+#include "res.h"
+#include <inttypes.h>
+
+static const char *path_mig_to_str(uint8_t idx)
+{
+ static const char *const path_mig_str[] = { "MIGRATED", "REARM",
+ "ARMED" };
+
+ if (idx < ARRAY_SIZE(path_mig_str))
+ return path_mig_str[idx];
+ return "UNKNOWN";
+}
+
+static const char *qp_states_to_str(uint8_t idx)
+{
+ static const char *const qp_states_str[] = { "RESET", "INIT", "RTR",
+ "RTS", "SQD", "SQE",
+ "ERR" };
+
+ if (idx < ARRAY_SIZE(qp_states_str))
+ return qp_states_str[idx];
+ return "UNKNOWN";
+}
+
+static void print_rqpn(struct rd *rd, uint32_t val, struct nlattr **nla_line)
+{
+ if (!nla_line[RDMA_NLDEV_ATTR_RES_RQPN])
+ return;
+
+ if (rd->json_output)
+ jsonw_uint_field(rd->jw, "rqpn", val);
+ else
+ pr_out("rqpn %u ", val);
+}
+
+static void print_type(struct rd *rd, uint32_t val)
+{
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "type", qp_types_to_str(val));
+ else
+ pr_out("type %s ", qp_types_to_str(val));
+}
+
+static void print_state(struct rd *rd, uint32_t val)
+{
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "state", qp_states_to_str(val));
+ else
+ pr_out("state %s ", qp_states_to_str(val));
+}
+
+static void print_rqpsn(struct rd *rd, uint32_t val, struct nlattr **nla_line)
+{
+ if (!nla_line[RDMA_NLDEV_ATTR_RES_RQ_PSN])
+ return;
+
+ if (rd->json_output)
+ jsonw_uint_field(rd->jw, "rq-psn", val);
+ else
+ pr_out("rq-psn %u ", val);
+}
+
+static void print_pathmig(struct rd *rd, uint32_t val, struct nlattr **nla_line)
+{
+ if (!nla_line[RDMA_NLDEV_ATTR_RES_PATH_MIG_STATE])
+ return;
+
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "path-mig-state",
+ path_mig_to_str(val));
+ else
+ pr_out("path-mig-state %s ", path_mig_to_str(val));
+}
+
+static int res_qp_line(struct rd *rd, const char *name, int idx,
+ struct nlattr **nla_line)
+{
+ uint32_t lqpn, rqpn = 0, rq_psn = 0, sq_psn;
+ uint8_t type, state, path_mig_state = 0;
+ uint32_t port = 0, pid = 0;
+ uint32_t pdn = 0;
+ char *comm = NULL;
+
+ if (!nla_line[RDMA_NLDEV_ATTR_RES_LQPN] ||
+ !nla_line[RDMA_NLDEV_ATTR_RES_SQ_PSN] ||
+ !nla_line[RDMA_NLDEV_ATTR_RES_TYPE] ||
+ !nla_line[RDMA_NLDEV_ATTR_RES_STATE] ||
+ (!nla_line[RDMA_NLDEV_ATTR_RES_PID] &&
+ !nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME])) {
+ return MNL_CB_ERROR;
+ }
+
+ if (nla_line[RDMA_NLDEV_ATTR_PORT_INDEX])
+ port = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_PORT_INDEX]);
+
+ if (port != rd->port_idx)
+ goto out;
+
+ lqpn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_LQPN]);
+ if (rd_is_filtered_attr(rd, "lqpn", lqpn,
+ nla_line[RDMA_NLDEV_ATTR_RES_LQPN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PDN])
+ pdn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_PDN]);
+ if (rd_is_filtered_attr(rd, "pdn", pdn,
+ nla_line[RDMA_NLDEV_ATTR_RES_PDN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_RQPN])
+ rqpn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_RQPN]);
+ if (rd_is_filtered_attr(rd, "rqpn", rqpn,
+ nla_line[RDMA_NLDEV_ATTR_RES_RQPN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_RQ_PSN])
+ rq_psn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_RQ_PSN]);
+ if (rd_is_filtered_attr(rd, "rq-psn", rq_psn,
+ nla_line[RDMA_NLDEV_ATTR_RES_RQ_PSN]))
+ goto out;
+
+ sq_psn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_SQ_PSN]);
+ if (rd_is_filtered_attr(rd, "sq-psn", sq_psn,
+ nla_line[RDMA_NLDEV_ATTR_RES_SQ_PSN]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PATH_MIG_STATE])
+ path_mig_state = mnl_attr_get_u8(
+ nla_line[RDMA_NLDEV_ATTR_RES_PATH_MIG_STATE]);
+ if (rd_is_string_filtered_attr(
+ rd, "path-mig-state", path_mig_to_str(path_mig_state),
+ nla_line[RDMA_NLDEV_ATTR_RES_PATH_MIG_STATE]))
+ goto out;
+
+ type = mnl_attr_get_u8(nla_line[RDMA_NLDEV_ATTR_RES_TYPE]);
+ if (rd_is_string_filtered_attr(rd, "type", qp_types_to_str(type),
+ nla_line[RDMA_NLDEV_ATTR_RES_TYPE]))
+ goto out;
+
+ state = mnl_attr_get_u8(nla_line[RDMA_NLDEV_ATTR_RES_STATE]);
+ if (rd_is_string_filtered_attr(rd, "state", qp_states_to_str(state),
+ nla_line[RDMA_NLDEV_ATTR_RES_STATE]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PID]) {
+ pid = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ comm = get_task_name(pid);
+ }
+
+ if (rd_is_filtered_attr(rd, "pid", pid,
+ nla_line[RDMA_NLDEV_ATTR_RES_PID]))
+ goto out;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME])
+ /* discard const from mnl_attr_get_str */
+ comm = (char *)mnl_attr_get_str(
+ nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME]);
+
+ if (rd->json_output)
+ jsonw_start_array(rd->jw);
+
+ print_link(rd, idx, name, port, nla_line);
+
+ res_print_uint(rd, "lqpn", lqpn, nla_line[RDMA_NLDEV_ATTR_RES_LQPN]);
+ print_rqpn(rd, rqpn, nla_line);
+
+ print_type(rd, type);
+ print_state(rd, state);
+
+ print_rqpsn(rd, rq_psn, nla_line);
+ res_print_uint(rd, "sq-psn", sq_psn,
+ nla_line[RDMA_NLDEV_ATTR_RES_SQ_PSN]);
+
+ print_pathmig(rd, path_mig_state, nla_line);
+ res_print_uint(rd, "pdn", pdn, nla_line[RDMA_NLDEV_ATTR_RES_PDN]);
+ res_print_uint(rd, "pid", pid, nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ print_comm(rd, comm, nla_line);
+
+ print_driver_table(rd, nla_line[RDMA_NLDEV_ATTR_DRIVER]);
+ newline(rd);
+out:
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PID])
+ free(comm);
+ return MNL_CB_OK;
+}
+
+int res_qp_idx_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct rd *rd = data;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+
+ return res_qp_line(rd, name, idx, tb);
+}
+
+int res_qp_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct nlattr *nla_table, *nla_entry;
+ struct rd *rd = data;
+ int ret = MNL_CB_OK;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
+ !tb[RDMA_NLDEV_ATTR_RES_QP])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ nla_table = tb[RDMA_NLDEV_ATTR_RES_QP];
+
+ mnl_attr_for_each_nested(nla_entry, nla_table) {
+ struct nlattr *nla_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ ret = mnl_attr_parse_nested(nla_entry, rd_attr_cb, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+
+ ret = res_qp_line(rd, name, idx, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+ }
+ return ret;
+}
diff --git a/rdma/res.c b/rdma/res.c
new file mode 100644
index 0000000..6003006
--- /dev/null
+++ b/rdma/res.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * res.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+
+#include "res.h"
+#include <inttypes.h>
+
+static int res_help(struct rd *rd)
+{
+ pr_out("Usage: %s resource\n", rd->filename);
+ pr_out(" resource show [DEV]\n");
+ pr_out(" resource show [qp|cm_id|pd|mr|cq]\n");
+ pr_out(" resource show qp link [DEV/PORT]\n");
+ pr_out(" resource show qp link [DEV/PORT] [FILTER-NAME FILTER-VALUE]\n");
+ pr_out(" resource show cm_id link [DEV/PORT]\n");
+ pr_out(" resource show cm_id link [DEV/PORT] [FILTER-NAME FILTER-VALUE]\n");
+ pr_out(" resource show cq link [DEV/PORT]\n");
+ pr_out(" resource show cq link [DEV/PORT] [FILTER-NAME FILTER-VALUE]\n");
+ pr_out(" resource show pd dev [DEV]\n");
+ pr_out(" resource show pd dev [DEV] [FILTER-NAME FILTER-VALUE]\n");
+ pr_out(" resource show mr dev [DEV]\n");
+ pr_out(" resource show mr dev [DEV] [FILTER-NAME FILTER-VALUE]\n");
+ return 0;
+}
+
+static int res_print_summary(struct rd *rd, struct nlattr **tb)
+{
+ struct nlattr *nla_table = tb[RDMA_NLDEV_ATTR_RES_SUMMARY];
+ struct nlattr *nla_entry;
+ const char *name;
+ uint64_t curr;
+ int err;
+
+ mnl_attr_for_each_nested(nla_entry, nla_table) {
+ struct nlattr *nla_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ err = mnl_attr_parse_nested(nla_entry, rd_attr_cb, nla_line);
+ if (err != MNL_CB_OK)
+ return -EINVAL;
+
+ if (!nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_NAME] ||
+ !nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR]) {
+ return -EINVAL;
+ }
+
+ name = mnl_attr_get_str(nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_NAME]);
+ curr = mnl_attr_get_u64(nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR]);
+ res_print_uint(
+ rd, name, curr,
+ nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR]);
+ }
+ return 0;
+}
+
+static int res_no_args_idx_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ return MNL_CB_OK;
+}
+
+static int res_no_args_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct rd *rd = data;
+ const char *name;
+ uint32_t idx;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] ||
+ !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
+ !tb[RDMA_NLDEV_ATTR_RES_SUMMARY])
+ return MNL_CB_ERROR;
+
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ if (rd->json_output) {
+ jsonw_uint_field(rd->jw, "ifindex", idx);
+ jsonw_string_field(rd->jw, "ifname", name);
+ } else {
+ pr_out("%u: %s: ", idx, name);
+ }
+
+ res_print_summary(rd, tb);
+
+ if (!rd->json_output)
+ pr_out("\n");
+ return MNL_CB_OK;
+}
+
+int _res_send_idx_msg(struct rd *rd, uint32_t command, mnl_cb_t callback,
+ uint32_t idx, uint32_t id)
+{
+ uint32_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ uint32_t seq;
+ int ret;
+
+ rd_prepare_msg(rd, command, &seq, flags);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ if (rd->port_idx)
+ mnl_attr_put_u32(rd->nlh,
+ RDMA_NLDEV_ATTR_PORT_INDEX, rd->port_idx);
+
+ mnl_attr_put_u32(rd->nlh, id, idx);
+
+ ret = rd_send_msg(rd);
+ if (ret)
+ return ret;
+
+ if (rd->json_output)
+ jsonw_start_object(rd->jw);
+ ret = rd_recv_msg(rd, callback, rd, seq);
+ if (rd->json_output)
+ jsonw_end_object(rd->jw);
+ return ret;
+}
+
+int _res_send_msg(struct rd *rd, uint32_t command, mnl_cb_t callback)
+{
+ uint32_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ uint32_t seq;
+ int ret;
+
+ if (command != RDMA_NLDEV_CMD_RES_GET)
+ flags |= NLM_F_DUMP;
+
+ rd_prepare_msg(rd, command, &seq, flags);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ if (rd->port_idx)
+ mnl_attr_put_u32(rd->nlh,
+ RDMA_NLDEV_ATTR_PORT_INDEX, rd->port_idx);
+
+ ret = rd_send_msg(rd);
+ if (ret)
+ return ret;
+
+ if (rd->json_output)
+ jsonw_start_object(rd->jw);
+ ret = rd_recv_msg(rd, callback, rd, seq);
+ if (rd->json_output)
+ jsonw_end_object(rd->jw);
+ return ret;
+}
+
+const char *qp_types_to_str(uint8_t idx)
+{
+ static const char * const qp_types_str[] = { "SMI", "GSI", "RC",
+ "UC", "UD", "RAW_IPV6",
+ "RAW_ETHERTYPE",
+ "UNKNOWN", "RAW_PACKET",
+ "XRC_INI", "XRC_TGT",
+ [0xFF] = "DRIVER",
+ };
+
+ if (idx < ARRAY_SIZE(qp_types_str) && qp_types_str[idx])
+ return qp_types_str[idx];
+ return "UNKNOWN";
+}
+
+void print_comm(struct rd *rd, const char *str, struct nlattr **nla_line)
+{
+ char tmp[18];
+
+ if (!str)
+ return;
+
+ if (rd->json_output) {
+ /* Don't beatify output in JSON format */
+ jsonw_string_field(rd->jw, "comm", str);
+ return;
+ }
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PID])
+ snprintf(tmp, sizeof(tmp), "%s", str);
+ else
+ snprintf(tmp, sizeof(tmp), "[%s]", str);
+
+ pr_out("comm %s ", tmp);
+}
+
+void print_dev(struct rd *rd, uint32_t idx, const char *name)
+{
+ if (rd->json_output) {
+ jsonw_uint_field(rd->jw, "ifindex", idx);
+ jsonw_string_field(rd->jw, "ifname", name);
+ } else {
+ pr_out("dev %s ", name);
+ }
+}
+
+void print_link(struct rd *rd, uint32_t idx, const char *name, uint32_t port,
+ struct nlattr **nla_line)
+{
+ if (rd->json_output) {
+ jsonw_uint_field(rd->jw, "ifindex", idx);
+
+ if (nla_line[RDMA_NLDEV_ATTR_PORT_INDEX])
+ jsonw_uint_field(rd->jw, "port", port);
+
+ jsonw_string_field(rd->jw, "ifname", name);
+ } else {
+ if (nla_line[RDMA_NLDEV_ATTR_PORT_INDEX])
+ pr_out("link %s/%u ", name, port);
+ else
+ pr_out("link %s/- ", name);
+ }
+}
+
+char *get_task_name(uint32_t pid)
+{
+ char *comm;
+ FILE *f;
+
+ if (asprintf(&comm, "/proc/%d/comm", pid) < 0)
+ return NULL;
+
+ f = fopen(comm, "r");
+ free(comm);
+ if (!f)
+ return NULL;
+
+ if (fscanf(f, "%ms\n", &comm) != 1)
+ comm = NULL;
+
+ fclose(f);
+
+ return comm;
+}
+
+void print_key(struct rd *rd, const char *name, uint64_t val,
+ struct nlattr *nlattr)
+{
+ if (!nlattr)
+ return;
+
+ if (rd->json_output)
+ jsonw_xint_field(rd->jw, name, val);
+ else
+ pr_out("%s 0x%" PRIx64 " ", name, val);
+}
+
+void res_print_uint(struct rd *rd, const char *name, uint64_t val,
+ struct nlattr *nlattr)
+{
+ if (!nlattr)
+ return;
+
+ if (rd->json_output)
+ jsonw_u64_field(rd->jw, name, val);
+ else
+ pr_out("%s %" PRIu64 " ", name, val);
+}
+
+RES_FUNC(res_no_args, RDMA_NLDEV_CMD_RES_GET, NULL, true, 0);
+
+static int res_show(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, res_no_args },
+ { "qp", res_qp },
+ { "cm_id", res_cm_id },
+ { "cq", res_cq },
+ { "mr", res_mr },
+ { "pd", res_pd },
+ { 0 }
+ };
+
+ /*
+ * Special case to support "rdma res show DEV_NAME"
+ */
+ if (rd_argc(rd) == 1 && dev_map_lookup(rd, false))
+ return rd_exec_dev(rd, _res_no_args);
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+int cmd_res(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, res_show },
+ { "show", res_show },
+ { "list", res_show },
+ { "help", res_help },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "resource command");
+}
diff --git a/rdma/res.h b/rdma/res.h
new file mode 100644
index 0000000..525171f
--- /dev/null
+++ b/rdma/res.h
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB */
+/*
+ * res.h RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+#ifndef _RDMA_TOOL_RES_H_
+#define _RDMA_TOOL_RES_H_
+
+#include "rdma.h"
+
+int _res_send_msg(struct rd *rd, uint32_t command, mnl_cb_t callback);
+int _res_send_idx_msg(struct rd *rd, uint32_t command, mnl_cb_t callback,
+ uint32_t idx, uint32_t id);
+
+int res_pd_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_pd_idx_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_mr_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_mr_idx_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_cq_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_cq_idx_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_cm_id_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_cm_id_idx_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_qp_parse_cb(const struct nlmsghdr *nlh, void *data);
+int res_qp_idx_parse_cb(const struct nlmsghdr *nlh, void *data);
+
+#define RES_FUNC(name, command, valid_filters, strict_port, id) \
+ static inline int _##name(struct rd *rd) \
+ { \
+ uint32_t idx; \
+ int ret; \
+ if (id) { \
+ ret = rd_doit_index(rd, &idx); \
+ if (ret) { \
+ rd->suppress_errors = true; \
+ ret = _res_send_idx_msg(rd, command, \
+ name##_idx_parse_cb, \
+ idx, id); \
+ if (!ret) \
+ return ret; \
+ /* Fallback for old systems without .doit callbacks */ \
+ } \
+ } \
+ return _res_send_msg(rd, command, name##_parse_cb); \
+ } \
+ static inline int name(struct rd *rd) \
+ { \
+ int ret = rd_build_filter(rd, valid_filters); \
+ if (ret) \
+ return ret; \
+ if ((uintptr_t)valid_filters != (uintptr_t)NULL) { \
+ ret = rd_set_arg_to_devname(rd); \
+ if (ret) \
+ return ret; \
+ } \
+ if (strict_port) \
+ return rd_exec_dev(rd, _##name); \
+ else \
+ return rd_exec_link(rd, _##name, strict_port); \
+ }
+
+static const
+struct filters pd_valid_filters[MAX_NUMBER_OF_FILTERS] = {
+ { .name = "dev", .is_number = false },
+ { .name = "users", .is_number = true },
+ { .name = "pid", .is_number = true },
+ { .name = "ctxn", .is_number = true },
+ { .name = "pdn", .is_number = true, .is_doit = true },
+ { .name = "ctxn", .is_number = true }
+};
+
+RES_FUNC(res_pd, RDMA_NLDEV_CMD_RES_PD_GET, pd_valid_filters, true,
+ RDMA_NLDEV_ATTR_RES_PDN);
+
+static const
+struct filters mr_valid_filters[MAX_NUMBER_OF_FILTERS] = {
+ { .name = "dev", .is_number = false },
+ { .name = "rkey", .is_number = true },
+ { .name = "lkey", .is_number = true },
+ { .name = "mrlen", .is_number = true },
+ { .name = "pid", .is_number = true },
+ { .name = "mrn", .is_number = true, .is_doit = true },
+ { .name = "pdn", .is_number = true }
+};
+
+RES_FUNC(res_mr, RDMA_NLDEV_CMD_RES_MR_GET, mr_valid_filters, true,
+ RDMA_NLDEV_ATTR_RES_MRN);
+
+static const
+struct filters cq_valid_filters[MAX_NUMBER_OF_FILTERS] = {
+ { .name = "dev", .is_number = false },
+ { .name = "users", .is_number = true },
+ { .name = "poll-ctx", .is_number = false },
+ { .name = "pid", .is_number = true },
+ { .name = "cqn", .is_number = true, .is_doit = true },
+ { .name = "ctxn", .is_number = true }
+};
+
+RES_FUNC(res_cq, RDMA_NLDEV_CMD_RES_CQ_GET, cq_valid_filters, true,
+ RDMA_NLDEV_ATTR_RES_CQN);
+
+static const
+struct filters cm_id_valid_filters[MAX_NUMBER_OF_FILTERS] = {
+ { .name = "link", .is_number = false },
+ { .name = "lqpn", .is_number = true },
+ { .name = "qp-type", .is_number = false },
+ { .name = "state", .is_number = false },
+ { .name = "ps", .is_number = false },
+ { .name = "dev-type", .is_number = false },
+ { .name = "transport-type", .is_number = false },
+ { .name = "pid", .is_number = true },
+ { .name = "src-addr", .is_number = false },
+ { .name = "src-port", .is_number = true },
+ { .name = "dst-addr", .is_number = false },
+ { .name = "dst-port", .is_number = true },
+ { .name = "cm-idn", .is_number = true, .is_doit = true }
+};
+
+RES_FUNC(res_cm_id, RDMA_NLDEV_CMD_RES_CM_ID_GET, cm_id_valid_filters, false,
+ RDMA_NLDEV_ATTR_RES_CM_IDN);
+
+static const struct
+filters qp_valid_filters[MAX_NUMBER_OF_FILTERS] = {
+ { .name = "link", .is_number = false },
+ { .name = "lqpn", .is_number = true, .is_doit = true },
+ { .name = "rqpn", .is_number = true },
+ { .name = "pid", .is_number = true },
+ { .name = "sq-psn", .is_number = true },
+ { .name = "rq-psn", .is_number = true },
+ { .name = "type", .is_number = false },
+ { .name = "path-mig-state", .is_number = false },
+ { .name = "state", .is_number = false },
+ { .name = "pdn", .is_number = true },
+};
+
+RES_FUNC(res_qp, RDMA_NLDEV_CMD_RES_QP_GET, qp_valid_filters, false,
+ RDMA_NLDEV_ATTR_RES_LQPN);
+
+char *get_task_name(uint32_t pid);
+void print_dev(struct rd *rd, uint32_t idx, const char *name);
+void print_link(struct rd *rd, uint32_t idx, const char *name, uint32_t port,
+ struct nlattr **nla_line);
+void print_key(struct rd *rd, const char *name, uint64_t val,
+ struct nlattr *nlattr);
+void res_print_uint(struct rd *rd, const char *name, uint64_t val,
+ struct nlattr *nlattr);
+void print_comm(struct rd *rd, const char *str, struct nlattr **nla_line);
+const char *qp_types_to_str(uint8_t idx);
+
+#endif /* _RDMA_TOOL_RES_H_ */
diff --git a/rdma/stat.c b/rdma/stat.c
new file mode 100644
index 0000000..ef0bbcf
--- /dev/null
+++ b/rdma/stat.c
@@ -0,0 +1,759 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * rdma.c RDMA tool
+ * Authors: Mark Zhang <markz@mellanox.com>
+ */
+
+#include "rdma.h"
+#include "res.h"
+#include <inttypes.h>
+
+static int stat_help(struct rd *rd)
+{
+ pr_out("Usage: %s [ OPTIONS ] statistic { COMMAND | help }\n", rd->filename);
+ pr_out(" %s statistic OBJECT show\n", rd->filename);
+ pr_out(" %s statistic OBJECT show link [ DEV/PORT_INDEX ] [ FILTER-NAME FILTER-VALUE ]\n", rd->filename);
+ pr_out(" %s statistic OBJECT mode\n", rd->filename);
+ pr_out(" %s statistic OBJECT set COUNTER_SCOPE [DEV/PORT_INDEX] auto {CRITERIA | off}\n", rd->filename);
+ pr_out(" %s statistic OBJECT bind COUNTER_SCOPE [DEV/PORT_INDEX] [OBJECT-ID] [COUNTER-ID]\n", rd->filename);
+ pr_out(" %s statistic OBJECT unbind COUNTER_SCOPE [DEV/PORT_INDEX] [COUNTER-ID]\n", rd->filename);
+ pr_out(" %s statistic show\n", rd->filename);
+ pr_out(" %s statistic show link [ DEV/PORT_INDEX ]\n", rd->filename);
+ pr_out("where OBJECT: = { qp }\n");
+ pr_out(" CRITERIA : = { type }\n");
+ pr_out(" COUNTER_SCOPE: = { link | dev }\n");
+ pr_out("Examples:\n");
+ pr_out(" %s statistic qp show\n", rd->filename);
+ pr_out(" %s statistic qp show link mlx5_2/1\n", rd->filename);
+ pr_out(" %s statistic qp mode\n", rd->filename);
+ pr_out(" %s statistic qp mode link mlx5_0\n", rd->filename);
+ pr_out(" %s statistic qp set link mlx5_2/1 auto type on\n", rd->filename);
+ pr_out(" %s statistic qp set link mlx5_2/1 auto off\n", rd->filename);
+ pr_out(" %s statistic qp bind link mlx5_2/1 lqpn 178\n", rd->filename);
+ pr_out(" %s statistic qp bind link mlx5_2/1 lqpn 178 cntn 4\n", rd->filename);
+ pr_out(" %s statistic qp unbind link mlx5_2/1 cntn 4\n", rd->filename);
+ pr_out(" %s statistic qp unbind link mlx5_2/1 cntn 4 lqpn 178\n", rd->filename);
+ pr_out(" %s statistic show\n", rd->filename);
+ pr_out(" %s statistic show link mlx5_2/1\n", rd->filename);
+
+ return 0;
+}
+
+struct counter_param {
+ char *name;
+ uint32_t attr;
+};
+
+static struct counter_param auto_params[] = {
+ { "type", RDMA_COUNTER_MASK_QP_TYPE, },
+ { NULL },
+};
+
+static int prepare_auto_mode_str(struct nlattr **tb, uint32_t mask,
+ char *output, int len)
+{
+ char s[] = "qp auto";
+ int i, outlen = strlen(s);
+
+ memset(output, 0, len);
+ snprintf(output, len, "%s", s);
+
+ if (mask) {
+ for (i = 0; auto_params[i].name != NULL; i++) {
+ if (mask & auto_params[i].attr) {
+ outlen += strlen(auto_params[i].name) + 1;
+ if (outlen >= len)
+ return -EINVAL;
+ strcat(output, " ");
+ strcat(output, auto_params[i].name);
+ }
+ }
+
+ if (outlen + strlen(" on") >= len)
+ return -EINVAL;
+ strcat(output, " on");
+ } else {
+ if (outlen + strlen(" off") >= len)
+ return -EINVAL;
+ strcat(output, " off");
+ }
+
+ return 0;
+}
+
+static int qp_link_get_mode_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ uint32_t mode = 0, mask = 0;
+ char output[128] = {};
+ struct rd *rd = data;
+ uint32_t idx, port;
+ const char *name;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+
+ if (!tb[RDMA_NLDEV_ATTR_PORT_INDEX]) {
+ pr_err("This tool doesn't support switches yet\n");
+ return MNL_CB_ERROR;
+ }
+
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ port = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_PORT_INDEX]);
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ if (tb[RDMA_NLDEV_ATTR_STAT_MODE])
+ mode = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_STAT_MODE]);
+
+ if (mode == RDMA_COUNTER_MODE_AUTO) {
+ if (!tb[RDMA_NLDEV_ATTR_STAT_AUTO_MODE_MASK])
+ return MNL_CB_ERROR;
+ mask = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_STAT_AUTO_MODE_MASK]);
+ prepare_auto_mode_str(tb, mask, output, sizeof(output));
+ } else {
+ snprintf(output, sizeof(output), "qp auto off");
+ }
+
+ if (rd->json_output) {
+ jsonw_uint_field(rd->jw, "ifindex", idx);
+ jsonw_uint_field(rd->jw, "port", port);
+ jsonw_string_field(rd->jw, "mode", output);
+ } else {
+ pr_out("%u/%u: %s/%u: %s\n", idx, port, name, port, output);
+ }
+
+ return MNL_CB_OK;
+}
+
+static int stat_one_qp_link_get_mode(struct rd *rd)
+{
+ uint32_t seq;
+ int ret;
+
+ if (!rd->port_idx)
+ return 0;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_STAT_GET,
+ &seq, (NLM_F_REQUEST | NLM_F_ACK));
+
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_PORT_INDEX, rd->port_idx);
+ /* Make RDMA_NLDEV_ATTR_STAT_MODE valid so that kernel knows
+ * return only mode instead of all counters
+ */
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_MODE,
+ RDMA_COUNTER_MODE_MANUAL);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_RES, RDMA_NLDEV_ATTR_RES_QP);
+ ret = rd_send_msg(rd);
+ if (ret)
+ return ret;
+
+ if (rd->json_output)
+ jsonw_start_object(rd->jw);
+ ret = rd_recv_msg(rd, qp_link_get_mode_parse_cb, rd, seq);
+ if (rd->json_output)
+ jsonw_end_object(rd->jw);
+
+ return ret;
+}
+
+static int stat_qp_link_get_mode(struct rd *rd)
+{
+ return rd_exec_link(rd, stat_one_qp_link_get_mode, false);
+}
+
+static int stat_qp_get_mode(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_qp_link_get_mode },
+ { "link", stat_qp_link_get_mode },
+ { "help", stat_help },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int res_get_hwcounters(struct rd *rd, struct nlattr *hwc_table, bool print)
+{
+ struct nlattr *nla_entry;
+ const char *nm;
+ uint64_t v;
+ int err;
+
+ mnl_attr_for_each_nested(nla_entry, hwc_table) {
+ struct nlattr *hw_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ err = mnl_attr_parse_nested(nla_entry, rd_attr_cb, hw_line);
+ if (err != MNL_CB_OK)
+ return -EINVAL;
+
+ if (!hw_line[RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY_NAME] ||
+ !hw_line[RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY_VALUE]) {
+ return -EINVAL;
+ }
+
+ if (!print)
+ continue;
+
+ nm = mnl_attr_get_str(hw_line[RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY_NAME]);
+ v = mnl_attr_get_u64(hw_line[RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY_VALUE]);
+ if (rd->pretty_output && !rd->json_output)
+ newline_indent(rd);
+ res_print_uint(rd, nm, v, hw_line[RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY_NAME]);
+ }
+
+ return MNL_CB_OK;
+}
+
+static int res_counter_line(struct rd *rd, const char *name, int index,
+ struct nlattr **nla_line)
+{
+ uint32_t cntn, port = 0, pid = 0, qpn;
+ struct nlattr *hwc_table, *qp_table;
+ struct nlattr *nla_entry;
+ const char *comm = NULL;
+ bool isfirst;
+ int err;
+
+ if (nla_line[RDMA_NLDEV_ATTR_PORT_INDEX])
+ port = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_PORT_INDEX]);
+
+ hwc_table = nla_line[RDMA_NLDEV_ATTR_STAT_HWCOUNTERS];
+ qp_table = nla_line[RDMA_NLDEV_ATTR_RES_QP];
+ if (!hwc_table || !qp_table ||
+ !nla_line[RDMA_NLDEV_ATTR_STAT_COUNTER_ID])
+ return MNL_CB_ERROR;
+
+ cntn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_STAT_COUNTER_ID]);
+ if (rd_is_filtered_attr(rd, "cntn", cntn,
+ nla_line[RDMA_NLDEV_ATTR_STAT_COUNTER_ID]))
+ return MNL_CB_OK;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_PID]) {
+ pid = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ comm = get_task_name(pid);
+ }
+ if (rd_is_filtered_attr(rd, "pid", pid,
+ nla_line[RDMA_NLDEV_ATTR_RES_PID]))
+ return MNL_CB_OK;
+
+ if (nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME])
+ comm = (char *)mnl_attr_get_str(
+ nla_line[RDMA_NLDEV_ATTR_RES_KERN_NAME]);
+
+ mnl_attr_for_each_nested(nla_entry, qp_table) {
+ struct nlattr *qp_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ err = mnl_attr_parse_nested(nla_entry, rd_attr_cb, qp_line);
+ if (err != MNL_CB_OK)
+ return -EINVAL;
+
+ if (!qp_line[RDMA_NLDEV_ATTR_RES_LQPN])
+ return -EINVAL;
+
+ qpn = mnl_attr_get_u32(qp_line[RDMA_NLDEV_ATTR_RES_LQPN]);
+ if (rd_is_filtered_attr(rd, "lqpn", qpn,
+ qp_line[RDMA_NLDEV_ATTR_RES_LQPN]))
+ return MNL_CB_OK;
+ }
+
+ err = res_get_hwcounters(rd, hwc_table, false);
+ if (err != MNL_CB_OK)
+ return err;
+
+ if (rd->json_output) {
+ jsonw_string_field(rd->jw, "ifname", name);
+ if (port)
+ jsonw_uint_field(rd->jw, "port", port);
+ jsonw_uint_field(rd->jw, "cntn", cntn);
+ } else {
+ if (port)
+ pr_out("link %s/%u cntn %u ", name, port, cntn);
+ else
+ pr_out("dev %s cntn %u ", name, cntn);
+ }
+
+ res_print_uint(rd, "pid", pid, nla_line[RDMA_NLDEV_ATTR_RES_PID]);
+ print_comm(rd, comm, nla_line);
+
+ res_get_hwcounters(rd, hwc_table, true);
+
+ isfirst = true;
+ mnl_attr_for_each_nested(nla_entry, qp_table) {
+ struct nlattr *qp_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ if (isfirst && !rd->json_output)
+ pr_out("\n LQPN: <");
+
+ err = mnl_attr_parse_nested(nla_entry, rd_attr_cb, qp_line);
+ if (err != MNL_CB_OK)
+ return -EINVAL;
+
+ if (!qp_line[RDMA_NLDEV_ATTR_RES_LQPN])
+ return -EINVAL;
+
+ qpn = mnl_attr_get_u32(qp_line[RDMA_NLDEV_ATTR_RES_LQPN]);
+ if (rd->json_output) {
+ jsonw_uint_field(rd->jw, "lqpn", qpn);
+ } else {
+ if (isfirst)
+ pr_out("%d", qpn);
+ else
+ pr_out(", %d", qpn);
+ }
+ isfirst = false;
+ }
+
+ if (!rd->json_output)
+ pr_out(">\n");
+ return MNL_CB_OK;
+}
+
+static int stat_qp_show_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct nlattr *nla_table, *nla_entry;
+ struct rd *rd = data;
+ const char *name;
+ uint32_t idx;
+ int ret;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
+ !tb[RDMA_NLDEV_ATTR_STAT_COUNTER])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ nla_table = tb[RDMA_NLDEV_ATTR_STAT_COUNTER];
+
+ mnl_attr_for_each_nested(nla_entry, nla_table) {
+ struct nlattr *nla_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ ret = mnl_attr_parse_nested(nla_entry, rd_attr_cb, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+
+ ret = res_counter_line(rd, name, idx, nla_line);
+ if (ret != MNL_CB_OK)
+ break;
+ }
+
+ return ret;
+}
+
+static const struct filters stat_valid_filters[MAX_NUMBER_OF_FILTERS] = {
+ { .name = "cntn", .is_number = true },
+ { .name = "lqpn", .is_number = true },
+ { .name = "pid", .is_number = true },
+};
+
+static int stat_qp_show_one_link(struct rd *rd)
+{
+ int flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
+ uint32_t seq;
+ int ret;
+
+ if (!rd->port_idx)
+ return 0;
+
+ ret = rd_build_filter(rd, stat_valid_filters);
+ if (ret)
+ return ret;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_STAT_GET, &seq, flags);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_PORT_INDEX, rd->port_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_RES, RDMA_NLDEV_ATTR_RES_QP);
+ ret = rd_send_msg(rd);
+ if (ret)
+ return ret;
+
+ if (rd->json_output)
+ jsonw_start_object(rd->jw);
+ ret = rd_recv_msg(rd, stat_qp_show_parse_cb, rd, seq);
+ if (rd->json_output)
+ jsonw_end_object(rd->jw);
+
+ return ret;
+}
+
+static int stat_qp_show_link(struct rd *rd)
+{
+ return rd_exec_link(rd, stat_qp_show_one_link, false);
+}
+
+static int stat_qp_show(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_qp_show_link },
+ { "link", stat_qp_show_link },
+ { "help", stat_help },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int stat_qp_set_link_auto_sendmsg(struct rd *rd, uint32_t mask)
+{
+ uint32_t seq;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_STAT_SET,
+ &seq, (NLM_F_REQUEST | NLM_F_ACK));
+
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_PORT_INDEX, rd->port_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_RES, RDMA_NLDEV_ATTR_RES_QP);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_MODE,
+ RDMA_COUNTER_MODE_AUTO);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_AUTO_MODE_MASK, mask);
+
+ return rd_sendrecv_msg(rd, seq);
+}
+
+static int stat_one_qp_set_link_auto_off(struct rd *rd)
+{
+ return stat_qp_set_link_auto_sendmsg(rd, 0);
+}
+
+static int stat_one_qp_set_auto_type_on(struct rd *rd)
+{
+ return stat_qp_set_link_auto_sendmsg(rd, RDMA_COUNTER_MASK_QP_TYPE);
+}
+
+static int stat_one_qp_set_link_auto_type(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_help },
+ { "on", stat_one_qp_set_auto_type_on },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int stat_one_qp_set_link_auto(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_one_qp_link_get_mode },
+ { "off", stat_one_qp_set_link_auto_off },
+ { "type", stat_one_qp_set_link_auto_type },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int stat_one_qp_set_link(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_one_qp_link_get_mode },
+ { "auto", stat_one_qp_set_link_auto },
+ { 0 }
+ };
+
+ if (!rd->port_idx)
+ return 0;
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int stat_qp_set_link(struct rd *rd)
+{
+ return rd_exec_link(rd, stat_one_qp_set_link, false);
+}
+
+static int stat_qp_set(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_help },
+ { "link", stat_qp_set_link },
+ { "help", stat_help },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int stat_get_arg(struct rd *rd, const char *arg)
+{
+ int value = 0;
+ char *endp;
+
+ if (strcmpx(rd_argv(rd), arg) != 0)
+ return -EINVAL;
+
+ rd_arg_inc(rd);
+ value = strtol(rd_argv(rd), &endp, 10);
+ rd_arg_inc(rd);
+
+ return value;
+}
+
+static int stat_one_qp_bind(struct rd *rd)
+{
+ int lqpn = 0, cntn = 0, ret;
+ uint32_t seq;
+
+ if (rd_no_arg(rd)) {
+ stat_help(rd);
+ return -EINVAL;
+ }
+
+ ret = rd_build_filter(rd, stat_valid_filters);
+ if (ret)
+ return ret;
+
+ lqpn = stat_get_arg(rd, "lqpn");
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_STAT_SET,
+ &seq, (NLM_F_REQUEST | NLM_F_ACK));
+
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_MODE,
+ RDMA_COUNTER_MODE_MANUAL);
+
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_RES, RDMA_NLDEV_ATTR_RES_QP);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_PORT_INDEX, rd->port_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_RES_LQPN, lqpn);
+
+ if (rd_argc(rd)) {
+ cntn = stat_get_arg(rd, "cntn");
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_COUNTER_ID,
+ cntn);
+ }
+
+ return rd_sendrecv_msg(rd, seq);
+}
+
+static int do_stat_qp_unbind_lqpn(struct rd *rd, uint32_t cntn, uint32_t lqpn)
+{
+ uint32_t seq;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_STAT_DEL,
+ &seq, (NLM_F_REQUEST | NLM_F_ACK));
+
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_MODE,
+ RDMA_COUNTER_MODE_MANUAL);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_RES, RDMA_NLDEV_ATTR_RES_QP);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_PORT_INDEX, rd->port_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_COUNTER_ID, cntn);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_RES_LQPN, lqpn);
+
+ return rd_sendrecv_msg(rd, seq);
+}
+
+static int stat_get_counter_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct nlattr *nla_table, *nla_entry;
+ struct rd *rd = data;
+ uint32_t lqpn, cntn;
+ int err;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+
+ if (!tb[RDMA_NLDEV_ATTR_STAT_COUNTER_ID])
+ return MNL_CB_ERROR;
+ cntn = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_STAT_COUNTER_ID]);
+
+ nla_table = tb[RDMA_NLDEV_ATTR_RES_QP];
+ if (!nla_table)
+ return MNL_CB_ERROR;
+
+ mnl_attr_for_each_nested(nla_entry, nla_table) {
+ struct nlattr *nla_line[RDMA_NLDEV_ATTR_MAX] = {};
+
+ err = mnl_attr_parse_nested(nla_entry, rd_attr_cb, nla_line);
+ if (err != MNL_CB_OK)
+ return -EINVAL;
+
+ if (!nla_line[RDMA_NLDEV_ATTR_RES_LQPN])
+ return -EINVAL;
+
+ lqpn = mnl_attr_get_u32(nla_line[RDMA_NLDEV_ATTR_RES_LQPN]);
+ err = do_stat_qp_unbind_lqpn(rd, cntn, lqpn);
+ if (err)
+ return MNL_CB_ERROR;
+ }
+
+ return MNL_CB_OK;
+}
+
+static int stat_one_qp_unbind(struct rd *rd)
+{
+ int flags = NLM_F_REQUEST | NLM_F_ACK, ret;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ int lqpn = 0, cntn = 0;
+ unsigned int portid;
+ uint32_t seq;
+
+ ret = rd_build_filter(rd, stat_valid_filters);
+ if (ret)
+ return ret;
+
+ cntn = stat_get_arg(rd, "cntn");
+ if (rd_argc(rd)) {
+ lqpn = stat_get_arg(rd, "lqpn");
+ return do_stat_qp_unbind_lqpn(rd, cntn, lqpn);
+ }
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_STAT_GET, &seq, flags);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_PORT_INDEX, rd->port_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_RES, RDMA_NLDEV_ATTR_RES_QP);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_COUNTER_ID, cntn);
+ ret = rd_send_msg(rd);
+ if (ret)
+ return ret;
+
+
+ /* Can't use rd_recv_msg() since the callback also calls it (recursively),
+ * then rd_recv_msg() always return -1 here
+ */
+ portid = mnl_socket_get_portid(rd->nl);
+ ret = mnl_socket_recvfrom(rd->nl, buf, sizeof(buf));
+ if (ret <= 0)
+ return ret;
+
+ ret = mnl_cb_run(buf, ret, seq, portid, stat_get_counter_parse_cb, rd);
+ mnl_socket_close(rd->nl);
+ if (ret != MNL_CB_OK)
+ return ret;
+
+ return 0;
+}
+
+static int stat_qp_bind_link(struct rd *rd)
+{
+ return rd_exec_link(rd, stat_one_qp_bind, true);
+}
+
+static int stat_qp_bind(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_help },
+ { "link", stat_qp_bind_link },
+ { "help", stat_help },
+ { 0 },
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int stat_qp_unbind_link(struct rd *rd)
+{
+ return rd_exec_link(rd, stat_one_qp_unbind, true);
+}
+
+static int stat_qp_unbind(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_help },
+ { "link", stat_qp_unbind_link },
+ { "help", stat_help },
+ { 0 },
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int stat_qp(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_qp_show },
+ { "show", stat_qp_show },
+ { "list", stat_qp_show },
+ { "mode", stat_qp_get_mode },
+ { "set", stat_qp_set },
+ { "bind", stat_qp_bind },
+ { "unbind", stat_qp_unbind },
+ { "help", stat_help },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int stat_show_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct rd *rd = data;
+ const char *name;
+ uint32_t port;
+ int ret;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_INDEX] || !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
+ !tb[RDMA_NLDEV_ATTR_PORT_INDEX] ||
+ !tb[RDMA_NLDEV_ATTR_STAT_HWCOUNTERS])
+ return MNL_CB_ERROR;
+
+ name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+ port = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_PORT_INDEX]);
+ if (rd->json_output) {
+ jsonw_string_field(rd->jw, "ifname", name);
+ jsonw_uint_field(rd->jw, "port", port);
+ } else {
+ pr_out("link %s/%u ", name, port);
+ }
+
+ ret = res_get_hwcounters(rd, tb[RDMA_NLDEV_ATTR_STAT_HWCOUNTERS], true);
+
+ if (!rd->json_output)
+ pr_out("\n");
+ return ret;
+}
+
+static int stat_show_one_link(struct rd *rd)
+{
+ int flags = NLM_F_REQUEST | NLM_F_ACK;
+ uint32_t seq;
+ int ret;
+
+ if (!rd->port_idx)
+ return 0;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_STAT_GET, &seq, flags);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_DEV_INDEX, rd->dev_idx);
+ mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_PORT_INDEX, rd->port_idx);
+ ret = rd_send_msg(rd);
+ if (ret)
+ return ret;
+
+ return rd_recv_msg(rd, stat_show_parse_cb, rd, seq);
+}
+
+static int stat_show_link(struct rd *rd)
+{
+ return rd_exec_link(rd, stat_show_one_link, false);
+}
+
+static int stat_show(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_show_link },
+ { "link", stat_show_link },
+ { "help", stat_help },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+int cmd_stat(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, stat_show },
+ { "show", stat_show },
+ { "list", stat_show },
+ { "help", stat_help },
+ { "qp", stat_qp },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "statistic command");
+}
diff --git a/rdma/sys.c b/rdma/sys.c
new file mode 100644
index 0000000..1a434a2
--- /dev/null
+++ b/rdma/sys.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * sys.c RDMA tool
+ */
+
+#include "rdma.h"
+
+static int sys_help(struct rd *rd)
+{
+ pr_out("Usage: %s system show [ netns ]\n", rd->filename);
+ pr_out(" %s system set netns { shared | exclusive }\n", rd->filename);
+ return 0;
+}
+
+static const char *netns_modes_str[] = {
+ "exclusive",
+ "shared",
+};
+
+static int sys_show_parse_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct rd *rd = data;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+
+ if (tb[RDMA_NLDEV_SYS_ATTR_NETNS_MODE]) {
+ const char *mode_str;
+ uint8_t netns_mode;
+
+ netns_mode =
+ mnl_attr_get_u8(tb[RDMA_NLDEV_SYS_ATTR_NETNS_MODE]);
+
+ if (netns_mode < ARRAY_SIZE(netns_modes_str))
+ mode_str = netns_modes_str[netns_mode];
+ else
+ mode_str = "unknown";
+
+ if (rd->json_output)
+ jsonw_string_field(rd->jw, "netns", mode_str);
+ else
+ pr_out("netns %s\n", mode_str);
+ }
+ return MNL_CB_OK;
+}
+
+static int sys_show_no_args(struct rd *rd)
+{
+ uint32_t seq;
+ int ret;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_SYS_GET,
+ &seq, (NLM_F_REQUEST | NLM_F_ACK));
+ ret = rd_send_msg(rd);
+ if (ret)
+ return ret;
+
+ return rd_recv_msg(rd, sys_show_parse_cb, rd, seq);
+}
+
+static int sys_show(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, sys_show_no_args},
+ { "netns", sys_show_no_args},
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+static int sys_set_netns_cmd(struct rd *rd, bool enable)
+{
+ uint32_t seq;
+
+ rd_prepare_msg(rd, RDMA_NLDEV_CMD_SYS_SET,
+ &seq, (NLM_F_REQUEST | NLM_F_ACK));
+ mnl_attr_put_u8(rd->nlh, RDMA_NLDEV_SYS_ATTR_NETNS_MODE, enable);
+
+ return rd_sendrecv_msg(rd, seq);
+}
+
+static bool sys_valid_netns_cmd(const char *cmd)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(netns_modes_str); i++) {
+ if (!strcmp(cmd, netns_modes_str[i]))
+ return true;
+ }
+ return false;
+}
+
+static int sys_set_netns_args(struct rd *rd)
+{
+ bool cmd;
+
+ if (rd_no_arg(rd) || !sys_valid_netns_cmd(rd_argv(rd))) {
+ pr_err("valid options are: { shared | exclusive }\n");
+ return -EINVAL;
+ }
+
+ cmd = (strcmp(rd_argv(rd), "shared") == 0) ? true : false;
+
+ return sys_set_netns_cmd(rd, cmd);
+}
+
+static int sys_set_help(struct rd *rd)
+{
+ pr_out("Usage: %s system set [PARAM] value\n", rd->filename);
+ pr_out(" system set netns { shared | exclusive }\n");
+ return 0;
+}
+
+static int sys_set(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, sys_set_help },
+ { "help", sys_set_help },
+ { "netns", sys_set_netns_args},
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "parameter");
+}
+
+int cmd_sys(struct rd *rd)
+{
+ const struct rd_cmd cmds[] = {
+ { NULL, sys_show },
+ { "show", sys_show },
+ { "set", sys_set },
+ { "help", sys_help },
+ { 0 }
+ };
+
+ return rd_exec_cmd(rd, cmds, "system command");
+}
diff --git a/rdma/utils.c b/rdma/utils.c
new file mode 100644
index 0000000..3765901
--- /dev/null
+++ b/rdma/utils.c
@@ -0,0 +1,949 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * utils.c RDMA tool
+ * Authors: Leon Romanovsky <leonro@mellanox.com>
+ */
+
+#include "rdma.h"
+#include <ctype.h>
+#include <inttypes.h>
+
+int rd_argc(struct rd *rd)
+{
+ return rd->argc;
+}
+
+char *rd_argv(struct rd *rd)
+{
+ if (!rd_argc(rd))
+ return NULL;
+ return *rd->argv;
+}
+
+int strcmpx(const char *str1, const char *str2)
+{
+ if (strlen(str1) > strlen(str2))
+ return -1;
+ return strncmp(str1, str2, strlen(str1));
+}
+
+static bool rd_argv_match(struct rd *rd, const char *pattern)
+{
+ if (!rd_argc(rd))
+ return false;
+ return strcmpx(rd_argv(rd), pattern) == 0;
+}
+
+void rd_arg_inc(struct rd *rd)
+{
+ if (!rd_argc(rd))
+ return;
+ rd->argc--;
+ rd->argv++;
+}
+
+bool rd_no_arg(struct rd *rd)
+{
+ return rd_argc(rd) == 0;
+}
+
+/*
+ * Possible input:output
+ * dev/port | first port | is_dump_all
+ * mlx5_1 | 0 | true
+ * mlx5_1/ | 0 | true
+ * mlx5_1/0 | 0 | false
+ * mlx5_1/1 | 1 | false
+ * mlx5_1/- | 0 | false
+ *
+ * In strict port mode, a non-0 port must be provided
+ */
+static int get_port_from_argv(struct rd *rd, uint32_t *port,
+ bool *is_dump_all, bool strict_port)
+{
+ char *slash;
+
+ *port = 0;
+ *is_dump_all = strict_port ? false : true;
+
+ slash = strchr(rd_argv(rd), '/');
+ /* if no port found, return 0 */
+ if (slash++) {
+ if (*slash == '-') {
+ if (strict_port)
+ return -EINVAL;
+ *is_dump_all = false;
+ return 0;
+ }
+
+ if (isdigit(*slash)) {
+ *is_dump_all = false;
+ *port = atoi(slash);
+ }
+ if (!*port && strlen(slash))
+ return -EINVAL;
+ }
+ if (strict_port && (*port == 0))
+ return -EINVAL;
+
+ return 0;
+}
+
+static struct dev_map *dev_map_alloc(const char *dev_name)
+{
+ struct dev_map *dev_map;
+
+ dev_map = calloc(1, sizeof(*dev_map));
+ if (!dev_map)
+ return NULL;
+ dev_map->dev_name = strdup(dev_name);
+ if (!dev_map->dev_name) {
+ free(dev_map);
+ return NULL;
+ }
+
+ return dev_map;
+}
+
+static void dev_map_cleanup(struct rd *rd)
+{
+ struct dev_map *dev_map, *tmp;
+
+ list_for_each_entry_safe(dev_map, tmp,
+ &rd->dev_map_list, list) {
+ list_del(&dev_map->list);
+ free(dev_map->dev_name);
+ free(dev_map);
+ }
+}
+
+static int add_filter(struct rd *rd, char *key, char *value,
+ const struct filters valid_filters[])
+{
+ char cset[] = "1234567890,-";
+ struct filter_entry *fe;
+ bool key_found = false;
+ int idx = 0;
+ char *endp;
+ int ret;
+
+ fe = calloc(1, sizeof(*fe));
+ if (!fe)
+ return -ENOMEM;
+
+ while (idx < MAX_NUMBER_OF_FILTERS && valid_filters[idx].name) {
+ if (!strcmpx(key, valid_filters[idx].name)) {
+ key_found = true;
+ break;
+ }
+ idx++;
+ }
+ if (!key_found) {
+ pr_err("Unsupported filter option: %s\n", key);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /*
+ * Check the filter validity, not optimal, but works
+ *
+ * Actually, there are three types of filters
+ * numeric - for example PID or QPN
+ * string - for example states
+ * link - user requested to filter on specific link
+ * e.g. mlx5_1/1, mlx5_1/-, mlx5_1 ...
+ */
+ if (valid_filters[idx].is_number &&
+ strspn(value, cset) != strlen(value)) {
+ pr_err("%s filter accepts \"%s\" characters only\n", key, cset);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ fe->key = strdup(key);
+ fe->value = strdup(value);
+ if (!fe->key || !fe->value) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ errno = 0;
+ strtol(fe->value, &endp, 10);
+ if (valid_filters[idx].is_doit && !errno && *endp == '\0')
+ fe->is_doit = true;
+
+ for (idx = 0; idx < strlen(fe->value); idx++)
+ fe->value[idx] = tolower(fe->value[idx]);
+
+ list_add_tail(&fe->list, &rd->filter_list);
+ return 0;
+
+err_alloc:
+ free(fe->value);
+ free(fe->key);
+err:
+ free(fe);
+ return ret;
+}
+
+bool rd_doit_index(struct rd *rd, uint32_t *idx)
+{
+ struct filter_entry *fe;
+
+ list_for_each_entry(fe, &rd->filter_list, list) {
+ if (fe->is_doit) {
+ *idx = atoi(fe->value);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int rd_build_filter(struct rd *rd, const struct filters valid_filters[])
+{
+ int ret = 0;
+ int idx = 0;
+
+ if (!valid_filters || !rd_argc(rd))
+ goto out;
+
+ if (rd_argc(rd) == 1) {
+ pr_err("No filter data was supplied to filter option %s\n", rd_argv(rd));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (rd_argc(rd) % 2) {
+ pr_err("There is filter option without data\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ while (idx != rd_argc(rd)) {
+ /*
+ * We can do micro-optimization and skip "dev"
+ * and "link" filters, but it is not worth of it.
+ */
+ ret = add_filter(rd, *(rd->argv + idx),
+ *(rd->argv + idx + 1), valid_filters);
+ if (ret)
+ goto out;
+ idx += 2;
+ }
+
+out:
+ return ret;
+}
+
+static bool rd_check_is_key_exist(struct rd *rd, const char *key)
+{
+ struct filter_entry *fe;
+
+ list_for_each_entry(fe, &rd->filter_list, list) {
+ if (!strcmpx(fe->key, key))
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Check if string entry is filtered:
+ * * key doesn't exist -> user didn't request -> not filtered
+ */
+static bool rd_check_is_string_filtered(struct rd *rd, const char *key,
+ const char *val)
+{
+ bool key_is_filtered = false;
+ struct filter_entry *fe;
+ char *p = NULL;
+ char *str;
+
+ list_for_each_entry(fe, &rd->filter_list, list) {
+ if (!strcmpx(fe->key, key)) {
+ /* We found the key */
+ p = strdup(fe->value);
+ key_is_filtered = true;
+ if (!p) {
+ /*
+ * Something extremely wrong if we fail
+ * to allocate small amount of bytes.
+ */
+ pr_err("Found key, but failed to allocate memory to store value\n");
+ return key_is_filtered;
+ }
+
+ /*
+ * Need to check if value in range
+ * It can come in the following formats
+ * and their permutations:
+ * str
+ * str1,str2
+ */
+ str = strtok(p, ",");
+ while (str) {
+ if (strlen(str) == strlen(val) &&
+ !strcasecmp(str, val)) {
+ key_is_filtered = false;
+ goto out;
+ }
+ str = strtok(NULL, ",");
+ }
+ goto out;
+ }
+ }
+
+out:
+ free(p);
+ return key_is_filtered;
+}
+
+/*
+ * Check if key is filtered:
+ * key doesn't exist -> user didn't request -> not filtered
+ */
+static bool rd_check_is_filtered(struct rd *rd, const char *key, uint32_t val)
+{
+ bool key_is_filtered = false;
+ struct filter_entry *fe;
+
+ list_for_each_entry(fe, &rd->filter_list, list) {
+ uint32_t left_val = 0, fe_value = 0;
+ bool range_check = false;
+ char *p = fe->value;
+
+ if (!strcmpx(fe->key, key)) {
+ /* We found the key */
+ key_is_filtered = true;
+ /*
+ * Need to check if value in range
+ * It can come in the following formats
+ * (and their permutations):
+ * numb
+ * numb1,numb2
+ * ,numb1,numb2
+ * numb1-numb2
+ * numb1,numb2-numb3,numb4-numb5
+ */
+ while (*p) {
+ if (isdigit(*p)) {
+ fe_value = strtol(p, &p, 10);
+ if (fe_value == val ||
+ (range_check && left_val < val &&
+ val < fe_value)) {
+ key_is_filtered = false;
+ goto out;
+ }
+ range_check = false;
+ } else {
+ if (*p == '-') {
+ left_val = fe_value;
+ range_check = true;
+ }
+ p++;
+ }
+ }
+ goto out;
+ }
+ }
+
+out:
+ return key_is_filtered;
+}
+
+bool rd_is_filtered_attr(struct rd *rd, const char *key, uint32_t val,
+ struct nlattr *attr)
+{
+ if (!attr)
+ return rd_check_is_key_exist(rd, key);
+
+ return rd_check_is_filtered(rd, key, val);
+}
+
+bool rd_is_string_filtered_attr(struct rd *rd, const char *key, const char *val,
+ struct nlattr *attr)
+{
+ if (!attr)
+ rd_check_is_key_exist(rd, key);
+
+ return rd_check_is_string_filtered(rd, key, val);
+}
+
+static void filters_cleanup(struct rd *rd)
+{
+ struct filter_entry *fe, *tmp;
+
+ list_for_each_entry_safe(fe, tmp,
+ &rd->filter_list, list) {
+ list_del(&fe->list);
+ free(fe->key);
+ free(fe->value);
+ free(fe);
+ }
+}
+
+static const enum mnl_attr_data_type nldev_policy[RDMA_NLDEV_ATTR_MAX] = {
+ [RDMA_NLDEV_ATTR_DEV_INDEX] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_DEV_NAME] = MNL_TYPE_NUL_STRING,
+ [RDMA_NLDEV_ATTR_PORT_INDEX] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_CAP_FLAGS] = MNL_TYPE_U64,
+ [RDMA_NLDEV_ATTR_FW_VERSION] = MNL_TYPE_NUL_STRING,
+ [RDMA_NLDEV_ATTR_NODE_GUID] = MNL_TYPE_U64,
+ [RDMA_NLDEV_ATTR_SYS_IMAGE_GUID] = MNL_TYPE_U64,
+ [RDMA_NLDEV_ATTR_LID] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_SM_LID] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_LMC] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_PORT_STATE] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_PORT_PHYS_STATE] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_DEV_NODE_TYPE] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_RES_SUMMARY] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_NAME] = MNL_TYPE_NUL_STRING,
+ [RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR] = MNL_TYPE_U64,
+ [RDMA_NLDEV_ATTR_RES_QP] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_RES_QP_ENTRY] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_RES_LQPN] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_RES_RQPN] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_RES_RQ_PSN] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_RES_SQ_PSN] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_RES_PATH_MIG_STATE] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_RES_TYPE] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_RES_STATE] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_RES_PID] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_RES_KERN_NAME] = MNL_TYPE_NUL_STRING,
+ [RDMA_NLDEV_ATTR_RES_CM_ID] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_RES_CM_ID_ENTRY] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_RES_PS] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_RES_SRC_ADDR] = MNL_TYPE_UNSPEC,
+ [RDMA_NLDEV_ATTR_RES_DST_ADDR] = MNL_TYPE_UNSPEC,
+ [RDMA_NLDEV_ATTR_RES_CQ] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_RES_CQ_ENTRY] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_RES_CQE] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_RES_USECNT] = MNL_TYPE_U64,
+ [RDMA_NLDEV_ATTR_RES_POLL_CTX] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_RES_MR] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_RES_MR_ENTRY] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_RES_RKEY] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_RES_LKEY] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_RES_IOVA] = MNL_TYPE_U64,
+ [RDMA_NLDEV_ATTR_RES_MRLEN] = MNL_TYPE_U64,
+ [RDMA_NLDEV_ATTR_NDEV_INDEX] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_NDEV_NAME] = MNL_TYPE_NUL_STRING,
+ [RDMA_NLDEV_ATTR_DRIVER] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_DRIVER_ENTRY] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_DRIVER_STRING] = MNL_TYPE_NUL_STRING,
+ [RDMA_NLDEV_ATTR_DRIVER_PRINT_TYPE] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_DRIVER_S32] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_DRIVER_U32] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_DRIVER_S64] = MNL_TYPE_U64,
+ [RDMA_NLDEV_ATTR_DRIVER_U64] = MNL_TYPE_U64,
+ [RDMA_NLDEV_SYS_ATTR_NETNS_MODE] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_STAT_COUNTER] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_STAT_COUNTER_ENTRY] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_STAT_COUNTER_ID] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_STAT_HWCOUNTERS] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY] = MNL_TYPE_NESTED,
+ [RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY_NAME] = MNL_TYPE_NUL_STRING,
+ [RDMA_NLDEV_ATTR_STAT_HWCOUNTER_ENTRY_VALUE] = MNL_TYPE_U64,
+ [RDMA_NLDEV_ATTR_STAT_MODE] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_STAT_RES] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_STAT_AUTO_MODE_MASK] = MNL_TYPE_U32,
+ [RDMA_NLDEV_ATTR_DEV_DIM] = MNL_TYPE_U8,
+};
+
+int rd_attr_check(const struct nlattr *attr, int *typep)
+{
+ int type;
+
+ if (mnl_attr_type_valid(attr, RDMA_NLDEV_ATTR_MAX) < 0)
+ return MNL_CB_ERROR;
+
+ type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_validate(attr, nldev_policy[type]) < 0)
+ return MNL_CB_ERROR;
+
+ *typep = nldev_policy[type];
+ return MNL_CB_OK;
+}
+
+int rd_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type;
+
+ if (mnl_attr_type_valid(attr, RDMA_NLDEV_ATTR_MAX - 1) < 0)
+ /* We received unknown attribute */
+ return MNL_CB_OK;
+
+ type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_validate(attr, nldev_policy[type]) < 0)
+ return MNL_CB_ERROR;
+
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+int rd_dev_init_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
+ struct dev_map *dev_map;
+ struct rd *rd = data;
+ const char *dev_name;
+
+ mnl_attr_parse(nlh, 0, rd_attr_cb, tb);
+ if (!tb[RDMA_NLDEV_ATTR_DEV_NAME] || !tb[RDMA_NLDEV_ATTR_DEV_INDEX])
+ return MNL_CB_ERROR;
+ if (!tb[RDMA_NLDEV_ATTR_PORT_INDEX]) {
+ pr_err("This tool doesn't support switches yet\n");
+ return MNL_CB_ERROR;
+ }
+
+ dev_name = mnl_attr_get_str(tb[RDMA_NLDEV_ATTR_DEV_NAME]);
+
+ dev_map = dev_map_alloc(dev_name);
+ if (!dev_map)
+ /* The main function will cleanup the allocations */
+ return MNL_CB_ERROR;
+ list_add_tail(&dev_map->list, &rd->dev_map_list);
+
+ dev_map->num_ports = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_PORT_INDEX]);
+ dev_map->idx = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
+ return MNL_CB_OK;
+}
+
+void rd_free(struct rd *rd)
+{
+ if (!rd)
+ return;
+ free(rd->buff);
+ dev_map_cleanup(rd);
+ filters_cleanup(rd);
+}
+
+int rd_set_arg_to_devname(struct rd *rd)
+{
+ int ret = 0;
+
+ while (!rd_no_arg(rd)) {
+ if (rd_argv_match(rd, "dev") || rd_argv_match(rd, "link")) {
+ rd_arg_inc(rd);
+ if (rd_no_arg(rd)) {
+ pr_err("No device name was supplied\n");
+ ret = -EINVAL;
+ }
+ goto out;
+ }
+ rd_arg_inc(rd);
+ }
+out:
+ return ret;
+}
+
+int rd_exec_link(struct rd *rd, int (*cb)(struct rd *rd), bool strict_port)
+{
+ struct dev_map *dev_map;
+ uint32_t port;
+ int ret = 0;
+
+ if (rd->json_output)
+ jsonw_start_array(rd->jw);
+ if (rd_no_arg(rd)) {
+ list_for_each_entry(dev_map, &rd->dev_map_list, list) {
+ rd->dev_idx = dev_map->idx;
+ port = (strict_port) ? 1 : 0;
+ for (; port < dev_map->num_ports + 1; port++) {
+ rd->port_idx = port;
+ ret = cb(rd);
+ if (ret)
+ goto out;
+ }
+ }
+
+ } else {
+ bool is_dump_all;
+
+ dev_map = dev_map_lookup(rd, true);
+ ret = get_port_from_argv(rd, &port, &is_dump_all, strict_port);
+ if (!dev_map || port > dev_map->num_ports || (!port && ret)) {
+ pr_err("Wrong device name\n");
+ ret = -ENOENT;
+ goto out;
+ }
+ rd_arg_inc(rd);
+ rd->dev_idx = dev_map->idx;
+ rd->port_idx = port;
+ for (; rd->port_idx < dev_map->num_ports + 1; rd->port_idx++) {
+ ret = cb(rd);
+ if (ret)
+ goto out;
+ if (!is_dump_all)
+ /*
+ * We got request to show link for devname
+ * with port index.
+ */
+ break;
+ }
+ }
+
+out:
+ if (rd->json_output)
+ jsonw_end_array(rd->jw);
+ return ret;
+}
+
+int rd_exec_dev(struct rd *rd, int (*cb)(struct rd *rd))
+{
+ struct dev_map *dev_map;
+ int ret = 0;
+
+ if (rd->json_output)
+ jsonw_start_array(rd->jw);
+ if (rd_no_arg(rd)) {
+ list_for_each_entry(dev_map, &rd->dev_map_list, list) {
+ rd->dev_idx = dev_map->idx;
+ ret = cb(rd);
+ if (ret)
+ goto out;
+ }
+ } else {
+ dev_map = dev_map_lookup(rd, false);
+ if (!dev_map) {
+ pr_err("Wrong device name - %s\n", rd_argv(rd));
+ ret = -ENOENT;
+ goto out;
+ }
+ rd_arg_inc(rd);
+ rd->dev_idx = dev_map->idx;
+ ret = cb(rd);
+ }
+out:
+ if (rd->json_output)
+ jsonw_end_array(rd->jw);
+ return ret;
+}
+
+int rd_exec_require_dev(struct rd *rd, int (*cb)(struct rd *rd))
+{
+ if (rd_no_arg(rd)) {
+ pr_err("Please provide device name.\n");
+ return -EINVAL;
+ }
+
+ return rd_exec_dev(rd, cb);
+}
+
+int rd_exec_cmd(struct rd *rd, const struct rd_cmd *cmds, const char *str)
+{
+ const struct rd_cmd *c;
+
+ /* First argument in objs table is default variant */
+ if (rd_no_arg(rd))
+ return cmds->func(rd);
+
+ for (c = cmds + 1; c->cmd; ++c) {
+ if (rd_argv_match(rd, c->cmd)) {
+ /* Move to next argument */
+ rd_arg_inc(rd);
+ return c->func(rd);
+ }
+ }
+
+ pr_err("Unknown %s '%s'.\n", str, rd_argv(rd));
+ return 0;
+}
+
+void rd_prepare_msg(struct rd *rd, uint32_t cmd, uint32_t *seq, uint16_t flags)
+{
+ *seq = time(NULL);
+
+ rd->nlh = mnl_nlmsg_put_header(rd->buff);
+ rd->nlh->nlmsg_type = RDMA_NL_GET_TYPE(RDMA_NL_NLDEV, cmd);
+ rd->nlh->nlmsg_seq = *seq;
+ rd->nlh->nlmsg_flags = flags;
+}
+
+int rd_send_msg(struct rd *rd)
+{
+ int ret;
+
+ rd->nl = mnl_socket_open(NETLINK_RDMA);
+ if (!rd->nl) {
+ pr_err("Failed to open NETLINK_RDMA socket\n");
+ return -ENODEV;
+ }
+
+ ret = mnl_socket_bind(rd->nl, 0, MNL_SOCKET_AUTOPID);
+ if (ret < 0) {
+ pr_err("Failed to bind socket with err %d\n", ret);
+ goto err;
+ }
+
+ ret = mnl_socket_sendto(rd->nl, rd->nlh, rd->nlh->nlmsg_len);
+ if (ret < 0) {
+ pr_err("Failed to send to socket with err %d\n", ret);
+ goto err;
+ }
+ return 0;
+
+err:
+ mnl_socket_close(rd->nl);
+ return ret;
+}
+
+int rd_recv_msg(struct rd *rd, mnl_cb_t callback, void *data, unsigned int seq)
+{
+ int ret;
+ unsigned int portid;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ portid = mnl_socket_get_portid(rd->nl);
+ do {
+ ret = mnl_socket_recvfrom(rd->nl, buf, sizeof(buf));
+ if (ret <= 0)
+ break;
+
+ ret = mnl_cb_run(buf, ret, seq, portid, callback, data);
+ } while (ret > 0);
+
+ if (ret < 0 && !rd->suppress_errors)
+ perror("error");
+
+ mnl_socket_close(rd->nl);
+ return ret;
+}
+
+static int null_cb(const struct nlmsghdr *nlh, void *data)
+{
+ return MNL_CB_OK;
+}
+
+int rd_sendrecv_msg(struct rd *rd, unsigned int seq)
+{
+ int ret;
+
+ ret = rd_send_msg(rd);
+ if (!ret)
+ ret = rd_recv_msg(rd, null_cb, rd, seq);
+ return ret;
+}
+
+static struct dev_map *_dev_map_lookup(struct rd *rd, const char *dev_name)
+{
+ struct dev_map *dev_map;
+
+ list_for_each_entry(dev_map, &rd->dev_map_list, list)
+ if (strcmp(dev_name, dev_map->dev_name) == 0)
+ return dev_map;
+
+ return NULL;
+}
+
+struct dev_map *dev_map_lookup(struct rd *rd, bool allow_port_index)
+{
+ struct dev_map *dev_map;
+ char *dev_name;
+ char *slash;
+
+ if (rd_no_arg(rd))
+ return NULL;
+
+ dev_name = strdup(rd_argv(rd));
+ if (allow_port_index) {
+ slash = strrchr(dev_name, '/');
+ if (slash)
+ *slash = '\0';
+ }
+
+ dev_map = _dev_map_lookup(rd, dev_name);
+ free(dev_name);
+ return dev_map;
+}
+
+#define nla_type(attr) ((attr)->nla_type & NLA_TYPE_MASK)
+
+void newline(struct rd *rd)
+{
+ if (rd->json_output)
+ jsonw_end_array(rd->jw);
+ else
+ pr_out("\n");
+}
+
+void newline_indent(struct rd *rd)
+{
+ newline(rd);
+ if (!rd->json_output)
+ pr_out(" ");
+}
+
+static int print_driver_string(struct rd *rd, const char *key_str,
+ const char *val_str)
+{
+ if (rd->json_output) {
+ jsonw_string_field(rd->jw, key_str, val_str);
+ return 0;
+ } else {
+ return pr_out("%s %s ", key_str, val_str);
+ }
+}
+
+void print_on_off(struct rd *rd, const char *key_str, bool on)
+{
+ print_driver_string(rd, key_str, (on) ? "on":"off");
+}
+
+static int print_driver_s32(struct rd *rd, const char *key_str, int32_t val,
+ enum rdma_nldev_print_type print_type)
+{
+ if (rd->json_output) {
+ jsonw_int_field(rd->jw, key_str, val);
+ return 0;
+ }
+ switch (print_type) {
+ case RDMA_NLDEV_PRINT_TYPE_UNSPEC:
+ return pr_out("%s %d ", key_str, val);
+ case RDMA_NLDEV_PRINT_TYPE_HEX:
+ return pr_out("%s 0x%x ", key_str, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int print_driver_u32(struct rd *rd, const char *key_str, uint32_t val,
+ enum rdma_nldev_print_type print_type)
+{
+ if (rd->json_output) {
+ jsonw_int_field(rd->jw, key_str, val);
+ return 0;
+ }
+ switch (print_type) {
+ case RDMA_NLDEV_PRINT_TYPE_UNSPEC:
+ return pr_out("%s %u ", key_str, val);
+ case RDMA_NLDEV_PRINT_TYPE_HEX:
+ return pr_out("%s 0x%x ", key_str, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int print_driver_s64(struct rd *rd, const char *key_str, int64_t val,
+ enum rdma_nldev_print_type print_type)
+{
+ if (rd->json_output) {
+ jsonw_int_field(rd->jw, key_str, val);
+ return 0;
+ }
+ switch (print_type) {
+ case RDMA_NLDEV_PRINT_TYPE_UNSPEC:
+ return pr_out("%s %" PRId64 " ", key_str, val);
+ case RDMA_NLDEV_PRINT_TYPE_HEX:
+ return pr_out("%s 0x%" PRIx64 " ", key_str, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int print_driver_u64(struct rd *rd, const char *key_str, uint64_t val,
+ enum rdma_nldev_print_type print_type)
+{
+ if (rd->json_output) {
+ jsonw_int_field(rd->jw, key_str, val);
+ return 0;
+ }
+ switch (print_type) {
+ case RDMA_NLDEV_PRINT_TYPE_UNSPEC:
+ return pr_out("%s %" PRIu64 " ", key_str, val);
+ case RDMA_NLDEV_PRINT_TYPE_HEX:
+ return pr_out("%s 0x%" PRIx64 " ", key_str, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int print_driver_entry(struct rd *rd, struct nlattr *key_attr,
+ struct nlattr *val_attr,
+ enum rdma_nldev_print_type print_type)
+{
+ int attr_type = nla_type(val_attr);
+ int ret = -EINVAL;
+ char *key_str;
+
+ if (asprintf(&key_str, "drv_%s", mnl_attr_get_str(key_attr)) == -1)
+ return -ENOMEM;
+
+ switch (attr_type) {
+ case RDMA_NLDEV_ATTR_DRIVER_STRING:
+ ret = print_driver_string(rd, key_str,
+ mnl_attr_get_str(val_attr));
+ break;
+ case RDMA_NLDEV_ATTR_DRIVER_S32:
+ ret = print_driver_s32(rd, key_str, mnl_attr_get_u32(val_attr),
+ print_type);
+ break;
+ case RDMA_NLDEV_ATTR_DRIVER_U32:
+ ret = print_driver_u32(rd, key_str, mnl_attr_get_u32(val_attr),
+ print_type);
+ break;
+ case RDMA_NLDEV_ATTR_DRIVER_S64:
+ ret = print_driver_s64(rd, key_str, mnl_attr_get_u64(val_attr),
+ print_type);
+ break;
+ case RDMA_NLDEV_ATTR_DRIVER_U64:
+ ret = print_driver_u64(rd, key_str, mnl_attr_get_u64(val_attr),
+ print_type);
+ break;
+ }
+ free(key_str);
+ return ret;
+}
+
+void print_driver_table(struct rd *rd, struct nlattr *tb)
+{
+ int print_type = RDMA_NLDEV_PRINT_TYPE_UNSPEC;
+ struct nlattr *tb_entry, *key = NULL, *val;
+ int type, cc = 0;
+ int ret;
+
+ if (!rd->show_driver_details || !tb)
+ return;
+
+ if (rd->pretty_output)
+ newline_indent(rd);
+
+ /*
+ * Driver attrs are tuples of {key, [print-type], value}.
+ * The key must be a string. If print-type is present, it
+ * defines an alternate printf format type vs the native format
+ * for the attribute. And the value can be any available
+ * driver type.
+ */
+ mnl_attr_for_each_nested(tb_entry, tb) {
+
+ if (cc > MAX_LINE_LENGTH) {
+ if (rd->pretty_output)
+ newline_indent(rd);
+ cc = 0;
+ }
+ if (rd_attr_check(tb_entry, &type) != MNL_CB_OK)
+ return;
+ if (!key) {
+ if (type != MNL_TYPE_NUL_STRING)
+ return;
+ key = tb_entry;
+ } else if (type == MNL_TYPE_U8) {
+ print_type = mnl_attr_get_u8(tb_entry);
+ } else {
+ val = tb_entry;
+ ret = print_driver_entry(rd, key, val, print_type);
+ if (ret < 0)
+ return;
+ cc += ret;
+ print_type = RDMA_NLDEV_PRINT_TYPE_UNSPEC;
+ key = NULL;
+ }
+ }
+ return;
+}
diff --git a/schema/bridge_fdb_schema.json b/schema/bridge_fdb_schema.json
new file mode 100644
index 0000000..3e5be8d
--- /dev/null
+++ b/schema/bridge_fdb_schema.json
@@ -0,0 +1,62 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "bridge fdb show",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "dev": {
+ "type": "string"
+ },
+ "dst": {
+ "description" : "host name or ip address",
+ "type": "string"
+ },
+ "flags": {
+ "type": "array",
+ "items": {
+ "enum": ["self", "master", "router", "offload"]
+ },
+ "uniqueItems": true
+ },
+ "linkNetNsId": {
+ "type": "integer"
+ },
+ "mac": {
+ "type": "string"
+ },
+ "master": {
+ "type": "string"
+ },
+ "opCode": {
+ "description" : "used to indicate fdb entry del",
+ "enum": ["deleted"]
+ },
+ "port": {
+ "type": "integer"
+ },
+ "state": {
+ "description" : "permanent, static, stale, state=#x",
+ "type": "string"
+ },
+ "updated": {
+ "type": "integer"
+ },
+ "used": {
+ "type": "integer"
+ },
+ "viaIf": {
+ "type": "string"
+ },
+ "viaIfIndex": {
+ "type": "integer"
+ },
+ "vlan": {
+ "type": "integer"
+ },
+ "vni": {
+ "type": "integer"
+ }
+ }
+ }
+}
diff --git a/tc/.gitignore b/tc/.gitignore
new file mode 100644
index 0000000..e8e86c9
--- /dev/null
+++ b/tc/.gitignore
@@ -0,0 +1,5 @@
+*.yacc.c
+*.lex.c
+*.output
+*.yacc.h
+tc
diff --git a/tc/Android.mk b/tc/Android.mk
new file mode 100644
index 0000000..5c62394
--- /dev/null
+++ b/tc/Android.mk
@@ -0,0 +1,48 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := tc.c tc_qdisc.c tc_class.c tc_filter.c tc_util.c tc_monitor.c \
+ tc_exec.c m_police.c m_estimator.c m_action.c m_ematch.c \
+ emp_ematch.yacc.c emp_ematch.lex.c
+LOCAL_TCLIB := tc_core.c tc_red.c tc_cbq.c tc_estimator.c tc_stab.c
+LOCAL_TCMODULES := q_fifo.c q_sfq.c q_red.c q_prio.c q_skbprio.c q_tbf.c q_cbq.c \
+ q_rr.c q_multiq.c q_netem.c q_choke.c q_sfb.c f_rsvp.c f_u32.c \
+ f_route.c f_fw.c f_basic.c f_bpf.c f_flow.c f_cgroup.c \
+ f_flower.c q_dsmark.c q_gred.c f_tcindex.c q_ingress.c \
+ q_hfsc.c q_htb.c q_drr.c q_qfq.c m_gact.c m_mirred.c m_mpls.c \
+ m_nat.c m_pedit.c m_ife.c m_skbedit.c m_skbmod.c m_csum.c \
+ m_simple.c m_vlan.c m_connmark.c m_ctinfo.c m_bpf.c \
+ m_tunnel_key.c m_sample.c m_ct.c p_ip.c p_ip6.c p_icmp.c \
+ p_eth.c p_tcp.c p_udp.c em_nbyte.c em_cmp.c em_u32.c \
+ em_canid.c em_meta.c q_mqprio.c q_codel.c q_fq_codel.c q_fq.c \
+ q_pie.c q_cake.c q_hhf.c q_nss.c q_clsact.c e_bpf.c \
+ f_matchall.c q_cbs.c q_etf.c q_taprio.c q_plug.c q_arl.c
+
+# TC_CONFIG_XT and TC_CONFIG_IPSET depends on newer version of iptables
+# LOCAL_TCMODULES += em_ipt.c em_ipset.c
+# LOCAL_SHARED_LIB += m_xt
+
+LOCAL_MODULE := tc
+LOCAL_MODULE_TAGS := eng
+
+LOCAL_STATIC_LIBRARIES += libiprouteutil libnetlink
+LOCAL_SRC_FILES += $(LOCAL_TCLIB) $(LOCAL_TCMODULES)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/uapi
+LOCAL_LDLIBS += -lm -dl
+LOCAL_LDFLAGS += -Wl,-export-dynamic
+LOCAL_CFLAGS += -O2 -g -W -Wall -Wstrict-prototypes -Wmissing-prototypes \
+ -Wmissing-declarations -Wold-style-definition -Wformat=2 \
+ -Wno-uninitialized -DHAVE_SETNS -DNEED_STRLCPY \
+ -DCONFIG_GACT -DCONFIG_GACT_PROB -DYY_NO_INPUT
+
+# LOCAL_CFLAGS := -Wall -Wno-pointer-arith -Wno-sign-compare -Werror \
+# -Wno-missing-field-initializers -Wno-unused-parameter \
+
+%.yacc.c: %.y
+ bison -d -t -v -o$@ $<
+
+%.lex.c: %.l
+ flex -o$@ $<
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/tc/MODULE_LICENSE_GPL b/tc/MODULE_LICENSE_GPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tc/MODULE_LICENSE_GPL
diff --git a/tc/Makefile b/tc/Makefile
new file mode 100644
index 0000000..d6e6794
--- /dev/null
+++ b/tc/Makefile
@@ -0,0 +1,204 @@
+# SPDX-License-Identifier: GPL-2.0
+TCOBJ= tc.o tc_qdisc.o tc_class.o tc_filter.o tc_util.o tc_monitor.o \
+ tc_exec.o m_police.o m_estimator.o m_action.o m_ematch.o \
+ emp_ematch.yacc.o emp_ematch.lex.o
+
+include ../config.mk
+
+SHARED_LIBS ?= y
+
+TCMODULES :=
+TCMODULES += q_fifo.o
+TCMODULES += q_sfq.o
+TCMODULES += q_red.o
+TCMODULES += q_prio.o
+TCMODULES += q_skbprio.o
+TCMODULES += q_tbf.o
+TCMODULES += q_cbq.o
+TCMODULES += q_rr.o
+TCMODULES += q_multiq.o
+TCMODULES += q_netem.o
+TCMODULES += q_choke.o
+TCMODULES += q_sfb.o
+TCMODULES += f_rsvp.o
+TCMODULES += f_u32.o
+TCMODULES += f_route.o
+TCMODULES += f_fw.o
+TCMODULES += f_basic.o
+TCMODULES += f_bpf.o
+TCMODULES += f_flow.o
+TCMODULES += f_cgroup.o
+TCMODULES += f_flower.o
+TCMODULES += q_dsmark.o
+TCMODULES += q_gred.o
+TCMODULES += f_tcindex.o
+TCMODULES += q_ingress.o
+TCMODULES += q_hfsc.o
+TCMODULES += q_htb.o
+TCMODULES += q_drr.o
+TCMODULES += q_qfq.o
+TCMODULES += m_gact.o
+TCMODULES += m_mirred.o
+TCMODULES += m_mpls.o
+TCMODULES += m_nat.o
+TCMODULES += m_pedit.o
+TCMODULES += m_ife.o
+TCMODULES += m_skbedit.o
+TCMODULES += m_skbmod.o
+TCMODULES += m_csum.o
+TCMODULES += m_simple.o
+TCMODULES += m_vlan.o
+TCMODULES += m_connmark.o
+TCMODULES += m_ctinfo.o
+TCMODULES += m_bpf.o
+TCMODULES += m_tunnel_key.o
+TCMODULES += m_sample.o
+TCMODULES += m_ct.o
+TCMODULES += p_ip.o
+TCMODULES += p_ip6.o
+TCMODULES += p_icmp.o
+TCMODULES += p_eth.o
+TCMODULES += p_tcp.o
+TCMODULES += p_udp.o
+TCMODULES += em_nbyte.o
+TCMODULES += em_cmp.o
+TCMODULES += em_u32.o
+TCMODULES += em_canid.o
+TCMODULES += em_meta.o
+TCMODULES += q_mqprio.o
+TCMODULES += q_codel.o
+TCMODULES += q_fq_codel.o
+TCMODULES += q_fq.o
+TCMODULES += q_pie.o
+TCMODULES += q_cake.o
+TCMODULES += q_hhf.o
+TCMODULES += q_nss.o
+TCMODULES += q_clsact.o
+TCMODULES += e_bpf.o
+TCMODULES += f_matchall.o
+TCMODULES += q_cbs.o
+TCMODULES += q_etf.o
+TCMODULES += q_taprio.o
+TCMODULES += q_plug.o
+TCMODULES += q_arl.o
+
+TCSO :=
+ifeq ($(TC_CONFIG_ATM),y)
+ TCSO += q_atm.so
+endif
+
+ifneq ($(TC_CONFIG_NO_XT),y)
+ ifeq ($(TC_CONFIG_XT),y)
+ TCSO += m_xt.so
+ TCMODULES += em_ipt.o
+ ifeq ($(TC_CONFIG_IPSET),y)
+ TCMODULES += em_ipset.o
+ endif
+ else
+ ifeq ($(TC_CONFIG_XT_OLD),y)
+ TCSO += m_xt_old.so
+ else
+ ifeq ($(TC_CONFIG_XT_OLD_H),y)
+ CFLAGS += -DTC_CONFIG_XT_H
+ TCSO += m_xt_old.so
+ else
+ TCMODULES += m_ipt.o
+ endif
+ endif
+ endif
+endif
+
+TCOBJ += $(TCMODULES)
+LDLIBS += -L. -lm
+
+ifeq ($(SHARED_LIBS),y)
+LDLIBS += -ldl
+LDFLAGS += -Wl,-export-dynamic
+endif
+
+TCLIB := tc_core.o
+TCLIB += tc_red.o
+TCLIB += tc_cbq.o
+TCLIB += tc_estimator.o
+TCLIB += tc_stab.o
+
+CFLAGS += -DCONFIG_GACT -DCONFIG_GACT_PROB
+ifneq ($(IPT_LIB_DIR),)
+ CFLAGS += -DIPT_LIB_DIR=\"$(IPT_LIB_DIR)\"
+endif
+
+YACC := bison
+LEX := flex
+CFLAGS += -DYY_NO_INPUT
+
+MODDESTDIR := $(DESTDIR)$(LIBDIR)/tc
+
+%.so: %.c
+ $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -shared -fpic $< -o $@
+
+
+all: tc $(TCSO)
+
+tc: $(TCOBJ) $(LIBNETLINK) libtc.a
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+libtc.a: $(TCLIB)
+ $(QUIET_AR)$(AR) rcs $@ $^
+
+install: all
+ mkdir -p $(MODDESTDIR)
+ install -m 0755 tc $(DESTDIR)$(SBINDIR)
+ for i in $(TCSO); \
+ do install -m 755 $$i $(MODDESTDIR); \
+ done
+ if [ ! -f $(MODDESTDIR)/m_ipt.so ]; then \
+ if [ -f $(MODDESTDIR)/m_xt.so ]; \
+ then ln -s m_xt.so $(MODDESTDIR)/m_ipt.so ; \
+ elif [ -f $(MODDESTDIR)/m_xt_old.so ]; \
+ then ln -s m_xt_old.so $(MODDESTDIR)/m_ipt.so ; \
+ fi; \
+ fi
+
+clean:
+ rm -f $(TCOBJ) $(TCLIB) libtc.a tc *.so emp_ematch.yacc.h; \
+ rm -f emp_ematch.yacc.*
+
+q_atm.so: q_atm.c
+ $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -shared -fpic -o q_atm.so q_atm.c -latm
+
+m_xt.so: m_xt.c
+ $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -shared -fpic -o m_xt.so m_xt.c $$($(PKG_CONFIG) xtables --cflags --libs)
+
+m_xt_old.so: m_xt_old.c
+ $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -shared -fpic -o m_xt_old.so m_xt_old.c $$($(PKG_CONFIG) xtables --cflags --libs)
+
+em_ipset.o: CFLAGS += $$($(PKG_CONFIG) xtables --cflags)
+
+em_ipt.o: CFLAGS += $$($(PKG_CONFIG) xtables --cflags)
+
+ifeq ($(TC_CONFIG_XT),y)
+ LDLIBS += $$($(PKG_CONFIG) xtables --libs)
+endif
+
+%.yacc.c: %.y
+ $(QUIET_YACC)$(YACC) $(YACCFLAGS) -o $@ $<
+
+%.lex.c: %.l
+ $(QUIET_LEX)$(LEX) $(LEXFLAGS) -o$@ $<
+
+# our lexer includes the header from yacc, so make sure
+# we don't attempt to compile it before the header has
+# been generated as part of the yacc step.
+emp_ematch.lex.o: emp_ematch.yacc.c
+
+ifneq ($(SHARED_LIBS),y)
+
+tc: 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/tc/NOTICE b/tc/NOTICE
new file mode 100644
index 0000000..3912109
--- /dev/null
+++ b/tc/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/tc/e_bpf.c b/tc/e_bpf.c
new file mode 100644
index 0000000..a48393b
--- /dev/null
+++ b/tc/e_bpf.c
@@ -0,0 +1,178 @@
+/*
+ * e_bpf.c BPF exec proxy
+ *
+ * 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>
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "utils.h"
+
+#include "tc_util.h"
+
+#include "bpf_util.h"
+#include "bpf_elf.h"
+#include "bpf_scm.h"
+
+#define BPF_DEFAULT_CMD "/bin/sh"
+
+static char *argv_default[] = { BPF_DEFAULT_CMD, NULL };
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... bpf [ import UDS_FILE ] [ run CMD ]\n"
+ " ... bpf [ debug ]\n"
+ " ... bpf [ graft MAP_FILE ] [ key KEY ]\n"
+ " `... [ object-file OBJ_FILE ] [ type TYPE ] [ section NAME ] [ verbose ]\n"
+ " `... [ object-pinned PROG_FILE ]\n"
+ "\n"
+ "Where UDS_FILE provides the name of a unix domain socket file\n"
+ "to import eBPF maps and the optional CMD denotes the command\n"
+ "to be executed (default: \'%s\').\n"
+ "Where MAP_FILE points to a pinned map, OBJ_FILE to an object file\n"
+ "and PROG_FILE to a pinned program. TYPE can be {cls, act}, where\n"
+ "\'cls\' is default. KEY is optional and can be inferred from the\n"
+ "section name, otherwise it needs to be provided.\n",
+ BPF_DEFAULT_CMD);
+}
+
+static int bpf_num_env_entries(void)
+{
+ char **envp;
+ int num;
+
+ for (num = 0, envp = environ; *envp != NULL; envp++)
+ num++;
+ return num;
+}
+
+static int parse_bpf(struct exec_util *eu, int argc, char **argv)
+{
+ char **argv_run = argv_default, **envp_run, *tmp;
+ int ret, i, env_old, env_num, env_map;
+ const char *bpf_uds_name = NULL;
+ int fds[BPF_SCM_MAX_FDS] = {};
+ struct bpf_map_aux aux = {};
+
+ if (argc == 0)
+ return 0;
+
+ while (argc > 0) {
+ if (matches(*argv, "run") == 0) {
+ NEXT_ARG();
+ argv_run = argv;
+ break;
+ } else if (matches(*argv, "import") == 0) {
+ NEXT_ARG();
+ bpf_uds_name = *argv;
+ } else if (matches(*argv, "debug") == 0 ||
+ matches(*argv, "dbg") == 0) {
+ if (bpf_trace_pipe())
+ fprintf(stderr,
+ "No trace pipe, tracefs not mounted?\n");
+ return -1;
+ } else if (matches(*argv, "graft") == 0) {
+ const char *bpf_map_path;
+ bool has_key = false;
+ uint32_t key;
+
+ NEXT_ARG();
+ bpf_map_path = *argv;
+ NEXT_ARG();
+ if (matches(*argv, "key") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&key, *argv, 0)) {
+ fprintf(stderr, "Illegal \"key\"\n");
+ return -1;
+ }
+ has_key = true;
+ NEXT_ARG();
+ }
+ return bpf_graft_map(bpf_map_path, has_key ?
+ &key : NULL, argc, argv);
+ } else {
+ explain();
+ return -1;
+ }
+
+ NEXT_ARG_FWD();
+ }
+
+ if (!bpf_uds_name) {
+ fprintf(stderr, "bpf: No import parameter provided!\n");
+ explain();
+ return -1;
+ }
+
+ if (argv_run != argv_default && argc == 0) {
+ fprintf(stderr, "bpf: No run command provided!\n");
+ explain();
+ return -1;
+ }
+
+ ret = bpf_recv_map_fds(bpf_uds_name, fds, &aux, ARRAY_SIZE(fds));
+ if (ret < 0) {
+ fprintf(stderr, "bpf: Could not receive fds!\n");
+ return -1;
+ }
+
+ if (aux.num_ent == 0) {
+ envp_run = environ;
+ goto out;
+ }
+
+ env_old = bpf_num_env_entries();
+ env_num = env_old + aux.num_ent + 2;
+ env_map = env_old + 1;
+
+ envp_run = malloc(sizeof(*envp_run) * env_num);
+ if (!envp_run) {
+ fprintf(stderr, "bpf: No memory left to allocate env!\n");
+ goto err;
+ }
+
+ for (i = 0; i < env_old; i++)
+ envp_run[i] = environ[i];
+
+ ret = asprintf(&tmp, "BPF_NUM_MAPS=%u", aux.num_ent);
+ if (ret < 0)
+ goto err_free;
+
+ envp_run[env_old] = tmp;
+
+ for (i = env_map; i < env_num - 1; i++) {
+ ret = asprintf(&tmp, "BPF_MAP%u=%u",
+ aux.ent[i - env_map].id,
+ fds[i - env_map]);
+ if (ret < 0)
+ goto err_free_env;
+
+ envp_run[i] = tmp;
+ }
+
+ envp_run[env_num - 1] = NULL;
+out:
+ return execvpe(argv_run[0], argv_run, envp_run);
+
+err_free_env:
+ for (--i; i >= env_old; i--)
+ free(envp_run[i]);
+err_free:
+ free(envp_run);
+err:
+ for (i = 0; i < aux.num_ent; i++)
+ close(fds[i]);
+ return -1;
+}
+
+struct exec_util bpf_exec_util = {
+ .id = "bpf",
+ .parse_eopt = parse_bpf,
+};
diff --git a/tc/em_canid.c b/tc/em_canid.c
new file mode 100644
index 0000000..197c707
--- /dev/null
+++ b/tc/em_canid.c
@@ -0,0 +1,190 @@
+/*
+ * em_canid.c Ematch rule to match CAN frames according to their CAN identifiers
+ *
+ * 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.
+ *
+ * Idea: Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
+ * Copyright: (c) 2011 Czech Technical University in Prague
+ * (c) 2011 Volkswagen Group Research
+ * Authors: Michal Sojka <sojkam1@fel.cvut.cz>
+ * Pavel Pisa <pisa@cmp.felk.cvut.cz>
+ * Rostislav Lisovy <lisovy@gmail.cz>
+ * Funded by: Volkswagen Group Research
+ *
+ * Documentation: http://rtime.felk.cvut.cz/can/socketcan-qdisc-final.pdf
+ */
+
+#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 <errno.h>
+#include <linux/can.h>
+#include <inttypes.h>
+#include "m_ematch.h"
+
+#define EM_CANID_RULES_MAX 400 /* Main reason for this number is Nelink
+ message size limit equal to Single memory page size. When dump()
+ is invoked, there are even some ematch related headers sent from
+ kernel to userspace together with em_canid configuration --
+ 400*sizeof(struct can_filter) should fit without any problems */
+
+extern struct ematch_util canid_ematch_util;
+struct rules {
+ struct can_filter *rules_raw;
+ int rules_capacity; /* Size of array allocated for rules_raw */
+ int rules_cnt; /* Actual number of rules stored in rules_raw */
+};
+
+static void canid_print_usage(FILE *fd)
+{
+ fprintf(fd,
+ "Usage: canid(IDLIST)\n" \
+ "where: IDLIST := IDSPEC [ IDLIST ]\n" \
+ " IDSPEC := { ’sff’ CANID | ’eff’ CANID }\n" \
+ " CANID := ID[:MASK]\n" \
+ " ID, MASK := hexadecimal number (i.e. 0x123)\n" \
+ "Example: canid(sff 0x123 sff 0x124 sff 0x125:0xf)\n");
+}
+
+static int canid_parse_rule(struct rules *rules, struct bstr *a, int iseff)
+{
+ unsigned int can_id = 0;
+ unsigned int can_mask = 0;
+
+ if (sscanf(a->data, "%"SCNx32 ":" "%"SCNx32, &can_id, &can_mask) != 2) {
+ if (sscanf(a->data, "%"SCNx32, &can_id) != 1) {
+ return -1;
+ } else {
+ can_mask = (iseff) ? CAN_EFF_MASK : CAN_SFF_MASK;
+ }
+ }
+
+ /* Stretch rules array up to EM_CANID_RULES_MAX if necessary */
+ if (rules->rules_cnt == rules->rules_capacity) {
+ if (rules->rules_capacity <= EM_CANID_RULES_MAX/2) {
+ rules->rules_capacity *= 2;
+ rules->rules_raw = realloc(rules->rules_raw,
+ sizeof(struct can_filter) * rules->rules_capacity);
+ } else {
+ return -2;
+ }
+ }
+
+ rules->rules_raw[rules->rules_cnt].can_id =
+ can_id | ((iseff) ? CAN_EFF_FLAG : 0);
+ rules->rules_raw[rules->rules_cnt].can_mask =
+ can_mask | CAN_EFF_FLAG;
+
+ rules->rules_cnt++;
+
+ return 0;
+}
+
+static int canid_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+ struct bstr *args)
+{
+ int iseff = 0;
+ int ret = 0;
+ struct rules rules = {
+ .rules_capacity = 25, /* Denominator of EM_CANID_RULES_MAX
+ Will be multiplied by 2 to calculate the size for realloc() */
+ .rules_cnt = 0
+ };
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+ em_parse_error(EINVAL, args, CARG, &canid_ematch_util, FMT, ##ARGS)
+
+ if (args == NULL)
+ return PARSE_ERR(args, "canid: missing arguments");
+
+ rules.rules_raw = calloc(rules.rules_capacity,
+ sizeof(struct can_filter));
+
+ do {
+ if (!bstrcmp(args, "sff")) {
+ iseff = 0;
+ } else if (!bstrcmp(args, "eff")) {
+ iseff = 1;
+ } else {
+ ret = PARSE_ERR(args, "canid: invalid key");
+ goto exit;
+ }
+
+ args = bstr_next(args);
+ if (args == NULL) {
+ ret = PARSE_ERR(args, "canid: missing argument");
+ goto exit;
+ }
+
+ ret = canid_parse_rule(&rules, args, iseff);
+ if (ret == -1) {
+ ret = PARSE_ERR(args, "canid: Improperly formed CAN ID & mask\n");
+ goto exit;
+ } else if (ret == -2) {
+ ret = PARSE_ERR(args, "canid: Too many arguments on input\n");
+ goto exit;
+ }
+ } while ((args = bstr_next(args)) != NULL);
+
+ addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+ addraw_l(n, MAX_MSG, rules.rules_raw,
+ sizeof(struct can_filter) * rules.rules_cnt);
+
+#undef PARSE_ERR
+exit:
+ free(rules.rules_raw);
+ return ret;
+}
+
+static int canid_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+ int data_len)
+{
+ struct can_filter *conf = data; /* Array with rules */
+ int rules_count;
+ int i;
+
+ rules_count = data_len / sizeof(struct can_filter);
+
+ for (i = 0; i < rules_count; i++) {
+ struct can_filter *pcfltr = &conf[i];
+
+ if (pcfltr->can_id & CAN_EFF_FLAG) {
+ if (pcfltr->can_mask == (CAN_EFF_FLAG | CAN_EFF_MASK))
+ fprintf(fd, "eff 0x%"PRIX32,
+ pcfltr->can_id & CAN_EFF_MASK);
+ else
+ fprintf(fd, "eff 0x%"PRIX32":0x%"PRIX32,
+ pcfltr->can_id & CAN_EFF_MASK,
+ pcfltr->can_mask & CAN_EFF_MASK);
+ } else {
+ if (pcfltr->can_mask == (CAN_EFF_FLAG | CAN_SFF_MASK))
+ fprintf(fd, "sff 0x%"PRIX32,
+ pcfltr->can_id & CAN_SFF_MASK);
+ else
+ fprintf(fd, "sff 0x%"PRIX32":0x%"PRIX32,
+ pcfltr->can_id & CAN_SFF_MASK,
+ pcfltr->can_mask & CAN_SFF_MASK);
+ }
+
+ if ((i + 1) < rules_count)
+ fprintf(fd, " ");
+ }
+
+ return 0;
+}
+
+struct ematch_util canid_ematch_util = {
+ .kind = "canid",
+ .kind_num = TCF_EM_CANID,
+ .parse_eopt = canid_parse_eopt,
+ .print_eopt = canid_print_eopt,
+ .print_usage = canid_print_usage
+};
diff --git a/tc/em_cmp.c b/tc/em_cmp.c
new file mode 100644
index 0000000..e051656
--- /dev/null
+++ b/tc/em_cmp.c
@@ -0,0 +1,184 @@
+/*
+ * em_cmp.c Simple comparison Ematch
+ *
+ * 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: Thomas Graf <tgraf@suug.ch>
+ */
+
+#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 <errno.h>
+
+#include "m_ematch.h"
+#include <linux/tc_ematch/tc_em_cmp.h>
+
+extern struct ematch_util cmp_ematch_util;
+
+static void cmp_print_usage(FILE *fd)
+{
+ fprintf(fd,
+ "Usage: cmp(ALIGN at OFFSET [ ATTRS ] { eq | lt | gt } VALUE)\n" \
+ "where: ALIGN := { u8 | u16 | u32 }\n" \
+ " ATTRS := [ layer LAYER ] [ mask MASK ] [ trans ]\n" \
+ " LAYER := { link | network | transport | 0..%d }\n" \
+ "\n" \
+ "Example: cmp(u16 at 3 layer 2 mask 0xff00 gt 20)\n",
+ TCF_LAYER_MAX);
+}
+
+static int cmp_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+ struct bstr *args)
+{
+ struct bstr *a;
+ int align, opnd = 0;
+ unsigned long offset = 0, layer = TCF_LAYER_NETWORK, mask = 0, value = 0;
+ int offset_present = 0, value_present = 0;
+ struct tcf_em_cmp cmp = {};
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+ em_parse_error(EINVAL, args, CARG, &cmp_ematch_util, FMT, ##ARGS)
+
+ if (args == NULL)
+ return PARSE_ERR(args, "cmp: missing arguments");
+
+ if (!bstrcmp(args, "u8"))
+ align = TCF_EM_ALIGN_U8;
+ else if (!bstrcmp(args, "u16"))
+ align = TCF_EM_ALIGN_U16;
+ else if (!bstrcmp(args, "u32"))
+ align = TCF_EM_ALIGN_U32;
+ else
+ return PARSE_ERR(args, "cmp: invalid alignment");
+
+ for (a = bstr_next(args); a; a = bstr_next(a)) {
+ if (!bstrcmp(a, "at")) {
+ if (a->next == NULL)
+ return PARSE_ERR(a, "cmp: missing argument");
+ a = bstr_next(a);
+
+ offset = bstrtoul(a);
+ if (offset == ULONG_MAX)
+ return PARSE_ERR(a, "cmp: invalid offset, " \
+ "must be numeric");
+
+ offset_present = 1;
+ } else if (!bstrcmp(a, "layer")) {
+ if (a->next == NULL)
+ return PARSE_ERR(a, "cmp: missing argument");
+ a = bstr_next(a);
+
+ layer = parse_layer(a);
+ if (layer == INT_MAX) {
+ layer = bstrtoul(a);
+ if (layer == ULONG_MAX)
+ return PARSE_ERR(a, "cmp: invalid " \
+ "layer");
+ }
+
+ if (layer > TCF_LAYER_MAX)
+ return PARSE_ERR(a, "cmp: illegal layer, " \
+ "must be in 0..%d", TCF_LAYER_MAX);
+ } else if (!bstrcmp(a, "mask")) {
+ if (a->next == NULL)
+ return PARSE_ERR(a, "cmp: missing argument");
+ a = bstr_next(a);
+
+ mask = bstrtoul(a);
+ if (mask == ULONG_MAX)
+ return PARSE_ERR(a, "cmp: invalid mask");
+ } else if (!bstrcmp(a, "trans")) {
+ cmp.flags |= TCF_EM_CMP_TRANS;
+ } else if (!bstrcmp(a, "eq") || !bstrcmp(a, "gt") ||
+ !bstrcmp(a, "lt")) {
+
+ if (!bstrcmp(a, "eq"))
+ opnd = TCF_EM_OPND_EQ;
+ else if (!bstrcmp(a, "gt"))
+ opnd = TCF_EM_OPND_GT;
+ else if (!bstrcmp(a, "lt"))
+ opnd = TCF_EM_OPND_LT;
+
+ if (a->next == NULL)
+ return PARSE_ERR(a, "cmp: missing argument");
+ a = bstr_next(a);
+
+ value = bstrtoul(a);
+ if (value == ULONG_MAX)
+ return PARSE_ERR(a, "cmp: invalid value");
+
+ value_present = 1;
+ } else
+ return PARSE_ERR(a, "nbyte: unknown parameter");
+ }
+
+ if (offset_present == 0 || value_present == 0)
+ return PARSE_ERR(a, "cmp: offset and value required");
+
+ cmp.val = (__u32) value;
+ cmp.mask = (__u32) mask;
+ cmp.off = (__u16) offset;
+ cmp.align = (__u8) align;
+ cmp.layer = (__u8) layer;
+ cmp.opnd = (__u8) opnd;
+
+ addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+ addraw_l(n, MAX_MSG, &cmp, sizeof(cmp));
+
+#undef PARSE_ERR
+ return 0;
+}
+
+static int cmp_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+ int data_len)
+{
+ struct tcf_em_cmp *cmp = data;
+
+ if (data_len < sizeof(*cmp)) {
+ fprintf(stderr, "CMP header size mismatch\n");
+ return -1;
+ }
+
+ if (cmp->align == TCF_EM_ALIGN_U8)
+ fprintf(fd, "u8 ");
+ else if (cmp->align == TCF_EM_ALIGN_U16)
+ fprintf(fd, "u16 ");
+ else if (cmp->align == TCF_EM_ALIGN_U32)
+ fprintf(fd, "u32 ");
+
+ fprintf(fd, "at %d layer %d ", cmp->off, cmp->layer);
+
+ if (cmp->mask)
+ fprintf(fd, "mask 0x%x ", cmp->mask);
+
+ if (cmp->flags & TCF_EM_CMP_TRANS)
+ fprintf(fd, "trans ");
+
+ if (cmp->opnd == TCF_EM_OPND_EQ)
+ fprintf(fd, "eq ");
+ else if (cmp->opnd == TCF_EM_OPND_LT)
+ fprintf(fd, "lt ");
+ else if (cmp->opnd == TCF_EM_OPND_GT)
+ fprintf(fd, "gt ");
+
+ fprintf(fd, "%d", cmp->val);
+
+ return 0;
+}
+
+struct ematch_util cmp_ematch_util = {
+ .kind = "cmp",
+ .kind_num = TCF_EM_CMP,
+ .parse_eopt = cmp_parse_eopt,
+ .print_eopt = cmp_print_eopt,
+ .print_usage = cmp_print_usage
+};
diff --git a/tc/em_ipset.c b/tc/em_ipset.c
new file mode 100644
index 0000000..48b287f
--- /dev/null
+++ b/tc/em_ipset.c
@@ -0,0 +1,263 @@
+/*
+ * em_ipset.c IPset Ematch
+ *
+ * (C) 2012 Florian Westphal <fw@strlen.de>
+ *
+ * Parts taken from iptables libxt_set.h:
+ * Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ * Patrick Schaaf <bof@bof.de>
+ * Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 2003-2010 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <errno.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+#include <xtables.h>
+#include <linux/netfilter/ipset/ip_set.h>
+
+#ifndef IPSET_INVALID_ID
+typedef __u16 ip_set_id_t;
+
+enum ip_set_dim {
+ IPSET_DIM_ZERO = 0,
+ IPSET_DIM_ONE,
+ IPSET_DIM_TWO,
+ IPSET_DIM_THREE,
+ IPSET_DIM_MAX = 6,
+};
+#endif /* IPSET_INVALID_ID */
+
+#include <linux/netfilter/xt_set.h>
+#include "m_ematch.h"
+
+#ifndef IPSET_INVALID_ID
+#define IPSET_INVALID_ID 65535
+#define SO_IP_SET 83
+
+union ip_set_name_index {
+ char name[IPSET_MAXNAMELEN];
+ __u16 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_VERSION 0x00000100 /* Ask kernel version */
+struct ip_set_req_version {
+ unsigned int op;
+ unsigned int version;
+};
+#endif /* IPSET_INVALID_ID */
+
+extern struct ematch_util ipset_ematch_util;
+
+static int get_version(unsigned int *version)
+{
+ int res, sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ struct ip_set_req_version req_version;
+ socklen_t size = sizeof(req_version);
+
+ if (sockfd < 0) {
+ fputs("Can't open socket to ipset.\n", stderr);
+ return -1;
+ }
+
+ req_version.op = IP_SET_OP_VERSION;
+ res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req_version, &size);
+ if (res != 0) {
+ perror("xt_set getsockopt");
+ close(sockfd);
+ return -1;
+ }
+
+ *version = req_version.version;
+ return sockfd;
+}
+
+static int do_getsockopt(struct ip_set_req_get_set *req)
+{
+ int sockfd, res;
+ socklen_t size = sizeof(struct ip_set_req_get_set);
+
+ sockfd = get_version(&req->version);
+ if (sockfd < 0)
+ return -1;
+ res = getsockopt(sockfd, SOL_IP, SO_IP_SET, req, &size);
+ if (res != 0)
+ perror("Problem when communicating with ipset");
+ close(sockfd);
+ if (res != 0)
+ return -1;
+
+ if (size != sizeof(struct ip_set_req_get_set)) {
+ fprintf(stderr,
+ "Incorrect return size from kernel during ipset lookup, (want %zu, got %zu)\n",
+ sizeof(struct ip_set_req_get_set), (size_t)size);
+ return -1;
+ }
+
+ return res;
+}
+
+static int
+get_set_byid(char *setname, unsigned int idx)
+{
+ struct ip_set_req_get_set req;
+ int res;
+
+ req.op = IP_SET_OP_GET_BYINDEX;
+ req.set.index = idx;
+ res = do_getsockopt(&req);
+ if (res != 0)
+ return -1;
+ if (req.set.name[0] == '\0') {
+ fprintf(stderr,
+ "Set with index %i in kernel doesn't exist.\n", idx);
+ return -1;
+ }
+
+ strncpy(setname, req.set.name, IPSET_MAXNAMELEN);
+ return 0;
+}
+
+static int
+get_set_byname(const char *setname, struct xt_set_info *info)
+{
+ struct ip_set_req_get_set req;
+ int res;
+
+ req.op = IP_SET_OP_GET_BYNAME;
+ strlcpy(req.set.name, setname, IPSET_MAXNAMELEN);
+ res = do_getsockopt(&req);
+ if (res != 0)
+ return -1;
+ if (req.set.index == IPSET_INVALID_ID)
+ return -1;
+ info->index = req.set.index;
+ return 0;
+}
+
+static int
+parse_dirs(const char *opt_arg, struct xt_set_info *info)
+{
+ char *saved = strdup(opt_arg);
+ char *ptr, *tmp = saved;
+
+ if (!tmp) {
+ perror("strdup");
+ return -1;
+ }
+
+ while (info->dim < IPSET_DIM_MAX && tmp != NULL) {
+ info->dim++;
+ ptr = strsep(&tmp, ",");
+ if (strncmp(ptr, "src", 3) == 0)
+ info->flags |= (1 << info->dim);
+ else if (strncmp(ptr, "dst", 3) != 0) {
+ fputs("You must specify (the comma separated list of) 'src' or 'dst'\n", stderr);
+ free(saved);
+ return -1;
+ }
+ }
+
+ if (tmp)
+ fprintf(stderr, "Can't be more src/dst options than %u", IPSET_DIM_MAX);
+ free(saved);
+ return tmp ? -1 : 0;
+}
+
+static void ipset_print_usage(FILE *fd)
+{
+ fprintf(fd,
+ "Usage: ipset(SETNAME FLAGS)\n" \
+ "where: SETNAME:= string\n" \
+ " FLAGS := { FLAG[,FLAGS] }\n" \
+ " FLAG := { src | dst }\n" \
+ "\n" \
+ "Example: 'ipset(bulk src,dst)'\n");
+}
+
+static int ipset_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+ struct bstr *args)
+{
+ struct xt_set_info set_info = {};
+ int ret;
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+ em_parse_error(EINVAL, args, CARG, &ipset_ematch_util, FMT, ##ARGS)
+
+ if (args == NULL)
+ return PARSE_ERR(args, "ipset: missing set name");
+
+ if (args->len >= IPSET_MAXNAMELEN)
+ return PARSE_ERR(args, "ipset: set name too long (max %u)", IPSET_MAXNAMELEN - 1);
+ ret = get_set_byname(args->data, &set_info);
+ if (ret < 0)
+ return PARSE_ERR(args, "ipset: unknown set name '%s'", args->data);
+
+ if (args->next == NULL)
+ return PARSE_ERR(args, "ipset: missing set flags");
+
+ args = bstr_next(args);
+ if (parse_dirs(args->data, &set_info))
+ return PARSE_ERR(args, "ipset: error parsing set flags");
+
+ if (args->next) {
+ args = bstr_next(args);
+ return PARSE_ERR(args, "ipset: unknown parameter");
+ }
+
+ addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+ addraw_l(n, MAX_MSG, &set_info, sizeof(set_info));
+
+#undef PARSE_ERR
+ return 0;
+}
+
+static int ipset_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+ int data_len)
+{
+ int i;
+ char setname[IPSET_MAXNAMELEN];
+ const struct xt_set_info *set_info = data;
+
+ if (data_len != sizeof(*set_info)) {
+ fprintf(stderr, "xt_set_info struct size mismatch\n");
+ return -1;
+ }
+
+ if (get_set_byid(setname, set_info->index))
+ return -1;
+ fputs(setname, fd);
+ for (i = 1; i <= set_info->dim; i++) {
+ fprintf(fd, "%s%s", i == 1 ? " " : ",", set_info->flags & (1 << i) ? "src" : "dst");
+ }
+
+ return 0;
+}
+
+struct ematch_util ipset_ematch_util = {
+ .kind = "ipset",
+ .kind_num = TCF_EM_IPSET,
+ .parse_eopt = ipset_parse_eopt,
+ .print_eopt = ipset_print_eopt,
+ .print_usage = ipset_print_usage
+};
diff --git a/tc/em_ipt.c b/tc/em_ipt.c
new file mode 100644
index 0000000..b15c3ba
--- /dev/null
+++ b/tc/em_ipt.c
@@ -0,0 +1,210 @@
+/*
+ * em_ipt.c IPtables extensions matching Ematch
+ *
+ * (C) 2018 Eyal Birger <eyal.birger@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <getopt.h>
+
+#include <linux/tc_ematch/tc_em_ipt.h>
+#include <linux/pkt_cls.h>
+#include <xtables.h>
+#include "m_ematch.h"
+
+static void em_ipt_print_usage(FILE *fd)
+{
+ fprintf(fd,
+ "Usage: ipt([-6] -m MATCH_NAME [MATCH_OPTS])\n"
+ "Example: 'ipt(-m policy --reqid 1 --pol ipsec --dir in)'\n");
+}
+
+static struct option original_opts[] = {
+ {
+ .name = "match",
+ .has_arg = 1,
+ .val = 'm'
+ },
+ {
+ .name = "ipv6",
+ .val = '6'
+ },
+ {}
+};
+
+static struct xtables_globals em_tc_ipt_globals = {
+ .option_offset = 0,
+ .program_name = "tc-em-ipt",
+ .program_version = "0.1",
+ .orig_opts = original_opts,
+ .opts = original_opts,
+#if (XTABLES_VERSION_CODE >= 11)
+ .compat_rev = xtables_compatible_revision,
+#endif
+};
+
+static struct xt_entry_match *fake_xt_entry_match(int data_size, void *data)
+{
+ struct xt_entry_match *m;
+
+ m = xtables_calloc(1, XT_ALIGN(sizeof(*m)) + data_size);
+ if (!m)
+ return NULL;
+
+ if (data)
+ memcpy(m->data, data, data_size);
+
+ m->u.user.match_size = data_size;
+ return m;
+}
+
+static void scrub_match(struct xtables_match *match)
+{
+ match->mflags = 0;
+ free(match->m);
+ match->m = NULL;
+}
+
+/* IPv4 and IPv6 share the same hooking enumeration */
+#define HOOK_PRE_ROUTING 0
+#define HOOK_POST_ROUTING 4
+
+static __u32 em_ipt_hook(struct nlmsghdr *n)
+{
+ struct tcmsg *t = NLMSG_DATA(n);
+
+ if (t->tcm_parent != TC_H_ROOT &&
+ t->tcm_parent == TC_H_MAJ(TC_H_INGRESS))
+ return HOOK_PRE_ROUTING;
+
+ return HOOK_POST_ROUTING;
+}
+
+static int em_ipt_parse_eopt_argv(struct nlmsghdr *n,
+ struct tcf_ematch_hdr *hdr,
+ int argc, char **argv)
+{
+ struct xtables_globals tmp_tcipt_globals = em_tc_ipt_globals;
+ struct xtables_match *match = NULL;
+ __u8 nfproto = NFPROTO_IPV4;
+
+ while (1) {
+ struct option *opts;
+ int c;
+
+ c = getopt_long(argc, argv, "6m:", tmp_tcipt_globals.opts,
+ NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'm':
+ xtables_init_all(&tmp_tcipt_globals, nfproto);
+
+ match = xtables_find_match(optarg, XTF_TRY_LOAD, NULL);
+ if (!match || !match->x6_parse) {
+ fprintf(stderr, " failed to find match %s\n\n",
+ optarg);
+ return -1;
+ }
+
+ match->m = fake_xt_entry_match(match->size, NULL);
+ if (!match->m) {
+ printf(" %s error\n", match->name);
+ return -1;
+ }
+
+ if (match->init)
+ match->init(match->m);
+
+ opts = xtables_options_xfrm(tmp_tcipt_globals.orig_opts,
+ tmp_tcipt_globals.opts,
+ match->x6_options,
+ &match->option_offset);
+ if (!opts) {
+ scrub_match(match);
+ return -1;
+ }
+
+ tmp_tcipt_globals.opts = opts;
+ break;
+
+ case '6':
+ nfproto = NFPROTO_IPV6;
+ break;
+
+ default:
+ if (!match) {
+ fprintf(stderr, "failed to find match %s\n\n",
+ optarg);
+ return -1;
+
+ }
+ xtables_option_mpcall(c, argv, 0, match, NULL);
+ break;
+ }
+ }
+
+ if (!match) {
+ fprintf(stderr, " failed to parse parameters (%s)\n", *argv);
+ return -1;
+ }
+
+ /* check that we passed the correct parameters to the match */
+ xtables_option_mfcall(match);
+
+ addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+ addattr32(n, MAX_MSG, TCA_EM_IPT_HOOK, em_ipt_hook(n));
+ addattrstrz(n, MAX_MSG, TCA_EM_IPT_MATCH_NAME, match->name);
+ addattr8(n, MAX_MSG, TCA_EM_IPT_MATCH_REVISION, match->revision);
+ addattr8(n, MAX_MSG, TCA_EM_IPT_NFPROTO, nfproto);
+ addattr_l(n, MAX_MSG, TCA_EM_IPT_MATCH_DATA, match->m->data,
+ match->size);
+
+ xtables_free_opts(1);
+
+ scrub_match(match);
+ return 0;
+}
+
+static int em_ipt_print_epot(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+ int data_len)
+{
+ struct rtattr *tb[TCA_EM_IPT_MAX + 1];
+ struct xtables_match *match;
+ const char *mname;
+ __u8 nfproto;
+
+ if (parse_rtattr(tb, TCA_EM_IPT_MAX, data, data_len) < 0)
+ return -1;
+
+ nfproto = rta_getattr_u8(tb[TCA_EM_IPT_NFPROTO]);
+
+ xtables_init_all(&em_tc_ipt_globals, nfproto);
+
+ mname = rta_getattr_str(tb[TCA_EM_IPT_MATCH_NAME]);
+ match = xtables_find_match(mname, XTF_TRY_LOAD, NULL);
+ if (!match)
+ return -1;
+
+ match->m = fake_xt_entry_match(RTA_PAYLOAD(tb[TCA_EM_IPT_MATCH_DATA]),
+ RTA_DATA(tb[TCA_EM_IPT_MATCH_DATA]));
+ if (!match->m)
+ return -1;
+
+ match->print(NULL, match->m, 0);
+
+ scrub_match(match);
+ return 0;
+}
+
+struct ematch_util ipt_ematch_util = {
+ .kind = "ipt",
+ .kind_num = TCF_EM_IPT,
+ .parse_eopt_argv = em_ipt_parse_eopt_argv,
+ .print_eopt = em_ipt_print_epot,
+ .print_usage = em_ipt_print_usage
+};
diff --git a/tc/em_meta.c b/tc/em_meta.c
new file mode 100644
index 0000000..2ddc65e
--- /dev/null
+++ b/tc/em_meta.c
@@ -0,0 +1,545 @@
+/*
+ * em_meta.c Metadata Ematch
+ *
+ * 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: Thomas Graf <tgraf@suug.ch>
+ */
+
+#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 <errno.h>
+
+#include "m_ematch.h"
+#include <linux/tc_ematch/tc_em_meta.h>
+
+extern struct ematch_util meta_ematch_util;
+
+static void meta_print_usage(FILE *fd)
+{
+ fprintf(fd,
+ "Usage: meta(OBJECT { eq | lt | gt } OBJECT)\n" \
+ "where: OBJECT := { META_ID | VALUE }\n" \
+ " META_ID := id [ shift SHIFT ] [ mask MASK ]\n" \
+ "\n" \
+ "Example: meta(nf_mark gt 24)\n" \
+ " meta(indev shift 1 eq \"ppp\")\n" \
+ " meta(tcindex mask 0xf0 eq 0xf0)\n" \
+ "\n" \
+ "For a list of meta identifiers, use meta(list).\n");
+}
+
+static const struct meta_entry {
+ int id;
+ char *kind;
+ char *mask;
+ char *desc;
+} meta_table[] = {
+#define TCF_META_ID_SECTION 0
+#define __A(id, name, mask, desc) { TCF_META_ID_##id, name, mask, desc }
+ __A(SECTION, "Generic", "", ""),
+ __A(RANDOM, "random", "i",
+ "Random value (32 bit)"),
+ __A(LOADAVG_0, "loadavg_1", "i",
+ "Load average in last minute"),
+ __A(LOADAVG_1, "loadavg_5", "i",
+ "Load average in last 5 minutes"),
+ __A(LOADAVG_2, "loadavg_15", "i",
+ "Load average in last 15 minutes"),
+
+ __A(SECTION, "Interfaces", "", ""),
+ __A(DEV, "dev", "iv",
+ "Device the packet is on"),
+ __A(SECTION, "Packet attributes", "", ""),
+ __A(PRIORITY, "priority", "i",
+ "Priority of packet"),
+ __A(PROTOCOL, "protocol", "i",
+ "Link layer protocol"),
+ __A(PKTTYPE, "pkt_type", "i",
+ "Packet type (uni|multi|broad|...)cast"),
+ __A(PKTLEN, "pkt_len", "i",
+ "Length of packet"),
+ __A(DATALEN, "data_len", "i",
+ "Length of data in packet"),
+ __A(MACLEN, "mac_len", "i",
+ "Length of link layer header"),
+
+ __A(SECTION, "Netfilter", "", ""),
+ __A(NFMARK, "nf_mark", "i",
+ "Netfilter mark"),
+ __A(NFMARK, "fwmark", "i",
+ "Alias for nf_mark"),
+
+ __A(SECTION, "Traffic Control", "", ""),
+ __A(TCINDEX, "tc_index", "i", "TC Index"),
+ __A(SECTION, "Routing", "", ""),
+ __A(RTCLASSID, "rt_classid", "i",
+ "Routing ClassID (cls_route)"),
+ __A(RTIIF, "rt_iif", "i",
+ "Incoming interface index"),
+ __A(VLAN_TAG, "vlan", "i", "Vlan tag"),
+
+ __A(SECTION, "Sockets", "", ""),
+ __A(SK_FAMILY, "sk_family", "i", "Address family"),
+ __A(SK_STATE, "sk_state", "i", "State"),
+ __A(SK_REUSE, "sk_reuse", "i", "Reuse Flag"),
+ __A(SK_BOUND_IF, "sk_bind_if", "iv", "Bound interface"),
+ __A(SK_REFCNT, "sk_refcnt", "i", "Reference counter"),
+ __A(SK_SHUTDOWN, "sk_shutdown", "i", "Shutdown mask"),
+ __A(SK_PROTO, "sk_proto", "i", "Protocol"),
+ __A(SK_TYPE, "sk_type", "i", "Type"),
+ __A(SK_RCVBUF, "sk_rcvbuf", "i", "Receive buffer size"),
+ __A(SK_RMEM_ALLOC, "sk_rmem", "i", "RMEM"),
+ __A(SK_WMEM_ALLOC, "sk_wmem", "i", "WMEM"),
+ __A(SK_OMEM_ALLOC, "sk_omem", "i", "OMEM"),
+ __A(SK_WMEM_QUEUED, "sk_wmem_queue", "i", "WMEM queue"),
+ __A(SK_SND_QLEN, "sk_snd_queue", "i", "Send queue length"),
+ __A(SK_RCV_QLEN, "sk_rcv_queue", "i", "Receive queue length"),
+ __A(SK_ERR_QLEN, "sk_err_queue", "i", "Error queue length"),
+ __A(SK_FORWARD_ALLOCS, "sk_fwd_alloc", "i", "Forward allocations"),
+ __A(SK_SNDBUF, "sk_sndbuf", "i", "Send buffer size"),
+#undef __A
+};
+
+static inline int map_type(char k)
+{
+ switch (k) {
+ case 'i': return TCF_META_TYPE_INT;
+ case 'v': return TCF_META_TYPE_VAR;
+ }
+
+ fprintf(stderr, "BUG: Unknown map character '%c'\n", k);
+ return INT_MAX;
+}
+
+static const struct meta_entry *lookup_meta_entry(struct bstr *kind)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(meta_table); i++)
+ if (!bstrcmp(kind, meta_table[i].kind) &&
+ meta_table[i].id != 0)
+ return &meta_table[i];
+
+ return NULL;
+}
+
+static const struct meta_entry *lookup_meta_entry_byid(int id)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(meta_table); i++)
+ if (meta_table[i].id == id)
+ return &meta_table[i];
+
+ return NULL;
+}
+
+static inline void dump_value(struct nlmsghdr *n, int tlv, unsigned long val,
+ struct tcf_meta_val *hdr)
+{
+ __u32 t;
+
+ switch (TCF_META_TYPE(hdr->kind)) {
+ case TCF_META_TYPE_INT:
+ t = val;
+ addattr_l(n, MAX_MSG, tlv, &t, sizeof(t));
+ break;
+
+ case TCF_META_TYPE_VAR:
+ if (TCF_META_ID(hdr->kind) == TCF_META_ID_VALUE) {
+ struct bstr *a = (struct bstr *) val;
+
+ addattr_l(n, MAX_MSG, tlv, a->data, a->len);
+ }
+ break;
+ }
+}
+
+static inline int is_compatible(struct tcf_meta_val *what,
+ struct tcf_meta_val *needed)
+{
+ const struct meta_entry *entry;
+ char *p;
+
+ entry = lookup_meta_entry_byid(TCF_META_ID(what->kind));
+
+ if (entry == NULL)
+ return 0;
+
+ for (p = entry->mask; p; p++)
+ if (map_type(*p) == TCF_META_TYPE(needed->kind))
+ return 1;
+
+ return 0;
+}
+
+static void list_meta_ids(FILE *fd)
+{
+ int i;
+
+ fprintf(fd,
+ "--------------------------------------------------------\n" \
+ " ID Type Description\n" \
+ "--------------------------------------------------------");
+
+ for (i = 0; i < ARRAY_SIZE(meta_table); i++) {
+ if (meta_table[i].id == TCF_META_ID_SECTION) {
+ fprintf(fd, "\n%s:\n", meta_table[i].kind);
+ } else {
+ char *p = meta_table[i].mask;
+ char buf[64] = {0};
+
+ fprintf(fd, " %-16s ", meta_table[i].kind);
+
+ while (*p) {
+ int type = map_type(*p);
+
+ switch (type) {
+ case TCF_META_TYPE_INT:
+ strcat(buf, "INT");
+ break;
+
+ case TCF_META_TYPE_VAR:
+ strcat(buf, "VAR");
+ break;
+ }
+
+ if (*(++p))
+ strcat(buf, ",");
+ }
+
+ fprintf(fd, "%-10s %s\n", buf, meta_table[i].desc);
+ }
+ }
+
+ fprintf(fd,
+ "--------------------------------------------------------\n");
+}
+
+#undef TCF_META_ID_SECTION
+
+#define PARSE_FAILURE ((void *) (-1))
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+ em_parse_error(EINVAL, args, CARG, &meta_ematch_util, FMT, ##ARGS)
+
+static inline int can_adopt(struct tcf_meta_val *val)
+{
+ return !!TCF_META_ID(val->kind);
+}
+
+static inline int overwrite_type(struct tcf_meta_val *src,
+ struct tcf_meta_val *dst)
+{
+ return (TCF_META_TYPE(dst->kind) << 12) | TCF_META_ID(src->kind);
+}
+
+
+static inline struct bstr *
+parse_object(struct bstr *args, struct bstr *arg, struct tcf_meta_val *obj,
+ unsigned long *dst, struct tcf_meta_val *left)
+{
+ const struct meta_entry *entry;
+ unsigned long num;
+ struct bstr *a;
+
+ if (arg->quoted) {
+ obj->kind = TCF_META_TYPE_VAR << 12;
+ obj->kind |= TCF_META_ID_VALUE;
+ *dst = (unsigned long) arg;
+ return bstr_next(arg);
+ }
+
+ num = bstrtoul(arg);
+ if (num != ULONG_MAX) {
+ obj->kind = TCF_META_TYPE_INT << 12;
+ obj->kind |= TCF_META_ID_VALUE;
+ *dst = (unsigned long) num;
+ return bstr_next(arg);
+ }
+
+ entry = lookup_meta_entry(arg);
+
+ if (entry == NULL) {
+ PARSE_ERR(arg, "meta: unknown meta id\n");
+ return PARSE_FAILURE;
+ }
+
+ obj->kind = entry->id | (map_type(entry->mask[0]) << 12);
+
+ if (left) {
+ struct tcf_meta_val *right = obj;
+
+ if (TCF_META_TYPE(right->kind) == TCF_META_TYPE(left->kind))
+ goto compatible;
+
+ if (can_adopt(left) && !can_adopt(right)) {
+ if (is_compatible(left, right))
+ left->kind = overwrite_type(left, right);
+ else
+ goto not_compatible;
+ } else if (can_adopt(right) && !can_adopt(left)) {
+ if (is_compatible(right, left))
+ right->kind = overwrite_type(right, left);
+ else
+ goto not_compatible;
+ } else if (can_adopt(left) && can_adopt(right)) {
+ if (is_compatible(left, right))
+ left->kind = overwrite_type(left, right);
+ else if (is_compatible(right, left))
+ right->kind = overwrite_type(right, left);
+ else
+ goto not_compatible;
+ } else
+ goto not_compatible;
+ }
+
+compatible:
+
+ a = bstr_next(arg);
+
+ while (a) {
+ if (!bstrcmp(a, "shift")) {
+ unsigned long shift;
+
+ if (a->next == NULL) {
+ PARSE_ERR(a, "meta: missing argument");
+ return PARSE_FAILURE;
+ }
+ a = bstr_next(a);
+
+ shift = bstrtoul(a);
+ if (shift == ULONG_MAX) {
+ PARSE_ERR(a, "meta: invalid shift, must " \
+ "be numeric");
+ return PARSE_FAILURE;
+ }
+
+ obj->shift = (__u8) shift;
+ a = bstr_next(a);
+ } else if (!bstrcmp(a, "mask")) {
+ unsigned long mask;
+
+ if (a->next == NULL) {
+ PARSE_ERR(a, "meta: missing argument");
+ return PARSE_FAILURE;
+ }
+ a = bstr_next(a);
+
+ mask = bstrtoul(a);
+ if (mask == ULONG_MAX) {
+ PARSE_ERR(a, "meta: invalid mask, must be " \
+ "numeric");
+ return PARSE_FAILURE;
+ }
+ *dst = (unsigned long) mask;
+ a = bstr_next(a);
+ } else
+ break;
+ }
+
+ return a;
+
+not_compatible:
+ PARSE_ERR(arg, "lvalue and rvalue are not compatible.");
+ return PARSE_FAILURE;
+}
+
+static int meta_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+ struct bstr *args)
+{
+ int opnd;
+ struct bstr *a;
+ struct tcf_meta_hdr meta_hdr = {};
+ unsigned long lvalue = 0, rvalue = 0;
+
+ if (args == NULL)
+ return PARSE_ERR(args, "meta: missing arguments");
+
+ if (!bstrcmp(args, "list")) {
+ list_meta_ids(stderr);
+ return -1;
+ }
+
+ a = parse_object(args, args, &meta_hdr.left, &lvalue, NULL);
+ if (a == PARSE_FAILURE)
+ return -1;
+ else if (a == NULL)
+ return PARSE_ERR(args, "meta: missing operand");
+
+ if (!bstrcmp(a, "eq"))
+ opnd = TCF_EM_OPND_EQ;
+ else if (!bstrcmp(a, "gt"))
+ opnd = TCF_EM_OPND_GT;
+ else if (!bstrcmp(a, "lt"))
+ opnd = TCF_EM_OPND_LT;
+ else
+ return PARSE_ERR(a, "meta: invalid operand");
+
+ meta_hdr.left.op = (__u8) opnd;
+
+ if (a->next == NULL)
+ return PARSE_ERR(args, "meta: missing rvalue");
+ a = bstr_next(a);
+
+ a = parse_object(args, a, &meta_hdr.right, &rvalue, &meta_hdr.left);
+ if (a == PARSE_FAILURE)
+ return -1;
+ else if (a != NULL)
+ return PARSE_ERR(a, "meta: unexpected trailer");
+
+
+ addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+
+ addattr_l(n, MAX_MSG, TCA_EM_META_HDR, &meta_hdr, sizeof(meta_hdr));
+
+ dump_value(n, TCA_EM_META_LVALUE, lvalue, &meta_hdr.left);
+ dump_value(n, TCA_EM_META_RVALUE, rvalue, &meta_hdr.right);
+
+ return 0;
+}
+#undef PARSE_ERR
+
+static inline void print_binary(FILE *fd, unsigned char *str, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ if (!isprint(str[i]))
+ goto binary;
+
+ for (i = 0; i < len; i++)
+ fprintf(fd, "%c", str[i]);
+ return;
+
+binary:
+ for (i = 0; i < len; i++)
+ fprintf(fd, "%02x ", str[i]);
+
+ fprintf(fd, "\"");
+ for (i = 0; i < len; i++)
+ fprintf(fd, "%c", isprint(str[i]) ? str[i] : '.');
+ fprintf(fd, "\"");
+}
+
+static inline int print_value(FILE *fd, int type, struct rtattr *rta)
+{
+ if (rta == NULL) {
+ fprintf(stderr, "Missing value TLV\n");
+ return -1;
+ }
+
+ switch (type) {
+ case TCF_META_TYPE_INT:
+ if (RTA_PAYLOAD(rta) < sizeof(__u32)) {
+ fprintf(stderr, "meta int type value TLV " \
+ "size mismatch.\n");
+ return -1;
+ }
+ fprintf(fd, "%d", rta_getattr_u32(rta));
+ break;
+
+ case TCF_META_TYPE_VAR:
+ print_binary(fd, RTA_DATA(rta), RTA_PAYLOAD(rta));
+ break;
+ }
+
+ return 0;
+}
+
+static int print_object(FILE *fd, struct tcf_meta_val *obj, struct rtattr *rta)
+{
+ int id = TCF_META_ID(obj->kind);
+ int type = TCF_META_TYPE(obj->kind);
+ const struct meta_entry *entry;
+
+ if (id == TCF_META_ID_VALUE)
+ return print_value(fd, type, rta);
+
+ entry = lookup_meta_entry_byid(id);
+
+ if (entry == NULL)
+ fprintf(fd, "[unknown meta id %d]", id);
+ else
+ fprintf(fd, "%s", entry->kind);
+
+ if (obj->shift)
+ fprintf(fd, " shift %d", obj->shift);
+
+ switch (type) {
+ case TCF_META_TYPE_INT:
+ if (rta) {
+ if (RTA_PAYLOAD(rta) < sizeof(__u32))
+ goto size_mismatch;
+
+ if (rta_getattr_u32(rta))
+ fprintf(fd, " mask 0x%08x",
+ rta_getattr_u32(rta));
+ }
+ break;
+ }
+
+ return 0;
+
+size_mismatch:
+ fprintf(stderr, "meta int type mask TLV size mismatch\n");
+ return -1;
+}
+
+
+static int meta_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+ int data_len)
+{
+ struct rtattr *tb[TCA_EM_META_MAX+1];
+ struct tcf_meta_hdr *meta_hdr;
+
+ if (parse_rtattr(tb, TCA_EM_META_MAX, data, data_len) < 0)
+ return -1;
+
+ if (tb[TCA_EM_META_HDR] == NULL) {
+ fprintf(stderr, "Missing meta header\n");
+ return -1;
+ }
+
+ if (RTA_PAYLOAD(tb[TCA_EM_META_HDR]) < sizeof(*meta_hdr)) {
+ fprintf(stderr, "Meta header size mismatch\n");
+ return -1;
+ }
+
+ meta_hdr = RTA_DATA(tb[TCA_EM_META_HDR]);
+
+ if (print_object(fd, &meta_hdr->left, tb[TCA_EM_META_LVALUE]) < 0)
+ return -1;
+
+ switch (meta_hdr->left.op) {
+ case TCF_EM_OPND_EQ:
+ fprintf(fd, " eq ");
+ break;
+ case TCF_EM_OPND_LT:
+ fprintf(fd, " lt ");
+ break;
+ case TCF_EM_OPND_GT:
+ fprintf(fd, " gt ");
+ break;
+ }
+
+ return print_object(fd, &meta_hdr->right, tb[TCA_EM_META_RVALUE]);
+}
+
+struct ematch_util meta_ematch_util = {
+ .kind = "meta",
+ .kind_num = TCF_EM_META,
+ .parse_eopt = meta_parse_eopt,
+ .print_eopt = meta_print_eopt,
+ .print_usage = meta_print_usage
+};
diff --git a/tc/em_nbyte.c b/tc/em_nbyte.c
new file mode 100644
index 0000000..274d713
--- /dev/null
+++ b/tc/em_nbyte.c
@@ -0,0 +1,140 @@
+/*
+ * em_nbyte.c N-Byte Ematch
+ *
+ * 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: Thomas Graf <tgraf@suug.ch>
+ */
+
+#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 <errno.h>
+
+#include "m_ematch.h"
+#include <linux/tc_ematch/tc_em_nbyte.h>
+
+extern struct ematch_util nbyte_ematch_util;
+
+static void nbyte_print_usage(FILE *fd)
+{
+ fprintf(fd,
+ "Usage: nbyte(NEEDLE at OFFSET [layer LAYER])\n" \
+ "where: NEEDLE := { string | \"c-escape-sequence\" }\n" \
+ " OFFSET := int\n" \
+ " LAYER := { link | network | transport | 0..%d }\n" \
+ "\n" \
+ "Example: nbyte(\"ababa\" at 12 layer 1)\n",
+ TCF_LAYER_MAX);
+}
+
+static int nbyte_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+ struct bstr *args)
+{
+ struct bstr *a;
+ struct bstr *needle = args;
+ unsigned long offset = 0, layer = TCF_LAYER_NETWORK;
+ int offset_present = 0;
+ struct tcf_em_nbyte nb = {};
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+ em_parse_error(EINVAL, args, CARG, &nbyte_ematch_util, FMT, ##ARGS)
+
+ if (args == NULL)
+ return PARSE_ERR(args, "nbyte: missing arguments");
+
+ if (needle->len <= 0)
+ return PARSE_ERR(args, "nbyte: needle length is 0");
+
+ for (a = bstr_next(args); a; a = bstr_next(a)) {
+ if (!bstrcmp(a, "at")) {
+ if (a->next == NULL)
+ return PARSE_ERR(a, "nbyte: missing argument");
+ a = bstr_next(a);
+
+ offset = bstrtoul(a);
+ if (offset == ULONG_MAX)
+ return PARSE_ERR(a, "nbyte: invalid offset, " \
+ "must be numeric");
+
+ offset_present = 1;
+ } else if (!bstrcmp(a, "layer")) {
+ if (a->next == NULL)
+ return PARSE_ERR(a, "nbyte: missing argument");
+ a = bstr_next(a);
+
+ layer = parse_layer(a);
+ if (layer == INT_MAX) {
+ layer = bstrtoul(a);
+ if (layer == ULONG_MAX)
+ return PARSE_ERR(a, "nbyte: invalid " \
+ "layer");
+ }
+
+ if (layer > TCF_LAYER_MAX)
+ return PARSE_ERR(a, "nbyte: illegal layer, " \
+ "must be in 0..%d", TCF_LAYER_MAX);
+ } else
+ return PARSE_ERR(a, "nbyte: unknown parameter");
+ }
+
+ if (offset_present == 0)
+ return PARSE_ERR(a, "nbyte: offset required");
+
+ nb.len = needle->len;
+ nb.layer = (__u8) layer;
+ nb.off = (__u16) offset;
+
+ addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+ addraw_l(n, MAX_MSG, &nb, sizeof(nb));
+ addraw_l(n, MAX_MSG, needle->data, needle->len);
+
+#undef PARSE_ERR
+ return 0;
+}
+
+static int nbyte_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+ int data_len)
+{
+ int i;
+ struct tcf_em_nbyte *nb = data;
+ __u8 *needle;
+
+ if (data_len < sizeof(*nb)) {
+ fprintf(stderr, "NByte header size mismatch\n");
+ return -1;
+ }
+
+ if (data_len < sizeof(*nb) + nb->len) {
+ fprintf(stderr, "NByte payload size mismatch\n");
+ return -1;
+ }
+
+ needle = data + sizeof(*nb);
+
+ for (i = 0; i < nb->len; i++)
+ fprintf(fd, "%02x ", needle[i]);
+
+ fprintf(fd, "\"");
+ for (i = 0; i < nb->len; i++)
+ fprintf(fd, "%c", isprint(needle[i]) ? needle[i] : '.');
+ fprintf(fd, "\" at %d layer %d", nb->off, nb->layer);
+
+ return 0;
+}
+
+struct ematch_util nbyte_ematch_util = {
+ .kind = "nbyte",
+ .kind_num = TCF_EM_NBYTE,
+ .parse_eopt = nbyte_parse_eopt,
+ .print_eopt = nbyte_print_eopt,
+ .print_usage = nbyte_print_usage
+};
diff --git a/tc/em_u32.c b/tc/em_u32.c
new file mode 100644
index 0000000..bc284af
--- /dev/null
+++ b/tc/em_u32.c
@@ -0,0 +1,175 @@
+/*
+ * em_u32.c U32 Ematch
+ *
+ * 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: Thomas Graf <tgraf@suug.ch>
+ */
+
+#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 <errno.h>
+
+#include "m_ematch.h"
+
+extern struct ematch_util u32_ematch_util;
+
+static void u32_print_usage(FILE *fd)
+{
+ fprintf(fd,
+ "Usage: u32(ALIGN VALUE MASK at [ nexthdr+ ] OFFSET)\n" \
+ "where: ALIGN := { u8 | u16 | u32 }\n" \
+ "\n" \
+ "Example: u32(u16 0x1122 0xffff at nexthdr+4)\n");
+}
+
+static int u32_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+ struct bstr *args)
+{
+ struct bstr *a;
+ int align, nh_len;
+ unsigned long key, mask, offmask = 0, offset;
+ struct tc_u32_key u_key = {};
+
+#define PARSE_ERR(CARG, FMT, ARGS...) \
+ em_parse_error(EINVAL, args, CARG, &u32_ematch_util, FMT, ##ARGS)
+
+ if (args == NULL)
+ return PARSE_ERR(args, "u32: missing arguments");
+
+ if (!bstrcmp(args, "u8"))
+ align = 1;
+ else if (!bstrcmp(args, "u16"))
+ align = 2;
+ else if (!bstrcmp(args, "u32"))
+ align = 4;
+ else
+ return PARSE_ERR(args, "u32: invalid alignment");
+
+ a = bstr_next(args);
+ if (a == NULL)
+ return PARSE_ERR(a, "u32: missing key");
+
+ key = bstrtoul(a);
+ if (key == ULONG_MAX)
+ return PARSE_ERR(a, "u32: invalid key, must be numeric");
+
+ a = bstr_next(a);
+ if (a == NULL)
+ return PARSE_ERR(a, "u32: missing mask");
+
+ mask = bstrtoul(a);
+ if (mask == ULONG_MAX)
+ return PARSE_ERR(a, "u32: invalid mask, must be numeric");
+
+ a = bstr_next(a);
+ if (a == NULL || bstrcmp(a, "at") != 0)
+ return PARSE_ERR(a, "u32: missing \"at\"");
+
+ a = bstr_next(a);
+ if (a == NULL)
+ return PARSE_ERR(a, "u32: missing offset");
+
+ nh_len = strlen("nexthdr+");
+ if (a->len > nh_len && !memcmp(a->data, "nexthdr+", nh_len)) {
+ char buf[a->len - nh_len + 1];
+
+ offmask = -1;
+ memcpy(buf, a->data + nh_len, a->len - nh_len);
+ offset = strtoul(buf, NULL, 0);
+ } else if (!bstrcmp(a, "nexthdr+")) {
+ a = bstr_next(a);
+ if (a == NULL)
+ return PARSE_ERR(a, "u32: missing offset");
+ offset = bstrtoul(a);
+ } else
+ offset = bstrtoul(a);
+
+ if (offset == ULONG_MAX)
+ return PARSE_ERR(a, "u32: invalid offset");
+
+ if (a->next)
+ return PARSE_ERR(a->next, "u32: unexpected trailer");
+
+ switch (align) {
+ case 1:
+ if (key > 0xFF)
+ return PARSE_ERR(a, "Illegal key (>0xFF)");
+ if (mask > 0xFF)
+ return PARSE_ERR(a, "Illegal mask (>0xFF)");
+
+ key <<= 24 - ((offset & 3) * 8);
+ mask <<= 24 - ((offset & 3) * 8);
+ offset &= ~3;
+ break;
+
+ case 2:
+ if (key > 0xFFFF)
+ return PARSE_ERR(a, "Illegal key (>0xFFFF)");
+ if (mask > 0xFFFF)
+ return PARSE_ERR(a, "Illegal mask (>0xFFFF)");
+
+ if ((offset & 3) == 0) {
+ key <<= 16;
+ mask <<= 16;
+ }
+ offset &= ~3;
+ break;
+ }
+
+ key = htonl(key);
+ mask = htonl(mask);
+
+ if (offset % 4)
+ return PARSE_ERR(a, "u32: invalid offset alignment, " \
+ "must be aligned to 4.");
+
+ key &= mask;
+
+ u_key.mask = mask;
+ u_key.val = key;
+ u_key.off = offset;
+ u_key.offmask = offmask;
+
+ addraw_l(n, MAX_MSG, hdr, sizeof(*hdr));
+ addraw_l(n, MAX_MSG, &u_key, sizeof(u_key));
+
+#undef PARSE_ERR
+ return 0;
+}
+
+static int u32_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data,
+ int data_len)
+{
+ struct tc_u32_key *u_key = data;
+
+ if (data_len < sizeof(*u_key)) {
+ fprintf(stderr, "U32 header size mismatch\n");
+ return -1;
+ }
+
+ fprintf(fd, "%08x/%08x at %s%d",
+ (unsigned int) ntohl(u_key->val),
+ (unsigned int) ntohl(u_key->mask),
+ u_key->offmask ? "nexthdr+" : "",
+ u_key->off);
+
+ return 0;
+}
+
+struct ematch_util u32_ematch_util = {
+ .kind = "u32",
+ .kind_num = TCF_EM_U32,
+ .parse_eopt = u32_parse_eopt,
+ .print_eopt = u32_print_eopt,
+ .print_usage = u32_print_usage
+};
diff --git a/tc/emp_ematch.l b/tc/emp_ematch.l
new file mode 100644
index 0000000..d7a9930
--- /dev/null
+++ b/tc/emp_ematch.l
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+%{
+ #include "emp_ematch.yacc.h"
+ #include "m_ematch.h"
+
+ extern int ematch_argc;
+ extern char **ematch_argv;
+
+ #define yylval ematch_lval
+
+ #define NEXT_EM_ARG() do { ematch_argc--; ematch_argv++; } while(0);
+
+ #define YY_INPUT(buf, result, max_size) \
+ { \
+ next: \
+ if (ematch_argc <= 0) \
+ result = YY_NULL; \
+ else if (**ematch_argv == '\0') { \
+ NEXT_EM_ARG(); \
+ goto next; \
+ } else { \
+ if (max_size <= strlen(*ematch_argv) + 1) { \
+ fprintf(stderr, "match argument too long.\n"); \
+ result = YY_NULL; \
+ } else { \
+ strcpy(buf, *ematch_argv); \
+ result = strlen(*ematch_argv) + 1; \
+ buf[result-1] = ' '; \
+ buf[result] = '\0'; \
+ NEXT_EM_ARG(); \
+ } \
+ } \
+ }
+
+ static void __attribute__ ((unused)) yyunput (int c,char *buf_ptr );
+ static void __attribute__ ((unused)) yy_push_state (int new_state );
+ static void __attribute__ ((unused)) yy_pop_state (void);
+ static int __attribute__ ((unused)) yy_top_state (void );
+
+ static char *strbuf;
+ static unsigned int strbuf_size;
+ static unsigned int strbuf_index;
+
+ static void strbuf_enlarge(void)
+ {
+ strbuf_size += 512;
+ strbuf = realloc(strbuf, strbuf_size);
+ }
+
+ static void strbuf_append_char(char c)
+ {
+ while (strbuf_index >= strbuf_size)
+ strbuf_enlarge();
+ strbuf[strbuf_index++] = c;
+ }
+
+ static void strbuf_append_charp(char *s)
+ {
+ while (strbuf_index >= strbuf_size)
+ strbuf_enlarge();
+ memcpy(strbuf + strbuf_index, s, strlen(s));
+ strbuf_index += strlen(s);
+ }
+
+%}
+
+%x lexstr
+
+%option 8bit stack warn noyywrap prefix="ematch_"
+%%
+[ \t\r\n]+
+
+\" {
+ if (strbuf == NULL) {
+ strbuf_size = 512;
+ strbuf = calloc(1, strbuf_size);
+ if (strbuf == NULL)
+ return ERROR;
+ }
+ strbuf_index = 0;
+
+ BEGIN(lexstr);
+ }
+
+<lexstr>\" {
+ BEGIN(INITIAL);
+ yylval.b = bstr_new(strbuf, strbuf_index);
+ yylval.b->quoted = 1;
+ return ATTRIBUTE;
+ }
+
+<lexstr>\\[0-7]{1,3} { /* octal escape sequence */
+ int res;
+
+ sscanf(yytext + 1, "%o", &res);
+ if (res > 0xFF) {
+ fprintf(stderr, "error: octal escape sequence" \
+ " out of range\n");
+ return ERROR;
+ }
+ strbuf_append_char((unsigned char) res);
+ }
+
+<lexstr>\\[0-9]+ { /* catch wrong octal escape seq. */
+ fprintf(stderr, "error: invalid octale escape sequence\n");
+ return ERROR;
+ }
+
+<lexstr>\\x[0-9a-fA-F]{1,2} {
+ int res;
+
+ sscanf(yytext + 2, "%x", &res);
+
+ if (res > 0xFF) {
+ fprintf(stderr, "error: hexadecimal escape " \
+ "sequence out of range\n");
+ return ERROR;
+ }
+ strbuf_append_char((unsigned char) res);
+ }
+
+<lexstr>\\n strbuf_append_char('\n');
+<lexstr>\\r strbuf_append_char('\r');
+<lexstr>\\t strbuf_append_char('\t');
+<lexstr>\\v strbuf_append_char('\v');
+<lexstr>\\b strbuf_append_char('\b');
+<lexstr>\\f strbuf_append_char('\f');
+<lexstr>\\a strbuf_append_char('\a');
+
+<lexstr>\\(.|\n) strbuf_append_char(yytext[1]);
+<lexstr>[^\\\n\"]+ strbuf_append_charp(yytext);
+
+[aA][nN][dD] return AND;
+[oO][rR] return OR;
+[nN][oO][tT] return NOT;
+"(" |
+")" {
+ return yylval.i = *yytext;
+ }
+[^" \t\r\n()][^ \t\r\n()]* {
+ yylval.b = bstr_alloc(yytext);
+ if (yylval.b == NULL)
+ return ERROR;
+ return ATTRIBUTE;
+ }
+%%
diff --git a/tc/emp_ematch.y b/tc/emp_ematch.y
new file mode 100644
index 0000000..a02e831
--- /dev/null
+++ b/tc/emp_ematch.y
@@ -0,0 +1,100 @@
+%{
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <malloc.h>
+ #include <string.h>
+ #include "m_ematch.h"
+%}
+
+%locations
+%token-table
+%define parse.error verbose
+%define api.prefix {ematch_}
+
+%union {
+ unsigned int i;
+ struct bstr *b;
+ struct ematch *e;
+}
+
+%{
+ extern int ematch_lex(void);
+ extern void yyerror(const char *s);
+ extern struct ematch *ematch_root;
+ extern char *ematch_err;
+%}
+
+%token <i> ERROR
+%token <b> ATTRIBUTE
+%token <i> AND OR NOT
+%type <i> invert relation
+%type <e> match expr
+%type <b> args
+%right AND OR
+%start input
+%%
+input:
+ /* empty */
+ | expr
+ { ematch_root = $1; }
+ | expr error
+ {
+ ematch_root = $1;
+ YYACCEPT;
+ }
+ ;
+
+expr:
+ match
+ { $$ = $1; }
+ | match relation expr
+ {
+ $1->relation = $2;
+ $1->next = $3;
+ $$ = $1;
+ }
+ ;
+
+match:
+ invert ATTRIBUTE '(' args ')'
+ {
+ $2->next = $4;
+ $$ = new_ematch($2, $1);
+ if ($$ == NULL)
+ YYABORT;
+ }
+ | invert '(' expr ')'
+ {
+ $$ = new_ematch(NULL, $1);
+ if ($$ == NULL)
+ YYABORT;
+ $$->child = $3;
+ }
+ ;
+
+args:
+ ATTRIBUTE
+ { $$ = $1; }
+ | ATTRIBUTE args
+ { $1->next = $2; }
+ ;
+
+relation:
+ AND
+ { $$ = TCF_EM_REL_AND; }
+ | OR
+ { $$ = TCF_EM_REL_OR; }
+ ;
+
+invert:
+ /* empty */
+ { $$ = 0; }
+ | NOT
+ { $$ = 1; }
+ ;
+%%
+
+ void yyerror(const char *s)
+ {
+ ematch_err = strdup(s);
+ }
diff --git a/tc/f_basic.c b/tc/f_basic.c
new file mode 100644
index 0000000..7b19cea
--- /dev/null
+++ b/tc/f_basic.c
@@ -0,0 +1,149 @@
+/*
+ * f_basic.c Basic Classifier
+ *
+ * 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: Thomas Graf <tgraf@suug.ch>
+ *
+ */
+
+#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 <linux/if.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "m_ematch.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... basic [ match EMATCH_TREE ]\n"
+ " [ action ACTION_SPEC ] [ classid CLASSID ]\n"
+ "\n"
+ "Where: SELECTOR := SAMPLE SAMPLE ...\n"
+ " FILTERID := X:Y:Z\n"
+ " ACTION_SPEC := ... look at individual actions\n"
+ "\n"
+ "NOTE: CLASSID is parsed as hexadecimal input.\n");
+}
+
+static int basic_parse_opt(struct filter_util *qu, char *handle,
+ int argc, char **argv, struct nlmsghdr *n)
+{
+ struct tcmsg *t = NLMSG_DATA(n);
+ struct rtattr *tail;
+ long h = 0;
+
+ if (handle) {
+ h = strtol(handle, NULL, 0);
+ if (h == LONG_MIN || h == LONG_MAX) {
+ fprintf(stderr, "Illegal handle \"%s\", must be numeric.\n",
+ handle);
+ return -1;
+ }
+ }
+ t->tcm_handle = h;
+
+ if (argc == 0)
+ return 0;
+
+ tail = (struct rtattr *)(((void *)n)+NLMSG_ALIGN(n->nlmsg_len));
+ addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);
+
+ while (argc > 0) {
+ if (matches(*argv, "match") == 0) {
+ NEXT_ARG();
+ if (parse_ematch(&argc, &argv, TCA_BASIC_EMATCHES, n)) {
+ fprintf(stderr, "Illegal \"ematch\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "classid") == 0 ||
+ strcmp(*argv, "flowid") == 0) {
+ unsigned int handle;
+
+ NEXT_ARG();
+ if (get_tc_classid(&handle, *argv)) {
+ fprintf(stderr, "Illegal \"classid\"\n");
+ return -1;
+ }
+ addattr_l(n, MAX_MSG, TCA_BASIC_CLASSID, &handle, 4);
+ } else if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (parse_action(&argc, &argv, TCA_BASIC_ACT, n)) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ continue;
+
+ } else if (matches(*argv, "police") == 0) {
+ NEXT_ARG();
+ if (parse_police(&argc, &argv, TCA_BASIC_POLICE, n)) {
+ fprintf(stderr, "Illegal \"police\"\n");
+ return -1;
+ }
+ continue;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail->rta_len = (((void *)n)+n->nlmsg_len) - (void *)tail;
+ return 0;
+}
+
+static int basic_print_opt(struct filter_util *qu, FILE *f,
+ struct rtattr *opt, __u32 handle)
+{
+ struct rtattr *tb[TCA_BASIC_MAX+1];
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_BASIC_MAX, opt);
+
+ if (handle)
+ fprintf(f, "handle 0x%x ", handle);
+
+ if (tb[TCA_BASIC_CLASSID]) {
+ SPRINT_BUF(b1);
+ fprintf(f, "flowid %s ",
+ sprint_tc_classid(rta_getattr_u32(tb[TCA_BASIC_CLASSID]), b1));
+ }
+
+ if (tb[TCA_BASIC_EMATCHES])
+ print_ematch(f, tb[TCA_BASIC_EMATCHES]);
+
+ if (tb[TCA_BASIC_POLICE]) {
+ fprintf(f, "\n");
+ tc_print_police(f, tb[TCA_BASIC_POLICE]);
+ }
+
+ if (tb[TCA_BASIC_ACT]) {
+ tc_print_action(f, tb[TCA_BASIC_ACT], 0);
+ }
+
+ return 0;
+}
+
+struct filter_util basic_filter_util = {
+ .id = "basic",
+ .parse_fopt = basic_parse_opt,
+ .print_fopt = basic_print_opt,
+};
diff --git a/tc/f_bpf.c b/tc/f_bpf.c
new file mode 100644
index 0000000..135271a
--- /dev/null
+++ b/tc/f_bpf.c
@@ -0,0 +1,269 @@
+/*
+ * f_bpf.c BPF-based Classifier
+ *
+ * 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>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <linux/bpf.h>
+
+#include "utils.h"
+
+#include "tc_util.h"
+#include "bpf_util.h"
+
+static const enum bpf_prog_type bpf_type = BPF_PROG_TYPE_SCHED_CLS;
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... bpf ...\n"
+ "\n"
+ "BPF use case:\n"
+ " bytecode BPF_BYTECODE\n"
+ " bytecode-file FILE\n"
+ "\n"
+ "eBPF use case:\n"
+ " object-file FILE [ section CLS_NAME ] [ export UDS_FILE ]"
+ " [ verbose ] [ direct-action ] [ skip_hw | skip_sw ]\n"
+ " object-pinned FILE [ direct-action ] [ skip_hw | skip_sw ]\n"
+ "\n"
+ "Common remaining options:\n"
+ " [ action ACTION_SPEC ]\n"
+ " [ classid CLASSID ]\n"
+ "\n"
+ "Where BPF_BYTECODE := \'s,c t f k,c t f k,c t f k,...\'\n"
+ "c,t,f,k and s are decimals; s denotes number of 4-tuples\n"
+ "\n"
+ "Where FILE points to a file containing the BPF_BYTECODE string,\n"
+ "an ELF file containing eBPF map definitions and bytecode, or a\n"
+ "pinned eBPF program.\n"
+ "\n"
+ "Where CLS_NAME refers to the section name containing the\n"
+ "classifier (default \'%s\').\n"
+ "\n"
+ "Where UDS_FILE points to a unix domain socket file in order\n"
+ "to hand off control of all created eBPF maps to an agent.\n"
+ "\n"
+ "ACTION_SPEC := ... look at individual actions\n"
+ "NOTE: CLASSID is parsed as hexadecimal input.\n",
+ bpf_prog_to_default_section(bpf_type));
+}
+
+static void bpf_cbpf_cb(void *nl, const struct sock_filter *ops, int ops_len)
+{
+ addattr16(nl, MAX_MSG, TCA_BPF_OPS_LEN, ops_len);
+ addattr_l(nl, MAX_MSG, TCA_BPF_OPS, ops,
+ ops_len * sizeof(struct sock_filter));
+}
+
+static void bpf_ebpf_cb(void *nl, int fd, const char *annotation)
+{
+ addattr32(nl, MAX_MSG, TCA_BPF_FD, fd);
+ addattrstrz(nl, MAX_MSG, TCA_BPF_NAME, annotation);
+}
+
+static const struct bpf_cfg_ops bpf_cb_ops = {
+ .cbpf_cb = bpf_cbpf_cb,
+ .ebpf_cb = bpf_ebpf_cb,
+};
+
+static int bpf_parse_opt(struct filter_util *qu, char *handle,
+ int argc, char **argv, struct nlmsghdr *n)
+{
+ const char *bpf_obj = NULL, *bpf_uds_name = NULL;
+ struct tcmsg *t = NLMSG_DATA(n);
+ unsigned int bpf_gen_flags = 0;
+ unsigned int bpf_flags = 0;
+ struct bpf_cfg_in cfg = {};
+ bool seen_run = false;
+ bool skip_sw = false;
+ struct rtattr *tail;
+ int ret = 0;
+
+ if (handle) {
+ if (get_u32(&t->tcm_handle, handle, 0)) {
+ fprintf(stderr, "Illegal \"handle\"\n");
+ return -1;
+ }
+ }
+
+ if (argc == 0)
+ return 0;
+
+ tail = (struct rtattr *)(((void *)n) + NLMSG_ALIGN(n->nlmsg_len));
+ addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);
+
+ while (argc > 0) {
+ if (matches(*argv, "run") == 0) {
+ NEXT_ARG();
+
+ if (seen_run)
+ duparg("run", *argv);
+opt_bpf:
+ seen_run = true;
+ cfg.type = bpf_type;
+ cfg.argc = argc;
+ cfg.argv = argv;
+
+ if (bpf_parse_common(&cfg, &bpf_cb_ops) < 0) {
+ fprintf(stderr,
+ "Unable to parse bpf command line\n");
+ return -1;
+ }
+
+ argc = cfg.argc;
+ argv = cfg.argv;
+
+ bpf_obj = cfg.object;
+ bpf_uds_name = cfg.uds;
+ } else if (matches(*argv, "classid") == 0 ||
+ matches(*argv, "flowid") == 0) {
+ unsigned int handle;
+
+ NEXT_ARG();
+ if (get_tc_classid(&handle, *argv)) {
+ fprintf(stderr, "Illegal \"classid\"\n");
+ return -1;
+ }
+ addattr32(n, MAX_MSG, TCA_BPF_CLASSID, handle);
+ } else if (matches(*argv, "direct-action") == 0 ||
+ matches(*argv, "da") == 0) {
+ bpf_flags |= TCA_BPF_FLAG_ACT_DIRECT;
+ } else if (matches(*argv, "skip_hw") == 0) {
+ bpf_gen_flags |= TCA_CLS_FLAGS_SKIP_HW;
+ } else if (matches(*argv, "skip_sw") == 0) {
+ bpf_gen_flags |= TCA_CLS_FLAGS_SKIP_SW;
+ skip_sw = true;
+ } else if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (parse_action(&argc, &argv, TCA_BPF_ACT, n)) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "police") == 0) {
+ NEXT_ARG();
+ if (parse_police(&argc, &argv, TCA_BPF_POLICE, n)) {
+ fprintf(stderr, "Illegal \"police\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ if (!seen_run)
+ goto opt_bpf;
+
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+
+ NEXT_ARG_FWD();
+ }
+
+ if (skip_sw)
+ cfg.ifindex = t->tcm_ifindex;
+ if (bpf_load_common(&cfg, &bpf_cb_ops, n) < 0) {
+ fprintf(stderr, "Unable to load program\n");
+ return -1;
+ }
+
+ if (bpf_gen_flags)
+ addattr32(n, MAX_MSG, TCA_BPF_FLAGS_GEN, bpf_gen_flags);
+ if (bpf_flags)
+ addattr32(n, MAX_MSG, TCA_BPF_FLAGS, bpf_flags);
+
+ tail->rta_len = (((void *)n) + n->nlmsg_len) - (void *)tail;
+
+ if (bpf_uds_name)
+ ret = bpf_send_map_fds(bpf_uds_name, bpf_obj);
+
+ return ret;
+}
+
+static int bpf_print_opt(struct filter_util *qu, FILE *f,
+ struct rtattr *opt, __u32 handle)
+{
+ struct rtattr *tb[TCA_BPF_MAX + 1];
+ int dump_ok = 0;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_BPF_MAX, opt);
+
+ if (handle)
+ fprintf(f, "handle 0x%x ", handle);
+
+ if (tb[TCA_BPF_CLASSID]) {
+ SPRINT_BUF(b1);
+ fprintf(f, "flowid %s ",
+ sprint_tc_classid(rta_getattr_u32(tb[TCA_BPF_CLASSID]), b1));
+ }
+
+ if (tb[TCA_BPF_NAME])
+ fprintf(f, "%s ", rta_getattr_str(tb[TCA_BPF_NAME]));
+
+ if (tb[TCA_BPF_FLAGS]) {
+ unsigned int flags = rta_getattr_u32(tb[TCA_BPF_FLAGS]);
+
+ if (flags & TCA_BPF_FLAG_ACT_DIRECT)
+ fprintf(f, "direct-action ");
+ }
+
+ if (tb[TCA_BPF_FLAGS_GEN]) {
+ unsigned int flags =
+ rta_getattr_u32(tb[TCA_BPF_FLAGS_GEN]);
+
+ if (flags & TCA_CLS_FLAGS_SKIP_HW)
+ fprintf(f, "skip_hw ");
+ if (flags & TCA_CLS_FLAGS_SKIP_SW)
+ fprintf(f, "skip_sw ");
+
+ if (flags & TCA_CLS_FLAGS_IN_HW)
+ fprintf(f, "in_hw ");
+ else if (flags & TCA_CLS_FLAGS_NOT_IN_HW)
+ fprintf(f, "not_in_hw ");
+ }
+
+ if (tb[TCA_BPF_OPS] && tb[TCA_BPF_OPS_LEN])
+ bpf_print_ops(tb[TCA_BPF_OPS],
+ rta_getattr_u16(tb[TCA_BPF_OPS_LEN]));
+
+ if (tb[TCA_BPF_ID])
+ dump_ok = bpf_dump_prog_info(f, rta_getattr_u32(tb[TCA_BPF_ID]));
+ if (!dump_ok && tb[TCA_BPF_TAG]) {
+ SPRINT_BUF(b);
+
+ fprintf(f, "tag %s ",
+ hexstring_n2a(RTA_DATA(tb[TCA_BPF_TAG]),
+ RTA_PAYLOAD(tb[TCA_BPF_TAG]),
+ b, sizeof(b)));
+ }
+
+ if (tb[TCA_BPF_POLICE]) {
+ fprintf(f, "\n");
+ tc_print_police(f, tb[TCA_BPF_POLICE]);
+ }
+
+ if (tb[TCA_BPF_ACT])
+ tc_print_action(f, tb[TCA_BPF_ACT], 0);
+
+ return 0;
+}
+
+struct filter_util bpf_filter_util = {
+ .id = "bpf",
+ .parse_fopt = bpf_parse_opt,
+ .print_fopt = bpf_print_opt,
+};
diff --git a/tc/f_cgroup.c b/tc/f_cgroup.c
new file mode 100644
index 0000000..633700e
--- /dev/null
+++ b/tc/f_cgroup.c
@@ -0,0 +1,114 @@
+/*
+ * f_cgroup.c Control Group Classifier
+ *
+ * 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: Thomas Graf <tgraf@infradead.org>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "utils.h"
+#include "tc_util.h"
+#include "m_ematch.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... cgroup [ match EMATCH_TREE ]\n");
+ fprintf(stderr, " [ action ACTION_SPEC ]\n");
+}
+
+static int cgroup_parse_opt(struct filter_util *qu, char *handle,
+ int argc, char **argv, struct nlmsghdr *n)
+{
+ struct tcmsg *t = NLMSG_DATA(n);
+ struct rtattr *tail;
+ long h = 0;
+
+ if (handle) {
+ h = strtol(handle, NULL, 0);
+ if (h == LONG_MIN || h == LONG_MAX) {
+ fprintf(stderr, "Illegal handle \"%s\", must be numeric.\n",
+ handle);
+ return -1;
+ }
+ }
+
+ t->tcm_handle = h;
+
+ tail = (struct rtattr *)(((void *)n)+NLMSG_ALIGN(n->nlmsg_len));
+ addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);
+
+ while (argc > 0) {
+ if (matches(*argv, "match") == 0) {
+ NEXT_ARG();
+ if (parse_ematch(&argc, &argv, TCA_CGROUP_EMATCHES, n)) {
+ fprintf(stderr, "Illegal \"ematch\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (parse_action(&argc, &argv, TCA_CGROUP_ACT, n)) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ continue;
+
+ } else if (matches(*argv, "police") == 0) {
+ NEXT_ARG();
+ if (parse_police(&argc, &argv, TCA_CGROUP_POLICE, n)) {
+ fprintf(stderr, "Illegal \"police\"\n");
+ return -1;
+ }
+ continue;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ }
+
+ tail->rta_len = (((void *)n)+n->nlmsg_len) - (void *)tail;
+ return 0;
+}
+
+static int cgroup_print_opt(struct filter_util *qu, FILE *f,
+ struct rtattr *opt, __u32 handle)
+{
+ struct rtattr *tb[TCA_CGROUP_MAX+1];
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_CGROUP_MAX, opt);
+
+ if (handle)
+ fprintf(f, "handle 0x%x ", handle);
+
+ if (tb[TCA_CGROUP_EMATCHES])
+ print_ematch(f, tb[TCA_CGROUP_EMATCHES]);
+
+ if (tb[TCA_CGROUP_POLICE]) {
+ fprintf(f, "\n");
+ tc_print_police(f, tb[TCA_CGROUP_POLICE]);
+ }
+
+ if (tb[TCA_CGROUP_ACT])
+ tc_print_action(f, tb[TCA_CGROUP_ACT], 0);
+
+ return 0;
+}
+
+struct filter_util cgroup_filter_util = {
+ .id = "cgroup",
+ .parse_fopt = cgroup_parse_opt,
+ .print_fopt = cgroup_print_opt,
+};
diff --git a/tc/f_flow.c b/tc/f_flow.c
new file mode 100644
index 0000000..9dd50df
--- /dev/null
+++ b/tc/f_flow.c
@@ -0,0 +1,358 @@
+/*
+ * f_flow.c Flow filter
+ *
+ * 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 <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "m_ematch.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... flow ...\n"
+ "\n"
+ " [mapping mode]: map key KEY [ OPS ] ...\n"
+ " [hashing mode]: hash keys KEY-LIST ... [ perturb SECS ]\n"
+ "\n"
+ " [ divisor NUM ] [ baseclass ID ] [ match EMATCH_TREE ]\n"
+ " [ action ACTION_SPEC ]\n"
+ "\n"
+ "KEY-LIST := [ KEY-LIST , ] KEY\n"
+ "KEY := [ src | dst | proto | proto-src | proto-dst | iif | priority |\n"
+ " mark | nfct | nfct-src | nfct-dst | nfct-proto-src |\n"
+ " nfct-proto-dst | rt-classid | sk-uid | sk-gid |\n"
+ " vlan-tag | rxhash ]\n"
+ "OPS := [ or NUM | and NUM | xor NUM | rshift NUM | addend NUM ]\n"
+ "ID := X:Y\n"
+ );
+}
+
+static const char *flow_keys[FLOW_KEY_MAX+1] = {
+ [FLOW_KEY_SRC] = "src",
+ [FLOW_KEY_DST] = "dst",
+ [FLOW_KEY_PROTO] = "proto",
+ [FLOW_KEY_PROTO_SRC] = "proto-src",
+ [FLOW_KEY_PROTO_DST] = "proto-dst",
+ [FLOW_KEY_IIF] = "iif",
+ [FLOW_KEY_PRIORITY] = "priority",
+ [FLOW_KEY_MARK] = "mark",
+ [FLOW_KEY_NFCT] = "nfct",
+ [FLOW_KEY_NFCT_SRC] = "nfct-src",
+ [FLOW_KEY_NFCT_DST] = "nfct-dst",
+ [FLOW_KEY_NFCT_PROTO_SRC] = "nfct-proto-src",
+ [FLOW_KEY_NFCT_PROTO_DST] = "nfct-proto-dst",
+ [FLOW_KEY_RTCLASSID] = "rt-classid",
+ [FLOW_KEY_SKUID] = "sk-uid",
+ [FLOW_KEY_SKGID] = "sk-gid",
+ [FLOW_KEY_VLAN_TAG] = "vlan-tag",
+ [FLOW_KEY_RXHASH] = "rxhash",
+};
+
+static int flow_parse_keys(__u32 *keys, __u32 *nkeys, char *argv)
+{
+ char *s, *sep;
+ unsigned int i;
+
+ *keys = 0;
+ *nkeys = 0;
+ s = argv;
+ while (s != NULL) {
+ sep = strchr(s, ',');
+ if (sep)
+ *sep = '\0';
+
+ for (i = 0; i <= FLOW_KEY_MAX; i++) {
+ if (matches(s, flow_keys[i]) == 0) {
+ *keys |= 1 << i;
+ (*nkeys)++;
+ break;
+ }
+ }
+ if (i > FLOW_KEY_MAX) {
+ fprintf(stderr, "Unknown flow key \"%s\"\n", s);
+ return -1;
+ }
+ s = sep ? sep + 1 : NULL;
+ }
+ return 0;
+}
+
+static void transfer_bitop(__u32 *mask, __u32 *xor, __u32 m, __u32 x)
+{
+ *xor = x ^ (*xor & m);
+ *mask &= m;
+}
+
+static int get_addend(__u32 *addend, char *argv, __u32 keys)
+{
+ inet_prefix addr;
+ int sign = 0;
+ __u32 tmp;
+
+ if (*argv == '-') {
+ sign = 1;
+ argv++;
+ }
+
+ if (get_u32(&tmp, argv, 0) == 0)
+ goto out;
+
+ if (keys & (FLOW_KEY_SRC | FLOW_KEY_DST |
+ FLOW_KEY_NFCT_SRC | FLOW_KEY_NFCT_DST) &&
+ get_addr(&addr, argv, AF_UNSPEC) == 0) {
+ switch (addr.family) {
+ case AF_INET:
+ tmp = ntohl(addr.data[0]);
+ goto out;
+ case AF_INET6:
+ tmp = ntohl(addr.data[3]);
+ goto out;
+ }
+ }
+
+ return -1;
+out:
+ if (sign)
+ tmp = -tmp;
+ *addend = tmp;
+ return 0;
+}
+
+static int flow_parse_opt(struct filter_util *fu, char *handle,
+ int argc, char **argv, struct nlmsghdr *n)
+{
+ struct tcmsg *t = NLMSG_DATA(n);
+ struct rtattr *tail;
+ __u32 mask = ~0U, xor = 0;
+ __u32 keys = 0, nkeys = 0;
+ __u32 mode = FLOW_MODE_MAP;
+ __u32 tmp;
+
+ if (handle) {
+ if (get_u32(&t->tcm_handle, handle, 0)) {
+ fprintf(stderr, "Illegal \"handle\"\n");
+ return -1;
+ }
+ }
+
+ tail = addattr_nest(n, 4096, TCA_OPTIONS);
+
+ while (argc > 0) {
+ if (matches(*argv, "map") == 0) {
+ mode = FLOW_MODE_MAP;
+ } else if (matches(*argv, "hash") == 0) {
+ mode = FLOW_MODE_HASH;
+ } else if (matches(*argv, "keys") == 0) {
+ NEXT_ARG();
+ if (flow_parse_keys(&keys, &nkeys, *argv))
+ return -1;
+ addattr32(n, 4096, TCA_FLOW_KEYS, keys);
+ } else if (matches(*argv, "and") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tmp, *argv, 0)) {
+ fprintf(stderr, "Illegal \"mask\"\n");
+ return -1;
+ }
+ transfer_bitop(&mask, &xor, tmp, 0);
+ } else if (matches(*argv, "or") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tmp, *argv, 0)) {
+ fprintf(stderr, "Illegal \"or\"\n");
+ return -1;
+ }
+ transfer_bitop(&mask, &xor, ~tmp, tmp);
+ } else if (matches(*argv, "xor") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tmp, *argv, 0)) {
+ fprintf(stderr, "Illegal \"xor\"\n");
+ return -1;
+ }
+ transfer_bitop(&mask, &xor, ~0, tmp);
+ } else if (matches(*argv, "rshift") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tmp, *argv, 0)) {
+ fprintf(stderr, "Illegal \"rshift\"\n");
+ return -1;
+ }
+ addattr32(n, 4096, TCA_FLOW_RSHIFT, tmp);
+ } else if (matches(*argv, "addend") == 0) {
+ NEXT_ARG();
+ if (get_addend(&tmp, *argv, keys)) {
+ fprintf(stderr, "Illegal \"addend\"\n");
+ return -1;
+ }
+ addattr32(n, 4096, TCA_FLOW_ADDEND, tmp);
+ } else if (matches(*argv, "divisor") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tmp, *argv, 0)) {
+ fprintf(stderr, "Illegal \"divisor\"\n");
+ return -1;
+ }
+ addattr32(n, 4096, TCA_FLOW_DIVISOR, tmp);
+ } else if (matches(*argv, "baseclass") == 0) {
+ NEXT_ARG();
+ if (get_tc_classid(&tmp, *argv) || TC_H_MIN(tmp) == 0) {
+ fprintf(stderr, "Illegal \"baseclass\"\n");
+ return -1;
+ }
+ addattr32(n, 4096, TCA_FLOW_BASECLASS, tmp);
+ } else if (matches(*argv, "perturb") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tmp, *argv, 0)) {
+ fprintf(stderr, "Illegal \"perturb\"\n");
+ return -1;
+ }
+ addattr32(n, 4096, TCA_FLOW_PERTURB, tmp);
+ } else if (matches(*argv, "police") == 0) {
+ NEXT_ARG();
+ if (parse_police(&argc, &argv, TCA_FLOW_POLICE, n)) {
+ fprintf(stderr, "Illegal \"police\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (parse_action(&argc, &argv, TCA_FLOW_ACT, n)) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "match") == 0) {
+ NEXT_ARG();
+ if (parse_ematch(&argc, &argv, TCA_FLOW_EMATCHES, n)) {
+ fprintf(stderr, "Illegal \"ematch\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argv++, argc--;
+ }
+
+ if (nkeys > 1 && mode != FLOW_MODE_HASH) {
+ fprintf(stderr, "Invalid mode \"map\" for multiple keys\n");
+ return -1;
+ }
+ addattr32(n, 4096, TCA_FLOW_MODE, mode);
+
+ if (mask != ~0 || xor != 0) {
+ addattr32(n, 4096, TCA_FLOW_MASK, mask);
+ addattr32(n, 4096, TCA_FLOW_XOR, xor);
+ }
+
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int flow_print_opt(struct filter_util *fu, FILE *f, struct rtattr *opt,
+ __u32 handle)
+{
+ struct rtattr *tb[TCA_FLOW_MAX+1];
+
+ SPRINT_BUF(b1);
+ unsigned int i;
+ __u32 mask = ~0, val = 0;
+
+ if (opt == NULL)
+ return -EINVAL;
+
+ parse_rtattr_nested(tb, TCA_FLOW_MAX, opt);
+
+ fprintf(f, "handle 0x%x ", handle);
+
+ if (tb[TCA_FLOW_MODE]) {
+ __u32 mode = rta_getattr_u32(tb[TCA_FLOW_MODE]);
+
+ switch (mode) {
+ case FLOW_MODE_MAP:
+ fprintf(f, "map ");
+ break;
+ case FLOW_MODE_HASH:
+ fprintf(f, "hash ");
+ break;
+ }
+ }
+
+ if (tb[TCA_FLOW_KEYS]) {
+ __u32 keymask = rta_getattr_u32(tb[TCA_FLOW_KEYS]);
+ char *sep = "";
+
+ fprintf(f, "keys ");
+ for (i = 0; i <= FLOW_KEY_MAX; i++) {
+ if (keymask & (1 << i)) {
+ fprintf(f, "%s%s", sep, flow_keys[i]);
+ sep = ",";
+ }
+ }
+ fprintf(f, " ");
+ }
+
+ if (tb[TCA_FLOW_MASK])
+ mask = rta_getattr_u32(tb[TCA_FLOW_MASK]);
+ if (tb[TCA_FLOW_XOR])
+ val = rta_getattr_u32(tb[TCA_FLOW_XOR]);
+
+ if (mask != ~0 || val != 0) {
+ __u32 or = (mask & val) ^ val;
+ __u32 xor = mask & val;
+
+ if (mask != ~0)
+ fprintf(f, "and 0x%.8x ", mask);
+ if (xor != 0)
+ fprintf(f, "xor 0x%.8x ", xor);
+ if (or != 0)
+ fprintf(f, "or 0x%.8x ", or);
+ }
+
+ if (tb[TCA_FLOW_RSHIFT])
+ fprintf(f, "rshift %u ",
+ rta_getattr_u32(tb[TCA_FLOW_RSHIFT]));
+ if (tb[TCA_FLOW_ADDEND])
+ fprintf(f, "addend 0x%x ",
+ rta_getattr_u32(tb[TCA_FLOW_ADDEND]));
+
+ if (tb[TCA_FLOW_DIVISOR])
+ fprintf(f, "divisor %u ",
+ rta_getattr_u32(tb[TCA_FLOW_DIVISOR]));
+ if (tb[TCA_FLOW_BASECLASS])
+ fprintf(f, "baseclass %s ",
+ sprint_tc_classid(rta_getattr_u32(tb[TCA_FLOW_BASECLASS]), b1));
+
+ if (tb[TCA_FLOW_PERTURB])
+ fprintf(f, "perturb %usec ",
+ rta_getattr_u32(tb[TCA_FLOW_PERTURB]));
+
+ if (tb[TCA_FLOW_EMATCHES])
+ print_ematch(f, tb[TCA_FLOW_EMATCHES]);
+ if (tb[TCA_FLOW_POLICE])
+ tc_print_police(f, tb[TCA_FLOW_POLICE]);
+ if (tb[TCA_FLOW_ACT]) {
+ fprintf(f, "\n");
+ tc_print_action(f, tb[TCA_FLOW_ACT], 0);
+ }
+ return 0;
+}
+
+struct filter_util flow_filter_util = {
+ .id = "flow",
+ .parse_fopt = flow_parse_opt,
+ .print_fopt = flow_print_opt,
+};
diff --git a/tc/f_flower.c b/tc/f_flower.c
new file mode 100644
index 0000000..1b518ef
--- /dev/null
+++ b/tc/f_flower.c
@@ -0,0 +1,2247 @@
+/*
+ * f_flower.c Flower Classifier
+ *
+ * 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: Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <net/if.h>
+#include <linux/limits.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/tc_act/tc_vlan.h>
+#include <linux/mpls.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "rt_names.h"
+
+enum flower_matching_flags {
+ FLOWER_IP_FLAGS,
+};
+
+enum flower_endpoint {
+ FLOWER_ENDPOINT_SRC,
+ FLOWER_ENDPOINT_DST
+};
+
+enum flower_icmp_field {
+ FLOWER_ICMP_FIELD_TYPE,
+ FLOWER_ICMP_FIELD_CODE
+};
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... flower [ MATCH-LIST ] [ verbose ]\n"
+ " [ skip_sw | skip_hw ]\n"
+ " [ action ACTION-SPEC ] [ classid CLASSID ]\n"
+ "\n"
+ "Where: MATCH-LIST := [ MATCH-LIST ] MATCH\n"
+ " MATCH := { indev DEV-NAME |\n"
+ " vlan_id VID |\n"
+ " vlan_prio PRIORITY |\n"
+ " vlan_ethtype [ ipv4 | ipv6 | ETH-TYPE ] |\n"
+ " cvlan_id VID |\n"
+ " cvlan_prio PRIORITY |\n"
+ " cvlan_ethtype [ ipv4 | ipv6 | ETH-TYPE ] |\n"
+ " dst_mac MASKED-LLADDR |\n"
+ " src_mac MASKED-LLADDR |\n"
+ " ip_proto [tcp | udp | sctp | icmp | icmpv6 | IP-PROTO ] |\n"
+ " ip_tos MASKED-IP_TOS |\n"
+ " ip_ttl MASKED-IP_TTL |\n"
+ " mpls_label LABEL |\n"
+ " mpls_tc TC |\n"
+ " mpls_bos BOS |\n"
+ " mpls_ttl TTL |\n"
+ " dst_ip PREFIX |\n"
+ " src_ip PREFIX |\n"
+ " dst_port PORT-NUMBER |\n"
+ " src_port PORT-NUMBER |\n"
+ " tcp_flags MASKED-TCP_FLAGS |\n"
+ " type MASKED-ICMP-TYPE |\n"
+ " code MASKED-ICMP-CODE |\n"
+ " arp_tip IPV4-PREFIX |\n"
+ " arp_sip IPV4-PREFIX |\n"
+ " arp_op [ request | reply | OP ] |\n"
+ " arp_tha MASKED-LLADDR |\n"
+ " arp_sha MASKED-LLADDR |\n"
+ " enc_dst_ip [ IPV4-ADDR | IPV6-ADDR ] |\n"
+ " enc_src_ip [ IPV4-ADDR | IPV6-ADDR ] |\n"
+ " enc_key_id [ KEY-ID ] |\n"
+ " enc_tos MASKED-IP_TOS |\n"
+ " enc_ttl MASKED-IP_TTL |\n"
+ " geneve_opts MASKED-OPTIONS |\n"
+ " ip_flags IP-FLAGS | \n"
+ " enc_dst_port [ port_number ] |\n"
+ " ct_state MASKED_CT_STATE |\n"
+ " ct_label MASKED_CT_LABEL |\n"
+ " ct_mark MASKED_CT_MARK |\n"
+ " ct_zone MASKED_CT_ZONE }\n"
+ " FILTERID := X:Y:Z\n"
+ " MASKED_LLADDR := { LLADDR | LLADDR/MASK | LLADDR/BITS }\n"
+ " MASKED_CT_STATE := combination of {+|-} and flags trk,est,new\n"
+ " ACTION-SPEC := ... look at individual actions\n"
+ "\n"
+ "NOTE: CLASSID, IP-PROTO are parsed as hexadecimal input.\n"
+ "NOTE: There can be only used one mask per one prio. If user needs\n"
+ " to specify different mask, he has to use different prio.\n");
+}
+
+static int flower_parse_eth_addr(char *str, int addr_type, int mask_type,
+ struct nlmsghdr *n)
+{
+ int ret, err = -1;
+ char addr[ETH_ALEN], *slash;
+
+ slash = strchr(str, '/');
+ if (slash)
+ *slash = '\0';
+
+ ret = ll_addr_a2n(addr, sizeof(addr), str);
+ if (ret < 0)
+ goto err;
+ addattr_l(n, MAX_MSG, addr_type, addr, sizeof(addr));
+
+ if (slash) {
+ unsigned bits;
+
+ if (!get_unsigned(&bits, slash + 1, 10)) {
+ uint64_t mask;
+
+ /* Extra 16 bit shift to push mac address into
+ * high bits of uint64_t
+ */
+ mask = htonll(0xffffffffffffULL << (16 + 48 - bits));
+ memcpy(addr, &mask, ETH_ALEN);
+ } else {
+ ret = ll_addr_a2n(addr, sizeof(addr), slash + 1);
+ if (ret < 0)
+ goto err;
+ }
+ } else {
+ memset(addr, 0xff, ETH_ALEN);
+ }
+ addattr_l(n, MAX_MSG, mask_type, addr, sizeof(addr));
+
+ err = 0;
+err:
+ if (slash)
+ *slash = '/';
+ return err;
+}
+
+static bool eth_type_vlan(__be16 ethertype)
+{
+ return ethertype == htons(ETH_P_8021Q) ||
+ ethertype == htons(ETH_P_8021AD);
+}
+
+static int flower_parse_vlan_eth_type(char *str, __be16 eth_type, int type,
+ __be16 *p_vlan_eth_type,
+ struct nlmsghdr *n)
+{
+ __be16 vlan_eth_type;
+
+ if (!eth_type_vlan(eth_type)) {
+ fprintf(stderr, "Can't set \"%s\" if ethertype isn't 802.1Q or 802.1AD\n",
+ type == TCA_FLOWER_KEY_VLAN_ETH_TYPE ? "vlan_ethtype" : "cvlan_ethtype");
+ return -1;
+ }
+
+ if (ll_proto_a2n(&vlan_eth_type, str))
+ invarg("invalid vlan_ethtype", str);
+ addattr16(n, MAX_MSG, type, vlan_eth_type);
+ *p_vlan_eth_type = vlan_eth_type;
+ return 0;
+}
+
+struct flag_to_string {
+ int flag;
+ enum flower_matching_flags type;
+ char *string;
+};
+
+static struct flag_to_string flags_str[] = {
+ { TCA_FLOWER_KEY_FLAGS_IS_FRAGMENT, FLOWER_IP_FLAGS, "frag" },
+ { TCA_FLOWER_KEY_FLAGS_FRAG_IS_FIRST, FLOWER_IP_FLAGS, "firstfrag" },
+};
+
+static int flower_parse_matching_flags(char *str,
+ enum flower_matching_flags type,
+ __u32 *mtf, __u32 *mtf_mask)
+{
+ char *token;
+ bool no;
+ bool found;
+ int i;
+
+ token = strtok(str, "/");
+
+ while (token) {
+ if (!strncmp(token, "no", 2)) {
+ no = true;
+ token += 2;
+ } else
+ no = false;
+
+ found = false;
+ for (i = 0; i < ARRAY_SIZE(flags_str); i++) {
+ if (type != flags_str[i].type)
+ continue;
+
+ if (!strcmp(token, flags_str[i].string)) {
+ if (no)
+ *mtf &= ~flags_str[i].flag;
+ else
+ *mtf |= flags_str[i].flag;
+
+ *mtf_mask |= flags_str[i].flag;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return -1;
+
+ token = strtok(NULL, "/");
+ }
+
+ return 0;
+}
+
+static int flower_parse_u16(char *str, int value_type, int mask_type,
+ struct nlmsghdr *n)
+{
+ __u16 value, mask;
+ char *slash;
+
+ slash = strchr(str, '/');
+ if (slash)
+ *slash = '\0';
+
+ if (get_u16(&value, str, 0))
+ return -1;
+
+ if (slash) {
+ if (get_u16(&mask, slash + 1, 0))
+ return -1;
+ } else {
+ mask = UINT16_MAX;
+ }
+
+ addattr16(n, MAX_MSG, value_type, value);
+ addattr16(n, MAX_MSG, mask_type, mask);
+
+ return 0;
+}
+
+static int flower_parse_u32(char *str, int value_type, int mask_type,
+ struct nlmsghdr *n)
+{
+ __u32 value, mask;
+ char *slash;
+
+ slash = strchr(str, '/');
+ if (slash)
+ *slash = '\0';
+
+ if (get_u32(&value, str, 0))
+ return -1;
+
+ if (slash) {
+ if (get_u32(&mask, slash + 1, 0))
+ return -1;
+ } else {
+ mask = UINT32_MAX;
+ }
+
+ addattr32(n, MAX_MSG, value_type, value);
+ addattr32(n, MAX_MSG, mask_type, mask);
+
+ return 0;
+}
+
+static int flower_parse_ct_mark(char *str, struct nlmsghdr *n)
+{
+ return flower_parse_u32(str,
+ TCA_FLOWER_KEY_CT_MARK,
+ TCA_FLOWER_KEY_CT_MARK_MASK,
+ n);
+}
+
+static int flower_parse_ct_zone(char *str, struct nlmsghdr *n)
+{
+ return flower_parse_u16(str,
+ TCA_FLOWER_KEY_CT_ZONE,
+ TCA_FLOWER_KEY_CT_ZONE_MASK,
+ n);
+}
+
+static int flower_parse_ct_labels(char *str, struct nlmsghdr *n)
+{
+#define LABELS_SIZE 16
+ uint8_t labels[LABELS_SIZE], lmask[LABELS_SIZE];
+ char *slash, *mask = NULL;
+ size_t slen, slen_mask = 0;
+
+ slash = index(str, '/');
+ if (slash) {
+ *slash = 0;
+ mask = slash + 1;
+ slen_mask = strlen(mask);
+ }
+
+ slen = strlen(str);
+ if (slen > LABELS_SIZE * 2 || slen_mask > LABELS_SIZE * 2) {
+ char errmsg[128];
+
+ snprintf(errmsg, sizeof(errmsg),
+ "%zd Max allowed size %d",
+ slen, LABELS_SIZE*2);
+ invarg(errmsg, str);
+ }
+
+ if (hex2mem(str, labels, slen / 2) < 0)
+ invarg("labels must be a hex string\n", str);
+ addattr_l(n, MAX_MSG, TCA_FLOWER_KEY_CT_LABELS, labels, slen / 2);
+
+ if (mask) {
+ if (hex2mem(mask, lmask, slen_mask / 2) < 0)
+ invarg("labels mask must be a hex string\n", mask);
+ } else {
+ memset(lmask, 0xff, sizeof(lmask));
+ slen_mask = sizeof(lmask) * 2;
+ }
+ addattr_l(n, MAX_MSG, TCA_FLOWER_KEY_CT_LABELS_MASK, lmask,
+ slen_mask / 2);
+
+ return 0;
+}
+
+static struct flower_ct_states {
+ char *str;
+ int flag;
+} flower_ct_states[] = {
+ { "trk", TCA_FLOWER_KEY_CT_FLAGS_TRACKED },
+ { "new", TCA_FLOWER_KEY_CT_FLAGS_NEW },
+ { "est", TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED },
+};
+
+static int flower_parse_ct_state(char *str, struct nlmsghdr *n)
+{
+ int flags = 0, mask = 0, len, i;
+ bool p;
+
+ while (*str != '\0') {
+ if (*str == '+')
+ p = true;
+ else if (*str == '-')
+ p = false;
+ else
+ return -1;
+
+ for (i = 0; i < ARRAY_SIZE(flower_ct_states); i++) {
+ len = strlen(flower_ct_states[i].str);
+ if (strncmp(str + 1, flower_ct_states[i].str, len))
+ continue;
+
+ if (p)
+ flags |= flower_ct_states[i].flag;
+ mask |= flower_ct_states[i].flag;
+ break;
+ }
+
+ if (i == ARRAY_SIZE(flower_ct_states))
+ return -1;
+
+ str += len + 1;
+ }
+
+ addattr16(n, MAX_MSG, TCA_FLOWER_KEY_CT_STATE, flags);
+ addattr16(n, MAX_MSG, TCA_FLOWER_KEY_CT_STATE_MASK, mask);
+ return 0;
+}
+
+static int flower_parse_ip_proto(char *str, __be16 eth_type, int type,
+ __u8 *p_ip_proto, struct nlmsghdr *n)
+{
+ int ret;
+ __u8 ip_proto;
+
+ if (eth_type != htons(ETH_P_IP) && eth_type != htons(ETH_P_IPV6))
+ goto err;
+
+ if (matches(str, "tcp") == 0) {
+ ip_proto = IPPROTO_TCP;
+ } else if (matches(str, "udp") == 0) {
+ ip_proto = IPPROTO_UDP;
+ } else if (matches(str, "sctp") == 0) {
+ ip_proto = IPPROTO_SCTP;
+ } else if (matches(str, "icmp") == 0) {
+ if (eth_type != htons(ETH_P_IP))
+ goto err;
+ ip_proto = IPPROTO_ICMP;
+ } else if (matches(str, "icmpv6") == 0) {
+ if (eth_type != htons(ETH_P_IPV6))
+ goto err;
+ ip_proto = IPPROTO_ICMPV6;
+ } else {
+ ret = get_u8(&ip_proto, str, 16);
+ if (ret)
+ return -1;
+ }
+ addattr8(n, MAX_MSG, type, ip_proto);
+ *p_ip_proto = ip_proto;
+ return 0;
+
+err:
+ fprintf(stderr, "Illegal \"eth_type\" for ip proto\n");
+ return -1;
+}
+
+static int __flower_parse_ip_addr(char *str, int family,
+ int addr4_type, int mask4_type,
+ int addr6_type, int mask6_type,
+ struct nlmsghdr *n)
+{
+ int ret;
+ inet_prefix addr;
+ int bits;
+ int i;
+
+ ret = get_prefix(&addr, str, family);
+ if (ret)
+ return -1;
+
+ if (family && (addr.family != family)) {
+ fprintf(stderr, "Illegal \"eth_type\" for ip address\n");
+ return -1;
+ }
+
+ addattr_l(n, MAX_MSG, addr.family == AF_INET ? addr4_type : addr6_type,
+ addr.data, addr.bytelen);
+
+ memset(addr.data, 0xff, addr.bytelen);
+ bits = addr.bitlen;
+ for (i = 0; i < addr.bytelen / 4; i++) {
+ if (!bits) {
+ addr.data[i] = 0;
+ } else if (bits / 32 >= 1) {
+ bits -= 32;
+ } else {
+ addr.data[i] <<= 32 - bits;
+ addr.data[i] = htonl(addr.data[i]);
+ bits = 0;
+ }
+ }
+
+ addattr_l(n, MAX_MSG, addr.family == AF_INET ? mask4_type : mask6_type,
+ addr.data, addr.bytelen);
+
+ return 0;
+}
+
+static int flower_parse_ip_addr(char *str, __be16 eth_type,
+ int addr4_type, int mask4_type,
+ int addr6_type, int mask6_type,
+ struct nlmsghdr *n)
+{
+ int family;
+
+ if (eth_type == htons(ETH_P_IP)) {
+ family = AF_INET;
+ } else if (eth_type == htons(ETH_P_IPV6)) {
+ family = AF_INET6;
+ } else if (!eth_type) {
+ family = AF_UNSPEC;
+ } else {
+ return -1;
+ }
+
+ return __flower_parse_ip_addr(str, family, addr4_type, mask4_type,
+ addr6_type, mask6_type, n);
+}
+
+static bool flower_eth_type_arp(__be16 eth_type)
+{
+ return eth_type == htons(ETH_P_ARP) || eth_type == htons(ETH_P_RARP);
+}
+
+static int flower_parse_arp_ip_addr(char *str, __be16 eth_type,
+ int addr_type, int mask_type,
+ struct nlmsghdr *n)
+{
+ if (!flower_eth_type_arp(eth_type))
+ return -1;
+
+ return __flower_parse_ip_addr(str, AF_INET, addr_type, mask_type,
+ TCA_FLOWER_UNSPEC, TCA_FLOWER_UNSPEC, n);
+}
+
+static int flower_parse_u8(char *str, int value_type, int mask_type,
+ int (*value_from_name)(const char *str,
+ __u8 *value),
+ bool (*value_validate)(__u8 value),
+ struct nlmsghdr *n)
+{
+ char *slash;
+ int ret, err = -1;
+ __u8 value, mask;
+
+ slash = strchr(str, '/');
+ if (slash)
+ *slash = '\0';
+
+ ret = value_from_name ? value_from_name(str, &value) : -1;
+ if (ret < 0) {
+ ret = get_u8(&value, str, 10);
+ if (ret)
+ goto err;
+ }
+
+ if (value_validate && !value_validate(value))
+ goto err;
+
+ if (slash) {
+ ret = get_u8(&mask, slash + 1, 10);
+ if (ret)
+ goto err;
+ }
+ else {
+ mask = UINT8_MAX;
+ }
+
+ addattr8(n, MAX_MSG, value_type, value);
+ addattr8(n, MAX_MSG, mask_type, mask);
+
+ err = 0;
+err:
+ if (slash)
+ *slash = '/';
+ return err;
+}
+
+static const char *flower_print_arp_op_to_name(__u8 op)
+{
+ switch (op) {
+ case ARPOP_REQUEST:
+ return "request";
+ case ARPOP_REPLY:
+ return "reply";
+ default:
+ return NULL;
+ }
+}
+
+static int flower_arp_op_from_name(const char *name, __u8 *op)
+{
+ if (!strcmp(name, "request"))
+ *op = ARPOP_REQUEST;
+ else if (!strcmp(name, "reply"))
+ *op = ARPOP_REPLY;
+ else
+ return -1;
+
+ return 0;
+}
+
+static bool flow_arp_op_validate(__u8 op)
+{
+ return !op || op == ARPOP_REQUEST || op == ARPOP_REPLY;
+}
+
+static int flower_parse_arp_op(char *str, __be16 eth_type,
+ int op_type, int mask_type,
+ struct nlmsghdr *n)
+{
+ if (!flower_eth_type_arp(eth_type))
+ return -1;
+
+ return flower_parse_u8(str, op_type, mask_type, flower_arp_op_from_name,
+ flow_arp_op_validate, n);
+}
+
+static int flower_icmp_attr_type(__be16 eth_type, __u8 ip_proto,
+ enum flower_icmp_field field)
+{
+ if (eth_type == htons(ETH_P_IP) && ip_proto == IPPROTO_ICMP)
+ return field == FLOWER_ICMP_FIELD_CODE ?
+ TCA_FLOWER_KEY_ICMPV4_CODE :
+ TCA_FLOWER_KEY_ICMPV4_TYPE;
+ else if (eth_type == htons(ETH_P_IPV6) && ip_proto == IPPROTO_ICMPV6)
+ return field == FLOWER_ICMP_FIELD_CODE ?
+ TCA_FLOWER_KEY_ICMPV6_CODE :
+ TCA_FLOWER_KEY_ICMPV6_TYPE;
+
+ return -1;
+}
+
+static int flower_icmp_attr_mask_type(__be16 eth_type, __u8 ip_proto,
+ enum flower_icmp_field field)
+{
+ if (eth_type == htons(ETH_P_IP) && ip_proto == IPPROTO_ICMP)
+ return field == FLOWER_ICMP_FIELD_CODE ?
+ TCA_FLOWER_KEY_ICMPV4_CODE_MASK :
+ TCA_FLOWER_KEY_ICMPV4_TYPE_MASK;
+ else if (eth_type == htons(ETH_P_IPV6) && ip_proto == IPPROTO_ICMPV6)
+ return field == FLOWER_ICMP_FIELD_CODE ?
+ TCA_FLOWER_KEY_ICMPV6_CODE_MASK :
+ TCA_FLOWER_KEY_ICMPV6_TYPE_MASK;
+
+ return -1;
+}
+
+static int flower_parse_icmp(char *str, __u16 eth_type, __u8 ip_proto,
+ enum flower_icmp_field field, struct nlmsghdr *n)
+{
+ int value_type, mask_type;
+
+ value_type = flower_icmp_attr_type(eth_type, ip_proto, field);
+ mask_type = flower_icmp_attr_mask_type(eth_type, ip_proto, field);
+ if (value_type < 0 || mask_type < 0)
+ return -1;
+
+ return flower_parse_u8(str, value_type, mask_type, NULL, NULL, n);
+}
+
+static int flower_port_attr_type(__u8 ip_proto, enum flower_endpoint endpoint)
+{
+ if (ip_proto == IPPROTO_TCP)
+ return endpoint == FLOWER_ENDPOINT_SRC ?
+ TCA_FLOWER_KEY_TCP_SRC :
+ TCA_FLOWER_KEY_TCP_DST;
+ else if (ip_proto == IPPROTO_UDP)
+ return endpoint == FLOWER_ENDPOINT_SRC ?
+ TCA_FLOWER_KEY_UDP_SRC :
+ TCA_FLOWER_KEY_UDP_DST;
+ else if (ip_proto == IPPROTO_SCTP)
+ return endpoint == FLOWER_ENDPOINT_SRC ?
+ TCA_FLOWER_KEY_SCTP_SRC :
+ TCA_FLOWER_KEY_SCTP_DST;
+ else
+ return -1;
+}
+
+static int flower_port_range_attr_type(__u8 ip_proto, enum flower_endpoint type,
+ __be16 *min_port_type,
+ __be16 *max_port_type)
+{
+ if (ip_proto == IPPROTO_TCP || ip_proto == IPPROTO_UDP ||
+ ip_proto == IPPROTO_SCTP) {
+ if (type == FLOWER_ENDPOINT_SRC) {
+ *min_port_type = TCA_FLOWER_KEY_PORT_SRC_MIN;
+ *max_port_type = TCA_FLOWER_KEY_PORT_SRC_MAX;
+ } else {
+ *min_port_type = TCA_FLOWER_KEY_PORT_DST_MIN;
+ *max_port_type = TCA_FLOWER_KEY_PORT_DST_MAX;
+ }
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
+/* parse range args in format 10-20 */
+static int parse_range(char *str, __be16 *min, __be16 *max)
+{
+ char *sep;
+
+ sep = strchr(str, '-');
+ if (sep) {
+ *sep = '\0';
+
+ if (get_be16(min, str, 10))
+ return -1;
+
+ if (get_be16(max, sep + 1, 10))
+ return -1;
+ } else {
+ if (get_be16(min, str, 10))
+ return -1;
+ }
+ return 0;
+}
+
+static int flower_parse_port(char *str, __u8 ip_proto,
+ enum flower_endpoint endpoint,
+ struct nlmsghdr *n)
+{
+ __be16 min = 0;
+ __be16 max = 0;
+ int ret;
+
+ ret = parse_range(str, &min, &max);
+ if (ret)
+ return -1;
+
+ if (min && max) {
+ __be16 min_port_type, max_port_type;
+
+ if (max <= min) {
+ fprintf(stderr, "max value should be greater than min value\n");
+ return -1;
+ }
+ if (flower_port_range_attr_type(ip_proto, endpoint,
+ &min_port_type, &max_port_type))
+ return -1;
+
+ addattr16(n, MAX_MSG, min_port_type, min);
+ addattr16(n, MAX_MSG, max_port_type, max);
+ } else if (min && !max) {
+ int type;
+
+ type = flower_port_attr_type(ip_proto, endpoint);
+ if (type < 0)
+ return -1;
+ addattr16(n, MAX_MSG, type, min);
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
+#define TCP_FLAGS_MAX_MASK 0xfff
+
+static int flower_parse_tcp_flags(char *str, int flags_type, int mask_type,
+ struct nlmsghdr *n)
+{
+ char *slash;
+ int ret, err = -1;
+ __u16 flags;
+
+ slash = strchr(str, '/');
+ if (slash)
+ *slash = '\0';
+
+ ret = get_u16(&flags, str, 16);
+ if (ret < 0 || flags & ~TCP_FLAGS_MAX_MASK)
+ goto err;
+
+ addattr16(n, MAX_MSG, flags_type, htons(flags));
+
+ if (slash) {
+ ret = get_u16(&flags, slash + 1, 16);
+ if (ret < 0 || flags & ~TCP_FLAGS_MAX_MASK)
+ goto err;
+ } else {
+ flags = TCP_FLAGS_MAX_MASK;
+ }
+ addattr16(n, MAX_MSG, mask_type, htons(flags));
+
+ err = 0;
+err:
+ if (slash)
+ *slash = '/';
+ return err;
+}
+
+static int flower_parse_ip_tos_ttl(char *str, int key_type, int mask_type,
+ struct nlmsghdr *n)
+{
+ char *slash;
+ int ret, err = -1;
+ __u8 tos_ttl;
+
+ slash = strchr(str, '/');
+ if (slash)
+ *slash = '\0';
+
+ ret = get_u8(&tos_ttl, str, 10);
+ if (ret < 0)
+ ret = get_u8(&tos_ttl, str, 16);
+ if (ret < 0)
+ goto err;
+
+ addattr8(n, MAX_MSG, key_type, tos_ttl);
+
+ if (slash) {
+ ret = get_u8(&tos_ttl, slash + 1, 16);
+ if (ret < 0)
+ goto err;
+ } else {
+ tos_ttl = 0xff;
+ }
+ addattr8(n, MAX_MSG, mask_type, tos_ttl);
+
+ err = 0;
+err:
+ if (slash)
+ *slash = '/';
+ return err;
+}
+
+static int flower_parse_key_id(const char *str, int type, struct nlmsghdr *n)
+{
+ int ret;
+ __be32 key_id;
+
+ ret = get_be32(&key_id, str, 10);
+ if (!ret)
+ addattr32(n, MAX_MSG, type, key_id);
+
+ return ret;
+}
+
+static int flower_parse_enc_port(char *str, int type, struct nlmsghdr *n)
+{
+ int ret;
+ __be16 port;
+
+ ret = get_be16(&port, str, 10);
+ if (ret)
+ return -1;
+
+ addattr16(n, MAX_MSG, type, port);
+
+ return 0;
+}
+
+static int flower_parse_geneve_opts(char *str, struct nlmsghdr *n)
+{
+ struct rtattr *nest;
+ char *token;
+ int i, err;
+
+ nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS_GENEVE);
+
+ i = 1;
+ token = strsep(&str, ":");
+ while (token) {
+ switch (i) {
+ case TCA_FLOWER_KEY_ENC_OPT_GENEVE_CLASS:
+ {
+ __be16 opt_class;
+
+ if (!strlen(token))
+ break;
+ err = get_be16(&opt_class, token, 16);
+ if (err)
+ return err;
+
+ addattr16(n, MAX_MSG, i, opt_class);
+ break;
+ }
+ case TCA_FLOWER_KEY_ENC_OPT_GENEVE_TYPE:
+ {
+ __u8 opt_type;
+
+ if (!strlen(token))
+ break;
+ err = get_u8(&opt_type, token, 16);
+ if (err)
+ return err;
+
+ addattr8(n, MAX_MSG, i, opt_type);
+ break;
+ }
+ case TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA:
+ {
+ size_t token_len = strlen(token);
+ __u8 *opts;
+
+ if (!token_len)
+ break;
+ opts = malloc(token_len / 2);
+ if (!opts)
+ return -1;
+ if (hex2mem(token, opts, token_len / 2) < 0) {
+ free(opts);
+ return -1;
+ }
+ addattr_l(n, MAX_MSG, i, opts, token_len / 2);
+ free(opts);
+
+ break;
+ }
+ default:
+ fprintf(stderr, "Unknown \"geneve_opts\" type\n");
+ return -1;
+ }
+
+ token = strsep(&str, ":");
+ i++;
+ }
+ addattr_nest_end(n, nest);
+
+ return 0;
+}
+
+static int flower_parse_enc_opt_part(char *str, struct nlmsghdr *n)
+{
+ char *token;
+ int err;
+
+ token = strsep(&str, ",");
+ while (token) {
+ err = flower_parse_geneve_opts(token, n);
+ if (err)
+ return err;
+
+ token = strsep(&str, ",");
+ }
+
+ return 0;
+}
+
+static int flower_check_enc_opt_key(char *key)
+{
+ int key_len, col_cnt = 0;
+
+ key_len = strlen(key);
+ while ((key = strchr(key, ':'))) {
+ if (strlen(key) == key_len)
+ return -1;
+
+ key_len = strlen(key) - 1;
+ col_cnt++;
+ key++;
+ }
+
+ if (col_cnt != 2 || !key_len)
+ return -1;
+
+ return 0;
+}
+
+static int flower_parse_enc_opts(char *str, struct nlmsghdr *n)
+{
+ char key[XATTR_SIZE_MAX], mask[XATTR_SIZE_MAX];
+ int data_len, key_len, mask_len, err;
+ char *token, *slash;
+ struct rtattr *nest;
+
+ key_len = 0;
+ mask_len = 0;
+ token = strsep(&str, ",");
+ while (token) {
+ slash = strchr(token, '/');
+ if (slash)
+ *slash = '\0';
+
+ if ((key_len + strlen(token) > XATTR_SIZE_MAX) ||
+ flower_check_enc_opt_key(token))
+ return -1;
+
+ strcpy(&key[key_len], token);
+ key_len += strlen(token) + 1;
+ key[key_len - 1] = ',';
+
+ if (!slash) {
+ /* Pad out mask when not provided */
+ if (mask_len + strlen(token) > XATTR_SIZE_MAX)
+ return -1;
+
+ data_len = strlen(rindex(token, ':'));
+ sprintf(&mask[mask_len], "ffff:ff:");
+ mask_len += 8;
+ memset(&mask[mask_len], 'f', data_len - 1);
+ mask_len += data_len;
+ mask[mask_len - 1] = ',';
+ token = strsep(&str, ",");
+ continue;
+ }
+
+ if (mask_len + strlen(slash + 1) > XATTR_SIZE_MAX)
+ return -1;
+
+ strcpy(&mask[mask_len], slash + 1);
+ mask_len += strlen(slash + 1) + 1;
+ mask[mask_len - 1] = ',';
+
+ *slash = '/';
+ token = strsep(&str, ",");
+ }
+ key[key_len - 1] = '\0';
+ mask[mask_len - 1] = '\0';
+
+ nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS);
+ err = flower_parse_enc_opt_part(key, n);
+ if (err)
+ return err;
+ addattr_nest_end(n, nest);
+
+ nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS_MASK);
+ err = flower_parse_enc_opt_part(mask, n);
+ if (err)
+ return err;
+ addattr_nest_end(n, nest);
+
+ return 0;
+}
+
+static int flower_parse_opt(struct filter_util *qu, char *handle,
+ int argc, char **argv, struct nlmsghdr *n)
+{
+ int ret;
+ struct tcmsg *t = NLMSG_DATA(n);
+ struct rtattr *tail;
+ __be16 eth_type = TC_H_MIN(t->tcm_info);
+ __be16 vlan_ethtype = 0;
+ __be16 cvlan_ethtype = 0;
+ __u8 ip_proto = 0xff;
+ __u32 flags = 0;
+ __u32 mtf = 0;
+ __u32 mtf_mask = 0;
+
+ if (handle) {
+ ret = get_u32(&t->tcm_handle, handle, 0);
+ if (ret) {
+ fprintf(stderr, "Illegal \"handle\"\n");
+ return -1;
+ }
+ }
+
+ tail = (struct rtattr *) (((void *) n) + NLMSG_ALIGN(n->nlmsg_len));
+ addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);
+
+ if (argc == 0) {
+ /*at minimal we will match all ethertype packets */
+ goto parse_done;
+ }
+
+ while (argc > 0) {
+ if (matches(*argv, "classid") == 0 ||
+ matches(*argv, "flowid") == 0) {
+ unsigned int handle;
+
+ NEXT_ARG();
+ ret = get_tc_classid(&handle, *argv);
+ if (ret) {
+ fprintf(stderr, "Illegal \"classid\"\n");
+ return -1;
+ }
+ addattr_l(n, MAX_MSG, TCA_FLOWER_CLASSID, &handle, 4);
+ } else if (matches(*argv, "hw_tc") == 0) {
+ unsigned int handle;
+ __u32 tc;
+ char *end;
+
+ NEXT_ARG();
+ tc = strtoul(*argv, &end, 0);
+ if (*end) {
+ fprintf(stderr, "Illegal TC index\n");
+ return -1;
+ }
+ if (tc >= TC_QOPT_MAX_QUEUE) {
+ fprintf(stderr, "TC index exceeds max range\n");
+ return -1;
+ }
+ handle = TC_H_MAKE(TC_H_MAJ(t->tcm_parent),
+ TC_H_MIN(tc + TC_H_MIN_PRIORITY));
+ addattr_l(n, MAX_MSG, TCA_FLOWER_CLASSID, &handle,
+ sizeof(handle));
+ } else if (matches(*argv, "ip_flags") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_matching_flags(*argv,
+ FLOWER_IP_FLAGS,
+ &mtf,
+ &mtf_mask);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"ip_flags\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "verbose") == 0) {
+ flags |= TCA_CLS_FLAGS_VERBOSE;
+ } else if (matches(*argv, "skip_hw") == 0) {
+ flags |= TCA_CLS_FLAGS_SKIP_HW;
+ } else if (matches(*argv, "skip_sw") == 0) {
+ flags |= TCA_CLS_FLAGS_SKIP_SW;
+ } else if (matches(*argv, "ct_state") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ct_state(*argv, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"ct_state\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "ct_zone") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ct_zone(*argv, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"ct_zone\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "ct_mark") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ct_mark(*argv, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"ct_mark\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "ct_label") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ct_labels(*argv, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"ct_label\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "indev") == 0) {
+ NEXT_ARG();
+ if (check_ifname(*argv))
+ invarg("\"indev\" not a valid ifname", *argv);
+ addattrstrz(n, MAX_MSG, TCA_FLOWER_INDEV, *argv);
+ } else if (matches(*argv, "vlan_id") == 0) {
+ __u16 vid;
+
+ NEXT_ARG();
+ if (!eth_type_vlan(eth_type)) {
+ fprintf(stderr, "Can't set \"vlan_id\" if ethertype isn't 802.1Q or 802.1AD\n");
+ return -1;
+ }
+ ret = get_u16(&vid, *argv, 10);
+ if (ret < 0 || vid & ~0xfff) {
+ fprintf(stderr, "Illegal \"vlan_id\"\n");
+ return -1;
+ }
+ addattr16(n, MAX_MSG, TCA_FLOWER_KEY_VLAN_ID, vid);
+ } else if (matches(*argv, "vlan_prio") == 0) {
+ __u8 vlan_prio;
+
+ NEXT_ARG();
+ if (!eth_type_vlan(eth_type)) {
+ fprintf(stderr, "Can't set \"vlan_prio\" if ethertype isn't 802.1Q or 802.1AD\n");
+ return -1;
+ }
+ ret = get_u8(&vlan_prio, *argv, 10);
+ if (ret < 0 || vlan_prio & ~0x7) {
+ fprintf(stderr, "Illegal \"vlan_prio\"\n");
+ return -1;
+ }
+ addattr8(n, MAX_MSG,
+ TCA_FLOWER_KEY_VLAN_PRIO, vlan_prio);
+ } else if (matches(*argv, "vlan_ethtype") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_vlan_eth_type(*argv, eth_type,
+ TCA_FLOWER_KEY_VLAN_ETH_TYPE,
+ &vlan_ethtype, n);
+ if (ret < 0)
+ return -1;
+ } else if (matches(*argv, "cvlan_id") == 0) {
+ __u16 vid;
+
+ NEXT_ARG();
+ if (!eth_type_vlan(vlan_ethtype)) {
+ fprintf(stderr, "Can't set \"cvlan_id\" if inner vlan ethertype isn't 802.1Q or 802.1AD\n");
+ return -1;
+ }
+ ret = get_u16(&vid, *argv, 10);
+ if (ret < 0 || vid & ~0xfff) {
+ fprintf(stderr, "Illegal \"cvlan_id\"\n");
+ return -1;
+ }
+ addattr16(n, MAX_MSG, TCA_FLOWER_KEY_CVLAN_ID, vid);
+ } else if (matches(*argv, "cvlan_prio") == 0) {
+ __u8 cvlan_prio;
+
+ NEXT_ARG();
+ if (!eth_type_vlan(vlan_ethtype)) {
+ fprintf(stderr, "Can't set \"cvlan_prio\" if inner vlan ethertype isn't 802.1Q or 802.1AD\n");
+ return -1;
+ }
+ ret = get_u8(&cvlan_prio, *argv, 10);
+ if (ret < 0 || cvlan_prio & ~0x7) {
+ fprintf(stderr, "Illegal \"cvlan_prio\"\n");
+ return -1;
+ }
+ addattr8(n, MAX_MSG,
+ TCA_FLOWER_KEY_CVLAN_PRIO, cvlan_prio);
+ } else if (matches(*argv, "cvlan_ethtype") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_vlan_eth_type(*argv, vlan_ethtype,
+ TCA_FLOWER_KEY_CVLAN_ETH_TYPE,
+ &cvlan_ethtype, n);
+ if (ret < 0)
+ return -1;
+ } else if (matches(*argv, "mpls_label") == 0) {
+ __u32 label;
+
+ NEXT_ARG();
+ if (eth_type != htons(ETH_P_MPLS_UC) &&
+ eth_type != htons(ETH_P_MPLS_MC)) {
+ fprintf(stderr,
+ "Can't set \"mpls_label\" if ethertype isn't MPLS\n");
+ return -1;
+ }
+ ret = get_u32(&label, *argv, 10);
+ if (ret < 0 || label & ~(MPLS_LS_LABEL_MASK >> MPLS_LS_LABEL_SHIFT)) {
+ fprintf(stderr, "Illegal \"mpls_label\"\n");
+ return -1;
+ }
+ addattr32(n, MAX_MSG, TCA_FLOWER_KEY_MPLS_LABEL, label);
+ } else if (matches(*argv, "mpls_tc") == 0) {
+ __u8 tc;
+
+ NEXT_ARG();
+ if (eth_type != htons(ETH_P_MPLS_UC) &&
+ eth_type != htons(ETH_P_MPLS_MC)) {
+ fprintf(stderr,
+ "Can't set \"mpls_tc\" if ethertype isn't MPLS\n");
+ return -1;
+ }
+ ret = get_u8(&tc, *argv, 10);
+ if (ret < 0 || tc & ~(MPLS_LS_TC_MASK >> MPLS_LS_TC_SHIFT)) {
+ fprintf(stderr, "Illegal \"mpls_tc\"\n");
+ return -1;
+ }
+ addattr8(n, MAX_MSG, TCA_FLOWER_KEY_MPLS_TC, tc);
+ } else if (matches(*argv, "mpls_bos") == 0) {
+ __u8 bos;
+
+ NEXT_ARG();
+ if (eth_type != htons(ETH_P_MPLS_UC) &&
+ eth_type != htons(ETH_P_MPLS_MC)) {
+ fprintf(stderr,
+ "Can't set \"mpls_bos\" if ethertype isn't MPLS\n");
+ return -1;
+ }
+ ret = get_u8(&bos, *argv, 10);
+ if (ret < 0 || bos & ~(MPLS_LS_S_MASK >> MPLS_LS_S_SHIFT)) {
+ fprintf(stderr, "Illegal \"mpls_bos\"\n");
+ return -1;
+ }
+ addattr8(n, MAX_MSG, TCA_FLOWER_KEY_MPLS_BOS, bos);
+ } else if (matches(*argv, "mpls_ttl") == 0) {
+ __u8 ttl;
+
+ NEXT_ARG();
+ if (eth_type != htons(ETH_P_MPLS_UC) &&
+ eth_type != htons(ETH_P_MPLS_MC)) {
+ fprintf(stderr,
+ "Can't set \"mpls_ttl\" if ethertype isn't MPLS\n");
+ return -1;
+ }
+ ret = get_u8(&ttl, *argv, 10);
+ if (ret < 0 || ttl & ~(MPLS_LS_TTL_MASK >> MPLS_LS_TTL_SHIFT)) {
+ fprintf(stderr, "Illegal \"mpls_ttl\"\n");
+ return -1;
+ }
+ addattr8(n, MAX_MSG, TCA_FLOWER_KEY_MPLS_TTL, ttl);
+ } else if (matches(*argv, "dst_mac") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_eth_addr(*argv,
+ TCA_FLOWER_KEY_ETH_DST,
+ TCA_FLOWER_KEY_ETH_DST_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"dst_mac\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "src_mac") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_eth_addr(*argv,
+ TCA_FLOWER_KEY_ETH_SRC,
+ TCA_FLOWER_KEY_ETH_SRC_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"src_mac\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "ip_proto") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ip_proto(*argv, cvlan_ethtype ?
+ cvlan_ethtype : vlan_ethtype ?
+ vlan_ethtype : eth_type,
+ TCA_FLOWER_KEY_IP_PROTO,
+ &ip_proto, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"ip_proto\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "ip_tos") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ip_tos_ttl(*argv,
+ TCA_FLOWER_KEY_IP_TOS,
+ TCA_FLOWER_KEY_IP_TOS_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"ip_tos\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "ip_ttl") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ip_tos_ttl(*argv,
+ TCA_FLOWER_KEY_IP_TTL,
+ TCA_FLOWER_KEY_IP_TTL_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"ip_ttl\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "dst_ip") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ip_addr(*argv, cvlan_ethtype ?
+ cvlan_ethtype : vlan_ethtype ?
+ vlan_ethtype : eth_type,
+ TCA_FLOWER_KEY_IPV4_DST,
+ TCA_FLOWER_KEY_IPV4_DST_MASK,
+ TCA_FLOWER_KEY_IPV6_DST,
+ TCA_FLOWER_KEY_IPV6_DST_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"dst_ip\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "src_ip") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ip_addr(*argv, cvlan_ethtype ?
+ cvlan_ethtype : vlan_ethtype ?
+ vlan_ethtype : eth_type,
+ TCA_FLOWER_KEY_IPV4_SRC,
+ TCA_FLOWER_KEY_IPV4_SRC_MASK,
+ TCA_FLOWER_KEY_IPV6_SRC,
+ TCA_FLOWER_KEY_IPV6_SRC_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"src_ip\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "dst_port") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_port(*argv, ip_proto,
+ FLOWER_ENDPOINT_DST, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"dst_port\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "src_port") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_port(*argv, ip_proto,
+ FLOWER_ENDPOINT_SRC, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"src_port\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "tcp_flags") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_tcp_flags(*argv,
+ TCA_FLOWER_KEY_TCP_FLAGS,
+ TCA_FLOWER_KEY_TCP_FLAGS_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"tcp_flags\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "type") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_icmp(*argv, eth_type, ip_proto,
+ FLOWER_ICMP_FIELD_TYPE, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"icmp type\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "code") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_icmp(*argv, eth_type, ip_proto,
+ FLOWER_ICMP_FIELD_CODE, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"icmp code\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "arp_tip") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_arp_ip_addr(*argv, vlan_ethtype ?
+ vlan_ethtype : eth_type,
+ TCA_FLOWER_KEY_ARP_TIP,
+ TCA_FLOWER_KEY_ARP_TIP_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"arp_tip\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "arp_sip") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_arp_ip_addr(*argv, vlan_ethtype ?
+ vlan_ethtype : eth_type,
+ TCA_FLOWER_KEY_ARP_SIP,
+ TCA_FLOWER_KEY_ARP_SIP_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"arp_sip\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "arp_op") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_arp_op(*argv, vlan_ethtype ?
+ vlan_ethtype : eth_type,
+ TCA_FLOWER_KEY_ARP_OP,
+ TCA_FLOWER_KEY_ARP_OP_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"arp_op\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "arp_tha") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_eth_addr(*argv,
+ TCA_FLOWER_KEY_ARP_THA,
+ TCA_FLOWER_KEY_ARP_THA_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"arp_tha\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "arp_sha") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_eth_addr(*argv,
+ TCA_FLOWER_KEY_ARP_SHA,
+ TCA_FLOWER_KEY_ARP_SHA_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"arp_sha\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "enc_dst_ip") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ip_addr(*argv, 0,
+ TCA_FLOWER_KEY_ENC_IPV4_DST,
+ TCA_FLOWER_KEY_ENC_IPV4_DST_MASK,
+ TCA_FLOWER_KEY_ENC_IPV6_DST,
+ TCA_FLOWER_KEY_ENC_IPV6_DST_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"enc_dst_ip\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "enc_src_ip") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ip_addr(*argv, 0,
+ TCA_FLOWER_KEY_ENC_IPV4_SRC,
+ TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK,
+ TCA_FLOWER_KEY_ENC_IPV6_SRC,
+ TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"enc_src_ip\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "enc_key_id") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_key_id(*argv,
+ TCA_FLOWER_KEY_ENC_KEY_ID, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"enc_key_id\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "enc_dst_port") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_enc_port(*argv,
+ TCA_FLOWER_KEY_ENC_UDP_DST_PORT, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"enc_dst_port\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "enc_tos") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ip_tos_ttl(*argv,
+ TCA_FLOWER_KEY_ENC_IP_TOS,
+ TCA_FLOWER_KEY_ENC_IP_TOS_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"enc_tos\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "enc_ttl") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_ip_tos_ttl(*argv,
+ TCA_FLOWER_KEY_ENC_IP_TTL,
+ TCA_FLOWER_KEY_ENC_IP_TTL_MASK,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"enc_ttl\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "geneve_opts") == 0) {
+ NEXT_ARG();
+ ret = flower_parse_enc_opts(*argv, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"geneve_opts\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ ret = parse_action(&argc, &argv, TCA_FLOWER_ACT, n);
+ if (ret) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ continue;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+parse_done:
+ ret = addattr32(n, MAX_MSG, TCA_FLOWER_FLAGS, flags);
+ if (ret)
+ return ret;
+
+ if (mtf_mask) {
+ ret = addattr32(n, MAX_MSG, TCA_FLOWER_KEY_FLAGS, htonl(mtf));
+ if (ret)
+ return ret;
+
+ ret = addattr32(n, MAX_MSG, TCA_FLOWER_KEY_FLAGS_MASK, htonl(mtf_mask));
+ if (ret)
+ return ret;
+ }
+
+ if (eth_type != htons(ETH_P_ALL)) {
+ ret = addattr16(n, MAX_MSG, TCA_FLOWER_KEY_ETH_TYPE, eth_type);
+ if (ret)
+ return ret;
+ }
+
+ tail->rta_len = (((void *)n)+n->nlmsg_len) - (void *)tail;
+
+ return 0;
+}
+
+static int __mask_bits(char *addr, size_t len)
+{
+ int bits = 0;
+ bool hole = false;
+ int i;
+ int j;
+
+ for (i = 0; i < len; i++, addr++) {
+ for (j = 7; j >= 0; j--) {
+ if (((*addr) >> j) & 0x1) {
+ if (hole)
+ return -1;
+ bits++;
+ } else if (bits) {
+ hole = true;
+ } else{
+ return -1;
+ }
+ }
+ }
+ return bits;
+}
+
+static void flower_print_eth_addr(char *name, struct rtattr *addr_attr,
+ struct rtattr *mask_attr)
+{
+ SPRINT_BUF(namefrm);
+ SPRINT_BUF(out);
+ SPRINT_BUF(b1);
+ size_t done;
+ int bits;
+
+ if (!addr_attr || RTA_PAYLOAD(addr_attr) != ETH_ALEN)
+ return;
+ done = sprintf(out, "%s",
+ ll_addr_n2a(RTA_DATA(addr_attr), ETH_ALEN,
+ 0, b1, sizeof(b1)));
+ if (mask_attr && RTA_PAYLOAD(mask_attr) == ETH_ALEN) {
+ bits = __mask_bits(RTA_DATA(mask_attr), ETH_ALEN);
+ if (bits < 0)
+ sprintf(out + done, "/%s",
+ ll_addr_n2a(RTA_DATA(mask_attr), ETH_ALEN,
+ 0, b1, sizeof(b1)));
+ else if (bits < ETH_ALEN * 8)
+ sprintf(out + done, "/%d", bits);
+ }
+
+ sprintf(namefrm, "\n %s %%s", name);
+ print_string(PRINT_ANY, name, namefrm, out);
+}
+
+static void flower_print_eth_type(__be16 *p_eth_type,
+ struct rtattr *eth_type_attr)
+{
+ SPRINT_BUF(out);
+ __be16 eth_type;
+
+ if (!eth_type_attr)
+ return;
+
+ eth_type = rta_getattr_u16(eth_type_attr);
+ if (eth_type == htons(ETH_P_IP))
+ sprintf(out, "ipv4");
+ else if (eth_type == htons(ETH_P_IPV6))
+ sprintf(out, "ipv6");
+ else if (eth_type == htons(ETH_P_ARP))
+ sprintf(out, "arp");
+ else if (eth_type == htons(ETH_P_RARP))
+ sprintf(out, "rarp");
+ else
+ sprintf(out, "%04x", ntohs(eth_type));
+
+ print_string(PRINT_ANY, "eth_type", "\n eth_type %s", out);
+ *p_eth_type = eth_type;
+}
+
+static void flower_print_ip_proto(__u8 *p_ip_proto,
+ struct rtattr *ip_proto_attr)
+{
+ SPRINT_BUF(out);
+ __u8 ip_proto;
+
+ if (!ip_proto_attr)
+ return;
+
+ ip_proto = rta_getattr_u8(ip_proto_attr);
+ if (ip_proto == IPPROTO_TCP)
+ sprintf(out, "tcp");
+ else if (ip_proto == IPPROTO_UDP)
+ sprintf(out, "udp");
+ else if (ip_proto == IPPROTO_SCTP)
+ sprintf(out, "sctp");
+ else if (ip_proto == IPPROTO_ICMP)
+ sprintf(out, "icmp");
+ else if (ip_proto == IPPROTO_ICMPV6)
+ sprintf(out, "icmpv6");
+ else
+ sprintf(out, "%02x", ip_proto);
+
+ print_string(PRINT_ANY, "ip_proto", "\n ip_proto %s", out);
+ *p_ip_proto = ip_proto;
+}
+
+static void flower_print_ip_attr(const char *name, struct rtattr *key_attr,
+ struct rtattr *mask_attr)
+{
+ print_masked_u8(name, key_attr, mask_attr, true);
+}
+
+static void flower_print_matching_flags(char *name,
+ enum flower_matching_flags type,
+ struct rtattr *attr,
+ struct rtattr *mask_attr)
+{
+ int i;
+ int count = 0;
+ __u32 mtf;
+ __u32 mtf_mask;
+
+ if (!mask_attr || RTA_PAYLOAD(mask_attr) != 4)
+ return;
+
+ mtf = ntohl(rta_getattr_u32(attr));
+ mtf_mask = ntohl(rta_getattr_u32(mask_attr));
+
+ for (i = 0; i < ARRAY_SIZE(flags_str); i++) {
+ if (type != flags_str[i].type)
+ continue;
+ if (mtf_mask & flags_str[i].flag) {
+ if (++count == 1) {
+ print_string(PRINT_FP, NULL, "\n %s ", name);
+ open_json_object(name);
+ } else {
+ print_string(PRINT_FP, NULL, "/", NULL);
+ }
+
+ print_bool(PRINT_JSON, flags_str[i].string, NULL,
+ mtf & flags_str[i].flag);
+ if (mtf & flags_str[i].flag)
+ print_string(PRINT_FP, NULL, "%s",
+ flags_str[i].string);
+ else
+ print_string(PRINT_FP, NULL, "no%s",
+ flags_str[i].string);
+ }
+ }
+ if (count)
+ close_json_object();
+}
+
+static void flower_print_ip_addr(char *name, __be16 eth_type,
+ struct rtattr *addr4_attr,
+ struct rtattr *mask4_attr,
+ struct rtattr *addr6_attr,
+ struct rtattr *mask6_attr)
+{
+ struct rtattr *addr_attr;
+ struct rtattr *mask_attr;
+ SPRINT_BUF(namefrm);
+ SPRINT_BUF(out);
+ size_t done;
+ int family;
+ size_t len;
+ int bits;
+
+ if (eth_type == htons(ETH_P_IP)) {
+ family = AF_INET;
+ addr_attr = addr4_attr;
+ mask_attr = mask4_attr;
+ len = 4;
+ } else if (eth_type == htons(ETH_P_IPV6)) {
+ family = AF_INET6;
+ addr_attr = addr6_attr;
+ mask_attr = mask6_attr;
+ len = 16;
+ } else {
+ return;
+ }
+ if (!addr_attr || RTA_PAYLOAD(addr_attr) != len)
+ return;
+ if (!mask_attr || RTA_PAYLOAD(mask_attr) != len)
+ return;
+ done = sprintf(out, "%s", rt_addr_n2a_rta(family, addr_attr));
+ bits = __mask_bits(RTA_DATA(mask_attr), len);
+ if (bits < 0)
+ sprintf(out + done, "/%s", rt_addr_n2a_rta(family, mask_attr));
+ else if (bits < len * 8)
+ sprintf(out + done, "/%d", bits);
+
+ sprintf(namefrm, "\n %s %%s", name);
+ print_string(PRINT_ANY, name, namefrm, out);
+}
+static void flower_print_ip4_addr(char *name, struct rtattr *addr_attr,
+ struct rtattr *mask_attr)
+{
+ return flower_print_ip_addr(name, htons(ETH_P_IP),
+ addr_attr, mask_attr, 0, 0);
+}
+
+static void flower_print_port(char *name, struct rtattr *attr)
+{
+ SPRINT_BUF(namefrm);
+
+ if (!attr)
+ return;
+
+ sprintf(namefrm,"\n %s %%u", name);
+ print_hu(PRINT_ANY, name, namefrm, rta_getattr_be16(attr));
+}
+
+static void flower_print_port_range(char *name, struct rtattr *min_attr,
+ struct rtattr *max_attr)
+{
+ if (!min_attr || !max_attr)
+ return;
+
+ if (is_json_context()) {
+ open_json_object(name);
+ print_hu(PRINT_JSON, "start", NULL, rta_getattr_be16(min_attr));
+ print_hu(PRINT_JSON, "end", NULL, rta_getattr_be16(max_attr));
+ close_json_object();
+ } else {
+ SPRINT_BUF(namefrm);
+ SPRINT_BUF(out);
+ size_t done;
+
+ done = sprintf(out, "%u", rta_getattr_be16(min_attr));
+ sprintf(out + done, "-%u", rta_getattr_be16(max_attr));
+ sprintf(namefrm, "\n %s %%s", name);
+ print_string(PRINT_ANY, name, namefrm, out);
+ }
+}
+
+static void flower_print_tcp_flags(const char *name, struct rtattr *flags_attr,
+ struct rtattr *mask_attr)
+{
+ SPRINT_BUF(namefrm);
+ SPRINT_BUF(out);
+ size_t done;
+
+ if (!flags_attr)
+ return;
+
+ done = sprintf(out, "0x%x", rta_getattr_be16(flags_attr));
+ if (mask_attr)
+ sprintf(out + done, "/%x", rta_getattr_be16(mask_attr));
+
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+ sprintf(namefrm, "%s %%s", name);
+ print_string(PRINT_ANY, name, namefrm, out);
+}
+
+static void flower_print_ct_state(struct rtattr *flags_attr,
+ struct rtattr *mask_attr)
+{
+ SPRINT_BUF(out);
+ uint16_t state;
+ uint16_t state_mask;
+ size_t done = 0;
+ int i;
+
+ if (!flags_attr)
+ return;
+
+ state = rta_getattr_u16(flags_attr);
+ if (mask_attr)
+ state_mask = rta_getattr_u16(mask_attr);
+ else
+ state_mask = UINT16_MAX;
+
+ for (i = 0; i < ARRAY_SIZE(flower_ct_states); i++) {
+ if (!(state_mask & flower_ct_states[i].flag))
+ continue;
+
+ if (state & flower_ct_states[i].flag)
+ done += sprintf(out + done, "+%s",
+ flower_ct_states[i].str);
+ else
+ done += sprintf(out + done, "-%s",
+ flower_ct_states[i].str);
+ }
+
+ print_string(PRINT_ANY, "ct_state", "\n ct_state %s", out);
+}
+
+static void flower_print_ct_label(struct rtattr *attr,
+ struct rtattr *mask_attr)
+{
+ const unsigned char *str;
+ bool print_mask = false;
+ int data_len, i;
+ SPRINT_BUF(out);
+ char *p;
+
+ if (!attr)
+ return;
+
+ data_len = RTA_PAYLOAD(attr);
+ hexstring_n2a(RTA_DATA(attr), data_len, out, sizeof(out));
+ p = out + data_len*2;
+
+ data_len = RTA_PAYLOAD(attr);
+ str = RTA_DATA(mask_attr);
+ if (data_len != 16)
+ print_mask = true;
+ for (i = 0; !print_mask && i < data_len; i++) {
+ if (str[i] != 0xff)
+ print_mask = true;
+ }
+ if (print_mask) {
+ *p++ = '/';
+ hexstring_n2a(RTA_DATA(mask_attr), data_len, p,
+ sizeof(out)-(p-out));
+ p += data_len*2;
+ }
+ *p = '\0';
+
+ print_string(PRINT_ANY, "ct_label", "\n ct_label %s", out);
+}
+
+static void flower_print_ct_zone(struct rtattr *attr,
+ struct rtattr *mask_attr)
+{
+ print_masked_u16("ct_zone", attr, mask_attr, true);
+}
+
+static void flower_print_ct_mark(struct rtattr *attr,
+ struct rtattr *mask_attr)
+{
+ print_masked_u32("ct_mark", attr, mask_attr, true);
+}
+
+static void flower_print_key_id(const char *name, struct rtattr *attr)
+{
+ SPRINT_BUF(namefrm);
+
+ if (!attr)
+ return;
+
+ sprintf(namefrm,"\n %s %%u", name);
+ print_uint(PRINT_ANY, name, namefrm, rta_getattr_be32(attr));
+}
+
+static void flower_print_geneve_opts(const char *name, struct rtattr *attr,
+ char *strbuf)
+{
+ struct rtattr *tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX + 1];
+ int ii, data_len, offset = 0, slen = 0;
+ struct rtattr *i = RTA_DATA(attr);
+ int rem = RTA_PAYLOAD(attr);
+ __u8 type, data_r[rem];
+ char data[rem * 2 + 1];
+ __u16 class;
+
+ open_json_array(PRINT_JSON, name);
+ while (rem) {
+ parse_rtattr(tb, TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX, i, rem);
+ class = rta_getattr_be16(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_CLASS]);
+ type = rta_getattr_u8(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_TYPE]);
+ data_len = RTA_PAYLOAD(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA]);
+ hexstring_n2a(RTA_DATA(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA]),
+ data_len, data, sizeof(data));
+ hex2mem(data, data_r, data_len);
+ offset += data_len + 20;
+ rem -= data_len + 20;
+ i = RTA_DATA(attr) + offset;
+
+ open_json_object(NULL);
+ print_uint(PRINT_JSON, "class", NULL, class);
+ print_uint(PRINT_JSON, "type", NULL, type);
+ open_json_array(PRINT_JSON, "data");
+ for (ii = 0; ii < data_len; ii++)
+ print_uint(PRINT_JSON, NULL, NULL, data_r[ii]);
+ close_json_array(PRINT_JSON, "data");
+ close_json_object();
+
+ slen += sprintf(strbuf + slen, "%04x:%02x:%s",
+ class, type, data);
+ if (rem)
+ slen += sprintf(strbuf + slen, ",");
+ }
+ close_json_array(PRINT_JSON, name);
+}
+
+static void flower_print_geneve_parts(const char *name, struct rtattr *attr,
+ char *key, char *mask)
+{
+ char *namefrm = "\n geneve_opt %s";
+ char *key_token, *mask_token, *out;
+ int len;
+
+ out = malloc(RTA_PAYLOAD(attr) * 4 + 3);
+ if (!out)
+ return;
+
+ len = 0;
+ key_token = strsep(&key, ",");
+ mask_token = strsep(&mask, ",");
+ while (key_token) {
+ len += sprintf(&out[len], "%s/%s,", key_token, mask_token);
+ mask_token = strsep(&mask, ",");
+ key_token = strsep(&key, ",");
+ }
+
+ out[len - 1] = '\0';
+ print_string(PRINT_FP, name, namefrm, out);
+ free(out);
+}
+
+static void flower_print_enc_opts(const char *name, struct rtattr *attr,
+ struct rtattr *mask_attr)
+{
+ struct rtattr *key_tb[TCA_FLOWER_KEY_ENC_OPTS_MAX + 1];
+ struct rtattr *msk_tb[TCA_FLOWER_KEY_ENC_OPTS_MAX + 1];
+ char *key, *msk;
+
+ if (!attr)
+ return;
+
+ key = malloc(RTA_PAYLOAD(attr) * 2 + 1);
+ if (!key)
+ return;
+
+ msk = malloc(RTA_PAYLOAD(attr) * 2 + 1);
+ if (!msk)
+ goto err_key_free;
+
+ parse_rtattr_nested(key_tb, TCA_FLOWER_KEY_ENC_OPTS_MAX, attr);
+ flower_print_geneve_opts("geneve_opt_key",
+ key_tb[TCA_FLOWER_KEY_ENC_OPTS_GENEVE], key);
+
+ parse_rtattr_nested(msk_tb, TCA_FLOWER_KEY_ENC_OPTS_MAX, mask_attr);
+ flower_print_geneve_opts("geneve_opt_mask",
+ msk_tb[TCA_FLOWER_KEY_ENC_OPTS_GENEVE], msk);
+
+ flower_print_geneve_parts(name, attr, key, msk);
+
+ free(msk);
+err_key_free:
+ free(key);
+}
+
+static void flower_print_masked_u8(const char *name, struct rtattr *attr,
+ struct rtattr *mask_attr,
+ const char *(*value_to_str)(__u8 value))
+{
+ const char *value_str = NULL;
+ __u8 value, mask;
+ SPRINT_BUF(namefrm);
+ SPRINT_BUF(out);
+ size_t done;
+
+ if (!attr)
+ return;
+
+ value = rta_getattr_u8(attr);
+ mask = mask_attr ? rta_getattr_u8(mask_attr) : UINT8_MAX;
+ if (mask == UINT8_MAX && value_to_str)
+ value_str = value_to_str(value);
+
+ if (value_str)
+ done = sprintf(out, "%s", value_str);
+ else
+ done = sprintf(out, "%d", value);
+
+ if (mask != UINT8_MAX)
+ sprintf(out + done, "/%d", mask);
+
+ sprintf(namefrm,"\n %s %%s", name);
+ print_string(PRINT_ANY, name, namefrm, out);
+}
+
+static void flower_print_u8(const char *name, struct rtattr *attr)
+{
+ flower_print_masked_u8(name, attr, NULL, NULL);
+}
+
+static void flower_print_u32(const char *name, struct rtattr *attr)
+{
+ SPRINT_BUF(namefrm);
+
+ if (!attr)
+ return;
+
+ sprintf(namefrm,"\n %s %%u", name);
+ print_uint(PRINT_ANY, name, namefrm, rta_getattr_u32(attr));
+}
+
+static void flower_print_arp_op(const char *name,
+ struct rtattr *op_attr,
+ struct rtattr *mask_attr)
+{
+ flower_print_masked_u8(name, op_attr, mask_attr,
+ flower_print_arp_op_to_name);
+}
+
+static int flower_print_opt(struct filter_util *qu, FILE *f,
+ struct rtattr *opt, __u32 handle)
+{
+ struct rtattr *tb[TCA_FLOWER_MAX + 1];
+ __be16 min_port_type, max_port_type;
+ int nl_type, nl_mask_type;
+ __be16 eth_type = 0;
+ __u8 ip_proto = 0xff;
+
+ if (!opt)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_FLOWER_MAX, opt);
+
+ if (handle)
+ print_uint(PRINT_ANY, "handle", "handle 0x%x ", handle);
+
+ if (tb[TCA_FLOWER_CLASSID]) {
+ __u32 h = rta_getattr_u32(tb[TCA_FLOWER_CLASSID]);
+
+ if (TC_H_MIN(h) < TC_H_MIN_PRIORITY ||
+ TC_H_MIN(h) > (TC_H_MIN_PRIORITY + TC_QOPT_MAX_QUEUE - 1)) {
+ SPRINT_BUF(b1);
+ print_string(PRINT_ANY, "classid", "classid %s ",
+ sprint_tc_classid(h, b1));
+ } else {
+ print_uint(PRINT_ANY, "hw_tc", "hw_tc %u ",
+ TC_H_MIN(h) - TC_H_MIN_PRIORITY);
+ }
+ }
+
+ if (tb[TCA_FLOWER_INDEV]) {
+ struct rtattr *attr = tb[TCA_FLOWER_INDEV];
+
+ print_string(PRINT_ANY, "indev", "\n indev %s",
+ rta_getattr_str(attr));
+ }
+
+ open_json_object("keys");
+
+ if (tb[TCA_FLOWER_KEY_VLAN_ID]) {
+ struct rtattr *attr = tb[TCA_FLOWER_KEY_VLAN_ID];
+
+ print_uint(PRINT_ANY, "vlan_id", "\n vlan_id %u",
+ rta_getattr_u16(attr));
+ }
+
+ if (tb[TCA_FLOWER_KEY_VLAN_PRIO]) {
+ struct rtattr *attr = tb[TCA_FLOWER_KEY_VLAN_PRIO];
+
+ print_uint(PRINT_ANY, "vlan_prio", "\n vlan_prio %d",
+ rta_getattr_u8(attr));
+ }
+
+ if (tb[TCA_FLOWER_KEY_VLAN_ETH_TYPE]) {
+ SPRINT_BUF(buf);
+ struct rtattr *attr = tb[TCA_FLOWER_KEY_VLAN_ETH_TYPE];
+
+ print_string(PRINT_ANY, "vlan_ethtype", "\n vlan_ethtype %s",
+ ll_proto_n2a(rta_getattr_u16(attr),
+ buf, sizeof(buf)));
+ }
+
+ if (tb[TCA_FLOWER_KEY_CVLAN_ID]) {
+ struct rtattr *attr = tb[TCA_FLOWER_KEY_CVLAN_ID];
+
+ print_uint(PRINT_ANY, "cvlan_id", "\n cvlan_id %u",
+ rta_getattr_u16(attr));
+ }
+
+ if (tb[TCA_FLOWER_KEY_CVLAN_PRIO]) {
+ struct rtattr *attr = tb[TCA_FLOWER_KEY_CVLAN_PRIO];
+
+ print_uint(PRINT_ANY, "cvlan_prio", "\n cvlan_prio %d",
+ rta_getattr_u8(attr));
+ }
+
+ if (tb[TCA_FLOWER_KEY_CVLAN_ETH_TYPE]) {
+ SPRINT_BUF(buf);
+ struct rtattr *attr = tb[TCA_FLOWER_KEY_CVLAN_ETH_TYPE];
+
+ print_string(PRINT_ANY, "cvlan_ethtype", "\n cvlan_ethtype %s",
+ ll_proto_n2a(rta_getattr_u16(attr),
+ buf, sizeof(buf)));
+ }
+
+ flower_print_eth_addr("dst_mac", tb[TCA_FLOWER_KEY_ETH_DST],
+ tb[TCA_FLOWER_KEY_ETH_DST_MASK]);
+ flower_print_eth_addr("src_mac", tb[TCA_FLOWER_KEY_ETH_SRC],
+ tb[TCA_FLOWER_KEY_ETH_SRC_MASK]);
+
+ flower_print_eth_type(ð_type, tb[TCA_FLOWER_KEY_ETH_TYPE]);
+ flower_print_ip_proto(&ip_proto, tb[TCA_FLOWER_KEY_IP_PROTO]);
+
+ flower_print_ip_attr("ip_tos", tb[TCA_FLOWER_KEY_IP_TOS],
+ tb[TCA_FLOWER_KEY_IP_TOS_MASK]);
+ flower_print_ip_attr("ip_ttl", tb[TCA_FLOWER_KEY_IP_TTL],
+ tb[TCA_FLOWER_KEY_IP_TTL_MASK]);
+
+ flower_print_u32("mpls_label", tb[TCA_FLOWER_KEY_MPLS_LABEL]);
+ flower_print_u8("mpls_tc", tb[TCA_FLOWER_KEY_MPLS_TC]);
+ flower_print_u8("mpls_bos", tb[TCA_FLOWER_KEY_MPLS_BOS]);
+ flower_print_u8("mpls_ttl", tb[TCA_FLOWER_KEY_MPLS_TTL]);
+
+ flower_print_ip_addr("dst_ip", eth_type,
+ tb[TCA_FLOWER_KEY_IPV4_DST],
+ tb[TCA_FLOWER_KEY_IPV4_DST_MASK],
+ tb[TCA_FLOWER_KEY_IPV6_DST],
+ tb[TCA_FLOWER_KEY_IPV6_DST_MASK]);
+
+ flower_print_ip_addr("src_ip", eth_type,
+ tb[TCA_FLOWER_KEY_IPV4_SRC],
+ tb[TCA_FLOWER_KEY_IPV4_SRC_MASK],
+ tb[TCA_FLOWER_KEY_IPV6_SRC],
+ tb[TCA_FLOWER_KEY_IPV6_SRC_MASK]);
+
+ nl_type = flower_port_attr_type(ip_proto, FLOWER_ENDPOINT_DST);
+ if (nl_type >= 0)
+ flower_print_port("dst_port", tb[nl_type]);
+ nl_type = flower_port_attr_type(ip_proto, FLOWER_ENDPOINT_SRC);
+ if (nl_type >= 0)
+ flower_print_port("src_port", tb[nl_type]);
+
+ if (!flower_port_range_attr_type(ip_proto, FLOWER_ENDPOINT_DST,
+ &min_port_type, &max_port_type))
+ flower_print_port_range("dst_port",
+ tb[min_port_type], tb[max_port_type]);
+
+ if (!flower_port_range_attr_type(ip_proto, FLOWER_ENDPOINT_SRC,
+ &min_port_type, &max_port_type))
+ flower_print_port_range("src_port",
+ tb[min_port_type], tb[max_port_type]);
+
+ flower_print_tcp_flags("tcp_flags", tb[TCA_FLOWER_KEY_TCP_FLAGS],
+ tb[TCA_FLOWER_KEY_TCP_FLAGS_MASK]);
+
+ nl_type = flower_icmp_attr_type(eth_type, ip_proto,
+ FLOWER_ICMP_FIELD_TYPE);
+ nl_mask_type = flower_icmp_attr_mask_type(eth_type, ip_proto,
+ FLOWER_ICMP_FIELD_TYPE);
+ if (nl_type >= 0 && nl_mask_type >= 0)
+ flower_print_masked_u8("icmp_type", tb[nl_type],
+ tb[nl_mask_type], NULL);
+
+ nl_type = flower_icmp_attr_type(eth_type, ip_proto,
+ FLOWER_ICMP_FIELD_CODE);
+ nl_mask_type = flower_icmp_attr_mask_type(eth_type, ip_proto,
+ FLOWER_ICMP_FIELD_CODE);
+ if (nl_type >= 0 && nl_mask_type >= 0)
+ flower_print_masked_u8("icmp_code", tb[nl_type],
+ tb[nl_mask_type], NULL);
+
+ flower_print_ip4_addr("arp_sip", tb[TCA_FLOWER_KEY_ARP_SIP],
+ tb[TCA_FLOWER_KEY_ARP_SIP_MASK]);
+ flower_print_ip4_addr("arp_tip", tb[TCA_FLOWER_KEY_ARP_TIP],
+ tb[TCA_FLOWER_KEY_ARP_TIP_MASK]);
+ flower_print_arp_op("arp_op", tb[TCA_FLOWER_KEY_ARP_OP],
+ tb[TCA_FLOWER_KEY_ARP_OP_MASK]);
+ flower_print_eth_addr("arp_sha", tb[TCA_FLOWER_KEY_ARP_SHA],
+ tb[TCA_FLOWER_KEY_ARP_SHA_MASK]);
+ flower_print_eth_addr("arp_tha", tb[TCA_FLOWER_KEY_ARP_THA],
+ tb[TCA_FLOWER_KEY_ARP_THA_MASK]);
+
+ flower_print_ip_addr("enc_dst_ip",
+ tb[TCA_FLOWER_KEY_ENC_IPV4_DST_MASK] ?
+ htons(ETH_P_IP) : htons(ETH_P_IPV6),
+ tb[TCA_FLOWER_KEY_ENC_IPV4_DST],
+ tb[TCA_FLOWER_KEY_ENC_IPV4_DST_MASK],
+ tb[TCA_FLOWER_KEY_ENC_IPV6_DST],
+ tb[TCA_FLOWER_KEY_ENC_IPV6_DST_MASK]);
+
+ flower_print_ip_addr("enc_src_ip",
+ tb[TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK] ?
+ htons(ETH_P_IP) : htons(ETH_P_IPV6),
+ tb[TCA_FLOWER_KEY_ENC_IPV4_SRC],
+ tb[TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK],
+ tb[TCA_FLOWER_KEY_ENC_IPV6_SRC],
+ tb[TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK]);
+
+ flower_print_key_id("enc_key_id", tb[TCA_FLOWER_KEY_ENC_KEY_ID]);
+
+ flower_print_port("enc_dst_port", tb[TCA_FLOWER_KEY_ENC_UDP_DST_PORT]);
+
+ flower_print_ip_attr("enc_tos", tb[TCA_FLOWER_KEY_ENC_IP_TOS],
+ tb[TCA_FLOWER_KEY_ENC_IP_TOS_MASK]);
+ flower_print_ip_attr("enc_ttl", tb[TCA_FLOWER_KEY_ENC_IP_TTL],
+ tb[TCA_FLOWER_KEY_ENC_IP_TTL_MASK]);
+ flower_print_enc_opts("enc_opt", tb[TCA_FLOWER_KEY_ENC_OPTS],
+ tb[TCA_FLOWER_KEY_ENC_OPTS_MASK]);
+
+ flower_print_matching_flags("ip_flags", FLOWER_IP_FLAGS,
+ tb[TCA_FLOWER_KEY_FLAGS],
+ tb[TCA_FLOWER_KEY_FLAGS_MASK]);
+
+ flower_print_ct_state(tb[TCA_FLOWER_KEY_CT_STATE],
+ tb[TCA_FLOWER_KEY_CT_STATE_MASK]);
+ flower_print_ct_zone(tb[TCA_FLOWER_KEY_CT_ZONE],
+ tb[TCA_FLOWER_KEY_CT_ZONE_MASK]);
+ flower_print_ct_mark(tb[TCA_FLOWER_KEY_CT_MARK],
+ tb[TCA_FLOWER_KEY_CT_MARK_MASK]);
+ flower_print_ct_label(tb[TCA_FLOWER_KEY_CT_LABELS],
+ tb[TCA_FLOWER_KEY_CT_LABELS_MASK]);
+
+ close_json_object();
+
+ if (tb[TCA_FLOWER_FLAGS]) {
+ __u32 flags = rta_getattr_u32(tb[TCA_FLOWER_FLAGS]);
+
+ if (flags & TCA_CLS_FLAGS_SKIP_HW)
+ print_bool(PRINT_ANY, "skip_hw", "\n skip_hw", true);
+ if (flags & TCA_CLS_FLAGS_SKIP_SW)
+ print_bool(PRINT_ANY, "skip_sw", "\n skip_sw", true);
+
+ if (flags & TCA_CLS_FLAGS_IN_HW) {
+ print_bool(PRINT_ANY, "in_hw", "\n in_hw", true);
+
+ if (tb[TCA_FLOWER_IN_HW_COUNT]) {
+ __u32 count = rta_getattr_u32(tb[TCA_FLOWER_IN_HW_COUNT]);
+
+ print_uint(PRINT_ANY, "in_hw_count",
+ " in_hw_count %u", count);
+ }
+ }
+ else if (flags & TCA_CLS_FLAGS_NOT_IN_HW)
+ print_bool(PRINT_ANY, "not_in_hw", "\n not_in_hw", true);
+ }
+
+ if (tb[TCA_FLOWER_ACT])
+ tc_print_action(f, tb[TCA_FLOWER_ACT], 0);
+
+ return 0;
+}
+
+struct filter_util flower_filter_util = {
+ .id = "flower",
+ .parse_fopt = flower_parse_opt,
+ .print_fopt = flower_print_opt,
+};
diff --git a/tc/f_fw.c b/tc/f_fw.c
new file mode 100644
index 0000000..688364f
--- /dev/null
+++ b/tc/f_fw.c
@@ -0,0 +1,165 @@
+/*
+ * f_fw.c FW filter.
+ *
+ * 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 <linux/if.h> /* IFNAMSIZ */
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... fw [ classid CLASSID ] [ indev DEV ] [ action ACTION_SPEC ]\n"
+ " CLASSID := Push matching packets to the class identified by CLASSID with format X:Y\n"
+ " CLASSID is parsed as hexadecimal input.\n"
+ " DEV := specify device for incoming device classification.\n"
+ " ACTION_SPEC := Apply an action on matching packets.\n"
+ " NOTE: handle is represented as HANDLE[/FWMASK].\n"
+ " FWMASK is 0xffffffff by default.\n");
+}
+
+static int fw_parse_opt(struct filter_util *qu, char *handle, int argc, char **argv, struct nlmsghdr *n)
+{
+ struct tcmsg *t = NLMSG_DATA(n);
+ struct rtattr *tail;
+ __u32 mask = 0;
+ int mask_set = 0;
+
+ if (handle) {
+ char *slash;
+
+ if ((slash = strchr(handle, '/')) != NULL)
+ *slash = '\0';
+ if (get_u32(&t->tcm_handle, handle, 0)) {
+ fprintf(stderr, "Illegal \"handle\"\n");
+ return -1;
+ }
+ if (slash) {
+ if (get_u32(&mask, slash+1, 0)) {
+ fprintf(stderr, "Illegal \"handle\" mask\n");
+ return -1;
+ }
+ mask_set = 1;
+ }
+ }
+
+ if (argc == 0)
+ return 0;
+
+ tail = addattr_nest(n, 4096, TCA_OPTIONS);
+
+ if (mask_set)
+ addattr32(n, MAX_MSG, TCA_FW_MASK, mask);
+
+ while (argc > 0) {
+ if (matches(*argv, "classid") == 0 ||
+ matches(*argv, "flowid") == 0) {
+ unsigned int handle;
+
+ NEXT_ARG();
+ if (get_tc_classid(&handle, *argv)) {
+ fprintf(stderr, "Illegal \"classid\"\n");
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_FW_CLASSID, &handle, 4);
+ } else if (matches(*argv, "police") == 0) {
+ NEXT_ARG();
+ if (parse_police(&argc, &argv, TCA_FW_POLICE, n)) {
+ fprintf(stderr, "Illegal \"police\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (parse_action(&argc, &argv, TCA_FW_ACT, n)) {
+ fprintf(stderr, "Illegal fw \"action\"\n");
+ return -1;
+ }
+ continue;
+ } else if (strcmp(*argv, "indev") == 0) {
+ char d[IFNAMSIZ+1] = {};
+
+ argc--;
+ argv++;
+ if (argc < 1) {
+ fprintf(stderr, "Illegal indev\n");
+ return -1;
+ }
+ strncpy(d, *argv, sizeof(d) - 1);
+ addattr_l(n, MAX_MSG, TCA_FW_INDEV, d, strlen(d) + 1);
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int fw_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 handle)
+{
+ struct rtattr *tb[TCA_FW_MAX+1];
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_FW_MAX, opt);
+
+ if (handle || tb[TCA_FW_MASK]) {
+ __u32 mark = 0, mask = 0;
+
+ if (handle)
+ mark = handle;
+ if (tb[TCA_FW_MASK] &&
+ (mask = rta_getattr_u32(tb[TCA_FW_MASK])) != 0xFFFFFFFF)
+ fprintf(f, "handle 0x%x/0x%x ", mark, mask);
+ else
+ fprintf(f, "handle 0x%x ", handle);
+ }
+
+ if (tb[TCA_FW_CLASSID]) {
+ SPRINT_BUF(b1);
+ fprintf(f, "classid %s ", sprint_tc_classid(rta_getattr_u32(tb[TCA_FW_CLASSID]), b1));
+ }
+
+ if (tb[TCA_FW_POLICE])
+ tc_print_police(f, tb[TCA_FW_POLICE]);
+ if (tb[TCA_FW_INDEV]) {
+ struct rtattr *idev = tb[TCA_FW_INDEV];
+
+ fprintf(f, "input dev %s ", rta_getattr_str(idev));
+ }
+
+ if (tb[TCA_FW_ACT]) {
+ fprintf(f, "\n");
+ tc_print_action(f, tb[TCA_FW_ACT], 0);
+ }
+ return 0;
+}
+
+struct filter_util fw_filter_util = {
+ .id = "fw",
+ .parse_fopt = fw_parse_opt,
+ .print_fopt = fw_print_opt,
+};
diff --git a/tc/f_matchall.c b/tc/f_matchall.c
new file mode 100644
index 0000000..253ed5c
--- /dev/null
+++ b/tc/f_matchall.c
@@ -0,0 +1,172 @@
+/*
+ * f_matchall.c Match-all Classifier
+ *
+ * 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: Jiri Pirko <jiri@mellanox.com>, Yotam Gigi <yotamg@mellanox.com>
+ *
+ */
+
+#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 <linux/if.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... matchall [skip_sw | skip_hw]\n"
+ " [ action ACTION_SPEC ] [ classid CLASSID ]\n"
+ "\n"
+ "Where: SELECTOR := SAMPLE SAMPLE ...\n"
+ " FILTERID := X:Y:Z\n"
+ " ACTION_SPEC := ... look at individual actions\n"
+ "\n"
+ "NOTE: CLASSID is parsed as hexadecimal input.\n");
+}
+
+static int matchall_parse_opt(struct filter_util *qu, char *handle,
+ int argc, char **argv, struct nlmsghdr *n)
+{
+ struct tcmsg *t = NLMSG_DATA(n);
+ struct rtattr *tail;
+ __u32 flags = 0;
+ long h = 0;
+
+ if (handle) {
+ h = strtol(handle, NULL, 0);
+ if (h == LONG_MIN || h == LONG_MAX) {
+ fprintf(stderr, "Illegal handle \"%s\", must be numeric.\n",
+ handle);
+ return -1;
+ }
+ }
+ t->tcm_handle = h;
+
+ if (argc == 0)
+ return 0;
+
+ tail = (struct rtattr *)(((void *)n)+NLMSG_ALIGN(n->nlmsg_len));
+ addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);
+
+ while (argc > 0) {
+ if (matches(*argv, "classid") == 0 ||
+ strcmp(*argv, "flowid") == 0) {
+ unsigned int handle;
+
+ NEXT_ARG();
+ if (get_tc_classid(&handle, *argv)) {
+ fprintf(stderr, "Illegal \"classid\"\n");
+ return -1;
+ }
+ addattr_l(n, MAX_MSG, TCA_MATCHALL_CLASSID, &handle, 4);
+ } else if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (parse_action(&argc, &argv, TCA_MATCHALL_ACT, n)) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ continue;
+
+ } else if (strcmp(*argv, "skip_hw") == 0) {
+ NEXT_ARG();
+ flags |= TCA_CLS_FLAGS_SKIP_HW;
+ continue;
+ } else if (strcmp(*argv, "skip_sw") == 0) {
+ NEXT_ARG();
+ flags |= TCA_CLS_FLAGS_SKIP_SW;
+ continue;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (flags) {
+ if (!(flags ^ (TCA_CLS_FLAGS_SKIP_HW |
+ TCA_CLS_FLAGS_SKIP_SW))) {
+ fprintf(stderr,
+ "skip_hw and skip_sw are mutually exclusive\n");
+ return -1;
+ }
+ addattr_l(n, MAX_MSG, TCA_MATCHALL_FLAGS, &flags, 4);
+ }
+
+ tail->rta_len = (((void *)n)+n->nlmsg_len) - (void *)tail;
+ return 0;
+}
+
+static int matchall_print_opt(struct filter_util *qu, FILE *f,
+ struct rtattr *opt, __u32 handle)
+{
+ struct rtattr *tb[TCA_MATCHALL_MAX+1];
+ struct tc_matchall_pcnt *pf = NULL;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_MATCHALL_MAX, opt);
+
+ if (handle)
+ print_uint(PRINT_ANY, "handle", "handle 0x%x ", handle);
+
+ if (tb[TCA_MATCHALL_CLASSID]) {
+ SPRINT_BUF(b1);
+ print_string(PRINT_ANY, "flowid", "flowid %s ",
+ sprint_tc_classid(rta_getattr_u32(tb[TCA_MATCHALL_CLASSID]), b1));
+ }
+
+ if (tb[TCA_MATCHALL_FLAGS]) {
+ __u32 flags = rta_getattr_u32(tb[TCA_MATCHALL_FLAGS]);
+
+ if (flags & TCA_CLS_FLAGS_SKIP_HW)
+ print_bool(PRINT_ANY, "skip_hw", "\n skip_hw", true);
+ if (flags & TCA_CLS_FLAGS_SKIP_SW)
+ print_bool(PRINT_ANY, "skip_sw", "\n skip_sw", true);
+
+ if (flags & TCA_CLS_FLAGS_IN_HW)
+ print_bool(PRINT_ANY, "in_hw", "\n in_hw", true);
+ else if (flags & TCA_CLS_FLAGS_NOT_IN_HW)
+ print_bool(PRINT_ANY, "not_in_hw", "\n not_in_hw", true);
+ }
+
+ if (tb[TCA_MATCHALL_PCNT]) {
+ if (RTA_PAYLOAD(tb[TCA_MATCHALL_PCNT]) < sizeof(*pf)) {
+ print_string(PRINT_FP, NULL, "Broken perf counters\n", NULL);
+ return -1;
+ }
+ pf = RTA_DATA(tb[TCA_MATCHALL_PCNT]);
+ }
+
+ if (show_stats && NULL != pf)
+ print_u64(PRINT_ANY, "rule_hit", " (rule hit %llu)",
+ (unsigned long long) pf->rhit);
+
+
+ if (tb[TCA_MATCHALL_ACT])
+ tc_print_action(f, tb[TCA_MATCHALL_ACT], 0);
+
+ return 0;
+}
+
+struct filter_util matchall_filter_util = {
+ .id = "matchall",
+ .parse_fopt = matchall_parse_opt,
+ .print_fopt = matchall_print_opt,
+};
diff --git a/tc/f_route.c b/tc/f_route.c
new file mode 100644
index 0000000..31fa96a
--- /dev/null
+++ b/tc/f_route.c
@@ -0,0 +1,179 @@
+/*
+ * f_route.c ROUTE filter.
+ *
+ * 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 "utils.h"
+#include "rt_names.h"
+#include "tc_common.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... route [ from REALM | fromif TAG ] [ to REALM ]\n"
+ " [ classid CLASSID ] [ action ACTION_SPEC ]\n"
+ " ACTION_SPEC := ... look at individual actions\n"
+ " CLASSID := X:Y\n"
+ "\n"
+ "NOTE: CLASSID is parsed as hexadecimal input.\n");
+}
+
+static int route_parse_opt(struct filter_util *qu, char *handle, int argc, char **argv, struct nlmsghdr *n)
+{
+ struct tcmsg *t = NLMSG_DATA(n);
+ struct rtattr *tail;
+ __u32 fh = 0xFFFF8000;
+ __u32 order = 0;
+
+ if (handle) {
+ if (get_u32(&t->tcm_handle, handle, 0)) {
+ fprintf(stderr, "Illegal \"handle\"\n");
+ return -1;
+ }
+ }
+
+ if (argc == 0)
+ return 0;
+
+ tail = addattr_nest(n, 4096, TCA_OPTIONS);
+
+ while (argc > 0) {
+ if (matches(*argv, "to") == 0) {
+ __u32 id;
+
+ NEXT_ARG();
+ if (rtnl_rtrealm_a2n(&id, *argv)) {
+ fprintf(stderr, "Illegal \"to\"\n");
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_ROUTE4_TO, &id, 4);
+ fh &= ~0x80FF;
+ fh |= id&0xFF;
+ } else if (matches(*argv, "from") == 0) {
+ __u32 id;
+
+ NEXT_ARG();
+ if (rtnl_rtrealm_a2n(&id, *argv)) {
+ fprintf(stderr, "Illegal \"from\"\n");
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_ROUTE4_FROM, &id, 4);
+ fh &= 0xFFFF;
+ fh |= id<<16;
+ } else if (matches(*argv, "fromif") == 0) {
+ __u32 id;
+
+ NEXT_ARG();
+ ll_init_map(&rth);
+ if ((id = ll_name_to_index(*argv)) <= 0) {
+ fprintf(stderr, "Illegal \"fromif\"\n");
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_ROUTE4_IIF, &id, 4);
+ fh &= 0xFFFF;
+ fh |= (0x8000|id)<<16;
+ } else if (matches(*argv, "classid") == 0 ||
+ strcmp(*argv, "flowid") == 0) {
+ unsigned int handle;
+
+ NEXT_ARG();
+ if (get_tc_classid(&handle, *argv)) {
+ fprintf(stderr, "Illegal \"classid\"\n");
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_ROUTE4_CLASSID, &handle, 4);
+ } else if (matches(*argv, "police") == 0) {
+ NEXT_ARG();
+ if (parse_police(&argc, &argv, TCA_ROUTE4_POLICE, n)) {
+ fprintf(stderr, "Illegal \"police\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (parse_action(&argc, &argv, TCA_ROUTE4_ACT, n)) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "order") == 0) {
+ NEXT_ARG();
+ if (get_u32(&order, *argv, 0)) {
+ fprintf(stderr, "Illegal \"order\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+ addattr_nest_end(n, tail);
+ if (order) {
+ fh &= ~0x7F00;
+ fh |= (order<<8)&0x7F00;
+ }
+ if (!t->tcm_handle)
+ t->tcm_handle = fh;
+ return 0;
+}
+
+static int route_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 handle)
+{
+ struct rtattr *tb[TCA_ROUTE4_MAX+1];
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_ROUTE4_MAX, opt);
+
+ if (handle)
+ fprintf(f, "fh 0x%08x ", handle);
+ if (handle&0x7F00)
+ fprintf(f, "order %d ", (handle>>8)&0x7F);
+
+ if (tb[TCA_ROUTE4_CLASSID]) {
+ SPRINT_BUF(b1);
+ fprintf(f, "flowid %s ", sprint_tc_classid(rta_getattr_u32(tb[TCA_ROUTE4_CLASSID]), b1));
+ }
+ if (tb[TCA_ROUTE4_TO])
+ fprintf(f, "to %s ", rtnl_rtrealm_n2a(rta_getattr_u32(tb[TCA_ROUTE4_TO]), b1, sizeof(b1)));
+ if (tb[TCA_ROUTE4_FROM])
+ fprintf(f, "from %s ", rtnl_rtrealm_n2a(rta_getattr_u32(tb[TCA_ROUTE4_FROM]), b1, sizeof(b1)));
+ if (tb[TCA_ROUTE4_IIF])
+ fprintf(f, "fromif %s", ll_index_to_name(rta_getattr_u32(tb[TCA_ROUTE4_IIF])));
+ if (tb[TCA_ROUTE4_POLICE])
+ tc_print_police(f, tb[TCA_ROUTE4_POLICE]);
+ if (tb[TCA_ROUTE4_ACT])
+ tc_print_action(f, tb[TCA_ROUTE4_ACT], 0);
+ return 0;
+}
+
+struct filter_util route_filter_util = {
+ .id = "route",
+ .parse_fopt = route_parse_opt,
+ .print_fopt = route_print_opt,
+};
diff --git a/tc/f_rsvp.c b/tc/f_rsvp.c
new file mode 100644
index 0000000..388e9ee
--- /dev/null
+++ b/tc/f_rsvp.c
@@ -0,0 +1,422 @@
+/*
+ * q_rsvp.c RSVP filter.
+ *
+ * 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 "rt_names.h"
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... rsvp ipproto PROTOCOL session DST[/PORT | GPI ]\n"
+ " [ sender SRC[/PORT | GPI ] ]\n"
+ " [ classid CLASSID ] [ action ACTION_SPEC ]\n"
+ " [ tunnelid ID ] [ tunnel ID skip NUMBER ]\n"
+ "Where: GPI := { flowlabel NUMBER | spi/ah SPI | spi/esp SPI |\n"
+ " u{8|16|32} NUMBER mask MASK at OFFSET}\n"
+ " ACTION_SPEC := ... look at individual actions\n"
+ " FILTERID := X:Y\n"
+ "\nNOTE: CLASSID is parsed as hexadecimal input.\n");
+}
+
+static int get_addr_and_pi(int *argc_p, char ***argv_p, inet_prefix *addr,
+ struct tc_rsvp_pinfo *pinfo, int dir, int family)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ char *p = strchr(*argv, '/');
+ struct tc_rsvp_gpi *pi = dir ? &pinfo->dpi : &pinfo->spi;
+
+ if (p) {
+ __u16 tmp;
+
+ if (get_u16(&tmp, p+1, 0))
+ return -1;
+
+ if (dir == 0) {
+ /* Source port: u16 at offset 0 */
+ pi->key = htonl(((__u32)tmp)<<16);
+ pi->mask = htonl(0xFFFF0000);
+ } else {
+ /* Destination port: u16 at offset 2 */
+ pi->key = htonl(((__u32)tmp));
+ pi->mask = htonl(0x0000FFFF);
+ }
+ pi->offset = 0;
+ *p = 0;
+ }
+ if (get_addr_1(addr, *argv, family))
+ return -1;
+ if (p)
+ *p = '/';
+
+ argc--; argv++;
+
+ if (pi->mask || argc <= 0)
+ goto done;
+
+ if (strcmp(*argv, "spi/ah") == 0 ||
+ strcmp(*argv, "gpi/ah") == 0) {
+ __u32 gpi;
+
+ NEXT_ARG();
+ if (get_u32(&gpi, *argv, 0))
+ return -1;
+ pi->mask = htonl(0xFFFFFFFF);
+ pi->key = htonl(gpi);
+ pi->offset = 4;
+ if (pinfo->protocol == 0)
+ pinfo->protocol = IPPROTO_AH;
+ argc--; argv++;
+ } else if (strcmp(*argv, "spi/esp") == 0 ||
+ strcmp(*argv, "gpi/esp") == 0) {
+ __u32 gpi;
+
+ NEXT_ARG();
+ if (get_u32(&gpi, *argv, 0))
+ return -1;
+ pi->mask = htonl(0xFFFFFFFF);
+ pi->key = htonl(gpi);
+ pi->offset = 0;
+ if (pinfo->protocol == 0)
+ pinfo->protocol = IPPROTO_ESP;
+ argc--; argv++;
+ } else if (strcmp(*argv, "flowlabel") == 0) {
+ __u32 flabel;
+
+ NEXT_ARG();
+ if (get_u32(&flabel, *argv, 0))
+ return -1;
+ if (family != AF_INET6)
+ return -1;
+ pi->mask = htonl(0x000FFFFF);
+ pi->key = htonl(flabel) & pi->mask;
+ pi->offset = -40;
+ argc--; argv++;
+ } else if (strcmp(*argv, "u32") == 0 ||
+ strcmp(*argv, "u16") == 0 ||
+ strcmp(*argv, "u8") == 0) {
+ int sz = 1;
+ __u32 tmp;
+ __u32 mask = 0xff;
+
+ if (strcmp(*argv, "u32") == 0) {
+ sz = 4;
+ mask = 0xffff;
+ } else if (strcmp(*argv, "u16") == 0) {
+ mask = 0xffffffff;
+ sz = 2;
+ }
+ NEXT_ARG();
+ if (get_u32(&tmp, *argv, 0))
+ return -1;
+ argc--; argv++;
+ if (strcmp(*argv, "mask") == 0) {
+ NEXT_ARG();
+ if (get_u32(&mask, *argv, 16))
+ return -1;
+ argc--; argv++;
+ }
+ if (strcmp(*argv, "at") == 0) {
+ NEXT_ARG();
+ if (get_integer(&pi->offset, *argv, 0))
+ return -1;
+ argc--; argv++;
+ }
+ if (sz == 1) {
+ if ((pi->offset & 3) == 0) {
+ mask <<= 24;
+ tmp <<= 24;
+ } else if ((pi->offset & 3) == 1) {
+ mask <<= 16;
+ tmp <<= 16;
+ } else if ((pi->offset & 3) == 3) {
+ mask <<= 8;
+ tmp <<= 8;
+ }
+ } else if (sz == 2) {
+ if ((pi->offset & 3) == 0) {
+ mask <<= 16;
+ tmp <<= 16;
+ }
+ }
+ pi->offset &= ~3;
+ pi->mask = htonl(mask);
+ pi->key = htonl(tmp) & pi->mask;
+ }
+
+done:
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+
+static int rsvp_parse_opt(struct filter_util *qu, char *handle, int argc,
+ char **argv, struct nlmsghdr *n)
+{
+ int family = strcmp(qu->id, "rsvp") == 0 ? AF_INET : AF_INET6;
+ struct tc_rsvp_pinfo pinfo = {};
+ struct tcmsg *t = NLMSG_DATA(n);
+ int pinfo_ok = 0;
+ struct rtattr *tail;
+
+ if (handle) {
+ if (get_u32(&t->tcm_handle, handle, 0)) {
+ fprintf(stderr, "Illegal \"handle\"\n");
+ return -1;
+ }
+ }
+
+ if (argc == 0)
+ return 0;
+
+ tail = addattr_nest(n, 4096, TCA_OPTIONS);
+
+ while (argc > 0) {
+ if (matches(*argv, "session") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (get_addr_and_pi(&argc, &argv, &addr, &pinfo, 1, family)) {
+ fprintf(stderr, "Illegal \"session\"\n");
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_RSVP_DST, &addr.data, addr.bytelen);
+ if (pinfo.dpi.mask || pinfo.protocol)
+ pinfo_ok++;
+ continue;
+ } else if (matches(*argv, "sender") == 0 ||
+ matches(*argv, "flowspec") == 0) {
+ inet_prefix addr;
+
+ NEXT_ARG();
+ if (get_addr_and_pi(&argc, &argv, &addr, &pinfo, 0, family)) {
+ fprintf(stderr, "Illegal \"sender\"\n");
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_RSVP_SRC, &addr.data, addr.bytelen);
+ if (pinfo.spi.mask || pinfo.protocol)
+ pinfo_ok++;
+ continue;
+ } else if (matches("ipproto", *argv) == 0) {
+ int num;
+
+ NEXT_ARG();
+ num = inet_proto_a2n(*argv);
+ if (num < 0) {
+ fprintf(stderr, "Illegal \"ipproto\"\n");
+ return -1;
+ }
+ pinfo.protocol = num;
+ pinfo_ok++;
+ } else if (matches(*argv, "classid") == 0 ||
+ strcmp(*argv, "flowid") == 0) {
+ unsigned int handle;
+
+ NEXT_ARG();
+ if (get_tc_classid(&handle, *argv)) {
+ fprintf(stderr, "Illegal \"classid\"\n");
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_RSVP_CLASSID, &handle, 4);
+ } else if (strcmp(*argv, "tunnelid") == 0) {
+ unsigned int tid;
+
+ NEXT_ARG();
+ if (get_unsigned(&tid, *argv, 0)) {
+ fprintf(stderr, "Illegal \"tunnelid\"\n");
+ return -1;
+ }
+ pinfo.tunnelid = tid;
+ pinfo_ok++;
+ } else if (strcmp(*argv, "tunnel") == 0) {
+ unsigned int tid;
+
+ NEXT_ARG();
+ if (get_unsigned(&tid, *argv, 0)) {
+ fprintf(stderr, "Illegal \"tunnel\"\n");
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_RSVP_CLASSID, &tid, 4);
+ NEXT_ARG();
+ if (strcmp(*argv, "skip") == 0) {
+ NEXT_ARG();
+ }
+ if (get_unsigned(&tid, *argv, 0)) {
+ fprintf(stderr, "Illegal \"skip\"\n");
+ return -1;
+ }
+ pinfo.tunnelhdr = tid;
+ pinfo_ok++;
+ } else if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (parse_action(&argc, &argv, TCA_RSVP_ACT, n)) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "police") == 0) {
+ NEXT_ARG();
+ if (parse_police(&argc, &argv, TCA_RSVP_POLICE, n)) {
+ fprintf(stderr, "Illegal \"police\"\n");
+ return -1;
+ }
+ continue;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (pinfo_ok)
+ addattr_l(n, 4096, TCA_RSVP_PINFO, &pinfo, sizeof(pinfo));
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static char *sprint_spi(struct tc_rsvp_gpi *pi, int dir, char *buf)
+{
+ if (pi->offset == 0) {
+ if (dir && pi->mask == htonl(0xFFFF)) {
+ snprintf(buf, SPRINT_BSIZE-1, "/%d", htonl(pi->key));
+ return buf;
+ }
+ if (!dir && pi->mask == htonl(0xFFFF0000)) {
+ snprintf(buf, SPRINT_BSIZE-1, "/%d", htonl(pi->key)>>16);
+ return buf;
+ }
+ if (pi->mask == htonl(0xFFFFFFFF)) {
+ snprintf(buf, SPRINT_BSIZE-1, " spi/esp 0x%08x", htonl(pi->key));
+ return buf;
+ }
+ } else if (pi->offset == 4 && pi->mask == htonl(0xFFFFFFFF)) {
+ snprintf(buf, SPRINT_BSIZE-1, " spi/ah 0x%08x", htonl(pi->key));
+ return buf;
+ } else if (pi->offset == -40 && pi->mask == htonl(0x000FFFFF)) {
+ snprintf(buf, SPRINT_BSIZE-1, " flowlabel 0x%05x", htonl(pi->key));
+ return buf;
+ }
+ snprintf(buf, SPRINT_BSIZE-1, " u32 0x%08x mask %08x at %d",
+ htonl(pi->key), htonl(pi->mask), pi->offset);
+ return buf;
+}
+
+static int rsvp_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 handle)
+{
+ int family = strcmp(qu->id, "rsvp") == 0 ? AF_INET : AF_INET6;
+ struct rtattr *tb[TCA_RSVP_MAX+1];
+ struct tc_rsvp_pinfo *pinfo = NULL;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_RSVP_MAX, opt);
+
+ if (handle)
+ fprintf(f, "fh 0x%08x ", handle);
+
+ if (tb[TCA_RSVP_PINFO]) {
+ if (RTA_PAYLOAD(tb[TCA_RSVP_PINFO]) < sizeof(*pinfo))
+ return -1;
+
+ pinfo = RTA_DATA(tb[TCA_RSVP_PINFO]);
+ }
+
+ if (tb[TCA_RSVP_CLASSID]) {
+ SPRINT_BUF(b1);
+ if (!pinfo || pinfo->tunnelhdr == 0)
+ fprintf(f, "flowid %s ", sprint_tc_classid(rta_getattr_u32(tb[TCA_RSVP_CLASSID]), b1));
+ else
+ fprintf(f, "tunnel %d skip %d ", rta_getattr_u32(tb[TCA_RSVP_CLASSID]), pinfo->tunnelhdr);
+ } else if (pinfo && pinfo->tunnelhdr)
+ fprintf(f, "tunnel [BAD] skip %d ", pinfo->tunnelhdr);
+
+ if (tb[TCA_RSVP_DST]) {
+ char buf[128];
+
+ fprintf(f, "session ");
+ if (inet_ntop(family, RTA_DATA(tb[TCA_RSVP_DST]), buf, sizeof(buf)) == 0)
+ fprintf(f, " [INVALID DADDR] ");
+ else
+ fprintf(f, "%s", buf);
+ if (pinfo && pinfo->dpi.mask) {
+ SPRINT_BUF(b2);
+ fprintf(f, "%s ", sprint_spi(&pinfo->dpi, 1, b2));
+ } else
+ fprintf(f, " ");
+ } else {
+ if (pinfo && pinfo->dpi.mask) {
+ SPRINT_BUF(b2);
+ fprintf(f, "session [NONE]%s ", sprint_spi(&pinfo->dpi, 1, b2));
+ } else
+ fprintf(f, "session NONE ");
+ }
+
+ if (pinfo && pinfo->protocol) {
+ SPRINT_BUF(b1);
+ fprintf(f, "ipproto %s ", inet_proto_n2a(pinfo->protocol, b1, sizeof(b1)));
+ }
+ if (pinfo && pinfo->tunnelid)
+ fprintf(f, "tunnelid %d ", pinfo->tunnelid);
+ if (tb[TCA_RSVP_SRC]) {
+ char buf[128];
+
+ fprintf(f, "sender ");
+ if (inet_ntop(family, RTA_DATA(tb[TCA_RSVP_SRC]), buf, sizeof(buf)) == 0) {
+ fprintf(f, "[BAD]");
+ } else {
+ fprintf(f, " %s", buf);
+ }
+ if (pinfo && pinfo->spi.mask) {
+ SPRINT_BUF(b2);
+ fprintf(f, "%s ", sprint_spi(&pinfo->spi, 0, b2));
+ } else
+ fprintf(f, " ");
+ } else if (pinfo && pinfo->spi.mask) {
+ SPRINT_BUF(b2);
+ fprintf(f, "sender [NONE]%s ", sprint_spi(&pinfo->spi, 0, b2));
+ }
+
+ if (tb[TCA_RSVP_ACT]) {
+ tc_print_action(f, tb[TCA_RSVP_ACT], 0);
+ }
+ if (tb[TCA_RSVP_POLICE])
+ tc_print_police(f, tb[TCA_RSVP_POLICE]);
+ return 0;
+}
+
+struct filter_util rsvp_filter_util = {
+ .id = "rsvp",
+ .parse_fopt = rsvp_parse_opt,
+ .print_fopt = rsvp_print_opt,
+};
+
+struct filter_util rsvp6_filter_util = {
+ .id = "rsvp6",
+ .parse_fopt = rsvp_parse_opt,
+ .print_fopt = rsvp_print_opt,
+};
diff --git a/tc/f_tcindex.c b/tc/f_tcindex.c
new file mode 100644
index 0000000..ae4cbf1
--- /dev/null
+++ b/tc/f_tcindex.c
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * f_tcindex.c Traffic control index filter
+ *
+ * Written 1998,1999 by Werner Almesberger
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <netinet/in.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ " Usage: ... tcindex [ hash SIZE ] [ mask MASK ] [ shift SHIFT ]\n"
+ " [ pass_on | fall_through ]\n"
+ " [ classid CLASSID ] [ action ACTION_SPEC ]\n");
+}
+
+static int tcindex_parse_opt(struct filter_util *qu, char *handle, int argc,
+ char **argv, struct nlmsghdr *n)
+{
+ struct tcmsg *t = NLMSG_DATA(n);
+ struct rtattr *tail;
+ char *end;
+
+ if (handle) {
+ t->tcm_handle = strtoul(handle, &end, 0);
+ if (*end) {
+ fprintf(stderr, "Illegal filter ID\n");
+ return -1;
+ }
+ }
+ if (!argc) return 0;
+ tail = addattr_nest(n, 4096, TCA_OPTIONS);
+ while (argc) {
+ if (!strcmp(*argv, "hash")) {
+ int hash;
+
+ NEXT_ARG();
+ hash = strtoul(*argv, &end, 0);
+ if (*end || !hash || hash > 0x10000) {
+ explain();
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_TCINDEX_HASH, &hash,
+ sizeof(hash));
+ } else if (!strcmp(*argv,"mask")) {
+ __u16 mask;
+
+ NEXT_ARG();
+ mask = strtoul(*argv, &end, 0);
+ if (*end) {
+ explain();
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_TCINDEX_MASK, &mask,
+ sizeof(mask));
+ } else if (!strcmp(*argv,"shift")) {
+ int shift;
+
+ NEXT_ARG();
+ shift = strtoul(*argv, &end, 0);
+ if (*end) {
+ explain();
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_TCINDEX_SHIFT, &shift,
+ sizeof(shift));
+ } else if (!strcmp(*argv,"fall_through")) {
+ int value = 1;
+
+ addattr_l(n, 4096, TCA_TCINDEX_FALL_THROUGH, &value,
+ sizeof(value));
+ } else if (!strcmp(*argv,"pass_on")) {
+ int value = 0;
+
+ addattr_l(n, 4096, TCA_TCINDEX_FALL_THROUGH, &value,
+ sizeof(value));
+ } else if (!strcmp(*argv,"classid")) {
+ __u32 handle;
+
+ NEXT_ARG();
+ if (get_tc_classid(&handle, *argv)) {
+ fprintf(stderr, "Illegal \"classid\"\n");
+ return -1;
+ }
+ addattr_l(n, 4096, TCA_TCINDEX_CLASSID, &handle, 4);
+ } else if (!strcmp(*argv,"police")) {
+ NEXT_ARG();
+ if (parse_police(&argc, &argv, TCA_TCINDEX_POLICE, n)) {
+ fprintf(stderr, "Illegal \"police\"\n");
+ return -1;
+ }
+ continue;
+ } else if (!strcmp(*argv,"action")) {
+ NEXT_ARG();
+ if (parse_action(&argc, &argv, TCA_TCINDEX_ACT, n)) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ continue;
+ } else {
+ explain();
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+
+static int tcindex_print_opt(struct filter_util *qu, FILE *f,
+ struct rtattr *opt, __u32 handle)
+{
+ struct rtattr *tb[TCA_TCINDEX_MAX+1];
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_TCINDEX_MAX, opt);
+
+ if (handle != ~0) fprintf(f, "handle 0x%04x ", handle);
+ if (tb[TCA_TCINDEX_HASH]) {
+ __u16 hash;
+
+ if (RTA_PAYLOAD(tb[TCA_TCINDEX_HASH]) < sizeof(hash))
+ return -1;
+ hash = rta_getattr_u16(tb[TCA_TCINDEX_HASH]);
+ fprintf(f, "hash %d ", hash);
+ }
+ if (tb[TCA_TCINDEX_MASK]) {
+ __u16 mask;
+
+ if (RTA_PAYLOAD(tb[TCA_TCINDEX_MASK]) < sizeof(mask))
+ return -1;
+ mask = rta_getattr_u16(tb[TCA_TCINDEX_MASK]);
+ fprintf(f, "mask 0x%04x ", mask);
+ }
+ if (tb[TCA_TCINDEX_SHIFT]) {
+ int shift;
+
+ if (RTA_PAYLOAD(tb[TCA_TCINDEX_SHIFT]) < sizeof(shift))
+ return -1;
+ shift = rta_getattr_u32(tb[TCA_TCINDEX_SHIFT]);
+ fprintf(f, "shift %d ", shift);
+ }
+ if (tb[TCA_TCINDEX_FALL_THROUGH]) {
+ int fall_through;
+
+ if (RTA_PAYLOAD(tb[TCA_TCINDEX_FALL_THROUGH]) <
+ sizeof(fall_through))
+ return -1;
+ fall_through = rta_getattr_u32(tb[TCA_TCINDEX_FALL_THROUGH]);
+ fprintf(f, fall_through ? "fall_through " : "pass_on ");
+ }
+ if (tb[TCA_TCINDEX_CLASSID]) {
+ SPRINT_BUF(b1);
+ fprintf(f, "classid %s ", sprint_tc_classid(*(__u32 *)
+ RTA_DATA(tb[TCA_TCINDEX_CLASSID]), b1));
+ }
+ if (tb[TCA_TCINDEX_POLICE]) {
+ fprintf(f, "\n");
+ tc_print_police(f, tb[TCA_TCINDEX_POLICE]);
+ }
+ if (tb[TCA_TCINDEX_ACT]) {
+ fprintf(f, "\n");
+ tc_print_action(f, tb[TCA_TCINDEX_ACT], 0);
+ }
+ return 0;
+}
+
+struct filter_util tcindex_filter_util = {
+ .id = "tcindex",
+ .parse_fopt = tcindex_parse_opt,
+ .print_fopt = tcindex_print_opt,
+};
diff --git a/tc/f_u32.c b/tc/f_u32.c
new file mode 100644
index 0000000..e0a322d
--- /dev/null
+++ b/tc/f_u32.c
@@ -0,0 +1,1340 @@
+/*
+ * q_u32.c U32 filter.
+ *
+ * This program is free software; you can u32istribute 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>
+ * Match mark added by Catalin(ux aka Dino) BOIE <catab at umbrella.ro> [5 nov 2004]
+ *
+ */
+
+#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 <linux/if.h>
+#include <linux/if_ether.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... u32 [ match SELECTOR ... ] [ link HTID ] [ classid CLASSID ]\n"
+ " [ action ACTION_SPEC ] [ offset OFFSET_SPEC ]\n"
+ " [ ht HTID ] [ hashkey HASHKEY_SPEC ]\n"
+ " [ sample SAMPLE ] [skip_hw | skip_sw]\n"
+ "or u32 divisor DIVISOR\n"
+ "\n"
+ "Where: SELECTOR := SAMPLE SAMPLE ...\n"
+ " SAMPLE := { ip | ip6 | udp | tcp | icmp | u{32|16|8} | mark }\n"
+ " SAMPLE_ARGS [ divisor DIVISOR ]\n"
+ " FILTERID := X:Y:Z\n"
+ "\nNOTE: CLASSID is parsed at hexadecimal input.\n");
+}
+
+static int get_u32_handle(__u32 *handle, const char *str)
+{
+ __u32 htid = 0, hash = 0, nodeid = 0;
+ char *tmp = strchr(str, ':');
+
+ if (tmp == NULL) {
+ if (memcmp("0x", str, 2) == 0)
+ return get_u32(handle, str, 16);
+ return -1;
+ }
+ htid = strtoul(str, &tmp, 16);
+ if (tmp == str && *str != ':' && *str != 0)
+ return -1;
+ if (htid >= 0x1000)
+ return -1;
+ if (*tmp) {
+ str = tmp + 1;
+ hash = strtoul(str, &tmp, 16);
+ if (tmp == str && *str != ':' && *str != 0)
+ return -1;
+ if (hash >= 0x100)
+ return -1;
+ if (*tmp) {
+ str = tmp + 1;
+ nodeid = strtoul(str, &tmp, 16);
+ if (tmp == str && *str != 0)
+ return -1;
+ if (nodeid >= 0x1000)
+ return -1;
+ }
+ }
+ *handle = (htid<<20)|(hash<<12)|nodeid;
+ return 0;
+}
+
+static char *sprint_u32_handle(__u32 handle, char *buf)
+{
+ int bsize = SPRINT_BSIZE-1;
+ __u32 htid = TC_U32_HTID(handle);
+ __u32 hash = TC_U32_HASH(handle);
+ __u32 nodeid = TC_U32_NODE(handle);
+ char *b = buf;
+
+ if (handle == 0) {
+ snprintf(b, bsize, "none");
+ return b;
+ }
+ if (htid) {
+ int l = snprintf(b, bsize, "%x:", htid>>20);
+
+ bsize -= l;
+ b += l;
+ }
+ if (nodeid|hash) {
+ if (hash) {
+ int l = snprintf(b, bsize, "%x", hash);
+
+ bsize -= l;
+ b += l;
+ }
+ if (nodeid) {
+ int l = snprintf(b, bsize, ":%x", nodeid);
+
+ bsize -= l;
+ b += l;
+ }
+ }
+ if (show_raw)
+ snprintf(b, bsize, "[%08x] ", handle);
+ return buf;
+}
+
+static int pack_key(struct tc_u32_sel *sel, __u32 key, __u32 mask,
+ int off, int offmask)
+{
+ int i;
+ int hwm = sel->nkeys;
+
+ key &= mask;
+
+ for (i = 0; i < hwm; i++) {
+ if (sel->keys[i].off == off && sel->keys[i].offmask == offmask) {
+ __u32 intersect = mask & sel->keys[i].mask;
+
+ if ((key ^ sel->keys[i].val) & intersect)
+ return -1;
+ sel->keys[i].val |= key;
+ sel->keys[i].mask |= mask;
+ return 0;
+ }
+ }
+
+ if (hwm >= 128)
+ return -1;
+ if (off % 4)
+ return -1;
+ sel->keys[hwm].val = key;
+ sel->keys[hwm].mask = mask;
+ sel->keys[hwm].off = off;
+ sel->keys[hwm].offmask = offmask;
+ sel->nkeys++;
+ return 0;
+}
+
+static int pack_key32(struct tc_u32_sel *sel, __u32 key, __u32 mask,
+ int off, int offmask)
+{
+ key = htonl(key);
+ mask = htonl(mask);
+ return pack_key(sel, key, mask, off, offmask);
+}
+
+static int pack_key16(struct tc_u32_sel *sel, __u32 key, __u32 mask,
+ int off, int offmask)
+{
+ if (key > 0xFFFF || mask > 0xFFFF)
+ return -1;
+
+ if ((off & 3) == 0) {
+ key <<= 16;
+ mask <<= 16;
+ }
+ off &= ~3;
+ key = htonl(key);
+ mask = htonl(mask);
+
+ return pack_key(sel, key, mask, off, offmask);
+}
+
+static int pack_key8(struct tc_u32_sel *sel, __u32 key, __u32 mask, int off,
+ int offmask)
+{
+ if (key > 0xFF || mask > 0xFF)
+ return -1;
+
+ if ((off & 3) == 0) {
+ key <<= 24;
+ mask <<= 24;
+ } else if ((off & 3) == 1) {
+ key <<= 16;
+ mask <<= 16;
+ } else if ((off & 3) == 2) {
+ key <<= 8;
+ mask <<= 8;
+ }
+ off &= ~3;
+ key = htonl(key);
+ mask = htonl(mask);
+
+ return pack_key(sel, key, mask, off, offmask);
+}
+
+
+static int parse_at(int *argc_p, char ***argv_p, int *off, int *offmask)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ char *p = *argv;
+
+ if (argc <= 0)
+ return -1;
+
+ if (strlen(p) > strlen("nexthdr+") &&
+ memcmp(p, "nexthdr+", strlen("nexthdr+")) == 0) {
+ *offmask = -1;
+ p += strlen("nexthdr+");
+ } else if (matches(*argv, "nexthdr+") == 0) {
+ NEXT_ARG();
+ *offmask = -1;
+ p = *argv;
+ }
+
+ if (get_integer(off, p, 0))
+ return -1;
+ argc--; argv++;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+
+static int parse_u32(int *argc_p, char ***argv_p, struct tc_u32_sel *sel,
+ int off, int offmask)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ __u32 key;
+ __u32 mask;
+
+ if (argc < 2)
+ return -1;
+
+ if (get_u32(&key, *argv, 0))
+ return -1;
+ argc--; argv++;
+
+ if (get_u32(&mask, *argv, 16))
+ return -1;
+ argc--; argv++;
+
+ if (argc > 0 && strcmp(argv[0], "at") == 0) {
+ NEXT_ARG();
+ if (parse_at(&argc, &argv, &off, &offmask))
+ return -1;
+ }
+
+ res = pack_key32(sel, key, mask, off, offmask);
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_u16(int *argc_p, char ***argv_p, struct tc_u32_sel *sel,
+ int off, int offmask)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ __u32 key;
+ __u32 mask;
+
+ if (argc < 2)
+ return -1;
+
+ if (get_u32(&key, *argv, 0))
+ return -1;
+ argc--; argv++;
+
+ if (get_u32(&mask, *argv, 16))
+ return -1;
+ argc--; argv++;
+
+ if (argc > 0 && strcmp(argv[0], "at") == 0) {
+ NEXT_ARG();
+ if (parse_at(&argc, &argv, &off, &offmask))
+ return -1;
+ }
+ res = pack_key16(sel, key, mask, off, offmask);
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_u8(int *argc_p, char ***argv_p, struct tc_u32_sel *sel,
+ int off, int offmask)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ __u32 key;
+ __u32 mask;
+
+ if (argc < 2)
+ return -1;
+
+ if (get_u32(&key, *argv, 0))
+ return -1;
+ argc--; argv++;
+
+ if (get_u32(&mask, *argv, 16))
+ return -1;
+ argc--; argv++;
+
+ if (key > 0xFF || mask > 0xFF)
+ return -1;
+
+ if (argc > 0 && strcmp(argv[0], "at") == 0) {
+ NEXT_ARG();
+ if (parse_at(&argc, &argv, &off, &offmask))
+ return -1;
+ }
+
+ res = pack_key8(sel, key, mask, off, offmask);
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_ip_addr(int *argc_p, char ***argv_p, struct tc_u32_sel *sel,
+ int off)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ inet_prefix addr;
+ __u32 mask;
+ int offmask = 0;
+
+ if (argc < 1)
+ return -1;
+
+ if (get_prefix_1(&addr, *argv, AF_INET))
+ return -1;
+ argc--; argv++;
+
+ if (argc > 0 && strcmp(argv[0], "at") == 0) {
+ NEXT_ARG();
+ if (parse_at(&argc, &argv, &off, &offmask))
+ return -1;
+ }
+
+ mask = 0;
+ if (addr.bitlen)
+ mask = htonl(0xFFFFFFFF << (32 - addr.bitlen));
+ if (pack_key(sel, addr.data[0], mask, off, offmask) < 0)
+ return -1;
+ res = 0;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_ip6_addr(int *argc_p, char ***argv_p,
+ struct tc_u32_sel *sel, int off)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int plen = 128;
+ int i;
+ inet_prefix addr;
+ int offmask = 0;
+
+ if (argc < 1)
+ return -1;
+
+ if (get_prefix_1(&addr, *argv, AF_INET6))
+ return -1;
+ argc--; argv++;
+
+ if (argc > 0 && strcmp(argv[0], "at") == 0) {
+ NEXT_ARG();
+ if (parse_at(&argc, &argv, &off, &offmask))
+ return -1;
+ }
+
+ plen = addr.bitlen;
+ for (i = 0; i < plen; i += 32) {
+ if (i + 31 < plen) {
+ res = pack_key(sel, addr.data[i / 32],
+ 0xFFFFFFFF, off + 4 * (i / 32), offmask);
+ if (res < 0)
+ return -1;
+ } else if (i < plen) {
+ __u32 mask = htonl(0xFFFFFFFF << (32 - (plen - i)));
+
+ res = pack_key(sel, addr.data[i / 32],
+ mask, off + 4 * (i / 32), offmask);
+ if (res < 0)
+ return -1;
+ }
+ }
+ res = 0;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_ip6_class(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ __u32 key;
+ __u32 mask;
+ int off = 0;
+ int offmask = 0;
+
+ if (argc < 2)
+ return -1;
+
+ if (get_u32(&key, *argv, 0))
+ return -1;
+ argc--; argv++;
+
+ if (get_u32(&mask, *argv, 16))
+ return -1;
+ argc--; argv++;
+
+ if (key > 0xFF || mask > 0xFF)
+ return -1;
+
+ key <<= 20;
+ mask <<= 20;
+ key = htonl(key);
+ mask = htonl(mask);
+
+ res = pack_key(sel, key, mask, off, offmask);
+ if (res < 0)
+ return -1;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int parse_ether_addr(int *argc_p, char ***argv_p,
+ struct tc_u32_sel *sel, int off)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ __u8 addr[6];
+ int offmask = 0;
+ int i;
+
+ if (argc < 1)
+ return -1;
+
+ if (sscanf(*argv, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ addr + 0, addr + 1, addr + 2,
+ addr + 3, addr + 4, addr + 5) != 6) {
+ fprintf(stderr, "parse_ether_addr: improperly formed address '%s'\n",
+ *argv);
+ return -1;
+ }
+
+ argc--; argv++;
+ if (argc > 0 && strcmp(argv[0], "at") == 0) {
+ NEXT_ARG();
+ if (parse_at(&argc, &argv, &off, &offmask))
+ return -1;
+ }
+
+ for (i = 0; i < 6; i++) {
+ res = pack_key8(sel, addr[i], 0xFF, off + i, offmask);
+ if (res < 0)
+ return -1;
+ }
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_ip(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+ res = parse_ip_addr(&argc, &argv, sel, 12);
+ } else if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+ res = parse_ip_addr(&argc, &argv, sel, 16);
+ } else if (strcmp(*argv, "tos") == 0 ||
+ matches(*argv, "dsfield") == 0 ||
+ matches(*argv, "precedence") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 1, 0);
+ } else if (strcmp(*argv, "ihl") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 0, 0);
+ } else if (strcmp(*argv, "protocol") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 9, 0);
+ } else if (strcmp(*argv, "nofrag") == 0) {
+ argc--; argv++;
+ res = pack_key16(sel, 0, 0x3FFF, 6, 0);
+ } else if (strcmp(*argv, "firstfrag") == 0) {
+ argc--; argv++;
+ res = pack_key16(sel, 0x2000, 0x3FFF, 6, 0);
+ } else if (strcmp(*argv, "df") == 0) {
+ argc--; argv++;
+ res = pack_key16(sel, 0x4000, 0x4000, 6, 0);
+ } else if (strcmp(*argv, "mf") == 0) {
+ argc--; argv++;
+ res = pack_key16(sel, 0x2000, 0x2000, 6, 0);
+ } else if (strcmp(*argv, "dport") == 0) {
+ NEXT_ARG();
+ res = parse_u16(&argc, &argv, sel, 22, 0);
+ } else if (strcmp(*argv, "sport") == 0) {
+ NEXT_ARG();
+ res = parse_u16(&argc, &argv, sel, 20, 0);
+ } else if (strcmp(*argv, "icmp_type") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 20, 0);
+ } else if (strcmp(*argv, "icmp_code") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 21, 0);
+ } else
+ return -1;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_ip6(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+ res = parse_ip6_addr(&argc, &argv, sel, 8);
+ } else if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+ res = parse_ip6_addr(&argc, &argv, sel, 24);
+ } else if (strcmp(*argv, "priority") == 0) {
+ NEXT_ARG();
+ res = parse_ip6_class(&argc, &argv, sel);
+ } else if (strcmp(*argv, "protocol") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 6, 0);
+ } else if (strcmp(*argv, "flowlabel") == 0) {
+ NEXT_ARG();
+ res = parse_u32(&argc, &argv, sel, 0, 0);
+ } else if (strcmp(*argv, "dport") == 0) {
+ NEXT_ARG();
+ res = parse_u16(&argc, &argv, sel, 42, 0);
+ } else if (strcmp(*argv, "sport") == 0) {
+ NEXT_ARG();
+ res = parse_u16(&argc, &argv, sel, 40, 0);
+ } else if (strcmp(*argv, "icmp_type") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 40, 0);
+ } else if (strcmp(*argv, "icmp_code") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 41, 1);
+ } else
+ return -1;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_ether(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+ res = parse_ether_addr(&argc, &argv, sel, -8);
+ } else if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+ res = parse_ether_addr(&argc, &argv, sel, -14);
+ } else {
+ fprintf(stderr, "Unknown match: ether %s\n", *argv);
+ return -1;
+ }
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+#define parse_tcp parse_udp
+static int parse_udp(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+ res = parse_u16(&argc, &argv, sel, 0, -1);
+ } else if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+ res = parse_u16(&argc, &argv, sel, 2, -1);
+ } else
+ return -1;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+
+static int parse_icmp(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ if (strcmp(*argv, "type") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 0, -1);
+ } else if (strcmp(*argv, "code") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 1, -1);
+ } else
+ return -1;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_mark(int *argc_p, char ***argv_p, struct nlmsghdr *n)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ struct tc_u32_mark mark;
+
+ if (argc <= 1)
+ return -1;
+
+ if (get_u32(&mark.val, *argv, 0)) {
+ fprintf(stderr, "Illegal \"mark\" value\n");
+ return -1;
+ }
+ NEXT_ARG();
+
+ if (get_u32(&mark.mask, *argv, 0)) {
+ fprintf(stderr, "Illegal \"mark\" mask\n");
+ return -1;
+ }
+ NEXT_ARG();
+
+ if ((mark.val & mark.mask) != mark.val) {
+ fprintf(stderr, "Illegal \"mark\" (impossible combination)\n");
+ return -1;
+ }
+
+ addattr_l(n, MAX_MSG, TCA_U32_MARK, &mark, sizeof(mark));
+ res = 0;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_selector(int *argc_p, char ***argv_p,
+ struct tc_u32_sel *sel, struct nlmsghdr *n)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int res = -1;
+
+ if (argc <= 0)
+ return -1;
+
+ if (matches(*argv, "u32") == 0) {
+ NEXT_ARG();
+ res = parse_u32(&argc, &argv, sel, 0, 0);
+ } else if (matches(*argv, "u16") == 0) {
+ NEXT_ARG();
+ res = parse_u16(&argc, &argv, sel, 0, 0);
+ } else if (matches(*argv, "u8") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, sel, 0, 0);
+ } else if (matches(*argv, "ip") == 0) {
+ NEXT_ARG();
+ res = parse_ip(&argc, &argv, sel);
+ } else if (matches(*argv, "ip6") == 0) {
+ NEXT_ARG();
+ res = parse_ip6(&argc, &argv, sel);
+ } else if (matches(*argv, "udp") == 0) {
+ NEXT_ARG();
+ res = parse_udp(&argc, &argv, sel);
+ } else if (matches(*argv, "tcp") == 0) {
+ NEXT_ARG();
+ res = parse_tcp(&argc, &argv, sel);
+ } else if (matches(*argv, "icmp") == 0) {
+ NEXT_ARG();
+ res = parse_icmp(&argc, &argv, sel);
+ } else if (matches(*argv, "mark") == 0) {
+ NEXT_ARG();
+ res = parse_mark(&argc, &argv, n);
+ } else if (matches(*argv, "ether") == 0) {
+ NEXT_ARG();
+ res = parse_ether(&argc, &argv, sel);
+ } else
+ return -1;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_offset(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ while (argc > 0) {
+ if (matches(*argv, "plus") == 0) {
+ int off;
+
+ NEXT_ARG();
+ if (get_integer(&off, *argv, 0))
+ return -1;
+ sel->off = off;
+ sel->flags |= TC_U32_OFFSET;
+ } else if (matches(*argv, "at") == 0) {
+ int off;
+
+ NEXT_ARG();
+ if (get_integer(&off, *argv, 0))
+ return -1;
+ sel->offoff = off;
+ if (off%2) {
+ fprintf(stderr, "offset \"at\" must be even\n");
+ return -1;
+ }
+ sel->flags |= TC_U32_VAROFFSET;
+ } else if (matches(*argv, "mask") == 0) {
+ NEXT_ARG();
+ if (get_be16(&sel->offmask, *argv, 16))
+ return -1;
+ sel->flags |= TC_U32_VAROFFSET;
+ } else if (matches(*argv, "shift") == 0) {
+ int shift;
+
+ NEXT_ARG();
+ if (get_integer(&shift, *argv, 0))
+ return -1;
+ sel->offshift = shift;
+ sel->flags |= TC_U32_VAROFFSET;
+ } else if (matches(*argv, "eat") == 0) {
+ sel->flags |= TC_U32_EAT;
+ } else {
+ break;
+ }
+ argc--; argv++;
+ }
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int parse_hashkey(int *argc_p, char ***argv_p, struct tc_u32_sel *sel)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ while (argc > 0) {
+ if (matches(*argv, "mask") == 0) {
+ NEXT_ARG();
+ if (get_be32(&sel->hmask, *argv, 16))
+ return -1;
+ } else if (matches(*argv, "at") == 0) {
+ int num;
+
+ NEXT_ARG();
+ if (get_integer(&num, *argv, 0))
+ return -1;
+ if (num%4)
+ return -1;
+ sel->hoff = num;
+ } else {
+ break;
+ }
+ argc--; argv++;
+ }
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static void print_ipv4(FILE *f, const struct tc_u32_key *key)
+{
+ char abuf[256];
+
+ switch (key->off) {
+ case 0:
+ switch (ntohl(key->mask)) {
+ case 0x0f000000:
+ fprintf(f, "\n match IP ihl %u",
+ ntohl(key->val) >> 24);
+ return;
+ case 0x00ff0000:
+ fprintf(f, "\n match IP dsfield %#x",
+ ntohl(key->val) >> 16);
+ return;
+ }
+ break;
+ case 8:
+ if (ntohl(key->mask) == 0x00ff0000) {
+ fprintf(f, "\n match IP protocol %d",
+ ntohl(key->val) >> 16);
+ return;
+ }
+ break;
+ case 12:
+ case 16: {
+ int bits = mask2bits(key->mask);
+
+ if (bits >= 0) {
+ fprintf(f, "\n %s %s/%d",
+ key->off == 12 ? "match IP src" : "match IP dst",
+ inet_ntop(AF_INET, &key->val,
+ abuf, sizeof(abuf)),
+ bits);
+ return;
+ }
+ }
+ break;
+
+ case 20:
+ switch (ntohl(key->mask)) {
+ case 0x0000ffff:
+ fprintf(f, "\n match dport %u",
+ ntohl(key->val) & 0xffff);
+ return;
+ case 0xffff0000:
+ fprintf(f, "\n match sport %u",
+ ntohl(key->val) >> 16);
+ return;
+ case 0xffffffff:
+ fprintf(f, "\n match dport %u, match sport %u",
+ ntohl(key->val) & 0xffff,
+ ntohl(key->val) >> 16);
+
+ return;
+ }
+ /* XXX: Default print_raw */
+ }
+}
+
+static void print_ipv6(FILE *f, const struct tc_u32_key *key)
+{
+ char abuf[256];
+
+ switch (key->off) {
+ case 0:
+ switch (ntohl(key->mask)) {
+ case 0x0f000000:
+ fprintf(f, "\n match IP ihl %u",
+ ntohl(key->val) >> 24);
+ return;
+ case 0x00ff0000:
+ fprintf(f, "\n match IP dsfield %#x",
+ ntohl(key->val) >> 16);
+ return;
+ }
+ break;
+ case 8:
+ if (ntohl(key->mask) == 0x00ff0000) {
+ fprintf(f, "\n match IP protocol %d",
+ ntohl(key->val) >> 16);
+ return;
+ }
+ break;
+ case 12:
+ case 16: {
+ int bits = mask2bits(key->mask);
+
+ if (bits >= 0) {
+ fprintf(f, "\n %s %s/%d",
+ key->off == 12 ? "match IP src" : "match IP dst",
+ inet_ntop(AF_INET, &key->val,
+ abuf, sizeof(abuf)),
+ bits);
+ return;
+ }
+ }
+ break;
+
+ case 20:
+ switch (ntohl(key->mask)) {
+ case 0x0000ffff:
+ fprintf(f, "\n match sport %u",
+ ntohl(key->val) & 0xffff);
+ return;
+ case 0xffff0000:
+ fprintf(f, "\n match dport %u",
+ ntohl(key->val) >> 16);
+ return;
+ case 0xffffffff:
+ fprintf(f, "\n match sport %u, match dport %u",
+ ntohl(key->val) & 0xffff,
+ ntohl(key->val) >> 16);
+
+ return;
+ }
+ /* XXX: Default print_raw */
+ }
+}
+
+static void print_raw(FILE *f, const struct tc_u32_key *key)
+{
+ fprintf(f, "\n match %08x/%08x at %s%d",
+ (unsigned int)ntohl(key->val),
+ (unsigned int)ntohl(key->mask),
+ key->offmask ? "nexthdr+" : "",
+ key->off);
+}
+
+static const struct {
+ __u16 proto;
+ __u16 pad;
+ void (*pprinter)(FILE *f, const struct tc_u32_key *key);
+} u32_pprinters[] = {
+ {0, 0, print_raw},
+ {ETH_P_IP, 0, print_ipv4},
+ {ETH_P_IPV6, 0, print_ipv6},
+};
+
+static void show_keys(FILE *f, const struct tc_u32_key *key)
+{
+ int i = 0;
+
+ if (!pretty)
+ goto show_k;
+
+ for (i = 0; i < ARRAY_SIZE(u32_pprinters); i++) {
+ if (u32_pprinters[i].proto == ntohs(f_proto)) {
+show_k:
+ u32_pprinters[i].pprinter(f, key);
+ return;
+ }
+ }
+
+ i = 0;
+ goto show_k;
+}
+
+static int u32_parse_opt(struct filter_util *qu, char *handle,
+ int argc, char **argv, struct nlmsghdr *n)
+{
+ struct {
+ struct tc_u32_sel sel;
+ struct tc_u32_key keys[128];
+ } sel = {};
+ struct tcmsg *t = NLMSG_DATA(n);
+ struct rtattr *tail;
+ int sel_ok = 0, terminal_ok = 0;
+ int sample_ok = 0;
+ __u32 htid = 0;
+ __u32 order = 0;
+ __u32 flags = 0;
+
+ if (handle && get_u32_handle(&t->tcm_handle, handle)) {
+ fprintf(stderr, "Illegal filter ID\n");
+ return -1;
+ }
+
+ if (argc == 0)
+ return 0;
+
+ tail = addattr_nest(n, MAX_MSG, TCA_OPTIONS);
+
+ while (argc > 0) {
+ if (matches(*argv, "match") == 0) {
+ NEXT_ARG();
+ if (parse_selector(&argc, &argv, &sel.sel, n)) {
+ fprintf(stderr, "Illegal \"match\"\n");
+ return -1;
+ }
+ sel_ok++;
+ continue;
+ } else if (matches(*argv, "offset") == 0) {
+ NEXT_ARG();
+ if (parse_offset(&argc, &argv, &sel.sel)) {
+ fprintf(stderr, "Illegal \"offset\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "hashkey") == 0) {
+ NEXT_ARG();
+ if (parse_hashkey(&argc, &argv, &sel.sel)) {
+ fprintf(stderr, "Illegal \"hashkey\"\n");
+ return -1;
+ }
+ continue;
+ } else if (matches(*argv, "classid") == 0 ||
+ strcmp(*argv, "flowid") == 0) {
+ unsigned int flowid;
+
+ NEXT_ARG();
+ if (get_tc_classid(&flowid, *argv)) {
+ fprintf(stderr, "Illegal \"classid\"\n");
+ return -1;
+ }
+ addattr_l(n, MAX_MSG, TCA_U32_CLASSID, &flowid, 4);
+ sel.sel.flags |= TC_U32_TERMINAL;
+ } else if (matches(*argv, "divisor") == 0) {
+ unsigned int divisor;
+
+ NEXT_ARG();
+ if (get_unsigned(&divisor, *argv, 0) ||
+ divisor == 0 ||
+ divisor > 0x100 || ((divisor - 1) & divisor)) {
+ fprintf(stderr, "Illegal \"divisor\"\n");
+ return -1;
+ }
+ addattr_l(n, MAX_MSG, TCA_U32_DIVISOR, &divisor, 4);
+ } else if (matches(*argv, "order") == 0) {
+ NEXT_ARG();
+ if (get_u32(&order, *argv, 0)) {
+ fprintf(stderr, "Illegal \"order\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "link") == 0) {
+ unsigned int linkid;
+
+ NEXT_ARG();
+ if (get_u32_handle(&linkid, *argv)) {
+ fprintf(stderr, "Illegal \"link\"\n");
+ return -1;
+ }
+ if (linkid && TC_U32_NODE(linkid)) {
+ fprintf(stderr, "\"link\" must be a hash table.\n");
+ return -1;
+ }
+ addattr_l(n, MAX_MSG, TCA_U32_LINK, &linkid, 4);
+ } else if (strcmp(*argv, "ht") == 0) {
+ unsigned int ht;
+
+ NEXT_ARG();
+ if (get_u32_handle(&ht, *argv)) {
+ fprintf(stderr, "Illegal \"ht\"\n");
+ return -1;
+ }
+ if (handle && TC_U32_NODE(ht)) {
+ fprintf(stderr, "\"ht\" must be a hash table.\n");
+ return -1;
+ }
+ if (sample_ok)
+ htid = (htid & 0xFF000) | (ht & 0xFFF00000);
+ else
+ htid = (ht & 0xFFFFF000);
+ } else if (strcmp(*argv, "sample") == 0) {
+ __u32 hash;
+ unsigned int divisor = 0x100;
+ struct {
+ struct tc_u32_sel sel;
+ struct tc_u32_key keys[4];
+ } sel2 = {};
+
+ NEXT_ARG();
+ if (parse_selector(&argc, &argv, &sel2.sel, n)) {
+ fprintf(stderr, "Illegal \"sample\"\n");
+ return -1;
+ }
+ if (sel2.sel.nkeys != 1) {
+ fprintf(stderr, "\"sample\" must contain exactly ONE key.\n");
+ return -1;
+ }
+ if (*argv != 0 && strcmp(*argv, "divisor") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&divisor, *argv, 0) ||
+ divisor == 0 || divisor > 0x100 ||
+ ((divisor - 1) & divisor)) {
+ fprintf(stderr, "Illegal sample \"divisor\"\n");
+ return -1;
+ }
+ NEXT_ARG();
+ }
+ hash = sel2.sel.keys[0].val & sel2.sel.keys[0].mask;
+ hash ^= hash >> 16;
+ hash ^= hash >> 8;
+ htid = ((hash % divisor) << 12) | (htid & 0xFFF00000);
+ sample_ok = 1;
+ continue;
+ } else if (strcmp(*argv, "indev") == 0) {
+ char ind[IFNAMSIZ + 1] = {};
+
+ argc--;
+ argv++;
+ if (argc < 1) {
+ fprintf(stderr, "Illegal indev\n");
+ return -1;
+ }
+ strncpy(ind, *argv, sizeof(ind) - 1);
+ addattr_l(n, MAX_MSG, TCA_U32_INDEV, ind,
+ strlen(ind) + 1);
+
+ } else if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ if (parse_action(&argc, &argv, TCA_U32_ACT, n)) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ terminal_ok++;
+ continue;
+
+ } else if (matches(*argv, "police") == 0) {
+ NEXT_ARG();
+ if (parse_police(&argc, &argv, TCA_U32_POLICE, n)) {
+ fprintf(stderr, "Illegal \"police\"\n");
+ return -1;
+ }
+ terminal_ok++;
+ continue;
+ } else if (strcmp(*argv, "skip_hw") == 0) {
+ flags |= TCA_CLS_FLAGS_SKIP_HW;
+ } else if (strcmp(*argv, "skip_sw") == 0) {
+ flags |= TCA_CLS_FLAGS_SKIP_SW;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ /* We don't necessarily need class/flowids */
+ if (terminal_ok)
+ sel.sel.flags |= TC_U32_TERMINAL;
+
+ if (order) {
+ if (TC_U32_NODE(t->tcm_handle) &&
+ order != TC_U32_NODE(t->tcm_handle)) {
+ fprintf(stderr, "\"order\" contradicts \"handle\"\n");
+ return -1;
+ }
+ t->tcm_handle |= order;
+ }
+
+ if (htid)
+ addattr_l(n, MAX_MSG, TCA_U32_HASH, &htid, 4);
+ if (sel_ok)
+ addattr_l(n, MAX_MSG, TCA_U32_SEL, &sel,
+ sizeof(sel.sel) +
+ sel.sel.nkeys * sizeof(struct tc_u32_key));
+ if (flags) {
+ if (!(flags ^ (TCA_CLS_FLAGS_SKIP_HW |
+ TCA_CLS_FLAGS_SKIP_SW))) {
+ fprintf(stderr,
+ "skip_hw and skip_sw are mutually exclusive\n");
+ return -1;
+ }
+ addattr_l(n, MAX_MSG, TCA_U32_FLAGS, &flags, 4);
+ }
+
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int u32_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt,
+ __u32 handle)
+{
+ struct rtattr *tb[TCA_U32_MAX + 1];
+ struct tc_u32_sel *sel = NULL;
+ struct tc_u32_pcnt *pf = NULL;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_U32_MAX, opt);
+
+ if (handle) {
+ SPRINT_BUF(b1);
+ fprintf(f, "fh %s ", sprint_u32_handle(handle, b1));
+ }
+
+ if (TC_U32_NODE(handle))
+ fprintf(f, "order %d ", TC_U32_NODE(handle));
+
+ if (tb[TCA_U32_SEL]) {
+ if (RTA_PAYLOAD(tb[TCA_U32_SEL]) < sizeof(*sel))
+ return -1;
+
+ sel = RTA_DATA(tb[TCA_U32_SEL]);
+ }
+
+ if (tb[TCA_U32_DIVISOR]) {
+ fprintf(f, "ht divisor %d ",
+ rta_getattr_u32(tb[TCA_U32_DIVISOR]));
+ } else if (tb[TCA_U32_HASH]) {
+ __u32 htid = rta_getattr_u32(tb[TCA_U32_HASH]);
+
+ fprintf(f, "key ht %x bkt %x ", TC_U32_USERHTID(htid),
+ TC_U32_HASH(htid));
+ } else {
+ fprintf(f, "??? ");
+ }
+ if (tb[TCA_U32_CLASSID]) {
+ SPRINT_BUF(b1);
+ fprintf(f, "%sflowid %s ",
+ !sel || !(sel->flags & TC_U32_TERMINAL) ? "*" : "",
+ sprint_tc_classid(rta_getattr_u32(tb[TCA_U32_CLASSID]),
+ b1));
+ } else if (sel && sel->flags & TC_U32_TERMINAL) {
+ fprintf(f, "terminal flowid ??? ");
+ }
+ if (tb[TCA_U32_LINK]) {
+ SPRINT_BUF(b1);
+ fprintf(f, "link %s ",
+ sprint_u32_handle(rta_getattr_u32(tb[TCA_U32_LINK]),
+ b1));
+ }
+
+ if (tb[TCA_U32_FLAGS]) {
+ __u32 flags = rta_getattr_u32(tb[TCA_U32_FLAGS]);
+
+ if (flags & TCA_CLS_FLAGS_SKIP_HW)
+ fprintf(f, "skip_hw ");
+ if (flags & TCA_CLS_FLAGS_SKIP_SW)
+ fprintf(f, "skip_sw ");
+
+ if (flags & TCA_CLS_FLAGS_IN_HW)
+ fprintf(f, "in_hw ");
+ else if (flags & TCA_CLS_FLAGS_NOT_IN_HW)
+ fprintf(f, "not_in_hw ");
+ }
+
+ if (tb[TCA_U32_PCNT]) {
+ if (RTA_PAYLOAD(tb[TCA_U32_PCNT]) < sizeof(*pf)) {
+ fprintf(f, "Broken perf counters\n");
+ return -1;
+ }
+ pf = RTA_DATA(tb[TCA_U32_PCNT]);
+ }
+
+ if (sel && show_stats && NULL != pf)
+ fprintf(f, " (rule hit %llu success %llu)",
+ (unsigned long long) pf->rcnt,
+ (unsigned long long) pf->rhit);
+
+ if (tb[TCA_U32_MARK]) {
+ struct tc_u32_mark *mark = RTA_DATA(tb[TCA_U32_MARK]);
+
+ if (RTA_PAYLOAD(tb[TCA_U32_MARK]) < sizeof(*mark)) {
+ fprintf(f, "\n Invalid mark (kernel&iproute2 mismatch)\n");
+ } else {
+ fprintf(f, "\n mark 0x%04x 0x%04x (success %d)",
+ mark->val, mark->mask, mark->success);
+ }
+ }
+
+ if (sel) {
+ if (sel->nkeys) {
+ int i;
+
+ for (i = 0; i < sel->nkeys; i++) {
+ show_keys(f, sel->keys + i);
+ if (show_stats && NULL != pf)
+ fprintf(f, " (success %llu ) ",
+ (unsigned long long) pf->kcnts[i]);
+ }
+ }
+
+ if (sel->flags & (TC_U32_VAROFFSET | TC_U32_OFFSET)) {
+ fprintf(f, "\n offset ");
+ if (sel->flags & TC_U32_VAROFFSET)
+ fprintf(f, "%04x>>%d at %d ",
+ ntohs(sel->offmask),
+ sel->offshift, sel->offoff);
+ if (sel->off)
+ fprintf(f, "plus %d ", sel->off);
+ }
+ if (sel->flags & TC_U32_EAT)
+ fprintf(f, " eat ");
+
+ if (sel->hmask) {
+ fprintf(f, "\n hash mask %08x at %d ",
+ (unsigned int)htonl(sel->hmask), sel->hoff);
+ }
+ }
+
+ if (tb[TCA_U32_POLICE]) {
+ fprintf(f, "\n");
+ tc_print_police(f, tb[TCA_U32_POLICE]);
+ }
+
+ if (tb[TCA_U32_INDEV]) {
+ struct rtattr *idev = tb[TCA_U32_INDEV];
+
+ fprintf(f, "\n input dev %s\n", rta_getattr_str(idev));
+ }
+
+ if (tb[TCA_U32_ACT])
+ tc_print_action(f, tb[TCA_U32_ACT], 0);
+
+ return 0;
+}
+
+struct filter_util u32_filter_util = {
+ .id = "u32",
+ .parse_fopt = u32_parse_opt,
+ .print_fopt = u32_print_opt,
+};
diff --git a/tc/m_action.c b/tc/m_action.c
new file mode 100644
index 0000000..36c744b
--- /dev/null
+++ b/tc/m_action.c
@@ -0,0 +1,755 @@
+/*
+ * m_action.c Action Management
+ *
+ * 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)
+ *
+ * TODO:
+ * - parse to be passed a filedescriptor for logging purposes
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <dlfcn.h>
+
+#include "utils.h"
+#include "tc_common.h"
+#include "tc_util.h"
+
+static struct action_util *action_list;
+#ifdef CONFIG_GACT
+static int gact_ld; /* f*ckin backward compatibility */
+#endif
+static int tab_flush;
+
+static void act_usage(void)
+{
+ /*XXX: In the near future add a action->print_help to improve
+ * usability
+ * This would mean new tc will not be backward compatible
+ * with any action .so from the old days. But if someone really
+ * does that, they would know how to fix this ..
+ *
+ */
+ fprintf(stderr,
+ "usage: tc actions <ACTSPECOP>*\n"
+ "Where: ACTSPECOP := ACR | GD | FL\n"
+ " ACR := add | change | replace <ACTSPEC>*\n"
+ " GD := get | delete | <ACTISPEC>*\n"
+ " FL := ls | list | flush | <ACTNAMESPEC>\n"
+ " ACTNAMESPEC := action <ACTNAME>\n"
+ " ACTISPEC := <ACTNAMESPEC> <INDEXSPEC>\n"
+ " ACTSPEC := action <ACTDETAIL> [INDEXSPEC]\n"
+ " INDEXSPEC := index <32 bit indexvalue>\n"
+ " ACTDETAIL := <ACTNAME> <ACTPARAMS>\n"
+ " Example ACTNAME is gact, mirred, bpf, etc\n"
+ " Each action has its own parameters (ACTPARAMS)\n"
+ "\n");
+
+ exit(-1);
+}
+
+static int print_noaopt(struct action_util *au, FILE *f, struct rtattr *opt)
+{
+ if (opt && RTA_PAYLOAD(opt))
+ fprintf(f, "[Unknown action, optlen=%u] ",
+ (unsigned int) RTA_PAYLOAD(opt));
+ return 0;
+}
+
+static int parse_noaopt(struct action_util *au, int *argc_p,
+ char ***argv_p, int code, struct nlmsghdr *n)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc)
+ fprintf(stderr,
+ "Unknown action \"%s\", hence option \"%s\" is unparsable\n",
+ au->id, *argv);
+ else
+ fprintf(stderr, "Unknown action \"%s\"\n", au->id);
+
+ return -1;
+}
+
+static struct action_util *get_action_kind(char *str)
+{
+ static void *aBODY;
+ void *dlh;
+ char buf[256];
+ struct action_util *a;
+#ifdef CONFIG_GACT
+ int looked4gact = 0;
+restart_s:
+#endif
+ for (a = action_list; a; a = a->next) {
+ if (strcmp(a->id, str) == 0)
+ return a;
+ }
+
+ snprintf(buf, sizeof(buf), "%s/m_%s.so", get_tc_lib(), str);
+ dlh = dlopen(buf, RTLD_LAZY | RTLD_GLOBAL);
+ if (dlh == NULL) {
+ dlh = aBODY;
+ if (dlh == NULL) {
+ dlh = aBODY = dlopen(NULL, RTLD_LAZY);
+ if (dlh == NULL)
+ goto noexist;
+ }
+ }
+
+ snprintf(buf, sizeof(buf), "%s_action_util", str);
+ a = dlsym(dlh, buf);
+ if (a == NULL)
+ goto noexist;
+
+reg:
+ a->next = action_list;
+ action_list = a;
+ return a;
+
+noexist:
+#ifdef CONFIG_GACT
+ if (!looked4gact) {
+ looked4gact = 1;
+ strcpy(str, "gact");
+ goto restart_s;
+ }
+#endif
+ a = calloc(1, sizeof(*a));
+ if (a) {
+ strncpy(a->id, "noact", 15);
+ a->parse_aopt = parse_noaopt;
+ a->print_aopt = print_noaopt;
+ goto reg;
+ }
+ return a;
+}
+
+static bool
+new_cmd(char **argv)
+{
+ return (matches(*argv, "change") == 0) ||
+ (matches(*argv, "replace") == 0) ||
+ (matches(*argv, "delete") == 0) ||
+ (matches(*argv, "get") == 0) ||
+ (matches(*argv, "add") == 0);
+}
+
+int parse_action(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ struct rtattr *tail, *tail2;
+ char k[FILTER_NAMESZ];
+ int act_ck_len = 0;
+ int ok = 0;
+ int eap = 0; /* expect action parameters */
+
+ int ret = 0;
+ int prio = 0;
+ unsigned char act_ck[TC_COOKIE_MAX_SIZE];
+
+ if (argc <= 0)
+ return -1;
+
+ tail2 = addattr_nest(n, MAX_MSG, tca_id);
+
+ while (argc > 0) {
+
+ memset(k, 0, sizeof(k));
+
+ if (strcmp(*argv, "action") == 0) {
+ argc--;
+ argv++;
+ eap = 1;
+#ifdef CONFIG_GACT
+ if (!gact_ld)
+ get_action_kind("gact");
+#endif
+ continue;
+ } else if (strcmp(*argv, "flowid") == 0) {
+ break;
+ } else if (strcmp(*argv, "classid") == 0) {
+ break;
+ } else if (strcmp(*argv, "help") == 0) {
+ return -1;
+ } else if (new_cmd(argv)) {
+ goto done0;
+ } else {
+ struct action_util *a = NULL;
+
+ if (!action_a2n(*argv, NULL, false))
+ strncpy(k, "gact", sizeof(k) - 1);
+ else
+ strncpy(k, *argv, sizeof(k) - 1);
+ eap = 0;
+ if (argc > 0) {
+ a = get_action_kind(k);
+ } else {
+done0:
+ if (ok)
+ break;
+ else
+ goto done;
+ }
+
+ if (a == NULL)
+ goto bad_val;
+
+
+ tail = addattr_nest(n, MAX_MSG, ++prio);
+ addattr_l(n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
+
+ ret = a->parse_aopt(a, &argc, &argv,
+ TCA_ACT_OPTIONS | NLA_F_NESTED,
+ n);
+
+ if (ret < 0) {
+ fprintf(stderr, "bad action parsing\n");
+ goto bad_val;
+ }
+
+ if (*argv && strcmp(*argv, "cookie") == 0) {
+ size_t slen;
+
+ NEXT_ARG();
+ slen = strlen(*argv);
+ if (slen > TC_COOKIE_MAX_SIZE * 2) {
+ char cookie_err_m[128];
+
+ snprintf(cookie_err_m, 128,
+ "%zd Max allowed size %d",
+ slen, TC_COOKIE_MAX_SIZE*2);
+ invarg(cookie_err_m, *argv);
+ }
+
+ if (hex2mem(*argv, act_ck, slen / 2) < 0)
+ invarg("cookie must be a hex string\n",
+ *argv);
+
+ act_ck_len = slen / 2;
+ argc--;
+ argv++;
+ }
+
+ if (act_ck_len)
+ addattr_l(n, MAX_MSG, TCA_ACT_COOKIE,
+ &act_ck, act_ck_len);
+
+ addattr_nest_end(n, tail);
+ ok++;
+ }
+ }
+
+ if (eap > 0) {
+ fprintf(stderr, "bad action empty %d\n", eap);
+ goto bad_val;
+ }
+
+ addattr_nest_end(n, tail2);
+
+done:
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+bad_val:
+ /* no need to undo things, returning from here should
+ * cause enough pain
+ */
+ fprintf(stderr, "parse_action: bad value (%d:%s)!\n", argc, *argv);
+ return -1;
+}
+
+static int tc_print_one_action(FILE *f, struct rtattr *arg)
+{
+
+ struct rtattr *tb[TCA_ACT_MAX + 1];
+ int err = 0;
+ struct action_util *a = NULL;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_ACT_MAX, arg);
+
+ if (tb[TCA_ACT_KIND] == NULL) {
+ fprintf(stderr, "NULL Action!\n");
+ return -1;
+ }
+
+
+ a = get_action_kind(RTA_DATA(tb[TCA_ACT_KIND]));
+ if (a == NULL)
+ return err;
+
+ err = a->print_aopt(a, f, tb[TCA_ACT_OPTIONS]);
+
+ if (err < 0)
+ return err;
+
+ if (show_stats && tb[TCA_ACT_STATS]) {
+ print_string(PRINT_FP, NULL, "\tAction statistics:", NULL);
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ open_json_object("stats");
+ print_tcstats2_attr(f, tb[TCA_ACT_STATS], "\t", NULL);
+ close_json_object();
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ }
+ if (tb[TCA_ACT_COOKIE]) {
+ int strsz = RTA_PAYLOAD(tb[TCA_ACT_COOKIE]);
+ char b1[strsz * 2 + 1];
+
+ print_string(PRINT_ANY, "cookie", "\tcookie %s",
+ hexstring_n2a(RTA_DATA(tb[TCA_ACT_COOKIE]),
+ strsz, b1, sizeof(b1)));
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ }
+
+ return 0;
+}
+
+static int
+tc_print_action_flush(FILE *f, const struct rtattr *arg)
+{
+
+ struct rtattr *tb[TCA_MAX + 1];
+ int err = 0;
+ struct action_util *a = NULL;
+ __u32 *delete_count = 0;
+
+ parse_rtattr_nested(tb, TCA_MAX, arg);
+
+ if (tb[TCA_KIND] == NULL) {
+ fprintf(stderr, "NULL Action!\n");
+ return -1;
+ }
+
+ a = get_action_kind(RTA_DATA(tb[TCA_KIND]));
+ if (a == NULL)
+ return err;
+
+ delete_count = RTA_DATA(tb[TCA_FCNT]);
+ fprintf(f, " %s (%d entries)\n", a->id, *delete_count);
+ tab_flush = 0;
+ return 0;
+}
+
+int
+tc_print_action(FILE *f, const struct rtattr *arg, unsigned short tot_acts)
+{
+
+ int i;
+
+ if (arg == NULL)
+ return 0;
+
+ if (!tot_acts)
+ tot_acts = TCA_ACT_MAX_PRIO;
+
+ struct rtattr *tb[tot_acts + 1];
+
+ parse_rtattr_nested(tb, tot_acts, arg);
+
+ if (tab_flush && tb[0] && !tb[1])
+ return tc_print_action_flush(f, tb[0]);
+
+ open_json_array(PRINT_JSON, "actions");
+ for (i = 0; i <= tot_acts; i++) {
+ if (tb[i]) {
+ open_json_object(NULL);
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "order",
+ "\taction order %u: ", i);
+ if (tc_print_one_action(f, tb[i]) < 0) {
+ print_string(PRINT_FP, NULL,
+ "Error printing action\n", NULL);
+ }
+ close_json_object();
+ }
+
+ }
+ close_json_array(PRINT_JSON, NULL);
+
+ return 0;
+}
+
+int print_action(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct tcamsg *t = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ __u32 *tot_acts = NULL;
+ struct rtattr *tb[TCA_ROOT_MAX+1];
+
+ len -= NLMSG_LENGTH(sizeof(*t));
+
+ if (len < 0) {
+ fprintf(stderr, "Wrong len %d\n", len);
+ return -1;
+ }
+
+ parse_rtattr(tb, TCA_ROOT_MAX, TA_RTA(t), len);
+
+ if (tb[TCA_ROOT_COUNT])
+ tot_acts = RTA_DATA(tb[TCA_ROOT_COUNT]);
+
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "total acts", "total acts %u",
+ tot_acts ? *tot_acts : 0);
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ close_json_object();
+ if (tb[TCA_ACT_TAB] == NULL) {
+ if (n->nlmsg_type != RTM_GETACTION)
+ fprintf(stderr, "print_action: NULL kind\n");
+ return -1;
+ }
+
+ if (n->nlmsg_type == RTM_DELACTION) {
+ if (n->nlmsg_flags & NLM_F_ROOT) {
+ fprintf(fp, "Flushed table ");
+ tab_flush = 1;
+ } else {
+ fprintf(fp, "Deleted action ");
+ }
+ }
+
+ if (n->nlmsg_type == RTM_NEWACTION) {
+ if ((n->nlmsg_flags & NLM_F_CREATE) &&
+ !(n->nlmsg_flags & NLM_F_REPLACE)) {
+ fprintf(fp, "Added action ");
+ } else if (n->nlmsg_flags & NLM_F_REPLACE) {
+ fprintf(fp, "Replaced action ");
+ }
+ }
+
+ open_json_object(NULL);
+ tc_print_action(fp, tb[TCA_ACT_TAB], tot_acts ? *tot_acts:0);
+ close_json_object();
+
+ return 0;
+}
+
+static int tc_action_gd(int cmd, unsigned int flags,
+ int *argc_p, char ***argv_p)
+{
+ char k[FILTER_NAMESZ];
+ struct action_util *a = NULL;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int prio = 0;
+ int ret = 0;
+ __u32 i = 0;
+ struct rtattr *tail;
+ struct rtattr *tail2;
+ struct nlmsghdr *ans = NULL;
+
+ struct {
+ struct nlmsghdr n;
+ struct tcamsg t;
+ char buf[MAX_MSG];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .t.tca_family = AF_UNSPEC,
+ };
+
+ argc -= 1;
+ argv += 1;
+
+
+ tail = addattr_nest(&req.n, MAX_MSG, TCA_ACT_TAB);
+
+ while (argc > 0) {
+ if (strcmp(*argv, "action") == 0) {
+ argc--;
+ argv++;
+ continue;
+ } else if (strcmp(*argv, "help") == 0) {
+ return -1;
+ }
+
+ strncpy(k, *argv, sizeof(k) - 1);
+ a = get_action_kind(k);
+ if (a == NULL) {
+ fprintf(stderr, "Error: non existent action: %s\n", k);
+ ret = -1;
+ goto bad_val;
+ }
+ if (strcmp(a->id, k) != 0) {
+ fprintf(stderr, "Error: non existent action: %s\n", k);
+ ret = -1;
+ goto bad_val;
+ }
+
+ argc -= 1;
+ argv += 1;
+ if (argc <= 0) {
+ fprintf(stderr,
+ "Error: no index specified action: %s\n", k);
+ ret = -1;
+ goto bad_val;
+ }
+
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&i, *argv, 10)) {
+ fprintf(stderr, "Illegal \"index\"\n");
+ ret = -1;
+ goto bad_val;
+ }
+ argc -= 1;
+ argv += 1;
+ } else {
+ fprintf(stderr,
+ "Error: no index specified action: %s\n", k);
+ ret = -1;
+ goto bad_val;
+ }
+
+ tail2 = addattr_nest(&req.n, MAX_MSG, ++prio);
+ addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
+ if (i > 0)
+ addattr32(&req.n, MAX_MSG, TCA_ACT_INDEX, i);
+ addattr_nest_end(&req.n, tail2);
+
+ }
+
+ addattr_nest_end(&req.n, tail);
+
+ req.n.nlmsg_seq = rth.dump = ++rth.seq;
+
+ if (rtnl_talk(&rth, &req.n, cmd == RTM_DELACTION ? NULL : &ans) < 0) {
+ fprintf(stderr, "We have an error talking to the kernel\n");
+ return 1;
+ }
+
+ if (cmd == RTM_GETACTION) {
+ new_json_obj(json);
+ ret = print_action(ans, stdout);
+ if (ret < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ free(ans);
+ delete_json_obj();
+ return 1;
+ }
+ delete_json_obj();
+ }
+ free(ans);
+
+ *argc_p = argc;
+ *argv_p = argv;
+bad_val:
+ return ret;
+}
+
+static int tc_action_modify(int cmd, unsigned int flags,
+ int *argc_p, char ***argv_p)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int ret = 0;
+ struct {
+ struct nlmsghdr n;
+ struct tcamsg t;
+ char buf[MAX_MSG];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .t.tca_family = AF_UNSPEC,
+ };
+ struct rtattr *tail = NLMSG_TAIL(&req.n);
+
+ argc -= 1;
+ argv += 1;
+ if (parse_action(&argc, &argv, TCA_ACT_TAB, &req.n)) {
+ fprintf(stderr, "Illegal \"action\"\n");
+ return -1;
+ }
+ tail->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail;
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0) {
+ fprintf(stderr, "We have an error talking to the kernel\n");
+ ret = -1;
+ }
+
+ *argc_p = argc;
+ *argv_p = argv;
+
+ return ret;
+}
+
+static int tc_act_list_or_flush(int *argc_p, char ***argv_p, int event)
+{
+ struct rtattr *tail, *tail2, *tail3, *tail4;
+ int ret = 0, prio = 0, msg_size = 0;
+ struct action_util *a = NULL;
+ struct nla_bitfield32 flag_select = { 0 };
+ char **argv = *argv_p;
+ __u32 msec_since = 0;
+ int argc = *argc_p;
+ char k[FILTER_NAMESZ];
+ struct {
+ struct nlmsghdr n;
+ struct tcamsg t;
+ char buf[MAX_MSG];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg)),
+ .t.tca_family = AF_UNSPEC,
+ };
+
+ tail = addattr_nest(&req.n, MAX_MSG, TCA_ACT_TAB);
+ tail2 = NLMSG_TAIL(&req.n);
+
+ strncpy(k, *argv, sizeof(k) - 1);
+#ifdef CONFIG_GACT
+ if (!gact_ld)
+ get_action_kind("gact");
+
+#endif
+ a = get_action_kind(k);
+ if (a == NULL) {
+ fprintf(stderr, "bad action %s\n", k);
+ goto bad_val;
+ }
+ if (strcmp(a->id, k) != 0) {
+ fprintf(stderr, "bad action %s\n", k);
+ goto bad_val;
+ }
+ strncpy(k, *argv, sizeof(k) - 1);
+
+ argc -= 1;
+ argv += 1;
+
+ if (argc && (strcmp(*argv, "since") == 0)) {
+ NEXT_ARG();
+ if (get_u32(&msec_since, *argv, 0))
+ invarg("dump time \"since\" is invalid", *argv);
+ }
+
+ addattr_l(&req.n, MAX_MSG, ++prio, NULL, 0);
+ addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
+ tail2->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail2;
+ addattr_nest_end(&req.n, tail);
+
+ tail3 = NLMSG_TAIL(&req.n);
+ flag_select.value |= TCA_FLAG_LARGE_DUMP_ON;
+ flag_select.selector |= TCA_FLAG_LARGE_DUMP_ON;
+ addattr_l(&req.n, MAX_MSG, TCA_ROOT_FLAGS, &flag_select,
+ sizeof(struct nla_bitfield32));
+ tail3->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail3;
+ if (msec_since) {
+ tail4 = NLMSG_TAIL(&req.n);
+ addattr32(&req.n, MAX_MSG, TCA_ROOT_TIME_DELTA, msec_since);
+ tail4->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail4;
+ }
+ msg_size = NLMSG_ALIGN(req.n.nlmsg_len)
+ - NLMSG_ALIGN(sizeof(struct nlmsghdr));
+
+ if (event == RTM_GETACTION) {
+ if (rtnl_dump_request(&rth, event,
+ (void *)&req.t, msg_size) < 0) {
+ perror("Cannot send dump request");
+ return 1;
+ }
+ new_json_obj(json);
+ ret = rtnl_dump_filter(&rth, print_action, stdout);
+ delete_json_obj();
+ }
+
+ if (event == RTM_DELACTION) {
+ req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len);
+ req.n.nlmsg_type = RTM_DELACTION;
+ req.n.nlmsg_flags |= NLM_F_ROOT;
+ req.n.nlmsg_flags |= NLM_F_REQUEST;
+ if (rtnl_talk(&rth, &req.n, NULL) < 0) {
+ fprintf(stderr, "We have an error flushing\n");
+ return 1;
+ }
+
+ }
+
+bad_val:
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return ret;
+}
+
+int do_action(int argc, char **argv)
+{
+
+ int ret = 0;
+
+ while (argc > 0) {
+
+ if (matches(*argv, "add") == 0) {
+ ret = tc_action_modify(RTM_NEWACTION,
+ NLM_F_EXCL | NLM_F_CREATE,
+ &argc, &argv);
+ } else if (matches(*argv, "change") == 0 ||
+ matches(*argv, "replace") == 0) {
+ ret = tc_action_modify(RTM_NEWACTION,
+ NLM_F_CREATE | NLM_F_REPLACE,
+ &argc, &argv);
+ } else if (matches(*argv, "delete") == 0) {
+ argc -= 1;
+ argv += 1;
+ ret = tc_action_gd(RTM_DELACTION, 0, &argc, &argv);
+ } else if (matches(*argv, "get") == 0) {
+ argc -= 1;
+ argv += 1;
+ ret = tc_action_gd(RTM_GETACTION, 0, &argc, &argv);
+ } else if (matches(*argv, "list") == 0 ||
+ matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0) {
+ if (argc <= 2) {
+ act_usage();
+ return -1;
+ }
+
+ argc -= 2;
+ argv += 2;
+ return tc_act_list_or_flush(&argc, &argv,
+ RTM_GETACTION);
+ } else if (matches(*argv, "flush") == 0) {
+ if (argc <= 2) {
+ act_usage();
+ return -1;
+ }
+
+ argc -= 2;
+ argv += 2;
+ return tc_act_list_or_flush(&argc, &argv,
+ RTM_DELACTION);
+ } else if (matches(*argv, "help") == 0) {
+ act_usage();
+ return -1;
+ } else {
+ fprintf(stderr,
+ "Command \"%s\" is unknown, try \"tc actions help\".\n",
+ *argv);
+ return -1;
+ }
+
+ if (ret < 0)
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/tc/m_bpf.c b/tc/m_bpf.c
new file mode 100644
index 0000000..e8d704b
--- /dev/null
+++ b/tc/m_bpf.c
@@ -0,0 +1,219 @@
+/*
+ * m_bpf.c BPF based action 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: Jiri Pirko <jiri@resnulli.us>
+ * Daniel Borkmann <daniel@iogearbox.net>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <linux/bpf.h>
+#include <linux/tc_act/tc_bpf.h>
+
+#include "utils.h"
+
+#include "tc_util.h"
+#include "bpf_util.h"
+
+static const enum bpf_prog_type bpf_type = BPF_PROG_TYPE_SCHED_ACT;
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... bpf ... [ index INDEX ]\n"
+ "\n"
+ "BPF use case:\n"
+ " bytecode BPF_BYTECODE\n"
+ " bytecode-file FILE\n"
+ "\n"
+ "eBPF use case:\n"
+ " object-file FILE [ section ACT_NAME ] [ export UDS_FILE ]"
+ " [ verbose ]\n"
+ " object-pinned FILE\n"
+ "\n"
+ "Where BPF_BYTECODE := \'s,c t f k,c t f k,c t f k,...\'\n"
+ "c,t,f,k and s are decimals; s denotes number of 4-tuples\n"
+ "\n"
+ "Where FILE points to a file containing the BPF_BYTECODE string,\n"
+ "an ELF file containing eBPF map definitions and bytecode, or a\n"
+ "pinned eBPF program.\n"
+ "\n"
+ "Where ACT_NAME refers to the section name containing the\n"
+ "action (default \'%s\').\n"
+ "\n"
+ "Where UDS_FILE points to a unix domain socket file in order\n"
+ "to hand off control of all created eBPF maps to an agent.\n"
+ "\n"
+ "Where optionally INDEX points to an existing action, or\n"
+ "explicitly specifies an action index upon creation.\n",
+ bpf_prog_to_default_section(bpf_type));
+}
+
+static void bpf_cbpf_cb(void *nl, const struct sock_filter *ops, int ops_len)
+{
+ addattr16(nl, MAX_MSG, TCA_ACT_BPF_OPS_LEN, ops_len);
+ addattr_l(nl, MAX_MSG, TCA_ACT_BPF_OPS, ops,
+ ops_len * sizeof(struct sock_filter));
+}
+
+static void bpf_ebpf_cb(void *nl, int fd, const char *annotation)
+{
+ addattr32(nl, MAX_MSG, TCA_ACT_BPF_FD, fd);
+ addattrstrz(nl, MAX_MSG, TCA_ACT_BPF_NAME, annotation);
+}
+
+static const struct bpf_cfg_ops bpf_cb_ops = {
+ .cbpf_cb = bpf_cbpf_cb,
+ .ebpf_cb = bpf_ebpf_cb,
+};
+
+static int bpf_parse_opt(struct action_util *a, int *ptr_argc, char ***ptr_argv,
+ int tca_id, struct nlmsghdr *n)
+{
+ const char *bpf_obj = NULL, *bpf_uds_name = NULL;
+ struct tc_act_bpf parm = {};
+ struct bpf_cfg_in cfg = {};
+ bool seen_run = false;
+ struct rtattr *tail;
+ int argc, ret = 0;
+ char **argv;
+
+ argv = *ptr_argv;
+ argc = *ptr_argc;
+
+ if (matches(*argv, "bpf") != 0)
+ return -1;
+
+ NEXT_ARG();
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+
+ while (argc > 0) {
+ if (matches(*argv, "run") == 0) {
+ NEXT_ARG();
+
+ if (seen_run)
+ duparg("run", *argv);
+opt_bpf:
+ seen_run = true;
+ cfg.type = bpf_type;
+ cfg.argc = argc;
+ cfg.argv = argv;
+
+ if (bpf_parse_and_load_common(&cfg, &bpf_cb_ops, n))
+ return -1;
+
+ argc = cfg.argc;
+ argv = cfg.argv;
+
+ bpf_obj = cfg.object;
+ bpf_uds_name = cfg.uds;
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else if (matches(*argv, "index") == 0) {
+ break;
+ } else {
+ if (!seen_run)
+ goto opt_bpf;
+ break;
+ }
+
+ NEXT_ARG_FWD();
+ }
+
+ parse_action_control_dflt(&argc, &argv, &parm.action,
+ false, TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&parm.index, *argv, 10)) {
+ fprintf(stderr, "bpf: Illegal \"index\"\n");
+ return -1;
+ }
+
+ NEXT_ARG_FWD();
+ }
+ }
+
+ addattr_l(n, MAX_MSG, TCA_ACT_BPF_PARMS, &parm, sizeof(parm));
+ addattr_nest_end(n, tail);
+
+ if (bpf_uds_name)
+ ret = bpf_send_map_fds(bpf_uds_name, bpf_obj);
+
+ *ptr_argc = argc;
+ *ptr_argv = argv;
+
+ return ret;
+}
+
+static int bpf_print_opt(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct rtattr *tb[TCA_ACT_BPF_MAX + 1];
+ struct tc_act_bpf *parm;
+ int d_ok = 0;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_ACT_BPF_MAX, arg);
+
+ if (!tb[TCA_ACT_BPF_PARMS]) {
+ fprintf(stderr, "Missing bpf parameters\n");
+ return -1;
+ }
+
+ parm = RTA_DATA(tb[TCA_ACT_BPF_PARMS]);
+ print_string(PRINT_ANY, "kind", "%s ", "bpf");
+
+ if (tb[TCA_ACT_BPF_NAME])
+ print_string(PRINT_ANY, "bpf_name", "%s ",
+ rta_getattr_str(tb[TCA_ACT_BPF_NAME]));
+ if (tb[TCA_ACT_BPF_OPS] && tb[TCA_ACT_BPF_OPS_LEN]) {
+ bpf_print_ops(tb[TCA_ACT_BPF_OPS],
+ rta_getattr_u16(tb[TCA_ACT_BPF_OPS_LEN]));
+ print_string(PRINT_FP, NULL, "%s", " ");
+ }
+
+ if (tb[TCA_ACT_BPF_ID])
+ d_ok = bpf_dump_prog_info(f,
+ rta_getattr_u32(tb[TCA_ACT_BPF_ID]));
+ if (!d_ok && tb[TCA_ACT_BPF_TAG]) {
+ SPRINT_BUF(b);
+
+ print_string(PRINT_ANY, "tag", "tag %s ",
+ hexstring_n2a(RTA_DATA(tb[TCA_ACT_BPF_TAG]),
+ RTA_PAYLOAD(tb[TCA_ACT_BPF_TAG]),
+ b, sizeof(b)));
+ }
+
+ print_action_control(f, "default-action ", parm->action, _SL_);
+ print_uint(PRINT_ANY, "index", "\t index %u", parm->index);
+ print_int(PRINT_ANY, "ref", " ref %d", parm->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", parm->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_ACT_BPF_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_ACT_BPF_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+
+ fprintf(f, "\n ");
+ return 0;
+}
+
+struct action_util bpf_action_util = {
+ .id = "bpf",
+ .parse_aopt = bpf_parse_opt,
+ .print_aopt = bpf_print_opt,
+};
diff --git a/tc/m_connmark.c b/tc/m_connmark.c
new file mode 100644
index 0000000..eac2348
--- /dev/null
+++ b/tc/m_connmark.c
@@ -0,0 +1,149 @@
+/*
+ * m_connmark.c Connection tracking marking import
+ *
+ * Copyright (c) 2011 Felix Fietkau <nbd@openwrt.org>
+ *
+ * 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, see <http://www.gnu.org/licenses>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_connmark.h>
+
+static void
+explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... connmark [zone ZONE] [CONTROL] [index <INDEX>]\n"
+ "where :\n"
+ "\tZONE is the conntrack zone\n"
+ "\tCONTROL := reclassify | pipe | drop | continue | ok |\n"
+ "\t goto chain <CHAIN_INDEX>\n");
+}
+
+static void
+usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static int
+parse_connmark(struct action_util *a, int *argc_p, char ***argv_p, int tca_id,
+ struct nlmsghdr *n)
+{
+ struct tc_connmark sel = {};
+ char **argv = *argv_p;
+ int argc = *argc_p;
+ int ok = 0;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (matches(*argv, "connmark") == 0) {
+ ok = 1;
+ argc--;
+ argv++;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+
+ }
+
+ if (!ok) {
+ explain();
+ return -1;
+ }
+
+ if (argc) {
+ if (matches(*argv, "zone") == 0) {
+ NEXT_ARG();
+ if (get_u16(&sel.zone, *argv, 10)) {
+ fprintf(stderr, "connmark: Illegal \"zone\"\n");
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+ }
+
+ parse_action_control_dflt(&argc, &argv, &sel.action, false, TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&sel.index, *argv, 10)) {
+ fprintf(stderr, "connmark: Illegal \"index\"\n");
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_CONNMARK_PARMS, &sel, sizeof(sel));
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int print_connmark(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct rtattr *tb[TCA_CONNMARK_MAX + 1];
+ struct tc_connmark *ci;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_CONNMARK_MAX, arg);
+ if (tb[TCA_CONNMARK_PARMS] == NULL) {
+ fprintf(stderr, "Missing connmark parameters\n");
+ return -1;
+ }
+
+ ci = RTA_DATA(tb[TCA_CONNMARK_PARMS]);
+
+ print_string(PRINT_ANY, "kind", "%s ", "connmark");
+ print_uint(PRINT_ANY, "zone", "zone %u", ci->zone);
+ print_action_control(f, " ", ci->action, "");
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "index", "\t index %u", ci->index);
+ print_int(PRINT_ANY, "ref", " ref %d", ci->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", ci->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_CONNMARK_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_CONNMARK_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+
+ return 0;
+}
+
+struct action_util connmark_action_util = {
+ .id = "connmark",
+ .parse_aopt = parse_connmark,
+ .print_aopt = print_connmark,
+};
diff --git a/tc/m_csum.c b/tc/m_csum.c
new file mode 100644
index 0000000..3e3dc25
--- /dev/null
+++ b/tc/m_csum.c
@@ -0,0 +1,229 @@
+/*
+ * m_csum.c checksum updating action
+ *
+ * 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: Gregoire Baron <baronchon@n7mm.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <linux/tc_act/tc_csum.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void
+explain(void)
+{
+ fprintf(stderr, "Usage: ... csum <UPDATE>\n"
+ "Where: UPDATE := <TARGET> [<UPDATE>]\n"
+ " TARGET := { ip4h | icmp | igmp | tcp | udp | udplite | sctp | <SWEETS> }\n"
+ " SWEETS := { and | or | \'+\' }\n");
+}
+
+static void
+usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static int
+parse_csum_args(int *argc_p, char ***argv_p, struct tc_csum *sel)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc <= 0)
+ return -1;
+
+ while (argc > 0) {
+ if ((matches(*argv, "iph") == 0) ||
+ (matches(*argv, "ip4h") == 0) ||
+ (matches(*argv, "ipv4h") == 0))
+ sel->update_flags |= TCA_CSUM_UPDATE_FLAG_IPV4HDR;
+
+ else if (matches(*argv, "icmp") == 0)
+ sel->update_flags |= TCA_CSUM_UPDATE_FLAG_ICMP;
+
+ else if (matches(*argv, "igmp") == 0)
+ sel->update_flags |= TCA_CSUM_UPDATE_FLAG_IGMP;
+
+ else if (matches(*argv, "tcp") == 0)
+ sel->update_flags |= TCA_CSUM_UPDATE_FLAG_TCP;
+
+ else if (matches(*argv, "udp") == 0)
+ sel->update_flags |= TCA_CSUM_UPDATE_FLAG_UDP;
+
+ else if (matches(*argv, "udplite") == 0)
+ sel->update_flags |= TCA_CSUM_UPDATE_FLAG_UDPLITE;
+
+ else if (matches(*argv, "sctp") == 0)
+ sel->update_flags |= TCA_CSUM_UPDATE_FLAG_SCTP;
+
+ else if ((matches(*argv, "and") == 0) ||
+ (matches(*argv, "or") == 0) ||
+ (matches(*argv, "+") == 0))
+ ; /* just ignore: ... csum iph and tcp or udp */
+ else
+ break;
+ argc--;
+ argv++;
+ }
+
+ *argc_p = argc;
+ *argv_p = argv;
+
+ return 0;
+}
+
+static int
+parse_csum(struct action_util *a, int *argc_p,
+ char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+ struct tc_csum sel = {};
+
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int ok = 0;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (matches(*argv, "csum") == 0) {
+ NEXT_ARG();
+ if (parse_csum_args(&argc, &argv, &sel)) {
+ fprintf(stderr, "Illegal csum construct (%s)\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ ok++;
+ continue;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+ }
+
+ if (!ok) {
+ explain();
+ return -1;
+ }
+
+ if (sel.update_flags == 0) {
+ fprintf(stderr, "Illegal csum construct, empty <UPDATE> list\n");
+ return -1;
+ }
+
+ parse_action_control_dflt(&argc, &argv, &sel.action, false, TC_ACT_OK);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&sel.index, *argv, 10)) {
+ fprintf(stderr, "Illegal \"index\" (%s) <csum>\n",
+ *argv);
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_CSUM_PARMS, &sel, sizeof(sel));
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+
+ return 0;
+}
+
+static int
+print_csum(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct tc_csum *sel;
+
+ struct rtattr *tb[TCA_CSUM_MAX + 1];
+
+ char *uflag_1 = "";
+ char *uflag_2 = "";
+ char *uflag_3 = "";
+ char *uflag_4 = "";
+ char *uflag_5 = "";
+ char *uflag_6 = "";
+ char *uflag_7 = "";
+ SPRINT_BUF(buf);
+
+ int uflag_count = 0;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_CSUM_MAX, arg);
+
+ if (tb[TCA_CSUM_PARMS] == NULL) {
+ fprintf(stderr, "Missing csum parameters\n");
+ return -1;
+ }
+ sel = RTA_DATA(tb[TCA_CSUM_PARMS]);
+
+ if (sel->update_flags & TCA_CSUM_UPDATE_FLAG_IPV4HDR) {
+ uflag_1 = "iph";
+ uflag_count++;
+ }
+ #define CSUM_UFLAG_BUFFER(flag_buffer, flag_value, flag_string) \
+ do { \
+ if (sel->update_flags & flag_value) { \
+ flag_buffer = uflag_count > 0 ? \
+ ", " flag_string : flag_string; \
+ uflag_count++; \
+ } \
+ } while (0)
+ CSUM_UFLAG_BUFFER(uflag_2, TCA_CSUM_UPDATE_FLAG_ICMP, "icmp");
+ CSUM_UFLAG_BUFFER(uflag_3, TCA_CSUM_UPDATE_FLAG_IGMP, "igmp");
+ CSUM_UFLAG_BUFFER(uflag_4, TCA_CSUM_UPDATE_FLAG_TCP, "tcp");
+ CSUM_UFLAG_BUFFER(uflag_5, TCA_CSUM_UPDATE_FLAG_UDP, "udp");
+ CSUM_UFLAG_BUFFER(uflag_6, TCA_CSUM_UPDATE_FLAG_UDPLITE, "udplite");
+ CSUM_UFLAG_BUFFER(uflag_7, TCA_CSUM_UPDATE_FLAG_SCTP, "sctp");
+ if (!uflag_count) {
+ uflag_1 = "?empty";
+ }
+
+ print_string(PRINT_ANY, "kind", "%s ", "csum");
+ snprintf(buf, sizeof(buf), "%s%s%s%s%s%s%s",
+ uflag_1, uflag_2, uflag_3,
+ uflag_4, uflag_5, uflag_6, uflag_7);
+ print_string(PRINT_ANY, "csum", "(%s) ", buf);
+
+ print_action_control(f, "action ", sel->action, "\n");
+ print_uint(PRINT_ANY, "index", "\tindex %u", sel->index);
+ print_int(PRINT_ANY, "ref", " ref %d", sel->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", sel->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_CSUM_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_CSUM_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ print_string(PRINT_FP, NULL, "%s", "\n");
+
+ return 0;
+}
+
+struct action_util csum_action_util = {
+ .id = "csum",
+ .parse_aopt = parse_csum,
+ .print_aopt = print_csum,
+};
diff --git a/tc/m_ct.c b/tc/m_ct.c
new file mode 100644
index 0000000..8df2f61
--- /dev/null
+++ b/tc/m_ct.c
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/* -
+ * m_ct.c Connection tracking action
+ *
+ * Authors: Paul Blakey <paulb@mellanox.com>
+ * Yossi Kuperman <yossiku@mellanox.com>
+ * Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_ct.h>
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: ct clear\n"
+ " ct commit [force] [zone ZONE] [mark MASKED_MARK] [label MASKED_LABEL] [nat NAT_SPEC]\n"
+ " ct [nat] [zone ZONE]\n"
+ "Where: ZONE is the conntrack zone table number\n"
+ " NAT_SPEC is {src|dst} addr addr1[-addr2] [port port1[-port2]]\n"
+ "\n");
+ exit(-1);
+}
+
+static int ct_parse_nat_addr_range(const char *str, struct nlmsghdr *n)
+{
+ inet_prefix addr = { .family = AF_UNSPEC, };
+ char *addr1, *addr2 = 0;
+ SPRINT_BUF(buffer);
+ int attr;
+ int ret;
+
+ strncpy(buffer, str, sizeof(buffer) - 1);
+
+ addr1 = buffer;
+ addr2 = strchr(addr1, '-');
+ if (addr2) {
+ *addr2 = '\0';
+ addr2++;
+ }
+
+ ret = get_addr(&addr, addr1, AF_UNSPEC);
+ if (ret)
+ return ret;
+ attr = addr.family == AF_INET ? TCA_CT_NAT_IPV4_MIN :
+ TCA_CT_NAT_IPV6_MIN;
+ addattr_l(n, MAX_MSG, attr, addr.data, addr.bytelen);
+
+ if (addr2) {
+ ret = get_addr(&addr, addr2, addr.family);
+ if (ret)
+ return ret;
+ }
+ attr = addr.family == AF_INET ? TCA_CT_NAT_IPV4_MAX :
+ TCA_CT_NAT_IPV6_MAX;
+ addattr_l(n, MAX_MSG, attr, addr.data, addr.bytelen);
+
+ return 0;
+}
+
+static int ct_parse_nat_port_range(const char *str, struct nlmsghdr *n)
+{
+ char *port1, *port2 = 0;
+ SPRINT_BUF(buffer);
+ __be16 port;
+ int ret;
+
+ strncpy(buffer, str, sizeof(buffer) - 1);
+
+ port1 = buffer;
+ port2 = strchr(port1, '-');
+ if (port2) {
+ *port2 = '\0';
+ port2++;
+ }
+
+ ret = get_be16(&port, port1, 10);
+ if (ret)
+ return -1;
+ addattr16(n, MAX_MSG, TCA_CT_NAT_PORT_MIN, port);
+
+ if (port2) {
+ ret = get_be16(&port, port2, 10);
+ if (ret)
+ return -1;
+ }
+ addattr16(n, MAX_MSG, TCA_CT_NAT_PORT_MAX, port);
+
+ return 0;
+}
+
+
+static int ct_parse_u16(char *str, int value_type, int mask_type,
+ struct nlmsghdr *n)
+{
+ __u16 value, mask;
+ char *slash = 0;
+
+ if (mask_type != TCA_CT_UNSPEC) {
+ slash = strchr(str, '/');
+ if (slash)
+ *slash = '\0';
+ }
+
+ if (get_u16(&value, str, 0))
+ return -1;
+
+ if (slash) {
+ if (get_u16(&mask, slash + 1, 0))
+ return -1;
+ } else {
+ mask = UINT16_MAX;
+ }
+
+ addattr16(n, MAX_MSG, value_type, value);
+ if (mask_type != TCA_CT_UNSPEC)
+ addattr16(n, MAX_MSG, mask_type, mask);
+
+ return 0;
+}
+
+static int ct_parse_u32(char *str, int value_type, int mask_type,
+ struct nlmsghdr *n)
+{
+ __u32 value, mask;
+ char *slash;
+
+ slash = strchr(str, '/');
+ if (slash)
+ *slash = '\0';
+
+ if (get_u32(&value, str, 0))
+ return -1;
+
+ if (slash) {
+ if (get_u32(&mask, slash + 1, 0))
+ return -1;
+ } else {
+ mask = UINT32_MAX;
+ }
+
+ addattr32(n, MAX_MSG, value_type, value);
+ addattr32(n, MAX_MSG, mask_type, mask);
+
+ return 0;
+}
+
+static int ct_parse_mark(char *str, struct nlmsghdr *n)
+{
+ return ct_parse_u32(str, TCA_CT_MARK, TCA_CT_MARK_MASK, n);
+}
+
+static int ct_parse_labels(char *str, struct nlmsghdr *n)
+{
+#define LABELS_SIZE 16
+ uint8_t labels[LABELS_SIZE], lmask[LABELS_SIZE];
+ char *slash, *mask = NULL;
+ size_t slen, slen_mask = 0;
+
+ slash = index(str, '/');
+ if (slash) {
+ *slash = 0;
+ mask = slash+1;
+ slen_mask = strlen(mask);
+ }
+
+ slen = strlen(str);
+ if (slen > LABELS_SIZE*2 || slen_mask > LABELS_SIZE*2) {
+ char errmsg[128];
+
+ snprintf(errmsg, sizeof(errmsg),
+ "%zd Max allowed size %d",
+ slen, LABELS_SIZE*2);
+ invarg(errmsg, str);
+ }
+
+ if (hex2mem(str, labels, slen/2) < 0)
+ invarg("ct: labels must be a hex string\n", str);
+ addattr_l(n, MAX_MSG, TCA_CT_LABELS, labels, slen/2);
+
+ if (mask) {
+ if (hex2mem(mask, lmask, slen_mask/2) < 0)
+ invarg("ct: labels mask must be a hex string\n", mask);
+ } else {
+ memset(lmask, 0xff, sizeof(lmask));
+ slen_mask = sizeof(lmask)*2;
+ }
+ addattr_l(n, MAX_MSG, TCA_CT_LABELS_MASK, lmask, slen_mask/2);
+
+ return 0;
+}
+
+static int
+parse_ct(struct action_util *a, int *argc_p, char ***argv_p, int tca_id,
+ struct nlmsghdr *n)
+{
+ struct tc_ct sel = {};
+ char **argv = *argv_p;
+ struct rtattr *tail;
+ int argc = *argc_p;
+ int ct_action = 0;
+ int ret;
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+
+ if (argc && matches(*argv, "ct") == 0)
+ NEXT_ARG_FWD();
+
+ while (argc > 0) {
+ if (matches(*argv, "zone") == 0) {
+ NEXT_ARG();
+
+ if (ct_parse_u16(*argv,
+ TCA_CT_ZONE, TCA_CT_UNSPEC, n)) {
+ fprintf(stderr, "ct: Illegal \"zone\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "nat") == 0) {
+ ct_action |= TCA_CT_ACT_NAT;
+
+ NEXT_ARG();
+ if (matches(*argv, "src") == 0)
+ ct_action |= TCA_CT_ACT_NAT_SRC;
+ else if (matches(*argv, "dst") == 0)
+ ct_action |= TCA_CT_ACT_NAT_DST;
+ else
+ continue;
+
+ NEXT_ARG();
+ if (matches(*argv, "addr") != 0)
+ usage();
+
+ NEXT_ARG();
+ ret = ct_parse_nat_addr_range(*argv, n);
+ if (ret) {
+ fprintf(stderr, "ct: Illegal nat address range\n");
+ return -1;
+ }
+
+ NEXT_ARG_FWD();
+ if (matches(*argv, "port") != 0)
+ continue;
+
+ NEXT_ARG();
+ ret = ct_parse_nat_port_range(*argv, n);
+ if (ret) {
+ fprintf(stderr, "ct: Illegal nat port range\n");
+ return -1;
+ }
+ } else if (matches(*argv, "clear") == 0) {
+ ct_action |= TCA_CT_ACT_CLEAR;
+ } else if (matches(*argv, "commit") == 0) {
+ ct_action |= TCA_CT_ACT_COMMIT;
+ } else if (matches(*argv, "force") == 0) {
+ ct_action |= TCA_CT_ACT_FORCE;
+ } else if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&sel.index, *argv, 10)) {
+ fprintf(stderr, "ct: Illegal \"index\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "mark") == 0) {
+ NEXT_ARG();
+
+ ret = ct_parse_mark(*argv, n);
+ if (ret) {
+ fprintf(stderr, "ct: Illegal \"mark\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "label") == 0) {
+ NEXT_ARG();
+
+ ret = ct_parse_labels(*argv, n);
+ if (ret) {
+ fprintf(stderr, "ct: Illegal \"label\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+ NEXT_ARG_FWD();
+ }
+
+ if (ct_action & TCA_CT_ACT_CLEAR &&
+ ct_action & ~TCA_CT_ACT_CLEAR) {
+ fprintf(stderr, "ct: clear can only be used alone\n");
+ return -1;
+ }
+
+ if (ct_action & TCA_CT_ACT_NAT_SRC &&
+ ct_action & TCA_CT_ACT_NAT_DST) {
+ fprintf(stderr, "ct: src and dst nat can't be used together\n");
+ return -1;
+ }
+
+ if ((ct_action & TCA_CT_ACT_COMMIT) &&
+ (ct_action & TCA_CT_ACT_NAT) &&
+ !(ct_action & (TCA_CT_ACT_NAT_SRC | TCA_CT_ACT_NAT_DST))) {
+ fprintf(stderr, "ct: commit and nat must set src or dst\n");
+ return -1;
+ }
+
+ if (!(ct_action & TCA_CT_ACT_COMMIT) &&
+ (ct_action & (TCA_CT_ACT_NAT_SRC | TCA_CT_ACT_NAT_DST))) {
+ fprintf(stderr, "ct: src or dst is only valid if commit is set\n");
+ return -1;
+ }
+
+ parse_action_control_dflt(&argc, &argv, &sel.action, false,
+ TC_ACT_PIPE);
+
+ addattr16(n, MAX_MSG, TCA_CT_ACTION, ct_action);
+ addattr_l(n, MAX_MSG, TCA_CT_PARMS, &sel, sizeof(sel));
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int ct_sprint_port(char *buf, const char *prefix, struct rtattr *attr)
+{
+ if (!attr)
+ return 0;
+
+ return sprintf(buf, "%s%d", prefix, rta_getattr_be16(attr));
+}
+
+static int ct_sprint_ip_addr(char *buf, const char *prefix,
+ struct rtattr *attr)
+{
+ int family;
+ size_t len;
+
+ if (!attr)
+ return 0;
+
+ len = RTA_PAYLOAD(attr);
+
+ if (len == 4)
+ family = AF_INET;
+ else if (len == 16)
+ family = AF_INET6;
+ else
+ return 0;
+
+ return sprintf(buf, "%s%s", prefix, rt_addr_n2a_rta(family, attr));
+}
+
+static void ct_print_nat(int ct_action, struct rtattr **tb)
+{
+ size_t done = 0;
+ char out[256] = "";
+ bool nat;
+
+ if (!(ct_action & TCA_CT_ACT_NAT))
+ return;
+
+ if (ct_action & TCA_CT_ACT_NAT_SRC) {
+ nat = true;
+ done += sprintf(out + done, "src");
+ } else if (ct_action & TCA_CT_ACT_NAT_DST) {
+ nat = true;
+ done += sprintf(out + done, "dst");
+ }
+
+ if (nat) {
+ done += ct_sprint_ip_addr(out + done, " addr ",
+ tb[TCA_CT_NAT_IPV4_MIN]);
+ done += ct_sprint_ip_addr(out + done, " addr ",
+ tb[TCA_CT_NAT_IPV6_MIN]);
+ if (tb[TCA_CT_NAT_IPV4_MAX] &&
+ memcmp(RTA_DATA(tb[TCA_CT_NAT_IPV4_MIN]),
+ RTA_DATA(tb[TCA_CT_NAT_IPV4_MAX]), 4))
+ done += ct_sprint_ip_addr(out + done, "-",
+ tb[TCA_CT_NAT_IPV4_MAX]);
+ else if (tb[TCA_CT_NAT_IPV6_MAX] &&
+ memcmp(RTA_DATA(tb[TCA_CT_NAT_IPV6_MIN]),
+ RTA_DATA(tb[TCA_CT_NAT_IPV6_MAX]), 16))
+ done += ct_sprint_ip_addr(out + done, "-",
+ tb[TCA_CT_NAT_IPV6_MAX]);
+ done += ct_sprint_port(out + done, " port ",
+ tb[TCA_CT_NAT_PORT_MIN]);
+ if (tb[TCA_CT_NAT_PORT_MAX] &&
+ memcmp(RTA_DATA(tb[TCA_CT_NAT_PORT_MIN]),
+ RTA_DATA(tb[TCA_CT_NAT_PORT_MAX]), 2))
+ done += ct_sprint_port(out + done, "-",
+ tb[TCA_CT_NAT_PORT_MAX]);
+ }
+
+ if (done)
+ print_string(PRINT_ANY, "nat", " nat %s", out);
+ else
+ print_string(PRINT_ANY, "nat", " nat", "");
+}
+
+static void ct_print_labels(struct rtattr *attr,
+ struct rtattr *mask_attr)
+{
+ const unsigned char *str;
+ bool print_mask = false;
+ char out[256], *p;
+ int data_len, i;
+
+ if (!attr)
+ return;
+
+ data_len = RTA_PAYLOAD(attr);
+ hexstring_n2a(RTA_DATA(attr), data_len, out, sizeof(out));
+ p = out + data_len*2;
+
+ data_len = RTA_PAYLOAD(attr);
+ str = RTA_DATA(mask_attr);
+ if (data_len != 16)
+ print_mask = true;
+ for (i = 0; !print_mask && i < data_len; i++) {
+ if (str[i] != 0xff)
+ print_mask = true;
+ }
+ if (print_mask) {
+ *p++ = '/';
+ hexstring_n2a(RTA_DATA(mask_attr), data_len, p,
+ sizeof(out)-(p-out));
+ p += data_len*2;
+ }
+ *p = '\0';
+
+ print_string(PRINT_ANY, "label", " label %s", out);
+}
+
+static int print_ct(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct rtattr *tb[TCA_CT_MAX + 1];
+ const char *commit;
+ struct tc_ct *p;
+ int ct_action = 0;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_CT_MAX, arg);
+ if (tb[TCA_CT_PARMS] == NULL) {
+ print_string(PRINT_FP, NULL, "%s", "[NULL ct parameters]");
+ return -1;
+ }
+
+ p = RTA_DATA(tb[TCA_CT_PARMS]);
+
+ print_string(PRINT_ANY, "kind", "%s", "ct");
+
+ if (tb[TCA_CT_ACTION])
+ ct_action = rta_getattr_u16(tb[TCA_CT_ACTION]);
+ if (ct_action & TCA_CT_ACT_COMMIT) {
+ commit = ct_action & TCA_CT_ACT_FORCE ?
+ "commit force" : "commit";
+ print_string(PRINT_ANY, "action", " %s", commit);
+ } else if (ct_action & TCA_CT_ACT_CLEAR) {
+ print_string(PRINT_ANY, "action", " %s", "clear");
+ }
+
+ print_masked_u32("mark", tb[TCA_CT_MARK], tb[TCA_CT_MARK_MASK], false);
+ print_masked_u16("zone", tb[TCA_CT_ZONE], NULL, false);
+ ct_print_labels(tb[TCA_CT_LABELS], tb[TCA_CT_LABELS_MASK]);
+ ct_print_nat(ct_action, tb);
+
+ print_action_control(f, " ", p->action, "");
+
+ print_uint(PRINT_ANY, "index", "\n\t index %u", p->index);
+ print_int(PRINT_ANY, "ref", " ref %d", p->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_CT_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_CT_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ print_string(PRINT_FP, NULL, "%s", "\n ");
+
+ return 0;
+}
+
+struct action_util ct_action_util = {
+ .id = "ct",
+ .parse_aopt = parse_ct,
+ .print_aopt = print_ct,
+};
diff --git a/tc/m_ctinfo.c b/tc/m_ctinfo.c
new file mode 100644
index 0000000..5e451f8
--- /dev/null
+++ b/tc/m_ctinfo.c
@@ -0,0 +1,268 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * m_ctinfo.c netfilter ctinfo mark action
+ *
+ * Copyright (c) 2019 Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_ctinfo.h>
+
+static void
+explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... ctinfo [dscp mask [statemask]] [cpmark [mask]] [zone ZONE] [CONTROL] [index <INDEX>]\n"
+ "where :\n"
+ "\tdscp MASK bitmask location of stored DSCP\n"
+ "\t STATEMASK bitmask to determine conditional restoring\n"
+ "\tcpmark MASK mask applied to mark on restoration\n"
+ "\tZONE is the conntrack zone\n"
+ "\tCONTROL := reclassify | pipe | drop | continue | ok |\n"
+ "\t goto chain <CHAIN_INDEX>\n");
+}
+
+static void
+usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static int
+parse_ctinfo(struct action_util *a, int *argc_p, char ***argv_p, int tca_id,
+ struct nlmsghdr *n)
+{
+ unsigned int cpmarkmask = 0, dscpmask = 0, dscpstatemask = 0;
+ struct tc_ctinfo sel = {};
+ unsigned short zone = 0;
+ char **argv = *argv_p;
+ struct rtattr *tail;
+ int argc = *argc_p;
+ int ok = 0;
+ __u8 i;
+
+ while (argc > 0) {
+ if (matches(*argv, "ctinfo") == 0) {
+ ok = 1;
+ NEXT_ARG_FWD();
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+
+ }
+
+ if (!ok) {
+ explain();
+ return -1;
+ }
+
+ if (argc) {
+ if (matches(*argv, "dscp") == 0) {
+ NEXT_ARG();
+ if (get_u32(&dscpmask, *argv, 0)) {
+ fprintf(stderr,
+ "ctinfo: Illegal dscp \"mask\"\n");
+ return -1;
+ }
+ if (NEXT_ARG_OK()) {
+ NEXT_ARG_FWD();
+ if (!get_u32(&dscpstatemask, *argv, 0))
+ NEXT_ARG_FWD(); /* was a statemask */
+ } else {
+ NEXT_ARG_FWD();
+ }
+ }
+ }
+
+ /* cpmark has optional mask parameter, so the next arg might not */
+ /* exist, or it might be the next option, or it may actually be a */
+ /* 32bit mask */
+ if (argc) {
+ if (matches(*argv, "cpmark") == 0) {
+ cpmarkmask = ~0;
+ if (NEXT_ARG_OK()) {
+ NEXT_ARG_FWD();
+ if (!get_u32(&cpmarkmask, *argv, 0))
+ NEXT_ARG_FWD(); /* was a mask */
+ } else {
+ NEXT_ARG_FWD();
+ }
+ }
+ }
+
+ if (argc) {
+ if (matches(*argv, "zone") == 0) {
+ NEXT_ARG();
+ if (get_u16(&zone, *argv, 10)) {
+ fprintf(stderr, "ctinfo: Illegal \"zone\"\n");
+ return -1;
+ }
+ NEXT_ARG_FWD();
+ }
+ }
+
+ parse_action_control_dflt(&argc, &argv, &sel.action,
+ false, TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&sel.index, *argv, 10)) {
+ fprintf(stderr, "ctinfo: Illegal \"index\"\n");
+ return -1;
+ }
+ NEXT_ARG_FWD();
+ }
+ }
+
+ if (dscpmask & dscpstatemask) {
+ fprintf(stderr,
+ "ctinfo: dscp mask & statemask must NOT overlap\n");
+ return -1;
+ }
+
+ i = ffs(dscpmask);
+ if (i && ((~0 & (dscpmask >> (i - 1))) != 0x3f)) {
+ fprintf(stderr,
+ "ctinfo: dscp mask must be 6 contiguous bits long\n");
+ return -1;
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_CTINFO_ACT, &sel, sizeof(sel));
+ addattr16(n, MAX_MSG, TCA_CTINFO_ZONE, zone);
+
+ if (dscpmask)
+ addattr32(n, MAX_MSG,
+ TCA_CTINFO_PARMS_DSCP_MASK, dscpmask);
+
+ if (dscpstatemask)
+ addattr32(n, MAX_MSG,
+ TCA_CTINFO_PARMS_DSCP_STATEMASK, dscpstatemask);
+
+ if (cpmarkmask)
+ addattr32(n, MAX_MSG,
+ TCA_CTINFO_PARMS_CPMARK_MASK, cpmarkmask);
+
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static void print_ctinfo_stats(FILE *f, struct rtattr *tb[TCA_CTINFO_MAX + 1])
+{
+ struct tcf_t *tm;
+
+ if (tb[TCA_CTINFO_TM]) {
+ tm = RTA_DATA(tb[TCA_CTINFO_TM]);
+
+ print_tm(f, tm);
+ }
+
+ if (tb[TCA_CTINFO_STATS_DSCP_SET])
+ print_lluint(PRINT_ANY, "dscpset", " DSCP set %llu",
+ rta_getattr_u64(tb[TCA_CTINFO_STATS_DSCP_SET]));
+ if (tb[TCA_CTINFO_STATS_DSCP_ERROR])
+ print_lluint(PRINT_ANY, "dscperror", " error %llu",
+ rta_getattr_u64(tb[TCA_CTINFO_STATS_DSCP_ERROR]));
+
+ if (tb[TCA_CTINFO_STATS_CPMARK_SET])
+ print_lluint(PRINT_ANY, "cpmarkset", " CPMARK set %llu",
+ rta_getattr_u64(tb[TCA_CTINFO_STATS_CPMARK_SET]));
+}
+
+static int print_ctinfo(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ unsigned int cpmarkmask = ~0, dscpmask = 0, dscpstatemask = 0;
+ struct rtattr *tb[TCA_CTINFO_MAX + 1];
+ unsigned short zone = 0;
+ struct tc_ctinfo *ci;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_CTINFO_MAX, arg);
+ if (!tb[TCA_CTINFO_ACT]) {
+ print_string(PRINT_FP, NULL, "%s",
+ "[NULL ctinfo action parameters]");
+ return -1;
+ }
+
+ ci = RTA_DATA(tb[TCA_CTINFO_ACT]);
+
+ if (tb[TCA_CTINFO_PARMS_DSCP_MASK]) {
+ if (RTA_PAYLOAD(tb[TCA_CTINFO_PARMS_DSCP_MASK]) >=
+ sizeof(__u32))
+ dscpmask = rta_getattr_u32(
+ tb[TCA_CTINFO_PARMS_DSCP_MASK]);
+ else
+ print_string(PRINT_FP, NULL, "%s",
+ "[invalid dscp mask parameter]");
+ }
+
+ if (tb[TCA_CTINFO_PARMS_DSCP_STATEMASK]) {
+ if (RTA_PAYLOAD(tb[TCA_CTINFO_PARMS_DSCP_STATEMASK]) >=
+ sizeof(__u32))
+ dscpstatemask = rta_getattr_u32(
+ tb[TCA_CTINFO_PARMS_DSCP_STATEMASK]);
+ else
+ print_string(PRINT_FP, NULL, "%s",
+ "[invalid dscp statemask parameter]");
+ }
+
+ if (tb[TCA_CTINFO_PARMS_CPMARK_MASK]) {
+ if (RTA_PAYLOAD(tb[TCA_CTINFO_PARMS_CPMARK_MASK]) >=
+ sizeof(__u32))
+ cpmarkmask = rta_getattr_u32(
+ tb[TCA_CTINFO_PARMS_CPMARK_MASK]);
+ else
+ print_string(PRINT_FP, NULL, "%s",
+ "[invalid cpmark mask parameter]");
+ }
+
+ if (tb[TCA_CTINFO_ZONE] && RTA_PAYLOAD(tb[TCA_CTINFO_ZONE]) >=
+ sizeof(__u16))
+ zone = rta_getattr_u16(tb[TCA_CTINFO_ZONE]);
+
+ print_string(PRINT_ANY, "kind", "%s ", "ctinfo");
+ print_hu(PRINT_ANY, "zone", "zone %u", zone);
+ print_action_control(f, " ", ci->action, "");
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "index", "\t index %u", ci->index);
+ print_int(PRINT_ANY, "ref", " ref %d", ci->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", ci->bindcnt);
+
+ if (tb[TCA_CTINFO_PARMS_DSCP_MASK]) {
+ print_0xhex(PRINT_ANY, "dscpmask", " dscp %#010llx", dscpmask);
+ print_0xhex(PRINT_ANY, "dscpstatemask", " %#010llx",
+ dscpstatemask);
+ }
+
+ if (tb[TCA_CTINFO_PARMS_CPMARK_MASK])
+ print_0xhex(PRINT_ANY, "cpmark", " cpmark %#010llx",
+ cpmarkmask);
+
+ if (show_stats)
+ print_ctinfo_stats(f, tb);
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+
+ return 0;
+}
+
+struct action_util ctinfo_action_util = {
+ .id = "ctinfo",
+ .parse_aopt = parse_ctinfo,
+ .print_aopt = print_ctinfo,
+};
diff --git a/tc/m_ematch.c b/tc/m_ematch.c
new file mode 100644
index 0000000..8840a0d
--- /dev/null
+++ b/tc/m_ematch.c
@@ -0,0 +1,569 @@
+/*
+ * m_ematch.c Extended Matches
+ *
+ * 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: Thomas Graf <tgraf@suug.ch>
+ */
+
+#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 <dlfcn.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "m_ematch.h"
+
+#define EMATCH_MAP "/etc/iproute2/ematch_map"
+
+static struct ematch_util *ematch_list;
+
+/* export to bison parser */
+int ematch_argc;
+char **ematch_argv;
+char *ematch_err;
+struct ematch *ematch_root;
+
+static int begin_argc;
+static char **begin_argv;
+
+static void bstr_print(FILE *fd, const struct bstr *b, int ascii);
+
+static inline void map_warning(int num, char *kind)
+{
+ fprintf(stderr,
+ "Error: Unable to find ematch \"%s\" in %s\n" \
+ "Please assign a unique ID to the ematch kind the suggested " \
+ "entry is:\n" \
+ "\t%d\t%s\n",
+ kind, EMATCH_MAP, num, kind);
+}
+
+static int lookup_map(__u16 num, char *dst, int len, const char *file)
+{
+ int err = -EINVAL;
+ char buf[512];
+ FILE *fd = fopen(file, "r");
+
+ if (fd == NULL)
+ return -errno;
+
+ while (fgets(buf, sizeof(buf), fd)) {
+ char namebuf[512], *p = buf;
+ int id;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (*p == '#' || *p == '\n' || *p == 0)
+ continue;
+
+ if (sscanf(p, "%d %s", &id, namebuf) != 2) {
+ fprintf(stderr, "ematch map %s corrupted at %s\n",
+ file, p);
+ goto out;
+ }
+
+ if (id == num) {
+ if (dst)
+ strncpy(dst, namebuf, len - 1);
+ err = 0;
+ goto out;
+ }
+ }
+
+ err = -ENOENT;
+out:
+ fclose(fd);
+ return err;
+}
+
+static int lookup_map_id(char *kind, int *dst, const char *file)
+{
+ int err = -EINVAL;
+ char buf[512];
+ FILE *fd = fopen(file, "r");
+
+ if (fd == NULL)
+ return -errno;
+
+ while (fgets(buf, sizeof(buf), fd)) {
+ char namebuf[512], *p = buf;
+ int id;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (*p == '#' || *p == '\n' || *p == 0)
+ continue;
+
+ if (sscanf(p, "%d %s", &id, namebuf) != 2) {
+ fprintf(stderr, "ematch map %s corrupted at %s\n",
+ file, p);
+ goto out;
+ }
+
+ if (!strcasecmp(namebuf, kind)) {
+ if (dst)
+ *dst = id;
+ err = 0;
+ goto out;
+ }
+ }
+
+ err = -ENOENT;
+ *dst = 0;
+out:
+ fclose(fd);
+ return err;
+}
+
+static struct ematch_util *get_ematch_kind(char *kind)
+{
+ static void *body;
+ void *dlh;
+ char buf[256];
+ struct ematch_util *e;
+
+ for (e = ematch_list; e; e = e->next) {
+ if (strcmp(e->kind, kind) == 0)
+ return e;
+ }
+
+ snprintf(buf, sizeof(buf), "em_%s.so", kind);
+ dlh = dlopen(buf, RTLD_LAZY);
+ if (dlh == NULL) {
+ dlh = body;
+ if (dlh == NULL) {
+ dlh = body = dlopen(NULL, RTLD_LAZY);
+ if (dlh == NULL)
+ return NULL;
+ }
+ }
+
+ snprintf(buf, sizeof(buf), "%s_ematch_util", kind);
+ e = dlsym(dlh, buf);
+ if (e == NULL)
+ return NULL;
+
+ e->next = ematch_list;
+ ematch_list = e;
+
+ return e;
+}
+
+static struct ematch_util *get_ematch_kind_num(__u16 kind)
+{
+ char name[513];
+
+ if (lookup_map(kind, name, sizeof(name), EMATCH_MAP) < 0)
+ return NULL;
+
+ return get_ematch_kind(name);
+}
+
+static int em_parse_call(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr,
+ struct ematch_util *e, struct ematch *t)
+{
+ if (e->parse_eopt_argv) {
+ int argc = 0, i = 0, ret;
+ struct bstr *args;
+ char **argv;
+
+ for (args = t->args; args; args = bstr_next(args))
+ argc++;
+ argv = calloc(argc, sizeof(char *));
+ if (!argv)
+ return -1;
+ for (args = t->args; args; args = bstr_next(args))
+ argv[i++] = args->data;
+
+ ret = e->parse_eopt_argv(n, hdr, argc, argv);
+
+ free(argv);
+ return ret;
+ }
+
+ return e->parse_eopt(n, hdr, t->args->next);
+}
+
+static int parse_tree(struct nlmsghdr *n, struct ematch *tree)
+{
+ int index = 1;
+ struct ematch *t;
+
+ for (t = tree; t; t = t->next) {
+ struct rtattr *tail;
+ struct tcf_ematch_hdr hdr = { .flags = t->relation };
+
+ if (t->inverted)
+ hdr.flags |= TCF_EM_INVERT;
+
+ tail = addattr_nest(n, MAX_MSG, index++);
+
+ if (t->child) {
+ __u32 r = t->child_ref;
+
+ addraw_l(n, MAX_MSG, &hdr, sizeof(hdr));
+ addraw_l(n, MAX_MSG, &r, sizeof(r));
+ } else {
+ int num = 0, err;
+ char buf[64];
+ struct ematch_util *e;
+
+ if (t->args == NULL)
+ return -1;
+
+ strncpy(buf, (char *) t->args->data, sizeof(buf)-1);
+ e = get_ematch_kind(buf);
+ if (e == NULL) {
+ fprintf(stderr, "Unknown ematch \"%s\"\n",
+ buf);
+ return -1;
+ }
+
+ err = lookup_map_id(buf, &num, EMATCH_MAP);
+ if (err < 0) {
+ if (err == -ENOENT)
+ map_warning(e->kind_num, buf);
+ return err;
+ }
+
+ hdr.kind = num;
+ if (em_parse_call(n, &hdr, e, t) < 0)
+ return -1;
+ }
+
+ addattr_nest_end(n, tail);
+ }
+
+ return 0;
+}
+
+static int flatten_tree(struct ematch *head, struct ematch *tree)
+{
+ int i, count = 0;
+ struct ematch *t;
+
+ for (;;) {
+ count++;
+
+ if (tree->child) {
+ for (t = head; t->next; t = t->next);
+ t->next = tree->child;
+ count += flatten_tree(head, tree->child);
+ }
+
+ if (tree->relation == 0)
+ break;
+
+ tree = tree->next;
+ }
+
+ for (i = 0, t = head; t; t = t->next, i++)
+ t->index = i;
+
+ for (t = head; t; t = t->next)
+ if (t->child)
+ t->child_ref = t->child->index;
+
+ return count;
+}
+
+__attribute__((format(printf, 5, 6)))
+int em_parse_error(int err, struct bstr *args, struct bstr *carg,
+ struct ematch_util *e, char *fmt, ...)
+{
+ va_list a;
+
+ va_start(a, fmt);
+ vfprintf(stderr, fmt, a);
+ va_end(a);
+
+ if (ematch_err)
+ fprintf(stderr, ": %s\n... ", ematch_err);
+ else
+ fprintf(stderr, "\n... ");
+
+ while (ematch_argc < begin_argc) {
+ if (ematch_argc == (begin_argc - 1))
+ fprintf(stderr, ">>%s<< ", *begin_argv);
+ else
+ fprintf(stderr, "%s ", *begin_argv);
+ begin_argv++;
+ begin_argc--;
+ }
+
+ fprintf(stderr, "...\n");
+
+ if (args) {
+ fprintf(stderr, "... %s(", e->kind);
+ while (args) {
+ fprintf(stderr, "%s", args == carg ? ">>" : "");
+ bstr_print(stderr, args, 1);
+ fprintf(stderr, "%s%s", args == carg ? "<<" : "",
+ args->next ? " " : "");
+ args = args->next;
+ }
+ fprintf(stderr, ")...\n");
+
+ }
+
+ if (e == NULL) {
+ fprintf(stderr,
+ "Usage: EXPR\n" \
+ "where: EXPR := TERM [ { and | or } EXPR ]\n" \
+ " TERM := [ not ] { MATCH | '(' EXPR ')' }\n" \
+ " MATCH := module '(' ARGS ')'\n" \
+ " ARGS := ARG1 ARG2 ...\n" \
+ "\n" \
+ "Example: a(x y) and not (b(x) or c(x y z))\n");
+ } else
+ e->print_usage(stderr);
+
+ return -err;
+}
+
+static inline void free_ematch_err(void)
+{
+ if (ematch_err) {
+ free(ematch_err);
+ ematch_err = NULL;
+ }
+}
+
+extern int ematch_parse(void);
+
+int parse_ematch(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+ begin_argc = ematch_argc = *argc_p;
+ begin_argv = ematch_argv = *argv_p;
+
+ if (ematch_parse()) {
+ int err = em_parse_error(EINVAL, NULL, NULL, NULL,
+ "Parse error");
+ free_ematch_err();
+ return err;
+ }
+
+ free_ematch_err();
+
+ /* undo look ahead by parser */
+ ematch_argc++;
+ ematch_argv--;
+
+ if (ematch_root) {
+ struct rtattr *tail, *tail_list;
+
+ struct tcf_ematch_tree_hdr hdr = {
+ .nmatches = flatten_tree(ematch_root, ematch_root),
+ .progid = TCF_EM_PROG_TC
+ };
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_EMATCH_TREE_HDR, &hdr, sizeof(hdr));
+
+ tail_list = addattr_nest(n, MAX_MSG, TCA_EMATCH_TREE_LIST);
+
+ if (parse_tree(n, ematch_root) < 0)
+ return -1;
+
+ addattr_nest_end(n, tail_list);
+ addattr_nest_end(n, tail);
+ }
+
+ *argc_p = ematch_argc;
+ *argv_p = ematch_argv;
+
+ return 0;
+}
+
+static int print_ematch_seq(FILE *fd, struct rtattr **tb, int start,
+ int prefix)
+{
+ int n, i = start;
+ struct tcf_ematch_hdr *hdr;
+ int dlen;
+ void *data;
+
+ for (;;) {
+ if (tb[i] == NULL)
+ return -1;
+
+ dlen = RTA_PAYLOAD(tb[i]) - sizeof(*hdr);
+ data = (void *) RTA_DATA(tb[i]) + sizeof(*hdr);
+
+ if (dlen < 0)
+ return -1;
+
+ hdr = RTA_DATA(tb[i]);
+
+ if (hdr->flags & TCF_EM_INVERT)
+ fprintf(fd, "NOT ");
+
+ if (hdr->kind == 0) {
+ __u32 ref;
+
+ if (dlen < sizeof(__u32))
+ return -1;
+
+ ref = *(__u32 *) data;
+ fprintf(fd, "(\n");
+ for (n = 0; n <= prefix; n++)
+ fprintf(fd, " ");
+ if (print_ematch_seq(fd, tb, ref + 1, prefix + 1) < 0)
+ return -1;
+ for (n = 0; n < prefix; n++)
+ fprintf(fd, " ");
+ fprintf(fd, ") ");
+
+ } else {
+ struct ematch_util *e;
+
+ e = get_ematch_kind_num(hdr->kind);
+ if (e == NULL)
+ fprintf(fd, "[unknown ematch %d]\n",
+ hdr->kind);
+ else {
+ fprintf(fd, "%s(", e->kind);
+ if (e->print_eopt(fd, hdr, data, dlen) < 0)
+ return -1;
+ fprintf(fd, ")\n");
+ }
+ if (hdr->flags & TCF_EM_REL_MASK)
+ for (n = 0; n < prefix; n++)
+ fprintf(fd, " ");
+ }
+
+ switch (hdr->flags & TCF_EM_REL_MASK) {
+ case TCF_EM_REL_AND:
+ fprintf(fd, "AND ");
+ break;
+
+ case TCF_EM_REL_OR:
+ fprintf(fd, "OR ");
+ break;
+
+ default:
+ return 0;
+ }
+
+ i++;
+ }
+
+ return 0;
+}
+
+static int print_ematch_list(FILE *fd, struct tcf_ematch_tree_hdr *hdr,
+ struct rtattr *rta)
+{
+ int err = -1;
+ struct rtattr **tb;
+
+ tb = malloc((hdr->nmatches + 1) * sizeof(struct rtattr *));
+ if (tb == NULL)
+ return -1;
+
+ if (hdr->nmatches > 0) {
+ if (parse_rtattr_nested(tb, hdr->nmatches, rta) < 0)
+ goto errout;
+
+ fprintf(fd, "\n ");
+ if (print_ematch_seq(fd, tb, 1, 1) < 0)
+ goto errout;
+ }
+
+ err = 0;
+errout:
+ free(tb);
+ return err;
+}
+
+int print_ematch(FILE *fd, const struct rtattr *rta)
+{
+ struct rtattr *tb[TCA_EMATCH_TREE_MAX+1];
+ struct tcf_ematch_tree_hdr *hdr;
+
+ if (parse_rtattr_nested(tb, TCA_EMATCH_TREE_MAX, rta) < 0)
+ return -1;
+
+ if (tb[TCA_EMATCH_TREE_HDR] == NULL) {
+ fprintf(stderr, "Missing ematch tree header\n");
+ return -1;
+ }
+
+ if (tb[TCA_EMATCH_TREE_LIST] == NULL) {
+ fprintf(stderr, "Missing ematch tree list\n");
+ return -1;
+ }
+
+ if (RTA_PAYLOAD(tb[TCA_EMATCH_TREE_HDR]) < sizeof(*hdr)) {
+ fprintf(stderr, "Ematch tree header size mismatch\n");
+ return -1;
+ }
+
+ hdr = RTA_DATA(tb[TCA_EMATCH_TREE_HDR]);
+
+ return print_ematch_list(fd, hdr, tb[TCA_EMATCH_TREE_LIST]);
+}
+
+struct bstr *bstr_alloc(const char *text)
+{
+ struct bstr *b = calloc(1, sizeof(*b));
+
+ if (b == NULL)
+ return NULL;
+
+ b->data = strdup(text);
+ if (b->data == NULL) {
+ free(b);
+ return NULL;
+ }
+
+ b->len = strlen(text);
+
+ return b;
+}
+
+unsigned long bstrtoul(const struct bstr *b)
+{
+ char *inv = NULL;
+ unsigned long l;
+ char buf[b->len+1];
+
+ memcpy(buf, b->data, b->len);
+ buf[b->len] = '\0';
+
+ l = strtoul(buf, &inv, 0);
+ if (l == ULONG_MAX || inv == buf)
+ return ULONG_MAX;
+
+ return l;
+}
+
+static void bstr_print(FILE *fd, const struct bstr *b, int ascii)
+{
+ int i;
+ char *s = b->data;
+
+ if (ascii)
+ for (i = 0; i < b->len; i++)
+ fprintf(fd, "%c", isprint(s[i]) ? s[i] : '.');
+ else {
+ for (i = 0; i < b->len; i++)
+ fprintf(fd, "%02x", s[i]);
+ fprintf(fd, "\"");
+ for (i = 0; i < b->len; i++)
+ fprintf(fd, "%c", isprint(s[i]) ? s[i] : '.');
+ fprintf(fd, "\"");
+ }
+}
diff --git a/tc/m_ematch.h b/tc/m_ematch.h
new file mode 100644
index 0000000..c4443ee
--- /dev/null
+++ b/tc/m_ematch.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __TC_EMATCH_H_
+#define __TC_EMATCH_H_
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+#define EMATCHKINDSIZ 16
+
+struct bstr {
+ char *data;
+ unsigned int len;
+ int quoted;
+ struct bstr *next;
+};
+
+struct bstr *bstr_alloc(const char *text);
+
+static inline struct bstr *bstr_new(char *data, unsigned int len)
+{
+ struct bstr *b = calloc(1, sizeof(*b));
+
+ if (b == NULL)
+ return NULL;
+
+ b->data = data;
+ b->len = len;
+
+ return b;
+}
+
+static inline int bstrcmp(const struct bstr *b, const char *text)
+{
+ int len = strlen(text);
+ int d = b->len - len;
+
+ if (d == 0)
+ return strncmp(b->data, text, len);
+
+ return d;
+}
+
+static inline struct bstr *bstr_next(struct bstr *b)
+{
+ return b->next;
+}
+
+unsigned long bstrtoul(const struct bstr *b);
+
+struct ematch {
+ struct bstr *args;
+ int index;
+ int inverted;
+ int relation;
+ int child_ref;
+ struct ematch *child;
+ struct ematch *next;
+};
+
+static inline struct ematch *new_ematch(struct bstr *args, int inverted)
+{
+ struct ematch *e = calloc(1, sizeof(*e));
+
+ if (e == NULL)
+ return NULL;
+
+ e->args = args;
+ e->inverted = inverted;
+
+ return e;
+}
+
+void print_ematch_tree(const struct ematch *tree);
+
+struct ematch_util {
+ char kind[EMATCHKINDSIZ];
+ int kind_num;
+ int (*parse_eopt)(struct nlmsghdr *, struct tcf_ematch_hdr *,
+ struct bstr *);
+ int (*parse_eopt_argv)(struct nlmsghdr *, struct tcf_ematch_hdr *,
+ int, char **);
+ int (*print_eopt)(FILE *, struct tcf_ematch_hdr *, void *, int);
+ void (*print_usage)(FILE *);
+ struct ematch_util *next;
+};
+
+static inline int parse_layer(const struct bstr *b)
+{
+ if (*((char *) b->data) == 'l')
+ return TCF_LAYER_LINK;
+ else if (*((char *) b->data) == 'n')
+ return TCF_LAYER_NETWORK;
+ else if (*((char *) b->data) == 't')
+ return TCF_LAYER_TRANSPORT;
+ else
+ return INT_MAX;
+}
+
+__attribute__((format(printf, 5, 6)))
+int em_parse_error(int err, struct bstr *args, struct bstr *carg,
+ struct ematch_util *, char *fmt, ...);
+int print_ematch(FILE *, const struct rtattr *);
+int parse_ematch(int *, char ***, int, struct nlmsghdr *);
+
+#endif
diff --git a/tc/m_estimator.c b/tc/m_estimator.c
new file mode 100644
index 0000000..ef62e1b
--- /dev/null
+++ b/tc/m_estimator.c
@@ -0,0 +1,64 @@
+/*
+ * m_estimator.c Parse/print estimator module options.
+ *
+ * This program is free software; you can u32istribute 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 "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+static void est_help(void);
+
+static void est_help(void)
+{
+ fprintf(stderr,
+ "Usage: ... estimator INTERVAL TIME-CONST\n"
+ " INTERVAL is interval between measurements\n"
+ " TIME-CONST is averaging time constant\n"
+ "Example: ... est 1sec 8sec\n");
+}
+
+int parse_estimator(int *p_argc, char ***p_argv, struct tc_estimator *est)
+{
+ int argc = *p_argc;
+ char **argv = *p_argv;
+ unsigned int A, time_const;
+
+ NEXT_ARG();
+ if (est->ewma_log)
+ duparg("estimator", *argv);
+ if (matches(*argv, "help") == 0)
+ est_help();
+ if (get_time(&A, *argv))
+ invarg("estimator", "invalid estimator interval");
+ NEXT_ARG();
+ if (matches(*argv, "help") == 0)
+ est_help();
+ if (get_time(&time_const, *argv))
+ invarg("estimator", "invalid estimator time constant");
+ if (tc_setup_estimator(A, time_const, est) < 0) {
+ fprintf(stderr, "Error: estimator parameters are out of range.\n");
+ return -1;
+ }
+ if (show_raw)
+ fprintf(stderr, "[estimator i=%u e=%u]\n", est->interval, est->ewma_log);
+ *p_argc = argc;
+ *p_argv = argv;
+ return 0;
+}
diff --git a/tc/m_gact.c b/tc/m_gact.c
new file mode 100644
index 0000000..b06e8ee
--- /dev/null
+++ b/tc/m_gact.c
@@ -0,0 +1,220 @@
+/*
+ * m_gact.c generic actions module
+ *
+ * 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)
+ *
+ */
+
+#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 "tc_util.h"
+#include <linux/tc_act/tc_gact.h>
+
+/* define to turn on probablity stuff */
+
+#ifdef CONFIG_GACT_PROB
+static const char *prob_n2a(int p)
+{
+ if (p == PGACT_NONE)
+ return "none";
+ if (p == PGACT_NETRAND)
+ return "netrand";
+ if (p == PGACT_DETERM)
+ return "determ";
+ return "none";
+}
+#endif
+
+static void
+explain(void)
+{
+#ifdef CONFIG_GACT_PROB
+ fprintf(stderr, "Usage: ... gact <ACTION> [RAND] [INDEX]\n");
+ fprintf(stderr,
+ "Where: \tACTION := reclassify | drop | continue | pass | pipe |\n"
+ " \t goto chain <CHAIN_INDEX> | jump <JUMP_COUNT>\n"
+ "\tRAND := random <RANDTYPE> <ACTION> <VAL>\n"
+ "\tRANDTYPE := netrand | determ\n"
+ "\tVAL : = value not exceeding 10000\n"
+ "\tJUMP_COUNT := Absolute jump from start of action list\n"
+ "\tINDEX := index value used\n"
+ "\n");
+#else
+ fprintf(stderr, "Usage: ... gact <ACTION> [INDEX]\n"
+ "Where: \tACTION := reclassify | drop | continue | pass | pipe |\n"
+ " \t goto chain <CHAIN_INDEX> | jump <JUMP_COUNT>\n"
+ "\tINDEX := index value used\n"
+ "\tJUMP_COUNT := Absolute jump from start of action list\n"
+ "\n");
+#endif
+}
+
+
+static void
+usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static int
+parse_gact(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ struct tc_gact p = { 0 };
+#ifdef CONFIG_GACT_PROB
+ int rd = 0;
+ struct tc_gact_p pp;
+#endif
+ struct rtattr *tail;
+
+ if (argc < 0)
+ return -1;
+
+ if (!matches(*argv, "gact"))
+ NEXT_ARG();
+ /* we're binding existing gact action to filter by index. */
+ if (!matches(*argv, "index"))
+ goto skip_args;
+ if (parse_action_control(&argc, &argv, &p.action, false))
+ usage(); /* does not return */
+
+#ifdef CONFIG_GACT_PROB
+ if (argc > 0) {
+ if (matches(*argv, "random") == 0) {
+ rd = 1;
+ NEXT_ARG();
+ if (matches(*argv, "netrand") == 0) {
+ NEXT_ARG();
+ pp.ptype = PGACT_NETRAND;
+ } else if (matches(*argv, "determ") == 0) {
+ NEXT_ARG();
+ pp.ptype = PGACT_DETERM;
+ } else {
+ fprintf(stderr, "Illegal \"random type\"\n");
+ return -1;
+ }
+
+ if (parse_action_control(&argc, &argv,
+ &pp.paction, false) == -1)
+ usage();
+ if (get_u16(&pp.pval, *argv, 10)) {
+ fprintf(stderr,
+ "Illegal probability val 0x%x\n",
+ pp.pval);
+ return -1;
+ }
+ if (pp.pval > 10000) {
+ fprintf(stderr,
+ "Illegal probability val 0x%x\n",
+ pp.pval);
+ return -1;
+ }
+ argc--;
+ argv++;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ }
+ }
+#endif
+
+ if (argc > 0) {
+ if (matches(*argv, "index") == 0) {
+skip_args:
+ NEXT_ARG();
+ if (get_u32(&p.index, *argv, 10)) {
+ fprintf(stderr, "Illegal \"index\"\n");
+ return -1;
+ }
+ argc--;
+ argv++;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ }
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_GACT_PARMS, &p, sizeof(p));
+#ifdef CONFIG_GACT_PROB
+ if (rd)
+ addattr_l(n, MAX_MSG, TCA_GACT_PROB, &pp, sizeof(pp));
+#endif
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int
+print_gact(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+#ifdef CONFIG_GACT_PROB
+ struct tc_gact_p *pp = NULL;
+ struct tc_gact_p pp_dummy;
+#endif
+ struct tc_gact *p = NULL;
+ struct rtattr *tb[TCA_GACT_MAX + 1];
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_GACT_MAX, arg);
+
+ if (tb[TCA_GACT_PARMS] == NULL) {
+ fprintf(stderr, "Missing gact parameters\n");
+ return -1;
+ }
+ p = RTA_DATA(tb[TCA_GACT_PARMS]);
+
+ print_string(PRINT_ANY, "kind", "%s ", "gact");
+ print_action_control(f, "action ", p->action, "");
+#ifdef CONFIG_GACT_PROB
+ if (tb[TCA_GACT_PROB] != NULL) {
+ pp = RTA_DATA(tb[TCA_GACT_PROB]);
+ } else {
+ /* need to keep consistent output */
+ memset(&pp_dummy, 0, sizeof(pp_dummy));
+ pp = &pp_dummy;
+ }
+ open_json_object("prob");
+ print_string(PRINT_ANY, "random_type", "\n\t random type %s",
+ prob_n2a(pp->ptype));
+ print_action_control(f, " ", pp->paction, " ");
+ print_int(PRINT_ANY, "val", "val %d", pp->pval);
+ close_json_object();
+#endif
+ print_uint(PRINT_ANY, "index", "\n\t index %u", p->index);
+ print_int(PRINT_ANY, "ref", " ref %d", p->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt);
+ if (show_stats) {
+ if (tb[TCA_GACT_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_GACT_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ print_string(PRINT_FP, NULL, "%s", "\n");
+ return 0;
+}
+
+struct action_util gact_action_util = {
+ .id = "gact",
+ .parse_aopt = parse_gact,
+ .print_aopt = print_gact,
+};
diff --git a/tc/m_ife.c b/tc/m_ife.c
new file mode 100644
index 0000000..7c612c0
--- /dev/null
+++ b/tc/m_ife.c
@@ -0,0 +1,336 @@
+/*
+ * m_ife.c IFE actions module
+ *
+ * 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 (jhs@mojatatu.com)
+ *
+ */
+
+#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 <linux/netdevice.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_ife.h>
+
+static void ife_explain(void)
+{
+ fprintf(stderr,
+ "Usage:... ife {decode|encode} [{ALLOW|USE} ATTR] [dst DMAC] [src SMAC] [type TYPE] [CONTROL] [index INDEX]\n"
+ "\tALLOW := Encode direction. Allows encoding specified metadata\n"
+ "\t\t e.g \"allow mark\"\n"
+ "\tUSE := Encode direction. Enforce Static encoding of specified metadata\n"
+ "\t\t e.g \"use mark 0x12\"\n"
+ "\tATTR := mark (32-bit), prio (32-bit), tcindex (16-bit)\n"
+ "\tDMAC := 6 byte Destination MAC address to encode\n"
+ "\tSMAC := optional 6 byte Source MAC address to encode\n"
+ "\tTYPE := optional 16 bit ethertype to encode\n"
+ "\tCONTROL := reclassify|pipe|drop|continue|ok\n"
+ "\tINDEX := optional IFE table index value used\n"
+ "encode is used for sending IFE packets\n"
+ "decode is used for receiving IFE packets\n");
+}
+
+static void ife_usage(void)
+{
+ ife_explain();
+ exit(-1);
+}
+
+static int parse_ife(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int ok = 0;
+ struct tc_ife p = { 0 };
+ struct rtattr *tail;
+ struct rtattr *tail2;
+ char dbuf[ETH_ALEN];
+ char sbuf[ETH_ALEN];
+ __u16 ife_type = 0;
+ int user_type = 0;
+ __u32 ife_prio = 0;
+ __u32 ife_prio_v = 0;
+ __u32 ife_mark = 0;
+ __u32 ife_mark_v = 0;
+ __u16 ife_tcindex = 0;
+ __u16 ife_tcindex_v = 0;
+ char *daddr = NULL;
+ char *saddr = NULL;
+
+ if (argc <= 0)
+ return -1;
+
+ while (argc > 0) {
+ if (matches(*argv, "ife") == 0) {
+ NEXT_ARG();
+ continue;
+ } else if (matches(*argv, "decode") == 0) {
+ p.flags = IFE_DECODE; /* readability aid */
+ ok++;
+ } else if (matches(*argv, "encode") == 0) {
+ p.flags = IFE_ENCODE;
+ ok++;
+ } else if (matches(*argv, "allow") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "mark") == 0) {
+ ife_mark = IFE_META_SKBMARK;
+ } else if (matches(*argv, "prio") == 0) {
+ ife_prio = IFE_META_PRIO;
+ } else if (matches(*argv, "tcindex") == 0) {
+ ife_tcindex = IFE_META_TCINDEX;
+ } else {
+ invarg("Illegal meta define", *argv);
+ }
+ } else if (matches(*argv, "use") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "mark") == 0) {
+ NEXT_ARG();
+ if (get_u32(&ife_mark_v, *argv, 0))
+ invarg("ife mark val is invalid",
+ *argv);
+ } else if (matches(*argv, "prio") == 0) {
+ NEXT_ARG();
+ if (get_u32(&ife_prio_v, *argv, 0))
+ invarg("ife prio val is invalid",
+ *argv);
+ } else if (matches(*argv, "tcindex") == 0) {
+ NEXT_ARG();
+ if (get_u16(&ife_tcindex_v, *argv, 0))
+ invarg("ife tcindex val is invalid",
+ *argv);
+ } else {
+ invarg("Illegal meta use type", *argv);
+ }
+ } else if (matches(*argv, "type") == 0) {
+ NEXT_ARG();
+ if (get_u16(&ife_type, *argv, 0))
+ invarg("ife type is invalid", *argv);
+ fprintf(stderr, "IFE type 0x%04X\n", ife_type);
+ user_type = 1;
+ } else if (matches(*argv, "dst") == 0) {
+ NEXT_ARG();
+ daddr = *argv;
+ if (sscanf(daddr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ dbuf, dbuf + 1, dbuf + 2,
+ dbuf + 3, dbuf + 4, dbuf + 5) != 6) {
+ invarg("Invalid mac address", *argv);
+ }
+ fprintf(stderr, "dst MAC address <%s>\n", daddr);
+
+ } else if (matches(*argv, "src") == 0) {
+ NEXT_ARG();
+ saddr = *argv;
+ if (sscanf(saddr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ sbuf, sbuf + 1, sbuf + 2,
+ sbuf + 3, sbuf + 4, sbuf + 5) != 6) {
+ invarg("Invalid mac address", *argv);
+ }
+ fprintf(stderr, "src MAC address <%s>\n", saddr);
+ } else if (matches(*argv, "help") == 0) {
+ ife_usage();
+ } else {
+ break;
+ }
+
+ argc--;
+ argv++;
+ }
+
+ parse_action_control_dflt(&argc, &argv, &p.action, false, TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&p.index, *argv, 0)) {
+ fprintf(stderr, "ife: Illegal \"index\"\n");
+ return -1;
+ }
+ ok++;
+ argc--;
+ argv++;
+ }
+ }
+
+ if (!ok) {
+ fprintf(stderr, "IFE requires decode/encode specified\n");
+ ife_usage();
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_IFE_PARMS, &p, sizeof(p));
+
+ if (!(p.flags & IFE_ENCODE))
+ goto skip_encode;
+
+ if (daddr)
+ addattr_l(n, MAX_MSG, TCA_IFE_DMAC, dbuf, ETH_ALEN);
+ if (user_type)
+ addattr_l(n, MAX_MSG, TCA_IFE_TYPE, &ife_type, 2);
+ else
+ fprintf(stderr, "IFE type 0x%04X\n", ETH_P_IFE);
+ if (saddr)
+ addattr_l(n, MAX_MSG, TCA_IFE_SMAC, sbuf, ETH_ALEN);
+
+ tail2 = addattr_nest(n, MAX_MSG, TCA_IFE_METALST);
+ if (ife_mark || ife_mark_v) {
+ if (ife_mark_v)
+ addattr_l(n, MAX_MSG, IFE_META_SKBMARK, &ife_mark_v, 4);
+ else
+ addattr_l(n, MAX_MSG, IFE_META_SKBMARK, NULL, 0);
+ }
+ if (ife_prio || ife_prio_v) {
+ if (ife_prio_v)
+ addattr_l(n, MAX_MSG, IFE_META_PRIO, &ife_prio_v, 4);
+ else
+ addattr_l(n, MAX_MSG, IFE_META_PRIO, NULL, 0);
+ }
+ if (ife_tcindex || ife_tcindex_v) {
+ if (ife_tcindex_v)
+ addattr_l(n, MAX_MSG, IFE_META_TCINDEX, &ife_tcindex_v,
+ 2);
+ else
+ addattr_l(n, MAX_MSG, IFE_META_TCINDEX, NULL, 0);
+ }
+
+ addattr_nest_end(n, tail2);
+
+skip_encode:
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int print_ife(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct tc_ife *p;
+ struct rtattr *tb[TCA_IFE_MAX + 1];
+ __u16 ife_type = 0;
+ __u32 mmark = 0;
+ __u16 mtcindex = 0;
+ __u32 mprio = 0;
+ int has_optional = 0;
+ SPRINT_BUF(b2);
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_IFE_MAX, arg);
+
+ if (tb[TCA_IFE_PARMS] == NULL) {
+ fprintf(stderr, "Missing ife parameters\n");
+ return -1;
+ }
+ p = RTA_DATA(tb[TCA_IFE_PARMS]);
+
+ print_string(PRINT_ANY, "kind", "%s ", "ife");
+ print_string(PRINT_ANY, "mode", "%s ",
+ p->flags & IFE_ENCODE ? "encode" : "decode");
+ print_action_control(f, "action ", p->action, " ");
+
+ if (tb[TCA_IFE_TYPE]) {
+ ife_type = rta_getattr_u16(tb[TCA_IFE_TYPE]);
+ has_optional = 1;
+ print_0xhex(PRINT_ANY, "type", "type %#llX ", ife_type);
+ }
+
+ if (has_optional)
+ print_string(PRINT_FP, NULL, "%s\t", _SL_);
+
+ if (tb[TCA_IFE_METALST]) {
+ struct rtattr *metalist[IFE_META_MAX + 1];
+ int len = 0;
+
+ parse_rtattr_nested(metalist, IFE_META_MAX,
+ tb[TCA_IFE_METALST]);
+
+ if (metalist[IFE_META_SKBMARK]) {
+ len = RTA_PAYLOAD(metalist[IFE_META_SKBMARK]);
+ if (len) {
+ mmark = rta_getattr_u32(metalist[IFE_META_SKBMARK]);
+ print_uint(PRINT_ANY, "mark", "use mark %u ",
+ mmark);
+ } else
+ print_string(PRINT_ANY, "mark", "%s mark ",
+ "allow");
+ }
+
+ if (metalist[IFE_META_TCINDEX]) {
+ len = RTA_PAYLOAD(metalist[IFE_META_TCINDEX]);
+ if (len) {
+ mtcindex =
+ rta_getattr_u16(metalist[IFE_META_TCINDEX]);
+ print_uint(PRINT_ANY, "tcindex",
+ "use tcindex %u ", mtcindex);
+ } else
+ print_string(PRINT_ANY, "tcindex",
+ "%s tcindex ", "allow");
+ }
+
+ if (metalist[IFE_META_PRIO]) {
+ len = RTA_PAYLOAD(metalist[IFE_META_PRIO]);
+ if (len) {
+ mprio = rta_getattr_u32(metalist[IFE_META_PRIO]);
+ print_uint(PRINT_ANY, "prio", "use prio %u ",
+ mprio);
+ } else
+ print_string(PRINT_ANY, "prio", "%s prio ",
+ "allow");
+ }
+
+ }
+
+ if (tb[TCA_IFE_DMAC]) {
+ has_optional = 1;
+ print_string(PRINT_ANY, "dst", "dst %s ",
+ ll_addr_n2a(RTA_DATA(tb[TCA_IFE_DMAC]),
+ RTA_PAYLOAD(tb[TCA_IFE_DMAC]), 0, b2,
+ sizeof(b2)));
+ }
+
+ if (tb[TCA_IFE_SMAC]) {
+ has_optional = 1;
+ print_string(PRINT_ANY, "src", "src %s ",
+ ll_addr_n2a(RTA_DATA(tb[TCA_IFE_SMAC]),
+ RTA_PAYLOAD(tb[TCA_IFE_SMAC]), 0, b2,
+ sizeof(b2)));
+ }
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "index", "\t index %u", p->index);
+ print_int(PRINT_ANY, "ref", " ref %d", p->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_IFE_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_IFE_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+
+ return 0;
+}
+
+struct action_util ife_action_util = {
+ .id = "ife",
+ .parse_aopt = parse_ife,
+ .print_aopt = print_ife,
+};
diff --git a/tc/m_ipt.c b/tc/m_ipt.c
new file mode 100644
index 0000000..cc95eab
--- /dev/null
+++ b/tc/m_ipt.c
@@ -0,0 +1,516 @@
+/*
+ * m_ipt.c iptables based targets
+ * utilities mostly ripped from iptables <duh, its the linux way>
+ *
+ * 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)
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <iptables.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_ipt.h>
+#include <stdio.h>
+#include <dlfcn.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+static const char *pname = "tc-ipt";
+static const char *tname = "mangle";
+static const char *pversion = "0.1";
+
+static const char *ipthooks[] = {
+ "NF_IP_PRE_ROUTING",
+ "NF_IP_LOCAL_IN",
+ "NF_IP_FORWARD",
+ "NF_IP_LOCAL_OUT",
+ "NF_IP_POST_ROUTING",
+};
+
+static struct option original_opts[] = {
+ {"jump", 1, 0, 'j'},
+ {0, 0, 0, 0}
+};
+
+static struct xtables_target *t_list;
+static struct option *opts = original_opts;
+static unsigned int global_option_offset;
+#define OPTION_OFFSET 256
+
+char *lib_dir;
+
+void
+xtables_register_target(struct xtables_target *me)
+{
+ me->next = t_list;
+ t_list = me;
+
+}
+
+static void exit_tryhelp(int status)
+{
+ fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
+ pname, pname);
+ exit(status);
+}
+
+static void exit_error(enum xtables_exittype status, char *msg, ...)
+{
+ va_list args;
+
+ va_start(args, msg);
+ fprintf(stderr, "%s v%s: ", pname, pversion);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+ if (status == PARAMETER_PROBLEM)
+ exit_tryhelp(status);
+ if (status == VERSION_PROBLEM)
+ fprintf(stderr,
+ "Perhaps iptables or your kernel needs to be upgraded.\n");
+ exit(status);
+}
+
+/* stolen from iptables 1.2.11
+They should really have them as a library so i can link to them
+Email them next time i remember
+*/
+
+static void free_opts(struct option *local_opts)
+{
+ if (local_opts != original_opts) {
+ free(local_opts);
+ opts = original_opts;
+ global_option_offset = 0;
+ }
+}
+
+static struct option *
+merge_options(struct option *oldopts, const struct option *newopts,
+ unsigned int *option_offset)
+{
+ struct option *merge;
+ unsigned int num_old, num_new, i;
+
+ for (num_old = 0; oldopts[num_old].name; num_old++);
+ for (num_new = 0; newopts[num_new].name; num_new++);
+
+ *option_offset = global_option_offset + OPTION_OFFSET;
+
+ merge = malloc(sizeof(struct option) * (num_new + num_old + 1));
+ memcpy(merge, oldopts, num_old * sizeof(struct option));
+ for (i = 0; i < num_new; i++) {
+ merge[num_old + i] = newopts[i];
+ merge[num_old + i].val += *option_offset;
+ }
+ memset(merge + num_old + num_new, 0, sizeof(struct option));
+
+ return merge;
+}
+
+static void *
+fw_calloc(size_t count, size_t size)
+{
+ void *p;
+
+ if ((p = (void *) calloc(count, size)) == NULL) {
+ perror("iptables: calloc failed");
+ exit(1);
+ }
+ return p;
+}
+
+static struct xtables_target *
+find_t(char *name)
+{
+ struct xtables_target *m;
+
+ for (m = t_list; m; m = m->next) {
+ if (strcmp(m->name, name) == 0)
+ return m;
+ }
+
+ return NULL;
+}
+
+static struct xtables_target *
+get_target_name(const char *name)
+{
+ void *handle;
+ char *error;
+ char *new_name, *lname;
+ struct xtables_target *m;
+ char path[strlen(lib_dir) + sizeof("/libipt_.so") + strlen(name)];
+
+#ifdef NO_SHARED_LIBS
+ return NULL;
+#endif
+
+ new_name = calloc(1, strlen(name) + 1);
+ lname = calloc(1, strlen(name) + 1);
+ if (!new_name)
+ exit_error(PARAMETER_PROBLEM, "get_target_name");
+ if (!lname)
+ exit_error(PARAMETER_PROBLEM, "get_target_name");
+
+ strcpy(new_name, name);
+ strcpy(lname, name);
+
+ if (isupper(lname[0])) {
+ int i;
+
+ for (i = 0; i < strlen(name); i++) {
+ lname[i] = tolower(lname[i]);
+ }
+ }
+
+ if (islower(new_name[0])) {
+ int i;
+
+ for (i = 0; i < strlen(new_name); i++) {
+ new_name[i] = toupper(new_name[i]);
+ }
+ }
+
+ /* try libxt_xx first */
+ sprintf(path, "%s/libxt_%s.so", lib_dir, new_name);
+ handle = dlopen(path, RTLD_LAZY);
+ if (!handle) {
+ /* try libipt_xx next */
+ sprintf(path, "%s/libipt_%s.so", lib_dir, new_name);
+ handle = dlopen(path, RTLD_LAZY);
+
+ if (!handle) {
+ sprintf(path, "%s/libxt_%s.so", lib_dir, lname);
+ handle = dlopen(path, RTLD_LAZY);
+ }
+
+ if (!handle) {
+ sprintf(path, "%s/libipt_%s.so", lib_dir, lname);
+ handle = dlopen(path, RTLD_LAZY);
+ }
+ /* ok, lets give up .. */
+ if (!handle) {
+ fputs(dlerror(), stderr);
+ printf("\n");
+ free(new_name);
+ free(lname);
+ return NULL;
+ }
+ }
+
+ m = dlsym(handle, new_name);
+ if ((error = dlerror()) != NULL) {
+ m = (struct xtables_target *) dlsym(handle, lname);
+ if ((error = dlerror()) != NULL) {
+ m = find_t(new_name);
+ if (m == NULL) {
+ m = find_t(lname);
+ if (m == NULL) {
+ fputs(error, stderr);
+ fprintf(stderr, "\n");
+ dlclose(handle);
+ free(new_name);
+ free(lname);
+ return NULL;
+ }
+ }
+ }
+ }
+
+ free(new_name);
+ free(lname);
+ return m;
+}
+
+static void set_revision(char *name, u_int8_t revision)
+{
+ /* Old kernel sources don't have ".revision" field,
+ * but we stole a byte from name. */
+ name[IPT_FUNCTION_MAXNAMELEN - 2] = '\0';
+ name[IPT_FUNCTION_MAXNAMELEN - 1] = revision;
+}
+
+/*
+ * we may need to check for version mismatch
+*/
+static int build_st(struct xtables_target *target, struct ipt_entry_target *t)
+{
+ if (target) {
+ size_t size;
+
+ size =
+ XT_ALIGN(sizeof(struct ipt_entry_target)) + target->size;
+
+ if (t == NULL) {
+ target->t = fw_calloc(1, size);
+ target->t->u.target_size = size;
+
+ if (target->init != NULL)
+ target->init(target->t);
+ set_revision(target->t->u.user.name, target->revision);
+ } else {
+ target->t = t;
+ }
+ strcpy(target->t->u.user.name, target->name);
+ return 0;
+ }
+
+ return -1;
+}
+
+static int parse_ipt(struct action_util *a, int *argc_p,
+ char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+ struct xtables_target *m = NULL;
+ struct ipt_entry fw;
+ struct rtattr *tail;
+ int c;
+ int rargc = *argc_p;
+ char **argv = *argv_p;
+ int argc = 0, iargc = 0;
+ char k[FILTER_NAMESZ];
+ int size = 0;
+ int iok = 0, ok = 0;
+ __u32 hook = 0, index = 0;
+
+ lib_dir = getenv("IPTABLES_LIB_DIR");
+ if (!lib_dir)
+ lib_dir = IPT_LIB_DIR;
+
+ {
+ int i;
+
+ for (i = 0; i < rargc; i++) {
+ if (!argv[i] || strcmp(argv[i], "action") == 0)
+ break;
+ }
+ iargc = argc = i;
+ }
+
+ if (argc <= 2) {
+ fprintf(stderr, "bad arguments to ipt %d vs %d\n", argc, rargc);
+ return -1;
+ }
+
+ while (1) {
+ c = getopt_long(argc, argv, "j:", opts, NULL);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'j':
+ m = get_target_name(optarg);
+ if (m != NULL) {
+
+ if (build_st(m, NULL) < 0) {
+ printf(" %s error\n", m->name);
+ return -1;
+ }
+ opts =
+ merge_options(opts, m->extra_opts,
+ &m->option_offset);
+ } else {
+ fprintf(stderr, " failed to find target %s\n\n", optarg);
+ return -1;
+ }
+ ok++;
+ break;
+
+ default:
+ memset(&fw, 0, sizeof(fw));
+ if (m) {
+ m->parse(c - m->option_offset, argv, 0,
+ &m->tflags, NULL, &m->t);
+ } else {
+ fprintf(stderr, " failed to find target %s\n\n", optarg);
+ return -1;
+
+ }
+ ok++;
+ break;
+
+ }
+ }
+
+ if (iargc > optind) {
+ if (matches(argv[optind], "index") == 0) {
+ if (get_u32(&index, argv[optind + 1], 10)) {
+ fprintf(stderr, "Illegal \"index\"\n");
+ free_opts(opts);
+ return -1;
+ }
+ iok++;
+
+ optind += 2;
+ }
+ }
+
+ if (!ok && !iok) {
+ fprintf(stderr, " ipt Parser BAD!! (%s)\n", *argv);
+ return -1;
+ }
+
+ /* check that we passed the correct parameters to the target */
+ if (m)
+ m->final_check(m->tflags);
+
+ {
+ struct tcmsg *t = NLMSG_DATA(n);
+
+ if (t->tcm_parent != TC_H_ROOT
+ && t->tcm_parent == TC_H_MAJ(TC_H_INGRESS)) {
+ hook = NF_IP_PRE_ROUTING;
+ } else {
+ hook = NF_IP_POST_ROUTING;
+ }
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ fprintf(stdout, "tablename: %s hook: %s\n ", tname, ipthooks[hook]);
+ fprintf(stdout, "\ttarget: ");
+
+ if (m)
+ m->print(NULL, m->t, 0);
+ fprintf(stdout, " index %d\n", index);
+
+ if (strlen(tname) > 16) {
+ size = 16;
+ k[15] = 0;
+ } else {
+ size = 1 + strlen(tname);
+ }
+ strncpy(k, tname, size);
+
+ addattr_l(n, MAX_MSG, TCA_IPT_TABLE, k, size);
+ addattr_l(n, MAX_MSG, TCA_IPT_HOOK, &hook, 4);
+ addattr_l(n, MAX_MSG, TCA_IPT_INDEX, &index, 4);
+ if (m)
+ addattr_l(n, MAX_MSG, TCA_IPT_TARG, m->t, m->t->u.target_size);
+ addattr_nest_end(n, tail);
+
+ argc -= optind;
+ argv += optind;
+ *argc_p = rargc - iargc;
+ *argv_p = argv;
+
+ optind = 0;
+ free_opts(opts);
+ /* Clear flags if target will be used again */
+ m->tflags = 0;
+ m->used = 0;
+ /* Free allocated memory */
+ if (m->t)
+ free(m->t);
+
+
+ return 0;
+
+}
+
+static int
+print_ipt(struct action_util *au, FILE * f, struct rtattr *arg)
+{
+ struct rtattr *tb[TCA_IPT_MAX + 1];
+ struct ipt_entry_target *t = NULL;
+ struct xtables_target *m;
+ __u32 hook;
+
+ if (arg == NULL)
+ return -1;
+
+ lib_dir = getenv("IPTABLES_LIB_DIR");
+ if (!lib_dir)
+ lib_dir = IPT_LIB_DIR;
+
+ parse_rtattr_nested(tb, TCA_IPT_MAX, arg);
+
+ if (tb[TCA_IPT_TABLE] == NULL) {
+ fprintf(stderr, "Missing ipt table name, assuming mangle\n");
+ } else {
+ fprintf(f, "tablename: %s ",
+ rta_getattr_str(tb[TCA_IPT_TABLE]));
+ }
+
+ if (tb[TCA_IPT_HOOK] == NULL) {
+ fprintf(stderr, "Missing ipt hook name\n ");
+ return -1;
+ }
+
+ hook = rta_getattr_u32(tb[TCA_IPT_HOOK]);
+ fprintf(f, " hook: %s\n", ipthooks[hook]);
+
+ if (tb[TCA_IPT_TARG] == NULL) {
+ fprintf(stderr, "Missing ipt target parameters\n");
+ return -1;
+ }
+
+
+ t = RTA_DATA(tb[TCA_IPT_TARG]);
+ m = get_target_name(t->u.user.name);
+ if (m != NULL) {
+ if (build_st(m, t) < 0) {
+ fprintf(stderr, " %s error\n", m->name);
+ return -1;
+ }
+
+ opts =
+ merge_options(opts, m->extra_opts,
+ &m->option_offset);
+ } else {
+ fprintf(stderr, " failed to find target %s\n\n",
+ t->u.user.name);
+ return -1;
+ }
+
+ fprintf(f, "\ttarget ");
+ m->print(NULL, m->t, 0);
+ if (tb[TCA_IPT_INDEX] == NULL) {
+ fprintf(stderr, "Missing ipt target index\n");
+ } else {
+ __u32 index;
+
+ index = rta_getattr_u32(tb[TCA_IPT_INDEX]);
+ fprintf(f, "\n\tindex %u", index);
+ }
+
+ if (tb[TCA_IPT_CNT]) {
+ struct tc_cnt *c = RTA_DATA(tb[TCA_IPT_CNT]);
+
+ fprintf(f, " ref %d bind %d", c->refcnt, c->bindcnt);
+ }
+ if (show_stats) {
+ if (tb[TCA_IPT_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_IPT_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ fprintf(f, "\n");
+
+ free_opts(opts);
+
+ return 0;
+}
+
+struct action_util ipt_action_util = {
+ .id = "ipt",
+ .parse_aopt = parse_ipt,
+ .print_aopt = print_ipt,
+};
diff --git a/tc/m_mirred.c b/tc/m_mirred.c
new file mode 100644
index 0000000..1320952
--- /dev/null
+++ b/tc/m_mirred.c
@@ -0,0 +1,329 @@
+/*
+ * m_egress.c ingress/egress packet mirror/redir actions module
+ *
+ * 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)
+ *
+ * TODO: Add Ingress support
+ *
+ */
+
+#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 "tc_util.h"
+#include "tc_common.h"
+#include <linux/tc_act/tc_mirred.h>
+
+static void
+explain(void)
+{
+ fprintf(stderr,
+ "Usage: mirred <DIRECTION> <ACTION> [index INDEX] <dev DEVICENAME>\n"
+ "where:\n"
+ "\tDIRECTION := <ingress | egress>\n"
+ "\tACTION := <mirror | redirect>\n"
+ "\tINDEX is the specific policy instance id\n"
+ "\tDEVICENAME is the devicename\n");
+}
+
+static void
+usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static const char *mirred_n2a(int action)
+{
+ switch (action) {
+ case TCA_EGRESS_REDIR:
+ return "Egress Redirect";
+ case TCA_INGRESS_REDIR:
+ return "Ingress Redirect";
+ case TCA_EGRESS_MIRROR:
+ return "Egress Mirror";
+ case TCA_INGRESS_MIRROR:
+ return "Ingress Mirror";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *mirred_direction(int action)
+{
+ switch (action) {
+ case TCA_EGRESS_REDIR:
+ case TCA_EGRESS_MIRROR:
+ return "egress";
+ case TCA_INGRESS_REDIR:
+ case TCA_INGRESS_MIRROR:
+ return "ingress";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *mirred_action(int action)
+{
+ switch (action) {
+ case TCA_EGRESS_REDIR:
+ case TCA_INGRESS_REDIR:
+ return "redirect";
+ case TCA_EGRESS_MIRROR:
+ case TCA_INGRESS_MIRROR:
+ return "mirror";
+ default:
+ return "unknown";
+ }
+}
+
+static int
+parse_direction(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int ok = 0, iok = 0, mirror = 0, redir = 0, ingress = 0, egress = 0;
+ struct tc_mirred p = {};
+ struct rtattr *tail;
+ char d[IFNAMSIZ] = {};
+
+ while (argc > 0) {
+
+ if (matches(*argv, "action") == 0) {
+ NEXT_ARG();
+ break;
+ } else if (!egress && matches(*argv, "egress") == 0) {
+ egress = 1;
+ if (ingress) {
+ fprintf(stderr,
+ "Can't have both egress and ingress\n");
+ return -1;
+ }
+ NEXT_ARG();
+ ok++;
+ continue;
+ } else if (!ingress && matches(*argv, "ingress") == 0) {
+ ingress = 1;
+ if (egress) {
+ fprintf(stderr,
+ "Can't have both ingress and egress\n");
+ return -1;
+ }
+ NEXT_ARG();
+ ok++;
+ continue;
+ } else {
+
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&p.index, *argv, 10)) {
+ fprintf(stderr, "Illegal \"index\"\n");
+ return -1;
+ }
+ iok++;
+ if (!ok) {
+ argc--;
+ argv++;
+ break;
+ }
+ } else if (!ok) {
+ fprintf(stderr,
+ "was expecting egress or ingress (%s)\n",
+ *argv);
+ break;
+
+ } else if (!mirror && matches(*argv, "mirror") == 0) {
+ mirror = 1;
+ if (redir) {
+ fprintf(stderr,
+ "Can't have both mirror and redir\n");
+ return -1;
+ }
+ p.eaction = egress ? TCA_EGRESS_MIRROR :
+ TCA_INGRESS_MIRROR;
+ p.action = TC_ACT_PIPE;
+ ok++;
+ } else if (!redir && matches(*argv, "redirect") == 0) {
+ redir = 1;
+ if (mirror) {
+ fprintf(stderr,
+ "Can't have both mirror and redir\n");
+ return -1;
+ }
+ p.eaction = egress ? TCA_EGRESS_REDIR :
+ TCA_INGRESS_REDIR;
+ p.action = TC_ACT_STOLEN;
+ ok++;
+ } else if ((redir || mirror) &&
+ matches(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (strlen(d))
+ duparg("dev", *argv);
+
+ strncpy(d, *argv, sizeof(d)-1);
+ argc--;
+ argv++;
+
+ break;
+
+ }
+ }
+
+ NEXT_ARG();
+ }
+
+ if (!ok && !iok)
+ return -1;
+
+ if (d[0]) {
+ int idx;
+
+ ll_init_map(&rth);
+
+ idx = ll_name_to_index(d);
+ if (!idx)
+ return nodev(d);
+
+ p.ifindex = idx;
+ }
+
+
+ if (p.eaction == TCA_EGRESS_MIRROR || p.eaction == TCA_INGRESS_MIRROR)
+ parse_action_control_dflt(&argc, &argv, &p.action, false,
+ TC_ACT_PIPE);
+
+ if (argc) {
+ if (iok && matches(*argv, "index") == 0) {
+ fprintf(stderr, "mirred: Illegal double index\n");
+ return -1;
+ }
+
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&p.index, *argv, 10)) {
+ fprintf(stderr,
+ "mirred: Illegal \"index\"\n");
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_MIRRED_PARMS, &p, sizeof(p));
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+
+static int
+parse_mirred(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 0) {
+ fprintf(stderr, "mirred bad argument count %d\n", argc);
+ return -1;
+ }
+
+ if (matches(*argv, "mirred") == 0) {
+ NEXT_ARG();
+ } else {
+ fprintf(stderr, "mirred bad argument %s\n", *argv);
+ return -1;
+ }
+
+
+ if (matches(*argv, "egress") == 0 || matches(*argv, "ingress") == 0 ||
+ matches(*argv, "index") == 0) {
+ int ret = parse_direction(a, &argc, &argv, tca_id, n);
+
+ if (ret == 0) {
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+ }
+
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ fprintf(stderr, "mirred option not supported %s\n", *argv);
+ }
+
+ return -1;
+
+}
+
+static int
+print_mirred(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct tc_mirred *p;
+ struct rtattr *tb[TCA_MIRRED_MAX + 1];
+ const char *dev;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_MIRRED_MAX, arg);
+
+ if (tb[TCA_MIRRED_PARMS] == NULL) {
+ fprintf(stderr, "Missing mirred parameters\n");
+ return -1;
+ }
+ p = RTA_DATA(tb[TCA_MIRRED_PARMS]);
+
+ dev = ll_index_to_name(p->ifindex);
+ if (dev == 0) {
+ fprintf(stderr, "Cannot find device %d\n", p->ifindex);
+ return -1;
+ }
+
+ print_string(PRINT_ANY, "kind", "%s ", "mirred");
+ print_string(PRINT_FP, NULL, "(%s", mirred_n2a(p->eaction));
+ print_string(PRINT_JSON, "mirred_action", NULL,
+ mirred_action(p->eaction));
+ print_string(PRINT_JSON, "direction", NULL,
+ mirred_direction(p->eaction));
+ print_string(PRINT_ANY, "to_dev", " to device %s)", dev);
+ print_action_control(f, " ", p->action, "");
+
+ print_uint(PRINT_ANY, "index", "\n \tindex %u", p->index);
+ print_int(PRINT_ANY, "ref", " ref %d", p->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_MIRRED_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_MIRRED_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ print_string(PRINT_FP, NULL, "%s", "\n ");
+ return 0;
+}
+
+struct action_util mirred_action_util = {
+ .id = "mirred",
+ .parse_aopt = parse_mirred,
+ .print_aopt = print_mirred,
+};
diff --git a/tc/m_mpls.c b/tc/m_mpls.c
new file mode 100644
index 0000000..4b1ec70
--- /dev/null
+++ b/tc/m_mpls.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/* Copyright (C) 2019 Netronome Systems, Inc. */
+
+#include <linux/if_ether.h>
+#include <linux/tc_act/tc_mpls.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "utils.h"
+#include "rt_names.h"
+#include "tc_util.h"
+
+static const char * const action_names[] = {
+ [TCA_MPLS_ACT_POP] = "pop",
+ [TCA_MPLS_ACT_PUSH] = "push",
+ [TCA_MPLS_ACT_MODIFY] = "modify",
+ [TCA_MPLS_ACT_DEC_TTL] = "dec_ttl",
+};
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: mpls pop [ protocol MPLS_PROTO ]\n"
+ " mpls push [ protocol MPLS_PROTO ] [ label MPLS_LABEL ] [ tc MPLS_TC ]\n"
+ " [ ttl MPLS_TTL ] [ bos MPLS_BOS ] [CONTROL]\n"
+ " mpls modify [ label MPLS_LABEL ] [ tc MPLS_TC ] [ ttl MPLS_TTL ] [CONTROL]\n"
+ " for pop MPLS_PROTO is next header of packet - e.g. ip or mpls_uc\n"
+ " for push MPLS_PROTO is one of mpls_uc or mpls_mc\n"
+ " with default: mpls_uc\n"
+ " CONTROL := reclassify | pipe | drop | continue | pass |\n"
+ " goto chain <CHAIN_INDEX>\n");
+}
+
+static void usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static bool can_modify_mpls_fields(unsigned int action)
+{
+ return action == TCA_MPLS_ACT_PUSH || action == TCA_MPLS_ACT_MODIFY;
+}
+
+static bool can_modify_ethtype(unsigned int action)
+{
+ return action == TCA_MPLS_ACT_PUSH || action == TCA_MPLS_ACT_POP;
+}
+
+static bool is_valid_label(__u32 label)
+{
+ return label <= 0xfffff;
+}
+
+static bool check_double_action(unsigned int action, const char *arg)
+{
+ if (!action)
+ return false;
+
+ fprintf(stderr,
+ "Error: got \"%s\" but action already set to \"%s\"\n",
+ arg, action_names[action]);
+ explain();
+ return true;
+}
+
+static int parse_mpls(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+ struct tc_mpls parm = {};
+ __u32 label = 0xffffffff;
+ unsigned int action = 0;
+ char **argv = *argv_p;
+ struct rtattr *tail;
+ int argc = *argc_p;
+ __u16 proto = 0;
+ __u8 bos = 0xff;
+ __u8 tc = 0xff;
+ __u8 ttl = 0;
+
+ if (matches(*argv, "mpls") != 0)
+ return -1;
+
+ NEXT_ARG();
+
+ while (argc > 0) {
+ if (matches(*argv, "pop") == 0) {
+ if (check_double_action(action, *argv))
+ return -1;
+ action = TCA_MPLS_ACT_POP;
+ } else if (matches(*argv, "push") == 0) {
+ if (check_double_action(action, *argv))
+ return -1;
+ action = TCA_MPLS_ACT_PUSH;
+ } else if (matches(*argv, "modify") == 0) {
+ if (check_double_action(action, *argv))
+ return -1;
+ action = TCA_MPLS_ACT_MODIFY;
+ } else if (matches(*argv, "dec_ttl") == 0) {
+ if (check_double_action(action, *argv))
+ return -1;
+ action = TCA_MPLS_ACT_DEC_TTL;
+ } else if (matches(*argv, "label") == 0) {
+ if (!can_modify_mpls_fields(action))
+ invarg("only valid for push/modify", *argv);
+ NEXT_ARG();
+ if (get_u32(&label, *argv, 0) || !is_valid_label(label))
+ invarg("label must be <=0xFFFFF", *argv);
+ } else if (matches(*argv, "tc") == 0) {
+ if (!can_modify_mpls_fields(action))
+ invarg("only valid for push/modify", *argv);
+ NEXT_ARG();
+ if (get_u8(&tc, *argv, 0) || (tc & ~0x7))
+ invarg("tc field is 3 bits max", *argv);
+ } else if (matches(*argv, "ttl") == 0) {
+ if (!can_modify_mpls_fields(action))
+ invarg("only valid for push/modify", *argv);
+ NEXT_ARG();
+ if (get_u8(&ttl, *argv, 0) || !ttl)
+ invarg("ttl must be >0 and <=255", *argv);
+ } else if (matches(*argv, "bos") == 0) {
+ if (!can_modify_mpls_fields(action))
+ invarg("only valid for push/modify", *argv);
+ NEXT_ARG();
+ if (get_u8(&bos, *argv, 0) || (bos & ~0x1))
+ invarg("bos must be 0 or 1", *argv);
+ } else if (matches(*argv, "protocol") == 0) {
+ if (!can_modify_ethtype(action))
+ invarg("only valid for push/pop", *argv);
+ NEXT_ARG();
+ if (ll_proto_a2n(&proto, *argv))
+ invarg("protocol is invalid", *argv);
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+
+ NEXT_ARG_FWD();
+ }
+
+ if (!action)
+ incomplete_command();
+
+ parse_action_control_dflt(&argc, &argv, &parm.action,
+ false, TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&parm.index, *argv, 10))
+ invarg("illegal index", *argv);
+ NEXT_ARG_FWD();
+ }
+ }
+
+ if (action == TCA_MPLS_ACT_PUSH && !label)
+ missarg("label");
+
+ if (action == TCA_MPLS_ACT_PUSH && proto &&
+ proto != htons(ETH_P_MPLS_UC) && proto != htons(ETH_P_MPLS_MC)) {
+ fprintf(stderr,
+ "invalid push protocol \"0x%04x\" - use mpls_(uc|mc)\n",
+ ntohs(proto));
+ return -1;
+ }
+
+ if (action == TCA_MPLS_ACT_POP && !proto)
+ missarg("protocol");
+
+ parm.m_action = action;
+ tail = addattr_nest(n, MAX_MSG, tca_id | NLA_F_NESTED);
+ addattr_l(n, MAX_MSG, TCA_MPLS_PARMS, &parm, sizeof(parm));
+ if (label != 0xffffffff)
+ addattr_l(n, MAX_MSG, TCA_MPLS_LABEL, &label, sizeof(label));
+ if (proto)
+ addattr_l(n, MAX_MSG, TCA_MPLS_PROTO, &proto, sizeof(proto));
+ if (tc != 0xff)
+ addattr8(n, MAX_MSG, TCA_MPLS_TC, tc);
+ if (ttl)
+ addattr8(n, MAX_MSG, TCA_MPLS_TTL, ttl);
+ if (bos != 0xff)
+ addattr8(n, MAX_MSG, TCA_MPLS_BOS, bos);
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int print_mpls(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct rtattr *tb[TCA_MPLS_MAX + 1];
+ struct tc_mpls *parm;
+ SPRINT_BUF(b1);
+ __u32 val;
+
+ if (!arg)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_MPLS_MAX, arg);
+
+ if (!tb[TCA_MPLS_PARMS]) {
+ fprintf(stderr, "[NULL mpls parameters]\n");
+ return -1;
+ }
+ parm = RTA_DATA(tb[TCA_MPLS_PARMS]);
+
+ print_string(PRINT_ANY, "kind", "%s ", "mpls");
+ print_string(PRINT_ANY, "mpls_action", " %s",
+ action_names[parm->m_action]);
+
+ switch (parm->m_action) {
+ case TCA_MPLS_ACT_POP:
+ if (tb[TCA_MPLS_PROTO]) {
+ __u16 proto;
+
+ proto = rta_getattr_u16(tb[TCA_MPLS_PROTO]);
+ print_string(PRINT_ANY, "protocol", " protocol %s",
+ ll_proto_n2a(proto, b1, sizeof(b1)));
+ }
+ break;
+ case TCA_MPLS_ACT_PUSH:
+ if (tb[TCA_MPLS_PROTO]) {
+ __u16 proto;
+
+ proto = rta_getattr_u16(tb[TCA_MPLS_PROTO]);
+ print_string(PRINT_ANY, "protocol", " protocol %s",
+ ll_proto_n2a(proto, b1, sizeof(b1)));
+ }
+ /* Fallthrough */
+ case TCA_MPLS_ACT_MODIFY:
+ if (tb[TCA_MPLS_LABEL]) {
+ val = rta_getattr_u32(tb[TCA_MPLS_LABEL]);
+ print_uint(PRINT_ANY, "label", " label %u", val);
+ }
+ if (tb[TCA_MPLS_TC]) {
+ val = rta_getattr_u8(tb[TCA_MPLS_TC]);
+ print_uint(PRINT_ANY, "tc", " tc %u", val);
+ }
+ if (tb[TCA_MPLS_BOS]) {
+ val = rta_getattr_u8(tb[TCA_MPLS_BOS]);
+ print_uint(PRINT_ANY, "bos", " bos %u", val);
+ }
+ if (tb[TCA_MPLS_TTL]) {
+ val = rta_getattr_u8(tb[TCA_MPLS_TTL]);
+ print_uint(PRINT_ANY, "ttl", " ttl %u", val);
+ }
+ break;
+ }
+ print_action_control(f, " ", parm->action, "");
+
+ print_uint(PRINT_ANY, "index", "\n\t index %u", parm->index);
+ print_int(PRINT_ANY, "ref", " ref %d", parm->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", parm->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_MPLS_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_MPLS_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+
+ return 0;
+}
+
+struct action_util mpls_action_util = {
+ .id = "mpls",
+ .parse_aopt = parse_mpls,
+ .print_aopt = print_mpls,
+};
diff --git a/tc/m_nat.c b/tc/m_nat.c
new file mode 100644
index 0000000..c4b02a8
--- /dev/null
+++ b/tc/m_nat.c
@@ -0,0 +1,197 @@
+/*
+ * m_nat.c NAT module
+ *
+ * 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: Herbert Xu <herbert@gondor.apana.org.au>
+ *
+ */
+
+#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 "tc_util.h"
+#include <linux/tc_act/tc_nat.h>
+
+static void
+explain(void)
+{
+ fprintf(stderr, "Usage: ... nat NAT\n"
+ "NAT := DIRECTION OLD NEW\n"
+ "DIRECTION := { ingress | egress }\n"
+ "OLD := PREFIX\n"
+ "NEW := ADDRESS\n");
+}
+
+static void
+usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static int
+parse_nat_args(int *argc_p, char ***argv_p, struct tc_nat *sel)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ inet_prefix addr;
+
+ if (argc <= 0)
+ return -1;
+
+ if (matches(*argv, "egress") == 0)
+ sel->flags |= TCA_NAT_FLAG_EGRESS;
+ else if (matches(*argv, "ingress") != 0)
+ goto bad_val;
+
+ NEXT_ARG();
+
+ if (get_prefix_1(&addr, *argv, AF_INET))
+ goto bad_val;
+
+ sel->old_addr = addr.data[0];
+ sel->mask = htonl(~0u << (32 - addr.bitlen));
+
+ NEXT_ARG();
+
+ if (get_prefix_1(&addr, *argv, AF_INET))
+ goto bad_val;
+
+ sel->new_addr = addr.data[0];
+
+ argc--;
+ argv++;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+
+bad_val:
+ return -1;
+}
+
+static int
+parse_nat(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+ struct tc_nat sel = {};
+
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int ok = 0;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (matches(*argv, "nat") == 0) {
+ NEXT_ARG();
+ if (parse_nat_args(&argc, &argv, &sel)) {
+ fprintf(stderr, "Illegal nat construct (%s)\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ ok++;
+ continue;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+
+ }
+
+ if (!ok) {
+ explain();
+ return -1;
+ }
+
+ parse_action_control_dflt(&argc, &argv, &sel.action, false, TC_ACT_OK);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&sel.index, *argv, 10)) {
+ fprintf(stderr, "Nat: Illegal \"index\"\n");
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_NAT_PARMS, &sel, sizeof(sel));
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int
+print_nat(struct action_util *au, FILE * f, struct rtattr *arg)
+{
+ struct tc_nat *sel;
+ struct rtattr *tb[TCA_NAT_MAX + 1];
+ SPRINT_BUF(buf1);
+ SPRINT_BUF(buf2);
+ int len;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_NAT_MAX, arg);
+
+ if (tb[TCA_NAT_PARMS] == NULL) {
+ fprintf(stderr, "Missing nat parameters\n");
+ return -1;
+ }
+ sel = RTA_DATA(tb[TCA_NAT_PARMS]);
+
+ len = ffs(sel->mask);
+ len = len ? 33 - len : 0;
+
+ print_string(PRINT_ANY, "type", " %s ", "nat");
+ print_string(PRINT_ANY, "direction", "%s",
+ sel->flags & TCA_NAT_FLAG_EGRESS ? "egress" : "ingress");
+
+ snprintf(buf2, sizeof(buf2), "%s/%d",
+ format_host_r(AF_INET, 4, &sel->old_addr, buf1, sizeof(buf1)),
+ len);
+ print_string(PRINT_ANY, "old_addr", " %s", buf2);
+ print_string(PRINT_ANY, "new_addr", " %s",
+ format_host_r(AF_INET, 4, &sel->new_addr, buf1, sizeof(buf1)));
+
+ print_action_control(f, " ", sel->action, "");
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "index", "\t index %u", sel->index);
+ print_int(PRINT_ANY, "ref", " ref %d", sel->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", sel->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_NAT_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_NAT_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+
+ return 0;
+}
+
+struct action_util nat_action_util = {
+ .id = "nat",
+ .parse_aopt = parse_nat,
+ .print_aopt = print_nat,
+};
diff --git a/tc/m_pedit.c b/tc/m_pedit.c
new file mode 100644
index 0000000..1cd2d16
--- /dev/null
+++ b/tc/m_pedit.c
@@ -0,0 +1,833 @@
+/*
+ * m_pedit.c generic packet editor actions module
+ *
+ * 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)
+ *
+ * TODO:
+ * 1) Big endian broken in some spots
+ * 2) A lot of this stuff was added on the fly; get a big double-double
+ * and clean it up at some point.
+ *
+ */
+
+#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 <dlfcn.h>
+#include "utils.h"
+#include "tc_util.h"
+#include "m_pedit.h"
+#include "rt_names.h"
+
+static struct m_pedit_util *pedit_list;
+static int pedit_debug;
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... pedit munge [ex] <MUNGE> [CONTROL]\n"
+ "Where: MUNGE := <RAW>|<LAYERED>\n"
+ "\t<RAW>:= <OFFSETC>[ATC]<CMD>\n \t\tOFFSETC:= offset <offval> <u8|u16|u32>\n"
+ "\t\tATC:= at <atval> offmask <maskval> shift <shiftval>\n"
+ "\t\tNOTE: offval is byte offset, must be multiple of 4\n"
+ "\t\tNOTE: maskval is a 32 bit hex number\n \t\tNOTE: shiftval is a shift value\n"
+ "\t\tCMD:= clear | invert | set <setval>| add <addval> | retain\n"
+ "\t<LAYERED>:= ip <ipdata> | ip6 <ip6data>\n"
+ " \t\t| udp <udpdata> | tcp <tcpdata> | icmp <icmpdata>\n"
+ "\tCONTROL:= reclassify | pipe | drop | continue | pass |\n"
+ "\t goto chain <CHAIN_INDEX>\n"
+ "\tNOTE: if 'ex' is set, extended functionality will be supported (kernel >= 4.11)\n"
+ "For Example usage look at the examples directory\n");
+
+}
+
+static void usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static int pedit_parse_nopopt(int *argc_p, char ***argv_p,
+ struct m_pedit_sel *sel,
+ struct m_pedit_key *tkey)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc) {
+ fprintf(stderr,
+ "Unknown action hence option \"%s\" is unparsable\n",
+ *argv);
+ return -1;
+ }
+
+ return 0;
+
+}
+
+static struct m_pedit_util *get_pedit_kind(const char *str)
+{
+ static void *pBODY;
+ void *dlh;
+ char buf[256];
+ struct m_pedit_util *p;
+
+ for (p = pedit_list; p; p = p->next) {
+ if (strcmp(p->id, str) == 0)
+ return p;
+ }
+
+ snprintf(buf, sizeof(buf), "p_%s.so", str);
+ dlh = dlopen(buf, RTLD_LAZY);
+ if (dlh == NULL) {
+ dlh = pBODY;
+ if (dlh == NULL) {
+ dlh = pBODY = dlopen(NULL, RTLD_LAZY);
+ if (dlh == NULL)
+ goto noexist;
+ }
+ }
+
+ snprintf(buf, sizeof(buf), "p_pedit_%s", str);
+ p = dlsym(dlh, buf);
+ if (p == NULL)
+ goto noexist;
+
+reg:
+ p->next = pedit_list;
+ pedit_list = p;
+ return p;
+
+noexist:
+ p = calloc(1, sizeof(*p));
+ if (p) {
+ strlcpy(p->id, str, sizeof(p->id));
+ p->parse_peopt = pedit_parse_nopopt;
+ goto reg;
+ }
+ return p;
+}
+
+static int pack_key(struct m_pedit_sel *_sel, struct m_pedit_key *tkey)
+{
+ struct tc_pedit_sel *sel = &_sel->sel;
+ struct m_pedit_key_ex *keys_ex = _sel->keys_ex;
+ int hwm = sel->nkeys;
+
+ if (hwm >= MAX_OFFS)
+ return -1;
+
+ if (tkey->off % 4) {
+ fprintf(stderr, "offsets MUST be in 32 bit boundaries\n");
+ return -1;
+ }
+
+ sel->keys[hwm].val = tkey->val;
+ sel->keys[hwm].mask = tkey->mask;
+ sel->keys[hwm].off = tkey->off;
+ sel->keys[hwm].at = tkey->at;
+ sel->keys[hwm].offmask = tkey->offmask;
+ sel->keys[hwm].shift = tkey->shift;
+
+ if (_sel->extended) {
+ keys_ex[hwm].htype = tkey->htype;
+ keys_ex[hwm].cmd = tkey->cmd;
+ } else {
+ if (tkey->htype != TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK ||
+ tkey->cmd != TCA_PEDIT_KEY_EX_CMD_SET) {
+ fprintf(stderr,
+ "Munge parameters not supported. Use 'pedit ex munge ...'.\n");
+ return -1;
+ }
+ }
+
+ sel->nkeys++;
+ return 0;
+}
+
+static int pack_key32(__u32 retain, struct m_pedit_sel *sel,
+ struct m_pedit_key *tkey)
+{
+ if (tkey->off > (tkey->off & ~3)) {
+ fprintf(stderr,
+ "pack_key32: 32 bit offsets must begin in 32bit boundaries\n");
+ return -1;
+ }
+
+ tkey->val = htonl(tkey->val & retain);
+ tkey->mask = htonl(tkey->mask | ~retain);
+ return pack_key(sel, tkey);
+}
+
+static int pack_key16(__u32 retain, struct m_pedit_sel *sel,
+ struct m_pedit_key *tkey)
+{
+ int ind, stride;
+ __u32 m[4] = { 0x0000FFFF, 0xFF0000FF, 0xFFFF0000 };
+
+ if (tkey->val > 0xFFFF || tkey->mask > 0xFFFF) {
+ fprintf(stderr, "pack_key16 bad value\n");
+ return -1;
+ }
+
+ ind = tkey->off & 3;
+
+ if (ind == 3) {
+ fprintf(stderr, "pack_key16 bad index value %d\n", ind);
+ return -1;
+ }
+
+ stride = 8 * (2 - ind);
+ tkey->val = htonl((tkey->val & retain) << stride);
+ tkey->mask = htonl(((tkey->mask | ~retain) << stride) | m[ind]);
+
+ tkey->off &= ~3;
+
+ if (pedit_debug)
+ printf("pack_key16: Final val %08x mask %08x\n",
+ tkey->val, tkey->mask);
+ return pack_key(sel, tkey);
+}
+
+static int pack_key8(__u32 retain, struct m_pedit_sel *sel,
+ struct m_pedit_key *tkey)
+{
+ int ind, stride;
+ __u32 m[4] = { 0x00FFFFFF, 0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00 };
+
+ if (tkey->val > 0xFF || tkey->mask > 0xFF) {
+ fprintf(stderr, "pack_key8 bad value (val %x mask %x\n",
+ tkey->val, tkey->mask);
+ return -1;
+ }
+
+ ind = tkey->off & 3;
+
+ stride = 8 * (3 - ind);
+ tkey->val = htonl((tkey->val & retain) << stride);
+ tkey->mask = htonl(((tkey->mask | ~retain) << stride) | m[ind]);
+
+ tkey->off &= ~3;
+
+ if (pedit_debug)
+ printf("pack_key8: Final word off %d val %08x mask %08x\n",
+ tkey->off, tkey->val, tkey->mask);
+ return pack_key(sel, tkey);
+}
+
+static int pack_mac(struct m_pedit_sel *sel, struct m_pedit_key *tkey,
+ __u8 *mac)
+{
+ int ret = 0;
+
+ if (!(tkey->off & 0x3)) {
+ tkey->mask = 0;
+ tkey->val = ntohl(*((__u32 *)mac));
+ ret |= pack_key32(~0, sel, tkey);
+
+ tkey->off += 4;
+ tkey->mask = 0;
+ tkey->val = ntohs(*((__u16 *)&mac[4]));
+ ret |= pack_key16(~0, sel, tkey);
+ } else if (!(tkey->off & 0x1)) {
+ tkey->mask = 0;
+ tkey->val = ntohs(*((__u16 *)mac));
+ ret |= pack_key16(~0, sel, tkey);
+
+ tkey->off += 4;
+ tkey->mask = 0;
+ tkey->val = ntohl(*((__u32 *)(mac + 2)));
+ ret |= pack_key32(~0, sel, tkey);
+ } else {
+ fprintf(stderr,
+ "pack_mac: mac offsets must begin in 32bit or 16bit boundaries\n");
+ return -1;
+ }
+
+ return ret;
+}
+
+static int pack_ipv6(struct m_pedit_sel *sel, struct m_pedit_key *tkey,
+ __u32 *ipv6)
+{
+ int ret = 0;
+ int i;
+
+ if (tkey->off & 0x3) {
+ fprintf(stderr,
+ "pack_ipv6: IPv6 offsets must begin in 32bit boundaries\n");
+ return -1;
+ }
+
+ for (i = 0; i < 4; i++) {
+ tkey->mask = 0;
+ tkey->val = ntohl(ipv6[i]);
+
+ ret = pack_key32(~0, sel, tkey);
+ if (ret)
+ return ret;
+
+ tkey->off += 4;
+ }
+
+ return 0;
+}
+
+static int parse_val(int *argc_p, char ***argv_p, __u32 *val, int type)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc <= 0)
+ return -1;
+
+ if (type == TINT)
+ return get_integer((int *)val, *argv, 0);
+
+ if (type == TU32)
+ return get_u32(val, *argv, 0);
+
+ if (type == TIPV4) {
+ inet_prefix addr;
+
+ if (get_prefix_1(&addr, *argv, AF_INET))
+ return -1;
+
+ *val = addr.data[0];
+ return 0;
+ }
+
+ if (type == TIPV6) {
+ inet_prefix addr;
+
+ if (get_prefix_1(&addr, *argv, AF_INET6))
+ return -1;
+
+ memcpy(val, addr.data, addr.bytelen);
+
+ return 0;
+ }
+
+ if (type == TMAC) {
+#define MAC_ALEN 6
+ int ret = ll_addr_a2n((char *)val, MAC_ALEN, *argv);
+
+ if (ret == MAC_ALEN)
+ return 0;
+ }
+
+ return -1;
+}
+
+int parse_cmd(int *argc_p, char ***argv_p, __u32 len, int type, __u32 retain,
+ struct m_pedit_sel *sel, struct m_pedit_key *tkey)
+{
+ __u32 mask[4] = { 0 };
+ __u32 val[4] = { 0 };
+ __u32 *m = &mask[0];
+ __u32 *v = &val[0];
+ __u32 o = 0xFF;
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc <= 0)
+ return -1;
+
+ if (pedit_debug)
+ printf("parse_cmd argc %d %s offset %d length %d\n",
+ argc, *argv, tkey->off, len);
+
+ if (len == 2)
+ o = 0xFFFF;
+ if (len == 4)
+ o = 0xFFFFFFFF;
+
+ if (matches(*argv, "invert") == 0) {
+ *v = *m = o;
+ } else if (matches(*argv, "set") == 0 ||
+ matches(*argv, "add") == 0) {
+ if (matches(*argv, "add") == 0)
+ tkey->cmd = TCA_PEDIT_KEY_EX_CMD_ADD;
+
+ if (!sel->extended && tkey->cmd) {
+ fprintf(stderr,
+ "Non extended mode. only 'set' command is supported\n");
+ return -1;
+ }
+
+ NEXT_ARG();
+ if (parse_val(&argc, &argv, val, type))
+ return -1;
+ } else if (matches(*argv, "preserve") == 0) {
+ retain = 0;
+ } else {
+ if (matches(*argv, "clear") != 0)
+ return -1;
+ }
+
+ argc--;
+ argv++;
+
+ if (argc && matches(*argv, "retain") == 0) {
+ NEXT_ARG();
+ if (parse_val(&argc, &argv, &retain, TU32))
+ return -1;
+ argc--;
+ argv++;
+ }
+
+ if (len > 4 && retain != ~0) {
+ fprintf(stderr,
+ "retain is not supported for fields longer the 32 bits\n");
+ return -1;
+ }
+
+ if (type == TMAC) {
+ res = pack_mac(sel, tkey, (__u8 *)val);
+ goto done;
+ }
+
+ if (type == TIPV6) {
+ res = pack_ipv6(sel, tkey, val);
+ goto done;
+ }
+
+ tkey->val = *v;
+ tkey->mask = *m;
+
+ if (type == TIPV4)
+ tkey->val = ntohl(tkey->val);
+
+ if (len == 1) {
+ res = pack_key8(retain, sel, tkey);
+ goto done;
+ }
+ if (len == 2) {
+ res = pack_key16(retain, sel, tkey);
+ goto done;
+ }
+ if (len == 4) {
+ res = pack_key32(retain, sel, tkey);
+ goto done;
+ }
+
+ return -1;
+done:
+ if (pedit_debug)
+ printf("parse_cmd done argc %d %s offset %d length %d\n",
+ argc, *argv, tkey->off, len);
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+
+}
+
+static int parse_offset(int *argc_p, char ***argv_p, struct m_pedit_sel *sel,
+ struct m_pedit_key *tkey)
+{
+ int off;
+ __u32 len, retain;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int res = -1;
+
+ if (argc <= 0)
+ return -1;
+
+ if (get_integer(&off, *argv, 0))
+ return -1;
+ tkey->off = off;
+
+ argc--;
+ argv++;
+
+ if (argc <= 0)
+ return -1;
+
+ if (matches(*argv, "u32") == 0) {
+ len = 4;
+ retain = 0xFFFFFFFF;
+ goto done;
+ }
+ if (matches(*argv, "u16") == 0) {
+ len = 2;
+ retain = 0xffff;
+ goto done;
+ }
+ if (matches(*argv, "u8") == 0) {
+ len = 1;
+ retain = 0xff;
+ goto done;
+ }
+
+ return -1;
+
+done:
+
+ NEXT_ARG();
+
+ /* [at <someval> offmask <maskval> shift <shiftval>] */
+ if (matches(*argv, "at") == 0) {
+
+ __u32 atv = 0, offmask = 0x0, shift = 0;
+
+ NEXT_ARG();
+ if (get_u32(&atv, *argv, 0))
+ return -1;
+ tkey->at = atv;
+
+ NEXT_ARG();
+
+ if (get_u32(&offmask, *argv, 16))
+ return -1;
+ tkey->offmask = offmask;
+
+ NEXT_ARG();
+
+ if (get_u32(&shift, *argv, 0))
+ return -1;
+ tkey->shift = shift;
+
+ NEXT_ARG();
+ }
+
+ res = parse_cmd(&argc, &argv, len, TU32, retain, sel, tkey);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int parse_munge(int *argc_p, char ***argv_p, struct m_pedit_sel *sel)
+{
+ struct m_pedit_key tkey = {};
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int res = -1;
+
+ if (argc <= 0)
+ return -1;
+
+ if (matches(*argv, "offset") == 0) {
+ NEXT_ARG();
+ res = parse_offset(&argc, &argv, sel, &tkey);
+ goto done;
+ } else {
+ char k[FILTER_NAMESZ];
+ struct m_pedit_util *p = NULL;
+
+ strncpy(k, *argv, sizeof(k) - 1);
+
+ if (argc > 0) {
+ p = get_pedit_kind(k);
+ if (p == NULL)
+ goto bad_val;
+ NEXT_ARG();
+ res = p->parse_peopt(&argc, &argv, sel, &tkey);
+ if (res < 0) {
+ fprintf(stderr, "bad pedit parsing\n");
+ goto bad_val;
+ }
+ goto done;
+ }
+ }
+
+bad_val:
+ return -1;
+
+done:
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+static int pedit_keys_ex_getattr(struct rtattr *attr,
+ struct m_pedit_key_ex *keys_ex, int n)
+{
+ struct rtattr *i;
+ int rem = RTA_PAYLOAD(attr);
+ struct rtattr *tb[TCA_PEDIT_KEY_EX_MAX + 1];
+ struct m_pedit_key_ex *k = keys_ex;
+
+ for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ if (!n)
+ return -1;
+
+ if (i->rta_type != TCA_PEDIT_KEY_EX)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_PEDIT_KEY_EX_MAX, i);
+
+ k->htype = rta_getattr_u16(tb[TCA_PEDIT_KEY_EX_HTYPE]);
+ k->cmd = rta_getattr_u16(tb[TCA_PEDIT_KEY_EX_CMD]);
+
+ k++;
+ n--;
+ }
+
+ return !!n;
+}
+
+static int pedit_keys_ex_addattr(struct m_pedit_sel *sel, struct nlmsghdr *n)
+{
+ struct m_pedit_key_ex *k = sel->keys_ex;
+ struct rtattr *keys_start;
+ int i;
+
+ if (!sel->extended)
+ return 0;
+
+ keys_start = addattr_nest(n, MAX_MSG, TCA_PEDIT_KEYS_EX | NLA_F_NESTED);
+
+ for (i = 0; i < sel->sel.nkeys; i++) {
+ struct rtattr *key_start;
+
+ key_start = addattr_nest(n, MAX_MSG,
+ TCA_PEDIT_KEY_EX | NLA_F_NESTED);
+
+ if (addattr16(n, MAX_MSG, TCA_PEDIT_KEY_EX_HTYPE, k->htype) ||
+ addattr16(n, MAX_MSG, TCA_PEDIT_KEY_EX_CMD, k->cmd)) {
+ return -1;
+ }
+
+ addattr_nest_end(n, key_start);
+
+ k++;
+ }
+
+ addattr_nest_end(n, keys_start);
+
+ return 0;
+}
+
+static int parse_pedit(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+ struct m_pedit_sel sel = {};
+
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int ok = 0, iok = 0;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (pedit_debug > 1)
+ fprintf(stderr, "while pedit (%d:%s)\n", argc, *argv);
+ if (matches(*argv, "pedit") == 0) {
+ NEXT_ARG();
+ ok++;
+
+ if (matches(*argv, "ex") == 0) {
+ if (ok > 1) {
+ fprintf(stderr,
+ "'ex' must be before first 'munge'\n");
+ explain();
+ return -1;
+ }
+ sel.extended = true;
+ NEXT_ARG();
+ }
+
+ continue;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else if (matches(*argv, "munge") == 0) {
+ if (!ok) {
+ fprintf(stderr, "Bad pedit construct (%s)\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ NEXT_ARG();
+
+ if (parse_munge(&argc, &argv, &sel)) {
+ fprintf(stderr, "Bad pedit construct (%s)\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ ok++;
+ } else {
+ break;
+ }
+
+ }
+
+ if (!ok) {
+ explain();
+ return -1;
+ }
+
+ parse_action_control_dflt(&argc, &argv, &sel.sel.action, false, TC_ACT_OK);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&sel.sel.index, *argv, 10)) {
+ fprintf(stderr, "Pedit: Illegal \"index\"\n");
+ return -1;
+ }
+ argc--;
+ argv++;
+ iok++;
+ }
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ if (!sel.extended) {
+ addattr_l(n, MAX_MSG, TCA_PEDIT_PARMS, &sel,
+ sizeof(sel.sel) +
+ sel.sel.nkeys * sizeof(struct tc_pedit_key));
+ } else {
+ addattr_l(n, MAX_MSG, TCA_PEDIT_PARMS_EX, &sel,
+ sizeof(sel.sel) +
+ sel.sel.nkeys * sizeof(struct tc_pedit_key));
+
+ pedit_keys_ex_addattr(&sel, n);
+ }
+
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static const char * const pedit_htype_str[] = {
+ [TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK] = "",
+ [TCA_PEDIT_KEY_EX_HDR_TYPE_ETH] = "eth",
+ [TCA_PEDIT_KEY_EX_HDR_TYPE_IP4] = "ipv4",
+ [TCA_PEDIT_KEY_EX_HDR_TYPE_IP6] = "ipv6",
+ [TCA_PEDIT_KEY_EX_HDR_TYPE_TCP] = "tcp",
+ [TCA_PEDIT_KEY_EX_HDR_TYPE_UDP] = "udp",
+};
+
+static void print_pedit_location(FILE *f,
+ enum pedit_header_type htype, __u32 off)
+{
+ if (htype == TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK) {
+ fprintf(f, "%d", (unsigned int)off);
+ return;
+ }
+
+ if (htype < ARRAY_SIZE(pedit_htype_str))
+ fprintf(f, "%s", pedit_htype_str[htype]);
+ else
+ fprintf(f, "unknown(%d)", htype);
+
+ fprintf(f, "%c%d", (int)off >= 0 ? '+' : '-', abs((int)off));
+}
+
+static int print_pedit(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct tc_pedit_sel *sel;
+ struct rtattr *tb[TCA_PEDIT_MAX + 1];
+ struct m_pedit_key_ex *keys_ex = NULL;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_PEDIT_MAX, arg);
+
+ if (!tb[TCA_PEDIT_PARMS] && !tb[TCA_PEDIT_PARMS_EX]) {
+ fprintf(stderr, "Missing pedit parameters\n");
+ return -1;
+ }
+
+ if (tb[TCA_PEDIT_PARMS]) {
+ sel = RTA_DATA(tb[TCA_PEDIT_PARMS]);
+ } else {
+ int err;
+
+ sel = RTA_DATA(tb[TCA_PEDIT_PARMS_EX]);
+
+ if (!tb[TCA_PEDIT_KEYS_EX]) {
+ fprintf(f, "Netlink error\n");
+ return -1;
+ }
+
+ keys_ex = calloc(sel->nkeys, sizeof(*keys_ex));
+ if (!keys_ex) {
+ fprintf(f, "Out of memory\n");
+ return -1;
+ }
+
+ err = pedit_keys_ex_getattr(tb[TCA_PEDIT_KEYS_EX], keys_ex,
+ sel->nkeys);
+ if (err) {
+ fprintf(f, "Netlink error\n");
+
+ free(keys_ex);
+ return -1;
+ }
+ }
+
+ fprintf(f, " pedit ");
+ print_action_control(f, "action ", sel->action, " ");
+ fprintf(f,"keys %d\n ", sel->nkeys);
+ fprintf(f, "\t index %u ref %d bind %d", sel->index, sel->refcnt,
+ sel->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_PEDIT_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_PEDIT_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ if (sel->nkeys) {
+ int i;
+ struct tc_pedit_key *key = sel->keys;
+ struct m_pedit_key_ex *key_ex = keys_ex;
+
+ for (i = 0; i < sel->nkeys; i++, key++) {
+ enum pedit_header_type htype =
+ TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK;
+ enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET;
+
+ if (keys_ex) {
+ htype = key_ex->htype;
+ cmd = key_ex->cmd;
+
+ key_ex++;
+ }
+
+ fprintf(f, "\n\t key #%d", i);
+
+ fprintf(f, " at ");
+
+ print_pedit_location(f, htype, key->off);
+
+ fprintf(f, ": %s %08x mask %08x",
+ cmd ? "add" : "val",
+ (unsigned int)ntohl(key->val),
+ (unsigned int)ntohl(key->mask));
+ }
+ } else {
+ fprintf(f, "\npedit %x keys %d is not LEGIT", sel->index,
+ sel->nkeys);
+ }
+
+ fprintf(f, "\n ");
+
+ free(keys_ex);
+ return 0;
+}
+
+struct action_util pedit_action_util = {
+ .id = "pedit",
+ .parse_aopt = parse_pedit,
+ .print_aopt = print_pedit,
+};
diff --git a/tc/m_pedit.h b/tc/m_pedit.h
new file mode 100644
index 0000000..5d3628a
--- /dev/null
+++ b/tc/m_pedit.h
@@ -0,0 +1,77 @@
+/*
+ * m_pedit.h generic packet editor actions module
+ *
+ * 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)
+ *
+ */
+
+#ifndef _ACT_PEDIT_H_
+#define _ACT_PEDIT_H_ 1
+
+#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 "tc_util.h"
+#include <linux/tc_act/tc_pedit.h>
+
+#define MAX_OFFS 128
+
+#define TIPV4 1
+#define TIPV6 2
+#define TINT 3
+#define TU32 4
+#define TMAC 5
+
+#define RU32 0xFFFFFFFF
+#define RU16 0xFFFF
+#define RU8 0xFF
+
+#define PEDITKINDSIZ 16
+
+struct m_pedit_key {
+ __u32 mask; /* AND */
+ __u32 val; /*XOR */
+ __u32 off; /*offset */
+ __u32 at;
+ __u32 offmask;
+ __u32 shift;
+
+ enum pedit_header_type htype;
+ enum pedit_cmd cmd;
+};
+
+struct m_pedit_key_ex {
+ enum pedit_header_type htype;
+ enum pedit_cmd cmd;
+};
+
+struct m_pedit_sel {
+ struct tc_pedit_sel sel;
+ struct tc_pedit_key keys[MAX_OFFS];
+ struct m_pedit_key_ex keys_ex[MAX_OFFS];
+ bool extended;
+};
+
+struct m_pedit_util {
+ struct m_pedit_util *next;
+ char id[PEDITKINDSIZ];
+ int (*parse_peopt)(int *argc_p, char ***argv_p,
+ struct m_pedit_sel *sel,
+ struct m_pedit_key *tkey);
+};
+
+int parse_cmd(int *argc_p, char ***argv_p, __u32 len, int type,
+ __u32 retain,
+ struct m_pedit_sel *sel, struct m_pedit_key *tkey);
+#endif
diff --git a/tc/m_police.c b/tc/m_police.c
new file mode 100644
index 0000000..a5bc20c
--- /dev/null
+++ b/tc/m_police.c
@@ -0,0 +1,322 @@
+/*
+ * m_police.c Parse/print policing module options.
+ *
+ * This program is free software; you can u32istribute 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>
+ * FIXES: 19990619 - J Hadi Salim (hadi@cyberus.ca)
+ * simple addattr packaging fix.
+ * 2002: J Hadi Salim - Add tc action extensions syntax
+ *
+ */
+
+#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 "tc_util.h"
+
+static int act_parse_police(struct action_util *a, int *argc_p,
+ char ***argv_p, int tca_id, struct nlmsghdr *n);
+static int print_police(struct action_util *a, FILE *f, struct rtattr *tb);
+
+struct action_util police_action_util = {
+ .id = "police",
+ .parse_aopt = act_parse_police,
+ .print_aopt = print_police,
+};
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: ... police rate BPS burst BYTES[/BYTES] [ mtu BYTES[/BYTES] ]\n"
+ " [ peakrate BPS ] [ avrate BPS ] [ overhead BYTES ]\n"
+ " [ linklayer TYPE ] [ CONTROL ]\n"
+ "Where: CONTROL := conform-exceed <EXCEEDACT>[/NOTEXCEEDACT]\n"
+ " Define how to handle packets which exceed (<EXCEEDACT>)\n"
+ " or conform (<NOTEXCEEDACT>) the configured bandwidth limit.\n"
+ " EXCEEDACT/NOTEXCEEDACT := { pipe | ok | reclassify | drop | continue |\n"
+ " goto chain <CHAIN_INDEX> }\n");
+ exit(-1);
+}
+
+static int act_parse_police(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int res = -1;
+ int ok = 0;
+ struct tc_police p = { .action = TC_POLICE_RECLASSIFY };
+ __u32 rtab[256];
+ __u32 ptab[256];
+ __u32 avrate = 0;
+ int presult = 0;
+ unsigned buffer = 0, mtu = 0, mpu = 0;
+ unsigned short overhead = 0;
+ unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+ int Rcell_log = -1, Pcell_log = -1;
+ struct rtattr *tail;
+ __u64 rate64 = 0, prate64 = 0;
+
+ if (a) /* new way of doing things */
+ NEXT_ARG();
+
+ if (argc <= 0)
+ return -1;
+
+ while (argc > 0) {
+
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&p.index, *argv, 10))
+ invarg("index", *argv);
+ } else if (matches(*argv, "burst") == 0 ||
+ strcmp(*argv, "buffer") == 0 ||
+ strcmp(*argv, "maxburst") == 0) {
+ NEXT_ARG();
+ if (buffer)
+ duparg("buffer/burst", *argv);
+ if (get_size_and_cell(&buffer, &Rcell_log, *argv) < 0)
+ invarg("buffer", *argv);
+ } else if (strcmp(*argv, "mtu") == 0 ||
+ strcmp(*argv, "minburst") == 0) {
+ NEXT_ARG();
+ if (mtu)
+ duparg("mtu/minburst", *argv);
+ if (get_size_and_cell(&mtu, &Pcell_log, *argv) < 0)
+ invarg("mtu", *argv);
+ } else if (strcmp(*argv, "mpu") == 0) {
+ NEXT_ARG();
+ if (mpu)
+ duparg("mpu", *argv);
+ if (get_size(&mpu, *argv))
+ invarg("mpu", *argv);
+ } else if (strcmp(*argv, "rate") == 0) {
+ NEXT_ARG();
+ if (rate64)
+ duparg("rate", *argv);
+ if (get_rate64(&rate64, *argv))
+ invarg("rate", *argv);
+ } else if (strcmp(*argv, "avrate") == 0) {
+ NEXT_ARG();
+ if (avrate)
+ duparg("avrate", *argv);
+ if (get_rate(&avrate, *argv))
+ invarg("avrate", *argv);
+ } else if (matches(*argv, "peakrate") == 0) {
+ NEXT_ARG();
+ if (prate64)
+ duparg("peakrate", *argv);
+ if (get_rate64(&prate64, *argv))
+ invarg("peakrate", *argv);
+ } else if (matches(*argv, "reclassify") == 0 ||
+ matches(*argv, "drop") == 0 ||
+ matches(*argv, "shot") == 0 ||
+ matches(*argv, "continue") == 0 ||
+ matches(*argv, "pass") == 0 ||
+ matches(*argv, "ok") == 0 ||
+ matches(*argv, "pipe") == 0 ||
+ matches(*argv, "goto") == 0) {
+ if (!parse_action_control(&argc, &argv, &p.action, false))
+ goto action_ctrl_ok;
+ return -1;
+ } else if (strcmp(*argv, "conform-exceed") == 0) {
+ NEXT_ARG();
+ if (!parse_action_control_slash(&argc, &argv, &p.action,
+ &presult, true))
+ goto action_ctrl_ok;
+ return -1;
+ } else if (matches(*argv, "overhead") == 0) {
+ NEXT_ARG();
+ if (get_u16(&overhead, *argv, 10))
+ invarg("overhead", *argv);
+ } else if (matches(*argv, "linklayer") == 0) {
+ NEXT_ARG();
+ if (get_linklayer(&linklayer, *argv))
+ invarg("linklayer", *argv);
+ } else if (strcmp(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+ NEXT_ARG_FWD();
+action_ctrl_ok:
+ ok++;
+ }
+
+ if (!ok)
+ return -1;
+
+ if (rate64 && avrate)
+ return -1;
+
+ /* Must at least do late binding, use TB or ewma policing */
+ if (!rate64 && !avrate && !p.index) {
+ fprintf(stderr, "\"rate\" or \"avrate\" MUST be specified.\n");
+ return -1;
+ }
+
+ /* When the TB policer is used, burst is required */
+ if (rate64 && !buffer && !avrate) {
+ fprintf(stderr, "\"burst\" requires \"rate\".\n");
+ return -1;
+ }
+
+ if (prate64) {
+ if (!rate64) {
+ fprintf(stderr, "\"peakrate\" requires \"rate\".\n");
+ return -1;
+ }
+ if (!mtu) {
+ fprintf(stderr, "\"mtu\" is required, if \"peakrate\" is requested.\n");
+ return -1;
+ }
+ }
+
+ if (rate64) {
+ p.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64;
+ p.rate.mpu = mpu;
+ p.rate.overhead = overhead;
+ if (tc_calc_rtable_64(&p.rate, rtab, Rcell_log, mtu,
+ linklayer, rate64) < 0) {
+ fprintf(stderr, "POLICE: failed to calculate rate table.\n");
+ return -1;
+ }
+ p.burst = tc_calc_xmittime(rate64, buffer);
+ }
+ p.mtu = mtu;
+ if (prate64) {
+ p.peakrate.rate = (prate64 >= (1ULL << 32)) ? ~0U : prate64;
+ p.peakrate.mpu = mpu;
+ p.peakrate.overhead = overhead;
+ if (tc_calc_rtable_64(&p.peakrate, ptab, Pcell_log, mtu,
+ linklayer, prate64) < 0) {
+ fprintf(stderr, "POLICE: failed to calculate peak rate table.\n");
+ return -1;
+ }
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_POLICE_TBF, &p, sizeof(p));
+ if (rate64) {
+ addattr_l(n, MAX_MSG, TCA_POLICE_RATE, rtab, 1024);
+ if (rate64 >= (1ULL << 32))
+ addattr64(n, MAX_MSG, TCA_POLICE_RATE64, rate64);
+ }
+ if (prate64) {
+ addattr_l(n, MAX_MSG, TCA_POLICE_PEAKRATE, ptab, 1024);
+ if (prate64 >= (1ULL << 32))
+ addattr64(n, MAX_MSG, TCA_POLICE_PEAKRATE64, prate64);
+ }
+ if (avrate)
+ addattr32(n, MAX_MSG, TCA_POLICE_AVRATE, avrate);
+ if (presult)
+ addattr32(n, MAX_MSG, TCA_POLICE_RESULT, presult);
+
+ addattr_nest_end(n, tail);
+ res = 0;
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+int parse_police(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+ return act_parse_police(NULL, argc_p, argv_p, tca_id, n);
+}
+
+static int print_police(struct action_util *a, FILE *f, struct rtattr *arg)
+{
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+ struct tc_police *p;
+ struct rtattr *tb[TCA_POLICE_MAX+1];
+ unsigned int buffer;
+ unsigned int linklayer;
+ __u64 rate64, prate64;
+
+ if (arg == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_POLICE_MAX, arg);
+
+ if (tb[TCA_POLICE_TBF] == NULL) {
+ fprintf(f, "[NULL police tbf]");
+ return 0;
+ }
+#ifndef STOOPID_8BYTE
+ if (RTA_PAYLOAD(tb[TCA_POLICE_TBF]) < sizeof(*p)) {
+ fprintf(f, "[truncated police tbf]");
+ return -1;
+ }
+#endif
+ p = RTA_DATA(tb[TCA_POLICE_TBF]);
+
+ rate64 = p->rate.rate;
+ if (tb[TCA_POLICE_RATE64] &&
+ RTA_PAYLOAD(tb[TCA_POLICE_RATE64]) >= sizeof(rate64))
+ rate64 = rta_getattr_u64(tb[TCA_POLICE_RATE64]);
+
+ fprintf(f, " police 0x%x ", p->index);
+ fprintf(f, "rate %s ", sprint_rate(rate64, b1));
+ buffer = tc_calc_xmitsize(rate64, p->burst);
+ fprintf(f, "burst %s ", sprint_size(buffer, b1));
+ fprintf(f, "mtu %s ", sprint_size(p->mtu, b1));
+ if (show_raw)
+ fprintf(f, "[%08x] ", p->burst);
+
+ prate64 = p->peakrate.rate;
+ if (tb[TCA_POLICE_PEAKRATE64] &&
+ RTA_PAYLOAD(tb[TCA_POLICE_PEAKRATE64]) >= sizeof(prate64))
+ prate64 = rta_getattr_u64(tb[TCA_POLICE_PEAKRATE64]);
+
+ if (prate64)
+ fprintf(f, "peakrate %s ", sprint_rate(prate64, b1));
+
+ if (tb[TCA_POLICE_AVRATE])
+ fprintf(f, "avrate %s ",
+ sprint_rate(rta_getattr_u32(tb[TCA_POLICE_AVRATE]),
+ b1));
+
+ print_action_control(f, "action ", p->action, "");
+
+ if (tb[TCA_POLICE_RESULT]) {
+ __u32 action = rta_getattr_u32(tb[TCA_POLICE_RESULT]);
+
+ print_action_control(f, "/", action, " ");
+ } else
+ fprintf(f, " ");
+
+ fprintf(f, "overhead %ub ", p->rate.overhead);
+ linklayer = (p->rate.linklayer & TC_LINKLAYER_MASK);
+ if (linklayer > TC_LINKLAYER_ETHERNET || show_details)
+ fprintf(f, "linklayer %s ", sprint_linklayer(linklayer, b2));
+ fprintf(f, "\n\tref %d bind %d", p->refcnt, p->bindcnt);
+ if (show_stats) {
+ if (tb[TCA_POLICE_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_POLICE_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ fprintf(f, "\n");
+
+
+ return 0;
+}
+
+int tc_print_police(FILE *f, struct rtattr *arg)
+{
+ return print_police(&police_action_util, f, arg);
+}
diff --git a/tc/m_sample.c b/tc/m_sample.c
new file mode 100644
index 0000000..c068e63
--- /dev/null
+++ b/tc/m_sample.c
@@ -0,0 +1,190 @@
+/*
+ * m_sample.c ingress/egress packet sampling module
+ *
+ * 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: Yotam Gigi <yotamg@mellanox.com>
+ *
+ */
+
+#include <stdio.h>
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+#include <linux/tc_act/tc_sample.h>
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: sample SAMPLE_CONF\n"
+ "where:\n"
+ "\tSAMPLE_CONF := SAMPLE_PARAMS | SAMPLE_INDEX\n"
+ "\tSAMPLE_PARAMS := rate RATE group GROUP [trunc SIZE] [SAMPLE_INDEX]\n"
+ "\tSAMPLE_INDEX := index INDEX\n"
+ "\tRATE := The ratio of packets observed at the data source to the samples generated.\n"
+ "\tGROUP := the psample sampling group\n"
+ "\tSIZE := the truncation size\n"
+ "\tINDEX := integer index of the sample action\n");
+}
+
+static void usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static int parse_sample(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+ struct tc_sample p = { 0 };
+ bool trunc_set = false;
+ bool group_set = false;
+ bool rate_set = false;
+ char **argv = *argv_p;
+ struct rtattr *tail;
+ int argc = *argc_p;
+ __u32 trunc;
+ __u32 group;
+ __u32 rate;
+
+ if (argc <= 1) {
+ fprintf(stderr, "sample bad argument count %d\n", argc);
+ usage();
+ return -1;
+ }
+
+ if (matches(*argv, "sample") == 0) {
+ NEXT_ARG();
+ } else {
+ fprintf(stderr, "sample bad argument %s\n", *argv);
+ return -1;
+ }
+
+ while (argc > 0) {
+ if (matches(*argv, "rate") == 0) {
+ NEXT_ARG();
+ if (get_u32(&rate, *argv, 10) != 0) {
+ fprintf(stderr, "Illegal rate %s\n", *argv);
+ usage();
+ return -1;
+ }
+ rate_set = true;
+ } else if (matches(*argv, "group") == 0) {
+ NEXT_ARG();
+ if (get_u32(&group, *argv, 10) != 0) {
+ fprintf(stderr, "Illegal group num %s\n",
+ *argv);
+ usage();
+ return -1;
+ }
+ group_set = true;
+ } else if (matches(*argv, "trunc") == 0) {
+ NEXT_ARG();
+ if (get_u32(&trunc, *argv, 10) != 0) {
+ fprintf(stderr, "Illegal truncation size %s\n",
+ *argv);
+ usage();
+ return -1;
+ }
+ trunc_set = true;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+
+ NEXT_ARG_FWD();
+ }
+
+ parse_action_control_dflt(&argc, &argv, &p.action, false, TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&p.index, *argv, 10)) {
+ fprintf(stderr, "sample: Illegal \"index\"\n");
+ return -1;
+ }
+ NEXT_ARG_FWD();
+ }
+ }
+
+ if (!p.index && !group_set) {
+ fprintf(stderr, "param \"group\" not set\n");
+ usage();
+ }
+
+ if (!p.index && !rate_set) {
+ fprintf(stderr, "param \"rate\" not set\n");
+ usage();
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_SAMPLE_PARMS, &p, sizeof(p));
+ if (rate_set)
+ addattr32(n, MAX_MSG, TCA_SAMPLE_RATE, rate);
+ if (group_set)
+ addattr32(n, MAX_MSG, TCA_SAMPLE_PSAMPLE_GROUP, group);
+ if (trunc_set)
+ addattr32(n, MAX_MSG, TCA_SAMPLE_TRUNC_SIZE, trunc);
+
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int print_sample(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct rtattr *tb[TCA_SAMPLE_MAX + 1];
+ struct tc_sample *p;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_SAMPLE_MAX, arg);
+
+ if (!tb[TCA_SAMPLE_PARMS] || !tb[TCA_SAMPLE_RATE] ||
+ !tb[TCA_SAMPLE_PSAMPLE_GROUP]) {
+ fprintf(stderr, "Missing sample parameters\n");
+ return -1;
+ }
+ p = RTA_DATA(tb[TCA_SAMPLE_PARMS]);
+
+ print_string(PRINT_ANY, "kind", "%s ", "sample");
+ print_uint(PRINT_ANY, "rate", "rate 1/%u ",
+ rta_getattr_u32(tb[TCA_SAMPLE_RATE]));
+ print_uint(PRINT_ANY, "group", "group %u",
+ rta_getattr_u32(tb[TCA_SAMPLE_PSAMPLE_GROUP]));
+
+ if (tb[TCA_SAMPLE_TRUNC_SIZE])
+ print_uint(PRINT_ANY, "trunc_size", " trunc_size %u",
+ rta_getattr_u32(tb[TCA_SAMPLE_TRUNC_SIZE]));
+
+ print_action_control(f, " ", p->action, "");
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "index", "\t index %u", p->index);
+ print_int(PRINT_ANY, "ref", " ref %d", p->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_SAMPLE_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_SAMPLE_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ return 0;
+}
+
+struct action_util sample_action_util = {
+ .id = "sample",
+ .parse_aopt = parse_sample,
+ .print_aopt = print_sample,
+};
diff --git a/tc/m_simple.c b/tc/m_simple.c
new file mode 100644
index 0000000..49e2504
--- /dev/null
+++ b/tc/m_simple.c
@@ -0,0 +1,206 @@
+/*
+ * m_simple.c simple action
+ *
+ * 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 <jhs@mojatatu.com>
+ *
+ * Pedagogical example. Adds a string that will be printed every time
+ * the simple instance is hit.
+ * Use this as a skeleton action and keep modifying it to meet your needs.
+ * Look at linux/tc_act/tc_defact.h for the different components ids and
+ * definitions used in this actions
+ *
+ * example use, yell "Incoming ICMP!" every time you see an incoming ICMP on
+ * eth0. Steps are:
+ * 1) Add an ingress qdisc point to eth0
+ * 2) Start a chain on ingress of eth0 that first matches ICMP then invokes
+ * the simple action to shout.
+ * 3) display stats and show that no packet has been seen by the action
+ * 4) Send one ping packet to google (expect to receive a response back)
+ * 5) grep the logs to see the logged message
+ * 6) display stats again and observe increment by 1
+ *
+ hadi@noma1:$ tc qdisc add dev eth0 ingress
+ hadi@noma1:$tc filter add dev eth0 parent ffff: protocol ip prio 5 \
+ u32 match ip protocol 1 0xff flowid 1:1 action simple "Incoming ICMP"
+
+ hadi@noma1:$ sudo tc -s filter ls dev eth0 parent ffff:
+ filter protocol ip pref 5 u32
+ filter protocol ip pref 5 u32 fh 800: ht divisor 1
+ filter protocol ip pref 5 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1
+ match 00010000/00ff0000 at 8
+ action order 1: Simple <Incoming ICMP>
+ index 4 ref 1 bind 1 installed 29 sec used 29 sec
+ Action statistics:
+ Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
+ backlog 0b 0p requeues 0
+
+
+ hadi@noma1$ ping -c 1 www.google.ca
+ PING www.google.ca (74.125.225.120) 56(84) bytes of data.
+ 64 bytes from ord08s08-in-f24.1e100.net (74.125.225.120): icmp_req=1 ttl=53 time=31.3 ms
+
+ --- www.google.ca ping statistics ---
+ 1 packets transmitted, 1 received, 0% packet loss, time 0ms
+ rtt min/avg/max/mdev = 31.316/31.316/31.316/0.000 ms
+
+ hadi@noma1$ dmesg | grep simple
+ [135354.473951] simple: Incoming ICMP_1
+
+ hadi@noma1$ sudo tc/tc -s filter ls dev eth0 parent ffff:
+ filter protocol ip pref 5 u32
+ filter protocol ip pref 5 u32 fh 800: ht divisor 1
+ filter protocol ip pref 5 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1
+ match 00010000/00ff0000 at 8
+ action order 1: Simple <Incoming ICMP>
+ index 4 ref 1 bind 1 installed 206 sec used 67 sec
+ Action statistics:
+ Sent 84 bytes 1 pkt (dropped 0, overlimits 0 requeues 0)
+ backlog 0b 0p requeues 0
+*/
+
+#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 "tc_util.h"
+#include <linux/tc_act/tc_defact.h>
+
+#ifndef SIMP_MAX_DATA
+#define SIMP_MAX_DATA 32
+#endif
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage:... simple [sdata STRING] [index INDEX] [CONTROL]\n"
+ "\tSTRING being an arbitrary string\n"
+ "\tINDEX := optional index value used\n"
+ "\tCONTROL := reclassify|pipe|drop|continue|ok\n");
+}
+
+static void usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static int
+parse_simple(struct action_util *a, int *argc_p, char ***argv_p, int tca_id,
+ struct nlmsghdr *n)
+{
+ struct tc_defact sel = {};
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int ok = 0;
+ struct rtattr *tail;
+ char *simpdata = NULL;
+
+ while (argc > 0) {
+ if (matches(*argv, "simple") == 0) {
+ NEXT_ARG();
+ } else if (matches(*argv, "sdata") == 0) {
+ NEXT_ARG();
+ ok += 1;
+ simpdata = *argv;
+ argc--;
+ argv++;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+ }
+
+ parse_action_control_dflt(&argc, &argv, &sel.action, false,
+ TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&sel.index, *argv, 10)) {
+ fprintf(stderr, "simple: Illegal \"index\" (%s)\n",
+ *argv);
+ return -1;
+ }
+ ok += 1;
+ argc--;
+ argv++;
+ }
+ }
+
+ if (!ok) {
+ explain();
+ return -1;
+ }
+
+ if (simpdata && (strlen(simpdata) > (SIMP_MAX_DATA - 1))) {
+ fprintf(stderr, "simple: Illegal string len %zu <%s>\n",
+ strlen(simpdata), simpdata);
+ return -1;
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_DEF_PARMS, &sel, sizeof(sel));
+ if (simpdata)
+ addattr_l(n, MAX_MSG, TCA_DEF_DATA, simpdata, SIMP_MAX_DATA);
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int print_simple(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct tc_defact *sel;
+ struct rtattr *tb[TCA_DEF_MAX + 1];
+ char *simpdata;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_DEF_MAX, arg);
+
+ if (tb[TCA_DEF_PARMS] == NULL) {
+ fprintf(stderr, "Missing simple parameters\n");
+ return -1;
+ }
+ sel = RTA_DATA(tb[TCA_DEF_PARMS]);
+
+ if (tb[TCA_DEF_DATA] == NULL) {
+ fprintf(stderr, "Missing simple string\n");
+ return -1;
+ }
+
+ simpdata = RTA_DATA(tb[TCA_DEF_DATA]);
+
+ fprintf(f, "Simple <%s>\n", simpdata);
+ fprintf(f, "\t index %u ref %d bind %d", sel->index,
+ sel->refcnt, sel->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_DEF_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_DEF_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ fprintf(f, "\n");
+
+ return 0;
+}
+
+struct action_util simple_action_util = {
+ .id = "simple",
+ .parse_aopt = parse_simple,
+ .print_aopt = print_simple,
+};
diff --git a/tc/m_skbedit.c b/tc/m_skbedit.c
new file mode 100644
index 0000000..761cad5
--- /dev/null
+++ b/tc/m_skbedit.c
@@ -0,0 +1,279 @@
+/*
+ * m_skbedit.c SKB Editing module
+ *
+ * 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, see <http://www.gnu.org/licenses>.
+ *
+ * Authors: Alexander Duyck <alexander.h.duyck@intel.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_skbedit.h>
+#include <linux/if_packet.h>
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... skbedit <[QM] [PM] [MM] [PT] [IF]>\n"
+ "QM = queue_mapping QUEUE_MAPPING\n"
+ "PM = priority PRIORITY\n"
+ "MM = mark MARK[/MASK]\n"
+ "PT = ptype PACKETYPE\n"
+ "IF = inheritdsfield\n"
+ "PACKETYPE = is one of:\n"
+ " host, otherhost, broadcast, multicast\n"
+ "QUEUE_MAPPING = device transmit queue to use\n"
+ "PRIORITY = classID to assign to priority field\n"
+ "MARK = firewall mark to set\n"
+ "MASK = mask applied to firewall mark (0xffffffff by default)\n"
+ "note: inheritdsfield maps DS field to skb->priority\n");
+}
+
+static void
+usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static int
+parse_skbedit(struct action_util *a, int *argc_p, char ***argv_p, int tca_id,
+ struct nlmsghdr *n)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int ok = 0;
+ struct rtattr *tail;
+ unsigned int tmp;
+ __u16 queue_mapping, ptype;
+ __u32 flags = 0, priority, mark, mask;
+ __u64 pure_flags = 0;
+ struct tc_skbedit sel = { 0 };
+
+ if (matches(*argv, "skbedit") != 0)
+ return -1;
+
+ NEXT_ARG();
+
+ while (argc > 0) {
+ if (matches(*argv, "queue_mapping") == 0) {
+ flags |= SKBEDIT_F_QUEUE_MAPPING;
+ NEXT_ARG();
+ if (get_unsigned(&tmp, *argv, 10) || tmp > 65535) {
+ fprintf(stderr, "Illegal queue_mapping\n");
+ return -1;
+ }
+ queue_mapping = tmp;
+ ok++;
+ } else if (matches(*argv, "priority") == 0) {
+ flags |= SKBEDIT_F_PRIORITY;
+ NEXT_ARG();
+ if (get_tc_classid(&priority, *argv)) {
+ fprintf(stderr, "Illegal priority\n");
+ return -1;
+ }
+ ok++;
+ } else if (matches(*argv, "mark") == 0) {
+ char *slash;
+
+ NEXT_ARG();
+ slash = strchr(*argv, '/');
+ if (slash)
+ *slash = '\0';
+
+ flags |= SKBEDIT_F_MARK;
+ if (get_u32(&mark, *argv, 0)) {
+ fprintf(stderr, "Illegal mark\n");
+ return -1;
+ }
+
+ if (slash) {
+ if (get_u32(&mask, slash + 1, 0)) {
+ fprintf(stderr, "Illegal mask\n");
+ return -1;
+ }
+ flags |= SKBEDIT_F_MASK;
+ }
+ ok++;
+ } else if (matches(*argv, "ptype") == 0) {
+
+ NEXT_ARG();
+ if (matches(*argv, "host") == 0) {
+ ptype = PACKET_HOST;
+ } else if (matches(*argv, "broadcast") == 0) {
+ ptype = PACKET_BROADCAST;
+ } else if (matches(*argv, "multicast") == 0) {
+ ptype = PACKET_MULTICAST;
+ } else if (matches(*argv, "otherhost") == 0) {
+ ptype = PACKET_OTHERHOST;
+ } else {
+ fprintf(stderr, "Illegal ptype (%s)\n",
+ *argv);
+ return -1;
+ }
+ flags |= SKBEDIT_F_PTYPE;
+ ok++;
+ } else if (matches(*argv, "inheritdsfield") == 0) {
+ pure_flags |= SKBEDIT_F_INHERITDSFIELD;
+ ok++;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+ argc--;
+ argv++;
+ }
+
+ parse_action_control_dflt(&argc, &argv, &sel.action,
+ false, TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&sel.index, *argv, 10)) {
+ fprintf(stderr, "skbedit: Illegal \"index\"\n");
+ return -1;
+ }
+ argc--;
+ argv++;
+ ok++;
+ }
+ }
+
+ if (!ok) {
+ explain();
+ return -1;
+ }
+
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_SKBEDIT_PARMS, &sel, sizeof(sel));
+ if (flags & SKBEDIT_F_QUEUE_MAPPING)
+ addattr_l(n, MAX_MSG, TCA_SKBEDIT_QUEUE_MAPPING,
+ &queue_mapping, sizeof(queue_mapping));
+ if (flags & SKBEDIT_F_PRIORITY)
+ addattr_l(n, MAX_MSG, TCA_SKBEDIT_PRIORITY,
+ &priority, sizeof(priority));
+ if (flags & SKBEDIT_F_MARK)
+ addattr_l(n, MAX_MSG, TCA_SKBEDIT_MARK,
+ &mark, sizeof(mark));
+ if (flags & SKBEDIT_F_MASK)
+ addattr_l(n, MAX_MSG, TCA_SKBEDIT_MASK,
+ &mask, sizeof(mask));
+ if (flags & SKBEDIT_F_PTYPE)
+ addattr_l(n, MAX_MSG, TCA_SKBEDIT_PTYPE,
+ &ptype, sizeof(ptype));
+ if (pure_flags != 0)
+ addattr64(n, MAX_MSG, TCA_SKBEDIT_FLAGS, pure_flags);
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int print_skbedit(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct rtattr *tb[TCA_SKBEDIT_MAX + 1];
+
+ SPRINT_BUF(b1);
+ __u32 priority;
+ __u16 ptype;
+ struct tc_skbedit *p;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_SKBEDIT_MAX, arg);
+
+ if (tb[TCA_SKBEDIT_PARMS] == NULL) {
+ fprintf(stderr, "Missing skbedit parameters\n");
+ return -1;
+ }
+ p = RTA_DATA(tb[TCA_SKBEDIT_PARMS]);
+
+ print_string(PRINT_ANY, "kind", "%s ", "skbedit");
+
+ if (tb[TCA_SKBEDIT_QUEUE_MAPPING] != NULL) {
+ print_uint(PRINT_ANY, "queue_mapping", "queue_mapping %u",
+ rta_getattr_u16(tb[TCA_SKBEDIT_QUEUE_MAPPING]));
+ }
+ if (tb[TCA_SKBEDIT_PRIORITY] != NULL) {
+ priority = rta_getattr_u32(tb[TCA_SKBEDIT_PRIORITY]);
+ print_string(PRINT_ANY, "priority", " priority %s",
+ sprint_tc_classid(priority, b1));
+ }
+ if (tb[TCA_SKBEDIT_MARK] != NULL) {
+ print_uint(PRINT_ANY, "mark", " mark %u",
+ rta_getattr_u32(tb[TCA_SKBEDIT_MARK]));
+ }
+ if (tb[TCA_SKBEDIT_MASK]) {
+ print_hex(PRINT_ANY, "mask", "/%#x",
+ rta_getattr_u32(tb[TCA_SKBEDIT_MASK]));
+ }
+ if (tb[TCA_SKBEDIT_PTYPE] != NULL) {
+ ptype = rta_getattr_u16(tb[TCA_SKBEDIT_PTYPE]);
+ if (ptype == PACKET_HOST)
+ print_string(PRINT_ANY, "ptype", " ptype %s", "host");
+ else if (ptype == PACKET_BROADCAST)
+ print_string(PRINT_ANY, "ptype", " ptype %s",
+ "broadcast");
+ else if (ptype == PACKET_MULTICAST)
+ print_string(PRINT_ANY, "ptype", " ptype %s",
+ "multicast");
+ else if (ptype == PACKET_OTHERHOST)
+ print_string(PRINT_ANY, "ptype", " ptype %s",
+ "otherhost");
+ else
+ print_uint(PRINT_ANY, "ptype", " ptype %u", ptype);
+ }
+ if (tb[TCA_SKBEDIT_FLAGS] != NULL) {
+ __u64 flags = rta_getattr_u64(tb[TCA_SKBEDIT_FLAGS]);
+
+ if (flags & SKBEDIT_F_INHERITDSFIELD)
+ print_null(PRINT_ANY, "inheritdsfield", " %s",
+ "inheritdsfield");
+ }
+
+ print_action_control(f, " ", p->action, "");
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "index", "\t index %u", p->index);
+ print_int(PRINT_ANY, "ref", " ref %d", p->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_SKBEDIT_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_SKBEDIT_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+
+ return 0;
+}
+
+struct action_util skbedit_action_util = {
+ .id = "skbedit",
+ .parse_aopt = parse_skbedit,
+ .print_aopt = print_skbedit,
+};
diff --git a/tc/m_skbmod.c b/tc/m_skbmod.c
new file mode 100644
index 0000000..d38a5c1
--- /dev/null
+++ b/tc/m_skbmod.c
@@ -0,0 +1,234 @@
+/*
+ * m_skbmod.c skb modifier action module
+ *
+ * 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 (jhs@mojatatu.com)
+ *
+ */
+
+#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 <linux/netdevice.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_skbmod.h>
+
+static void skbmod_explain(void)
+{
+ fprintf(stderr,
+ "Usage:... skbmod {[set <SETTABLE>] [swap <SWAPABLE>]} [CONTROL] [index INDEX]\n"
+ "where SETTABLE is: [dmac DMAC] [smac SMAC] [etype ETYPE]\n"
+ "where SWAPABLE is: \"mac\" to swap mac addresses\n"
+ "note: \"swap mac\" is done after any outstanding D/SMAC change\n"
+ "\tDMAC := 6 byte Destination MAC address\n"
+ "\tSMAC := optional 6 byte Source MAC address\n"
+ "\tETYPE := optional 16 bit ethertype\n"
+ "\tCONTROL := reclassify | pipe | drop | continue | ok |\n"
+ "\t goto chain <CHAIN_INDEX>\n"
+ "\tINDEX := skbmod index value to use\n");
+}
+
+static void skbmod_usage(void)
+{
+ skbmod_explain();
+ exit(-1);
+}
+
+static int parse_skbmod(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int ok = 0;
+ struct tc_skbmod p;
+ struct rtattr *tail;
+ char dbuf[ETH_ALEN];
+ char sbuf[ETH_ALEN];
+ __u16 skbmod_etype = 0;
+ char *daddr = NULL;
+ char *saddr = NULL;
+
+ memset(&p, 0, sizeof(p));
+
+ if (argc <= 0)
+ return -1;
+
+ while (argc > 0) {
+ if (matches(*argv, "skbmod") == 0) {
+ NEXT_ARG();
+ continue;
+ } else if (matches(*argv, "swap") == 0) {
+ NEXT_ARG();
+ continue;
+ } else if (matches(*argv, "mac") == 0) {
+ p.flags |= SKBMOD_F_SWAPMAC;
+ ok += 1;
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG();
+ continue;
+ } else if (matches(*argv, "etype") == 0) {
+ NEXT_ARG();
+ if (get_u16(&skbmod_etype, *argv, 0))
+ invarg("ethertype is invalid", *argv);
+ fprintf(stderr, "skbmod etype 0x%x\n", skbmod_etype);
+ p.flags |= SKBMOD_F_ETYPE;
+ ok += 1;
+ } else if (matches(*argv, "dmac") == 0) {
+ NEXT_ARG();
+ daddr = *argv;
+ if (sscanf(daddr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ dbuf, dbuf + 1, dbuf + 2,
+ dbuf + 3, dbuf + 4, dbuf + 5) != 6) {
+ fprintf(stderr, "Invalid dst mac address %s\n",
+ daddr);
+ return -1;
+ }
+ p.flags |= SKBMOD_F_DMAC;
+ fprintf(stderr, "dst MAC address <%s>\n", daddr);
+ ok += 1;
+
+ } else if (matches(*argv, "smac") == 0) {
+ NEXT_ARG();
+ saddr = *argv;
+ if (sscanf(saddr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ sbuf, sbuf + 1, sbuf + 2,
+ sbuf + 3, sbuf + 4, sbuf + 5) != 6) {
+ fprintf(stderr, "Invalid smac address %s\n",
+ saddr);
+ return -1;
+ }
+ p.flags |= SKBMOD_F_SMAC;
+ fprintf(stderr, "src MAC address <%s>\n", saddr);
+ ok += 1;
+ } else if (matches(*argv, "help") == 0) {
+ skbmod_usage();
+ } else {
+ break;
+ }
+
+ argc--;
+ argv++;
+ }
+
+ parse_action_control_dflt(&argc, &argv, &p.action, false, TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&p.index, *argv, 0)) {
+ fprintf(stderr, "skbmod: Illegal \"index\"\n");
+ return -1;
+ }
+ ok++;
+ argc--;
+ argv++;
+ }
+ }
+
+ if (!ok) {
+ fprintf(stderr, "skbmod requires at least one option\n");
+ skbmod_usage();
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_SKBMOD_PARMS, &p, sizeof(p));
+
+ if (daddr)
+ addattr_l(n, MAX_MSG, TCA_SKBMOD_DMAC, dbuf, ETH_ALEN);
+ if (skbmod_etype)
+ addattr16(n, MAX_MSG, TCA_SKBMOD_ETYPE, skbmod_etype);
+ if (saddr)
+ addattr_l(n, MAX_MSG, TCA_SKBMOD_SMAC, sbuf, ETH_ALEN);
+
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int print_skbmod(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct tc_skbmod *p;
+ struct rtattr *tb[TCA_SKBMOD_MAX + 1];
+ __u16 skbmod_etype = 0;
+ int has_optional = 0;
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_SKBMOD_MAX, arg);
+
+ if (tb[TCA_SKBMOD_PARMS] == NULL) {
+ fprintf(stderr, "Missing skbmod parameters\n");
+ return -1;
+ }
+
+ p = RTA_DATA(tb[TCA_SKBMOD_PARMS]);
+
+ fprintf(f, "skbmod ");
+ print_action_control(f, "", p->action, " ");
+
+ if (tb[TCA_SKBMOD_ETYPE]) {
+ skbmod_etype = rta_getattr_u16(tb[TCA_SKBMOD_ETYPE]);
+ has_optional = 1;
+ fprintf(f, "set etype 0x%X ", skbmod_etype);
+ }
+
+ if (has_optional)
+ fprintf(f, "\n\t ");
+
+ if (tb[TCA_SKBMOD_DMAC]) {
+ has_optional = 1;
+ fprintf(f, "set dmac %s ",
+ ll_addr_n2a(RTA_DATA(tb[TCA_SKBMOD_DMAC]),
+ RTA_PAYLOAD(tb[TCA_SKBMOD_DMAC]), 0, b1,
+ sizeof(b1)));
+
+ }
+
+ if (tb[TCA_SKBMOD_SMAC]) {
+ has_optional = 1;
+ fprintf(f, "set smac %s ",
+ ll_addr_n2a(RTA_DATA(tb[TCA_SKBMOD_SMAC]),
+ RTA_PAYLOAD(tb[TCA_SKBMOD_SMAC]), 0, b2,
+ sizeof(b2)));
+ }
+
+ if (p->flags & SKBMOD_F_SWAPMAC)
+ fprintf(f, "swap mac ");
+
+ fprintf(f, "\n\t index %u ref %d bind %d", p->index, p->refcnt,
+ p->bindcnt);
+ if (show_stats) {
+ if (tb[TCA_SKBMOD_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_SKBMOD_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+
+ fprintf(f, "\n");
+
+ return 0;
+}
+
+struct action_util skbmod_action_util = {
+ .id = "skbmod",
+ .parse_aopt = parse_skbmod,
+ .print_aopt = print_skbmod,
+};
diff --git a/tc/m_tunnel_key.c b/tc/m_tunnel_key.c
new file mode 100644
index 0000000..4e65e44
--- /dev/null
+++ b/tc/m_tunnel_key.c
@@ -0,0 +1,555 @@
+/*
+ * m_tunnel_key.c ip tunnel manipulation 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: Amir Vadai <amir@vadai.me>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <linux/if_ether.h>
+#include "utils.h"
+#include "rt_names.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_tunnel_key.h>
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: tunnel_key unset\n"
+ " tunnel_key set <TUNNEL_KEY>\n"
+ "Where TUNNEL_KEY is a combination of:\n"
+ "id <TUNNELID>\n"
+ "src_ip <IP> (mandatory)\n"
+ "dst_ip <IP> (mandatory)\n"
+ "dst_port <UDP_PORT>\n"
+ "geneve_opts <OPTIONS>\n"
+ "csum | nocsum (default is \"csum\")\n");
+}
+
+static void usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static int tunnel_key_parse_ip_addr(const char *str, int addr4_type,
+ int addr6_type, struct nlmsghdr *n)
+{
+ inet_prefix addr;
+ int ret;
+
+ ret = get_addr(&addr, str, AF_UNSPEC);
+ if (ret)
+ return ret;
+
+ addattr_l(n, MAX_MSG, addr.family == AF_INET ? addr4_type : addr6_type,
+ addr.data, addr.bytelen);
+
+ return 0;
+}
+
+static int tunnel_key_parse_key_id(const char *str, int type,
+ struct nlmsghdr *n)
+{
+ __be32 key_id;
+ int ret;
+
+ ret = get_be32(&key_id, str, 10);
+ if (!ret)
+ addattr32(n, MAX_MSG, type, key_id);
+
+ return ret;
+}
+
+static int tunnel_key_parse_dst_port(char *str, int type, struct nlmsghdr *n)
+{
+ int ret;
+ __be16 dst_port;
+
+ ret = get_be16(&dst_port, str, 10);
+ if (ret)
+ return -1;
+
+ addattr16(n, MAX_MSG, type, dst_port);
+
+ return 0;
+}
+
+static int tunnel_key_parse_be16(char *str, int base, int type,
+ struct nlmsghdr *n)
+{
+ int ret;
+ __be16 value;
+
+ ret = get_be16(&value, str, base);
+ if (ret)
+ return ret;
+
+ addattr16(n, MAX_MSG, type, value);
+
+ return 0;
+}
+
+static int tunnel_key_parse_u8(char *str, int base, int type,
+ struct nlmsghdr *n)
+{
+ int ret;
+ __u8 value;
+
+ ret = get_u8(&value, str, base);
+ if (ret)
+ return ret;
+
+ addattr8(n, MAX_MSG, type, value);
+
+ return 0;
+}
+
+static int tunnel_key_parse_geneve_opt(char *str, struct nlmsghdr *n)
+{
+ char *token, *saveptr = NULL;
+ struct rtattr *nest;
+ int i, ret;
+
+ nest = addattr_nest(n, MAX_MSG, TCA_TUNNEL_KEY_ENC_OPTS_GENEVE);
+
+ token = strtok_r(str, ":", &saveptr);
+ i = 1;
+ while (token) {
+ switch (i) {
+ case TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS:
+ {
+ ret = tunnel_key_parse_be16(token, 16, i, n);
+ if (ret)
+ return ret;
+ break;
+ }
+ case TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE:
+ {
+ ret = tunnel_key_parse_u8(token, 16, i, n);
+ if (ret)
+ return ret;
+ break;
+ }
+ case TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA:
+ {
+ size_t token_len = strlen(token);
+ uint8_t *opts;
+
+ opts = malloc(token_len / 2);
+ if (!opts)
+ return -1;
+ if (hex2mem(token, opts, token_len / 2) < 0) {
+ free(opts);
+ return -1;
+ }
+ addattr_l(n, MAX_MSG, i, opts, token_len / 2);
+ free(opts);
+
+ break;
+ }
+ default:
+ return -1;
+ }
+
+ token = strtok_r(NULL, ":", &saveptr);
+ i++;
+ }
+
+ addattr_nest_end(n, nest);
+
+ return 0;
+}
+
+static int tunnel_key_parse_geneve_opts(char *str, struct nlmsghdr *n)
+{
+ char *token, *saveptr = NULL;
+ struct rtattr *nest;
+ int ret;
+
+ nest = addattr_nest(n, MAX_MSG, TCA_TUNNEL_KEY_ENC_OPTS);
+
+ token = strtok_r(str, ",", &saveptr);
+ while (token) {
+ ret = tunnel_key_parse_geneve_opt(token, n);
+ if (ret)
+ return ret;
+
+ token = strtok_r(NULL, ",", &saveptr);
+ }
+
+ addattr_nest_end(n, nest);
+
+ return 0;
+}
+
+static int tunnel_key_parse_tos_ttl(char *str, int type, struct nlmsghdr *n)
+{
+ int ret;
+ __u8 val;
+
+ ret = get_u8(&val, str, 10);
+ if (ret)
+ ret = get_u8(&val, str, 16);
+ if (ret)
+ return -1;
+
+ addattr8(n, MAX_MSG, type, val);
+
+ return 0;
+}
+
+static int parse_tunnel_key(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+ struct tc_tunnel_key parm = {};
+ char **argv = *argv_p;
+ int argc = *argc_p;
+ struct rtattr *tail;
+ int action = 0;
+ int ret;
+ int has_src_ip = 0;
+ int has_dst_ip = 0;
+ int csum = 1;
+
+ if (matches(*argv, "tunnel_key") != 0)
+ return -1;
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+
+ NEXT_ARG();
+
+ while (argc > 0) {
+ if (matches(*argv, "unset") == 0) {
+ if (action) {
+ fprintf(stderr, "unexpected \"%s\" - action already specified\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ action = TCA_TUNNEL_KEY_ACT_RELEASE;
+ } else if (matches(*argv, "set") == 0) {
+ if (action) {
+ fprintf(stderr, "unexpected \"%s\" - action already specified\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ action = TCA_TUNNEL_KEY_ACT_SET;
+ } else if (matches(*argv, "src_ip") == 0) {
+ NEXT_ARG();
+ ret = tunnel_key_parse_ip_addr(*argv,
+ TCA_TUNNEL_KEY_ENC_IPV4_SRC,
+ TCA_TUNNEL_KEY_ENC_IPV6_SRC,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"src_ip\"\n");
+ return -1;
+ }
+ has_src_ip = 1;
+ } else if (matches(*argv, "dst_ip") == 0) {
+ NEXT_ARG();
+ ret = tunnel_key_parse_ip_addr(*argv,
+ TCA_TUNNEL_KEY_ENC_IPV4_DST,
+ TCA_TUNNEL_KEY_ENC_IPV6_DST,
+ n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"dst_ip\"\n");
+ return -1;
+ }
+ has_dst_ip = 1;
+ } else if (matches(*argv, "id") == 0) {
+ NEXT_ARG();
+ ret = tunnel_key_parse_key_id(*argv, TCA_TUNNEL_KEY_ENC_KEY_ID, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"id\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "dst_port") == 0) {
+ NEXT_ARG();
+ ret = tunnel_key_parse_dst_port(*argv,
+ TCA_TUNNEL_KEY_ENC_DST_PORT, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"dst port\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "geneve_opts") == 0) {
+ NEXT_ARG();
+
+ if (tunnel_key_parse_geneve_opts(*argv, n)) {
+ fprintf(stderr, "Illegal \"geneve_opts\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "tos") == 0) {
+ NEXT_ARG();
+ ret = tunnel_key_parse_tos_ttl(*argv,
+ TCA_TUNNEL_KEY_ENC_TOS, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"tos\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "ttl") == 0) {
+ NEXT_ARG();
+ ret = tunnel_key_parse_tos_ttl(*argv,
+ TCA_TUNNEL_KEY_ENC_TTL, n);
+ if (ret < 0) {
+ fprintf(stderr, "Illegal \"ttl\"\n");
+ return -1;
+ }
+ } else if (matches(*argv, "csum") == 0) {
+ csum = 1;
+ } else if (matches(*argv, "nocsum") == 0) {
+ csum = 0;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+ NEXT_ARG_FWD();
+ }
+
+ addattr8(n, MAX_MSG, TCA_TUNNEL_KEY_NO_CSUM, !csum);
+
+ parse_action_control_dflt(&argc, &argv, &parm.action,
+ false, TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&parm.index, *argv, 10)) {
+ fprintf(stderr, "tunnel_key: Illegal \"index\"\n");
+ return -1;
+ }
+
+ NEXT_ARG_FWD();
+ }
+ }
+
+ if (action == TCA_TUNNEL_KEY_ACT_SET &&
+ (!has_src_ip || !has_dst_ip)) {
+ fprintf(stderr, "set needs tunnel_key parameters\n");
+ explain();
+ return -1;
+ }
+
+ parm.t_action = action;
+ addattr_l(n, MAX_MSG, TCA_TUNNEL_KEY_PARMS, &parm, sizeof(parm));
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+
+ return 0;
+}
+
+static void tunnel_key_print_ip_addr(FILE *f, const char *name,
+ struct rtattr *attr)
+{
+ int family;
+ size_t len;
+
+ if (!attr)
+ return;
+
+ len = RTA_PAYLOAD(attr);
+
+ if (len == 4)
+ family = AF_INET;
+ else if (len == 16)
+ family = AF_INET6;
+ else
+ return;
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ if (matches(name, "src_ip") == 0)
+ print_string(PRINT_ANY, "src_ip", "\tsrc_ip %s",
+ rt_addr_n2a_rta(family, attr));
+ else if (matches(name, "dst_ip") == 0)
+ print_string(PRINT_ANY, "dst_ip", "\tdst_ip %s",
+ rt_addr_n2a_rta(family, attr));
+}
+
+static void tunnel_key_print_key_id(FILE *f, const char *name,
+ struct rtattr *attr)
+{
+ if (!attr)
+ return;
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "key_id", "\tkey_id %u", rta_getattr_be32(attr));
+}
+
+static void tunnel_key_print_dst_port(FILE *f, char *name,
+ struct rtattr *attr)
+{
+ if (!attr)
+ return;
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "dst_port", "\tdst_port %u",
+ rta_getattr_be16(attr));
+}
+
+static void tunnel_key_print_flag(FILE *f, const char *name_on,
+ const char *name_off,
+ struct rtattr *attr)
+{
+ if (!attr)
+ return;
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_string(PRINT_ANY, "flag", "\t%s",
+ rta_getattr_u8(attr) ? name_on : name_off);
+}
+
+static void tunnel_key_print_geneve_options(const char *name,
+ struct rtattr *attr)
+{
+ struct rtattr *tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX + 1];
+ struct rtattr *i = RTA_DATA(attr);
+ int ii, data_len = 0, offset = 0;
+ int rem = RTA_PAYLOAD(attr);
+ char strbuf[rem * 2 + 1];
+ char data[rem * 2 + 1];
+ uint8_t data_r[rem];
+ uint16_t clss;
+ uint8_t type;
+
+ open_json_array(PRINT_JSON, name);
+ print_string(PRINT_FP, name, "\n\t%s ", "geneve_opt");
+
+ while (rem) {
+ parse_rtattr(tb, TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX, i, rem);
+ clss = rta_getattr_be16(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS]);
+ type = rta_getattr_u8(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE]);
+ data_len = RTA_PAYLOAD(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA]);
+ hexstring_n2a(RTA_DATA(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA]),
+ data_len, data, sizeof(data));
+ hex2mem(data, data_r, data_len);
+ offset += data_len + 20;
+ rem -= data_len + 20;
+ i = RTA_DATA(attr) + offset;
+
+ open_json_object(NULL);
+ print_uint(PRINT_JSON, "class", NULL, clss);
+ print_uint(PRINT_JSON, "type", NULL, type);
+ open_json_array(PRINT_JSON, "data");
+ for (ii = 0; ii < data_len; ii++)
+ print_uint(PRINT_JSON, NULL, NULL, data_r[ii]);
+ close_json_array(PRINT_JSON, "data");
+ close_json_object();
+
+ sprintf(strbuf, "%04x:%02x:%s", clss, type, data);
+ if (rem)
+ print_string(PRINT_FP, NULL, "%s,", strbuf);
+ else
+ print_string(PRINT_FP, NULL, "%s", strbuf);
+ }
+
+ close_json_array(PRINT_JSON, name);
+}
+
+static void tunnel_key_print_key_opt(const char *name, struct rtattr *attr)
+{
+ struct rtattr *tb[TCA_TUNNEL_KEY_ENC_OPTS_MAX + 1];
+
+ if (!attr)
+ return;
+
+ parse_rtattr_nested(tb, TCA_TUNNEL_KEY_ENC_OPTS_MAX, attr);
+ tunnel_key_print_geneve_options(name,
+ tb[TCA_TUNNEL_KEY_ENC_OPTS_GENEVE]);
+}
+
+static void tunnel_key_print_tos_ttl(FILE *f, char *name,
+ struct rtattr *attr)
+{
+ if (!attr)
+ return;
+
+ if (matches(name, "tos") == 0 && rta_getattr_u8(attr) != 0) {
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "tos", "\ttos 0x%x",
+ rta_getattr_u8(attr));
+ } else if (matches(name, "ttl") == 0 && rta_getattr_u8(attr) != 0) {
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "ttl", "\tttl %u",
+ rta_getattr_u8(attr));
+ }
+}
+
+static int print_tunnel_key(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct rtattr *tb[TCA_TUNNEL_KEY_MAX + 1];
+ struct tc_tunnel_key *parm;
+
+ if (!arg)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_TUNNEL_KEY_MAX, arg);
+
+ if (!tb[TCA_TUNNEL_KEY_PARMS]) {
+ fprintf(stderr, "Missing tunnel_key parameters\n");
+ return -1;
+ }
+ parm = RTA_DATA(tb[TCA_TUNNEL_KEY_PARMS]);
+
+ print_string(PRINT_ANY, "kind", "%s ", "tunnel_key");
+
+ switch (parm->t_action) {
+ case TCA_TUNNEL_KEY_ACT_RELEASE:
+ print_string(PRINT_ANY, "mode", " %s", "unset");
+ break;
+ case TCA_TUNNEL_KEY_ACT_SET:
+ print_string(PRINT_ANY, "mode", " %s", "set");
+ tunnel_key_print_ip_addr(f, "src_ip",
+ tb[TCA_TUNNEL_KEY_ENC_IPV4_SRC]);
+ tunnel_key_print_ip_addr(f, "dst_ip",
+ tb[TCA_TUNNEL_KEY_ENC_IPV4_DST]);
+ tunnel_key_print_ip_addr(f, "src_ip",
+ tb[TCA_TUNNEL_KEY_ENC_IPV6_SRC]);
+ tunnel_key_print_ip_addr(f, "dst_ip",
+ tb[TCA_TUNNEL_KEY_ENC_IPV6_DST]);
+ tunnel_key_print_key_id(f, "key_id",
+ tb[TCA_TUNNEL_KEY_ENC_KEY_ID]);
+ tunnel_key_print_dst_port(f, "dst_port",
+ tb[TCA_TUNNEL_KEY_ENC_DST_PORT]);
+ tunnel_key_print_key_opt("geneve_opts",
+ tb[TCA_TUNNEL_KEY_ENC_OPTS]);
+ tunnel_key_print_flag(f, "nocsum", "csum",
+ tb[TCA_TUNNEL_KEY_NO_CSUM]);
+ tunnel_key_print_tos_ttl(f, "tos",
+ tb[TCA_TUNNEL_KEY_ENC_TOS]);
+ tunnel_key_print_tos_ttl(f, "ttl",
+ tb[TCA_TUNNEL_KEY_ENC_TTL]);
+ break;
+ }
+ print_action_control(f, " ", parm->action, "");
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_uint(PRINT_ANY, "index", "\t index %u", parm->index);
+ print_int(PRINT_ANY, "ref", " ref %d", parm->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", parm->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_TUNNEL_KEY_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_TUNNEL_KEY_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+
+ return 0;
+}
+
+struct action_util tunnel_key_action_util = {
+ .id = "tunnel_key",
+ .parse_aopt = parse_tunnel_key,
+ .print_aopt = print_tunnel_key,
+};
diff --git a/tc/m_vlan.c b/tc/m_vlan.c
new file mode 100644
index 0000000..9c8071e
--- /dev/null
+++ b/tc/m_vlan.c
@@ -0,0 +1,243 @@
+/*
+ * m_vlan.c vlan manipulation 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: Jiri Pirko <jiri@resnulli.us>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <linux/if_ether.h>
+#include "utils.h"
+#include "rt_names.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_vlan.h>
+
+static const char * const action_names[] = {
+ [TCA_VLAN_ACT_POP] = "pop",
+ [TCA_VLAN_ACT_PUSH] = "push",
+ [TCA_VLAN_ACT_MODIFY] = "modify",
+};
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: vlan pop\n"
+ " vlan push [ protocol VLANPROTO ] id VLANID [ priority VLANPRIO ] [CONTROL]\n"
+ " vlan modify [ protocol VLANPROTO ] id VLANID [ priority VLANPRIO ] [CONTROL]\n"
+ " VLANPROTO is one of 802.1Q or 802.1AD\n"
+ " with default: 802.1Q\n"
+ " CONTROL := reclassify | pipe | drop | continue | pass |\n"
+ " goto chain <CHAIN_INDEX>\n");
+}
+
+static void usage(void)
+{
+ explain();
+ exit(-1);
+}
+
+static bool has_push_attribs(int action)
+{
+ return action == TCA_VLAN_ACT_PUSH || action == TCA_VLAN_ACT_MODIFY;
+}
+
+static void unexpected(const char *arg)
+{
+ fprintf(stderr,
+ "unexpected \"%s\" - action already specified\n",
+ arg);
+ explain();
+}
+
+static int parse_vlan(struct action_util *a, int *argc_p, char ***argv_p,
+ int tca_id, struct nlmsghdr *n)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ struct rtattr *tail;
+ int action = 0;
+ __u16 id;
+ int id_set = 0;
+ __u16 proto;
+ int proto_set = 0;
+ __u8 prio;
+ int prio_set = 0;
+ struct tc_vlan parm = {};
+
+ if (matches(*argv, "vlan") != 0)
+ return -1;
+
+ NEXT_ARG();
+
+ while (argc > 0) {
+ if (matches(*argv, "pop") == 0) {
+ if (action) {
+ unexpected(*argv);
+ return -1;
+ }
+ action = TCA_VLAN_ACT_POP;
+ } else if (matches(*argv, "push") == 0) {
+ if (action) {
+ unexpected(*argv);
+ return -1;
+ }
+ action = TCA_VLAN_ACT_PUSH;
+ } else if (matches(*argv, "modify") == 0) {
+ if (action) {
+ unexpected(*argv);
+ return -1;
+ }
+ action = TCA_VLAN_ACT_MODIFY;
+ } else if (matches(*argv, "id") == 0) {
+ if (!has_push_attribs(action))
+ invarg("only valid for push/modify", *argv);
+
+ NEXT_ARG();
+ if (get_u16(&id, *argv, 0))
+ invarg("id is invalid", *argv);
+ id_set = 1;
+ } else if (matches(*argv, "protocol") == 0) {
+ if (!has_push_attribs(action))
+ invarg("only valid for push/modify", *argv);
+
+ NEXT_ARG();
+ if (ll_proto_a2n(&proto, *argv))
+ invarg("protocol is invalid", *argv);
+ proto_set = 1;
+ } else if (matches(*argv, "priority") == 0) {
+ if (!has_push_attribs(action))
+ invarg("only valid for push/modify", *argv);
+
+ NEXT_ARG();
+ if (get_u8(&prio, *argv, 0) || (prio & ~0x7))
+ invarg("prio is invalid", *argv);
+ prio_set = 1;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+ argc--;
+ argv++;
+ }
+
+ parse_action_control_dflt(&argc, &argv, &parm.action,
+ false, TC_ACT_PIPE);
+
+ if (argc) {
+ if (matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&parm.index, *argv, 10)) {
+ fprintf(stderr, "vlan: Illegal \"index\"\n");
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+ }
+
+ if (has_push_attribs(action) && !id_set) {
+ fprintf(stderr, "id needs to be set for %s\n",
+ action_names[action]);
+ explain();
+ return -1;
+ }
+
+ parm.v_action = action;
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ addattr_l(n, MAX_MSG, TCA_VLAN_PARMS, &parm, sizeof(parm));
+ if (id_set)
+ addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_ID, &id, 2);
+ if (proto_set) {
+ if (proto != htons(ETH_P_8021Q) &&
+ proto != htons(ETH_P_8021AD)) {
+ fprintf(stderr, "protocol not supported\n");
+ explain();
+ return -1;
+ }
+
+ addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_PROTOCOL, &proto, 2);
+ }
+ if (prio_set)
+ addattr8(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_PRIORITY, prio);
+
+ addattr_nest_end(n, tail);
+
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+static int print_vlan(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ SPRINT_BUF(b1);
+ struct rtattr *tb[TCA_VLAN_MAX + 1];
+ __u16 val;
+ struct tc_vlan *parm;
+
+ if (arg == NULL)
+ return -1;
+
+ parse_rtattr_nested(tb, TCA_VLAN_MAX, arg);
+
+ if (!tb[TCA_VLAN_PARMS]) {
+ fprintf(stderr, "Missing vlanparameters\n");
+ return -1;
+ }
+ parm = RTA_DATA(tb[TCA_VLAN_PARMS]);
+
+ print_string(PRINT_ANY, "kind", "%s ", "vlan");
+ print_string(PRINT_ANY, "vlan_action", " %s",
+ action_names[parm->v_action]);
+
+ switch (parm->v_action) {
+ case TCA_VLAN_ACT_PUSH:
+ case TCA_VLAN_ACT_MODIFY:
+ if (tb[TCA_VLAN_PUSH_VLAN_ID]) {
+ val = rta_getattr_u16(tb[TCA_VLAN_PUSH_VLAN_ID]);
+ print_uint(PRINT_ANY, "id", " id %u", val);
+ }
+ if (tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]) {
+ __u16 proto;
+
+ proto = rta_getattr_u16(tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]);
+ print_string(PRINT_ANY, "protocol", " protocol %s",
+ ll_proto_n2a(proto, b1, sizeof(b1)));
+ }
+ if (tb[TCA_VLAN_PUSH_VLAN_PRIORITY]) {
+ val = rta_getattr_u8(tb[TCA_VLAN_PUSH_VLAN_PRIORITY]);
+ print_uint(PRINT_ANY, "priority", " priority %u", val);
+ }
+ break;
+ }
+ print_action_control(f, " ", parm->action, "");
+
+ print_uint(PRINT_ANY, "index", "\n\t index %u", parm->index);
+ print_int(PRINT_ANY, "ref", " ref %d", parm->refcnt);
+ print_int(PRINT_ANY, "bind", " bind %d", parm->bindcnt);
+
+ if (show_stats) {
+ if (tb[TCA_VLAN_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_VLAN_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+
+ print_string(PRINT_FP, NULL, "%s", "\n");
+
+ return 0;
+}
+
+struct action_util vlan_action_util = {
+ .id = "vlan",
+ .parse_aopt = parse_vlan,
+ .print_aopt = print_vlan,
+};
diff --git a/tc/m_xt.c b/tc/m_xt.c
new file mode 100644
index 0000000..bf0db2b
--- /dev/null
+++ b/tc/m_xt.c
@@ -0,0 +1,405 @@
+/*
+ * m_xt.c xtables based targets
+ * utilities mostly ripped from iptables <duh, its the linux way>
+ *
+ * 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)
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <limits.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <xtables.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_ipt.h>
+#include <stdio.h>
+#include <dlfcn.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#ifndef XT_LIB_DIR
+# define XT_LIB_DIR "/lib/xtables"
+#endif
+
+#ifndef __ALIGN_KERNEL
+#define __ALIGN_KERNEL(x, a) \
+ __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
+#define __ALIGN_KERNEL_MASK(x, mask) \
+ (((x) + (mask)) & ~(mask))
+#endif
+
+#ifndef ALIGN
+#define ALIGN(x, a) __ALIGN_KERNEL((x), (a))
+#endif
+
+static const char *tname = "mangle";
+
+char *lib_dir;
+
+static const char * const ipthooks[] = {
+ "NF_IP_PRE_ROUTING",
+ "NF_IP_LOCAL_IN",
+ "NF_IP_FORWARD",
+ "NF_IP_LOCAL_OUT",
+ "NF_IP_POST_ROUTING",
+};
+
+static struct option original_opts[] = {
+ {
+ .name = "jump",
+ .has_arg = 1,
+ .val = 'j'
+ },
+ {0, 0, 0, 0}
+};
+
+static struct xtables_globals tcipt_globals = {
+ .option_offset = 0,
+ .program_name = "tc-ipt",
+ .program_version = "0.2",
+ .orig_opts = original_opts,
+ .opts = original_opts,
+ .exit_err = NULL,
+#if XTABLES_VERSION_CODE >= 11
+ .compat_rev = xtables_compatible_revision,
+#endif
+};
+
+/*
+ * we may need to check for version mismatch
+*/
+static int
+build_st(struct xtables_target *target, struct xt_entry_target *t)
+{
+
+ size_t size =
+ XT_ALIGN(sizeof(struct xt_entry_target)) + target->size;
+
+ if (t == NULL) {
+ target->t = xtables_calloc(1, size);
+ target->t->u.target_size = size;
+ strncpy(target->t->u.user.name, target->name,
+ sizeof(target->t->u.user.name) - 1);
+ target->t->u.user.revision = target->revision;
+
+ if (target->init != NULL)
+ target->init(target->t);
+ } else {
+ target->t = t;
+ }
+ return 0;
+
+}
+
+static void set_lib_dir(void)
+{
+
+ lib_dir = getenv("XTABLES_LIBDIR");
+ if (!lib_dir) {
+ lib_dir = getenv("IPTABLES_LIB_DIR");
+ if (lib_dir)
+ fprintf(stderr, "using deprecated IPTABLES_LIB_DIR\n");
+ }
+ if (lib_dir == NULL)
+ lib_dir = XT_LIB_DIR;
+
+}
+
+static int get_xtables_target_opts(struct xtables_globals *globals,
+ struct xtables_target *m)
+{
+ struct option *opts;
+
+#if XTABLES_VERSION_CODE >= 6
+ opts = xtables_options_xfrm(globals->orig_opts,
+ globals->opts,
+ m->x6_options,
+ &m->option_offset);
+#else
+ opts = xtables_merge_options(globals->opts,
+ m->extra_opts,
+ &m->option_offset);
+#endif
+ if (!opts)
+ return -1;
+ globals->opts = opts;
+ return 0;
+}
+
+static int parse_ipt(struct action_util *a, int *argc_p,
+ char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+ struct xtables_target *m = NULL;
+#if XTABLES_VERSION_CODE >= 6
+ struct ipt_entry fw = {};
+#endif
+ struct rtattr *tail;
+
+ int c;
+ char **argv = *argv_p;
+ int argc;
+ char k[FILTER_NAMESZ];
+ int size = 0;
+ int iok = 0, ok = 0;
+ __u32 hook = 0, index = 0;
+
+ /* copy tcipt_globals because .opts will be modified by iptables */
+ struct xtables_globals tmp_tcipt_globals = tcipt_globals;
+
+ xtables_init_all(&tmp_tcipt_globals, NFPROTO_IPV4);
+ set_lib_dir();
+
+ /* parse only up until the next action */
+ for (argc = 0; argc < *argc_p; argc++) {
+ if (!argv[argc] || !strcmp(argv[argc], "action"))
+ break;
+ }
+
+ if (argc <= 2) {
+ fprintf(stderr,
+ "too few arguments for xt, need at least '-j <target>'\n");
+ return -1;
+ }
+
+ while (1) {
+ c = getopt_long(argc, argv, "j:", tmp_tcipt_globals.opts, NULL);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'j':
+ m = xtables_find_target(optarg, XTF_TRY_LOAD);
+ if (!m) {
+ fprintf(stderr,
+ " failed to find target %s\n\n",
+ optarg);
+ return -1;
+ }
+
+ if (build_st(m, NULL) < 0) {
+ printf(" %s error\n", m->name);
+ return -1;
+ }
+
+ if (get_xtables_target_opts(&tmp_tcipt_globals,
+ m) < 0) {
+ fprintf(stderr,
+ " failed to find additional options for target %s\n\n",
+ optarg);
+ return -1;
+ }
+ ok++;
+ break;
+
+ default:
+#if XTABLES_VERSION_CODE >= 6
+ if (m != NULL && m->x6_parse != NULL) {
+ xtables_option_tpcall(c, argv, 0, m, &fw);
+#else
+ if (m != NULL && m->parse != NULL) {
+ m->parse(c - m->option_offset, argv, 0,
+ &m->tflags, NULL, &m->t);
+#endif
+ } else {
+ fprintf(stderr,
+ "failed to find target %s\n\n", optarg);
+ return -1;
+
+ }
+ ok++;
+ break;
+ }
+ }
+
+ if (argc > optind) {
+ if (matches(argv[optind], "index") == 0) {
+ if (get_u32(&index, argv[optind + 1], 10)) {
+ fprintf(stderr, "Illegal \"index\"\n");
+ xtables_free_opts(1);
+ return -1;
+ }
+ iok++;
+
+ optind += 2;
+ }
+ }
+
+ if (!ok && !iok) {
+ fprintf(stderr, " ipt Parser BAD!! (%s)\n", *argv);
+ return -1;
+ }
+
+ /* check that we passed the correct parameters to the target */
+#if XTABLES_VERSION_CODE >= 6
+ if (m)
+ xtables_option_tfcall(m);
+#else
+ if (m && m->final_check)
+ m->final_check(m->tflags);
+#endif
+
+ {
+ struct tcmsg *t = NLMSG_DATA(n);
+
+ if (t->tcm_parent != TC_H_ROOT
+ && t->tcm_parent == TC_H_MAJ(TC_H_INGRESS)) {
+ hook = NF_IP_PRE_ROUTING;
+ } else {
+ hook = NF_IP_POST_ROUTING;
+ }
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ fprintf(stdout, "tablename: %s hook: %s\n ", tname, ipthooks[hook]);
+ fprintf(stdout, "\ttarget: ");
+
+ if (m) {
+ if (m->print)
+ m->print(NULL, m->t, 0);
+ else
+ printf("%s ", m->name);
+ }
+ fprintf(stdout, " index %d\n", index);
+
+ if (strlen(tname) >= 16) {
+ size = 15;
+ k[15] = 0;
+ } else {
+ size = 1 + strlen(tname);
+ }
+ strncpy(k, tname, size);
+
+ addattr_l(n, MAX_MSG, TCA_IPT_TABLE, k, size);
+ addattr_l(n, MAX_MSG, TCA_IPT_HOOK, &hook, 4);
+ addattr_l(n, MAX_MSG, TCA_IPT_INDEX, &index, 4);
+ if (m)
+ addattr_l(n, MAX_MSG, TCA_IPT_TARG, m->t, m->t->u.target_size);
+ addattr_nest_end(n, tail);
+
+ argv += optind;
+ *argc_p -= argc;
+ *argv_p = argv;
+
+ optind = 0;
+ xtables_free_opts(1);
+
+ if (m) {
+ /* Clear flags if target will be used again */
+ m->tflags = 0;
+ m->used = 0;
+ /* Free allocated memory */
+ if (m->t)
+ free(m->t);
+ }
+
+ return 0;
+
+}
+
+static int
+print_ipt(struct action_util *au, FILE *f, struct rtattr *arg)
+{
+ struct xtables_target *m;
+ struct rtattr *tb[TCA_IPT_MAX + 1];
+ struct xt_entry_target *t = NULL;
+ __u32 hook;
+
+ if (arg == NULL)
+ return -1;
+
+ /* copy tcipt_globals because .opts will be modified by iptables */
+ struct xtables_globals tmp_tcipt_globals = tcipt_globals;
+
+ xtables_init_all(&tmp_tcipt_globals, NFPROTO_IPV4);
+ set_lib_dir();
+
+ parse_rtattr_nested(tb, TCA_IPT_MAX, arg);
+
+ if (tb[TCA_IPT_TABLE] == NULL) {
+ fprintf(stderr, "Missing ipt table name, assuming mangle\n");
+ } else {
+ fprintf(f, "tablename: %s ",
+ rta_getattr_str(tb[TCA_IPT_TABLE]));
+ }
+
+ if (tb[TCA_IPT_HOOK] == NULL) {
+ fprintf(stderr, "Missing ipt hook name\n ");
+ return -1;
+ }
+
+ if (tb[TCA_IPT_TARG] == NULL) {
+ fprintf(stderr, "Missing ipt target parameters\n");
+ return -1;
+ }
+
+ hook = rta_getattr_u32(tb[TCA_IPT_HOOK]);
+ fprintf(f, " hook: %s\n", ipthooks[hook]);
+
+ t = RTA_DATA(tb[TCA_IPT_TARG]);
+ m = xtables_find_target(t->u.user.name, XTF_TRY_LOAD);
+ if (!m) {
+ fprintf(stderr, " failed to find target %s\n\n",
+ t->u.user.name);
+ return -1;
+ }
+ if (build_st(m, t) < 0) {
+ fprintf(stderr, " %s error\n", m->name);
+ return -1;
+ }
+
+ if (get_xtables_target_opts(&tmp_tcipt_globals, m) < 0) {
+ fprintf(stderr,
+ " failed to find additional options for target %s\n\n",
+ t->u.user.name);
+ return -1;
+ }
+ fprintf(f, "\ttarget ");
+ m->print(NULL, m->t, 0);
+ if (tb[TCA_IPT_INDEX] == NULL) {
+ fprintf(f, " [NULL ipt target index ]\n");
+ } else {
+ __u32 index;
+
+ index = rta_getattr_u32(tb[TCA_IPT_INDEX]);
+ fprintf(f, "\n\tindex %u", index);
+ }
+
+ if (tb[TCA_IPT_CNT]) {
+ struct tc_cnt *c = RTA_DATA(tb[TCA_IPT_CNT]);
+
+ fprintf(f, " ref %d bind %d", c->refcnt, c->bindcnt);
+ }
+ if (show_stats) {
+ if (tb[TCA_IPT_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_IPT_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ fprintf(f, "\n");
+
+ xtables_free_opts(1);
+
+ return 0;
+}
+
+struct action_util xt_action_util = {
+ .id = "xt",
+ .parse_aopt = parse_ipt,
+ .print_aopt = print_ipt,
+};
diff --git a/tc/m_xt_old.c b/tc/m_xt_old.c
new file mode 100644
index 0000000..6a4509a
--- /dev/null
+++ b/tc/m_xt_old.c
@@ -0,0 +1,437 @@
+/*
+ * m_xt.c xtables based targets
+ * utilities mostly ripped from iptables <duh, its the linux way>
+ *
+ * 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)
+ */
+
+/*XXX: in the future (xtables 1.4.3?) get rid of everything tagged
+ * as TC_CONFIG_XT_H */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <xtables.h>
+#include "utils.h"
+#include "tc_util.h"
+#include <linux/tc_act/tc_ipt.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#ifdef TC_CONFIG_XT_H
+#include "xt-internal.h"
+#endif
+
+#ifndef ALIGN
+#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a)-1)
+#define __ALIGN_MASK(x, mask) (((x)+(mask))&~(mask))
+#endif
+
+static const char *pname = "tc-ipt";
+static const char *tname = "mangle";
+static const char *pversion = "0.2";
+
+static const char *ipthooks[] = {
+ "NF_IP_PRE_ROUTING",
+ "NF_IP_LOCAL_IN",
+ "NF_IP_FORWARD",
+ "NF_IP_LOCAL_OUT",
+ "NF_IP_POST_ROUTING",
+};
+
+static struct option original_opts[] = {
+ {"jump", 1, 0, 'j'},
+ {0, 0, 0, 0}
+};
+
+static struct option *opts = original_opts;
+static unsigned int global_option_offset;
+char *lib_dir;
+const char *program_version = XTABLES_VERSION;
+const char *program_name = "tc-ipt";
+struct afinfo afinfo = {
+ .family = AF_INET,
+ .libprefix = "libxt_",
+ .ipproto = IPPROTO_IP,
+ .kmod = "ip_tables",
+ .so_rev_target = IPT_SO_GET_REVISION_TARGET,
+};
+
+
+#define OPTION_OFFSET 256
+
+/*XXX: TC_CONFIG_XT_H */
+static void free_opts(struct option *local_opts)
+{
+ if (local_opts != original_opts) {
+ free(local_opts);
+ opts = original_opts;
+ global_option_offset = 0;
+ }
+}
+
+/*XXX: TC_CONFIG_XT_H */
+static struct option *
+merge_options(struct option *oldopts, const struct option *newopts,
+ unsigned int *option_offset)
+{
+ struct option *merge;
+ unsigned int num_old, num_new, i;
+
+ for (num_old = 0; oldopts[num_old].name; num_old++);
+ for (num_new = 0; newopts[num_new].name; num_new++);
+
+ *option_offset = global_option_offset + OPTION_OFFSET;
+
+ merge = malloc(sizeof(struct option) * (num_new + num_old + 1));
+ memcpy(merge, oldopts, num_old * sizeof(struct option));
+ for (i = 0; i < num_new; i++) {
+ merge[num_old + i] = newopts[i];
+ merge[num_old + i].val += *option_offset;
+ }
+ memset(merge + num_old + num_new, 0, sizeof(struct option));
+
+ return merge;
+}
+
+
+/*XXX: TC_CONFIG_XT_H */
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+/*XXX: TC_CONFIG_XT_H */
+int
+check_inverse(const char option[], int *invert, int *my_optind, int argc)
+{
+ if (option && strcmp(option, "!") == 0) {
+ if (*invert)
+ exit_error(PARAMETER_PROBLEM,
+ "Multiple `!' flags not allowed");
+ *invert = TRUE;
+ if (my_optind != NULL) {
+ ++*my_optind;
+ if (argc && *my_optind > argc)
+ exit_error(PARAMETER_PROBLEM,
+ "no argument following `!'");
+ }
+
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*XXX: TC_CONFIG_XT_H */
+void exit_error(enum exittype status, const char *msg, ...)
+{
+ va_list args;
+
+ va_start(args, msg);
+ fprintf(stderr, "%s v%s: ", pname, pversion);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+ /* On error paths, make sure that we don't leak memory */
+ exit(status);
+}
+
+/*XXX: TC_CONFIG_XT_H */
+static void set_revision(char *name, u_int8_t revision)
+{
+ /* Old kernel sources don't have ".revision" field,
+ * but we stole a byte from name. */
+ name[IPT_FUNCTION_MAXNAMELEN - 2] = '\0';
+ name[IPT_FUNCTION_MAXNAMELEN - 1] = revision;
+}
+
+/*
+ * we may need to check for version mismatch
+*/
+int
+build_st(struct xtables_target *target, struct xt_entry_target *t)
+{
+
+ size_t size =
+ XT_ALIGN(sizeof(struct xt_entry_target)) + target->size;
+
+ if (t == NULL) {
+ target->t = fw_calloc(1, size);
+ target->t->u.target_size = size;
+ strcpy(target->t->u.user.name, target->name);
+ set_revision(target->t->u.user.name, target->revision);
+
+ if (target->init != NULL)
+ target->init(target->t);
+ } else {
+ target->t = t;
+ }
+ return 0;
+
+}
+
+inline void set_lib_dir(void)
+{
+
+ lib_dir = getenv("XTABLES_LIBDIR");
+ if (!lib_dir) {
+ lib_dir = getenv("IPTABLES_LIB_DIR");
+ if (lib_dir)
+ fprintf(stderr, "using deprecated IPTABLES_LIB_DIR\n");
+ }
+ if (lib_dir == NULL)
+ lib_dir = XT_LIB_DIR;
+
+}
+
+static int parse_ipt(struct action_util *a, int *argc_p,
+ char ***argv_p, int tca_id, struct nlmsghdr *n)
+{
+ struct xtables_target *m = NULL;
+ struct ipt_entry fw;
+ struct rtattr *tail;
+ int c;
+ int rargc = *argc_p;
+ char **argv = *argv_p;
+ int argc = 0, iargc = 0;
+ char k[FILTER_NAMESZ];
+ int size = 0;
+ int iok = 0, ok = 0;
+ __u32 hook = 0, index = 0;
+
+ set_lib_dir();
+
+ {
+ int i;
+
+ for (i = 0; i < rargc; i++) {
+ if (!argv[i] || strcmp(argv[i], "action") == 0)
+ break;
+ }
+ iargc = argc = i;
+ }
+
+ if (argc <= 2) {
+ fprintf(stderr, "bad arguments to ipt %d vs %d\n", argc, rargc);
+ return -1;
+ }
+
+ while (1) {
+ c = getopt_long(argc, argv, "j:", opts, NULL);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'j':
+ m = find_target(optarg, TRY_LOAD);
+ if (m != NULL) {
+
+ if (build_st(m, NULL) < 0) {
+ printf(" %s error\n", m->name);
+ return -1;
+ }
+ opts =
+ merge_options(opts, m->extra_opts,
+ &m->option_offset);
+ } else {
+ fprintf(stderr, " failed to find target %s\n\n", optarg);
+ return -1;
+ }
+ ok++;
+ break;
+
+ default:
+ memset(&fw, 0, sizeof(fw));
+ if (m) {
+ m->parse(c - m->option_offset, argv, 0,
+ &m->tflags, NULL, &m->t);
+ } else {
+ fprintf(stderr, " failed to find target %s\n\n", optarg);
+ return -1;
+
+ }
+ ok++;
+ break;
+
+ }
+ }
+
+ if (iargc > optind) {
+ if (matches(argv[optind], "index") == 0) {
+ if (get_u32(&index, argv[optind + 1], 10)) {
+ fprintf(stderr, "Illegal \"index\"\n");
+ free_opts(opts);
+ return -1;
+ }
+ iok++;
+
+ optind += 2;
+ }
+ }
+
+ if (!ok && !iok) {
+ fprintf(stderr, " ipt Parser BAD!! (%s)\n", *argv);
+ return -1;
+ }
+
+ /* check that we passed the correct parameters to the target */
+ if (m)
+ m->final_check(m->tflags);
+
+ {
+ struct tcmsg *t = NLMSG_DATA(n);
+
+ if (t->tcm_parent != TC_H_ROOT
+ && t->tcm_parent == TC_H_MAJ(TC_H_INGRESS)) {
+ hook = NF_IP_PRE_ROUTING;
+ } else {
+ hook = NF_IP_POST_ROUTING;
+ }
+ }
+
+ tail = addattr_nest(n, MAX_MSG, tca_id);
+ fprintf(stdout, "tablename: %s hook: %s\n ", tname, ipthooks[hook]);
+ fprintf(stdout, "\ttarget: ");
+
+ if (m)
+ m->print(NULL, m->t, 0);
+ fprintf(stdout, " index %d\n", index);
+
+ if (strlen(tname) > 16) {
+ size = 16;
+ k[15] = 0;
+ } else {
+ size = 1 + strlen(tname);
+ }
+ strncpy(k, tname, size);
+
+ addattr_l(n, MAX_MSG, TCA_IPT_TABLE, k, size);
+ addattr_l(n, MAX_MSG, TCA_IPT_HOOK, &hook, 4);
+ addattr_l(n, MAX_MSG, TCA_IPT_INDEX, &index, 4);
+ if (m)
+ addattr_l(n, MAX_MSG, TCA_IPT_TARG, m->t, m->t->u.target_size);
+ addattr_nest_end(n, tail);
+
+ argc -= optind;
+ argv += optind;
+ *argc_p = rargc - iargc;
+ *argv_p = argv;
+
+ optind = 0;
+ free_opts(opts);
+ /* Clear flags if target will be used again */
+ m->tflags = 0;
+ m->used = 0;
+ /* Free allocated memory */
+ if (m->t)
+ free(m->t);
+
+
+ return 0;
+
+}
+
+static int
+print_ipt(struct action_util *au, FILE * f, struct rtattr *arg)
+{
+ struct rtattr *tb[TCA_IPT_MAX + 1];
+ struct xt_entry_target *t = NULL;
+ struct xtables_target *m;
+ __u32 hook;
+
+ if (arg == NULL)
+ return -1;
+
+ set_lib_dir();
+
+ parse_rtattr_nested(tb, TCA_IPT_MAX, arg);
+
+ if (tb[TCA_IPT_TABLE] == NULL) {
+ fprintf(stderr, "Missing ipt table name, assuming mangle\n");
+ } else {
+ fprintf(f, "tablename: %s ",
+ rta_getattr_str(tb[TCA_IPT_TABLE]));
+ }
+
+ if (tb[TCA_IPT_HOOK] == NULL) {
+ fprintf(stderr, "Missing ipt hook name\n");
+ return -1;
+ }
+
+ if (tb[TCA_IPT_TARG] == NULL) {
+ fprintf(stderr, "Missing ipt target parameters\n");
+ return -1;
+ }
+
+ hook = rta_getattr_u32(tb[TCA_IPT_HOOK]);
+ fprintf(f, " hook: %s\n", ipthooks[hook]);
+
+ t = RTA_DATA(tb[TCA_IPT_TARG]);
+ m = find_target(t->u.user.name, TRY_LOAD);
+ if (m != NULL) {
+ if (build_st(m, t) < 0) {
+ fprintf(stderr, " %s error\n", m->name);
+ return -1;
+ }
+
+ opts =
+ merge_options(opts, m->extra_opts,
+ &m->option_offset);
+ } else {
+ fprintf(stderr, " failed to find target %s\n\n",
+ t->u.user.name);
+ return -1;
+ }
+ fprintf(f, "\ttarget ");
+ m->print(NULL, m->t, 0);
+ if (tb[TCA_IPT_INDEX] == NULL) {
+ fprintf(f, " [NULL ipt target index ]\n");
+ } else {
+ __u32 index;
+
+ index = rta_getattr_u32(tb[TCA_IPT_INDEX]);
+ fprintf(f, "\n\tindex %u", index);
+ }
+
+ if (tb[TCA_IPT_CNT]) {
+ struct tc_cnt *c = RTA_DATA(tb[TCA_IPT_CNT]);
+
+ fprintf(f, " ref %d bind %d", c->refcnt, c->bindcnt);
+ }
+ if (show_stats) {
+ if (tb[TCA_IPT_TM]) {
+ struct tcf_t *tm = RTA_DATA(tb[TCA_IPT_TM]);
+
+ print_tm(f, tm);
+ }
+ }
+ fprintf(f, "\n");
+
+ free_opts(opts);
+
+ return 0;
+}
+
+struct action_util ipt_action_util = {
+ .id = "ipt",
+ .parse_aopt = parse_ipt,
+ .print_aopt = print_ipt,
+};
diff --git a/tc/p_eth.c b/tc/p_eth.c
new file mode 100644
index 0000000..674f9c1
--- /dev/null
+++ b/tc/p_eth.c
@@ -0,0 +1,73 @@
+/*
+ * m_pedit_eth.c packet editor: ETH header
+ *
+ * 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: Amir Vadai (amir@vadai.me)
+ *
+ */
+
+#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 "tc_util.h"
+#include "m_pedit.h"
+
+static int
+parse_eth(int *argc_p, char ***argv_p,
+ struct m_pedit_sel *sel, struct m_pedit_key *tkey)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ if (!sel->extended)
+ return -1;
+
+ tkey->htype = TCA_PEDIT_KEY_EX_HDR_TYPE_ETH;
+
+ if (strcmp(*argv, "type") == 0) {
+ NEXT_ARG();
+ tkey->off = 12;
+ res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey);
+ goto done;
+ }
+
+ if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+ tkey->off = 0;
+ res = parse_cmd(&argc, &argv, 6, TMAC, RU32, sel, tkey);
+ goto done;
+ }
+
+ if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+ tkey->off = 6;
+ res = parse_cmd(&argc, &argv, 6, TMAC, RU32, sel, tkey);
+ goto done;
+ }
+
+ return -1;
+
+done:
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+struct m_pedit_util p_pedit_eth = {
+ .id = "eth",
+ .parse_peopt = parse_eth,
+};
diff --git a/tc/p_icmp.c b/tc/p_icmp.c
new file mode 100644
index 0000000..15ce323
--- /dev/null
+++ b/tc/p_icmp.c
@@ -0,0 +1,60 @@
+/*
+ * m_pedit_icmp.c packet editor: ICMP header
+ *
+ * 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)
+ *
+ */
+
+#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 "tc_util.h"
+#include "m_pedit.h"
+
+
+static int
+parse_icmp(int *argc_p, char ***argv_p,
+ struct m_pedit_sel *sel, struct m_pedit_key *tkey)
+{
+ int res = -1;
+#if 0
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ if (strcmp(*argv, "type") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, 0);
+ goto done;
+ }
+ if (strcmp(*argv, "code") == 0) {
+ NEXT_ARG();
+ res = parse_u8(&argc, &argv, 1);
+ goto done;
+ }
+ return -1;
+
+done:
+ *argc_p = argc;
+ *argv_p = argv;
+#endif
+ return res;
+}
+
+struct m_pedit_util p_pedit_icmp = {
+ .id = "icmp",
+ .parse_peopt = parse_icmp,
+};
diff --git a/tc/p_ip.c b/tc/p_ip.c
new file mode 100644
index 0000000..c385ac6
--- /dev/null
+++ b/tc/p_ip.c
@@ -0,0 +1,161 @@
+/*
+ * p_ip.c packet editor: IPV4 header
+ *
+ * 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)
+ *
+ */
+
+#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 "tc_util.h"
+#include "m_pedit.h"
+
+static int
+parse_ip(int *argc_p, char ***argv_p,
+ struct m_pedit_sel *sel, struct m_pedit_key *tkey)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ tkey->htype = sel->extended ?
+ TCA_PEDIT_KEY_EX_HDR_TYPE_IP4 :
+ TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK;
+
+ if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+ tkey->off = 12;
+ res = parse_cmd(&argc, &argv, 4, TIPV4, RU32, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+ tkey->off = 16;
+ res = parse_cmd(&argc, &argv, 4, TIPV4, RU32, sel, tkey);
+ goto done;
+ }
+ /* jamal - look at these and make them either old or new
+ ** scheme given diffserv
+ ** don't forget the CE bit
+ */
+ if (strcmp(*argv, "tos") == 0 || matches(*argv, "dsfield") == 0) {
+ NEXT_ARG();
+ tkey->off = 1;
+ res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "ihl") == 0) {
+ NEXT_ARG();
+ tkey->off = 0;
+ res = parse_cmd(&argc, &argv, 1, TU32, 0x0f, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "ttl") == 0) {
+ NEXT_ARG();
+ tkey->off = 8;
+ res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "protocol") == 0) {
+ NEXT_ARG();
+ tkey->off = 9;
+ res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey);
+ goto done;
+ }
+ /* jamal - fix this */
+ if (matches(*argv, "precedence") == 0) {
+ NEXT_ARG();
+ tkey->off = 1;
+ res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey);
+ goto done;
+ }
+ /* jamal - validate this at some point */
+ if (strcmp(*argv, "nofrag") == 0) {
+ NEXT_ARG();
+ tkey->off = 6;
+ res = parse_cmd(&argc, &argv, 1, TU32, 0x3F, sel, tkey);
+ goto done;
+ }
+ /* jamal - validate this at some point */
+ if (strcmp(*argv, "firstfrag") == 0) {
+ NEXT_ARG();
+ tkey->off = 6;
+ res = parse_cmd(&argc, &argv, 1, TU32, 0x1F, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "ce") == 0) {
+ NEXT_ARG();
+ tkey->off = 6;
+ res = parse_cmd(&argc, &argv, 1, TU32, 0x80, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "df") == 0) {
+ NEXT_ARG();
+ tkey->off = 6;
+ res = parse_cmd(&argc, &argv, 1, TU32, 0x40, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "mf") == 0) {
+ NEXT_ARG();
+ tkey->off = 6;
+ res = parse_cmd(&argc, &argv, 1, TU32, 0x20, sel, tkey);
+ goto done;
+ }
+
+ if (sel->extended)
+ return -1; /* fields located outside IP header should be
+ * addressed using the relevant header type in
+ * extended pedit kABI
+ */
+
+ if (strcmp(*argv, "dport") == 0) {
+ NEXT_ARG();
+ tkey->off = 22;
+ res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "sport") == 0) {
+ NEXT_ARG();
+ tkey->off = 20;
+ res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "icmp_type") == 0) {
+ NEXT_ARG();
+ tkey->off = 20;
+ res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "icmp_code") == 0) {
+ NEXT_ARG();
+ tkey->off = 20;
+ res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey);
+ goto done;
+ }
+ return -1;
+
+done:
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+struct m_pedit_util p_pedit_ip = {
+ .id = "ip",
+ .parse_peopt = parse_ip,
+};
diff --git a/tc/p_ip6.c b/tc/p_ip6.c
new file mode 100644
index 0000000..7cc7997
--- /dev/null
+++ b/tc/p_ip6.c
@@ -0,0 +1,89 @@
+/*
+ * p_ip6.c packet editor: IPV6 header
+ *
+ * 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: Amir Vadai <amir@vadai.me>
+ *
+ */
+
+#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 "tc_util.h"
+#include "m_pedit.h"
+
+static int
+parse_ip6(int *argc_p, char ***argv_p,
+ struct m_pedit_sel *sel, struct m_pedit_key *tkey)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ if (!sel->extended)
+ return -1;
+
+ tkey->htype = TCA_PEDIT_KEY_EX_HDR_TYPE_IP6;
+
+ if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+ tkey->off = 8;
+ res = parse_cmd(&argc, &argv, 16, TIPV6, RU32, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+ tkey->off = 24;
+ res = parse_cmd(&argc, &argv, 16, TIPV6, RU32, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "flow_lbl") == 0) {
+ NEXT_ARG();
+ tkey->off = 0;
+ res = parse_cmd(&argc, &argv, 4, TU32, 0x0007ffff, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "payload_len") == 0) {
+ NEXT_ARG();
+ tkey->off = 4;
+ res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "nexthdr") == 0) {
+ NEXT_ARG();
+ tkey->off = 6;
+ res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey);
+ goto done;
+ }
+ if (strcmp(*argv, "hoplimit") == 0) {
+ NEXT_ARG();
+ tkey->off = 7;
+ res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey);
+ goto done;
+ }
+
+ return -1;
+
+done:
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+struct m_pedit_util p_pedit_ip6 = {
+ .id = "ip6",
+ .parse_peopt = parse_ip6,
+};
diff --git a/tc/p_tcp.c b/tc/p_tcp.c
new file mode 100644
index 0000000..d2dbfd7
--- /dev/null
+++ b/tc/p_tcp.c
@@ -0,0 +1,72 @@
+/*
+ * m_pedit_tcp.c packet editor: TCP header
+ *
+ * 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)
+ *
+ */
+
+#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 "tc_util.h"
+#include "m_pedit.h"
+
+static int
+parse_tcp(int *argc_p, char ***argv_p,
+ struct m_pedit_sel *sel, struct m_pedit_key *tkey)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ if (!sel->extended)
+ return -1;
+
+ tkey->htype = TCA_PEDIT_KEY_EX_HDR_TYPE_TCP;
+
+ if (strcmp(*argv, "sport") == 0) {
+ NEXT_ARG();
+ tkey->off = 0;
+ res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey);
+ goto done;
+ }
+
+ if (strcmp(*argv, "dport") == 0) {
+ NEXT_ARG();
+ tkey->off = 2;
+ res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey);
+ goto done;
+ }
+
+ if (strcmp(*argv, "flags") == 0) {
+ NEXT_ARG();
+ tkey->off = 13;
+ res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey);
+ goto done;
+ }
+
+ return -1;
+
+done:
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+struct m_pedit_util p_pedit_tcp = {
+ .id = "tcp",
+ .parse_peopt = parse_tcp,
+};
diff --git a/tc/p_udp.c b/tc/p_udp.c
new file mode 100644
index 0000000..bab456d
--- /dev/null
+++ b/tc/p_udp.c
@@ -0,0 +1,66 @@
+/*
+ * m_pedit_udp.c packet editor: UDP header
+ *
+ * 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)
+ *
+ */
+
+#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 "tc_util.h"
+#include "m_pedit.h"
+
+static int
+parse_udp(int *argc_p, char ***argv_p,
+ struct m_pedit_sel *sel, struct m_pedit_key *tkey)
+{
+ int res = -1;
+ int argc = *argc_p;
+ char **argv = *argv_p;
+
+ if (argc < 2)
+ return -1;
+
+ if (!sel->extended)
+ return -1;
+
+ tkey->htype = TCA_PEDIT_KEY_EX_HDR_TYPE_UDP;
+
+ if (strcmp(*argv, "sport") == 0) {
+ NEXT_ARG();
+ tkey->off = 0;
+ res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey);
+ goto done;
+ }
+
+ if (strcmp(*argv, "dport") == 0) {
+ NEXT_ARG();
+ tkey->off = 2;
+ res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey);
+ goto done;
+ }
+
+ return -1;
+
+done:
+ *argc_p = argc;
+ *argv_p = argv;
+ return res;
+}
+
+struct m_pedit_util p_pedit_udp = {
+ .id = "udp",
+ .parse_peopt = parse_udp,
+};
diff --git a/tc/q_arl.c b/tc/q_arl.c
new file mode 100644
index 0000000..4ba63a9
--- /dev/null
+++ b/tc/q_arl.c
@@ -0,0 +1,232 @@
+/*
+ * 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.
+ *
+ * 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.
+ *
+ * Copyright 2018 Google LLC.
+ * Author: Kan Yan <kyan@google.com>
+ *
+ */
+
+#include <stdio.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static const char * const arl_state_names[] = {
+ "STABLE",
+ "DRAIN",
+ "BW_PROBE",
+ "LATENCY_PROBE",
+ "UNTHROTTLED",
+ "UNDEFINED",
+};
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... arl [ limit PACKETS] [ buffer TIME ]");
+ fprintf(stderr, " [ minrate KBPS ] [ maxbw KBPS ]\n");
+ fprintf(stderr, " [ latency TIME ] ");
+ fprintf(stderr, "[ latency_hysteresis TIME ]\n");
+ fprintf(stderr, "[ codel_target TIME ]\n");
+ fprintf(stderr, "[ ingress ]\n");
+}
+
+static int arl_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+ unsigned int buffer = 0, limit = 0, latency = 0, latency_hysteresis = 0,
+ mode = 0, codel_target = 0;
+ __u64 min_rate = 0, max_bw = 0;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "buffer") == 0) {
+ NEXT_ARG();
+ if (get_time(&buffer, *argv)) {
+ fprintf(stderr, "Illegal \"buffer\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "minrate") == 0) {
+ NEXT_ARG();
+ if (get_rate64(&min_rate, *argv)) {
+ fprintf(stderr, "Illegal \"minrate\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "maxbw") == 0) {
+ NEXT_ARG();
+ if (get_rate64(&max_bw, *argv)) {
+ fprintf(stderr, "Illegal \"max_bw\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&limit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "latency") == 0) {
+ NEXT_ARG();
+ if (get_time(&latency, *argv)) {
+ fprintf(stderr, "Illegal \"latency\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "latency_hysteresis") == 0) {
+ NEXT_ARG();
+ if (get_time(&latency_hysteresis, *argv)) {
+ fprintf(stderr,
+ "Illegal \"latency hysteresis\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "codel_target") == 0) {
+ NEXT_ARG();
+ if (get_time(&codel_target, *argv)) {
+ fprintf(stderr, "Illegal \"codel_target\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ingress") == 0) {
+ mode = 1;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "arl: unknown parameter \"%s\"\n",
+ *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ if (buffer)
+ addattr_l(n, 1024, TCA_ARL_BUFFER, &buffer, sizeof(__u32));
+ if (min_rate)
+ addattr_l(n, 1024, TCA_ARL_MIN_RATE, &min_rate, sizeof(__u64));
+ if (max_bw)
+ addattr_l(n, 1024, TCA_ARL_MAX_BW, &max_bw, sizeof(__u64));
+ if (limit)
+ addattr_l(n, 1024, TCA_ARL_LIMIT, &limit, sizeof(__u32));
+ if (latency)
+ addattr_l(n, 1024, TCA_ARL_MAX_LATENCY, &latency,
+ sizeof(__u32));
+ if (latency_hysteresis)
+ addattr_l(n, 1024, TCA_ARL_LATENCY_HYSTERESIS,
+ &latency_hysteresis, sizeof(__u32));
+ if (codel_target)
+ addattr_l(n, 1024, TCA_ARL_CODEL_TARGET, &codel_target,
+ sizeof(__u32));
+ if (mode)
+ addattr_l(n, 1024, TCA_ARL_MODE, &mode, sizeof(__u32));
+
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int arl_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ unsigned int buffer, limit, latency, latency_hysteresis, mode,
+ codel_target;
+ __u64 min_rate = 0, max_bw;
+ struct rtattr *tb[TCA_ARL_MAX + 1];
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_ARL_MAX, opt);
+ if (tb[TCA_ARL_MIN_RATE] &&
+ RTA_PAYLOAD(tb[TCA_ARL_MIN_RATE]) >= sizeof(__u64)) {
+ min_rate = rta_getattr_u64(tb[TCA_ARL_MIN_RATE]);
+ fprintf(f, "minrate %s ", sprint_rate(min_rate, b1));
+ }
+
+ if (tb[TCA_ARL_BUFFER] &&
+ RTA_PAYLOAD(tb[TCA_ARL_BUFFER]) >= sizeof(__u32)) {
+ buffer = rta_getattr_u32(tb[TCA_ARL_BUFFER]);
+ fprintf(f, "buffer %s ", sprint_time(buffer, b1));
+ }
+
+ if (tb[TCA_ARL_MAX_BW] &&
+ RTA_PAYLOAD(tb[TCA_ARL_MAX_BW]) >= sizeof(__u64)) {
+ max_bw = rta_getattr_u64(tb[TCA_ARL_MAX_BW]);
+ fprintf(f, "max_bw %s ", sprint_rate(max_bw, b1));
+ }
+
+ if (tb[TCA_ARL_LIMIT] &&
+ RTA_PAYLOAD(tb[TCA_ARL_LIMIT]) >= sizeof(__u32)) {
+ limit = rta_getattr_u32(tb[TCA_ARL_LIMIT]);
+ fprintf(f, "limit %u ", limit);
+ }
+
+ if (tb[TCA_ARL_MAX_LATENCY] &&
+ RTA_PAYLOAD(tb[TCA_ARL_MAX_LATENCY]) >= sizeof(__u32)) {
+ latency = rta_getattr_u32(tb[TCA_ARL_MAX_LATENCY]);
+ fprintf(f, "latency %s ", sprint_time(latency, b1));
+ }
+
+ if (tb[TCA_ARL_LATENCY_HYSTERESIS] &&
+ RTA_PAYLOAD(tb[TCA_ARL_LATENCY_HYSTERESIS]) >= sizeof(__u32)) {
+ latency_hysteresis =
+ rta_getattr_u32(tb[TCA_ARL_LATENCY_HYSTERESIS]);
+ fprintf(f, "latency_hysteresis %s ",
+ sprint_time(latency_hysteresis, b1));
+ }
+
+ if (tb[TCA_ARL_CODEL_TARGET] &&
+ RTA_PAYLOAD(tb[TCA_ARL_CODEL_TARGET]) >= sizeof(__u32)) {
+ codel_target = rta_getattr_u32(tb[TCA_ARL_CODEL_TARGET]);
+ fprintf(f, "codel_target %s ", sprint_time(codel_target, b1));
+ }
+
+ if (tb[TCA_ARL_MODE] &&
+ RTA_PAYLOAD(tb[TCA_ARL_MODE]) >= sizeof(__u32)) {
+ mode = rta_getattr_u32(tb[TCA_ARL_MODE]);
+ if (mode)
+ fprintf(f, "mode ingress");
+ else
+ fprintf(f, "mode egress");
+ }
+
+ return 0;
+}
+
+static int arl_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct tc_arl_xstats *st;
+
+ if (xstats == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+
+ st = RTA_DATA(xstats);
+
+ fprintf(f, "state %s base_rate %uKbit current_rate %uKbit latency %uus bw %uKbit",
+ arl_state_names[st->state], st->base_rate, st->current_rate,
+ st->latency, st->current_bw);
+ if (st->max_bw)
+ fprintf(f, " max_bw %uKbit", st->max_bw);
+
+ if (st->min_rate)
+ fprintf(f, " min_base_rate %uKbit", st->min_rate);
+
+ return 0;
+}
+
+struct qdisc_util arl_qdisc_util = {
+ .id = "arl",
+ .parse_qopt = arl_parse_opt,
+ .print_qopt = arl_print_opt,
+ .print_xstats = arl_print_xstats,
+};
diff --git a/tc/q_atm.c b/tc/q_atm.c
new file mode 100644
index 0000000..77b5682
--- /dev/null
+++ b/tc/q_atm.c
@@ -0,0 +1,250 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * q_atm.c ATM.
+ *
+ * Hacked 1998-2000 by Werner Almesberger, EPFL ICA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <atm.h>
+#include <linux/atmdev.h>
+#include <linux/atmarp.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+
+#define MAX_HDR_LEN 64
+
+
+static int atm_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ if (argc) {
+ fprintf(stderr, "Usage: atm\n");
+ return -1;
+ }
+ return 0;
+}
+
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... atm ( pvc ADDR | svc ADDR [ sap SAP ] ) [ qos QOS ] [ sndbuf BYTES ]\n"
+ " [ hdr HEX... ] [ excess ( CLASSID | clp ) ] [ clip ]\n");
+}
+
+
+static int atm_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct sockaddr_atmsvc addr = {};
+ struct atm_qos qos;
+ struct atm_sap sap;
+ unsigned char hdr[MAX_HDR_LEN];
+ __u32 excess = 0;
+ struct rtattr *tail;
+ int sndbuf = 0;
+ int hdr_len = -1;
+ int set_clip = 0;
+ int s;
+
+ (void) text2qos("aal5,ubr:sdu=9180,rx:none", &qos, 0);
+ (void) text2sap("blli:l2=iso8802", &sap, 0);
+ while (argc > 0) {
+ if (!strcmp(*argv, "pvc")) {
+ NEXT_ARG();
+ if (text2atm(*argv, (struct sockaddr *) &addr,
+ sizeof(addr), T2A_PVC | T2A_NAME) < 0) {
+ explain();
+ return -1;
+ }
+ } else if (!strcmp(*argv,"svc")) {
+ NEXT_ARG();
+ if (text2atm(*argv, (struct sockaddr *) &addr,
+ sizeof(addr), T2A_SVC | T2A_NAME) < 0) {
+ explain();
+ return -1;
+ }
+ } else if (!strcmp(*argv,"qos")) {
+ NEXT_ARG();
+ if (text2qos(*argv, &qos, 0) < 0) {
+ explain();
+ return -1;
+ }
+ } else if (!strcmp(*argv,"sndbuf")) {
+ char *end;
+
+ NEXT_ARG();
+ sndbuf = strtol(*argv, &end, 0);
+ if (*end) {
+ explain();
+ return -1;
+ }
+ } else if (!strcmp(*argv,"sap")) {
+ NEXT_ARG();
+ if (addr.sas_family != AF_ATMSVC ||
+ text2sap(*argv, &sap, T2A_NAME) < 0) {
+ explain();
+ return -1;
+ }
+ } else if (!strcmp(*argv,"hdr")) {
+ unsigned char *ptr;
+ char *walk;
+
+ NEXT_ARG();
+ ptr = hdr;
+ for (walk = *argv; *walk; walk++) {
+ int tmp;
+
+ if (ptr == hdr+MAX_HDR_LEN) {
+ fprintf(stderr, "header is too long\n");
+ return -1;
+ }
+ if (*walk == '.') continue;
+ if (!isxdigit(walk[0]) || !walk[1] ||
+ !isxdigit(walk[1])) {
+ explain();
+ return -1;
+ }
+ sscanf(walk, "%2x", &tmp);
+ *ptr++ = tmp;
+ walk++;
+ }
+ hdr_len = ptr-hdr;
+ } else if (!strcmp(*argv,"excess")) {
+ NEXT_ARG();
+ if (!strcmp(*argv, "clp")) excess = 0;
+ else if (get_tc_classid(&excess, *argv)) {
+ explain();
+ return -1;
+ }
+ } else if (!strcmp(*argv,"clip")) {
+ set_clip = 1;
+ } else {
+ explain();
+ return 1;
+ }
+ argc--;
+ argv++;
+ }
+ s = socket(addr.sas_family, SOCK_DGRAM, 0);
+ if (s < 0) {
+ perror("socket");
+ return -1;
+ }
+ if (setsockopt(s, SOL_ATM, SO_ATMQOS, &qos, sizeof(qos)) < 0) {
+ perror("SO_ATMQOS");
+ return -1;
+ }
+ if (sndbuf)
+ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) {
+ perror("SO_SNDBUF");
+ return -1;
+ }
+ if (addr.sas_family == AF_ATMSVC && setsockopt(s, SOL_ATM, SO_ATMSAP,
+ &sap, sizeof(sap)) < 0) {
+ perror("SO_ATMSAP");
+ return -1;
+ }
+ if (connect(s, (struct sockaddr *) &addr, addr.sas_family == AF_ATMPVC ?
+ sizeof(struct sockaddr_atmpvc) : sizeof(addr)) < 0) {
+ perror("connect");
+ return -1;
+ }
+ if (set_clip)
+ if (ioctl(s, ATMARP_MKIP, 0) < 0) {
+ perror("ioctl ATMARP_MKIP");
+ return -1;
+ }
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 1024, TCA_ATM_FD, &s, sizeof(s));
+ if (excess)
+ addattr_l(n, 1024, TCA_ATM_EXCESS, &excess, sizeof(excess));
+ if (hdr_len != -1)
+ addattr_l(n, 1024, TCA_ATM_HDR, hdr, hdr_len);
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+
+
+static int atm_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_ATM_MAX+1];
+ char buffer[MAX_ATM_ADDR_LEN+1];
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_ATM_MAX, opt);
+ if (tb[TCA_ATM_ADDR]) {
+ if (RTA_PAYLOAD(tb[TCA_ATM_ADDR]) <
+ sizeof(struct sockaddr_atmpvc))
+ fprintf(stderr, "ATM: address too short\n");
+ else {
+ if (atm2text(buffer, MAX_ATM_ADDR_LEN,
+ RTA_DATA(tb[TCA_ATM_ADDR]), A2T_PRETTY | A2T_NAME) <
+ 0) fprintf(stderr, "atm2text error\n");
+ fprintf(f, "pvc %s ", buffer);
+ }
+ }
+ if (tb[TCA_ATM_HDR]) {
+ int i;
+ const __u8 *hdr = RTA_DATA(tb[TCA_ATM_HDR]);
+
+ fprintf(f, "hdr");
+ for (i = 0; i < RTA_PAYLOAD(tb[TCA_ATM_HDR]); i++)
+ fprintf(f, "%c%02x", i ? '.' : ' ', hdr[i]);
+ if (!i) fprintf(f, " .");
+ fprintf(f, " ");
+ }
+ if (tb[TCA_ATM_EXCESS]) {
+ __u32 excess;
+
+ if (RTA_PAYLOAD(tb[TCA_ATM_EXCESS]) < sizeof(excess))
+ fprintf(stderr, "ATM: excess class ID too short\n");
+ else {
+ excess = rta_getattr_u32(tb[TCA_ATM_EXCESS]);
+ if (!excess) fprintf(f, "excess clp ");
+ else {
+ char buf[64];
+
+ print_tc_classid(buf, sizeof(buf), excess);
+ fprintf(f, "excess %s ", buf);
+ }
+ }
+ }
+ if (tb[TCA_ATM_STATE]) {
+ static const char *map[] = { ATM_VS2TXT_MAP };
+ int state;
+
+ if (RTA_PAYLOAD(tb[TCA_ATM_STATE]) < sizeof(state))
+ fprintf(stderr, "ATM: state field too short\n");
+ else {
+ state = rta_getattr_u32(tb[TCA_ATM_STATE]);
+ fprintf(f, "%s ", map[state]);
+ }
+ }
+ return 0;
+}
+
+
+struct qdisc_util atm_qdisc_util = {
+ .id = "atm",
+ .parse_qopt = atm_parse_opt,
+ .print_qopt = atm_print_opt,
+ .parse_copt = atm_parse_class_opt,
+ .print_copt = atm_print_opt,
+};
diff --git a/tc/q_cake.c b/tc/q_cake.c
new file mode 100644
index 0000000..65ea07e
--- /dev/null
+++ b/tc/q_cake.c
@@ -0,0 +1,829 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+/*
+ * Common Applications Kept Enhanced -- CAKE
+ *
+ * Copyright (C) 2014-2018 Jonathan Morton <chromatix99@gmail.com>
+ * Copyright (C) 2017-2018 Toke Høiland-Jørgensen <toke@toke.dk>
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+struct cake_preset {
+ char *name;
+ unsigned int target;
+ unsigned int interval;
+};
+
+static struct cake_preset presets[] = {
+ {"datacentre", 5, 100},
+ {"lan", 50, 1000},
+ {"metro", 500, 10000},
+ {"regional", 1500, 30000},
+ {"internet", 5000, 100000},
+ {"oceanic", 15000, 300000},
+ {"satellite", 50000, 1000000},
+ {"interplanetary", 50000000, 1000000000},
+};
+
+static const char * diffserv_names[CAKE_DIFFSERV_MAX] = {
+ [CAKE_DIFFSERV_DIFFSERV3] = "diffserv3",
+ [CAKE_DIFFSERV_DIFFSERV4] = "diffserv4",
+ [CAKE_DIFFSERV_DIFFSERV8] = "diffserv8",
+ [CAKE_DIFFSERV_BESTEFFORT] = "besteffort",
+ [CAKE_DIFFSERV_PRECEDENCE] = "precedence",
+};
+
+static const char * flowmode_names[CAKE_FLOW_MAX] = {
+ [CAKE_FLOW_NONE] = "flowblind",
+ [CAKE_FLOW_SRC_IP] = "srchost",
+ [CAKE_FLOW_DST_IP] = "dsthost",
+ [CAKE_FLOW_HOSTS] = "hosts",
+ [CAKE_FLOW_FLOWS] = "flows",
+ [CAKE_FLOW_DUAL_SRC] = "dual-srchost",
+ [CAKE_FLOW_DUAL_DST] = "dual-dsthost",
+ [CAKE_FLOW_TRIPLE] = "triple-isolate",
+};
+
+static struct cake_preset *find_preset(char *argv)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(presets); i++)
+ if (!strcmp(argv, presets[i].name))
+ return &presets[i];
+ return NULL;
+}
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... cake [ bandwidth RATE | unlimited* | autorate-ingress ]\n"
+ " [ rtt TIME | datacentre | lan | metro | regional |\n"
+ " internet* | oceanic | satellite | interplanetary ]\n"
+ " [ besteffort | diffserv8 | diffserv4 | diffserv3* ]\n"
+ " [ flowblind | srchost | dsthost | hosts | flows |\n"
+ " dual-srchost | dual-dsthost | triple-isolate* ]\n"
+ " [ nat | nonat* ]\n"
+ " [ wash | nowash* ]\n"
+ " [ split-gso* | no-split-gso ]\n"
+ " [ ack-filter | ack-filter-aggressive | no-ack-filter* ]\n"
+ " [ memlimit LIMIT ]\n"
+ " [ fwmark MASK ]\n"
+ " [ ptm | atm | noatm* ] [ overhead N | conservative | raw* ]\n"
+ " [ mpu N ] [ ingress | egress* ]\n"
+ " (* marks defaults)\n");
+}
+
+static int cake_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct cake_preset *preset, *preset_set = NULL;
+ bool overhead_override = false;
+ bool overhead_set = false;
+ unsigned int interval = 0;
+ unsigned int diffserv = 0;
+ unsigned int memlimit = 0;
+ unsigned int target = 0;
+ __u64 bandwidth = 0;
+ int ack_filter = -1;
+ struct rtattr *tail;
+ int split_gso = -1;
+ int unlimited = 0;
+ int flowmode = -1;
+ int autorate = -1;
+ int ingress = -1;
+ int overhead = 0;
+ int fwmark = -1;
+ int wash = -1;
+ int nat = -1;
+ int atm = -1;
+ int mpu = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bandwidth") == 0) {
+ NEXT_ARG();
+ if (get_rate64(&bandwidth, *argv)) {
+ fprintf(stderr, "Illegal \"bandwidth\"\n");
+ return -1;
+ }
+ unlimited = 0;
+ autorate = 0;
+ } else if (strcmp(*argv, "unlimited") == 0) {
+ bandwidth = 0;
+ unlimited = 1;
+ autorate = 0;
+ } else if (strcmp(*argv, "autorate-ingress") == 0) {
+ autorate = 1;
+ } else if (strcmp(*argv, "rtt") == 0) {
+ NEXT_ARG();
+ if (get_time(&interval, *argv)) {
+ fprintf(stderr, "Illegal \"rtt\"\n");
+ return -1;
+ }
+ target = interval / 20;
+ if (!target)
+ target = 1;
+ } else if ((preset = find_preset(*argv))) {
+ if (preset_set)
+ duparg(*argv, preset_set->name);
+ preset_set = preset;
+ target = preset->target;
+ interval = preset->interval;
+ } else if (strcmp(*argv, "besteffort") == 0) {
+ diffserv = CAKE_DIFFSERV_BESTEFFORT;
+ } else if (strcmp(*argv, "precedence") == 0) {
+ diffserv = CAKE_DIFFSERV_PRECEDENCE;
+ } else if (strcmp(*argv, "diffserv8") == 0) {
+ diffserv = CAKE_DIFFSERV_DIFFSERV8;
+ } else if (strcmp(*argv, "diffserv4") == 0) {
+ diffserv = CAKE_DIFFSERV_DIFFSERV4;
+ } else if (strcmp(*argv, "diffserv") == 0) {
+ diffserv = CAKE_DIFFSERV_DIFFSERV4;
+ } else if (strcmp(*argv, "diffserv3") == 0) {
+ diffserv = CAKE_DIFFSERV_DIFFSERV3;
+ } else if (strcmp(*argv, "nowash") == 0) {
+ wash = 0;
+ } else if (strcmp(*argv, "wash") == 0) {
+ wash = 1;
+ } else if (strcmp(*argv, "split-gso") == 0) {
+ split_gso = 1;
+ } else if (strcmp(*argv, "no-split-gso") == 0) {
+ split_gso = 0;
+ } else if (strcmp(*argv, "flowblind") == 0) {
+ flowmode = CAKE_FLOW_NONE;
+ } else if (strcmp(*argv, "srchost") == 0) {
+ flowmode = CAKE_FLOW_SRC_IP;
+ } else if (strcmp(*argv, "dsthost") == 0) {
+ flowmode = CAKE_FLOW_DST_IP;
+ } else if (strcmp(*argv, "hosts") == 0) {
+ flowmode = CAKE_FLOW_HOSTS;
+ } else if (strcmp(*argv, "flows") == 0) {
+ flowmode = CAKE_FLOW_FLOWS;
+ } else if (strcmp(*argv, "dual-srchost") == 0) {
+ flowmode = CAKE_FLOW_DUAL_SRC;
+ } else if (strcmp(*argv, "dual-dsthost") == 0) {
+ flowmode = CAKE_FLOW_DUAL_DST;
+ } else if (strcmp(*argv, "triple-isolate") == 0) {
+ flowmode = CAKE_FLOW_TRIPLE;
+ } else if (strcmp(*argv, "nat") == 0) {
+ nat = 1;
+ } else if (strcmp(*argv, "nonat") == 0) {
+ nat = 0;
+ } else if (strcmp(*argv, "ptm") == 0) {
+ atm = CAKE_ATM_PTM;
+ } else if (strcmp(*argv, "atm") == 0) {
+ atm = CAKE_ATM_ATM;
+ } else if (strcmp(*argv, "noatm") == 0) {
+ atm = CAKE_ATM_NONE;
+ } else if (strcmp(*argv, "raw") == 0) {
+ atm = CAKE_ATM_NONE;
+ overhead = 0;
+ overhead_set = true;
+ overhead_override = true;
+ } else if (strcmp(*argv, "conservative") == 0) {
+ /*
+ * Deliberately over-estimate overhead:
+ * one whole ATM cell plus ATM framing.
+ * A safe choice if the actual overhead is unknown.
+ */
+ atm = CAKE_ATM_ATM;
+ overhead = 48;
+ overhead_set = true;
+
+ /* Various ADSL framing schemes, all over ATM cells */
+ } else if (strcmp(*argv, "ipoa-vcmux") == 0) {
+ atm = CAKE_ATM_ATM;
+ overhead += 8;
+ overhead_set = true;
+ } else if (strcmp(*argv, "ipoa-llcsnap") == 0) {
+ atm = CAKE_ATM_ATM;
+ overhead += 16;
+ overhead_set = true;
+ } else if (strcmp(*argv, "bridged-vcmux") == 0) {
+ atm = CAKE_ATM_ATM;
+ overhead += 24;
+ overhead_set = true;
+ } else if (strcmp(*argv, "bridged-llcsnap") == 0) {
+ atm = CAKE_ATM_ATM;
+ overhead += 32;
+ overhead_set = true;
+ } else if (strcmp(*argv, "pppoa-vcmux") == 0) {
+ atm = CAKE_ATM_ATM;
+ overhead += 10;
+ overhead_set = true;
+ } else if (strcmp(*argv, "pppoa-llc") == 0) {
+ atm = CAKE_ATM_ATM;
+ overhead += 14;
+ overhead_set = true;
+ } else if (strcmp(*argv, "pppoe-vcmux") == 0) {
+ atm = CAKE_ATM_ATM;
+ overhead += 32;
+ overhead_set = true;
+ } else if (strcmp(*argv, "pppoe-llcsnap") == 0) {
+ atm = CAKE_ATM_ATM;
+ overhead += 40;
+ overhead_set = true;
+
+ /* Typical VDSL2 framing schemes, both over PTM */
+ /* PTM has 64b/65b coding which absorbs some bandwidth */
+ } else if (strcmp(*argv, "pppoe-ptm") == 0) {
+ /* 2B PPP + 6B PPPoE + 6B dest MAC + 6B src MAC
+ * + 2B ethertype + 4B Frame Check Sequence
+ * + 1B Start of Frame (S) + 1B End of Frame (Ck)
+ * + 2B TC-CRC (PTM-FCS) = 30B
+ */
+ atm = CAKE_ATM_PTM;
+ overhead += 30;
+ overhead_set = true;
+ } else if (strcmp(*argv, "bridged-ptm") == 0) {
+ /* 6B dest MAC + 6B src MAC + 2B ethertype
+ * + 4B Frame Check Sequence
+ * + 1B Start of Frame (S) + 1B End of Frame (Ck)
+ * + 2B TC-CRC (PTM-FCS) = 22B
+ */
+ atm = CAKE_ATM_PTM;
+ overhead += 22;
+ overhead_set = true;
+ } else if (strcmp(*argv, "via-ethernet") == 0) {
+ /*
+ * We used to use this flag to manually compensate for
+ * Linux including the Ethernet header on Ethernet-type
+ * interfaces, but not on IP-type interfaces.
+ *
+ * It is no longer needed, because Cake now adjusts for
+ * that automatically, and is thus ignored.
+ *
+ * It would be deleted entirely, but it appears in the
+ * stats output when the automatic compensation is
+ * active.
+ */
+ } else if (strcmp(*argv, "ethernet") == 0) {
+ /* ethernet pre-amble & interframe gap & FCS
+ * you may need to add vlan tag
+ */
+ overhead += 38;
+ overhead_set = true;
+ mpu = 84;
+
+ /* Additional Ethernet-related overhead used by some ISPs */
+ } else if (strcmp(*argv, "ether-vlan") == 0) {
+ /* 802.1q VLAN tag - may be repeated */
+ overhead += 4;
+ overhead_set = true;
+
+ /*
+ * DOCSIS cable shapers account for Ethernet frame with FCS,
+ * but not interframe gap or preamble.
+ */
+ } else if (strcmp(*argv, "docsis") == 0) {
+ atm = CAKE_ATM_NONE;
+ overhead += 18;
+ overhead_set = true;
+ mpu = 64;
+ } else if (strcmp(*argv, "overhead") == 0) {
+ char *p = NULL;
+
+ NEXT_ARG();
+ overhead = strtol(*argv, &p, 10);
+ if (!p || *p || !*argv ||
+ overhead < -64 || overhead > 256) {
+ fprintf(stderr,
+ "Illegal \"overhead\", valid range is -64 to 256\\n");
+ return -1;
+ }
+ overhead_set = true;
+
+ } else if (strcmp(*argv, "mpu") == 0) {
+ char *p = NULL;
+
+ NEXT_ARG();
+ mpu = strtol(*argv, &p, 10);
+ if (!p || *p || !*argv || mpu < 0 || mpu > 256) {
+ fprintf(stderr,
+ "Illegal \"mpu\", valid range is 0 to 256\\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ingress") == 0) {
+ ingress = 1;
+ } else if (strcmp(*argv, "egress") == 0) {
+ ingress = 0;
+ } else if (strcmp(*argv, "no-ack-filter") == 0) {
+ ack_filter = CAKE_ACK_NONE;
+ } else if (strcmp(*argv, "ack-filter") == 0) {
+ ack_filter = CAKE_ACK_FILTER;
+ } else if (strcmp(*argv, "ack-filter-aggressive") == 0) {
+ ack_filter = CAKE_ACK_AGGRESSIVE;
+ } else if (strcmp(*argv, "memlimit") == 0) {
+ NEXT_ARG();
+ if (get_size(&memlimit, *argv)) {
+ fprintf(stderr,
+ "Illegal value for \"memlimit\": \"%s\"\n", *argv);
+ return -1;
+ }
+ } else if (strcmp(*argv, "fwmark") == 0) {
+ unsigned int fwm;
+
+ NEXT_ARG();
+ if (get_u32(&fwm, *argv, 0)) {
+ fprintf(stderr,
+ "Illegal value for \"fwmark\": \"%s\"\n", *argv);
+ return -1;
+ }
+ fwmark = fwm;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ if (bandwidth || unlimited)
+ addattr_l(n, 1024, TCA_CAKE_BASE_RATE64, &bandwidth,
+ sizeof(bandwidth));
+ if (diffserv)
+ addattr_l(n, 1024, TCA_CAKE_DIFFSERV_MODE, &diffserv,
+ sizeof(diffserv));
+ if (atm != -1)
+ addattr_l(n, 1024, TCA_CAKE_ATM, &atm, sizeof(atm));
+ if (flowmode != -1)
+ addattr_l(n, 1024, TCA_CAKE_FLOW_MODE, &flowmode,
+ sizeof(flowmode));
+ if (overhead_set)
+ addattr_l(n, 1024, TCA_CAKE_OVERHEAD, &overhead,
+ sizeof(overhead));
+ if (overhead_override) {
+ unsigned int zero = 0;
+
+ addattr_l(n, 1024, TCA_CAKE_RAW, &zero, sizeof(zero));
+ }
+ if (mpu > 0)
+ addattr_l(n, 1024, TCA_CAKE_MPU, &mpu, sizeof(mpu));
+ if (interval)
+ addattr_l(n, 1024, TCA_CAKE_RTT, &interval, sizeof(interval));
+ if (target)
+ addattr_l(n, 1024, TCA_CAKE_TARGET, &target, sizeof(target));
+ if (autorate != -1)
+ addattr_l(n, 1024, TCA_CAKE_AUTORATE, &autorate,
+ sizeof(autorate));
+ if (memlimit)
+ addattr_l(n, 1024, TCA_CAKE_MEMORY, &memlimit,
+ sizeof(memlimit));
+ if (fwmark != -1)
+ addattr_l(n, 1024, TCA_CAKE_FWMARK, &fwmark,
+ sizeof(fwmark));
+ if (nat != -1)
+ addattr_l(n, 1024, TCA_CAKE_NAT, &nat, sizeof(nat));
+ if (wash != -1)
+ addattr_l(n, 1024, TCA_CAKE_WASH, &wash, sizeof(wash));
+ if (split_gso != -1)
+ addattr_l(n, 1024, TCA_CAKE_SPLIT_GSO, &split_gso,
+ sizeof(split_gso));
+ if (ingress != -1)
+ addattr_l(n, 1024, TCA_CAKE_INGRESS, &ingress, sizeof(ingress));
+ if (ack_filter != -1)
+ addattr_l(n, 1024, TCA_CAKE_ACK_FILTER, &ack_filter,
+ sizeof(ack_filter));
+
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static void cake_print_mode(unsigned int value, unsigned int max,
+ const char *key, const char **table)
+{
+ if (value < max && table[value]) {
+ print_string(PRINT_ANY, key, "%s ", table[value]);
+ } else {
+ print_string(PRINT_JSON, key, NULL, "unknown");
+ print_string(PRINT_FP, NULL, "(?%s?)", key);
+ }
+}
+
+static int cake_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_CAKE_MAX + 1];
+ unsigned int interval = 0;
+ unsigned int memlimit = 0;
+ unsigned int fwmark = 0;
+ __u64 bandwidth = 0;
+ int ack_filter = 0;
+ int split_gso = 0;
+ int overhead = 0;
+ int autorate = 0;
+ int ingress = 0;
+ int wash = 0;
+ int raw = 0;
+ int mpu = 0;
+ int atm = 0;
+ int nat = 0;
+
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_CAKE_MAX, opt);
+
+ if (tb[TCA_CAKE_BASE_RATE64] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_BASE_RATE64]) >= sizeof(bandwidth)) {
+ bandwidth = rta_getattr_u64(tb[TCA_CAKE_BASE_RATE64]);
+ if (bandwidth) {
+ print_uint(PRINT_JSON, "bandwidth", NULL, bandwidth);
+ print_string(PRINT_FP, NULL, "bandwidth %s ",
+ sprint_rate(bandwidth, b1));
+ } else
+ print_string(PRINT_ANY, "bandwidth", "bandwidth %s ",
+ "unlimited");
+ }
+ if (tb[TCA_CAKE_AUTORATE] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_AUTORATE]) >= sizeof(__u32)) {
+ autorate = rta_getattr_u32(tb[TCA_CAKE_AUTORATE]);
+ if (autorate == 1)
+ print_string(PRINT_ANY, "autorate", "%s ",
+ "autorate-ingress");
+ else if (autorate)
+ print_string(PRINT_ANY, "autorate", "(?autorate?) ",
+ "unknown");
+ }
+ if (tb[TCA_CAKE_DIFFSERV_MODE] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_DIFFSERV_MODE]) >= sizeof(__u32)) {
+ cake_print_mode(rta_getattr_u32(tb[TCA_CAKE_DIFFSERV_MODE]),
+ CAKE_DIFFSERV_MAX, "diffserv", diffserv_names);
+ }
+ if (tb[TCA_CAKE_FLOW_MODE] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_FLOW_MODE]) >= sizeof(__u32)) {
+ cake_print_mode(rta_getattr_u32(tb[TCA_CAKE_FLOW_MODE]),
+ CAKE_FLOW_MAX, "flowmode", flowmode_names);
+ }
+
+ if (tb[TCA_CAKE_NAT] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_NAT]) >= sizeof(__u32)) {
+ nat = rta_getattr_u32(tb[TCA_CAKE_NAT]);
+ }
+
+ if (nat)
+ print_string(PRINT_FP, NULL, "nat ", NULL);
+ else
+ print_string(PRINT_FP, NULL, "nonat ", NULL);
+ print_bool(PRINT_JSON, "nat", NULL, nat);
+
+ if (tb[TCA_CAKE_WASH] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_WASH]) >= sizeof(__u32)) {
+ wash = rta_getattr_u32(tb[TCA_CAKE_WASH]);
+ }
+ if (tb[TCA_CAKE_ATM] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_ATM]) >= sizeof(__u32)) {
+ atm = rta_getattr_u32(tb[TCA_CAKE_ATM]);
+ }
+ if (tb[TCA_CAKE_OVERHEAD] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_OVERHEAD]) >= sizeof(__s32)) {
+ overhead = *(__s32 *) RTA_DATA(tb[TCA_CAKE_OVERHEAD]);
+ }
+ if (tb[TCA_CAKE_MPU] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_MPU]) >= sizeof(__u32)) {
+ mpu = rta_getattr_u32(tb[TCA_CAKE_MPU]);
+ }
+ if (tb[TCA_CAKE_INGRESS] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_INGRESS]) >= sizeof(__u32)) {
+ ingress = rta_getattr_u32(tb[TCA_CAKE_INGRESS]);
+ }
+ if (tb[TCA_CAKE_ACK_FILTER] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_ACK_FILTER]) >= sizeof(__u32)) {
+ ack_filter = rta_getattr_u32(tb[TCA_CAKE_ACK_FILTER]);
+ }
+ if (tb[TCA_CAKE_SPLIT_GSO] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_SPLIT_GSO]) >= sizeof(__u32)) {
+ split_gso = rta_getattr_u32(tb[TCA_CAKE_SPLIT_GSO]);
+ }
+ if (tb[TCA_CAKE_RAW]) {
+ raw = 1;
+ }
+ if (tb[TCA_CAKE_RTT] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_RTT]) >= sizeof(__u32)) {
+ interval = rta_getattr_u32(tb[TCA_CAKE_RTT]);
+ }
+ if (tb[TCA_CAKE_FWMARK] &&
+ RTA_PAYLOAD(tb[TCA_CAKE_FWMARK]) >= sizeof(__u32)) {
+ fwmark = rta_getattr_u32(tb[TCA_CAKE_FWMARK]);
+ }
+
+ if (wash)
+ print_string(PRINT_FP, NULL, "wash ", NULL);
+ else
+ print_string(PRINT_FP, NULL, "nowash ", NULL);
+ print_bool(PRINT_JSON, "wash", NULL, wash);
+
+ if (ingress)
+ print_string(PRINT_FP, NULL, "ingress ", NULL);
+ print_bool(PRINT_JSON, "ingress", NULL, ingress);
+
+ if (ack_filter == CAKE_ACK_AGGRESSIVE)
+ print_string(PRINT_ANY, "ack-filter", "ack-filter-%s ",
+ "aggressive");
+ else if (ack_filter == CAKE_ACK_FILTER)
+ print_string(PRINT_ANY, "ack-filter", "ack-filter ", "enabled");
+ else
+ print_string(PRINT_ANY, "ack-filter", "no-ack-filter ", "disabled");
+
+ if (split_gso)
+ print_string(PRINT_FP, NULL, "split-gso ", NULL);
+ else
+ print_string(PRINT_FP, NULL, "no-split-gso ", NULL);
+ print_bool(PRINT_JSON, "split_gso", NULL, split_gso);
+
+ if (interval)
+ print_string(PRINT_FP, NULL, "rtt %s ",
+ sprint_time(interval, b2));
+ print_uint(PRINT_JSON, "rtt", NULL, interval);
+
+ if (raw)
+ print_string(PRINT_FP, NULL, "raw ", NULL);
+ print_bool(PRINT_JSON, "raw", NULL, raw);
+
+ if (atm == CAKE_ATM_ATM)
+ print_string(PRINT_ANY, "atm", "%s ", "atm");
+ else if (atm == CAKE_ATM_PTM)
+ print_string(PRINT_ANY, "atm", "%s ", "ptm");
+ else if (!raw)
+ print_string(PRINT_ANY, "atm", "%s ", "noatm");
+
+ print_int(PRINT_ANY, "overhead", "overhead %d ", overhead);
+
+ if (mpu)
+ print_uint(PRINT_ANY, "mpu", "mpu %u ", mpu);
+
+ if (memlimit) {
+ print_uint(PRINT_JSON, "memlimit", NULL, memlimit);
+ print_string(PRINT_FP, NULL, "memlimit %s",
+ sprint_size(memlimit, b1));
+ }
+
+ if (fwmark)
+ print_uint(PRINT_FP, NULL, "fwmark 0x%x ", fwmark);
+ print_0xhex(PRINT_JSON, "fwmark", NULL, fwmark);
+
+ return 0;
+}
+
+static void cake_print_json_tin(struct rtattr **tstat)
+{
+#define PRINT_TSTAT_JSON(type, name, attr) if (tstat[TCA_CAKE_TIN_STATS_ ## attr]) \
+ print_u64(PRINT_JSON, name, NULL, \
+ rta_getattr_ ## type((struct rtattr *) \
+ tstat[TCA_CAKE_TIN_STATS_ ## attr]))
+
+ open_json_object(NULL);
+ PRINT_TSTAT_JSON(u64, "threshold_rate", THRESHOLD_RATE64);
+ PRINT_TSTAT_JSON(u64, "sent_bytes", SENT_BYTES64);
+ PRINT_TSTAT_JSON(u32, "backlog_bytes", BACKLOG_BYTES);
+ PRINT_TSTAT_JSON(u32, "target_us", TARGET_US);
+ PRINT_TSTAT_JSON(u32, "interval_us", INTERVAL_US);
+ PRINT_TSTAT_JSON(u32, "peak_delay_us", PEAK_DELAY_US);
+ PRINT_TSTAT_JSON(u32, "avg_delay_us", AVG_DELAY_US);
+ PRINT_TSTAT_JSON(u32, "base_delay_us", BASE_DELAY_US);
+ PRINT_TSTAT_JSON(u32, "sent_packets", SENT_PACKETS);
+ PRINT_TSTAT_JSON(u32, "way_indirect_hits", WAY_INDIRECT_HITS);
+ PRINT_TSTAT_JSON(u32, "way_misses", WAY_MISSES);
+ PRINT_TSTAT_JSON(u32, "way_collisions", WAY_COLLISIONS);
+ PRINT_TSTAT_JSON(u32, "drops", DROPPED_PACKETS);
+ PRINT_TSTAT_JSON(u32, "ecn_mark", ECN_MARKED_PACKETS);
+ PRINT_TSTAT_JSON(u32, "ack_drops", ACKS_DROPPED_PACKETS);
+ PRINT_TSTAT_JSON(u32, "sparse_flows", SPARSE_FLOWS);
+ PRINT_TSTAT_JSON(u32, "bulk_flows", BULK_FLOWS);
+ PRINT_TSTAT_JSON(u32, "unresponsive_flows", UNRESPONSIVE_FLOWS);
+ PRINT_TSTAT_JSON(u32, "max_pkt_len", MAX_SKBLEN);
+ PRINT_TSTAT_JSON(u32, "flow_quantum", FLOW_QUANTUM);
+ close_json_object();
+
+#undef PRINT_TSTAT_JSON
+}
+
+static int cake_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct rtattr *st[TCA_CAKE_STATS_MAX + 1];
+ SPRINT_BUF(b1);
+ int i;
+
+ if (xstats == NULL)
+ return 0;
+
+#define GET_STAT_U32(attr) rta_getattr_u32(st[TCA_CAKE_STATS_ ## attr])
+#define GET_STAT_S32(attr) (*(__s32 *)RTA_DATA(st[TCA_CAKE_STATS_ ## attr]))
+#define GET_STAT_U64(attr) rta_getattr_u64(st[TCA_CAKE_STATS_ ## attr])
+
+ parse_rtattr_nested(st, TCA_CAKE_STATS_MAX, xstats);
+
+ if (st[TCA_CAKE_STATS_MEMORY_USED] &&
+ st[TCA_CAKE_STATS_MEMORY_LIMIT]) {
+ print_string(PRINT_FP, NULL, " memory used: %s",
+ sprint_size(GET_STAT_U32(MEMORY_USED), b1));
+
+ print_string(PRINT_FP, NULL, " of %s\n",
+ sprint_size(GET_STAT_U32(MEMORY_LIMIT), b1));
+
+ print_uint(PRINT_JSON, "memory_used", NULL,
+ GET_STAT_U32(MEMORY_USED));
+ print_uint(PRINT_JSON, "memory_limit", NULL,
+ GET_STAT_U32(MEMORY_LIMIT));
+ }
+
+ if (st[TCA_CAKE_STATS_CAPACITY_ESTIMATE64]) {
+ print_string(PRINT_FP, NULL, " capacity estimate: %s\n",
+ sprint_rate(GET_STAT_U64(CAPACITY_ESTIMATE64), b1));
+ print_uint(PRINT_JSON, "capacity_estimate", NULL,
+ GET_STAT_U64(CAPACITY_ESTIMATE64));
+ }
+
+ if (st[TCA_CAKE_STATS_MIN_NETLEN] &&
+ st[TCA_CAKE_STATS_MAX_NETLEN]) {
+ print_uint(PRINT_ANY, "min_network_size",
+ " min/max network layer size: %12u",
+ GET_STAT_U32(MIN_NETLEN));
+ print_uint(PRINT_ANY, "max_network_size",
+ " /%8u\n", GET_STAT_U32(MAX_NETLEN));
+ }
+
+ if (st[TCA_CAKE_STATS_MIN_ADJLEN] &&
+ st[TCA_CAKE_STATS_MAX_ADJLEN]) {
+ print_uint(PRINT_ANY, "min_adj_size",
+ " min/max overhead-adjusted size: %8u",
+ GET_STAT_U32(MIN_ADJLEN));
+ print_uint(PRINT_ANY, "max_adj_size",
+ " /%8u\n", GET_STAT_U32(MAX_ADJLEN));
+ }
+
+ if (st[TCA_CAKE_STATS_AVG_NETOFF])
+ print_uint(PRINT_ANY, "avg_hdr_offset",
+ " average network hdr offset: %12u\n\n",
+ GET_STAT_U32(AVG_NETOFF));
+
+ /* class stats */
+ if (st[TCA_CAKE_STATS_DEFICIT])
+ print_int(PRINT_ANY, "deficit", " deficit %u",
+ GET_STAT_S32(DEFICIT));
+ if (st[TCA_CAKE_STATS_COBALT_COUNT])
+ print_uint(PRINT_ANY, "count", " count %u",
+ GET_STAT_U32(COBALT_COUNT));
+
+ if (st[TCA_CAKE_STATS_DROPPING] && GET_STAT_U32(DROPPING)) {
+ print_bool(PRINT_ANY, "dropping", " dropping", true);
+ if (st[TCA_CAKE_STATS_DROP_NEXT_US]) {
+ int drop_next = GET_STAT_S32(DROP_NEXT_US);
+
+ if (drop_next < 0) {
+ print_string(PRINT_FP, NULL, " drop_next -%s",
+ sprint_time(drop_next, b1));
+ } else {
+ print_uint(PRINT_JSON, "drop_next", NULL,
+ drop_next);
+ print_string(PRINT_FP, NULL, " drop_next %s",
+ sprint_time(drop_next, b1));
+ }
+ }
+ }
+
+ if (st[TCA_CAKE_STATS_P_DROP]) {
+ print_uint(PRINT_ANY, "blue_prob", " blue_prob %u",
+ GET_STAT_U32(P_DROP));
+ if (st[TCA_CAKE_STATS_BLUE_TIMER_US]) {
+ int blue_timer = GET_STAT_S32(BLUE_TIMER_US);
+
+ if (blue_timer < 0) {
+ print_string(PRINT_FP, NULL, " blue_timer -%s",
+ sprint_time(blue_timer, b1));
+ } else {
+ print_uint(PRINT_JSON, "blue_timer", NULL,
+ blue_timer);
+ print_string(PRINT_FP, NULL, " blue_timer %s",
+ sprint_time(blue_timer, b1));
+ }
+ }
+ }
+
+#undef GET_STAT_U32
+#undef GET_STAT_S32
+#undef GET_STAT_U64
+
+ if (st[TCA_CAKE_STATS_TIN_STATS]) {
+ struct rtattr *tstat[TC_CAKE_MAX_TINS][TCA_CAKE_TIN_STATS_MAX + 1];
+ struct rtattr *tins[TC_CAKE_MAX_TINS + 1];
+ int num_tins = 0;
+
+ parse_rtattr_nested(tins, TC_CAKE_MAX_TINS,
+ st[TCA_CAKE_STATS_TIN_STATS]);
+
+ for (i = 1; i <= TC_CAKE_MAX_TINS && tins[i]; i++) {
+ parse_rtattr_nested(tstat[i-1], TCA_CAKE_TIN_STATS_MAX,
+ tins[i]);
+ num_tins++;
+ }
+
+ if (!num_tins)
+ return 0;
+
+ if (is_json_context()) {
+ open_json_array(PRINT_JSON, "tins");
+ for (i = 0; i < num_tins; i++)
+ cake_print_json_tin(tstat[i]);
+ close_json_array(PRINT_JSON, NULL);
+
+ return 0;
+ }
+
+
+ switch (num_tins) {
+ case 3:
+ fprintf(f, " Bulk Best Effort Voice\n");
+ break;
+
+ case 4:
+ fprintf(f, " Bulk Best Effort Video Voice\n");
+ break;
+
+ default:
+ fprintf(f, " ");
+ for (i = 0; i < num_tins; i++)
+ fprintf(f, " Tin %u", i);
+ fprintf(f, "\n");
+ };
+
+#define GET_TSTAT(i, attr) (tstat[i][TCA_CAKE_TIN_STATS_ ## attr])
+#define PRINT_TSTAT(name, attr, fmts, val) do { \
+ if (GET_TSTAT(0, attr)) { \
+ fprintf(f, name); \
+ for (i = 0; i < num_tins; i++) \
+ fprintf(f, " %12" fmts, val); \
+ fprintf(f, "\n"); \
+ } \
+ } while (0)
+
+#define SPRINT_TSTAT(pfunc, type, name, attr) PRINT_TSTAT( \
+ name, attr, "s", sprint_ ## pfunc( \
+ rta_getattr_ ## type(GET_TSTAT(i, attr)), b1))
+
+#define PRINT_TSTAT_U32(name, attr) PRINT_TSTAT( \
+ name, attr, "u", rta_getattr_u32(GET_TSTAT(i, attr)))
+
+#define PRINT_TSTAT_U64(name, attr) PRINT_TSTAT( \
+ name, attr, "llu", rta_getattr_u64(GET_TSTAT(i, attr)))
+
+ SPRINT_TSTAT(rate, u64, " thresh ", THRESHOLD_RATE64);
+ SPRINT_TSTAT(time, u32, " target ", TARGET_US);
+ SPRINT_TSTAT(time, u32, " interval", INTERVAL_US);
+ SPRINT_TSTAT(time, u32, " pk_delay", PEAK_DELAY_US);
+ SPRINT_TSTAT(time, u32, " av_delay", AVG_DELAY_US);
+ SPRINT_TSTAT(time, u32, " sp_delay", BASE_DELAY_US);
+ SPRINT_TSTAT(size, u32, " backlog ", BACKLOG_BYTES);
+
+ PRINT_TSTAT_U32(" pkts ", SENT_PACKETS);
+ PRINT_TSTAT_U64(" bytes ", SENT_BYTES64);
+
+ PRINT_TSTAT_U32(" way_inds", WAY_INDIRECT_HITS);
+ PRINT_TSTAT_U32(" way_miss", WAY_MISSES);
+ PRINT_TSTAT_U32(" way_cols", WAY_COLLISIONS);
+ PRINT_TSTAT_U32(" drops ", DROPPED_PACKETS);
+ PRINT_TSTAT_U32(" marks ", ECN_MARKED_PACKETS);
+ PRINT_TSTAT_U32(" ack_drop", ACKS_DROPPED_PACKETS);
+ PRINT_TSTAT_U32(" sp_flows", SPARSE_FLOWS);
+ PRINT_TSTAT_U32(" bk_flows", BULK_FLOWS);
+ PRINT_TSTAT_U32(" un_flows", UNRESPONSIVE_FLOWS);
+ PRINT_TSTAT_U32(" max_len ", MAX_SKBLEN);
+ PRINT_TSTAT_U32(" quantum ", FLOW_QUANTUM);
+
+#undef GET_STAT
+#undef PRINT_TSTAT
+#undef SPRINT_TSTAT
+#undef PRINT_TSTAT_U32
+#undef PRINT_TSTAT_U64
+ }
+ return 0;
+}
+
+struct qdisc_util cake_qdisc_util = {
+ .id = "cake",
+ .parse_qopt = cake_parse_opt,
+ .print_qopt = cake_print_opt,
+ .print_xstats = cake_print_xstats,
+};
diff --git a/tc/q_cbq.c b/tc/q_cbq.c
new file mode 100644
index 0000000..6518ef4
--- /dev/null
+++ b/tc/q_cbq.c
@@ -0,0 +1,600 @@
+/*
+ * q_cbq.c CBQ.
+ *
+ * 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 "utils.h"
+#include "tc_util.h"
+#include "tc_cbq.h"
+
+static void explain_class(void)
+{
+ fprintf(stderr,
+ "Usage: ... cbq bandwidth BPS rate BPS maxburst PKTS [ avpkt BYTES ]\n"
+ " [ minburst PKTS ] [ bounded ] [ isolated ]\n"
+ " [ allot BYTES ] [ mpu BYTES ] [ weight RATE ]\n"
+ " [ prio NUMBER ] [ cell BYTES ] [ ewma LOG ]\n"
+ " [ estimator INTERVAL TIME_CONSTANT ]\n"
+ " [ split CLASSID ] [ defmap MASK/CHANGE ]\n"
+ " [ overhead BYTES ] [ linklayer TYPE ]\n");
+}
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... cbq bandwidth BPS avpkt BYTES [ mpu BYTES ]\n"
+ " [ cell BYTES ] [ ewma LOG ]\n");
+}
+
+static void explain1(char *arg)
+{
+ fprintf(stderr, "Illegal \"%s\"\n", arg);
+}
+
+
+static int cbq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev)
+{
+ struct tc_ratespec r = {};
+ struct tc_cbq_lssopt lss = {};
+ __u32 rtab[256];
+ unsigned mpu = 0, avpkt = 0, allot = 0;
+ unsigned short overhead = 0;
+ unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+ int cell_log = -1;
+ int ewma_log = -1;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (matches(*argv, "bandwidth") == 0 ||
+ matches(*argv, "rate") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&r.rate, *argv, dev)) {
+ explain1("bandwidth");
+ return -1;
+ }
+ } else if (get_rate(&r.rate, *argv)) {
+ explain1("bandwidth");
+ return -1;
+ }
+ } else if (matches(*argv, "ewma") == 0) {
+ NEXT_ARG();
+ if (get_integer(&ewma_log, *argv, 0)) {
+ explain1("ewma");
+ return -1;
+ }
+ if (ewma_log > 31) {
+ fprintf(stderr, "ewma_log must be < 32\n");
+ return -1;
+ }
+ } else if (matches(*argv, "cell") == 0) {
+ unsigned int cell;
+ int i;
+
+ NEXT_ARG();
+ if (get_size(&cell, *argv)) {
+ explain1("cell");
+ return -1;
+ }
+ for (i = 0; i < 32; i++)
+ if ((1<<i) == cell)
+ break;
+ if (i >= 32) {
+ fprintf(stderr, "cell must be 2^n\n");
+ return -1;
+ }
+ cell_log = i;
+ } else if (matches(*argv, "avpkt") == 0) {
+ NEXT_ARG();
+ if (get_size(&avpkt, *argv)) {
+ explain1("avpkt");
+ return -1;
+ }
+ } else if (matches(*argv, "mpu") == 0) {
+ NEXT_ARG();
+ if (get_size(&mpu, *argv)) {
+ explain1("mpu");
+ return -1;
+ }
+ } else if (matches(*argv, "allot") == 0) {
+ NEXT_ARG();
+ /* Accept and ignore "allot" for backward compatibility */
+ if (get_size(&allot, *argv)) {
+ explain1("allot");
+ return -1;
+ }
+ } else if (matches(*argv, "overhead") == 0) {
+ NEXT_ARG();
+ if (get_u16(&overhead, *argv, 10)) {
+ explain1("overhead"); return -1;
+ }
+ } else if (matches(*argv, "linklayer") == 0) {
+ NEXT_ARG();
+ if (get_linklayer(&linklayer, *argv)) {
+ explain1("linklayer"); return -1;
+ }
+ } else if (matches(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ /* OK. All options are parsed. */
+
+ if (r.rate == 0) {
+ fprintf(stderr, "CBQ: bandwidth is required parameter.\n");
+ return -1;
+ }
+ if (avpkt == 0) {
+ fprintf(stderr, "CBQ: \"avpkt\" is required.\n");
+ return -1;
+ }
+ if (allot < (avpkt*3)/2)
+ allot = (avpkt*3)/2;
+
+ r.mpu = mpu;
+ r.overhead = overhead;
+ if (tc_calc_rtable(&r, rtab, cell_log, allot, linklayer) < 0) {
+ fprintf(stderr, "CBQ: failed to calculate rate table.\n");
+ return -1;
+ }
+
+ if (ewma_log < 0)
+ ewma_log = TC_CBQ_DEF_EWMA;
+ lss.ewma_log = ewma_log;
+ lss.maxidle = tc_calc_xmittime(r.rate, avpkt);
+ lss.change = TCF_CBQ_LSS_MAXIDLE|TCF_CBQ_LSS_EWMA|TCF_CBQ_LSS_AVPKT;
+ lss.avpkt = avpkt;
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 1024, TCA_CBQ_RATE, &r, sizeof(r));
+ addattr_l(n, 1024, TCA_CBQ_LSSOPT, &lss, sizeof(lss));
+ addattr_l(n, 3024, TCA_CBQ_RTAB, rtab, 1024);
+ if (show_raw) {
+ int i;
+
+ for (i = 0; i < 256; i++)
+ printf("%u ", rtab[i]);
+ printf("\n");
+ }
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int cbq_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev)
+{
+ int wrr_ok = 0, fopt_ok = 0;
+ struct tc_ratespec r = {};
+ struct tc_cbq_lssopt lss = {};
+ struct tc_cbq_wrropt wrr = {};
+ struct tc_cbq_fopt fopt = {};
+ __u32 rtab[256];
+ unsigned mpu = 0;
+ int cell_log = -1;
+ int ewma_log = -1;
+ unsigned int bndw = 0;
+ unsigned minburst = 0, maxburst = 0;
+ unsigned short overhead = 0;
+ unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (matches(*argv, "rate") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&r.rate, *argv, dev)) {
+ explain1("rate");
+ return -1;
+ }
+ } else if (get_rate(&r.rate, *argv)) {
+ explain1("rate");
+ return -1;
+ }
+ } else if (matches(*argv, "bandwidth") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&bndw, *argv, dev)) {
+ explain1("bandwidth");
+ return -1;
+ }
+ } else if (get_rate(&bndw, *argv)) {
+ explain1("bandwidth");
+ return -1;
+ }
+ } else if (matches(*argv, "minidle") == 0) {
+ NEXT_ARG();
+ if (get_u32(&lss.minidle, *argv, 0)) {
+ explain1("minidle");
+ return -1;
+ }
+ lss.change |= TCF_CBQ_LSS_MINIDLE;
+ } else if (matches(*argv, "minburst") == 0) {
+ NEXT_ARG();
+ if (get_u32(&minburst, *argv, 0)) {
+ explain1("minburst");
+ return -1;
+ }
+ lss.change |= TCF_CBQ_LSS_OFFTIME;
+ } else if (matches(*argv, "maxburst") == 0) {
+ NEXT_ARG();
+ if (get_u32(&maxburst, *argv, 0)) {
+ explain1("maxburst");
+ return -1;
+ }
+ lss.change |= TCF_CBQ_LSS_MAXIDLE;
+ } else if (matches(*argv, "bounded") == 0) {
+ lss.flags |= TCF_CBQ_LSS_BOUNDED;
+ lss.change |= TCF_CBQ_LSS_FLAGS;
+ } else if (matches(*argv, "borrow") == 0) {
+ lss.flags &= ~TCF_CBQ_LSS_BOUNDED;
+ lss.change |= TCF_CBQ_LSS_FLAGS;
+ } else if (matches(*argv, "isolated") == 0) {
+ lss.flags |= TCF_CBQ_LSS_ISOLATED;
+ lss.change |= TCF_CBQ_LSS_FLAGS;
+ } else if (matches(*argv, "sharing") == 0) {
+ lss.flags &= ~TCF_CBQ_LSS_ISOLATED;
+ lss.change |= TCF_CBQ_LSS_FLAGS;
+ } else if (matches(*argv, "ewma") == 0) {
+ NEXT_ARG();
+ if (get_integer(&ewma_log, *argv, 0)) {
+ explain1("ewma");
+ return -1;
+ }
+ if (ewma_log > 31) {
+ fprintf(stderr, "ewma_log must be < 32\n");
+ return -1;
+ }
+ lss.change |= TCF_CBQ_LSS_EWMA;
+ } else if (matches(*argv, "cell") == 0) {
+ unsigned int cell;
+ int i;
+
+ NEXT_ARG();
+ if (get_size(&cell, *argv)) {
+ explain1("cell");
+ return -1;
+ }
+ for (i = 0; i < 32; i++)
+ if ((1<<i) == cell)
+ break;
+ if (i >= 32) {
+ fprintf(stderr, "cell must be 2^n\n");
+ return -1;
+ }
+ cell_log = i;
+ } else if (matches(*argv, "prio") == 0) {
+ unsigned int prio;
+
+ NEXT_ARG();
+ if (get_u32(&prio, *argv, 0)) {
+ explain1("prio");
+ return -1;
+ }
+ if (prio > TC_CBQ_MAXPRIO) {
+ fprintf(stderr, "\"prio\" must be number in the range 1...%d\n", TC_CBQ_MAXPRIO);
+ return -1;
+ }
+ wrr.priority = prio;
+ wrr_ok++;
+ } else if (matches(*argv, "allot") == 0) {
+ NEXT_ARG();
+ if (get_size(&wrr.allot, *argv)) {
+ explain1("allot");
+ return -1;
+ }
+ } else if (matches(*argv, "avpkt") == 0) {
+ NEXT_ARG();
+ if (get_size(&lss.avpkt, *argv)) {
+ explain1("avpkt");
+ return -1;
+ }
+ lss.change |= TCF_CBQ_LSS_AVPKT;
+ } else if (matches(*argv, "mpu") == 0) {
+ NEXT_ARG();
+ if (get_size(&mpu, *argv)) {
+ explain1("mpu");
+ return -1;
+ }
+ } else if (matches(*argv, "weight") == 0) {
+ NEXT_ARG();
+ if (get_size(&wrr.weight, *argv)) {
+ explain1("weight");
+ return -1;
+ }
+ wrr_ok++;
+ } else if (matches(*argv, "split") == 0) {
+ NEXT_ARG();
+ if (get_tc_classid(&fopt.split, *argv)) {
+ fprintf(stderr, "Invalid split node ID.\n");
+ return -1;
+ }
+ fopt_ok++;
+ } else if (matches(*argv, "defmap") == 0) {
+ int err;
+
+ NEXT_ARG();
+ err = sscanf(*argv, "%08x/%08x", &fopt.defmap, &fopt.defchange);
+ if (err < 1) {
+ fprintf(stderr, "Invalid defmap, should be MASK32[/MASK]\n");
+ return -1;
+ }
+ if (err == 1)
+ fopt.defchange = ~0;
+ fopt_ok++;
+ } else if (matches(*argv, "overhead") == 0) {
+ NEXT_ARG();
+ if (get_u16(&overhead, *argv, 10)) {
+ explain1("overhead"); return -1;
+ }
+ } else if (matches(*argv, "linklayer") == 0) {
+ NEXT_ARG();
+ if (get_linklayer(&linklayer, *argv)) {
+ explain1("linklayer"); return -1;
+ }
+ } else if (matches(*argv, "help") == 0) {
+ explain_class();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain_class();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ /* OK. All options are parsed. */
+
+ /* 1. Prepare link sharing scheduler parameters */
+ if (r.rate) {
+ unsigned int pktsize = wrr.allot;
+
+ if (wrr.allot < (lss.avpkt*3)/2)
+ wrr.allot = (lss.avpkt*3)/2;
+ r.mpu = mpu;
+ r.overhead = overhead;
+ if (tc_calc_rtable(&r, rtab, cell_log, pktsize, linklayer) < 0) {
+ fprintf(stderr, "CBQ: failed to calculate rate table.\n");
+ return -1;
+ }
+ }
+ if (ewma_log < 0)
+ ewma_log = TC_CBQ_DEF_EWMA;
+ lss.ewma_log = ewma_log;
+ if (lss.change&(TCF_CBQ_LSS_OFFTIME|TCF_CBQ_LSS_MAXIDLE)) {
+ if (lss.avpkt == 0) {
+ fprintf(stderr, "CBQ: avpkt is required for max/minburst.\n");
+ return -1;
+ }
+ if (bndw == 0 || r.rate == 0) {
+ fprintf(stderr, "CBQ: bandwidth&rate are required for max/minburst.\n");
+ return -1;
+ }
+ }
+ if (wrr.priority == 0 && (n->nlmsg_flags&NLM_F_EXCL)) {
+ wrr_ok = 1;
+ wrr.priority = TC_CBQ_MAXPRIO;
+ if (wrr.allot == 0)
+ wrr.allot = (lss.avpkt*3)/2;
+ }
+ if (wrr_ok) {
+ if (wrr.weight == 0)
+ wrr.weight = (wrr.priority == TC_CBQ_MAXPRIO) ? 1 : r.rate;
+ if (wrr.allot == 0) {
+ fprintf(stderr, "CBQ: \"allot\" is required to set WRR parameters.\n");
+ return -1;
+ }
+ }
+ if (lss.change&TCF_CBQ_LSS_MAXIDLE) {
+ lss.maxidle = tc_cbq_calc_maxidle(bndw, r.rate, lss.avpkt, ewma_log, maxburst);
+ lss.change |= TCF_CBQ_LSS_MAXIDLE;
+ lss.change |= TCF_CBQ_LSS_EWMA|TCF_CBQ_LSS_AVPKT;
+ }
+ if (lss.change&TCF_CBQ_LSS_OFFTIME) {
+ lss.offtime = tc_cbq_calc_offtime(bndw, r.rate, lss.avpkt, ewma_log, minburst);
+ lss.change |= TCF_CBQ_LSS_OFFTIME;
+ lss.change |= TCF_CBQ_LSS_EWMA|TCF_CBQ_LSS_AVPKT;
+ }
+ if (lss.change&TCF_CBQ_LSS_MINIDLE) {
+ lss.minidle <<= lss.ewma_log;
+ lss.change |= TCF_CBQ_LSS_EWMA;
+ }
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ if (lss.change) {
+ lss.change |= TCF_CBQ_LSS_FLAGS;
+ addattr_l(n, 1024, TCA_CBQ_LSSOPT, &lss, sizeof(lss));
+ }
+ if (wrr_ok)
+ addattr_l(n, 1024, TCA_CBQ_WRROPT, &wrr, sizeof(wrr));
+ if (fopt_ok)
+ addattr_l(n, 1024, TCA_CBQ_FOPT, &fopt, sizeof(fopt));
+ if (r.rate) {
+ addattr_l(n, 1024, TCA_CBQ_RATE, &r, sizeof(r));
+ addattr_l(n, 3024, TCA_CBQ_RTAB, rtab, 1024);
+ if (show_raw) {
+ int i;
+
+ for (i = 0; i < 256; i++)
+ printf("%u ", rtab[i]);
+ printf("\n");
+ }
+ }
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+
+static int cbq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_CBQ_MAX+1];
+ struct tc_ratespec *r = NULL;
+ struct tc_cbq_lssopt *lss = NULL;
+ struct tc_cbq_wrropt *wrr = NULL;
+ struct tc_cbq_fopt *fopt = NULL;
+ struct tc_cbq_ovl *ovl = NULL;
+ unsigned int linklayer;
+
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_CBQ_MAX, opt);
+
+ if (tb[TCA_CBQ_RATE]) {
+ if (RTA_PAYLOAD(tb[TCA_CBQ_RATE]) < sizeof(*r))
+ fprintf(stderr, "CBQ: too short rate opt\n");
+ else
+ r = RTA_DATA(tb[TCA_CBQ_RATE]);
+ }
+ if (tb[TCA_CBQ_LSSOPT]) {
+ if (RTA_PAYLOAD(tb[TCA_CBQ_LSSOPT]) < sizeof(*lss))
+ fprintf(stderr, "CBQ: too short lss opt\n");
+ else
+ lss = RTA_DATA(tb[TCA_CBQ_LSSOPT]);
+ }
+ if (tb[TCA_CBQ_WRROPT]) {
+ if (RTA_PAYLOAD(tb[TCA_CBQ_WRROPT]) < sizeof(*wrr))
+ fprintf(stderr, "CBQ: too short wrr opt\n");
+ else
+ wrr = RTA_DATA(tb[TCA_CBQ_WRROPT]);
+ }
+ if (tb[TCA_CBQ_FOPT]) {
+ if (RTA_PAYLOAD(tb[TCA_CBQ_FOPT]) < sizeof(*fopt))
+ fprintf(stderr, "CBQ: too short fopt\n");
+ else
+ fopt = RTA_DATA(tb[TCA_CBQ_FOPT]);
+ }
+ if (tb[TCA_CBQ_OVL_STRATEGY]) {
+ if (RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]) < sizeof(*ovl))
+ fprintf(stderr, "CBQ: too short overlimit strategy %u/%u\n",
+ (unsigned int) RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]),
+ (unsigned int) sizeof(*ovl));
+ else
+ ovl = RTA_DATA(tb[TCA_CBQ_OVL_STRATEGY]);
+ }
+
+ if (r) {
+ char buf[64];
+
+ print_rate(buf, sizeof(buf), r->rate);
+ fprintf(f, "rate %s ", buf);
+ linklayer = (r->linklayer & TC_LINKLAYER_MASK);
+ if (linklayer > TC_LINKLAYER_ETHERNET || show_details)
+ fprintf(f, "linklayer %s ", sprint_linklayer(linklayer, b2));
+ if (show_details) {
+ fprintf(f, "cell %ub ", 1<<r->cell_log);
+ if (r->mpu)
+ fprintf(f, "mpu %ub ", r->mpu);
+ if (r->overhead)
+ fprintf(f, "overhead %ub ", r->overhead);
+ }
+ }
+ if (lss && lss->flags) {
+ int comma = 0;
+
+ fprintf(f, "(");
+ if (lss->flags&TCF_CBQ_LSS_BOUNDED) {
+ fprintf(f, "bounded");
+ comma = 1;
+ }
+ if (lss->flags&TCF_CBQ_LSS_ISOLATED) {
+ if (comma)
+ fprintf(f, ",");
+ fprintf(f, "isolated");
+ }
+ fprintf(f, ") ");
+ }
+ if (wrr) {
+ if (wrr->priority != TC_CBQ_MAXPRIO)
+ fprintf(f, "prio %u", wrr->priority);
+ else
+ fprintf(f, "prio no-transmit");
+ if (show_details) {
+ char buf[64];
+
+ fprintf(f, "/%u ", wrr->cpriority);
+ if (wrr->weight != 1) {
+ print_rate(buf, sizeof(buf), wrr->weight);
+ fprintf(f, "weight %s ", buf);
+ }
+ if (wrr->allot)
+ fprintf(f, "allot %ub ", wrr->allot);
+ }
+ }
+ if (lss && show_details) {
+ fprintf(f, "\nlevel %u ewma %u avpkt %ub ", lss->level, lss->ewma_log, lss->avpkt);
+ if (lss->maxidle) {
+ fprintf(f, "maxidle %s ", sprint_ticks(lss->maxidle>>lss->ewma_log, b1));
+ if (show_raw)
+ fprintf(f, "[%08x] ", lss->maxidle);
+ }
+ if (lss->minidle != 0x7fffffff) {
+ fprintf(f, "minidle %s ", sprint_ticks(lss->minidle>>lss->ewma_log, b1));
+ if (show_raw)
+ fprintf(f, "[%08x] ", lss->minidle);
+ }
+ if (lss->offtime) {
+ fprintf(f, "offtime %s ", sprint_ticks(lss->offtime, b1));
+ if (show_raw)
+ fprintf(f, "[%08x] ", lss->offtime);
+ }
+ }
+ if (fopt && show_details) {
+ char buf[64];
+
+ print_tc_classid(buf, sizeof(buf), fopt->split);
+ fprintf(f, "\nsplit %s ", buf);
+ if (fopt->defmap) {
+ fprintf(f, "defmap %08x", fopt->defmap);
+ }
+ }
+ return 0;
+}
+
+static int cbq_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+ struct tc_cbq_xstats *st;
+
+ if (xstats == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+
+ st = RTA_DATA(xstats);
+ fprintf(f, " borrowed %u overactions %u avgidle %g undertime %g", st->borrows,
+ st->overactions, (double)st->avgidle, (double)st->undertime);
+ return 0;
+}
+
+struct qdisc_util cbq_qdisc_util = {
+ .id = "cbq",
+ .parse_qopt = cbq_parse_opt,
+ .print_qopt = cbq_print_opt,
+ .print_xstats = cbq_print_xstats,
+ .parse_copt = cbq_parse_class_opt,
+ .print_copt = cbq_print_opt,
+};
diff --git a/tc/q_cbs.c b/tc/q_cbs.c
new file mode 100644
index 0000000..9515a1f
--- /dev/null
+++ b/tc/q_cbs.c
@@ -0,0 +1,141 @@
+/*
+ * q_cbs.c CBS.
+ *
+ * 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: Vinicius Costa Gomes <vinicius.gomes@intel.com>
+ *
+ */
+
+#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 "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... cbs hicredit BYTES locredit BYTES sendslope BPS idleslope BPS\n"
+ " [offload 0|1]\n");
+}
+
+static void explain1(const char *arg, const char *val)
+{
+ fprintf(stderr, "cbs: illegal value for \"%s\": \"%s\"\n", arg, val);
+}
+
+static int cbs_parse_opt(struct qdisc_util *qu, int argc,
+ char **argv, struct nlmsghdr *n, const char *dev)
+{
+ struct tc_cbs_qopt opt = {};
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (matches(*argv, "offload") == 0) {
+ NEXT_ARG();
+ if (opt.offload) {
+ fprintf(stderr, "cbs: duplicate \"offload\" specification\n");
+ return -1;
+ }
+ if (get_u8(&opt.offload, *argv, 0)) {
+ explain1("offload", *argv);
+ return -1;
+ }
+ } else if (matches(*argv, "hicredit") == 0) {
+ NEXT_ARG();
+ if (opt.hicredit) {
+ fprintf(stderr, "cbs: duplicate \"hicredit\" specification\n");
+ return -1;
+ }
+ if (get_s32(&opt.hicredit, *argv, 0)) {
+ explain1("hicredit", *argv);
+ return -1;
+ }
+ } else if (matches(*argv, "locredit") == 0) {
+ NEXT_ARG();
+ if (opt.locredit) {
+ fprintf(stderr, "cbs: duplicate \"locredit\" specification\n");
+ return -1;
+ }
+ if (get_s32(&opt.locredit, *argv, 0)) {
+ explain1("locredit", *argv);
+ return -1;
+ }
+ } else if (matches(*argv, "sendslope") == 0) {
+ NEXT_ARG();
+ if (opt.sendslope) {
+ fprintf(stderr, "cbs: duplicate \"sendslope\" specification\n");
+ return -1;
+ }
+ if (get_s32(&opt.sendslope, *argv, 0)) {
+ explain1("sendslope", *argv);
+ return -1;
+ }
+ } else if (matches(*argv, "idleslope") == 0) {
+ NEXT_ARG();
+ if (opt.idleslope) {
+ fprintf(stderr, "cbs: duplicate \"idleslope\" specification\n");
+ return -1;
+ }
+ if (get_s32(&opt.idleslope, *argv, 0)) {
+ explain1("idleslope", *argv);
+ return -1;
+ }
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "cbs: unknown parameter \"%s\"\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 2024, TCA_CBS_PARMS, &opt, sizeof(opt));
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int cbs_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_CBS_MAX+1];
+ struct tc_cbs_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_CBS_MAX, opt);
+
+ if (tb[TCA_CBS_PARMS] == NULL)
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_CBS_PARMS]);
+ if (RTA_PAYLOAD(tb[TCA_CBS_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ fprintf(f, "hicredit %d ", qopt->hicredit);
+ fprintf(f, "locredit %d ", qopt->locredit);
+ fprintf(f, "sendslope %d ", qopt->sendslope);
+ fprintf(f, "idleslope %d ", qopt->idleslope);
+ fprintf(f, "offload %d ", qopt->offload);
+
+ return 0;
+}
+
+struct qdisc_util cbs_qdisc_util = {
+ .id = "cbs",
+ .parse_qopt = cbs_parse_opt,
+ .print_qopt = cbs_print_opt,
+};
diff --git a/tc/q_choke.c b/tc/q_choke.c
new file mode 100644
index 0000000..648d9ad
--- /dev/null
+++ b/tc/q_choke.c
@@ -0,0 +1,228 @@
+/*
+ * q_choke.c CHOKE.
+ *
+ * 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 <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <math.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+#include "tc_red.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... choke limit PACKETS bandwidth KBPS [ecn]\n"
+ " [ min PACKETS ] [ max PACKETS ] [ burst PACKETS ]\n");
+}
+
+static int choke_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct tc_red_qopt opt = {};
+ unsigned int burst = 0;
+ unsigned int avpkt = 1000;
+ double probability = 0.02;
+ unsigned int rate = 0;
+ int ecn_ok = 0;
+ int wlog;
+ __u8 sbuf[256];
+ __u32 max_P;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&opt.limit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "bandwidth") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&rate, *argv, dev)) {
+ fprintf(stderr, "Illegal \"bandwidth\"\n");
+ return -1;
+ }
+ } else if (get_rate(&rate, *argv)) {
+ fprintf(stderr, "Illegal \"bandwidth\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ecn") == 0) {
+ ecn_ok = 1;
+ } else if (strcmp(*argv, "min") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&opt.qth_min, *argv, 0)) {
+ fprintf(stderr, "Illegal \"min\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "max") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&opt.qth_max, *argv, 0)) {
+ fprintf(stderr, "Illegal \"max\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "burst") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&burst, *argv, 0)) {
+ fprintf(stderr, "Illegal \"burst\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "avpkt") == 0) {
+ NEXT_ARG();
+ if (get_size(&avpkt, *argv)) {
+ fprintf(stderr, "Illegal \"avpkt\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "probability") == 0) {
+ NEXT_ARG();
+ if (sscanf(*argv, "%lg", &probability) != 1) {
+ fprintf(stderr, "Illegal \"probability\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!rate || !opt.limit) {
+ fprintf(stderr, "Required parameter (bandwidth, limit) is missing\n");
+ return -1;
+ }
+
+ /* Compute default min/max thresholds based on
+ Sally Floyd's recommendations:
+ http://www.icir.org/floyd/REDparameters.txt
+ */
+ if (!opt.qth_max)
+ opt.qth_max = opt.limit / 4;
+ if (!opt.qth_min)
+ opt.qth_min = opt.qth_max / 3;
+ if (!burst)
+ burst = (2 * opt.qth_min + opt.qth_max) / 3;
+
+ if (opt.qth_max > opt.limit) {
+ fprintf(stderr, "\"max\" is larger than \"limit\"\n");
+ return -1;
+ }
+
+ if (opt.qth_min >= opt.qth_max) {
+ fprintf(stderr, "\"min\" is not smaller than \"max\"\n");
+ return -1;
+ }
+
+ wlog = tc_red_eval_ewma(opt.qth_min*avpkt, burst, avpkt);
+ if (wlog < 0) {
+ fprintf(stderr, "CHOKE: failed to calculate EWMA constant.\n");
+ return -1;
+ }
+ if (wlog >= 10)
+ fprintf(stderr, "CHOKE: WARNING. Burst %d seems to be too large.\n", burst);
+ opt.Wlog = wlog;
+
+ wlog = tc_red_eval_P(opt.qth_min*avpkt, opt.qth_max*avpkt, probability);
+ if (wlog < 0) {
+ fprintf(stderr, "CHOKE: failed to calculate probability.\n");
+ return -1;
+ }
+ opt.Plog = wlog;
+
+ wlog = tc_red_eval_idle_damping(opt.Wlog, avpkt, rate, sbuf);
+ if (wlog < 0) {
+ fprintf(stderr, "CHOKE: failed to calculate idle damping table.\n");
+ return -1;
+ }
+ opt.Scell_log = wlog;
+ if (ecn_ok)
+ opt.flags |= TC_RED_ECN;
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 1024, TCA_CHOKE_PARMS, &opt, sizeof(opt));
+ addattr_l(n, 1024, TCA_CHOKE_STAB, sbuf, 256);
+ max_P = probability * pow(2, 32);
+ addattr_l(n, 1024, TCA_CHOKE_MAX_P, &max_P, sizeof(max_P));
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int choke_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_CHOKE_MAX+1];
+ const struct tc_red_qopt *qopt;
+ __u32 max_P = 0;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_CHOKE_MAX, opt);
+
+ if (tb[TCA_CHOKE_PARMS] == NULL)
+ return -1;
+ qopt = RTA_DATA(tb[TCA_CHOKE_PARMS]);
+ if (RTA_PAYLOAD(tb[TCA_CHOKE_PARMS]) < sizeof(*qopt))
+ return -1;
+ if (tb[TCA_CHOKE_MAX_P] &&
+ RTA_PAYLOAD(tb[TCA_CHOKE_MAX_P]) >= sizeof(__u32))
+ max_P = rta_getattr_u32(tb[TCA_CHOKE_MAX_P]);
+
+ fprintf(f, "limit %up min %up max %up ",
+ qopt->limit, qopt->qth_min, qopt->qth_max);
+
+ tc_red_print_flags(qopt->flags);
+
+ if (show_details) {
+ fprintf(f, "ewma %u ", qopt->Wlog);
+ if (max_P)
+ fprintf(f, "probability %g ", max_P / pow(2, 32));
+ else
+ fprintf(f, "Plog %u ", qopt->Plog);
+ fprintf(f, "Scell_log %u", qopt->Scell_log);
+ }
+ return 0;
+}
+
+static int choke_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct tc_choke_xstats *st;
+
+ if (xstats == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+
+ st = RTA_DATA(xstats);
+ fprintf(f, " marked %u early %u pdrop %u other %u matched %u",
+ st->marked, st->early, st->pdrop, st->other, st->matched);
+ return 0;
+
+}
+
+struct qdisc_util choke_qdisc_util = {
+ .id = "choke",
+ .parse_qopt = choke_parse_opt,
+ .print_qopt = choke_print_opt,
+ .print_xstats = choke_print_xstats,
+};
diff --git a/tc/q_clsact.c b/tc/q_clsact.c
new file mode 100644
index 0000000..341f653
--- /dev/null
+++ b/tc/q_clsact.c
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... clsact\n");
+}
+
+static int clsact_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ if (argc > 0) {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+
+ return 0;
+}
+
+static int clsact_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ return 0;
+}
+
+struct qdisc_util clsact_qdisc_util = {
+ .id = "clsact",
+ .parse_qopt = clsact_parse_opt,
+ .print_qopt = clsact_print_opt,
+};
diff --git a/tc/q_codel.c b/tc/q_codel.c
new file mode 100644
index 0000000..849cc04
--- /dev/null
+++ b/tc/q_codel.c
@@ -0,0 +1,211 @@
+/*
+ * Codel - The Controlled-Delay Active Queue Management algorithm
+ *
+ * Copyright (C) 2011-2012 Kathleen Nichols <nichols@pollere.com>
+ * Copyright (C) 2011-2012 Van Jacobson <van@pollere.com>
+ * Copyright (C) 2012 Michael D. Taht <dave.taht@bufferbloat.net>
+ * Copyright (C) 2012,2015 Eric Dumazet <edumazet@google.com>
+ *
+ * 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,
+ * without modification.
+ * 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. The names of the authors may not 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.
+ *
+ * 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.
+ *
+ */
+
+#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 "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... codel [ limit PACKETS ] [ target TIME ]\n"
+ " [ interval TIME ] [ ecn | noecn ]\n"
+ " [ ce_threshold TIME ]\n");
+}
+
+static int codel_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ unsigned int limit = 0;
+ unsigned int target = 0;
+ unsigned int interval = 0;
+ unsigned int ce_threshold = ~0U;
+ int ecn = -1;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&limit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "target") == 0) {
+ NEXT_ARG();
+ if (get_time(&target, *argv)) {
+ fprintf(stderr, "Illegal \"target\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ce_threshold") == 0) {
+ NEXT_ARG();
+ if (get_time(&ce_threshold, *argv)) {
+ fprintf(stderr, "Illegal \"ce_threshold\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "interval") == 0) {
+ NEXT_ARG();
+ if (get_time(&interval, *argv)) {
+ fprintf(stderr, "Illegal \"interval\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ecn") == 0) {
+ ecn = 1;
+ } else if (strcmp(*argv, "noecn") == 0) {
+ ecn = 0;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ if (limit)
+ addattr_l(n, 1024, TCA_CODEL_LIMIT, &limit, sizeof(limit));
+ if (interval)
+ addattr_l(n, 1024, TCA_CODEL_INTERVAL, &interval, sizeof(interval));
+ if (target)
+ addattr_l(n, 1024, TCA_CODEL_TARGET, &target, sizeof(target));
+ if (ecn != -1)
+ addattr_l(n, 1024, TCA_CODEL_ECN, &ecn, sizeof(ecn));
+ if (ce_threshold != ~0U)
+ addattr_l(n, 1024, TCA_CODEL_CE_THRESHOLD,
+ &ce_threshold, sizeof(ce_threshold));
+
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int codel_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_CODEL_MAX + 1];
+ unsigned int limit;
+ unsigned int interval;
+ unsigned int target;
+ unsigned int ecn;
+ unsigned int ce_threshold;
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_CODEL_MAX, opt);
+
+ if (tb[TCA_CODEL_LIMIT] &&
+ RTA_PAYLOAD(tb[TCA_CODEL_LIMIT]) >= sizeof(__u32)) {
+ limit = rta_getattr_u32(tb[TCA_CODEL_LIMIT]);
+ fprintf(f, "limit %up ", limit);
+ }
+ if (tb[TCA_CODEL_TARGET] &&
+ RTA_PAYLOAD(tb[TCA_CODEL_TARGET]) >= sizeof(__u32)) {
+ target = rta_getattr_u32(tb[TCA_CODEL_TARGET]);
+ fprintf(f, "target %s ", sprint_time(target, b1));
+ }
+ if (tb[TCA_CODEL_CE_THRESHOLD] &&
+ RTA_PAYLOAD(tb[TCA_CODEL_CE_THRESHOLD]) >= sizeof(__u32)) {
+ ce_threshold = rta_getattr_u32(tb[TCA_CODEL_CE_THRESHOLD]);
+ fprintf(f, "ce_threshold %s ", sprint_time(ce_threshold, b1));
+ }
+ if (tb[TCA_CODEL_INTERVAL] &&
+ RTA_PAYLOAD(tb[TCA_CODEL_INTERVAL]) >= sizeof(__u32)) {
+ interval = rta_getattr_u32(tb[TCA_CODEL_INTERVAL]);
+ fprintf(f, "interval %s ", sprint_time(interval, b1));
+ }
+ if (tb[TCA_CODEL_ECN] &&
+ RTA_PAYLOAD(tb[TCA_CODEL_ECN]) >= sizeof(__u32)) {
+ ecn = rta_getattr_u32(tb[TCA_CODEL_ECN]);
+ if (ecn)
+ fprintf(f, "ecn ");
+ }
+
+ return 0;
+}
+
+static int codel_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct tc_codel_xstats _st = {}, *st;
+
+ SPRINT_BUF(b1);
+
+ if (xstats == NULL)
+ return 0;
+
+ st = RTA_DATA(xstats);
+ if (RTA_PAYLOAD(xstats) < sizeof(*st)) {
+ memcpy(&_st, st, RTA_PAYLOAD(xstats));
+ st = &_st;
+ }
+
+ fprintf(f, " count %u lastcount %u ldelay %s",
+ st->count, st->lastcount, sprint_time(st->ldelay, b1));
+ if (st->dropping)
+ fprintf(f, " dropping");
+ if (st->drop_next < 0)
+ fprintf(f, " drop_next -%s", sprint_time(-st->drop_next, b1));
+ else
+ fprintf(f, " drop_next %s", sprint_time(st->drop_next, b1));
+ fprintf(f, "\n maxpacket %u ecn_mark %u drop_overlimit %u",
+ st->maxpacket, st->ecn_mark, st->drop_overlimit);
+ if (st->ce_mark)
+ fprintf(f, " ce_mark %u", st->ce_mark);
+ return 0;
+
+}
+
+struct qdisc_util codel_qdisc_util = {
+ .id = "codel",
+ .parse_qopt = codel_parse_opt,
+ .print_qopt = codel_print_opt,
+ .print_xstats = codel_print_xstats,
+};
diff --git a/tc/q_drr.c b/tc/q_drr.c
new file mode 100644
index 0000000..f9c90f3
--- /dev/null
+++ b/tc/q_drr.c
@@ -0,0 +1,123 @@
+/*
+ * q_drr.c DRR.
+ *
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... drr\n");
+}
+
+static void explain2(void)
+{
+ fprintf(stderr, "Usage: ... drr quantum SIZE\n");
+}
+
+
+static int drr_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ while (argc) {
+ if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int drr_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct rtattr *tail;
+ __u32 tmp;
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+
+ while (argc > 0) {
+ if (strcmp(*argv, "quantum") == 0) {
+ NEXT_ARG();
+ if (get_size(&tmp, *argv)) {
+ fprintf(stderr, "Illegal \"quantum\"\n");
+ return -1;
+ }
+ addattr_l(n, 1024, TCA_DRR_QUANTUM, &tmp, sizeof(tmp));
+ } else if (strcmp(*argv, "help") == 0) {
+ explain2();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain2();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int drr_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_DRR_MAX + 1];
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_DRR_MAX, opt);
+
+ if (tb[TCA_DRR_QUANTUM])
+ fprintf(f, "quantum %s ",
+ sprint_size(rta_getattr_u32(tb[TCA_DRR_QUANTUM]), b1));
+ return 0;
+}
+
+static int drr_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+ struct tc_drr_stats *x;
+
+ SPRINT_BUF(b1);
+
+ if (xstats == NULL)
+ return 0;
+ if (RTA_PAYLOAD(xstats) < sizeof(*x))
+ return -1;
+ x = RTA_DATA(xstats);
+
+ fprintf(f, " deficit %s ", sprint_size(x->deficit, b1));
+ return 0;
+}
+
+struct qdisc_util drr_qdisc_util = {
+ .id = "drr",
+ .parse_qopt = drr_parse_opt,
+ .print_qopt = drr_print_opt,
+ .print_xstats = drr_print_xstats,
+ .parse_copt = drr_parse_class_opt,
+ .print_copt = drr_print_opt,
+};
diff --git a/tc/q_dsmark.c b/tc/q_dsmark.c
new file mode 100644
index 0000000..d3e8292
--- /dev/null
+++ b/tc/q_dsmark.c
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * q_dsmark.c Differentiated Services field marking.
+ *
+ * Hacked 1998,1999 by Werner Almesberger, EPFL ICA
+ *
+ */
+
+#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 "tc_util.h"
+
+
+static void explain(void)
+{
+ fprintf(stderr,"Usage: dsmark indices INDICES [ default_index DEFAULT_INDEX ] [ set_tc_index ]\n");
+}
+
+
+static int dsmark_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct rtattr *tail;
+ __u16 ind;
+ char *end;
+ int dflt, set_tc_index;
+
+ ind = set_tc_index = 0;
+ dflt = -1;
+ while (argc > 0) {
+ if (!strcmp(*argv, "indices")) {
+ NEXT_ARG();
+ ind = strtoul(*argv, &end, 0);
+ if (*end) {
+ explain();
+ return -1;
+ }
+ } else if (!strcmp(*argv,"default_index") || !strcmp(*argv,
+ "default")) {
+ NEXT_ARG();
+ dflt = strtoul(*argv, &end, 0);
+ if (*end) {
+ explain();
+ return -1;
+ }
+ } else if (!strcmp(*argv,"set_tc_index")) {
+ set_tc_index = 1;
+ } else {
+ explain();
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+ if (!ind) {
+ explain();
+ return -1;
+ }
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 1024, TCA_DSMARK_INDICES, &ind, sizeof(ind));
+ if (dflt != -1) {
+ __u16 tmp = dflt;
+
+ addattr_l(n, 1024, TCA_DSMARK_DEFAULT_INDEX, &tmp, sizeof(tmp));
+ }
+ if (set_tc_index)
+ addattr_l(n, 1024, TCA_DSMARK_SET_TC_INDEX, NULL, 0);
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+
+static void explain_class(void)
+{
+ fprintf(stderr, "Usage: ... dsmark [ mask MASK ] [ value VALUE ]\n");
+}
+
+
+static int dsmark_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct rtattr *tail;
+ __u8 tmp;
+ char *end;
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ while (argc > 0) {
+ if (!strcmp(*argv, "mask")) {
+ NEXT_ARG();
+ tmp = strtoul(*argv, &end, 0);
+ if (*end) {
+ explain_class();
+ return -1;
+ }
+ addattr_l(n, 1024, TCA_DSMARK_MASK, &tmp, 1);
+ } else if (!strcmp(*argv,"value")) {
+ NEXT_ARG();
+ tmp = strtoul(*argv, &end, 0);
+ if (*end) {
+ explain_class();
+ return -1;
+ }
+ addattr_l(n, 1024, TCA_DSMARK_VALUE, &tmp, 1);
+ } else {
+ explain_class();
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+
+
+static int dsmark_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_DSMARK_MAX+1];
+
+ if (!opt) return 0;
+ parse_rtattr(tb, TCA_DSMARK_MAX, RTA_DATA(opt), RTA_PAYLOAD(opt));
+ if (tb[TCA_DSMARK_MASK]) {
+ if (!RTA_PAYLOAD(tb[TCA_DSMARK_MASK]))
+ fprintf(stderr, "dsmark: empty mask\n");
+ else fprintf(f, "mask 0x%02x ",
+ rta_getattr_u8(tb[TCA_DSMARK_MASK]));
+ }
+ if (tb[TCA_DSMARK_VALUE]) {
+ if (!RTA_PAYLOAD(tb[TCA_DSMARK_VALUE]))
+ fprintf(stderr, "dsmark: empty value\n");
+ else fprintf(f, "value 0x%02x ",
+ rta_getattr_u8(tb[TCA_DSMARK_VALUE]));
+ }
+ if (tb[TCA_DSMARK_INDICES]) {
+ if (RTA_PAYLOAD(tb[TCA_DSMARK_INDICES]) < sizeof(__u16))
+ fprintf(stderr, "dsmark: indices too short\n");
+ else fprintf(f, "indices 0x%04x ",
+ rta_getattr_u16(tb[TCA_DSMARK_INDICES]));
+ }
+ if (tb[TCA_DSMARK_DEFAULT_INDEX]) {
+ if (RTA_PAYLOAD(tb[TCA_DSMARK_DEFAULT_INDEX]) < sizeof(__u16))
+ fprintf(stderr, "dsmark: default_index too short\n");
+ else fprintf(f, "default_index 0x%04x ",
+ rta_getattr_u16(tb[TCA_DSMARK_DEFAULT_INDEX]));
+ }
+ if (tb[TCA_DSMARK_SET_TC_INDEX]) fprintf(f, "set_tc_index ");
+ return 0;
+}
+
+
+struct qdisc_util dsmark_qdisc_util = {
+ .id = "dsmark",
+ .parse_qopt = dsmark_parse_opt,
+ .print_qopt = dsmark_print_opt,
+ .parse_copt = dsmark_parse_class_opt,
+ .print_copt = dsmark_print_opt,
+};
diff --git a/tc/q_etf.c b/tc/q_etf.c
new file mode 100644
index 0000000..c209058
--- /dev/null
+++ b/tc/q_etf.c
@@ -0,0 +1,193 @@
+/*
+ * q_etf.c Earliest TxTime First (ETF).
+ *
+ * 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: Vinicius Costa Gomes <vinicius.gomes@intel.com>
+ * Jesus Sanchez-Palencia <jesus.sanchez-palencia@intel.com>
+ *
+ */
+
+#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 "utils.h"
+#include "tc_util.h"
+
+#define CLOCKID_INVALID (-1)
+static const struct static_clockid {
+ const char *name;
+ clockid_t clockid;
+} clockids_sysv[] = {
+ { "REALTIME", CLOCK_REALTIME },
+ { "TAI", CLOCK_TAI },
+ { "BOOTTIME", CLOCK_BOOTTIME },
+ { "MONOTONIC", CLOCK_MONOTONIC },
+ { NULL }
+};
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... etf delta NANOS clockid CLOCKID [offload] [deadline_mode]\n"
+ "CLOCKID must be a valid SYS-V id (i.e. CLOCK_TAI)\n");
+}
+
+static void explain1(const char *arg, const char *val)
+{
+ fprintf(stderr, "etf: illegal value for \"%s\": \"%s\"\n", arg, val);
+}
+
+static void explain_clockid(const char *val)
+{
+ fprintf(stderr,
+ "etf: illegal value for \"clockid\": \"%s\".\n"
+ "It must be a valid SYS-V id (i.e. CLOCK_TAI)\n",
+ val);
+}
+
+static int get_clockid(__s32 *val, const char *arg)
+{
+ const struct static_clockid *c;
+
+ /* Drop the CLOCK_ prefix if that is being used. */
+ if (strcasestr(arg, "CLOCK_") != NULL)
+ arg += sizeof("CLOCK_") - 1;
+
+ for (c = clockids_sysv; c->name; c++) {
+ if (strcasecmp(c->name, arg) == 0) {
+ *val = c->clockid;
+
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static const char* get_clock_name(clockid_t clockid)
+{
+ const struct static_clockid *c;
+
+ for (c = clockids_sysv; c->name; c++) {
+ if (clockid == c->clockid)
+ return c->name;
+ }
+
+ return "invalid";
+}
+
+static int etf_parse_opt(struct qdisc_util *qu, int argc,
+ char **argv, struct nlmsghdr *n, const char *dev)
+{
+ struct tc_etf_qopt opt = {
+ .clockid = CLOCKID_INVALID,
+ };
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (matches(*argv, "offload") == 0) {
+ if (opt.flags & TC_ETF_OFFLOAD_ON) {
+ fprintf(stderr, "etf: duplicate \"offload\" specification\n");
+ return -1;
+ }
+
+ opt.flags |= TC_ETF_OFFLOAD_ON;
+ } else if (matches(*argv, "deadline_mode") == 0) {
+ if (opt.flags & TC_ETF_DEADLINE_MODE_ON) {
+ fprintf(stderr, "etf: duplicate \"deadline_mode\" specification\n");
+ return -1;
+ }
+
+ opt.flags |= TC_ETF_DEADLINE_MODE_ON;
+ } else if (matches(*argv, "delta") == 0) {
+ NEXT_ARG();
+ if (opt.delta) {
+ fprintf(stderr, "etf: duplicate \"delta\" specification\n");
+ return -1;
+ }
+ if (get_s32(&opt.delta, *argv, 0)) {
+ explain1("delta", *argv);
+ return -1;
+ }
+ } else if (matches(*argv, "clockid") == 0) {
+ NEXT_ARG();
+ if (opt.clockid != CLOCKID_INVALID) {
+ fprintf(stderr, "etf: duplicate \"clockid\" specification\n");
+ return -1;
+ }
+ if (get_clockid(&opt.clockid, *argv)) {
+ explain_clockid(*argv);
+ return -1;
+ }
+ } else if (strcmp(*argv, "skip_sock_check") == 0) {
+ if (opt.flags & TC_ETF_SKIP_SOCK_CHECK) {
+ fprintf(stderr, "etf: duplicate \"skip_sock_check\" specification\n");
+ return -1;
+ }
+
+ opt.flags |= TC_ETF_SKIP_SOCK_CHECK;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "etf: unknown parameter \"%s\"\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 2024, TCA_ETF_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int etf_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_ETF_MAX+1];
+ struct tc_etf_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_ETF_MAX, opt);
+
+ if (tb[TCA_ETF_PARMS] == NULL)
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_ETF_PARMS]);
+ if (RTA_PAYLOAD(tb[TCA_ETF_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ print_string(PRINT_ANY, "clockid", "clockid %s ",
+ get_clock_name(qopt->clockid));
+
+ print_uint(PRINT_ANY, "delta", "delta %d ", qopt->delta);
+ print_string(PRINT_ANY, "offload", "offload %s ",
+ (qopt->flags & TC_ETF_OFFLOAD_ON) ? "on" : "off");
+ print_string(PRINT_ANY, "deadline_mode", "deadline_mode %s ",
+ (qopt->flags & TC_ETF_DEADLINE_MODE_ON) ? "on" : "off");
+ print_string(PRINT_ANY, "skip_sock_check", "skip_sock_check %s",
+ (qopt->flags & TC_ETF_SKIP_SOCK_CHECK) ? "on" : "off");
+
+ return 0;
+}
+
+struct qdisc_util etf_qdisc_util = {
+ .id = "etf",
+ .parse_qopt = etf_parse_opt,
+ .print_qopt = etf_print_opt,
+};
diff --git a/tc/q_fifo.c b/tc/q_fifo.c
new file mode 100644
index 0000000..61493fb
--- /dev/null
+++ b/tc/q_fifo.c
@@ -0,0 +1,103 @@
+/*
+ * q_fifo.c FIFO.
+ *
+ * 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 "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... <[p|b]fifo | pfifo_head_drop> [ limit NUMBER ]\n");
+}
+
+static int fifo_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ int ok = 0;
+ struct tc_fifo_qopt opt = {};
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.limit, *argv)) {
+ fprintf(stderr, "%s: Illegal value for \"limit\": \"%s\"\n", qu->id, *argv);
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "%s: unknown parameter \"%s\"\n", qu->id, *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (ok)
+ addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+ return 0;
+}
+
+static int fifo_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct tc_fifo_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(opt) < sizeof(*qopt))
+ return -1;
+ qopt = RTA_DATA(opt);
+ if (strcmp(qu->id, "bfifo") == 0) {
+ SPRINT_BUF(b1);
+ print_uint(PRINT_JSON, "limit", NULL, qopt->limit);
+ print_string(PRINT_FP, NULL, "limit %s",
+ sprint_size(qopt->limit, b1));
+ } else {
+ print_uint(PRINT_ANY, "limit", "limit %up", qopt->limit);
+ }
+ return 0;
+}
+
+
+struct qdisc_util bfifo_qdisc_util = {
+ .id = "bfifo",
+ .parse_qopt = fifo_parse_opt,
+ .print_qopt = fifo_print_opt,
+};
+
+struct qdisc_util pfifo_qdisc_util = {
+ .id = "pfifo",
+ .parse_qopt = fifo_parse_opt,
+ .print_qopt = fifo_print_opt,
+};
+
+struct qdisc_util pfifo_head_drop_qdisc_util = {
+ .id = "pfifo_head_drop",
+ .parse_qopt = fifo_parse_opt,
+ .print_qopt = fifo_print_opt,
+};
+
+struct qdisc_util pfifo_fast_qdisc_util = {
+ .id = "pfifo_fast",
+ .print_qopt = prio_print_opt,
+};
diff --git a/tc/q_fq.c b/tc/q_fq.c
new file mode 100644
index 0000000..caf232e
--- /dev/null
+++ b/tc/q_fq.c
@@ -0,0 +1,386 @@
+/*
+ * Fair Queue
+ *
+ * Copyright (C) 2013-2015 Eric Dumazet <edumazet@google.com>
+ *
+ * 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,
+ * without modification.
+ * 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. The names of the authors may not 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.
+ *
+ * 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.
+ *
+ */
+
+#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 <stdbool.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... fq [ limit PACKETS ] [ flow_limit PACKETS ]\n"
+ " [ quantum BYTES ] [ initial_quantum BYTES ]\n"
+ " [ maxrate RATE ] [ buckets NUMBER ]\n"
+ " [ [no]pacing ] [ refill_delay TIME ]\n"
+ " [ low_rate_threshold RATE ]\n"
+ " [ orphan_mask MASK]\n"
+ " [ ce_threshold TIME ]\n");
+}
+
+static unsigned int ilog2(unsigned int val)
+{
+ unsigned int res = 0;
+
+ val--;
+ while (val) {
+ res++;
+ val >>= 1;
+ }
+ return res;
+}
+
+static int fq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ unsigned int plimit;
+ unsigned int flow_plimit;
+ unsigned int quantum;
+ unsigned int initial_quantum;
+ unsigned int buckets = 0;
+ unsigned int maxrate;
+ unsigned int low_rate_threshold;
+ unsigned int defrate;
+ unsigned int refill_delay;
+ unsigned int orphan_mask;
+ unsigned int ce_threshold;
+ bool set_plimit = false;
+ bool set_flow_plimit = false;
+ bool set_quantum = false;
+ bool set_initial_quantum = false;
+ bool set_maxrate = false;
+ bool set_defrate = false;
+ bool set_refill_delay = false;
+ bool set_orphan_mask = false;
+ bool set_low_rate_threshold = false;
+ bool set_ce_threshold = false;
+ int pacing = -1;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&plimit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ set_plimit = true;
+ } else if (strcmp(*argv, "flow_limit") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&flow_plimit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"flow_limit\"\n");
+ return -1;
+ }
+ set_flow_plimit = true;
+ } else if (strcmp(*argv, "buckets") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&buckets, *argv, 0)) {
+ fprintf(stderr, "Illegal \"buckets\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "maxrate") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&maxrate, *argv, dev)) {
+ fprintf(stderr, "Illegal \"maxrate\"\n");
+ return -1;
+ }
+ } else if (get_rate(&maxrate, *argv)) {
+ fprintf(stderr, "Illegal \"maxrate\"\n");
+ return -1;
+ }
+ set_maxrate = true;
+ } else if (strcmp(*argv, "low_rate_threshold") == 0) {
+ NEXT_ARG();
+ if (get_rate(&low_rate_threshold, *argv)) {
+ fprintf(stderr, "Illegal \"low_rate_threshold\"\n");
+ return -1;
+ }
+ set_low_rate_threshold = true;
+ } else if (strcmp(*argv, "ce_threshold") == 0) {
+ NEXT_ARG();
+ if (get_time(&ce_threshold, *argv)) {
+ fprintf(stderr, "Illegal \"ce_threshold\"\n");
+ return -1;
+ }
+ set_ce_threshold = true;
+ } else if (strcmp(*argv, "defrate") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&defrate, *argv, dev)) {
+ fprintf(stderr, "Illegal \"defrate\"\n");
+ return -1;
+ }
+ } else if (get_rate(&defrate, *argv)) {
+ fprintf(stderr, "Illegal \"defrate\"\n");
+ return -1;
+ }
+ set_defrate = true;
+ } else if (strcmp(*argv, "quantum") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&quantum, *argv, 0)) {
+ fprintf(stderr, "Illegal \"quantum\"\n");
+ return -1;
+ }
+ set_quantum = true;
+ } else if (strcmp(*argv, "initial_quantum") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&initial_quantum, *argv, 0)) {
+ fprintf(stderr, "Illegal \"initial_quantum\"\n");
+ return -1;
+ }
+ set_initial_quantum = true;
+ } else if (strcmp(*argv, "orphan_mask") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&orphan_mask, *argv, 0)) {
+ fprintf(stderr, "Illegal \"initial_quantum\"\n");
+ return -1;
+ }
+ set_orphan_mask = true;
+ } else if (strcmp(*argv, "refill_delay") == 0) {
+ NEXT_ARG();
+ if (get_time(&refill_delay, *argv)) {
+ fprintf(stderr, "Illegal \"refill_delay\"\n");
+ return -1;
+ }
+ set_refill_delay = true;
+ } else if (strcmp(*argv, "pacing") == 0) {
+ pacing = 1;
+ } else if (strcmp(*argv, "nopacing") == 0) {
+ pacing = 0;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ if (buckets) {
+ unsigned int log = ilog2(buckets);
+
+ addattr_l(n, 1024, TCA_FQ_BUCKETS_LOG,
+ &log, sizeof(log));
+ }
+ if (set_plimit)
+ addattr_l(n, 1024, TCA_FQ_PLIMIT,
+ &plimit, sizeof(plimit));
+ if (set_flow_plimit)
+ addattr_l(n, 1024, TCA_FQ_FLOW_PLIMIT,
+ &flow_plimit, sizeof(flow_plimit));
+ if (set_quantum)
+ addattr_l(n, 1024, TCA_FQ_QUANTUM, &quantum, sizeof(quantum));
+ if (set_initial_quantum)
+ addattr_l(n, 1024, TCA_FQ_INITIAL_QUANTUM,
+ &initial_quantum, sizeof(initial_quantum));
+ if (pacing != -1)
+ addattr_l(n, 1024, TCA_FQ_RATE_ENABLE,
+ &pacing, sizeof(pacing));
+ if (set_maxrate)
+ addattr_l(n, 1024, TCA_FQ_FLOW_MAX_RATE,
+ &maxrate, sizeof(maxrate));
+ if (set_low_rate_threshold)
+ addattr_l(n, 1024, TCA_FQ_LOW_RATE_THRESHOLD,
+ &low_rate_threshold, sizeof(low_rate_threshold));
+ if (set_defrate)
+ addattr_l(n, 1024, TCA_FQ_FLOW_DEFAULT_RATE,
+ &defrate, sizeof(defrate));
+ if (set_refill_delay)
+ addattr_l(n, 1024, TCA_FQ_FLOW_REFILL_DELAY,
+ &refill_delay, sizeof(refill_delay));
+ if (set_orphan_mask)
+ addattr_l(n, 1024, TCA_FQ_ORPHAN_MASK,
+ &orphan_mask, sizeof(refill_delay));
+ if (set_ce_threshold)
+ addattr_l(n, 1024, TCA_FQ_CE_THRESHOLD,
+ &ce_threshold, sizeof(ce_threshold));
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int fq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_FQ_MAX + 1];
+ unsigned int plimit, flow_plimit;
+ unsigned int buckets_log;
+ int pacing;
+ unsigned int rate, quantum;
+ unsigned int refill_delay;
+ unsigned int orphan_mask;
+ unsigned int ce_threshold;
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_FQ_MAX, opt);
+
+ if (tb[TCA_FQ_PLIMIT] &&
+ RTA_PAYLOAD(tb[TCA_FQ_PLIMIT]) >= sizeof(__u32)) {
+ plimit = rta_getattr_u32(tb[TCA_FQ_PLIMIT]);
+ fprintf(f, "limit %up ", plimit);
+ }
+ if (tb[TCA_FQ_FLOW_PLIMIT] &&
+ RTA_PAYLOAD(tb[TCA_FQ_FLOW_PLIMIT]) >= sizeof(__u32)) {
+ flow_plimit = rta_getattr_u32(tb[TCA_FQ_FLOW_PLIMIT]);
+ fprintf(f, "flow_limit %up ", flow_plimit);
+ }
+ if (tb[TCA_FQ_BUCKETS_LOG] &&
+ RTA_PAYLOAD(tb[TCA_FQ_BUCKETS_LOG]) >= sizeof(__u32)) {
+ buckets_log = rta_getattr_u32(tb[TCA_FQ_BUCKETS_LOG]);
+ fprintf(f, "buckets %u ", 1U << buckets_log);
+ }
+ if (tb[TCA_FQ_ORPHAN_MASK] &&
+ RTA_PAYLOAD(tb[TCA_FQ_ORPHAN_MASK]) >= sizeof(__u32)) {
+ orphan_mask = rta_getattr_u32(tb[TCA_FQ_ORPHAN_MASK]);
+ fprintf(f, "orphan_mask %u ", orphan_mask);
+ }
+ if (tb[TCA_FQ_RATE_ENABLE] &&
+ RTA_PAYLOAD(tb[TCA_FQ_RATE_ENABLE]) >= sizeof(int)) {
+ pacing = rta_getattr_u32(tb[TCA_FQ_RATE_ENABLE]);
+ if (pacing == 0)
+ fprintf(f, "nopacing ");
+ }
+ if (tb[TCA_FQ_QUANTUM] &&
+ RTA_PAYLOAD(tb[TCA_FQ_QUANTUM]) >= sizeof(__u32)) {
+ quantum = rta_getattr_u32(tb[TCA_FQ_QUANTUM]);
+ fprintf(f, "quantum %u ", quantum);
+ }
+ if (tb[TCA_FQ_INITIAL_QUANTUM] &&
+ RTA_PAYLOAD(tb[TCA_FQ_INITIAL_QUANTUM]) >= sizeof(__u32)) {
+ quantum = rta_getattr_u32(tb[TCA_FQ_INITIAL_QUANTUM]);
+ fprintf(f, "initial_quantum %u ", quantum);
+ }
+ if (tb[TCA_FQ_FLOW_MAX_RATE] &&
+ RTA_PAYLOAD(tb[TCA_FQ_FLOW_MAX_RATE]) >= sizeof(__u32)) {
+ rate = rta_getattr_u32(tb[TCA_FQ_FLOW_MAX_RATE]);
+
+ if (rate != ~0U)
+ fprintf(f, "maxrate %s ", sprint_rate(rate, b1));
+ }
+ if (tb[TCA_FQ_FLOW_DEFAULT_RATE] &&
+ RTA_PAYLOAD(tb[TCA_FQ_FLOW_DEFAULT_RATE]) >= sizeof(__u32)) {
+ rate = rta_getattr_u32(tb[TCA_FQ_FLOW_DEFAULT_RATE]);
+
+ if (rate != 0)
+ fprintf(f, "defrate %s ", sprint_rate(rate, b1));
+ }
+ if (tb[TCA_FQ_LOW_RATE_THRESHOLD] &&
+ RTA_PAYLOAD(tb[TCA_FQ_LOW_RATE_THRESHOLD]) >= sizeof(__u32)) {
+ rate = rta_getattr_u32(tb[TCA_FQ_LOW_RATE_THRESHOLD]);
+
+ if (rate != 0)
+ fprintf(f, "low_rate_threshold %s ", sprint_rate(rate, b1));
+ }
+ if (tb[TCA_FQ_FLOW_REFILL_DELAY] &&
+ RTA_PAYLOAD(tb[TCA_FQ_FLOW_REFILL_DELAY]) >= sizeof(__u32)) {
+ refill_delay = rta_getattr_u32(tb[TCA_FQ_FLOW_REFILL_DELAY]);
+ fprintf(f, "refill_delay %s ", sprint_time(refill_delay, b1));
+ }
+
+ if (tb[TCA_FQ_CE_THRESHOLD] &&
+ RTA_PAYLOAD(tb[TCA_FQ_CE_THRESHOLD]) >= sizeof(__u32)) {
+ ce_threshold = rta_getattr_u32(tb[TCA_FQ_CE_THRESHOLD]);
+ if (ce_threshold != ~0U)
+ fprintf(f, "ce_threshold %s ", sprint_time(ce_threshold, b1));
+ }
+
+ return 0;
+}
+
+static int fq_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct tc_fq_qd_stats *st, _st;
+
+ if (xstats == NULL)
+ return 0;
+
+ memset(&_st, 0, sizeof(_st));
+ memcpy(&_st, RTA_DATA(xstats), min(RTA_PAYLOAD(xstats), sizeof(*st)));
+
+ st = &_st;
+
+ fprintf(f, " %u flows (%u inactive, %u throttled)",
+ st->flows, st->inactive_flows, st->throttled_flows);
+
+ if (st->time_next_delayed_flow > 0)
+ fprintf(f, ", next packet delay %llu ns", st->time_next_delayed_flow);
+
+ fprintf(f, "\n %llu gc, %llu highprio",
+ st->gc_flows, st->highprio_packets);
+
+ if (st->tcp_retrans)
+ fprintf(f, ", %llu retrans", st->tcp_retrans);
+
+ fprintf(f, ", %llu throttled", st->throttled);
+
+ if (st->unthrottle_latency_ns)
+ fprintf(f, ", %u ns latency", st->unthrottle_latency_ns);
+
+ if (st->ce_mark)
+ fprintf(f, ", %llu ce_mark", st->ce_mark);
+
+ if (st->flows_plimit)
+ fprintf(f, ", %llu flows_plimit", st->flows_plimit);
+
+ if (st->pkts_too_long || st->allocation_errors)
+ fprintf(f, "\n %llu too long pkts, %llu alloc errors\n",
+ st->pkts_too_long, st->allocation_errors);
+
+ return 0;
+}
+
+struct qdisc_util fq_qdisc_util = {
+ .id = "fq",
+ .parse_qopt = fq_parse_opt,
+ .print_qopt = fq_print_opt,
+ .print_xstats = fq_print_xstats,
+};
diff --git a/tc/q_fq_codel.c b/tc/q_fq_codel.c
new file mode 100644
index 0000000..376ac50
--- /dev/null
+++ b/tc/q_fq_codel.c
@@ -0,0 +1,298 @@
+/*
+ * Fair Queue Codel
+ *
+ * Copyright (C) 2012,2015 Eric Dumazet <edumazet@google.com>
+ *
+ * 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,
+ * without modification.
+ * 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. The names of the authors may not 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.
+ *
+ * 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.
+ *
+ */
+
+#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 "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... fq_codel [ limit PACKETS ] [ flows NUMBER ]\n"
+ "[ memory_limit BYTES ]\n"
+ "[ target TIME ] [ interval TIME ]\n"
+ "[ quantum BYTES ] [ [no]ecn ]\n"
+ "[ ce_threshold TIME ]\n");
+}
+
+static int fq_codel_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ unsigned int limit = 0;
+ unsigned int flows = 0;
+ unsigned int target = 0;
+ unsigned int interval = 0;
+ unsigned int quantum = 0;
+ unsigned int ce_threshold = ~0U;
+ unsigned int memory = ~0U;
+ int ecn = -1;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&limit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "flows") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&flows, *argv, 0)) {
+ fprintf(stderr, "Illegal \"flows\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "quantum") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&quantum, *argv, 0)) {
+ fprintf(stderr, "Illegal \"quantum\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "target") == 0) {
+ NEXT_ARG();
+ if (get_time(&target, *argv)) {
+ fprintf(stderr, "Illegal \"target\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ce_threshold") == 0) {
+ NEXT_ARG();
+ if (get_time(&ce_threshold, *argv)) {
+ fprintf(stderr, "Illegal \"ce_threshold\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "memory_limit") == 0) {
+ NEXT_ARG();
+ if (get_size(&memory, *argv)) {
+ fprintf(stderr, "Illegal \"memory_limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "interval") == 0) {
+ NEXT_ARG();
+ if (get_time(&interval, *argv)) {
+ fprintf(stderr, "Illegal \"interval\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ecn") == 0) {
+ ecn = 1;
+ } else if (strcmp(*argv, "noecn") == 0) {
+ ecn = 0;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ if (limit)
+ addattr_l(n, 1024, TCA_FQ_CODEL_LIMIT, &limit, sizeof(limit));
+ if (flows)
+ addattr_l(n, 1024, TCA_FQ_CODEL_FLOWS, &flows, sizeof(flows));
+ if (quantum)
+ addattr_l(n, 1024, TCA_FQ_CODEL_QUANTUM, &quantum, sizeof(quantum));
+ if (interval)
+ addattr_l(n, 1024, TCA_FQ_CODEL_INTERVAL, &interval, sizeof(interval));
+ if (target)
+ addattr_l(n, 1024, TCA_FQ_CODEL_TARGET, &target, sizeof(target));
+ if (ecn != -1)
+ addattr_l(n, 1024, TCA_FQ_CODEL_ECN, &ecn, sizeof(ecn));
+ if (ce_threshold != ~0U)
+ addattr_l(n, 1024, TCA_FQ_CODEL_CE_THRESHOLD,
+ &ce_threshold, sizeof(ce_threshold));
+ if (memory != ~0U)
+ addattr_l(n, 1024, TCA_FQ_CODEL_MEMORY_LIMIT,
+ &memory, sizeof(memory));
+
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int fq_codel_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_FQ_CODEL_MAX + 1];
+ unsigned int limit;
+ unsigned int flows;
+ unsigned int interval;
+ unsigned int target;
+ unsigned int ecn;
+ unsigned int quantum;
+ unsigned int ce_threshold;
+ unsigned int memory_limit;
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_FQ_CODEL_MAX, opt);
+
+ if (tb[TCA_FQ_CODEL_LIMIT] &&
+ RTA_PAYLOAD(tb[TCA_FQ_CODEL_LIMIT]) >= sizeof(__u32)) {
+ limit = rta_getattr_u32(tb[TCA_FQ_CODEL_LIMIT]);
+ print_uint(PRINT_ANY, "limit", "limit %up ", limit);
+ }
+ if (tb[TCA_FQ_CODEL_FLOWS] &&
+ RTA_PAYLOAD(tb[TCA_FQ_CODEL_FLOWS]) >= sizeof(__u32)) {
+ flows = rta_getattr_u32(tb[TCA_FQ_CODEL_FLOWS]);
+ print_uint(PRINT_ANY, "flows", "flows %u ", flows);
+ }
+ if (tb[TCA_FQ_CODEL_QUANTUM] &&
+ RTA_PAYLOAD(tb[TCA_FQ_CODEL_QUANTUM]) >= sizeof(__u32)) {
+ quantum = rta_getattr_u32(tb[TCA_FQ_CODEL_QUANTUM]);
+ print_uint(PRINT_ANY, "quantum", "quantum %u ", quantum);
+ }
+ if (tb[TCA_FQ_CODEL_TARGET] &&
+ RTA_PAYLOAD(tb[TCA_FQ_CODEL_TARGET]) >= sizeof(__u32)) {
+ target = rta_getattr_u32(tb[TCA_FQ_CODEL_TARGET]);
+ print_uint(PRINT_JSON, "target", NULL, target);
+ print_string(PRINT_FP, NULL, "target %s ",
+ sprint_time(target, b1));
+ }
+ if (tb[TCA_FQ_CODEL_CE_THRESHOLD] &&
+ RTA_PAYLOAD(tb[TCA_FQ_CODEL_CE_THRESHOLD]) >= sizeof(__u32)) {
+ ce_threshold = rta_getattr_u32(tb[TCA_FQ_CODEL_CE_THRESHOLD]);
+ print_uint(PRINT_JSON, "ce_threshold", NULL, ce_threshold);
+ print_string(PRINT_FP, NULL, "ce_threshold %s ",
+ sprint_time(ce_threshold, b1));
+ }
+ if (tb[TCA_FQ_CODEL_INTERVAL] &&
+ RTA_PAYLOAD(tb[TCA_FQ_CODEL_INTERVAL]) >= sizeof(__u32)) {
+ interval = rta_getattr_u32(tb[TCA_FQ_CODEL_INTERVAL]);
+ print_uint(PRINT_JSON, "interval", NULL, interval);
+ print_string(PRINT_FP, NULL, "interval %s ",
+ sprint_time(interval, b1));
+ }
+ if (tb[TCA_FQ_CODEL_MEMORY_LIMIT] &&
+ RTA_PAYLOAD(tb[TCA_FQ_CODEL_MEMORY_LIMIT]) >= sizeof(__u32)) {
+ memory_limit = rta_getattr_u32(tb[TCA_FQ_CODEL_MEMORY_LIMIT]);
+ print_uint(PRINT_JSON, "memory_limit", NULL, memory_limit);
+ print_string(PRINT_FP, NULL, "memory_limit %s ",
+ sprint_size(memory_limit, b1));
+ }
+ if (tb[TCA_FQ_CODEL_ECN] &&
+ RTA_PAYLOAD(tb[TCA_FQ_CODEL_ECN]) >= sizeof(__u32)) {
+ ecn = rta_getattr_u32(tb[TCA_FQ_CODEL_ECN]);
+ if (ecn)
+ print_bool(PRINT_ANY, "ecn", "ecn ", true);
+ }
+
+ return 0;
+}
+
+static int fq_codel_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct tc_fq_codel_xstats _st = {}, *st;
+
+ SPRINT_BUF(b1);
+
+ if (xstats == NULL)
+ return 0;
+
+ st = RTA_DATA(xstats);
+ if (RTA_PAYLOAD(xstats) < sizeof(*st)) {
+ memcpy(&_st, st, RTA_PAYLOAD(xstats));
+ st = &_st;
+ }
+ if (st->type == TCA_FQ_CODEL_XSTATS_QDISC) {
+ print_uint(PRINT_ANY, "maxpacket", " maxpacket %u",
+ st->qdisc_stats.maxpacket);
+ print_uint(PRINT_ANY, "drop_overlimit", " drop_overlimit %u",
+ st->qdisc_stats.drop_overlimit);
+ print_uint(PRINT_ANY, "new_flow_count", " new_flow_count %u",
+ st->qdisc_stats.new_flow_count);
+ print_uint(PRINT_ANY, "ecn_mark", " ecn_mark %u",
+ st->qdisc_stats.ecn_mark);
+ if (st->qdisc_stats.ce_mark)
+ print_uint(PRINT_ANY, "ce_mark", " ce_mark %u",
+ st->qdisc_stats.ce_mark);
+ if (st->qdisc_stats.memory_usage)
+ print_uint(PRINT_ANY, "memory_used", " memory_used %u",
+ st->qdisc_stats.memory_usage);
+ if (st->qdisc_stats.drop_overmemory)
+ print_uint(PRINT_ANY, "drop_overmemory", " drop_overmemory %u",
+ st->qdisc_stats.drop_overmemory);
+ print_uint(PRINT_ANY, "new_flows_len", "\n new_flows_len %u",
+ st->qdisc_stats.new_flows_len);
+ print_uint(PRINT_ANY, "old_flows_len", " old_flows_len %u",
+ st->qdisc_stats.old_flows_len);
+ }
+ if (st->type == TCA_FQ_CODEL_XSTATS_CLASS) {
+ print_uint(PRINT_ANY, "deficit", " deficit %u",
+ st->class_stats.deficit);
+ print_uint(PRINT_ANY, "count", " count %u",
+ st->class_stats.count);
+ print_uint(PRINT_ANY, "lastcount", " lastcount %u",
+ st->class_stats.lastcount);
+ print_uint(PRINT_JSON, "ldelay", NULL,
+ st->class_stats.ldelay);
+ print_string(PRINT_FP, NULL, " ldelay %s",
+ sprint_time(st->class_stats.ldelay, b1));
+ if (st->class_stats.dropping) {
+ print_bool(PRINT_ANY, "dropping", " dropping", true);
+ if (st->class_stats.drop_next < 0)
+ print_string(PRINT_FP, NULL, " drop_next -%s",
+ sprint_time(-st->class_stats.drop_next, b1));
+ else {
+ print_uint(PRINT_JSON, "drop_next", NULL,
+ st->class_stats.drop_next);
+ print_string(PRINT_FP, NULL, " drop_next %s",
+ sprint_time(st->class_stats.drop_next, b1));
+ }
+ }
+ }
+ return 0;
+
+}
+
+struct qdisc_util fq_codel_qdisc_util = {
+ .id = "fq_codel",
+ .parse_qopt = fq_codel_parse_opt,
+ .print_qopt = fq_codel_print_opt,
+ .print_xstats = fq_codel_print_xstats,
+};
diff --git a/tc/q_gred.c b/tc/q_gred.c
new file mode 100644
index 0000000..8a1cecf
--- /dev/null
+++ b/tc/q_gred.c
@@ -0,0 +1,530 @@
+/*
+ * q_gred.c GRED.
+ *
+ * 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: J Hadi Salim(hadi@nortelnetworks.com)
+ * code ruthlessly ripped from
+ * 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 <math.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+#include "tc_red.h"
+
+
+#if 0
+#define DPRINTF(format, args...) fprintf(stderr, format, ##args)
+#else
+#define DPRINTF(format, args...)
+#endif
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: tc qdisc { add | replace | change } ... gred setup vqs NUMBER\n"
+ " default DEFAULT_VQ [ grio ] [ limit BYTES ] [ecn] [harddrop]\n"
+ " tc qdisc change ... gred vq VQ [ prio VALUE ] limit BYTES\n"
+ " min BYTES max BYTES avpkt BYTES [ burst PACKETS ]\n"
+ " [ probability PROBABILITY ] [ bandwidth KBPS ] [ecn] [harddrop]\n");
+}
+
+static int init_gred(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n)
+{
+
+ struct rtattr *tail;
+ struct tc_gred_sopt opt = { 0 };
+ __u32 limit = 0;
+
+ opt.def_DP = MAX_DPs;
+
+ while (argc > 0) {
+ DPRINTF(stderr, "init_gred: invoked with %s\n", *argv);
+ if (strcmp(*argv, "vqs") == 0 ||
+ strcmp(*argv, "DPs") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&opt.DPs, *argv, 10)) {
+ fprintf(stderr, "Illegal \"vqs\"\n");
+ return -1;
+ } else if (opt.DPs > MAX_DPs) {
+ fprintf(stderr, "GRED: only %u VQs are currently supported\n",
+ MAX_DPs);
+ return -1;
+ }
+ } else if (strcmp(*argv, "default") == 0) {
+ if (opt.DPs == 0) {
+ fprintf(stderr, "\"default\" must be defined after \"vqs\"\n");
+ return -1;
+ }
+ NEXT_ARG();
+ if (get_unsigned(&opt.def_DP, *argv, 10)) {
+ fprintf(stderr, "Illegal \"default\"\n");
+ return -1;
+ } else if (opt.def_DP >= opt.DPs) {
+ fprintf(stderr, "\"default\" must be less than \"vqs\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "grio") == 0) {
+ opt.grio = 1;
+ } else if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_size(&limit, *argv)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ecn") == 0) {
+ opt.flags |= TC_RED_ECN;
+ } else if (strcmp(*argv, "harddrop") == 0) {
+ opt.flags |= TC_RED_HARDDROP;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!opt.DPs || opt.def_DP == MAX_DPs) {
+ fprintf(stderr, "Illegal gred setup parameters\n");
+ return -1;
+ }
+
+ DPRINTF("TC_GRED: sending DPs=%u def_DP=%u\n", opt.DPs, opt.def_DP);
+ n->nlmsg_flags |= NLM_F_CREATE;
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 1024, TCA_GRED_DPS, &opt, sizeof(struct tc_gred_sopt));
+ if (limit)
+ addattr32(n, 1024, TCA_GRED_LIMIT, limit);
+ addattr_nest_end(n, tail);
+ return 0;
+}
+/*
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+*/
+static int gred_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev)
+{
+ struct rtattr *tail, *entry, *vqs;
+ int ok = 0;
+ struct tc_gred_qopt opt = { 0 };
+ unsigned int burst = 0;
+ unsigned int avpkt = 0;
+ unsigned int flags = 0;
+ double probability = 0.02;
+ unsigned int rate = 0;
+ int parm;
+ __u8 sbuf[256];
+ __u32 max_P;
+
+ opt.DP = MAX_DPs;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.limit, *argv)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "setup") == 0) {
+ if (ok) {
+ fprintf(stderr, "Illegal \"setup\"\n");
+ return -1;
+ }
+ return init_gred(qu, argc-1, argv+1, n);
+ } else if (strcmp(*argv, "min") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.qth_min, *argv)) {
+ fprintf(stderr, "Illegal \"min\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "max") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.qth_max, *argv)) {
+ fprintf(stderr, "Illegal \"max\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "vq") == 0 ||
+ strcmp(*argv, "DP") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&opt.DP, *argv, 10)) {
+ fprintf(stderr, "Illegal \"vq\"\n");
+ return -1;
+ } else if (opt.DP >= MAX_DPs) {
+ fprintf(stderr, "GRED: only %u VQs are currently supported\n",
+ MAX_DPs);
+ return -1;
+ } /* need a better error check */
+ ok++;
+ } else if (strcmp(*argv, "burst") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&burst, *argv, 0)) {
+ fprintf(stderr, "Illegal \"burst\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "avpkt") == 0) {
+ NEXT_ARG();
+ if (get_size(&avpkt, *argv)) {
+ fprintf(stderr, "Illegal \"avpkt\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "probability") == 0) {
+ NEXT_ARG();
+ if (sscanf(*argv, "%lg", &probability) != 1) {
+ fprintf(stderr, "Illegal \"probability\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "prio") == 0) {
+ NEXT_ARG();
+ opt.prio = strtol(*argv, (char **)NULL, 10);
+ /* some error check here */
+ ok++;
+ } else if (strcmp(*argv, "bandwidth") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&rate, *argv, dev)) {
+ fprintf(stderr, "Illegal \"bandwidth\"\n");
+ return -1;
+ }
+ } else if (get_rate(&rate, *argv)) {
+ fprintf(stderr, "Illegal \"bandwidth\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "ecn") == 0) {
+ flags |= TC_RED_ECN;
+ } else if (strcmp(*argv, "harddrop") == 0) {
+ flags |= TC_RED_HARDDROP;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!ok) {
+ explain();
+ return -1;
+ }
+ if (opt.DP == MAX_DPs || !opt.limit || !opt.qth_min || !opt.qth_max ||
+ !avpkt) {
+ fprintf(stderr, "Required parameter (vq, limit, min, max, avpkt) is missing\n");
+ return -1;
+ }
+ if (!burst) {
+ burst = (2 * opt.qth_min + opt.qth_max) / (3 * avpkt);
+ fprintf(stderr, "GRED: set burst to %u\n", burst);
+ }
+ if (!rate) {
+ get_rate(&rate, "10Mbit");
+ fprintf(stderr, "GRED: set bandwidth to 10Mbit\n");
+ }
+ if ((parm = tc_red_eval_ewma(opt.qth_min, burst, avpkt)) < 0) {
+ fprintf(stderr, "GRED: failed to calculate EWMA constant.\n");
+ return -1;
+ }
+ if (parm >= 10)
+ fprintf(stderr, "GRED: WARNING. Burst %u seems to be too large.\n",
+ burst);
+ opt.Wlog = parm;
+ if ((parm = tc_red_eval_P(opt.qth_min, opt.qth_max, probability)) < 0) {
+ fprintf(stderr, "GRED: failed to calculate probability.\n");
+ return -1;
+ }
+ opt.Plog = parm;
+ if ((parm = tc_red_eval_idle_damping(opt.Wlog, avpkt, rate, sbuf)) < 0)
+ {
+ fprintf(stderr, "GRED: failed to calculate idle damping table.\n");
+ return -1;
+ }
+ opt.Scell_log = parm;
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 1024, TCA_GRED_PARMS, &opt, sizeof(opt));
+ addattr_l(n, 1024, TCA_GRED_STAB, sbuf, 256);
+ max_P = probability * pow(2, 32);
+ addattr32(n, 1024, TCA_GRED_MAX_P, max_P);
+
+ vqs = addattr_nest(n, 1024, TCA_GRED_VQ_LIST);
+ entry = addattr_nest(n, 1024, TCA_GRED_VQ_ENTRY);
+ addattr32(n, 1024, TCA_GRED_VQ_DP, opt.DP);
+ addattr32(n, 1024, TCA_GRED_VQ_FLAGS, flags);
+ addattr_nest_end(n, entry);
+ addattr_nest_end(n, vqs);
+
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+struct tc_gred_info {
+ bool flags_present;
+ __u64 bytes;
+ __u32 packets;
+ __u32 backlog;
+ __u32 prob_drop;
+ __u32 prob_mark;
+ __u32 forced_drop;
+ __u32 forced_mark;
+ __u32 pdrop;
+ __u32 other;
+ __u32 flags;
+};
+
+static void
+gred_parse_vqs(struct tc_gred_info *info, struct rtattr *vqs)
+{
+ int rem = RTA_PAYLOAD(vqs);
+ unsigned int offset = 0;
+
+ while (rem > offset) {
+ struct rtattr *tb_entry[TCA_GRED_VQ_ENTRY_MAX + 1] = {};
+ struct rtattr *tb[TCA_GRED_VQ_MAX + 1] = {};
+ struct rtattr *entry;
+ unsigned int len;
+ unsigned int dp;
+
+ entry = RTA_DATA(vqs) + offset;
+
+ parse_rtattr(tb_entry, TCA_GRED_VQ_ENTRY_MAX, entry,
+ rem - offset);
+ len = RTA_LENGTH(RTA_PAYLOAD(entry));
+ offset += len;
+
+ if (!tb_entry[TCA_GRED_VQ_ENTRY]) {
+ fprintf(stderr,
+ "ERROR: Failed to parse Virtual Queue entry\n");
+ continue;
+ }
+
+ parse_rtattr_nested(tb, TCA_GRED_VQ_MAX,
+ tb_entry[TCA_GRED_VQ_ENTRY]);
+
+ if (!tb[TCA_GRED_VQ_DP]) {
+ fprintf(stderr,
+ "ERROR: Virtual Queue without DP attribute\n");
+ continue;
+ }
+
+ dp = rta_getattr_u32(tb[TCA_GRED_VQ_DP]);
+
+ if (tb[TCA_GRED_VQ_STAT_BYTES])
+ info[dp].bytes =
+ rta_getattr_u32(tb[TCA_GRED_VQ_STAT_BYTES]);
+ if (tb[TCA_GRED_VQ_STAT_PACKETS])
+ info[dp].packets =
+ rta_getattr_u32(tb[TCA_GRED_VQ_STAT_PACKETS]);
+ if (tb[TCA_GRED_VQ_STAT_BACKLOG])
+ info[dp].backlog =
+ rta_getattr_u32(tb[TCA_GRED_VQ_STAT_BACKLOG]);
+ if (tb[TCA_GRED_VQ_STAT_PROB_DROP])
+ info[dp].prob_drop =
+ rta_getattr_u32(tb[TCA_GRED_VQ_STAT_PROB_DROP]);
+ if (tb[TCA_GRED_VQ_STAT_PROB_MARK])
+ info[dp].prob_mark =
+ rta_getattr_u32(tb[TCA_GRED_VQ_STAT_PROB_MARK]);
+ if (tb[TCA_GRED_VQ_STAT_FORCED_DROP])
+ info[dp].forced_drop =
+ rta_getattr_u32(tb[TCA_GRED_VQ_STAT_FORCED_DROP]);
+ if (tb[TCA_GRED_VQ_STAT_FORCED_MARK])
+ info[dp].forced_mark =
+ rta_getattr_u32(tb[TCA_GRED_VQ_STAT_FORCED_MARK]);
+ if (tb[TCA_GRED_VQ_STAT_PDROP])
+ info[dp].pdrop =
+ rta_getattr_u32(tb[TCA_GRED_VQ_STAT_PDROP]);
+ if (tb[TCA_GRED_VQ_STAT_OTHER])
+ info[dp].other =
+ rta_getattr_u32(tb[TCA_GRED_VQ_STAT_OTHER]);
+ info[dp].flags_present = !!tb[TCA_GRED_VQ_FLAGS];
+ if (tb[TCA_GRED_VQ_FLAGS])
+ info[dp].flags =
+ rta_getattr_u32(tb[TCA_GRED_VQ_FLAGS]);
+ }
+}
+
+static void
+gred_print_stats(struct tc_gred_info *info, struct tc_gred_qopt *qopt)
+{
+ __u64 bytes = info ? info->bytes : qopt->bytesin;
+
+ SPRINT_BUF(b1);
+
+ if (!is_json_context())
+ printf("\n Queue size: ");
+
+ print_uint(PRINT_JSON, "qave", NULL, qopt->qave);
+ print_string(PRINT_FP, NULL, "average %s ",
+ sprint_size(qopt->qave, b1));
+
+ print_uint(PRINT_JSON, "backlog", NULL, qopt->backlog);
+ print_string(PRINT_FP, NULL, "current %s ",
+ sprint_size(qopt->backlog, b1));
+
+ if (!is_json_context())
+ printf("\n Dropped packets: ");
+
+ if (info) {
+ print_uint(PRINT_ANY, "forced_drop", "forced %u ",
+ info->forced_drop);
+ print_uint(PRINT_ANY, "prob_drop", "early %u ",
+ info->prob_drop);
+ print_uint(PRINT_ANY, "pdrop", "pdrop %u ", info->pdrop);
+ print_uint(PRINT_ANY, "other", "other %u ", info->other);
+
+ if (!is_json_context())
+ printf("\n Marked packets: ");
+ print_uint(PRINT_ANY, "forced_mark", "forced %u ",
+ info->forced_mark);
+ print_uint(PRINT_ANY, "prob_mark", "early %u ",
+ info->prob_mark);
+ } else {
+ print_uint(PRINT_ANY, "forced_drop", "forced %u ",
+ qopt->forced);
+ print_uint(PRINT_ANY, "prob_drop", "early %u ", qopt->early);
+ print_uint(PRINT_ANY, "pdrop", "pdrop %u ", qopt->pdrop);
+ print_uint(PRINT_ANY, "other", "other %u ", qopt->other);
+ }
+
+ if (!is_json_context())
+ printf("\n Total packets: ");
+
+ print_uint(PRINT_ANY, "packets", "%u ", qopt->packets);
+
+ print_uint(PRINT_JSON, "bytes", NULL, bytes);
+ print_string(PRINT_FP, NULL, "(%s) ", sprint_size(bytes, b1));
+}
+
+static int gred_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct tc_gred_info infos[MAX_DPs] = {};
+ struct rtattr *tb[TCA_GRED_MAX + 1];
+ struct tc_gred_sopt *sopt;
+ struct tc_gred_qopt *qopt;
+ bool vq_info = false;
+ __u32 *max_p = NULL;
+ __u32 *limit = NULL;
+ unsigned int i;
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_GRED_MAX, opt);
+
+ if (tb[TCA_GRED_PARMS] == NULL)
+ return -1;
+
+ if (tb[TCA_GRED_MAX_P] &&
+ RTA_PAYLOAD(tb[TCA_GRED_MAX_P]) >= sizeof(__u32) * MAX_DPs)
+ max_p = RTA_DATA(tb[TCA_GRED_MAX_P]);
+
+ if (tb[TCA_GRED_LIMIT] &&
+ RTA_PAYLOAD(tb[TCA_GRED_LIMIT]) == sizeof(__u32))
+ limit = RTA_DATA(tb[TCA_GRED_LIMIT]);
+
+ sopt = RTA_DATA(tb[TCA_GRED_DPS]);
+ qopt = RTA_DATA(tb[TCA_GRED_PARMS]);
+ if (RTA_PAYLOAD(tb[TCA_GRED_DPS]) < sizeof(*sopt) ||
+ RTA_PAYLOAD(tb[TCA_GRED_PARMS]) < sizeof(*qopt)*MAX_DPs) {
+ fprintf(f, "\n GRED received message smaller than expected\n");
+ return -1;
+ }
+
+ if (tb[TCA_GRED_VQ_LIST]) {
+ gred_parse_vqs(infos, tb[TCA_GRED_VQ_LIST]);
+ vq_info = true;
+ }
+
+ print_uint(PRINT_ANY, "dp_cnt", "vqs %u ", sopt->DPs);
+ print_uint(PRINT_ANY, "dp_default", "default %u ", sopt->def_DP);
+
+ if (sopt->grio)
+ print_bool(PRINT_ANY, "grio", "grio ", true);
+ else
+ print_bool(PRINT_ANY, "grio", NULL, false);
+
+ if (limit) {
+ print_uint(PRINT_JSON, "limit", NULL, *limit);
+ print_string(PRINT_FP, NULL, "limit %s ",
+ sprint_size(*limit, b1));
+ }
+
+ tc_red_print_flags(sopt->flags);
+
+ open_json_array(PRINT_JSON, "vqs");
+ for (i = 0; i < MAX_DPs; i++, qopt++) {
+ if (qopt->DP >= MAX_DPs)
+ continue;
+
+ open_json_object(NULL);
+
+ print_uint(PRINT_ANY, "vq", "\n vq %u ", qopt->DP);
+ print_hhu(PRINT_ANY, "prio", "prio %hhu ", qopt->prio);
+
+ print_uint(PRINT_JSON, "limit", NULL, qopt->limit);
+ print_string(PRINT_FP, NULL, "limit %s ",
+ sprint_size(qopt->limit, b1));
+
+ print_uint(PRINT_JSON, "min", NULL, qopt->qth_min);
+ print_string(PRINT_FP, NULL, "min %s ",
+ sprint_size(qopt->qth_min, b1));
+
+ print_uint(PRINT_JSON, "max", NULL, qopt->qth_max);
+ print_string(PRINT_FP, NULL, "max %s ",
+ sprint_size(qopt->qth_max, b1));
+
+ if (infos[i].flags_present)
+ tc_red_print_flags(infos[i].flags);
+
+ if (show_details) {
+ print_uint(PRINT_ANY, "ewma", "ewma %u ", qopt->Wlog);
+ if (max_p)
+ print_float(PRINT_ANY, "probability",
+ "probability %lg ",
+ max_p[i] / pow(2, 32));
+ else
+ print_uint(PRINT_ANY, "Plog", "Plog %u ",
+ qopt->Plog);
+ print_uint(PRINT_ANY, "Scell_log", "Scell_log %u ",
+ qopt->Scell_log);
+ }
+ if (show_stats)
+ gred_print_stats(vq_info ? &infos[i] : NULL, qopt);
+ close_json_object();
+ }
+ close_json_array(PRINT_JSON, "vqs");
+ return 0;
+}
+
+struct qdisc_util gred_qdisc_util = {
+ .id = "gred",
+ .parse_qopt = gred_parse_opt,
+ .print_qopt = gred_print_opt,
+};
diff --git a/tc/q_hfsc.c b/tc/q_hfsc.c
new file mode 100644
index 0000000..f34b1b2
--- /dev/null
+++ b/tc/q_hfsc.c
@@ -0,0 +1,416 @@
+/*
+ * q_hfsc.c HFSC.
+ *
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <math.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static int hfsc_get_sc(int *, char ***,
+ struct tc_service_curve *, const char *);
+
+static void
+explain_qdisc(void)
+{
+ fprintf(stderr,
+ "Usage: ... hfsc [ default CLASSID ]\n"
+ "\n"
+ " default: default class for unclassified packets\n"
+ );
+}
+
+static void
+explain_class(void)
+{
+ fprintf(stderr,
+ "Usage: ... hfsc [ [ rt SC ] [ ls SC ] | [ sc SC ] ] [ ul SC ]\n"
+ "\n"
+ "SC := [ [ m1 BPS ] d SEC ] m2 BPS\n"
+ "\n"
+ " m1 : slope of first segment\n"
+ " d : x-coordinate of intersection\n"
+ " m2 : slope of second segment\n"
+ "\n"
+ "Alternative format:\n"
+ "\n"
+ "SC := [ [ umax BYTE ] dmax SEC ] rate BPS\n"
+ "\n"
+ " umax : maximum unit of work\n"
+ " dmax : maximum delay\n"
+ " rate : rate\n"
+ "\n"
+ "Remarks:\n"
+ " - at least one of 'rt', 'ls' or 'sc' must be specified\n"
+ " - 'ul' can only be specified with 'ls' or 'sc'\n"
+ "\n"
+ );
+}
+
+static void
+explain1(char *arg)
+{
+ fprintf(stderr, "HFSC: Illegal \"%s\"\n", arg);
+}
+
+static int
+hfsc_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct tc_hfsc_qopt qopt = {};
+
+ while (argc > 0) {
+ if (matches(*argv, "default") == 0) {
+ NEXT_ARG();
+ if (qopt.defcls != 0) {
+ fprintf(stderr, "HFSC: Double \"default\"\n");
+ return -1;
+ }
+ if (get_u16(&qopt.defcls, *argv, 16) < 0) {
+ explain1("default");
+ return -1;
+ }
+ } else if (matches(*argv, "help") == 0) {
+ explain_qdisc();
+ return -1;
+ } else {
+ fprintf(stderr, "HFSC: What is \"%s\" ?\n", *argv);
+ explain_qdisc();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ addattr_l(n, 1024, TCA_OPTIONS, &qopt, sizeof(qopt));
+ return 0;
+}
+
+static int
+hfsc_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct tc_hfsc_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+ if (RTA_PAYLOAD(opt) < sizeof(*qopt))
+ return -1;
+ qopt = RTA_DATA(opt);
+
+ if (qopt->defcls != 0)
+ fprintf(f, "default %x ", qopt->defcls);
+
+ return 0;
+}
+
+static int
+hfsc_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+ struct tc_hfsc_stats *st;
+
+ if (xstats == NULL)
+ return 0;
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+ st = RTA_DATA(xstats);
+
+ fprintf(f, " period %u ", st->period);
+ if (st->work != 0)
+ fprintf(f, "work %llu bytes ", (unsigned long long) st->work);
+ if (st->rtwork != 0)
+ fprintf(f, "rtwork %llu bytes ", (unsigned long long) st->rtwork);
+ fprintf(f, "level %u ", st->level);
+ fprintf(f, "\n");
+
+ return 0;
+}
+
+static int
+hfsc_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct tc_service_curve rsc = {}, fsc = {}, usc = {};
+ int rsc_ok = 0, fsc_ok = 0, usc_ok = 0;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (matches(*argv, "rt") == 0) {
+ NEXT_ARG();
+ if (hfsc_get_sc(&argc, &argv, &rsc, dev) < 0) {
+ explain1("rt");
+ return -1;
+ }
+ rsc_ok = 1;
+ } else if (matches(*argv, "ls") == 0) {
+ NEXT_ARG();
+ if (hfsc_get_sc(&argc, &argv, &fsc, dev) < 0) {
+ explain1("ls");
+ return -1;
+ }
+ fsc_ok = 1;
+ } else if (matches(*argv, "sc") == 0) {
+ NEXT_ARG();
+ if (hfsc_get_sc(&argc, &argv, &rsc, dev) < 0) {
+ explain1("sc");
+ return -1;
+ }
+ memcpy(&fsc, &rsc, sizeof(fsc));
+ rsc_ok = 1;
+ fsc_ok = 1;
+ } else if (matches(*argv, "ul") == 0) {
+ NEXT_ARG();
+ if (hfsc_get_sc(&argc, &argv, &usc, dev) < 0) {
+ explain1("ul");
+ return -1;
+ }
+ usc_ok = 1;
+ } else if (matches(*argv, "help") == 0) {
+ explain_class();
+ return -1;
+ } else {
+ fprintf(stderr, "HFSC: What is \"%s\" ?\n", *argv);
+ explain_class();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ if (!(rsc_ok || fsc_ok || usc_ok)) {
+ fprintf(stderr, "HFSC: no parameters given\n");
+ explain_class();
+ return -1;
+ }
+ if (usc_ok && !fsc_ok) {
+ fprintf(stderr, "HFSC: Upper-limit Service Curve without Link-Share Service Curve\n");
+ explain_class();
+ return -1;
+ }
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ if (rsc_ok)
+ addattr_l(n, 1024, TCA_HFSC_RSC, &rsc, sizeof(rsc));
+ if (fsc_ok)
+ addattr_l(n, 1024, TCA_HFSC_FSC, &fsc, sizeof(fsc));
+ if (usc_ok)
+ addattr_l(n, 1024, TCA_HFSC_USC, &usc, sizeof(usc));
+
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static void
+hfsc_print_sc(FILE *f, char *name, struct tc_service_curve *sc)
+{
+ SPRINT_BUF(b1);
+
+ fprintf(f, "%s ", name);
+ fprintf(f, "m1 %s ", sprint_rate(sc->m1, b1));
+ fprintf(f, "d %s ", sprint_time(tc_core_ktime2time(sc->d), b1));
+ fprintf(f, "m2 %s ", sprint_rate(sc->m2, b1));
+}
+
+static int
+hfsc_print_class_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_HFSC_MAX+1];
+ struct tc_service_curve *rsc = NULL, *fsc = NULL, *usc = NULL;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_HFSC_MAX, opt);
+
+ if (tb[TCA_HFSC_RSC]) {
+ if (RTA_PAYLOAD(tb[TCA_HFSC_RSC]) < sizeof(*rsc))
+ fprintf(stderr, "HFSC: truncated realtime option\n");
+ else
+ rsc = RTA_DATA(tb[TCA_HFSC_RSC]);
+ }
+ if (tb[TCA_HFSC_FSC]) {
+ if (RTA_PAYLOAD(tb[TCA_HFSC_FSC]) < sizeof(*fsc))
+ fprintf(stderr, "HFSC: truncated linkshare option\n");
+ else
+ fsc = RTA_DATA(tb[TCA_HFSC_FSC]);
+ }
+ if (tb[TCA_HFSC_USC]) {
+ if (RTA_PAYLOAD(tb[TCA_HFSC_USC]) < sizeof(*usc))
+ fprintf(stderr, "HFSC: truncated upperlimit option\n");
+ else
+ usc = RTA_DATA(tb[TCA_HFSC_USC]);
+ }
+
+
+ if (rsc != NULL && fsc != NULL &&
+ memcmp(rsc, fsc, sizeof(*rsc)) == 0)
+ hfsc_print_sc(f, "sc", rsc);
+ else {
+ if (rsc != NULL)
+ hfsc_print_sc(f, "rt", rsc);
+ if (fsc != NULL)
+ hfsc_print_sc(f, "ls", fsc);
+ }
+ if (usc != NULL)
+ hfsc_print_sc(f, "ul", usc);
+
+ return 0;
+}
+
+struct qdisc_util hfsc_qdisc_util = {
+ .id = "hfsc",
+ .parse_qopt = hfsc_parse_opt,
+ .print_qopt = hfsc_print_opt,
+ .print_xstats = hfsc_print_xstats,
+ .parse_copt = hfsc_parse_class_opt,
+ .print_copt = hfsc_print_class_opt,
+};
+
+static int
+hfsc_get_sc1(int *argcp, char ***argvp,
+ struct tc_service_curve *sc, const char *dev)
+{
+ char **argv = *argvp;
+ int argc = *argcp;
+ unsigned int m1 = 0, d = 0, m2 = 0;
+
+ if (matches(*argv, "m1") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&m1, *argv, dev)) {
+ explain1("m1");
+ return -1;
+ }
+ } else if (get_rate(&m1, *argv) < 0) {
+ explain1("m1");
+ return -1;
+ }
+ NEXT_ARG();
+ }
+
+ if (matches(*argv, "d") == 0) {
+ NEXT_ARG();
+ if (get_time(&d, *argv) < 0) {
+ explain1("d");
+ return -1;
+ }
+ NEXT_ARG();
+ }
+
+ if (matches(*argv, "m2") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&m2, *argv, dev)) {
+ explain1("m2");
+ return -1;
+ }
+ } else if (get_rate(&m2, *argv) < 0) {
+ explain1("m2");
+ return -1;
+ }
+ } else
+ return -1;
+
+ sc->m1 = m1;
+ sc->d = tc_core_time2ktime(d);
+ sc->m2 = m2;
+
+ *argvp = argv;
+ *argcp = argc;
+ return 0;
+}
+
+static int
+hfsc_get_sc2(int *argcp, char ***argvp, struct tc_service_curve *sc, const char *dev)
+{
+ char **argv = *argvp;
+ int argc = *argcp;
+ unsigned int umax = 0, dmax = 0, rate = 0;
+
+ if (matches(*argv, "umax") == 0) {
+ NEXT_ARG();
+ if (get_size(&umax, *argv) < 0) {
+ explain1("umax");
+ return -1;
+ }
+ NEXT_ARG();
+ }
+
+ if (matches(*argv, "dmax") == 0) {
+ NEXT_ARG();
+ if (get_time(&dmax, *argv) < 0) {
+ explain1("dmax");
+ return -1;
+ }
+ NEXT_ARG();
+ }
+
+ if (matches(*argv, "rate") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&rate, *argv, dev)) {
+ explain1("rate");
+ return -1;
+ }
+ } else if (get_rate(&rate, *argv) < 0) {
+ explain1("rate");
+ return -1;
+ }
+ } else
+ return -1;
+
+ if (umax != 0 && dmax == 0) {
+ fprintf(stderr, "HFSC: umax given but dmax is zero.\n");
+ return -1;
+ }
+
+ if (dmax != 0 && ceil(1.0 * umax * TIME_UNITS_PER_SEC / dmax) > rate) {
+ /*
+ * concave curve, slope of first segment is umax/dmax,
+ * intersection is at dmax
+ */
+ sc->m1 = ceil(1.0 * umax * TIME_UNITS_PER_SEC / dmax); /* in bps */
+ sc->d = tc_core_time2ktime(dmax);
+ sc->m2 = rate;
+ } else {
+ /*
+ * convex curve, slope of first segment is 0, intersection
+ * is at dmax - umax / rate
+ */
+ sc->m1 = 0;
+ sc->d = tc_core_time2ktime(ceil(dmax - umax * TIME_UNITS_PER_SEC / rate));
+ sc->m2 = rate;
+ }
+
+ *argvp = argv;
+ *argcp = argc;
+ return 0;
+}
+
+static int
+hfsc_get_sc(int *argcp, char ***argvp, struct tc_service_curve *sc, const char *dev)
+{
+ if (hfsc_get_sc1(argcp, argvp, sc, dev) < 0 &&
+ hfsc_get_sc2(argcp, argvp, sc, dev) < 0)
+ return -1;
+
+ if (sc->m1 == 0 && sc->m2 == 0) {
+ fprintf(stderr, "HFSC: Service Curve has two zero slopes\n");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/tc/q_hhf.c b/tc/q_hhf.c
new file mode 100644
index 0000000..5ee6642
--- /dev/null
+++ b/tc/q_hhf.c
@@ -0,0 +1,200 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* q_hhf.c Heavy-Hitter Filter (HHF)
+ *
+ * Copyright (C) 2013 Terry Lam <vtlam@google.com>
+ */
+#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 "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... hhf [ limit PACKETS ] [ quantum BYTES]\n"
+ " [ hh_limit NUMBER ]\n"
+ " [ reset_timeout TIME ]\n"
+ " [ admit_bytes BYTES ]\n"
+ " [ evict_timeout TIME ]\n"
+ " [ non_hh_weight NUMBER ]\n");
+}
+
+static int hhf_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ unsigned int limit = 0;
+ unsigned int quantum = 0;
+ unsigned int hh_limit = 0;
+ unsigned int reset_timeout = 0;
+ unsigned int admit_bytes = 0;
+ unsigned int evict_timeout = 0;
+ unsigned int non_hh_weight = 0;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&limit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "quantum") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&quantum, *argv, 0)) {
+ fprintf(stderr, "Illegal \"quantum\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "hh_limit") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&hh_limit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"hh_limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "reset_timeout") == 0) {
+ NEXT_ARG();
+ if (get_time(&reset_timeout, *argv)) {
+ fprintf(stderr, "Illegal \"reset_timeout\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "admit_bytes") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&admit_bytes, *argv, 0)) {
+ fprintf(stderr, "Illegal \"admit_bytes\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "evict_timeout") == 0) {
+ NEXT_ARG();
+ if (get_time(&evict_timeout, *argv)) {
+ fprintf(stderr, "Illegal \"evict_timeout\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "non_hh_weight") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&non_hh_weight, *argv, 0)) {
+ fprintf(stderr, "Illegal \"non_hh_weight\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ if (limit)
+ addattr_l(n, 1024, TCA_HHF_BACKLOG_LIMIT, &limit,
+ sizeof(limit));
+ if (quantum)
+ addattr_l(n, 1024, TCA_HHF_QUANTUM, &quantum, sizeof(quantum));
+ if (hh_limit)
+ addattr_l(n, 1024, TCA_HHF_HH_FLOWS_LIMIT, &hh_limit,
+ sizeof(hh_limit));
+ if (reset_timeout)
+ addattr_l(n, 1024, TCA_HHF_RESET_TIMEOUT, &reset_timeout,
+ sizeof(reset_timeout));
+ if (admit_bytes)
+ addattr_l(n, 1024, TCA_HHF_ADMIT_BYTES, &admit_bytes,
+ sizeof(admit_bytes));
+ if (evict_timeout)
+ addattr_l(n, 1024, TCA_HHF_EVICT_TIMEOUT, &evict_timeout,
+ sizeof(evict_timeout));
+ if (non_hh_weight)
+ addattr_l(n, 1024, TCA_HHF_NON_HH_WEIGHT, &non_hh_weight,
+ sizeof(non_hh_weight));
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int hhf_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_HHF_MAX + 1];
+ unsigned int limit;
+ unsigned int quantum;
+ unsigned int hh_limit;
+ unsigned int reset_timeout;
+ unsigned int admit_bytes;
+ unsigned int evict_timeout;
+ unsigned int non_hh_weight;
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_HHF_MAX, opt);
+
+ if (tb[TCA_HHF_BACKLOG_LIMIT] &&
+ RTA_PAYLOAD(tb[TCA_HHF_BACKLOG_LIMIT]) >= sizeof(__u32)) {
+ limit = rta_getattr_u32(tb[TCA_HHF_BACKLOG_LIMIT]);
+ fprintf(f, "limit %up ", limit);
+ }
+ if (tb[TCA_HHF_QUANTUM] &&
+ RTA_PAYLOAD(tb[TCA_HHF_QUANTUM]) >= sizeof(__u32)) {
+ quantum = rta_getattr_u32(tb[TCA_HHF_QUANTUM]);
+ fprintf(f, "quantum %u ", quantum);
+ }
+ if (tb[TCA_HHF_HH_FLOWS_LIMIT] &&
+ RTA_PAYLOAD(tb[TCA_HHF_HH_FLOWS_LIMIT]) >= sizeof(__u32)) {
+ hh_limit = rta_getattr_u32(tb[TCA_HHF_HH_FLOWS_LIMIT]);
+ fprintf(f, "hh_limit %u ", hh_limit);
+ }
+ if (tb[TCA_HHF_RESET_TIMEOUT] &&
+ RTA_PAYLOAD(tb[TCA_HHF_RESET_TIMEOUT]) >= sizeof(__u32)) {
+ reset_timeout = rta_getattr_u32(tb[TCA_HHF_RESET_TIMEOUT]);
+ fprintf(f, "reset_timeout %s ", sprint_time(reset_timeout, b1));
+ }
+ if (tb[TCA_HHF_ADMIT_BYTES] &&
+ RTA_PAYLOAD(tb[TCA_HHF_ADMIT_BYTES]) >= sizeof(__u32)) {
+ admit_bytes = rta_getattr_u32(tb[TCA_HHF_ADMIT_BYTES]);
+ fprintf(f, "admit_bytes %u ", admit_bytes);
+ }
+ if (tb[TCA_HHF_EVICT_TIMEOUT] &&
+ RTA_PAYLOAD(tb[TCA_HHF_EVICT_TIMEOUT]) >= sizeof(__u32)) {
+ evict_timeout = rta_getattr_u32(tb[TCA_HHF_EVICT_TIMEOUT]);
+ fprintf(f, "evict_timeout %s ", sprint_time(evict_timeout, b1));
+ }
+ if (tb[TCA_HHF_NON_HH_WEIGHT] &&
+ RTA_PAYLOAD(tb[TCA_HHF_NON_HH_WEIGHT]) >= sizeof(__u32)) {
+ non_hh_weight = rta_getattr_u32(tb[TCA_HHF_NON_HH_WEIGHT]);
+ fprintf(f, "non_hh_weight %u ", non_hh_weight);
+ }
+ return 0;
+}
+
+static int hhf_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct tc_hhf_xstats *st;
+
+ if (xstats == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+
+ st = RTA_DATA(xstats);
+
+ fprintf(f, " drop_overlimit %u hh_overlimit %u tot_hh %u cur_hh %u",
+ st->drop_overlimit, st->hh_overlimit,
+ st->hh_tot_count, st->hh_cur_count);
+ return 0;
+}
+
+struct qdisc_util hhf_qdisc_util = {
+ .id = "hhf",
+ .parse_qopt = hhf_parse_opt,
+ .print_qopt = hhf_print_opt,
+ .print_xstats = hhf_print_xstats,
+};
diff --git a/tc/q_htb.c b/tc/q_htb.c
new file mode 100644
index 0000000..5205222
--- /dev/null
+++ b/tc/q_htb.c
@@ -0,0 +1,377 @@
+/*
+ * q_htb.c HTB.
+ *
+ * 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: Martin Devera, devik@cdi.cz
+ *
+ */
+
+#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 "tc_util.h"
+
+#define HTB_TC_VER 0x30003
+#if HTB_TC_VER >> 16 != TC_HTB_PROTOVER
+#error "Different kernel and TC HTB versions"
+#endif
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... qdisc add ... htb [default N] [r2q N]\n"
+ " [direct_qlen P]\n"
+ " default minor id of class to which unclassified packets are sent {0}\n"
+ " r2q DRR quantums are computed as rate in Bps/r2q {10}\n"
+ " debug string of 16 numbers each 0-3 {0}\n\n"
+ " direct_qlen Limit of the direct queue {in packets}\n"
+ "... class add ... htb rate R1 [burst B1] [mpu B] [overhead O]\n"
+ " [prio P] [slot S] [pslot PS]\n"
+ " [ceil R2] [cburst B2] [mtu MTU] [quantum Q]\n"
+ " rate rate allocated to this class (class can still borrow)\n"
+ " burst max bytes burst which can be accumulated during idle period {computed}\n"
+ " mpu minimum packet size used in rate computations\n"
+ " overhead per-packet size overhead used in rate computations\n"
+ " linklay adapting to a linklayer e.g. atm\n"
+ " ceil definite upper class rate (no borrows) {rate}\n"
+ " cburst burst but for ceil {computed}\n"
+ " mtu max packet size we create rate map for {1600}\n"
+ " prio priority of leaf; lower are served first {0}\n"
+ " quantum how much bytes to serve from leaf at once {use r2q}\n"
+ "\nTC HTB version %d.%d\n", HTB_TC_VER>>16, HTB_TC_VER&0xffff
+ );
+}
+
+static void explain1(char *arg)
+{
+ fprintf(stderr, "Illegal \"%s\"\n", arg);
+ explain();
+}
+
+static int htb_parse_opt(struct qdisc_util *qu, int argc,
+ char **argv, struct nlmsghdr *n, const char *dev)
+{
+ unsigned int direct_qlen = ~0U;
+ struct tc_htb_glob opt = {
+ .rate2quantum = 10,
+ .version = 3,
+ };
+ struct rtattr *tail;
+ unsigned int i; char *p;
+
+ while (argc > 0) {
+ if (matches(*argv, "r2q") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.rate2quantum, *argv, 10)) {
+ explain1("r2q"); return -1;
+ }
+ } else if (matches(*argv, "default") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.defcls, *argv, 16)) {
+ explain1("default"); return -1;
+ }
+ } else if (matches(*argv, "debug") == 0) {
+ NEXT_ARG(); p = *argv;
+ for (i = 0; i < 16; i++, p++) {
+ if (*p < '0' || *p > '3') break;
+ opt.debug |= (*p-'0')<<(2*i);
+ }
+ } else if (matches(*argv, "direct_qlen") == 0) {
+ NEXT_ARG();
+ if (get_u32(&direct_qlen, *argv, 10)) {
+ explain1("direct_qlen"); return -1;
+ }
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 2024, TCA_HTB_INIT, &opt, NLMSG_ALIGN(sizeof(opt)));
+ if (direct_qlen != ~0U)
+ addattr_l(n, 2024, TCA_HTB_DIRECT_QLEN,
+ &direct_qlen, sizeof(direct_qlen));
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev)
+{
+ struct tc_htb_opt opt = {};
+ __u32 rtab[256], ctab[256];
+ unsigned buffer = 0, cbuffer = 0;
+ int cell_log = -1, ccell_log = -1;
+ unsigned int mtu = 1600; /* eth packet len */
+ unsigned short mpu = 0;
+ unsigned short overhead = 0;
+ unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+ struct rtattr *tail;
+ __u64 ceil64 = 0, rate64 = 0;
+
+ while (argc > 0) {
+ if (matches(*argv, "prio") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.prio, *argv, 10)) {
+ explain1("prio"); return -1;
+ }
+ } else if (matches(*argv, "mtu") == 0) {
+ NEXT_ARG();
+ if (get_u32(&mtu, *argv, 10)) {
+ explain1("mtu"); return -1;
+ }
+ } else if (matches(*argv, "mpu") == 0) {
+ NEXT_ARG();
+ if (get_u16(&mpu, *argv, 10)) {
+ explain1("mpu"); return -1;
+ }
+ } else if (matches(*argv, "overhead") == 0) {
+ NEXT_ARG();
+ if (get_u16(&overhead, *argv, 10)) {
+ explain1("overhead"); return -1;
+ }
+ } else if (matches(*argv, "linklayer") == 0) {
+ NEXT_ARG();
+ if (get_linklayer(&linklayer, *argv)) {
+ explain1("linklayer"); return -1;
+ }
+ } else if (matches(*argv, "quantum") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.quantum, *argv, 10)) {
+ explain1("quantum"); return -1;
+ }
+ } else if (matches(*argv, "burst") == 0 ||
+ strcmp(*argv, "buffer") == 0 ||
+ strcmp(*argv, "maxburst") == 0) {
+ NEXT_ARG();
+ if (get_size_and_cell(&buffer, &cell_log, *argv) < 0) {
+ explain1("buffer");
+ return -1;
+ }
+ } else if (matches(*argv, "cburst") == 0 ||
+ strcmp(*argv, "cbuffer") == 0 ||
+ strcmp(*argv, "cmaxburst") == 0) {
+ NEXT_ARG();
+ if (get_size_and_cell(&cbuffer, &ccell_log, *argv) < 0) {
+ explain1("cbuffer");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ceil") == 0) {
+ NEXT_ARG();
+ if (ceil64) {
+ fprintf(stderr, "Double \"ceil\" spec\n");
+ return -1;
+ }
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate64(&ceil64, *argv, dev)) {
+ explain1("ceil");
+ return -1;
+ }
+ } else if (get_rate64(&ceil64, *argv)) {
+ explain1("ceil");
+ return -1;
+ }
+ } else if (strcmp(*argv, "rate") == 0) {
+ NEXT_ARG();
+ if (rate64) {
+ fprintf(stderr, "Double \"rate\" spec\n");
+ return -1;
+ }
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate64(&rate64, *argv, dev)) {
+ explain1("rate");
+ return -1;
+ }
+ } else if (get_rate64(&rate64, *argv)) {
+ explain1("rate");
+ return -1;
+ }
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!rate64) {
+ fprintf(stderr, "\"rate\" is required.\n");
+ return -1;
+ }
+ /* if ceil params are missing, use the same as rate */
+ if (!ceil64)
+ ceil64 = rate64;
+
+ opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64;
+ opt.ceil.rate = (ceil64 >= (1ULL << 32)) ? ~0U : ceil64;
+
+ /* compute minimal allowed burst from rate; mtu is added here to make
+ sute that buffer is larger than mtu and to have some safeguard space */
+ if (!buffer)
+ buffer = rate64 / get_hz() + mtu;
+ if (!cbuffer)
+ cbuffer = ceil64 / get_hz() + mtu;
+
+ opt.ceil.overhead = overhead;
+ opt.rate.overhead = overhead;
+
+ opt.ceil.mpu = mpu;
+ opt.rate.mpu = mpu;
+
+ if (tc_calc_rtable(&opt.rate, rtab, cell_log, mtu, linklayer) < 0) {
+ fprintf(stderr, "htb: failed to calculate rate table.\n");
+ return -1;
+ }
+ opt.buffer = tc_calc_xmittime(rate64, buffer);
+
+ if (tc_calc_rtable(&opt.ceil, ctab, ccell_log, mtu, linklayer) < 0) {
+ fprintf(stderr, "htb: failed to calculate ceil rate table.\n");
+ return -1;
+ }
+ opt.cbuffer = tc_calc_xmittime(ceil64, cbuffer);
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+
+ if (rate64 >= (1ULL << 32))
+ addattr_l(n, 1124, TCA_HTB_RATE64, &rate64, sizeof(rate64));
+
+ if (ceil64 >= (1ULL << 32))
+ addattr_l(n, 1224, TCA_HTB_CEIL64, &ceil64, sizeof(ceil64));
+
+ addattr_l(n, 2024, TCA_HTB_PARMS, &opt, sizeof(opt));
+ addattr_l(n, 3024, TCA_HTB_RTAB, rtab, 1024);
+ addattr_l(n, 4024, TCA_HTB_CTAB, ctab, 1024);
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int htb_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_HTB_MAX + 1];
+ struct tc_htb_opt *hopt;
+ struct tc_htb_glob *gopt;
+ double buffer, cbuffer;
+ unsigned int linklayer;
+ __u64 rate64, ceil64;
+
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+ SPRINT_BUF(b3);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_HTB_MAX, opt);
+
+ if (tb[TCA_HTB_PARMS]) {
+ hopt = RTA_DATA(tb[TCA_HTB_PARMS]);
+ if (RTA_PAYLOAD(tb[TCA_HTB_PARMS]) < sizeof(*hopt)) return -1;
+
+ if (!hopt->level) {
+ print_int(PRINT_ANY, "prio", "prio %d ", (int)hopt->prio);
+ if (show_details)
+ print_int(PRINT_ANY, "quantum", "quantum %d ",
+ (int)hopt->quantum);
+ }
+
+ rate64 = hopt->rate.rate;
+ if (tb[TCA_HTB_RATE64] &&
+ RTA_PAYLOAD(tb[TCA_HTB_RATE64]) >= sizeof(rate64)) {
+ rate64 = rta_getattr_u64(tb[TCA_HTB_RATE64]);
+ }
+
+ ceil64 = hopt->ceil.rate;
+ if (tb[TCA_HTB_CEIL64] &&
+ RTA_PAYLOAD(tb[TCA_HTB_CEIL64]) >= sizeof(ceil64))
+ ceil64 = rta_getattr_u64(tb[TCA_HTB_CEIL64]);
+
+ fprintf(f, "rate %s ", sprint_rate(rate64, b1));
+ if (hopt->rate.overhead)
+ fprintf(f, "overhead %u ", hopt->rate.overhead);
+ buffer = tc_calc_xmitsize(rate64, hopt->buffer);
+
+ fprintf(f, "ceil %s ", sprint_rate(ceil64, b1));
+ cbuffer = tc_calc_xmitsize(ceil64, hopt->cbuffer);
+ linklayer = (hopt->rate.linklayer & TC_LINKLAYER_MASK);
+ if (linklayer > TC_LINKLAYER_ETHERNET || show_details)
+ fprintf(f, "linklayer %s ", sprint_linklayer(linklayer, b3));
+ if (show_details) {
+ fprintf(f, "burst %s/%u mpu %s ",
+ sprint_size(buffer, b1),
+ 1<<hopt->rate.cell_log,
+ sprint_size(hopt->rate.mpu, b2));
+ fprintf(f, "cburst %s/%u mpu %s ",
+ sprint_size(cbuffer, b1),
+ 1<<hopt->ceil.cell_log,
+ sprint_size(hopt->ceil.mpu, b2));
+ fprintf(f, "level %d ", (int)hopt->level);
+ } else {
+ fprintf(f, "burst %s ", sprint_size(buffer, b1));
+ fprintf(f, "cburst %s ", sprint_size(cbuffer, b1));
+ }
+ if (show_raw)
+ fprintf(f, "buffer [%08x] cbuffer [%08x] ",
+ hopt->buffer, hopt->cbuffer);
+ }
+ if (tb[TCA_HTB_INIT]) {
+ gopt = RTA_DATA(tb[TCA_HTB_INIT]);
+ if (RTA_PAYLOAD(tb[TCA_HTB_INIT]) < sizeof(*gopt)) return -1;
+
+ print_int(PRINT_ANY, "r2q", "r2q %d", gopt->rate2quantum);
+ print_0xhex(PRINT_ANY, "default", " default %#llx", gopt->defcls);
+ print_uint(PRINT_ANY, "direct_packets_stat",
+ " direct_packets_stat %u", gopt->direct_pkts);
+ if (show_details) {
+ sprintf(b1, "%d.%d", gopt->version >> 16, gopt->version & 0xffff);
+ print_string(PRINT_ANY, "ver", " ver %s", b1);
+ }
+ }
+ if (tb[TCA_HTB_DIRECT_QLEN] &&
+ RTA_PAYLOAD(tb[TCA_HTB_DIRECT_QLEN]) >= sizeof(__u32)) {
+ __u32 direct_qlen = rta_getattr_u32(tb[TCA_HTB_DIRECT_QLEN]);
+
+ print_uint(PRINT_ANY, "direct_qlen", " direct_qlen %u",
+ direct_qlen);
+ }
+ return 0;
+}
+
+static int htb_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+ struct tc_htb_xstats *st;
+
+ if (xstats == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+
+ st = RTA_DATA(xstats);
+ fprintf(f, " lended: %u borrowed: %u giants: %u\n",
+ st->lends, st->borrows, st->giants);
+ fprintf(f, " tokens: %d ctokens: %d\n", st->tokens, st->ctokens);
+ return 0;
+}
+
+struct qdisc_util htb_qdisc_util = {
+ .id = "htb",
+ .parse_qopt = htb_parse_opt,
+ .print_qopt = htb_print_opt,
+ .print_xstats = htb_print_xstats,
+ .parse_copt = htb_parse_class_opt,
+ .print_copt = htb_print_opt,
+};
diff --git a/tc/q_ingress.c b/tc/q_ingress.c
new file mode 100644
index 0000000..93313c9
--- /dev/null
+++ b/tc/q_ingress.c
@@ -0,0 +1,51 @@
+/*
+ * q_ingress.c INGRESS.
+ *
+ * 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: J Hadi Salim
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... ingress\n");
+}
+
+static int ingress_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ while (argc > 0) {
+ if (strcmp(*argv, "handle") == 0) {
+ NEXT_ARG();
+ argc--; argv++;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int ingress_print_opt(struct qdisc_util *qu, FILE *f,
+ struct rtattr *opt)
+{
+ print_string(PRINT_FP, NULL, "---------------- ", NULL);
+ return 0;
+}
+
+struct qdisc_util ingress_qdisc_util = {
+ .id = "ingress",
+ .parse_qopt = ingress_parse_opt,
+ .print_qopt = ingress_print_opt,
+};
diff --git a/tc/q_mqprio.c b/tc/q_mqprio.c
new file mode 100644
index 0000000..0eb4130
--- /dev/null
+++ b/tc/q_mqprio.c
@@ -0,0 +1,310 @@
+/*
+ * q_mqprio.c MQ prio qdisc
+ *
+ * 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: John Fastabend, <john.r.fastabend@intel.com>
+ */
+
+#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 "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... mqprio [num_tc NUMBER] [map P0 P1 ...]\n"
+ " [queues count1@offset1 count2@offset2 ...] "
+ "[hw 1|0]\n"
+ " [mode dcb|channel]\n"
+ " [shaper bw_rlimit SHAPER_PARAMS]\n"
+ "Where: SHAPER_PARAMS := { min_rate MIN_RATE1 MIN_RATE2 ...|\n"
+ " max_rate MAX_RATE1 MAX_RATE2 ... }\n");
+}
+
+static int mqprio_parse_opt(struct qdisc_util *qu, int argc,
+ char **argv, struct nlmsghdr *n, const char *dev)
+{
+ int idx;
+ struct tc_mqprio_qopt opt = {
+ .num_tc = 8,
+ .prio_tc_map = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 1, 1, 3, 3, 3, 3 },
+ .hw = 1,
+ .count = { },
+ .offset = { },
+ };
+ __u64 min_rate64[TC_QOPT_MAX_QUEUE] = {0};
+ __u64 max_rate64[TC_QOPT_MAX_QUEUE] = {0};
+ __u16 shaper = TC_MQPRIO_SHAPER_DCB;
+ __u16 mode = TC_MQPRIO_MODE_DCB;
+ struct rtattr *tail;
+ __u32 flags = 0;
+
+ while (argc > 0) {
+ idx = 0;
+ if (strcmp(*argv, "num_tc") == 0) {
+ NEXT_ARG();
+ if (get_u8(&opt.num_tc, *argv, 10)) {
+ fprintf(stderr, "Illegal \"num_tc\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "map") == 0) {
+ while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (get_u8(&opt.prio_tc_map[idx], *argv, 10)) {
+ PREV_ARG();
+ break;
+ }
+ idx++;
+ }
+ for ( ; idx < TC_QOPT_MAX_QUEUE; idx++)
+ opt.prio_tc_map[idx] = 0;
+ } else if (strcmp(*argv, "queues") == 0) {
+ char *tmp, *tok;
+
+ while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
+ NEXT_ARG();
+
+ tmp = strdup(*argv);
+ if (!tmp)
+ break;
+
+ tok = strtok(tmp, "@");
+ if (get_u16(&opt.count[idx], tok, 10)) {
+ free(tmp);
+ PREV_ARG();
+ break;
+ }
+ tok = strtok(NULL, "@");
+ if (get_u16(&opt.offset[idx], tok, 10)) {
+ free(tmp);
+ PREV_ARG();
+ break;
+ }
+ free(tmp);
+ idx++;
+ }
+ } else if (strcmp(*argv, "hw") == 0) {
+ NEXT_ARG();
+ if (get_u8(&opt.hw, *argv, 10)) {
+ fprintf(stderr, "Illegal \"hw\"\n");
+ return -1;
+ }
+ idx++;
+ } else if (opt.hw && strcmp(*argv, "mode") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "dcb") == 0) {
+ mode = TC_MQPRIO_MODE_DCB;
+ } else if (matches(*argv, "channel") == 0) {
+ mode = TC_MQPRIO_MODE_CHANNEL;
+ } else {
+ fprintf(stderr, "Illegal mode (%s)\n",
+ *argv);
+ return -1;
+ }
+ if (mode != TC_MQPRIO_MODE_DCB)
+ flags |= TC_MQPRIO_F_MODE;
+ idx++;
+ } else if (opt.hw && strcmp(*argv, "shaper") == 0) {
+ NEXT_ARG();
+ if (matches(*argv, "dcb") == 0) {
+ shaper = TC_MQPRIO_SHAPER_DCB;
+ } else if (matches(*argv, "bw_rlimit") == 0) {
+ shaper = TC_MQPRIO_SHAPER_BW_RATE;
+ if (!NEXT_ARG_OK()) {
+ fprintf(stderr, "Incomplete shaper arguments\n");
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "Illegal shaper (%s)\n",
+ *argv);
+ return -1;
+ }
+ if (shaper != TC_MQPRIO_SHAPER_DCB)
+ flags |= TC_MQPRIO_F_SHAPER;
+ idx++;
+ } else if ((shaper == TC_MQPRIO_SHAPER_BW_RATE) &&
+ strcmp(*argv, "min_rate") == 0) {
+ while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (get_rate64(&min_rate64[idx], *argv)) {
+ PREV_ARG();
+ break;
+ }
+ idx++;
+ }
+ if (idx < opt.num_tc && !NEXT_ARG_OK()) {
+ fprintf(stderr, "Incomplete arguments, min_rate values expected\n");
+ return -1;
+ }
+ flags |= TC_MQPRIO_F_MIN_RATE;
+ } else if ((shaper == TC_MQPRIO_SHAPER_BW_RATE) &&
+ strcmp(*argv, "max_rate") == 0) {
+ while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (get_rate64(&max_rate64[idx], *argv)) {
+ PREV_ARG();
+ break;
+ }
+ idx++;
+ }
+ if (idx < opt.num_tc && !NEXT_ARG_OK()) {
+ fprintf(stderr, "Incomplete arguments, max_rate values expected\n");
+ return -1;
+ }
+ flags |= TC_MQPRIO_F_MAX_RATE;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ invarg("unknown argument", *argv);
+ }
+ argc--; argv++;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+
+ if (flags & TC_MQPRIO_F_MODE)
+ addattr_l(n, 1024, TCA_MQPRIO_MODE,
+ &mode, sizeof(mode));
+ if (flags & TC_MQPRIO_F_SHAPER)
+ addattr_l(n, 1024, TCA_MQPRIO_SHAPER,
+ &shaper, sizeof(shaper));
+
+ if (flags & TC_MQPRIO_F_MIN_RATE) {
+ struct rtattr *start;
+
+ start = addattr_nest(n, 1024,
+ TCA_MQPRIO_MIN_RATE64 | NLA_F_NESTED);
+
+ for (idx = 0; idx < TC_QOPT_MAX_QUEUE; idx++)
+ addattr_l(n, 1024, TCA_MQPRIO_MIN_RATE64,
+ &min_rate64[idx], sizeof(min_rate64[idx]));
+
+ addattr_nest_end(n, start);
+ }
+
+ if (flags & TC_MQPRIO_F_MAX_RATE) {
+ struct rtattr *start;
+
+ start = addattr_nest(n, 1024,
+ TCA_MQPRIO_MAX_RATE64 | NLA_F_NESTED);
+
+ for (idx = 0; idx < TC_QOPT_MAX_QUEUE; idx++)
+ addattr_l(n, 1024, TCA_MQPRIO_MAX_RATE64,
+ &max_rate64[idx], sizeof(max_rate64[idx]));
+
+ addattr_nest_end(n, start);
+ }
+
+ tail->rta_len = (void *)NLMSG_TAIL(n) - (void *)tail;
+
+ return 0;
+}
+
+static int mqprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ int i;
+ struct tc_mqprio_qopt *qopt;
+ __u64 min_rate64[TC_QOPT_MAX_QUEUE] = {0};
+ __u64 max_rate64[TC_QOPT_MAX_QUEUE] = {0};
+ int len;
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ len = RTA_PAYLOAD(opt) - RTA_ALIGN(sizeof(*qopt));
+ if (len < 0) {
+ fprintf(stderr, "options size error\n");
+ return -1;
+ }
+
+ qopt = RTA_DATA(opt);
+
+ fprintf(f, " tc %u map ", qopt->num_tc);
+ for (i = 0; i <= TC_PRIO_MAX; i++)
+ fprintf(f, "%u ", qopt->prio_tc_map[i]);
+ fprintf(f, "\n queues:");
+ for (i = 0; i < qopt->num_tc; i++)
+ fprintf(f, "(%u:%u) ", qopt->offset[i],
+ qopt->offset[i] + qopt->count[i] - 1);
+
+ if (len > 0) {
+ struct rtattr *tb[TCA_MQPRIO_MAX + 1];
+
+ parse_rtattr(tb, TCA_MQPRIO_MAX,
+ RTA_DATA(opt) + RTA_ALIGN(sizeof(*qopt)),
+ len);
+
+ if (tb[TCA_MQPRIO_MODE]) {
+ __u16 *mode = RTA_DATA(tb[TCA_MQPRIO_MODE]);
+
+ if (*mode == TC_MQPRIO_MODE_CHANNEL)
+ fprintf(f, "\n mode:channel");
+ } else {
+ fprintf(f, "\n mode:dcb");
+ }
+
+ if (tb[TCA_MQPRIO_SHAPER]) {
+ __u16 *shaper = RTA_DATA(tb[TCA_MQPRIO_SHAPER]);
+
+ if (*shaper == TC_MQPRIO_SHAPER_BW_RATE)
+ fprintf(f, "\n shaper:bw_rlimit");
+ } else {
+ fprintf(f, "\n shaper:dcb");
+ }
+
+ if (tb[TCA_MQPRIO_MIN_RATE64]) {
+ struct rtattr *r;
+ int rem = RTA_PAYLOAD(tb[TCA_MQPRIO_MIN_RATE64]);
+ __u64 *min = min_rate64;
+
+ for (r = RTA_DATA(tb[TCA_MQPRIO_MIN_RATE64]);
+ RTA_OK(r, rem); r = RTA_NEXT(r, rem)) {
+ if (r->rta_type != TCA_MQPRIO_MIN_RATE64)
+ return -1;
+ *(min++) = rta_getattr_u64(r);
+ }
+ fprintf(f, " min_rate:");
+ for (i = 0; i < qopt->num_tc; i++)
+ fprintf(f, "%s ", sprint_rate(min_rate64[i], b1));
+ }
+
+ if (tb[TCA_MQPRIO_MAX_RATE64]) {
+ struct rtattr *r;
+ int rem = RTA_PAYLOAD(tb[TCA_MQPRIO_MAX_RATE64]);
+ __u64 *max = max_rate64;
+
+ for (r = RTA_DATA(tb[TCA_MQPRIO_MAX_RATE64]);
+ RTA_OK(r, rem); r = RTA_NEXT(r, rem)) {
+ if (r->rta_type != TCA_MQPRIO_MAX_RATE64)
+ return -1;
+ *(max++) = rta_getattr_u64(r);
+ }
+ fprintf(f, " max_rate:");
+ for (i = 0; i < qopt->num_tc; i++)
+ fprintf(f, "%s ", sprint_rate(max_rate64[i], b1));
+ }
+ }
+ return 0;
+}
+
+struct qdisc_util mqprio_qdisc_util = {
+ .id = "mqprio",
+ .parse_qopt = mqprio_parse_opt,
+ .print_qopt = mqprio_print_opt,
+};
diff --git a/tc/q_multiq.c b/tc/q_multiq.c
new file mode 100644
index 0000000..8ad9e0b
--- /dev/null
+++ b/tc/q_multiq.c
@@ -0,0 +1,82 @@
+/*
+ * q_multiq.c Multiqueue aware qdisc
+ *
+ * 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, see <http://www.gnu.org/licenses>.
+ *
+ * Author: Alexander Duyck <alexander.h.duyck@intel.com>
+ *
+ * Original Authors: PJ Waskiewicz, <peter.p.waskiewicz.jr@intel.com> (RR)
+ * Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> (from PRIO)
+ *
+ */
+
+#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 "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... multiq [help]\n");
+}
+
+static int multiq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct tc_multiq_qopt opt = {};
+
+ if (argc) {
+ if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ }
+
+ addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+ return 0;
+}
+
+static int multiq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct tc_multiq_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+ if (RTA_PAYLOAD(opt) < sizeof(*qopt))
+ return 0;
+
+ qopt = RTA_DATA(opt);
+
+ fprintf(f, "bands %u/%u ", qopt->bands, qopt->max_bands);
+
+ return 0;
+}
+
+struct qdisc_util multiq_qdisc_util = {
+ .id = "multiq",
+ .parse_qopt = multiq_parse_opt,
+ .print_qopt = multiq_print_opt,
+};
diff --git a/tc/q_netem.c b/tc/q_netem.c
new file mode 100644
index 0000000..d01450f
--- /dev/null
+++ b/tc/q_netem.c
@@ -0,0 +1,840 @@
+/*
+ * q_netem.c NETEM.
+ *
+ * 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@linux-foundation.org>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <errno.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... netem [ limit PACKETS ]\n" \
+ " [ delay TIME [ JITTER [CORRELATION]]]\n" \
+ " [ distribution {uniform|normal|pareto|paretonormal} ]\n" \
+ " [ corrupt PERCENT [CORRELATION]]\n" \
+ " [ duplicate PERCENT [CORRELATION]]\n" \
+ " [ loss random PERCENT [CORRELATION]]\n" \
+ " [ loss state P13 [P31 [P32 [P23 P14]]]\n" \
+ " [ loss gemodel PERCENT [R [1-H [1-K]]]\n" \
+ " [ ecn ]\n" \
+ " [ reorder PERCENT [CORRELATION] [ gap DISTANCE ]]\n" \
+ " [ rate RATE [PACKETOVERHEAD] [CELLSIZE] [CELLOVERHEAD]]\n" \
+ " [ slot MIN_DELAY [MAX_DELAY] [packets MAX_PACKETS]" \
+ " [bytes MAX_BYTES]]\n" \
+ " [ slot distribution" \
+ " {uniform|normal|pareto|paretonormal|custom} DELAY JITTER" \
+ " [packets MAX_PACKETS] [bytes MAX_BYTES]]\n");
+}
+
+static void explain1(const char *arg)
+{
+ fprintf(stderr, "Illegal \"%s\"\n", arg);
+}
+
+/* Upper bound on size of distribution
+ * really (TCA_BUF_MAX - other headers) / sizeof (__s16)
+ */
+#define MAX_DIST (16*1024)
+
+/* Print values only if they are non-zero */
+static void __print_int_opt(const char *label_json, const char *label_fp,
+ int val)
+{
+ print_int(PRINT_ANY, label_json, val ? label_fp : "", val);
+}
+#define PRINT_INT_OPT(label, val) \
+ __print_int_opt(label, " " label " %d", (val))
+
+/* Time print prints normally with varying units, but for JSON prints
+ * in seconds (1ms vs 0.001).
+ */
+static void __print_time64(const char *label_json, const char *label_fp,
+ __u64 val)
+{
+ SPRINT_BUF(b1);
+
+ print_string(PRINT_FP, NULL, label_fp, sprint_time64(val, b1));
+ print_float(PRINT_JSON, label_json, NULL, val / 1000000000.);
+}
+#define __PRINT_TIME64(label_json, label_fp, val) \
+ __print_time64(label_json, label_fp " %s", (val))
+#define PRINT_TIME64(label, val) __PRINT_TIME64(label, " " label, (val))
+
+/* Percent print prints normally in percentage points, but for JSON prints
+ * an absolute value (1% vs 0.01).
+ */
+static void __print_percent(const char *label_json, const char *label_fp,
+ __u32 per)
+{
+ print_float(PRINT_FP, NULL, label_fp, (100. * per) / UINT32_MAX);
+ print_float(PRINT_JSON, label_json, NULL, (1. * per) / UINT32_MAX);
+}
+#define __PRINT_PERCENT(label_json, label_fp, per) \
+ __print_percent(label_json, label_fp " %g%%", (per))
+#define PRINT_PERCENT(label, per) __PRINT_PERCENT(label, " " label, (per))
+
+/* scaled value used to percent of maximum. */
+static void set_percent(__u32 *percent, double per)
+{
+ *percent = rint(per * UINT32_MAX);
+}
+
+static int get_percent(__u32 *percent, const char *str)
+{
+ double per;
+
+ if (parse_percent(&per, str))
+ return -1;
+
+ set_percent(percent, per);
+ return 0;
+}
+
+static void print_corr(bool present, __u32 value)
+{
+ if (!is_json_context()) {
+ if (present)
+ __PRINT_PERCENT("", "", value);
+ } else {
+ PRINT_PERCENT("correlation", value);
+ }
+}
+
+/*
+ * Simplistic file parser for distrbution data.
+ * Format is:
+ * # comment line(s)
+ * data0 data1 ...
+ */
+static int get_distribution(const char *type, __s16 *data, int maxdata)
+{
+ FILE *f;
+ int n;
+ long x;
+ size_t len;
+ char *line = NULL;
+ char name[128];
+
+ snprintf(name, sizeof(name), "%s/%s.dist", get_tc_lib(), type);
+ if ((f = fopen(name, "r")) == NULL) {
+ fprintf(stderr, "No distribution data for %s (%s: %s)\n",
+ type, name, strerror(errno));
+ return -1;
+ }
+
+ n = 0;
+ while (getline(&line, &len, f) != -1) {
+ char *p, *endp;
+
+ if (*line == '\n' || *line == '#')
+ continue;
+
+ for (p = line; ; p = endp) {
+ x = strtol(p, &endp, 0);
+ if (endp == p)
+ break;
+
+ if (n >= maxdata) {
+ fprintf(stderr, "%s: too much data\n",
+ name);
+ n = -1;
+ goto error;
+ }
+ data[n++] = x;
+ }
+ }
+ error:
+ free(line);
+ fclose(f);
+ return n;
+}
+
+#define NEXT_IS_NUMBER() (NEXT_ARG_OK() && isdigit(argv[1][0]))
+#define NEXT_IS_SIGNED_NUMBER() \
+ (NEXT_ARG_OK() && (isdigit(argv[1][0]) || argv[1][0] == '-'))
+
+/* Adjust for the fact that psched_ticks aren't always usecs
+ (based on kernel PSCHED_CLOCK configuration */
+static int get_ticks(__u32 *ticks, const char *str)
+{
+ unsigned int t;
+
+ if (get_time(&t, str))
+ return -1;
+
+ if (tc_core_time2big(t)) {
+ fprintf(stderr, "Illegal %u time (too large)\n", t);
+ return -1;
+ }
+
+ *ticks = tc_core_time2tick(t);
+ return 0;
+}
+
+static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ int dist_size = 0;
+ int slot_dist_size = 0;
+ struct rtattr *tail;
+ struct tc_netem_qopt opt = { .limit = 1000 };
+ struct tc_netem_corr cor = {};
+ struct tc_netem_reorder reorder = {};
+ struct tc_netem_corrupt corrupt = {};
+ struct tc_netem_gimodel gimodel;
+ struct tc_netem_gemodel gemodel;
+ struct tc_netem_rate rate = {};
+ struct tc_netem_slot slot = {};
+ __s16 *dist_data = NULL;
+ __s16 *slot_dist_data = NULL;
+ __u16 loss_type = NETEM_LOSS_UNSPEC;
+ int present[__TCA_NETEM_MAX] = {};
+ __u64 rate64 = 0;
+
+ for ( ; argc > 0; --argc, ++argv) {
+ if (matches(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.limit, *argv)) {
+ explain1("limit");
+ return -1;
+ }
+ } else if (matches(*argv, "latency") == 0 ||
+ matches(*argv, "delay") == 0) {
+ NEXT_ARG();
+ if (get_ticks(&opt.latency, *argv)) {
+ explain1("latency");
+ return -1;
+ }
+
+ if (NEXT_IS_NUMBER()) {
+ NEXT_ARG();
+ if (get_ticks(&opt.jitter, *argv)) {
+ explain1("latency");
+ return -1;
+ }
+
+ if (NEXT_IS_NUMBER()) {
+ NEXT_ARG();
+ ++present[TCA_NETEM_CORR];
+ if (get_percent(&cor.delay_corr, *argv)) {
+ explain1("latency");
+ return -1;
+ }
+ }
+ }
+ } else if (matches(*argv, "loss") == 0 ||
+ matches(*argv, "drop") == 0) {
+ if (opt.loss > 0 || loss_type != NETEM_LOSS_UNSPEC) {
+ explain1("duplicate loss argument\n");
+ return -1;
+ }
+
+ NEXT_ARG();
+ /* Old (deprecated) random loss model syntax */
+ if (isdigit(argv[0][0]))
+ goto random_loss_model;
+
+ if (!strcmp(*argv, "random")) {
+ NEXT_ARG();
+ random_loss_model:
+ if (get_percent(&opt.loss, *argv)) {
+ explain1("loss percent");
+ return -1;
+ }
+ if (NEXT_IS_NUMBER()) {
+ NEXT_ARG();
+ ++present[TCA_NETEM_CORR];
+ if (get_percent(&cor.loss_corr, *argv)) {
+ explain1("loss correllation");
+ return -1;
+ }
+ }
+ } else if (!strcmp(*argv, "state")) {
+ double p13;
+
+ NEXT_ARG();
+ if (parse_percent(&p13, *argv)) {
+ explain1("loss p13");
+ return -1;
+ }
+
+ /* set defaults */
+ set_percent(&gimodel.p13, p13);
+ set_percent(&gimodel.p31, 1. - p13);
+ set_percent(&gimodel.p32, 0);
+ set_percent(&gimodel.p23, 1.);
+ set_percent(&gimodel.p14, 0);
+ loss_type = NETEM_LOSS_GI;
+
+ if (!NEXT_IS_NUMBER())
+ continue;
+ NEXT_ARG();
+ if (get_percent(&gimodel.p31, *argv)) {
+ explain1("loss p31");
+ return -1;
+ }
+
+ if (!NEXT_IS_NUMBER())
+ continue;
+ NEXT_ARG();
+ if (get_percent(&gimodel.p32, *argv)) {
+ explain1("loss p32");
+ return -1;
+ }
+
+ if (!NEXT_IS_NUMBER())
+ continue;
+ NEXT_ARG();
+ if (get_percent(&gimodel.p23, *argv)) {
+ explain1("loss p23");
+ return -1;
+ }
+ if (!NEXT_IS_NUMBER())
+ continue;
+ NEXT_ARG();
+ if (get_percent(&gimodel.p14, *argv)) {
+ explain1("loss p14");
+ return -1;
+ }
+
+ } else if (!strcmp(*argv, "gemodel")) {
+ double p;
+
+ NEXT_ARG();
+ if (parse_percent(&p, *argv)) {
+ explain1("loss gemodel p");
+ return -1;
+ }
+ set_percent(&gemodel.p, p);
+
+ /* set defaults */
+ set_percent(&gemodel.r, 1. - p);
+ set_percent(&gemodel.h, 0);
+ set_percent(&gemodel.k1, 0);
+ loss_type = NETEM_LOSS_GE;
+
+ if (!NEXT_IS_NUMBER())
+ continue;
+ NEXT_ARG();
+ if (get_percent(&gemodel.r, *argv)) {
+ explain1("loss gemodel r");
+ return -1;
+ }
+
+ if (!NEXT_IS_NUMBER())
+ continue;
+ NEXT_ARG();
+ if (get_percent(&gemodel.h, *argv)) {
+ explain1("loss gemodel h");
+ return -1;
+ }
+ /* netem option is "1-h" but kernel
+ * expects "h".
+ */
+ gemodel.h = UINT32_MAX - gemodel.h;
+
+ if (!NEXT_IS_NUMBER())
+ continue;
+ NEXT_ARG();
+ if (get_percent(&gemodel.k1, *argv)) {
+ explain1("loss gemodel k");
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "Unknown loss parameter: %s\n",
+ *argv);
+ return -1;
+ }
+ } else if (matches(*argv, "ecn") == 0) {
+ present[TCA_NETEM_ECN] = 1;
+ } else if (matches(*argv, "reorder") == 0) {
+ NEXT_ARG();
+ present[TCA_NETEM_REORDER] = 1;
+ if (get_percent(&reorder.probability, *argv)) {
+ explain1("reorder");
+ return -1;
+ }
+ if (NEXT_IS_NUMBER()) {
+ NEXT_ARG();
+ ++present[TCA_NETEM_CORR];
+ if (get_percent(&reorder.correlation, *argv)) {
+ explain1("reorder");
+ return -1;
+ }
+ }
+ } else if (matches(*argv, "corrupt") == 0) {
+ NEXT_ARG();
+ present[TCA_NETEM_CORRUPT] = 1;
+ if (get_percent(&corrupt.probability, *argv)) {
+ explain1("corrupt");
+ return -1;
+ }
+ if (NEXT_IS_NUMBER()) {
+ NEXT_ARG();
+ ++present[TCA_NETEM_CORR];
+ if (get_percent(&corrupt.correlation, *argv)) {
+ explain1("corrupt");
+ return -1;
+ }
+ }
+ } else if (matches(*argv, "gap") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.gap, *argv, 0)) {
+ explain1("gap");
+ return -1;
+ }
+ } else if (matches(*argv, "duplicate") == 0) {
+ NEXT_ARG();
+ if (get_percent(&opt.duplicate, *argv)) {
+ explain1("duplicate");
+ return -1;
+ }
+ if (NEXT_IS_NUMBER()) {
+ NEXT_ARG();
+ if (get_percent(&cor.dup_corr, *argv)) {
+ explain1("duplicate");
+ return -1;
+ }
+ }
+ } else if (matches(*argv, "distribution") == 0) {
+ NEXT_ARG();
+ dist_data = calloc(sizeof(dist_data[0]), MAX_DIST);
+ dist_size = get_distribution(*argv, dist_data, MAX_DIST);
+ if (dist_size <= 0) {
+ free(dist_data);
+ return -1;
+ }
+ } else if (matches(*argv, "rate") == 0) {
+ ++present[TCA_NETEM_RATE];
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate64(&rate64, *argv, dev)) {
+ explain1("rate");
+ return -1;
+ }
+ } else if (get_rate64(&rate64, *argv)) {
+ explain1("rate");
+ return -1;
+ }
+ if (NEXT_IS_SIGNED_NUMBER()) {
+ NEXT_ARG();
+ if (get_s32(&rate.packet_overhead, *argv, 0)) {
+ explain1("rate");
+ return -1;
+ }
+ }
+ if (NEXT_IS_NUMBER()) {
+ NEXT_ARG();
+ if (get_u32(&rate.cell_size, *argv, 0)) {
+ explain1("rate");
+ return -1;
+ }
+ }
+ if (NEXT_IS_SIGNED_NUMBER()) {
+ NEXT_ARG();
+ if (get_s32(&rate.cell_overhead, *argv, 0)) {
+ explain1("rate");
+ return -1;
+ }
+ }
+ } else if (matches(*argv, "slot") == 0) {
+ if (NEXT_IS_NUMBER()) {
+ NEXT_ARG();
+ present[TCA_NETEM_SLOT] = 1;
+ if (get_time64(&slot.min_delay, *argv)) {
+ explain1("slot min_delay");
+ return -1;
+ }
+ if (NEXT_IS_NUMBER()) {
+ NEXT_ARG();
+ if (get_time64(&slot.max_delay, *argv) ||
+ slot.max_delay < slot.min_delay) {
+ explain1("slot max_delay");
+ return -1;
+ }
+ } else {
+ slot.max_delay = slot.min_delay;
+ }
+ } else {
+ NEXT_ARG();
+ if (strcmp(*argv, "distribution") == 0) {
+ present[TCA_NETEM_SLOT] = 1;
+ NEXT_ARG();
+ slot_dist_data = calloc(sizeof(slot_dist_data[0]), MAX_DIST);
+ if (!slot_dist_data)
+ return -1;
+ slot_dist_size = get_distribution(*argv, slot_dist_data, MAX_DIST);
+ if (slot_dist_size <= 0) {
+ free(slot_dist_data);
+ return -1;
+ }
+ NEXT_ARG();
+ if (get_time64(&slot.dist_delay, *argv)) {
+ explain1("slot delay");
+ return -1;
+ }
+ NEXT_ARG();
+ if (get_time64(&slot.dist_jitter, *argv)) {
+ explain1("slot jitter");
+ return -1;
+ }
+ if (slot.dist_jitter <= 0) {
+ fprintf(stderr, "Non-positive jitter\n");
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "Unknown slot parameter: %s\n",
+ *argv);
+ return -1;
+ }
+ }
+ if (NEXT_ARG_OK() &&
+ matches(*(argv+1), "packets") == 0) {
+ NEXT_ARG();
+ if (!NEXT_ARG_OK() ||
+ get_s32(&slot.max_packets, *(argv+1), 0)) {
+ explain1("slot packets");
+ return -1;
+ }
+ NEXT_ARG();
+ }
+ if (NEXT_ARG_OK() &&
+ matches(*(argv+1), "bytes") == 0) {
+ unsigned int max_bytes;
+ NEXT_ARG();
+ if (!NEXT_ARG_OK() ||
+ get_size(&max_bytes, *(argv+1))) {
+ explain1("slot bytes");
+ return -1;
+ }
+ slot.max_bytes = (int) max_bytes;
+ NEXT_ARG();
+ }
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ }
+
+ tail = NLMSG_TAIL(n);
+
+ if (reorder.probability) {
+ if (opt.latency == 0) {
+ fprintf(stderr, "reordering not possible without specifying some delay\n");
+ explain();
+ return -1;
+ }
+ if (opt.gap == 0)
+ opt.gap = 1;
+ } else if (opt.gap > 0) {
+ fprintf(stderr, "gap specified without reorder probability\n");
+ explain();
+ return -1;
+ }
+
+ if (present[TCA_NETEM_ECN]) {
+ if (opt.loss <= 0 && loss_type == NETEM_LOSS_UNSPEC) {
+ fprintf(stderr, "ecn requested without loss model\n");
+ explain();
+ return -1;
+ }
+ }
+
+ if (dist_data && (opt.latency == 0 || opt.jitter == 0)) {
+ fprintf(stderr, "distribution specified but no latency and jitter values\n");
+ explain();
+ return -1;
+ }
+
+ if (addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)) < 0)
+ return -1;
+
+ if (present[TCA_NETEM_CORR] &&
+ addattr_l(n, 1024, TCA_NETEM_CORR, &cor, sizeof(cor)) < 0)
+ return -1;
+
+ if (present[TCA_NETEM_REORDER] &&
+ addattr_l(n, 1024, TCA_NETEM_REORDER, &reorder, sizeof(reorder)) < 0)
+ return -1;
+
+ if (present[TCA_NETEM_ECN] &&
+ addattr_l(n, 1024, TCA_NETEM_ECN, &present[TCA_NETEM_ECN],
+ sizeof(present[TCA_NETEM_ECN])) < 0)
+ return -1;
+
+ if (present[TCA_NETEM_CORRUPT] &&
+ addattr_l(n, 1024, TCA_NETEM_CORRUPT, &corrupt, sizeof(corrupt)) < 0)
+ return -1;
+
+ if (present[TCA_NETEM_SLOT] &&
+ addattr_l(n, 1024, TCA_NETEM_SLOT, &slot, sizeof(slot)) < 0)
+ return -1;
+
+ if (loss_type != NETEM_LOSS_UNSPEC) {
+ struct rtattr *start;
+
+ start = addattr_nest(n, 1024, TCA_NETEM_LOSS | NLA_F_NESTED);
+ if (loss_type == NETEM_LOSS_GI) {
+ if (addattr_l(n, 1024, NETEM_LOSS_GI,
+ &gimodel, sizeof(gimodel)) < 0)
+ return -1;
+ } else if (loss_type == NETEM_LOSS_GE) {
+ if (addattr_l(n, 1024, NETEM_LOSS_GE,
+ &gemodel, sizeof(gemodel)) < 0)
+ return -1;
+ } else {
+ fprintf(stderr, "loss in the weeds!\n");
+ return -1;
+ }
+
+ addattr_nest_end(n, start);
+ }
+
+ if (present[TCA_NETEM_RATE]) {
+ if (rate64 >= (1ULL << 32)) {
+ if (addattr_l(n, 1024,
+ TCA_NETEM_RATE64, &rate64, sizeof(rate64)) < 0)
+ return -1;
+ rate.rate = ~0U;
+ } else {
+ rate.rate = rate64;
+ }
+ if (addattr_l(n, 1024, TCA_NETEM_RATE, &rate, sizeof(rate)) < 0)
+ return -1;
+ }
+
+ if (dist_data) {
+ if (addattr_l(n, MAX_DIST * sizeof(dist_data[0]),
+ TCA_NETEM_DELAY_DIST,
+ dist_data, dist_size * sizeof(dist_data[0])) < 0)
+ return -1;
+ free(dist_data);
+ }
+
+ if (slot_dist_data) {
+ if (addattr_l(n, MAX_DIST * sizeof(slot_dist_data[0]),
+ TCA_NETEM_SLOT_DIST,
+ slot_dist_data, slot_dist_size * sizeof(slot_dist_data[0])) < 0)
+ return -1;
+ free(slot_dist_data);
+ }
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ const struct tc_netem_corr *cor = NULL;
+ const struct tc_netem_reorder *reorder = NULL;
+ const struct tc_netem_corrupt *corrupt = NULL;
+ const struct tc_netem_gimodel *gimodel = NULL;
+ const struct tc_netem_gemodel *gemodel = NULL;
+ int *ecn = NULL;
+ struct tc_netem_qopt qopt;
+ const struct tc_netem_rate *rate = NULL;
+ const struct tc_netem_slot *slot = NULL;
+ int len;
+ __u64 rate64 = 0;
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ len = RTA_PAYLOAD(opt) - sizeof(qopt);
+ if (len < 0) {
+ fprintf(stderr, "options size error\n");
+ return -1;
+ }
+ memcpy(&qopt, RTA_DATA(opt), sizeof(qopt));
+
+ if (len > 0) {
+ struct rtattr *tb[TCA_NETEM_MAX+1];
+
+ parse_rtattr(tb, TCA_NETEM_MAX, RTA_DATA(opt) + sizeof(qopt),
+ len);
+
+ if (tb[TCA_NETEM_CORR]) {
+ if (RTA_PAYLOAD(tb[TCA_NETEM_CORR]) < sizeof(*cor))
+ return -1;
+ cor = RTA_DATA(tb[TCA_NETEM_CORR]);
+ }
+ if (tb[TCA_NETEM_REORDER]) {
+ if (RTA_PAYLOAD(tb[TCA_NETEM_REORDER]) < sizeof(*reorder))
+ return -1;
+ reorder = RTA_DATA(tb[TCA_NETEM_REORDER]);
+ }
+ if (tb[TCA_NETEM_CORRUPT]) {
+ if (RTA_PAYLOAD(tb[TCA_NETEM_CORRUPT]) < sizeof(*corrupt))
+ return -1;
+ corrupt = RTA_DATA(tb[TCA_NETEM_CORRUPT]);
+ }
+ if (tb[TCA_NETEM_LOSS]) {
+ struct rtattr *lb[NETEM_LOSS_MAX + 1];
+
+ parse_rtattr_nested(lb, NETEM_LOSS_MAX, tb[TCA_NETEM_LOSS]);
+ if (lb[NETEM_LOSS_GI])
+ gimodel = RTA_DATA(lb[NETEM_LOSS_GI]);
+ if (lb[NETEM_LOSS_GE])
+ gemodel = RTA_DATA(lb[NETEM_LOSS_GE]);
+ }
+ if (tb[TCA_NETEM_RATE]) {
+ if (RTA_PAYLOAD(tb[TCA_NETEM_RATE]) < sizeof(*rate))
+ return -1;
+ rate = RTA_DATA(tb[TCA_NETEM_RATE]);
+ }
+ if (tb[TCA_NETEM_ECN]) {
+ if (RTA_PAYLOAD(tb[TCA_NETEM_ECN]) < sizeof(*ecn))
+ return -1;
+ ecn = RTA_DATA(tb[TCA_NETEM_ECN]);
+ }
+ if (tb[TCA_NETEM_RATE64]) {
+ if (RTA_PAYLOAD(tb[TCA_NETEM_RATE64]) < sizeof(rate64))
+ return -1;
+ rate64 = rta_getattr_u64(tb[TCA_NETEM_RATE64]);
+ }
+ if (tb[TCA_NETEM_SLOT]) {
+ if (RTA_PAYLOAD(tb[TCA_NETEM_SLOT]) < sizeof(*slot))
+ return -1;
+ slot = RTA_DATA(tb[TCA_NETEM_SLOT]);
+ }
+ }
+
+ print_uint(PRINT_ANY, "limit", "limit %d", qopt.limit);
+
+ if (qopt.latency) {
+ open_json_object("delay");
+ if (!is_json_context()) {
+ print_string(PRINT_FP, NULL, " delay %s",
+ sprint_ticks(qopt.latency, b1));
+
+ if (qopt.jitter)
+ print_string(PRINT_FP, NULL, " %s",
+ sprint_ticks(qopt.jitter, b1));
+ } else {
+ print_float(PRINT_JSON, "delay", NULL,
+ tc_core_tick2time(qopt.latency) /
+ 1000000.);
+ print_float(PRINT_JSON, "jitter", NULL,
+ tc_core_tick2time(qopt.jitter) /
+ 1000000.);
+ }
+ print_corr(qopt.jitter && cor && cor->delay_corr,
+ cor ? cor->delay_corr : 0);
+ close_json_object();
+ }
+
+ if (qopt.loss) {
+ open_json_object("loss-random");
+ PRINT_PERCENT("loss", qopt.loss);
+ print_corr(cor && cor->loss_corr, cor ? cor->loss_corr : 0);
+ close_json_object();
+ }
+
+ if (gimodel) {
+ open_json_object("loss-state");
+ __PRINT_PERCENT("p13", " loss state p13", gimodel->p13);
+ PRINT_PERCENT("p31", gimodel->p31);
+ PRINT_PERCENT("p32", gimodel->p32);
+ PRINT_PERCENT("p23", gimodel->p23);
+ PRINT_PERCENT("p14", gimodel->p14);
+ close_json_object();
+ }
+
+ if (gemodel) {
+ open_json_object("loss-gemodel");
+ __PRINT_PERCENT("p", " loss gemodel p", gemodel->p);
+ PRINT_PERCENT("r", gemodel->r);
+ PRINT_PERCENT("1-h", UINT32_MAX - gemodel->h);
+ PRINT_PERCENT("1-k", gemodel->k1);
+ close_json_object();
+ }
+
+ if (qopt.duplicate) {
+ open_json_object("duplicate");
+ PRINT_PERCENT("duplicate", qopt.duplicate);
+ print_corr(cor && cor->dup_corr, cor ? cor->dup_corr : 0);
+ close_json_object();
+ }
+
+ if (reorder && reorder->probability) {
+ open_json_object("reorder");
+ PRINT_PERCENT("reorder", reorder->probability);
+ print_corr(reorder->correlation, reorder->correlation);
+ close_json_object();
+ }
+
+ if (corrupt && corrupt->probability) {
+ open_json_object("corrupt");
+ PRINT_PERCENT("corrupt", corrupt->probability);
+ print_corr(corrupt->correlation, corrupt->correlation);
+ close_json_object();
+ }
+
+ if (rate && rate->rate) {
+ open_json_object("rate");
+ rate64 = rate64 ? : rate->rate;
+ print_string(PRINT_FP, NULL, " rate %s",
+ sprint_rate(rate64, b1));
+ print_lluint(PRINT_JSON, "rate", NULL, rate64);
+ PRINT_INT_OPT("packetoverhead", rate->packet_overhead);
+ print_uint(PRINT_ANY, "cellsize",
+ rate->cell_size ? " cellsize %u" : "",
+ rate->cell_size);
+ PRINT_INT_OPT("celloverhead", rate->cell_overhead);
+ close_json_object();
+ }
+
+ if (slot) {
+ open_json_object("slot");
+ if (slot->dist_jitter > 0) {
+ __PRINT_TIME64("distribution", " slot distribution",
+ slot->dist_delay);
+ __PRINT_TIME64("jitter", "", slot->dist_jitter);
+ } else {
+ __PRINT_TIME64("min-delay", " slot", slot->min_delay);
+ __PRINT_TIME64("max-delay", "", slot->max_delay);
+ }
+ PRINT_INT_OPT("packets", slot->max_packets);
+ PRINT_INT_OPT("bytes", slot->max_bytes);
+ close_json_object();
+ }
+
+ print_bool(PRINT_ANY, "ecn", ecn ? " ecn " : "", ecn);
+ print_luint(PRINT_ANY, "gap", qopt.gap ? " gap %lu" : "",
+ (unsigned long)qopt.gap);
+
+ return 0;
+}
+
+struct qdisc_util netem_qdisc_util = {
+ .id = "netem",
+ .parse_qopt = netem_parse_opt,
+ .print_qopt = netem_print_opt,
+};
diff --git a/tc/q_nss.c b/tc/q_nss.c
new file mode 100644
index 0000000..e84166d
--- /dev/null
+++ b/tc/q_nss.c
@@ -0,0 +1,1411 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ **************************************************************************
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <math.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_red.h"
+
+/* ======================== NSSWRED =======================*/
+
+static void nssred_explain(void)
+{
+ fprintf(stderr, "Usage: ... nssred limit BYTES avpkt BYTES [ min BYTES ] [ max BYTES ] [ probability VALUE ]\n");
+ fprintf(stderr, " [ burst PACKETS ] [ecn] [ set_default ]\n");
+}
+
+static void nsswred_explain(void)
+{
+ fprintf(stderr, "Usage: ... nsswred setup DPs NUMBER dp_default NUMBER [ weight_mode dscp ] [ecn] [ set_default ]\n");
+ fprintf(stderr, " nsswred limit BYTES DP NUMBER min BYTES max BYTES avpkt BYTES dscp NUMBER [ probability VALUE ] [ burst PACKETS ]\n");
+}
+
+static int nsswred_setup(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ struct rtattr *tail;
+ struct tc_nsswred_qopt opt;
+
+ memset(&opt, 0, sizeof(opt));
+ unsigned int dps = 0;
+ unsigned int def_dp = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "DPs") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&dps, *argv, 0) || dps > NSSWRED_CLASS_MAX) {
+
+ fprintf(stderr, "NSSWRED: DPs should be between 1 - %d\n", NSSWRED_CLASS_MAX);
+ return -1;
+ }
+ } else if (strcmp(*argv, "weight_mode") == 0) {
+ NEXT_ARG();
+ if (strcmp(*argv, "dscp") == 0) {
+ opt.weight_mode = TC_NSSWRED_WEIGHT_MODE_DSCP;
+ } else {
+ fprintf(stderr, "Illegal \"weight_mode\", we only support dscp at this moment\n");
+ }
+ } else if (strcmp(*argv, "ecn") == 0) {
+ opt.ecn = 1;
+ } else if (strcmp(*argv, "dp_default") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&def_dp, *argv, 0) || def_dp > dps) {
+ fprintf(stderr, "NSSWRED: Illegal dp_default value\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "help") == 0) {
+ nsswred_explain();
+ return -1;
+ } else if (strcmp(*argv, "set_default") == 0) {
+ opt.set_default = 1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ nsswred_explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!dps || !def_dp) {
+ fprintf(stderr, "NSSWRED: Illegal nsswred setup parameters\n");
+ return -1;
+ }
+ opt.traffic_classes = dps;
+ opt.def_traffic_class = def_dp;
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSWRED_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nsswred_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ struct rtattr *tail;
+ struct tc_nsswred_qopt opt;
+
+ int total_args = argc;
+ unsigned burst = 0;
+ unsigned avpkt = 0;
+ double probability = 0.0;
+ unsigned char weighted = (strcmp(qu->id, "nsswred") == 0);
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.limit, *argv)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "set_default") == 0) {
+ opt.set_default = 1;
+ } else if (strcmp(*argv, "min") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.rap.min, *argv)) {
+ fprintf(stderr, "Illegal \"min\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "max") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.rap.max, *argv)) {
+ fprintf(stderr, "Illegal \"max\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "burst") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&burst, *argv, 0)) {
+ fprintf(stderr, "Illegal \"burst\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "avpkt") == 0) {
+ NEXT_ARG();
+ if (get_size(&avpkt, *argv)) {
+ fprintf(stderr, "Illegal \"avpkt\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "probability") == 0) {
+ NEXT_ARG();
+ if (sscanf(*argv, "%lg", &probability) != 1) {
+ fprintf(stderr, "Illegal \"probability\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ecn") == 0) {
+ opt.ecn = 1;
+ } else if (strcmp(*argv, "help") == 0) {
+ if (weighted) {
+ nsswred_explain();
+ } else {
+ nssred_explain();
+ }
+ return -1;
+ } else if (weighted) {
+ if (strcmp(*argv, "setup") == 0) {
+ if (argc != total_args) {
+ fprintf(stderr, "Setup command must be the first parameter\n");
+ return -1;
+ }
+ return nsswred_setup(qu, argc-1, argv+1, n);
+ } else if (strcmp(*argv, "DP") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&opt.traffic_id, *argv, 0)) {
+ fprintf(stderr, "Illegal \"DP\"");
+ return -1;
+ }
+ } else if (strcmp(*argv, "dscp") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&opt.weight_mode_value, *argv, 0)) {
+ fprintf(stderr, "Illegal \"dscp\" value\n");
+ return -1;
+ }
+ }
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ if (weighted) {
+ nsswred_explain();
+ } else {
+ nssred_explain();
+ }
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (weighted) {
+ if (!opt.limit || !opt.rap.min || !opt.rap.max || !opt.traffic_id || !avpkt || !opt.weight_mode_value) {
+ fprintf(stderr, "NSSWRED: Require limit, min, max, avpkt, DP, weight_mode_value\n");
+ return -1;
+ }
+ } else {
+ if (!opt.limit || !avpkt) {
+ fprintf(stderr, "NSSRED: Require limit, avpkt");
+ return -1;
+ }
+ }
+
+ /*
+ * Compute default min/max thresholds based on
+ * Sally Floyd's recommendations:
+ * http://www.icir.org/floyd/REDparameters.txt
+ */
+ if (!opt.rap.max)
+ opt.rap.max = opt.rap.min ? opt.rap.min * 3 : opt.limit / 4;
+ if (!opt.rap.min)
+ opt.rap.min = opt.rap.max / 3;
+ if (!burst)
+ burst = (2 * opt.rap.min + opt.rap.max) / (3 * avpkt);
+ if ((opt.rap.exp_weight_factor = tc_red_eval_ewma(opt.rap.min, burst, avpkt)) < 0) {
+ fprintf(stderr, "RED: failed to calculate EWMA constant.\n");
+ return -1;
+ }
+
+ /*
+ * project [0.0-1.0] to [0-255] to avoid floating point calculation
+ */
+ opt.rap.probability = probability * (pow(2, 8)-1);
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSWRED_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nsswred_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_NSSWRED_MAX + 1];
+ struct tc_nsswred_qopt *qopt;
+ int i;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_NSSWRED_MAX, opt);
+
+ if (tb[TCA_NSSWRED_PARMS] == NULL)
+ return -1;
+
+ if (RTA_PAYLOAD(tb[TCA_NSSWRED_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_NSSWRED_PARMS]);
+
+ if (strcmp(qu->id, "nsswred") == 0) {
+ fprintf(f, "DPs %d def_DP %d weight mode: " , qopt->traffic_classes, qopt->def_traffic_class);
+ if (qopt->weight_mode == TC_NSSWRED_WEIGHT_MODE_DSCP)
+ fprintf(f, "DSCP\n");
+ else
+ fprintf(f, "Unknown\n");
+ for (i = 0;i < qopt->traffic_classes; i ++) {
+ if (qopt->tntc[i].rap.exp_weight_factor) {
+ double prob = (double)qopt->tntc[i].rap.probability;
+ fprintf(f, "DP %d: limit %d, weight mode value: %d min: %d max: %d exp_weight_factor: %d probability %.2f\n", i+1, qopt->tntc[i].limit, qopt->tntc[i].weight_mode_value
+ , qopt->tntc[i].rap.min,qopt->tntc[i].rap.max,qopt->tntc[i].rap.exp_weight_factor,prob/255);
+ }
+ }
+ } else {
+ double prob = (double)qopt->rap.probability;
+ fprintf(f, "limit %d, min: %d max: %d exp_weight_factor: %d probability %.2f\n", qopt->limit, qopt->rap.min,qopt->rap.max,qopt->rap.exp_weight_factor,prob/255);
+ }
+
+ if (qopt->ecn)
+ fprintf(f, "ECN enabled ");
+ if (qopt->set_default)
+ fprintf(f, "set_default ");
+
+ return 0;
+}
+
+struct qdisc_util nssred_qdisc_util = {
+ .id = "nssred",
+ .parse_qopt = nsswred_parse_opt,
+ .print_qopt = nsswred_print_opt,
+};
+
+struct qdisc_util nsswred_qdisc_util = {
+ .id = "nsswred",
+ .parse_qopt = nsswred_parse_opt,
+ .print_qopt = nsswred_print_opt,
+};
+
+/* ======================== NSSFIFO =======================*/
+
+static void nssfifo_explain(void)
+{
+ fprintf(stderr, "Usage: ... nsspfifo [ limit PACKETS ] [ set_default ]\n");
+}
+
+static int nssfifo_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ struct rtattr *tail;
+ struct tc_nssfifo_qopt opt;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.limit, *argv) || opt.limit == 0) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "set_default") == 0) {
+ opt.set_default = 1;
+ } else if (strcmp(*argv, "help") == 0) {
+ nssfifo_explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ nssfifo_explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSFIFO_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nssfifo_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_NSSFIFO_MAX + 1];
+ struct tc_nssfifo_qopt *qopt;
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_NSSFIFO_MAX, opt);
+
+ if (tb[TCA_NSSFIFO_PARMS] == NULL)
+ return -1;
+
+ if (RTA_PAYLOAD(tb[TCA_NSSFIFO_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_NSSFIFO_PARMS]);
+
+ if (strcmp(qu->id, "nssbfifo") == 0)
+ fprintf(f, "limit %s ", sprint_size(qopt->limit, b1));
+ else
+ fprintf(f, "limit %up ", qopt->limit);
+
+ if (qopt->set_default)
+ fprintf(f, "set_default ");
+
+ return 0;
+}
+
+struct qdisc_util nsspfifo_qdisc_util = {
+ .id = "nsspfifo",
+ .parse_qopt = nssfifo_parse_opt,
+ .print_qopt = nssfifo_print_opt,
+};
+
+struct qdisc_util nssbfifo_qdisc_util = {
+ .id = "nssbfifo",
+ .parse_qopt = nssfifo_parse_opt,
+ .print_qopt = nssfifo_print_opt,
+};
+
+/* ======================== NSSCODEL =======================*/
+
+static void nsscodel_explain(void)
+{
+ fprintf(stderr, "Usage: ... nsscodel target TIME interval TIME [ limit PACKETS ] [ set_default ]\n");
+}
+
+static void nsscodel_explain_err1(void)
+{
+ fprintf(stderr, "Value of target and interval should be greater than 1ms\n");
+}
+
+static int nsscodel_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ struct rtattr *tail;
+ struct tc_nsscodel_qopt opt;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "target") == 0) {
+ NEXT_ARG();
+ if (get_time(&opt.target, *argv)) {
+ fprintf(stderr, "Illegal \"target\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.limit, *argv) || opt.limit == 0) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "interval") == 0) {
+ NEXT_ARG();
+ if (get_time(&opt.interval, *argv)) {
+ fprintf(stderr, "Illegal \"interval\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "set_default") == 0) {
+ opt.set_default = 1;
+ } else if (strcmp(*argv, "help") == 0) {
+ nsscodel_explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ nsscodel_explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!opt.target || !opt.interval) {
+ nsscodel_explain();
+ return -1;
+ }
+
+ if (opt.target < 1000 || opt.interval < 1000) {
+ nsscodel_explain_err1();
+ return -1;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSCODEL_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nsscodel_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_NSSCODEL_MAX + 1];
+ struct tc_nsscodel_qopt *qopt;
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_NSSCODEL_MAX, opt);
+
+ if (tb[TCA_NSSCODEL_PARMS] == NULL)
+ return -1;
+
+ if (RTA_PAYLOAD(tb[TCA_NSSCODEL_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_NSSCODEL_PARMS]);
+
+ fprintf(f, "target %s limit %up interval %s ",
+ sprint_time(qopt->target, b1),
+ qopt->limit,
+ sprint_time(qopt->interval, b2));
+
+ if (qopt->set_default)
+ fprintf(f, "set_default ");
+
+ return 0;
+}
+
+static int nsscodel_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+ struct tc_nsscodel_xstats *st;
+
+ if (xstats == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+
+ st = RTA_DATA(xstats);
+ fprintf(f, " peak queue delay %ums peak drop delay %ums",
+ st->peak_queue_delay, st->peak_drop_delay);
+
+ return 0;
+}
+
+struct qdisc_util nsscodel_qdisc_util = {
+ .id = "nsscodel",
+ .parse_qopt = nsscodel_parse_opt,
+ .print_qopt = nsscodel_print_opt,
+ .print_xstats = nsscodel_print_xstats,
+};
+
+/* ======================== NSSTBL =======================*/
+
+static void nsstbl_explain(void)
+{
+ fprintf(stderr, "Usage: ... nsstbl burst BYTES rate BPS [ mtu BYTES ]\n");
+}
+
+static int nsstbl_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ int ok = 0;
+ struct rtattr *tail;
+ struct tc_nsstbl_qopt opt;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "burst") == 0 ||
+ strcmp(*argv, "buffer") == 0 ||
+ strcmp(*argv, "maxburst") == 0) {
+ NEXT_ARG();
+ if (opt.burst) {
+ fprintf(stderr, "Double \"buffer/burst\" spec\n");
+ return -1;
+ }
+ if (get_size(&opt.burst, *argv)) {
+ fprintf(stderr, "Illegal \"burst\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "mtu") == 0 ||
+ strcmp(*argv, "minburst") == 0) {
+ NEXT_ARG();
+ if (opt.mtu) {
+ fprintf(stderr, "Double \"mtu/minburst\" spec\n");
+ return -1;
+ }
+ if (get_size(&opt.mtu, *argv)) {
+ fprintf(stderr, "Illegal \"mtu\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "rate") == 0) {
+ NEXT_ARG();
+ if (opt.rate) {
+ fprintf(stderr, "Double \"rate\" spec\n");
+ return -1;
+ }
+ if (get_rate(&opt.rate, *argv)) {
+ fprintf(stderr, "Illegal \"rate\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "help") == 0) {
+ nsstbl_explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ nsstbl_explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!ok) {
+ nsstbl_explain();
+ return -1;
+ }
+
+ if (!opt.rate || !opt.burst) {
+ fprintf(stderr, "Both \"rate\" and \"burst\" are required.\n");
+ return -1;
+ }
+
+ if (opt.peakrate) {
+ if (!opt.mtu) {
+ fprintf(stderr, "\"mtu\" is required, if \"peakrate\" is requested.\n");
+ return -1;
+ }
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSTBL_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nsstbl_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_NSSTBL_MAX + 1];
+ struct tc_nsstbl_qopt *qopt;
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_NSSTBL_MAX, opt);
+
+ if (tb[TCA_NSSTBL_PARMS] == NULL)
+ return -1;
+
+ if (RTA_PAYLOAD(tb[TCA_NSSTBL_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_NSSTBL_PARMS]);
+
+ fprintf(f, "buffer/maxburst %s ", sprint_size(qopt->burst, b1));
+ fprintf(f, "rate %s ", sprint_rate(qopt->rate, b1));
+ fprintf(f, "peakrate %s ", sprint_rate(qopt->peakrate, b1));
+ fprintf(f, "mtu/minburst %s ", sprint_size(qopt->mtu, b1));
+ return 0;
+}
+
+struct qdisc_util nsstbl_qdisc_util = {
+ .id = "nsstbl",
+ .parse_qopt = nsstbl_parse_opt,
+ .print_qopt = nsstbl_print_opt,
+};
+
+/* ======================== NSSPRIO =======================*/
+
+static void nssprio_explain(void)
+{
+ fprintf(stderr, "Usage: ... nssprio [ bands NUMBER (default 256) ]\n");
+}
+
+static int nssprio_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ int ok = 0;
+ struct rtattr *tail;
+ struct tc_nssprio_qopt opt;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bands") == 0) {
+ NEXT_ARG();
+ if (get_integer(&opt.bands, *argv, 10)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "help") == 0) {
+ nssprio_explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ nssprio_explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!ok) {
+ opt.bands = TCA_NSSPRIO_MAX_BANDS;
+ } else if (opt.bands > TCA_NSSPRIO_MAX_BANDS) {
+ nssprio_explain();
+ return -1;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSPRIO_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nssprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_NSSPRIO_MAX + 1];
+ struct tc_nssprio_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_NSSPRIO_MAX, opt);
+
+ if (tb[TCA_NSSPRIO_PARMS] == NULL)
+ return -1;
+
+ if (RTA_PAYLOAD(tb[TCA_NSSPRIO_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_NSSPRIO_PARMS]);
+
+ fprintf(f, "bands %u ", qopt->bands);
+
+ return 0;
+}
+
+struct qdisc_util nssprio_qdisc_util = {
+ .id = "nssprio",
+ .parse_qopt = nssprio_parse_opt,
+ .print_qopt = nssprio_print_opt,
+};
+
+/* ======================== NSSBF =======================*/
+
+static void nssbf_explain_qdisc(void)
+{
+ fprintf(stderr,
+ "Usage: ... nssbf \n"
+ );
+}
+
+static void nssbf_explain_class(void)
+{
+ fprintf(stderr, "Usage: ... nssbf rate BPS burst BYTES [ mtu BYTES ]\n");
+ fprintf(stderr, " [ quantum BYTES ]\n");
+}
+
+static void nssbf_explain1(char *arg)
+{
+ fprintf(stderr, "NSSBF: Illegal \"%s\"\n", arg);
+}
+
+static int nssbf_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ struct tc_nssbf_qopt opt;
+ struct rtattr *tail;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (matches(*argv, "default") == 0) {
+ NEXT_ARG();
+ if (opt.defcls != 0) {
+ fprintf(stderr, "NSSBF: Double \"default\"\n");
+ return -1;
+ }
+ if (get_u16(&opt.defcls, *argv, 16) < 0) {
+ nssbf_explain1("default");
+ return -1;
+ }
+ } else if (matches(*argv, "help") == 0) {
+ nssbf_explain_qdisc();
+ return -1;
+ } else {
+ fprintf(stderr, "NSSBF: What is \"%s\" ?\n", *argv);
+ nssbf_explain_qdisc();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSBF_QDISC_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nssbf_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct tc_nssbf_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+ if (RTA_PAYLOAD(opt) < sizeof(*qopt))
+ return -1;
+ qopt = RTA_DATA(opt);
+
+ if (qopt->defcls != 0)
+ fprintf(f, "default %x ", qopt->defcls);
+
+ return 0;
+}
+
+static int nssbf_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ int ok = 0;
+ struct rtattr *tail;
+ struct tc_nssbf_class_qopt opt;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "burst") == 0 ||
+ strcmp(*argv, "buffer") == 0 ||
+ strcmp(*argv, "maxburst") == 0) {
+ NEXT_ARG();
+ if (opt.burst) {
+ fprintf(stderr, "Double \"buffer/burst\" spec\n");
+ return -1;
+ }
+ if (get_size(&opt.burst, *argv)) {
+ fprintf(stderr, "Illegal \"burst\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "mtu") == 0) {
+ NEXT_ARG();
+ if (opt.mtu) {
+ fprintf(stderr, "Double \"mtu\" spec\n");
+ return -1;
+ }
+ if (get_size(&opt.mtu, *argv)) {
+ fprintf(stderr, "Illegal \"mtu\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "quantum") == 0) {
+ NEXT_ARG();
+ if (opt.quantum) {
+ fprintf(stderr, "Double \"quantum\" spec\n");
+ return -1;
+ }
+ if (get_size(&opt.quantum, *argv)) {
+ fprintf(stderr, "Illegal \"quantum\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "rate") == 0) {
+ NEXT_ARG();
+ if (opt.rate) {
+ fprintf(stderr, "Double \"rate\" spec\n");
+ return -1;
+ }
+ if (get_rate(&opt.rate, *argv)) {
+ fprintf(stderr, "Illegal \"rate\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "help") == 0) {
+ nssbf_explain_class();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ nssbf_explain_class();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!ok) {
+ nssbf_explain_class();
+ return -1;
+ }
+
+ if (!opt.rate || !opt.burst) {
+ fprintf(stderr, "Both \"rate\" and \"burst\" are required.\n");
+ return -1;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSBF_CLASS_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nssbf_print_class_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_NSSBF_MAX + 1];
+ struct tc_nssbf_class_qopt *qopt;
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_NSSBF_MAX, opt);
+
+ if (tb[TCA_NSSBF_CLASS_PARMS] == NULL)
+ return -1;
+
+ if (RTA_PAYLOAD(tb[TCA_NSSBF_CLASS_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_NSSBF_CLASS_PARMS]);
+
+ fprintf(f, "burst %s ", sprint_size(qopt->burst, b1));
+ fprintf(f, "rate %s ", sprint_rate(qopt->rate, b1));
+ fprintf(f, "quantum %s ", sprint_size(qopt->quantum, b1));
+ fprintf(f, "mtu %s ", sprint_size(qopt->mtu, b1));
+ return 0;
+}
+
+struct qdisc_util nssbf_qdisc_util = {
+ .id = "nssbf",
+ .parse_qopt = nssbf_parse_opt,
+ .print_qopt = nssbf_print_opt,
+ .parse_copt = nssbf_parse_class_opt,
+ .print_copt = nssbf_print_class_opt,
+};
+
+/* ======================== NSSWRR =======================*/
+
+static void nsswrr_explain_qdisc(void)
+{
+ fprintf(stderr,
+ "Usage (qdisc): ... nsswrr\n"
+ "\n"
+ "nsswrr qdisc does not take in any parameters\n"
+ );
+}
+
+static void nsswrr_explain_class(void)
+{
+ fprintf(stderr, "Usage (class): ... nsswrr quantum PACKETS ]\n");
+}
+
+static int nsswrr_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ while (argc > 0) {
+ if (matches(*argv, "help") == 0) {
+ nsswrr_explain_qdisc();
+ return -1;
+ } else {
+ fprintf(stderr, "NSSWRR: What is \"%s\" ?\n", *argv);
+ nsswrr_explain_qdisc();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ return 0;
+}
+
+static int nsswrr_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ /*
+ * We currently dont have any parameters to print
+ */
+
+ return 0;
+}
+
+static int nsswrr_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ int ok = 0;
+ struct rtattr *tail;
+ struct tc_nsswrr_class_qopt opt;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "quantum") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.quantum, *argv, 10)) {
+ fprintf(stderr, "Illegal \"quantum\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "help") == 0) {
+ nsswrr_explain_class();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ nsswrr_explain_class();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!ok) {
+ nsswrr_explain_class();
+ return -1;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSWRR_CLASS_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nsswrr_print_class_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_NSSWRR_MAX + 1];
+ struct tc_nsswrr_class_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_NSSWRR_MAX, opt);
+
+ if (tb[TCA_NSSWRR_CLASS_PARMS] == NULL)
+ return -1;
+
+ if (RTA_PAYLOAD(tb[TCA_NSSWRR_CLASS_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_NSSWRR_CLASS_PARMS]);
+
+ fprintf(f, "quantum %up ", qopt->quantum);
+ return 0;
+}
+
+struct qdisc_util nsswrr_qdisc_util = {
+ .id = "nsswrr",
+ .parse_qopt = nsswrr_parse_opt,
+ .print_qopt = nsswrr_print_opt,
+ .parse_copt = nsswrr_parse_class_opt,
+ .print_copt = nsswrr_print_class_opt,
+};
+
+/* ======================== NSSWFQ =======================*/
+
+static void nsswfq_explain_qdisc(void)
+{
+ fprintf(stderr,
+ "Usage (qdisc): ... nsswfq\n"
+ "\n"
+ "nsswfq qdisc does not take in any parameters\n"
+ );
+}
+
+static void nsswfq_explain_class(void)
+{
+ fprintf(stderr, "Usage (class): ... nsswfq quantum BYTES ]\n");
+}
+
+static int nsswfq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ while (argc > 0) {
+ if (matches(*argv, "help") == 0) {
+ nsswfq_explain_qdisc();
+ return -1;
+ } else {
+ fprintf(stderr, "NSSWFQ: What is \"%s\" ?\n", *argv);
+ nsswfq_explain_qdisc();
+ return -1;
+ }
+ argc--, argv++;
+ }
+ return 0;
+}
+
+static int nsswfq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ /*
+ * We currently dont have any parameters to print
+ */
+ return 0;
+}
+
+static int nsswfq_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ int ok = 0;
+ struct rtattr *tail;
+ struct tc_nsswfq_class_qopt opt;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "quantum") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.quantum, *argv, 10)) {
+ fprintf(stderr, "Illegal \"quantum\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "help") == 0) {
+ nsswfq_explain_class();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ nsswfq_explain_class();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!ok) {
+ nsswfq_explain_class();
+ return -1;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSWFQ_CLASS_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nsswfq_print_class_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_NSSWFQ_MAX + 1];
+ struct tc_nsswfq_class_qopt *qopt;
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_NSSWFQ_MAX, opt);
+
+ if (tb[TCA_NSSWFQ_CLASS_PARMS] == NULL)
+ return -1;
+
+ if (RTA_PAYLOAD(tb[TCA_NSSWFQ_CLASS_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_NSSWFQ_CLASS_PARMS]);
+
+ fprintf(f, "quantum %s ", sprint_size(qopt->quantum, b1));
+ return 0;
+}
+
+struct qdisc_util nsswfq_qdisc_util = {
+ .id = "nsswfq",
+ .parse_qopt = nsswfq_parse_opt,
+ .print_qopt = nsswfq_print_opt,
+ .parse_copt = nsswfq_parse_class_opt,
+ .print_copt = nsswfq_print_class_opt,
+};
+
+/* ======================== NSSHTB =======================*/
+
+static void nsshtb_explain_qdisc(void)
+{
+ fprintf(stderr,
+ "Usage: ... nsshtb [ r2q ]\n"
+ );
+}
+
+static void nsshtb_explain_class(void)
+{
+ fprintf(stderr, "Usage: ... nsshtb priority 0-3 [ quantum BYTES ] [ rate BPS ] [ burst BYTES ] [crate BPS ] [ cburst BYTES ]\n");
+ fprintf(stderr, " [ overhead BYTES ] \n");
+}
+
+static void nsshtb_explain1(char *arg)
+{
+ fprintf(stderr, "NSSHTB: Illegal \"%s\"\n", arg);
+}
+
+static int nsshtb_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ struct tc_nsshtb_qopt opt;
+ struct rtattr *tail;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "r2q") == 0) {
+ NEXT_ARG();
+ if (opt.r2q != 0) {
+ fprintf(stderr, "NSSHTB: Double \"r2q\"\n");
+ return -1;
+ }
+ if (get_u32(&opt.r2q, *argv, 10) < 0) {
+ nsshtb_explain1("r2q");
+ return -1;
+ }
+ } else if (strcmp(*argv, "help") == 0) {
+ nsshtb_explain_qdisc();
+ return -1;
+ } else {
+ fprintf(stderr, "NSSHTB: What is \"%s\" ?\n", *argv);
+ nsshtb_explain_qdisc();
+ return -1;
+ }
+ argc--, argv++;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSHTB_QDISC_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nsshtb_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct tc_nsshtb_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+ if (RTA_PAYLOAD(opt) < sizeof(*qopt))
+ return -1;
+ qopt = RTA_DATA(opt);
+
+ if (qopt->r2q != 0)
+ fprintf(f, "r2q %x ", qopt->r2q);
+
+ return 0;
+}
+
+static int nsshtb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ int ok = 0;
+ struct rtattr *tail;
+ struct tc_nsshtb_class_qopt opt;
+ int crate = 0;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "burst") == 0) {
+ NEXT_ARG();
+ if (opt.burst) {
+ fprintf(stderr, "Double \"burst\" spec\n");
+ return -1;
+ }
+ if (get_size(&opt.burst, *argv)) {
+ fprintf(stderr, "Illegal \"burst\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "rate") == 0) {
+ NEXT_ARG();
+ if (opt.rate) {
+ fprintf(stderr, "Double \"rate\" spec\n");
+ return -1;
+ }
+ if (get_rate(&opt.rate, *argv)) {
+ fprintf(stderr, "Illegal \"rate\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "cburst") == 0) {
+ NEXT_ARG();
+ if (opt.cburst) {
+ fprintf(stderr, "Double \"cburst\" spec\n");
+ return -1;
+ }
+ if (get_size(&opt.cburst, *argv)) {
+ fprintf(stderr, "Illegal \"cburst\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "crate") == 0) {
+ NEXT_ARG();
+ if (opt.crate) {
+ fprintf(stderr, "Double \"crate\" spec\n");
+ return -1;
+ }
+ if (get_rate(&opt.crate, *argv)) {
+ fprintf(stderr, "Illegal \"crate\"\n");
+ return -1;
+ }
+ crate++;
+ ok++;
+ } else if (strcmp(*argv, "priority") == 0) {
+ NEXT_ARG();
+ if (opt.priority) {
+ fprintf(stderr, "Double \"priority\" spec\n");
+ return -1;
+ }
+ if (get_u32(&opt.priority, *argv, 10) < 0) {
+ fprintf(stderr, "Illegal \"priority\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "quantum") == 0) {
+ NEXT_ARG();
+ if (opt.quantum) {
+ fprintf(stderr, "Double \"quantum\" spec\n");
+ return -1;
+ }
+ if (get_size(&opt.quantum, *argv)) {
+ fprintf(stderr, "Illegal \"quantum\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "overhead") == 0) {
+ NEXT_ARG();
+ if (opt.overhead) {
+ fprintf(stderr, "Double \"overhead\" spec\n");
+ return -1;
+ }
+ if (get_size(&opt.overhead, *argv)) {
+ fprintf(stderr, "Illegal \"overhead\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "help") == 0) {
+ nsshtb_explain_class();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ nsshtb_explain_class();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!ok) {
+ nsshtb_explain_class();
+ return -1;
+ }
+
+ if (opt.rate && !opt.burst) {
+ fprintf(stderr, "\"burst\" required if \"rate\" is specified.\n");
+ return -1;
+ }
+
+ if (!crate) {
+ fprintf(stderr, "\"crate\" is required.\n");
+ return -1;
+ }
+
+ if (opt.crate && !opt.cburst) {
+ fprintf(stderr, "\"cburst\" required if \"crate\" is non-zero.\n");
+ return -1;
+ }
+
+ if (opt.priority > 3) {
+ fprintf(stderr, "\"priority\" should be an integer between 0 and 3.\n");
+ return -1;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSHTB_CLASS_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nsshtb_print_class_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_NSSHTB_MAX + 1];
+ struct tc_nsshtb_class_qopt *qopt;
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_NSSHTB_MAX, opt);
+
+ if (tb[TCA_NSSHTB_CLASS_PARMS] == NULL)
+ return -1;
+
+ if (RTA_PAYLOAD(tb[TCA_NSSHTB_CLASS_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_NSSHTB_CLASS_PARMS]);
+
+ fprintf(f, "burst %s ", sprint_size(qopt->burst, b1));
+ fprintf(f, "rate %s ", sprint_rate(qopt->rate, b1));
+ fprintf(f, "cburst %s ", sprint_size(qopt->cburst, b1));
+ fprintf(f, "crate %s ", sprint_rate(qopt->crate, b1));
+ fprintf(f, "priority %u ", qopt->priority);
+ fprintf(f, "quantum %s ", sprint_size(qopt->quantum, b1));
+ fprintf(f, "overhead %s ", sprint_size(qopt->overhead, b1));
+ return 0;
+}
+
+struct qdisc_util nsshtb_qdisc_util = {
+ .id = "nsshtb",
+ .parse_qopt = nsshtb_parse_opt,
+ .print_qopt = nsshtb_print_opt,
+ .parse_copt = nsshtb_parse_class_opt,
+ .print_copt = nsshtb_print_class_opt,
+};
+
+/* ======================== NSSBLACKHOLE ======================= */
+
+static void nssblackhole_explain(void)
+{
+ fprintf(stderr, "Usage: ... nssblackhole [ set_default ]\n");
+}
+
+static int nssblackhole_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n)
+{
+ struct rtattr *tail;
+ struct tc_nssblackhole_qopt opt;
+
+ memset(&opt, 0, sizeof(opt));
+
+ while (argc > 0) {
+ if (strcmp(*argv, "set_default") == 0) {
+ opt.set_default = 1;
+ } else if (strcmp(*argv, "help") == 0) {
+ nssblackhole_explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ nssblackhole_explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 1024, TCA_NSSBLACKHOLE_PARMS, &opt, sizeof(opt));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int nssblackhole_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_NSSBLACKHOLE_MAX + 1];
+ struct tc_nssblackhole_qopt *qopt;
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_NSSBLACKHOLE_MAX, opt);
+
+ if (tb[TCA_NSSBLACKHOLE_PARMS] == NULL)
+ return -1;
+
+ if (RTA_PAYLOAD(tb[TCA_NSSBLACKHOLE_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_NSSBLACKHOLE_PARMS]);
+
+ if (qopt->set_default)
+ fprintf(f, "set_default ");
+
+ return 0;
+}
+
+struct qdisc_util nssblackhole_qdisc_util = {
+ .id = "nssblackhole",
+ .parse_qopt = nssblackhole_parse_opt,
+ .print_qopt = nssblackhole_print_opt,
+};
diff --git a/tc/q_pie.c b/tc/q_pie.c
new file mode 100644
index 0000000..40982f9
--- /dev/null
+++ b/tc/q_pie.c
@@ -0,0 +1,216 @@
+/* Copyright (C) 2013 Cisco Systems, Inc, 2013.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Author: Vijay Subramanian <vijaynsu@cisco.com>
+ * Author: Mythili Prabhu <mysuryan@cisco.com>
+ *
+ */
+
+#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 <math.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... pie [ limit PACKETS ][ target TIME us]\n"
+ " [ tupdate TIME us][ alpha ALPHA ]"
+ "[beta BETA ][bytemode | nobytemode][ecn | noecn ]\n");
+}
+
+#define ALPHA_MAX 32
+#define BETA_MAX 32
+
+static int pie_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ unsigned int limit = 0;
+ unsigned int target = 0;
+ unsigned int tupdate = 0;
+ unsigned int alpha = 0;
+ unsigned int beta = 0;
+ int ecn = -1;
+ int bytemode = -1;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&limit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "target") == 0) {
+ NEXT_ARG();
+ if (get_time(&target, *argv)) {
+ fprintf(stderr, "Illegal \"target\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "tupdate") == 0) {
+ NEXT_ARG();
+ if (get_time(&tupdate, *argv)) {
+ fprintf(stderr, "Illegal \"tupdate\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "alpha") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&alpha, *argv, 0) ||
+ (alpha > ALPHA_MAX)) {
+ fprintf(stderr, "Illegal \"alpha\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "beta") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&beta, *argv, 0) ||
+ (beta > BETA_MAX)) {
+ fprintf(stderr, "Illegal \"beta\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ecn") == 0) {
+ ecn = 1;
+ } else if (strcmp(*argv, "noecn") == 0) {
+ ecn = 0;
+ } else if (strcmp(*argv, "bytemode") == 0) {
+ bytemode = 1;
+ } else if (strcmp(*argv, "nobytemode") == 0) {
+ bytemode = 0;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--;
+ argv++;
+ }
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ if (limit)
+ addattr_l(n, 1024, TCA_PIE_LIMIT, &limit, sizeof(limit));
+ if (tupdate)
+ addattr_l(n, 1024, TCA_PIE_TUPDATE, &tupdate, sizeof(tupdate));
+ if (target)
+ addattr_l(n, 1024, TCA_PIE_TARGET, &target, sizeof(target));
+ if (alpha)
+ addattr_l(n, 1024, TCA_PIE_ALPHA, &alpha, sizeof(alpha));
+ if (beta)
+ addattr_l(n, 1024, TCA_PIE_BETA, &beta, sizeof(beta));
+ if (ecn != -1)
+ addattr_l(n, 1024, TCA_PIE_ECN, &ecn, sizeof(ecn));
+ if (bytemode != -1)
+ addattr_l(n, 1024, TCA_PIE_BYTEMODE, &bytemode,
+ sizeof(bytemode));
+
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int pie_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_PIE_MAX + 1];
+ unsigned int limit;
+ unsigned int tupdate;
+ unsigned int target;
+ unsigned int alpha;
+ unsigned int beta;
+ unsigned int ecn;
+ unsigned int bytemode;
+
+ SPRINT_BUF(b1);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_PIE_MAX, opt);
+
+ if (tb[TCA_PIE_LIMIT] &&
+ RTA_PAYLOAD(tb[TCA_PIE_LIMIT]) >= sizeof(__u32)) {
+ limit = rta_getattr_u32(tb[TCA_PIE_LIMIT]);
+ fprintf(f, "limit %up ", limit);
+ }
+ if (tb[TCA_PIE_TARGET] &&
+ RTA_PAYLOAD(tb[TCA_PIE_TARGET]) >= sizeof(__u32)) {
+ target = rta_getattr_u32(tb[TCA_PIE_TARGET]);
+ fprintf(f, "target %s ", sprint_time(target, b1));
+ }
+ if (tb[TCA_PIE_TUPDATE] &&
+ RTA_PAYLOAD(tb[TCA_PIE_TUPDATE]) >= sizeof(__u32)) {
+ tupdate = rta_getattr_u32(tb[TCA_PIE_TUPDATE]);
+ fprintf(f, "tupdate %s ", sprint_time(tupdate, b1));
+ }
+ if (tb[TCA_PIE_ALPHA] &&
+ RTA_PAYLOAD(tb[TCA_PIE_ALPHA]) >= sizeof(__u32)) {
+ alpha = rta_getattr_u32(tb[TCA_PIE_ALPHA]);
+ fprintf(f, "alpha %u ", alpha);
+ }
+ if (tb[TCA_PIE_BETA] &&
+ RTA_PAYLOAD(tb[TCA_PIE_BETA]) >= sizeof(__u32)) {
+ beta = rta_getattr_u32(tb[TCA_PIE_BETA]);
+ fprintf(f, "beta %u ", beta);
+ }
+
+ if (tb[TCA_PIE_ECN] && RTA_PAYLOAD(tb[TCA_PIE_ECN]) >= sizeof(__u32)) {
+ ecn = rta_getattr_u32(tb[TCA_PIE_ECN]);
+ if (ecn)
+ fprintf(f, "ecn ");
+ }
+
+ if (tb[TCA_PIE_BYTEMODE] &&
+ RTA_PAYLOAD(tb[TCA_PIE_BYTEMODE]) >= sizeof(__u32)) {
+ bytemode = rta_getattr_u32(tb[TCA_PIE_BYTEMODE]);
+ if (bytemode)
+ fprintf(f, "bytemode ");
+ }
+
+ return 0;
+}
+
+static int pie_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct tc_pie_xstats *st;
+
+ if (xstats == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+
+ st = RTA_DATA(xstats);
+ /*prob is returned as a fracion of maximum integer value */
+ fprintf(f, "prob %f delay %uus avg_dq_rate %u\n",
+ (double)st->prob / UINT64_MAX, st->delay,
+ st->avg_dq_rate);
+ fprintf(f, "pkts_in %u overlimit %u dropped %u maxq %u ecn_mark %u\n",
+ st->packets_in, st->overlimit, st->dropped, st->maxq,
+ st->ecn_mark);
+ return 0;
+
+}
+
+struct qdisc_util pie_qdisc_util = {
+ .id = "pie",
+ .parse_qopt = pie_parse_opt,
+ .print_qopt = pie_print_opt,
+ .print_xstats = pie_print_xstats,
+};
diff --git a/tc/q_plug.c b/tc/q_plug.c
new file mode 100644
index 0000000..2c1c1a0
--- /dev/null
+++ b/tc/q_plug.c
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * q_log.c plug scheduler
+ *
+ * Copyright (C) 2019 Paolo Abeni <pabeni@redhat.com>
+ */
+
+#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 "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... plug [block | release | release_indefinite | limit NUMBER]\n");
+}
+
+static int plug_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct tc_plug_qopt opt = {};
+ int ok = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "block") == 0) {
+ opt.action = TCQ_PLUG_BUFFER;
+ ok++;
+ } else if (strcmp(*argv, "release") == 0) {
+ opt.action = TCQ_PLUG_RELEASE_ONE;
+ ok++;
+ } else if (strcmp(*argv, "release_indefinite") == 0) {
+ opt.action = TCQ_PLUG_RELEASE_INDEFINITE;
+ ok++;
+ } else if (strcmp(*argv, "limit") == 0) {
+ opt.action = TCQ_PLUG_LIMIT;
+ NEXT_ARG();
+ if (get_size(&opt.limit, *argv)) {
+ fprintf(stderr, "Illegal value for \"limit\": \"%s\"\n", *argv);
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "%s: unknown parameter \"%s\"\n", qu->id, *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (ok)
+ addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+ return 0;
+}
+
+static int plug_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ /* dummy implementation as sch_plug does not implement a dump op */
+ return 0;
+}
+
+
+struct qdisc_util plug_qdisc_util = {
+ .id = "plug",
+ .parse_qopt = plug_parse_opt,
+ .print_qopt = plug_print_opt,
+};
diff --git a/tc/q_prio.c b/tc/q_prio.c
new file mode 100644
index 0000000..8ef7cfa
--- /dev/null
+++ b/tc/q_prio.c
@@ -0,0 +1,129 @@
+/*
+ * q_prio.c PRIO.
+ *
+ * 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 "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... prio bands NUMBER priomap P1 P2...[multiqueue]\n");
+}
+
+static int prio_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ int pmap_mode = 0;
+ int idx = 0;
+ struct tc_prio_qopt opt = {3, { 1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 } };
+ struct rtattr *nest;
+ unsigned char mq = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bands") == 0) {
+ if (pmap_mode)
+ explain();
+ NEXT_ARG();
+ if (get_integer(&opt.bands, *argv, 10)) {
+ fprintf(stderr, "Illegal \"bands\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "priomap") == 0) {
+ if (pmap_mode) {
+ fprintf(stderr, "Error: duplicate priomap\n");
+ return -1;
+ }
+ pmap_mode = 1;
+ } else if (strcmp(*argv, "multiqueue") == 0) {
+ mq = 1;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ unsigned int band;
+
+ if (!pmap_mode) {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ if (get_unsigned(&band, *argv, 10)) {
+ fprintf(stderr, "Illegal \"priomap\" element\n");
+ return -1;
+ }
+ if (band >= opt.bands) {
+ fprintf(stderr, "\"priomap\" element is out of bands\n");
+ return -1;
+ }
+ if (idx > TC_PRIO_MAX) {
+ fprintf(stderr, "\"priomap\" index > TC_PRIO_MAX=%u\n", TC_PRIO_MAX);
+ return -1;
+ }
+ opt.priomap[idx++] = band;
+ }
+ argc--; argv++;
+ }
+
+/*
+ if (pmap_mode) {
+ for (; idx < TC_PRIO_MAX; idx++)
+ opt.priomap[idx] = opt.priomap[TC_PRIO_BESTEFFORT];
+ }
+*/
+ nest = addattr_nest_compat(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+ if (mq)
+ addattr_l(n, 1024, TCA_PRIO_MQ, NULL, 0);
+ addattr_nest_compat_end(n, nest);
+ return 0;
+}
+
+int prio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ int i;
+ struct tc_prio_qopt *qopt;
+ struct rtattr *tb[TCA_PRIO_MAX+1];
+
+ if (opt == NULL)
+ return 0;
+
+ if (parse_rtattr_nested_compat(tb, TCA_PRIO_MAX, opt, qopt,
+ sizeof(*qopt)))
+ return -1;
+
+ print_uint(PRINT_ANY, "bands", "bands %u ", qopt->bands);
+ open_json_array(PRINT_ANY, "priomap ");
+ for (i = 0; i <= TC_PRIO_MAX; i++)
+ print_uint(PRINT_ANY, NULL, " %d", qopt->priomap[i]);
+ close_json_array(PRINT_ANY, "");
+
+ if (tb[TCA_PRIO_MQ])
+ print_string(PRINT_FP, NULL, " multiqueue: %s ",
+ rta_getattr_u8(tb[TCA_PRIO_MQ]) ? "on" : "off");
+ print_bool(PRINT_JSON, "multiqueue", NULL,
+ tb[TCA_PRIO_MQ] && rta_getattr_u8(tb[TCA_PRIO_MQ]));
+
+ return 0;
+}
+
+struct qdisc_util prio_qdisc_util = {
+ .id = "prio",
+ .parse_qopt = prio_parse_opt,
+ .print_qopt = prio_print_opt,
+};
diff --git a/tc/q_qfq.c b/tc/q_qfq.c
new file mode 100644
index 0000000..eb8fa4b
--- /dev/null
+++ b/tc/q_qfq.c
@@ -0,0 +1,115 @@
+/*
+ * q_qfq.c QFQ.
+ *
+ * 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>
+ * Fabio Checconi <fabio@gandalf.sssup.it>
+ *
+ */
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... qfq\n");
+}
+
+static void explain1(const char *arg)
+{
+ fprintf(stderr, "Illegal \"%s\"\n", arg);
+}
+
+static void explain_class(void)
+{
+ fprintf(stderr, "Usage: ... qfq weight NUMBER maxpkt BYTES\n");
+}
+
+static int qfq_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ if (argc > 0) {
+ if (matches(*argv, "help") != 0)
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+
+ return 0;
+}
+
+static int qfq_parse_class_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct rtattr *tail;
+ __u32 tmp;
+
+ tail = addattr_nest(n, 4096, TCA_OPTIONS);
+
+ while (argc > 0) {
+ if (matches(*argv, "weight") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tmp, *argv, 10)) {
+ explain1("weight"); return -1;
+ }
+ addattr32(n, 4096, TCA_QFQ_WEIGHT, tmp);
+ } else if (matches(*argv, "maxpkt") == 0) {
+ NEXT_ARG();
+ if (get_u32(&tmp, *argv, 10)) {
+ explain1("maxpkt"); return -1;
+ }
+ addattr32(n, 4096, TCA_QFQ_LMAX, tmp);
+ } else if (strcmp(*argv, "help") == 0) {
+ explain_class();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain_class();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ addattr_nest_end(n, tail);
+
+ return 0;
+}
+
+static int qfq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_QFQ_MAX + 1];
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_QFQ_MAX, opt);
+
+ if (tb[TCA_QFQ_WEIGHT]) {
+ fprintf(f, "weight %u ",
+ rta_getattr_u32(tb[TCA_QFQ_WEIGHT]));
+ }
+
+ if (tb[TCA_QFQ_LMAX]) {
+ fprintf(f, "maxpkt %u ",
+ rta_getattr_u32(tb[TCA_QFQ_LMAX]));
+ }
+
+ return 0;
+}
+
+struct qdisc_util qfq_qdisc_util = {
+ .id = "qfq",
+ .parse_qopt = qfq_parse_opt,
+ .print_qopt = qfq_print_opt,
+ .parse_copt = qfq_parse_class_opt,
+ .print_copt = qfq_print_opt,
+};
diff --git a/tc/q_red.c b/tc/q_red.c
new file mode 100644
index 0000000..6256420
--- /dev/null
+++ b/tc/q_red.c
@@ -0,0 +1,234 @@
+/*
+ * q_red.c RED.
+ *
+ * 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 <math.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+#include "tc_red.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... red limit BYTES [min BYTES] [max BYTES] avpkt BYTES [burst PACKETS]\n"
+ " [adaptive] [probability PROBABILITY] [bandwidth KBPS]\n"
+ " [ecn] [harddrop]\n");
+}
+
+static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct tc_red_qopt opt = {};
+ unsigned int burst = 0;
+ unsigned int avpkt = 0;
+ double probability = 0.02;
+ unsigned int rate = 0;
+ int parm;
+ __u8 sbuf[256];
+ __u32 max_P;
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.limit, *argv)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "min") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.qth_min, *argv)) {
+ fprintf(stderr, "Illegal \"min\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "max") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.qth_max, *argv)) {
+ fprintf(stderr, "Illegal \"max\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "burst") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&burst, *argv, 0)) {
+ fprintf(stderr, "Illegal \"burst\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "avpkt") == 0) {
+ NEXT_ARG();
+ if (get_size(&avpkt, *argv)) {
+ fprintf(stderr, "Illegal \"avpkt\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "probability") == 0) {
+ NEXT_ARG();
+ if (sscanf(*argv, "%lg", &probability) != 1) {
+ fprintf(stderr, "Illegal \"probability\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "bandwidth") == 0) {
+ NEXT_ARG();
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate(&rate, *argv, dev)) {
+ fprintf(stderr, "Illegal \"bandwidth\"\n");
+ return -1;
+ }
+ } else if (get_rate(&rate, *argv)) {
+ fprintf(stderr, "Illegal \"bandwidth\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "ecn") == 0) {
+ opt.flags |= TC_RED_ECN;
+ } else if (strcmp(*argv, "harddrop") == 0) {
+ opt.flags |= TC_RED_HARDDROP;
+ } else if (strcmp(*argv, "adaptative") == 0) {
+ opt.flags |= TC_RED_ADAPTATIVE;
+ } else if (strcmp(*argv, "adaptive") == 0) {
+ opt.flags |= TC_RED_ADAPTATIVE;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (!opt.limit || !avpkt) {
+ fprintf(stderr, "RED: Required parameter (limit, avpkt) is missing\n");
+ return -1;
+ }
+ /* Compute default min/max thresholds based on
+ * Sally Floyd's recommendations:
+ * http://www.icir.org/floyd/REDparameters.txt
+ */
+ if (!opt.qth_max)
+ opt.qth_max = opt.qth_min ? opt.qth_min * 3 : opt.limit / 4;
+ if (!opt.qth_min)
+ opt.qth_min = opt.qth_max / 3;
+ if (!burst)
+ burst = (2 * opt.qth_min + opt.qth_max) / (3 * avpkt);
+ if (!rate) {
+ get_rate(&rate, "10Mbit");
+ fprintf(stderr, "RED: set bandwidth to 10Mbit\n");
+ }
+ if ((parm = tc_red_eval_ewma(opt.qth_min, burst, avpkt)) < 0) {
+ fprintf(stderr, "RED: failed to calculate EWMA constant.\n");
+ return -1;
+ }
+ if (parm >= 10)
+ fprintf(stderr, "RED: WARNING. Burst %u seems to be too large.\n", burst);
+ opt.Wlog = parm;
+ if ((parm = tc_red_eval_P(opt.qth_min, opt.qth_max, probability)) < 0) {
+ fprintf(stderr, "RED: failed to calculate probability.\n");
+ return -1;
+ }
+ opt.Plog = parm;
+ if ((parm = tc_red_eval_idle_damping(opt.Wlog, avpkt, rate, sbuf)) < 0) {
+ fprintf(stderr, "RED: failed to calculate idle damping table.\n");
+ return -1;
+ }
+ opt.Scell_log = parm;
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 1024, TCA_RED_PARMS, &opt, sizeof(opt));
+ addattr_l(n, 1024, TCA_RED_STAB, sbuf, 256);
+ max_P = probability * pow(2, 32);
+ addattr_l(n, 1024, TCA_RED_MAX_P, &max_P, sizeof(max_P));
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int red_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_RED_MAX + 1];
+ struct tc_red_qopt *qopt;
+ __u32 max_P = 0;
+
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+ SPRINT_BUF(b3);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_RED_MAX, opt);
+
+ if (tb[TCA_RED_PARMS] == NULL)
+ return -1;
+ qopt = RTA_DATA(tb[TCA_RED_PARMS]);
+ if (RTA_PAYLOAD(tb[TCA_RED_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ if (tb[TCA_RED_MAX_P] &&
+ RTA_PAYLOAD(tb[TCA_RED_MAX_P]) >= sizeof(__u32))
+ max_P = rta_getattr_u32(tb[TCA_RED_MAX_P]);
+
+ print_uint(PRINT_JSON, "limit", NULL, qopt->limit);
+ print_string(PRINT_FP, NULL, "limit %s ", sprint_size(qopt->limit, b1));
+ print_uint(PRINT_JSON, "min", NULL, qopt->qth_min);
+ print_string(PRINT_FP, NULL, "min %s ", sprint_size(qopt->qth_min, b2));
+ print_uint(PRINT_JSON, "max", NULL, qopt->qth_max);
+ print_string(PRINT_FP, NULL, "max %s ", sprint_size(qopt->qth_max, b3));
+
+ tc_red_print_flags(qopt->flags);
+
+ if (show_details) {
+ print_uint(PRINT_ANY, "ewma", "ewma %u ", qopt->Wlog);
+ if (max_P)
+ print_float(PRINT_ANY, "probability",
+ "probability %lg ", max_P / pow(2, 32));
+ else
+ print_uint(PRINT_ANY, "Plog", "Plog %u ", qopt->Plog);
+ print_uint(PRINT_ANY, "Scell_log", "Scell_log %u",
+ qopt->Scell_log);
+ }
+ return 0;
+}
+
+static int red_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats)
+{
+#ifdef TC_RED_ECN
+ struct tc_red_xstats *st;
+
+ if (xstats == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+
+ st = RTA_DATA(xstats);
+ print_uint(PRINT_ANY, "marked", " marked %u ", st->marked);
+ print_uint(PRINT_ANY, "early", "early %u ", st->early);
+ print_uint(PRINT_ANY, "pdrop", "pdrop %u ", st->pdrop);
+ print_uint(PRINT_ANY, "other", "other %u ", st->other);
+#endif
+ return 0;
+}
+
+
+struct qdisc_util red_qdisc_util = {
+ .id = "red",
+ .parse_qopt = red_parse_opt,
+ .print_qopt = red_print_opt,
+ .print_xstats = red_print_xstats,
+};
diff --git a/tc/q_rr.c b/tc/q_rr.c
new file mode 100644
index 0000000..843a4fa
--- /dev/null
+++ b/tc/q_rr.c
@@ -0,0 +1,119 @@
+/*
+ * q_rr.c RR.
+ *
+ * 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: PJ Waskiewicz, <peter.p.waskiewicz.jr@intel.com>
+ * Original Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> (from PRIO)
+ */
+
+#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 "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... rr bands NUMBER priomap P1 P2... [multiqueue]\n");
+}
+
+
+static int rr_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev)
+{
+ int pmap_mode = 0;
+ int idx = 0;
+ struct tc_prio_qopt opt = {3, { 1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 } };
+ struct rtattr *nest;
+ unsigned char mq = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "bands") == 0) {
+ if (pmap_mode)
+ explain();
+ NEXT_ARG();
+ if (get_integer(&opt.bands, *argv, 10)) {
+ fprintf(stderr, "Illegal \"bands\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "priomap") == 0) {
+ if (pmap_mode) {
+ fprintf(stderr, "Error: duplicate priomap\n");
+ return -1;
+ }
+ pmap_mode = 1;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else if (strcmp(*argv, "multiqueue") == 0) {
+ mq = 1;
+ } else {
+ unsigned int band;
+
+ if (!pmap_mode) {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ if (get_unsigned(&band, *argv, 10)) {
+ fprintf(stderr, "Illegal \"priomap\" element\n");
+ return -1;
+ }
+ if (band > opt.bands) {
+ fprintf(stderr, "\"priomap\" element is out of bands\n");
+ return -1;
+ }
+ if (idx > TC_PRIO_MAX) {
+ fprintf(stderr, "\"priomap\" index > TC_RR_MAX=%u\n", TC_PRIO_MAX);
+ return -1;
+ }
+ opt.priomap[idx++] = band;
+ }
+ argc--; argv++;
+ }
+
+ nest = addattr_nest_compat(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+ if (mq)
+ addattr_l(n, 1024, TCA_PRIO_MQ, NULL, 0);
+ addattr_nest_compat_end(n, nest);
+ return 0;
+}
+
+static int rr_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ int i;
+ struct tc_prio_qopt *qopt;
+ struct rtattr *tb[TCA_PRIO_MAX + 1];
+
+ if (opt == NULL)
+ return 0;
+
+ if (parse_rtattr_nested_compat(tb, TCA_PRIO_MAX, opt, qopt,
+ sizeof(*qopt)))
+ return -1;
+
+ fprintf(f, "bands %u priomap ", qopt->bands);
+ for (i = 0; i <= TC_PRIO_MAX; i++)
+ fprintf(f, " %d", qopt->priomap[i]);
+
+ if (tb[TCA_PRIO_MQ])
+ fprintf(f, " multiqueue: %s ",
+ rta_getattr_u8(tb[TCA_PRIO_MQ]) ? "on" : "off");
+
+ return 0;
+}
+
+struct qdisc_util rr_qdisc_util = {
+ .id = "rr",
+ .parse_qopt = rr_parse_opt,
+ .print_qopt = rr_print_opt,
+};
diff --git a/tc/q_sfb.c b/tc/q_sfb.c
new file mode 100644
index 0000000..7f48c6e
--- /dev/null
+++ b/tc/q_sfb.c
@@ -0,0 +1,196 @@
+/*
+ * q_sfb.c Stochastic Fair Blue.
+ *
+ * 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: Juliusz Chroboczek <jch@pps.jussieu.fr>
+ *
+ */
+
+
+
+#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 "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... sfb [ rehash SECS ] [ db SECS ]\n"
+ " [ limit PACKETS ] [ max PACKETS ] [ target PACKETS ]\n"
+ " [ increment FLOAT ] [ decrement FLOAT ]\n"
+ " [ penalty_rate PPS ] [ penalty_burst PACKETS ]\n");
+}
+
+static int get_prob(__u32 *val, const char *arg)
+{
+ double d;
+ char *ptr;
+
+ if (!arg || !*arg)
+ return -1;
+ d = strtod(arg, &ptr);
+ if (!ptr || ptr == arg || d < 0.0 || d > 1.0)
+ return -1;
+ *val = (__u32)(d * SFB_MAX_PROB + 0.5);
+ return 0;
+}
+
+static int sfb_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ struct tc_sfb_qopt opt = {
+ .rehash_interval = 600*1000,
+ .warmup_time = 60*1000,
+ .penalty_rate = 10,
+ .penalty_burst = 20,
+ .increment = (SFB_MAX_PROB + 1000) / 2000,
+ .decrement = (SFB_MAX_PROB + 10000) / 20000,
+ };
+ struct rtattr *tail;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "rehash") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.rehash_interval, *argv, 0)) {
+ fprintf(stderr, "Illegal \"rehash\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "db") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.warmup_time, *argv, 0)) {
+ fprintf(stderr, "Illegal \"db\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.limit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "max") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.max, *argv, 0)) {
+ fprintf(stderr, "Illegal \"max\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "target") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.bin_size, *argv, 0)) {
+ fprintf(stderr, "Illegal \"target\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "increment") == 0) {
+ NEXT_ARG();
+ if (get_prob(&opt.increment, *argv)) {
+ fprintf(stderr, "Illegal \"increment\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "decrement") == 0) {
+ NEXT_ARG();
+ if (get_prob(&opt.decrement, *argv)) {
+ fprintf(stderr, "Illegal \"decrement\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "penalty_rate") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.penalty_rate, *argv, 0)) {
+ fprintf(stderr, "Illegal \"penalty_rate\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "penalty_burst") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.penalty_burst, *argv, 0)) {
+ fprintf(stderr, "Illegal \"penalty_burst\"\n");
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (opt.max == 0) {
+ if (opt.bin_size >= 1)
+ opt.max = (opt.bin_size * 5 + 1) / 4;
+ else
+ opt.max = 25;
+ }
+ if (opt.bin_size == 0)
+ opt.bin_size = (opt.max * 4 + 3) / 5;
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 1024, TCA_SFB_PARMS, &opt, sizeof(opt));
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int sfb_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[__TCA_SFB_MAX];
+ struct tc_sfb_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_SFB_MAX, opt);
+ if (tb[TCA_SFB_PARMS] == NULL)
+ return -1;
+ qopt = RTA_DATA(tb[TCA_SFB_PARMS]);
+ if (RTA_PAYLOAD(tb[TCA_SFB_PARMS]) < sizeof(*qopt))
+ return -1;
+
+ fprintf(f,
+ "limit %d max %d target %d\n"
+ " increment %.5f decrement %.5f penalty rate %d burst %d (%ums %ums)",
+ qopt->limit, qopt->max, qopt->bin_size,
+ (double)qopt->increment / SFB_MAX_PROB,
+ (double)qopt->decrement / SFB_MAX_PROB,
+ qopt->penalty_rate, qopt->penalty_burst,
+ qopt->rehash_interval, qopt->warmup_time);
+
+ return 0;
+}
+
+static int sfb_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct tc_sfb_xstats *st;
+
+ if (xstats == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+
+ st = RTA_DATA(xstats);
+ fprintf(f,
+ " earlydrop %u penaltydrop %u bucketdrop %u queuedrop %u childdrop %u marked %u\n"
+ " maxqlen %u maxprob %.5f avgprob %.5f ",
+ st->earlydrop, st->penaltydrop, st->bucketdrop, st->queuedrop, st->childdrop,
+ st->marked,
+ st->maxqlen, (double)st->maxprob / SFB_MAX_PROB,
+ (double)st->avgprob / SFB_MAX_PROB);
+
+ return 0;
+}
+
+struct qdisc_util sfb_qdisc_util = {
+ .id = "sfb",
+ .parse_qopt = sfb_parse_opt,
+ .print_qopt = sfb_print_opt,
+ .print_xstats = sfb_print_xstats,
+};
diff --git a/tc/q_sfq.c b/tc/q_sfq.c
new file mode 100644
index 0000000..4998921
--- /dev/null
+++ b/tc/q_sfq.c
@@ -0,0 +1,275 @@
+/*
+ * q_sfq.c SFQ.
+ *
+ * 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 <math.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_red.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... sfq [ limit NUMBER ] [ perturb SECS ] [ quantum BYTES ]\n"
+ " [ divisor NUMBER ] [ flows NUMBER] [ depth NUMBER ]\n"
+ " [ headdrop ]\n"
+ " [ redflowlimit BYTES ] [ min BYTES ] [ max BYTES ]\n"
+ " [ avpkt BYTES ] [ burst PACKETS ] [ probability P ]\n"
+ " [ ecn ] [ harddrop ]\n");
+}
+
+static int sfq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev)
+{
+ int ok = 0, red = 0;
+ struct tc_sfq_qopt_v1 opt = {};
+ unsigned int burst = 0;
+ int wlog;
+ unsigned int avpkt = 1000;
+ double probability = 0.02;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "quantum") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.v0.quantum, *argv)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "perturb") == 0) {
+ NEXT_ARG();
+ if (get_integer(&opt.v0.perturb_period, *argv, 0)) {
+ fprintf(stderr, "Illegal \"perturb\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.v0.limit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"limit\"\n");
+ return -1;
+ }
+ if (opt.v0.limit < 2) {
+ fprintf(stderr, "Illegal \"limit\", must be > 1\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "divisor") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.v0.divisor, *argv, 0)) {
+ fprintf(stderr, "Illegal \"divisor\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "flows") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.v0.flows, *argv, 0)) {
+ fprintf(stderr, "Illegal \"flows\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "depth") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.depth, *argv, 0)) {
+ fprintf(stderr, "Illegal \"flows\"\n");
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "headdrop") == 0) {
+ opt.headdrop = 1;
+ ok++;
+ } else if (strcmp(*argv, "redflowlimit") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.limit, *argv, 0)) {
+ fprintf(stderr, "Illegal \"redflowlimit\"\n");
+ return -1;
+ }
+ red++;
+ } else if (strcmp(*argv, "min") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.qth_min, *argv, 0)) {
+ fprintf(stderr, "Illegal \"min\"\n");
+ return -1;
+ }
+ red++;
+ } else if (strcmp(*argv, "max") == 0) {
+ NEXT_ARG();
+ if (get_u32(&opt.qth_max, *argv, 0)) {
+ fprintf(stderr, "Illegal \"max\"\n");
+ return -1;
+ }
+ red++;
+ } else if (strcmp(*argv, "burst") == 0) {
+ NEXT_ARG();
+ if (get_unsigned(&burst, *argv, 0)) {
+ fprintf(stderr, "Illegal \"burst\"\n");
+ return -1;
+ }
+ red++;
+ } else if (strcmp(*argv, "avpkt") == 0) {
+ NEXT_ARG();
+ if (get_size(&avpkt, *argv)) {
+ fprintf(stderr, "Illegal \"avpkt\"\n");
+ return -1;
+ }
+ red++;
+ } else if (strcmp(*argv, "probability") == 0) {
+ NEXT_ARG();
+ if (sscanf(*argv, "%lg", &probability) != 1) {
+ fprintf(stderr, "Illegal \"probability\"\n");
+ return -1;
+ }
+ red++;
+ } else if (strcmp(*argv, "ecn") == 0) {
+ opt.flags |= TC_RED_ECN;
+ red++;
+ } else if (strcmp(*argv, "harddrop") == 0) {
+ opt.flags |= TC_RED_HARDDROP;
+ red++;
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+ if (red) {
+ if (!opt.limit) {
+ fprintf(stderr, "Required parameter (redflowlimit) is missing\n");
+ return -1;
+ }
+ /* Compute default min/max thresholds based on
+ Sally Floyd's recommendations:
+ http://www.icir.org/floyd/REDparameters.txt
+ */
+ if (!opt.qth_max)
+ opt.qth_max = opt.limit / 4;
+ if (!opt.qth_min)
+ opt.qth_min = opt.qth_max / 3;
+ if (!burst)
+ burst = (2 * opt.qth_min + opt.qth_max) / (3 * avpkt);
+
+ if (opt.qth_max > opt.limit) {
+ fprintf(stderr, "\"max\" is larger than \"limit\"\n");
+ return -1;
+ }
+
+ if (opt.qth_min >= opt.qth_max) {
+ fprintf(stderr, "\"min\" is not smaller than \"max\"\n");
+ return -1;
+ }
+
+ wlog = tc_red_eval_ewma(opt.qth_min, burst, avpkt);
+ if (wlog < 0) {
+ fprintf(stderr, "SFQ: failed to calculate EWMA constant.\n");
+ return -1;
+ }
+ if (wlog >= 10)
+ fprintf(stderr, "SFQ: WARNING. Burst %u seems to be too large.\n", burst);
+ opt.Wlog = wlog;
+
+ wlog = tc_red_eval_P(opt.qth_min, opt.qth_max, probability);
+ if (wlog < 0) {
+ fprintf(stderr, "SFQ: failed to calculate probability.\n");
+ return -1;
+ }
+ opt.Plog = wlog;
+ opt.max_P = probability * pow(2, 32);
+ }
+
+ if (ok || red)
+ addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+ return 0;
+}
+
+static int sfq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct tc_sfq_qopt *qopt;
+ struct tc_sfq_qopt_v1 *qopt_ext = NULL;
+
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+ SPRINT_BUF(b3);
+ if (opt == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(opt) < sizeof(*qopt))
+ return -1;
+ if (RTA_PAYLOAD(opt) >= sizeof(*qopt_ext))
+ qopt_ext = RTA_DATA(opt);
+ qopt = RTA_DATA(opt);
+ fprintf(f, "limit %up ", qopt->limit);
+ fprintf(f, "quantum %s ", sprint_size(qopt->quantum, b1));
+ if (qopt_ext && qopt_ext->depth)
+ fprintf(f, "depth %u ", qopt_ext->depth);
+ if (qopt_ext && qopt_ext->headdrop)
+ fprintf(f, "headdrop ");
+
+ if (show_details) {
+ fprintf(f, "flows %u/%u ", qopt->flows, qopt->divisor);
+ }
+ fprintf(f, "divisor %u ", qopt->divisor);
+ if (qopt->perturb_period)
+ fprintf(f, "perturb %dsec ", qopt->perturb_period);
+ if (qopt_ext && qopt_ext->qth_min) {
+ fprintf(f, "\n ewma %u ", qopt_ext->Wlog);
+ fprintf(f, "min %s max %s probability %g ",
+ sprint_size(qopt_ext->qth_min, b2),
+ sprint_size(qopt_ext->qth_max, b3),
+ qopt_ext->max_P / pow(2, 32));
+ tc_red_print_flags(qopt_ext->flags);
+ if (show_stats) {
+ fprintf(f, "\n prob_mark %u prob_mark_head %u prob_drop %u",
+ qopt_ext->stats.prob_mark,
+ qopt_ext->stats.prob_mark_head,
+ qopt_ext->stats.prob_drop);
+ fprintf(f, "\n forced_mark %u forced_mark_head %u forced_drop %u",
+ qopt_ext->stats.forced_mark,
+ qopt_ext->stats.forced_mark_head,
+ qopt_ext->stats.forced_drop);
+ }
+ }
+ return 0;
+}
+
+static int sfq_print_xstats(struct qdisc_util *qu, FILE *f,
+ struct rtattr *xstats)
+{
+ struct tc_sfq_xstats *st;
+
+ if (xstats == NULL)
+ return 0;
+ if (RTA_PAYLOAD(xstats) < sizeof(*st))
+ return -1;
+ st = RTA_DATA(xstats);
+
+ fprintf(f, " allot %d ", st->allot);
+ fprintf(f, "\n");
+ return 0;
+}
+
+struct qdisc_util sfq_qdisc_util = {
+ .id = "sfq",
+ .parse_qopt = sfq_parse_opt,
+ .print_qopt = sfq_print_opt,
+ .print_xstats = sfq_print_xstats,
+};
diff --git a/tc/q_skbprio.c b/tc/q_skbprio.c
new file mode 100644
index 0000000..2e65a58
--- /dev/null
+++ b/tc/q_skbprio.c
@@ -0,0 +1,84 @@
+/*
+ * q_skbprio.c SKB PRIORITY QUEUE.
+ *
+ * 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: Nishanth Devarajan, <ndev2021@gmail.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr, "Usage: ... <skbprio> [ limit NUMBER ]\n");
+}
+
+static int skbprio_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ int ok = 0;
+ struct tc_skbprio_qopt opt = {};
+
+ while (argc > 0) {
+ if (strcmp(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (get_size(&opt.limit, *argv)) {
+ fprintf(stderr,
+ "%s: Illegal \"limit\" value:\"%s\"\n",
+ qu->id, *argv);
+ return -1;
+ }
+ ok++;
+ }
+ else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr,
+ "%s: unknown parameter \"%s\"\n",
+ qu->id, *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (ok)
+ addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt));
+ return 0;
+}
+
+static int skbprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct tc_skbprio_qopt *qopt;
+
+ if (opt == NULL)
+ return 0;
+
+ if (RTA_PAYLOAD(opt) < sizeof(*qopt))
+ return -1;
+ qopt = RTA_DATA(opt);
+ fprintf(f, "limit %u ", qopt->limit);
+ return 0;
+}
+
+struct qdisc_util skbprio_qdisc_util = {
+ .id = "skbprio",
+ .parse_qopt = skbprio_parse_opt,
+ .print_qopt = skbprio_print_opt,
+};
diff --git a/tc/q_taprio.c b/tc/q_taprio.c
new file mode 100644
index 0000000..b995443
--- /dev/null
+++ b/tc/q_taprio.c
@@ -0,0 +1,511 @@
+/*
+ * q_taprio.c Time Aware Priority Scheduler
+ *
+ * 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: Vinicius Costa Gomes <vinicius.gomes@intel.com>
+ * Jesus Sanchez-Palencia <jesus.sanchez-palencia@intel.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "list.h"
+
+struct sched_entry {
+ struct list_head list;
+ uint32_t index;
+ uint32_t interval;
+ uint32_t gatemask;
+ uint8_t cmd;
+};
+
+#define CLOCKID_INVALID (-1)
+static const struct static_clockid {
+ const char *name;
+ clockid_t clockid;
+} clockids_sysv[] = {
+ { "REALTIME", CLOCK_REALTIME },
+ { "TAI", CLOCK_TAI },
+ { "BOOTTIME", CLOCK_BOOTTIME },
+ { "MONOTONIC", CLOCK_MONOTONIC },
+ { NULL }
+};
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... taprio clockid CLOCKID\n"
+ " [num_tc NUMBER] [map P0 P1 ...] "
+ " [queues COUNT@OFFSET COUNT@OFFSET COUNT@OFFSET ...] "
+ " [ [sched-entry index cmd gate-mask interval] ... ] "
+ " [base-time time] [txtime-delay delay]"
+ "\n"
+ "CLOCKID must be a valid SYS-V id (i.e. CLOCK_TAI)\n");
+}
+
+static void explain_clockid(const char *val)
+{
+ fprintf(stderr, "taprio: illegal value for \"clockid\": \"%s\".\n", val);
+ fprintf(stderr, "It must be a valid SYS-V id (i.e. CLOCK_TAI)\n");
+}
+
+static int get_clockid(__s32 *val, const char *arg)
+{
+ const struct static_clockid *c;
+
+ /* Drop the CLOCK_ prefix if that is being used. */
+ if (strcasestr(arg, "CLOCK_") != NULL)
+ arg += sizeof("CLOCK_") - 1;
+
+ for (c = clockids_sysv; c->name; c++) {
+ if (strcasecmp(c->name, arg) == 0) {
+ *val = c->clockid;
+
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static const char* get_clock_name(clockid_t clockid)
+{
+ const struct static_clockid *c;
+
+ for (c = clockids_sysv; c->name; c++) {
+ if (clockid == c->clockid)
+ return c->name;
+ }
+
+ return "invalid";
+}
+
+static const char *entry_cmd_to_str(__u8 cmd)
+{
+ switch (cmd) {
+ case TC_TAPRIO_CMD_SET_GATES:
+ return "S";
+ default:
+ return "Invalid";
+ }
+}
+
+static int str_to_entry_cmd(const char *str)
+{
+ if (strcmp(str, "S") == 0)
+ return TC_TAPRIO_CMD_SET_GATES;
+
+ return -1;
+}
+
+static int add_sched_list(struct list_head *sched_entries, struct nlmsghdr *n)
+{
+ struct sched_entry *e;
+
+ list_for_each_entry(e, sched_entries, list) {
+ struct rtattr *a;
+
+ a = addattr_nest(n, 1024, TCA_TAPRIO_SCHED_ENTRY);
+
+ addattr_l(n, 1024, TCA_TAPRIO_SCHED_ENTRY_CMD, &e->cmd, sizeof(e->cmd));
+ addattr_l(n, 1024, TCA_TAPRIO_SCHED_ENTRY_GATE_MASK, &e->gatemask, sizeof(e->gatemask));
+ addattr_l(n, 1024, TCA_TAPRIO_SCHED_ENTRY_INTERVAL, &e->interval, sizeof(e->interval));
+
+ addattr_nest_end(n, a);
+ }
+
+ return 0;
+}
+
+static void explain_sched_entry(void)
+{
+ fprintf(stderr, "Usage: ... taprio ... sched-entry <cmd> <gate mask> <interval>\n");
+}
+
+static struct sched_entry *create_entry(uint32_t gatemask, uint32_t interval, uint8_t cmd)
+{
+ struct sched_entry *e;
+
+ e = calloc(1, sizeof(*e));
+ if (!e)
+ return NULL;
+
+ e->gatemask = gatemask;
+ e->interval = interval;
+ e->cmd = cmd;
+
+ return e;
+}
+
+static int taprio_parse_opt(struct qdisc_util *qu, int argc,
+ char **argv, struct nlmsghdr *n, const char *dev)
+{
+ __s32 clockid = CLOCKID_INVALID;
+ struct tc_mqprio_qopt opt = { };
+ __s64 cycle_time_extension = 0;
+ struct list_head sched_entries;
+ struct rtattr *tail, *l;
+ __u32 taprio_flags = 0;
+ __u32 txtime_delay = 0;
+ __s64 cycle_time = 0;
+ __s64 base_time = 0;
+ int err, idx;
+
+ INIT_LIST_HEAD(&sched_entries);
+
+ while (argc > 0) {
+ idx = 0;
+ if (strcmp(*argv, "num_tc") == 0) {
+ NEXT_ARG();
+ if (get_u8(&opt.num_tc, *argv, 10)) {
+ fprintf(stderr, "Illegal \"num_tc\"\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "map") == 0) {
+ while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
+ NEXT_ARG();
+ if (get_u8(&opt.prio_tc_map[idx], *argv, 10)) {
+ PREV_ARG();
+ break;
+ }
+ idx++;
+ }
+ for ( ; idx < TC_QOPT_MAX_QUEUE; idx++)
+ opt.prio_tc_map[idx] = 0;
+ } else if (strcmp(*argv, "queues") == 0) {
+ char *tmp, *tok;
+
+ while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) {
+ NEXT_ARG();
+
+ tmp = strdup(*argv);
+ if (!tmp)
+ break;
+
+ tok = strtok(tmp, "@");
+ if (get_u16(&opt.count[idx], tok, 10)) {
+ free(tmp);
+ PREV_ARG();
+ break;
+ }
+ tok = strtok(NULL, "@");
+ if (get_u16(&opt.offset[idx], tok, 10)) {
+ free(tmp);
+ PREV_ARG();
+ break;
+ }
+ free(tmp);
+ idx++;
+ }
+ } else if (strcmp(*argv, "sched-entry") == 0) {
+ uint32_t mask, interval;
+ struct sched_entry *e;
+ uint8_t cmd;
+
+ NEXT_ARG();
+ err = str_to_entry_cmd(*argv);
+ if (err < 0) {
+ explain_sched_entry();
+ return -1;
+ }
+ cmd = err;
+
+ NEXT_ARG();
+ if (get_u32(&mask, *argv, 16)) {
+ explain_sched_entry();
+ return -1;
+ }
+
+ NEXT_ARG();
+ if (get_u32(&interval, *argv, 0)) {
+ explain_sched_entry();
+ return -1;
+ }
+
+ e = create_entry(mask, interval, cmd);
+ if (!e) {
+ fprintf(stderr, "taprio: not enough memory for new schedule entry\n");
+ return -1;
+ }
+
+ list_add_tail(&e->list, &sched_entries);
+
+ } else if (strcmp(*argv, "base-time") == 0) {
+ NEXT_ARG();
+ if (get_s64(&base_time, *argv, 10)) {
+ PREV_ARG();
+ break;
+ }
+ } else if (strcmp(*argv, "cycle-time") == 0) {
+ NEXT_ARG();
+ if (cycle_time) {
+ fprintf(stderr, "taprio: duplicate \"cycle-time\" specification\n");
+ return -1;
+ }
+
+ if (get_s64(&cycle_time, *argv, 10)) {
+ PREV_ARG();
+ break;
+ }
+
+ } else if (strcmp(*argv, "cycle-time-extension") == 0) {
+ NEXT_ARG();
+ if (cycle_time_extension) {
+ fprintf(stderr, "taprio: duplicate \"cycle-time-extension\" specification\n");
+ return -1;
+ }
+
+ if (get_s64(&cycle_time_extension, *argv, 10)) {
+ PREV_ARG();
+ break;
+ }
+ } else if (strcmp(*argv, "clockid") == 0) {
+ NEXT_ARG();
+ if (clockid != CLOCKID_INVALID) {
+ fprintf(stderr, "taprio: duplicate \"clockid\" specification\n");
+ return -1;
+ }
+ if (get_clockid(&clockid, *argv)) {
+ explain_clockid(*argv);
+ return -1;
+ }
+ } else if (strcmp(*argv, "flags") == 0) {
+ NEXT_ARG();
+ if (taprio_flags) {
+ fprintf(stderr, "taprio: duplicate \"flags\" specification\n");
+ return -1;
+ }
+ if (get_u32(&taprio_flags, *argv, 0)) {
+ PREV_ARG();
+ return -1;
+ }
+
+ } else if (strcmp(*argv, "txtime-delay") == 0) {
+ NEXT_ARG();
+ if (txtime_delay != 0) {
+ fprintf(stderr, "taprio: duplicate \"txtime-delay\" specification\n");
+ return -1;
+ }
+ if (get_u32(&txtime_delay, *argv, 0)) {
+ PREV_ARG();
+ return -1;
+ }
+
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "Unknown argument\n");
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+
+ if (clockid != CLOCKID_INVALID)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CLOCKID, &clockid, sizeof(clockid));
+
+ if (taprio_flags)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_FLAGS, &taprio_flags, sizeof(taprio_flags));
+
+ if (opt.num_tc > 0)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_PRIOMAP, &opt, sizeof(opt));
+
+ if (txtime_delay)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_TXTIME_DELAY, &txtime_delay, sizeof(txtime_delay));
+
+ if (base_time)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_BASE_TIME, &base_time, sizeof(base_time));
+
+ if (cycle_time)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME,
+ &cycle_time, sizeof(cycle_time));
+
+ if (cycle_time_extension)
+ addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION,
+ &cycle_time_extension, sizeof(cycle_time_extension));
+
+ l = addattr_nest(n, 1024, TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST | NLA_F_NESTED);
+
+ err = add_sched_list(&sched_entries, n);
+ if (err < 0) {
+ fprintf(stderr, "Could not add schedule to netlink message\n");
+ return -1;
+ }
+
+ addattr_nest_end(n, l);
+
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+
+ return 0;
+}
+
+static int print_sched_list(FILE *f, struct rtattr *list)
+{
+ struct rtattr *item;
+ int rem;
+
+ if (list == NULL)
+ return 0;
+
+ rem = RTA_PAYLOAD(list);
+
+ open_json_array(PRINT_JSON, "schedule");
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+
+ for (item = RTA_DATA(list); RTA_OK(item, rem); item = RTA_NEXT(item, rem)) {
+ struct rtattr *tb[TCA_TAPRIO_SCHED_ENTRY_MAX + 1];
+ __u32 index = 0, gatemask = 0, interval = 0;
+ __u8 command = 0;
+
+ parse_rtattr_nested(tb, TCA_TAPRIO_SCHED_ENTRY_MAX, item);
+
+ if (tb[TCA_TAPRIO_SCHED_ENTRY_INDEX])
+ index = rta_getattr_u32(tb[TCA_TAPRIO_SCHED_ENTRY_INDEX]);
+
+ if (tb[TCA_TAPRIO_SCHED_ENTRY_CMD])
+ command = rta_getattr_u8(tb[TCA_TAPRIO_SCHED_ENTRY_CMD]);
+
+ if (tb[TCA_TAPRIO_SCHED_ENTRY_GATE_MASK])
+ gatemask = rta_getattr_u32(tb[TCA_TAPRIO_SCHED_ENTRY_GATE_MASK]);
+
+ if (tb[TCA_TAPRIO_SCHED_ENTRY_INTERVAL])
+ interval = rta_getattr_u32(tb[TCA_TAPRIO_SCHED_ENTRY_INTERVAL]);
+
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "index", "\tindex %u", index);
+ print_string(PRINT_ANY, "cmd", " cmd %s", entry_cmd_to_str(command));
+ print_0xhex(PRINT_ANY, "gatemask", " gatemask %#llx", gatemask);
+ print_uint(PRINT_ANY, "interval", " interval %u", interval);
+ close_json_object();
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ }
+
+ close_json_array(PRINT_ANY, "");
+
+ return 0;
+}
+
+static int print_schedule(FILE *f, struct rtattr **tb)
+{
+ int64_t base_time = 0, cycle_time = 0, cycle_time_extension = 0;
+
+ if (tb[TCA_TAPRIO_ATTR_SCHED_BASE_TIME])
+ base_time = rta_getattr_s64(tb[TCA_TAPRIO_ATTR_SCHED_BASE_TIME]);
+
+ if (tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME])
+ cycle_time = rta_getattr_s64(tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME]);
+
+ if (tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION])
+ cycle_time_extension = rta_getattr_s64(
+ tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION]);
+
+ print_lluint(PRINT_ANY, "base_time", "\tbase-time %lld", base_time);
+
+ print_lluint(PRINT_ANY, "cycle_time", " cycle-time %lld", cycle_time);
+
+ print_lluint(PRINT_ANY, "cycle_time_extension",
+ " cycle-time-extension %lld", cycle_time_extension);
+
+ print_sched_list(f, tb[TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST]);
+
+ return 0;
+}
+
+static int taprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_TAPRIO_ATTR_MAX + 1];
+ struct tc_mqprio_qopt *qopt = 0;
+ __s32 clockid = CLOCKID_INVALID;
+ int i;
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_TAPRIO_ATTR_MAX, opt);
+
+ if (tb[TCA_TAPRIO_ATTR_PRIOMAP] == NULL)
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_TAPRIO_ATTR_PRIOMAP]);
+
+ print_uint(PRINT_ANY, "tc", "tc %u ", qopt->num_tc);
+
+ open_json_array(PRINT_ANY, "map");
+ for (i = 0; i <= TC_PRIO_MAX; i++)
+ print_uint(PRINT_ANY, NULL, " %u", qopt->prio_tc_map[i]);
+ close_json_array(PRINT_ANY, "");
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+
+ open_json_array(PRINT_ANY, "queues");
+ for (i = 0; i < qopt->num_tc; i++) {
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "offset", " offset %u", qopt->offset[i]);
+ print_uint(PRINT_ANY, "count", " count %u", qopt->count[i]);
+ close_json_object();
+ }
+ close_json_array(PRINT_ANY, "");
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+
+ if (tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID])
+ clockid = rta_getattr_s32(tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]);
+
+ print_string(PRINT_ANY, "clockid", "clockid %s", get_clock_name(clockid));
+
+ if (tb[TCA_TAPRIO_ATTR_FLAGS]) {
+ __u32 flags;
+
+ flags = rta_getattr_u32(tb[TCA_TAPRIO_ATTR_FLAGS]);
+ print_0xhex(PRINT_ANY, "flags", " flags %#x", flags);
+ }
+
+ if (tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]) {
+ __u32 txtime_delay;
+
+ txtime_delay = rta_getattr_s32(tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]);
+ print_uint(PRINT_ANY, "txtime_delay", " txtime delay %d", txtime_delay);
+ }
+
+ print_schedule(f, tb);
+
+ if (tb[TCA_TAPRIO_ATTR_ADMIN_SCHED]) {
+ struct rtattr *t[TCA_TAPRIO_ATTR_MAX + 1];
+
+ parse_rtattr_nested(t, TCA_TAPRIO_ATTR_MAX,
+ tb[TCA_TAPRIO_ATTR_ADMIN_SCHED]);
+
+ open_json_object(NULL);
+
+ print_schedule(f, t);
+
+ close_json_object();
+ }
+
+ return 0;
+}
+
+struct qdisc_util taprio_qdisc_util = {
+ .id = "taprio",
+ .parse_qopt = taprio_parse_opt,
+ .print_qopt = taprio_print_opt,
+};
diff --git a/tc/q_tbf.c b/tc/q_tbf.c
new file mode 100644
index 0000000..57a9736
--- /dev/null
+++ b/tc/q_tbf.c
@@ -0,0 +1,344 @@
+/*
+ * q_tbf.c TBF.
+ *
+ * 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 "utils.h"
+#include "tc_util.h"
+
+static void explain(void)
+{
+ fprintf(stderr,
+ "Usage: ... tbf limit BYTES burst BYTES[/BYTES] rate KBPS [ mtu BYTES[/BYTES] ]\n"
+ " [ peakrate KBPS ] [ latency TIME ] "
+ "[ overhead BYTES ] [ linklayer TYPE ]\n");
+}
+
+static void explain1(const char *arg, const char *val)
+{
+ fprintf(stderr, "tbf: illegal value for \"%s\": \"%s\"\n", arg, val);
+}
+
+
+static int tbf_parse_opt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ int ok = 0;
+ struct tc_tbf_qopt opt = {};
+ __u32 rtab[256];
+ __u32 ptab[256];
+ unsigned buffer = 0, mtu = 0, mpu = 0, latency = 0;
+ int Rcell_log = -1, Pcell_log = -1;
+ unsigned short overhead = 0;
+ unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+ struct rtattr *tail;
+ __u64 rate64 = 0, prate64 = 0;
+
+ while (argc > 0) {
+ if (matches(*argv, "limit") == 0) {
+ NEXT_ARG();
+ if (opt.limit) {
+ fprintf(stderr, "tbf: duplicate \"limit\" specification\n");
+ return -1;
+ }
+ if (latency) {
+ fprintf(stderr, "tbf: specifying both \"latency\" and \"limit\" is not allowed\n");
+ return -1;
+ }
+ if (get_size(&opt.limit, *argv)) {
+ explain1("limit", *argv);
+ return -1;
+ }
+ ok++;
+ } else if (matches(*argv, "latency") == 0) {
+ NEXT_ARG();
+ if (latency) {
+ fprintf(stderr, "tbf: duplicate \"latency\" specification\n");
+ return -1;
+ }
+ if (opt.limit) {
+ fprintf(stderr, "tbf: specifying both \"limit\" and \"/latency\" is not allowed\n");
+ return -1;
+ }
+ if (get_time(&latency, *argv)) {
+ explain1("latency", *argv);
+ return -1;
+ }
+ ok++;
+ } else if (matches(*argv, "burst") == 0 ||
+ strcmp(*argv, "buffer") == 0 ||
+ strcmp(*argv, "maxburst") == 0) {
+ const char *parm_name = *argv;
+
+ NEXT_ARG();
+ if (buffer) {
+ fprintf(stderr, "tbf: duplicate \"buffer/burst/maxburst\" specification\n");
+ return -1;
+ }
+ if (get_size_and_cell(&buffer, &Rcell_log, *argv) < 0) {
+ explain1(parm_name, *argv);
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "mtu") == 0 ||
+ strcmp(*argv, "minburst") == 0) {
+ const char *parm_name = *argv;
+
+ NEXT_ARG();
+ if (mtu) {
+ fprintf(stderr, "tbf: duplicate \"mtu/minburst\" specification\n");
+ return -1;
+ }
+ if (get_size_and_cell(&mtu, &Pcell_log, *argv) < 0) {
+ explain1(parm_name, *argv);
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "mpu") == 0) {
+ NEXT_ARG();
+ if (mpu) {
+ fprintf(stderr, "tbf: duplicate \"mpu\" specification\n");
+ return -1;
+ }
+ if (get_size(&mpu, *argv)) {
+ explain1("mpu", *argv);
+ return -1;
+ }
+ ok++;
+ } else if (strcmp(*argv, "rate") == 0) {
+ NEXT_ARG();
+ if (rate64) {
+ fprintf(stderr, "tbf: duplicate \"rate\" specification\n");
+ return -1;
+ }
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate64(&rate64, *argv, dev)) {
+ explain1("rate", *argv);
+ return -1;
+ }
+ } else if (get_rate64(&rate64, *argv)) {
+ explain1("rate", *argv);
+ return -1;
+ }
+ ok++;
+ } else if (matches(*argv, "peakrate") == 0) {
+ NEXT_ARG();
+ if (prate64) {
+ fprintf(stderr, "tbf: duplicate \"peakrate\" specification\n");
+ return -1;
+ }
+ if (strchr(*argv, '%')) {
+ if (get_percent_rate64(&prate64, *argv, dev)) {
+ explain1("peakrate", *argv);
+ return -1;
+ }
+ } else if (get_rate64(&prate64, *argv)) {
+ explain1("peakrate", *argv);
+ return -1;
+ }
+ ok++;
+ } else if (matches(*argv, "overhead") == 0) {
+ NEXT_ARG();
+ if (overhead) {
+ fprintf(stderr, "tbf: duplicate \"overhead\" specification\n");
+ return -1;
+ }
+ if (get_u16(&overhead, *argv, 10)) {
+ explain1("overhead", *argv); return -1;
+ }
+ } else if (matches(*argv, "linklayer") == 0) {
+ NEXT_ARG();
+ if (get_linklayer(&linklayer, *argv)) {
+ explain1("linklayer", *argv); return -1;
+ }
+ } else if (strcmp(*argv, "help") == 0) {
+ explain();
+ return -1;
+ } else {
+ fprintf(stderr, "tbf: unknown parameter \"%s\"\n", *argv);
+ explain();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ int verdict = 0;
+
+ /* Be nice to the user: try to emit all error messages in
+ * one go rather than reveal one more problem when a
+ * previous one has been fixed.
+ */
+ if (rate64 == 0) {
+ fprintf(stderr, "tbf: the \"rate\" parameter is mandatory.\n");
+ verdict = -1;
+ }
+ if (!buffer) {
+ fprintf(stderr, "tbf: the \"burst\" parameter is mandatory.\n");
+ verdict = -1;
+ }
+ if (prate64) {
+ if (!mtu) {
+ fprintf(stderr, "tbf: when \"peakrate\" is specified, \"mtu\" must also be specified.\n");
+ verdict = -1;
+ }
+ }
+
+ if (opt.limit == 0 && latency == 0) {
+ fprintf(stderr, "tbf: either \"limit\" or \"latency\" is required.\n");
+ verdict = -1;
+ }
+
+ if (verdict != 0) {
+ explain();
+ return verdict;
+ }
+
+ opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64;
+ opt.peakrate.rate = (prate64 >= (1ULL << 32)) ? ~0U : prate64;
+
+ if (opt.limit == 0) {
+ double lim = rate64*(double)latency/TIME_UNITS_PER_SEC + buffer;
+
+ if (prate64) {
+ double lim2 = prate64*(double)latency/TIME_UNITS_PER_SEC + mtu;
+
+ if (lim2 < lim)
+ lim = lim2;
+ }
+ opt.limit = lim;
+ }
+
+ opt.rate.mpu = mpu;
+ opt.rate.overhead = overhead;
+ if (tc_calc_rtable(&opt.rate, rtab, Rcell_log, mtu, linklayer) < 0) {
+ fprintf(stderr, "tbf: failed to calculate rate table.\n");
+ return -1;
+ }
+ opt.buffer = tc_calc_xmittime(opt.rate.rate, buffer);
+
+ if (opt.peakrate.rate) {
+ opt.peakrate.mpu = mpu;
+ opt.peakrate.overhead = overhead;
+ if (tc_calc_rtable(&opt.peakrate, ptab, Pcell_log, mtu, linklayer) < 0) {
+ fprintf(stderr, "tbf: failed to calculate peak rate table.\n");
+ return -1;
+ }
+ opt.mtu = tc_calc_xmittime(opt.peakrate.rate, mtu);
+ }
+
+ tail = addattr_nest(n, 1024, TCA_OPTIONS);
+ addattr_l(n, 2024, TCA_TBF_PARMS, &opt, sizeof(opt));
+ addattr_l(n, 2124, TCA_TBF_BURST, &buffer, sizeof(buffer));
+ if (rate64 >= (1ULL << 32))
+ addattr_l(n, 2124, TCA_TBF_RATE64, &rate64, sizeof(rate64));
+ addattr_l(n, 3024, TCA_TBF_RTAB, rtab, 1024);
+ if (opt.peakrate.rate) {
+ if (prate64 >= (1ULL << 32))
+ addattr_l(n, 3124, TCA_TBF_PRATE64, &prate64, sizeof(prate64));
+ addattr_l(n, 3224, TCA_TBF_PBURST, &mtu, sizeof(mtu));
+ addattr_l(n, 4096, TCA_TBF_PTAB, ptab, 1024);
+ }
+ addattr_nest_end(n, tail);
+ return 0;
+}
+
+static int tbf_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_TBF_MAX+1];
+ struct tc_tbf_qopt *qopt;
+ unsigned int linklayer;
+ double buffer, mtu;
+ double latency;
+ __u64 rate64 = 0, prate64 = 0;
+
+ SPRINT_BUF(b1);
+ SPRINT_BUF(b2);
+ SPRINT_BUF(b3);
+
+ if (opt == NULL)
+ return 0;
+
+ parse_rtattr_nested(tb, TCA_TBF_MAX, opt);
+
+ if (tb[TCA_TBF_PARMS] == NULL)
+ return -1;
+
+ qopt = RTA_DATA(tb[TCA_TBF_PARMS]);
+ if (RTA_PAYLOAD(tb[TCA_TBF_PARMS]) < sizeof(*qopt))
+ return -1;
+ rate64 = qopt->rate.rate;
+ if (tb[TCA_TBF_RATE64] &&
+ RTA_PAYLOAD(tb[TCA_TBF_RATE64]) >= sizeof(rate64))
+ rate64 = rta_getattr_u64(tb[TCA_TBF_RATE64]);
+ fprintf(f, "rate %s ", sprint_rate(rate64, b1));
+ buffer = tc_calc_xmitsize(rate64, qopt->buffer);
+ if (show_details) {
+ fprintf(f, "burst %s/%u mpu %s ", sprint_size(buffer, b1),
+ 1<<qopt->rate.cell_log, sprint_size(qopt->rate.mpu, b2));
+ } else {
+ fprintf(f, "burst %s ", sprint_size(buffer, b1));
+ }
+ if (show_raw)
+ fprintf(f, "[%08x] ", qopt->buffer);
+ prate64 = qopt->peakrate.rate;
+ if (tb[TCA_TBF_PRATE64] &&
+ RTA_PAYLOAD(tb[TCA_TBF_PRATE64]) >= sizeof(prate64))
+ prate64 = rta_getattr_u64(tb[TCA_TBF_PRATE64]);
+ if (prate64) {
+ fprintf(f, "peakrate %s ", sprint_rate(prate64, b1));
+ if (qopt->mtu || qopt->peakrate.mpu) {
+ mtu = tc_calc_xmitsize(prate64, qopt->mtu);
+ if (show_details) {
+ fprintf(f, "mtu %s/%u mpu %s ", sprint_size(mtu, b1),
+ 1<<qopt->peakrate.cell_log, sprint_size(qopt->peakrate.mpu, b2));
+ } else {
+ fprintf(f, "minburst %s ", sprint_size(mtu, b1));
+ }
+ if (show_raw)
+ fprintf(f, "[%08x] ", qopt->mtu);
+ }
+ }
+
+ latency = TIME_UNITS_PER_SEC*(qopt->limit/(double)rate64) - tc_core_tick2time(qopt->buffer);
+ if (prate64) {
+ double lat2 = TIME_UNITS_PER_SEC*(qopt->limit/(double)prate64) - tc_core_tick2time(qopt->mtu);
+
+ if (lat2 > latency)
+ latency = lat2;
+ }
+ if (latency >= 0.0)
+ fprintf(f, "lat %s ", sprint_time(latency, b1));
+ if (show_raw || latency < 0.0)
+ fprintf(f, "limit %s ", sprint_size(qopt->limit, b1));
+
+ if (qopt->rate.overhead) {
+ fprintf(f, "overhead %d", qopt->rate.overhead);
+ }
+ linklayer = (qopt->rate.linklayer & TC_LINKLAYER_MASK);
+ if (linklayer > TC_LINKLAYER_ETHERNET || show_details)
+ fprintf(f, "linklayer %s ", sprint_linklayer(linklayer, b3));
+
+ return 0;
+}
+
+struct qdisc_util tbf_qdisc_util = {
+ .id = "tbf",
+ .parse_qopt = tbf_parse_opt,
+ .print_qopt = tbf_print_opt,
+};
diff --git a/tc/static-syms.c b/tc/static-syms.c
new file mode 100644
index 0000000..47c4092
--- /dev/null
+++ b/tc/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/tc/tc.c b/tc/tc.c
new file mode 100644
index 0000000..37294b3
--- /dev/null
+++ b/tc/tc.c
@@ -0,0 +1,379 @@
+/*
+ * tc.c "tc" 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>
+ *
+ * Fixes:
+ *
+ * Petri Mattila <petri@prihateam.fi> 990308: wrong memset's resulted in faults
+ */
+
+#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 "SNAPSHOT.h"
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+#include "namespace.h"
+
+int show_stats;
+int show_details;
+int show_raw;
+int show_graph;
+int timestamp;
+
+int batch_mode;
+int use_iec;
+int force;
+bool use_names;
+int json;
+int color;
+int oneline;
+int numeric;
+
+static char *conf_file;
+
+struct rtnl_handle rth;
+
+static void *BODY; /* cached handle dlopen(NULL) */
+static struct qdisc_util *qdisc_list;
+static struct filter_util *filter_list;
+
+static int print_noqopt(struct qdisc_util *qu, FILE *f,
+ struct rtattr *opt)
+{
+ if (opt && RTA_PAYLOAD(opt))
+ fprintf(f, "[Unknown qdisc, optlen=%u] ",
+ (unsigned int) RTA_PAYLOAD(opt));
+ return 0;
+}
+
+static int parse_noqopt(struct qdisc_util *qu, int argc, char **argv,
+ struct nlmsghdr *n, const char *dev)
+{
+ if (argc) {
+ fprintf(stderr,
+ "Unknown qdisc \"%s\", hence option \"%s\" is unparsable\n",
+ qu->id, *argv);
+ return -1;
+ }
+ return 0;
+}
+
+static int print_nofopt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 fhandle)
+{
+ if (opt && RTA_PAYLOAD(opt))
+ fprintf(f, "fh %08x [Unknown filter, optlen=%u] ",
+ fhandle, (unsigned int) RTA_PAYLOAD(opt));
+ else if (fhandle)
+ fprintf(f, "fh %08x ", fhandle);
+ return 0;
+}
+
+static int parse_nofopt(struct filter_util *qu, char *fhandle,
+ int argc, char **argv, struct nlmsghdr *n)
+{
+ __u32 handle;
+
+ if (argc) {
+ fprintf(stderr,
+ "Unknown filter \"%s\", hence option \"%s\" is unparsable\n",
+ qu->id, *argv);
+ return -1;
+ }
+ if (fhandle) {
+ struct tcmsg *t = NLMSG_DATA(n);
+
+ if (get_u32(&handle, fhandle, 16)) {
+ fprintf(stderr, "Unparsable filter ID \"%s\"\n", fhandle);
+ return -1;
+ }
+ t->tcm_handle = handle;
+ }
+ return 0;
+}
+
+struct qdisc_util *get_qdisc_kind(const char *str)
+{
+ void *dlh;
+ char buf[256];
+ struct qdisc_util *q;
+
+ for (q = qdisc_list; q; q = q->next)
+ if (strcmp(q->id, str) == 0)
+ return q;
+
+ snprintf(buf, sizeof(buf), "%s/q_%s.so", get_tc_lib(), str);
+ dlh = dlopen(buf, RTLD_LAZY);
+ if (!dlh) {
+ /* look in current binary, only open once */
+ dlh = BODY;
+ if (dlh == NULL) {
+ dlh = BODY = dlopen(NULL, RTLD_LAZY);
+ if (dlh == NULL)
+ goto noexist;
+ }
+ }
+
+ snprintf(buf, sizeof(buf), "%s_qdisc_util", str);
+ q = dlsym(dlh, buf);
+ if (q == NULL)
+ goto noexist;
+
+reg:
+ q->next = qdisc_list;
+ qdisc_list = q;
+ return q;
+
+noexist:
+ q = calloc(1, sizeof(*q));
+ if (q) {
+ q->id = strdup(str);
+ q->parse_qopt = parse_noqopt;
+ q->print_qopt = print_noqopt;
+ goto reg;
+ }
+ return q;
+}
+
+
+struct filter_util *get_filter_kind(const char *str)
+{
+ void *dlh;
+ char buf[256];
+ struct filter_util *q;
+
+ for (q = filter_list; q; q = q->next)
+ if (strcmp(q->id, str) == 0)
+ return q;
+
+ snprintf(buf, sizeof(buf), "%s/f_%s.so", get_tc_lib(), 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_filter_util", str);
+ q = dlsym(dlh, buf);
+ if (q == NULL)
+ goto noexist;
+
+reg:
+ q->next = filter_list;
+ filter_list = q;
+ return q;
+noexist:
+ q = calloc(1, sizeof(*q));
+ if (q) {
+ strncpy(q->id, str, 15);
+ q->parse_fopt = parse_nofopt;
+ q->print_fopt = print_nofopt;
+ goto reg;
+ }
+ return q;
+}
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: tc [ OPTIONS ] OBJECT { COMMAND | help }\n"
+ " tc [-force] -batch filename\n"
+ "where OBJECT := { qdisc | class | filter | chain |\n"
+ " action | monitor | exec }\n"
+ " OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[aw] |\n"
+ " -o[neline] | -j[son] | -p[retty] | -c[olor]\n"
+ " -b[atch] [filename] | -n[etns] name | -N[umeric] |\n"
+ " -nm | -nam[es] | { -cf | -conf } path }\n");
+}
+
+static int do_cmd(int argc, char **argv)
+{
+ if (matches(*argv, "qdisc") == 0)
+ return do_qdisc(argc-1, argv+1);
+ if (matches(*argv, "class") == 0)
+ return do_class(argc-1, argv+1);
+ if (matches(*argv, "filter") == 0)
+ return do_filter(argc-1, argv+1);
+ if (matches(*argv, "chain") == 0)
+ return do_chain(argc-1, argv+1);
+ if (matches(*argv, "actions") == 0)
+ return do_action(argc-1, argv+1);
+ if (matches(*argv, "monitor") == 0)
+ return do_tcmonitor(argc-1, argv+1);
+ if (matches(*argv, "exec") == 0)
+ return do_exec(argc-1, argv+1);
+ if (matches(*argv, "help") == 0) {
+ usage();
+ return 0;
+ }
+
+ fprintf(stderr, "Object \"%s\" is unknown, try \"tc help\".\n",
+ *argv);
+ return -1;
+}
+
+static int batch(const char *name)
+{
+ char *line = NULL;
+ size_t len = 0;
+ int ret = 0;
+
+ 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 -1;
+ }
+ }
+
+ tc_core_init();
+
+ if (rtnl_open(&rth, 0) < 0) {
+ fprintf(stderr, "Cannot open rtnetlink\n");
+ return -1;
+ }
+
+ 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(largc, largv)) {
+ fprintf(stderr, "Command failed %s:%d\n",
+ name, cmdlineno);
+ ret = 1;
+ if (!force)
+ break;
+ }
+ fflush(stdout);
+ }
+
+ free(line);
+ rtnl_close(&rth);
+ return ret;
+}
+
+
+int main(int argc, char **argv)
+{
+ int ret;
+ char *batch_file = NULL;
+
+ 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], "-pretty") == 0) {
+ ++pretty;
+ } else if (matches(argv[1], "-graph") == 0) {
+ show_graph = 1;
+ } else if (matches(argv[1], "-Version") == 0) {
+ printf("tc utility, iproute2-ss%s\n", SNAPSHOT);
+ return 0;
+ } else if (matches(argv[1], "-iec") == 0) {
+ ++use_iec;
+ } else if (matches(argv[1], "-help") == 0) {
+ usage();
+ return 0;
+ } else if (matches(argv[1], "-force") == 0) {
+ ++force;
+ } else if (matches(argv[1], "-batch") == 0) {
+ argc--; argv++;
+ if (argc <= 1)
+ usage();
+ batch_file = argv[1];
+ } else if (matches(argv[1], "-netns") == 0) {
+ NEXT_ARG();
+ if (netns_switch(argv[1]))
+ return -1;
+ } else if (matches(argv[1], "-Numeric") == 0) {
+ ++numeric;
+ } else if (matches(argv[1], "-names") == 0 ||
+ matches(argv[1], "-nm") == 0) {
+ use_names = true;
+ } else if (matches(argv[1], "-cf") == 0 ||
+ matches(argv[1], "-conf") == 0) {
+ NEXT_ARG();
+ conf_file = argv[1];
+ } else if (matches_color(argv[1], &color)) {
+ } else if (matches(argv[1], "-timestamp") == 0) {
+ timestamp++;
+ } else if (matches(argv[1], "-tshort") == 0) {
+ ++timestamp;
+ ++timestamp_short;
+ } else if (matches(argv[1], "-json") == 0) {
+ ++json;
+ } else if (matches(argv[1], "-oneline") == 0) {
+ ++oneline;
+ } else {
+ fprintf(stderr,
+ "Option \"%s\" is unknown, try \"tc -help\".\n",
+ argv[1]);
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ _SL_ = oneline ? "\\" : "\n";
+
+ check_enable_color(color, json);
+
+ if (batch_file)
+ return batch(batch_file);
+
+ if (argc <= 1) {
+ usage();
+ return 0;
+ }
+
+ tc_core_init();
+ if (rtnl_open(&rth, 0) < 0) {
+ fprintf(stderr, "Cannot open rtnetlink\n");
+ exit(1);
+ }
+
+ if (use_names && cls_names_init(conf_file)) {
+ ret = -1;
+ goto Exit;
+ }
+
+ ret = do_cmd(argc-1, argv+1);
+Exit:
+ rtnl_close(&rth);
+
+ if (use_names)
+ cls_names_uninit();
+
+ return ret;
+}
diff --git a/tc/tc_cbq.c b/tc/tc_cbq.c
new file mode 100644
index 0000000..f56011a
--- /dev/null
+++ b/tc/tc_cbq.c
@@ -0,0 +1,58 @@
+/*
+ * tc_cbq.c CBQ maintenance 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 <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_core.h"
+#include "tc_cbq.h"
+
+unsigned int tc_cbq_calc_maxidle(unsigned int bndw, unsigned int rate, unsigned int avpkt,
+ int ewma_log, unsigned int maxburst)
+{
+ double maxidle;
+ double g = 1.0 - 1.0/(1<<ewma_log);
+ double xmt = (double)avpkt/bndw;
+
+ maxidle = xmt*(1-g);
+ if (bndw != rate && maxburst) {
+ double vxmt = (double)avpkt/rate - xmt;
+
+ vxmt *= (pow(g, -(double)maxburst) - 1);
+ if (vxmt > maxidle)
+ maxidle = vxmt;
+ }
+ return tc_core_time2tick(maxidle*(1<<ewma_log)*TIME_UNITS_PER_SEC);
+}
+
+unsigned int tc_cbq_calc_offtime(unsigned int bndw, unsigned int rate, unsigned int avpkt,
+ int ewma_log, unsigned int minburst)
+{
+ double g = 1.0 - 1.0/(1<<ewma_log);
+ double offtime = (double)avpkt/rate - (double)avpkt/bndw;
+
+ if (minburst == 0)
+ return 0;
+ if (minburst == 1)
+ offtime *= pow(g, -(double)minburst) - 1;
+ else
+ offtime *= 1 + (pow(g, -(double)(minburst-1)) - 1)/(1-g);
+ return tc_core_time2tick(offtime*TIME_UNITS_PER_SEC);
+}
diff --git a/tc/tc_cbq.h b/tc/tc_cbq.h
new file mode 100644
index 0000000..fa17d24
--- /dev/null
+++ b/tc/tc_cbq.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _TC_CBQ_H_
+#define _TC_CBQ_H_ 1
+
+unsigned tc_cbq_calc_maxidle(unsigned bndw, unsigned rate, unsigned avpkt,
+ int ewma_log, unsigned maxburst);
+unsigned tc_cbq_calc_offtime(unsigned bndw, unsigned rate, unsigned avpkt,
+ int ewma_log, unsigned minburst);
+
+#endif
diff --git a/tc/tc_class.c b/tc/tc_class.c
new file mode 100644
index 0000000..c7e3cfd
--- /dev/null
+++ b/tc/tc_class.c
@@ -0,0 +1,490 @@
+/*
+ * tc_class.c "tc class".
+ *
+ * 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 <math.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+#include "list.h"
+
+struct graph_node {
+ struct hlist_node hlist;
+ __u32 id;
+ __u32 parent_id;
+ struct graph_node *parent_node;
+ struct graph_node *right_node;
+ void *data;
+ int data_len;
+ int nodes_count;
+};
+
+static struct hlist_head cls_list = {};
+static struct hlist_head root_cls_list = {};
+
+static void usage(void);
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: tc class [ add | del | change | replace | show ] dev STRING\n"
+ " [ classid CLASSID ] [ root | parent CLASSID ]\n"
+ " [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n"
+ "\n"
+ " tc class show [ dev STRING ] [ root | parent CLASSID ]\n"
+ "Where:\n"
+ "QDISC_KIND := { prio | cbq | etc. }\n"
+ "OPTIONS := ... try tc class add <desired QDISC_KIND> help\n");
+}
+
+static int tc_class_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct tcmsg t;
+ char buf[4096];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .t.tcm_family = AF_UNSPEC,
+ };
+ struct qdisc_util *q = NULL;
+ struct tc_estimator est = {};
+ char d[IFNAMSIZ] = {};
+ char k[FILTER_NAMESZ] = {};
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (d[0])
+ duparg("dev", *argv);
+ strncpy(d, *argv, sizeof(d)-1);
+ } else if (strcmp(*argv, "classid") == 0) {
+ __u32 handle;
+
+ NEXT_ARG();
+ if (req.t.tcm_handle)
+ duparg("classid", *argv);
+ if (get_tc_classid(&handle, *argv))
+ invarg("invalid class ID", *argv);
+ req.t.tcm_handle = handle;
+ } else if (strcmp(*argv, "handle") == 0) {
+ fprintf(stderr, "Error: try \"classid\" instead of \"handle\"\n");
+ return -1;
+ } else if (strcmp(*argv, "root") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr, "Error: \"root\" is duplicate parent ID.\n");
+ return -1;
+ }
+ req.t.tcm_parent = TC_H_ROOT;
+ } else if (strcmp(*argv, "parent") == 0) {
+ __u32 handle;
+
+ NEXT_ARG();
+ if (req.t.tcm_parent)
+ duparg("parent", *argv);
+ if (get_tc_classid(&handle, *argv))
+ invarg("invalid parent ID", *argv);
+ req.t.tcm_parent = handle;
+ } else if (matches(*argv, "estimator") == 0) {
+ if (parse_estimator(&argc, &argv, &est))
+ return -1;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ strncpy(k, *argv, sizeof(k)-1);
+
+ q = get_qdisc_kind(k);
+ argc--; argv++;
+ break;
+ }
+ argc--; argv++;
+ }
+
+ if (k[0])
+ addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
+ if (est.ewma_log)
+ addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
+
+ if (q) {
+ if (q->parse_copt == NULL) {
+ fprintf(stderr, "Error: Qdisc \"%s\" is classless.\n", k);
+ return 1;
+ }
+ if (q->parse_copt(q, argc, argv, &req.n, d))
+ return 1;
+ } else {
+ if (argc) {
+ if (matches(*argv, "help") == 0)
+ usage();
+ fprintf(stderr, "Garbage instead of arguments \"%s ...\". Try \"tc class help\".", *argv);
+ return -1;
+ }
+ }
+
+ if (d[0]) {
+ ll_init_map(&rth);
+
+ req.t.tcm_ifindex = ll_name_to_index(d);
+ if (!req.t.tcm_ifindex)
+ return -nodev(d);
+ }
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return 2;
+
+ return 0;
+}
+
+static int filter_ifindex;
+static __u32 filter_qdisc;
+static __u32 filter_classid;
+
+static void graph_node_add(__u32 parent_id, __u32 id, void *data,
+ int len)
+{
+ struct graph_node *node = calloc(1, sizeof(struct graph_node));
+
+ node->id = id;
+ node->parent_id = parent_id;
+
+ if (data && len) {
+ node->data = malloc(len);
+ node->data_len = len;
+ memcpy(node->data, data, len);
+ }
+
+ if (parent_id == TC_H_ROOT)
+ hlist_add_head(&node->hlist, &root_cls_list);
+ else
+ hlist_add_head(&node->hlist, &cls_list);
+}
+
+static void graph_indent(char *buf, struct graph_node *node, int is_newline,
+ int add_spaces)
+{
+ char spaces[100] = {0};
+
+ while (node && node->parent_node) {
+ node->parent_node->right_node = node;
+ node = node->parent_node;
+ }
+ while (node && node->right_node) {
+ if (node->hlist.next)
+ strcat(buf, "| ");
+ else
+ strcat(buf, " ");
+
+ node = node->right_node;
+ }
+
+ if (is_newline) {
+ if (node->hlist.next && node->nodes_count)
+ strcat(buf, "| |");
+ else if (node->hlist.next)
+ strcat(buf, "| ");
+ else if (node->nodes_count)
+ strcat(buf, " |");
+ else if (!node->hlist.next)
+ strcat(buf, " ");
+ }
+ if (add_spaces > 0) {
+ sprintf(spaces, "%-*s", add_spaces, "");
+ strcat(buf, spaces);
+ }
+}
+
+static void graph_cls_show(FILE *fp, char *buf, struct hlist_head *root_list,
+ int level)
+{
+ struct hlist_node *n, *tmp_cls;
+ char cls_id_str[256] = {};
+ struct rtattr *tb[TCA_MAX + 1];
+ struct qdisc_util *q;
+ char str[300] = {};
+
+ hlist_for_each_safe(n, tmp_cls, root_list) {
+ struct hlist_node *c, *tmp_chld;
+ struct hlist_head children = {};
+ struct graph_node *cls = container_of(n, struct graph_node,
+ hlist);
+
+ hlist_for_each_safe(c, tmp_chld, &cls_list) {
+ struct graph_node *child = container_of(c,
+ struct graph_node, hlist);
+
+ if (cls->id == child->parent_id) {
+ hlist_del(c);
+ hlist_add_head(c, &children);
+ cls->nodes_count++;
+ child->parent_node = cls;
+ }
+ }
+
+ graph_indent(buf, cls, 0, 0);
+
+ print_tc_classid(cls_id_str, sizeof(cls_id_str), cls->id);
+ snprintf(str, sizeof(str),
+ "+---(%s)", cls_id_str);
+ strcat(buf, str);
+
+ parse_rtattr(tb, TCA_MAX, (struct rtattr *)cls->data,
+ cls->data_len);
+
+ if (tb[TCA_KIND] == NULL) {
+ strcat(buf, " [unknown qdisc kind] ");
+ } else {
+ const char *kind = rta_getattr_str(tb[TCA_KIND]);
+
+ sprintf(str, " %s ", kind);
+ strcat(buf, str);
+ fprintf(fp, "%s", buf);
+ buf[0] = '\0';
+
+ q = get_qdisc_kind(kind);
+ if (q && q->print_copt) {
+ q->print_copt(q, fp, tb[TCA_OPTIONS]);
+ }
+ if (q && show_stats) {
+ int cls_indent = strlen(q->id) - 2 +
+ strlen(cls_id_str);
+ struct rtattr *stats = NULL;
+
+ graph_indent(buf, cls, 1, cls_indent);
+
+ if (tb[TCA_STATS] || tb[TCA_STATS2]) {
+ fprintf(fp, "\n");
+ print_tcstats_attr(fp, tb, buf, &stats);
+ buf[0] = '\0';
+ }
+ if (cls->hlist.next || cls->nodes_count) {
+ strcat(buf, "\n");
+ graph_indent(buf, cls, 1, 0);
+ }
+ }
+ }
+ free(cls->data);
+ fprintf(fp, "%s\n", buf);
+ buf[0] = '\0';
+
+ graph_cls_show(fp, buf, &children, level + 1);
+ if (!cls->hlist.next) {
+ graph_indent(buf, cls, 0, 0);
+ strcat(buf, "\n");
+ }
+
+ fprintf(fp, "%s", buf);
+ buf[0] = '\0';
+ free(cls);
+ }
+}
+
+int print_class(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct tcmsg *t = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[TCA_MAX + 1];
+ struct qdisc_util *q;
+ char abuf[256];
+
+ if (n->nlmsg_type != RTM_NEWTCLASS && n->nlmsg_type != RTM_DELTCLASS) {
+ fprintf(stderr, "Not a class\n");
+ return 0;
+ }
+ len -= NLMSG_LENGTH(sizeof(*t));
+ if (len < 0) {
+ fprintf(stderr, "Wrong len %d\n", len);
+ return -1;
+ }
+
+ if (show_graph) {
+ graph_node_add(t->tcm_parent, t->tcm_handle, TCA_RTA(t), len);
+ return 0;
+ }
+
+ if (filter_qdisc && TC_H_MAJ(t->tcm_handle^filter_qdisc))
+ return 0;
+
+ if (filter_classid && t->tcm_handle != filter_classid)
+ return 0;
+
+ parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len);
+
+ if (tb[TCA_KIND] == NULL) {
+ fprintf(stderr, "print_class: NULL kind\n");
+ return -1;
+ }
+
+ if (n->nlmsg_type == RTM_DELTCLASS)
+ fprintf(fp, "deleted ");
+
+ abuf[0] = 0;
+ if (t->tcm_handle) {
+ if (filter_qdisc)
+ print_tc_classid(abuf, sizeof(abuf), TC_H_MIN(t->tcm_handle));
+ else
+ print_tc_classid(abuf, sizeof(abuf), t->tcm_handle);
+ }
+ fprintf(fp, "class %s %s ", rta_getattr_str(tb[TCA_KIND]), abuf);
+
+ if (filter_ifindex == 0)
+ fprintf(fp, "dev %s ", ll_index_to_name(t->tcm_ifindex));
+
+ if (t->tcm_parent == TC_H_ROOT)
+ fprintf(fp, "root ");
+ else {
+ if (filter_qdisc)
+ print_tc_classid(abuf, sizeof(abuf), TC_H_MIN(t->tcm_parent));
+ else
+ print_tc_classid(abuf, sizeof(abuf), t->tcm_parent);
+ fprintf(fp, "parent %s ", abuf);
+ }
+ if (t->tcm_info)
+ fprintf(fp, "leaf %x: ", t->tcm_info>>16);
+ q = get_qdisc_kind(RTA_DATA(tb[TCA_KIND]));
+ if (tb[TCA_OPTIONS]) {
+ if (q && q->print_copt)
+ q->print_copt(q, fp, tb[TCA_OPTIONS]);
+ else
+ fprintf(fp, "[cannot parse class parameters]");
+ }
+ fprintf(fp, "\n");
+ if (show_stats) {
+ struct rtattr *xstats = NULL;
+
+ if (tb[TCA_STATS] || tb[TCA_STATS2]) {
+ print_tcstats_attr(fp, tb, " ", &xstats);
+ fprintf(fp, "\n");
+ }
+ if (q && (xstats || tb[TCA_XSTATS]) && q->print_xstats) {
+ q->print_xstats(q, fp, xstats ? : tb[TCA_XSTATS]);
+ fprintf(fp, "\n");
+ }
+ }
+ fflush(fp);
+ return 0;
+}
+
+
+static int tc_class_list(int argc, char **argv)
+{
+ struct tcmsg t = { .tcm_family = AF_UNSPEC };
+ char d[IFNAMSIZ] = {};
+ char buf[1024] = {0};
+
+ filter_qdisc = 0;
+ filter_classid = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (d[0])
+ duparg("dev", *argv);
+ strncpy(d, *argv, sizeof(d)-1);
+ } else if (strcmp(*argv, "qdisc") == 0) {
+ NEXT_ARG();
+ if (filter_qdisc)
+ duparg("qdisc", *argv);
+ if (get_qdisc_handle(&filter_qdisc, *argv))
+ invarg("invalid qdisc ID", *argv);
+ } else if (strcmp(*argv, "classid") == 0) {
+ NEXT_ARG();
+ if (filter_classid)
+ duparg("classid", *argv);
+ if (get_tc_classid(&filter_classid, *argv))
+ invarg("invalid class ID", *argv);
+ } else if (strcmp(*argv, "root") == 0) {
+ if (t.tcm_parent) {
+ fprintf(stderr, "Error: \"root\" is duplicate parent ID\n");
+ return -1;
+ }
+ t.tcm_parent = TC_H_ROOT;
+ } else if (strcmp(*argv, "parent") == 0) {
+ __u32 handle;
+
+ if (t.tcm_parent)
+ duparg("parent", *argv);
+ NEXT_ARG();
+ if (get_tc_classid(&handle, *argv))
+ invarg("invalid parent ID", *argv);
+ t.tcm_parent = handle;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ fprintf(stderr, "What is \"%s\"? Try \"tc class help\".\n", *argv);
+ return -1;
+ }
+
+ argc--; argv++;
+ }
+
+ ll_init_map(&rth);
+
+ if (d[0]) {
+ t.tcm_ifindex = ll_name_to_index(d);
+ if (!t.tcm_ifindex)
+ return -nodev(d);
+ filter_ifindex = t.tcm_ifindex;
+ }
+
+ if (rtnl_dump_request(&rth, RTM_GETTCLASS, &t, sizeof(t)) < 0) {
+ perror("Cannot send dump request");
+ return 1;
+ }
+
+ if (rtnl_dump_filter(&rth, print_class, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ return 1;
+ }
+
+ if (show_graph)
+ graph_cls_show(stdout, &buf[0], &root_cls_list, 0);
+
+ return 0;
+}
+
+int do_class(int argc, char **argv)
+{
+ if (argc < 1)
+ return tc_class_list(0, NULL);
+ if (matches(*argv, "add") == 0)
+ return tc_class_modify(RTM_NEWTCLASS, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1);
+ if (matches(*argv, "change") == 0)
+ return tc_class_modify(RTM_NEWTCLASS, 0, argc-1, argv+1);
+ if (matches(*argv, "replace") == 0)
+ return tc_class_modify(RTM_NEWTCLASS, NLM_F_CREATE, argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return tc_class_modify(RTM_DELTCLASS, 0, argc-1, argv+1);
+#if 0
+ if (matches(*argv, "get") == 0)
+ return tc_class_get(RTM_GETTCLASS, 0, argc-1, argv+1);
+#endif
+ if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+ || matches(*argv, "lst") == 0)
+ return tc_class_list(argc-1, argv+1);
+ if (matches(*argv, "help") == 0) {
+ usage();
+ return 0;
+ }
+ fprintf(stderr, "Command \"%s\" is unknown, try \"tc class help\".\n", *argv);
+ return -1;
+}
diff --git a/tc/tc_common.h b/tc/tc_common.h
new file mode 100644
index 0000000..802fb7f
--- /dev/null
+++ b/tc/tc_common.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define TCA_BUF_MAX (64*1024)
+
+extern struct rtnl_handle rth;
+
+int do_qdisc(int argc, char **argv);
+int do_class(int argc, char **argv);
+int do_filter(int argc, char **argv);
+int do_chain(int argc, char **argv);
+int do_action(int argc, char **argv);
+int do_tcmonitor(int argc, char **argv);
+int do_exec(int argc, char **argv);
+
+int print_action(struct nlmsghdr *n, void *arg);
+int print_filter(struct nlmsghdr *n, void *arg);
+int print_qdisc(struct nlmsghdr *n, void *arg);
+int print_class(struct nlmsghdr *n, void *arg);
+void print_size_table(FILE *fp, const char *prefix, struct rtattr *rta);
+
+struct tc_estimator;
+int parse_estimator(int *p_argc, char ***p_argv, struct tc_estimator *est);
+
+struct tc_sizespec;
+int parse_size_table(int *p_argc, char ***p_argv, struct tc_sizespec *s);
+int check_size_table_opts(struct tc_sizespec *s);
+
+extern int show_graph;
+extern bool use_names;
diff --git a/tc/tc_core.c b/tc/tc_core.c
new file mode 100644
index 0000000..498d35d
--- /dev/null
+++ b/tc/tc_core.c
@@ -0,0 +1,258 @@
+/*
+ * tc_core.c TC core library.
+ *
+ * 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 <stdint.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_core.h"
+#include <linux/atm.h>
+
+static double tick_in_usec = 1;
+static double clock_factor = 1;
+
+int tc_core_time2big(unsigned int time)
+{
+ __u64 t = time;
+
+ t *= tick_in_usec;
+ return (t >> 32) != 0;
+}
+
+
+unsigned int tc_core_time2tick(unsigned int time)
+{
+ return time*tick_in_usec;
+}
+
+unsigned int tc_core_tick2time(unsigned int tick)
+{
+ return tick/tick_in_usec;
+}
+
+unsigned int tc_core_time2ktime(unsigned int time)
+{
+ return time * clock_factor;
+}
+
+unsigned int tc_core_ktime2time(unsigned int ktime)
+{
+ return ktime / clock_factor;
+}
+
+unsigned int tc_calc_xmittime(__u64 rate, unsigned int size)
+{
+ return tc_core_time2tick(TIME_UNITS_PER_SEC*((double)size/(double)rate));
+}
+
+unsigned int tc_calc_xmitsize(__u64 rate, unsigned int ticks)
+{
+ return ((double)rate*tc_core_tick2time(ticks))/TIME_UNITS_PER_SEC;
+}
+
+/*
+ * The align to ATM cells is used for determining the (ATM) SAR
+ * alignment overhead at the ATM layer. (SAR = Segmentation And
+ * Reassembly). This is for example needed when scheduling packet on
+ * an ADSL connection. Note that the extra ATM-AAL overhead is _not_
+ * included in this calculation. This overhead is added in the kernel
+ * before doing the rate table lookup, as this gives better precision
+ * (as the table will always be aligned for 48 bytes).
+ * --Hawk, d.7/11-2004. <hawk@diku.dk>
+ */
+static unsigned int tc_align_to_atm(unsigned int size)
+{
+ int linksize, cells;
+
+ cells = size / ATM_CELL_PAYLOAD;
+ if ((size % ATM_CELL_PAYLOAD) > 0)
+ cells++;
+
+ linksize = cells * ATM_CELL_SIZE; /* Use full cell size to add ATM tax */
+ return linksize;
+}
+
+static unsigned int tc_adjust_size(unsigned int sz, unsigned int mpu, enum link_layer linklayer)
+{
+ if (sz < mpu)
+ sz = mpu;
+
+ switch (linklayer) {
+ case LINKLAYER_ATM:
+ return tc_align_to_atm(sz);
+ case LINKLAYER_ETHERNET:
+ default:
+ /* No size adjustments on Ethernet */
+ return sz;
+ }
+}
+
+/* Notice, the rate table calculated here, have gotten replaced in the
+ * kernel and is no-longer used for lookups.
+ *
+ * This happened in kernel release v3.8 caused by kernel
+ * - commit 56b765b79 ("htb: improved accuracy at high rates").
+ * This change unfortunately caused breakage of tc overhead and
+ * linklayer parameters.
+ *
+ * Kernel overhead handling got fixed in kernel v3.10 by
+ * - commit 01cb71d2d47 (net_sched: restore "overhead xxx" handling)
+ *
+ * Kernel linklayer handling got fixed in kernel v3.11 by
+ * - commit 8a8e3d84b17 (net_sched: restore "linklayer atm" handling)
+ */
+
+/*
+ rtab[pkt_len>>cell_log] = pkt_xmit_time
+ */
+
+int tc_calc_rtable(struct tc_ratespec *r, __u32 *rtab,
+ int cell_log, unsigned int mtu,
+ enum link_layer linklayer)
+{
+ int i;
+ unsigned int sz;
+ unsigned int bps = r->rate;
+ unsigned int mpu = r->mpu;
+
+ if (mtu == 0)
+ mtu = 2047;
+
+ if (cell_log < 0) {
+ cell_log = 0;
+ while ((mtu >> cell_log) > 255)
+ cell_log++;
+ }
+
+ for (i = 0; i < 256; i++) {
+ sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer);
+ rtab[i] = tc_calc_xmittime(bps, sz);
+ }
+
+ r->cell_align = -1;
+ r->cell_log = cell_log;
+ r->linklayer = (linklayer & TC_LINKLAYER_MASK);
+ return cell_log;
+}
+
+int tc_calc_rtable_64(struct tc_ratespec *r, __u32 *rtab,
+ int cell_log, unsigned int mtu,
+ enum link_layer linklayer, __u64 rate)
+{
+ int i;
+ unsigned int sz;
+ __u64 bps = rate;
+ unsigned int mpu = r->mpu;
+
+ if (mtu == 0)
+ mtu = 2047;
+
+ if (cell_log < 0) {
+ cell_log = 0;
+ while ((mtu >> cell_log) > 255)
+ cell_log++;
+ }
+
+ for (i = 0; i < 256; i++) {
+ sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer);
+ rtab[i] = tc_calc_xmittime(bps, sz);
+ }
+
+ r->cell_align = -1;
+ r->cell_log = cell_log;
+ r->linklayer = (linklayer & TC_LINKLAYER_MASK);
+ return cell_log;
+}
+
+/*
+ stab[pkt_len>>cell_log] = pkt_xmit_size>>size_log
+ */
+
+int tc_calc_size_table(struct tc_sizespec *s, __u16 **stab)
+{
+ int i;
+ enum link_layer linklayer = s->linklayer;
+ unsigned int sz;
+
+ if (linklayer <= LINKLAYER_ETHERNET && s->mpu == 0) {
+ /* don't need data table in this case (only overhead set) */
+ s->mtu = 0;
+ s->tsize = 0;
+ s->cell_log = 0;
+ s->cell_align = 0;
+ *stab = NULL;
+ return 0;
+ }
+
+ if (s->mtu == 0)
+ s->mtu = 2047;
+ if (s->tsize == 0)
+ s->tsize = 512;
+
+ s->cell_log = 0;
+ while ((s->mtu >> s->cell_log) > s->tsize - 1)
+ s->cell_log++;
+
+ *stab = malloc(s->tsize * sizeof(__u16));
+ if (!*stab)
+ return -1;
+
+again:
+ for (i = s->tsize - 1; i >= 0; i--) {
+ sz = tc_adjust_size((i + 1) << s->cell_log, s->mpu, linklayer);
+ if ((sz >> s->size_log) > UINT16_MAX) {
+ s->size_log++;
+ goto again;
+ }
+ (*stab)[i] = sz >> s->size_log;
+ }
+
+ s->cell_align = -1; /* Due to the sz calc */
+ return 0;
+}
+
+int tc_core_init(void)
+{
+ FILE *fp;
+ __u32 clock_res;
+ __u32 t2us;
+ __u32 us2t;
+
+ fp = fopen("/proc/net/psched", "r");
+ if (fp == NULL)
+ return -1;
+
+ if (fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res) != 3) {
+ fclose(fp);
+ return -1;
+ }
+ fclose(fp);
+
+ /* compatibility hack: for old iproute binaries (ignoring
+ * the kernel clock resolution) the kernel advertises a
+ * tick multiplier of 1000 in case of nano-second resolution,
+ * which really is 1. */
+ if (clock_res == 1000000000)
+ t2us = us2t;
+
+ clock_factor = (double)clock_res / TIME_UNITS_PER_SEC;
+ tick_in_usec = (double)t2us / us2t * clock_factor;
+ return 0;
+}
diff --git a/tc/tc_core.h b/tc/tc_core.h
new file mode 100644
index 0000000..6dab272
--- /dev/null
+++ b/tc/tc_core.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _TC_CORE_H_
+#define _TC_CORE_H_ 1
+
+#include <asm/types.h>
+#include <linux/pkt_sched.h>
+
+enum link_layer {
+ LINKLAYER_UNSPEC,
+ LINKLAYER_ETHERNET,
+ LINKLAYER_ATM,
+};
+
+
+int tc_core_time2big(unsigned time);
+unsigned tc_core_time2tick(unsigned time);
+unsigned tc_core_tick2time(unsigned tick);
+unsigned tc_core_time2ktime(unsigned time);
+unsigned tc_core_ktime2time(unsigned ktime);
+unsigned tc_calc_xmittime(__u64 rate, unsigned size);
+unsigned tc_calc_xmitsize(__u64 rate, unsigned ticks);
+int tc_calc_rtable(struct tc_ratespec *r, __u32 *rtab,
+ int cell_log, unsigned mtu, enum link_layer link_layer);
+int tc_calc_rtable_64(struct tc_ratespec *r, __u32 *rtab,
+ int cell_log, unsigned mtu, enum link_layer link_layer,
+ __u64 rate);
+int tc_calc_size_table(struct tc_sizespec *s, __u16 **stab);
+
+int tc_setup_estimator(unsigned A, unsigned time_const, struct tc_estimator *est);
+
+int tc_core_init(void);
+
+extern struct rtnl_handle g_rth;
+extern int is_batch_mode;
+
+#endif
diff --git a/tc/tc_estimator.c b/tc/tc_estimator.c
new file mode 100644
index 0000000..f494b7c
--- /dev/null
+++ b/tc/tc_estimator.c
@@ -0,0 +1,45 @@
+/*
+ * tc_core.c TC core library.
+ *
+ * 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 <math.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_core.h"
+
+int tc_setup_estimator(unsigned int A, unsigned int time_const, struct tc_estimator *est)
+{
+ for (est->interval = 0; est->interval <= 5; est->interval++) {
+ if (A <= (1<<est->interval)*(TIME_UNITS_PER_SEC/4))
+ break;
+ }
+ if (est->interval > 5)
+ return -1;
+ est->interval -= 2;
+ for (est->ewma_log = 1; est->ewma_log < 32; est->ewma_log++) {
+ double w = 1.0 - 1.0/(1<<est->ewma_log);
+
+ if (A/(-log(w)) > time_const)
+ break;
+ }
+ est->ewma_log--;
+ if (est->ewma_log == 0 || est->ewma_log >= 31)
+ return -1;
+ return 0;
+}
diff --git a/tc/tc_exec.c b/tc/tc_exec.c
new file mode 100644
index 0000000..9b912ce
--- /dev/null
+++ b/tc/tc_exec.c
@@ -0,0 +1,108 @@
+/*
+ * tc_exec.c "tc exec".
+ *
+ * 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 <dlfcn.h>
+
+#include "utils.h"
+
+#include "tc_util.h"
+#include "tc_common.h"
+
+static struct exec_util *exec_list;
+static void *BODY;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: tc exec [ EXEC_TYPE ] [ help | OPTIONS ]\n"
+ "Where:\n"
+ "EXEC_TYPE := { bpf | etc. }\n"
+ "OPTIONS := ... try tc exec <desired EXEC_KIND> help\n");
+}
+
+static int parse_noeopt(struct exec_util *eu, int argc, char **argv)
+{
+ if (argc) {
+ fprintf(stderr, "Unknown exec \"%s\", hence option \"%s\" is unparsable\n",
+ eu->id, *argv);
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct exec_util *get_exec_kind(const char *name)
+{
+ struct exec_util *eu;
+ char buf[256];
+ void *dlh;
+
+ for (eu = exec_list; eu; eu = eu->next)
+ if (strcmp(eu->id, name) == 0)
+ return eu;
+
+ snprintf(buf, sizeof(buf), "%s/e_%s.so", get_tc_lib(), name);
+ 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_exec_util", name);
+ eu = dlsym(dlh, buf);
+ if (eu == NULL)
+ goto noexist;
+reg:
+ eu->next = exec_list;
+ exec_list = eu;
+
+ return eu;
+noexist:
+ eu = calloc(1, sizeof(*eu));
+ if (eu) {
+ strncpy(eu->id, name, sizeof(eu->id) - 1);
+ eu->parse_eopt = parse_noeopt;
+ goto reg;
+ }
+
+ return eu;
+}
+
+int do_exec(int argc, char **argv)
+{
+ struct exec_util *eu;
+ char kind[FILTER_NAMESZ] = {};
+
+ if (argc < 1) {
+ fprintf(stderr, "No command given, try \"tc exec help\".\n");
+ return -1;
+ }
+
+ if (matches(*argv, "help") == 0) {
+ usage();
+ return 0;
+ }
+
+ strncpy(kind, *argv, sizeof(kind) - 1);
+
+ eu = get_exec_kind(kind);
+
+ argc--;
+ argv++;
+
+ return eu->parse_eopt(eu, argc, argv);
+}
diff --git a/tc/tc_filter.c b/tc/tc_filter.c
new file mode 100644
index 0000000..f7d2e4a
--- /dev/null
+++ b/tc/tc_filter.c
@@ -0,0 +1,790 @@
+/*
+ * tc_filter.c "tc filter".
+ *
+ * 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 <linux/if_ether.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: tc filter [ add | del | change | replace | show ] [ dev STRING ]\n"
+ " tc filter [ add | del | change | replace | show ] [ block BLOCK_INDEX ]\n"
+ " tc filter get dev STRING parent CLASSID protocol PROTO handle FILTERID pref PRIO FILTER_TYPE\n"
+ " tc filter get block BLOCK_INDEX protocol PROTO handle FILTERID pref PRIO FILTER_TYPE\n"
+ " [ pref PRIO ] protocol PROTO [ chain CHAIN_INDEX ]\n"
+ " [ estimator INTERVAL TIME_CONSTANT ]\n"
+ " [ root | ingress | egress | parent CLASSID ]\n"
+ " [ handle FILTERID ] [ [ FILTER_TYPE ] [ help | OPTIONS ] ]\n"
+ "\n"
+ " tc filter show [ dev STRING ] [ root | ingress | egress | parent CLASSID ]\n"
+ " tc filter show [ block BLOCK_INDEX ]\n"
+ "Where:\n"
+ "FILTER_TYPE := { rsvp | u32 | bpf | fw | route | etc. }\n"
+ "FILTERID := ... format depends on classifier, see there\n"
+ "OPTIONS := ... try tc filter add <desired FILTER_KIND> help\n");
+}
+
+static void chain_usage(void)
+{
+ fprintf(stderr,
+ "Usage: tc chain [ add | del | get | show ] [ dev STRING ]\n"
+ " tc chain [ add | del | get | show ] [ block BLOCK_INDEX ] ]\n");
+}
+
+struct tc_filter_req {
+ struct nlmsghdr n;
+ struct tcmsg t;
+ char buf[MAX_MSG];
+};
+
+static int tc_filter_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct tcmsg t;
+ char buf[MAX_MSG];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .t.tcm_family = AF_UNSPEC,
+ };
+ struct filter_util *q = NULL;
+ __u32 prio = 0;
+ __u32 protocol = 0;
+ int protocol_set = 0;
+ __u32 block_index = 0;
+ __u32 chain_index;
+ int chain_index_set = 0;
+ char *fhandle = NULL;
+ char d[IFNAMSIZ] = {};
+ char k[FILTER_NAMESZ] = {};
+ struct tc_estimator est = {};
+
+ if (cmd == RTM_NEWTFILTER && flags & NLM_F_CREATE)
+ protocol = htons(ETH_P_ALL);
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (d[0])
+ duparg("dev", *argv);
+ if (block_index) {
+ fprintf(stderr, "Error: \"dev\" and \"block\" are mutually exclusive\n");
+ return -1;
+ }
+ strncpy(d, *argv, sizeof(d)-1);
+ } else if (matches(*argv, "block") == 0) {
+ NEXT_ARG();
+ if (block_index)
+ duparg("block", *argv);
+ if (d[0]) {
+ fprintf(stderr, "Error: \"dev\" and \"block\" are mutually exclusive\n");
+ return -1;
+ }
+ if (get_u32(&block_index, *argv, 0) || !block_index)
+ invarg("invalid block index value", *argv);
+ } else if (strcmp(*argv, "root") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr,
+ "Error: \"root\" is duplicate parent ID\n");
+ return -1;
+ }
+ req.t.tcm_parent = TC_H_ROOT;
+ } else if (strcmp(*argv, "ingress") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr,
+ "Error: \"ingress\" is duplicate parent ID\n");
+ return -1;
+ }
+ req.t.tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ TC_H_MIN_INGRESS);
+ } else if (strcmp(*argv, "egress") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr,
+ "Error: \"egress\" is duplicate parent ID\n");
+ return -1;
+ }
+ req.t.tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ TC_H_MIN_EGRESS);
+ } else if (strcmp(*argv, "parent") == 0) {
+ __u32 handle;
+
+ NEXT_ARG();
+ if (req.t.tcm_parent)
+ duparg("parent", *argv);
+ if (get_tc_classid(&handle, *argv))
+ invarg("Invalid parent ID", *argv);
+ req.t.tcm_parent = handle;
+ } else if (strcmp(*argv, "handle") == 0) {
+ NEXT_ARG();
+ if (fhandle)
+ duparg("handle", *argv);
+ fhandle = *argv;
+ } else if (matches(*argv, "preference") == 0 ||
+ matches(*argv, "priority") == 0) {
+ NEXT_ARG();
+ if (prio)
+ duparg("priority", *argv);
+ if (get_u32(&prio, *argv, 0) || prio > 0xFFFF)
+ invarg("invalid priority value", *argv);
+ } else if (matches(*argv, "protocol") == 0) {
+ __u16 id;
+
+ NEXT_ARG();
+ if (protocol_set)
+ duparg("protocol", *argv);
+ if (ll_proto_a2n(&id, *argv))
+ invarg("invalid protocol", *argv);
+ protocol = id;
+ protocol_set = 1;
+ } else if (matches(*argv, "chain") == 0) {
+ NEXT_ARG();
+ if (chain_index_set)
+ duparg("chain", *argv);
+ if (get_u32(&chain_index, *argv, 0))
+ invarg("invalid chain index value", *argv);
+ chain_index_set = 1;
+ } else if (matches(*argv, "estimator") == 0) {
+ if (parse_estimator(&argc, &argv, &est) < 0)
+ return -1;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ return 0;
+ } else {
+ strncpy(k, *argv, sizeof(k)-1);
+
+ q = get_filter_kind(k);
+ argc--; argv++;
+ break;
+ }
+
+ argc--; argv++;
+ }
+
+ req.t.tcm_info = TC_H_MAKE(prio<<16, protocol);
+
+ if (chain_index_set)
+ addattr32(&req.n, sizeof(req), TCA_CHAIN, chain_index);
+
+ if (k[0])
+ addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
+
+ if (d[0]) {
+ ll_init_map(&rth);
+
+ req.t.tcm_ifindex = ll_name_to_index(d);
+ if (req.t.tcm_ifindex == 0) {
+ fprintf(stderr, "Cannot find device \"%s\"\n", d);
+ return 1;
+ }
+ } else if (block_index) {
+ req.t.tcm_ifindex = TCM_IFINDEX_MAGIC_BLOCK;
+ req.t.tcm_block_index = block_index;
+ }
+
+ if (q) {
+ if (q->parse_fopt(q, fhandle, argc, argv, &req.n))
+ return 1;
+ } else {
+ if (fhandle) {
+ fprintf(stderr,
+ "Must specify filter type when using \"handle\"\n");
+ return -1;
+ }
+ if (argc) {
+ if (matches(*argv, "help") == 0)
+ usage();
+ fprintf(stderr,
+ "Garbage instead of arguments \"%s ...\". Try \"tc filter help\".\n",
+ *argv);
+ return -1;
+ }
+ }
+
+ if (est.ewma_log)
+ addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0) {
+ fprintf(stderr, "We have an error talking to the kernel\n");
+ return 2;
+ }
+
+ return 0;
+}
+
+static __u32 filter_parent;
+static int filter_ifindex;
+static __u32 filter_prio;
+static __u32 filter_protocol;
+static __u32 filter_chain_index;
+static int filter_chain_index_set;
+static __u32 filter_block_index;
+__u16 f_proto;
+
+int print_filter(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct tcmsg *t = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[TCA_MAX+1];
+ struct filter_util *q;
+ char abuf[256];
+
+ if (n->nlmsg_type != RTM_NEWTFILTER &&
+ n->nlmsg_type != RTM_GETTFILTER &&
+ n->nlmsg_type != RTM_DELTFILTER &&
+ n->nlmsg_type != RTM_NEWCHAIN &&
+ n->nlmsg_type != RTM_GETCHAIN &&
+ n->nlmsg_type != RTM_DELCHAIN) {
+ fprintf(stderr, "Not a filter(cmd %d)\n", n->nlmsg_type);
+ return 0;
+ }
+ len -= NLMSG_LENGTH(sizeof(*t));
+ if (len < 0) {
+ fprintf(stderr, "Wrong len %d\n", len);
+ return -1;
+ }
+
+ parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len);
+
+ if (tb[TCA_KIND] == NULL && (n->nlmsg_type == RTM_NEWTFILTER ||
+ n->nlmsg_type == RTM_GETTFILTER ||
+ n->nlmsg_type == RTM_DELTFILTER)) {
+ fprintf(stderr, "print_filter: NULL kind\n");
+ return -1;
+ }
+
+ open_json_object(NULL);
+
+ if (n->nlmsg_type == RTM_DELTFILTER || n->nlmsg_type == RTM_DELCHAIN)
+ print_bool(PRINT_ANY, "deleted", "deleted ", true);
+
+ if ((n->nlmsg_type == RTM_NEWTFILTER ||
+ n->nlmsg_type == RTM_NEWCHAIN) &&
+ (n->nlmsg_flags & NLM_F_CREATE) &&
+ !(n->nlmsg_flags & NLM_F_EXCL))
+ print_bool(PRINT_ANY, "replaced", "replaced ", true);
+
+ if ((n->nlmsg_type == RTM_NEWTFILTER ||
+ n->nlmsg_type == RTM_NEWCHAIN) &&
+ (n->nlmsg_flags & NLM_F_CREATE) &&
+ (n->nlmsg_flags & NLM_F_EXCL))
+ print_bool(PRINT_ANY, "added", "added ", true);
+
+ if (n->nlmsg_type == RTM_NEWTFILTER ||
+ n->nlmsg_type == RTM_GETTFILTER ||
+ n->nlmsg_type == RTM_DELTFILTER)
+ print_string(PRINT_FP, NULL, "filter ", NULL);
+ else
+ print_string(PRINT_FP, NULL, "chain ", NULL);
+ if (t->tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK) {
+ if (!filter_block_index ||
+ filter_block_index != t->tcm_block_index)
+ print_uint(PRINT_ANY, "block", "block %u ",
+ t->tcm_block_index);
+ } else {
+ if (!filter_ifindex || filter_ifindex != t->tcm_ifindex)
+ print_devname(PRINT_ANY, t->tcm_ifindex);
+
+ if (!filter_parent || filter_parent != t->tcm_parent) {
+ if (t->tcm_parent == TC_H_ROOT)
+ print_bool(PRINT_ANY, "root", "root ", true);
+ else if (t->tcm_parent == TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS))
+ print_bool(PRINT_ANY, "ingress", "ingress ", true);
+ else if (t->tcm_parent == TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS))
+ print_bool(PRINT_ANY, "egress", "egress ", true);
+ else {
+ print_tc_classid(abuf, sizeof(abuf), t->tcm_parent);
+ print_string(PRINT_ANY, "parent", "parent %s ", abuf);
+ }
+ }
+ }
+
+ if (t->tcm_info && (n->nlmsg_type == RTM_NEWTFILTER ||
+ n->nlmsg_type == RTM_DELTFILTER ||
+ n->nlmsg_type == RTM_GETTFILTER)) {
+ f_proto = TC_H_MIN(t->tcm_info);
+ __u32 prio = TC_H_MAJ(t->tcm_info)>>16;
+
+ if (!filter_protocol || filter_protocol != f_proto) {
+ if (f_proto) {
+ SPRINT_BUF(b1);
+ print_string(PRINT_ANY, "protocol",
+ "protocol %s ",
+ ll_proto_n2a(f_proto, b1, sizeof(b1)));
+ }
+ }
+ if (!filter_prio || filter_prio != prio) {
+ if (prio)
+ print_uint(PRINT_ANY, "pref", "pref %u ", prio);
+ }
+ }
+ if (tb[TCA_KIND])
+ print_string(PRINT_ANY, "kind", "%s ", rta_getattr_str(tb[TCA_KIND]));
+
+ if (tb[TCA_CHAIN]) {
+ __u32 chain_index = rta_getattr_u32(tb[TCA_CHAIN]);
+
+ if (!filter_chain_index_set ||
+ filter_chain_index != chain_index)
+ print_uint(PRINT_ANY, "chain", "chain %u ",
+ chain_index);
+ }
+
+ if (tb[TCA_KIND]) {
+ q = get_filter_kind(RTA_DATA(tb[TCA_KIND]));
+ if (tb[TCA_OPTIONS]) {
+ open_json_object("options");
+ if (q)
+ q->print_fopt(q, fp, tb[TCA_OPTIONS], t->tcm_handle);
+ else
+ fprintf(stderr, "cannot parse option parameters\n");
+ close_json_object();
+ }
+ }
+ print_string(PRINT_FP, NULL, "\n", NULL);
+
+ if (show_stats && (tb[TCA_STATS] || tb[TCA_STATS2])) {
+ print_tcstats_attr(fp, tb, " ", NULL);
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ }
+
+ close_json_object();
+ fflush(fp);
+ return 0;
+}
+
+static int tc_filter_get(int cmd, unsigned int flags, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct tcmsg t;
+ char buf[MAX_MSG];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)),
+ /* NLM_F_ECHO is for backward compatibility. old kernels never
+ * respond without it and newer kernels will ignore it.
+ * In old kernels there is a side effect:
+ * In addition to a response to the GET you will receive an
+ * event (if you do tc mon).
+ */
+ .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ECHO | flags,
+ .n.nlmsg_type = cmd,
+ .t.tcm_parent = TC_H_UNSPEC,
+ .t.tcm_family = AF_UNSPEC,
+ };
+ struct nlmsghdr *answer;
+ struct filter_util *q = NULL;
+ __u32 prio = 0;
+ __u32 protocol = 0;
+ int protocol_set = 0;
+ __u32 chain_index;
+ int chain_index_set = 0;
+ __u32 block_index = 0;
+ __u32 parent_handle = 0;
+ char *fhandle = NULL;
+ char d[IFNAMSIZ] = {};
+ char k[FILTER_NAMESZ] = {};
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (d[0])
+ duparg("dev", *argv);
+ if (block_index) {
+ fprintf(stderr, "Error: \"dev\" and \"block\" are mutually exclusive\n");
+ return -1;
+ }
+ strncpy(d, *argv, sizeof(d)-1);
+ } else if (matches(*argv, "block") == 0) {
+ NEXT_ARG();
+ if (block_index)
+ duparg("block", *argv);
+ if (d[0]) {
+ fprintf(stderr, "Error: \"dev\" and \"block\" are mutually exclusive\n");
+ return -1;
+ }
+ if (get_u32(&block_index, *argv, 0) || !block_index)
+ invarg("invalid block index value", *argv);
+ } else if (strcmp(*argv, "root") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr,
+ "Error: \"root\" is duplicate parent ID\n");
+ return -1;
+ }
+ req.t.tcm_parent = TC_H_ROOT;
+ } else if (strcmp(*argv, "ingress") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr,
+ "Error: \"ingress\" is duplicate parent ID\n");
+ return -1;
+ }
+ req.t.tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ TC_H_MIN_INGRESS);
+ } else if (strcmp(*argv, "egress") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr,
+ "Error: \"egress\" is duplicate parent ID\n");
+ return -1;
+ }
+ req.t.tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ TC_H_MIN_EGRESS);
+ } else if (strcmp(*argv, "parent") == 0) {
+
+ NEXT_ARG();
+ if (req.t.tcm_parent)
+ duparg("parent", *argv);
+ if (get_tc_classid(&parent_handle, *argv))
+ invarg("Invalid parent ID", *argv);
+ req.t.tcm_parent = parent_handle;
+ } else if (strcmp(*argv, "handle") == 0) {
+ NEXT_ARG();
+ if (fhandle)
+ duparg("handle", *argv);
+ fhandle = *argv;
+ } else if (matches(*argv, "preference") == 0 ||
+ matches(*argv, "priority") == 0) {
+ NEXT_ARG();
+ if (prio)
+ duparg("priority", *argv);
+ if (get_u32(&prio, *argv, 0) || prio > 0xFFFF)
+ invarg("invalid priority value", *argv);
+ } else if (matches(*argv, "protocol") == 0) {
+ __u16 id;
+
+ NEXT_ARG();
+ if (protocol_set)
+ duparg("protocol", *argv);
+ if (ll_proto_a2n(&id, *argv))
+ invarg("invalid protocol", *argv);
+ protocol = id;
+ protocol_set = 1;
+ } else if (matches(*argv, "chain") == 0) {
+ NEXT_ARG();
+ if (chain_index_set)
+ duparg("chain", *argv);
+ if (get_u32(&chain_index, *argv, 0))
+ invarg("invalid chain index value", *argv);
+ chain_index_set = 1;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ return 0;
+ } else {
+ if (!**argv)
+ invarg("invalid filter name", *argv);
+
+ strncpy(k, *argv, sizeof(k)-1);
+
+ q = get_filter_kind(k);
+ argc--; argv++;
+ break;
+ }
+
+ argc--; argv++;
+ }
+
+ if (cmd == RTM_GETTFILTER) {
+ if (!protocol_set) {
+ fprintf(stderr, "Must specify filter protocol\n");
+ return -1;
+ }
+
+ if (!prio) {
+ fprintf(stderr, "Must specify filter priority\n");
+ return -1;
+ }
+
+ req.t.tcm_info = TC_H_MAKE(prio<<16, protocol);
+ }
+
+ if (chain_index_set)
+ addattr32(&req.n, sizeof(req), TCA_CHAIN, chain_index);
+
+ if (req.t.tcm_parent == TC_H_UNSPEC) {
+ fprintf(stderr, "Must specify filter parent\n");
+ return -1;
+ }
+
+ if (cmd == RTM_GETTFILTER) {
+ if (k[0])
+ addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
+ else {
+ fprintf(stderr, "Must specify filter type\n");
+ return -1;
+ }
+ }
+
+ if (d[0]) {
+ ll_init_map(&rth);
+
+ req.t.tcm_ifindex = ll_name_to_index(d);
+ if (!req.t.tcm_ifindex)
+ return -nodev(d);
+ filter_ifindex = req.t.tcm_ifindex;
+ } else if (block_index) {
+ req.t.tcm_ifindex = TCM_IFINDEX_MAGIC_BLOCK;
+ req.t.tcm_block_index = block_index;
+ filter_block_index = block_index;
+ } else {
+ fprintf(stderr, "Must specify netdevice \"dev\" or block index \"block\"\n");
+ return -1;
+ }
+
+ if (cmd == RTM_GETTFILTER &&
+ q->parse_fopt(q, fhandle, argc, argv, &req.n))
+ return 1;
+
+ if (!fhandle && cmd == RTM_GETTFILTER) {
+ fprintf(stderr, "Must specify filter \"handle\"\n");
+ return -1;
+ }
+
+ if (argc) {
+ if (matches(*argv, "help") == 0)
+ usage();
+ fprintf(stderr,
+ "Garbage instead of arguments \"%s ...\". Try \"tc filter help\".\n",
+ *argv);
+ return -1;
+ }
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0) {
+ fprintf(stderr, "We have an error talking to the kernel\n");
+ return 2;
+ }
+
+ new_json_obj(json);
+ print_filter(answer, (void *)stdout);
+ delete_json_obj();
+
+ free(answer);
+ return 0;
+}
+
+static int tc_filter_list(int cmd, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct tcmsg t;
+ char buf[MAX_MSG];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)),
+ .n.nlmsg_type = cmd,
+ .t.tcm_parent = TC_H_UNSPEC,
+ .t.tcm_family = AF_UNSPEC,
+ };
+ char d[IFNAMSIZ] = {};
+ __u32 prio = 0;
+ __u32 protocol = 0;
+ __u32 chain_index;
+ __u32 block_index = 0;
+ char *fhandle = NULL;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (d[0])
+ duparg("dev", *argv);
+ if (block_index) {
+ fprintf(stderr, "Error: \"dev\" cannot be used in the same time as \"block\"\n");
+ return -1;
+ }
+ strncpy(d, *argv, sizeof(d)-1);
+ } else if (matches(*argv, "block") == 0) {
+ NEXT_ARG();
+ if (block_index)
+ duparg("block", *argv);
+ if (d[0]) {
+ fprintf(stderr, "Error: \"block\" cannot be used in the same time as \"dev\"\n");
+ return -1;
+ }
+ if (get_u32(&block_index, *argv, 0) || !block_index)
+ invarg("invalid block index value", *argv);
+ } else if (strcmp(*argv, "root") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr,
+ "Error: \"root\" is duplicate parent ID\n");
+ return -1;
+ }
+ filter_parent = req.t.tcm_parent = TC_H_ROOT;
+ } else if (strcmp(*argv, "ingress") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr,
+ "Error: \"ingress\" is duplicate parent ID\n");
+ return -1;
+ }
+ filter_parent = TC_H_MAKE(TC_H_CLSACT,
+ TC_H_MIN_INGRESS);
+ req.t.tcm_parent = filter_parent;
+ } else if (strcmp(*argv, "egress") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr,
+ "Error: \"egress\" is duplicate parent ID\n");
+ return -1;
+ }
+ filter_parent = TC_H_MAKE(TC_H_CLSACT,
+ TC_H_MIN_EGRESS);
+ req.t.tcm_parent = filter_parent;
+ } else if (strcmp(*argv, "parent") == 0) {
+ __u32 handle;
+
+ NEXT_ARG();
+ if (req.t.tcm_parent)
+ duparg("parent", *argv);
+ if (get_tc_classid(&handle, *argv))
+ invarg("invalid parent ID", *argv);
+ filter_parent = req.t.tcm_parent = handle;
+ } else if (strcmp(*argv, "handle") == 0) {
+ NEXT_ARG();
+ if (fhandle)
+ duparg("handle", *argv);
+ fhandle = *argv;
+ } else if (matches(*argv, "preference") == 0 ||
+ matches(*argv, "priority") == 0) {
+ NEXT_ARG();
+ if (prio)
+ duparg("priority", *argv);
+ if (get_u32(&prio, *argv, 0))
+ invarg("invalid preference", *argv);
+ filter_prio = prio;
+ } else if (matches(*argv, "protocol") == 0) {
+ __u16 res;
+
+ NEXT_ARG();
+ if (protocol)
+ duparg("protocol", *argv);
+ if (ll_proto_a2n(&res, *argv))
+ invarg("invalid protocol", *argv);
+ protocol = res;
+ filter_protocol = protocol;
+ } else if (matches(*argv, "chain") == 0) {
+ NEXT_ARG();
+ if (filter_chain_index_set)
+ duparg("chain", *argv);
+ if (get_u32(&chain_index, *argv, 0))
+ invarg("invalid chain index value", *argv);
+ filter_chain_index_set = 1;
+ filter_chain_index = chain_index;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ fprintf(stderr,
+ " What is \"%s\"? Try \"tc filter help\"\n",
+ *argv);
+ return -1;
+ }
+
+ argc--; argv++;
+ }
+
+ req.t.tcm_info = TC_H_MAKE(prio<<16, protocol);
+
+ ll_init_map(&rth);
+
+ if (d[0]) {
+ req.t.tcm_ifindex = ll_name_to_index(d);
+ if (!req.t.tcm_ifindex)
+ return -nodev(d);
+ filter_ifindex = req.t.tcm_ifindex;
+ } else if (block_index) {
+ if (!tc_qdisc_block_exists(block_index)) {
+ fprintf(stderr, "Cannot find block \"%u\"\n", block_index);
+ return 1;
+ }
+ req.t.tcm_ifindex = TCM_IFINDEX_MAGIC_BLOCK;
+ req.t.tcm_block_index = block_index;
+ filter_block_index = block_index;
+ }
+
+ if (filter_chain_index_set)
+ addattr32(&req.n, sizeof(req), TCA_CHAIN, chain_index);
+
+ if (rtnl_dump_request_n(&rth, &req.n) < 0) {
+ perror("Cannot send dump request");
+ return 1;
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_filter, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ return 1;
+ }
+ delete_json_obj();
+
+ return 0;
+}
+
+int do_filter(int argc, char **argv)
+{
+ if (argc < 1)
+ return tc_filter_list(RTM_GETTFILTER, 0, NULL);
+ if (matches(*argv, "add") == 0)
+ return tc_filter_modify(RTM_NEWTFILTER, NLM_F_EXCL|NLM_F_CREATE,
+ argc-1, argv+1);
+ if (matches(*argv, "change") == 0)
+ return tc_filter_modify(RTM_NEWTFILTER, 0, argc-1, argv+1);
+ if (matches(*argv, "replace") == 0)
+ return tc_filter_modify(RTM_NEWTFILTER, NLM_F_CREATE, argc-1,
+ argv+1);
+ if (matches(*argv, "delete") == 0)
+ return tc_filter_modify(RTM_DELTFILTER, 0, argc-1, argv+1);
+ if (matches(*argv, "get") == 0)
+ return tc_filter_get(RTM_GETTFILTER, 0, argc-1, argv+1);
+ if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+ || matches(*argv, "lst") == 0)
+ return tc_filter_list(RTM_GETTFILTER, argc-1, argv+1);
+ if (matches(*argv, "help") == 0) {
+ usage();
+ return 0;
+ }
+ fprintf(stderr, "Command \"%s\" is unknown, try \"tc filter help\".\n",
+ *argv);
+ return -1;
+}
+
+int do_chain(int argc, char **argv)
+{
+ if (argc < 1)
+ return tc_filter_list(RTM_GETCHAIN, 0, NULL);
+ if (matches(*argv, "add") == 0) {
+ return tc_filter_modify(RTM_NEWCHAIN, NLM_F_EXCL | NLM_F_CREATE,
+ argc - 1, argv + 1);
+ } else if (matches(*argv, "delete") == 0) {
+ return tc_filter_modify(RTM_DELCHAIN, 0,
+ argc - 1, argv + 1);
+ } else if (matches(*argv, "get") == 0) {
+ return tc_filter_get(RTM_GETCHAIN, 0,
+ argc - 1, argv + 1);
+ } else if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0) {
+ return tc_filter_list(RTM_GETCHAIN, argc - 1, argv + 1);
+ } else if (matches(*argv, "help") == 0) {
+ chain_usage();
+ return 0;
+ }
+ fprintf(stderr, "Command \"%s\" is unknown, try \"tc chain help\".\n",
+ *argv);
+ return -1;
+}
diff --git a/tc/tc_monitor.c b/tc/tc_monitor.c
new file mode 100644
index 0000000..f8816cc
--- /dev/null
+++ b/tc/tc_monitor.c
@@ -0,0 +1,120 @@
+/*
+ * tc_monitor.c "tc 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: Jamal Hadi Salim
+ *
+ */
+
+#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 "rt_names.h"
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: tc [-timestamp [-tshort] monitor\n");
+ exit(-1);
+}
+
+
+static int accept_tcmsg(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+
+ if (timestamp)
+ print_timestamp(fp);
+
+ if (n->nlmsg_type == RTM_NEWTFILTER ||
+ n->nlmsg_type == RTM_DELTFILTER ||
+ n->nlmsg_type == RTM_NEWCHAIN ||
+ n->nlmsg_type == RTM_DELCHAIN) {
+ print_filter(n, arg);
+ return 0;
+ }
+ if (n->nlmsg_type == RTM_NEWTCLASS || n->nlmsg_type == RTM_DELTCLASS) {
+ print_class(n, arg);
+ return 0;
+ }
+ if (n->nlmsg_type == RTM_NEWQDISC || n->nlmsg_type == RTM_DELQDISC) {
+ print_qdisc(n, arg);
+ return 0;
+ }
+ if (n->nlmsg_type == RTM_GETACTION || n->nlmsg_type == RTM_NEWACTION ||
+ n->nlmsg_type == RTM_DELACTION) {
+ print_action(n, arg);
+ return 0;
+ }
+ if (n->nlmsg_type != NLMSG_ERROR && n->nlmsg_type != NLMSG_NOOP &&
+ n->nlmsg_type != NLMSG_DONE) {
+ fprintf(fp, "Unknown message: length %08d type %08x flags %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ }
+ return 0;
+}
+
+int do_tcmonitor(int argc, char **argv)
+{
+ struct rtnl_handle rth;
+ char *file = NULL;
+ unsigned int groups = nl_mgrp(RTNLGRP_TC);
+
+ while (argc > 0) {
+ if (matches(*argv, "file") == 0) {
+ NEXT_ARG();
+ file = *argv;
+ } else {
+ if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ fprintf(stderr, "Argument \"%s\" is unknown, try \"tc monitor help\".\n", *argv);
+ exit(-1);
+ }
+ }
+ argc--; argv++;
+ }
+
+ if (file) {
+ FILE *fp = fopen(file, "r");
+ int ret;
+
+ if (fp == NULL) {
+ perror("Cannot fopen");
+ exit(-1);
+ }
+
+ ret = rtnl_from_file(fp, accept_tcmsg, stdout);
+ fclose(fp);
+ return ret;
+ }
+
+ if (rtnl_open(&rth, groups) < 0)
+ exit(1);
+
+ ll_init_map(&rth);
+
+ if (rtnl_listen(&rth, accept_tcmsg, (void *)stdout) < 0) {
+ rtnl_close(&rth);
+ exit(2);
+ }
+
+ rtnl_close(&rth);
+ exit(0);
+}
diff --git a/tc/tc_qdisc.c b/tc/tc_qdisc.c
new file mode 100644
index 0000000..17e3998
--- /dev/null
+++ b/tc/tc_qdisc.c
@@ -0,0 +1,503 @@
+/*
+ * tc_qdisc.c "tc qdisc".
+ *
+ * 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>
+ * J Hadi Salim: Extension to ingress
+ */
+
+#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 <math.h>
+#include <malloc.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+static int usage(void)
+{
+ fprintf(stderr,
+ "Usage: tc qdisc [ add | del | replace | change | show ] dev STRING\n"
+ " [ handle QHANDLE ] [ root | ingress | clsact | parent CLASSID ]\n"
+ " [ estimator INTERVAL TIME_CONSTANT ]\n"
+ " [ stab [ help | STAB_OPTIONS] ]\n"
+ " [ ingress_block BLOCK_INDEX ] [ egress_block BLOCK_INDEX ]\n"
+ " [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n"
+ "\n"
+ " tc qdisc show [ dev STRING ] [ ingress | clsact ] [ invisible ]\n"
+ "Where:\n"
+ "QDISC_KIND := { [p|b]fifo | tbf | prio | cbq | red | etc. }\n"
+ "OPTIONS := ... try tc qdisc add <desired QDISC_KIND> help\n"
+ "STAB_OPTIONS := ... try tc qdisc add stab help\n");
+ return -1;
+}
+
+static int tc_qdisc_modify(int cmd, unsigned int flags, int argc, char **argv)
+{
+ struct qdisc_util *q = NULL;
+ struct tc_estimator est = {};
+ struct {
+ struct tc_sizespec szopts;
+ __u16 *data;
+ } stab = {};
+ char d[IFNAMSIZ] = {};
+ char k[FILTER_NAMESZ] = {};
+ struct {
+ struct nlmsghdr n;
+ struct tcmsg t;
+ char buf[TCA_BUF_MAX];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .t.tcm_family = AF_UNSPEC,
+ };
+ __u32 ingress_block = 0;
+ __u32 egress_block = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (d[0])
+ duparg("dev", *argv);
+ strncpy(d, *argv, sizeof(d)-1);
+ } else if (strcmp(*argv, "handle") == 0) {
+ __u32 handle;
+
+ if (req.t.tcm_handle)
+ duparg("handle", *argv);
+ NEXT_ARG();
+ if (get_qdisc_handle(&handle, *argv))
+ invarg("invalid qdisc ID", *argv);
+ req.t.tcm_handle = handle;
+ } else if (strcmp(*argv, "root") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr, "Error: \"root\" is duplicate parent ID\n");
+ return -1;
+ }
+ req.t.tcm_parent = TC_H_ROOT;
+ } else if (strcmp(*argv, "clsact") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr, "Error: \"clsact\" is a duplicate parent ID\n");
+ return -1;
+ }
+ req.t.tcm_parent = TC_H_CLSACT;
+ strncpy(k, "clsact", sizeof(k) - 1);
+ q = get_qdisc_kind(k);
+ req.t.tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0);
+ NEXT_ARG_FWD();
+ break;
+ } else if (strcmp(*argv, "ingress") == 0) {
+ if (req.t.tcm_parent) {
+ fprintf(stderr, "Error: \"ingress\" is a duplicate parent ID\n");
+ return -1;
+ }
+ req.t.tcm_parent = TC_H_INGRESS;
+ strncpy(k, "ingress", sizeof(k) - 1);
+ q = get_qdisc_kind(k);
+ req.t.tcm_handle = TC_H_MAKE(TC_H_INGRESS, 0);
+ NEXT_ARG_FWD();
+ break;
+ } else if (strcmp(*argv, "parent") == 0) {
+ __u32 handle;
+
+ NEXT_ARG();
+ if (req.t.tcm_parent)
+ duparg("parent", *argv);
+ if (get_tc_classid(&handle, *argv))
+ invarg("invalid parent ID", *argv);
+ req.t.tcm_parent = handle;
+ } else if (matches(*argv, "estimator") == 0) {
+ if (parse_estimator(&argc, &argv, &est))
+ return -1;
+ } else if (matches(*argv, "stab") == 0) {
+ if (parse_size_table(&argc, &argv, &stab.szopts) < 0)
+ return -1;
+ continue;
+ } else if (matches(*argv, "ingress_block") == 0) {
+ NEXT_ARG();
+ if (get_u32(&ingress_block, *argv, 0) || !ingress_block)
+ invarg("invalid ingress block index value", *argv);
+ } else if (matches(*argv, "egress_block") == 0) {
+ NEXT_ARG();
+ if (get_u32(&egress_block, *argv, 0) || !egress_block)
+ invarg("invalid egress block index value", *argv);
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ strncpy(k, *argv, sizeof(k)-1);
+
+ q = get_qdisc_kind(k);
+ argc--; argv++;
+ break;
+ }
+ argc--; argv++;
+ }
+
+ if (k[0])
+ addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
+ if (est.ewma_log)
+ addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
+
+ if (ingress_block)
+ addattr32(&req.n, sizeof(req),
+ TCA_INGRESS_BLOCK, ingress_block);
+ if (egress_block)
+ addattr32(&req.n, sizeof(req),
+ TCA_EGRESS_BLOCK, egress_block);
+
+ if (q) {
+ if (q->parse_qopt) {
+ if (q->parse_qopt(q, argc, argv, &req.n, d))
+ return 1;
+ } else if (argc) {
+ fprintf(stderr, "qdisc '%s' does not support option parsing\n", k);
+ return -1;
+ }
+ } else {
+ if (argc) {
+ if (matches(*argv, "help") == 0)
+ usage();
+
+ fprintf(stderr, "Garbage instead of arguments \"%s ...\". Try \"tc qdisc help\".\n", *argv);
+ return -1;
+ }
+ }
+
+ if (check_size_table_opts(&stab.szopts)) {
+ struct rtattr *tail;
+
+ if (tc_calc_size_table(&stab.szopts, &stab.data) < 0) {
+ fprintf(stderr, "failed to calculate size table.\n");
+ return -1;
+ }
+
+ tail = addattr_nest(&req.n, sizeof(req), TCA_STAB);
+ addattr_l(&req.n, sizeof(req), TCA_STAB_BASE, &stab.szopts,
+ sizeof(stab.szopts));
+ if (stab.data)
+ addattr_l(&req.n, sizeof(req), TCA_STAB_DATA, stab.data,
+ stab.szopts.tsize * sizeof(__u16));
+ addattr_nest_end(&req.n, tail);
+ if (stab.data)
+ free(stab.data);
+ }
+
+ if (d[0]) {
+ int idx;
+
+ ll_init_map(&rth);
+
+ idx = ll_name_to_index(d);
+ if (!idx)
+ return -nodev(d);
+ req.t.tcm_ifindex = idx;
+ }
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return 2;
+
+ return 0;
+}
+
+static int filter_ifindex;
+
+int print_qdisc(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = (FILE *)arg;
+ struct tcmsg *t = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[TCA_MAX+1];
+ struct qdisc_util *q;
+ char abuf[256];
+
+ if (n->nlmsg_type != RTM_NEWQDISC && n->nlmsg_type != RTM_DELQDISC) {
+ fprintf(stderr, "Not a qdisc\n");
+ return 0;
+ }
+ len -= NLMSG_LENGTH(sizeof(*t));
+ if (len < 0) {
+ fprintf(stderr, "Wrong len %d\n", len);
+ return -1;
+ }
+
+ if (filter_ifindex && filter_ifindex != t->tcm_ifindex)
+ return 0;
+
+ parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len);
+
+ if (tb[TCA_KIND] == NULL) {
+ fprintf(stderr, "print_qdisc: NULL kind\n");
+ return -1;
+ }
+
+ open_json_object(NULL);
+
+ if (n->nlmsg_type == RTM_DELQDISC)
+ print_bool(PRINT_ANY, "deleted", "deleted ", true);
+
+ if (n->nlmsg_type == RTM_NEWQDISC &&
+ (n->nlmsg_flags & NLM_F_CREATE) &&
+ (n->nlmsg_flags & NLM_F_REPLACE))
+ print_bool(PRINT_ANY, "replaced", "replaced ", true);
+
+ if (n->nlmsg_type == RTM_NEWQDISC &&
+ (n->nlmsg_flags & NLM_F_CREATE) &&
+ (n->nlmsg_flags & NLM_F_EXCL))
+ print_bool(PRINT_ANY, "added", "added ", true);
+
+ print_string(PRINT_ANY, "kind", "qdisc %s",
+ rta_getattr_str(tb[TCA_KIND]));
+ sprintf(abuf, "%x:", t->tcm_handle >> 16);
+ print_string(PRINT_ANY, "handle", " %s", abuf);
+ if (show_raw) {
+ sprintf(abuf, "[%08x]", t->tcm_handle);
+ print_string(PRINT_FP, NULL, "%s", abuf);
+ }
+ print_string(PRINT_FP, NULL, " ", NULL);
+
+ if (filter_ifindex == 0)
+ print_devname(PRINT_ANY, t->tcm_ifindex);
+
+ if (t->tcm_parent == TC_H_ROOT)
+ print_bool(PRINT_ANY, "root", "root ", true);
+ else if (t->tcm_parent) {
+ print_tc_classid(abuf, sizeof(abuf), t->tcm_parent);
+ print_string(PRINT_ANY, "parent", "parent %s ", abuf);
+ }
+
+ if (t->tcm_info != 1)
+ print_uint(PRINT_ANY, "refcnt", "refcnt %u ", t->tcm_info);
+
+ if (tb[TCA_HW_OFFLOAD] &&
+ (rta_getattr_u8(tb[TCA_HW_OFFLOAD])))
+ print_bool(PRINT_ANY, "offloaded", "offloaded ", true);
+
+ if (tb[TCA_INGRESS_BLOCK] &&
+ RTA_PAYLOAD(tb[TCA_INGRESS_BLOCK]) >= sizeof(__u32)) {
+ __u32 block = rta_getattr_u32(tb[TCA_INGRESS_BLOCK]);
+
+ if (block)
+ print_uint(PRINT_ANY, "ingress_block",
+ "ingress_block %u ", block);
+ }
+
+ if (tb[TCA_EGRESS_BLOCK] &&
+ RTA_PAYLOAD(tb[TCA_EGRESS_BLOCK]) >= sizeof(__u32)) {
+ __u32 block = rta_getattr_u32(tb[TCA_EGRESS_BLOCK]);
+
+ if (block)
+ print_uint(PRINT_ANY, "egress_block",
+ "egress_block %u ", block);
+ }
+
+ /* pfifo_fast is generic enough to warrant the hardcoding --JHS */
+ if (strcmp("pfifo_fast", RTA_DATA(tb[TCA_KIND])) == 0)
+ q = get_qdisc_kind("prio");
+ else
+ q = get_qdisc_kind(RTA_DATA(tb[TCA_KIND]));
+
+ open_json_object("options");
+ if (tb[TCA_OPTIONS]) {
+ if (q)
+ q->print_qopt(q, fp, tb[TCA_OPTIONS]);
+ else
+ fprintf(stderr, "Cannot parse qdisc parameters\n");
+ }
+ close_json_object();
+
+ print_string(PRINT_FP, NULL, "\n", NULL);
+
+ if (show_details && tb[TCA_STAB]) {
+ print_size_table(fp, " ", tb[TCA_STAB]);
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ }
+
+ if (show_stats) {
+ struct rtattr *xstats = NULL;
+
+ if (tb[TCA_STATS] || tb[TCA_STATS2] || tb[TCA_XSTATS]) {
+ print_tcstats_attr(fp, tb, " ", &xstats);
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ }
+
+ if (q && xstats && q->print_xstats) {
+ q->print_xstats(q, fp, xstats);
+ print_string(PRINT_FP, NULL, "\n", NULL);
+ }
+ }
+ close_json_object();
+ fflush(fp);
+ return 0;
+}
+
+static int tc_qdisc_list(int argc, char **argv)
+{
+ struct tcmsg t = { .tcm_family = AF_UNSPEC };
+ char d[IFNAMSIZ] = {};
+ bool dump_invisible = false;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ strncpy(d, *argv, sizeof(d)-1);
+ } else if (strcmp(*argv, "ingress") == 0 ||
+ strcmp(*argv, "clsact") == 0) {
+ if (t.tcm_parent) {
+ fprintf(stderr, "Duplicate parent ID\n");
+ usage();
+ }
+ t.tcm_parent = TC_H_INGRESS;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else if (strcmp(*argv, "invisible") == 0) {
+ dump_invisible = true;
+ } else {
+ fprintf(stderr, "What is \"%s\"? Try \"tc qdisc help\".\n", *argv);
+ return -1;
+ }
+
+ argc--; argv++;
+ }
+
+ ll_init_map(&rth);
+
+ if (d[0]) {
+ t.tcm_ifindex = ll_name_to_index(d);
+ if (!t.tcm_ifindex)
+ return -nodev(d);
+ filter_ifindex = t.tcm_ifindex;
+ }
+
+ if (dump_invisible) {
+ struct {
+ struct nlmsghdr n;
+ struct tcmsg t;
+ char buf[256];
+ } req = {
+ .n.nlmsg_type = RTM_GETQDISC,
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)),
+ };
+
+ req.t.tcm_family = AF_UNSPEC;
+
+ addattr(&req.n, 256, TCA_DUMP_INVISIBLE);
+ if (rtnl_dump_request_n(&rth, &req.n) < 0) {
+ perror("Cannot send dump request");
+ return 1;
+ }
+
+ } else if (rtnl_dump_request(&rth, RTM_GETQDISC, &t, sizeof(t)) < 0) {
+ perror("Cannot send dump request");
+ return 1;
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_qdisc, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ return 1;
+ }
+ delete_json_obj();
+
+ return 0;
+}
+
+int do_qdisc(int argc, char **argv)
+{
+ if (argc < 1)
+ return tc_qdisc_list(0, NULL);
+ if (matches(*argv, "add") == 0)
+ return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1);
+ if (matches(*argv, "change") == 0)
+ return tc_qdisc_modify(RTM_NEWQDISC, 0, argc-1, argv+1);
+ if (matches(*argv, "replace") == 0)
+ return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
+ if (matches(*argv, "link") == 0)
+ return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_REPLACE, argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return tc_qdisc_modify(RTM_DELQDISC, 0, argc-1, argv+1);
+#if 0
+ if (matches(*argv, "get") == 0)
+ return tc_qdisc_get(RTM_GETQDISC, 0, argc-1, argv+1);
+#endif
+ if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
+ || matches(*argv, "lst") == 0)
+ return tc_qdisc_list(argc-1, argv+1);
+ if (matches(*argv, "help") == 0) {
+ usage();
+ return 0;
+ }
+ fprintf(stderr, "Command \"%s\" is unknown, try \"tc qdisc help\".\n", *argv);
+ return -1;
+}
+
+struct tc_qdisc_block_exists_ctx {
+ __u32 block_index;
+ bool found;
+};
+
+static int tc_qdisc_block_exists_cb(struct nlmsghdr *n, void *arg)
+{
+ struct tc_qdisc_block_exists_ctx *ctx = arg;
+ struct tcmsg *t = NLMSG_DATA(n);
+ struct rtattr *tb[TCA_MAX+1];
+ int len = n->nlmsg_len;
+
+ if (n->nlmsg_type != RTM_NEWQDISC)
+ return 0;
+
+ len -= NLMSG_LENGTH(sizeof(*t));
+ if (len < 0)
+ return -1;
+
+ parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len);
+
+ if (tb[TCA_KIND] == NULL)
+ return -1;
+
+ if (tb[TCA_INGRESS_BLOCK] &&
+ RTA_PAYLOAD(tb[TCA_INGRESS_BLOCK]) >= sizeof(__u32)) {
+ __u32 block = rta_getattr_u32(tb[TCA_INGRESS_BLOCK]);
+
+ if (block == ctx->block_index)
+ ctx->found = true;
+ }
+
+ if (tb[TCA_EGRESS_BLOCK] &&
+ RTA_PAYLOAD(tb[TCA_EGRESS_BLOCK]) >= sizeof(__u32)) {
+ __u32 block = rta_getattr_u32(tb[TCA_EGRESS_BLOCK]);
+
+ if (block == ctx->block_index)
+ ctx->found = true;
+ }
+ return 0;
+}
+
+bool tc_qdisc_block_exists(__u32 block_index)
+{
+ struct tc_qdisc_block_exists_ctx ctx = { .block_index = block_index };
+ struct tcmsg t = { .tcm_family = AF_UNSPEC };
+
+ if (rtnl_dump_request(&rth, RTM_GETQDISC, &t, sizeof(t)) < 0) {
+ perror("Cannot send dump request");
+ return false;
+ }
+
+ if (rtnl_dump_filter(&rth, tc_qdisc_block_exists_cb, &ctx) < 0) {
+ perror("Dump terminated\n");
+ return false;
+ }
+
+ return ctx.found;
+}
diff --git a/tc/tc_red.c b/tc/tc_red.c
new file mode 100644
index 0000000..681ca29
--- /dev/null
+++ b/tc/tc_red.c
@@ -0,0 +1,119 @@
+/*
+ * tc_red.c RED maintenance 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 <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "utils.h"
+#include "tc_core.h"
+#include "tc_util.h"
+#include "tc_red.h"
+
+/*
+ Plog = log(prob/(qmax - qmin))
+ */
+int tc_red_eval_P(unsigned int qmin, unsigned int qmax, double prob)
+{
+ int i = qmax - qmin;
+
+ if (!i)
+ return 0;
+ if (i < 0)
+ return -1;
+
+ prob /= i;
+
+ for (i = 0; i < 32; i++) {
+ if (prob > 1.0)
+ break;
+ prob *= 2;
+ }
+ if (i >= 32)
+ return -1;
+ return i;
+}
+
+/*
+ burst + 1 - qmin/avpkt < (1-(1-W)^burst)/W
+ */
+
+int tc_red_eval_ewma(unsigned int qmin, unsigned int burst, unsigned int avpkt)
+{
+ int wlog = 1;
+ double W = 0.5;
+ double a = (double)burst + 1 - (double)qmin/avpkt;
+
+ if (a < 1.0) {
+ fprintf(stderr, "tc_red_eval_ewma() burst %u is too small ? Try burst %u\n",
+ burst, 1 + qmin/avpkt);
+ return -1;
+ }
+ for (wlog = 1; wlog < 32; wlog++, W /= 2) {
+ if (a <= (1 - pow(1-W, burst))/W)
+ return wlog;
+ }
+ return -1;
+}
+
+/*
+ Stab[t>>Scell_log] = -log(1-W) * t/xmit_time
+ */
+
+int tc_red_eval_idle_damping(int Wlog, unsigned int avpkt, unsigned int bps, __u8 *sbuf)
+{
+ double xmit_time = tc_calc_xmittime(bps, avpkt);
+ double lW = -log(1.0 - 1.0/(1<<Wlog))/xmit_time;
+ double maxtime = 31/lW;
+ int clog;
+ int i;
+
+ for (clog = 0; clog < 32; clog++) {
+ if (maxtime/(1<<clog) < 512)
+ break;
+ }
+ if (clog >= 32)
+ return -1;
+
+ sbuf[0] = 0;
+ for (i = 1; i < 255; i++) {
+ sbuf[i] = (i<<clog)*lW;
+ if (sbuf[i] > 31)
+ sbuf[i] = 31;
+ }
+ sbuf[255] = 31;
+ return clog;
+}
+
+void tc_red_print_flags(__u32 flags)
+{
+ if (flags & TC_RED_ECN)
+ print_bool(PRINT_ANY, "ecn", "ecn ", true);
+ else
+ print_bool(PRINT_ANY, "ecn", NULL, false);
+
+ if (flags & TC_RED_HARDDROP)
+ print_bool(PRINT_ANY, "harddrop", "harddrop ", true);
+ else
+ print_bool(PRINT_ANY, "harddrop", NULL, false);
+
+ if (flags & TC_RED_ADAPTATIVE)
+ print_bool(PRINT_ANY, "adaptive", "adaptive ", true);
+ else
+ print_bool(PRINT_ANY, "adaptive", NULL, false);
+}
diff --git a/tc/tc_red.h b/tc/tc_red.h
new file mode 100644
index 0000000..3882c83
--- /dev/null
+++ b/tc/tc_red.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _TC_RED_H_
+#define _TC_RED_H_ 1
+
+int tc_red_eval_P(unsigned qmin, unsigned qmax, double prob);
+int tc_red_eval_ewma(unsigned qmin, unsigned burst, unsigned avpkt);
+int tc_red_eval_idle_damping(int wlog, unsigned avpkt, unsigned bandwidth,
+ __u8 *sbuf);
+void tc_red_print_flags(__u32 flags);
+
+#endif
diff --git a/tc/tc_stab.c b/tc/tc_stab.c
new file mode 100644
index 0000000..c0f1f16
--- /dev/null
+++ b/tc/tc_stab.c
@@ -0,0 +1,149 @@
+/*
+ * tc_stab.c "tc qdisc ... stab *".
+ *
+ * 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: Jussi Kivilinna, <jussi.kivilinna@mbnet.fi>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <malloc.h>
+
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_core.h"
+#include "tc_common.h"
+
+static void stab_help(void)
+{
+ fprintf(stderr,
+ "Usage: ... stab [ mtu BYTES ] [ tsize SLOTS ] [ mpu BYTES ]\n"
+ " [ overhead BYTES ] [ linklayer TYPE ] ...\n"
+ " mtu : max packet size we create rate map for {2047}\n"
+ " tsize : how many slots should size table have {512}\n"
+ " mpu : minimum packet size used in rate computations\n"
+ " overhead : per-packet size overhead used in rate computations\n"
+ " linklayer : adapting to a linklayer e.g. atm\n"
+ "Example: ... stab overhead 20 linklayer atm\n");
+
+}
+
+int check_size_table_opts(struct tc_sizespec *s)
+{
+ return s->linklayer >= LINKLAYER_ETHERNET || s->mpu != 0 ||
+ s->overhead != 0;
+}
+
+int parse_size_table(int *argcp, char ***argvp, struct tc_sizespec *sp)
+{
+ char **argv = *argvp;
+ int argc = *argcp;
+ struct tc_sizespec s = {};
+
+ NEXT_ARG();
+ if (matches(*argv, "help") == 0) {
+ stab_help();
+ return -1;
+ }
+ while (argc > 0) {
+ if (matches(*argv, "mtu") == 0) {
+ NEXT_ARG();
+ if (s.mtu)
+ duparg("mtu", *argv);
+ if (get_u32(&s.mtu, *argv, 10))
+ invarg("mtu", "invalid mtu");
+ } else if (matches(*argv, "mpu") == 0) {
+ NEXT_ARG();
+ if (s.mpu)
+ duparg("mpu", *argv);
+ if (get_u32(&s.mpu, *argv, 10))
+ invarg("mpu", "invalid mpu");
+ } else if (matches(*argv, "overhead") == 0) {
+ NEXT_ARG();
+ if (s.overhead)
+ duparg("overhead", *argv);
+ if (get_integer(&s.overhead, *argv, 10))
+ invarg("overhead", "invalid overhead");
+ } else if (matches(*argv, "tsize") == 0) {
+ NEXT_ARG();
+ if (s.tsize)
+ duparg("tsize", *argv);
+ if (get_u32(&s.tsize, *argv, 10))
+ invarg("tsize", "invalid table size");
+ } else if (matches(*argv, "linklayer") == 0) {
+ NEXT_ARG();
+ if (s.linklayer != LINKLAYER_UNSPEC)
+ duparg("linklayer", *argv);
+ if (get_linklayer(&s.linklayer, *argv))
+ invarg("linklayer", "invalid linklayer");
+ } else
+ break;
+ argc--; argv++;
+ }
+
+ if (!check_size_table_opts(&s))
+ return -1;
+
+ *sp = s;
+ *argvp = argv;
+ *argcp = argc;
+ return 0;
+}
+
+void print_size_table(FILE *fp, const char *prefix, struct rtattr *rta)
+{
+ struct rtattr *tb[TCA_STAB_MAX + 1];
+
+ SPRINT_BUF(b1);
+
+ parse_rtattr_nested(tb, TCA_STAB_MAX, rta);
+
+ if (tb[TCA_STAB_BASE]) {
+ struct tc_sizespec s = {0};
+
+ memcpy(&s, RTA_DATA(tb[TCA_STAB_BASE]),
+ MIN(RTA_PAYLOAD(tb[TCA_STAB_BASE]), sizeof(s)));
+
+ fprintf(fp, "%s", prefix);
+ if (s.linklayer)
+ fprintf(fp, "linklayer %s ",
+ sprint_linklayer(s.linklayer, b1));
+ if (s.overhead)
+ fprintf(fp, "overhead %d ", s.overhead);
+ if (s.mpu)
+ fprintf(fp, "mpu %u ", s.mpu);
+ if (s.mtu)
+ fprintf(fp, "mtu %u ", s.mtu);
+ if (s.tsize)
+ fprintf(fp, "tsize %u ", s.tsize);
+ }
+
+#if 0
+ if (tb[TCA_STAB_DATA]) {
+ unsigned int i, j, dlen;
+ __u16 *data = RTA_DATA(tb[TCA_STAB_DATA]);
+
+ dlen = RTA_PAYLOAD(tb[TCA_STAB_DATA]) / sizeof(__u16);
+
+ fprintf(fp, "\n%sstab data:", prefix);
+ for (i = 0; i < dlen/12; i++) {
+ fprintf(fp, "\n%s %3u:", prefix, i * 12);
+ for (j = 0; i * 12 + j < dlen; j++)
+ fprintf(fp, " %05x", data[i * 12 + j]);
+ }
+ }
+#endif
+}
diff --git a/tc/tc_util.c b/tc/tc_util.c
new file mode 100644
index 0000000..393721e
--- /dev/null
+++ b/tc/tc_util.c
@@ -0,0 +1,987 @@
+/*
+ * tc_util.c Misc TC utility functions.
+ *
+ * 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/param.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <math.h>
+#include <errno.h>
+
+#include "utils.h"
+#include "names.h"
+#include "tc_util.h"
+#include "tc_common.h"
+
+#ifndef LIBDIR
+#define LIBDIR "/usr/lib"
+#endif
+
+static struct db_names *cls_names;
+
+#define NAMES_DB "/etc/iproute2/tc_cls"
+
+int cls_names_init(char *path)
+{
+ int ret;
+
+ cls_names = db_names_alloc();
+ if (!cls_names)
+ return -1;
+
+ ret = db_names_load(cls_names, path ?: NAMES_DB);
+ if (ret == -ENOENT && path) {
+ fprintf(stderr, "Can't open class names file: %s\n", path);
+ return -1;
+ }
+ if (ret) {
+ db_names_free(cls_names);
+ cls_names = NULL;
+ }
+
+ return 0;
+}
+
+void cls_names_uninit(void)
+{
+ db_names_free(cls_names);
+}
+
+const char *get_tc_lib(void)
+{
+ const char *lib_dir;
+
+ lib_dir = getenv("TC_LIB_DIR");
+ if (!lib_dir)
+ lib_dir = LIBDIR "/tc/";
+
+ return lib_dir;
+}
+
+int get_qdisc_handle(__u32 *h, const char *str)
+{
+ __u32 maj;
+ char *p;
+
+ maj = TC_H_UNSPEC;
+ if (strcmp(str, "none") == 0)
+ goto ok;
+ maj = strtoul(str, &p, 16);
+ if (p == str || maj >= (1 << 16))
+ return -1;
+ maj <<= 16;
+ if (*p != ':' && *p != 0)
+ return -1;
+ok:
+ *h = maj;
+ return 0;
+}
+
+int get_tc_classid(__u32 *h, const char *str)
+{
+ __u32 maj, min;
+ char *p;
+
+ maj = TC_H_ROOT;
+ if (strcmp(str, "root") == 0)
+ goto ok;
+ maj = TC_H_UNSPEC;
+ if (strcmp(str, "none") == 0)
+ goto ok;
+ maj = strtoul(str, &p, 16);
+ if (p == str) {
+ maj = 0;
+ if (*p != ':')
+ return -1;
+ }
+ if (*p == ':') {
+ if (maj >= (1<<16))
+ return -1;
+ maj <<= 16;
+ str = p+1;
+ min = strtoul(str, &p, 16);
+ if (*p != 0)
+ return -1;
+ if (min >= (1<<16))
+ return -1;
+ maj |= min;
+ } else if (*p != 0)
+ return -1;
+
+ok:
+ *h = maj;
+ return 0;
+}
+
+int print_tc_classid(char *buf, int blen, __u32 h)
+{
+ SPRINT_BUF(handle) = {};
+ int hlen = SPRINT_BSIZE - 1;
+
+ if (h == TC_H_ROOT)
+ sprintf(handle, "root");
+ else if (h == TC_H_UNSPEC)
+ snprintf(handle, hlen, "none");
+ else if (TC_H_MAJ(h) == 0)
+ snprintf(handle, hlen, ":%x", TC_H_MIN(h));
+ else if (TC_H_MIN(h) == 0)
+ snprintf(handle, hlen, "%x:", TC_H_MAJ(h) >> 16);
+ else
+ snprintf(handle, hlen, "%x:%x", TC_H_MAJ(h) >> 16, TC_H_MIN(h));
+
+ if (use_names) {
+ char clname[IDNAME_MAX] = {};
+
+ if (id_to_name(cls_names, h, clname))
+ snprintf(buf, blen, "%s#%s", clname, handle);
+ else
+ snprintf(buf, blen, "%s", handle);
+ } else {
+ snprintf(buf, blen, "%s", handle);
+ }
+
+ return 0;
+}
+
+char *sprint_tc_classid(__u32 h, char *buf)
+{
+ if (print_tc_classid(buf, SPRINT_BSIZE-1, h))
+ strcpy(buf, "???");
+ return buf;
+}
+
+/* See http://physics.nist.gov/cuu/Units/binary.html */
+static const struct rate_suffix {
+ const char *name;
+ double scale;
+} suffixes[] = {
+ { "bit", 1. },
+ { "Kibit", 1024. },
+ { "kbit", 1000. },
+ { "mibit", 1024.*1024. },
+ { "mbit", 1000000. },
+ { "gibit", 1024.*1024.*1024. },
+ { "gbit", 1000000000. },
+ { "tibit", 1024.*1024.*1024.*1024. },
+ { "tbit", 1000000000000. },
+ { "Bps", 8. },
+ { "KiBps", 8.*1024. },
+ { "KBps", 8000. },
+ { "MiBps", 8.*1024*1024. },
+ { "MBps", 8000000. },
+ { "GiBps", 8.*1024.*1024.*1024. },
+ { "GBps", 8000000000. },
+ { "TiBps", 8.*1024.*1024.*1024.*1024. },
+ { "TBps", 8000000000000. },
+ { NULL }
+};
+
+/* Parse a percent e.g: '30%'
+ * return: 0 = ok, -1 = error, 1 = out of range
+ */
+int parse_percent(double *val, const char *str)
+{
+ char *p;
+
+ *val = strtod(str, &p) / 100.;
+ if (*val > 1.0 || *val < 0.0)
+ return 1;
+ if (*p && strcmp(p, "%"))
+ return -1;
+
+ return 0;
+}
+
+static int parse_percent_rate(char *rate, size_t len,
+ const char *str, const char *dev)
+{
+ long dev_mbit;
+ int ret;
+ double perc, rate_bit;
+ char *str_perc = NULL;
+
+ if (!dev[0]) {
+ fprintf(stderr, "No device specified; specify device to rate limit by percentage\n");
+ return -1;
+ }
+
+ if (read_prop(dev, "speed", &dev_mbit))
+ return -1;
+
+ ret = sscanf(str, "%m[0-9.%]", &str_perc);
+ if (ret != 1)
+ goto malf;
+
+ ret = parse_percent(&perc, str_perc);
+ if (ret == 1) {
+ fprintf(stderr, "Invalid rate specified; should be between [0,100]%% but is %s\n", str);
+ goto err;
+ } else if (ret == -1) {
+ goto malf;
+ }
+
+ free(str_perc);
+
+ rate_bit = perc * dev_mbit * 1000 * 1000;
+
+ ret = snprintf(rate, len, "%lf", rate_bit);
+ if (ret <= 0 || ret >= len) {
+ fprintf(stderr, "Unable to parse calculated rate\n");
+ return -1;
+ }
+
+ return 0;
+
+malf:
+ fprintf(stderr, "Specified rate value could not be read or is malformed\n");
+err:
+ free(str_perc);
+ return -1;
+}
+
+int get_percent_rate(unsigned int *rate, const char *str, const char *dev)
+{
+ char r_str[20];
+
+ if (parse_percent_rate(r_str, sizeof(r_str), str, dev))
+ return -1;
+
+ return get_rate(rate, r_str);
+}
+
+int get_percent_rate64(__u64 *rate, const char *str, const char *dev)
+{
+ char r_str[20];
+
+ if (parse_percent_rate(r_str, sizeof(r_str), str, dev))
+ return -1;
+
+ return get_rate64(rate, r_str);
+}
+
+int get_rate(unsigned int *rate, const char *str)
+{
+ char *p;
+ double bps = strtod(str, &p);
+ const struct rate_suffix *s;
+
+ if (p == str)
+ return -1;
+
+ for (s = suffixes; s->name; ++s) {
+ if (strcasecmp(s->name, p) == 0) {
+ bps *= s->scale;
+ p += strlen(p);
+ break;
+ }
+ }
+
+ if (*p)
+ return -1; /* unknown suffix */
+
+ bps /= 8; /* -> bytes per second */
+ *rate = bps;
+ /* detect if an overflow happened */
+ if (*rate != floor(bps))
+ return -1;
+ return 0;
+}
+
+int get_rate64(__u64 *rate, const char *str)
+{
+ char *p;
+ double bps = strtod(str, &p);
+ const struct rate_suffix *s;
+
+ if (p == str)
+ return -1;
+
+ for (s = suffixes; s->name; ++s) {
+ if (strcasecmp(s->name, p) == 0) {
+ bps *= s->scale;
+ p += strlen(p);
+ break;
+ }
+ }
+
+ if (*p)
+ return -1; /* unknown suffix */
+
+ bps /= 8; /* -> bytes per second */
+ *rate = bps;
+ return 0;
+}
+
+void print_rate(char *buf, int len, __u64 rate)
+{
+ extern int use_iec;
+ unsigned long kilo = use_iec ? 1024 : 1000;
+ const char *str = use_iec ? "i" : "";
+ static char *units[5] = {"", "K", "M", "G", "T"};
+ int i;
+
+ rate <<= 3; /* bytes/sec -> bits/sec */
+
+ for (i = 0; i < ARRAY_SIZE(units) - 1; i++) {
+ if (rate < kilo)
+ break;
+ if (((rate % kilo) != 0) && rate < 1000*kilo)
+ break;
+ rate /= kilo;
+ }
+
+ snprintf(buf, len, "%.0f%s%sbit", (double)rate, units[i], str);
+}
+
+char *sprint_rate(__u64 rate, char *buf)
+{
+ print_rate(buf, SPRINT_BSIZE-1, rate);
+ return buf;
+}
+
+char *sprint_ticks(__u32 ticks, char *buf)
+{
+ return sprint_time(tc_core_tick2time(ticks), buf);
+}
+
+int get_size(unsigned int *size, const char *str)
+{
+ double sz;
+ char *p;
+
+ sz = strtod(str, &p);
+ if (p == str)
+ return -1;
+
+ if (*p) {
+ if (strcasecmp(p, "kb") == 0 || strcasecmp(p, "k") == 0)
+ sz *= 1024;
+ else if (strcasecmp(p, "gb") == 0 || strcasecmp(p, "g") == 0)
+ sz *= 1024*1024*1024;
+ else if (strcasecmp(p, "gbit") == 0)
+ sz *= 1024*1024*1024/8;
+ else if (strcasecmp(p, "mb") == 0 || strcasecmp(p, "m") == 0)
+ sz *= 1024*1024;
+ else if (strcasecmp(p, "mbit") == 0)
+ sz *= 1024*1024/8;
+ else if (strcasecmp(p, "kbit") == 0)
+ sz *= 1024/8;
+ else if (strcasecmp(p, "b") != 0)
+ return -1;
+ }
+
+ *size = sz;
+ return 0;
+}
+
+int get_size_and_cell(unsigned int *size, int *cell_log, char *str)
+{
+ char *slash = strchr(str, '/');
+
+ if (slash)
+ *slash = 0;
+
+ if (get_size(size, str))
+ return -1;
+
+ if (slash) {
+ int cell;
+ int i;
+
+ if (get_integer(&cell, slash+1, 0))
+ return -1;
+ *slash = '/';
+
+ for (i = 0; i < 32; i++) {
+ if ((1<<i) == cell) {
+ *cell_log = i;
+ return 0;
+ }
+ }
+ return -1;
+ }
+ return 0;
+}
+
+void print_devname(enum output_type type, int ifindex)
+{
+ const char *ifname = ll_index_to_name(ifindex);
+
+ if (!is_json_context())
+ printf("dev ");
+
+ print_color_string(type, COLOR_IFNAME,
+ "dev", "%s ", ifname);
+}
+
+static void print_size(char *buf, int len, __u32 sz)
+{
+ double tmp = sz;
+
+ if (sz >= 1024*1024 && fabs(1024*1024*rint(tmp/(1024*1024)) - sz) < 1024)
+ snprintf(buf, len, "%gMb", rint(tmp/(1024*1024)));
+ else if (sz >= 1024 && fabs(1024*rint(tmp/1024) - sz) < 16)
+ snprintf(buf, len, "%gKb", rint(tmp/1024));
+ else
+ snprintf(buf, len, "%ub", sz);
+}
+
+char *sprint_size(__u32 size, char *buf)
+{
+ print_size(buf, SPRINT_BSIZE-1, size);
+ return buf;
+}
+
+static const char *action_n2a(int action)
+{
+ static char buf[64];
+
+ if (TC_ACT_EXT_CMP(action, TC_ACT_GOTO_CHAIN))
+ return "goto";
+ if (TC_ACT_EXT_CMP(action, TC_ACT_JUMP))
+ return "jump";
+ switch (action) {
+ case TC_ACT_UNSPEC:
+ return "continue";
+ case TC_ACT_OK:
+ return "pass";
+ case TC_ACT_SHOT:
+ return "drop";
+ case TC_ACT_RECLASSIFY:
+ return "reclassify";
+ case TC_ACT_PIPE:
+ return "pipe";
+ case TC_ACT_STOLEN:
+ return "stolen";
+ case TC_ACT_TRAP:
+ return "trap";
+ default:
+ snprintf(buf, 64, "%d", action);
+ return buf;
+ }
+}
+
+/* Convert action branch name into numeric format.
+ *
+ * Parameters:
+ * @arg - string to parse
+ * @result - pointer to output variable
+ * @allow_num - whether @arg may be in numeric format already
+ *
+ * In error case, returns -1 and does not touch @result. Otherwise returns 0.
+ */
+int action_a2n(char *arg, int *result, bool allow_num)
+{
+ int n;
+ char dummy;
+ struct {
+ const char *a;
+ int n;
+ } a2n[] = {
+ {"continue", TC_ACT_UNSPEC},
+ {"drop", TC_ACT_SHOT},
+ {"shot", TC_ACT_SHOT},
+ {"pass", TC_ACT_OK},
+ {"ok", TC_ACT_OK},
+ {"reclassify", TC_ACT_RECLASSIFY},
+ {"pipe", TC_ACT_PIPE},
+ {"goto", TC_ACT_GOTO_CHAIN},
+ {"jump", TC_ACT_JUMP},
+ {"trap", TC_ACT_TRAP},
+ { NULL },
+ }, *iter;
+
+ for (iter = a2n; iter->a; iter++) {
+ if (matches(arg, iter->a) != 0)
+ continue;
+ n = iter->n;
+ goto out_ok;
+ }
+ if (!allow_num || sscanf(arg, "%d%c", &n, &dummy) != 1)
+ return -1;
+
+out_ok:
+ if (result)
+ *result = n;
+ return 0;
+}
+
+static int __parse_action_control(int *argc_p, char ***argv_p, int *result_p,
+ bool allow_num, bool ignore_a2n_miss)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int result;
+
+ if (!argc)
+ return -1;
+ if (action_a2n(*argv, &result, allow_num) == -1) {
+ if (!ignore_a2n_miss)
+ fprintf(stderr, "Bad action type %s\n", *argv);
+ return -1;
+ }
+ if (result == TC_ACT_GOTO_CHAIN) {
+ __u32 chain_index;
+
+ NEXT_ARG();
+ if (matches(*argv, "chain") != 0) {
+ fprintf(stderr, "\"chain index\" expected\n");
+ return -1;
+ }
+ NEXT_ARG();
+ if (get_u32(&chain_index, *argv, 10) ||
+ chain_index > TC_ACT_EXT_VAL_MASK) {
+ fprintf(stderr, "Illegal \"chain index\"\n");
+ return -1;
+ }
+ result |= chain_index;
+ }
+ if (result == TC_ACT_JUMP) {
+ __u32 jump_cnt = 0;
+
+ NEXT_ARG();
+ if (get_u32(&jump_cnt, *argv, 10) ||
+ jump_cnt > TC_ACT_EXT_VAL_MASK) {
+ fprintf(stderr, "Invalid \"jump count\" (%s)\n", *argv);
+ return -1;
+ }
+ result |= jump_cnt;
+ }
+ NEXT_ARG_FWD();
+ *argc_p = argc;
+ *argv_p = argv;
+ *result_p = result;
+ return 0;
+}
+
+/* Parse action control including possible options.
+ *
+ * Parameters:
+ * @argc_p - pointer to argc to parse
+ * @argv_p - pointer to argv to parse
+ * @result_p - pointer to output variable
+ * @allow_num - whether action may be in numeric format already
+ *
+ * In error case, returns -1 and does not touch @result_1p. Otherwise returns 0.
+ */
+int parse_action_control(int *argc_p, char ***argv_p,
+ int *result_p, bool allow_num)
+{
+ return __parse_action_control(argc_p, argv_p, result_p,
+ allow_num, false);
+}
+
+/* Parse action control including possible options.
+ *
+ * Parameters:
+ * @argc_p - pointer to argc to parse
+ * @argv_p - pointer to argv to parse
+ * @result_p - pointer to output variable
+ * @allow_num - whether action may be in numeric format already
+ * @default_result - set as a result in case of parsing error
+ *
+ * In case there is an error during parsing, the default result is used.
+ */
+void parse_action_control_dflt(int *argc_p, char ***argv_p,
+ int *result_p, bool allow_num,
+ int default_result)
+{
+ if (__parse_action_control(argc_p, argv_p, result_p, allow_num, true))
+ *result_p = default_result;
+}
+
+static int parse_action_control_slash_spaces(int *argc_p, char ***argv_p,
+ int *result1_p, int *result2_p,
+ bool allow_num)
+{
+ int argc = *argc_p;
+ char **argv = *argv_p;
+ int result1 = -1, result2;
+ int *result_p = &result1;
+ int ok = 0;
+ int ret;
+
+ while (argc > 0) {
+ switch (ok) {
+ case 1:
+ if (strcmp(*argv, "/") != 0)
+ goto out;
+ result_p = &result2;
+ NEXT_ARG();
+ /* fall-through */
+ case 0: /* fall-through */
+ case 2:
+ ret = parse_action_control(&argc, &argv,
+ result_p, allow_num);
+ if (ret)
+ return ret;
+ ok++;
+ break;
+ default:
+ goto out;
+ }
+ }
+out:
+ *result1_p = result1;
+ if (ok == 2)
+ *result2_p = result2;
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+/* Parse action control with slash including possible options.
+ *
+ * Parameters:
+ * @argc_p - pointer to argc to parse
+ * @argv_p - pointer to argv to parse
+ * @result1_p - pointer to the first (before slash) output variable
+ * @result2_p - pointer to the second (after slash) output variable
+ * @allow_num - whether action may be in numeric format already
+ *
+ * In error case, returns -1 and does not touch @result*. Otherwise returns 0.
+ */
+int parse_action_control_slash(int *argc_p, char ***argv_p,
+ int *result1_p, int *result2_p, bool allow_num)
+{
+ int result1, result2, argc = *argc_p;
+ char **argv = *argv_p;
+ char *p = strchr(*argv, '/');
+
+ if (!p)
+ return parse_action_control_slash_spaces(argc_p, argv_p,
+ result1_p, result2_p,
+ allow_num);
+ *p = 0;
+ if (action_a2n(*argv, &result1, allow_num)) {
+ *p = '/';
+ return -1;
+ }
+
+ *p = '/';
+ if (action_a2n(p + 1, &result2, allow_num))
+ return -1;
+
+ *result1_p = result1;
+ *result2_p = result2;
+ NEXT_ARG_FWD();
+ *argc_p = argc;
+ *argv_p = argv;
+ return 0;
+}
+
+void print_action_control(FILE *f, const char *prefix,
+ int action, const char *suffix)
+{
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ open_json_object("control_action");
+ print_string(PRINT_ANY, "type", "%s", action_n2a(action));
+ if (TC_ACT_EXT_CMP(action, TC_ACT_GOTO_CHAIN))
+ print_uint(PRINT_ANY, "chain", " chain %u",
+ action & TC_ACT_EXT_VAL_MASK);
+ if (TC_ACT_EXT_CMP(action, TC_ACT_JUMP))
+ print_uint(PRINT_ANY, "jump", " %u",
+ action & TC_ACT_EXT_VAL_MASK);
+ close_json_object();
+ print_string(PRINT_FP, NULL, "%s", suffix);
+}
+
+int get_linklayer(unsigned int *val, const char *arg)
+{
+ int res;
+
+ if (matches(arg, "ethernet") == 0)
+ res = LINKLAYER_ETHERNET;
+ else if (matches(arg, "atm") == 0)
+ res = LINKLAYER_ATM;
+ else if (matches(arg, "adsl") == 0)
+ res = LINKLAYER_ATM;
+ else
+ return -1; /* Indicate error */
+
+ *val = res;
+ return 0;
+}
+
+static void print_linklayer(char *buf, int len, unsigned int linklayer)
+{
+ switch (linklayer) {
+ case LINKLAYER_UNSPEC:
+ snprintf(buf, len, "%s", "unspec");
+ return;
+ case LINKLAYER_ETHERNET:
+ snprintf(buf, len, "%s", "ethernet");
+ return;
+ case LINKLAYER_ATM:
+ snprintf(buf, len, "%s", "atm");
+ return;
+ default:
+ snprintf(buf, len, "%s", "unknown");
+ return;
+ }
+}
+
+char *sprint_linklayer(unsigned int linklayer, char *buf)
+{
+ print_linklayer(buf, SPRINT_BSIZE-1, linklayer);
+ return buf;
+}
+
+void print_tm(FILE *f, const struct tcf_t *tm)
+{
+ int hz = get_user_hz();
+
+ if (tm->install != 0) {
+ print_uint(PRINT_JSON, "installed", NULL, tm->install);
+ print_uint(PRINT_FP, NULL, " installed %u sec",
+ (unsigned int)(tm->install/hz));
+ }
+ if (tm->lastuse != 0) {
+ print_uint(PRINT_JSON, "last_used", NULL, tm->lastuse);
+ print_uint(PRINT_FP, NULL, " used %u sec",
+ (unsigned int)(tm->lastuse/hz));
+ }
+ if (tm->expires != 0) {
+ print_uint(PRINT_JSON, "expires", NULL, tm->expires);
+ print_uint(PRINT_FP, NULL, " expires %u sec",
+ (unsigned int)(tm->expires/hz));
+ }
+}
+
+static void print_tcstats_basic_hw(struct rtattr **tbs, char *prefix)
+{
+ struct gnet_stats_basic bs_hw;
+
+ if (!tbs[TCA_STATS_BASIC_HW])
+ return;
+
+ memcpy(&bs_hw, RTA_DATA(tbs[TCA_STATS_BASIC_HW]),
+ MIN(RTA_PAYLOAD(tbs[TCA_STATS_BASIC_HW]), sizeof(bs_hw)));
+
+ if (bs_hw.bytes == 0 && bs_hw.packets == 0)
+ return;
+
+ if (tbs[TCA_STATS_BASIC]) {
+ struct gnet_stats_basic bs;
+
+ memcpy(&bs, RTA_DATA(tbs[TCA_STATS_BASIC]),
+ MIN(RTA_PAYLOAD(tbs[TCA_STATS_BASIC]),
+ sizeof(bs)));
+
+ if (bs.bytes >= bs_hw.bytes && bs.packets >= bs_hw.packets) {
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ print_lluint(PRINT_ANY, "sw_bytes",
+ "Sent software %llu bytes",
+ bs.bytes - bs_hw.bytes);
+ print_uint(PRINT_ANY, "sw_packets", " %u pkt",
+ bs.packets - bs_hw.packets);
+ }
+ }
+
+ print_string(PRINT_FP, NULL, "%s", _SL_);
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ print_lluint(PRINT_ANY, "hw_bytes", "Sent hardware %llu bytes",
+ bs_hw.bytes);
+ print_uint(PRINT_ANY, "hw_packets", " %u pkt", bs_hw.packets);
+}
+
+void print_tcstats2_attr(FILE *fp, struct rtattr *rta, char *prefix, struct rtattr **xstats)
+{
+ SPRINT_BUF(b1);
+ struct rtattr *tbs[TCA_STATS_MAX + 1];
+
+ parse_rtattr_nested(tbs, TCA_STATS_MAX, rta);
+
+ if (tbs[TCA_STATS_BASIC]) {
+ struct gnet_stats_basic bs = {0};
+
+ memcpy(&bs, RTA_DATA(tbs[TCA_STATS_BASIC]), MIN(RTA_PAYLOAD(tbs[TCA_STATS_BASIC]), sizeof(bs)));
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ print_lluint(PRINT_ANY, "bytes", "Sent %llu bytes", bs.bytes);
+ print_uint(PRINT_ANY, "packets", " %u pkt", bs.packets);
+ }
+
+ if (tbs[TCA_STATS_QUEUE]) {
+ struct gnet_stats_queue q = {0};
+
+ memcpy(&q, RTA_DATA(tbs[TCA_STATS_QUEUE]), MIN(RTA_PAYLOAD(tbs[TCA_STATS_QUEUE]), sizeof(q)));
+ print_uint(PRINT_ANY, "drops", " (dropped %u", q.drops);
+ print_uint(PRINT_ANY, "overlimits", ", overlimits %u",
+ q.overlimits);
+ print_uint(PRINT_ANY, "requeues", " requeues %u) ", q.requeues);
+ }
+
+ if (tbs[TCA_STATS_BASIC_HW])
+ print_tcstats_basic_hw(tbs, prefix);
+
+ if (tbs[TCA_STATS_RATE_EST64]) {
+ struct gnet_stats_rate_est64 re = {0};
+
+ memcpy(&re, RTA_DATA(tbs[TCA_STATS_RATE_EST64]),
+ MIN(RTA_PAYLOAD(tbs[TCA_STATS_RATE_EST64]),
+ sizeof(re)));
+ print_string(PRINT_FP, NULL, "\n%s", prefix);
+ print_lluint(PRINT_JSON, "rate", NULL, re.bps);
+ print_string(PRINT_FP, NULL, "rate %s",
+ sprint_rate(re.bps, b1));
+ print_lluint(PRINT_ANY, "pps", " %llupps", re.pps);
+ } else if (tbs[TCA_STATS_RATE_EST]) {
+ struct gnet_stats_rate_est re = {0};
+
+ memcpy(&re, RTA_DATA(tbs[TCA_STATS_RATE_EST]),
+ MIN(RTA_PAYLOAD(tbs[TCA_STATS_RATE_EST]), sizeof(re)));
+ print_string(PRINT_FP, NULL, "\n%s", prefix);
+ print_uint(PRINT_JSON, "rate", NULL, re.bps);
+ print_string(PRINT_FP, NULL, "rate %s",
+ sprint_rate(re.bps, b1));
+ print_uint(PRINT_ANY, "pps", " %upps", re.pps);
+ }
+
+ if (tbs[TCA_STATS_QUEUE]) {
+ struct gnet_stats_queue q = {0};
+
+ memcpy(&q, RTA_DATA(tbs[TCA_STATS_QUEUE]), MIN(RTA_PAYLOAD(tbs[TCA_STATS_QUEUE]), sizeof(q)));
+ if (!tbs[TCA_STATS_RATE_EST])
+ print_string(PRINT_FP, NULL, "\n", "");
+ print_uint(PRINT_JSON, "backlog", NULL, q.backlog);
+ print_string(PRINT_FP, NULL, "%s", prefix);
+ print_string(PRINT_FP, NULL, "backlog %s",
+ sprint_size(q.backlog, b1));
+ print_uint(PRINT_ANY, "qlen", " %up", q.qlen);
+ print_uint(PRINT_FP, NULL, " requeues %u", q.requeues);
+ }
+
+ if (xstats)
+ *xstats = tbs[TCA_STATS_APP] ? : NULL;
+}
+
+void print_tcstats_attr(FILE *fp, struct rtattr *tb[], char *prefix, struct rtattr **xstats)
+{
+ SPRINT_BUF(b1);
+
+ if (tb[TCA_STATS2]) {
+ print_tcstats2_attr(fp, tb[TCA_STATS2], prefix, xstats);
+ if (xstats && !*xstats)
+ goto compat_xstats;
+ return;
+ }
+ /* backward compatibility */
+ if (tb[TCA_STATS]) {
+ struct tc_stats st = {};
+
+ /* handle case where kernel returns more/less than we know about */
+ memcpy(&st, RTA_DATA(tb[TCA_STATS]), MIN(RTA_PAYLOAD(tb[TCA_STATS]), sizeof(st)));
+
+ fprintf(fp, "%sSent %llu bytes %u pkts (dropped %u, overlimits %u) ",
+ prefix, (unsigned long long)st.bytes, st.packets, st.drops,
+ st.overlimits);
+
+ if (st.bps || st.pps || st.qlen || st.backlog) {
+ fprintf(fp, "\n%s", prefix);
+ if (st.bps || st.pps) {
+ fprintf(fp, "rate ");
+ if (st.bps)
+ fprintf(fp, "%s ", sprint_rate(st.bps, b1));
+ if (st.pps)
+ fprintf(fp, "%upps ", st.pps);
+ }
+ if (st.qlen || st.backlog) {
+ fprintf(fp, "backlog ");
+ if (st.backlog)
+ fprintf(fp, "%s ", sprint_size(st.backlog, b1));
+ if (st.qlen)
+ fprintf(fp, "%up ", st.qlen);
+ }
+ }
+ }
+
+compat_xstats:
+ if (tb[TCA_XSTATS] && xstats)
+ *xstats = tb[TCA_XSTATS];
+}
+
+static void print_masked_type(__u32 type_max,
+ __u32 (*rta_getattr_type)(const struct rtattr *),
+ const char *name, struct rtattr *attr,
+ struct rtattr *mask_attr, bool newline)
+{
+ SPRINT_BUF(namefrm);
+ __u32 value, mask;
+ SPRINT_BUF(out);
+ size_t done;
+
+ if (!attr)
+ return;
+
+ value = rta_getattr_type(attr);
+ mask = mask_attr ? rta_getattr_type(mask_attr) : type_max;
+
+ if (is_json_context()) {
+ sprintf(namefrm, "\n %s %%u", name);
+ print_hu(PRINT_ANY, name, namefrm,
+ rta_getattr_type(attr));
+ if (mask != type_max) {
+ char mask_name[SPRINT_BSIZE-6];
+
+ sprintf(mask_name, "%s_mask", name);
+ if (newline)
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+ sprintf(namefrm, " %s %%u", mask_name);
+ print_hu(PRINT_ANY, mask_name, namefrm, mask);
+ }
+ } else {
+ done = sprintf(out, "%u", value);
+ if (mask != type_max)
+ sprintf(out + done, "/0x%x", mask);
+ if (newline)
+ print_string(PRINT_FP, NULL, "%s ", _SL_);
+ sprintf(namefrm, " %s %%s", name);
+ print_string(PRINT_ANY, name, namefrm, out);
+ }
+}
+
+void print_masked_u32(const char *name, struct rtattr *attr,
+ struct rtattr *mask_attr, bool newline)
+{
+ print_masked_type(UINT32_MAX, rta_getattr_u32, name, attr, mask_attr,
+ newline);
+}
+
+static __u32 __rta_getattr_u16_u32(const struct rtattr *attr)
+{
+ return rta_getattr_u16(attr);
+}
+
+void print_masked_u16(const char *name, struct rtattr *attr,
+ struct rtattr *mask_attr, bool newline)
+{
+ print_masked_type(UINT16_MAX, __rta_getattr_u16_u32, name, attr,
+ mask_attr, newline);
+}
+
+static __u32 __rta_getattr_u8_u32(const struct rtattr *attr)
+{
+ return rta_getattr_u8(attr);
+}
+
+void print_masked_u8(const char *name, struct rtattr *attr,
+ struct rtattr *mask_attr, bool newline)
+{
+ print_masked_type(UINT8_MAX, __rta_getattr_u8_u32, name, attr,
+ mask_attr, newline);
+}
diff --git a/tc/tc_util.h b/tc/tc_util.h
new file mode 100644
index 0000000..9adf2ab
--- /dev/null
+++ b/tc/tc_util.h
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _TC_UTIL_H_
+#define _TC_UTIL_H_ 1
+
+#define MAX_MSG 16384
+#include <limits.h>
+#include <linux/if.h>
+
+#include <linux/pkt_sched.h>
+#include <linux/pkt_cls.h>
+#include <linux/gen_stats.h>
+
+#include "tc_core.h"
+#include "json_print.h"
+
+/* This is the deprecated multiqueue interface */
+#ifndef TCA_PRIO_MAX
+enum
+{
+ TCA_PRIO_UNSPEC,
+ TCA_PRIO_MQ,
+ __TCA_PRIO_MAX
+};
+
+#define TCA_PRIO_MAX (__TCA_PRIO_MAX - 1)
+#endif
+
+#define FILTER_NAMESZ 16
+
+struct qdisc_util {
+ struct qdisc_util *next;
+ const char *id;
+ int (*parse_qopt)(struct qdisc_util *qu, int argc,
+ char **argv, struct nlmsghdr *n, const char *dev);
+ int (*print_qopt)(struct qdisc_util *qu,
+ FILE *f, struct rtattr *opt);
+ int (*print_xstats)(struct qdisc_util *qu,
+ FILE *f, struct rtattr *xstats);
+
+ int (*parse_copt)(struct qdisc_util *qu, int argc,
+ char **argv, struct nlmsghdr *n, const char *dev);
+ int (*print_copt)(struct qdisc_util *qu, FILE *f, struct rtattr *opt);
+};
+
+extern __u16 f_proto;
+struct filter_util {
+ struct filter_util *next;
+ char id[FILTER_NAMESZ];
+ int (*parse_fopt)(struct filter_util *qu, char *fhandle,
+ int argc, char **argv, struct nlmsghdr *n);
+ int (*print_fopt)(struct filter_util *qu,
+ FILE *f, struct rtattr *opt, __u32 fhandle);
+};
+
+struct action_util {
+ struct action_util *next;
+ char id[FILTER_NAMESZ];
+ int (*parse_aopt)(struct action_util *a, int *argc,
+ char ***argv, int code, struct nlmsghdr *n);
+ int (*print_aopt)(struct action_util *au, FILE *f, struct rtattr *opt);
+ int (*print_xstats)(struct action_util *au,
+ FILE *f, struct rtattr *xstats);
+};
+
+struct exec_util {
+ struct exec_util *next;
+ char id[FILTER_NAMESZ];
+ int (*parse_eopt)(struct exec_util *eu, int argc, char **argv);
+};
+
+const char *get_tc_lib(void);
+
+struct qdisc_util *get_qdisc_kind(const char *str);
+struct filter_util *get_filter_kind(const char *str);
+
+int get_qdisc_handle(__u32 *h, const char *str);
+int get_rate(unsigned int *rate, const char *str);
+int get_percent_rate(unsigned int *rate, const char *str, const char *dev);
+int get_rate64(__u64 *rate, const char *str);
+int get_percent_rate64(__u64 *rate, const char *str, const char *dev);
+int get_size(unsigned int *size, const char *str);
+int get_size_and_cell(unsigned int *size, int *cell_log, char *str);
+int get_linklayer(unsigned int *val, const char *arg);
+
+void print_rate(char *buf, int len, __u64 rate);
+void print_devname(enum output_type type, int ifindex);
+
+char *sprint_rate(__u64 rate, char *buf);
+char *sprint_size(__u32 size, char *buf);
+char *sprint_tc_classid(__u32 h, char *buf);
+char *sprint_ticks(__u32 ticks, char *buf);
+char *sprint_linklayer(unsigned int linklayer, char *buf);
+
+void print_tcstats_attr(FILE *fp, struct rtattr *tb[],
+ char *prefix, struct rtattr **xstats);
+void print_tcstats2_attr(FILE *fp, struct rtattr *rta,
+ char *prefix, struct rtattr **xstats);
+
+int get_tc_classid(__u32 *h, const char *str);
+int print_tc_classid(char *buf, int len, __u32 h);
+char *sprint_tc_classid(__u32 h, char *buf);
+
+int tc_print_police(FILE *f, struct rtattr *tb);
+int parse_percent(double *val, const char *str);
+int parse_police(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n);
+
+int parse_action_control(int *argc_p, char ***argv_p,
+ int *result_p, bool allow_num);
+void parse_action_control_dflt(int *argc_p, char ***argv_p,
+ int *result_p, bool allow_num,
+ int default_result);
+int parse_action_control_slash(int *argc_p, char ***argv_p,
+ int *result1_p, int *result2_p, bool allow_num);
+void print_action_control(FILE *f, const char *prefix,
+ int action, const char *suffix);
+int police_print_xstats(struct action_util *a, FILE *f, struct rtattr *tb);
+int tc_print_action(FILE *f, const struct rtattr *tb, unsigned short tot_acts);
+int tc_print_ipt(FILE *f, const struct rtattr *tb);
+int parse_action(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n);
+void print_tm(FILE *f, const struct tcf_t *tm);
+int prio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt);
+
+int cls_names_init(char *path);
+void cls_names_uninit(void);
+
+int action_a2n(char *arg, int *result, bool allow_num);
+
+bool tc_qdisc_block_exists(__u32 block_index);
+
+void print_masked_u32(const char *name, struct rtattr *attr,
+ struct rtattr *mask_attr, bool newline);
+void print_masked_u16(const char *name, struct rtattr *attr,
+ struct rtattr *mask_attr, bool newline);
+void print_masked_u8(const char *name, struct rtattr *attr,
+ struct rtattr *mask_attr, bool newline);
+#endif
diff --git a/testsuite/Makefile b/testsuite/Makefile
new file mode 100644
index 0000000..4451f31
--- /dev/null
+++ b/testsuite/Makefile
@@ -0,0 +1,96 @@
+# SPDX-License-Identifier: GPL-2.0
+## -- Config --
+DEV := lo
+PREFIX := sudo -E unshare -n
+RESULTS_DIR := results
+## -- End Config --
+
+HAVE_UNSHARED_UTIL := $(shell unshare --version 2> /dev/null)
+
+rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
+
+TESTS := $(patsubst tests/%,%,$(call rwildcard,tests/,*.t))
+TESTS_DIR := $(dir $(TESTS))
+
+IPVERS := $(filter-out iproute2/Makefile,$(wildcard iproute2/*))
+
+KENVFN := $(shell mktemp /tmp/tc_testkenv.XXXXXX)
+ifneq (,$(wildcard /proc/config.gz))
+ KCPATH := /proc/config.gz
+else
+ KVER := $(shell uname -r)
+ KCPATHS := /lib/modules/$(KVER)/config /boot/config-$(KVER)
+ KCPATH := $(firstword $(wildcard $(KCPATHS)))
+endif
+
+.PHONY: compile listtests alltests configure $(TESTS)
+
+configure:
+ $(MAKE) -C iproute2 configure
+
+compile: configure generate_nlmsg
+ $(MAKE) -C iproute2
+
+listtests:
+ @for t in $(TESTS); do \
+ echo "$$t"; \
+ done
+
+generate_nlmsg:
+ $(MAKE) -C tools
+
+alltests: generate_nlmsg $(TESTS)
+
+testclean:
+ @echo "Removing $(RESULTS_DIR) dir ..."
+ @rm -rf $(RESULTS_DIR)
+
+clean: testclean
+ @rm -f iproute2/iproute2-this
+ @rm -f tests/ip/link/dev_wo_vf_rate.nl
+ $(MAKE) -C tools clean
+
+distclean: clean
+ $(MAKE) -C iproute2 distclean
+
+$(TESTS): generate_nlmsg testclean
+ifeq (,$(IPVERS))
+ $(error Please run make first)
+endif
+ifeq (,$(HAVE_UNSHARED_UTIL))
+ $(error Please install util-linux tools to run tests in separated network namespace)
+endif
+ @./tools/generate_nlmsg
+
+ @mkdir -p $(RESULTS_DIR)
+
+ @for d in $(TESTS_DIR); do \
+ mkdir -p $(RESULTS_DIR)/$$d; \
+ done
+
+ @if [ "$(KCPATH)" = "/proc/config.gz" ]; then \
+ gunzip -c $(KCPATH) >$(KENVFN); \
+ elif [ "$(KCPATH)" != "" ]; then \
+ cat $(KCPATH) >$(KENVFN); \
+ fi
+ @sed -i -e 's/^CONFIG_/export CONFIG_/' $(KENVFN)
+
+ @for i in $(IPVERS); do \
+ o=`echo $$i | sed -e 's/iproute2\///'`; \
+ echo -n "Running $@ [$$o/`uname -r`]: "; \
+ TMP_ERR=`mktemp /tmp/tc_testsuite.XXXXXX`; \
+ TMP_OUT=`mktemp /tmp/tc_testsuite.XXXXXX`; \
+ . $(KENVFN); \
+ STD_ERR="$$TMP_ERR" STD_OUT="$$TMP_OUT" \
+ TC="$$i/tc/tc" IP="$$i/ip/ip" SS=$$i/misc/ss DEV="$(DEV)" IPVER="$@" SNAME="$$i" \
+ ERRF="$(RESULTS_DIR)/$@.$$o.err" $(PREFIX) tests/$@ > $(RESULTS_DIR)/$@.$$o.out; \
+ if [ "$$?" = "127" ]; then \
+ printf "\033[1;35mSKIPPED\033[0m\n"; \
+ elif [ -e "$(RESULTS_DIR)/$@.$$o.err" ]; then \
+ printf "\033[0;31mFAILED\033[0m\n"; \
+ else \
+ printf "\033[0;32mPASS\033[0m\n"; \
+ fi; \
+ rm "$$TMP_ERR" "$$TMP_OUT"; \
+ sudo dmesg > $(RESULTS_DIR)/$@.$$o.dmesg; \
+ done
diff --git a/testsuite/iproute2/Makefile b/testsuite/iproute2/Makefile
new file mode 100644
index 0000000..f894605
--- /dev/null
+++ b/testsuite/iproute2/Makefile
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: GPL-2.0
+SUBDIRS := $(filter-out Makefile,$(wildcard *))
+.PHONY: all configure clean distclean show $(SUBDIRS)
+
+all: configure
+ @for dir in $(SUBDIRS); do \
+ $(MAKE) -C $$dir; \
+ done
+
+link:
+ @if [ ! -L iproute2-this ]; then \
+ ln -s ../.. iproute2-this; \
+ fi
+
+configure: link
+ @for dir in $(SUBDIRS); do \
+ echo "Entering $$dir" && cd $$dir && if [ -f configure ]; then ./configure; fi && cd ..; \
+ done
+
+clean: link
+ @for dir in $(SUBDIRS); do \
+ $(MAKE) -C $$dir clean; \
+ done
+
+distclean: clean
+ @for dir in $(SUBDIRS); do \
+ $(MAKE) -C $$dir distclean; \
+ done
+
+show: link
+ @echo "$(SUBDIRS)"
+
+$(SUBDIRS):
+ cd $@ && $(MAKE)
diff --git a/testsuite/lib/generic.sh b/testsuite/lib/generic.sh
new file mode 100644
index 0000000..f92260f
--- /dev/null
+++ b/testsuite/lib/generic.sh
@@ -0,0 +1,130 @@
+
+export DEST="127.0.0.1"
+
+ts_log()
+{
+ echo "$@"
+}
+
+ts_err()
+{
+ ts_log "$@" | tee >> $ERRF
+}
+
+ts_cat()
+{
+ cat "$@"
+}
+
+ts_err_cat()
+{
+ ts_cat "$@" | tee >> $ERRF
+}
+
+ts_skip()
+{
+ exit 127
+}
+
+__ts_cmd()
+{
+ CMD=$1; shift
+ SCRIPT=$1; shift
+ DESC=$1; shift
+
+ $CMD $@ 2> $STD_ERR > $STD_OUT
+
+ if [ -s $STD_ERR ]; then
+ ts_err "${SCRIPT}: ${DESC} failed:"
+ ts_err "command: $CMD $@"
+ ts_err "stderr output:"
+ ts_err_cat $STD_ERR
+ if [ -s $STD_OUT ]; then
+ ts_err "stdout output:"
+ ts_err_cat $STD_OUT
+ fi
+ elif [ -s $STD_OUT ]; then
+ echo "${SCRIPT}: ${DESC} succeeded with output:"
+ cat $STD_OUT
+ else
+ echo "${SCRIPT}: ${DESC} succeeded"
+ fi
+}
+
+ts_tc()
+{
+ __ts_cmd "$TC" "$@"
+}
+
+ts_ip()
+{
+ __ts_cmd "$IP" "$@"
+}
+
+ts_ss()
+{
+ __ts_cmd "$SS" "$@"
+}
+
+ts_qdisc_available()
+{
+ HELPOUT=`$TC qdisc add $1 help 2>&1`
+ if [ "`echo $HELPOUT | grep \"^Unknown qdisc\"`" ]; then
+ return 0;
+ else
+ return 1;
+ fi
+}
+
+rand_dev()
+{
+ rnd=""
+ while [ ${#rnd} -ne 6 ]; do
+ rnd="$(head -c 250 /dev/urandom | tr -dc '[:alpha:]' | head -c 6)"
+ done
+ echo "dev-$rnd"
+}
+
+pr_failed()
+{
+ echo " [FAILED]"
+ ts_err "matching failed"
+}
+
+pr_success()
+{
+ echo " [SUCCESS]"
+}
+
+test_on()
+{
+ echo -n "test on: \"$1\""
+ if cat "$STD_OUT" | grep -qE "$1"
+ then
+ pr_success
+ else
+ pr_failed
+ fi
+}
+
+test_on_not()
+{
+ echo -n "test on: \"$1\""
+ if cat "$STD_OUT" | grep -vqE "$1"
+ then
+ pr_success
+ else
+ pr_failed
+ fi
+}
+
+test_lines_count()
+{
+ echo -n "test on lines count ($1): "
+ if cat "$STD_OUT" | wc -l | grep -q "$1"
+ then
+ pr_success
+ else
+ pr_failed
+ fi
+}
diff --git a/testsuite/tests/ip/link/add_type_xfrm.t b/testsuite/tests/ip/link/add_type_xfrm.t
new file mode 100755
index 0000000..78ce28e
--- /dev/null
+++ b/testsuite/tests/ip/link/add_type_xfrm.t
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+. lib/generic.sh
+
+ts_log "[Testing Add XFRM Interface, With IF-ID]"
+
+PHYS_DEV="lo"
+NEW_DEV="$(rand_dev)"
+IF_ID="0xf"
+
+ts_ip "$0" "Add $NEW_DEV xfrm interface" link add dev $NEW_DEV type xfrm dev $PHYS_DEV if_id $IF_ID
+
+ts_ip "$0" "Show $NEW_DEV xfrm interface" -d link show dev $NEW_DEV
+test_on "$NEW_DEV"
+test_on "if_id $IF_ID"
+
+ts_ip "$0" "Del $NEW_DEV xfrm interface" link del dev $NEW_DEV
+
+
+ts_log "[Testing Add XFRM Interface, No IF-ID]"
+
+PHYS_DEV="lo"
+NEW_DEV="$(rand_dev)"
+IF_ID="0xf"
+
+ts_ip "$0" "Add $NEW_DEV xfrm interface" link add dev $NEW_DEV type xfrm dev $PHYS_DEV
+
+ts_ip "$0" "Show $NEW_DEV xfrm interface" -d link show dev $NEW_DEV
+test_on "$NEW_DEV"
+test_on_not "if_id $IF_ID"
+
+ts_ip "$0" "Del $NEW_DEV xfrm interface" link del dev $NEW_DEV
diff --git a/testsuite/tests/ip/link/new_link.t b/testsuite/tests/ip/link/new_link.t
new file mode 100755
index 0000000..c17650a
--- /dev/null
+++ b/testsuite/tests/ip/link/new_link.t
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+. lib/generic.sh
+
+ts_log "[Testing add/del virtual links]"
+
+NEW_DEV="$(rand_dev)"
+
+ts_ip "$0" "Add $NEW_DEV dummy interface" link add dev $NEW_DEV type dummy
+
+ts_ip "$0" "Show $NEW_DEV dummy interface" link show dev $NEW_DEV
+test_on "$NEW_DEV"
+test_lines_count 2
+
+ts_ip "$0" "Del $NEW_DEV dummy interface" link del dev $NEW_DEV
diff --git a/testsuite/tests/ip/link/show_dev_wo_vf_rate.t b/testsuite/tests/ip/link/show_dev_wo_vf_rate.t
new file mode 100755
index 0000000..5b3c004
--- /dev/null
+++ b/testsuite/tests/ip/link/show_dev_wo_vf_rate.t
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+. lib/generic.sh
+
+NL_FILE="tests/ip/link/dev_wo_vf_rate.nl"
+ts_ip "$0" "Show VF devices w/o VF rate info" -d monitor file $NL_FILE
diff --git a/testsuite/tests/ip/netns/set_nsid.t b/testsuite/tests/ip/netns/set_nsid.t
new file mode 100755
index 0000000..8f8c779
--- /dev/null
+++ b/testsuite/tests/ip/netns/set_nsid.t
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+. lib/generic.sh
+
+ts_log "[Testing netns nsid]"
+
+NS=testnsid
+NSID=99
+
+ts_ip "$0" "Add new netns $NS" netns add $NS
+ts_ip "$0" "Set $NS nsid to $NSID" netns set $NS $NSID
+
+ts_ip "$0" "List netns" netns list
+test_on "$NS \(id: $NSID\)"
+
+ts_ip "$0" "List netns without explicit list or show" netns
+test_on "$NS \(id: $NSID\)"
+
+ts_ip "$0" "List nsid" netns list-id
+test_on "$NSID \(iproute2 netns name: $NS\)"
+
+ts_ip "$0" "Delete netns $NS" netns del $NS
diff --git a/testsuite/tests/ip/netns/set_nsid_batch.t b/testsuite/tests/ip/netns/set_nsid_batch.t
new file mode 100755
index 0000000..196fd4b
--- /dev/null
+++ b/testsuite/tests/ip/netns/set_nsid_batch.t
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+. lib/generic.sh
+
+ts_log "[Testing netns nsid in batch mode]"
+
+NS=testnsid
+NSID=99
+BATCHFILE=`mktemp`
+
+echo "netns add $NS" >> $BATCHFILE
+echo "netns set $NS $NSID" >> $BATCHFILE
+echo "netns list-id" >> $BATCHFILE
+ts_ip "$0" "Add ns, set nsid and list in batch mode" -b $BATCHFILE
+test_on "nsid $NSID \(iproute2 netns name: $NS\)"
+rm -f $BATCHFILE
+
+ts_ip "$0" "Delete netns $NS" netns del $NS
diff --git a/testsuite/tests/ip/route/add_default_route.t b/testsuite/tests/ip/route/add_default_route.t
new file mode 100755
index 0000000..ded4edc
--- /dev/null
+++ b/testsuite/tests/ip/route/add_default_route.t
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+. lib/generic.sh
+
+ts_log "[Testing add default route]"
+
+DEV=dummy0
+
+ts_ip "$0" "Add new interface $DEV" link add $DEV type dummy
+ts_ip "$0" "Set $DEV into UP state" link set up dev $DEV
+
+ts_ip "$0" "Add 1.1.1.1/24 addr on $DEV" addr add 1.1.1.1/24 dev $DEV
+ts_ip "$0" "Add default route via 1.1.1.2" route add default via 1.1.1.2
+
+ts_ip "$0" "Show IPv4 default route" -4 route show default
+test_on "default via 1.1.1.2 dev $DEV"
+test_lines_count 1
+
+ts_ip "$0" "Add another IPv4 route dst 2.2.2.0/24" -4 route add 2.2.2.0/24 dev $DEV
+ts_ip "$0" "Show IPv4 default route" -4 route show default
+test_on "default via 1.1.1.2 dev $DEV"
+test_lines_count 1
+
+ts_ip "$0" "Add dead:beef::1/64 addr on $DEV" -6 addr add dead:beef::1/64 dev $DEV
+ts_ip "$0" "Add default route via dead:beef::2" route add default via dead:beef::2
+ts_ip "$0" "Show IPv6 default route" -6 route show default
+test_on "default via dead:beef::2 dev $DEV"
+test_lines_count 1
+
+ts_ip "$0" "Add another IPv6 route dst cafe:babe::/64" -6 route add cafe:babe::/64 dev $DEV
+ts_ip "$0" "Show IPv6 default route" -6 route show default
+test_on "default via dead:beef::2 dev $DEV"
+test_lines_count 1
+
+ts_ip "$0" "Del $DEV dummy interface" link del dev $DEV
diff --git a/testsuite/tests/ip/tunnel/add_tunnel.t b/testsuite/tests/ip/tunnel/add_tunnel.t
new file mode 100755
index 0000000..65db431
--- /dev/null
+++ b/testsuite/tests/ip/tunnel/add_tunnel.t
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+. lib/generic.sh
+
+TUNNEL_NAME="tunnel_test_ip"
+KMODS="ip6_gre ip6_tunnel ip_gre ip_tunnel gre"
+
+# unload kernel modules to remove dummy interfaces only if they were not in use beforehand
+kmods_remove=
+# note that checkbashism reports command -v, but dash supports it and it's POSIX 2008 compliant
+if command -v lsmod >/dev/null 2>&1 && command -v rmmod >/dev/null 2>&1; then
+ for i in $KMODS; do
+ lsmod | grep -q "^$i" || kmods_remove="$kmods_remove $i";
+ done
+fi
+
+ts_log "[Testing add/del tunnels]"
+
+ts_ip "$0" "Add GRE tunnel over IPv4" tunnel add name $TUNNEL_NAME mode gre local 1.1.1.1 remote 2.2.2.2
+ts_ip "$0" "Del GRE tunnel over IPv4" tunnel del $TUNNEL_NAME
+
+ts_ip "$0" "Add GRE tunnel over IPv6" tunnel add name $TUNNEL_NAME mode ip6gre local dead:beef::1 remote dead:beef::2
+ts_ip "$0" "Del GRE tunnel over IPv6" tunnel del $TUNNEL_NAME
+
+for mod in $kmods_remove; do
+ sudo rmmod "$mod"
+done
diff --git a/testsuite/tests/ss/ss1.dump b/testsuite/tests/ss/ss1.dump
new file mode 100644
index 0000000..9c27323
--- /dev/null
+++ b/testsuite/tests/ss/ss1.dump
Binary files differ
diff --git a/testsuite/tests/ss/ssfilter.t b/testsuite/tests/ss/ssfilter.t
new file mode 100755
index 0000000..3091054
--- /dev/null
+++ b/testsuite/tests/ss/ssfilter.t
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+. lib/generic.sh
+
+# % ./misc/ss -Htna
+# LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
+# ESTAB 0 0 10.0.0.1:22 10.0.0.1:36266
+# ESTAB 0 0 10.0.0.1:36266 10.0.0.1:22
+# ESTAB 0 0 10.0.0.1:22 10.0.0.2:50312
+export TCPDIAG_FILE="$(dirname $0)/ss1.dump"
+
+ts_log "[Testing ssfilter]"
+
+ts_ss "$0" "Match dport = 22" -Htna dport = 22
+test_on "ESTAB 0 0 10.0.0.1:36266 10.0.0.1:22"
+
+ts_ss "$0" "Match dport 22" -Htna dport 22
+test_on "ESTAB 0 0 10.0.0.1:36266 10.0.0.1:22"
+
+ts_ss "$0" "Match (dport)" -Htna '( dport = 22 )'
+test_on "ESTAB 0 0 10.0.0.1:36266 10.0.0.1:22"
+
+ts_ss "$0" "Match src = 0.0.0.0" -Htna src = 0.0.0.0
+test_on "LISTEN 0 128 0.0.0.0:22 0.0.0.0:*"
+
+ts_ss "$0" "Match src 0.0.0.0" -Htna src 0.0.0.0
+test_on "LISTEN 0 128 0.0.0.0:22 0.0.0.0:*"
+
+ts_ss "$0" "Match src sport" -Htna src 0.0.0.0 sport = 22
+test_on "LISTEN 0 128 0.0.0.0:22 0.0.0.0:*"
+
+ts_ss "$0" "Match src and sport" -Htna src 0.0.0.0 and sport = 22
+test_on "LISTEN 0 128 0.0.0.0:22 0.0.0.0:*"
+
+ts_ss "$0" "Match src and sport and dport" -Htna src 10.0.0.1 and sport = 22 and dport = 50312
+test_on "ESTAB 0 0 10.0.0.1:22 10.0.0.2:50312"
+
+ts_ss "$0" "Match src and sport and (dport)" -Htna 'src 10.0.0.1 and sport = 22 and ( dport = 50312 )'
+test_on "ESTAB 0 0 10.0.0.1:22 10.0.0.2:50312"
+
+ts_ss "$0" "Match src and (sport and dport)" -Htna 'src 10.0.0.1 and ( sport = 22 and dport = 50312 )'
+test_on "ESTAB 0 0 10.0.0.1:22 10.0.0.2:50312"
+
+ts_ss "$0" "Match (src and sport) and dport" -Htna '( src 10.0.0.1 and sport = 22 ) and dport = 50312'
+test_on "ESTAB 0 0 10.0.0.1:22 10.0.0.2:50312"
+
+ts_ss "$0" "Match (src or src) and dst" -Htna '( src 0.0.0.0 or src 10.0.0.1 ) and dst 10.0.0.2'
+test_on "ESTAB 0 0 10.0.0.1:22 10.0.0.2:50312"
diff --git a/testsuite/tests/tc/batch.t b/testsuite/tests/tc/batch.t
new file mode 100755
index 0000000..50e7ba3
--- /dev/null
+++ b/testsuite/tests/tc/batch.t
@@ -0,0 +1,23 @@
+#!/bin/sh
+. lib/generic.sh
+
+DEV="$(rand_dev)"
+ts_ip "$0" "Add $DEV dummy interface" link add dev $DEV type dummy
+ts_ip "$0" "Enable $DEV" link set $DEV up
+ts_tc "$0" "Add ingress qdisc" qdisc add dev $DEV clsact
+
+TMP="$(mktemp)"
+echo filt add dev $DEV ingress pref 1000 matchall action pass >> "$TMP"
+echo filt add dev $DEV ingress pref 1000 matchall action pass >> "$TMP"
+
+"$TC" -b "$TMP" 2> $STD_ERR > $STD_OUT
+if [ $? -eq 0 ]; then
+ ts_err "$0: batch passed when it should have failed"
+elif [ ! -s $STD_ERR ]; then
+ ts_err "$0: batch produced no error message"
+else
+ echo "$0: batch failed, as expected"
+fi
+
+rm "$TMP"
+ts_ip "$0" "Del $DEV dummy interface" link del dev $DEV
diff --git a/testsuite/tests/tc/cbq.t b/testsuite/tests/tc/cbq.t
new file mode 100755
index 0000000..bff814b
--- /dev/null
+++ b/testsuite/tests/tc/cbq.t
@@ -0,0 +1,10 @@
+#!/bin/sh
+$TC qdisc del dev $DEV root >/dev/null 2>&1
+$TC qdisc add dev $DEV root handle 10:0 cbq bandwidth 100Mbit avpkt 1400 mpu 64
+$TC class add dev $DEV parent 10:0 classid 10:12 cbq bandwidth 100mbit rate 100mbit allot 1514 prio 3 maxburst 1 avpkt 500 bounded
+$TC qdisc list dev $DEV
+$TC qdisc del dev $DEV root
+$TC qdisc list dev $DEV
+$TC qdisc add dev $DEV root handle 10:0 cbq bandwidth 100Mbit avpkt 1400 mpu 64
+$TC class add dev $DEV parent 10:0 classid 10:12 cbq bandwidth 100mbit rate 100mbit allot 1514 prio 3 maxburst 1 avpkt 500 bounded
+$TC qdisc del dev $DEV root
diff --git a/testsuite/tests/tc/dsmark.t b/testsuite/tests/tc/dsmark.t
new file mode 100755
index 0000000..3f1d5ef
--- /dev/null
+++ b/testsuite/tests/tc/dsmark.t
@@ -0,0 +1,31 @@
+#!/bin/sh
+# vim: ft=sh
+
+. lib/generic.sh
+
+ts_qdisc_available "dsmark"
+if [ $? -eq 0 ]; then
+ ts_log "dsmark: Unsupported by $TC, skipping"
+ exit 127
+fi
+
+ts_tc "dsmark" "dsmark root qdisc creation" \
+ qdisc add dev $DEV root handle 10:0 \
+ dsmark indices 64 default_index 1 set_tc_index
+
+ts_tc "dsmark" "dsmark class 1 creation" \
+ class change dev $DEV parent 10:0 classid 10:12 \
+ dsmark mask 0xff value 2
+
+ts_tc "dsmark" "dsmark class 2 creation" \
+ class change dev $DEV parent 10:0 classid 10:13 \
+ dsmark mask 0xfc value 4
+
+ts_tc "dsmark" "dsmark dump qdisc" \
+ qdisc list dev $DEV
+
+ts_tc "dsmark" "dsmark dump class" \
+ class list dev $DEV parent 10:0
+
+ts_tc "dsmark" "generic qdisc tree deletion" \
+ qdisc del dev $DEV root
diff --git a/testsuite/tests/tc/pedit.t b/testsuite/tests/tc/pedit.t
new file mode 100755
index 0000000..8d531a0
--- /dev/null
+++ b/testsuite/tests/tc/pedit.t
@@ -0,0 +1,217 @@
+#!/bin/sh
+
+. lib/generic.sh
+
+DEV="$(rand_dev)"
+ts_ip "$0" "Add $DEV dummy interface" link add dev $DEV type dummy
+ts_ip "$0" "Enable $DEV" link set $DEV up
+ts_tc "pedit" "Add ingress qdisc" qdisc add dev $DEV ingress
+
+
+do_pedit() {
+ ts_tc "pedit" "Drop ingress qdisc" \
+ qdisc del dev $DEV ingress
+ ts_tc "pedit" "Add ingress qdisc" \
+ qdisc add dev $DEV ingress
+ ts_tc "pedit" "Add pedit action $*" \
+ filter add dev $DEV parent ffff: \
+ u32 match u32 0 0 \
+ action pedit munge $@
+ ts_tc "pedit" "Show ingress filters" \
+ filter show dev $DEV parent ffff:
+}
+
+do_pedit offset 12 u32 set 0x12345678
+test_on "key #0 at 12: val 12345678 mask 00000000"
+do_pedit offset 12 u16 set 0x1234
+test_on "key #0 at 12: val 12340000 mask 0000ffff"
+do_pedit offset 14 u16 set 0x1234
+test_on "key #0 at 12: val 00001234 mask ffff0000"
+do_pedit offset 12 u8 set 0x23
+test_on "key #0 at 12: val 23000000 mask 00ffffff"
+do_pedit offset 13 u8 set 0x23
+test_on "key #0 at 12: val 00230000 mask ff00ffff"
+do_pedit offset 14 u8 set 0x23
+test_on "key #0 at 12: val 00002300 mask ffff00ff"
+do_pedit offset 15 u8 set 0x23
+test_on "key #0 at 12: val 00000023 mask ffffff00"
+
+do_pedit offset 13 u8 invert
+test_on "key #0 at 12: val 00ff0000 mask ffffffff"
+do_pedit offset 13 u8 clear
+test_on "key #0 at 12: val 00000000 mask ff00ffff"
+do_pedit offset 13 u8 preserve
+test_on "key #0 at 12: val 00000000 mask ffffffff"
+
+# the following set of tests has been auto-generated by running this little
+# shell script:
+#
+# do_it() {
+# echo "do_pedit $@"
+# tc qd del dev veth0 ingress >/dev/null 2>&1
+# tc qd add dev veth0 ingress >/dev/null 2>&1
+# tc filter add dev veth0 parent ffff: u32 \
+# match u32 0 0 \
+# action pedit munge $@ >/dev/null 2>&1
+# tc filter show dev veth0 parent ffff: | \
+# sed -n 's/^[\t ]*\(key #0.*\)/test_on "\1"/p'
+# }
+#
+# do_it_all() { # (field, val1 [, val2, ...])
+# local field=$1
+# shift
+# for val in $@; do
+# do_it ip $field set $val
+# done
+# for i in preserve invert clear; do
+# do_it ip $field $i
+# done
+# }
+#
+# do_it_all ihl 0x04 0x40
+# do_it_all src 1.2.3.4
+# do_it_all dst 1.2.3.4
+# do_it_all tos 0x1 0x10
+# do_it_all protocol 0x23
+# do_it_all nofrag 0x23 0xf4
+# do_it_all firstfrag 0x03 0xfa
+# do_it_all ce 0x23 0x04 0xf3
+# do_it_all df 0x23 0x04 0xf3
+# do_it_all mf 0x23 0x04 0xf3
+# do_it_all dport 0x1234
+# do_it_all sport 0x1234
+# do_it_all icmp_type 0x23
+# do_it_all icmp_code 0x23
+
+do_pedit ip ihl set 0x04
+test_on "key #0 at 0: val 04000000 mask f0ffffff"
+do_pedit ip ihl set 0x40
+test_on "key #0 at 0: val 00000000 mask f0ffffff"
+do_pedit ip ihl preserve
+test_on "key #0 at 0: val 00000000 mask ffffffff"
+do_pedit ip ihl invert
+test_on "key #0 at 0: val 0f000000 mask ffffffff"
+do_pedit ip ihl clear
+test_on "key #0 at 0: val 00000000 mask f0ffffff"
+do_pedit ip src set 1.2.3.4
+test_on "key #0 at 12: val 01020304 mask 00000000"
+do_pedit ip src preserve
+test_on "key #0 at 12: val 00000000 mask ffffffff"
+do_pedit ip src invert
+test_on "key #0 at 12: val ffffffff mask ffffffff"
+do_pedit ip src clear
+test_on "key #0 at 12: val 00000000 mask 00000000"
+do_pedit ip dst set 1.2.3.4
+test_on "key #0 at 16: val 01020304 mask 00000000"
+do_pedit ip dst preserve
+test_on "key #0 at 16: val 00000000 mask ffffffff"
+do_pedit ip dst invert
+test_on "key #0 at 16: val ffffffff mask ffffffff"
+do_pedit ip dst clear
+test_on "key #0 at 16: val 00000000 mask 00000000"
+do_pedit ip tos set 0x1
+test_on "key #0 at 0: val 00010000 mask ff00ffff"
+do_pedit ip tos set 0x10
+test_on "key #0 at 0: val 00100000 mask ff00ffff"
+do_pedit ip tos preserve
+test_on "key #0 at 0: val 00000000 mask ffffffff"
+do_pedit ip tos invert
+test_on "key #0 at 0: val 00ff0000 mask ffffffff"
+do_pedit ip tos clear
+test_on "key #0 at 0: val 00000000 mask ff00ffff"
+do_pedit ip protocol set 0x23
+test_on "key #0 at 8: val 00230000 mask ff00ffff"
+do_pedit ip protocol preserve
+test_on "key #0 at 8: val 00000000 mask ffffffff"
+do_pedit ip protocol invert
+test_on "key #0 at 8: val 00ff0000 mask ffffffff"
+do_pedit ip protocol clear
+test_on "key #0 at 8: val 00000000 mask ff00ffff"
+do_pedit ip nofrag set 0x23
+test_on "key #0 at 4: val 00002300 mask ffffc0ff"
+do_pedit ip nofrag set 0xf4
+test_on "key #0 at 4: val 00003400 mask ffffc0ff"
+do_pedit ip nofrag preserve
+test_on "key #0 at 4: val 00000000 mask ffffffff"
+do_pedit ip nofrag invert
+test_on "key #0 at 4: val 00003f00 mask ffffffff"
+do_pedit ip nofrag clear
+test_on "key #0 at 4: val 00000000 mask ffffc0ff"
+do_pedit ip firstfrag set 0x03
+test_on "key #0 at 4: val 00000300 mask ffffe0ff"
+do_pedit ip firstfrag set 0xfa
+test_on "key #0 at 4: val 00001a00 mask ffffe0ff"
+do_pedit ip firstfrag preserve
+test_on "key #0 at 4: val 00000000 mask ffffffff"
+do_pedit ip firstfrag invert
+test_on "key #0 at 4: val 00001f00 mask ffffffff"
+do_pedit ip firstfrag clear
+test_on "key #0 at 4: val 00000000 mask ffffe0ff"
+do_pedit ip ce set 0x23
+test_on "key #0 at 4: val 00000000 mask ffff7fff"
+do_pedit ip ce set 0x04
+test_on "key #0 at 4: val 00000000 mask ffff7fff"
+do_pedit ip ce set 0xf3
+test_on "key #0 at 4: val 00008000 mask ffff7fff"
+do_pedit ip ce preserve
+test_on "key #0 at 4: val 00000000 mask ffffffff"
+do_pedit ip ce invert
+test_on "key #0 at 4: val 00008000 mask ffffffff"
+do_pedit ip ce clear
+test_on "key #0 at 4: val 00000000 mask ffff7fff"
+do_pedit ip df set 0x23
+test_on "key #0 at 4: val 00000000 mask ffffbfff"
+do_pedit ip df set 0x04
+test_on "key #0 at 4: val 00000000 mask ffffbfff"
+do_pedit ip df set 0xf3
+test_on "key #0 at 4: val 00004000 mask ffffbfff"
+do_pedit ip df preserve
+test_on "key #0 at 4: val 00000000 mask ffffffff"
+do_pedit ip df invert
+test_on "key #0 at 4: val 00004000 mask ffffffff"
+do_pedit ip df clear
+test_on "key #0 at 4: val 00000000 mask ffffbfff"
+do_pedit ip mf set 0x23
+test_on "key #0 at 4: val 00002000 mask ffffdfff"
+do_pedit ip mf set 0x04
+test_on "key #0 at 4: val 00000000 mask ffffdfff"
+do_pedit ip mf set 0xf3
+test_on "key #0 at 4: val 00002000 mask ffffdfff"
+do_pedit ip mf preserve
+test_on "key #0 at 4: val 00000000 mask ffffffff"
+do_pedit ip mf invert
+test_on "key #0 at 4: val 00002000 mask ffffffff"
+do_pedit ip mf clear
+test_on "key #0 at 4: val 00000000 mask ffffdfff"
+do_pedit ip dport set 0x1234
+test_on "key #0 at 20: val 00001234 mask ffff0000"
+do_pedit ip dport preserve
+test_on "key #0 at 20: val 00000000 mask ffffffff"
+do_pedit ip dport invert
+test_on "key #0 at 20: val 0000ffff mask ffffffff"
+do_pedit ip dport clear
+test_on "key #0 at 20: val 00000000 mask ffff0000"
+do_pedit ip sport set 0x1234
+test_on "key #0 at 20: val 12340000 mask 0000ffff"
+do_pedit ip sport preserve
+test_on "key #0 at 20: val 00000000 mask ffffffff"
+do_pedit ip sport invert
+test_on "key #0 at 20: val ffff0000 mask ffffffff"
+do_pedit ip sport clear
+test_on "key #0 at 20: val 00000000 mask 0000ffff"
+do_pedit ip icmp_type set 0x23
+test_on "key #0 at 20: val 23000000 mask 00ffffff"
+do_pedit ip icmp_type preserve
+test_on "key #0 at 20: val 00000000 mask ffffffff"
+do_pedit ip icmp_type invert
+test_on "key #0 at 20: val ff000000 mask ffffffff"
+do_pedit ip icmp_type clear
+test_on "key #0 at 20: val 00000000 mask 00ffffff"
+do_pedit ip icmp_code set 0x23
+test_on "key #0 at 20: val 23000000 mask 00ffffff"
+do_pedit ip icmp_code preserve
+test_on "key #0 at 20: val 00000000 mask ffffffff"
+do_pedit ip icmp_code invert
+test_on "key #0 at 20: val ff000000 mask ffffffff"
+do_pedit ip icmp_code clear
+test_on "key #0 at 20: val 00000000 mask 00ffffff"
diff --git a/testsuite/tests/tc/policer.t b/testsuite/tests/tc/policer.t
new file mode 100755
index 0000000..eaf16ac
--- /dev/null
+++ b/testsuite/tests/tc/policer.t
@@ -0,0 +1,13 @@
+#!/bin/sh
+$TC qdisc del dev $DEV root >/dev/null 2>&1
+$TC qdisc add dev $DEV root handle 10:0 cbq bandwidth 100Mbit avpkt 1400 mpu 64
+$TC class add dev $DEV parent 10:0 classid 10:12 cbq bandwidth 100mbit rate 100mbit allot 1514 prio 3 maxburst 1 avpkt 500 bounded
+$TC filter add dev $DEV parent 10:0 protocol ip prio 10 u32 match ip protocol 1 0xff police rate 2kbit buffer 10k drop flowid 10:12
+$TC qdisc list dev $DEV
+$TC filter list dev $DEV parent 10:0
+$TC qdisc del dev $DEV root
+$TC qdisc list dev $DEV
+$TC qdisc add dev $DEV root handle 10:0 cbq bandwidth 100Mbit avpkt 1400 mpu 64
+$TC class add dev $DEV parent 10:0 classid 10:12 cbq bandwidth 100mbit rate 100mbit allot 1514 prio 3 maxburst 1 avpkt 500 bounded
+$TC filter add dev $DEV parent 10:0 protocol ip prio 10 u32 match ip protocol 1 0xff police rate 2kbit buffer 10k drop flowid 10:12
+$TC qdisc del dev $DEV root
diff --git a/testsuite/tools/Makefile b/testsuite/tools/Makefile
new file mode 100644
index 0000000..e3e771d
--- /dev/null
+++ b/testsuite/tools/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS=
+include ../../config.mk
+
+generate_nlmsg: generate_nlmsg.c ../../lib/libnetlink.c
+ $(QUIET_CC)$(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) -I../../include -I../../include/uapi -include../../include/uapi/linux/netlink.h -o $@ $^ -lmnl
+
+clean:
+ rm -f generate_nlmsg
diff --git a/testsuite/tools/generate_nlmsg.c b/testsuite/tools/generate_nlmsg.c
new file mode 100644
index 0000000..fe96f26
--- /dev/null
+++ b/testsuite/tools/generate_nlmsg.c
@@ -0,0 +1,116 @@
+/*
+ * generate_nlmsg.c Testsuite helper generating nlmsg blob
+ *
+ * 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: Phil Sutter <phil@nwl.cc>
+ */
+
+#include <netinet/ether.h>
+#include <libnetlink.h>
+#include <sys/socket.h>
+#include <linux/if.h>
+#include <errno.h>
+#include <stdio.h>
+
+int fill_vf_rate_test(void *buf, size_t buflen)
+{
+ char bcmac[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ struct ifla_vf_mac vf_mac = {
+ .mac = { 0x0, 0x26, 0x6c, 0xff, 0xb5, 0xc0 },
+ };
+ struct ifla_vf_link_state vf_link_state = { 0 };
+ struct ifla_vf_tx_rate vf_tx_rate = { 0 };
+ struct ifla_vf_spoofchk vf_spoofchk = {
+ .setting = 1,
+ };
+ struct ifla_vf_vlan vf_vlan = { 0 };
+ struct rtattr *vfinfo_list, *vfinfo;
+ struct nlmsghdr *h = buf;
+ struct ifinfomsg *ifi;
+
+ h->nlmsg_type = RTM_NEWLINK;
+ h->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
+
+ ifi = NLMSG_DATA(h);
+ ifi->ifi_type = ARPHRD_ETHER;
+ ifi->ifi_index = 1;
+ ifi->ifi_flags = IFF_RUNNING | IFF_BROADCAST |
+ IFF_MULTICAST | IFF_UP | IFF_LOWER_UP;
+
+#define ASSERT(x) if (x < 0) return -1
+#define ATTR_L(t, v, l) ASSERT(addattr_l(h, buflen, t, v, l))
+#define ATTR_8(t, v) ASSERT(addattr8(h, buflen, t, v))
+#define ATTR_32(t, v) ASSERT(addattr32(h, buflen, t, v))
+#define ATTR_STRZ(t, v) ASSERT(addattrstrz(h, buflen, t, v))
+
+#define NEST(t) addattr_nest(h, buflen, t)
+#define NEST_END(t) addattr_nest_end(h, t)
+
+ ATTR_STRZ(IFLA_IFNAME, "eth0");
+ ATTR_32(IFLA_TXQLEN, 10000);
+ ATTR_8(IFLA_OPERSTATE, 6);
+ ATTR_8(IFLA_LINKMODE, 0);
+ ATTR_32(IFLA_MTU, 9000);
+ ATTR_32(IFLA_GROUP, 0);
+ ATTR_32(IFLA_PROMISCUITY, 0);
+ ATTR_32(IFLA_NUM_TX_QUEUES, 8);
+ ATTR_32(IFLA_NUM_RX_QUEUES, 8);
+ ATTR_8(IFLA_CARRIER, 1);
+ ATTR_STRZ(IFLA_QDISC, "mq");
+ ATTR_L(IFLA_ADDRESS, vf_mac.mac, ETH_ALEN);
+ ATTR_L(IFLA_BROADCAST, bcmac, sizeof(bcmac));
+ ATTR_32(IFLA_NUM_VF, 2);
+
+ vfinfo_list = NEST(IFLA_VFINFO_LIST);
+
+ vfinfo = NEST(IFLA_VF_INFO);
+ ATTR_L(IFLA_VF_MAC, &vf_mac, sizeof(vf_mac));
+ ATTR_L(IFLA_VF_VLAN, &vf_vlan, sizeof(vf_vlan));
+ ATTR_L(IFLA_VF_TX_RATE, &vf_tx_rate, sizeof(vf_tx_rate));
+ ATTR_L(IFLA_VF_SPOOFCHK, &vf_spoofchk, sizeof(vf_spoofchk));
+ ATTR_L(IFLA_VF_LINK_STATE, &vf_link_state, sizeof(vf_link_state));
+ NEST_END(vfinfo);
+
+ vf_mac.vf = vf_vlan.vf = vf_tx_rate.vf = 1;
+ vf_spoofchk.vf = vf_link_state.vf = 1;
+
+ vfinfo = NEST(IFLA_VF_INFO);
+ ATTR_L(IFLA_VF_MAC, &vf_mac, sizeof(vf_mac));
+ ATTR_L(IFLA_VF_VLAN, &vf_vlan, sizeof(vf_vlan));
+ ATTR_L(IFLA_VF_TX_RATE, &vf_tx_rate, sizeof(vf_tx_rate));
+ ATTR_L(IFLA_VF_SPOOFCHK, &vf_spoofchk, sizeof(vf_spoofchk));
+ ATTR_L(IFLA_VF_LINK_STATE, &vf_link_state, sizeof(vf_link_state));
+ NEST_END(vfinfo);
+
+ NEST_END(vfinfo_list);
+
+ return h->nlmsg_len;
+}
+
+int main(void)
+{
+ char buf[16384] = { 0 };
+ int msglen;
+ FILE *fp;
+
+ msglen = fill_vf_rate_test(buf, sizeof(buf));
+ if (msglen < 0) {
+ fprintf(stderr, "fill_vf_rate_test() failed!\n");
+ return 1;
+ }
+ fp = fopen("tests/ip/link/dev_wo_vf_rate.nl", "w");
+ if (!fp) {
+ perror("fopen()");
+ return 1;
+ }
+ if (fwrite(buf, msglen, 1, fp) != 1) {
+ perror("fwrite()");
+ return 1;
+ }
+ fclose(fp);
+ return 0;
+}
diff --git a/tipc/.gitignore b/tipc/.gitignore
new file mode 100644
index 0000000..39ed83d
--- /dev/null
+++ b/tipc/.gitignore
@@ -0,0 +1 @@
+tipc
diff --git a/tipc/Makefile b/tipc/Makefile
new file mode 100644
index 0000000..a10debe
--- /dev/null
+++ b/tipc/Makefile
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../config.mk
+
+TARGETS :=
+
+ifeq ($(HAVE_MNL),y)
+
+TIPCOBJ=bearer.o \
+ cmdl.o link.o \
+ media.o misc.o \
+ msg.o nametable.o \
+ node.o socket.o \
+ peer.o tipc.o
+
+TARGETS += tipc
+
+endif
+
+all: $(TARGETS) $(LIBS)
+
+tipc: $(TIPCOBJ)
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+ for i in $(TARGETS); \
+ do install -m 0755 $$i $(DESTDIR)$(SBINDIR); \
+ done
+
+clean:
+ rm -f $(TIPCOBJ) $(TARGETS)
diff --git a/tipc/README b/tipc/README
new file mode 100644
index 0000000..578a0b7
--- /dev/null
+++ b/tipc/README
@@ -0,0 +1,63 @@
+DESIGN DECISIONS
+----------------
+
+HELP
+~~~~
+--help or -h is used for help. We do not reserve the bare word "help", which
+for example the ip command does. Reserving a bare word like help quickly
+becomes cumbersome to handle in the code. It might be simple to handle
+when it's passed early in the command chain like "ip addr help". But when
+the user tries to pass "help" further down this requires manual checks and
+special treatment. For example, at the time of writing this tool, it's
+possible to create a vlan named "help" with the ip tool, but it's impossible
+to remove it, the command just shows help. This is an effect of treating
+bare words specially.
+
+Help texts are not dynamically generated. That is, we do not pass datastructures
+like command list or option lists and print them dynamically. This is
+intentional. There is always that exception and when it comes to help texts
+these exceptions are normally neglected at the expence of usability.
+
+KEY-VALUE
+~~~~~~~~~
+All options are key-values. There are both drawbacks and benefits to this.
+The main drawback is that it becomes more to write for the user and
+information might seem redundant. The main benefits is scalability and code
+simplification. Consistency is important.
+
+Consider this.
+1. tipc link set priority PRIO link LINK
+2. tipc link set LINK priority PRIO
+
+Link might seem redundant in (1). However, if the command should live for many
+years and be able to evolve example (2) limits the set command to only work on a
+single link with no ability to extend. As an example, lets say we introduce
+grouping on the kernel side.
+
+1. tipc link set priority PRIO group GROUP
+2. tipc link set ??? priority PRIO group GROUP
+
+2. breaks, we can't extend the command to cover a group.
+
+PARSING
+~~~~~~~
+Commands are single words. As an example, all words in "tipc link list" are
+commands. Options are key-values that can be given in any order. In
+"tipc link set priority PRIO link LINK" "tipc link set" are commands while
+priority and link are options. Meaning that they can be given like
+"tipc link set link LINK priority PRIO".
+
+Abbreviation matching works for both command and options. Meaning that
+"tipc link set priority PRIO link LINK" could be given as
+"tipc l s p PRIO l LINK" and "tipc link list" as "tipc l l".
+
+MEMORY
+~~~~~~
+The tool strives to avoid allocating memory on the heap. Most (if not all)
+memory allocations are on the stack.
+
+RETURNING
+~~~~~~~~~
+The tool could throw exit() deep down in functions but doing so always seems
+to limit the program in the long run. So we output the error and return an
+appropriate error code upon failure.
diff --git a/tipc/bearer.c b/tipc/bearer.c
new file mode 100644
index 0000000..4470819
--- /dev/null
+++ b/tipc/bearer.c
@@ -0,0 +1,1102 @@
+/*
+ * bearer.c TIPC bearer functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <arpa/inet.h>
+
+#include <linux/tipc_netlink.h>
+#include <linux/tipc.h>
+#include <linux/genetlink.h>
+#include <linux/if.h>
+
+#include <libmnl/libmnl.h>
+#include <sys/socket.h>
+
+#include "utils.h"
+#include "cmdl.h"
+#include "msg.h"
+#include "bearer.h"
+
+#define UDP_PROP_IP 1
+#define UDP_PROP_PORT 2
+
+struct cb_data {
+ int attr;
+ int prop;
+ struct nlmsghdr *nlh;
+};
+
+static void _print_bearer_opts(void)
+{
+ fprintf(stderr,
+ "OPTIONS\n"
+ " priority - Bearer link priority\n"
+ " tolerance - Bearer link tolerance\n"
+ " window - Bearer link window\n"
+ " mtu - Bearer link mtu\n");
+}
+
+void print_bearer_media(void)
+{
+ fprintf(stderr,
+ "\nMEDIA\n"
+ " udp - User Datagram Protocol\n"
+ " ib - Infiniband\n"
+ " eth - Ethernet\n");
+}
+
+static void cmd_bearer_enable_l2_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr,
+ "Usage: %s bearer enable media %s device DEVICE [OPTIONS]\n"
+ "\nOPTIONS\n"
+ " domain DOMAIN - Discovery domain\n"
+ " priority PRIORITY - Bearer priority\n",
+ cmdl->argv[0], media);
+}
+
+static void cmd_bearer_enable_udp_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr,
+ "Usage: %s bearer enable [OPTIONS] media %s name NAME [localip IP|device DEVICE] [UDP OPTIONS]\n\n"
+ "OPTIONS\n"
+ " domain DOMAIN - Discovery domain\n"
+ " priority PRIORITY - Bearer priority\n\n"
+ "UDP OPTIONS\n"
+ " localport PORT - Local UDP port (default 6118)\n"
+ " remoteip IP - Remote IP address\n"
+ " remoteport PORT - Remote UDP port (default 6118)\n",
+ cmdl->argv[0], media);
+}
+
+static int get_netid_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_NET_MAX + 1] = {};
+ int *netid = (int*)data;
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_NET])
+ return MNL_CB_ERROR;
+ mnl_attr_parse_nested(info[TIPC_NLA_NET], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_NET_ID])
+ return MNL_CB_ERROR;
+ *netid = mnl_attr_get_u32(attrs[TIPC_NLA_NET_ID]);
+
+ return MNL_CB_OK;
+}
+
+static int generate_multicast(short af, char *buf, int bufsize)
+{
+ int netid;
+ char mnl_msg[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+
+ if (!(nlh = msg_init(mnl_msg, TIPC_NL_NET_GET))) {
+ fprintf(stderr, "error, message initialization failed\n");
+ return -1;
+ }
+ if (msg_dumpit(nlh, get_netid_cb, &netid)) {
+ fprintf(stderr, "error, failed to fetch TIPC network id from kernel\n");
+ return -EINVAL;
+ }
+ if (af == AF_INET)
+ snprintf(buf, bufsize, "228.0.%u.%u", (netid>>8) & 0xFF, netid & 0xFF);
+ else
+ snprintf(buf, bufsize, "ff02::%u", netid);
+
+ return 0;
+}
+
+static struct ifreq ifr;
+static int nl_dump_req_filter(struct nlmsghdr *nlh, int reqlen)
+{
+ struct ifaddrmsg *ifa = NLMSG_DATA(nlh);
+
+ ifa->ifa_index = ifr.ifr_ifindex;
+
+ return 0;
+}
+
+static int nl_dump_addr_filter(struct nlmsghdr *nlh, void *arg)
+{
+ struct ifaddrmsg *ifa = NLMSG_DATA(nlh);
+ char *r_addr = (char *)arg;
+ int len = nlh->nlmsg_len;
+ struct rtattr *addr_attr;
+
+ if (ifr.ifr_ifindex != ifa->ifa_index)
+ return 0;
+
+ if (strlen(r_addr) > 0)
+ return 0;
+
+ addr_attr = parse_rtattr_one(IFA_ADDRESS, IFA_RTA(ifa),
+ len - NLMSG_LENGTH(sizeof(*ifa)));
+ if (!addr_attr)
+ return 0;
+
+ if (ifa->ifa_family == AF_INET) {
+ struct sockaddr_in ip4addr;
+ memcpy(&ip4addr.sin_addr, RTA_DATA(addr_attr),
+ sizeof(struct in_addr));
+ inet_ntop(AF_INET, &ip4addr.sin_addr, r_addr,
+ INET_ADDRSTRLEN);
+ } else if (ifa->ifa_family == AF_INET6) {
+ struct sockaddr_in6 ip6addr;
+ memcpy(&ip6addr.sin6_addr, RTA_DATA(addr_attr),
+ sizeof(struct in6_addr));
+ inet_ntop(AF_INET6, &ip6addr.sin6_addr, r_addr,
+ INET6_ADDRSTRLEN);
+ }
+ return 0;
+}
+
+static int cmd_bearer_validate_and_get_addr(const char *name, char *r_addr)
+{
+ struct rtnl_handle rth = { .fd = -1 };
+ int err = -1;
+
+ memset(&ifr, 0, sizeof(ifr));
+ if (!name || !r_addr || get_ifname(ifr.ifr_name, name))
+ return err;
+
+ ifr.ifr_ifindex = ll_name_to_index(ifr.ifr_name);
+ if (!ifr.ifr_ifindex)
+ return err;
+
+ /* remove from cache */
+ ll_drop_by_index(ifr.ifr_ifindex);
+
+ if ((err = rtnl_open(&rth, 0)) < 0)
+ return err;
+
+ if ((err = rtnl_addrdump_req(&rth, AF_UNSPEC, nl_dump_req_filter)) > 0)
+ err = rtnl_dump_filter(&rth, nl_dump_addr_filter, r_addr);
+
+ rtnl_close(&rth);
+ return err;
+}
+
+static int nl_add_udp_enable_opts(struct nlmsghdr *nlh, struct opt *opts,
+ struct cmdl *cmdl)
+{
+ int err;
+ struct opt *opt;
+ struct nlattr *nest;
+ char buf[INET6_ADDRSTRLEN];
+ char *locport = "6118";
+ char *remport = "6118";
+ char *locip = NULL;
+ char *remip = NULL;
+ struct addrinfo *loc = NULL;
+ struct addrinfo *rem = NULL;
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM
+ };
+ char addr[INET6_ADDRSTRLEN] = {0};
+
+ opt = get_opt(opts, "device");
+ if (opt && cmd_bearer_validate_and_get_addr(opt->val, addr) < 0) {
+ fprintf(stderr, "error, no device name available\n");
+ return -EINVAL;
+ }
+
+ if (strlen(addr) > 0) {
+ locip = addr;
+ } else {
+ opt = get_opt(opts, "localip");
+ if (!opt) {
+ fprintf(stderr, "error, udp bearer localip/device missing\n");
+ cmd_bearer_enable_udp_help(cmdl, "udp");
+ return -EINVAL;
+ }
+ locip = opt->val;
+ }
+
+ if ((opt = get_opt(opts, "remoteip")))
+ remip = opt->val;
+
+ if ((opt = get_opt(opts, "localport")))
+ locport = opt->val;
+
+ if ((opt = get_opt(opts, "remoteport")))
+ remport = opt->val;
+
+ if ((err = getaddrinfo(locip, locport, &hints, &loc))) {
+ fprintf(stderr, "UDP local address error: %s\n",
+ gai_strerror(err));
+ return err;
+ }
+
+ if (!remip) {
+ if (generate_multicast(loc->ai_family, buf, sizeof(buf))) {
+ fprintf(stderr, "Failed to generate multicast address\n");
+ freeaddrinfo(loc);
+ return -EINVAL;
+ }
+ remip = buf;
+ }
+
+ if ((err = getaddrinfo(remip, remport, &hints, &rem))) {
+ fprintf(stderr, "UDP remote address error: %s\n",
+ gai_strerror(err));
+ freeaddrinfo(loc);
+ return err;
+ }
+
+ if (rem->ai_family != loc->ai_family) {
+ fprintf(stderr, "UDP local and remote AF mismatch\n");
+ freeaddrinfo(rem);
+ freeaddrinfo(loc);
+ return -EINVAL;
+ }
+
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER_UDP_OPTS);
+ mnl_attr_put(nlh, TIPC_NLA_UDP_LOCAL, loc->ai_addrlen, loc->ai_addr);
+ mnl_attr_put(nlh, TIPC_NLA_UDP_REMOTE, rem->ai_addrlen, rem->ai_addr);
+ mnl_attr_nest_end(nlh, nest);
+
+ freeaddrinfo(rem);
+ freeaddrinfo(loc);
+
+ return 0;
+}
+
+static char *cmd_get_media_type(const struct cmd *cmd, struct cmdl *cmdl,
+ struct opt *opts)
+{
+ struct opt *opt = get_opt(opts, "media");
+
+ if (!opt) {
+ if (help_flag)
+ (cmd->help)(cmdl);
+ else
+ fprintf(stderr, "error, missing bearer media\n");
+ return NULL;
+ }
+ return opt->val;
+}
+
+static int nl_add_bearer_name(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, struct opt *opts,
+ const struct tipc_sup_media *sup_media)
+{
+ char bname[TIPC_MAX_BEARER_NAME];
+ int err;
+
+ if ((err = cmd_get_unique_bearer_name(cmd, cmdl, opts, bname, sup_media)))
+ return err;
+
+ mnl_attr_put_strz(nlh, TIPC_NLA_BEARER_NAME, bname);
+ return 0;
+}
+
+int cmd_get_unique_bearer_name(const struct cmd *cmd, struct cmdl *cmdl,
+ struct opt *opts, char *bname,
+ const struct tipc_sup_media *sup_media)
+{
+ char *media;
+ char *identifier;
+ struct opt *opt;
+ const struct tipc_sup_media *entry;
+
+ if (!(media = cmd_get_media_type(cmd, cmdl, opts)))
+ return -EINVAL;
+
+ for (entry = sup_media; entry->media; entry++) {
+ if (strcmp(entry->media, media))
+ continue;
+
+ if (!(opt = get_opt(opts, entry->identifier))) {
+ if (help_flag)
+ (entry->help)(cmdl, media);
+ else
+ fprintf(stderr, "error, missing bearer %s\n",
+ entry->identifier);
+ return -EINVAL;
+ }
+
+ identifier = opt->val;
+ snprintf(bname, TIPC_MAX_BEARER_NAME, "%s:%s", media, identifier);
+
+ return 0;
+ }
+
+ fprintf(stderr, "error, invalid media type %s\n", media);
+
+ return -EINVAL;
+}
+
+static void cmd_bearer_add_udp_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr, "Usage: %s bearer add media %s name NAME remoteip REMOTEIP\n\n",
+ cmdl->argv[0], media);
+}
+
+static void cmd_bearer_add_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s bearer add media udp name NAME remoteip REMOTEIP\n",
+ cmdl->argv[0]);
+}
+
+static int udp_bearer_add(struct nlmsghdr *nlh, struct opt *opts,
+ struct cmdl *cmdl)
+{
+ int err;
+ struct opt *opt;
+ struct nlattr *opts_nest;
+ char *remport = "6118";
+
+ opts_nest = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER_UDP_OPTS);
+
+ if ((opt = get_opt(opts, "remoteport")))
+ remport = opt->val;
+
+ if ((opt = get_opt(opts, "remoteip"))) {
+ char *ip = opt->val;
+ struct addrinfo *addr = NULL;
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM
+ };
+
+ if ((err = getaddrinfo(ip, remport, &hints, &addr))) {
+ fprintf(stderr, "UDP address error: %s\n",
+ gai_strerror(err));
+ freeaddrinfo(addr);
+ return err;
+ }
+
+ mnl_attr_put(nlh, TIPC_NLA_UDP_REMOTE, addr->ai_addrlen,
+ addr->ai_addr);
+ freeaddrinfo(addr);
+ } else {
+ fprintf(stderr, "error, missing remoteip\n");
+ return -EINVAL;
+ }
+ mnl_attr_nest_end(nlh, opts_nest);
+
+ return 0;
+}
+
+static int cmd_bearer_add_media(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int err;
+ char *media;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct opt *opt;
+ struct nlattr *attrs;
+ struct opt opts[] = {
+ { "remoteip", OPT_KEYVAL, NULL },
+ { "remoteport", OPT_KEYVAL, NULL },
+ { "name", OPT_KEYVAL, NULL },
+ { "media", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+ const struct tipc_sup_media sup_media[] = {
+ { "udp", "name", cmd_bearer_add_udp_help},
+ { NULL, },
+ };
+
+ /* Rewind optind to include media in the option list */
+ cmdl->optind--;
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ if (!(opt = get_opt(opts, "media"))) {
+ fprintf(stderr, "error, missing media value\n");
+ return -EINVAL;
+ }
+ media = opt->val;
+
+ if (strcmp(media, "udp") != 0) {
+ fprintf(stderr, "error, no \"%s\" media specific options available\n",
+ media);
+ return -EINVAL;
+ }
+ if (!(opt = get_opt(opts, "name"))) {
+ fprintf(stderr, "error, missing media name\n");
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_BEARER_ADD))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ attrs = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER);
+ err = nl_add_bearer_name(nlh, cmd, cmdl, opts, sup_media);
+ if (err)
+ return err;
+
+ err = udp_bearer_add(nlh, opts, cmdl);
+ if (err)
+ return err;
+
+ mnl_attr_nest_end(nlh, attrs);
+
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static int cmd_bearer_add(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "media", cmd_bearer_add_media, cmd_bearer_add_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static void cmd_bearer_enable_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s bearer enable [OPTIONS] media MEDIA ARGS...\n\n"
+ "OPTIONS\n"
+ " domain DOMAIN - Discovery domain\n"
+ " priority PRIORITY - Bearer priority\n",
+ cmdl->argv[0]);
+ print_bearer_media();
+}
+
+static int cmd_bearer_enable(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int err;
+ struct opt *opt;
+ struct nlattr *nest;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct opt opts[] = {
+ { "device", OPT_KEYVAL, NULL },
+ { "domain", OPT_KEYVAL, NULL },
+ { "localip", OPT_KEYVAL, NULL },
+ { "localport", OPT_KEYVAL, NULL },
+ { "media", OPT_KEYVAL, NULL },
+ { "name", OPT_KEYVAL, NULL },
+ { "priority", OPT_KEYVAL, NULL },
+ { "remoteip", OPT_KEYVAL, NULL },
+ { "remoteport", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+ struct tipc_sup_media sup_media[] = {
+ { "udp", "name", cmd_bearer_enable_udp_help},
+ { "eth", "device", cmd_bearer_enable_l2_help },
+ { "ib", "device", cmd_bearer_enable_l2_help },
+ { NULL, },
+ };
+
+ if (parse_opts(opts, cmdl) < 0) {
+ if (help_flag)
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_BEARER_ENABLE))) {
+ fprintf(stderr, "error: message initialisation failed\n");
+ return -1;
+ }
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER);
+
+ if ((opt = get_opt(opts, "domain")))
+ mnl_attr_put_u32(nlh, TIPC_NLA_BEARER_DOMAIN, atoi(opt->val));
+
+ if ((opt = get_opt(opts, "priority"))) {
+ struct nlattr *props;
+
+ props = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER_PROP);
+ mnl_attr_put_u32(nlh, TIPC_NLA_PROP_PRIO, atoi(opt->val));
+ mnl_attr_nest_end(nlh, props);
+ }
+
+ err = nl_add_bearer_name(nlh, cmd, cmdl, opts, sup_media);
+ if (err)
+ return err;
+
+ opt = get_opt(opts, "media");
+ if (opt && strcmp(opt->val, "udp") == 0) {
+ err = nl_add_udp_enable_opts(nlh, opts, cmdl);
+ if (err)
+ return err;
+ }
+ mnl_attr_nest_end(nlh, nest);
+
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static void cmd_bearer_disable_l2_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr, "Usage: %s bearer disable media %s device DEVICE\n",
+ cmdl->argv[0], media);
+}
+
+static void cmd_bearer_disable_udp_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr, "Usage: %s bearer disable media %s name NAME\n",
+ cmdl->argv[0], media);
+}
+
+static void cmd_bearer_disable_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s bearer disable media MEDIA ARGS...\n",
+ cmdl->argv[0]);
+ print_bearer_media();
+}
+
+static int cmd_bearer_disable(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int err;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *nest;
+ struct opt opts[] = {
+ { "device", OPT_KEYVAL, NULL },
+ { "name", OPT_KEYVAL, NULL },
+ { "media", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+ struct tipc_sup_media sup_media[] = {
+ { "udp", "name", cmd_bearer_disable_udp_help},
+ { "eth", "device", cmd_bearer_disable_l2_help },
+ { "ib", "device", cmd_bearer_disable_l2_help },
+ { NULL, },
+ };
+
+ if (parse_opts(opts, cmdl) < 0) {
+ if (help_flag)
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_BEARER_DISABLE))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER);
+ err = nl_add_bearer_name(nlh, cmd, cmdl, opts, sup_media);
+ if (err)
+ return err;
+ mnl_attr_nest_end(nlh, nest);
+
+ return msg_doit(nlh, NULL, NULL);
+
+}
+
+static void cmd_bearer_set_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s bearer set OPTION media MEDIA ARGS...\n",
+ cmdl->argv[0]);
+ _print_bearer_opts();
+ print_bearer_media();
+}
+
+static void cmd_bearer_set_udp_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr, "Usage: %s bearer set OPTION media %s name NAME\n\n",
+ cmdl->argv[0], media);
+ _print_bearer_opts();
+}
+
+static void cmd_bearer_set_l2_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr,
+ "Usage: %s bearer set [OPTION]... media %s device DEVICE\n",
+ cmdl->argv[0], media);
+ _print_bearer_opts();
+}
+
+static int cmd_bearer_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int err;
+ int val;
+ int prop;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *props;
+ struct nlattr *attrs;
+ struct opt opts[] = {
+ { "device", OPT_KEYVAL, NULL },
+ { "media", OPT_KEYVAL, NULL },
+ { "name", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+ struct tipc_sup_media sup_media[] = {
+ { "udp", "name", cmd_bearer_set_udp_help},
+ { "eth", "device", cmd_bearer_set_l2_help },
+ { "ib", "device", cmd_bearer_set_l2_help },
+ { NULL, },
+ };
+
+ if (strcmp(cmd->cmd, "priority") == 0)
+ prop = TIPC_NLA_PROP_PRIO;
+ else if ((strcmp(cmd->cmd, "tolerance") == 0))
+ prop = TIPC_NLA_PROP_TOL;
+ else if ((strcmp(cmd->cmd, "window") == 0))
+ prop = TIPC_NLA_PROP_WIN;
+ else if ((strcmp(cmd->cmd, "mtu") == 0))
+ prop = TIPC_NLA_PROP_MTU;
+ else
+ return -EINVAL;
+
+ if (cmdl->optind >= cmdl->argc) {
+ fprintf(stderr, "error, missing value\n");
+ return -EINVAL;
+ }
+ val = atoi(shift_cmdl(cmdl));
+
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ if (prop == TIPC_NLA_PROP_MTU) {
+ char *media = cmd_get_media_type(cmd, cmdl, opts);
+
+ if (!media)
+ return -EINVAL;
+ else if (strcmp(media, "udp")) {
+ fprintf(stderr, "error, not supported for media\n");
+ return -EINVAL;
+ }
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_BEARER_SET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+ attrs = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER);
+
+ props = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER_PROP);
+ mnl_attr_put_u32(nlh, prop, val);
+ mnl_attr_nest_end(nlh, props);
+
+ err = nl_add_bearer_name(nlh, cmd, cmdl, opts, sup_media);
+ if (err)
+ return err;
+
+ mnl_attr_nest_end(nlh, attrs);
+
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static int cmd_bearer_set(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "priority", cmd_bearer_set_prop, cmd_bearer_set_help },
+ { "tolerance", cmd_bearer_set_prop, cmd_bearer_set_help },
+ { "window", cmd_bearer_set_prop, cmd_bearer_set_help },
+ { "mtu", cmd_bearer_set_prop, cmd_bearer_set_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static void cmd_bearer_get_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s bearer get [OPTION] media MEDIA ARGS...\n",
+ cmdl->argv[0]);
+ _print_bearer_opts();
+ print_bearer_media();
+}
+
+static void cmd_bearer_get_udp_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr, "Usage: %s bearer get [OPTION] media %s name NAME [UDP OPTIONS]\n\n",
+ cmdl->argv[0], media);
+ fprintf(stderr,
+ "UDP OPTIONS\n"
+ " remoteip - Remote ip address\n"
+ " remoteport - Remote port\n"
+ " localip - Local ip address\n"
+ " localport - Local port\n\n");
+ _print_bearer_opts();
+}
+
+static void cmd_bearer_get_l2_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr,
+ "Usage: %s bearer get OPTION media %s device DEVICE\n",
+ cmdl->argv[0], media);
+ _print_bearer_opts();
+}
+
+
+static int bearer_dump_udp_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct sockaddr_storage *addr;
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_UDP_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+
+ if (!info[TIPC_NLA_UDP_REMOTE])
+ return MNL_CB_ERROR;
+
+ addr = mnl_attr_get_payload(info[TIPC_NLA_UDP_REMOTE]);
+
+ if (addr->ss_family == AF_INET) {
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *) addr;
+
+ printf("%s\n", inet_ntoa(ipv4->sin_addr));
+ } else if (addr->ss_family == AF_INET6) {
+ char straddr[INET6_ADDRSTRLEN];
+ struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *) addr;
+
+ if (!inet_ntop(AF_INET6, &ipv6->sin6_addr, straddr,
+ sizeof(straddr))) {
+ fprintf(stderr, "error, parsing IPv6 addr\n");
+ return MNL_CB_ERROR;
+ }
+ printf("%s\n", straddr);
+
+ } else {
+ return MNL_CB_ERROR;
+ }
+
+ return MNL_CB_OK;
+}
+
+static int bearer_get_udp_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct cb_data *cb_data = (struct cb_data *) data;
+ struct sockaddr_storage *addr;
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_BEARER_MAX + 1] = {};
+ struct nlattr *opts[TIPC_NLA_UDP_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_BEARER])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_BEARER], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_BEARER_UDP_OPTS])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(attrs[TIPC_NLA_BEARER_UDP_OPTS], parse_attrs, opts);
+ if (!opts[TIPC_NLA_UDP_LOCAL])
+ return MNL_CB_ERROR;
+
+ if ((cb_data->attr == TIPC_NLA_UDP_REMOTE) &&
+ (cb_data->prop == UDP_PROP_IP) &&
+ opts[TIPC_NLA_UDP_MULTI_REMOTEIP]) {
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(cb_data->nlh);
+
+ genl->cmd = TIPC_NL_UDP_GET_REMOTEIP;
+ return msg_dumpit(cb_data->nlh, bearer_dump_udp_cb, NULL);
+ }
+
+ addr = mnl_attr_get_payload(opts[cb_data->attr]);
+
+ if (addr->ss_family == AF_INET) {
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *) addr;
+
+ switch (cb_data->prop) {
+ case UDP_PROP_IP:
+ printf("%s\n", inet_ntoa(ipv4->sin_addr));
+ break;
+ case UDP_PROP_PORT:
+ printf("%u\n", ntohs(ipv4->sin_port));
+ break;
+ default:
+ return MNL_CB_ERROR;
+ }
+
+ } else if (addr->ss_family == AF_INET6) {
+ char straddr[INET6_ADDRSTRLEN];
+ struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *) addr;
+
+ switch (cb_data->prop) {
+ case UDP_PROP_IP:
+ if (!inet_ntop(AF_INET6, &ipv6->sin6_addr, straddr,
+ sizeof(straddr))) {
+ fprintf(stderr, "error, parsing IPv6 addr\n");
+ return MNL_CB_ERROR;
+ }
+ printf("%s\n", straddr);
+ break;
+ case UDP_PROP_PORT:
+ printf("%u\n", ntohs(ipv6->sin6_port));
+ break;
+ default:
+ return MNL_CB_ERROR;
+ }
+
+ } else {
+ return MNL_CB_ERROR;
+ }
+
+ return MNL_CB_OK;
+}
+
+static int bearer_get_cb(const struct nlmsghdr *nlh, void *data)
+{
+ int *prop = data;
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_BEARER_MAX + 1] = {};
+ struct nlattr *props[TIPC_NLA_PROP_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_BEARER])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_BEARER], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_BEARER_PROP])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(attrs[TIPC_NLA_BEARER_PROP], parse_attrs, props);
+ if (!props[*prop])
+ return MNL_CB_ERROR;
+
+ printf("%u\n", mnl_attr_get_u32(props[*prop]));
+
+ return MNL_CB_OK;
+}
+
+static int cmd_bearer_get_media(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int err;
+ char *media;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct opt *opt;
+ struct cb_data cb_data = {0};
+ struct nlattr *attrs;
+ struct opt opts[] = {
+ { "localip", OPT_KEY, NULL },
+ { "localport", OPT_KEY, NULL },
+ { "remoteip", OPT_KEY, NULL },
+ { "remoteport", OPT_KEY, NULL },
+ { "name", OPT_KEYVAL, NULL },
+ { "media", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+ struct tipc_sup_media sup_media[] = {
+ { "udp", "name", cmd_bearer_get_udp_help},
+ { NULL, },
+ };
+
+ /* Rewind optind to include media in the option list */
+ cmdl->optind--;
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ if (!(opt = get_opt(opts, "media"))) {
+ fprintf(stderr, "error, missing media value\n");
+ return -EINVAL;
+ }
+ media = opt->val;
+
+ if (help_flag) {
+ cmd_bearer_get_udp_help(cmdl, media);
+ return -EINVAL;
+ }
+ if (strcmp(media, "udp") != 0) {
+ fprintf(stderr, "error, no \"%s\" media specific options\n", media);
+ return -EINVAL;
+ }
+ if (!(opt = get_opt(opts, "name"))) {
+ fprintf(stderr, "error, missing media name\n");
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_BEARER_GET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ attrs = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER);
+ err = nl_add_bearer_name(nlh, cmd, cmdl, opts, sup_media);
+ if (err)
+ return err;
+ mnl_attr_nest_end(nlh, attrs);
+ cb_data.nlh = nlh;
+
+ if (has_opt(opts, "localip")) {
+ cb_data.attr = TIPC_NLA_UDP_LOCAL;
+ cb_data.prop = UDP_PROP_IP;
+ return msg_doit(nlh, bearer_get_udp_cb, &cb_data);
+ } else if (has_opt(opts, "localport")) {
+ cb_data.attr = TIPC_NLA_UDP_LOCAL;
+ cb_data.prop = UDP_PROP_PORT;
+ return msg_doit(nlh, bearer_get_udp_cb, &cb_data);
+ } else if (has_opt(opts, "remoteip")) {
+ cb_data.attr = TIPC_NLA_UDP_REMOTE;
+ cb_data.prop = UDP_PROP_IP;
+ return msg_doit(nlh, bearer_get_udp_cb, &cb_data);
+ } else if (has_opt(opts, "remoteport")) {
+ cb_data.attr = TIPC_NLA_UDP_REMOTE;
+ cb_data.prop = UDP_PROP_PORT;
+ return msg_doit(nlh, bearer_get_udp_cb, &cb_data);
+ }
+ fprintf(stderr, "error, missing UDP option\n");
+ return -EINVAL;
+}
+
+static int cmd_bearer_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int err;
+ int prop;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *attrs;
+ struct opt opts[] = {
+ { "device", OPT_KEYVAL, NULL },
+ { "media", OPT_KEYVAL, NULL },
+ { "name", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+ struct tipc_sup_media sup_media[] = {
+ { "udp", "name", cmd_bearer_get_udp_help},
+ { "eth", "device", cmd_bearer_get_l2_help },
+ { "ib", "device", cmd_bearer_get_l2_help },
+ { NULL, },
+ };
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (strcmp(cmd->cmd, "priority") == 0)
+ prop = TIPC_NLA_PROP_PRIO;
+ else if ((strcmp(cmd->cmd, "tolerance") == 0))
+ prop = TIPC_NLA_PROP_TOL;
+ else if ((strcmp(cmd->cmd, "window") == 0))
+ prop = TIPC_NLA_PROP_WIN;
+ else if ((strcmp(cmd->cmd, "mtu") == 0))
+ prop = TIPC_NLA_PROP_MTU;
+ else
+ return -EINVAL;
+
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ if (prop == TIPC_NLA_PROP_MTU) {
+ char *media = cmd_get_media_type(cmd, cmdl, opts);
+
+ if (!media)
+ return -EINVAL;
+ else if (strcmp(media, "udp")) {
+ fprintf(stderr, "error, not supported for media\n");
+ return -EINVAL;
+ }
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_BEARER_GET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ attrs = mnl_attr_nest_start(nlh, TIPC_NLA_BEARER);
+ err = nl_add_bearer_name(nlh, cmd, cmdl, opts, sup_media);
+ if (err)
+ return err;
+ mnl_attr_nest_end(nlh, attrs);
+
+ return msg_doit(nlh, bearer_get_cb, &prop);
+}
+
+static int cmd_bearer_get(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "priority", cmd_bearer_get_prop, cmd_bearer_get_help },
+ { "tolerance", cmd_bearer_get_prop, cmd_bearer_get_help },
+ { "window", cmd_bearer_get_prop, cmd_bearer_get_help },
+ { "mtu", cmd_bearer_get_prop, cmd_bearer_get_help },
+ { "media", cmd_bearer_get_media, cmd_bearer_get_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static int bearer_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_BEARER_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_BEARER]) {
+ fprintf(stderr, "No bearer in netlink response\n");
+ return MNL_CB_ERROR;
+ }
+
+ mnl_attr_parse_nested(info[TIPC_NLA_BEARER], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_BEARER_NAME]) {
+ fprintf(stderr, "Bearer name missing in netlink response\n");
+ return MNL_CB_ERROR;
+ }
+
+ printf("%s\n", mnl_attr_get_str(attrs[TIPC_NLA_BEARER_NAME]));
+
+ return MNL_CB_OK;
+}
+
+static int cmd_bearer_list(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ if (help_flag) {
+ fprintf(stderr, "Usage: %s bearer list\n", cmdl->argv[0]);
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_BEARER_GET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ return msg_dumpit(nlh, bearer_list_cb, NULL);
+}
+
+void cmd_bearer_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s bearer COMMAND [ARGS] ...\n"
+ "\n"
+ "COMMANDS\n"
+ " add - Add data to existing bearer\n"
+ " enable - Enable a bearer\n"
+ " disable - Disable a bearer\n"
+ " set - Set various bearer properties\n"
+ " get - Get various bearer properties\n"
+ " list - List bearers\n", cmdl->argv[0]);
+}
+
+int cmd_bearer(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data)
+{
+ const struct cmd cmds[] = {
+ { "add", cmd_bearer_add, cmd_bearer_add_help },
+ { "disable", cmd_bearer_disable, cmd_bearer_disable_help },
+ { "enable", cmd_bearer_enable, cmd_bearer_enable_help },
+ { "get", cmd_bearer_get, cmd_bearer_get_help },
+ { "list", cmd_bearer_list, NULL },
+ { "set", cmd_bearer_set, cmd_bearer_set_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
diff --git a/tipc/bearer.h b/tipc/bearer.h
new file mode 100644
index 0000000..c0d0996
--- /dev/null
+++ b/tipc/bearer.h
@@ -0,0 +1,26 @@
+/*
+ * bearer.h TIPC bearer functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_BEARER_H
+#define _TIPC_BEARER_H
+
+#include "cmdl.h"
+
+extern int help_flag;
+
+int cmd_bearer(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl, void *data);
+void cmd_bearer_help(struct cmdl *cmdl);
+
+void print_bearer_media(void);
+int cmd_get_unique_bearer_name(const struct cmd *cmd, struct cmdl *cmdl,
+ struct opt *opts, char *bname,
+ const struct tipc_sup_media *sup_media);
+#endif
diff --git a/tipc/cmdl.c b/tipc/cmdl.c
new file mode 100644
index 0000000..f2f259c
--- /dev/null
+++ b/tipc/cmdl.c
@@ -0,0 +1,136 @@
+/*
+ * cmdl.c Framework for handling command line options.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <libmnl/libmnl.h>
+
+#include "cmdl.h"
+
+static const struct cmd *find_cmd(const struct cmd *cmds, char *str)
+{
+ const struct cmd *c;
+ const struct cmd *match = NULL;
+
+ for (c = cmds; c->cmd; c++) {
+ if (strstr(c->cmd, str) != c->cmd)
+ continue;
+ if (match)
+ return NULL;
+ match = c;
+ }
+
+ return match;
+}
+
+static struct opt *find_opt(struct opt *opts, char *str)
+{
+ struct opt *o;
+ struct opt *match = NULL;
+
+ for (o = opts; o->key; o++) {
+ if (strstr(o->key, str) != o->key)
+ continue;
+ if (match)
+ return NULL;
+
+ match = o;
+ }
+
+ return match;
+}
+
+struct opt *get_opt(struct opt *opts, char *key)
+{
+ struct opt *o;
+
+ for (o = opts; o->key; o++) {
+ if (strcmp(o->key, key) == 0 && o->val)
+ return o;
+ }
+
+ return NULL;
+}
+
+bool has_opt(struct opt *opts, char *key)
+{
+ return get_opt(opts, key) ? true : false;
+}
+
+char *shift_cmdl(struct cmdl *cmdl)
+{
+ int next;
+
+ if (cmdl->optind < cmdl->argc)
+ next = (cmdl->optind)++;
+ else
+ next = cmdl->argc;
+
+ return cmdl->argv[next];
+}
+
+/* Returns the number of options parsed or a negative error code upon failure */
+int parse_opts(struct opt *opts, struct cmdl *cmdl)
+{
+ int i;
+ int cnt = 0;
+
+ for (i = cmdl->optind; i < cmdl->argc; i++) {
+ struct opt *o;
+
+ o = find_opt(opts, cmdl->argv[i]);
+ if (!o) {
+ fprintf(stderr, "error, invalid option \"%s\"\n",
+ cmdl->argv[i]);
+ return -EINVAL;
+ }
+ if (o->flag & OPT_KEYVAL) {
+ cmdl->optind++;
+ i++;
+ }
+ cnt++;
+ o->val = cmdl->argv[i];
+ cmdl->optind++;
+ }
+
+ return cnt;
+}
+
+int run_cmd(struct nlmsghdr *nlh, const struct cmd *caller,
+ const struct cmd *cmds, struct cmdl *cmdl, void *data)
+{
+ char *name;
+ const struct cmd *cmd;
+
+ if ((cmdl->optind) >= cmdl->argc) {
+ if (caller->help)
+ (caller->help)(cmdl);
+ return -EINVAL;
+ }
+ name = cmdl->argv[cmdl->optind];
+ (cmdl->optind)++;
+
+ cmd = find_cmd(cmds, name);
+ if (!cmd) {
+ /* Show help about last command if we don't find this one */
+ if (help_flag && caller->help) {
+ (caller->help)(cmdl);
+ } else {
+ fprintf(stderr, "error, invalid command \"%s\"\n", name);
+ fprintf(stderr, "use --help for command help\n");
+ }
+ return -EINVAL;
+ }
+
+ return (cmd->func)(nlh, cmd, cmdl, data);
+}
diff --git a/tipc/cmdl.h b/tipc/cmdl.h
new file mode 100644
index 0000000..03db359
--- /dev/null
+++ b/tipc/cmdl.h
@@ -0,0 +1,57 @@
+/*
+ * cmdl.h Framework for handling command line options.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_CMDL_H
+#define _TIPC_CMDL_H
+
+#include <libmnl/libmnl.h>
+
+extern int help_flag;
+
+enum {
+ OPT_KEY = (1 << 0),
+ OPT_KEYVAL = (1 << 1),
+};
+
+struct cmdl {
+ int optind;
+ int argc;
+ char **argv;
+};
+
+struct tipc_sup_media {
+ char *media;
+ char *identifier;
+ void (*help)(struct cmdl *cmdl, char *media);
+};
+
+struct cmd {
+ const char *cmd;
+ int (*func)(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data);
+ void (*help)(struct cmdl *cmdl);
+};
+
+struct opt {
+ const char *key;
+ uint16_t flag;
+ char *val;
+};
+
+struct opt *get_opt(struct opt *opts, char *key);
+bool has_opt(struct opt *opts, char *key);
+int parse_opts(struct opt *opts, struct cmdl *cmdl);
+char *shift_cmdl(struct cmdl *cmdl);
+
+int run_cmd(struct nlmsghdr *nlh, const struct cmd *caller,
+ const struct cmd *cmds, struct cmdl *cmdl, void *data);
+
+#endif
diff --git a/tipc/link.c b/tipc/link.c
new file mode 100644
index 0000000..e123c18
--- /dev/null
+++ b/tipc/link.c
@@ -0,0 +1,1245 @@
+/*
+ * link.c TIPC link functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <linux/tipc_netlink.h>
+#include <linux/tipc.h>
+#include <linux/genetlink.h>
+#include <libmnl/libmnl.h>
+
+#include "cmdl.h"
+#include "msg.h"
+#include "link.h"
+#include "bearer.h"
+#include "utils.h"
+
+#define PRIORITY_STR "priority"
+#define TOLERANCE_STR "tolerance"
+#define WINDOW_STR "window"
+#define BROADCAST_STR "broadcast"
+
+static const char tipc_bclink_name[] = "broadcast-link";
+
+static int link_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_LINK_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_LINK])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_LINK], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_LINK_NAME])
+ return MNL_CB_ERROR;
+
+ print_string(PRINT_FP, NULL, "%s: ",
+ mnl_attr_get_str(attrs[TIPC_NLA_LINK_NAME]));
+ if (attrs[TIPC_NLA_LINK_UP])
+ print_string(PRINT_ANY,
+ mnl_attr_get_str(attrs[TIPC_NLA_LINK_NAME]),"%s\n", "up");
+ else
+ print_string(PRINT_ANY,
+ mnl_attr_get_str(attrs[TIPC_NLA_LINK_NAME]), "%s\n", "down");
+ return MNL_CB_OK;
+}
+
+static int cmd_link_list(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ int err = 0;
+
+ if (help_flag) {
+ fprintf(stderr, "Usage: %s link list\n", cmdl->argv[0]);
+ return -EINVAL;
+ }
+
+ nlh = msg_init(buf, TIPC_NL_LINK_GET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ new_json_obj(json);
+ open_json_object(NULL);
+ err = msg_dumpit(nlh, link_list_cb, NULL);
+ close_json_object();
+ delete_json_obj();
+ return err;
+}
+
+static int link_get_cb(const struct nlmsghdr *nlh, void *data)
+{
+ int *prop = data;
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_LINK_MAX + 1] = {};
+ struct nlattr *props[TIPC_NLA_PROP_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_LINK])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_LINK], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_LINK_PROP])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(attrs[TIPC_NLA_LINK_PROP], parse_attrs, props);
+ if (!props[*prop])
+ return MNL_CB_ERROR;
+
+ new_json_obj(json);
+ open_json_object(NULL);
+ switch (*prop) {
+ case TIPC_NLA_PROP_PRIO:
+ print_uint(PRINT_ANY, PRIORITY_STR, "%u\n", mnl_attr_get_u32(props[*prop]));
+ break;
+ case TIPC_NLA_PROP_TOL:
+ print_uint(PRINT_ANY, TOLERANCE_STR, "%u\n", mnl_attr_get_u32(props[*prop]));
+ break;
+ case TIPC_NLA_PROP_WIN:
+ print_uint(PRINT_ANY, WINDOW_STR, "%u\n", mnl_attr_get_u32(props[*prop]));
+ break;
+ default:
+ break;
+ }
+ close_json_object();
+ delete_json_obj();
+ return MNL_CB_OK;
+}
+
+static int cmd_link_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int prop;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *attrs;
+ struct opt *opt;
+ struct opt opts[] = {
+ { "link", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+
+ if (strcmp(cmd->cmd, PRIORITY_STR) == 0)
+ prop = TIPC_NLA_PROP_PRIO;
+ else if ((strcmp(cmd->cmd, TOLERANCE_STR) == 0))
+ prop = TIPC_NLA_PROP_TOL;
+ else if ((strcmp(cmd->cmd, WINDOW_STR) == 0))
+ prop = TIPC_NLA_PROP_WIN;
+ else
+ return -EINVAL;
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ nlh = msg_init(buf, TIPC_NL_LINK_GET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ opt = get_opt(opts, "link");
+ if (!opt) {
+ fprintf(stderr, "error, missing link\n");
+ return -EINVAL;
+ }
+ attrs = mnl_attr_nest_start(nlh, TIPC_NLA_LINK);
+ mnl_attr_put_strz(nlh, TIPC_NLA_LINK_NAME, opt->val);
+ mnl_attr_nest_end(nlh, attrs);
+
+ return msg_doit(nlh, link_get_cb, &prop);
+}
+
+static void cmd_link_get_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s link get PPROPERTY link LINK\n\n"
+ "PROPERTIES\n"
+ " tolerance - Get link tolerance\n"
+ " priority - Get link priority\n"
+ " window - Get link window\n"
+ " broadcast - Get link broadcast\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_link_get_bcast_cb(const struct nlmsghdr *nlh, void *data)
+{
+ int *prop = data;
+ int prop_ratio = TIPC_NLA_PROP_BROADCAST_RATIO;
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_LINK_MAX + 1] = {};
+ struct nlattr *props[TIPC_NLA_PROP_MAX + 1] = {};
+ int bc_mode;
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_LINK])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_LINK], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_LINK_PROP])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(attrs[TIPC_NLA_LINK_PROP], parse_attrs, props);
+ if (!props[*prop])
+ return MNL_CB_ERROR;
+
+ bc_mode = mnl_attr_get_u32(props[*prop]);
+
+ new_json_obj(json);
+ open_json_object(NULL);
+ switch (bc_mode) {
+ case 0x1:
+ print_string(PRINT_ANY, "method", "%s\n", "BROADCAST");
+ break;
+ case 0x2:
+ print_string(PRINT_ANY, "method", "%s\n", "REPLICAST");
+ break;
+ case 0x4:
+ print_string(PRINT_ANY, "method", "%s", "AUTOSELECT");
+ close_json_object();
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "ratio", " ratio:%u%\n",
+ mnl_attr_get_u32(props[prop_ratio]));
+ break;
+ default:
+ print_string(PRINT_ANY, NULL, "UNKNOWN\n", NULL);
+ break;
+ }
+ close_json_object();
+ delete_json_obj();
+ return MNL_CB_OK;
+}
+
+static void cmd_link_get_bcast_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s link get PPROPERTY\n\n"
+ "PROPERTIES\n"
+ " broadcast - Get link broadcast\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_link_get_bcast(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int prop = TIPC_NLA_PROP_BROADCAST;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *attrs;
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ nlh = msg_init(buf, TIPC_NL_LINK_GET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+ attrs = mnl_attr_nest_start(nlh, TIPC_NLA_LINK);
+ /* Direct to broadcast-link setting */
+ mnl_attr_put_strz(nlh, TIPC_NLA_LINK_NAME, tipc_bclink_name);
+ mnl_attr_nest_end(nlh, attrs);
+ return msg_doit(nlh, cmd_link_get_bcast_cb, &prop);
+}
+
+static int cmd_link_get(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { PRIORITY_STR, cmd_link_get_prop, cmd_link_get_help },
+ { TOLERANCE_STR, cmd_link_get_prop, cmd_link_get_help },
+ { WINDOW_STR, cmd_link_get_prop, cmd_link_get_help },
+ { BROADCAST_STR, cmd_link_get_bcast, cmd_link_get_bcast_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static void cmd_link_stat_reset_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s link stat reset link LINK\n\n", cmdl->argv[0]);
+}
+
+static int cmd_link_stat_reset(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char *link;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct opt *opt;
+ struct nlattr *nest;
+ struct opt opts[] = {
+ { "link", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (parse_opts(opts, cmdl) != 1) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ nlh = msg_init(buf, TIPC_NL_LINK_RESET_STATS);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ opt = get_opt(opts, "link");
+ if (!opt) {
+ fprintf(stderr, "error, missing link\n");
+ return -EINVAL;
+ }
+ link = opt->val;
+
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_LINK);
+ mnl_attr_put_strz(nlh, TIPC_NLA_LINK_NAME, link);
+ mnl_attr_nest_end(nlh, nest);
+
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static uint32_t perc(uint32_t count, uint32_t total)
+{
+ return (count * 100 + (total / 2)) / total;
+}
+
+static int _show_link_stat(const char *name, struct nlattr *attrs[],
+ struct nlattr *prop[], struct nlattr *stats[])
+{
+ uint32_t proft;
+
+ open_json_object(NULL);
+
+ print_string(PRINT_ANY, "link", "\nLink <%s>\n", name);
+ print_string(PRINT_JSON, "state", "", NULL);
+ open_json_array(PRINT_JSON, NULL);
+ if (attrs[TIPC_NLA_LINK_ACTIVE])
+ print_string(PRINT_ANY, NULL, " %s", "ACTIVE");
+ else if (attrs[TIPC_NLA_LINK_UP])
+ print_string(PRINT_ANY, NULL, " %s", "STANDBY");
+ else
+ print_string(PRINT_ANY, NULL, " %s", "DEFUNCT");
+ close_json_array(PRINT_JSON, NULL);
+
+ print_uint(PRINT_ANY, "mtu", " MTU:%u",
+ mnl_attr_get_u32(attrs[TIPC_NLA_LINK_MTU]));
+ print_uint(PRINT_ANY, PRIORITY_STR, " Priority:%u",
+ mnl_attr_get_u32(prop[TIPC_NLA_PROP_PRIO]));
+ print_uint(PRINT_ANY, TOLERANCE_STR, " Tolerance:%u ms",
+ mnl_attr_get_u32(prop[TIPC_NLA_PROP_TOL]));
+ print_uint(PRINT_ANY, WINDOW_STR, " Window:%u packets\n",
+ mnl_attr_get_u32(prop[TIPC_NLA_PROP_WIN]));
+
+ open_json_object("rx packets");
+ print_uint(PRINT_ANY, "rx packets", " RX packets:%u",
+ mnl_attr_get_u32(attrs[TIPC_NLA_LINK_RX]) -
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_INFO]));
+ print_uint(PRINT_ANY, "fragments", " fragments:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_FRAGMENTS]));
+ print_uint(PRINT_ANY, "fragmented", "/%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_FRAGMENTED]));
+ print_uint(PRINT_ANY, "bundles", " bundles:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_BUNDLES]));
+ print_uint(PRINT_ANY, "bundled", "/%u\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_BUNDLED]));
+ close_json_object();
+
+ open_json_object("tx packets");
+ print_uint(PRINT_ANY, "tx packets", " TX packets:%u",
+ mnl_attr_get_u32(attrs[TIPC_NLA_LINK_TX]) -
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_INFO]));
+ print_uint(PRINT_ANY, "fragments", " fragments:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_FRAGMENTS]));
+ print_uint(PRINT_ANY, "fragmented", "/%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_FRAGMENTED]));
+ print_uint(PRINT_ANY, "bundles", " bundles:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_BUNDLES]));
+ print_uint(PRINT_ANY, "bundled", "/%u\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_BUNDLED]));
+ close_json_object();
+
+ proft = mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_PROF_TOT]);
+ print_uint(PRINT_ANY, "tx profile sample", " TX profile sample:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_CNT]));
+ print_uint(PRINT_ANY, "packets average", " packets average:%u octets\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_TOT]) / proft);
+
+ print_uint(PRINT_ANY, "0-64", " 0-64:%u%%",
+ perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P0]), proft));
+ print_uint(PRINT_ANY, "-256", " -256:%u%%",
+ perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P1]), proft));
+ print_uint(PRINT_ANY, "-1024", " -1024:%u%%",
+ perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P2]), proft));
+ print_uint(PRINT_ANY, "-4096", " -4096:%u%%",
+ perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P3]), proft));
+ print_uint(PRINT_ANY, "-16384", " -16384:%u%%",
+ perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P4]), proft));
+ print_uint(PRINT_ANY, "-32768", " -32768:%u%%",
+ perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P5]), proft));
+ print_uint(PRINT_ANY, "-66000", " -66000:%u%%\n",
+ perc(mnl_attr_get_u32(stats[TIPC_NLA_STATS_MSG_LEN_P6]), proft));
+
+ open_json_object("rx states");
+ print_uint(PRINT_ANY, "rx states", " RX states:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_STATES]));
+ print_uint(PRINT_ANY, "probes", " probes:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_PROBES]));
+ print_uint(PRINT_ANY, "naks", " naks:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_NACKS]));
+ print_uint(PRINT_ANY, "defs", " defs:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_DEFERRED]));
+ print_uint(PRINT_ANY, "dups", " dups:%u\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_DUPLICATES]));
+ close_json_object();
+
+ open_json_object("tx states");
+ print_uint(PRINT_ANY, "tx states", " TX states:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_STATES]));
+ print_uint(PRINT_ANY, "probes", " probes:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_PROBES]));
+ print_uint(PRINT_ANY, "naks", " naks:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_NACKS]));
+ print_uint(PRINT_ANY, "acks", " acks:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_ACKS]));
+ print_uint(PRINT_ANY, "retrans", " retrans:%u\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RETRANSMITTED]));
+ close_json_object();
+
+ print_uint(PRINT_ANY, "congestion link", " Congestion link:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_LINK_CONGS]));
+ print_uint(PRINT_ANY, "send queue max", " Send queue max:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_MAX_QUEUE]));
+ print_uint(PRINT_ANY, "avg", " avg:%u\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_AVG_QUEUE]));
+
+ close_json_object();
+ return MNL_CB_OK;
+}
+
+static int _show_bc_link_stat(const char *name, struct nlattr *prop[],
+ struct nlattr *stats[])
+{
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "link", "Link <%s>\n", name);
+ print_uint(PRINT_ANY, WINDOW_STR, " Window:%u packets\n",
+ mnl_attr_get_u32(prop[TIPC_NLA_PROP_WIN]));
+
+ open_json_object("rx packets");
+ print_uint(PRINT_ANY, "rx packets", " RX packets:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_INFO]));
+ print_uint(PRINT_ANY, "fragments", " fragments:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_FRAGMENTS]));
+ print_uint(PRINT_ANY, "fragmented", "/%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_FRAGMENTED]));
+ print_uint(PRINT_ANY, "bundles", " bundles:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_BUNDLES]));
+ print_uint(PRINT_ANY, "bundled", "/%u\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_BUNDLED]));
+ close_json_object();
+
+ open_json_object("tx packets");
+ print_uint(PRINT_ANY, "tx packets", " TX packets:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_INFO]));
+ print_uint(PRINT_ANY, "fragments", " fragments:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_FRAGMENTS]));
+ print_uint(PRINT_ANY, "fragmented", "/%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_FRAGMENTED]));
+ print_uint(PRINT_ANY, "bundles", " bundles:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_BUNDLES]));
+ print_uint(PRINT_ANY, "bundled", "/%u\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_BUNDLED]));
+ close_json_object();
+
+ open_json_object("rx naks");
+ print_uint(PRINT_ANY, "rx naks", " RX naks:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_NACKS]));
+ print_uint(PRINT_ANY, "defs", " defs:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RX_DEFERRED]));
+ print_uint(PRINT_ANY, "dups", " dups:%u\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_DUPLICATES]));
+ close_json_object();
+
+ open_json_object("tx naks");
+ print_uint(PRINT_ANY, "tx naks", " TX naks:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_NACKS]));
+ print_uint(PRINT_ANY, "acks", " acks:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_TX_ACKS]));
+ print_uint(PRINT_ANY, "retrans", " retrans:%u\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_RETRANSMITTED]));
+ close_json_object();
+
+ print_uint(PRINT_ANY, "congestion link", " Congestion link:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_LINK_CONGS]));
+ print_uint(PRINT_ANY, "send queue max", " Send queue max:%u",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_MAX_QUEUE]));
+ print_uint(PRINT_ANY, "avg", " avg:%u\n",
+ mnl_attr_get_u32(stats[TIPC_NLA_STATS_AVG_QUEUE]));
+ close_json_object();
+
+ return MNL_CB_OK;
+}
+
+static int link_stat_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ const char *name;
+ const char *link = data;
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_LINK_MAX + 1] = {};
+ struct nlattr *prop[TIPC_NLA_PROP_MAX + 1] = {};
+ struct nlattr *stats[TIPC_NLA_STATS_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_LINK])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_LINK], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_LINK_NAME] || !attrs[TIPC_NLA_LINK_PROP] ||
+ !attrs[TIPC_NLA_LINK_STATS])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(attrs[TIPC_NLA_LINK_PROP], parse_attrs, prop);
+ mnl_attr_parse_nested(attrs[TIPC_NLA_LINK_STATS], parse_attrs, stats);
+
+ name = mnl_attr_get_str(attrs[TIPC_NLA_LINK_NAME]);
+
+ /* If a link is passed, skip all but that link */
+ if (link && (strcmp(name, link) != 0))
+ return MNL_CB_OK;
+
+ if (attrs[TIPC_NLA_LINK_BROADCAST]) {
+ return _show_bc_link_stat(name, prop, stats);
+ }
+
+ return _show_link_stat(name, attrs, prop, stats);
+}
+
+static void cmd_link_stat_show_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s link stat show [ link LINK ]\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_link_stat_show(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char *link = NULL;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct opt *opt;
+ struct opt opts[] = {
+ { "link", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+ int err = 0;
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ nlh = msg_init(buf, TIPC_NL_LINK_GET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ opt = get_opt(opts, "link");
+ if (opt)
+ link = opt->val;
+
+ new_json_obj(json);
+ err = msg_dumpit(nlh, link_stat_show_cb, link);
+ delete_json_obj();
+ return err;
+}
+
+static void cmd_link_stat_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s link stat COMMAND [ARGS]\n\n"
+ "COMMANDS:\n"
+ " reset - Reset link statistics for link\n"
+ " show - Get link priority\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_link_stat(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "reset", cmd_link_stat_reset, cmd_link_stat_reset_help },
+ { "show", cmd_link_stat_show, cmd_link_stat_show_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static void cmd_link_set_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s link set PPROPERTY link LINK\n\n"
+ "PROPERTIES\n"
+ " tolerance TOLERANCE - Set link tolerance\n"
+ " priority PRIORITY - Set link priority\n"
+ " window WINDOW - Set link window\n"
+ " broadcast BROADCAST - Set link broadcast\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_link_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int val;
+ int prop;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *props;
+ struct nlattr *attrs;
+ struct opt *opt;
+ struct opt opts[] = {
+ { "link", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+
+ if (strcmp(cmd->cmd, PRIORITY_STR) == 0)
+ prop = TIPC_NLA_PROP_PRIO;
+ else if ((strcmp(cmd->cmd, TOLERANCE_STR) == 0))
+ prop = TIPC_NLA_PROP_TOL;
+ else if ((strcmp(cmd->cmd, WINDOW_STR) == 0))
+ prop = TIPC_NLA_PROP_WIN;
+ else
+ return -EINVAL;
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (cmdl->optind >= cmdl->argc) {
+ fprintf(stderr, "error, missing value\n");
+ return -EINVAL;
+ }
+ val = atoi(shift_cmdl(cmdl));
+
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ nlh = msg_init(buf, TIPC_NL_LINK_SET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+ attrs = mnl_attr_nest_start(nlh, TIPC_NLA_LINK);
+
+ opt = get_opt(opts, "link");
+ if (!opt) {
+ fprintf(stderr, "error, missing link\n");
+ return -EINVAL;
+ }
+ mnl_attr_put_strz(nlh, TIPC_NLA_LINK_NAME, opt->val);
+
+ props = mnl_attr_nest_start(nlh, TIPC_NLA_LINK_PROP);
+ mnl_attr_put_u32(nlh, prop, val);
+ mnl_attr_nest_end(nlh, props);
+
+ mnl_attr_nest_end(nlh, attrs);
+
+ return msg_doit(nlh, link_get_cb, &prop);
+}
+
+static void cmd_link_set_bcast_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s link set broadcast PROPERTY\n\n"
+ "PROPERTIES\n"
+ " BROADCAST - Forces all multicast traffic to be\n"
+ " transmitted via broadcast only,\n"
+ " irrespective of cluster size and number\n"
+ " of destinations\n\n"
+ " REPLICAST - Forces all multicast traffic to be\n"
+ " transmitted via replicast only,\n"
+ " irrespective of cluster size and number\n"
+ " of destinations\n\n"
+ " AUTOSELECT - Auto switching to broadcast or replicast\n"
+ " depending on cluster size and destination\n"
+ " node number\n\n"
+ " ratio SIZE - Set the AUTOSELECT criteria, percentage of\n"
+ " destination nodes vs cluster size\n\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_link_set_bcast(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *props;
+ struct nlattr *attrs;
+ struct opt *opt;
+ struct opt opts[] = {
+ { "BROADCAST", OPT_KEY, NULL },
+ { "REPLICAST", OPT_KEY, NULL },
+ { "AUTOSELECT", OPT_KEY, NULL },
+ { "ratio", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+ int method = 0;
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ for (opt = opts; opt->key; opt++)
+ if (opt->val)
+ break;
+
+ if (!opt || !opt->key) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ nlh = msg_init(buf, TIPC_NL_LINK_SET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ attrs = mnl_attr_nest_start(nlh, TIPC_NLA_LINK);
+ /* Direct to broadcast-link setting */
+ mnl_attr_put_strz(nlh, TIPC_NLA_LINK_NAME, tipc_bclink_name);
+ props = mnl_attr_nest_start(nlh, TIPC_NLA_LINK_PROP);
+
+ if (get_opt(opts, "BROADCAST"))
+ method = 0x1;
+ else if (get_opt(opts, "REPLICAST"))
+ method = 0x2;
+ else if (get_opt(opts, "AUTOSELECT"))
+ method = 0x4;
+
+ opt = get_opt(opts, "ratio");
+ if (!method && !opt) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (method)
+ mnl_attr_put_u32(nlh, TIPC_NLA_PROP_BROADCAST, method);
+
+ if (opt)
+ mnl_attr_put_u32(nlh, TIPC_NLA_PROP_BROADCAST_RATIO,
+ atoi(opt->val));
+
+ mnl_attr_nest_end(nlh, props);
+ mnl_attr_nest_end(nlh, attrs);
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static int cmd_link_set(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { PRIORITY_STR, cmd_link_set_prop, cmd_link_set_help },
+ { TOLERANCE_STR, cmd_link_set_prop, cmd_link_set_help },
+ { WINDOW_STR, cmd_link_set_prop, cmd_link_set_help },
+ { BROADCAST_STR, cmd_link_set_bcast, cmd_link_set_bcast_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static int cmd_link_mon_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int size;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *attrs;
+
+ if (cmdl->argc != cmdl->optind + 1) {
+ fprintf(stderr, "error, missing value\n");
+ return -EINVAL;
+ }
+ size = atoi(shift_cmdl(cmdl));
+
+ nlh = msg_init(buf, TIPC_NL_MON_SET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+ attrs = mnl_attr_nest_start(nlh, TIPC_NLA_MON);
+
+ mnl_attr_put_u32(nlh, TIPC_NLA_MON_ACTIVATION_THRESHOLD, size);
+
+ mnl_attr_nest_end(nlh, attrs);
+
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static int link_mon_summary_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_MON_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_MON])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_MON], parse_attrs, attrs);
+
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "bearer", "\nbearer %s\n",
+ mnl_attr_get_str(attrs[TIPC_NLA_MON_BEARER_NAME]));
+
+ print_uint(PRINT_ANY, "table_generation", " table_generation %u\n",
+ mnl_attr_get_u32(attrs[TIPC_NLA_MON_LISTGEN]));
+ print_uint(PRINT_ANY, "cluster_size", " cluster_size %u\n",
+ mnl_attr_get_u32(attrs[TIPC_NLA_MON_PEERCNT]));
+ print_string(PRINT_ANY, "algorithm", " algorithm %s\n",
+ attrs[TIPC_NLA_MON_ACTIVE] ? "overlapping-ring" : "full-mesh");
+ close_json_object();
+
+ return MNL_CB_OK;
+}
+
+static int cmd_link_mon_summary(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ int err = 0;
+
+ if (help_flag) {
+ fprintf(stderr, "Usage: %s monitor summary\n", cmdl->argv[0]);
+ return -EINVAL;
+ }
+
+ nlh = msg_init(buf, TIPC_NL_MON_GET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ new_json_obj(json);
+ err = msg_dumpit(nlh, link_mon_summary_cb, NULL);
+ delete_json_obj();
+
+ return err;
+}
+
+#define STATUS_WIDTH 7
+#define MAX_NODE_WIDTH 14 /* 255.4095.4095 */
+#define MAX_DOM_GEN_WIDTH 11 /* 65535 */
+#define DIRECTLY_MON_WIDTH 10
+
+#define APPL_NODE_STATUS_WIDTH 5
+
+static int map_get(uint64_t up_map, int i)
+{
+ return (up_map & (1 << i)) >> i;
+}
+
+/* print the applied members, since we know the the members
+ * are listed in ascending order, we print only the state
+ */
+static void link_mon_print_applied(uint16_t applied, uint64_t up_map)
+{
+ int i;
+
+ open_json_array(PRINT_JSON, "applied_node_status");
+ for (i = 0; i < applied; i++) {
+ char state_str[2] = {0};
+
+ /* print the delimiter for every -n- entry */
+ if (i && !(i % APPL_NODE_STATUS_WIDTH))
+ print_string(PRINT_FP, NULL, "%s", ",");
+
+ sprintf(state_str, "%c", map_get(up_map, i) ? 'U' : 'D');
+ print_string(PRINT_ANY, NULL, "%s", state_str);
+ }
+ close_json_array(PRINT_JSON, "applied_node_status");
+}
+
+/* print the non applied members, since we don't know
+ * the members, we print them along with the state
+ */
+static void link_mon_print_non_applied(uint16_t applied, uint16_t member_cnt,
+ uint64_t up_map, uint32_t *members)
+{
+ int i;
+ char state;
+
+ open_json_array(PRINT_JSON, "[non_applied_node:status]");
+ print_string(PRINT_FP, NULL, " %s", "[");
+ for (i = applied; i < member_cnt; i++) {
+ char addr_str[16];
+ char full_state[17] = {0};
+
+ /* print the delimiter for every entry */
+ if (i != applied)
+ print_string(PRINT_FP, NULL, "%s", ",");
+
+ sprintf(addr_str, "%x:", members[i]);
+ state = map_get(up_map, i) ? 'U' : 'D';
+ sprintf(full_state, "%s%c", addr_str, state);
+ print_string(PRINT_ANY, NULL, "%s", full_state);
+ }
+ print_string(PRINT_FP, NULL, "%s", "]");
+ close_json_array(PRINT_JSON, "[non_applied_node:status]");
+}
+
+static void link_mon_print_peer_state(const uint32_t addr, const char *status,
+ const char *monitored,
+ const uint32_t dom_gen)
+{
+ char addr_str[16];
+
+ sprintf(addr_str, "%u.%u.%u", tipc_zone(addr), tipc_cluster(addr),
+ tipc_node(addr));
+ if (is_json_context()) {
+ print_string(PRINT_JSON, "node", NULL, addr_str);
+ print_string(PRINT_JSON, "status", NULL, status);
+ print_string(PRINT_JSON, "monitored", NULL, monitored);
+ print_uint(PRINT_JSON, "generation", NULL, dom_gen);
+ } else {
+ printf("%-*s", MAX_NODE_WIDTH, addr_str);
+ printf("%-*s", STATUS_WIDTH, status);
+ printf("%-*s", DIRECTLY_MON_WIDTH, monitored);
+ printf("%-*u", MAX_DOM_GEN_WIDTH, dom_gen);
+ }
+}
+
+static int link_mon_peer_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *attrs[TIPC_NLA_MON_PEER_MAX + 1] = {};
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ uint16_t member_cnt;
+ uint32_t applied;
+ uint32_t dom_gen;
+ uint64_t up_map;
+ char status[16];
+ char monitored[16];
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_MON_PEER])
+ return MNL_CB_ERROR;
+
+ open_json_object(NULL);
+ mnl_attr_parse_nested(info[TIPC_NLA_MON_PEER], parse_attrs, attrs);
+
+ (attrs[TIPC_NLA_MON_PEER_LOCAL] || attrs[TIPC_NLA_MON_PEER_HEAD]) ?
+ strcpy(monitored, "direct") :
+ strcpy(monitored, "indirect");
+
+ attrs[TIPC_NLA_MON_PEER_UP] ?
+ strcpy(status, "up") :
+ strcpy(status, "down");
+
+ dom_gen = attrs[TIPC_NLA_MON_PEER_DOMGEN] ?
+ mnl_attr_get_u32(attrs[TIPC_NLA_MON_PEER_DOMGEN]) : 0;
+
+ link_mon_print_peer_state(mnl_attr_get_u32(attrs[TIPC_NLA_MON_PEER_ADDR]),
+ status, monitored, dom_gen);
+
+ applied = mnl_attr_get_u32(attrs[TIPC_NLA_MON_PEER_APPLIED]);
+
+ if (!applied)
+ goto exit;
+
+ up_map = mnl_attr_get_u64(attrs[TIPC_NLA_MON_PEER_UPMAP]);
+
+ member_cnt = mnl_attr_get_payload_len(attrs[TIPC_NLA_MON_PEER_MEMBERS]);
+
+ /* each tipc address occupies 4 bytes of payload, hence compensate it */
+ member_cnt /= sizeof(uint32_t);
+
+ link_mon_print_applied(applied, up_map);
+
+ link_mon_print_non_applied(applied, member_cnt, up_map,
+ mnl_attr_get_payload(attrs[TIPC_NLA_MON_PEER_MEMBERS]));
+
+exit:
+ print_string(PRINT_FP, NULL, "\n", "");
+
+ close_json_object();
+ return MNL_CB_OK;
+}
+
+static int link_mon_peer_list(uint32_t mon_ref)
+{
+ struct nlmsghdr *nlh;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *nest;
+ int err = 0;
+
+ nlh = msg_init(buf, TIPC_NL_MON_PEER_GET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_MON);
+ mnl_attr_put_u32(nlh, TIPC_NLA_MON_REF, mon_ref);
+ mnl_attr_nest_end(nlh, nest);
+
+ err = msg_dumpit(nlh, link_mon_peer_list_cb, NULL);
+ return err;
+}
+
+static int link_mon_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_MON_MAX + 1] = {};
+ char *req_bearer = data;
+ const char *bname;
+ const char title[] =
+ "node status monitored generation applied_node_status [non_applied_node:status]";
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_MON])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_MON], parse_attrs, attrs);
+
+ bname = mnl_attr_get_str(attrs[TIPC_NLA_MON_BEARER_NAME]);
+
+ if (*req_bearer && (strcmp(req_bearer, bname) != 0))
+ return MNL_CB_OK;
+
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "bearer", "\nbearer %s\n", bname);
+ print_string(PRINT_FP, NULL, "%s\n", title);
+
+ open_json_array(PRINT_JSON, bname);
+ if (mnl_attr_get_u32(attrs[TIPC_NLA_MON_PEERCNT]))
+ link_mon_peer_list(mnl_attr_get_u32(attrs[TIPC_NLA_MON_REF]));
+ close_json_array(PRINT_JSON, bname);
+
+ close_json_object();
+ return MNL_CB_OK;
+}
+
+static void cmd_link_mon_list_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s monitor list [ media MEDIA ARGS...]\n\n",
+ cmdl->argv[0]);
+ print_bearer_media();
+}
+
+static void cmd_link_mon_list_l2_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr,
+ "Usage: %s monitor list media %s device DEVICE [OPTIONS]\n",
+ cmdl->argv[0], media);
+}
+
+static void cmd_link_mon_list_udp_help(struct cmdl *cmdl, char *media)
+{
+ fprintf(stderr,
+ "Usage: %s monitor list media udp name NAME\n\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_link_mon_list(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ char bname[TIPC_MAX_BEARER_NAME] = {0};
+ struct opt opts[] = {
+ { "media", OPT_KEYVAL, NULL },
+ { "device", OPT_KEYVAL, NULL },
+ { "name", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+ struct tipc_sup_media sup_media[] = {
+ { "udp", "name", cmd_link_mon_list_udp_help},
+ { "eth", "device", cmd_link_mon_list_l2_help },
+ { "ib", "device", cmd_link_mon_list_l2_help },
+ { NULL, },
+ };
+
+ int err;
+
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ if (get_opt(opts, "media")) {
+ err = cmd_get_unique_bearer_name(cmd, cmdl, opts, bname,
+ sup_media);
+ if (err)
+ return err;
+ }
+
+ if (help_flag) {
+ cmd->help(cmdl);
+ return -EINVAL;
+ }
+
+ nlh = msg_init(buf, TIPC_NL_MON_GET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ new_json_obj(json);
+ err = msg_dumpit(nlh, link_mon_list_cb, bname);
+ delete_json_obj();
+ return err;
+}
+
+static void cmd_link_mon_set_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s monitor set PPROPERTY\n\n"
+ "PROPERTIES\n"
+ " threshold SIZE - Set monitor activation threshold\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_link_mon_set(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "threshold", cmd_link_mon_set_prop, NULL },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static void cmd_link_mon_get_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s monitor get PPROPERTY\n\n"
+ "PROPERTIES\n"
+ " threshold - Get monitor activation threshold\n",
+ cmdl->argv[0]);
+}
+
+static int link_mon_get_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_MON_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_MON])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_MON], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_MON_ACTIVATION_THRESHOLD])
+ return MNL_CB_ERROR;
+
+ new_json_obj(json);
+ print_uint(PRINT_ANY, "threshold", "%u\n",
+ mnl_attr_get_u32(attrs[TIPC_NLA_MON_ACTIVATION_THRESHOLD]));
+ delete_json_obj();
+
+ return MNL_CB_OK;
+}
+
+static int cmd_link_mon_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ nlh = msg_init(buf, TIPC_NL_MON_GET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ return msg_doit(nlh, link_mon_get_cb, NULL);
+}
+
+static int cmd_link_mon_get(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "threshold", cmd_link_mon_get_prop, NULL},
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static void cmd_link_mon_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s montior COMMAND [ARGS] ...\n\n"
+ "COMMANDS\n"
+ " set - Set monitor properties\n"
+ " get - Get monitor properties\n"
+ " list - List all cluster members\n"
+ " summary - Show local node monitor summary\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_link_mon(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data)
+{
+ const struct cmd cmds[] = {
+ { "set", cmd_link_mon_set, cmd_link_mon_set_help },
+ { "get", cmd_link_mon_get, cmd_link_mon_get_help },
+ { "list", cmd_link_mon_list, cmd_link_mon_list_help },
+ { "summary", cmd_link_mon_summary, NULL },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+void cmd_link_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s link COMMAND [ARGS] ...\n"
+ "\n"
+ "COMMANDS\n"
+ " list - List links\n"
+ " get - Get various link properties\n"
+ " set - Set various link properties\n"
+ " statistics - Show or reset statistics\n"
+ " monitor - Show or set link supervision\n",
+ cmdl->argv[0]);
+}
+
+int cmd_link(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data)
+{
+ const struct cmd cmds[] = {
+ { "get", cmd_link_get, cmd_link_get_help },
+ { "list", cmd_link_list, NULL },
+ { "set", cmd_link_set, cmd_link_set_help },
+ { "statistics", cmd_link_stat, cmd_link_stat_help },
+ { "monitor", cmd_link_mon, cmd_link_mon_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
diff --git a/tipc/link.h b/tipc/link.h
new file mode 100644
index 0000000..6dc95e5
--- /dev/null
+++ b/tipc/link.h
@@ -0,0 +1,21 @@
+/*
+ * link.c TIPC link functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_LINK_H
+#define _TIPC_LINK_H
+
+extern int help_flag;
+
+int cmd_link(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data);
+void cmd_link_help(struct cmdl *cmdl);
+
+#endif
diff --git a/tipc/media.c b/tipc/media.c
new file mode 100644
index 0000000..969ef65
--- /dev/null
+++ b/tipc/media.c
@@ -0,0 +1,280 @@
+/*
+ * media.c TIPC link functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <linux/tipc_netlink.h>
+#include <linux/tipc.h>
+#include <linux/genetlink.h>
+#include <libmnl/libmnl.h>
+
+#include "cmdl.h"
+#include "msg.h"
+#include "media.h"
+
+static int media_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_MEDIA_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_MEDIA])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_MEDIA], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_MEDIA_NAME])
+ return MNL_CB_ERROR;
+
+ printf("%s\n", mnl_attr_get_str(attrs[TIPC_NLA_MEDIA_NAME]));
+
+ return MNL_CB_OK;
+}
+
+static int cmd_media_list(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ if (help_flag) {
+ fprintf(stderr, "Usage: %s media list\n", cmdl->argv[0]);
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_MEDIA_GET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ return msg_dumpit(nlh, media_list_cb, NULL);
+}
+
+static int media_get_cb(const struct nlmsghdr *nlh, void *data)
+{
+ int *prop = data;
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_MEDIA_MAX + 1] = {};
+ struct nlattr *props[TIPC_NLA_PROP_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_MEDIA])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_MEDIA], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_MEDIA_PROP])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(attrs[TIPC_NLA_MEDIA_PROP], parse_attrs, props);
+ if (!props[*prop])
+ return MNL_CB_ERROR;
+
+ printf("%u\n", mnl_attr_get_u32(props[*prop]));
+
+ return MNL_CB_OK;
+}
+
+static int cmd_media_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int prop;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *nest;
+ struct opt *opt;
+ struct opt opts[] = {
+ { "media", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+
+ if (strcmp(cmd->cmd, "priority") == 0)
+ prop = TIPC_NLA_PROP_PRIO;
+ else if ((strcmp(cmd->cmd, "tolerance") == 0))
+ prop = TIPC_NLA_PROP_TOL;
+ else if ((strcmp(cmd->cmd, "window") == 0))
+ prop = TIPC_NLA_PROP_WIN;
+ else if ((strcmp(cmd->cmd, "mtu") == 0))
+ prop = TIPC_NLA_PROP_MTU;
+ else
+ return -EINVAL;
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ if (!(nlh = msg_init(buf, TIPC_NL_MEDIA_GET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ if (!(opt = get_opt(opts, "media"))) {
+ fprintf(stderr, "error, missing media\n");
+ return -EINVAL;
+ }
+
+ if ((prop == TIPC_NLA_PROP_MTU) &&
+ (strcmp(opt->val, "udp"))) {
+ fprintf(stderr, "error, not supported for media\n");
+ return -EINVAL;
+ }
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_MEDIA);
+ mnl_attr_put_strz(nlh, TIPC_NLA_MEDIA_NAME, opt->val);
+ mnl_attr_nest_end(nlh, nest);
+
+ return msg_doit(nlh, media_get_cb, &prop);
+}
+
+static void cmd_media_get_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s media get PPROPERTY media MEDIA\n\n"
+ "PROPERTIES\n"
+ " tolerance - Get media tolerance\n"
+ " priority - Get media priority\n"
+ " window - Get media window\n"
+ " mtu - Get media mtu\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_media_get(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "priority", cmd_media_get_prop, cmd_media_get_help },
+ { "tolerance", cmd_media_get_prop, cmd_media_get_help },
+ { "window", cmd_media_get_prop, cmd_media_get_help },
+ { "mtu", cmd_media_get_prop, cmd_media_get_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static void cmd_media_set_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s media set PPROPERTY media MEDIA\n\n"
+ "PROPERTIES\n"
+ " tolerance TOLERANCE - Set media tolerance\n"
+ " priority PRIORITY - Set media priority\n"
+ " window WINDOW - Set media window\n"
+ " mtu MTU - Set media mtu\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_media_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int val;
+ int prop;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *props;
+ struct nlattr *attrs;
+ struct opt *opt;
+ struct opt opts[] = {
+ { "media", OPT_KEYVAL, NULL },
+ { NULL }
+ };
+
+ if (strcmp(cmd->cmd, "priority") == 0)
+ prop = TIPC_NLA_PROP_PRIO;
+ else if ((strcmp(cmd->cmd, "tolerance") == 0))
+ prop = TIPC_NLA_PROP_TOL;
+ else if ((strcmp(cmd->cmd, "window") == 0))
+ prop = TIPC_NLA_PROP_WIN;
+ else if ((strcmp(cmd->cmd, "mtu") == 0))
+ prop = TIPC_NLA_PROP_MTU;
+ else
+ return -EINVAL;
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (cmdl->optind >= cmdl->argc) {
+ fprintf(stderr, "error, missing value\n");
+ return -EINVAL;
+ }
+ val = atoi(shift_cmdl(cmdl));
+
+ if (parse_opts(opts, cmdl) < 0)
+ return -EINVAL;
+
+ if (!(nlh = msg_init(buf, TIPC_NL_MEDIA_SET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+ attrs = mnl_attr_nest_start(nlh, TIPC_NLA_MEDIA);
+
+ if (!(opt = get_opt(opts, "media"))) {
+ fprintf(stderr, "error, missing media\n");
+ return -EINVAL;
+ }
+
+ if ((prop == TIPC_NLA_PROP_MTU) &&
+ (strcmp(opt->val, "udp"))) {
+ fprintf(stderr, "error, not supported for media\n");
+ return -EINVAL;
+ }
+ mnl_attr_put_strz(nlh, TIPC_NLA_MEDIA_NAME, opt->val);
+
+ props = mnl_attr_nest_start(nlh, TIPC_NLA_MEDIA_PROP);
+ mnl_attr_put_u32(nlh, prop, val);
+ mnl_attr_nest_end(nlh, props);
+
+ mnl_attr_nest_end(nlh, attrs);
+
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static int cmd_media_set(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "priority", cmd_media_set_prop, cmd_media_set_help },
+ { "tolerance", cmd_media_set_prop, cmd_media_set_help },
+ { "window", cmd_media_set_prop, cmd_media_set_help },
+ { "mtu", cmd_media_set_prop, cmd_media_set_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+void cmd_media_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s media COMMAND [ARGS] ...\n"
+ "\n"
+ "Commands:\n"
+ " list - List active media types\n"
+ " get - Get various media properties\n"
+ " set - Set various media properties\n",
+ cmdl->argv[0]);
+}
+
+int cmd_media(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data)
+{
+ const struct cmd cmds[] = {
+ { "get", cmd_media_get, cmd_media_get_help },
+ { "list", cmd_media_list, NULL },
+ { "set", cmd_media_set, cmd_media_set_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
diff --git a/tipc/media.h b/tipc/media.h
new file mode 100644
index 0000000..8584af7
--- /dev/null
+++ b/tipc/media.h
@@ -0,0 +1,21 @@
+/*
+ * media.h TIPC link functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_MEDIA_H
+#define _TIPC_MEDIA_H
+
+extern int help_flag;
+
+int cmd_media(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data);
+void cmd_media_help(struct cmdl *cmdl);
+
+#endif
diff --git a/tipc/misc.c b/tipc/misc.c
new file mode 100644
index 0000000..e4b1cd0
--- /dev/null
+++ b/tipc/misc.c
@@ -0,0 +1,131 @@
+/*
+ * misc.c Miscellaneous TIPC helper functions.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <linux/tipc.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <errno.h>
+#include "misc.h"
+
+#define IN_RANGE(val, low, high) ((val) <= (high) && (val) >= (low))
+
+uint32_t str2addr(char *str)
+{
+ unsigned int z, c, n;
+ char dummy;
+
+ if (sscanf(str, "%u.%u.%u%c", &z, &c, &n, &dummy) != 3) {
+ fprintf(stderr, "invalid network address, syntax: Z.C.N\n");
+ return 0;
+ }
+
+ if (IN_RANGE(z, 0, 255) && IN_RANGE(c, 0, 4095) && IN_RANGE(n, 0, 4095))
+ return tipc_addr(z, c, n);
+
+ fprintf(stderr, "invalid network address \"%s\"\n", str);
+ return 0;
+}
+
+static int is_hex(char *arr, int last)
+{
+ int i;
+
+ while (!arr[last])
+ last--;
+
+ for (i = 0; i <= last; i++) {
+ if (!IN_RANGE(arr[i], '0', '9') &&
+ !IN_RANGE(arr[i], 'a', 'f') &&
+ !IN_RANGE(arr[i], 'A', 'F'))
+ return 0;
+ }
+ return 1;
+}
+
+static int is_name(char *arr, int last)
+{
+ int i;
+ char c;
+
+ while (!arr[last])
+ last--;
+
+ if (last > 15)
+ return 0;
+
+ for (i = 0; i <= last; i++) {
+ c = arr[i];
+ if (!IN_RANGE(c, '0', '9') && !IN_RANGE(c, 'a', 'z') &&
+ !IN_RANGE(c, 'A', 'Z') && c != '-' && c != '_' &&
+ c != '.' && c != ':' && c != '@')
+ return 0;
+ }
+ return 1;
+}
+
+int str2nodeid(char *str, uint8_t *id)
+{
+ int len = strlen(str);
+ int i;
+
+ if (len > 32)
+ return -1;
+
+ if (is_name(str, len - 1)) {
+ memcpy(id, str, len);
+ return 0;
+ }
+ if (!is_hex(str, len - 1))
+ return -1;
+
+ str[len] = '0';
+ for (i = 0; i < 16; i++) {
+ if (sscanf(&str[2 * i], "%2hhx", &id[i]) != 1)
+ break;
+ }
+ return 0;
+}
+
+void nodeid2str(uint8_t *id, char *str)
+{
+ int i;
+
+ if (is_name((char *)id, 15)) {
+ memcpy(str, id, 16);
+ return;
+ }
+
+ for (i = 0; i < 16; i++)
+ sprintf(&str[2 * i], "%02x", id[i]);
+
+ for (i = 31; str[i] == '0'; i--)
+ str[i] = 0;
+}
+
+void hash2nodestr(uint32_t hash, char *str)
+{
+ struct tipc_sioc_nodeid_req nr = {};
+ int sd;
+
+ sd = socket(AF_TIPC, SOCK_RDM, 0);
+ if (sd < 0) {
+ fprintf(stderr, "opening TIPC socket: %s\n", strerror(errno));
+ return;
+ }
+ nr.peer = hash;
+ if (!ioctl(sd, SIOCGETNODEID, &nr))
+ nodeid2str((uint8_t *)nr.node_id, str);
+ close(sd);
+}
diff --git a/tipc/misc.h b/tipc/misc.h
new file mode 100644
index 0000000..ff2f31f
--- /dev/null
+++ b/tipc/misc.h
@@ -0,0 +1,22 @@
+/*
+ * misc.h Miscellaneous TIPC helper functions.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_MISC_H
+#define _TIPC_MISC_H
+
+#include <stdint.h>
+
+uint32_t str2addr(char *str);
+int str2nodeid(char *str, uint8_t *id);
+void nodeid2str(uint8_t *id, char *str);
+void hash2nodestr(uint32_t hash, char *str);
+
+#endif
diff --git a/tipc/msg.c b/tipc/msg.c
new file mode 100644
index 0000000..dc09d05
--- /dev/null
+++ b/tipc/msg.c
@@ -0,0 +1,170 @@
+/*
+ * msg.c Messaging (netlink) helper functions.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+
+#include <linux/tipc_netlink.h>
+#include <linux/tipc.h>
+#include <linux/genetlink.h>
+#include <libmnl/libmnl.h>
+
+#include "msg.h"
+
+int parse_attrs(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ tb[type] = attr;
+
+ return MNL_CB_OK;
+}
+
+static int family_id_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[CTRL_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ int *id = data;
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, tb);
+ if (!tb[CTRL_ATTR_FAMILY_ID])
+ return MNL_CB_ERROR;
+
+ *id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+
+ return MNL_CB_OK;
+}
+
+static struct mnl_socket *msg_send(struct nlmsghdr *nlh)
+{
+ int ret;
+ struct mnl_socket *nl;
+
+ nl = mnl_socket_open(NETLINK_GENERIC);
+ if (nl == NULL) {
+ perror("mnl_socket_open");
+ return NULL;
+ }
+
+ ret = mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID);
+ if (ret < 0) {
+ perror("mnl_socket_bind");
+ return NULL;
+ }
+
+ ret = mnl_socket_sendto(nl, nlh, nlh->nlmsg_len);
+ if (ret < 0) {
+ perror("mnl_socket_send");
+ return NULL;
+ }
+
+ return nl;
+}
+
+static int msg_recv(struct mnl_socket *nl, mnl_cb_t callback, void *data, int seq)
+{
+ int ret;
+ unsigned int portid;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ portid = mnl_socket_get_portid(nl);
+
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ while (ret > 0) {
+ ret = mnl_cb_run(buf, ret, seq, portid, callback, data);
+ if (ret <= 0)
+ break;
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ }
+ if (ret == -1)
+ perror("error");
+
+ mnl_socket_close(nl);
+
+ return ret;
+}
+
+static int msg_query(struct nlmsghdr *nlh, mnl_cb_t callback, void *data)
+{
+ unsigned int seq;
+ struct mnl_socket *nl;
+
+ seq = time(NULL);
+ nlh->nlmsg_seq = seq;
+
+ nl = msg_send(nlh);
+ if (!nl)
+ return -ENOTSUP;
+
+ return msg_recv(nl, callback, data, seq);
+}
+
+static int get_family(void)
+{
+ int err;
+ int nl_family;
+ struct nlmsghdr *nlh;
+ struct genlmsghdr *genl;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ nlh = mnl_nlmsg_put_header(buf);
+ nlh->nlmsg_type = GENL_ID_CTRL;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+ genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+ genl->cmd = CTRL_CMD_GETFAMILY;
+ genl->version = 1;
+
+ mnl_attr_put_u16(nlh, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL);
+ mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, TIPC_GENL_V2_NAME);
+
+ if ((err = msg_query(nlh, family_id_cb, &nl_family)))
+ return err;
+
+ return nl_family;
+}
+
+int msg_doit(struct nlmsghdr *nlh, mnl_cb_t callback, void *data)
+{
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ return msg_query(nlh, callback, data);
+}
+
+int msg_dumpit(struct nlmsghdr *nlh, mnl_cb_t callback, void *data)
+{
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ return msg_query(nlh, callback, data);
+}
+
+struct nlmsghdr *msg_init(char *buf, int cmd)
+{
+ int family;
+ struct nlmsghdr *nlh;
+ struct genlmsghdr *genl;
+
+ family = get_family();
+ if (family <= 0) {
+ fprintf(stderr,
+ "Unable to get TIPC nl family id (module loaded?)\n");
+ return NULL;
+ }
+
+ nlh = mnl_nlmsg_put_header(buf);
+ nlh->nlmsg_type = family;
+
+ genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+ genl->cmd = cmd;
+ genl->version = 1;
+
+ return nlh;
+}
diff --git a/tipc/msg.h b/tipc/msg.h
new file mode 100644
index 0000000..41fd1ad
--- /dev/null
+++ b/tipc/msg.h
@@ -0,0 +1,20 @@
+/*
+ * msg.h Messaging (netlink) helper functions.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_MSG_H
+#define _TIPC_MSG_H
+
+struct nlmsghdr *msg_init(char *buf, int cmd);
+int msg_doit(struct nlmsghdr *nlh, mnl_cb_t callback, void *data);
+int msg_dumpit(struct nlmsghdr *nlh, mnl_cb_t callback, void *data);
+int parse_attrs(const struct nlattr *attr, void *data);
+
+#endif
diff --git a/tipc/nametable.c b/tipc/nametable.c
new file mode 100644
index 0000000..d899eeb
--- /dev/null
+++ b/tipc/nametable.c
@@ -0,0 +1,123 @@
+/*
+ * nametable.c TIPC nametable functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#include <linux/tipc_netlink.h>
+#include <linux/tipc.h>
+#include <linux/genetlink.h>
+#include <libmnl/libmnl.h>
+
+#include "cmdl.h"
+#include "msg.h"
+#include "nametable.h"
+#include "misc.h"
+#include "utils.h"
+
+#define PORTID_STR_LEN 45 /* Four u32 and five delimiter chars */
+
+static int nametable_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ int *iteration = data;
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_NAME_TABLE_MAX + 1] = {};
+ struct nlattr *publ[TIPC_NLA_PUBL_MAX + 1] = {};
+ const char *scope[] = { "", "zone", "cluster", "node" };
+ char str[33] = {0,};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_NAME_TABLE])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_NAME_TABLE], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_NAME_TABLE_PUBL])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(attrs[TIPC_NLA_NAME_TABLE_PUBL], parse_attrs, publ);
+ if (!publ[TIPC_NLA_NAME_TABLE_PUBL])
+ return MNL_CB_ERROR;
+
+ if (!*iteration && !is_json_context())
+ printf("%-10s %-10s %-10s %-8s %-10s %-33s\n",
+ "Type", "Lower", "Upper", "Scope", "Port",
+ "Node");
+ (*iteration)++;
+
+ hash2nodestr(mnl_attr_get_u32(publ[TIPC_NLA_PUBL_NODE]), str);
+
+ open_json_object(NULL);
+ print_uint(PRINT_ANY, "type", "%-10u",
+ mnl_attr_get_u32(publ[TIPC_NLA_PUBL_TYPE]));
+ print_string(PRINT_FP, NULL, " ", "");
+ print_uint(PRINT_ANY, "lower", "%-10u",
+ mnl_attr_get_u32(publ[TIPC_NLA_PUBL_LOWER]));
+ print_string(PRINT_FP, NULL, " ", "");
+ print_uint(PRINT_ANY, "upper", "%-10u",
+ mnl_attr_get_u32(publ[TIPC_NLA_PUBL_UPPER]));
+ print_string(PRINT_FP, NULL, " ", "");
+ print_string(PRINT_ANY, "scope", "%-8s",
+ scope[mnl_attr_get_u32(publ[TIPC_NLA_PUBL_SCOPE])]);
+ print_string(PRINT_FP, NULL, " ", "");
+ print_uint(PRINT_ANY, "port", "%-10u",
+ mnl_attr_get_u32(publ[TIPC_NLA_PUBL_REF]));
+ print_string(PRINT_FP, NULL, " ", "");
+ print_string(PRINT_ANY, "node", "%s", str);
+ print_string(PRINT_FP, NULL, "\n", "");
+ close_json_object();
+
+ return MNL_CB_OK;
+}
+
+static int cmd_nametable_show(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int iteration = 0;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ int rc = 0;
+
+ if (help_flag) {
+ fprintf(stderr, "Usage: %s nametable show\n", cmdl->argv[0]);
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_NAME_TABLE_GET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ new_json_obj(json);
+ rc = msg_dumpit(nlh, nametable_show_cb, &iteration);
+ delete_json_obj();
+
+ return rc;
+}
+
+void cmd_nametable_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s nametable COMMAND\n\n"
+ "COMMANDS\n"
+ " show - Show nametable\n",
+ cmdl->argv[0]);
+}
+
+int cmd_nametable(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data)
+{
+ const struct cmd cmds[] = {
+ { "show", cmd_nametable_show, NULL },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
diff --git a/tipc/nametable.h b/tipc/nametable.h
new file mode 100644
index 0000000..e0473e1
--- /dev/null
+++ b/tipc/nametable.h
@@ -0,0 +1,21 @@
+/*
+ * nametable.h TIPC nametable functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_NAMETABLE_H
+#define _TIPC_NAMETABLE_H
+
+extern int help_flag;
+
+void cmd_nametable_help(struct cmdl *cmdl);
+int cmd_nametable(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data);
+
+#endif
diff --git a/tipc/node.c b/tipc/node.c
new file mode 100644
index 0000000..2fec675
--- /dev/null
+++ b/tipc/node.c
@@ -0,0 +1,343 @@
+/*
+ * node.c TIPC node functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <linux/tipc_netlink.h>
+#include <linux/tipc.h>
+#include <linux/genetlink.h>
+#include <libmnl/libmnl.h>
+
+#include "cmdl.h"
+#include "msg.h"
+#include "misc.h"
+#include "node.h"
+
+static int node_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_NODE_MAX + 1] = {};
+ char str[33] = {};
+ uint32_t addr;
+
+ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_attrs, info);
+ if (!info[TIPC_NLA_NODE])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_NODE], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_NODE_ADDR])
+ return MNL_CB_ERROR;
+
+ addr = mnl_attr_get_u32(attrs[TIPC_NLA_NODE_ADDR]);
+ hash2nodestr(addr, str);
+ printf("%-32s %08x ", str, addr);
+ if (attrs[TIPC_NLA_NODE_UP])
+ printf("up\n");
+ else
+ printf("down\n");
+ return MNL_CB_OK;
+}
+
+static int cmd_node_list(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ if (help_flag) {
+ fprintf(stderr, "Usage: %s node list\n", cmdl->argv[0]);
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_NODE_GET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+ printf("Node Identity Hash State\n");
+ return msg_dumpit(nlh, node_list_cb, NULL);
+}
+
+static int cmd_node_set_addr(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char *str;
+ uint32_t addr;
+ struct nlattr *nest;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ if (cmdl->argc != cmdl->optind + 1) {
+ fprintf(stderr, "Usage: %s node set address ADDRESS\n",
+ cmdl->argv[0]);
+ return -EINVAL;
+ }
+
+ str = shift_cmdl(cmdl);
+ addr = str2addr(str);
+ if (!addr)
+ return -1;
+
+ if (!(nlh = msg_init(buf, TIPC_NL_NET_SET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_NET);
+ mnl_attr_put_u32(nlh, TIPC_NLA_NET_ADDR, addr);
+ mnl_attr_nest_end(nlh, nest);
+
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static int cmd_node_get_addr(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int sk;
+ socklen_t sz = sizeof(struct sockaddr_tipc);
+ struct sockaddr_tipc addr;
+
+ sk = socket(AF_TIPC, SOCK_RDM, 0);
+ if (sk < 0) {
+ fprintf(stderr, "opening TIPC socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (getsockname(sk, (struct sockaddr *)&addr, &sz) < 0) {
+ fprintf(stderr, "getting TIPC socket address: %s\n",
+ strerror(errno));
+ close(sk);
+ return -1;
+ }
+ close(sk);
+
+ printf("%08x\n", addr.addr.id.node);
+ return 0;
+}
+
+static int cmd_node_set_nodeid(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ uint8_t id[16] = {0,};
+ uint64_t *w0 = (uint64_t *) &id[0];
+ uint64_t *w1 = (uint64_t *) &id[8];
+ struct nlattr *nest;
+ char *str;
+
+ if (cmdl->argc != cmdl->optind + 1) {
+ fprintf(stderr, "Usage: %s node set nodeid NODE_ID\n",
+ cmdl->argv[0]);
+ return -EINVAL;
+ }
+
+ str = shift_cmdl(cmdl);
+ if (str2nodeid(str, id)) {
+ fprintf(stderr, "Invalid node identity\n");
+ return -EINVAL;
+ }
+
+ nlh = msg_init(buf, TIPC_NL_NET_SET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_NET);
+ mnl_attr_put_u64(nlh, TIPC_NLA_NET_NODEID, *w0);
+ mnl_attr_put_u64(nlh, TIPC_NLA_NET_NODEID_W1, *w1);
+ mnl_attr_nest_end(nlh, nest);
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static int nodeid_get_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_NET_MAX + 1] = {};
+ char str[33] = {0,};
+ uint8_t id[16] = {0,};
+ uint64_t *w0 = (uint64_t *) &id[0];
+ uint64_t *w1 = (uint64_t *) &id[8];
+
+ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_attrs, info);
+ if (!info[TIPC_NLA_NET])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_NET], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_NET_ID])
+ return MNL_CB_ERROR;
+
+ *w0 = mnl_attr_get_u64(attrs[TIPC_NLA_NET_NODEID]);
+ *w1 = mnl_attr_get_u64(attrs[TIPC_NLA_NET_NODEID_W1]);
+ nodeid2str(id, str);
+ printf("Node Identity Hash\n");
+ printf("%-33s", str);
+ cmd_node_get_addr(NULL, NULL, NULL, NULL);
+ return MNL_CB_OK;
+}
+
+static int cmd_node_get_nodeid(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ nlh = msg_init(buf, TIPC_NL_NET_GET);
+ if (!nlh) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ return msg_dumpit(nlh, nodeid_get_cb, NULL);
+}
+
+
+static int netid_get_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_NET_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_attrs, info);
+ if (!info[TIPC_NLA_NET])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_NET], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_NET_ID])
+ return MNL_CB_ERROR;
+
+ printf("%u\n", mnl_attr_get_u32(attrs[TIPC_NLA_NET_ID]));
+
+ return MNL_CB_OK;
+}
+
+static int cmd_node_get_netid(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_NET_GET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ return msg_dumpit(nlh, netid_get_cb, NULL);
+}
+
+static int cmd_node_set_netid(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ int netid;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *nest;
+
+ if (help_flag) {
+ (cmd->help)(cmdl);
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_NET_SET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ if (cmdl->argc != cmdl->optind + 1) {
+ fprintf(stderr, "Usage: %s node set netid NETID\n",
+ cmdl->argv[0]);
+ return -EINVAL;
+ }
+ netid = atoi(shift_cmdl(cmdl));
+
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_NET);
+ mnl_attr_put_u32(nlh, TIPC_NLA_NET_ID, netid);
+ mnl_attr_nest_end(nlh, nest);
+
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static void cmd_node_set_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s node set PROPERTY\n\n"
+ "PROPERTIES\n"
+ " identity NODEID - Set node identity\n"
+ " clusterid CLUSTERID - Set local cluster id\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_node_set(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "address", cmd_node_set_addr, NULL },
+ { "identity", cmd_node_set_nodeid, NULL },
+ { "netid", cmd_node_set_netid, NULL },
+ { "clusterid", cmd_node_set_netid, NULL },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+static void cmd_node_get_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s node get PROPERTY\n\n"
+ "PROPERTIES\n"
+ " identity - Get node identity\n"
+ " clusterid - Get local clusterid\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_node_get(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "address", cmd_node_get_addr, NULL },
+ { "identity", cmd_node_get_nodeid, NULL },
+ { "netid", cmd_node_get_netid, NULL },
+ { "clusterid", cmd_node_get_netid, NULL },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+void cmd_node_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s node COMMAND [ARGS] ...\n\n"
+ "COMMANDS\n"
+ " list - List remote nodes\n"
+ " get - Get local node parameters\n"
+ " set - Set local node parameters\n",
+ cmdl->argv[0]);
+}
+
+int cmd_node(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data)
+{
+ const struct cmd cmds[] = {
+ { "list", cmd_node_list, NULL },
+ { "get", cmd_node_get, cmd_node_get_help },
+ { "set", cmd_node_set, cmd_node_set_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
diff --git a/tipc/node.h b/tipc/node.h
new file mode 100644
index 0000000..afee1fd
--- /dev/null
+++ b/tipc/node.h
@@ -0,0 +1,21 @@
+/*
+ * node.h TIPC node functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_NODE_H
+#define _TIPC_NODE_H
+
+extern int help_flag;
+
+int cmd_node(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data);
+void cmd_node_help(struct cmdl *cmdl);
+
+#endif
diff --git a/tipc/peer.c b/tipc/peer.c
new file mode 100644
index 0000000..f638077
--- /dev/null
+++ b/tipc/peer.c
@@ -0,0 +1,97 @@
+/*
+ * peer.c TIPC peer functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <linux/tipc_netlink.h>
+#include <linux/tipc.h>
+#include <linux/genetlink.h>
+#include <libmnl/libmnl.h>
+
+#include "cmdl.h"
+#include "msg.h"
+#include "misc.h"
+#include "peer.h"
+
+static int cmd_peer_rm_addr(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char *str;
+ uint32_t addr;
+ struct nlattr *nest;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ if ((cmdl->argc != cmdl->optind + 1) || help_flag) {
+ fprintf(stderr, "Usage: %s peer remove address ADDRESS\n",
+ cmdl->argv[0]);
+ return -EINVAL;
+ }
+
+ str = shift_cmdl(cmdl);
+
+ /* First try legacy Z.C.N format, then integer format */
+ addr = str2addr(str);
+ if (!addr)
+ addr = atoi(str);
+ if (!addr)
+ return -1;
+
+ if (!(nlh = msg_init(buf, TIPC_NL_PEER_REMOVE))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_NET);
+ mnl_attr_put_u32(nlh, TIPC_NLA_NET_ADDR, addr);
+ mnl_attr_nest_end(nlh, nest);
+
+ return msg_doit(nlh, NULL, NULL);
+}
+
+static void cmd_peer_rm_help(struct cmdl *cmdl)
+{
+ fprintf(stderr, "Usage: %s peer remove address ADDRESS\n",
+ cmdl->argv[0]);
+}
+
+static int cmd_peer_rm(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ const struct cmd cmds[] = {
+ { "address", cmd_peer_rm_addr, cmd_peer_rm_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
+
+void cmd_peer_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s peer COMMAND [ARGS] ...\n\n"
+ "COMMANDS\n"
+ " remove - Remove an offline peer node\n",
+ cmdl->argv[0]);
+}
+
+int cmd_peer(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data)
+{
+ const struct cmd cmds[] = {
+ { "remove", cmd_peer_rm, cmd_peer_rm_help },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
diff --git a/tipc/peer.h b/tipc/peer.h
new file mode 100644
index 0000000..8972261
--- /dev/null
+++ b/tipc/peer.h
@@ -0,0 +1,21 @@
+/*
+ * peer.h TIPC peer functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_PEER_H
+#define _TIPC_PEER_H
+
+extern int help_flag;
+
+int cmd_peer(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data);
+void cmd_peer_help(struct cmdl *cmdl);
+
+#endif
diff --git a/tipc/socket.c b/tipc/socket.c
new file mode 100644
index 0000000..852984e
--- /dev/null
+++ b/tipc/socket.c
@@ -0,0 +1,139 @@
+/*
+ * socket.c TIPC socket functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#include <linux/tipc.h>
+#include <linux/tipc_netlink.h>
+#include <linux/genetlink.h>
+#include <libmnl/libmnl.h>
+
+#include "cmdl.h"
+#include "msg.h"
+#include "socket.h"
+
+#define PORTID_STR_LEN 45 /* Four u32 and five delimiter chars */
+
+static int publ_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_SOCK_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_PUBL])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_PUBL], parse_attrs, attrs);
+
+ printf(" bound to {%u,%u,%u}\n",
+ mnl_attr_get_u32(attrs[TIPC_NLA_PUBL_TYPE]),
+ mnl_attr_get_u32(attrs[TIPC_NLA_PUBL_LOWER]),
+ mnl_attr_get_u32(attrs[TIPC_NLA_PUBL_UPPER]));
+
+ return MNL_CB_OK;
+}
+
+static int publ_list(uint32_t sock)
+{
+ struct nlmsghdr *nlh;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *nest;
+
+ if (!(nlh = msg_init(buf, TIPC_NL_PUBL_GET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ nest = mnl_attr_nest_start(nlh, TIPC_NLA_SOCK);
+ mnl_attr_put_u32(nlh, TIPC_NLA_SOCK_REF, sock);
+ mnl_attr_nest_end(nlh, nest);
+
+ return msg_dumpit(nlh, publ_list_cb, NULL);
+}
+
+static int sock_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *info[TIPC_NLA_MAX + 1] = {};
+ struct nlattr *attrs[TIPC_NLA_SOCK_MAX + 1] = {};
+
+ mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, info);
+ if (!info[TIPC_NLA_SOCK])
+ return MNL_CB_ERROR;
+
+ mnl_attr_parse_nested(info[TIPC_NLA_SOCK], parse_attrs, attrs);
+ if (!attrs[TIPC_NLA_SOCK_REF])
+ return MNL_CB_ERROR;
+
+ printf("socket %u\n", mnl_attr_get_u32(attrs[TIPC_NLA_SOCK_REF]));
+
+ if (attrs[TIPC_NLA_SOCK_CON]) {
+ uint32_t node;
+ struct nlattr *con[TIPC_NLA_CON_MAX + 1] = {};
+
+ mnl_attr_parse_nested(attrs[TIPC_NLA_SOCK_CON], parse_attrs, con);
+ node = mnl_attr_get_u32(con[TIPC_NLA_CON_NODE]);
+
+ printf(" connected to %x:%u", node,
+ mnl_attr_get_u32(con[TIPC_NLA_CON_SOCK]));
+
+ if (con[TIPC_NLA_CON_FLAG])
+ printf(" via {%u,%u}\n",
+ mnl_attr_get_u32(con[TIPC_NLA_CON_TYPE]),
+ mnl_attr_get_u32(con[TIPC_NLA_CON_INST]));
+ else
+ printf("\n");
+ } else if (attrs[TIPC_NLA_SOCK_HAS_PUBL]) {
+ publ_list(mnl_attr_get_u32(attrs[TIPC_NLA_SOCK_REF]));
+ }
+
+ return MNL_CB_OK;
+}
+
+static int cmd_socket_list(struct nlmsghdr *nlh, const struct cmd *cmd,
+ struct cmdl *cmdl, void *data)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ if (help_flag) {
+ fprintf(stderr, "Usage: %s socket list\n", cmdl->argv[0]);
+ return -EINVAL;
+ }
+
+ if (!(nlh = msg_init(buf, TIPC_NL_SOCK_GET))) {
+ fprintf(stderr, "error, message initialisation failed\n");
+ return -1;
+ }
+
+ return msg_dumpit(nlh, sock_list_cb, NULL);
+}
+
+void cmd_socket_help(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Usage: %s socket COMMAND\n\n"
+ "Commands:\n"
+ " list - List sockets (ports)\n",
+ cmdl->argv[0]);
+}
+
+int cmd_socket(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data)
+{
+ const struct cmd cmds[] = {
+ { "list", cmd_socket_list, NULL },
+ { NULL }
+ };
+
+ return run_cmd(nlh, cmd, cmds, cmdl, NULL);
+}
diff --git a/tipc/socket.h b/tipc/socket.h
new file mode 100644
index 0000000..9d1b648
--- /dev/null
+++ b/tipc/socket.h
@@ -0,0 +1,21 @@
+/*
+ * socket.h TIPC socket functionality.
+ *
+ * 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#ifndef _TIPC_SOCKET_H
+#define _TIPC_SOCKET_H
+
+extern int help_flag;
+
+void cmd_socket_help(struct cmdl *cmdl);
+int cmd_socket(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
+ void *data);
+
+#endif
diff --git a/tipc/tipc.c b/tipc/tipc.c
new file mode 100644
index 0000000..f85ddee
--- /dev/null
+++ b/tipc/tipc.c
@@ -0,0 +1,117 @@
+/*
+ * tipc. TIPC 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: Richard Alpe <richard.alpe@ericsson.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <unistd.h>
+
+#include "bearer.h"
+#include "link.h"
+#include "nametable.h"
+#include "socket.h"
+#include "media.h"
+#include "node.h"
+#include "peer.h"
+#include "cmdl.h"
+
+int help_flag;
+int json;
+int pretty;
+
+static void about(struct cmdl *cmdl)
+{
+ fprintf(stderr,
+ "Transparent Inter-Process Communication Protocol\n"
+ "Usage: %s [OPTIONS] COMMAND [ARGS] ...\n"
+ "\n"
+ "Options:\n"
+ " -h, --help \t\tPrint help for last given command\n"
+ " -j, --json \t\tJson format printouts\n"
+ " -p, --pretty \t\tpretty print\n"
+ "\n"
+ "Commands:\n"
+ " bearer - Show or modify bearers\n"
+ " link - Show or modify links\n"
+ " media - Show or modify media\n"
+ " nametable - Show nametable\n"
+ " node - Show or modify node related parameters\n"
+ " peer - Peer related operations\n"
+ " socket - Show sockets\n",
+ cmdl->argv[0]);
+}
+
+int main(int argc, char *argv[])
+{
+ int i;
+ int res;
+ struct cmdl cmdl;
+ const struct cmd cmd = {"tipc", NULL, about};
+ struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"json", no_argument, 0, 'j'},
+ {"pretty", no_argument, 0, 'p'},
+ {0, 0, 0, 0}
+ };
+ const struct cmd cmds[] = {
+ { "bearer", cmd_bearer, cmd_bearer_help},
+ { "link", cmd_link, cmd_link_help},
+ { "media", cmd_media, cmd_media_help},
+ { "nametable", cmd_nametable, cmd_nametable_help},
+ { "node", cmd_node, cmd_node_help},
+ { "peer", cmd_peer, cmd_peer_help},
+ { "socket", cmd_socket, cmd_socket_help},
+ { NULL }
+ };
+
+ do {
+ int option_index = 0;
+
+ i = getopt_long(argc, argv, "hjp", long_options, &option_index);
+
+ switch (i) {
+ case 'h':
+ /*
+ * We want the help for the last command, so we flag
+ * here in order to print later.
+ */
+ help_flag = 1;
+ break;
+ case 'j':
+ /*
+ * Enable json format printouts
+ */
+ json = 1;
+ break;
+ case 'p':
+ /*
+ * Enable json pretty output
+ */
+ pretty = 1;
+ break;
+ case -1:
+ /* End of options */
+ break;
+ default:
+ /* Invalid option, error msg is printed by getopts */
+ return 1;
+ }
+ } while (i != -1);
+
+ cmdl.optind = optind;
+ cmdl.argc = argc;
+ cmdl.argv = argv;
+
+ if ((res = run_cmd(NULL, &cmd, cmds, &cmdl, NULL)) != 0)
+ return 1;
+
+ return 0;
+}