diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:14:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:14:35 +0000 |
commit | 9b8a97db9ec4b795e29e72289005fbc58484ebeb (patch) | |
tree | e24ca2d68215e57b4759fe5c032629821eabb250 | |
parent | Initial commit. (diff) | |
download | iproute2-9b8a97db9ec4b795e29e72289005fbc58484ebeb.tar.xz iproute2-9b8a97db9ec4b795e29e72289005fbc58484ebeb.zip |
Adding upstream version 6.8.0.upstream/6.8.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
694 files changed, 211439 insertions, 0 deletions
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> @@ -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. @@ -0,0 +1,30 @@ + This is at least a partial credits file of people that have + contributed to iproute2 over its history. + It is sorted by name and formatted to allow easy grepping + and beautification by scripts. The fields are: name (N), + email (E), web-address (W) + +N: Werner Almesberger +E: werner@almesberger.net +W: https://www.almesberger.net/ +D: Wrote original classifiers, qdiscs, etc. + +N: Stephen Hemminger +E: stephen@networkplumber.org +D: Took over iproute2 starting with 2.6 kernel + +N: Alexey Kuznetsov +E: kuznet@ms2.inr.ac.ru +D: Original author of iproute2 + +N: YOSHIFUJI Hideaki/ 吉藤英明 +E: yoshfuji@linux-ipv6.org +D: USAGI project - Linux IPv6 Development + +N: David S. Miller +E: davem@davemloft.net +D: Developed the kernel networking and routing system + +N: Jamal Hadi Salam +D: Original developer of TC classifier and action +E: hadi@cyberus.ca diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..1b49d69 --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,58 @@ +Iproute2 Maintainers +==================== + +The file provides a set of names that are are able to help +review patches and answer questions. This is in addition to +the netdev@vger.kernel.org mailing list used for all iproute2 +and kernel networking. + +Descriptions of section entries: + + M: Maintainer's Full Name <address@domain> + T: Git tree location. + F: Files and directories with wildcard patterns. + A trailing slash includes all files and subdirectory files. + A wildcard includes all files but not subdirectories. + One pattern per line. Multiple F: lines acceptable. + +Main Branch +M: Stephen Hemminger <stephen@networkplumber.org> +T: git://git.kernel.org/pub/scm/network/iproute2/iproute2.git +L: netdev@vger.kernel.org + +Next Tree +M: David Ahern <dsahern@gmail.com> +T: git://git.kernel.org/pub/scm/network/iproute2/iproute2-next.git +L: netdev@vger.kernel.org + +Ethernet Bridging - bridge +M: Roopa Prabhu <roopa@nvidia.com> +M: Nikolay Aleksandrov <razor@blackwall.org> +L: bridge@lists.linux-foundation.org (moderated for non-subscribers) +F: bridge/* + +Data Center Bridging - dcb +M: Petr Machata <me@pmachata.org> +F: dcb/* + +devlink +M: Jiri Pirko <jiri@resnulli.us> +F: devlink/* + +netkit +M: Daniel Borkmann <daniel@iogearbox.net> +M: Nikolay Aleksandrov <razor@blackwall.org> +L: bpf@vger.kernel.org +F: ip/iplink_netkit.c + +Remote DMA - rdma +M: Leon Romanovsky <leon@kernel.org> +F: rdma/* + +Transparent Inter-Process Communication - tipc +M: Jon Maloy <jmaloy@redhat.com> +F: tipc/* + +virtual Datapath Acceleration - vdpa +M: Parav Pandit <parav@nvidia.com> +F: vdpa/* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8024d45 --- /dev/null +++ b/Makefile @@ -0,0 +1,144 @@ +# SPDX-License-Identifier: GPL-2.0 +# Top level Makefile for iproute2 + +-include config.mk + +ifeq ("$(origin V)", "command line") +VERBOSE = $(V) +endif +ifndef VERBOSE +VERBOSE = 0 +endif + +ifeq ($(VERBOSE),0) +MAKEFLAGS += --no-print-directory +endif + +PREFIX?=/usr +SBINDIR?=/sbin +NETNS_RUN_DIR?=/var/run/netns +NETNS_ETC_DIR?=/etc/netns +DATADIR?=$(PREFIX)/share +HDRDIR?=$(PREFIX)/include/iproute2 +CONF_ETC_DIR?=/etc/iproute2 +CONF_USR_DIR?=$(DATADIR)/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+=-DCONF_USR_DIR=\"$(CONF_USR_DIR)\" \ + -DCONF_ETC_DIR=\"$(CONF_ETC_DIR)\" \ + -DNETNS_RUN_DIR=\"$(NETNS_RUN_DIR)\" \ + -DNETNS_ETC_DIR=\"$(NETNS_ETC_DIR)\" \ + -DCONF_COLOR=$(CONF_COLOR) + +#options for AX.25 +ADDLIB+=ax25_ntop.o + +#options for AX.25 +ADDLIB+=rose_ntop.o + +#options for mpls +ADDLIB+=mpls_ntop.o mpls_pton.o + +#options for NETROM +ADDLIB+=netrom_ntop.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 man +ifeq ($(HAVE_MNL),y) +SUBDIRS += tipc devlink rdma dcb vdpa +endif + +LIBNETLINK=../lib/libutil.a ../lib/libnetlink.a +LDLIBS += $(LIBNETLINK) + +all: config.mk + @set -e; \ + for i in $(SUBDIRS); \ + do echo; echo $$i; $(MAKE) -C $$i; done + +.PHONY: clean clobber distclean check cscope version + +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 " version - update version" + @echo "" + @echo "Make Arguments:" + @echo " V=[0|1] - set build verbosity level" + +config.mk: + @if [ ! -f config.mk -o configure -nt config.mk ]; then \ + sh configure $(KERNEL_INCLUDE); \ + fi + +install: all + install -m 0755 -d $(DESTDIR)$(SBINDIR) + install -m 0755 -d $(DESTDIR)$(CONF_USR_DIR) + 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)$(CONF_USR_DIR) + install -m 0755 -d $(DESTDIR)$(BASH_COMPDIR) + install -m 0644 bash-completion/tc $(DESTDIR)$(BASH_COMPDIR) + install -m 0644 bash-completion/devlink $(DESTDIR)$(BASH_COMPDIR) + install -m 0644 include/bpf_elf.h $(DESTDIR)$(HDRDIR) + +version: + echo "static const char version[] = \""`git describe --tags --long`"\";" \ + > include/version.h + +clean: + @for i in $(SUBDIRS) testsuite; \ + do $(MAKE) -C $$i clean; done + +clobber: + touch config.mk + $(MAKE) 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: @@ -0,0 +1,42 @@ +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. include/uapi + +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 + +David Ahern +dsahern@gmail.com 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/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..d5a7775 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Reporting a vulnerability + +The iproute2 suite of utilities is tightly coupled with the Linux +kernel networking. Therefore the bug reporting process mirrors +the Linux kernel. Most security problems reported related to +iproute2 are really Linux kernel issues (a.k.a Shoot the messenger) +and are best handled via +[Linux Security Bugs](https://docs.kernel.org/process/security-bugs.html). + +For other issues please report bugs to netdev@vger.kernel.org +and include an example script. + +## Supported Versions + +There are no official "Long Term Support" versions for iproute2. +The iproute2 version matches the Linux kernel versions. +There will be occasional maintenance releases for serious +issues if found. Users who need support are encouraged +to use the version of iproute2 found in major distributions. diff --git a/bash-completion/devlink b/bash-completion/devlink new file mode 100644 index 0000000..52dc82b --- /dev/null +++ b/bash-completion/devlink @@ -0,0 +1,1104 @@ +# bash completion for devlink(8) -*- shell-script -*- + +# Get all the optional commands for devlink +_devlink_get_optional_commands() +{ + local object=$1; shift + + local filter_options="" + local options="$(devlink $object help 2>&1 \ + | command sed -n -e "s/^.*devlink $object //p" \ + | cut -d " " -f 1)" + + # Remove duplicate options from "devlink $OBJECT help" command + local opt + for opt in $options; do + if [[ $filter_options =~ $opt ]]; then + continue + else + filter_options="$filter_options $opt" + fi + done + + echo $filter_options +} + +# Complete based on given word, for when an argument or an option name has +# but a few possible arguments. +_devlink_direct_complete() +{ + local dev port region value + + case $1 in + dev) + value=$(devlink dev show 2>/dev/null) + ;; + selftests_id) + dev=${words[4]} + value=$(devlink -j dev selftests show 2>/dev/null \ + | jq ".selftests[\"$dev\"][]") + ;; + param_name) + dev=${words[4]} + value=$(devlink -j dev param show 2>/dev/null \ + | jq ".param[\"$dev\"][].name") + ;; + port) + value=$(devlink -j port show 2>/dev/null \ + | jq '.port as $ports | $ports | keys[] as $key + | ($ports[$key].netdev // $key)') + ;; + lc) + dev=${words[3]} + value=$(devlink -j lc show 2>/dev/null \ + | jq ".lc[\"$dev\"]" \ + | jq '. as $lcs | $lcs | keys[] as $key |($lcs[$key].lc)' \ + 2>/dev/null) + ;; + lc_type) + dev=${words[3]} + lc=${words[5]} + value=$(devlink lc show $dev lc $lc -j 2>/dev/null \ + | jq '.[][][]["supported_types"][]') + ;; + region) + value=$(devlink -j region show 2>/dev/null \ + | jq '.regions' | jq 'keys[]') + ;; + snapshot) + region=${words[3]} + value=$(devlink -j region show 2>/dev/null \ + | jq ".regions[\"$region\"].snapshot[]") + ;; + trap) + dev=${words[3]} + value=$(devlink -j trap show 2>/dev/null \ + | jq ".trap[\"$dev\"][].name") + ;; + trap_group) + dev=${words[4]} + value=$(devlink -j trap group show 2>/dev/null \ + | jq ".trap_group[\"$dev\"][].name") + ;; + trap_policer) + dev=${words[4]} + value=$(devlink -j trap policer show 2>/dev/null \ + | jq ".trap_policer[\"$dev\"][].policer") + ;; + health_dev) + value=$(devlink -j health show 2>/dev/null | jq '.health' \ + | jq 'keys[]') + ;; + reporter) + dev=${words[cword - 2]} + value=$(devlink -j health show 2>/dev/null \ + | jq ".health[\"$dev\"][].reporter") + ;; + pool) + dev=$pprev + value=$(devlink -j sb pool show 2>/dev/null \ + | jq ".pool[\"$dev\"][].pool") + ;; + port_pool) + port=${words[5]} + value=$(devlink -j sb port pool show 2>/dev/null \ + | jq ".port_pool[\"$port\"][].pool") + ;; + tc) + port=$pprev + value=$(devlink -j sb tc bind show 2>/dev/null \ + | jq ".tc_bind[\"$port\"][].tc") + ;; + esac + + COMPREPLY+=( $( compgen -W "$value" -- "$cur" ) ) + # Remove colon containing prefix from COMPREPLY items in order to avoid + # wordbreaks with colon. + __ltrim_colon_completions "$cur" +} + +# Completion for devlink dev eswitch set +_devlink_dev_eswitch_set() +{ + local -A settings=( + [mode]=notseen + [inline-mode]=notseen + [encap-mode]=notseen + ) + + if [[ $cword -eq 5 ]]; then + COMPREPLY=( $( compgen -W "mode inline-mode encap-mode" -- "$cur" ) ) + fi + + # Mark seen settings + local word + for word in "${words[@]:5:${#words[@]}-1}"; do + if [[ -n $word ]]; then + if [[ "${settings[$word]}" ]]; then + settings[$word]=seen + fi + fi + done + + case $prev in + mode) + COMPREPLY=( $( compgen -W "legacy switchdev" -- "$cur" ) ) + return + ;; + inline-mode) + COMPREPLY=( $( compgen -W "none link network transport" -- \ + "$cur" ) ) + return + ;; + encap-mode) + COMPREPLY=( $( compgen -W "none basic" -- "$cur" ) ) + return + ;; + esac + + local -a comp_words=() + + # Add settings not seen to completions + local setting + for setting in "${!settings[@]}"; do + if [ "${settings[$setting]}" = notseen ]; then + comp_words+=( "$setting" ) + fi + done + + COMPREPLY=( $( compgen -W "${comp_words[*]}" -- "$cur" ) ) +} + +# Completion for devlink dev eswitch +_devlink_dev_eswitch() +{ + case "$cword" in + 3) + COMPREPLY=( $( compgen -W "show set" -- "$cur" ) ) + return + ;; + 4) + _devlink_direct_complete "dev" + return + ;; + esac + + case "${words[3]}" in + set) + _devlink_dev_eswitch_set + return + ;; + show) + return + ;; + esac +} + +# Completion for devlink dev param set +_devlink_dev_param_set() +{ + case $cword in + 7) + COMPREPLY=( $( compgen -W "value" -- "$cur" ) ) + return + ;; + 8) + # String argument + return + ;; + 9) + COMPREPLY=( $( compgen -W "cmode" -- "$cur" ) ) + return + ;; + 10) + COMPREPLY=( $( compgen -W "runtime driverinit permanent" -- \ + "$cur" ) ) + return + ;; + esac +} + +# Completion for devlink dev param +_devlink_dev_param() +{ + case "$cword" in + 3) + COMPREPLY=( $( compgen -W "show set" -- "$cur" ) ) + return + ;; + 4) + _devlink_direct_complete "dev" + return + ;; + 5) + COMPREPLY=( $( compgen -W "name" -- "$cur" ) ) + return + ;; + 6) + _devlink_direct_complete "param_name" + return + ;; + esac + + if [[ "${words[3]}" == "set" ]]; then + _devlink_dev_param_set + fi +} + +# Completion for devlink dev reload +_devlink_dev_reload() +{ + case "$cword" in + 4) + COMPREPLY=( $( compgen -W "netns" -- "$cur" ) ) + return + ;; + 5) + local nslist=$( ip netns list 2>/dev/null ) + COMPREPLY=( $( compgen -W "$nslist" -- "$cur" ) ) + return + ;; + esac +} + +# Completion for devlink dev flash +_devlink_dev_flash() +{ + case "$cword" in + 4) + COMPREPLY=( $( compgen -W "file" -- "$cur" ) ) + return + ;; + 5) + _filedir + return + ;; + 6) + COMPREPLY=( $( compgen -W "component" -- "$cur" ) ) + return + ;; + esac +} + +# Completion for devlink dev selftests +_devlink_dev_selftests() +{ + if [[ $cword -gt 5 ]]; then + _devlink_direct_complete "selftests_id" + return + fi + case "$cword" in + 3) + COMPREPLY=( $( compgen -W "show run" -- "$cur" ) ) + return + ;; + 4) + _devlink_direct_complete "dev" + return + ;; + 5) + COMPREPLY=( $( compgen -W "id" -- "$cur" ) ) + return + ;; + esac +} + +# Completion for devlink dev +_devlink_dev() +{ + case $command in + show|reload|info|flash) + if [[ $cword -le 3 ]]; then + _devlink_direct_complete "dev" + elif [[ $command == "reload" || $command == "flash" ]];then + _devlink_dev_$command + fi + return + ;; + eswitch|param|selftests) + _devlink_dev_$command + return + ;; + esac +} + +# Completion for devlink port set +_devlink_port_set() +{ + case "$cword" in + 3) + _devlink_direct_complete "port" + return + ;; + 4) + COMPREPLY=( $( compgen -W "type" -- "$cur" ) ) + return + ;; + 5) + COMPREPLY=( $( compgen -W "eth ib auto" -- "$cur" ) ) + return + ;; + esac +} + +# Completion for devlink port split +_devlink_port_split() +{ + case "$cword" in + 3) + _devlink_direct_complete "port" + return + ;; + 4) + COMPREPLY=( $( compgen -W "count" -- "$cur" ) ) + return + ;; + 5) + # Integer argument + return + ;; + esac +} + +# Completion for devlink port param set +_devlink_port_param_set() +{ + case $cword in + 7) + COMPREPLY=( $( compgen -W "value" -- "$cur" ) ) + return + ;; + 8) + # String argument + return + ;; + 9) + COMPREPLY=( $( compgen -W "cmode" -- "$cur" ) ) + return + ;; + 10) + COMPREPLY=( $( compgen -W "runtime driverinit permanent" -- \ + "$cur" ) ) + return + ;; + esac +} + +# Completion for devlink port param +_devlink_port_param() +{ + case "$cword" in + 3) + COMPREPLY=( $( compgen -W "show set" -- "$cur" ) ) + return + ;; + 4) + _devlink_direct_complete "port" + return + ;; + 5) + COMPREPLY=( $( compgen -W "name" -- "$cur" ) ) + return + ;; + 6) + _devlink_direct_complete "param_name" + return + ;; + esac + + if [[ "${words[3]}" == "set" ]]; then + _devlink_port_param_set + fi +} + +# Completion for devlink port +_devlink_port() +{ + case $command in + set) + _devlink_port_set + return + ;; + split) + _devlink_port_split + return + ;; + param) + _devlink_port_param + return + ;; + show|unsplit) + if [[ $cword -eq 3 ]]; then + _devlink_direct_complete "port" + fi + return + ;; + esac +} + +# Completion for devlink lc set +_devlink_lc_set() +{ + case "$cword" in + 3) + _devlink_direct_complete "dev" + return + ;; + 4) + COMPREPLY=( $( compgen -W "lc" -- "$cur" ) ) + ;; + 5) + _devlink_direct_complete "lc" + ;; + 6) + COMPREPLY=( $( compgen -W "type notype" -- "$cur" ) ) + return + ;; + 7) + if [[ "$prev" == "type" ]]; then + _devlink_direct_complete "lc_type" + fi + esac +} + +# Completion for devlink lc show +_devlink_lc_show() +{ + case $cword in + 3) + _devlink_direct_complete "dev" + ;; + 4) + COMPREPLY=( $( compgen -W "lc" -- "$cur" ) ) + ;; + 5) + _devlink_direct_complete "lc" + ;; + esac +} + +# Completion for devlink lc +_devlink_lc() +{ + case $command in + set) + _devlink_lc_set + return + ;; + show) + _devlink_lc_show + return + ;; + esac +} + +# Completion for devlink dpipe +_devlink_dpipe() +{ + local options="$(devlink dpipe help 2>&1 \ + | command sed -e '/OBJECT-LIST := /!d' \ + -e 's/.*{ //' -e 's/}.*//' -e 's/|//g' )" + + if [[ $cword -eq 2 ]]; then + COMPREPLY+=( $( compgen -W "$options" -- "$cur" ) ) + fi +} + +# Completion for devlink monitor +_devlink_monitor() +{ + local options="$(devlink monitor help 2>&1 \ + | command sed -e '/OBJECT-LIST := /!d' \ + -e 's/.*{ //' -e 's/}.*//' -e 's/|//g' )" + + if [[ $cword -eq 2 ]]; then + COMPREPLY+=( $( compgen -W "all $options" -- "$cur" ) ) + fi +} + +# Completion for the rest of devlink sb $command +_devlink_sb_command_options() +{ + local subcmd + + case $command in + pool) + subcmd=${words[3]} + if [[ $cword -eq 5 ]]; then + COMPREPLY=( $( compgen -W "pool" -- "$cur" ) ) + fi + if [[ $subcmd == "set" ]]; then + case $cword in + 7) + COMPREPLY+=( $( compgen -W "size" -- "$cur" ) ) + ;; + 9) + COMPREPLY+=( $( compgen -W "thtype" -- "$cur" ) ) + ;; + esac + fi + ;; + port) + subcmd=${words[4]} + if [[ $cword -eq 6 ]]; then + COMPREPLY+=( $( compgen -W "pool" -- "$cur" ) ) + fi + if [[ $subcmd == "set" ]]; then + case $cword in + 8) + COMPREPLY+=( $( compgen -W "th" -- "$cur" ) ) + ;; + esac + fi + ;; + tc) + subcmd=${words[4]} + case $cword in + 6) + COMPREPLY+=( $( compgen -W "tc" -- "$cur" ) ) + ;; + 8) + COMPREPLY+=( $( compgen -W "type" -- "$cur" ) ) + ;; + esac + if [[ $subcmd == "set" ]]; then + case $cword in + 10) + COMPREPLY+=( $( compgen -W "pool" -- "$cur" ) ) + ;; + 12) + COMPREPLY+=( $( compgen -W "th" -- "$cur" ) ) + ;; + esac + fi + ;; + esac +} + +# Completion for devlink sb +_devlink_sb() +{ + case $prev in + bind) + COMPREPLY=( $( compgen -W "set show" -- "$cur" ) ) + ;; + occupancy) + COMPREPLY=( $( compgen -W "show snapshot clearmax" -- "$cur" ) ) + ;; + pool) + if [[ $cword -eq 3 || $cword -eq 4 ]]; then + COMPREPLY=( $( compgen -W "set show" -- "$cur" ) ) + elif [[ $command == "port" || $command == "tc" ]]; then + _devlink_direct_complete "port_pool" + else + _devlink_direct_complete "pool" + fi + ;; + port) + if [[ $cword -eq 3 ]]; then + COMPREPLY=( $( compgen -W "pool" -- "$cur" ) ) + fi + ;; + show|set|snapshot|clearmax) + case $command in + show|pool|occupancy) + _devlink_direct_complete "dev" + if [[ $command == "occupancy" && $prev == "show" ]];then + _devlink_direct_complete "port" + fi + ;; + port|tc) + _devlink_direct_complete "port" + ;; + esac + ;; + size) + # Integer argument + ;; + thtype) + COMPREPLY=( $( compgen -W "static dynamic" -- "$cur" ) ) + ;; + th) + # Integer argument + ;; + tc) + if [[ $cword -eq 3 ]]; then + COMPREPLY=( $( compgen -W "bind" -- "$cur" ) ) + else + _devlink_direct_complete "tc" + fi + ;; + type) + COMPREPLY=( $( compgen -W "ingress egress" -- "$cur" ) ) + ;; + esac + + _devlink_sb_command_options + return +} + +# Completion for devlink resource set path argument +_devlink_resource_path() +{ + local path parents parent all_path + local dev=${words[3]} + local -a path + + local all_path=$( + devlink resource show $dev \ + | sed -E '# Of resource lines, keep only the name itself. + s/name ([^ ]*) .*/\1/ + # Drop headers. + /:$/d + # First layer is not aligned enough, align it. + s/^/ / + # Use slashes as unary code for resource depth. + s, ,/,g + # Separate tally count from resource name. + s,/*,&\t,' \ + | while read d name; do + while ((${#path[@]} > ${#d})); do + unset path[$((${#path[@]} - 1))] + done + path[$((${#d} - 1))]=$name + echo ${path[@]} + done \ + | sed '# Convert paths to slash-separated + s,^,/,;s, ,/,g;s,$,/,' + ) + COMPREPLY=( ${COMPREPLY[@]:-} $( compgen -W "$all_path" -- "$cur" ) ) +} + +# Completion for devlink resource set +_devlink_resource_set() +{ + case "$cword" in + 3) + _devlink_direct_complete "dev" + return + ;; + 4) + COMPREPLY=( $( compgen -W "path" -- "$cur" ) ) + return + ;; + 5) + _devlink_resource_path + return + ;; + 6) + COMPREPLY=( $( compgen -W "size" -- "$cur" ) ) + return + ;; + 7) + # Integer argument + return + ;; + esac +} + +# Completion for devlink resource +_devlink_resource() +{ + case $command in + show) + if [[ $cword -eq 3 ]]; then + _devlink_direct_complete "dev" + fi + return + ;; + set) + _devlink_resource_set + return + ;; + esac +} + +# Completion for devlink region read +_devlink_region_read() +{ + case "$cword" in + 6) + COMPREPLY=( $( compgen -W "address" -- "$cur" ) ) + return + ;; + 7) + # Address argument, for example: 0x10 + return + ;; + 8) + COMPREPLY=( $( compgen -W "length" -- "$cur" ) ) + return + ;; + 9) + # Integer argument + return + ;; + esac +} + +# Completion for devlink region +_devlink_region() +{ + if [[ $cword -eq 3 && $command != "help" ]]; then + _devlink_direct_complete "region" + fi + + case $command in + show) + return + ;; + del|dump|read) + case "$cword" in + 4) + COMPREPLY=( $( compgen -W "snapshot" -- "$cur" ) ) + ;; + 5) + _devlink_direct_complete "snapshot" + ;; + esac + + if [[ $command == "read" ]]; then + _devlink_region_read + fi + return + ;; + esac +} + +# Completion reporter for devlink health +_devlink_health_reporter() +{ + local i=$1; shift + + case $cword in + $((3 + $i))) + _devlink_direct_complete "health_dev" + ;; + $((4 + $i))) + COMPREPLY=( $( compgen -W "reporter" -- "$cur" ) ) + ;; + $((5 + $i))) + _devlink_direct_complete "reporter" + ;; + esac +} + +# Completion for devlink health +_devlink_health() +{ + case $command in + show|recover|diagnose|set|test) + _devlink_health_reporter 0 + if [[ $command == "set" ]]; then + case $cword in + 6) + COMPREPLY=( $( compgen -W "grace_period auto_recover" \ + -- "$cur" ) ) + ;; + 7) + case $prev in + grace_period) + # Integer argument- msec + ;; + auto_recover) + COMPREPLY=( $( compgen -W "true false" -- \ + "$cur" ) ) + ;; + esac + esac + fi + return + ;; + dump) + if [[ $cword -eq 3 ]]; then + COMPREPLY=( $( compgen -W "show clear" -- "$cur" ) ) + fi + + _devlink_health_reporter 1 + return + ;; + esac +} + +# Completion for action in devlink trap set +_devlink_trap_set_action() +{ + local i=$1; shift + + case $cword in + $((6 + $i))) + COMPREPLY=( $( compgen -W "action" -- "$cur" ) ) + ;; + $((7 + $i))) + COMPREPLY=( $( compgen -W "trap drop mirror" -- "$cur" ) ) + ;; + esac +} + +# Completion for devlink trap group set +_devlink_trap_group_set() +{ + local -A settings=( + [action]=notseen + [policer]=notseen + [nopolicer]=notseen + ) + + if [[ $cword -eq 7 ]]; then + COMPREPLY=( $( compgen -W "action policer nopolicer" -- "$cur" ) ) + fi + + # Mark seen settings + local word + for word in "${words[@]:7:${#words[@]}-1}"; do + if [[ -n $word ]]; then + if [[ "${settings[$word]}" ]]; then + settings[$word]=seen + fi + fi + done + + case $prev in + action) + COMPREPLY=( $( compgen -W "trap drop mirror" -- "$cur" ) ) + return + ;; + policer) + _devlink_direct_complete "trap_policer" + return + ;; + esac + + local -a comp_words=() + + # Add settings not seen to completions + local setting + for setting in "${!settings[@]}"; do + if [ "${settings[$setting]}" = notseen ]; then + comp_words+=( "$setting" ) + fi + done + + COMPREPLY=( $( compgen -W "${comp_words[*]}" -- "$cur" ) ) +} + +# Completion for devlink trap group +_devlink_trap_group() +{ + case $cword in + 3) + COMPREPLY=( $( compgen -W "set show" -- "$cur" ) ) + return + ;; + 4) + _devlink_direct_complete "dev" + return + ;; + 5) + COMPREPLY=( $( compgen -W "group" -- "$cur" ) ) + return + ;; + 6) + _devlink_direct_complete "trap_group" + return + ;; + esac + + if [[ ${words[3]} == "set" ]]; then + _devlink_trap_group_set + fi +} + +# Completion for devlink trap policer set +_devlink_trap_policer_set() +{ + local -A settings=( + [rate]=notseen + [burst]=notseen + ) + + if [[ $cword -eq 7 ]]; then + COMPREPLY=( $( compgen -W "rate burst" -- "$cur" ) ) + fi + + # Mark seen settings + local word + for word in "${words[@]:7:${#words[@]}-1}"; do + if [[ -n $word ]]; then + if [[ "${settings[$word]}" ]]; then + settings[$word]=seen + fi + fi + done + + case $prev in + rate) + # Integer argument + return + ;; + burst) + # Integer argument + return + ;; + esac + + local -a comp_words=() + + # Add settings not seen to completions + local setting + for setting in "${!settings[@]}"; do + if [ "${settings[$setting]}" = notseen ]; then + comp_words+=( "$setting" ) + fi + done + + COMPREPLY=( $( compgen -W "${comp_words[*]}" -- "$cur" ) ) +} + +# Completion for devlink trap policer +_devlink_trap_policer() +{ + case $cword in + 3) + COMPREPLY=( $( compgen -W "set show" -- "$cur" ) ) + return + ;; + 4) + _devlink_direct_complete "dev" + return + ;; + 5) + COMPREPLY=( $( compgen -W "policer" -- "$cur" ) ) + return + ;; + 6) + _devlink_direct_complete "trap_policer" + return + ;; + esac + + if [[ ${words[3]} == "set" ]]; then + _devlink_trap_policer_set + fi +} + +# Completion for devlink trap +_devlink_trap() +{ + case $command in + show|set) + case $cword in + 3) + _devlink_direct_complete "dev" + ;; + 4) + COMPREPLY=( $( compgen -W "trap" -- "$cur" ) ) + ;; + 5) + _devlink_direct_complete "trap" + ;; + esac + + if [[ $command == "set" ]]; then + _devlink_trap_set_action 0 + fi + return + ;; + group) + _devlink_trap_$command + return + ;; + policer) + _devlink_trap_$command + return + ;; + esac +} + +# Complete any devlink command +_devlink() +{ + local cur prev words cword + local opt='--Version --no-nice-names --json --pretty --verbose \ + --statistics --force --Netns --batch' + local objects="$(devlink help 2>&1 | command sed -e '/OBJECT := /!d' \ + -e 's/.*{//' -e 's/}.*//' -e \ 's/|//g' )" + + _init_completion || return + # Gets the word-to-complete without considering the colon as word breaks + _get_comp_words_by_ref -n : cur prev words cword + + if [[ $cword -eq 1 ]]; then + case $cur in + -*) + COMPREPLY=( $( compgen -W "$opt" -- "$cur" ) ) + return 0 + ;; + *) + COMPREPLY=( $( compgen -W "$objects" -- "$cur" ) ) + return 0 + ;; + esac + fi + + # Deal with options + if [[ $prev == -* ]]; then + case $prev in + -V|--Version) + return 0 + ;; + -b|--batch) + _filedir + return 0 + ;; + --force) + COMPREPLY=( $( compgen -W "--batch" -- "$cur" ) ) + return 0 + ;; + -N|--Netns) + local nslist=$( ip netns list 2>/dev/null ) + COMPREPLY=( $( compgen -W "$nslist" -- "$cur" ) ) + return 0 + ;; + -j|--json) + COMPREPLY=( $( compgen -W "--pretty $objects" -- "$cur" ) ) + return 0 + ;; + *) + COMPREPLY=( $( compgen -W "$objects" -- "$cur" ) ) + return 0 + ;; + esac + fi + + # Remove all options so completions don't have to deal with them. + local i + for (( i=1; i < ${#words[@]}; )); do + if [[ ${words[i]::1} == - ]]; then + words=( "${words[@]:0:i}" "${words[@]:i+1}" ) + [[ $i -le $cword ]] && cword=$(( cword - 1 )) + else + i=$(( ++i )) + fi + done + + local object=${words[1]} + local command=${words[2]} + local pprev=${words[cword - 2]} + local prev=${words[cword - 1]} + + if [[ $objects =~ $object ]]; then + if [[ $cword -eq 2 ]]; then + COMPREPLY=( $( compgen -W "help" -- "$cur") ) + if [[ $object != "monitor" && $object != "dpipe" ]]; then + COMPREPLY+=( $( compgen -W \ + "$(_devlink_get_optional_commands $object)" -- "$cur" ) ) + fi + fi + "_devlink_$object" + fi + +} && +complete -F _devlink devlink + +# ex: ts=4 sw=4 et filetype=sh diff --git a/bash-completion/tc b/bash-completion/tc new file mode 100644 index 0000000..61f0039 --- /dev/null +++ b/bash-completion/tc @@ -0,0 +1,785 @@ +# 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 fq_pie red sfb sfq tbf \ + drr hfsc htb prio qfq ' +FILTER_KIND=' basic bpf cgroup flow flower fw route 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 ethercat ieeepup \ + ieeepupat ip ipv4 ipv6 ipx irda lat localtalk loop mobitex \ + ppp_disc ppp_mp ppp_ses ppptalk profinet 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' + _tc_one_of_list 'dq_rate_estimator no_dq_rate_estimator' + return 0 + ;; + fq_pie) + _tc_once_attr 'limit flows target tupdate \ + alpha beta quantum memory_limit ecn_prob' + _tc_one_of_list 'ecn noecn' + _tc_one_of_list 'bytemode nobytemode' + _tc_one_of_list 'dq_rate_estimator no_dq_rate_estimator' + return 0 + ;; + red) + _tc_once_attr 'limit min max avpkt burst adaptive probability \ + bandwidth ecn harddrop' + return 0 + ;; + 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 + ;; + hfsc) + _tc_once_attr 'default' + return 0 + ;; + htb) + _tc_once_attr 'default r2q direct_qlen debug' + return 0 + ;; + multiq|pfifo_fast|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 + ;; + 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/Makefile b/bridge/Makefile new file mode 100644 index 0000000..01f8a45 --- /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 vni.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..704e76b --- /dev/null +++ b/bridge/br_common.h @@ -0,0 +1,33 @@ +/* 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)))) + +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); +void print_stp_state(__u8 state); +int parse_stp_state(const char *arg); +int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor, + bool global_only); +int print_vnifilter_rtm(struct nlmsghdr *n, void *arg); +void br_print_router_port_stats(struct rtattr *pattr); +void print_headers(FILE *fp, const char *label); + +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); +int do_vni(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..f480509 --- /dev/null +++ b/bridge/bridge.c @@ -0,0 +1,195 @@ +/* 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 "version.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; +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 | vni | 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 }, + { "vni", do_vni }, + { "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 br_batch_cmd(int argc, char *argv[], void *data) +{ + return do_cmd(argv[0], argc, argv); +} + +static int batch(const char *name) +{ + int ret; + + if (rtnl_open(&rth, 0) < 0) { + fprintf(stderr, "Cannot open rtnetlink\n"); + return EXIT_FAILURE; + } + + rtnl_set_strict_dump(&rth); + + ret = do_batch(name, force, br_batch_cmd, NULL); + + rtnl_close(&rth); + return ret; +} + +int +main(int argc, char **argv) +{ + int color = CONF_COLOR; + + 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, %s\n", version); + 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_color(opt, &color)) { + } else if (matches(opt, "-compressvlans") == 0) { + ++compress_vlans; + } 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..7b44436 --- /dev/null +++ b/bridge/fdb.c @@ -0,0 +1,914 @@ +/* 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_dynamic, filter_master, + filter_state, filter_vlan; + +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 ] [ vlan VID ]\n" + " { [ dst IPADDR ] [ port PORT] [ vni VNI ] | [ nhid NHID ] }\n" + " [ via DEV ] [ src_vni VNI ]\n" + " bridge fdb [ show [ br BRDEV ] [ brport DEV ] [ vlan VID ]\n" + " [ state STATE ] [ dynamic ] ]\n" + " bridge fdb get [ to ] LLADDR [ br BRDEV ] { brport | dev } DEV\n" + " [ vlan VID ] [ vni VNI ] [ self ] [ master ] [ dynamic ]\n" + " bridge fdb flush dev DEV [ brport DEV ] [ vlan VID ] [ src_vni VNI ]\n" + " [ nhid NHID ] [ vni VNI ] [ port PORT ] [ dst IPADDR ] [ self ]\n" + " [ master ] [ [no]permanent | [no]static | [no]dynamic ]\n" + " [ [no]added_by_user ] [ [no]extern_learn ] [ [no]sticky ]\n" + " [ [no]offloaded ] [ [no]router ]\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 ""; + + if (is_json_context()) + sprintf(buf, "%#x", s); + else + 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, __u32 ext_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"); + + if (ext_flags & NTF_EXT_LOCKED) + print_string(PRINT_ANY, NULL, "%s ", "locked"); + + 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]; + __u32 ext_flags = 0; + __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_FLAGS_EXT]) + ext_flags = rta_getattr_u32(tb[NDA_FLAGS_EXT]); + + if (tb[NDA_VLAN]) + vid = rta_getattr_u16(tb[NDA_VLAN]); + + if (filter_vlan && filter_vlan != vid) + return 0; + + if (filter_dynamic && (r->ndm_state & NUD_PERMANENT)) + return 0; + + print_headers(fp, "[NEIGH]"); + + 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_string(PRINT_FP, NULL, "dev ", NULL); + + print_color_string(PRINT_ANY, COLOR_IFNAME, + "ifname", "%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_string(PRINT_FP, NULL, "dst ", NULL); + + print_color_string(PRINT_ANY, + ifa_family_color(family), + "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_NH_ID]) + print_uint(PRINT_ANY, "nhid", "nhid %u ", + rta_getattr_u32(tb[NDA_NH_ID])); + + 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, ext_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 (strcmp(*argv, "dynamic") == 0) { + filter_dynamic = 1; + } 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; + __u32 nhid = 0; + + 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, "nhid") == 0) { + NEXT_ARG(); + if (get_u32(&nhid, *argv, 0)) + invarg("\"id\" value is invalid\n", *argv); + } 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; + } + + if (nhid && (dst_ok || port || vni != ~0)) { + fprintf(stderr, "dst, port, vni are mutually exclusive with nhid\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 (nhid > 0) + addattr32(&req.n, sizeof(req), NDA_NH_ID, nhid); + + 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; +} + +static int fdb_get(int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct ndmsg ndm; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_GETNEIGH, + .ndm.ndm_family = AF_BRIDGE, + }; + char *d = NULL, *br = NULL; + struct nlmsghdr *answer; + unsigned long vni = ~0; + char abuf[ETH_ALEN]; + int br_ifindex = 0; + char *addr = NULL; + short vlan = -1; + char *endptr; + int ret; + + while (argc > 0) { + if ((strcmp(*argv, "brport") == 0) || strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "br") == 0) { + NEXT_ARG(); + br = *argv; + } else if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *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, "self") == 0) { + req.ndm.ndm_flags |= NTF_SELF; + } else if (matches(*argv, "master") == 0) { + req.ndm.ndm_flags |= NTF_MASTER; + } else if (matches(*argv, "vlan") == 0) { + if (vlan >= 0) + duparg2("vlan", *argv); + NEXT_ARG(); + vlan = atoi(*argv); + } else if (matches(*argv, "dynamic") == 0) { + filter_dynamic = 1; + } 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 && br == NULL) || addr == NULL) { + fprintf(stderr, "Device or master and address are required arguments.\n"); + return -1; + } + + 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 (vlan >= 0) + addattr16(&req.n, sizeof(req), NDA_VLAN, vlan); + + if (vni != ~0) + addattr32(&req.n, sizeof(req), NDA_VNI, vni); + + if (d) { + req.ndm.ndm_ifindex = ll_name_to_index(d); + if (!req.ndm.ndm_ifindex) { + fprintf(stderr, "Cannot find device \"%s\"\n", d); + return -1; + } + } + + if (br) { + br_ifindex = ll_name_to_index(br); + if (!br_ifindex) { + fprintf(stderr, "Cannot find bridge device \"%s\"\n", br); + return -1; + } + addattr32(&req.n, sizeof(req), NDA_MASTER, br_ifindex); + } + + if (rtnl_talk(&rth, &req.n, &answer) < 0) + return -2; + + /* + * Initialize a json_writer and open an array object + * if -json was specified. + */ + new_json_obj(json); + ret = 0; + if (print_fdb(answer, stdout) < 0) { + fprintf(stderr, "An error :-)\n"); + ret = -1; + } + delete_json_obj(); + free(answer); + + return ret; +} + +static int fdb_flush(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 | NLM_F_BULK, + .n.nlmsg_type = RTM_DELNEIGH, + .ndm.ndm_family = PF_BRIDGE, + }; + unsigned short ndm_state_mask = 0; + unsigned short ndm_flags_mask = 0; + short vid = -1, brport_ifidx = -1; + char *d = NULL, *brport = NULL; + unsigned short ndm_flags = 0; + unsigned short ndm_state = 0; + unsigned long src_vni = ~0; + unsigned long vni = ~0; + unsigned long port = 0; + inet_prefix dst; + int dst_ok = 0; + __u32 nhid = 0; + char *endptr; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "master") == 0) { + ndm_flags |= NTF_MASTER; + } else if (strcmp(*argv, "self") == 0) { + ndm_flags |= NTF_SELF; + } else if (strcmp(*argv, "permanent") == 0) { + ndm_state |= NUD_PERMANENT; + ndm_state_mask |= NUD_PERMANENT; + } else if (strcmp(*argv, "nopermanent") == 0) { + ndm_state &= ~NUD_PERMANENT; + ndm_state_mask |= NUD_PERMANENT; + } else if (strcmp(*argv, "static") == 0) { + ndm_state |= NUD_NOARP; + ndm_state_mask |= NUD_NOARP | NUD_PERMANENT; + } else if (strcmp(*argv, "nostatic") == 0) { + ndm_state &= ~NUD_NOARP; + ndm_state_mask |= NUD_NOARP; + } else if (strcmp(*argv, "dynamic") == 0) { + ndm_state &= ~NUD_NOARP | NUD_PERMANENT; + ndm_state_mask |= NUD_NOARP | NUD_PERMANENT; + } else if (strcmp(*argv, "nodynamic") == 0) { + ndm_state |= NUD_NOARP; + ndm_state_mask |= NUD_NOARP; + } else if (strcmp(*argv, "added_by_user") == 0) { + ndm_flags |= NTF_USE; + ndm_flags_mask |= NTF_USE; + } else if (strcmp(*argv, "noadded_by_user") == 0) { + ndm_flags &= ~NTF_USE; + ndm_flags_mask |= NTF_USE; + } else if (strcmp(*argv, "extern_learn") == 0) { + ndm_flags |= NTF_EXT_LEARNED; + ndm_flags_mask |= NTF_EXT_LEARNED; + } else if (strcmp(*argv, "noextern_learn") == 0) { + ndm_flags &= ~NTF_EXT_LEARNED; + ndm_flags_mask |= NTF_EXT_LEARNED; + } else if (strcmp(*argv, "sticky") == 0) { + ndm_flags |= NTF_STICKY; + ndm_flags_mask |= NTF_STICKY; + } else if (strcmp(*argv, "nosticky") == 0) { + ndm_flags &= ~NTF_STICKY; + ndm_flags_mask |= NTF_STICKY; + } else if (strcmp(*argv, "offloaded") == 0) { + ndm_flags |= NTF_OFFLOADED; + ndm_flags_mask |= NTF_OFFLOADED; + } else if (strcmp(*argv, "nooffloaded") == 0) { + ndm_flags &= ~NTF_OFFLOADED; + ndm_flags_mask |= NTF_OFFLOADED; + } else if (strcmp(*argv, "router") == 0) { + ndm_flags |= NTF_ROUTER; + ndm_flags_mask |= NTF_ROUTER; + } else if (strcmp(*argv, "norouter") == 0) { + ndm_flags &= ~NTF_ROUTER; + ndm_flags_mask |= NTF_ROUTER; + } else if (strcmp(*argv, "brport") == 0) { + if (brport) + duparg2("brport", *argv); + NEXT_ARG(); + brport = *argv; + } else if (strcmp(*argv, "vlan") == 0) { + if (vid >= 0) + duparg2("vlan", *argv); + NEXT_ARG(); + vid = atoi(*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, "nhid") == 0) { + NEXT_ARG(); + if (get_u32(&nhid, *argv, 0)) + invarg("\"nid\" value is invalid\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, "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, "dst") == 0) { + NEXT_ARG(); + if (dst_ok) + duparg2("dst", *argv); + get_addr(&dst, *argv, preferred_family); + dst_ok = 1; + } else if (strcmp(*argv, "help") == 0) { + NEXT_ARG(); + } else { + fprintf(stderr, "bridge fdb: unknown command \"%s\"?\n", + *argv); + usage(); + return -1; + } + argc--; argv++; + } + + if (d == NULL) { + fprintf(stderr, "Device is a required argument.\n"); + return -1; + } + + req.ndm.ndm_ifindex = ll_name_to_index(d); + if (req.ndm.ndm_ifindex == 0) { + fprintf(stderr, "Cannot find bridge device \"%s\"\n", d); + return -1; + } + + if (brport) { + brport_ifidx = ll_name_to_index(brport); + if (brport_ifidx == 0) { + fprintf(stderr, "Cannot find bridge port device \"%s\"\n", + brport); + return -1; + } + } + + if (vid >= 4096) { + fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid); + return -1; + } + + /* if self and master were not specified assume self */ + if (!(ndm_flags & (NTF_SELF | NTF_MASTER))) + ndm_flags |= NTF_SELF; + + req.ndm.ndm_flags = ndm_flags; + req.ndm.ndm_state = ndm_state; + if (brport_ifidx > -1) + addattr32(&req.n, sizeof(req), NDA_IFINDEX, brport_ifidx); + if (vid > -1) + addattr16(&req.n, sizeof(req), NDA_VLAN, vid); + if (src_vni != ~0) + addattr32(&req.n, sizeof(req), NDA_SRC_VNI, src_vni); + if (nhid > 0) + addattr32(&req.n, sizeof(req), NDA_NH_ID, nhid); + if (vni != ~0) + addattr32(&req.n, sizeof(req), NDA_VNI, vni); + if (port) { + unsigned short dport; + + dport = htons((unsigned short)port); + addattr16(&req.n, sizeof(req), NDA_PORT, dport); + } + if (dst_ok) + addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen); + if (ndm_flags_mask) + addattr8(&req.n, sizeof(req), NDA_NDM_FLAGS_MASK, + ndm_flags_mask); + if (ndm_state_mask) + addattr16(&req.n, sizeof(req), NDA_NDM_STATE_MASK, + ndm_state_mask); + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +int do_fdb(int argc, char **argv) +{ + ll_init_map(&rth); + timestamp = 0; + + 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, "get") == 0) + return fdb_get(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 (strcmp(*argv, "flush") == 0) + return fdb_flush(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..1c8faa8 --- /dev/null +++ b/bridge/link.c @@ -0,0 +1,696 @@ +/* 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_dev_index; +static unsigned int filter_master_index; + +static const char *stp_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, "> "); +} + +void print_stp_state(__u8 state) +{ + if (state <= BR_STATE_BLOCKING) + print_string(PRINT_ANY, "state", + "state %s ", stp_states[state]); + else + print_uint(PRINT_ANY, "state", + "state (%d) ", state); +} + +int parse_stp_state(const char *arg) +{ + size_t nstates = ARRAY_SIZE(stp_states); + int state; + + for (state = 0; state < nstates; state++) + if (strcmp(stp_states[state], arg) == 0) + break; + + if (state == nstates) + state = -1; + + return state; +} + +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_stp_state(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_on_off(PRINT_ANY, "hairpin", "hairpin %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_MODE])); + if (prtb[IFLA_BRPORT_GUARD]) + print_on_off(PRINT_ANY, "guard", "guard %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_GUARD])); + if (prtb[IFLA_BRPORT_PROTECT]) + print_on_off(PRINT_ANY, "root_block", "root_block %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_PROTECT])); + if (prtb[IFLA_BRPORT_FAST_LEAVE]) + print_on_off(PRINT_ANY, "fastleave", "fastleave %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_FAST_LEAVE])); + if (prtb[IFLA_BRPORT_LEARNING]) + print_on_off(PRINT_ANY, "learning", "learning %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_LEARNING])); + if (prtb[IFLA_BRPORT_LEARNING_SYNC]) + print_on_off(PRINT_ANY, "learning_sync", "learning_sync %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_LEARNING_SYNC])); + if (prtb[IFLA_BRPORT_UNICAST_FLOOD]) + print_on_off(PRINT_ANY, "flood", "flood %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_UNICAST_FLOOD])); + if (prtb[IFLA_BRPORT_MCAST_FLOOD]) + print_on_off(PRINT_ANY, "mcast_flood", "mcast_flood %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_MCAST_FLOOD])); + if (prtb[IFLA_BRPORT_BCAST_FLOOD]) + print_on_off(PRINT_ANY, "bcast_flood", "bcast_flood %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_BCAST_FLOOD])); + if (prtb[IFLA_BRPORT_MULTICAST_ROUTER]) + print_uint(PRINT_ANY, "mcast_router", "mcast_router %u ", + rta_getattr_u8(prtb[IFLA_BRPORT_MULTICAST_ROUTER])); + if (prtb[IFLA_BRPORT_MCAST_TO_UCAST]) + print_on_off(PRINT_ANY, "mcast_to_unicast", "mcast_to_unicast %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_MCAST_TO_UCAST])); + if (prtb[IFLA_BRPORT_NEIGH_SUPPRESS]) + print_on_off(PRINT_ANY, "neigh_suppress", "neigh_suppress %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_NEIGH_SUPPRESS])); + if (prtb[IFLA_BRPORT_NEIGH_VLAN_SUPPRESS]) { + struct rtattr *at; + + at = prtb[IFLA_BRPORT_NEIGH_VLAN_SUPPRESS]; + print_on_off(PRINT_ANY, "neigh_vlan_suppress", + "neigh_vlan_suppress %s ", + rta_getattr_u8(at)); + } + if (prtb[IFLA_BRPORT_VLAN_TUNNEL]) + print_on_off(PRINT_ANY, "vlan_tunnel", "vlan_tunnel %s ", + 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_BACKUP_NHID]) + print_uint(PRINT_ANY, "backup_nhid", "backup_nhid %u ", + rta_getattr_u32(prtb[IFLA_BRPORT_BACKUP_NHID])); + + if (prtb[IFLA_BRPORT_ISOLATED]) + print_on_off(PRINT_ANY, "isolated", "isolated %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_ISOLATED])); + if (prtb[IFLA_BRPORT_LOCKED]) + print_on_off(PRINT_ANY, "locked", "locked %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_LOCKED])); + if (prtb[IFLA_BRPORT_MAB]) + print_on_off(PRINT_ANY, "mab", "mab %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_MAB])); + if (prtb[IFLA_BRPORT_MCAST_N_GROUPS]) { + struct rtattr *at = prtb[IFLA_BRPORT_MCAST_N_GROUPS]; + + print_uint(PRINT_ANY, "mcast_n_groups", + "mcast_n_groups %u ", rta_getattr_u32(at)); + } + if (prtb[IFLA_BRPORT_MCAST_MAX_GROUPS]) { + struct rtattr *at = prtb[IFLA_BRPORT_MCAST_MAX_GROUPS]; + + print_uint(PRINT_ANY, "mcast_max_groups", + "mcast_max_groups %u ", rta_getattr_u32(at)); + } + } else + print_stp_state(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])); +} + +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_dev_index && filter_dev_index != ifi->ifi_index) + return 0; + + parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(ifi), len, NLA_F_NESTED); + + if (filter_master_index && tb[IFLA_MASTER] && + filter_master_index != rta_getattr_u32(tb[IFLA_MASTER])) + return 0; + + name = get_ifname_rta(ifi->ifi_index, tb[IFLA_IFNAME]); + if (!name) + return -1; + + print_headers(fp, "[LINK]"); + + 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_router MULTICAST_ROUTER ]\n" + " [ mcast_flood {on | off} ]\n" + " [ bcast_flood {on | off} ]\n" + " [ mcast_to_unicast {on | off} ]\n" + " [ mcast_max_groups MAX_GROUPS ]\n" + " [ neigh_suppress {on | off} ]\n" + " [ neigh_vlan_suppress {on | off} ]\n" + " [ vlan_tunnel {on | off} ]\n" + " [ isolated {on | off} ]\n" + " [ locked {on | off} ]\n" + " [ mab {on | off} ]\n" + " [ hwmode {vepa | veb} ]\n" + " [ backup_port DEVICE ] [ nobackup_port ]\n" + " [ backup_nhid NHID ]\n" + " [ self ] [ master ]\n" + " bridge link show [dev DEV] [master DEVICE]\n"); + exit(-1); +} + +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; + bool backup_nhid_set = false; + __u32 backup_nhid; + int backup_port_idx = -1; + __s8 neigh_suppress = -1; + __s8 neigh_vlan_suppress = -1; + __s8 learning = -1; + __s8 learning_sync = -1; + __s8 flood = -1; + __s8 vlan_tunnel = -1; + __s8 mcast_router = -1; + __s8 mcast_flood = -1; + __s8 bcast_flood = -1; + __s8 mcast_to_unicast = -1; + __s32 max_groups = -1; + __s8 locked = -1; + __s8 macauth = -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; + int ret; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "guard") == 0) { + NEXT_ARG(); + bpdu_guard = parse_on_off("guard", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "hairpin") == 0) { + NEXT_ARG(); + hairpin = parse_on_off("hairpin", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "fastleave") == 0) { + NEXT_ARG(); + fast_leave = parse_on_off("fastleave", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "root_block") == 0) { + NEXT_ARG(); + root_block = parse_on_off("root_block", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "learning") == 0) { + NEXT_ARG(); + learning = parse_on_off("learning", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "learning_sync") == 0) { + NEXT_ARG(); + learning_sync = parse_on_off("learning_sync", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "flood") == 0) { + NEXT_ARG(); + flood = parse_on_off("flood", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "mcast_router") == 0) { + NEXT_ARG(); + mcast_router = atoi(*argv); + } else if (strcmp(*argv, "mcast_flood") == 0) { + NEXT_ARG(); + mcast_flood = parse_on_off("mcast_flood", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "bcast_flood") == 0) { + NEXT_ARG(); + bcast_flood = parse_on_off("bcast_flood", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "mcast_to_unicast") == 0) { + NEXT_ARG(); + mcast_to_unicast = parse_on_off("mcast_to_unicast", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "mcast_max_groups") == 0) { + NEXT_ARG(); + if (get_s32(&max_groups, *argv, 0)) + invarg("invalid mcast_max_groups", *argv); + } 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; + + state = strtol(*argv, &endptr, 10); + if (!(**argv != '\0' && *endptr == '\0')) { + state = parse_stp_state(*argv); + if (state == -1) { + 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(); + neigh_suppress = parse_on_off("neigh_suppress", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "neigh_vlan_suppress") == 0) { + NEXT_ARG(); + neigh_vlan_suppress = parse_on_off("neigh_vlan_suppress", + *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "vlan_tunnel") == 0) { + NEXT_ARG(); + vlan_tunnel = parse_on_off("vlan_tunnel", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "isolated") == 0) { + NEXT_ARG(); + isolated = parse_on_off("isolated", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "locked") == 0) { + NEXT_ARG(); + locked = parse_on_off("locked", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "mab") == 0) { + NEXT_ARG(); + macauth = parse_on_off("mab", *argv, &ret); + if (ret) + return ret; + } 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 if (strcmp(*argv, "backup_nhid") == 0) { + NEXT_ARG(); + if (get_u32(&backup_nhid, *argv, 0)) + invarg("invalid backup_nhid", *argv); + backup_nhid_set = true; + } 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_router >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_MULTICAST_ROUTER, + mcast_router); + if (mcast_flood >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_MCAST_FLOOD, + mcast_flood); + if (bcast_flood >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_BCAST_FLOOD, + bcast_flood); + if (mcast_to_unicast >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_MCAST_TO_UCAST, + mcast_to_unicast); + if (max_groups >= 0) + addattr32(&req.n, sizeof(req), IFLA_BRPORT_MCAST_MAX_GROUPS, + max_groups); + 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 (neigh_vlan_suppress != -1) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_NEIGH_VLAN_SUPPRESS, + neigh_vlan_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 (locked >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_LOCKED, locked); + + if (macauth >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_MAB, macauth); + + if (backup_port_idx != -1) + addattr32(&req.n, sizeof(req), IFLA_BRPORT_BACKUP_PORT, + backup_port_idx); + + if (backup_nhid_set) + addattr32(&req.n, sizeof(req), IFLA_BRPORT_BACKUP_NHID, + backup_nhid); + + 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; + char *filter_master = NULL; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + if (filter_dev) + duparg("dev", *argv); + filter_dev = *argv; + } + if (strcmp(*argv, "master") == 0) { + NEXT_ARG(); + if (filter_master) + duparg("master", *argv); + filter_master = *argv; + } + argc--; argv++; + } + + if (filter_dev) { + filter_dev_index = ll_name_to_index(filter_dev); + if (!filter_dev_index) + return nodev(filter_dev); + } + if (filter_master) { + filter_master_index = ll_name_to_index(filter_master); + if (!filter_master_index) + return nodev(filter_master); + } + + if (rtnl_linkdump_req(&rth, PF_BRIDGE) < 0) { + perror("Cannot 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); + timestamp = 0; + + 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..196363a --- /dev/null +++ b/bridge/mdb.c @@ -0,0 +1,1107 @@ +/* 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 <netdb.h> +#include <limits.h> + +#include "libnetlink.h" +#include "utils.h" +#include "br_common.h" +#include "rt_names.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 | replace } dev DEV port PORT grp GROUP [src SOURCE] [permanent | temp] [vid VID]\n" + " [ filter_mode { include | exclude } ] [ source_list SOURCE_LIST ] [ proto PROTO ] [ dst IPADDR ]\n" + " [ dst_port DST_PORT ] [ vni VNI ] [ src_vni SRC_VNI ] [ via DEV ]\n" + " bridge mdb {show} [ dev DEV ] [ vid VID ]\n" + " bridge mdb get dev DEV grp GROUP [ src SOURCE ] [ vid VID ] [ src_vni SRC_VNI ]\n" + " bridge mdb flush dev DEV [ port PORT ] [ vid VID ] [ src_vni SRC_VNI ] [ proto PROTO ]\n" + " [ [no]permanent ] [ dst IPADDR ] [ dst_port DST_PORT ] [ vni VNI ]\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, int align) +{ + struct timeval tv; + static char tbuf[32]; + + __jiffies_to_tv(&tv, ticks); + if (align) + snprintf(tbuf, sizeof(tbuf), "%4lu.%.2lu", + (unsigned long)tv.tv_sec, + (unsigned long)tv.tv_usec / 10000); + else + snprintf(tbuf, sizeof(tbuf), "%lu.%.2lu", + (unsigned long)tv.tv_sec, + (unsigned long)tv.tv_usec / 10000); + + return tbuf; +} + +void br_print_router_port_stats(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, 1)); + } + + 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) + br_print_router_port_stats(i); + close_json_object(); + } else if (show_stats) { + fprintf(f, "router ports on %s: %s", + brifname, port_ifname); + + br_print_router_port_stats(i); + fprintf(f, "\n"); + } else { + fprintf(f, "%s ", port_ifname); + } + } + + if (!show_stats) + print_nl(); + + close_json_array(PRINT_JSON, NULL); +} + +static void print_src_entry(struct rtattr *src_attr, int af, const char *sep) +{ + struct rtattr *stb[MDBA_MDB_SRCATTR_MAX + 1]; + SPRINT_BUF(abuf); + const char *addr; + __u32 timer_val; + + parse_rtattr_nested(stb, MDBA_MDB_SRCATTR_MAX, src_attr); + if (!stb[MDBA_MDB_SRCATTR_ADDRESS] || !stb[MDBA_MDB_SRCATTR_TIMER]) + return; + + addr = inet_ntop(af, RTA_DATA(stb[MDBA_MDB_SRCATTR_ADDRESS]), abuf, + sizeof(abuf)); + if (!addr) + return; + timer_val = rta_getattr_u32(stb[MDBA_MDB_SRCATTR_TIMER]); + + open_json_object(NULL); + print_string(PRINT_FP, NULL, "%s", sep); + print_color_string(PRINT_ANY, ifa_family_color(af), + "address", "%s", addr); + print_string(PRINT_ANY, "timer", "/%s", format_timer(timer_val, 0)); + close_json_object(); +} + +static void print_dst(const struct rtattr *dst_attr) +{ + SPRINT_BUF(abuf); + int af = AF_INET; + const void *dst; + + if (RTA_PAYLOAD(dst_attr) == sizeof(struct in6_addr)) + af = AF_INET6; + + dst = (const void *)RTA_DATA(dst_attr); + print_color_string(PRINT_ANY, ifa_family_color(af), + "dst", " dst %s", + inet_ntop(af, dst, abuf, sizeof(abuf))); +} + +static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, + struct nlmsghdr *n, struct rtattr **tb) +{ + const void *grp, *src; + const char *addr; + SPRINT_BUF(abuf); + const char *dev; + int af; + + if (filter_vlan && e->vid != filter_vlan) + return; + + if (!e->addr.proto) { + af = AF_PACKET; + grp = &e->addr.u.mac_addr; + } else if (e->addr.proto == htons(ETH_P_IP)) { + af = AF_INET; + grp = &e->addr.u.ip4; + } else { + af = AF_INET6; + grp = &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)); + + /* The ETH_ALEN argument is ignored for all cases but AF_PACKET */ + addr = rt_addr_n2a_r(af, ETH_ALEN, grp, abuf, sizeof(abuf)); + if (!addr) + return; + + print_color_string(PRINT_ANY, ifa_family_color(af), + "grp", " grp %s", addr); + + if (tb && tb[MDBA_MDB_EATTR_SOURCE]) { + src = (const void *)RTA_DATA(tb[MDBA_MDB_EATTR_SOURCE]); + print_color_string(PRINT_ANY, ifa_family_color(af), + "src", " src %s", + inet_ntop(af, src, abuf, sizeof(abuf))); + } + print_string(PRINT_ANY, "state", " %s", + (e->state & MDB_PERMANENT) ? "permanent" : "temp"); + if (show_details && tb) { + if (tb[MDBA_MDB_EATTR_GROUP_MODE]) { + __u8 mode = rta_getattr_u8(tb[MDBA_MDB_EATTR_GROUP_MODE]); + + print_string(PRINT_ANY, "filter_mode", " filter_mode %s", + mode == MCAST_INCLUDE ? "include" : + "exclude"); + } + if (tb[MDBA_MDB_EATTR_SRC_LIST]) { + struct rtattr *i, *attr = tb[MDBA_MDB_EATTR_SRC_LIST]; + const char *sep = " "; + int rem; + + open_json_array(PRINT_ANY, is_json_context() ? + "source_list" : + " source_list"); + rem = RTA_PAYLOAD(attr); + for (i = RTA_DATA(attr); RTA_OK(i, rem); + i = RTA_NEXT(i, rem)) { + print_src_entry(i, af, sep); + sep = ","; + } + close_json_array(PRINT_JSON, NULL); + } + if (tb[MDBA_MDB_EATTR_RTPROT]) { + __u8 rtprot = rta_getattr_u8(tb[MDBA_MDB_EATTR_RTPROT]); + SPRINT_BUF(rtb); + + print_string(PRINT_ANY, "protocol", " proto %s", + rtnl_rtprot_n2a(rtprot, rtb, sizeof(rtb))); + } + } + + open_json_array(PRINT_JSON, "flags"); + if (e->flags & MDB_FLAGS_OFFLOAD) + print_string(PRINT_ANY, NULL, " %s", "offload"); + if (e->flags & MDB_FLAGS_FAST_LEAVE) + print_string(PRINT_ANY, NULL, " %s", "fast_leave"); + if (e->flags & MDB_FLAGS_STAR_EXCL) + print_string(PRINT_ANY, NULL, " %s", "added_by_star_ex"); + if (e->flags & MDB_FLAGS_BLOCKED) + print_string(PRINT_ANY, NULL, " %s", "blocked"); + close_json_array(PRINT_JSON, NULL); + + if (e->vid) + print_uint(PRINT_ANY, "vid", " vid %u", e->vid); + + if (tb[MDBA_MDB_EATTR_DST]) + print_dst(tb[MDBA_MDB_EATTR_DST]); + + if (tb[MDBA_MDB_EATTR_DST_PORT]) + print_uint(PRINT_ANY, "dst_port", " dst_port %u", + rta_getattr_u16(tb[MDBA_MDB_EATTR_DST_PORT])); + + if (tb[MDBA_MDB_EATTR_VNI]) + print_uint(PRINT_ANY, "vni", " vni %u", + rta_getattr_u32(tb[MDBA_MDB_EATTR_VNI])); + + if (tb[MDBA_MDB_EATTR_SRC_VNI]) + print_uint(PRINT_ANY, "src_vni", " src_vni %u", + rta_getattr_u32(tb[MDBA_MDB_EATTR_SRC_VNI])); + + if (tb[MDBA_MDB_EATTR_IFINDEX]) { + unsigned int ifindex; + + ifindex = rta_getattr_u32(tb[MDBA_MDB_EATTR_IFINDEX]); + print_string(PRINT_ANY, "via", " via %s", + ll_index_to_name(ifindex)); + } + + 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, 1)); + } + + 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_flags(etb, MDBA_MDB_EATTR_MAX, MDB_RTA(RTA_DATA(i)), + RTA_PAYLOAD(i) - RTA_ALIGN(sizeof(*e)), + NLA_F_NESTED); + 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; + + print_headers(fp, "[MDB]"); + + 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"); + delete_json_obj(); + return -1; + } + + open_json_array(PRINT_JSON, "mdb"); + if (rtnl_dump_filter(&rth, print_mdbs, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + delete_json_obj(); + return -1; + } + close_json_array(PRINT_JSON, NULL); + + /* get router ports */ + if (rtnl_mdbdump_req(&rth, PF_BRIDGE) < 0) { + perror("Cannot send dump request"); + delete_json_obj(); + return -1; + } + + open_json_object("router"); + if (rtnl_dump_filter(&rth, print_rtrs, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + delete_json_obj(); + return -1; + } + close_json_object(); + + close_json_object(); + delete_json_obj(); + fflush(stdout); + + return 0; +} + +static int mdb_parse_grp(const char *grp, struct br_mdb_entry *e) +{ + if (inet_pton(AF_INET, grp, &e->addr.u.ip4)) { + e->addr.proto = htons(ETH_P_IP); + return 0; + } + if (inet_pton(AF_INET6, grp, &e->addr.u.ip6)) { + e->addr.proto = htons(ETH_P_IPV6); + return 0; + } + if (ll_addr_a2n((char *)e->addr.u.mac_addr, sizeof(e->addr.u.mac_addr), + grp) == ETH_ALEN) { + e->addr.proto = 0; + return 0; + } + + return -1; +} + +static int mdb_parse_src(struct nlmsghdr *n, int maxlen, const char *src) +{ + struct in6_addr src_ip6; + __be32 src_ip4; + + if (inet_pton(AF_INET, src, &src_ip4)) { + addattr32(n, maxlen, MDBE_ATTR_SOURCE, src_ip4); + return 0; + } + + if (inet_pton(AF_INET6, src, &src_ip6)) { + addattr_l(n, maxlen, MDBE_ATTR_SOURCE, &src_ip6, + sizeof(src_ip6)); + return 0; + } + + return -1; +} + +static int mdb_parse_mode(struct nlmsghdr *n, int maxlen, const char *mode) +{ + if (strcmp(mode, "include") == 0) { + addattr8(n, maxlen, MDBE_ATTR_GROUP_MODE, MCAST_INCLUDE); + return 0; + } + + if (strcmp(mode, "exclude") == 0) { + addattr8(n, maxlen, MDBE_ATTR_GROUP_MODE, MCAST_EXCLUDE); + return 0; + } + + return -1; +} + +static int mdb_parse_src_entry(struct nlmsghdr *n, int maxlen, char *src_entry) +{ + struct in6_addr src_ip6; + struct rtattr *nest; + __be32 src_ip4; + + nest = addattr_nest(n, maxlen, MDBE_SRC_LIST_ENTRY | NLA_F_NESTED); + + if (inet_pton(AF_INET, src_entry, &src_ip4)) + addattr32(n, maxlen, MDBE_SRCATTR_ADDRESS, src_ip4); + else if (inet_pton(AF_INET6, src_entry, &src_ip6)) + addattr_l(n, maxlen, MDBE_SRCATTR_ADDRESS, &src_ip6, + sizeof(src_ip6)); + else + return -1; + + addattr_nest_end(n, nest); + + return 0; +} + +static int mdb_parse_src_list(struct nlmsghdr *n, int maxlen, char *src_list) +{ + struct rtattr *nest; + char *sep; + + nest = addattr_nest(n, maxlen, MDBE_ATTR_SRC_LIST | NLA_F_NESTED); + + do { + sep = strchr(src_list, ','); + if (sep) + *sep = '\0'; + + if (mdb_parse_src_entry(n, maxlen, src_list)) { + fprintf(stderr, "Invalid source entry \"%s\" in source list\n", + src_list); + return -1; + } + + src_list = sep + 1; + } while (sep); + + addattr_nest_end(n, nest); + + return 0; +} + +static int mdb_parse_proto(struct nlmsghdr *n, int maxlen, const char *proto) +{ + __u32 proto_id; + int err; + + err = rtnl_rtprot_a2n(&proto_id, proto); + if (err) + return err; + + addattr8(n, maxlen, MDBE_ATTR_RTPROT, proto_id); + + return 0; +} + +static int mdb_parse_dst(struct nlmsghdr *n, int maxlen, const char *dst) +{ + struct in6_addr dst_ip6; + __be32 dst_ip4; + + if (inet_pton(AF_INET, dst, &dst_ip4)) { + addattr32(n, maxlen, MDBE_ATTR_DST, dst_ip4); + return 0; + } + + if (inet_pton(AF_INET6, dst, &dst_ip6)) { + addattr_l(n, maxlen, MDBE_ATTR_DST, &dst_ip6, + sizeof(dst_ip6)); + return 0; + } + + return -1; +} + +static int mdb_parse_dst_port(struct nlmsghdr *n, int maxlen, + const char *dst_port) +{ + unsigned long port; + char *endptr; + + port = strtoul(dst_port, &endptr, 0); + if (endptr && *endptr) { + struct servent *pse; + + pse = getservbyname(dst_port, "udp"); + if (!pse) + return -1; + port = ntohs(pse->s_port); + } else if (port > USHRT_MAX) { + return -1; + } + + addattr16(n, maxlen, MDBE_ATTR_DST_PORT, port); + + return 0; +} + +static int mdb_parse_vni(struct nlmsghdr *n, int maxlen, const char *vni, + int attr_type) +{ + unsigned long vni_num; + char *endptr; + + vni_num = strtoul(vni, &endptr, 0); + if ((endptr && *endptr) || vni_num == ULONG_MAX) + return -1; + + addattr32(n, maxlen, attr_type, vni_num); + + return 0; +} + +static int mdb_parse_dev(struct nlmsghdr *n, int maxlen, const char *dev) +{ + unsigned int ifindex; + + ifindex = ll_name_to_index(dev); + if (!ifindex) + return -1; + + addattr32(n, maxlen, MDBE_ATTR_IFINDEX, ifindex); + + 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, + }; + char *d = NULL, *p = NULL, *grp = NULL, *src = NULL, *mode = NULL; + char *dst_port = NULL, *vni = NULL, *src_vni = NULL, *via = NULL; + char *src_list = NULL, *proto = NULL, *dst = NULL; + struct br_mdb_entry entry = {}; + bool set_attrs = false; + 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 (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + src = *argv; + set_attrs = true; + } else if (strcmp(*argv, "filter_mode") == 0) { + NEXT_ARG(); + mode = *argv; + set_attrs = true; + } else if (strcmp(*argv, "source_list") == 0) { + NEXT_ARG(); + src_list = *argv; + set_attrs = true; + } else if (strcmp(*argv, "proto") == 0) { + NEXT_ARG(); + proto = *argv; + set_attrs = true; + } else if (strcmp(*argv, "dst") == 0) { + NEXT_ARG(); + dst = *argv; + set_attrs = true; + } else if (strcmp(*argv, "dst_port") == 0) { + NEXT_ARG(); + dst_port = *argv; + set_attrs = true; + } else if (strcmp(*argv, "vni") == 0) { + NEXT_ARG(); + vni = *argv; + set_attrs = true; + } else if (strcmp(*argv, "src_vni") == 0) { + NEXT_ARG(); + src_vni = *argv; + set_attrs = true; + } else if (strcmp(*argv, "via") == 0) { + NEXT_ARG(); + via = *argv; + set_attrs = true; + } 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 (mdb_parse_grp(grp, &entry)) { + fprintf(stderr, "Invalid address \"%s\"\n", grp); + return -1; + } + + entry.vid = vid; + addattr_l(&req.n, sizeof(req), MDBA_SET_ENTRY, &entry, sizeof(entry)); + if (set_attrs) { + struct rtattr *nest = addattr_nest(&req.n, sizeof(req), + MDBA_SET_ENTRY_ATTRS); + + nest->rta_type |= NLA_F_NESTED; + + if (src && mdb_parse_src(&req.n, sizeof(req), src)) { + fprintf(stderr, "Invalid source address \"%s\"\n", src); + return -1; + } + + if (mode && mdb_parse_mode(&req.n, sizeof(req), mode)) { + fprintf(stderr, "Invalid filter mode \"%s\"\n", mode); + return -1; + } + + if (src_list && mdb_parse_src_list(&req.n, sizeof(req), + src_list)) + return -1; + + if (proto && mdb_parse_proto(&req.n, sizeof(req), proto)) { + fprintf(stderr, "Invalid protocol value \"%s\"\n", + proto); + return -1; + } + + if (dst && mdb_parse_dst(&req.n, sizeof(req), dst)) { + fprintf(stderr, "Invalid underlay destination address \"%s\"\n", + dst); + return -1; + } + + if (dst_port && mdb_parse_dst_port(&req.n, sizeof(req), + dst_port)) { + fprintf(stderr, "Invalid destination port \"%s\"\n", dst_port); + return -1; + } + + if (vni && mdb_parse_vni(&req.n, sizeof(req), vni, + MDBE_ATTR_VNI)) { + fprintf(stderr, "Invalid destination VNI \"%s\"\n", + vni); + return -1; + } + + if (src_vni && mdb_parse_vni(&req.n, sizeof(req), src_vni, + MDBE_ATTR_SRC_VNI)) { + fprintf(stderr, "Invalid source VNI \"%s\"\n", src_vni); + return -1; + } + + if (via && mdb_parse_dev(&req.n, sizeof(req), via)) + return nodev(via); + + addattr_nest_end(&req.n, nest); + } + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +static int mdb_get(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, + .n.nlmsg_type = RTM_GETMDB, + .bpm.family = PF_BRIDGE, + }; + char *d = NULL, *grp = NULL, *src = NULL, *src_vni = NULL; + struct br_mdb_entry entry = {}; + struct nlmsghdr *answer; + bool get_attrs = false; + short vid = 0; + int ret = 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, "vid") == 0) { + NEXT_ARG(); + vid = atoi(*argv); + } else if (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + src = *argv; + get_attrs = true; + } else if (strcmp(*argv, "src_vni") == 0) { + NEXT_ARG(); + src_vni = *argv; + get_attrs = true; + } else { + if (strcmp(*argv, "help") == 0) + usage(); + } + argc--; argv++; + } + + if (d == NULL || grp == NULL) { + fprintf(stderr, "Device and group address are required arguments.\n"); + return -1; + } + + req.bpm.ifindex = ll_name_to_index(d); + if (!req.bpm.ifindex) + return nodev(d); + + if (mdb_parse_grp(grp, &entry)) { + fprintf(stderr, "Invalid address \"%s\"\n", grp); + return -1; + } + + entry.vid = vid; + addattr_l(&req.n, sizeof(req), MDBA_GET_ENTRY, &entry, sizeof(entry)); + if (get_attrs) { + struct rtattr *nest = addattr_nest(&req.n, sizeof(req), + MDBA_GET_ENTRY_ATTRS); + + nest->rta_type |= NLA_F_NESTED; + + if (src && mdb_parse_src(&req.n, sizeof(req), src)) { + fprintf(stderr, "Invalid source address \"%s\"\n", src); + return -1; + } + + if (src_vni && mdb_parse_vni(&req.n, sizeof(req), src_vni, + MDBE_ATTR_SRC_VNI)) { + fprintf(stderr, "Invalid source VNI \"%s\"\n", src_vni); + return -1; + } + + addattr_nest_end(&req.n, nest); + } + + if (rtnl_talk(&rth, &req.n, &answer) < 0) + return -2; + + new_json_obj(json); + + if (print_mdbs(answer, stdout) < 0) + ret = -1; + + delete_json_obj(); + free(answer); + + return ret; +} + +static int mdb_flush(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 | NLM_F_BULK, + .n.nlmsg_type = RTM_DELMDB, + .bpm.family = PF_BRIDGE, + }; + char *d = NULL, *p = NULL, *src_vni = NULL, *proto = NULL, *dst = NULL; + char *dst_port = NULL, *vni = NULL; + struct br_mdb_entry entry = {}; + unsigned short state_mask = 0; + bool set_attrs = false; + short vid = 0; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "port") == 0) { + NEXT_ARG(); + p = *argv; + } else if (strcmp(*argv, "vid") == 0) { + NEXT_ARG(); + vid = atoi(*argv); + } else if (strcmp(*argv, "src_vni") == 0) { + NEXT_ARG(); + src_vni = *argv; + set_attrs = true; + } else if (strcmp(*argv, "proto") == 0) { + NEXT_ARG(); + proto = *argv; + set_attrs = true; + } else if (strcmp(*argv, "permanent") == 0) { + entry.state |= MDB_PERMANENT; + state_mask |= MDB_PERMANENT; + set_attrs = true; + } else if (strcmp(*argv, "nopermanent") == 0) { + entry.state &= ~MDB_PERMANENT; + state_mask |= MDB_PERMANENT; + set_attrs = true; + } else if (strcmp(*argv, "dst") == 0) { + NEXT_ARG(); + dst = *argv; + set_attrs = true; + } else if (strcmp(*argv, "dst_port") == 0) { + NEXT_ARG(); + dst_port = *argv; + set_attrs = true; + } else if (strcmp(*argv, "vni") == 0) { + NEXT_ARG(); + vni = *argv; + set_attrs = true; + } else { + if (strcmp(*argv, "help") == 0) + usage(); + } + argc--; argv++; + } + + if (d == NULL) { + fprintf(stderr, "Device is a required argument.\n"); + return -1; + } + + req.bpm.ifindex = ll_name_to_index(d); + if (!req.bpm.ifindex) + return nodev(d); + + if (p) { + entry.ifindex = ll_name_to_index(p); + if (!entry.ifindex) + return nodev(p); + } + + entry.vid = vid; + addattr_l(&req.n, sizeof(req), MDBA_SET_ENTRY, &entry, sizeof(entry)); + if (set_attrs) { + struct rtattr *nest = addattr_nest(&req.n, sizeof(req), + MDBA_SET_ENTRY_ATTRS); + + nest->rta_type |= NLA_F_NESTED; + + if (proto && mdb_parse_proto(&req.n, sizeof(req), proto)) { + fprintf(stderr, "Invalid protocol value \"%s\"\n", + proto); + return -1; + } + + if (dst && mdb_parse_dst(&req.n, sizeof(req), dst)) { + fprintf(stderr, "Invalid underlay destination address \"%s\"\n", + dst); + return -1; + } + + if (dst_port && mdb_parse_dst_port(&req.n, sizeof(req), + dst_port)) { + fprintf(stderr, "Invalid destination port \"%s\"\n", dst_port); + return -1; + } + + if (vni && mdb_parse_vni(&req.n, sizeof(req), vni, + MDBE_ATTR_VNI)) { + fprintf(stderr, "Invalid destination VNI \"%s\"\n", + vni); + return -1; + } + + if (src_vni && mdb_parse_vni(&req.n, sizeof(req), src_vni, + MDBE_ATTR_SRC_VNI)) { + fprintf(stderr, "Invalid source VNI \"%s\"\n", src_vni); + return -1; + } + + if (state_mask) + addattr8(&req.n, sizeof(req), MDBE_ATTR_STATE_MASK, + state_mask); + + addattr_nest_end(&req.n, nest); + } + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +int do_mdb(int argc, char **argv) +{ + ll_init_map(&rth); + timestamp = 0; + + if (argc > 0) { + if (matches(*argv, "add") == 0) + return mdb_modify(RTM_NEWMDB, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1); + if (strcmp(*argv, "replace") == 0) + return mdb_modify(RTM_NEWMDB, NLM_F_CREATE|NLM_F_REPLACE, 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 (strcmp(*argv, "get") == 0) + return mdb_get(argc-1, argv+1); + if (strcmp(*argv, "flush") == 0) + return mdb_flush(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..552614d --- /dev/null +++ b/bridge/monitor.c @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * brmonitor.c "bridge monitor" + * + * 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 | vlan | vni | all]\n"); + exit(-1); +} + +static int accept_msg(struct rtnl_ctrl_data *ctrl, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = arg; + + switch (n->nlmsg_type) { + case RTM_NEWLINK: + case RTM_DELLINK: + return print_linkinfo(n, arg); + + case RTM_NEWNEIGH: + case RTM_DELNEIGH: + return print_fdb(n, arg); + + case RTM_NEWMDB: + case RTM_DELMDB: + return print_mdb_mon(n, arg); + + case NLMSG_TSTAMP: + print_nlmsg_timestamp(fp, n); + return 0; + + case RTM_NEWVLAN: + case RTM_DELVLAN: + return print_vlan_rtm(n, arg, true, false); + + case RTM_NEWTUNNEL: + case RTM_DELTUNNEL: + return print_vnifilter_rtm(n, arg); + + default: + return 0; + } +} + +void print_headers(FILE *fp, const char *label) +{ + if (timestamp) + print_timestamp(fp); + + if (prefix_banner) + fprintf(fp, "%s", label); +} + +int do_monitor(int argc, char **argv) +{ + char *file = NULL; + unsigned int groups = ~RTMGRP_TC; + int llink = 0; + int lneigh = 0; + int lmdb = 0; + int lvlan = 0; + int lvni = 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 (matches(*argv, "vlan") == 0) { + lvlan = 1; + groups = 0; + } else if (strcmp(*argv, "vni") == 0) { + lvni = 1; + groups = 0; + } else if (strcmp(*argv, "all") == 0) { + groups = ~RTMGRP_TC; + lvlan = 1; + lvni = 1; + 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); + + if (lvlan && rtnl_add_nl_group(&rth, RTNLGRP_BRVLAN) < 0) { + fprintf(stderr, "Failed to add bridge vlan group to list\n"); + exit(1); + } + + if (lvni && rtnl_add_nl_group(&rth, RTNLGRP_TUNNEL) < 0) { + fprintf(stderr, "Failed to add bridge vni group to list\n"); + 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..5352eb2 --- /dev/null +++ b/bridge/vlan.c @@ -0,0 +1,1382 @@ +/* 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 <errno.h> + +#include "json_print.h" +#include "libnetlink.h" +#include "br_common.h" +#include "utils.h" + +static unsigned int filter_index, filter_vlan; +static int vlan_rtm_cur_ifidx = -1; +static void print_vlan_info(struct rtattr *tb, int ifindex); + +enum vlan_show_subject { + VLAN_SHOW_VLAN, + VLAN_SHOW_TUNNELINFO, +}; + +#define VLAN_ID_LEN 9 + +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +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 { set } vid VLAN_ID dev DEV [ state STP_STATE ]\n" + " [ mcast_router MULTICAST_ROUTER ]\n" + " [ mcast_max_groups MAX_GROUPS ]\n" + " [ neigh_suppress {on | off} ]\n" + " bridge vlan { show } [ dev DEV ] [ vid VLAN_ID ]\n" + " bridge vlan { tunnelshow } [ dev DEV ] [ vid VLAN_ID ]\n" + " bridge vlan global { set } vid VLAN_ID dev DEV\n" + " [ mcast_snooping MULTICAST_SNOOPING ]\n" + " [ mcast_querier MULTICAST_QUERIER ]\n" + " [ mcast_igmp_version IGMP_VERSION ]\n" + " [ mcast_mld_version MLD_VERSION ]\n" + " [ mcast_last_member_count LAST_MEMBER_COUNT ]\n" + " [ mcast_last_member_interval LAST_MEMBER_INTERVAL ]\n" + " [ mcast_startup_query_count STARTUP_QUERY_COUNT ]\n" + " [ mcast_startup_query_interval STARTUP_QUERY_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" + " bridge vlan global { show } [ 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); + addattr16(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_VID, vid); + addattr16(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; +} + +static int vlan_option_set(int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct br_vlan_msg bvm; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_vlan_msg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_NEWVLAN, + .bvm.family = PF_BRIDGE, + }; + struct bridge_vlan_info vinfo = {}; + struct rtattr *afspec; + char *d = NULL; + short vid = -1; + + afspec = addattr_nest(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY); + afspec->rta_type |= NLA_F_NESTED; + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + req.bvm.ifindex = ll_name_to_index(d); + if (req.bvm.ifindex == 0) { + fprintf(stderr, + "Cannot find network device \"%s\"\n", + d); + return -1; + } + } else if (strcmp(*argv, "vid") == 0) { + short vid_end = -1; + char *p; + + NEXT_ARG(); + p = strchr(*argv, '-'); + if (p) { + *p = '\0'; + p++; + vid = atoi(*argv); + vid_end = atoi(p); + if (vid >= vid_end || vid_end >= 4096) { + fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n", + vid, vid_end); + return -1; + } + } else { + vid = atoi(*argv); + } + if (vid >= 4096) { + fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", + vid); + return -1; + } + + vinfo.flags = BRIDGE_VLAN_INFO_ONLY_OPTS; + vinfo.vid = vid; + addattr_l(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_INFO, + &vinfo, sizeof(vinfo)); + if (vid_end != -1) + addattr16(&req.n, sizeof(req), + BRIDGE_VLANDB_ENTRY_RANGE, vid_end); + } else if (strcmp(*argv, "state") == 0) { + char *endptr; + int state; + + NEXT_ARG(); + state = strtol(*argv, &endptr, 10); + if (!(**argv != '\0' && *endptr == '\0')) + state = parse_stp_state(*argv); + if (state == -1) { + fprintf(stderr, "Error: invalid STP state\n"); + return -1; + } + addattr8(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_STATE, + state); + } else if (strcmp(*argv, "mcast_router") == 0) { + __u8 mcast_router; + + NEXT_ARG(); + if (get_u8(&mcast_router, *argv, 0)) + invarg("invalid mcast_router", *argv); + addattr8(&req.n, sizeof(req), + BRIDGE_VLANDB_ENTRY_MCAST_ROUTER, + mcast_router); + } else if (strcmp(*argv, "mcast_max_groups") == 0) { + __u32 max_groups; + + NEXT_ARG(); + if (get_u32(&max_groups, *argv, 0)) + invarg("invalid mcast_max_groups", *argv); + addattr32(&req.n, sizeof(req), + BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS, + max_groups); + } else if (strcmp(*argv, "neigh_suppress") == 0) { + bool neigh_suppress; + int ret; + + NEXT_ARG(); + neigh_suppress = parse_on_off("neigh_suppress", *argv, + &ret); + if (ret) + return ret; + addattr8(&req.n, sizeof(req), + BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS, + neigh_suppress); + } else { + if (matches(*argv, "help") == 0) + NEXT_ARG(); + } + argc--; argv++; + } + addattr_nest_end(&req.n, afspec); + + if (d == NULL || vid == -1) { + fprintf(stderr, "Device and VLAN ID are required arguments.\n"); + return -1; + } + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +static int vlan_global_option_set(int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct br_vlan_msg bvm; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_vlan_msg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_NEWVLAN, + .bvm.family = PF_BRIDGE, + }; + struct rtattr *afspec; + short vid_end = -1; + char *d = NULL; + short vid = -1; + __u64 val64; + __u32 val32; + __u8 val8; + + afspec = addattr_nest(&req.n, sizeof(req), + BRIDGE_VLANDB_GLOBAL_OPTIONS); + afspec->rta_type |= NLA_F_NESTED; + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + req.bvm.ifindex = ll_name_to_index(d); + if (req.bvm.ifindex == 0) { + fprintf(stderr, "Cannot find network device \"%s\"\n", + d); + return -1; + } + } 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); + if (vid >= vid_end || vid_end >= 4096) { + fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n", + vid, vid_end); + return -1; + } + } else { + vid = atoi(*argv); + } + if (vid >= 4096) { + fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", + vid); + return -1; + } + addattr16(&req.n, sizeof(req), BRIDGE_VLANDB_GOPTS_ID, + vid); + if (vid_end != -1) + addattr16(&req.n, sizeof(req), + BRIDGE_VLANDB_GOPTS_RANGE, vid_end); + } else if (strcmp(*argv, "mcast_snooping") == 0) { + NEXT_ARG(); + if (get_u8(&val8, *argv, 0)) + invarg("invalid mcast_snooping", *argv); + addattr8(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING, val8); + } else if (strcmp(*argv, "mcast_querier") == 0) { + NEXT_ARG(); + if (get_u8(&val8, *argv, 0)) + invarg("invalid mcast_querier", *argv); + addattr8(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_QUERIER, val8); + } else if (strcmp(*argv, "mcast_igmp_version") == 0) { + NEXT_ARG(); + if (get_u8(&val8, *argv, 0)) + invarg("invalid mcast_igmp_version", *argv); + addattr8(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION, val8); + } else if (strcmp(*argv, "mcast_mld_version") == 0) { + NEXT_ARG(); + if (get_u8(&val8, *argv, 0)) + invarg("invalid mcast_mld_version", *argv); + addattr8(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION, val8); + } else if (strcmp(*argv, "mcast_last_member_count") == 0) { + NEXT_ARG(); + if (get_u32(&val32, *argv, 0)) + invarg("invalid mcast_last_member_count", *argv); + addattr32(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT, + val32); + } else if (strcmp(*argv, "mcast_startup_query_count") == 0) { + NEXT_ARG(); + if (get_u32(&val32, *argv, 0)) + invarg("invalid mcast_startup_query_count", + *argv); + addattr32(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT, + val32); + } else if (strcmp(*argv, "mcast_last_member_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_last_member_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL, + val64); + } else if (strcmp(*argv, "mcast_membership_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_membership_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL, + val64); + } else if (strcmp(*argv, "mcast_querier_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_querier_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL, + val64); + } else if (strcmp(*argv, "mcast_query_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_query_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL, + val64); + } else if (strcmp(*argv, "mcast_query_response_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_query_response_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL, + val64); + } else if (strcmp(*argv, "mcast_startup_query_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_startup_query_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL, + val64); + } else { + if (strcmp(*argv, "help") == 0) + NEXT_ARG(); + } + argc--; argv++; + } + addattr_nest_end(&req.n, afspec); + + if (d == NULL || vid == -1) { + fprintf(stderr, "Device and VLAN ID are required arguments.\n"); + return -1; + } + + 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, enum vlan_show_subject subject) +{ + open_json_object(NULL); + print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname", + "%-" __stringify(IFNAMSIZ) "s ", + ll_index_to_name(ifi_index)); + open_json_array(PRINT_JSON, + subject == VLAN_SHOW_VLAN ? "vlans": "tunnels"); +} + +static void close_vlan_port(void) +{ + close_json_array(PRINT_JSON, NULL); + close_json_object(); +} + +static void print_vlan_tunnel_info(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; + bool opened = false; + + 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; + unsigned int width; + 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_u16(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_u16(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 (!opened) { + open_vlan_port(ifindex, VLAN_SHOW_TUNNELINFO); + opened = true; + } else { + print_string(PRINT_FP, NULL, + "%-" __stringify(IFNAMSIZ) "s ", ""); + } + + open_json_object(NULL); + width = print_range("vlan", last_vid_start, tunnel_vid); + if (!is_json_context()) + printf("%-*s ", VLAN_ID_LEN - width, ""); + print_range("tunid", last_tunid_start, tunnel_id); + close_json_object(); + print_nl(); + } + + if (opened) + close_vlan_port(); +} + +static int print_vlan(struct nlmsghdr *n, void *arg) +{ + enum vlan_show_subject *subject = 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 (!tb[IFLA_AF_SPEC]) + return 0; + + switch (*subject) { + case VLAN_SHOW_VLAN: + print_vlan_info(tb[IFLA_AF_SPEC], ifm->ifi_index); + break; + case VLAN_SHOW_TUNNELINFO: + print_vlan_tunnel_info(tb[IFLA_AF_SPEC], ifm->ifi_index); + break; + } + + 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) +{ + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + print_lluint(PRINT_ANY, "rx_bytes", "RX: %llu bytes", + vstats->rx_bytes); + print_lluint(PRINT_ANY, "rx_packets", " %llu packets\n", + vstats->rx_packets); + + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + print_lluint(PRINT_ANY, "tx_bytes", "TX: %llu bytes", + vstats->tx_bytes); + print_lluint(PRINT_ANY, "tx_packets", " %llu packets\n", + vstats->tx_packets); +} + +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_nl(); + __print_one_vlan_stats(vstats); + + 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, VLAN_SHOW_VLAN); + found_vlan = true; + } else { + print_string(PRINT_FP, NULL, + "%-" __stringify(IFNAMSIZ) "s ", ""); + } + 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 void print_vlan_router_ports(struct rtattr *rattr) +{ + int rem = RTA_PAYLOAD(rattr); + struct rtattr *i; + + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + open_json_array(PRINT_ANY, is_json_context() ? "router_ports" : + "router ports: "); + for (i = RTA_DATA(rattr); 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); + + open_json_object(NULL); + if (show_stats && i != RTA_DATA(rattr)) { + print_nl(); + /* start: IFNAMSIZ + 4 + strlen("router ports: ") */ + print_string(PRINT_FP, NULL, + "%-" __stringify(IFNAMSIZ) "s " + " ", + ""); + } + print_string(PRINT_ANY, "port", "%s ", port_ifname); + if (show_stats) + br_print_router_port_stats(i); + close_json_object(); + } + close_json_array(PRINT_JSON, NULL); + print_nl(); +} + +static void print_vlan_global_opts(struct rtattr *a, int ifindex) +{ + struct rtattr *vtb[BRIDGE_VLANDB_GOPTS_MAX + 1], *vattr; + __u16 vid, vrange = 0; + + if (rta_type(a) != BRIDGE_VLANDB_GLOBAL_OPTIONS) + return; + + parse_rtattr_flags(vtb, BRIDGE_VLANDB_GOPTS_MAX, RTA_DATA(a), + RTA_PAYLOAD(a), NLA_F_NESTED); + vid = rta_getattr_u16(vtb[BRIDGE_VLANDB_GOPTS_ID]); + if (vtb[BRIDGE_VLANDB_GOPTS_RANGE]) + vrange = rta_getattr_u16(vtb[BRIDGE_VLANDB_GOPTS_RANGE]); + else + vrange = vid; + + if (filter_vlan && (filter_vlan < vid || filter_vlan > vrange)) + return; + + if (vlan_rtm_cur_ifidx != ifindex) { + open_vlan_port(ifindex, VLAN_SHOW_VLAN); + open_json_object(NULL); + vlan_rtm_cur_ifidx = ifindex; + } else { + open_json_object(NULL); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + } + print_range("vlan", vid, vrange); + print_nl(); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING]; + print_uint(PRINT_ANY, "mcast_snooping", "mcast_snooping %u ", + rta_getattr_u8(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER]; + print_uint(PRINT_ANY, "mcast_querier", "mcast_querier %u ", + rta_getattr_u8(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION]; + print_uint(PRINT_ANY, "mcast_igmp_version", + "mcast_igmp_version %u ", rta_getattr_u8(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]; + print_uint(PRINT_ANY, "mcast_mld_version", + "mcast_mld_version %u ", rta_getattr_u8(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT]; + print_uint(PRINT_ANY, "mcast_last_member_count", + "mcast_last_member_count %u ", + rta_getattr_u32(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL]; + print_lluint(PRINT_ANY, "mcast_last_member_interval", + "mcast_last_member_interval %llu ", + rta_getattr_u64(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT]; + print_uint(PRINT_ANY, "mcast_startup_query_count", + "mcast_startup_query_count %u ", + rta_getattr_u32(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL]; + print_lluint(PRINT_ANY, "mcast_startup_query_interval", + "mcast_startup_query_interval %llu ", + rta_getattr_u64(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL]; + print_lluint(PRINT_ANY, "mcast_membership_interval", + "mcast_membership_interval %llu ", + rta_getattr_u64(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL]; + print_lluint(PRINT_ANY, "mcast_querier_interval", + "mcast_querier_interval %llu ", + rta_getattr_u64(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL]; + print_lluint(PRINT_ANY, "mcast_query_interval", + "mcast_query_interval %llu ", + rta_getattr_u64(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL]; + print_lluint(PRINT_ANY, "mcast_query_response_interval", + "mcast_query_response_interval %llu ", + rta_getattr_u64(vattr)); + } + print_nl(); + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS]) { + vattr = RTA_DATA(vtb[BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS]); + print_vlan_router_ports(vattr); + } + close_json_object(); +} + +static void print_vlan_opts(struct rtattr *a, int ifindex) +{ + struct rtattr *vtb[BRIDGE_VLANDB_ENTRY_MAX + 1], *vattr; + struct bridge_vlan_xstats vstats; + struct bridge_vlan_info *vinfo; + __u16 vrange = 0; + __u8 state = 0; + + if (rta_type(a) != BRIDGE_VLANDB_ENTRY) + return; + + parse_rtattr_flags(vtb, BRIDGE_VLANDB_ENTRY_MAX, RTA_DATA(a), + RTA_PAYLOAD(a), NLA_F_NESTED); + vinfo = RTA_DATA(vtb[BRIDGE_VLANDB_ENTRY_INFO]); + + memset(&vstats, 0, sizeof(vstats)); + if (vtb[BRIDGE_VLANDB_ENTRY_RANGE]) + vrange = rta_getattr_u16(vtb[BRIDGE_VLANDB_ENTRY_RANGE]); + else + vrange = vinfo->vid; + + if (filter_vlan && (filter_vlan < vinfo->vid || filter_vlan > vrange)) + return; + + if (vtb[BRIDGE_VLANDB_ENTRY_STATE]) + state = rta_getattr_u8(vtb[BRIDGE_VLANDB_ENTRY_STATE]); + + if (vtb[BRIDGE_VLANDB_ENTRY_STATS]) { + struct rtattr *stb[BRIDGE_VLANDB_STATS_MAX+1]; + struct rtattr *attr; + + attr = vtb[BRIDGE_VLANDB_ENTRY_STATS]; + parse_rtattr(stb, BRIDGE_VLANDB_STATS_MAX, RTA_DATA(attr), + RTA_PAYLOAD(attr)); + + if (stb[BRIDGE_VLANDB_STATS_RX_BYTES]) { + attr = stb[BRIDGE_VLANDB_STATS_RX_BYTES]; + vstats.rx_bytes = rta_getattr_u64(attr); + } + if (stb[BRIDGE_VLANDB_STATS_RX_PACKETS]) { + attr = stb[BRIDGE_VLANDB_STATS_RX_PACKETS]; + vstats.rx_packets = rta_getattr_u64(attr); + } + if (stb[BRIDGE_VLANDB_STATS_TX_PACKETS]) { + attr = stb[BRIDGE_VLANDB_STATS_TX_PACKETS]; + vstats.tx_packets = rta_getattr_u64(attr); + } + if (stb[BRIDGE_VLANDB_STATS_TX_BYTES]) { + attr = stb[BRIDGE_VLANDB_STATS_TX_BYTES]; + vstats.tx_bytes = rta_getattr_u64(attr); + } + } + + if (vlan_rtm_cur_ifidx != ifindex) { + open_vlan_port(ifindex, VLAN_SHOW_VLAN); + open_json_object(NULL); + vlan_rtm_cur_ifidx = ifindex; + } else { + open_json_object(NULL); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + } + print_range("vlan", vinfo->vid, vrange); + print_vlan_flags(vinfo->flags); + print_nl(); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + print_stp_state(state); + if (vtb[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER]) { + vattr = vtb[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER]; + print_uint(PRINT_ANY, "mcast_router", "mcast_router %u ", + rta_getattr_u8(vattr)); + } + if (vtb[BRIDGE_VLANDB_ENTRY_MCAST_N_GROUPS]) { + vattr = vtb[BRIDGE_VLANDB_ENTRY_MCAST_N_GROUPS]; + print_uint(PRINT_ANY, "mcast_n_groups", "mcast_n_groups %u ", + rta_getattr_u32(vattr)); + } + if (vtb[BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS]) { + vattr = vtb[BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS]; + print_uint(PRINT_ANY, "mcast_max_groups", "mcast_max_groups %u ", + rta_getattr_u32(vattr)); + } + if (vtb[BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS]) { + vattr = vtb[BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS]; + print_on_off(PRINT_ANY, "neigh_suppress", "neigh_suppress %s ", + rta_getattr_u8(vattr)); + } + print_nl(); + if (show_stats) + __print_one_vlan_stats(&vstats); + close_json_object(); +} + +int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor, bool global_only) +{ + struct br_vlan_msg *bvm = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr *a; + FILE *fp = arg; + int rem; + + if (n->nlmsg_type != RTM_NEWVLAN && n->nlmsg_type != RTM_DELVLAN && + n->nlmsg_type != RTM_GETVLAN) { + fprintf(stderr, "Unknown vlan rtm message: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + return 0; + } + + len -= NLMSG_LENGTH(sizeof(*bvm)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + if (bvm->family != AF_BRIDGE) + return 0; + + if (filter_index && filter_index != bvm->ifindex) + return 0; + + print_headers(fp, "[VLAN]"); + + if (n->nlmsg_type == RTM_DELVLAN) + print_bool(PRINT_ANY, "deleted", "Deleted ", true); + + if (monitor) + vlan_rtm_cur_ifidx = -1; + + if (vlan_rtm_cur_ifidx != -1 && vlan_rtm_cur_ifidx != bvm->ifindex) { + close_vlan_port(); + vlan_rtm_cur_ifidx = -1; + } + + rem = len; + for (a = BRVLAN_RTA(bvm); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) { + unsigned short attr_type = rta_type(a); + + /* skip unknown attributes */ + if (attr_type > BRIDGE_VLANDB_MAX || + (global_only && attr_type != BRIDGE_VLANDB_GLOBAL_OPTIONS)) + continue; + + switch (attr_type) { + case BRIDGE_VLANDB_ENTRY: + print_vlan_opts(a, bvm->ifindex); + break; + case BRIDGE_VLANDB_GLOBAL_OPTIONS: + print_vlan_global_opts(a, bvm->ifindex); + break; + } + } + + return 0; +} + +static int print_vlan_rtm_filter(struct nlmsghdr *n, void *arg) +{ + return print_vlan_rtm(n, arg, false, false); +} + +static int print_vlan_rtm_global_filter(struct nlmsghdr *n, void *arg) +{ + return print_vlan_rtm(n, arg, false, true); +} + +static int vlan_show(int argc, char **argv, int subject) +{ + 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_details is true then use the new bridge vlan dump format */ + if (show_details && subject == VLAN_SHOW_VLAN) { + __u32 dump_flags = show_stats ? BRIDGE_VLANDB_DUMPF_STATS : 0; + + if (rtnl_brvlandump_req(&rth, PF_BRIDGE, dump_flags) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (!is_json_context()) { + printf("%-" __stringify(IFNAMSIZ) "s %-" + __stringify(VLAN_ID_LEN) "s", "port", + "vlan-id"); + printf("\n"); + } + + ret = rtnl_dump_filter(&rth, print_vlan_rtm_filter, &subject); + if (ret < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + + if (vlan_rtm_cur_ifidx != -1) + close_vlan_port(); + + goto out; + } + + if (!show_stats) { + if (rtnl_linkdump_req_filter(&rth, PF_BRIDGE, + (compress_vlans ? + RTEXT_FILTER_BRVLAN_COMPRESSED : + RTEXT_FILTER_BRVLAN)) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (!is_json_context()) { + printf("%-" __stringify(IFNAMSIZ) "s %-" + __stringify(VLAN_ID_LEN) "s", "port", + "vlan-id"); + if (subject == VLAN_SHOW_TUNNELINFO) + printf(" tunnel-id"); + printf("\n"); + } + + ret = rtnl_dump_filter(&rth, print_vlan, &subject); + if (ret < 0) { + fprintf(stderr, "Dump terminated\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, + NULL, NULL) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (!is_json_context()) + printf("%-" __stringify(IFNAMSIZ) "s 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, + NULL, NULL) < 0) { + perror("Cannot send slave dump request"); + exit(1); + } + + if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + } + +out: + delete_json_obj(); + fflush(stdout); + return 0; +} + +static int vlan_global_show(int argc, char **argv) +{ + __u32 dump_flags = BRIDGE_VLANDB_DUMPF_GLOBAL; + int ret = 0, subject = VLAN_SHOW_VLAN; + 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); + + if (rtnl_brvlandump_req(&rth, PF_BRIDGE, dump_flags) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (!is_json_context()) { + printf("%-" __stringify(IFNAMSIZ) "s %-" + __stringify(VLAN_ID_LEN) "s", "port", + "vlan-id"); + printf("\n"); + } + + ret = rtnl_dump_filter(&rth, print_vlan_rtm_global_filter, &subject); + if (ret < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + + if (vlan_rtm_cur_ifidx != -1) + close_vlan_port(); + + delete_json_obj(); + fflush(stdout); + return 0; +} + +static void print_vlan_info(struct rtattr *tb, int ifindex) +{ + struct rtattr *i, *list = tb; + int rem = RTA_PAYLOAD(list); + __u16 last_vid_start = 0; + bool opened = false; + + 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; + + if (!opened) { + open_vlan_port(ifindex, VLAN_SHOW_VLAN); + opened = true; + } else { + print_string(PRINT_FP, NULL, "%-" + __stringify(IFNAMSIZ) "s ", ""); + } + + open_json_object(NULL); + print_range("vlan", last_vid_start, vinfo->vid); + + print_vlan_flags(vinfo->flags); + close_json_object(); + print_nl(); + } + + if (opened) + close_vlan_port(); +} + +static int vlan_global(int argc, char **argv) +{ + if (argc > 0) { + if (strcmp(*argv, "show") == 0 || + strcmp(*argv, "lst") == 0 || + strcmp(*argv, "list") == 0) + return vlan_global_show(argc-1, argv+1); + else if (strcmp(*argv, "set") == 0) + return vlan_global_option_set(argc-1, argv+1); + else + usage(); + } else { + return vlan_global_show(0, NULL); + } + + return 0; +} + +int do_vlan(int argc, char **argv) +{ + ll_init_map(&rth); + timestamp = 0; + + 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, VLAN_SHOW_VLAN); + if (matches(*argv, "tunnelshow") == 0) { + return vlan_show(argc-1, argv+1, VLAN_SHOW_TUNNELINFO); + } + if (matches(*argv, "set") == 0) + return vlan_option_set(argc-1, argv+1); + if (strcmp(*argv, "global") == 0) + return vlan_global(argc-1, argv+1); + if (matches(*argv, "help") == 0) + usage(); + } else { + return vlan_show(0, NULL, VLAN_SHOW_VLAN); + } + + fprintf(stderr, "Command \"%s\" is unknown, try \"bridge vlan help\".\n", *argv); + exit(-1); +} diff --git a/bridge/vni.c b/bridge/vni.c new file mode 100644 index 0000000..a7abe6d --- /dev/null +++ b/bridge/vni.c @@ -0,0 +1,413 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Command to manage vnifiltering on a vxlan device + * + * Authors: Roopa Prabhu <roopa@nvidia.com> + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <linux/if_link.h> +#include <linux/if_bridge.h> +#include <linux/if_ether.h> + +#include "json_print.h" +#include "libnetlink.h" +#include "br_common.h" +#include "utils.h" + +static unsigned int filter_index; + +/* max len of "<start>-<end>" */ +#define VXLAN_ID_LEN 17 + +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +static void usage(void) +{ + fprintf(stderr, + "Usage: bridge vni { add | del } vni VNI\n" + " [ { group | remote } IP_ADDRESS ]\n" + " dev DEV\n" + " bridge vni { show } [ dev DEV ]\n" + "\n" + "Where: VNI := 0-16777215\n" + ); + exit(-1); +} + +static int parse_vni_filter(const char *argv, struct nlmsghdr *n, int reqsize, + inet_prefix *group) +{ + char *vnilist = strdupa(argv); + char *vni = strtok(vnilist, ","); + int group_type = AF_UNSPEC; + struct rtattr *nlvlist_e; + char *v; + + if (group && is_addrtype_inet(group)) + group_type = (group->family == AF_INET) ? VXLAN_VNIFILTER_ENTRY_GROUP : + VXLAN_VNIFILTER_ENTRY_GROUP6; + + while (vni != NULL) { + __u32 vni_start = 0, vni_end = 0; + + v = strchr(vni, '-'); + if (v) { + *v = '\0'; + v++; + vni_start = atoi(vni); + vni_end = atoi(v); + } else { + vni_start = atoi(vni); + } + nlvlist_e = addattr_nest(n, reqsize, VXLAN_VNIFILTER_ENTRY | + NLA_F_NESTED); + addattr32(n, 1024, VXLAN_VNIFILTER_ENTRY_START, vni_start); + if (vni_end) + addattr32(n, 1024, VXLAN_VNIFILTER_ENTRY_END, vni_end); + if (group) + addattr_l(n, 1024, group_type, group->data, group->bytelen); + addattr_nest_end(n, nlvlist_e); + vni = strtok(NULL, ","); + } + + return 0; +} + +static int vni_modify(int cmd, int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct tunnel_msg tmsg; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tunnel_msg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = cmd, + .tmsg.family = PF_BRIDGE, + }; + bool daddr_present = false; + inet_prefix daddr; + char *vni = NULL; + char *d = NULL; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "vni") == 0) { + NEXT_ARG(); + if (vni) + duparg("vni", *argv); + vni = *argv; + } else if (strcmp(*argv, "group") == 0) { + NEXT_ARG(); + if (daddr_present) + duparg("destination", *argv); + get_addr(&daddr, *argv, AF_UNSPEC); + if (!is_addrtype_inet_multi(&daddr)) + invarg("invalid group address", *argv); + daddr_present = true; + } else if (strcmp(*argv, "remote") == 0) { + NEXT_ARG(); + if (daddr_present) + duparg("destination", *argv); + get_addr(&daddr, *argv, AF_UNSPEC); + daddr_present = true; + } else { + if (strcmp(*argv, "help") == 0) + usage(); + } + argc--; argv++; + } + + if (d == NULL || vni == NULL) { + fprintf(stderr, "Device and VNI ID are required arguments.\n"); + return -1; + } + + parse_vni_filter(vni, &req.n, sizeof(req), + (daddr_present ? &daddr : NULL)); + + req.tmsg.ifindex = ll_name_to_index(d); + if (req.tmsg.ifindex == 0) { + fprintf(stderr, "Cannot find vxlan device \"%s\"\n", d); + return -1; + } + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +static void open_vni_port(int ifi_index) +{ + open_json_object(NULL); + print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname", + "%-" __stringify(IFNAMSIZ) "s ", + ll_index_to_name(ifi_index)); + open_json_array(PRINT_JSON, "vnis"); +} + +static void close_vni_port(void) +{ + close_json_array(PRINT_JSON, NULL); + close_json_object(); +} + +static void print_vnifilter_entry_stats(struct rtattr *stats_attr) +{ + struct rtattr *stb[VNIFILTER_ENTRY_STATS_MAX+1]; + __u64 stat; + + open_json_object("stats"); + parse_rtattr_flags(stb, VNIFILTER_ENTRY_STATS_MAX, RTA_DATA(stats_attr), + RTA_PAYLOAD(stats_attr), NLA_F_NESTED); + + print_nl(); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s RX: ", + ""); + + if (stb[VNIFILTER_ENTRY_STATS_RX_BYTES]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_BYTES]); + print_lluint(PRINT_ANY, "rx_bytes", "bytes %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_RX_PKTS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_PKTS]); + print_lluint(PRINT_ANY, "rx_pkts", "pkts %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_RX_DROPS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_DROPS]); + print_lluint(PRINT_ANY, "rx_drops", "drops %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_RX_ERRORS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_ERRORS]); + print_lluint(PRINT_ANY, "rx_errors", "errors %llu ", stat); + } + + print_nl(); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s TX: ", + ""); + + if (stb[VNIFILTER_ENTRY_STATS_TX_BYTES]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_BYTES]); + print_lluint(PRINT_ANY, "tx_bytes", "bytes %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_TX_PKTS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_PKTS]); + print_lluint(PRINT_ANY, "tx_pkts", "pkts %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_TX_DROPS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_DROPS]); + print_lluint(PRINT_ANY, "tx_drops", "drops %llu ", stat); + } + if (stb[VNIFILTER_ENTRY_STATS_TX_ERRORS]) { + stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_ERRORS]); + print_lluint(PRINT_ANY, "tx_errors", "errors %llu ", stat); + } + close_json_object(); +} + +static void print_vni(struct rtattr *t, int ifindex) +{ + struct rtattr *ttb[VXLAN_VNIFILTER_ENTRY_MAX+1]; + __u32 vni_start = 0; + unsigned int width; + __u32 vni_end; + + parse_rtattr_flags(ttb, VXLAN_VNIFILTER_ENTRY_MAX, RTA_DATA(t), + RTA_PAYLOAD(t), NLA_F_NESTED); + + if (ttb[VXLAN_VNIFILTER_ENTRY_START]) + vni_start = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_START]); + + if (ttb[VXLAN_VNIFILTER_ENTRY_END]) + vni_end = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_END]); + else + vni_end = vni_start; + + open_json_object(NULL); + width = print_range("vni", vni_start, vni_end); + if (!is_json_context()) + printf("%-*s ", VXLAN_ID_LEN - width, ""); + + if (ttb[VXLAN_VNIFILTER_ENTRY_GROUP]) { + __be32 addr = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_GROUP]); + + if (addr) { + if (IN_MULTICAST(ntohl(addr))) + print_string(PRINT_ANY, + "group", + "%s", + format_host(AF_INET, 4, &addr)); + else + print_string(PRINT_ANY, + "remote", + "%s", + format_host(AF_INET, 4, &addr)); + } + } else if (ttb[VXLAN_VNIFILTER_ENTRY_GROUP6]) { + struct in6_addr addr; + + memcpy(&addr, RTA_DATA(ttb[VXLAN_VNIFILTER_ENTRY_GROUP6]), sizeof(struct in6_addr)); + if (!IN6_IS_ADDR_UNSPECIFIED(&addr)) { + if (IN6_IS_ADDR_MULTICAST(&addr)) + print_string(PRINT_ANY, + "group", + "%s", + format_host(AF_INET6, + sizeof(struct in6_addr), + &addr)); + else + print_string(PRINT_ANY, + "remote", + "%s", + format_host(AF_INET6, + sizeof(struct in6_addr), + &addr)); + } + } + + if (ttb[VXLAN_VNIFILTER_ENTRY_STATS]) + print_vnifilter_entry_stats(ttb[VXLAN_VNIFILTER_ENTRY_STATS]); + + close_json_object(); + print_nl(); +} + +int print_vnifilter_rtm(struct nlmsghdr *n, void *arg) +{ + struct tunnel_msg *tmsg = NLMSG_DATA(n); + int len = n->nlmsg_len; + bool opened = false; + struct rtattr *t; + FILE *fp = arg; + int rem; + + if (n->nlmsg_type != RTM_NEWTUNNEL && + n->nlmsg_type != RTM_DELTUNNEL && + n->nlmsg_type != RTM_GETTUNNEL) { + fprintf(stderr, "Unknown vni tunnel rtm msg: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + return 0; + } + + len -= NLMSG_LENGTH(sizeof(*tmsg)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + if (tmsg->family != AF_BRIDGE) + return 0; + + if (filter_index && filter_index != tmsg->ifindex) + return 0; + + print_headers(fp, "[TUNNEL]"); + + if (n->nlmsg_type == RTM_DELTUNNEL) + print_bool(PRINT_ANY, "deleted", "Deleted ", true); + + rem = len; + for (t = TUNNEL_RTA(tmsg); RTA_OK(t, rem); t = RTA_NEXT(t, rem)) { + if (rta_type(t) != VXLAN_VNIFILTER_ENTRY) + continue; + + if (!opened) { + open_vni_port(tmsg->ifindex); + opened = true; + } else { + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + } + + print_vni(t, tmsg->ifindex); + } + + if (opened) + close_vni_port(); + + fflush(stdout); + return 0; +} + +static int vni_show(int argc, char **argv) +{ + char *filter_dev = NULL; + __u8 flags = 0; + int ret = 0; + + 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); + } + + new_json_obj(json); + + if (show_stats) + flags = TUNNEL_MSG_FLAG_STATS; + + if (rtnl_tunneldump_req(&rth, PF_BRIDGE, filter_index, flags) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (!is_json_context()) + printf("%-" __stringify(IFNAMSIZ) "s %-" + __stringify(VXLAN_ID_LEN) "s group/remote\n", "dev", + "vni"); + + ret = rtnl_dump_filter(&rth, print_vnifilter_rtm, NULL); + if (ret < 0) { + fprintf(stderr, "Dump ternminated\n"); + exit(1); + } + + delete_json_obj(); + fflush(stdout); + return 0; +} + +int do_vni(int argc, char **argv) +{ + ll_init_map(&rth); + timestamp = 0; + + if (argc > 0) { + if (strcmp(*argv, "add") == 0) + return vni_modify(RTM_NEWTUNNEL, argc-1, argv+1); + if (strcmp(*argv, "delete") == 0 || + strcmp(*argv, "del") == 0) + return vni_modify(RTM_DELTUNNEL, argc-1, argv+1); + if (strcmp(*argv, "show") == 0 || + strcmp(*argv, "lst") == 0 || + strcmp(*argv, "list") == 0) + return vni_show(argc-1, argv+1); + if (strcmp(*argv, "help") == 0) + usage(); + } else { + return vni_show(0, NULL); + } + + fprintf(stderr, "Command \"%s\" is unknown, try \"bridge vni help\".\n", *argv); + exit(-1); +} diff --git a/configure b/configure new file mode 100755 index 0000000..928048b --- /dev/null +++ b/configure @@ -0,0 +1,620 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# This is not an autoconf generated configure + +INCLUDE="$PWD/include" +PREFIX="/usr" +LIBDIR="\${prefix}/lib" +COLOR="never" + +# 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} + : ${YACC=bison} + echo "PKG_CONFIG:=${PKG_CONFIG}" >>$CONFIG + echo "AR:=${AR}" >>$CONFIG + echo "CC:=${CC}" >>$CONFIG + echo "YACC:=${YACC}" >>$CONFIG +} + +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_lib_dir() +{ + LIBDIR=$(echo $LIBDIR | sed "s|\${prefix}|$PREFIX|") + + echo -n "lib directory: " + echo "$LIBDIR" + echo "LIBDIR:=$LIBDIR" >> $CONFIG +} + +check_setns() +{ + cat >$TMPDIR/setnstest.c <<EOF +#define _GNU_SOURCE +#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_name_to_handle_at() +{ + cat >$TMPDIR/name_to_handle_at_test.c <<EOF +#define _GNU_SOURCE +#include <sys/types.h> +#include <fcntl.h> +int main(int argc, char **argv) +{ + struct file_handle *fhp; + int mount_id, flags, dirfd; + char *pathname; + name_to_handle_at(dirfd, pathname, fhp, &mount_id, flags); + return 0; +} +EOF + if $CC -I$INCLUDE -o $TMPDIR/name_to_handle_at_test $TMPDIR/name_to_handle_at_test.c >/dev/null 2>&1; then + echo "yes" + echo "CFLAGS += -DHAVE_HANDLE_AT" >>$CONFIG + else + echo "no" + fi + rm -f $TMPDIR/name_to_handle_at_test.c $TMPDIR/name_to_handle_at_test +} + +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 || IPSET_PROTOCOL == 7 +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 +} + +have_libbpf_basic() +{ + cat >$TMPDIR/libbpf_test.c <<EOF +#include <bpf/libbpf.h> +int main(int argc, char **argv) { + bpf_program__set_autoload(NULL, false); + bpf_map__ifindex(NULL); + bpf_map__set_pin_path(NULL, NULL); + bpf_object__open_file(NULL, NULL); + return 0; +} +EOF + + $CC -o $TMPDIR/libbpf_test $TMPDIR/libbpf_test.c $LIBBPF_CFLAGS $LIBBPF_LDLIBS >/dev/null 2>&1 + local ret=$? + + rm -f $TMPDIR/libbpf_test.c $TMPDIR/libbpf_test + return $ret +} + +have_libbpf_sec_name() +{ + cat >$TMPDIR/libbpf_sec_test.c <<EOF +#include <bpf/libbpf.h> +int main(int argc, char **argv) { + void *ptr; + bpf_program__section_name(NULL); + return 0; +} +EOF + + $CC -o $TMPDIR/libbpf_sec_test $TMPDIR/libbpf_sec_test.c $LIBBPF_CFLAGS $LIBBPF_LDLIBS >/dev/null 2>&1 + local ret=$? + + rm -f $TMPDIR/libbpf_sec_test.c $TMPDIR/libbpf_sec_test + return $ret +} + +check_force_libbpf_on() +{ + # if set LIBBPF_FORCE=on but no libbpf support, just exist the config + # process to make sure we don't build without libbpf. + if [ "$LIBBPF_FORCE" = on ]; then + echo " LIBBPF_FORCE=on set, but couldn't find a usable libbpf" + exit 1 + fi +} + +check_libbpf() +{ + # if set LIBBPF_FORCE=off, disable libbpf entirely + if [ "$LIBBPF_FORCE" = off ]; then + echo "no" + return + fi + + if ! ${PKG_CONFIG} libbpf --exists && [ -z "$LIBBPF_DIR" ] ; then + echo "no" + check_force_libbpf_on + return + fi + + if [ $(uname -m) = x86_64 ]; then + local LIBBPF_LIBDIR="${LIBBPF_DIR}/usr/lib64" + else + local LIBBPF_LIBDIR="${LIBBPF_DIR}/usr/lib" + fi + + if [ -n "$LIBBPF_DIR" ]; then + LIBBPF_CFLAGS="-I${LIBBPF_DIR}/usr/include" + LIBBPF_LDLIBS="${LIBBPF_LIBDIR}/libbpf.a -lz -lelf" + LIBBPF_VERSION=$(PKG_CONFIG_LIBDIR=${LIBBPF_LIBDIR}/pkgconfig ${PKG_CONFIG} libbpf --modversion) + else + LIBBPF_CFLAGS=$(${PKG_CONFIG} libbpf --cflags) + LIBBPF_LDLIBS=$(${PKG_CONFIG} libbpf --libs) + LIBBPF_VERSION=$(${PKG_CONFIG} libbpf --modversion) + fi + + if ! have_libbpf_basic; then + echo "no" + echo " libbpf version $LIBBPF_VERSION is too low, please update it to at least 0.1.0" + check_force_libbpf_on + return + else + echo "HAVE_LIBBPF:=y" >> $CONFIG + echo 'CFLAGS += -DHAVE_LIBBPF ' $LIBBPF_CFLAGS >> $CONFIG + echo "CFLAGS += -DLIBBPF_VERSION=\\\"$LIBBPF_VERSION\\\"" >> $CONFIG + echo 'LDLIBS += ' $LIBBPF_LDLIBS >> $CONFIG + + if [ -z "$LIBBPF_DIR" ]; then + echo "CFLAGS += -DLIBBPF_DYNAMIC" >> $CONFIG + fi + fi + + # bpf_program__title() is deprecated since libbpf 0.2.0, use + # bpf_program__section_name() instead if we support + if have_libbpf_sec_name; then + echo "HAVE_LIBBPF_SECTION_NAME:=y" >> $CONFIG + echo 'CFLAGS += -DHAVE_LIBBPF_SECTION_NAME ' >> $CONFIG + fi + + echo "yes" + echo " libbpf version $LIBBPF_VERSION" +} + +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_tirpc() +{ + if ${PKG_CONFIG} libtirpc --exists; then + echo "HAVE_RPC:=y" >>$CONFIG + echo "yes" + + echo 'LDLIBS +=' "$(${PKG_CONFIG} --libs libtirpc)" >>$CONFIG + echo 'CFLAGS += -DHAVE_RPC' "$(${PKG_CONFIG} --cflags libtirpc)" >>$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 +#define _GNU_SOURCE +#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 +} + +check_color() +{ + case "$COLOR" in + never) + echo 'CONF_COLOR:=COLOR_OPT_NEVER' >> $CONFIG + echo 'never' + ;; + auto) + echo 'CONF_COLOR:=COLOR_OPT_AUTO' >> $CONFIG + echo 'auto' + ;; + always) + echo 'CONF_COLOR:=COLOR_OPT_ALWAYS' >> $CONFIG + echo 'always' + ;; + esac +} + +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 +} + +usage() +{ + cat <<EOF +Usage: $0 [OPTIONS] + --color <never|auto|always> Default color output configuration. Available options: + never: color output is disabled (default) + auto: color output is enabled if stdout is a terminal + always: color output is enabled regardless of stdout state + --include_dir <dir> Path to iproute2 include dir + --libdir <dir> Path to iproute2 lib dir + --libbpf_dir <dir> Path to libbpf DESTDIR + --libbpf_force <on|off> Enable/disable libbpf by force. Available options: + on: require link against libbpf, quit config if no libbpf support + off: disable libbpf probing + --prefix <dir> Path prefix of the lib files to install + -h | --help Show this usage info +EOF + exit $1 +} + +# Compat with the old INCLUDE path setting method. +if [ $# -eq 1 ] && [ "$(echo $1 | cut -c 1)" != '-' ]; then + INCLUDE="$1" +else + while [ "$#" -gt 0 ]; do + case "$1" in + --color) + shift + COLOR="$1" ;; + --color=*) + COLOR="${1#*=}" ;; + --include_dir) + shift + INCLUDE="$1" ;; + --include_dir=*) + INCLUDE="${1#*=}" ;; + --libdir) + shift + LIBDIR="$1" ;; + --libdir=*) + LIBDIR="${1#*=}" ;; + --libbpf_dir) + shift + LIBBPF_DIR="$1" ;; + --libbpf_dir=*) + LIBBPF_DIR="${1#*=}" ;; + --libbpf_force) + shift + LIBBPF_FORCE="$1" ;; + --libbpf_force=*) + LIBBPF_FORCE="${1#*=}" ;; + --prefix) + shift + PREFIX="$1" ;; + --prefix=*) + PREFIX="${1#*=}" ;; + -h | --help) + usage 0 ;; + --*) + ;; + *) + usage 1 ;; + esac + [ "$#" -gt 0 ] && shift + done +fi + +case "$COLOR" in + never) ;; + auto) ;; + always) ;; + *) usage 1 ;; +esac +[ -d "$INCLUDE" ] || usage 1 +if [ "${LIBBPF_DIR-unused}" != "unused" ]; then + [ -d "$LIBBPF_DIR" ] || usage 1 +fi +if [ "${LIBBPF_FORCE-unused}" != "unused" ]; then + if [ "$LIBBPF_FORCE" != 'on' ] && [ "$LIBBPF_FORCE" != 'off' ]; then + usage 1 + fi +fi +[ -z "$PREFIX" ] && usage 1 +[ -z "$LIBDIR" ] && usage 1 + +echo "# Generated config based on" $INCLUDE >$CONFIG +quiet_config >> $CONFIG + +check_toolchain + +echo "TC schedulers" + +check_xtables +if ! grep -q TC_CONFIG_NO_XT $CONFIG; then + echo -n " IPT " + check_xt + check_xt_old + check_xt_old_internal_h + + echo -n " IPSET " + check_ipset +fi + +echo +check_lib_dir + +echo -n "libc has setns: " +check_setns + +echo -n "libc has name_to_handle_at: " +check_name_to_handle_at + +echo -n "SELinux support: " +check_selinux + +echo -n "libtirpc support: " +check_tirpc + +echo -n "libbpf support: " +check_libbpf + +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 -n "color output: " +check_color + +echo >> $CONFIG +echo "%.o: %.c" >> $CONFIG +echo ' $(QUIET_CC)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CPPFLAGS) -c -o $@ $<' >> $CONFIG diff --git a/dcb/.gitignore b/dcb/.gitignore new file mode 100644 index 0000000..3f26856 --- /dev/null +++ b/dcb/.gitignore @@ -0,0 +1 @@ +dcb diff --git a/dcb/Makefile b/dcb/Makefile new file mode 100644 index 0000000..10794c9 --- /dev/null +++ b/dcb/Makefile @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../config.mk + +DCBOBJ = dcb.o \ + dcb_app.o \ + dcb_buffer.o \ + dcb_dcbx.o \ + dcb_ets.o \ + dcb_maxrate.o \ + dcb_pfc.o \ + dcb_apptrust.o \ + dcb_rewr.o +TARGETS += dcb +LDLIBS += -lm + +all: $(TARGETS) $(LIBS) + +dcb: $(DCBOBJ) $(LIBNETLINK) + $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +install: all + for i in $(TARGETS); \ + do install -m 0755 $$i $(DESTDIR)$(SBINDIR); \ + done + +clean: + rm -f $(DCBOBJ) $(TARGETS) diff --git a/dcb/dcb.c b/dcb/dcb.c new file mode 100644 index 0000000..fe0a0f0 --- /dev/null +++ b/dcb/dcb.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <inttypes.h> +#include <stdio.h> +#include <linux/dcbnl.h> +#include <libmnl/libmnl.h> +#include <getopt.h> + +#include "dcb.h" +#include "mnl_utils.h" +#include "namespace.h" +#include "utils.h" +#include "version.h" + +static int dcb_init(struct dcb *dcb) +{ + dcb->buf = malloc(MNL_SOCKET_BUFFER_SIZE); + if (dcb->buf == NULL) { + perror("Netlink buffer allocation"); + return -1; + } + + dcb->nl = mnlu_socket_open(NETLINK_ROUTE); + if (dcb->nl == NULL) { + perror("Open netlink socket"); + goto err_socket_open; + } + + new_json_obj_plain(dcb->json_output); + return 0; + +err_socket_open: + free(dcb->buf); + return -1; +} + +static void dcb_fini(struct dcb *dcb) +{ + delete_json_obj_plain(); + mnl_socket_close(dcb->nl); + free(dcb->buf); +} + +static struct dcb *dcb_alloc(void) +{ + struct dcb *dcb; + + dcb = calloc(1, sizeof(*dcb)); + if (!dcb) + return NULL; + return dcb; +} + +static void dcb_free(struct dcb *dcb) +{ + free(dcb); +} + +struct dcb_get_attribute { + struct dcb *dcb; + int attr; + void *payload; + __u16 payload_len; +}; + +static int dcb_get_attribute_attr_ieee_cb(const struct nlattr *attr, void *data) +{ + struct dcb_get_attribute *ga = data; + + if (mnl_attr_get_type(attr) != ga->attr) + return MNL_CB_OK; + + ga->payload = mnl_attr_get_payload(attr); + ga->payload_len = mnl_attr_get_payload_len(attr); + return MNL_CB_OK; +} + +static int dcb_get_attribute_attr_cb(const struct nlattr *attr, void *data) +{ + if (mnl_attr_get_type(attr) != DCB_ATTR_IEEE) + return MNL_CB_OK; + + return mnl_attr_parse_nested(attr, dcb_get_attribute_attr_ieee_cb, data); +} + +static int dcb_get_attribute_cb(const struct nlmsghdr *nlh, void *data) +{ + return mnl_attr_parse(nlh, sizeof(struct dcbmsg), dcb_get_attribute_attr_cb, data); +} + +static int dcb_get_attribute_bare_cb(const struct nlmsghdr *nlh, void *data) +{ + /* Bare attributes (e.g. DCB_ATTR_DCBX) are not wrapped inside an IEEE + * container, so this does not have to go through unpacking in + * dcb_get_attribute_attr_cb(). + */ + return mnl_attr_parse(nlh, sizeof(struct dcbmsg), + dcb_get_attribute_attr_ieee_cb, data); +} + +struct dcb_set_attribute_response { + int response_attr; +}; + +static int dcb_set_attribute_attr_cb(const struct nlattr *attr, void *data) +{ + struct dcb_set_attribute_response *resp = data; + uint16_t len; + int8_t err; + + if (mnl_attr_get_type(attr) != resp->response_attr) + return MNL_CB_OK; + + len = mnl_attr_get_payload_len(attr); + if (len != 1) { + fprintf(stderr, "Response attribute expected to have size 1, not %d\n", len); + return MNL_CB_ERROR; + } + + /* The attribute is formally u8, but actually an i8 containing a + * negative errno value. + */ + err = mnl_attr_get_u8(attr); + if (err) { + errno = -err; + return MNL_CB_ERROR; + } + + return MNL_CB_OK; +} + +static int dcb_set_attribute_cb(const struct nlmsghdr *nlh, void *data) +{ + return mnl_attr_parse(nlh, sizeof(struct dcbmsg), dcb_set_attribute_attr_cb, data); +} + +static int dcb_talk(struct dcb *dcb, struct nlmsghdr *nlh, mnl_cb_t cb, void *data) +{ + int ret; + + ret = mnl_socket_sendto(dcb->nl, nlh, nlh->nlmsg_len); + if (ret < 0) { + perror("mnl_socket_sendto"); + return -1; + } + + return mnlu_socket_recv_run(dcb->nl, nlh->nlmsg_seq, dcb->buf, MNL_SOCKET_BUFFER_SIZE, + cb, data); +} + +static struct nlmsghdr *dcb_prepare(struct dcb *dcb, const char *dev, + uint32_t nlmsg_type, uint8_t dcb_cmd) +{ + struct dcbmsg dcbm = { + .cmd = dcb_cmd, + }; + struct nlmsghdr *nlh; + + nlh = mnlu_msg_prepare(dcb->buf, nlmsg_type, NLM_F_REQUEST | NLM_F_ACK, + &dcbm, sizeof(dcbm)); + mnl_attr_put_strz(nlh, DCB_ATTR_IFNAME, dev); + return nlh; +} + +static int __dcb_get_attribute(struct dcb *dcb, int command, + const char *dev, int attr, + void **payload_p, __u16 *payload_len_p, + int (*get_attribute_cb)(const struct nlmsghdr *nlh, + void *data)) +{ + struct dcb_get_attribute ga; + struct nlmsghdr *nlh; + int ret; + + nlh = dcb_prepare(dcb, dev, RTM_GETDCB, command); + + ga = (struct dcb_get_attribute) { + .dcb = dcb, + .attr = attr, + .payload = NULL, + }; + ret = dcb_talk(dcb, nlh, get_attribute_cb, &ga); + if (ret) { + perror("Attribute read"); + return ret; + } + if (ga.payload == NULL) { + perror("Attribute not found"); + return -ENOENT; + } + + *payload_p = ga.payload; + *payload_len_p = ga.payload_len; + return 0; +} + +int dcb_get_attribute_va(struct dcb *dcb, const char *dev, int attr, + void **payload_p, __u16 *payload_len_p) +{ + return __dcb_get_attribute(dcb, DCB_CMD_IEEE_GET, dev, attr, + payload_p, payload_len_p, + dcb_get_attribute_cb); +} + +int dcb_get_attribute_bare(struct dcb *dcb, int cmd, const char *dev, int attr, + void **payload_p, __u16 *payload_len_p) +{ + return __dcb_get_attribute(dcb, cmd, dev, attr, + payload_p, payload_len_p, + dcb_get_attribute_bare_cb); +} + +int dcb_get_attribute(struct dcb *dcb, const char *dev, int attr, void *data, size_t data_len) +{ + __u16 payload_len; + void *payload; + int ret; + + ret = dcb_get_attribute_va(dcb, dev, attr, &payload, &payload_len); + if (ret) + return ret; + + if (payload_len != data_len) { + fprintf(stderr, "Wrong len %d, expected %zd\n", payload_len, data_len); + return -EINVAL; + } + + memcpy(data, payload, data_len); + return 0; +} + +static int __dcb_set_attribute(struct dcb *dcb, int command, const char *dev, + int (*cb)(struct dcb *, struct nlmsghdr *, void *), + void *data, int response_attr) +{ + struct dcb_set_attribute_response resp = { + .response_attr = response_attr, + }; + struct nlmsghdr *nlh; + int ret; + + nlh = dcb_prepare(dcb, dev, RTM_SETDCB, command); + + ret = cb(dcb, nlh, data); + if (ret) + return ret; + + errno = 0; + ret = dcb_talk(dcb, nlh, dcb_set_attribute_cb, &resp); + if (ret) { + if (errno) + perror("Attribute write"); + return ret; + } + return 0; +} + +struct dcb_set_attribute_ieee_cb { + int (*cb)(struct dcb *dcb, struct nlmsghdr *nlh, void *data); + void *data; +}; + +static int dcb_set_attribute_ieee_cb(struct dcb *dcb, struct nlmsghdr *nlh, void *data) +{ + struct dcb_set_attribute_ieee_cb *ieee_data = data; + struct nlattr *nest; + int ret; + + nest = mnl_attr_nest_start(nlh, DCB_ATTR_IEEE); + ret = ieee_data->cb(dcb, nlh, ieee_data->data); + if (ret) + return ret; + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +int dcb_set_attribute_va(struct dcb *dcb, int command, const char *dev, + int (*cb)(struct dcb *dcb, struct nlmsghdr *nlh, void *data), + void *data) +{ + struct dcb_set_attribute_ieee_cb ieee_data = { + .cb = cb, + .data = data, + }; + + return __dcb_set_attribute(dcb, command, dev, + &dcb_set_attribute_ieee_cb, &ieee_data, + DCB_ATTR_IEEE); +} + +struct dcb_set_attribute { + int attr; + const void *data; + size_t data_len; +}; + +static int dcb_set_attribute_put(struct dcb *dcb, struct nlmsghdr *nlh, void *data) +{ + struct dcb_set_attribute *dsa = data; + + mnl_attr_put(nlh, dsa->attr, dsa->data_len, dsa->data); + return 0; +} + +int dcb_set_attribute(struct dcb *dcb, const char *dev, int attr, const void *data, size_t data_len) +{ + struct dcb_set_attribute dsa = { + .attr = attr, + .data = data, + .data_len = data_len, + }; + + return dcb_set_attribute_va(dcb, DCB_CMD_IEEE_SET, dev, + &dcb_set_attribute_put, &dsa); +} + +int dcb_set_attribute_bare(struct dcb *dcb, int command, const char *dev, + int attr, const void *data, size_t data_len, + int response_attr) +{ + struct dcb_set_attribute dsa = { + .attr = attr, + .data = data, + .data_len = data_len, + }; + + return __dcb_set_attribute(dcb, command, dev, + &dcb_set_attribute_put, &dsa, response_attr); +} + +void dcb_print_array_u8(const __u8 *array, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) { + print_uint(PRINT_JSON, NULL, NULL, array[i]); + print_uint(PRINT_FP, NULL, "%zd:", i); + print_uint(PRINT_FP, NULL, "%d ", array[i]); + } +} + +void dcb_print_array_u64(const __u64 *array, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) { + print_u64(PRINT_JSON, NULL, NULL, array[i]); + print_uint(PRINT_FP, NULL, "%zd:", i); + print_u64(PRINT_FP, NULL, "%" PRIu64 " ", array[i]); + } +} + +void dcb_print_array_on_off(const __u8 *array, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) { + print_on_off(PRINT_JSON, NULL, NULL, array[i]); + print_uint(PRINT_FP, NULL, "%zd:", i); + print_on_off(PRINT_FP, NULL, "%s ", array[i]); + } +} + +void dcb_print_array_kw(const __u8 *array, size_t array_size, + const char *const kw[], size_t kw_size) +{ + size_t i; + + for (i = 0; i < array_size; i++) { + const char *str = "???"; + __u8 emt = array[i]; + + if (emt < kw_size && kw[emt]) + str = kw[emt]; + print_string(PRINT_JSON, NULL, NULL, str); + print_uint(PRINT_FP, NULL, "%zd:", i); + print_string(PRINT_FP, NULL, "%s ", str); + } +} + +void dcb_print_named_array(const char *json_name, const char *fp_name, + const __u8 *array, size_t size, + void (*print_array)(const __u8 *, size_t)) +{ + open_json_array(PRINT_JSON, json_name); + print_string(PRINT_FP, NULL, "%s ", fp_name); + print_array(array, size); + close_json_array(PRINT_JSON, json_name); +} + +int dcb_parse_mapping(const char *what_key, __u32 key, __u32 max_key, + const char *what_value, __u64 value, __u64 max_value, + void (*set_array)(__u32 index, __u64 value, void *data), + void *set_array_data) +{ + bool is_all = key == (__u32) -1; + + if (!is_all && key > max_key) { + fprintf(stderr, "In %s:%s mapping, %s is expected to be 0..%d\n", + what_key, what_value, what_key, max_key); + return -EINVAL; + } + + if (value > max_value) { + fprintf(stderr, "In %s:%s mapping, %s is expected to be 0..%llu\n", + what_key, what_value, what_value, max_value); + return -EINVAL; + } + + if (is_all) { + for (key = 0; key <= max_key; key++) + set_array(key, value, set_array_data); + } else { + set_array(key, value, set_array_data); + } + + return 0; +} + +void dcb_set_u8(__u32 key, __u64 value, void *data) +{ + __u8 *array = data; + + array[key] = value; +} + +void dcb_set_u32(__u32 key, __u64 value, void *data) +{ + __u32 *array = data; + + array[key] = value; +} + +void dcb_set_u64(__u32 key, __u64 value, void *data) +{ + __u64 *array = data; + + array[key] = value; +} + +int dcb_cmd_parse_dev(struct dcb *dcb, int argc, char **argv, + int (*and_then)(struct dcb *dcb, const char *dev, + int argc, char **argv), + void (*help)(void)) +{ + const char *dev; + + if (!argc || matches(*argv, "help") == 0) { + help(); + return 0; + } else if (matches(*argv, "dev") == 0) { + NEXT_ARG(); + dev = *argv; + if (check_ifname(dev)) { + invarg("not a valid ifname", *argv); + return -EINVAL; + } + NEXT_ARG_FWD(); + return and_then(dcb, dev, argc, argv); + } else { + fprintf(stderr, "Expected `dev DEV', not `%s'", *argv); + help(); + return -EINVAL; + } +} + +static void dcb_help(void) +{ + fprintf(stderr, + "Usage: dcb [ OPTIONS ] OBJECT { COMMAND | help }\n" + " dcb [ -f | --force ] { -b | --batch } filename [ -n | --netns ] netnsname\n" + "where OBJECT := { app | apptrust | buffer | dcbx | ets | maxrate | pfc | rewr }\n" + " OPTIONS := [ -V | --Version | -i | --iec | -j | --json\n" + " | -N | --Numeric | -p | --pretty\n" + " | -s | --statistics | -v | --verbose]\n"); +} + +static int dcb_cmd(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || matches(*argv, "help") == 0) { + dcb_help(); + return 0; + } else if (matches(*argv, "app") == 0) { + return dcb_cmd_app(dcb, argc - 1, argv + 1); + } else if (strcmp(*argv, "apptrust") == 0) { + return dcb_cmd_apptrust(dcb, argc - 1, argv + 1); + } else if (strcmp(*argv, "rewr") == 0) { + return dcb_cmd_rewr(dcb, argc - 1, argv + 1); + } else if (matches(*argv, "buffer") == 0) { + return dcb_cmd_buffer(dcb, argc - 1, argv + 1); + } else if (matches(*argv, "dcbx") == 0) { + return dcb_cmd_dcbx(dcb, argc - 1, argv + 1); + } else if (matches(*argv, "ets") == 0) { + return dcb_cmd_ets(dcb, argc - 1, argv + 1); + } else if (matches(*argv, "maxrate") == 0) { + return dcb_cmd_maxrate(dcb, argc - 1, argv + 1); + } else if (matches(*argv, "pfc") == 0) { + return dcb_cmd_pfc(dcb, argc - 1, argv + 1); + } + + fprintf(stderr, "Object \"%s\" is unknown\n", *argv); + return -ENOENT; +} + +static int dcb_batch_cmd(int argc, char *argv[], void *data) +{ + struct dcb *dcb = data; + + return dcb_cmd(dcb, argc, argv); +} + +static int dcb_batch(struct dcb *dcb, const char *name, bool force) +{ + return do_batch(name, force, dcb_batch_cmd, dcb); +} + +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' }, + { "iec", no_argument, NULL, 'i' }, + { "json", no_argument, NULL, 'j' }, + { "Numeric", no_argument, NULL, 'N' }, + { "pretty", no_argument, NULL, 'p' }, + { "statistics", no_argument, NULL, 's' }, + { "netns", required_argument, NULL, 'n' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + const char *batch_file = NULL; + bool force = false; + struct dcb *dcb; + int opt; + int err; + int ret; + + dcb = dcb_alloc(); + if (!dcb) { + fprintf(stderr, "Failed to allocate memory for dcb\n"); + return EXIT_FAILURE; + } + + while ((opt = getopt_long(argc, argv, "b:fhijn:psvNV", + long_options, NULL)) >= 0) { + + switch (opt) { + case 'V': + printf("dcb utility, iproute2-%s\n", version); + ret = EXIT_SUCCESS; + goto dcb_free; + case 'f': + force = true; + break; + case 'b': + batch_file = optarg; + break; + case 'j': + dcb->json_output = true; + break; + case 'N': + dcb->numeric = true; + break; + case 'p': + pretty = true; + break; + case 's': + dcb->stats = true; + break; + case 'n': + if (netns_switch(optarg)) { + ret = EXIT_FAILURE; + goto dcb_free; + } + break; + case 'i': + dcb->use_iec = true; + break; + case 'h': + dcb_help(); + ret = EXIT_SUCCESS; + goto dcb_free; + default: + fprintf(stderr, "Unknown option.\n"); + dcb_help(); + ret = EXIT_FAILURE; + goto dcb_free; + } + } + + argc -= optind; + argv += optind; + + err = dcb_init(dcb); + if (err) { + ret = EXIT_FAILURE; + goto dcb_free; + } + + if (batch_file) + err = dcb_batch(dcb, batch_file, force); + else + err = dcb_cmd(dcb, argc, argv); + + if (err) { + ret = EXIT_FAILURE; + goto dcb_fini; + } + + ret = EXIT_SUCCESS; + +dcb_fini: + dcb_fini(dcb); +dcb_free: + dcb_free(dcb); + + return ret; +} diff --git a/dcb/dcb.h b/dcb/dcb.h new file mode 100644 index 0000000..b2e8e89 --- /dev/null +++ b/dcb/dcb.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DCB_H__ +#define __DCB_H__ 1 + +#include <libmnl/libmnl.h> +#include <stdbool.h> +#include <stddef.h> + +/* dcb.c */ + +struct dcb { + char *buf; + struct mnl_socket *nl; + bool json_output; + bool stats; + bool use_iec; + bool numeric; +}; + +int dcb_parse_mapping(const char *what_key, __u32 key, __u32 max_key, + const char *what_value, __u64 value, __u64 max_value, + void (*set_array)(__u32 index, __u64 value, void *data), + void *set_array_data); +int dcb_cmd_parse_dev(struct dcb *dcb, int argc, char **argv, + int (*and_then)(struct dcb *dcb, const char *dev, + int argc, char **argv), + void (*help)(void)); + +void dcb_set_u8(__u32 key, __u64 value, void *data); +void dcb_set_u32(__u32 key, __u64 value, void *data); +void dcb_set_u64(__u32 key, __u64 value, void *data); + +int dcb_get_attribute(struct dcb *dcb, const char *dev, int attr, + void *data, size_t data_len); +int dcb_set_attribute(struct dcb *dcb, const char *dev, int attr, + const void *data, size_t data_len); +int dcb_get_attribute_va(struct dcb *dcb, const char *dev, int attr, + void **payload_p, __u16 *payload_len_p); +int dcb_set_attribute_va(struct dcb *dcb, int command, const char *dev, + int (*cb)(struct dcb *dcb, struct nlmsghdr *nlh, void *data), + void *data); +int dcb_get_attribute_bare(struct dcb *dcb, int cmd, const char *dev, int attr, + void **payload_p, __u16 *payload_len_p); +int dcb_set_attribute_bare(struct dcb *dcb, int command, const char *dev, + int attr, const void *data, size_t data_len, + int response_attr); + +void dcb_print_named_array(const char *json_name, const char *fp_name, + const __u8 *array, size_t size, + void (*print_array)(const __u8 *, size_t)); +void dcb_print_array_u8(const __u8 *array, size_t size); +void dcb_print_array_u64(const __u64 *array, size_t size); +void dcb_print_array_on_off(const __u8 *array, size_t size); +void dcb_print_array_kw(const __u8 *array, size_t array_size, + const char *const kw[], size_t kw_size); + +/* dcp_rewr.c */ + +int dcb_cmd_rewr(struct dcb *dcb, int argc, char **argv); + +/* dcb_app.c */ + +struct dcb_app_table { + struct dcb_app *apps; + size_t n_apps; + int attr; +}; + +struct dcb_app_parse_mapping { + __u8 selector; + struct dcb_app_table *tab; + int err; +}; + +#define DCB_APP_PCP_MAX 15 +#define DCB_APP_DSCP_MAX 63 + +int dcb_cmd_app(struct dcb *dcb, int argc, char **argv); + +int dcb_app_get(struct dcb *dcb, const char *dev, struct dcb_app_table *tab); +int dcb_app_add_del(struct dcb *dcb, const char *dev, int command, + const struct dcb_app_table *tab, + bool (*filter)(const struct dcb_app *)); + +bool dcb_app_is_dscp(const struct dcb_app *app); +bool dcb_app_is_pcp(const struct dcb_app *app); + +int dcb_app_print_pid_dscp(__u16 protocol); +int dcb_app_print_pid_pcp(__u16 protocol); +int dcb_app_print_pid_dec(__u16 protocol); +void dcb_app_print_filtered(const struct dcb_app_table *tab, + bool (*filter)(const struct dcb_app *), + void (*print_pid_prio)(int (*print_pid)(__u16), + const struct dcb_app *), + int (*print_pid)(__u16 protocol), + const char *json_name, + const char *fp_name); + +enum ieee_attrs_app dcb_app_attr_type_get(__u8 selector); +bool dcb_app_attr_type_validate(enum ieee_attrs_app type); +bool dcb_app_selector_validate(enum ieee_attrs_app type, __u8 selector); + +int dcb_app_table_push(struct dcb_app_table *tab, struct dcb_app *app); +int dcb_app_table_copy(struct dcb_app_table *a, + const struct dcb_app_table *b); +void dcb_app_table_sort(struct dcb_app_table *tab); +void dcb_app_table_fini(struct dcb_app_table *tab); +void dcb_app_table_remove_existing(struct dcb_app_table *a, + const struct dcb_app_table *b); +void dcb_app_table_remove_replaced(struct dcb_app_table *a, + const struct dcb_app_table *b, + bool (*key_eq)(const struct dcb_app *aa, + const struct dcb_app *ab)); + +int dcb_app_parse_pcp(__u32 *key, const char *arg); +int dcb_app_parse_dscp(__u32 *key, const char *arg); + +/* dcb_apptrust.c */ + +int dcb_cmd_apptrust(struct dcb *dcb, int argc, char **argv); + +/* dcb_buffer.c */ + +int dcb_cmd_buffer(struct dcb *dcb, int argc, char **argv); + +/* dcb_dcbx.c */ + +int dcb_cmd_dcbx(struct dcb *dcb, int argc, char **argv); + +/* dcb_ets.c */ + +int dcb_cmd_ets(struct dcb *dcb, int argc, char **argv); + +/* dcb_maxrate.c */ + +int dcb_cmd_maxrate(struct dcb *dcb, int argc, char **argv); + +/* dcb_pfc.c */ + +int dcb_cmd_pfc(struct dcb *dcb, int argc, char **argv); + +#endif /* __DCB_H__ */ diff --git a/dcb/dcb_app.c b/dcb/dcb_app.c new file mode 100644 index 0000000..7040e62 --- /dev/null +++ b/dcb/dcb_app.c @@ -0,0 +1,933 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <libmnl/libmnl.h> +#include <linux/dcbnl.h> + +#include "dcb.h" +#include "utils.h" +#include "rt_names.h" + +static const char *const pcp_names[DCB_APP_PCP_MAX + 1] = { + "0nd", "1nd", "2nd", "3nd", "4nd", "5nd", "6nd", "7nd", + "0de", "1de", "2de", "3de", "4de", "5de", "6de", "7de" +}; + +static const char *const ieee_attrs_app_names[__DCB_ATTR_IEEE_APP_MAX] = { + [DCB_ATTR_IEEE_APP] = "DCB_ATTR_IEEE_APP", + [DCB_ATTR_DCB_APP] = "DCB_ATTR_DCB_APP" +}; + +static void dcb_app_help_add(void) +{ + fprintf(stderr, + "Usage: dcb app { add | del | replace } dev STRING\n" + " [ default-prio PRIO ]\n" + " [ ethtype-prio ET:PRIO ]\n" + " [ stream-port-prio PORT:PRIO ]\n" + " [ dgram-port-prio PORT:PRIO ]\n" + " [ port-prio PORT:PRIO ]\n" + " [ dscp-prio INTEGER:PRIO ]\n" + " [ pcp-prio PCP:PRIO ]\n" + "\n" + " where PRIO := { 0 .. 7 }\n" + " ET := { 0x600 .. 0xffff }\n" + " PORT := { 1 .. 65535 }\n" + " DSCP := { 0 .. 63 }\n" + " PCP := { 0(nd/de) .. 7(nd/de) }\n" + "\n" + ); +} + +static void dcb_app_help_show_flush(void) +{ + fprintf(stderr, + "Usage: dcb app { show | flush } dev STRING\n" + " [ default-prio ]\n" + " [ ethtype-prio ]\n" + " [ stream-port-prio ]\n" + " [ dgram-port-prio ]\n" + " [ port-prio ]\n" + " [ dscp-prio ]\n" + " [ pcp-prio ]\n" + "\n" + ); +} + +static void dcb_app_help(void) +{ + fprintf(stderr, + "Usage: dcb app help\n" + "\n" + ); + dcb_app_help_show_flush(); + dcb_app_help_add(); +} + +enum ieee_attrs_app dcb_app_attr_type_get(__u8 selector) +{ + switch (selector) { + case IEEE_8021QAZ_APP_SEL_ETHERTYPE: + case IEEE_8021QAZ_APP_SEL_STREAM: + case IEEE_8021QAZ_APP_SEL_DGRAM: + case IEEE_8021QAZ_APP_SEL_ANY: + case IEEE_8021QAZ_APP_SEL_DSCP: + return DCB_ATTR_IEEE_APP; + case DCB_APP_SEL_PCP: + return DCB_ATTR_DCB_APP; + default: + return DCB_ATTR_IEEE_APP_UNSPEC; + } +} + +bool dcb_app_attr_type_validate(enum ieee_attrs_app type) +{ + switch (type) { + case DCB_ATTR_IEEE_APP: + case DCB_ATTR_DCB_APP: + return true; + default: + return false; + } +} + +bool dcb_app_selector_validate(enum ieee_attrs_app type, __u8 selector) +{ + return dcb_app_attr_type_get(selector) == type; +} + +void dcb_app_table_fini(struct dcb_app_table *tab) +{ + free(tab->apps); +} + +int dcb_app_table_push(struct dcb_app_table *tab, struct dcb_app *app) +{ + struct dcb_app *apps = realloc(tab->apps, (tab->n_apps + 1) * sizeof(*tab->apps)); + + if (apps == NULL) { + perror("Cannot allocate APP table"); + return -ENOMEM; + } + + tab->apps = apps; + tab->apps[tab->n_apps++] = *app; + return 0; +} + +void dcb_app_table_remove_existing(struct dcb_app_table *a, + const struct dcb_app_table *b) +{ + size_t ia, ja; + size_t ib; + + for (ia = 0, ja = 0; ia < a->n_apps; ia++) { + struct dcb_app *aa = &a->apps[ia]; + bool found = false; + + for (ib = 0; ib < b->n_apps; ib++) { + const struct dcb_app *ab = &b->apps[ib]; + + if (aa->selector == ab->selector && + aa->protocol == ab->protocol && + aa->priority == ab->priority) { + found = true; + break; + } + } + + if (!found) + a->apps[ja++] = *aa; + } + + a->n_apps = ja; +} + +static bool dcb_app_pid_eq(const struct dcb_app *aa, const struct dcb_app *ab) +{ + return aa->selector == ab->selector && + aa->protocol == ab->protocol; +} + +void dcb_app_table_remove_replaced(struct dcb_app_table *a, + const struct dcb_app_table *b, + bool (*key_eq)(const struct dcb_app *aa, + const struct dcb_app *ab)) +{ + size_t ia, ja; + size_t ib; + + for (ia = 0, ja = 0; ia < a->n_apps; ia++) { + struct dcb_app *aa = &a->apps[ia]; + bool present = false; + bool found = false; + + for (ib = 0; ib < b->n_apps; ib++) { + const struct dcb_app *ab = &b->apps[ib]; + + if (key_eq(aa, ab)) + present = true; + else + continue; + + if (aa->protocol == ab->protocol && + aa->priority == ab->priority) { + found = true; + break; + } + } + + /* Entries that remain in A will be removed, so keep in the + * table only APP entries whose sel/pid is mentioned in B, + * but that do not have the full sel/pid/prio match. + */ + if (present && !found) + a->apps[ja++] = *aa; + } + + a->n_apps = ja; +} + +int dcb_app_table_copy(struct dcb_app_table *a, + const struct dcb_app_table *b) +{ + size_t i; + int ret; + + for (i = 0; i < b->n_apps; i++) { + ret = dcb_app_table_push(a, &b->apps[i]); + if (ret != 0) + return ret; + } + return 0; +} + +static int dcb_app_cmp(const struct dcb_app *a, const struct dcb_app *b) +{ + if (a->protocol < b->protocol) + return -1; + if (a->protocol > b->protocol) + return 1; + return a->priority - b->priority; +} + +static int dcb_app_cmp_cb(const void *a, const void *b) +{ + return dcb_app_cmp(a, b); +} + +void dcb_app_table_sort(struct dcb_app_table *tab) +{ + qsort(tab->apps, tab->n_apps, sizeof(*tab->apps), dcb_app_cmp_cb); +} + +static void dcb_app_parse_mapping_cb(__u32 key, __u64 value, void *data) +{ + struct dcb_app_parse_mapping *pm = data; + struct dcb_app app = { + .selector = pm->selector, + .priority = value, + .protocol = key, + }; + + if (pm->err) + return; + + pm->err = dcb_app_table_push(pm->tab, &app); +} + +static int dcb_app_parse_mapping_ethtype_prio(__u32 key, char *value, void *data) +{ + __u8 prio; + + if (key < 0x600) { + fprintf(stderr, "Protocol IDs < 0x600 are reserved for EtherType\n"); + return -EINVAL; + } + + if (get_u8(&prio, value, 0)) + return -EINVAL; + + return dcb_parse_mapping("ETHTYPE", key, 0xffff, + "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1, + dcb_app_parse_mapping_cb, data); +} + +int dcb_app_parse_pcp(__u32 *key, const char *arg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pcp_names); i++) { + if (pcp_names[i] && strcmp(arg, pcp_names[i]) == 0) { + *key = i; + return 0; + } + } + + return -EINVAL; +} + +static int dcb_app_parse_mapping_pcp_prio(__u32 key, char *value, void *data) +{ + __u8 prio; + + if (get_u8(&prio, value, 0)) + return -EINVAL; + + return dcb_parse_mapping("PCP", key, DCB_APP_PCP_MAX, + "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1, + dcb_app_parse_mapping_cb, data); +} + +int dcb_app_parse_dscp(__u32 *key, const char *arg) +{ + if (parse_mapping_num_all(key, arg) == 0) + return 0; + + if (rtnl_dsfield_a2n(key, arg) != 0) + return -1; + + if (*key & 0x03) { + fprintf(stderr, "The values `%s' uses non-DSCP bits.\n", arg); + return -1; + } + + /* Unshift the value to convert it from dsfield to DSCP. */ + *key >>= 2; + return 0; +} + +static int dcb_app_parse_mapping_dscp_prio(__u32 key, char *value, void *data) +{ + __u8 prio; + + if (get_u8(&prio, value, 0)) + return -EINVAL; + + return dcb_parse_mapping("DSCP", key, DCB_APP_DSCP_MAX, + "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1, + dcb_app_parse_mapping_cb, data); +} + +static int dcb_app_parse_mapping_port_prio(__u32 key, char *value, void *data) +{ + __u8 prio; + + if (key == 0) { + fprintf(stderr, "Port ID of 0 is invalid\n"); + return -EINVAL; + } + + if (get_u8(&prio, value, 0)) + return -EINVAL; + + return dcb_parse_mapping("PORT", key, 0xffff, + "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1, + dcb_app_parse_mapping_cb, data); +} + +static int dcb_app_parse_default_prio(int *argcp, char ***argvp, struct dcb_app_table *tab) +{ + int argc = *argcp; + char **argv = *argvp; + int ret = 0; + + while (argc > 0) { + struct dcb_app app; + __u8 prio; + + if (get_u8(&prio, *argv, 0)) { + ret = 1; + break; + } + + app = (struct dcb_app){ + .selector = IEEE_8021QAZ_APP_SEL_ETHERTYPE, + .protocol = 0, + .priority = prio, + }; + ret = dcb_app_table_push(tab, &app); + if (ret != 0) + break; + + argc--, argv++; + } + + *argcp = argc; + *argvp = argv; + return ret; +} + +static bool dcb_app_is_ethtype(const struct dcb_app *app) +{ + return app->selector == IEEE_8021QAZ_APP_SEL_ETHERTYPE && + app->protocol != 0; +} + +static bool dcb_app_is_default(const struct dcb_app *app) +{ + return app->selector == IEEE_8021QAZ_APP_SEL_ETHERTYPE && + app->protocol == 0; +} + +bool dcb_app_is_dscp(const struct dcb_app *app) +{ + return app->selector == IEEE_8021QAZ_APP_SEL_DSCP; +} + +bool dcb_app_is_pcp(const struct dcb_app *app) +{ + return app->selector == DCB_APP_SEL_PCP; +} + +static bool dcb_app_is_stream_port(const struct dcb_app *app) +{ + return app->selector == IEEE_8021QAZ_APP_SEL_STREAM; +} + +static bool dcb_app_is_dgram_port(const struct dcb_app *app) +{ + return app->selector == IEEE_8021QAZ_APP_SEL_DGRAM; +} + +static bool dcb_app_is_port(const struct dcb_app *app) +{ + return app->selector == IEEE_8021QAZ_APP_SEL_ANY; +} + +int dcb_app_print_pid_dec(__u16 protocol) +{ + return print_uint(PRINT_ANY, NULL, "%u", protocol); +} + +static int dcb_app_print_pid_hex(__u16 protocol) +{ + return print_uint(PRINT_ANY, NULL, "%x", protocol); +} + +int dcb_app_print_pid_dscp(__u16 protocol) +{ + const char *name = rtnl_dsfield_get_name(protocol << 2); + + + if (!is_json_context() && name != NULL) + return print_string(PRINT_FP, NULL, "%s", name); + return print_uint(PRINT_ANY, NULL, "%u", protocol); +} + +int dcb_app_print_pid_pcp(__u16 protocol) +{ + /* Print in numerical form, if protocol value is out-of-range */ + if (protocol > DCB_APP_PCP_MAX) + return print_uint(PRINT_ANY, NULL, "%u", protocol); + + return print_string(PRINT_ANY, NULL, "%s", pcp_names[protocol]); +} + +void dcb_app_print_filtered(const struct dcb_app_table *tab, + bool (*filter)(const struct dcb_app *), + void (*print_pid_prio)(int (*print_pid)(__u16), + const struct dcb_app *), + int (*print_pid)(__u16 protocol), + const char *json_name, + const char *fp_name) +{ + bool first = true; + size_t i; + + for (i = 0; i < tab->n_apps; i++) { + struct dcb_app *app = &tab->apps[i]; + + if (!filter(app)) + continue; + if (first) { + open_json_array(PRINT_JSON, json_name); + print_string(PRINT_FP, NULL, "%s ", fp_name); + first = false; + } + + open_json_array(PRINT_JSON, NULL); + print_pid_prio(print_pid, app); + print_string(PRINT_ANY, NULL, "%s", " "); + close_json_array(PRINT_JSON, NULL); + } + + if (!first) { + close_json_array(PRINT_JSON, json_name); + print_nl(); + } +} + +static void dcb_app_print_pid_prio(int (*print_pid)(__u16 protocol), + const struct dcb_app *app) +{ + print_pid(app->protocol); + print_uint(PRINT_ANY, NULL, ":%u", app->priority); +} + +static void dcb_app_print_ethtype_prio(const struct dcb_app_table *tab) +{ + dcb_app_print_filtered(tab, dcb_app_is_ethtype, + dcb_app_print_pid_prio, dcb_app_print_pid_hex, + "ethtype_prio", "ethtype-prio"); +} + +static void dcb_app_print_pcp_prio(const struct dcb *dcb, + const struct dcb_app_table *tab) +{ + dcb_app_print_filtered(tab, dcb_app_is_pcp, + dcb_app_print_pid_prio, + dcb->numeric ? dcb_app_print_pid_dec : + dcb_app_print_pid_pcp, + "pcp_prio", "pcp-prio"); +} + +static void dcb_app_print_dscp_prio(const struct dcb *dcb, + const struct dcb_app_table *tab) +{ + dcb_app_print_filtered(tab, dcb_app_is_dscp, + dcb_app_print_pid_prio, + dcb->numeric ? dcb_app_print_pid_dec : + dcb_app_print_pid_dscp, + "dscp_prio", "dscp-prio"); +} + +static void dcb_app_print_stream_port_prio(const struct dcb_app_table *tab) +{ + dcb_app_print_filtered(tab, dcb_app_is_stream_port, + dcb_app_print_pid_prio, dcb_app_print_pid_dec, + "stream_port_prio", "stream-port-prio"); +} + +static void dcb_app_print_dgram_port_prio(const struct dcb_app_table *tab) +{ + dcb_app_print_filtered(tab, dcb_app_is_dgram_port, + dcb_app_print_pid_prio, dcb_app_print_pid_dec, + "dgram_port_prio", "dgram-port-prio"); +} + +static void dcb_app_print_port_prio(const struct dcb_app_table *tab) +{ + dcb_app_print_filtered(tab, dcb_app_is_port, + dcb_app_print_pid_prio, dcb_app_print_pid_dec, + "port_prio", "port-prio"); +} + +static void dcb_app_print_default_prio(const struct dcb_app_table *tab) +{ + bool first = true; + size_t i; + + for (i = 0; i < tab->n_apps; i++) { + if (!dcb_app_is_default(&tab->apps[i])) + continue; + if (first) { + open_json_array(PRINT_JSON, "default_prio"); + print_string(PRINT_FP, NULL, "default-prio ", NULL); + first = false; + } + print_uint(PRINT_ANY, NULL, "%u ", tab->apps[i].priority); + } + + if (!first) { + close_json_array(PRINT_JSON, "default_prio"); + print_nl(); + } +} + +static void dcb_app_print(const struct dcb *dcb, const struct dcb_app_table *tab) +{ + dcb_app_print_ethtype_prio(tab); + dcb_app_print_default_prio(tab); + dcb_app_print_dscp_prio(dcb, tab); + dcb_app_print_stream_port_prio(tab); + dcb_app_print_dgram_port_prio(tab); + dcb_app_print_port_prio(tab); + dcb_app_print_pcp_prio(dcb, tab); +} + +static int dcb_app_get_table_attr_cb(const struct nlattr *attr, void *data) +{ + struct dcb_app_table *tab = data; + struct dcb_app *app; + uint16_t type; + int ret; + + type = mnl_attr_get_type(attr); + + if (!dcb_app_attr_type_validate(type)) { + fprintf(stderr, + "Unknown attribute in DCB_ATTR_IEEE_APP_TABLE: %u\n", + type); + return MNL_CB_OK; + } + if (mnl_attr_get_payload_len(attr) < sizeof(struct dcb_app)) { + fprintf(stderr, + "%s payload expected to have size %zu, not %u\n", + ieee_attrs_app_names[type], sizeof(struct dcb_app), + mnl_attr_get_payload_len(attr)); + return MNL_CB_OK; + } + + app = mnl_attr_get_payload(attr); + + /* Check that selector is encapsulated in the right attribute */ + if (!dcb_app_selector_validate(type, app->selector)) { + fprintf(stderr, "Wrong selector for type: %s\n", + ieee_attrs_app_names[type]); + return MNL_CB_OK; + } + + ret = dcb_app_table_push(tab, app); + if (ret != 0) + return MNL_CB_ERROR; + + return MNL_CB_OK; +} + +int dcb_app_get(struct dcb *dcb, const char *dev, struct dcb_app_table *tab) +{ + uint16_t payload_len; + void *payload; + int ret; + + ret = dcb_get_attribute_va(dcb, dev, tab->attr, &payload, &payload_len); + if (ret != 0) + return ret; + + ret = mnl_attr_parse_payload(payload, payload_len, dcb_app_get_table_attr_cb, tab); + if (ret != MNL_CB_OK) + return -EINVAL; + + return 0; +} + +struct dcb_app_add_del { + const struct dcb_app_table *tab; + bool (*filter)(const struct dcb_app *app); +}; + +static int dcb_app_add_del_cb(struct dcb *dcb, struct nlmsghdr *nlh, void *data) +{ + struct dcb_app_add_del *add_del = data; + enum ieee_attrs_app type; + struct nlattr *nest; + size_t i; + + nest = mnl_attr_nest_start(nlh, add_del->tab->attr); + + for (i = 0; i < add_del->tab->n_apps; i++) { + const struct dcb_app *app = &add_del->tab->apps[i]; + type = dcb_app_attr_type_get(app->selector); + + if (add_del->filter == NULL || add_del->filter(app)) + mnl_attr_put(nlh, type, sizeof(*app), app); + } + + mnl_attr_nest_end(nlh, nest); + return 0; +} + +int dcb_app_add_del(struct dcb *dcb, const char *dev, int command, + const struct dcb_app_table *tab, + bool (*filter)(const struct dcb_app *)) +{ + struct dcb_app_add_del add_del = { + .tab = tab, + .filter = filter, + }; + + if (tab->n_apps == 0) + return 0; + + return dcb_set_attribute_va(dcb, command, dev, dcb_app_add_del_cb, &add_del); +} + +static int dcb_cmd_app_parse_add_del(struct dcb *dcb, const char *dev, + int argc, char **argv, struct dcb_app_table *tab) +{ + struct dcb_app_parse_mapping pm = { + .tab = tab, + }; + int ret; + + if (!argc) { + dcb_app_help_add(); + return 0; + } + + do { + if (matches(*argv, "help") == 0) { + dcb_app_help_add(); + return 0; + } else if (matches(*argv, "ethtype-prio") == 0) { + NEXT_ARG(); + pm.selector = IEEE_8021QAZ_APP_SEL_ETHERTYPE; + ret = parse_mapping(&argc, &argv, false, + &dcb_app_parse_mapping_ethtype_prio, + &pm); + } else if (matches(*argv, "default-prio") == 0) { + NEXT_ARG(); + ret = dcb_app_parse_default_prio(&argc, &argv, pm.tab); + if (ret != 0) { + fprintf(stderr, "Invalid default priority %s\n", *argv); + return ret; + } + } else if (matches(*argv, "dscp-prio") == 0) { + NEXT_ARG(); + pm.selector = IEEE_8021QAZ_APP_SEL_DSCP; + ret = parse_mapping_gen(&argc, &argv, + &dcb_app_parse_dscp, + &dcb_app_parse_mapping_dscp_prio, + &pm); + } else if (matches(*argv, "stream-port-prio") == 0) { + NEXT_ARG(); + pm.selector = IEEE_8021QAZ_APP_SEL_STREAM; + ret = parse_mapping(&argc, &argv, false, + &dcb_app_parse_mapping_port_prio, + &pm); + } else if (matches(*argv, "dgram-port-prio") == 0) { + NEXT_ARG(); + pm.selector = IEEE_8021QAZ_APP_SEL_DGRAM; + ret = parse_mapping(&argc, &argv, false, + &dcb_app_parse_mapping_port_prio, + &pm); + } else if (matches(*argv, "port-prio") == 0) { + NEXT_ARG(); + pm.selector = IEEE_8021QAZ_APP_SEL_ANY; + ret = parse_mapping(&argc, &argv, false, + &dcb_app_parse_mapping_port_prio, + &pm); + } else if (strcmp(*argv, "pcp-prio") == 0) { + NEXT_ARG(); + pm.selector = DCB_APP_SEL_PCP; + ret = parse_mapping_gen(&argc, &argv, &dcb_app_parse_pcp, + &dcb_app_parse_mapping_pcp_prio, + &pm); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_app_help_add(); + return -EINVAL; + } + + if (ret != 0) { + fprintf(stderr, "Invalid mapping %s\n", *argv); + return ret; + } + if (pm.err) + return pm.err; + } while (argc > 0); + + return 0; +} + +static int dcb_cmd_app_add(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct dcb_app_table tab = { .attr = DCB_ATTR_IEEE_APP_TABLE }; + int ret; + + ret = dcb_cmd_app_parse_add_del(dcb, dev, argc, argv, &tab); + if (ret != 0) + return ret; + + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_SET, &tab, NULL); + dcb_app_table_fini(&tab); + return ret; +} + +static int dcb_cmd_app_del(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct dcb_app_table tab = { .attr = DCB_ATTR_IEEE_APP_TABLE }; + int ret; + + ret = dcb_cmd_app_parse_add_del(dcb, dev, argc, argv, &tab); + if (ret != 0) + return ret; + + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, NULL); + dcb_app_table_fini(&tab); + return ret; +} + +static int dcb_cmd_app_show(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct dcb_app_table tab = { .attr = DCB_ATTR_IEEE_APP_TABLE }; + int ret; + + ret = dcb_app_get(dcb, dev, &tab); + if (ret != 0) + return ret; + + dcb_app_table_sort(&tab); + + open_json_object(NULL); + + if (!argc) { + dcb_app_print(dcb, &tab); + goto out; + } + + do { + if (matches(*argv, "help") == 0) { + dcb_app_help_show_flush(); + goto out; + } else if (matches(*argv, "ethtype-prio") == 0) { + dcb_app_print_ethtype_prio(&tab); + } else if (matches(*argv, "dscp-prio") == 0) { + dcb_app_print_dscp_prio(dcb, &tab); + } else if (matches(*argv, "stream-port-prio") == 0) { + dcb_app_print_stream_port_prio(&tab); + } else if (matches(*argv, "dgram-port-prio") == 0) { + dcb_app_print_dgram_port_prio(&tab); + } else if (matches(*argv, "port-prio") == 0) { + dcb_app_print_port_prio(&tab); + } else if (matches(*argv, "default-prio") == 0) { + dcb_app_print_default_prio(&tab); + } else if (strcmp(*argv, "pcp-prio") == 0) { + dcb_app_print_pcp_prio(dcb, &tab); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_app_help_show_flush(); + ret = -EINVAL; + goto out; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + +out: + close_json_object(); + dcb_app_table_fini(&tab); + return ret; +} + +static int dcb_cmd_app_flush(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct dcb_app_table tab = { .attr = DCB_ATTR_IEEE_APP_TABLE }; + int ret; + + ret = dcb_app_get(dcb, dev, &tab); + if (ret != 0) + return ret; + + if (!argc) { + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, NULL); + goto out; + } + + do { + if (matches(*argv, "help") == 0) { + dcb_app_help_show_flush(); + goto out; + } else if (matches(*argv, "ethtype-prio") == 0) { + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, + &dcb_app_is_ethtype); + if (ret != 0) + goto out; + } else if (matches(*argv, "default-prio") == 0) { + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, + &dcb_app_is_default); + if (ret != 0) + goto out; + } else if (matches(*argv, "dscp-prio") == 0) { + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, + &dcb_app_is_dscp); + if (ret != 0) + goto out; + } else if (strcmp(*argv, "pcp-prio") == 0) { + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, + &dcb_app_is_pcp); + if (ret != 0) + goto out; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_app_help_show_flush(); + ret = -EINVAL; + goto out; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + +out: + dcb_app_table_fini(&tab); + return ret; +} + +static int dcb_cmd_app_replace(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct dcb_app_table orig = { .attr = DCB_ATTR_IEEE_APP_TABLE }; + struct dcb_app_table tab = { .attr = DCB_ATTR_IEEE_APP_TABLE }; + struct dcb_app_table new = { .attr = DCB_ATTR_IEEE_APP_TABLE }; + int ret; + + ret = dcb_app_get(dcb, dev, &orig); + if (ret != 0) + return ret; + + ret = dcb_cmd_app_parse_add_del(dcb, dev, argc, argv, &tab); + if (ret != 0) + goto out; + + /* Attempts to add an existing entry would be rejected, so drop + * these entries from tab. + */ + ret = dcb_app_table_copy(&new, &tab); + if (ret != 0) + goto out; + dcb_app_table_remove_existing(&new, &orig); + + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_SET, &new, NULL); + if (ret != 0) { + fprintf(stderr, "Could not add new APP entries\n"); + goto out; + } + + /* Remove the obsolete entries. */ + dcb_app_table_remove_replaced(&orig, &tab, dcb_app_pid_eq); + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &orig, NULL); + if (ret != 0) { + fprintf(stderr, "Could not remove replaced APP entries\n"); + goto out; + } + +out: + dcb_app_table_fini(&new); + dcb_app_table_fini(&tab); + dcb_app_table_fini(&orig); + return 0; +} + +int dcb_cmd_app(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || matches(*argv, "help") == 0) { + dcb_app_help(); + return 0; + } else if (matches(*argv, "show") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_app_show, dcb_app_help_show_flush); + } else if (matches(*argv, "flush") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_app_flush, dcb_app_help_show_flush); + } else if (matches(*argv, "add") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_app_add, dcb_app_help_add); + } else if (matches(*argv, "del") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_app_del, dcb_app_help_add); + } else if (matches(*argv, "replace") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_app_replace, dcb_app_help_add); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_app_help(); + return -EINVAL; + } +} diff --git a/dcb/dcb_apptrust.c b/dcb/dcb_apptrust.c new file mode 100644 index 0000000..ed1cba7 --- /dev/null +++ b/dcb/dcb_apptrust.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <errno.h> +#include <linux/dcbnl.h> + +#include "dcb.h" +#include "utils.h" + +static void dcb_apptrust_help_set(void) +{ + fprintf(stderr, + "Usage: dcb apptrust set dev STRING\n" + " [ order [ ethtype | stream-port | dgram-port | port | dscp | pcp ] ]\n" + "\n"); +} + +static void dcb_apptrust_help_show(void) +{ + fprintf(stderr, "Usage: dcb apptrust show dev STRING\n" + " [ order ]\n" + "\n"); +} + +static void dcb_apptrust_help(void) +{ + fprintf(stderr, "Usage: dcb apptrust help\n" + "\n"); + dcb_apptrust_help_show(); + dcb_apptrust_help_set(); +} + +static const char *const selector_names[] = { + [IEEE_8021QAZ_APP_SEL_ETHERTYPE] = "ethtype", + [IEEE_8021QAZ_APP_SEL_STREAM] = "stream-port", + [IEEE_8021QAZ_APP_SEL_DGRAM] = "dgram-port", + [IEEE_8021QAZ_APP_SEL_ANY] = "port", + [IEEE_8021QAZ_APP_SEL_DSCP] = "dscp", + [DCB_APP_SEL_PCP] = "pcp", +}; + +struct dcb_apptrust_table { + __u8 selectors[IEEE_8021QAZ_APP_SEL_MAX + 1]; + int nselectors; +}; + +static bool dcb_apptrust_contains(const struct dcb_apptrust_table *table, + __u8 selector) +{ + int i; + + for (i = 0; i < table->nselectors; i++) + if (table->selectors[i] == selector) + return true; + + return false; +} + +static void dcb_apptrust_print_order(const struct dcb_apptrust_table *table) +{ + const char *str; + __u8 selector; + int i; + + open_json_array(PRINT_JSON, "order"); + print_string(PRINT_FP, NULL, "order: ", NULL); + + for (i = 0; i < table->nselectors; i++) { + selector = table->selectors[i]; + str = selector_names[selector]; + print_string(PRINT_ANY, NULL, "%s ", str); + } + print_nl(); + + close_json_array(PRINT_JSON, "order"); +} + +static void dcb_apptrust_print(const struct dcb_apptrust_table *table) +{ + dcb_apptrust_print_order(table); + print_nl(); +} + +static int dcb_apptrust_get_cb(const struct nlattr *attr, void *data) +{ + struct dcb_apptrust_table *table = data; + uint16_t type; + __u8 selector; + + type = mnl_attr_get_type(attr); + + if (!dcb_app_attr_type_validate(type)) { + fprintf(stderr, + "Unknown attribute in DCB_ATTR_IEEE_APP_TRUST_TABLE: %d\n", + type); + return MNL_CB_OK; + } + + if (mnl_attr_get_payload_len(attr) < 1) { + fprintf(stderr, + "DCB_ATTR_IEEE_APP_TRUST payload expected to have size %zd, not %d\n", + sizeof(struct dcb_app), mnl_attr_get_payload_len(attr)); + return MNL_CB_OK; + } + + selector = mnl_attr_get_u8(attr); + + /* Check that selector is encapsulated in the right attribute */ + if (!dcb_app_selector_validate(type, selector)) { + fprintf(stderr, "Wrong type for selector: %s\n", + selector_names[selector]); + return MNL_CB_OK; + } + + table->selectors[table->nselectors++] = selector; + + return MNL_CB_OK; +} + +static int dcb_apptrust_get(struct dcb *dcb, const char *dev, + struct dcb_apptrust_table *table) +{ + uint16_t payload_len; + void *payload; + int ret; + + ret = dcb_get_attribute_va(dcb, dev, DCB_ATTR_DCB_APP_TRUST_TABLE, + &payload, &payload_len); + if (ret != 0) + return ret; + + ret = mnl_attr_parse_payload(payload, payload_len, dcb_apptrust_get_cb, + table); + if (ret != MNL_CB_OK) + return -EINVAL; + + return 0; +} + +static int dcb_apptrust_set_cb(struct dcb *dcb, struct nlmsghdr *nlh, + void *data) +{ + const struct dcb_apptrust_table *table = data; + enum ieee_attrs_app type; + struct nlattr *nest; + int i; + + nest = mnl_attr_nest_start(nlh, DCB_ATTR_DCB_APP_TRUST_TABLE); + + for (i = 0; i < table->nselectors; i++) { + type = dcb_app_attr_type_get(table->selectors[i]); + mnl_attr_put_u8(nlh, type, table->selectors[i]); + } + + mnl_attr_nest_end(nlh, nest); + + return 0; +} + +static int dcb_apptrust_set(struct dcb *dcb, const char *dev, + const struct dcb_apptrust_table *table) +{ + return dcb_set_attribute_va(dcb, DCB_CMD_IEEE_SET, dev, + &dcb_apptrust_set_cb, (void *)table); +} + +static __u8 dcb_apptrust_parse_selector(const char *selector, int *err) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(selector_names); i++) { + if (selector_names[i] && + strcmp(selector, selector_names[i]) == 0) { + *err = 0; + return i; + } + } + + *err = -EINVAL; + return 0; +} + +static int dcb_apptrust_parse_selector_list(int *argcp, char ***argvp, + struct dcb_apptrust_table *table) +{ + int argc = *argcp, err; + char **argv = *argvp; + __u8 selector; + + /* No trusted selectors ? */ + if (argc == 0) + goto out; + + while (argc > 0) { + selector = dcb_apptrust_parse_selector(*argv, &err); + if (err < 0) + goto out; + + if (table->nselectors > IEEE_8021QAZ_APP_SEL_MAX) + return -ERANGE; + + if (dcb_apptrust_contains(table, selector)) { + fprintf(stderr, "Duplicate selector: %s\n", + selector_names[selector]); + return -EINVAL; + } + + table->selectors[table->nselectors++] = selector; + + NEXT_ARG_FWD(); + } + +out: + *argcp = argc; + *argvp = argv; + + return 0; +} + +static int dcb_cmd_apptrust_set(struct dcb *dcb, const char *dev, int argc, + char **argv) +{ + struct dcb_apptrust_table table = { 0 }; + int ret; + + if (!argc) { + dcb_apptrust_help_set(); + return 0; + } + + do { + if (strcmp(*argv, "help") == 0) { + dcb_apptrust_help_set(); + return 0; + } else if (strcmp(*argv, "order") == 0) { + NEXT_ARG_FWD(); + ret = dcb_apptrust_parse_selector_list(&argc, &argv, + &table); + if (ret < 0) { + fprintf(stderr, "Invalid list of selectors\n"); + return -EINVAL; + } + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_apptrust_help_set(); + return -EINVAL; + } + } while (argc > 0); + + return dcb_apptrust_set(dcb, dev, &table); +} + +static int dcb_cmd_apptrust_show(struct dcb *dcb, const char *dev, int argc, + char **argv) +{ + struct dcb_apptrust_table table = { 0 }; + int ret; + + ret = dcb_apptrust_get(dcb, dev, &table); + if (ret) + return ret; + + open_json_object(NULL); + + if (!argc) { + dcb_apptrust_print(&table); + goto out; + } + + do { + if (strcmp(*argv, "help") == 0) { + dcb_apptrust_help_show(); + return 0; + } else if (strcmp(*argv, "order") == 0) { + dcb_apptrust_print_order(&table); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_apptrust_help_show(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + +out: + close_json_object(); + return 0; +} + +int dcb_cmd_apptrust(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || strcmp(*argv, "help") == 0) { + dcb_apptrust_help(); + return 0; + } else if (strcmp(*argv, "show") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_apptrust_show, + dcb_apptrust_help_show); + } else if (strcmp(*argv, "set") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_apptrust_set, + dcb_apptrust_help_set); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_apptrust_help(); + return -EINVAL; + } +} diff --git a/dcb/dcb_buffer.c b/dcb/dcb_buffer.c new file mode 100644 index 0000000..e6a88a0 --- /dev/null +++ b/dcb/dcb_buffer.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <linux/dcbnl.h> + +#include "dcb.h" +#include "utils.h" + +static void dcb_buffer_help_set(void) +{ + fprintf(stderr, + "Usage: dcb buffer set dev STRING\n" + " [ prio-buffer PRIO-MAP ]\n" + " [ buffer-size SIZE-MAP ]\n" + "\n" + " where PRIO-MAP := [ PRIO-MAP ] PRIO-MAPPING\n" + " PRIO-MAPPING := { all | PRIO }:BUFFER\n" + " SIZE-MAP := [ SIZE-MAP ] SIZE-MAPPING\n" + " SIZE-MAPPING := { all | BUFFER }:INTEGER\n" + " PRIO := { 0 .. 7 }\n" + " BUFFER := { 0 .. 7 }\n" + "\n" + ); +} + +static void dcb_buffer_help_show(void) +{ + fprintf(stderr, + "Usage: dcb buffer show dev STRING\n" + " [ prio-buffer ] [ buffer-size ] [ total-size ]\n" + "\n" + ); +} + +static void dcb_buffer_help(void) +{ + fprintf(stderr, + "Usage: dcb buffer help\n" + "\n" + ); + dcb_buffer_help_show(); + dcb_buffer_help_set(); +} + +static int dcb_buffer_parse_mapping_prio_buffer(__u32 key, char *value, void *data) +{ + struct dcbnl_buffer *buffer = data; + __u8 buf; + + if (get_u8(&buf, value, 0)) + return -EINVAL; + + return dcb_parse_mapping("PRIO", key, IEEE_8021Q_MAX_PRIORITIES - 1, + "BUFFER", buf, DCBX_MAX_BUFFERS - 1, + dcb_set_u8, buffer->prio2buffer); +} + +static int dcb_buffer_parse_mapping_buffer_size(__u32 key, char *value, void *data) +{ + struct dcbnl_buffer *buffer = data; + unsigned int size; + + if (get_size(&size, value)) { + fprintf(stderr, "%d:%s: Illegal value for buffer size\n", key, value); + return -EINVAL; + } + + return dcb_parse_mapping("BUFFER", key, DCBX_MAX_BUFFERS - 1, + "INTEGER", size, -1, + dcb_set_u32, buffer->buffer_size); +} + +static void dcb_buffer_print_total_size(const struct dcbnl_buffer *buffer) +{ + print_size(PRINT_ANY, "total_size", "total-size %s ", buffer->total_size); +} + +static void dcb_buffer_print_prio_buffer(const struct dcbnl_buffer *buffer) +{ + dcb_print_named_array("prio_buffer", "prio-buffer", + buffer->prio2buffer, ARRAY_SIZE(buffer->prio2buffer), + dcb_print_array_u8); +} + +static void dcb_buffer_print_buffer_size(const struct dcbnl_buffer *buffer) +{ + size_t size = ARRAY_SIZE(buffer->buffer_size); + SPRINT_BUF(b); + size_t i; + + open_json_array(PRINT_JSON, "buffer_size"); + print_string(PRINT_FP, NULL, "buffer-size ", NULL); + + for (i = 0; i < size; i++) { + snprintf(b, sizeof(b), "%zd:%%s ", i); + print_size(PRINT_ANY, NULL, b, buffer->buffer_size[i]); + } + + close_json_array(PRINT_JSON, "buffer_size"); +} + +static void dcb_buffer_print(const struct dcbnl_buffer *buffer) +{ + dcb_buffer_print_prio_buffer(buffer); + print_nl(); + + dcb_buffer_print_buffer_size(buffer); + print_nl(); + + dcb_buffer_print_total_size(buffer); + print_nl(); +} + +static int dcb_buffer_get(struct dcb *dcb, const char *dev, struct dcbnl_buffer *buffer) +{ + return dcb_get_attribute(dcb, dev, DCB_ATTR_DCB_BUFFER, buffer, sizeof(*buffer)); +} + +static int dcb_buffer_set(struct dcb *dcb, const char *dev, const struct dcbnl_buffer *buffer) +{ + return dcb_set_attribute(dcb, dev, DCB_ATTR_DCB_BUFFER, buffer, sizeof(*buffer)); +} + +static int dcb_cmd_buffer_set(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct dcbnl_buffer buffer; + int ret; + + if (!argc) { + dcb_buffer_help_set(); + return 0; + } + + ret = dcb_buffer_get(dcb, dev, &buffer); + if (ret) + return ret; + + do { + if (matches(*argv, "help") == 0) { + dcb_buffer_help_set(); + return 0; + } else if (matches(*argv, "prio-buffer") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, + &dcb_buffer_parse_mapping_prio_buffer, &buffer); + if (ret) { + fprintf(stderr, "Invalid priority mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "buffer-size") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, + &dcb_buffer_parse_mapping_buffer_size, &buffer); + if (ret) { + fprintf(stderr, "Invalid buffer size mapping %s\n", *argv); + return ret; + } + continue; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_buffer_help_set(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + + return dcb_buffer_set(dcb, dev, &buffer); +} + +static int dcb_cmd_buffer_show(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct dcbnl_buffer buffer; + int ret; + + ret = dcb_buffer_get(dcb, dev, &buffer); + if (ret) + return ret; + + open_json_object(NULL); + + if (!argc) { + dcb_buffer_print(&buffer); + goto out; + } + + do { + if (matches(*argv, "help") == 0) { + dcb_buffer_help_show(); + return 0; + } else if (matches(*argv, "prio-buffer") == 0) { + dcb_buffer_print_prio_buffer(&buffer); + print_nl(); + } else if (matches(*argv, "buffer-size") == 0) { + dcb_buffer_print_buffer_size(&buffer); + print_nl(); + } else if (matches(*argv, "total-size") == 0) { + dcb_buffer_print_total_size(&buffer); + print_nl(); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_buffer_help_show(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + +out: + close_json_object(); + return 0; +} + +int dcb_cmd_buffer(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || matches(*argv, "help") == 0) { + dcb_buffer_help(); + return 0; + } else if (matches(*argv, "show") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_buffer_show, dcb_buffer_help_show); + } else if (matches(*argv, "set") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_buffer_set, dcb_buffer_help_set); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_buffer_help(); + return -EINVAL; + } +} diff --git a/dcb/dcb_dcbx.c b/dcb/dcb_dcbx.c new file mode 100644 index 0000000..9e3dd2a --- /dev/null +++ b/dcb/dcb_dcbx.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <linux/dcbnl.h> + +#include "dcb.h" +#include "utils.h" + +static void dcb_dcbx_help_set(void) +{ + fprintf(stderr, + "Usage: dcb dcbx set dev STRING\n" + " [ host | lld-managed ]\n" + " [ cee | ieee ] [ static ]\n" + "\n" + ); +} + +static void dcb_dcbx_help_show(void) +{ + fprintf(stderr, + "Usage: dcb dcbx show dev STRING\n" + "\n" + ); +} + +static void dcb_dcbx_help(void) +{ + fprintf(stderr, + "Usage: dcb dcbx help\n" + "\n" + ); + dcb_dcbx_help_show(); + dcb_dcbx_help_set(); +} + +struct dcb_dcbx_flag { + __u8 value; + const char *key_fp; + const char *key_json; +}; + +static const struct dcb_dcbx_flag dcb_dcbx_flags[] = { + {DCB_CAP_DCBX_HOST, "host", NULL }, + {DCB_CAP_DCBX_LLD_MANAGED, "lld-managed", "lld_managed" }, + {DCB_CAP_DCBX_VER_CEE, "cee", NULL }, + {DCB_CAP_DCBX_VER_IEEE, "ieee", NULL }, + {DCB_CAP_DCBX_STATIC, "static", NULL }, +}; + +static void dcb_dcbx_print(__u8 dcbx) +{ + int bit; + int i; + + while ((bit = ffs(dcbx))) { + bool found = false; + + bit--; + for (i = 0; i < ARRAY_SIZE(dcb_dcbx_flags); i++) { + const struct dcb_dcbx_flag *flag = &dcb_dcbx_flags[i]; + + if (flag->value == 1 << bit) { + print_bool(PRINT_JSON, flag->key_json ?: flag->key_fp, + NULL, true); + print_string(PRINT_FP, NULL, "%s ", flag->key_fp); + found = true; + break; + } + } + + if (!found) + fprintf(stderr, "Unknown DCBX bit %#x.\n", 1 << bit); + + dcbx &= ~(1 << bit); + } + + print_nl(); +} + +static int dcb_dcbx_get(struct dcb *dcb, const char *dev, __u8 *dcbx) +{ + __u16 payload_len; + void *payload; + int err; + + err = dcb_get_attribute_bare(dcb, DCB_CMD_IEEE_GET, dev, DCB_ATTR_DCBX, + &payload, &payload_len); + if (err != 0) + return err; + + if (payload_len != 1) { + fprintf(stderr, "DCB_ATTR_DCBX payload has size %d, expected 1.\n", + payload_len); + return -EINVAL; + } + *dcbx = *(__u8 *) payload; + return 0; +} + +static int dcb_dcbx_set(struct dcb *dcb, const char *dev, __u8 dcbx) +{ + return dcb_set_attribute_bare(dcb, DCB_CMD_SDCBX, dev, DCB_ATTR_DCBX, + &dcbx, 1, DCB_ATTR_DCBX); +} + +static int dcb_cmd_dcbx_set(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + __u8 dcbx = 0; + __u8 i; + + if (!argc) { + dcb_dcbx_help_set(); + return 0; + } + + do { + if (matches(*argv, "help") == 0) { + dcb_dcbx_help_set(); + return 0; + } + + for (i = 0; i < ARRAY_SIZE(dcb_dcbx_flags); i++) { + const struct dcb_dcbx_flag *flag = &dcb_dcbx_flags[i]; + + if (matches(*argv, flag->key_fp) == 0) { + dcbx |= flag->value; + NEXT_ARG_FWD(); + goto next; + } + } + + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_dcbx_help_set(); + return -EINVAL; + +next: + ; + } while (argc > 0); + + return dcb_dcbx_set(dcb, dev, dcbx); +} + +static int dcb_cmd_dcbx_show(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + __u8 dcbx; + int ret; + + ret = dcb_dcbx_get(dcb, dev, &dcbx); + if (ret != 0) + return ret; + + while (argc > 0) { + if (matches(*argv, "help") == 0) { + dcb_dcbx_help_show(); + return 0; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_dcbx_help_show(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } + + open_json_object(NULL); + dcb_dcbx_print(dcbx); + close_json_object(); + return 0; +} + +int dcb_cmd_dcbx(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || matches(*argv, "help") == 0) { + dcb_dcbx_help(); + return 0; + } else if (matches(*argv, "show") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_dcbx_show, dcb_dcbx_help_show); + } else if (matches(*argv, "set") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_dcbx_set, dcb_dcbx_help_set); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_dcbx_help(); + return -EINVAL; + } +} diff --git a/dcb/dcb_ets.c b/dcb/dcb_ets.c new file mode 100644 index 0000000..c208810 --- /dev/null +++ b/dcb/dcb_ets.c @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <errno.h> +#include <stdio.h> +#include <linux/dcbnl.h> + +#include "dcb.h" +#include "utils.h" + +static void dcb_ets_help_set(void) +{ + fprintf(stderr, + "Usage: dcb ets set dev STRING\n" + " [ willing { on | off } ]\n" + " [ { tc-tsa | reco-tc-tsa } TSA-MAP ]\n" + " [ { pg-bw | tc-bw | reco-tc-bw } BW-MAP ]\n" + " [ { prio-tc | reco-prio-tc } PRIO-MAP ]\n" + "\n" + " where TSA-MAP := [ TSA-MAP ] TSA-MAPPING\n" + " TSA-MAPPING := { all | TC }:{ strict | cbs | ets | vendor }\n" + " BW-MAP := [ BW-MAP ] BW-MAPPING\n" + " BW-MAPPING := { all | TC }:INTEGER\n" + " PRIO-MAP := [ PRIO-MAP ] PRIO-MAPPING\n" + " PRIO-MAPPING := { all | PRIO }:TC\n" + " TC := { 0 .. 7 }\n" + " PRIO := { 0 .. 7 }\n" + "\n" + ); +} + +static void dcb_ets_help_show(void) +{ + fprintf(stderr, + "Usage: dcb ets show dev STRING\n" + " [ willing ] [ ets-cap ] [ cbs ] [ tc-tsa ]\n" + " [ reco-tc-tsa ] [ pg-bw ] [ tc-bw ] [ reco-tc-bw ]\n" + " [ prio-tc ] [ reco-prio-tc ]\n" + "\n" + ); +} + +static void dcb_ets_help(void) +{ + fprintf(stderr, + "Usage: dcb ets help\n" + "\n" + ); + dcb_ets_help_show(); + dcb_ets_help_set(); +} + +static const char *const tsa_names[] = { + [IEEE_8021QAZ_TSA_STRICT] = "strict", + [IEEE_8021QAZ_TSA_CB_SHAPER] = "cbs", + [IEEE_8021QAZ_TSA_ETS] = "ets", + [IEEE_8021QAZ_TSA_VENDOR] = "vendor", +}; + +static int dcb_ets_parse_mapping_tc_tsa(__u32 key, char *value, void *data) +{ + __u8 tsa; + int ret; + + tsa = parse_one_of("TSA", value, tsa_names, ARRAY_SIZE(tsa_names), &ret); + if (ret) + return ret; + + return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1, + "TSA", tsa, -1U, + dcb_set_u8, data); +} + +static int dcb_ets_parse_mapping_tc_bw(__u32 key, char *value, void *data) +{ + __u8 bw; + + if (get_u8(&bw, value, 0)) + return -EINVAL; + + return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1, + "BW", bw, 100, + dcb_set_u8, data); +} + +static int dcb_ets_parse_mapping_prio_tc(unsigned int key, char *value, void *data) +{ + __u8 tc; + + if (get_u8(&tc, value, 0)) + return -EINVAL; + + return dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1, + "TC", tc, IEEE_8021QAZ_MAX_TCS - 1, + dcb_set_u8, data); +} + +static void dcb_print_array_tsa(const __u8 *array, size_t size) +{ + dcb_print_array_kw(array, size, tsa_names, ARRAY_SIZE(tsa_names)); +} + +static void dcb_ets_print_willing(const struct ieee_ets *ets) +{ + print_on_off(PRINT_ANY, "willing", "willing %s ", ets->willing); +} + +static void dcb_ets_print_ets_cap(const struct ieee_ets *ets) +{ + print_uint(PRINT_ANY, "ets_cap", "ets-cap %d ", ets->ets_cap); +} + +static void dcb_ets_print_cbs(const struct ieee_ets *ets) +{ + print_on_off(PRINT_ANY, "cbs", "cbs %s ", ets->cbs); +} + +static void dcb_ets_print_tc_bw(const struct ieee_ets *ets) +{ + dcb_print_named_array("tc_bw", "tc-bw", + ets->tc_tx_bw, ARRAY_SIZE(ets->tc_tx_bw), + dcb_print_array_u8); +} + +static void dcb_ets_print_pg_bw(const struct ieee_ets *ets) +{ + dcb_print_named_array("pg_bw", "pg-bw", + ets->tc_rx_bw, ARRAY_SIZE(ets->tc_rx_bw), + dcb_print_array_u8); +} + +static void dcb_ets_print_tc_tsa(const struct ieee_ets *ets) +{ + dcb_print_named_array("tc_tsa", "tc-tsa", + ets->tc_tsa, ARRAY_SIZE(ets->tc_tsa), + dcb_print_array_tsa); +} + +static void dcb_ets_print_prio_tc(const struct ieee_ets *ets) +{ + dcb_print_named_array("prio_tc", "prio-tc", + ets->prio_tc, ARRAY_SIZE(ets->prio_tc), + dcb_print_array_u8); +} + +static void dcb_ets_print_reco_tc_bw(const struct ieee_ets *ets) +{ + dcb_print_named_array("reco_tc_bw", "reco-tc-bw", + ets->tc_reco_bw, ARRAY_SIZE(ets->tc_reco_bw), + dcb_print_array_u8); +} + +static void dcb_ets_print_reco_tc_tsa(const struct ieee_ets *ets) +{ + dcb_print_named_array("reco_tc_tsa", "reco-tc-tsa", + ets->tc_reco_tsa, ARRAY_SIZE(ets->tc_reco_tsa), + dcb_print_array_tsa); +} + +static void dcb_ets_print_reco_prio_tc(const struct ieee_ets *ets) +{ + dcb_print_named_array("reco_prio_tc", "reco-prio-tc", + ets->reco_prio_tc, ARRAY_SIZE(ets->reco_prio_tc), + dcb_print_array_u8); +} + +static void dcb_ets_print(const struct ieee_ets *ets) +{ + dcb_ets_print_willing(ets); + dcb_ets_print_ets_cap(ets); + dcb_ets_print_cbs(ets); + print_nl(); + + dcb_ets_print_tc_bw(ets); + print_nl(); + + dcb_ets_print_pg_bw(ets); + print_nl(); + + dcb_ets_print_tc_tsa(ets); + print_nl(); + + dcb_ets_print_prio_tc(ets); + print_nl(); + + dcb_ets_print_reco_tc_bw(ets); + print_nl(); + + dcb_ets_print_reco_tc_tsa(ets); + print_nl(); + + dcb_ets_print_reco_prio_tc(ets); + print_nl(); +} + +static int dcb_ets_get(struct dcb *dcb, const char *dev, struct ieee_ets *ets) +{ + return dcb_get_attribute(dcb, dev, DCB_ATTR_IEEE_ETS, ets, sizeof(*ets)); +} + +static int dcb_ets_validate_bw(const __u8 bw[], const __u8 tsa[], const char *what) +{ + bool has_ets = false; + unsigned int total = 0; + unsigned int tc; + + for (tc = 0; tc < IEEE_8021QAZ_MAX_TCS; tc++) { + if (tsa[tc] == IEEE_8021QAZ_TSA_ETS) { + has_ets = true; + break; + } + } + + /* TC bandwidth is only intended for ETS, but 802.1Q-2018 only requires + * that the sum be 100, and individual entries 0..100. It explicitly + * notes that non-ETS TCs can have non-0 TC bandwidth during + * reconfiguration. + */ + for (tc = 0; tc < IEEE_8021QAZ_MAX_TCS; tc++) { + if (bw[tc] > 100) { + fprintf(stderr, "%d%% for TC %d of %s is not a valid bandwidth percentage, expected 0..100%%\n", + bw[tc], tc, what); + return -EINVAL; + } + total += bw[tc]; + } + + /* This is what 802.1Q-2018 requires. */ + if (total == 100) + return 0; + + /* But this requirement does not make sense for all-strict + * configurations. Anything else than 0 does not make sense: either BW + * has not been reconfigured for the all-strict allocation yet, at which + * point we expect sum of 100. Or it has already been reconfigured, at + * which point accept 0. + */ + if (!has_ets && total == 0) + return 0; + + fprintf(stderr, "Bandwidth percentages in %s sum to %d%%, expected %d%%\n", + what, total, has_ets ? 100 : 0); + return -EINVAL; +} + +static int dcb_ets_set(struct dcb *dcb, const char *dev, const struct ieee_ets *ets) +{ + /* Do not validate pg-bw, which is not standard and has unclear + * meaning. + */ + if (dcb_ets_validate_bw(ets->tc_tx_bw, ets->tc_tsa, "tc-bw") || + dcb_ets_validate_bw(ets->tc_reco_bw, ets->tc_reco_tsa, "reco-tc-bw")) + return -EINVAL; + + return dcb_set_attribute(dcb, dev, DCB_ATTR_IEEE_ETS, ets, sizeof(*ets)); +} + +static int dcb_cmd_ets_set(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct ieee_ets ets; + int ret; + + if (!argc) { + dcb_ets_help_set(); + return 1; + } + + ret = dcb_ets_get(dcb, dev, &ets); + if (ret) + return ret; + + do { + if (matches(*argv, "help") == 0) { + dcb_ets_help_set(); + return 0; + } else if (matches(*argv, "willing") == 0) { + NEXT_ARG(); + ets.willing = parse_on_off("willing", *argv, &ret); + if (ret) + return ret; + } else if (matches(*argv, "tc-tsa") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_tsa, + ets.tc_tsa); + if (ret) { + fprintf(stderr, "Invalid tc-tsa mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "reco-tc-tsa") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_tsa, + ets.tc_reco_tsa); + if (ret) { + fprintf(stderr, "Invalid reco-tc-tsa mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "tc-bw") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw, + ets.tc_tx_bw); + if (ret) { + fprintf(stderr, "Invalid tc-bw mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "pg-bw") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw, + ets.tc_rx_bw); + if (ret) { + fprintf(stderr, "Invalid pg-bw mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "reco-tc-bw") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw, + ets.tc_reco_bw); + if (ret) { + fprintf(stderr, "Invalid reco-tc-bw mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "prio-tc") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_prio_tc, + ets.prio_tc); + if (ret) { + fprintf(stderr, "Invalid prio-tc mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "reco-prio-tc") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_prio_tc, + ets.reco_prio_tc); + if (ret) { + fprintf(stderr, "Invalid reco-prio-tc mapping %s\n", *argv); + return ret; + } + continue; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_ets_help_set(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + + return dcb_ets_set(dcb, dev, &ets); +} + +static int dcb_cmd_ets_show(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct ieee_ets ets; + int ret; + + ret = dcb_ets_get(dcb, dev, &ets); + if (ret) + return ret; + + open_json_object(NULL); + + if (!argc) { + dcb_ets_print(&ets); + goto out; + } + + do { + if (matches(*argv, "help") == 0) { + dcb_ets_help_show(); + return 0; + } else if (matches(*argv, "willing") == 0) { + dcb_ets_print_willing(&ets); + print_nl(); + } else if (matches(*argv, "ets-cap") == 0) { + dcb_ets_print_ets_cap(&ets); + print_nl(); + } else if (matches(*argv, "cbs") == 0) { + dcb_ets_print_cbs(&ets); + print_nl(); + } else if (matches(*argv, "tc-tsa") == 0) { + dcb_ets_print_tc_tsa(&ets); + print_nl(); + } else if (matches(*argv, "reco-tc-tsa") == 0) { + dcb_ets_print_reco_tc_tsa(&ets); + print_nl(); + } else if (matches(*argv, "tc-bw") == 0) { + dcb_ets_print_tc_bw(&ets); + print_nl(); + } else if (matches(*argv, "pg-bw") == 0) { + dcb_ets_print_pg_bw(&ets); + print_nl(); + } else if (matches(*argv, "reco-tc-bw") == 0) { + dcb_ets_print_reco_tc_bw(&ets); + print_nl(); + } else if (matches(*argv, "prio-tc") == 0) { + dcb_ets_print_prio_tc(&ets); + print_nl(); + } else if (matches(*argv, "reco-prio-tc") == 0) { + dcb_ets_print_reco_prio_tc(&ets); + print_nl(); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_ets_help_show(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + +out: + close_json_object(); + return 0; +} + +int dcb_cmd_ets(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || matches(*argv, "help") == 0) { + dcb_ets_help(); + return 0; + } else if (matches(*argv, "show") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_ets_show, dcb_ets_help_show); + } else if (matches(*argv, "set") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_ets_set, dcb_ets_help_set); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_ets_help(); + return -EINVAL; + } +} diff --git a/dcb/dcb_maxrate.c b/dcb/dcb_maxrate.c new file mode 100644 index 0000000..1538c6d --- /dev/null +++ b/dcb/dcb_maxrate.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <linux/dcbnl.h> + +#include "dcb.h" +#include "utils.h" + +static void dcb_maxrate_help_set(void) +{ + fprintf(stderr, + "Usage: dcb maxrate set dev STRING\n" + " [ tc-maxrate RATE-MAP ]\n" + "\n" + " where RATE-MAP := [ RATE-MAP ] RATE-MAPPING\n" + " RATE-MAPPING := { all | TC }:RATE\n" + " TC := { 0 .. 7 }\n" + "\n" + ); +} + +static void dcb_maxrate_help_show(void) +{ + fprintf(stderr, + "Usage: dcb [ -i ] maxrate show dev STRING\n" + " [ tc-maxrate ]\n" + "\n" + ); +} + +static void dcb_maxrate_help(void) +{ + fprintf(stderr, + "Usage: dcb maxrate help\n" + "\n" + ); + dcb_maxrate_help_show(); + dcb_maxrate_help_set(); +} + +static int dcb_maxrate_parse_mapping_tc_maxrate(__u32 key, char *value, void *data) +{ + __u64 rate; + + if (get_rate64(&rate, value)) + return -EINVAL; + + return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1, + "RATE", rate, -1, + dcb_set_u64, data); +} + +static void dcb_maxrate_print_tc_maxrate(struct dcb *dcb, const struct ieee_maxrate *maxrate) +{ + size_t size = ARRAY_SIZE(maxrate->tc_maxrate); + SPRINT_BUF(b); + size_t i; + + open_json_array(PRINT_JSON, "tc_maxrate"); + print_string(PRINT_FP, NULL, "tc-maxrate ", NULL); + + for (i = 0; i < size; i++) { + snprintf(b, sizeof(b), "%zd:%%s ", i); + print_rate(dcb->use_iec, PRINT_ANY, NULL, b, maxrate->tc_maxrate[i]); + } + + close_json_array(PRINT_JSON, "tc_maxrate"); +} + +static void dcb_maxrate_print(struct dcb *dcb, const struct ieee_maxrate *maxrate) +{ + dcb_maxrate_print_tc_maxrate(dcb, maxrate); + print_nl(); +} + +static int dcb_maxrate_get(struct dcb *dcb, const char *dev, struct ieee_maxrate *maxrate) +{ + return dcb_get_attribute(dcb, dev, DCB_ATTR_IEEE_MAXRATE, maxrate, sizeof(*maxrate)); +} + +static int dcb_maxrate_set(struct dcb *dcb, const char *dev, const struct ieee_maxrate *maxrate) +{ + return dcb_set_attribute(dcb, dev, DCB_ATTR_IEEE_MAXRATE, maxrate, sizeof(*maxrate)); +} + +static int dcb_cmd_maxrate_set(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct ieee_maxrate maxrate; + int ret; + + if (!argc) { + dcb_maxrate_help_set(); + return 0; + } + + ret = dcb_maxrate_get(dcb, dev, &maxrate); + if (ret) + return ret; + + do { + if (matches(*argv, "help") == 0) { + dcb_maxrate_help_set(); + return 0; + } else if (matches(*argv, "tc-maxrate") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, + &dcb_maxrate_parse_mapping_tc_maxrate, &maxrate); + if (ret) { + fprintf(stderr, "Invalid mapping %s\n", *argv); + return ret; + } + continue; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_maxrate_help_set(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + + return dcb_maxrate_set(dcb, dev, &maxrate); +} + +static int dcb_cmd_maxrate_show(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct ieee_maxrate maxrate; + int ret; + + ret = dcb_maxrate_get(dcb, dev, &maxrate); + if (ret) + return ret; + + open_json_object(NULL); + + if (!argc) { + dcb_maxrate_print(dcb, &maxrate); + goto out; + } + + do { + if (matches(*argv, "help") == 0) { + dcb_maxrate_help_show(); + return 0; + } else if (matches(*argv, "tc-maxrate") == 0) { + dcb_maxrate_print_tc_maxrate(dcb, &maxrate); + print_nl(); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_maxrate_help_show(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + +out: + close_json_object(); + return 0; +} + +int dcb_cmd_maxrate(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || matches(*argv, "help") == 0) { + dcb_maxrate_help(); + return 0; + } else if (matches(*argv, "show") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_maxrate_show, dcb_maxrate_help_show); + } else if (matches(*argv, "set") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_maxrate_set, dcb_maxrate_help_set); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_maxrate_help(); + return -EINVAL; + } +} diff --git a/dcb/dcb_pfc.c b/dcb/dcb_pfc.c new file mode 100644 index 0000000..aaa0902 --- /dev/null +++ b/dcb/dcb_pfc.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <errno.h> +#include <stdio.h> +#include <linux/dcbnl.h> + +#include "dcb.h" +#include "utils.h" + +static void dcb_pfc_help_set(void) +{ + fprintf(stderr, + "Usage: dcb pfc set dev STRING\n" + " [ prio-pfc PFC-MAP ]\n" + " [ macsec-bypass { on | off } ]\n" + " [ delay INTEGER ]\n" + "\n" + " where PFC-MAP := [ PFC-MAP ] PFC-MAPPING\n" + " PFC-MAPPING := { all | TC }:PFC\n" + " TC := { 0 .. 7 }\n" + " PFC := { on | off }\n" + "\n" + ); +} + +static void dcb_pfc_help_show(void) +{ + fprintf(stderr, + "Usage: dcb [ -s ] pfc show dev STRING\n" + " [ pfc-cap ] [ prio-pfc ] [ macsec-bypass ]\n" + " [ delay ] [ requests ] [ indications ]\n" + "\n" + ); +} + +static void dcb_pfc_help(void) +{ + fprintf(stderr, + "Usage: dcb pfc help\n" + "\n" + ); + dcb_pfc_help_show(); + dcb_pfc_help_set(); +} + +static void dcb_pfc_to_array(__u8 array[IEEE_8021QAZ_MAX_TCS], __u8 pfc_en) +{ + int i; + + for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) + array[i] = !!(pfc_en & (1 << i)); +} + +static void dcb_pfc_from_array(__u8 array[IEEE_8021QAZ_MAX_TCS], __u8 *pfc_en_p) +{ + __u8 pfc_en = 0; + int i; + + for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) { + if (array[i]) + pfc_en |= 1 << i; + } + + *pfc_en_p = pfc_en; +} + +static int dcb_pfc_parse_mapping_prio_pfc(__u32 key, char *value, void *data) +{ + struct ieee_pfc *pfc = data; + __u8 pfc_en[IEEE_8021QAZ_MAX_TCS]; + bool enabled; + int ret; + + dcb_pfc_to_array(pfc_en, pfc->pfc_en); + + enabled = parse_on_off("PFC", value, &ret); + if (ret) + return ret; + + ret = dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1, + "PFC", enabled, -1, + dcb_set_u8, pfc_en); + if (ret) + return ret; + + dcb_pfc_from_array(pfc_en, &pfc->pfc_en); + return 0; +} + +static void dcb_pfc_print_pfc_cap(const struct ieee_pfc *pfc) +{ + print_uint(PRINT_ANY, "pfc_cap", "pfc-cap %d ", pfc->pfc_cap); +} + +static void dcb_pfc_print_macsec_bypass(const struct ieee_pfc *pfc) +{ + print_on_off(PRINT_ANY, "macsec_bypass", "macsec-bypass %s ", pfc->mbc); +} + +static void dcb_pfc_print_delay(const struct ieee_pfc *pfc) +{ + print_uint(PRINT_ANY, "delay", "delay %d ", pfc->delay); +} + +static void dcb_pfc_print_prio_pfc(const struct ieee_pfc *pfc) +{ + __u8 pfc_en[IEEE_8021QAZ_MAX_TCS]; + + dcb_pfc_to_array(pfc_en, pfc->pfc_en); + dcb_print_named_array("prio_pfc", "prio-pfc", + pfc_en, ARRAY_SIZE(pfc_en), &dcb_print_array_on_off); +} + +static void dcb_pfc_print_requests(const struct ieee_pfc *pfc) +{ + open_json_array(PRINT_JSON, "requests"); + print_string(PRINT_FP, NULL, "requests ", NULL); + dcb_print_array_u64(pfc->requests, ARRAY_SIZE(pfc->requests)); + close_json_array(PRINT_JSON, "requests"); +} + +static void dcb_pfc_print_indications(const struct ieee_pfc *pfc) +{ + open_json_array(PRINT_JSON, "indications"); + print_string(PRINT_FP, NULL, "indications ", NULL); + dcb_print_array_u64(pfc->indications, ARRAY_SIZE(pfc->indications)); + close_json_array(PRINT_JSON, "indications"); +} + +static void dcb_pfc_print(const struct dcb *dcb, const struct ieee_pfc *pfc) +{ + dcb_pfc_print_pfc_cap(pfc); + dcb_pfc_print_macsec_bypass(pfc); + dcb_pfc_print_delay(pfc); + print_nl(); + + dcb_pfc_print_prio_pfc(pfc); + print_nl(); + + if (dcb->stats) { + dcb_pfc_print_requests(pfc); + print_nl(); + + dcb_pfc_print_indications(pfc); + print_nl(); + } +} + +static int dcb_pfc_get(struct dcb *dcb, const char *dev, struct ieee_pfc *pfc) +{ + return dcb_get_attribute(dcb, dev, DCB_ATTR_IEEE_PFC, pfc, sizeof(*pfc)); +} + +static int dcb_pfc_set(struct dcb *dcb, const char *dev, const struct ieee_pfc *pfc) +{ + return dcb_set_attribute(dcb, dev, DCB_ATTR_IEEE_PFC, pfc, sizeof(*pfc)); +} + +static int dcb_cmd_pfc_set(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct ieee_pfc pfc; + int ret; + + if (!argc) { + dcb_pfc_help_set(); + return 0; + } + + ret = dcb_pfc_get(dcb, dev, &pfc); + if (ret) + return ret; + + do { + if (matches(*argv, "help") == 0) { + dcb_pfc_help_set(); + return 0; + } else if (matches(*argv, "prio-pfc") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, + &dcb_pfc_parse_mapping_prio_pfc, &pfc); + if (ret) { + fprintf(stderr, "Invalid pfc mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "macsec-bypass") == 0) { + NEXT_ARG(); + pfc.mbc = parse_on_off("macsec-bypass", *argv, &ret); + if (ret) + return ret; + } else if (matches(*argv, "delay") == 0) { + NEXT_ARG(); + /* Do not support the size notations for delay. + * Delay is specified in "bit times", not bits, so + * it is not applicable. At the same time it would + * be confusing that 10Kbit does not mean 10240, + * but 1280. + */ + if (get_u16(&pfc.delay, *argv, 0)) { + fprintf(stderr, "Invalid delay `%s', expected an integer 0..65535\n", + *argv); + return -EINVAL; + } + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_pfc_help_set(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + + return dcb_pfc_set(dcb, dev, &pfc); +} + +static int dcb_cmd_pfc_show(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct ieee_pfc pfc; + int ret; + + ret = dcb_pfc_get(dcb, dev, &pfc); + if (ret) + return ret; + + open_json_object(NULL); + + if (!argc) { + dcb_pfc_print(dcb, &pfc); + goto out; + } + + do { + if (matches(*argv, "help") == 0) { + dcb_pfc_help_show(); + return 0; + } else if (matches(*argv, "prio-pfc") == 0) { + dcb_pfc_print_prio_pfc(&pfc); + print_nl(); + } else if (matches(*argv, "pfc-cap") == 0) { + dcb_pfc_print_pfc_cap(&pfc); + print_nl(); + } else if (matches(*argv, "macsec-bypass") == 0) { + dcb_pfc_print_macsec_bypass(&pfc); + print_nl(); + } else if (matches(*argv, "delay") == 0) { + dcb_pfc_print_delay(&pfc); + print_nl(); + } else if (matches(*argv, "requests") == 0) { + dcb_pfc_print_requests(&pfc); + print_nl(); + } else if (matches(*argv, "indications") == 0) { + dcb_pfc_print_indications(&pfc); + print_nl(); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_pfc_help_show(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + +out: + close_json_object(); + return 0; +} + +int dcb_cmd_pfc(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || matches(*argv, "help") == 0) { + dcb_pfc_help(); + return 0; + } else if (matches(*argv, "show") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_pfc_show, dcb_pfc_help_show); + } else if (matches(*argv, "set") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, + dcb_cmd_pfc_set, dcb_pfc_help_set); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_pfc_help(); + return -EINVAL; + } +} diff --git a/dcb/dcb_rewr.c b/dcb/dcb_rewr.c new file mode 100644 index 0000000..5bb649a --- /dev/null +++ b/dcb/dcb_rewr.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <errno.h> +#include <linux/dcbnl.h> +#include <stdio.h> + +#include "dcb.h" +#include "utils.h" + +static void dcb_rewr_help_add(void) +{ + fprintf(stderr, + "Usage: dcb rewr { add | del | replace } dev STRING\n" + " [ prio-pcp PRIO:PCP ]\n" + " [ prio-dscp PRIO:DSCP ]\n" + "\n" + " where PRIO := { 0 .. 7 }\n" + " PCP := { 0(nd/de) .. 7(nd/de) }\n" + " DSCP := { 0 .. 63 }\n" + "\n" + ); +} + +static void dcb_rewr_help_show_flush(void) +{ + fprintf(stderr, + "Usage: dcb rewr { show | flush } dev STRING\n" + " [ prio-pcp ]\n" + " [ prio-dscp ]\n" + "\n" + ); +} + +static void dcb_rewr_help(void) +{ + fprintf(stderr, + "Usage: dcb rewr help\n" + "\n" + ); + dcb_rewr_help_show_flush(); + dcb_rewr_help_add(); +} + +static void dcb_rewr_parse_mapping_cb(__u32 key, __u64 value, void *data) +{ + struct dcb_app_parse_mapping *pm = data; + struct dcb_app app = { + .selector = pm->selector, + .priority = key, + .protocol = value, + }; + + if (pm->err) + return; + + pm->err = dcb_app_table_push(pm->tab, &app); +} + +static int dcb_rewr_parse_mapping_prio_pcp(__u32 key, char *value, void *data) +{ + __u32 pcp; + + if (dcb_app_parse_pcp(&pcp, value)) + return -EINVAL; + + return dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1, + "PCP", pcp, DCB_APP_PCP_MAX, + dcb_rewr_parse_mapping_cb, data); +} + +static int dcb_rewr_parse_mapping_prio_dscp(__u32 key, char *value, void *data) +{ + __u32 dscp; + + if (dcb_app_parse_dscp(&dscp, value)) + return -EINVAL; + + return dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1, + "DSCP", dscp, DCB_APP_DSCP_MAX, + dcb_rewr_parse_mapping_cb, data); +} + +static void dcb_rewr_print_prio_pid(int (*print_pid)(__u16 protocol), + const struct dcb_app *app) +{ + print_uint(PRINT_ANY, NULL, "%u:", app->priority); + print_pid(app->protocol); +} + +static void dcb_rewr_print_prio_pcp(const struct dcb *dcb, + const struct dcb_app_table *tab) +{ + dcb_app_print_filtered(tab, dcb_app_is_pcp, + dcb_rewr_print_prio_pid, + dcb->numeric ? dcb_app_print_pid_dec : + dcb_app_print_pid_pcp, + "prio_pcp", "prio-pcp"); +} + +static void dcb_rewr_print_prio_dscp(const struct dcb *dcb, + const struct dcb_app_table *tab) +{ + dcb_app_print_filtered(tab, dcb_app_is_dscp, + dcb_rewr_print_prio_pid, + dcb->numeric ? dcb_app_print_pid_dec : + dcb_app_print_pid_dscp, + "prio_dscp", "prio-dscp"); +} + +static void dcb_rewr_print(const struct dcb *dcb, + const struct dcb_app_table *tab) +{ + dcb_rewr_print_prio_pcp(dcb, tab); + dcb_rewr_print_prio_dscp(dcb, tab); +} + +static bool dcb_rewr_prio_eq(const struct dcb_app *aa, const struct dcb_app *ab) +{ + return aa->selector == ab->selector && + aa->priority == ab->priority; +} + +static int dcb_cmd_rewr_parse_add_del(struct dcb *dcb, const char *dev, + int argc, char **argv, + struct dcb_app_table *tab) +{ + struct dcb_app_parse_mapping pm = { + .tab = tab, + }; + int ret; + + if (!argc) { + dcb_rewr_help_add(); + return 0; + } + + do { + if (strcmp(*argv, "help") == 0) { + dcb_rewr_help_add(); + return 0; + } else if (strcmp(*argv, "prio-pcp") == 0) { + NEXT_ARG(); + pm.selector = DCB_APP_SEL_PCP; + ret = parse_mapping(&argc, &argv, false, + &dcb_rewr_parse_mapping_prio_pcp, + &pm); + } else if (strcmp(*argv, "prio-dscp") == 0) { + NEXT_ARG(); + pm.selector = IEEE_8021QAZ_APP_SEL_DSCP; + ret = parse_mapping(&argc, &argv, false, + &dcb_rewr_parse_mapping_prio_dscp, + &pm); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_rewr_help_add(); + return -EINVAL; + } + + if (ret != 0) { + fprintf(stderr, "Invalid mapping %s\n", *argv); + return ret; + } + if (pm.err) + return pm.err; + } while (argc > 0); + + return 0; +} + +static int dcb_cmd_rewr_add(struct dcb *dcb, const char *dev, int argc, + char **argv) +{ + struct dcb_app_table tab = { .attr = DCB_ATTR_DCB_REWR_TABLE }; + int ret; + + ret = dcb_cmd_rewr_parse_add_del(dcb, dev, argc, argv, &tab); + if (ret != 0) + return ret; + + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_SET, &tab, NULL); + dcb_app_table_fini(&tab); + return ret; +} + +static int dcb_cmd_rewr_del(struct dcb *dcb, const char *dev, int argc, + char **argv) +{ + struct dcb_app_table tab = { .attr = DCB_ATTR_DCB_REWR_TABLE }; + int ret; + + ret = dcb_cmd_rewr_parse_add_del(dcb, dev, argc, argv, &tab); + if (ret != 0) + return ret; + + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, NULL); + dcb_app_table_fini(&tab); + return ret; +} + +static int dcb_cmd_rewr_replace(struct dcb *dcb, const char *dev, int argc, + char **argv) +{ + struct dcb_app_table orig = { .attr = DCB_ATTR_DCB_REWR_TABLE }; + struct dcb_app_table tab = { .attr = DCB_ATTR_DCB_REWR_TABLE }; + struct dcb_app_table new = { .attr = DCB_ATTR_DCB_REWR_TABLE }; + int ret; + + ret = dcb_app_get(dcb, dev, &orig); + if (ret != 0) + return ret; + + ret = dcb_cmd_rewr_parse_add_del(dcb, dev, argc, argv, &tab); + if (ret != 0) + goto out; + + /* Attempts to add an existing entry would be rejected, so drop + * these entries from tab. + */ + ret = dcb_app_table_copy(&new, &tab); + if (ret != 0) + goto out; + dcb_app_table_remove_existing(&new, &orig); + + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_SET, &new, NULL); + if (ret != 0) { + fprintf(stderr, "Could not add new rewrite entries\n"); + goto out; + } + + /* Remove the obsolete entries. */ + dcb_app_table_remove_replaced(&orig, &tab, dcb_rewr_prio_eq); + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &orig, NULL); + if (ret != 0) { + fprintf(stderr, "Could not remove replaced rewrite entries\n"); + goto out; + } + +out: + dcb_app_table_fini(&new); + dcb_app_table_fini(&tab); + dcb_app_table_fini(&orig); + return 0; +} + +static int dcb_cmd_rewr_show(struct dcb *dcb, const char *dev, int argc, + char **argv) +{ + struct dcb_app_table tab = { .attr = DCB_ATTR_DCB_REWR_TABLE }; + int ret; + + ret = dcb_app_get(dcb, dev, &tab); + if (ret != 0) + return ret; + + dcb_app_table_sort(&tab); + + open_json_object(NULL); + + if (!argc) { + dcb_rewr_print(dcb, &tab); + goto out; + } + + do { + if (strcmp(*argv, "help") == 0) { + dcb_rewr_help_show_flush(); + goto out; + } else if (strcmp(*argv, "prio-pcp") == 0) { + dcb_rewr_print_prio_pcp(dcb, &tab); + } else if (strcmp(*argv, "prio-dscp") == 0) { + dcb_rewr_print_prio_dscp(dcb, &tab); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_rewr_help_show_flush(); + ret = -EINVAL; + goto out; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + +out: + close_json_object(); + dcb_app_table_fini(&tab); + return ret; +} + +static int dcb_cmd_rewr_flush(struct dcb *dcb, const char *dev, int argc, + char **argv) +{ + struct dcb_app_table tab = { .attr = DCB_ATTR_DCB_REWR_TABLE }; + int ret; + + ret = dcb_app_get(dcb, dev, &tab); + if (ret != 0) + return ret; + + if (!argc) { + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, + NULL); + goto out; + } + + do { + if (strcmp(*argv, "help") == 0) { + dcb_rewr_help_show_flush(); + goto out; + } else if (strcmp(*argv, "prio-pcp") == 0) { + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, + &dcb_app_is_pcp); + if (ret != 0) + goto out; + } else if (strcmp(*argv, "prio-dscp") == 0) { + ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, + &dcb_app_is_dscp); + if (ret != 0) + goto out; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_rewr_help_show_flush(); + ret = -EINVAL; + goto out; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + +out: + dcb_app_table_fini(&tab); + return ret; +} + +int dcb_cmd_rewr(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || strcmp(*argv, "help") == 0) { + dcb_rewr_help(); + return 0; + } else if (strcmp(*argv, "show") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_rewr_show, + dcb_rewr_help_show_flush); + } else if (strcmp(*argv, "flush") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_rewr_flush, + dcb_rewr_help_show_flush); + } else if (strcmp(*argv, "add") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_rewr_add, + dcb_rewr_help_add); + } else if (strcmp(*argv, "del") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_rewr_del, + dcb_rewr_help_add); + } else if (strcmp(*argv, "replace") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_rewr_replace, + dcb_rewr_help_add); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_rewr_help(); + return -EINVAL; + } +} 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..1a1eed7 --- /dev/null +++ b/devlink/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../config.mk + +DEVLINKOBJ = devlink.o mnlg.o +TARGETS += devlink +LDLIBS += -lm + +all: $(TARGETS) $(LIBS) + +devlink: $(DEVLINKOBJ) $(LIBNETLINK) + $(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..dbeb6e3 --- /dev/null +++ b/devlink/devlink.c @@ -0,0 +1,10206 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * devlink.c Devlink tool + * + * 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 <signal.h> +#include <time.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/sysinfo.h> +#define _LINUX_SYSINFO_H /* avoid collision with musl header */ +#include <linux/genetlink.h> +#include <linux/devlink.h> +#include <linux/netlink.h> +#include <linux/net_namespace.h> +#include <libmnl/libmnl.h> +#include <netinet/ether.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/time.h> +#include <rt_names.h> + +#include "version.h" +#include "list.h" +#include "mnlg.h" +#include "mnl_utils.h" +#include "json_print.h" +#include "utils.h" +#include "namespace.h" +#include "libnetlink.h" +#include "../ip/ip_common.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 ESWITCH_ENCAP_MODE_NONE "none" +#define ESWITCH_ENCAP_MODE_BASIC "basic" + +#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; +static bool g_err_suspended; + +#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; + + if (g_err_suspended) + return; + 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 void dummy_signal_handler(int signum) +{ +} + +static int _mnlg_socket_recv_run_intr(struct mnlu_gen_socket *nlg, + mnl_cb_t data_cb, void *data) +{ + struct sigaction act, oact; + int err; + + act.sa_handler = dummy_signal_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_NODEFER; + + sigaction(SIGINT, &act, &oact); + err = mnlu_gen_socket_recv_run(nlg, data_cb, data); + sigaction(SIGINT, &oact, NULL); + if (err < 0 && errno != EINTR) { + pr_err("devlink answers: %s\n", strerror(errno)); + return -errno; + } + return 0; +} + +static int _mnlg_socket_send(struct mnlu_gen_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_group_add(struct mnlu_gen_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 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); +} + +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) { + ifname_map_free(ifname_map); + return NULL; + } + return ifname_map; +} + +static int ifname_map_update(struct ifname_map *ifname_map, const char *ifname) +{ + char *new_ifname; + + new_ifname = strdup(ifname); + if (!new_ifname) + return -ENOMEM; + free(ifname_map->ifname); + ifname_map->ifname = new_ifname; + return 0; +} + +#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) +#define DL_OPT_NETNS BIT(33) +#define DL_OPT_TRAP_POLICER_ID BIT(34) +#define DL_OPT_TRAP_POLICER_RATE BIT(35) +#define DL_OPT_TRAP_POLICER_BURST BIT(36) +#define DL_OPT_HEALTH_REPORTER_AUTO_DUMP BIT(37) +#define DL_OPT_PORT_FUNCTION_HW_ADDR BIT(38) +#define DL_OPT_FLASH_OVERWRITE BIT(39) +#define DL_OPT_RELOAD_ACTION BIT(40) +#define DL_OPT_RELOAD_LIMIT BIT(41) +#define DL_OPT_PORT_FLAVOUR BIT(42) +#define DL_OPT_PORT_PFNUMBER BIT(43) +#define DL_OPT_PORT_SFNUMBER BIT(44) +#define DL_OPT_PORT_FUNCTION_STATE BIT(45) +#define DL_OPT_PORT_CONTROLLER BIT(46) +#define DL_OPT_PORT_FN_RATE_TYPE BIT(47) +#define DL_OPT_PORT_FN_RATE_TX_SHARE BIT(48) +#define DL_OPT_PORT_FN_RATE_TX_MAX BIT(49) +#define DL_OPT_PORT_FN_RATE_NODE_NAME BIT(50) +#define DL_OPT_PORT_FN_RATE_PARENT BIT(51) +#define DL_OPT_LINECARD BIT(52) +#define DL_OPT_LINECARD_TYPE BIT(53) +#define DL_OPT_SELFTESTS BIT(54) +#define DL_OPT_PORT_FN_RATE_TX_PRIORITY BIT(55) +#define DL_OPT_PORT_FN_RATE_TX_WEIGHT BIT(56) +#define DL_OPT_PORT_FN_CAPS BIT(57) + +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_enabled; + enum devlink_eswitch_encap_mode eswitch_encap_mode; + const char *resource_path; + __u64 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; + __u64 region_address; + __u64 region_length; + const char *flash_file_name; + const char *flash_component; + const char *reporter_name; + __u64 reporter_graceful_period; + bool reporter_auto_recover; + bool reporter_auto_dump; + const char *trap_name; + const char *trap_group_name; + enum devlink_trap_action trap_action; + bool netns_is_pid; + uint32_t netns; + uint32_t trap_policer_id; + __u64 trap_policer_rate; + __u64 trap_policer_burst; + char port_function_hw_addr[MAX_ADDR_LEN]; + uint32_t port_function_hw_addr_len; + uint32_t overwrite_mask; + enum devlink_reload_action reload_action; + enum devlink_reload_limit reload_limit; + uint32_t port_controller; + uint32_t port_sfnumber; + uint16_t port_flavour; + uint16_t port_pfnumber; + uint8_t port_fn_state; + uint16_t rate_type; + uint64_t rate_tx_share; + uint64_t rate_tx_max; + uint32_t rate_tx_priority; + uint32_t rate_tx_weight; + char *rate_node_name; + const char *rate_parent_node; + uint32_t linecard_index; + const char *linecard_type; + bool selftests_opt[DEVLINK_ATTR_SELFTEST_ID_MAX + 1]; + struct nla_bitfield32 port_fn_caps; +}; + +struct dl { + struct mnlu_gen_socket nlg; + struct list_head ifname_map_list; + int argc; + char **argv; + char *handle_argv; + bool no_nice_names; + struct dl_opts opts; + bool json_output; + bool pretty_output; + bool verbose; + bool stats; + bool hex; + bool use_iec; + bool map_loaded; + 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 void dl_arg_dec(struct dl *dl) +{ + 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 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) + print_int(PRINT_JSON, NULL, NULL, 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_name(struct dl *dl, const char *name) +{ + __pr_out_indent_newline(dl); + if (dl->json_output) + print_string(PRINT_JSON, name, NULL, NULL); + else + pr_out("%s:", name); +} + +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 print_string_name_value(name, "unlimited"); + + if (dl->json_output) + print_u64(PRINT_JSON, name, NULL, val); + else + pr_out("%s %"PRIu64, name, val); +} + +static void pr_out_section_start(struct dl *dl, const char *name) +{ + if (dl->json_output) { + open_json_object(NULL); + open_json_object(name); + } +} + +static void pr_out_section_end(struct dl *dl) +{ + if (dl->json_output) { + if (dl->arr_last.present) + close_json_array(PRINT_JSON, NULL); + close_json_object(); + close_json_object(); + } +} + +static void pr_out_array_start(struct dl *dl, const char *name) +{ + if (dl->json_output) { + open_json_array(PRINT_JSON, name); + } 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) { + close_json_array(PRINT_JSON, NULL); + } 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) { + open_json_object(name); + } 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) { + close_json_object(); + } else { + __pr_out_indent_dec(); + __pr_out_indent_dec(); + } +} + +static void pr_out_entry_start(struct dl *dl) +{ + if (dl->json_output) + open_json_object(NULL); +} + +static void pr_out_entry_end(struct dl *dl) +{ + if (dl->json_output) + close_json_object(); + else + __pr_out_newline(); +} + +static void check_indent_newline(struct dl *dl) +{ + __pr_out_indent_newline(dl); + + if (g_indent_newline && !is_json_context()) { + printf("%s", g_indent_str); + g_indent_newline = false; + } + g_new_line_count = 0; +} + +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_PORT_LANES] = MNL_TYPE_U32, + [DEVLINK_ATTR_PORT_SPLITTABLE] = MNL_TYPE_U8, + [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_BOARD_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, + [DEVLINK_ATTR_DEV_STATS] = MNL_TYPE_NESTED, + [DEVLINK_ATTR_RELOAD_STATS] = MNL_TYPE_NESTED, + [DEVLINK_ATTR_RELOAD_STATS_ENTRY] = MNL_TYPE_NESTED, + [DEVLINK_ATTR_RELOAD_ACTION] = MNL_TYPE_U8, + [DEVLINK_ATTR_RELOAD_STATS_LIMIT] = MNL_TYPE_U8, + [DEVLINK_ATTR_RELOAD_STATS_VALUE] = MNL_TYPE_U32, + [DEVLINK_ATTR_REMOTE_RELOAD_STATS] = MNL_TYPE_NESTED, + [DEVLINK_ATTR_RELOAD_ACTION_INFO] = MNL_TYPE_NESTED, + [DEVLINK_ATTR_RELOAD_ACTION_STATS] = MNL_TYPE_NESTED, + [DEVLINK_ATTR_TRAP_POLICER_ID] = MNL_TYPE_U32, + [DEVLINK_ATTR_TRAP_POLICER_RATE] = MNL_TYPE_U64, + [DEVLINK_ATTR_TRAP_POLICER_BURST] = MNL_TYPE_U64, + [DEVLINK_ATTR_LINECARD_INDEX] = MNL_TYPE_U32, + [DEVLINK_ATTR_LINECARD_STATE] = MNL_TYPE_U8, + [DEVLINK_ATTR_LINECARD_TYPE] = MNL_TYPE_STRING, + [DEVLINK_ATTR_LINECARD_SUPPORTED_TYPES] = MNL_TYPE_NESTED, + [DEVLINK_ATTR_NESTED_DEVLINK] = MNL_TYPE_NESTED, + [DEVLINK_ATTR_SELFTESTS] = MNL_TYPE_NESTED, + [DEVLINK_ATTR_NETNS_ID] = MNL_TYPE_U32, +}; + +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, + [DEVLINK_ATTR_STATS_RX_DROPPED] = 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 const enum mnl_attr_data_type +devlink_function_policy[DEVLINK_PORT_FUNCTION_ATTR_MAX + 1] = { + [DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR ] = MNL_TYPE_BINARY, + [DEVLINK_PORT_FN_ATTR_STATE] = MNL_TYPE_U8, + [DEVLINK_PORT_FN_ATTR_DEVLINK] = MNL_TYPE_NESTED, +}; + +static int function_attr_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_PORT_FUNCTION_ATTR_MAX) < 0) + return MNL_CB_OK; + + type = mnl_attr_get_type(attr); + if (mnl_attr_validate(attr, devlink_function_policy[type]) < 0) + return MNL_CB_ERROR; + + tb[type] = attr; + return MNL_CB_OK; +} + +static int ifname_map_add(struct dl *dl, const char *ifname, + const char *bus_name, const char *dev_name, + uint32_t port_index) +{ + struct ifname_map *ifname_map; + + ifname_map = ifname_map_alloc(bus_name, dev_name, port_index, ifname); + if (!ifname_map) + return -ENOMEM; + list_add(&ifname_map->list, &dl->ifname_map_list); + return 0; +} + +static void ifname_map_del(struct ifname_map *ifname_map) +{ + list_del(&ifname_map->list); + ifname_map_free(ifname_map); +} + +static int ifname_map_rtnl_port_parse(struct dl *dl, const char *ifname, + struct rtattr *nest) +{ + struct rtattr *tb[DEVLINK_ATTR_MAX + 1]; + const char *bus_name; + const char *dev_name; + uint32_t port_index; + + parse_rtattr_nested(tb, DEVLINK_ATTR_MAX, nest); + if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] || + !tb[DEVLINK_ATTR_PORT_INDEX]) + return -ENOENT; + + bus_name = rta_getattr_str(tb[DEVLINK_ATTR_BUS_NAME]); + dev_name = rta_getattr_str(tb[DEVLINK_ATTR_DEV_NAME]); + port_index = rta_getattr_u32(tb[DEVLINK_ATTR_PORT_INDEX]); + return ifname_map_add(dl, ifname, bus_name, dev_name, port_index); +} + +static int ifname_map_rtnl_init(struct dl *dl, const char *ifname) +{ + 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 = AF_UNSPEC, + }; + struct rtattr *tb[IFLA_MAX + 1]; + struct rtnl_handle rth; + struct ifinfomsg *ifi; + struct nlmsghdr *n; + int len; + int err; + + if (rtnl_open(&rth, 0) < 0) { + pr_err("Cannot open rtnetlink\n"); + return -EINVAL; + } + + addattr_l(&req.n, sizeof(req), + !check_ifname(ifname) ? IFLA_IFNAME : IFLA_ALT_IFNAME, + ifname, strlen(ifname) + 1); + + if (rtnl_talk(&rth, &req.n, &n) < 0) { + err = -EINVAL; + goto out; + } + + if (n->nlmsg_type != RTM_NEWLINK) { + err = -EINVAL; + goto out; + } + + ifi = NLMSG_DATA(n); + len = n->nlmsg_len; + + len -= NLMSG_LENGTH(sizeof(*ifi)); + if (len < 0) { + err = -EINVAL; + goto out; + } + + parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(ifi), len, NLA_F_NESTED); + if (!tb[IFLA_DEVLINK_PORT]) { + err = -EOPNOTSUPP; + goto out; + } + + err = ifname_map_rtnl_port_parse(dl, ifname, tb[IFLA_DEVLINK_PORT]); + +out: + rtnl_close(&rth); + return err; +} + +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; + const char *bus_name; + const char *dev_name; + uint32_t port_index; + const char *port_ifname; + 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_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_index = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]); + port_ifname = mnl_attr_get_str(tb[DEVLINK_ATTR_PORT_NETDEV_NAME]); + err = ifname_map_add(dl, port_ifname, bus_name, dev_name, port_index); + if (err) + return MNL_CB_ERROR; + + 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) { + ifname_map_del(ifname_map); + } +} + +static void ifname_map_init(struct dl *dl) +{ + INIT_LIST_HEAD(&dl->ifname_map_list); +} + +static int ifname_map_load(struct dl *dl, const char *ifname) +{ + struct mnlu_gen_socket nlg_map; + struct nlmsghdr *nlh; + int err; + + if (ifname) { + err = ifname_map_rtnl_init(dl, ifname); + if (!err) + return 0; + /* In case kernel does not support devlink port info passed over + * RT netlink, fall-back to ports dump. + */ + } + + err = mnlu_gen_socket_open(&nlg_map, DEVLINK_GENL_NAME, + DEVLINK_GENL_VERSION); + if (err) + return err; + + nlh = mnlu_gen_socket_cmd_prepare(&nlg_map, DEVLINK_CMD_PORT_GET, + NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); + + err = mnlu_gen_socket_sndrcv(&nlg_map, nlh, ifname_map_cb, dl); + if (err) + ifname_map_fini(dl); + + mnlu_gen_socket_close(&nlg_map); + return err; +} + +static int ifname_map_check_load(struct dl *dl, const char *ifname) +{ + int err; + + if (dl->map_loaded) + return 0; + + err = ifname_map_load(dl, ifname); + if (err) { + pr_err("Failed to create index map\n"); + return err; + } + dl->map_loaded = true; + 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; + int err; + + err = ifname_map_check_load(dl, ifname); + if (err) + return err; + + 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, + const char **p_ifname) +{ + struct ifname_map *ifname_map; + + int err; + + err = ifname_map_check_load(dl, NULL); + if (err) + return err; + + 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) { + /* In case non-NULL ifname is passed, update the + * looked-up entry. + */ + if (*p_ifname) + return ifname_map_update(ifname_map, *p_ifname); + + *p_ifname = ifname_map->ifname; + return 0; + } + } + return -ENOENT; +} + +static int strtobool(const char *str, bool *p_val) +{ + bool val; + + if (!strcmp(str, "true") || !strcmp(str, "1") || + !strcmp(str, "enable")) + val = true; + else if (!strcmp(str, "false") || !strcmp(str, "0") || + !strcmp(str, "disable")) + val = false; + else + return -EINVAL; + *p_val = val; + return 0; +} + +static int ident_str_validate(char *str, unsigned int expected) +{ + if (!str) + return -ENOENT; + + if (get_str_char_count(str, '/') != expected) { + pr_err("Wrong identification string format.\n"); + return -EINVAL; + } + + return 0; +} + +static int __dl_argv_handle(char *str, char **p_bus_name, char **p_dev_name) +{ + int err; + + err = str_split_by_char(str, p_bus_name, p_dev_name, '/'); + if (err) { + pr_err("Devlink identification (\"bus_name/dev_name\") \"%s\" is invalid\n", str); + return err; + } + return 0; +} + +static int dl_argv_handle(char *str, char **p_bus_name, char **p_dev_name) +{ + int err; + + err = ident_str_validate(str, 1); + if (err) { + pr_err("Devlink identification (\"bus_name/dev_name\") expected\n"); + return err; + } + 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 = str_split_by_char(str, &handlestr, &portstr, '/'); + if (err) { + pr_err("Port identification \"%s\" is invalid\n", str); + return err; + } + err = get_u32(p_port_index, portstr, 10); + if (err) { + pr_err("Port index \"%s\" is not a number or not within range\n", + portstr); + return err; + } + err = str_split_by_char(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 *str, char **p_bus_name, + char **p_dev_name, uint32_t *p_port_index) +{ + unsigned int slash_count; + + if (!str) { + pr_err("Port identification (\"bus_name/dev_name/port_index\" or \"netdev ifname\") expected.\n"); + return -ENOENT; + } + slash_count = get_str_char_count(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 *str, char **p_bus_name, + char **p_dev_name, uint32_t *p_port_index, + uint64_t *p_handle_bit) +{ + 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 -ENOENT; + } + slash_count = get_str_char_count(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_name(char *str, char **p_bus_name, + char **p_dev_name, char **p_name) +{ + char *handlestr; + int err; + + err = str_split_by_char(str, &handlestr, p_name, '/'); + if (err) + return err; + + return str_split_by_char(handlestr, p_bus_name, p_dev_name, '/'); +} + +static int dl_argv_handle_region(char *str, char **p_bus_name, + char **p_dev_name, char **p_region) +{ + int err; + + err = ident_str_validate(str, 2); + if (err) { + pr_err("Expected \"bus_name/dev_name/region\" identification.\n"); + return err; + } + + err = __dl_argv_handle_name(str, p_bus_name, p_dev_name, p_region); + if (err) + pr_err("Region identification \"%s\" is invalid\n", str); + return err; +} + + +static int dl_argv_handle_rate_node(char *str, char **p_bus_name, + char **p_dev_name, char **p_node) +{ + int err; + + err = ident_str_validate(str, 2); + if (err) { + pr_err("Expected \"bus_name/dev_name/node\" identification.\n"); + return err; + } + + err = __dl_argv_handle_name(str, p_bus_name, p_dev_name, p_node); + if (err) { + pr_err("Node identification \"%s\" is invalid\n", str); + return err; + } + + if (!**p_node || strspn(*p_node, "0123456789") == strlen(*p_node)) { + err = -EINVAL; + pr_err("Node name cannot be a devlink port index or empty.\n"); + } + + return err; +} + +static int dl_argv_handle_rate(char *str, char **p_bus_name, + char **p_dev_name, uint32_t *p_port_index, + char **p_node_name, uint64_t *p_handle_bit) +{ + char *identifier; + int err; + + err = ident_str_validate(str, 2); + if (err) { + pr_err("Expected \"bus_name/dev_name/node\" or " + "\"bus_name/dev_name/port_index\" identification.\n"); + return err; + } + + err = __dl_argv_handle_name(str, p_bus_name, p_dev_name, &identifier); + if (err) { + pr_err("Identification \"%s\" is invalid\n", str); + return err; + } + + if (!*identifier) { + pr_err("Identifier cannot be empty"); + return -EINVAL; + } + + if (strspn(identifier, "0123456789") == strlen(identifier)) { + err = get_u32(p_port_index, identifier, 10); + if (err) { + pr_err("Port index \"%s\" is not a number" + " or not within range\n", identifier); + return err; + } + *p_handle_bit = DL_OPT_HANDLEP; + } else { + *p_handle_bit = DL_OPT_PORT_FN_RATE_NODE_NAME; + *p_node_name = identifier; + } + return 0; +} + +static int dl_argv_uint64_t(struct dl *dl, __u64 *p_val) +{ + char *str = dl_argv_next(dl); + int err; + + if (!str) { + pr_err("Unsigned number argument expected\n"); + return -EINVAL; + } + + err = get_u64(p_val, str, 10); + 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 = get_u32(p_val, str, 10); + 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 = get_u16(p_val, str, 10); + 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 +eswitch_encap_mode_get(const char *typestr, + enum devlink_eswitch_encap_mode *p_encap_mode) +{ + /* The initial implementation incorrectly accepted "enable"/"disable". + * Carry it to maintain backward compatibility. + */ + if (strcmp(typestr, "disable") == 0 || + strcmp(typestr, ESWITCH_ENCAP_MODE_NONE) == 0) { + *p_encap_mode = DEVLINK_ESWITCH_ENCAP_MODE_NONE; + } else if (strcmp(typestr, "enable") == 0 || + strcmp(typestr, ESWITCH_ENCAP_MODE_BASIC) == 0) { + *p_encap_mode = DEVLINK_ESWITCH_ENCAP_MODE_BASIC; + } else { + pr_err("Unknown eswitch encap mode \"%s\"\n", typestr); + return -EINVAL; + } + return 0; +} + +static int flash_overwrite_section_get(const char *sectionstr, uint32_t *mask) +{ + if (strcmp(sectionstr, "settings") == 0) { + *mask |= DEVLINK_FLASH_OVERWRITE_SETTINGS; + } else if (strcmp(sectionstr, "identifiers") == 0) { + *mask |= DEVLINK_FLASH_OVERWRITE_IDENTIFIERS; + } else { + pr_err("Unknown overwrite section \"%s\"\n", sectionstr); + 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 if (strcmp(actionstr, "mirror") == 0) { + *p_action = DEVLINK_TRAP_ACTION_MIRROR; + } else { + pr_err("Unknown trap action \"%s\"\n", actionstr); + return -EINVAL; + } + return 0; +} + +static int hw_addr_parse(const char *addrstr, char *hw_addr, uint32_t *len) +{ + int alen; + + alen = ll_addr_a2n(hw_addr, MAX_ADDR_LEN, addrstr); + if (alen < 0) + return -EINVAL; + *len = alen; + return 0; +} + +static int reload_action_get(struct dl *dl, const char *actionstr, + enum devlink_reload_action *action) +{ + if (strcmp(actionstr, "driver_reinit") == 0) { + *action = DEVLINK_RELOAD_ACTION_DRIVER_REINIT; + } else if (strcmp(actionstr, "fw_activate") == 0) { + *action = DEVLINK_RELOAD_ACTION_FW_ACTIVATE; + } else { + pr_err("Unknown reload action \"%s\"\n", actionstr); + return -EINVAL; + } + return 0; +} + +static int reload_limit_get(struct dl *dl, const char *limitstr, + enum devlink_reload_limit *limit) +{ + if (strcmp(limitstr, "no_reset") == 0) { + *limit = DEVLINK_RELOAD_LIMIT_NO_RESET; + } else { + pr_err("Unknown reload limit \"%s\"\n", limitstr); + return -EINVAL; + } + return 0; +} + +static struct str_num_map port_flavour_map[] = { + { .str = "physical", .num = DEVLINK_PORT_FLAVOUR_PHYSICAL }, + { .str = "cpu", .num = DEVLINK_PORT_FLAVOUR_CPU }, + { .str = "dsa", .num = DEVLINK_PORT_FLAVOUR_DSA }, + { .str = "pcipf", .num = DEVLINK_PORT_FLAVOUR_PCI_PF }, + { .str = "pcivf", .num = DEVLINK_PORT_FLAVOUR_PCI_VF }, + { .str = "pcisf", .num = DEVLINK_PORT_FLAVOUR_PCI_SF }, + { .str = "virtual", .num = DEVLINK_PORT_FLAVOUR_VIRTUAL}, + { .str = NULL, }, +}; + +static struct str_num_map port_fn_state_map[] = { + { .str = "inactive", .num = DEVLINK_PORT_FN_STATE_INACTIVE}, + { .str = "active", .num = DEVLINK_PORT_FN_STATE_ACTIVE }, + { .str = NULL, } +}; + +static struct str_num_map port_fn_opstate_map[] = { + { .str = "attached", .num = DEVLINK_PORT_FN_OPSTATE_ATTACHED}, + { .str = "detached", .num = DEVLINK_PORT_FN_OPSTATE_DETACHED}, + { .str = NULL, } +}; + +static int selftests_get(const char *selftest_name, bool *selftests_opt) +{ + if (strcmp(selftest_name, "flash") == 0) { + selftests_opt[0] = true; + } else { + pr_err("Unknown selftest \"%s\"\n", selftest_name); + return -EINVAL; + } + return 0; +} + +static int port_flavour_parse(const char *flavour, uint16_t *value) +{ + int num; + + num = str_map_lookup_str(port_flavour_map, flavour); + if (num < 0) { + invarg("unknown flavour", flavour); + return num; + } + *value = num; + return 0; +} + +static int port_fn_state_parse(const char *statestr, uint8_t *state) +{ + int num; + + num = str_map_lookup_str(port_fn_state_map, statestr); + if (num < 0) { + invarg("unknown state", statestr); + return num; + } + *state = num; + return 0; +} + +static int port_fn_rate_type_get(const char *typestr, uint16_t *type) +{ + if (!strcmp(typestr, "leaf")) + *type = DEVLINK_RATE_TYPE_LEAF; + else if (!strcmp(typestr, "node")) + *type = DEVLINK_RATE_TYPE_NODE; + else + return -EINVAL; + return 0; +} + +static int port_fn_rate_value_get(struct dl *dl, uint64_t *rate) +{ + const char *ratestr; + __u64 rate64; + int err; + + err = dl_argv_str(dl, &ratestr); + if (err) + return err; + err = get_rate64(&rate64, ratestr); + if (err) { + pr_err("Invalid rate value: \"%s\"\n", ratestr); + return -EINVAL; + } + + *rate = rate64; + 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."}, + {DL_OPT_PORT_FUNCTION_HW_ADDR, "Port function's hardware address is expected."}, + {DL_OPT_PORT_FLAVOUR, "Port flavour is expected."}, + {DL_OPT_PORT_PFNUMBER, "Port PCI PF number is expected."}, + {DL_OPT_LINECARD, "Linecard index expected."}, + {DL_OPT_LINECARD_TYPE, "Linecard type expected."}, + {DL_OPT_SELFTESTS, "Test 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 -ENOENT; + } + } + if (o_required & ~o_found) { + pr_err("BUG: unknown argument required but not found\n"); + 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; + char *str = dl_argv_next(dl); + uint64_t o_found = 0; + int err; + + if (str) { + str = strdup(str); + if (!str) + return -ENOMEM; + free(dl->handle_argv); + dl->handle_argv = str; + } + + if (o_required & DL_OPT_HANDLE && o_required & DL_OPT_HANDLEP) { + uint64_t handle_bit; + + err = dl_argv_handle_both(dl, str, &opts->bus_name, + &opts->dev_name, &opts->port_index, + &handle_bit); + if (err) + return err; + o_required &= ~(DL_OPT_HANDLE | DL_OPT_HANDLEP) | handle_bit; + o_found |= handle_bit; + } else if (o_required & DL_OPT_HANDLEP && + o_required & DL_OPT_PORT_FN_RATE_NODE_NAME) { + uint64_t handle_bit; + + err = dl_argv_handle_rate(str, &opts->bus_name, &opts->dev_name, + &opts->port_index, + &opts->rate_node_name, + &handle_bit); + if (err) + return err; + o_required &= ~(DL_OPT_HANDLEP | DL_OPT_PORT_FN_RATE_NODE_NAME) | + handle_bit; + o_found |= handle_bit; + } else if (o_required & DL_OPT_HANDLE) { + err = dl_argv_handle(str, &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, str, &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(str, &opts->bus_name, + &opts->dev_name, + &opts->region_name); + if (err) + return err; + o_found |= DL_OPT_HANDLE_REGION; + } else if (o_required & DL_OPT_PORT_FN_RATE_NODE_NAME) { + err = dl_argv_handle_rate_node(str, &opts->bus_name, + &opts->dev_name, + &opts->rate_node_name); + if (err) + return err; + o_found |= DL_OPT_PORT_FN_RATE_NODE_NAME; + } + + 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") || + dl_argv_match(dl, "counters_enabled")) && + (o_all & DL_OPT_DPIPE_TABLE_COUNTERS)) { + dl_arg_inc(dl); + err = dl_argv_bool(dl, &opts->dpipe_counters_enabled); + if (err) + return err; + o_found |= DL_OPT_DPIPE_TABLE_COUNTERS; + } else if ((dl_argv_match(dl, "encap") || /* Original incorrect implementation */ + dl_argv_match(dl, "encap-mode")) && + (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_uint64_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, "overwrite") && + (o_all & DL_OPT_FLASH_OVERWRITE)) { + const char *sectionstr; + + dl_arg_inc(dl); + err = dl_argv_str(dl, §ionstr); + if (err) + return err; + err = flash_overwrite_section_get(sectionstr, + &opts->overwrite_mask); + if (err) + return err; + o_found |= DL_OPT_FLASH_OVERWRITE; + + } else if (dl_argv_match(dl, "id") && + (o_all & DL_OPT_SELFTESTS)) { + dl_arg_inc(dl); + while (dl_argc(dl)) { + const char *selftest_name; + err = dl_argv_str(dl, &selftest_name); + if (err) + return err; + err = selftests_get(selftest_name, + opts->selftests_opt); + if (err) + return err; + } + o_found |= DL_OPT_SELFTESTS; + + } 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, "auto_dump") && + (o_all & DL_OPT_HEALTH_REPORTER_AUTO_DUMP)) { + dl_arg_inc(dl); + err = dl_argv_bool(dl, &opts->reporter_auto_dump); + if (err) + return err; + o_found |= DL_OPT_HEALTH_REPORTER_AUTO_DUMP; + } else if ((dl_argv_match(dl, "trap") || + dl_argv_match(dl, "name")) && + (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") || + dl_argv_match(dl, "name")) && + (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 if (dl_argv_match(dl, "netns") && + (o_all & DL_OPT_NETNS)) { + const char *netns_str; + + dl_arg_inc(dl); + err = dl_argv_str(dl, &netns_str); + if (err) + return err; + opts->netns = netns_get_fd(netns_str); + if ((int)opts->netns < 0) { + dl_arg_dec(dl); + err = dl_argv_uint32_t(dl, &opts->netns); + if (err) + return err; + opts->netns_is_pid = true; + } + o_found |= DL_OPT_NETNS; + } else if (dl_argv_match(dl, "action") && + (o_all & DL_OPT_RELOAD_ACTION)) { + const char *actionstr; + + dl_arg_inc(dl); + err = dl_argv_str(dl, &actionstr); + if (err) + return err; + err = reload_action_get(dl, actionstr, &opts->reload_action); + if (err) + return err; + o_found |= DL_OPT_RELOAD_ACTION; + } else if (dl_argv_match(dl, "limit") && + (o_all & DL_OPT_RELOAD_LIMIT)) { + const char *limitstr; + + dl_arg_inc(dl); + err = dl_argv_str(dl, &limitstr); + if (err) + return err; + err = reload_limit_get(dl, limitstr, &opts->reload_limit); + if (err) + return err; + o_found |= DL_OPT_RELOAD_LIMIT; + } else if (dl_argv_match(dl, "policer") && + (o_all & DL_OPT_TRAP_POLICER_ID)) { + dl_arg_inc(dl); + err = dl_argv_uint32_t(dl, &opts->trap_policer_id); + if (err) + return err; + o_found |= DL_OPT_TRAP_POLICER_ID; + } else if (dl_argv_match(dl, "nopolicer") && + (o_all & DL_OPT_TRAP_POLICER_ID)) { + dl_arg_inc(dl); + opts->trap_policer_id = 0; + o_found |= DL_OPT_TRAP_POLICER_ID; + } else if (dl_argv_match(dl, "rate") && + (o_all & DL_OPT_TRAP_POLICER_RATE)) { + dl_arg_inc(dl); + err = dl_argv_uint64_t(dl, &opts->trap_policer_rate); + if (err) + return err; + o_found |= DL_OPT_TRAP_POLICER_RATE; + } else if (dl_argv_match(dl, "burst") && + (o_all & DL_OPT_TRAP_POLICER_BURST)) { + dl_arg_inc(dl); + err = dl_argv_uint64_t(dl, &opts->trap_policer_burst); + if (err) + return err; + o_found |= DL_OPT_TRAP_POLICER_BURST; + } else if (dl_argv_match(dl, "hw_addr") && + (o_all & DL_OPT_PORT_FUNCTION_HW_ADDR)) { + const char *addrstr; + + dl_arg_inc(dl); + err = dl_argv_str(dl, &addrstr); + if (err) + return err; + err = hw_addr_parse(addrstr, opts->port_function_hw_addr, + &opts->port_function_hw_addr_len); + if (err) + return err; + o_found |= DL_OPT_PORT_FUNCTION_HW_ADDR; + } else if (dl_argv_match(dl, "state") && + (o_all & DL_OPT_PORT_FUNCTION_STATE)) { + const char *statestr; + + dl_arg_inc(dl); + err = dl_argv_str(dl, &statestr); + if (err) + return err; + err = port_fn_state_parse(statestr, &opts->port_fn_state); + if (err) + return err; + + o_found |= DL_OPT_PORT_FUNCTION_STATE; + } else if (dl_argv_match(dl, "flavour") && (o_all & DL_OPT_PORT_FLAVOUR)) { + const char *flavourstr; + + dl_arg_inc(dl); + err = dl_argv_str(dl, &flavourstr); + if (err) + return err; + err = port_flavour_parse(flavourstr, &opts->port_flavour); + if (err) + return err; + o_found |= DL_OPT_PORT_FLAVOUR; + } else if (dl_argv_match(dl, "pfnum") && (o_all & DL_OPT_PORT_PFNUMBER)) { + dl_arg_inc(dl); + err = dl_argv_uint16_t(dl, &opts->port_pfnumber); + if (err) + return err; + o_found |= DL_OPT_PORT_PFNUMBER; + } else if (dl_argv_match(dl, "sfnum") && (o_all & DL_OPT_PORT_SFNUMBER)) { + dl_arg_inc(dl); + err = dl_argv_uint32_t(dl, &opts->port_sfnumber); + if (err) + return err; + o_found |= DL_OPT_PORT_SFNUMBER; + } else if (dl_argv_match(dl, "controller") && (o_all & DL_OPT_PORT_CONTROLLER)) { + dl_arg_inc(dl); + err = dl_argv_uint32_t(dl, &opts->port_controller); + if (err) + return err; + o_found |= DL_OPT_PORT_CONTROLLER; + } else if (dl_argv_match(dl, "type") && + (o_all & DL_OPT_PORT_FN_RATE_TYPE)) { + const char *typestr; + + dl_arg_inc(dl); + err = dl_argv_str(dl, &typestr); + if (err) + return err; + err = port_fn_rate_type_get(typestr, &opts->rate_type); + if (err) + return err; + o_found |= DL_OPT_PORT_FN_RATE_TYPE; + } else if (dl_argv_match(dl, "tx_share") && + (o_all & DL_OPT_PORT_FN_RATE_TX_SHARE)) { + dl_arg_inc(dl); + err = port_fn_rate_value_get(dl, &opts->rate_tx_share); + if (err) + return err; + o_found |= DL_OPT_PORT_FN_RATE_TX_SHARE; + } else if (dl_argv_match(dl, "tx_max") && + (o_all & DL_OPT_PORT_FN_RATE_TX_MAX)) { + dl_arg_inc(dl); + err = port_fn_rate_value_get(dl, &opts->rate_tx_max); + if (err) + return err; + o_found |= DL_OPT_PORT_FN_RATE_TX_MAX; + } else if (dl_argv_match(dl, "tx_priority") && + (o_all & DL_OPT_PORT_FN_RATE_TX_PRIORITY)) { + dl_arg_inc(dl); + err = dl_argv_uint32_t(dl, &opts->rate_tx_priority); + if (err) + return err; + o_found |= DL_OPT_PORT_FN_RATE_TX_PRIORITY; + } else if (dl_argv_match(dl, "tx_weight") && + (o_all & DL_OPT_PORT_FN_RATE_TX_WEIGHT)) { + dl_arg_inc(dl); + err = dl_argv_uint32_t(dl, &opts->rate_tx_weight); + if (err) + return err; + o_found |= DL_OPT_PORT_FN_RATE_TX_WEIGHT; + } else if (dl_argv_match(dl, "parent") && + (o_all & DL_OPT_PORT_FN_RATE_PARENT)) { + dl_arg_inc(dl); + err = dl_argv_str(dl, &opts->rate_parent_node); + if (err) + return err; + o_found |= DL_OPT_PORT_FN_RATE_PARENT; + } else if (dl_argv_match(dl, "noparent") && + (o_all & DL_OPT_PORT_FN_RATE_PARENT)) { + dl_arg_inc(dl); + opts->rate_parent_node = ""; + o_found |= DL_OPT_PORT_FN_RATE_PARENT; + } else if (dl_argv_match(dl, "lc") && + (o_all & DL_OPT_LINECARD)) { + dl_arg_inc(dl); + err = dl_argv_uint32_t(dl, &opts->linecard_index); + if (err) + return err; + o_found |= DL_OPT_LINECARD; + } else if (dl_argv_match(dl, "type") && + (o_all & DL_OPT_LINECARD_TYPE)) { + dl_arg_inc(dl); + err = dl_argv_str(dl, &opts->linecard_type); + if (err) + return err; + o_found |= DL_OPT_LINECARD_TYPE; + } else if (dl_argv_match(dl, "notype") && + (o_all & DL_OPT_LINECARD_TYPE)) { + dl_arg_inc(dl); + opts->linecard_type = ""; + o_found |= DL_OPT_LINECARD_TYPE; + } else if (dl_argv_match(dl, "roce") && + (o_all & DL_OPT_PORT_FN_CAPS)) { + bool roce; + + dl_arg_inc(dl); + err = dl_argv_bool(dl, &roce); + if (err) + return err; + opts->port_fn_caps.selector |= DEVLINK_PORT_FN_CAP_ROCE; + if (roce) + opts->port_fn_caps.value |= DEVLINK_PORT_FN_CAP_ROCE; + o_found |= DL_OPT_PORT_FN_CAPS; + } else if (dl_argv_match(dl, "migratable") && + (o_all & DL_OPT_PORT_FN_CAPS)) { + bool mig; + + dl_arg_inc(dl); + err = dl_argv_bool(dl, &mig); + if (err) + return err; + opts->port_fn_caps.selector |= DEVLINK_PORT_FN_CAP_MIGRATABLE; + if (mig) + opts->port_fn_caps.value |= DEVLINK_PORT_FN_CAP_MIGRATABLE; + o_found |= DL_OPT_PORT_FN_CAPS; + } else if (dl_argv_match(dl, "ipsec_crypto") && + (o_all & DL_OPT_PORT_FN_CAPS)) { + bool ipsec_crypto; + + dl_arg_inc(dl); + err = dl_argv_bool(dl, &ipsec_crypto); + if (err) + return err; + opts->port_fn_caps.selector |= DEVLINK_PORT_FN_CAP_IPSEC_CRYPTO; + if (ipsec_crypto) + opts->port_fn_caps.value |= DEVLINK_PORT_FN_CAP_IPSEC_CRYPTO; + o_found |= DL_OPT_PORT_FN_CAPS; + } else if (dl_argv_match(dl, "ipsec_packet") && + (o_all & DL_OPT_PORT_FN_CAPS)) { + bool ipsec_packet; + + dl_arg_inc(dl); + err = dl_argv_bool(dl, &ipsec_packet); + if (err) + return err; + opts->port_fn_caps.selector |= DEVLINK_PORT_FN_CAP_IPSEC_PACKET; + if (ipsec_packet) + opts->port_fn_caps.value |= DEVLINK_PORT_FN_CAP_IPSEC_PACKET; + o_found |= DL_OPT_PORT_FN_CAPS; + } else { + pr_err("Unknown option \"%s\"\n", dl_argv(dl)); + return -EINVAL; + } + } + + if ((o_required & DL_OPT_SB) && !(o_found & DL_OPT_SB)) { + opts->sb_index = 0; + o_found |= DL_OPT_SB; + } + + opts->present = o_found; + + return dl_args_finding_required_validate(o_required, o_found); +} + +static int dl_argv_dry_parse(struct dl *dl, uint64_t o_required, + uint64_t o_optional) +{ + char **argv = dl->argv; + int argc = dl->argc; + int err; + + g_err_suspended = true; + err = dl_argv_parse(dl, o_required, o_optional); + g_err_suspended = false; + dl->argv = argv; + dl->argc = argc; + return err; +} + +/* List of extended handles with two slashes. */ +static const uint64_t dl_opt_extended_handle[] = { + DL_OPT_HANDLEP, + DL_OPT_HANDLE_REGION, + DL_OPT_PORT_FN_RATE_NODE_NAME, +}; + +static int dl_argv_parse_with_selector(struct dl *dl, uint16_t *flags, + uint8_t cmd, + uint64_t o_required, + uint64_t o_optional, + uint64_t o_dump_required, + uint64_t o_dump_optional) +{ + int err; + int i; + + if (dl_no_arg(dl)) + goto flag_set; + + /* In case the handle suggests it, do dry parsing first + * to see if all required options are there. Proceed with + * dump selector in case there are missing options on the + * command line. That means user provided partial + * object identification. + */ + + if ((o_required & (DL_OPT_HANDLE | DL_OPT_HANDLEP)) == + (DL_OPT_HANDLE | DL_OPT_HANDLEP)) { + /* Handle case when both devlink handle and port handle + * are allowed. Try both alone, if parsing of either + * is successful, we have a do parse case. + */ + err = dl_argv_dry_parse(dl, o_required & ~DL_OPT_HANDLEP, + o_optional); + if (err == -ENOENT) + goto dump_parse; + else if (!err) + goto do_parse; + err = dl_argv_dry_parse(dl, o_required & ~DL_OPT_HANDLE, + o_optional); + if (err == -ENOENT) + goto dump_parse; + goto do_parse; + } + + for (i = 0; i < ARRAY_SIZE(dl_opt_extended_handle); i++) { + uint64_t handle = dl_opt_extended_handle[i]; + + if ((o_required & handle) == handle) { + err = dl_argv_dry_parse(dl, (o_required & ~handle) | + DL_OPT_HANDLE, + o_optional); + if (err == -ENOENT || !err) + goto dump_parse; + goto do_parse; + } + } + + err = dl_argv_dry_parse(dl, o_required, o_optional); + if (err == -ENOENT) + goto dump_parse; + +do_parse: + return dl_argv_parse(dl, o_required, o_optional); + +dump_parse: + err = mnlu_gen_cmd_dump_policy(&dl->nlg, cmd); + if (err) { + pr_err("Dump selectors are not supported by kernel for this command\n"); + return -ENOTSUP; + } + + err = dl_argv_parse(dl, o_dump_required, o_dump_optional); + if (err) + return err; + +flag_set: + *flags |= NLM_F_DUMP; + return 0; +} + +static void +dl_function_attr_put(struct nlmsghdr *nlh, const struct dl_opts *opts) +{ + struct nlattr *nest; + + nest = mnl_attr_nest_start(nlh, DEVLINK_ATTR_PORT_FUNCTION); + + if (opts->present & DL_OPT_PORT_FUNCTION_HW_ADDR) + mnl_attr_put(nlh, DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR, + opts->port_function_hw_addr_len, + opts->port_function_hw_addr); + if (opts->present & DL_OPT_PORT_FUNCTION_STATE) + mnl_attr_put_u8(nlh, DEVLINK_PORT_FN_ATTR_STATE, + opts->port_fn_state); + if (opts->present & DL_OPT_PORT_FN_CAPS) + mnl_attr_put(nlh, DEVLINK_PORT_FN_ATTR_CAPS, + sizeof(opts->port_fn_caps), &opts->port_fn_caps); + + mnl_attr_nest_end(nlh, nest); +} + +static void +dl_flash_update_overwrite_put(struct nlmsghdr *nlh, const struct dl_opts *opts) +{ + struct nla_bitfield32 overwrite_mask; + + overwrite_mask.selector = DEVLINK_SUPPORTED_FLASH_OVERWRITE_SECTIONS; + overwrite_mask.value = opts->overwrite_mask; + + mnl_attr_put(nlh, DEVLINK_ATTR_FLASH_UPDATE_OVERWRITE_MASK, + sizeof(overwrite_mask), &overwrite_mask); +} + +static void +dl_reload_limits_put(struct nlmsghdr *nlh, const struct dl_opts *opts) +{ + struct nla_bitfield32 limits; + + limits.selector = DEVLINK_RELOAD_LIMITS_VALID_MASK; + limits.value = BIT(opts->reload_limit); + mnl_attr_put(nlh, DEVLINK_ATTR_RELOAD_LIMITS, sizeof(limits), &limits); +} + +static void +dl_selftests_put(struct nlmsghdr *nlh, const struct dl_opts *opts) +{ + bool test_sel = false; + struct nlattr *nest; + int id; + + nest = mnl_attr_nest_start(nlh, DEVLINK_ATTR_SELFTESTS); + + for (id = DEVLINK_ATTR_SELFTEST_ID_UNSPEC + 1; + id <= DEVLINK_ATTR_SELFTEST_ID_MAX && + opts->selftests_opt[id]; id++) { + if (opts->selftests_opt[id]) { + test_sel = true; + mnl_attr_put(nlh, id, 0, NULL); + } + } + + /* No test selected from user, select all */ + if (!test_sel) { + for (id = DEVLINK_ATTR_SELFTEST_ID_UNSPEC + 1; + id <= DEVLINK_ATTR_SELFTEST_ID_MAX; id++) + mnl_attr_put(nlh, id, 0, NULL); + } + + mnl_attr_nest_end(nlh, nest); +} + +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); + } else if (opts->present & DL_OPT_PORT_FN_RATE_NODE_NAME) { + 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_RATE_NODE_NAME, + opts->rate_node_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_enabled); + 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_FLASH_OVERWRITE) + dl_flash_update_overwrite_put(nlh, opts); + if (opts->present & DL_OPT_SELFTESTS) + dl_selftests_put(nlh, opts); + 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) |